[Freeipa-devel] [PATCH] 0102..0105 Better handling for cert-request to disabled CA

Fraser Tweedale ftweedal at redhat.com
Fri Aug 26 02:19:07 UTC 2016


The attached patches add better handling of cert-request failure due
to target CA being disabled (#6260).  To do this, rather than go and
do extra work in Dogtag that we would depend on, instead I bite the
bullet and refactor ra.request_certificate to use the Dogtag REST
API, which correctly responds with status 409 in this case.

Switching RA to Dogtag REST API is an old ticket (#3437) so these
patches address it in part, and show the way forward for the rest of
it.

These patches don't technically depend on patch 0101 which adds the
ca-enable and ca-disable commands, but 0101 may help for testing :)

Thanks,
Fraser
-------------- next part --------------
From 97501fad9bfe64af076a8c1a65bd732ac265b940 Mon Sep 17 00:00:00 2001
From: Fraser Tweedale <ftweedal at redhat.com>
Date: Fri, 26 Aug 2016 08:59:10 +1000
Subject: [PATCH 102/105] Allow Dogtag RestClient to perform requests without
 logging in

Currently the Dogtag RestClient '_ssldo' method requires a session
cookie unconditionally, however, not all REST methods require a
session: some do not require authentication at all, and some will
authenticate the agent on the fly.

To avoid unnecessary login/logout requests via the context manager,
add the 'use_session' keyword argument to '_ssldo'.  It defaults to
'True' to preserve existing behaviour (session required) but a
caller can set to 'False' to avoid the requirement.

Part of: https://fedorahosted.org/freeipa/ticket/6260
Part of: https://fedorahosted.org/freeipa/ticket/3473
---
 ipaserver/plugins/dogtag.py | 38 ++++++++++++++++++++++++++------------
 1 file changed, 26 insertions(+), 12 deletions(-)

diff --git a/ipaserver/plugins/dogtag.py b/ipaserver/plugins/dogtag.py
index 01e5f1383ee135696a8e968793863ce964025094..ac3fa40f80f7c23ab5dceda5e20a33812ff82a21 100644
--- a/ipaserver/plugins/dogtag.py
+++ b/ipaserver/plugins/dogtag.py
@@ -2071,26 +2071,40 @@ class RestClient(Backend):
         )
         self.cookie = None
 
-    def _ssldo(self, method, path, headers=None, body=None):
+    def _ssldo(self, method, path, headers=None, body=None, use_session=True):
         """
-        :param url: The URL to post to.
-        :param kw:  Keyword arguments to encode into POST body.
+        Perform an HTTPS request.
+
+        ``method``
+            HTTP method to use
+        ``path``
+            Path component.  This will *extend* the path defined for
+            the class (if any).
+        ``headers``
+            Additional headers to include in the request.
+        ``body``
+            Request body.
+        ``use_session``
+            If ``True``, session cookie is added to request (client
+            must be logged in).
+
         :return:   (http_status, http_headers, http_body)
                    as (integer, dict, str)
 
-        Perform an HTTPS request
         """
-        if self.cookie is None:
-            raise errors.RemoteRetrieveError(
-                reason=_("REST API is not logged in."))
-
         headers = headers or {}
-        headers['Cookie'] = self.cookie
 
+        if use_session:
+            if self.cookie is None:
+                raise errors.RemoteRetrieveError(
+                    reason=_("REST API is not logged in."))
+            headers['Cookie'] = self.cookie
+
+        resource = '/ca/rest'
+        if self.path is not None:
+            resource = os.path.join(resource, self.path)
         if path is not None:
