diff options
-rw-r--r-- | ACI.txt | 8 | ||||
-rw-r--r-- | API.txt | 62 | ||||
-rw-r--r-- | install/updates/40-certprofile.update | 9 | ||||
-rw-r--r-- | install/updates/40-delegation.update | 8 | ||||
-rw-r--r-- | install/updates/Makefile.am | 1 | ||||
-rw-r--r-- | ipalib/constants.py | 1 | ||||
-rw-r--r-- | ipalib/plugins/certprofile.py | 253 | ||||
-rw-r--r-- | ipapython/dogtag.py | 29 | ||||
-rw-r--r-- | ipaserver/plugins/dogtag.py | 176 |
9 files changed, 534 insertions, 13 deletions
@@ -22,6 +22,14 @@ dn: cn=automount,dc=ipa,dc=example aci: (targetattr = "automountmapname || description")(targetfilter = "(objectclass=automountmap)")(version 3.0;acl "permission:System: Modify Automount Maps";allow (write) groupdn = "ldap:///cn=System: Modify Automount Maps,cn=permissions,cn=pbac,dc=ipa,dc=example";) dn: cn=automount,dc=ipa,dc=example aci: (targetfilter = "(objectclass=automountmap)")(version 3.0;acl "permission:System: Remove Automount Maps";allow (delete) groupdn = "ldap:///cn=System: Remove Automount Maps,cn=permissions,cn=pbac,dc=ipa,dc=example";) +dn: cn=certprofiles,cn=ca,dc=ipa,dc=example +aci: (targetfilter = "(objectclass=ipacertprofile)")(version 3.0;acl "permission:System: Delete Certificate Profile";allow (delete) groupdn = "ldap:///cn=System: Delete Certificate Profile,cn=permissions,cn=pbac,dc=ipa,dc=example";) +dn: cn=certprofiles,cn=ca,dc=ipa,dc=example +aci: (targetfilter = "(objectclass=ipacertprofile)")(version 3.0;acl "permission:System: Import Certificate Profile";allow (add) groupdn = "ldap:///cn=System: Import Certificate Profile,cn=permissions,cn=pbac,dc=ipa,dc=example";) +dn: cn=certprofiles,cn=ca,dc=ipa,dc=example +aci: (targetattr = "cn || description || ipacertprofilestoreissued")(targetfilter = "(objectclass=ipacertprofile)")(version 3.0;acl "permission:System: Modify Certificate Profile";allow (write) groupdn = "ldap:///cn=System: Modify Certificate Profile,cn=permissions,cn=pbac,dc=ipa,dc=example";) +dn: cn=certprofiles,cn=ca,dc=ipa,dc=example +aci: (targetattr = "cn || createtimestamp || description || entryusn || ipacertprofilestoreissued || modifytimestamp || objectclass")(targetfilter = "(objectclass=ipacertprofile)")(version 3.0;acl "permission:System: Read Certificate Profiles";allow (compare,read,search) userdn = "ldap:///all";) dn: cn=ipaconfig,cn=etc,dc=ipa,dc=example aci: (targetattr = "cn || createtimestamp || entryusn || ipacertificatesubjectbase || ipaconfigstring || ipacustomfields || ipadefaultemaildomain || ipadefaultloginshell || ipadefaultprimarygroup || ipagroupobjectclasses || ipagroupsearchfields || ipahomesrootdir || ipakrbauthzdata || ipamaxusernamelength || ipamigrationenabled || ipapwdexpadvnotify || ipasearchrecordslimit || ipasearchtimelimit || ipaselinuxusermapdefault || ipaselinuxusermaporder || ipauserauthtype || ipauserobjectclasses || ipausersearchfields || modifytimestamp || objectclass")(targetfilter = "(objectclass=ipaguiconfig)")(version 3.0;acl "permission:System: Read Global Configuration";allow (compare,read,search) userdn = "ldap:///all";) dn: cn=costemplates,cn=accounts,dc=ipa,dc=example @@ -509,6 +509,68 @@ args: 1,1,1 arg: Str('request_id') option: Str('version?', exclude='webui') output: Output('result', None, None) +command: certprofile_del +args: 1,2,3 +arg: Str('cn', attribute=True, cli_name='id', multivalue=True, primary_key=True, query=True, required=True) +option: Flag('continue', autofill=True, cli_name='continue', default=False) +option: Str('version?', exclude='webui') +output: Output('result', <type 'dict'>, None) +output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None) +output: ListOfPrimaryKeys('value', None, None) +command: certprofile_find +args: 1,9,4 +arg: Str('criteria?', noextrawhitespace=False) +option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui') +option: Str('cn', attribute=True, autofill=False, cli_name='id', multivalue=False, primary_key=True, query=True, required=False) +option: Str('description', attribute=True, autofill=False, cli_name='desc', multivalue=False, query=True, required=False) +option: Bool('ipacertprofilestoreissued', attribute=True, autofill=False, cli_name='store', default=True, multivalue=False, query=True, required=False) +option: Flag('pkey_only?', autofill=True, default=False) +option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui') +option: Int('sizelimit?', autofill=False, minvalue=0) +option: Int('timelimit?', autofill=False, minvalue=0) +option: Str('version?', exclude='webui') +output: Output('count', <type 'int'>, None) +output: ListOfEntries('result', (<type 'list'>, <type 'tuple'>), Gettext('A list of LDAP entries', domain='ipa', localedir=None)) +output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None) +output: Output('truncated', <type 'bool'>, None) +command: certprofile_import +args: 1,6,3 +arg: Str('cn', attribute=True, cli_name='id', multivalue=False, primary_key=True, required=True) +option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui') +option: Str('description', attribute=True, cli_name='desc', multivalue=False, required=True) +option: File('file', cli_name='file') +option: Bool('ipacertprofilestoreissued', attribute=True, cli_name='store', default=True, multivalue=False, required=True) +option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui') +option: Str('version?', exclude='webui') +output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None)) +output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None) +output: PrimaryKey('value', None, None) +command: certprofile_mod +args: 1,10,3 +arg: Str('cn', attribute=True, cli_name='id', multivalue=False, primary_key=True, query=True, required=True) +option: Str('addattr*', cli_name='addattr', exclude='webui') +option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui') +option: Str('delattr*', cli_name='delattr', exclude='webui') +option: Str('description', attribute=True, autofill=False, cli_name='desc', multivalue=False, required=False) +option: Bool('ipacertprofilestoreissued', attribute=True, autofill=False, cli_name='store', default=True, multivalue=False, required=False) +option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui') +option: Str('rename', cli_name='rename', multivalue=False, primary_key=True, required=False) +option: Flag('rights', autofill=True, default=False) +option: Str('setattr*', cli_name='setattr', exclude='webui') +option: Str('version?', exclude='webui') +output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None)) +output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None) +output: PrimaryKey('value', None, None) +command: certprofile_show +args: 1,4,3 +arg: Str('cn', attribute=True, cli_name='id', multivalue=False, primary_key=True, query=True, required=True) +option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui') +option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui') +option: Flag('rights', autofill=True, default=False) +option: Str('version?', exclude='webui') +output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None)) +output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None) +output: PrimaryKey('value', None, None) command: compat_is_enabled args: 0,1,1 option: Str('version?', exclude='webui') diff --git a/install/updates/40-certprofile.update b/install/updates/40-certprofile.update new file mode 100644 index 000000000..6b0a81d0f --- /dev/null +++ b/install/updates/40-certprofile.update @@ -0,0 +1,9 @@ +dn: cn=ca,$SUFFIX +default: objectClass: nsContainer +default: objectClass: top +default: cn: ca + +dn: cn=certprofiles,cn=ca,$SUFFIX +default: objectClass: nsContainer +default: objectClass: top +default: cn: certprofiles diff --git a/install/updates/40-delegation.update b/install/updates/40-delegation.update index 975929bd7..bc0736c5b 100644 --- a/install/updates/40-delegation.update +++ b/install/updates/40-delegation.update @@ -237,3 +237,11 @@ default:ipapermissiontype: SYSTEM dn: cn=config add:aci: (version 3.0;acl "permission:Add Configuration Sub-Entries";allow (add) groupdn = "ldap:///cn=Add Configuration Sub-Entries,cn=permissions,cn=pbac,$SUFFIX";) + +# CA Administrators +dn: cn=CA Administrator,cn=privileges,cn=pbac,$SUFFIX +default:objectClass: nestedgroup +default:objectClass: groupofnames +default:objectClass: top +default:cn: CA Administrator +default:description: CA Administrator diff --git a/install/updates/Makefile.am b/install/updates/Makefile.am index 4e2da05d6..fc6bd624e 100644 --- a/install/updates/Makefile.am +++ b/install/updates/Makefile.am @@ -32,6 +32,7 @@ app_DATA = \ 40-replication.update \ 40-dns.update \ 40-automember.update \ + 40-certprofile.update \ 40-otp.update \ 40-vault.update \ 45-roles.update \ diff --git a/ipalib/constants.py b/ipalib/constants.py index 95dec54a5..96396a236 100644 --- a/ipalib/constants.py +++ b/ipalib/constants.py @@ -118,6 +118,7 @@ DEFAULT_CONFIG = ( ('container_radiusproxy', DN(('cn', 'radiusproxy'))), ('container_views', DN(('cn', 'views'), ('cn', 'accounts'))), ('container_masters', DN(('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'))), + ('container_certprofile', DN(('cn', 'certprofiles'), ('cn', 'ca'))), # Ports, hosts, and URIs: ('xmlrpc_uri', 'http://localhost:8888/ipa/xml'), diff --git a/ipalib/plugins/certprofile.py b/ipalib/plugins/certprofile.py new file mode 100644 index 000000000..1a2d14388 --- /dev/null +++ b/ipalib/plugins/certprofile.py @@ -0,0 +1,253 @@ +# +# Copyright (C) 2015 FreeIPA Contributors see COPYING for license +# + +import re + +from ipalib import api, Bool, File, Str +from ipalib import output +from ipalib.plugable import Registry +from ipalib.plugins.virtual import VirtualCommand +from ipalib.plugins.baseldap import ( + LDAPObject, LDAPSearch, LDAPCreate, + LDAPDelete, LDAPUpdate, LDAPRetrieve) +from ipalib import ngettext +from ipalib.text import _ + +from ipalib import errors + + +__doc__ = _(""" +Manage Certificate Profiles + +Certificate Profiles are used by Certificate Authority (CA) in the signing of +certificates to determine if a Certificate Signing Request (CSR) is acceptable, +and if so what features and extensions will be present on the certificate. + +The Certificate Profile format is the property-list format understood by the +Dogtag or Red Hat Certificate System CA. + +PROFILE ID SYNTAX: + +A Profile ID is a string without spaces or punctuation starting with a letter +and followed by a sequence of letters, digits or underscore ("_"). + +EXAMPLES: + + Import a profile that will not store issued certificates: + ipa certprofile-import ShortLivedUserCert \\ + --file UserCert.profile --summary "User Certificates" \\ + --store=false + + Delete a certificate profile: + ipa certprofile-del ShortLivedUserCert + + Show information about a profile: + ipa certprofile-show ShortLivedUserCert + + Search for profiles that do not store certificates: + ipa certprofile-find --store=false + +""") + + +register = Registry() + + +def ca_enabled_check(): + """Raise NotFound if CA is not enabled. + + This function is defined in multiple plugins to avoid circular imports + (cert depends on certprofile, so we cannot import cert here). + + """ + if not api.Command.ca_is_enabled()['result']: + raise errors.NotFound(reason=_('CA is not configured')) + + +profile_id_pattern = re.compile('^[a-zA-Z]\w*$') + + +def validate_profile_id(ugettext, value): + """Ensure profile ID matches form required by CA.""" + if profile_id_pattern.match(value) is None: + return _('invalid Profile ID') + else: + return None + + +@register() +class certprofile(LDAPObject): + """ + Certificate Profile object. + """ + container_dn = api.env.container_certprofile + object_name = _('Certificate Profile') + object_name_plural = _('Certificate Profiles') + object_class = ['ipacertprofile'] + default_attributes = [ + 'cn', 'description', 'ipacertprofilestoreissued' + ] + search_attributes = [ + 'cn', 'description', 'ipacertprofilestoreissued' + ] + rdn_is_primary_key = True + label = _('Certificate Profiles') + label_singular = _('Certificate Profile') + + takes_params = ( + Str('cn', validate_profile_id, + primary_key=True, + cli_name='id', + label=_('Profile ID'), + doc=_('Profile ID for referring to this profile'), + ), + Str('description', + required=True, + cli_name='desc', + label=_('Profile description'), + doc=_('Brief description of this profile'), + ), + Bool('ipacertprofilestoreissued', + default=True, + cli_name='store', + label=_('Store issued certificates'), + doc=_('Whether to store certs issued using this profile'), + ), + ) + + permission_filter_objectclasses = ['ipacertprofile'] + managed_permissions = { + 'System: Read Certificate Profiles': { + 'replaces_global_anonymous_aci': True, + 'ipapermbindruletype': 'all', + 'ipapermright': {'read', 'search', 'compare'}, + 'ipapermdefaultattr': { + 'cn', + 'description', + 'ipacertprofilestoreissued', + 'objectclass', + }, + }, + 'System: Import Certificate Profile': { + 'ipapermright': {'add'}, + 'replaces': [ + '(target = "ldap:///cn=*,cn=certprofiles,cn=ca,$SUFFIX")(version 3.0;acl "permission:Import Certificate Profile";allow (add) groupdn = "ldap:///cn=Import Certificate Profile,cn=permissions,cn=pbac,$SUFFIX";)', + ], + 'default_privileges': {'CA Administrator'}, + }, + 'System: Delete Certificate Profile': { + 'ipapermright': {'delete'}, + 'replaces': [ + '(target = "ldap:///cn=*,cn=certprofiles,cn=ca,$SUFFIX")(version 3.0;acl "permission:Delete Certificate Profile";allow (delete) groupdn = "ldap:///cn=Delete Certificate Profile,cn=permissions,cn=pbac,$SUFFIX";)', + ], + 'default_privileges': {'CA Administrator'}, + }, + 'System: Modify Certificate Profile': { + 'ipapermright': {'write'}, + 'ipapermdefaultattr': { + 'cn', + 'description', + 'ipacertprofilestoreissued', + }, + 'replaces': [ + '(targetattr = "cn || description || ipacertprofilestoreissued")(target = "ldap:///cn=*,cn=certprofiles,cn=ca,$SUFFIX")(version 3.0;acl "permission:Modify Certificate Profile";allow (write) groupdn = "ldap:///cn=Modify Certificate Profile,cn=permissions,cn=pbac,$SUFFIX";)', + ], + 'default_privileges': {'CA Administrator'}, + }, + } + + + +@register() +class certprofile_find(LDAPSearch): + __doc__ = _("Search for Certificate Profiles.") + msg_summary = ngettext( + '%(count)d profile matched', '%(count)d profiles matched', 0 + ) + + def execute(self, *args, **kwargs): + ca_enabled_check() + return super(certprofile_find, self).execute(*args, **kwargs) + + +@register() +class certprofile_show(LDAPRetrieve): + __doc__ = _("Display the properties of a Certificate Profile.") + + def execute(self, *args, **kwargs): + ca_enabled_check() + return super(certprofile_show, self).execute(*args, **kwargs) + + +@register() +class certprofile_import(LDAPCreate): + __doc__ = _("Import a Certificate Profile.") + msg_summary = _('Imported profile "%(value)s"') + takes_options = ( + File('file', + label=_('Filename'), + cli_name='file', + flags=('virtual_attribute',), + ), + ) + + PROFILE_ID_PATTERN = re.compile('^profileId=([a-zA-Z]\w*)', re.MULTILINE) + + def pre_callback(self, ldap, dn, entry, entry_attrs, *keys, **options): + ca_enabled_check() + + match = self.PROFILE_ID_PATTERN.search(options['file']) + if match is None: + raise errors.ValidationError(name='file', + error=_("Profile ID is not present in profile data")) + elif keys[0] != match.group(1): + raise errors.ValidationError(name='file', + error=_("Profile ID '%(cli_value)s' does not match profile data '%(file_value)s'") + % {'cli_value': keys[0], 'file_value': match.group(1)} + ) + return dn + + + def post_callback(self, ldap, dn, entry_attrs, *keys, **options): + """Import the profile into Dogtag and enable it. + + If the operation succeeds, update the LDAP entry to 'enabled'. + If the operation fails, remove the LDAP entry. + """ + try: + with self.api.Backend.ra_certprofile as profile_api: + profile_api.create_profile(options['file']) + profile_api.enable_profile(keys[0]) + except: + # something went wrong ; delete entry + ldap.delete_entry(dn) + raise + + return dn + + +@register() +class certprofile_del(LDAPDelete): + __doc__ = _("Delete a Certificate Profile.") + msg_summary = _('Deleted profile "%(value)s"') + + def execute(self, *args, **kwargs): + ca_enabled_check() + return super(certprofile_del, self).execute(*args, **kwargs) + + def post_callback(self, ldap, dn, *keys, **options): + with self.api.Backend.ra_certprofile as profile_api: + profile_api.disable_profile(keys[0]) + profile_api.delete_profile(keys[0]) + return dn + + +@register() +class certprofile_mod(LDAPUpdate): + __doc__ = _("Modify Certificate Profile configuration.") + msg_summary = _('Modified Certificate Profile "%(value)s') + + def execute(self, *args, **kwargs): + ca_enabled_check() + return super(certprofile_mod, self).execute(*args, **kwargs) diff --git a/ipapython/dogtag.py b/ipapython/dogtag.py index c74b8736a..11311cf7b 100644 --- a/ipapython/dogtag.py +++ b/ipapython/dogtag.py @@ -233,9 +233,12 @@ def ca_status(ca_host=None, use_proxy=True): return _parse_ca_status(body) -def https_request(host, port, url, secdir, password, nickname, **kw): +def https_request(host, port, url, secdir, password, nickname, + method='POST', headers=None, body=None, **kw): """ + :param method: HTTP request method (defalut: 'POST') :param url: The path (not complete URL!) to post to. + :param body: The request body (encodes kw if None) :param kw: Keyword arguments to encode into POST body. :return: (http_status, http_reason_phrase, http_headers, http_body) as (integer, unicode, dict, str) @@ -254,9 +257,11 @@ def https_request(host, port, url, secdir, password, nickname, **kw): nickname, password, nss.get_default_certdb()) return conn - body = urlencode(kw) + if body is None: + body = urlencode(kw) return _httplib_request( - 'https', host, port, url, connection_factory, body) + 'https', host, port, url, connection_factory, body, + method=method, headers=headers) def http_request(host, port, url, **kw): @@ -288,11 +293,13 @@ def unauthenticated_https_request(host, port, url, **kw): def _httplib_request( - protocol, host, port, path, connection_factory, request_body): + protocol, host, port, path, connection_factory, request_body, + method='POST', headers=None): """ :param request_body: Request body :param connection_factory: Connection class to use. Will be called with the host and port arguments. + :param method: HTTP request method (default: 'POST') Perform a HTTP(s) request. """ @@ -301,13 +308,17 @@ def _httplib_request( uri = '%s://%s%s' % (protocol, ipautil.format_netloc(host, port), path) root_logger.debug('request %r', uri) root_logger.debug('request body %r', request_body) + + headers = headers or {} + if ( + method == 'POST' + and 'content-type' not in (str(k).lower() for k in headers.viewkeys()) + ): + headers['content-type'] = 'application/x-www-form-urlencoded' + try: conn = connection_factory(host, port) - conn.request( - 'POST', uri, - body=request_body, - headers={'Content-type': 'application/x-www-form-urlencoded'}, - ) + conn.request(method, uri, body=request_body, headers=headers) res = conn.getresponse() http_status = res.status diff --git a/ipaserver/plugins/dogtag.py b/ipaserver/plugins/dogtag.py index 52bdb0d42..9654123b1 100644 --- a/ipaserver/plugins/dogtag.py +++ b/ipaserver/plugins/dogtag.py @@ -4,8 +4,9 @@ # Jason Gerard DeRose <jderose@redhat.com> # Rob Crittenden <rcritten@@redhat.com> # John Dennis <jdennis@redhat.com> +# Fraser Tweedale <ftweedal@redhat.com> # -# Copyright (C) 2014 Red Hat +# Copyright (C) 2014, 2015 Red Hat # see file 'COPYING' for use and warranty information # # This program is free software; you can redistribute it and/or modify @@ -238,17 +239,21 @@ digits and nothing else follows. ''' import datetime +import json from lxml import etree +import os import tempfile import time import urllib2 +import pki from pki.client import PKIConnection import pki.crypto as cryptoutil from pki.kra import KRAClient from ipalib import Backend from ipapython.dn import DN +import ipapython.cookie import ipapython.dogtag from ipapython import ipautil from ipaserver.install.certs import CertDB @@ -1262,13 +1267,12 @@ def select_any_master(ldap2, service='CA'): #------------------------------------------------------------------------------- -from ipalib import api, SkipPluginModule +from ipalib import api, errors, SkipPluginModule if api.env.ra_plugin != 'dogtag': # In this case, abort loading this plugin module... raise SkipPluginModule(reason='dogtag not selected as RA plugin') import os, random from ipaserver.plugins import rabase -from ipalib.errors import CertificateOperationError from ipalib.constants import TYPE_ERROR from ipalib.util import cachedproperty from ipapython import dogtag @@ -1318,7 +1322,7 @@ class ra(rabase.rabase): err_msg = u'%s (%s)' % (err_msg, detail) self.error('%s.%s(): %s', self.fullname, func_name, err_msg) - raise CertificateOperationError(error=err_msg) + raise errors.CertificateOperationError(error=err_msg) @cachedproperty def ca_host(self): @@ -1923,3 +1927,167 @@ class kra(Backend): return KRAClient(connection, crypto) api.register(kra) + + +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:: + + class ra_certprofile(RestClient): + path = 'profile' + ... + + api.register(ra_certprofile) + + 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: + return None + + def __init__(self): + 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(RestClient, self).__init__() + + # session cookie + self.cookie = None + + @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, status_text, resp_headers, resp_body = dogtag.https_request( + self.ca_host, 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.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): + """ + :param url: The URL to post to. + :param kw: Keyword arguments to encode into POST body. + :return: (http_status, http_reason_phrase, http_headers, http_body) + as (integer, unicode, 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 + + resource = os.path.join('/ca/rest', self.path, path) + + # perform main request + status, status_text, resp_headers, resp_body = dogtag.https_request( + self.ca_host, 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.RemoteRetrieveError( + reason=_('Non-2xx response from CA REST API: %(status)d %(status_text)s. %(explanation)s') + % {'status': status, 'status_text': status_text, 'explanation': explanation} + ) + return (status, status_text, resp_headers, resp_body) + + +class ra_certprofile(RestClient): + """ + Profile management backend plugin. + """ + path = 'profiles' + + def create_profile(self, profile_data): + """ + Import the profile into Dogtag + """ + self._ssldo('POST', 'raw', + headers={ + 'Content-type': 'application/xml', + 'Accept': 'application/json', + }, + body=profile_data + ) + + def enable_profile(self, profile_id): + """ + Enable the profile in Dogtag + """ + self._ssldo('POST', profile_id + '?action=enable') + + def disable_profile(self, profile_id): + """ + Enable the profile in Dogtag + """ + self._ssldo('POST', profile_id + '?action=disable') + + def delete_profile(self, profile_id): + """ + Delete the profile from Dogtag + """ + self._ssldo('DELETE', profile_id, headers={'Accept': 'application/json'}) + +api.register(ra_certprofile) |