-            resource = os.path.join('/ca/rest', self.path, path)
-        else:
-            resource = os.path.join('/ca/rest', self.path)
+            resource = os.path.join(resource, path)
 
         # perform main request
         status, resp_headers, resp_body = dogtag.https_request(
-- 
2.5.5

-------------- next part --------------
From 5b8c67c2f293cea32f0a492069bab4ce055ed8fa Mon Sep 17 00:00:00 2001
From: Fraser Tweedale <ftweedal at redhat.com>
Date: Fri, 26 Aug 2016 09:04:04 +1000
Subject: [PATCH 103/105] Add HTTPRequestError class

Currently, HTTP requests that respond with status not in the 2xx
range raise RemoteRetrieveError.  The exception includes no
information about the response status.

Add the 'HTTPRequestError' class which extends 'RemoteRequestError'
with an attribute for the response status, and update the Dogtag
RestClient to raise the new error.

Part of: https://fedorahosted.org/freeipa/ticket/6260
Part of: https://fedorahosted.org/freeipa/ticket/3473
---
 ipalib/errors.py            | 13 +++++++++++++
 ipaserver/plugins/dogtag.py |  3 ++-
 2 files changed, 15 insertions(+), 1 deletion(-)

diff --git a/ipalib/errors.py b/ipalib/errors.py
index 4cc4455b0abf7d2b1366e1ce6dbb3762bc551cc6..268376f513e7ba9dbfbffaa103002ae4859ecc47 100644
--- a/ipalib/errors.py
+++ b/ipalib/errors.py
@@ -1406,6 +1406,19 @@ class OperationNotSupportedForPrincipalType(ExecutionError):
         '%(operation)s is not supported for %(principal_type)s principals')
 
 
+class HTTPRequestError(RemoteRetrieveError):
+    """
+    **4035** Raised when an HTTP request fails.  Includes the response
+    status in the ``status`` attribute.
+    """
+
+    errno = 4035
+
+    def __init__(self, status=None, **kw):
+        assert status is not None
+        super(HTTPRequestError, self).__init__(status=status, **kw)
+
+
 class BuiltinError(ExecutionError):
     """
     **4100** Base class for builtin execution errors (*4100 - 4199*).
diff --git a/ipaserver/plugins/dogtag.py b/ipaserver/plugins/dogtag.py
index ac3fa40f80f7c23ab5dceda5e20a33812ff82a21..4f85c921040fa5065662d6e4963c4cddafd4f33d 100644
--- a/ipaserver/plugins/dogtag.py
+++ b/ipaserver/plugins/dogtag.py
@@ -2115,7 +2115,8 @@ class RestClient(Backend):
         )
         if status < 200 or status >= 300:
             explanation = self._parse_dogtag_error(resp_body) or ''
-            raise errors.RemoteRetrieveError(
+            raise errors.HTTPRequestError(
+                status=status,
                 reason=_('Non-2xx response from CA REST API: %(status)d. %(explanation)s')
                 % {'status': status, 'explanation': explanation}
             )
-- 
2.5.5

-------------- next part --------------
From 70850a04ba41aba9065fb9b66a45476d35e4054d Mon Sep 17 00:00:00 2001
From: Fraser Tweedale <ftweedal at redhat.com>
Date: Fri, 26 Aug 2016 10:02:21 +1000
Subject: [PATCH 104/105] Use Dogtag REST API for certificate requests

The Dogtag REST API gives better responses statuses than the RPC API
and properly reports failure due to disabled CA (status 409).  Make
'ra' extend 'RestClient' and refactor the 'request_certificate'
method to use Dogtag's REST API.

Part of: https://fedorahosted.org/freeipa/ticket/6260
Part of: https://fedorahosted.org/freeipa/ticket/3473
---
 install/conf/ipa-pki-proxy.conf |   4 +-
 ipaserver/plugins/dogtag.py     | 502 ++++++++++++++++------------------------
 2 files changed, 206 insertions(+), 300 deletions(-)

diff --git a/install/conf/ipa-pki-proxy.conf b/install/conf/ipa-pki-proxy.conf
index 545f21253ec8895397e43a3c9637956e94f40293..b48a3020d22df623fab471b2367adfd5d521544c 100644
--- a/install/conf/ipa-pki-proxy.conf
+++ b/install/conf/ipa-pki-proxy.conf
@@ -1,4 +1,4 @@
-# VERSION 9 - DO NOT REMOVE THIS LINE
+# VERSION 10 - DO NOT REMOVE THIS LINE
 
 ProxyRequests Off
 
@@ -27,7 +27,7 @@ ProxyRequests Off
 </LocationMatch>
 
 # matches for CA REST API
-<LocationMatch "^/ca/rest/account/login|^/ca/rest/account/logout|^/ca/rest/installer/installToken|^/ca/rest/securityDomain/domainInfo|^/ca/rest/securityDomain/installToken|^/ca/rest/profiles|^/ca/rest/authorities|^/ca/rest/admin/kraconnector/remove">
+<LocationMatch "^/ca/rest/account/login|^/ca/rest/account/logout|^/ca/rest/installer/installToken|^/ca/rest/securityDomain/domainInfo|^/ca/rest/securityDomain/installToken|^/ca/rest/profiles|^/ca/rest/authorities|^/ca/rest/certrequests|^/ca/rest/admin/kraconnector/remove">
     NSSOptions +StdEnvVars +ExportCertData +StrictRequire +OptRenegotiate
     NSSVerifyClient optional
     ProxyPassMatch ajp://localhost:$DOGTAG_PORT
diff --git a/ipaserver/plugins/dogtag.py b/ipaserver/plugins/dogtag.py
index 4f85c921040fa5065662d6e4963c4cddafd4f33d..db2bbc20e12be108cf02387e1eecfff60d34cd67 100644
--- a/ipaserver/plugins/dogtag.py
+++ b/ipaserver/plugins/dogtag.py
@@ -566,83 +566,6 @@ def parse_error_response_xml(doc):
 
     return response
 
-def parse_profile_submit_result_xml(doc):
-    '''
-    :param doc: The root node of the xml document to parse
-    :returns:   result dict
-    :except ValueError:
-
-    CMS returns an error code and an array of request records.
-
-    This function returns a response dict with the following format:
-    {'error_code' : int, 'requests' : [{}]}
-
-    The mapping of fields and data types is illustrated in the following table.
-
-    If the error_code is not SUCCESS then the response dict will have the
-    contents described in `parse_error_response_xml`.
-
-    +--------------------+----------------+------------------------+---------------+
-    |cms name            |cms type        |result name             |result type    |
-    +====================+================+========================+===============+
-    |Status              |int             |error_code              |int            |
-    +--------------------+----------------+------------------------+---------------+
-    |Requests[].Id       |string          |requests[].request_id   |unicode        |
-    +--------------------+----------------+------------------------+---------------+
-    |Requests[].SubjectDN|string          |requests[].subject      |unicode        |
-    +--------------------+----------------+------------------------+---------------+
-    |Requests[].serialno |BigInteger      |requests[].serial_number|int|long       |
-    +--------------------+----------------+------------------------+---------------+
-    |Requests[].b64      |string          |requests[].certificate  |unicode [1]_   |
-    +--------------------+----------------+------------------------+---------------+
-    |Requests[].pkcs7    |string          |                        |               |
-    +--------------------+----------------+------------------------+---------------+
-
-    .. [1] Base64 encoded
-
-    '''
-
-    error_code = get_error_code_xml(doc)
-    if error_code != CMS_SUCCESS:
-        response = parse_error_response_xml(doc)
-        return response
-
-    response = {}
-    response['error_code'] = error_code
-
-    requests = []
-    response['requests'] = requests
-
-    for request in doc.xpath('//XMLResponse/Requests[*]/Request'):
-        response_request = {}
-        requests.append(response_request)
-
-        request_id = request.xpath('Id[1]')
-        if len(request_id) == 1:
-            request_id = etree.tostring(request_id[0], method='text',
-                                        encoding=unicode).strip()
-            response_request['request_id'] = request_id
-
-        subject_dn = request.xpath('SubjectDN[1]')
-        if len(subject_dn) == 1:
-            subject_dn = etree.tostring(subject_dn[0], method='text',
-                                        encoding=unicode).strip()
-            response_request['subject'] = subject_dn
-
-        serial_number = request.xpath('serialno[1]')
-        if len(serial_number) == 1:
-            serial_number = int(serial_number[0].text, 16) # parse as hex
-            response_request['serial_number'] = serial_number
-            response['serial_number_hex'] = u'0x%X' % serial_number
-
-        certificate = request.xpath('b64[1]')
-        if len(certificate) == 1:
-            certificate = etree.tostring(certificate[0], method='text',
-                                         encoding=unicode).strip()
-            response_request['certificate'] = certificate
-
-    return response
-
 
 def parse_check_request_result_xml(doc):
     '''
@@ -1286,32 +1209,161 @@ from ipaplatform.paths import paths
 register = Registry()
 
 
+class RestClient(Backend):
+    """Simple Dogtag REST client to be subclassed by other backends.
+
+    This class is a context manager.  Authenticated calls must be
+    executed in a ``with`` suite::
+
+        @register()
+        class ra_certprofile(RestClient):
+            path = 'profile'
+            ...
+
+        with api.Backend.ra_certprofile as profile_api:
+            # REST client is now logged in
+            profile_api.create_profile(...)
+
+    """
+    path = None
+
+    @staticmethod
+    def _parse_dogtag_error(body):
+        try:
+            return pki.PKIException.from_json(json.loads(body))
+        except Exception:
+            return None
+
+    def __init__(self, api):
+        if api.env.in_tree:
+            self.sec_dir = api.env.dot_ipa + os.sep + 'alias'
+            self.pwd_file = self.sec_dir + os.sep + '.pwd'
+        else:
+            self.sec_dir = paths.HTTPD_ALIAS_DIR
+            self.pwd_file = paths.ALIAS_PWDFILE_TXT
+        self.noise_file = self.sec_dir + os.sep + '.noise'
+        self.ipa_key_size = "2048"
+        self.ipa_certificate_nickname = "ipaCert"
+        self.ca_certificate_nickname = "caCert"
+        self._read_password()
+        super(RestClient, self).__init__(api)
+
+        # session cookie
+        self.override_port = None
+        self.cookie = None
+
+    def _read_password(self):
+        try:
+            with open(self.pwd_file) as f:
+                self.password = f.readline().strip()
+        except IOError:
+            self.password = ''
+
+    @cachedproperty
+    def ca_host(self):
+        """
+        :return:   host
+                   as str
+
+        Select our CA host.
+        """
+        ldap2 = self.api.Backend.ldap2
+        if host_has_service(api.env.ca_host, ldap2, "CA"):
+            return api.env.ca_host
+        if api.env.host != api.env.ca_host:
+            if host_has_service(api.env.host, ldap2, "CA"):
+                return api.env.host
+        host = select_any_master(ldap2)
+        if host:
+            return host
+        else:
+            return api.env.ca_host
+
+    def __enter__(self):
+        """Log into the REST API"""
+        if self.cookie is not None:
+            return
+        status, resp_headers, resp_body = dogtag.https_request(
+            self.ca_host, self.override_port or self.env.ca_agent_port,
+            '/ca/rest/account/login',
+            self.sec_dir, self.password, self.ipa_certificate_nickname,
+            method='GET'
+        )
+        cookies = ipapython.cookie.Cookie.parse(resp_headers.get('set-cookie', ''))
+        if status != 200 or len(cookies) == 0:
+            raise errors.RemoteRetrieveError(reason=_('Failed to authenticate to CA REST API'))
+        self.cookie = str(cookies[0])
+        return self
+
+    def __exit__(self, exc_type, exc_value, traceback):
+        """Log out of the REST API"""
+        dogtag.https_request(
+            self.ca_host, self.override_port or self.env.ca_agent_port,
+            '/ca/rest/account/logout',
+            self.sec_dir, self.password, self.ipa_certificate_nickname,
+            method='GET'
+        )
+        self.cookie = None
+
+    def _ssldo(self, method, path, headers=None, body=None, use_session=True):
+        """
+        Perform an HTTPS request.
+
+        ``method``
+            HTTP method to use
+        ``path``
+            Path component.  This will *extend* the path defined for
+            the class (if any).
+        ``headers``
+            Additional headers to include in the request.
+        ``body``
+            Request body.
+        ``use_session``
+            If ``True``, session cookie is added to request (client
+            must be logged in).
+
+        :return:   (http_status, http_headers, http_body)
+                   as (integer, dict, str)
+
+        """
+        headers = headers or {}
+
+        if use_session:
+            if self.cookie is None:
+                raise errors.RemoteRetrieveError(
+                    reason=_("REST API is not logged in."))
+            headers['Cookie'] = self.cookie
+
+        resource = '/ca/rest'
+        if self.path is not None:
+            resource = os.path.join(resource, self.path)
+        if path is not None:
+            resource = os.path.join(resource, path)
+
+        # perform main request
+        status, resp_headers, resp_body = dogtag.https_request(
+            self.ca_host, self.override_port or self.env.ca_agent_port,
+            resource,
+            self.sec_dir, self.password, self.ipa_certificate_nickname,
+            method=method, headers=headers, body=body
+        )
+        if status < 200 or status >= 300:
+            explanation = self._parse_dogtag_error(resp_body) or ''
+            raise errors.HTTPRequestError(
+                status=status,
+                reason=_('Non-2xx response from CA REST API: %(status)d. %(explanation)s')
+                % {'status': status, 'explanation': explanation}
+            )
+        return (status, resp_headers, resp_body)
+
+
 @register()
-class ra(rabase.rabase):
+class ra(rabase.rabase, RestClient):
     """
     Request Authority backend plugin.
     """
     DEFAULT_PROFILE = dogtag.DEFAULT_PROFILE
 
-    def __init__(self, api):
-        if api.env.in_tree:
-            self.sec_dir = api.env.dot_ipa + os.sep + 'alias'
-            self.pwd_file = self.sec_dir + os.sep + '.pwd'
-        else:
-            self.sec_dir = paths.HTTPD_ALIAS_DIR
-            self.pwd_file = paths.ALIAS_PWDFILE_TXT
-        self.noise_file = self.sec_dir + os.sep + '.noise'
-        self.ipa_key_size = "2048"
-        self.ipa_certificate_nickname = "ipaCert"
-        self.ca_certificate_nickname = "caCert"
-        try:
-            f = open(self.pwd_file, "r")
-            self.password = f.readline().strip()
-            f.close()
-        except IOError:
-            self.password = ''
-        super(ra, self).__init__(api)
-
     def raise_certificate_operation_error(self, func_name, err_msg=None, detail=None):
         """
         :param func_name: function name where error occurred
@@ -1564,75 +1616,77 @@ class ra(rabase.rabase):
 
         Submit certificate signing request.
 
-        The command returns a dict with these possible key/value pairs.
-        Some key/value pairs may be absent.
+        The command returns a dict with these key/value pairs:
 
-        +---------------+---------------+---------------+
-        |result name    |result type    |comments       |
-        +===============+===============+===============+
-        |serial_number  |unicode [1]_   |               |
-        +---------------+---------------+---------------+
-        |certificate    |unicode [2]_   |               |
-        +---------------+---------------+---------------+
-        |request_id     |unicode        |               |
-        +---------------+---------------+---------------+
-        |subject        |unicode        |               |
-        +---------------+---------------+---------------+
-
-        .. [1] Passed through XMLRPC as decimal string. Can convert to
-               optimal integer type (int or long) via int(serial_number)
-
-        .. [2] Base64 encoded
+        ``serial_number``
+            ``unicode``, decimal representation
+        ``serial_number_hex``
+            ``unicode``, hex representation with ``'0x'`` leader
+        ``certificate``
+            ``unicode``, base64-encoded DER
+        ``request_id``
+            ``unicode``, decimal representation
 
         """
         self.debug('%s.request_certificate()', type(self).__name__)
 
         # Call CMS
-        kw = dict(
-            profileId=profile_id,
-            cert_request_type=request_type,
-            cert_request=csr,
-            xml='true')
+        template = '''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+            <CertEnrollmentRequest>
+                <ProfileID>{profile}</ProfileID>
+                <Input id="i1">
+                    <ClassID>certReqInputImpl</ClassID>
+                    <Attribute name="cert_request_type">
+                        <Value>{req_type}</Value>
+                    </Attribute>
+                    <Attribute name="cert_request">
+                        <Value>{req}</Value>
+                    </Attribute>
+                </Input>
+            </CertEnrollmentRequest>'''
+        data = template.format(
+            profile=profile_id,
+            req_type=request_type,
+            req=csr,
+        )
+
+        path = 'certrequests'
         if ca_id:
-            kw['authorityId'] = ca_id
+            path += '?issuer-id={}'.format(ca_id)
 
-        http_status, http_headers, http_body = self._sslget(
-            '/ca/eeca/ca/profileSubmitSSLClient', self.env.ca_ee_port, **kw)
-        # Parse and handle errors
-        if http_status != 200:
-            self.raise_certificate_operation_error('request_certificate',
-                                                   detail=http_status)
-
-        parse_result = self.get_parse_result_xml(http_body, parse_profile_submit_result_xml)
-        # Note different status return, it's not request_status, it's error_code
-        error_code = parse_result['error_code']
-        if error_code != CMS_SUCCESS:
-            self.raise_certificate_operation_error('request_certificate',
-                                                   cms_error_code_to_string(error_code),
-                                                   parse_result.get('error_string'))
+        http_status, http_headers, http_body = self._ssldo(
+            'POST', path,
+            headers={
+                'Content-Type': 'application/xml',
+                'Accept': 'application/json',
+            },
+            body=data,
+            use_session=False,
+        )
 
+        try:
+            resp_obj = json.loads(http_body)
+        except:
+            raise errors.RemoteRetrieveError(reason=_("Response from CA was not valid JSON"))
 
         # Return command result
         cmd_result = {}
 
-        # FIXME: should we return all the requests instead of just the first one?
-        if len(parse_result['requests']) < 1:
+        entries = resp_obj.get('entries', [])
+
+        # ipa cert-request only handles a single PKCS #10 request so
+        # there's only one certinfo in the result.
+        if len(entries) < 1:
             return cmd_result
-        request = parse_result['requests'][0]
+        certinfo = entries[0]
 
-        if 'serial_number' in request:
-            # see module documentation concerning serial numbers and XMLRPC
-            cmd_result['serial_number'] = unicode(request['serial_number'])
-            cmd_result['serial_number_hex'] = u'0x%X' % request['serial_number']
+        if 'certId' in certinfo:
+            cmd_result = self.get_certificate(certinfo['certId'])
+            cert = cmd_result['certificate'].splitlines()
+            cmd_result['certificate'] = ''.join(cert.splitlines())
 
-        if 'certificate' in request:
-            cmd_result['certificate'] = request['certificate']
-
-        if 'request_id' in request:
-            cmd_result['request_id'] = request['request_id']
-
-        if 'subject' in request:
-            cmd_result['subject'] = request['subject']
+        if 'requestURL' in certinfo:
+            cmd_result['request_id'] = certinfo['requestURL'].split('/')[-1]
 
         return cmd_result
 
@@ -1975,154 +2029,6 @@ class kra(Backend):
         return KRAClient(connection, crypto)
 
 
-class RestClient(Backend):
-    """Simple Dogtag REST client to be subclassed by other backends.
-
-    This class is a context manager.  Authenticated calls must be
-    executed in a ``with`` suite::
-
-        @register()
-        class ra_certprofile(RestClient):
-            path = 'profile'
-            ...
-
-        with api.Backend.ra_certprofile as profile_api:
-            # REST client is now logged in
-            profile_api.create_profile(...)
-
-    """
-    path = None
-
-    @staticmethod
-    def _parse_dogtag_error(body):
-        try:
-            return pki.PKIException.from_json(json.loads(body))
-        except Exception:
-            return None
-
-    def __init__(self, api):
-        if api.env.in_tree:
-            self.sec_dir = api.env.dot_ipa + os.sep + 'alias'
-            self.pwd_file = self.sec_dir + os.sep + '.pwd'
-        else:
-            self.sec_dir = paths.HTTPD_ALIAS_DIR
-            self.pwd_file = paths.ALIAS_PWDFILE_TXT
-        self.noise_file = self.sec_dir + os.sep + '.noise'
-        self.ipa_key_size = "2048"
-        self.ipa_certificate_nickname = "ipaCert"
-        self.ca_certificate_nickname = "caCert"
-        self._read_password()
-        super(RestClient, self).__init__(api)
-
-        # session cookie
-        self.override_port = None
-        self.cookie = None
-
-    def _read_password(self):
-        try:
-            with open(self.pwd_file) as f:
-                self.password = f.readline().strip()
-        except IOError:
-            self.password = ''
-
-    @cachedproperty
-    def ca_host(self):
-        """
-        :return:   host
-                   as str
-
-        Select our CA host.
-        """
-        ldap2 = self.api.Backend.ldap2
-        if host_has_service(api.env.ca_host, ldap2, "CA"):
-            return api.env.ca_host
-        if api.env.host != api.env.ca_host:
-            if host_has_service(api.env.host, ldap2, "CA"):
-                return api.env.host
-        host = select_any_master(ldap2)
-        if host:
-            return host
-        else:
-            return api.env.ca_host
-
-    def __enter__(self):
-        """Log into the REST API"""
-        if self.cookie is not None:
-            return
-        status, resp_headers, resp_body = dogtag.https_request(
-            self.ca_host, self.override_port or self.env.ca_agent_port,
-            '/ca/rest/account/login',
-            self.sec_dir, self.password, self.ipa_certificate_nickname,
-            method='GET'
-        )
-        cookies = ipapython.cookie.Cookie.parse(resp_headers.get('set-cookie', ''))
-        if status != 200 or len(cookies) == 0:
-            raise errors.RemoteRetrieveError(reason=_('Failed to authenticate to CA REST API'))
-        self.cookie = str(cookies[0])
-        return self
-
-    def __exit__(self, exc_type, exc_value, traceback):
-        """Log out of the REST API"""
-        dogtag.https_request(
-            self.ca_host, self.override_port or self.env.ca_agent_port,
-            '/ca/rest/account/logout',
-            self.sec_dir, self.password, self.ipa_certificate_nickname,
-            method='GET'
-        )
-        self.cookie = None
-
-    def _ssldo(self, method, path, headers=None, body=None, use_session=True):
-        """
-        Perform an HTTPS request.
-
-        ``method``
-            HTTP method to use
-        ``path``
-            Path component.  This will *extend* the path defined for
-            the class (if any).
-        ``headers``
-            Additional headers to include in the request.
-        ``body``
-            Request body.
-        ``use_session``
-            If ``True``, session cookie is added to request (client
-            must be logged in).
-
-        :return:   (http_status, http_headers, http_body)
-                   as (integer, dict, str)
-
-        """
-        headers = headers or {}
-
-        if use_session:
-            if self.cookie is None:
-                raise errors.RemoteRetrieveError(
-                    reason=_("REST API is not logged in."))
-            headers['Cookie'] = self.cookie
-
-        resource = '/ca/rest'
-        if self.path is not None:
-            resource = os.path.join(resource, self.path)
-        if path is not None:
-            resource = os.path.join(resource, path)
-
-        # perform main request
-        status, resp_headers, resp_body = dogtag.https_request(
-            self.ca_host, self.override_port or self.env.ca_agent_port,
-            resource,
-            self.sec_dir, self.password, self.ipa_certificate_nickname,
-            method=method, headers=headers, body=body
-        )
-        if status < 200 or status >= 300:
-            explanation = self._parse_dogtag_error(resp_body) or ''
-            raise errors.HTTPRequestError(
-                status=status,
-                reason=_('Non-2xx response from CA REST API: %(status)d. %(explanation)s')
-                % {'status': status, 'explanation': explanation}
-            )
-        return (status, resp_headers, resp_body)
-
-
 @register()
 class ra_certprofile(RestClient):
     """
-- 
2.5.5

-------------- next part --------------
From 40e3635a448ab22fef5dda38f7dc543407b0cf41 Mon Sep 17 00:00:00 2001
From: Fraser Tweedale <ftweedal at redhat.com>
Date: Fri, 26 Aug 2016 11:11:56 +1000
Subject: [PATCH 105/105] cert-request: raise CertificateOperationError if CA
 disabled

Detect when cert-request returns HTTP 409, which indicates that the
target CA is disabled - a valid scenario - and raise
CertificateOperationError with a friendly message instead of
HTTPRequestError.

Fixes: https://fedorahosted.org/freeipa/ticket/6260
---
 ipaserver/plugins/cert.py | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)

diff --git a/ipaserver/plugins/cert.py b/ipaserver/plugins/cert.py
index 6dd9f6ffcdcd9d051d50d912996fea2104d71dff..e7b0a74b5311f2bb4533fe822ad46df6eba98b35 100644
--- a/ipaserver/plugins/cert.py
+++ b/ipaserver/plugins/cert.py
@@ -618,8 +618,16 @@ class cert_request(Create, BaseCertMethod, VirtualCommand):
                          name_type)
 
         # Request the certificate
-        result = self.Backend.ra.request_certificate(
-            csr, profile_id, ca_id, request_type=request_type)
+        try:
+            result = self.Backend.ra.request_certificate(
+                csr, profile_id, ca_id, request_type=request_type)
+        except errors.HTTPRequestError as e:
+            if e.status == 409:  # pylint: disable=E1101
+                raise errors.CertificateOperationError(
+                    error=_("CA '%s' is disabled") % ca)
+            else:
+                raise e
+
         if not raw:
             self.obj._parse(result)
             result['request_id'] = int(result['request_id'])
-- 
2.5.5



More information about the Freeipa-devel mailing list