On 10/20/2015 02:19 PM, Martin Basti wrote:

NACK



1)

I still see many hardcoded passwords in the code

with change_principal(smime_user, "Secret123"):

For now changed to module variable.


2)

Also the 'alice' username can be extracted to module variable
instead hardcoding


The fixture should take the place of module variables in the tests. Changed u'alice' into local variable. Once we fix the problems with UserTracker, we should store the password here as well.

3)

File alice.conf.tmpl can be generalized to be used for more users,
replace alice in template to {username} and in code replace this
variable with alice, also do not forgot rename template to something
more general



Done.








Updated patch set attached.

--
Milan Kubik

From 42df41de0b621392e113140602c8c9fd10c7d719 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Milan=20Kub=C3=ADk?= <mku...@redhat.com>
Date: Fri, 17 Jul 2015 14:42:23 +0200
Subject: [PATCH 1/6] ipatests: add fuzzy instances for CA ACL DN and RDN

https://fedorahosted.org/freeipa/ticket/57
---
 ipatests/test_xmlrpc/xmlrpc_test.py | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/ipatests/test_xmlrpc/xmlrpc_test.py b/ipatests/test_xmlrpc/xmlrpc_test.py
index 80638e2efdd9d7ff07fd89688397acb7d44654cd..5880a4c6f2d9bae23f296aa6386f5705f18eea29 100644
--- a/ipatests/test_xmlrpc/xmlrpc_test.py
+++ b/ipatests/test_xmlrpc/xmlrpc_test.py
@@ -77,6 +77,14 @@ fuzzy_sudocmddn = Fuzzy(
     '(?i)ipauniqueid=%s,cn=sudocmds,cn=sudo,%s' % (uuid_re, api.env.basedn)
 )
 
+# Matches caacl dn
+fuzzy_caacldn = Fuzzy(
+    '(?i)ipauniqueid=%s,cn=caacls,cn=ca,%s' % (uuid_re, api.env.basedn)
+)
+
+# Matches fuzzy ipaUniqueID DN group (RDN)
+fuzzy_ipauniqueid = Fuzzy('(?i)ipauniqueid=%s' % uuid_re)
+
 # Matches a hash signature, not enforcing length
 fuzzy_hash = Fuzzy('^([a-f0-9][a-f0-9]:)+[a-f0-9][a-f0-9]$', type=six.string_types)
 
-- 
2.6.1

From 57fcb2f7e5e4a30963c698cf41aae0a6bab45d26 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Milan=20Kub=C3=ADk?= <mku...@redhat.com>
Date: Tue, 30 Jun 2015 17:00:18 +0200
Subject: [PATCH 2/6] ipatests: Add initial CAACLTracker implementation

The patch implements the tracker for CA ACL feature.
The basic CRUD checkers has been implemented. The methods
for adding and removing the association of the resources
with the ACL do not have the check methods. These will be provided
as a separate test suite.

https://fedorahosted.org/freeipa/ticket/57
---
 ipatests/test_xmlrpc/objectclasses.py     |   5 +
 ipatests/test_xmlrpc/test_caacl_plugin.py | 378 ++++++++++++++++++++++++++++++
 2 files changed, 383 insertions(+)
 create mode 100644 ipatests/test_xmlrpc/test_caacl_plugin.py

diff --git a/ipatests/test_xmlrpc/objectclasses.py b/ipatests/test_xmlrpc/objectclasses.py
index 1cd77c7f885fe408d0d9d48fc6d8284900c91b7f..134a08803f3abca1124c4d26274d9e3fc981b941 100644
--- a/ipatests/test_xmlrpc/objectclasses.py
+++ b/ipatests/test_xmlrpc/objectclasses.py
@@ -217,3 +217,8 @@ certprofile = [
     u'top',
     u'ipacertprofile',
 ]
+
+caacl = [
+    u'ipaassociation',
+    u'ipacaacl'
+]
diff --git a/ipatests/test_xmlrpc/test_caacl_plugin.py b/ipatests/test_xmlrpc/test_caacl_plugin.py
new file mode 100644
index 0000000000000000000000000000000000000000..6cf835b229f70797e32bcfd2309cfa7be5732f51
--- /dev/null
+++ b/ipatests/test_xmlrpc/test_caacl_plugin.py
@@ -0,0 +1,378 @@
+#
+# Copyright (C) 2015  FreeIPA Contributors see COPYING for license
+#
+
+"""
+Test the `ipalib.plugins.caacl` module.
+"""
+
+import os
+
+import pytest
+
+from ipapython import ipautil
+from ipalib import errors, x509
+from ipapython.dn import DN
+from ipatests.test_xmlrpc.ldaptracker import Tracker
+from ipatests.test_xmlrpc.xmlrpc_test import (XMLRPC_test, fuzzy_caacldn,
+                                              fuzzy_uuid, fuzzy_ipauniqueid,
+                                              raises_exact)
+from ipatests.test_xmlrpc import objectclasses
+from ipatests.util import assert_deepequal
+
+
+class CAACLTracker(Tracker):
+    """Tracker class for CA ACL LDAP object.
+
+    The class implements methods required by the base class
+    to help with basic CRUD operations.
+
+    Methods for adding and deleting actual member entries into an ACL
+    do not have check methods as these would make the class
+    unnecessarily complicated. The checks are implemented as
+    a standalone test suite. However, this makes the test crucial
+    in debugging more complicated test cases. Since the add/remove
+    operation won't be checked right away by the tracker, a problem
+    in this operation may propagate into more complicated test case.
+
+    It is possible to pass a list of member uids to these methods.
+
+    The test uses instances of Fuzzy class to compare results as they
+    are in the UUID format. The dn and rdn properties were modified
+    to reflect this as well.
+    """
+
+    member_keys = {
+        u'memberuser_user', u'memberuser_group',
+        u'memberhost_host', u'memberhost_hostgroup',
+        u'memberservice_service',
+        u'ipamembercertprofile_certprofile'}
+    category_keys = {
+        u'ipacacategory', u'ipacertprofilecategory', u'usercategory',
+        u'hostcategory', u'servicecategory'}
+    retrieve_keys = {
+        u'dn', u'cn', u'description', u'ipaenabledflag',
+        u'ipamemberca', u'ipamembercertprofile', u'memberuser',
+        u'memberhost', u'memberservice'} | member_keys | category_keys
+    retrieve_all_keys = retrieve_keys | {u'objectclass', u'ipauniqueid'}
+    create_keys = {u'dn', u'cn', u'description', u'ipacertprofilecategory',
+                   u'usercategory', u'hostcategory', u'ipacacategory',
+                   u'servicecategory', u'ipaenabledflag', u'objectclass',
+                   u'ipauniqueid'}
+    update_keys = create_keys - {u'dn'}
+
+    def __init__(self, name, ipacertprofile_category=None, user_category=None,
+                 service_category=None, host_category=None, description=None,
+                 default_version=None):
+        super(CAACLTracker, self).__init__(default_version=default_version)
+
+        self._name = name
+        self.description = description
+        self._categories = dict(
+            ipacertprofilecategory=ipacertprofile_category,
+            usercategory=user_category,
+            servicecategory=service_category,
+            hostcategory=host_category)
+
+        self.dn = fuzzy_caacldn
+
+    @property
+    def name(self):
+        return self._name
+
+    @property
+    def rdn(self):
+        return fuzzy_ipauniqueid
+
+    @property
+    def categories(self):
+        """To be used in track_create"""
+        return {cat: v for cat, v in self._categories.items() if v}
+
+    @property
+    def create_categories(self):
+        """ Return the categories set on create.
+            Unused categories are left out.
+        """
+        return {cat: [v] for cat, v in self.categories.items() if v}
+
+    def make_create_command(self, force=True):
+        return self.make_command(u'caacl_add', self.name,
+                                 description=self.description,
+                                 **self.categories)
+
+    def check_create(self, result):
+        assert_deepequal(dict(
+            value=self.name,
+            summary=u'Added CA ACL "{}"'.format(self.name),
+            result=dict(self.filter_attrs(self.create_keys))
+        ), result)
+
+    def track_create(self):
+        self.attrs = dict(
+            dn=self.dn,
+            ipauniqueid=[fuzzy_uuid],
+            cn=[self.name],
+            objectclass=objectclasses.caacl,
+            ipaenabledflag=[u'TRUE'])
+
+        self.attrs.update(self.create_categories)
+        if self.description:
+            self.attrs.update({u'description', [self.description]})
+
+        self.exists = True
+
+    def make_delete_command(self):
+        return self.make_command('caacl_del', self.name)
+
+    def check_delete(self, result):
+        assert_deepequal(dict(
+            value=[self.name],
+            summary=u'Deleted CA ACL "{}"'.format(self.name),
+            result=dict(failed=[])
+        ), result)
+
+    def make_retrieve_command(self, all=False, raw=False):
+        return self.make_command('caacl_show', self.name, all=all, raw=raw)
+
+    def check_retrieve(self, result, all=False, raw=False):
+        if all:
+            expected = self.filter_attrs(self.retrieve_all_keys)
+        else:
+            expected = self.filter_attrs(self.retrieve_keys)
+
+        assert_deepequal(dict(
+            value=self.name,
+            summary=None,
+            result=expected
+        ), result)
+
+    def make_find_command(self, *args, **kwargs):
+        return self.make_command('caacl_find', *args, **kwargs)
+
+    def check_find(self, result, all=False, raw=False):
+        if all:
+            expected = self.filter_attrs(self.retrieve_all_keys)
+        else:
+            expected = self.filter_attrs(self.retrieve_keys)
+
+        assert_deepequal(dict(
+            count=1,
+            truncated=False,
+            summary=u'1 CA ACL matched',
+            result=[expected]
+        ), result)
+
+    def make_update_command(self, updates):
+        return self.make_command('caacl_mod', self.name, **updates)
+
+    def update(self, updates, expected_updates=None, silent=False):
+        """If removing a category, delete it from tracker as well"""
+        # filter out empty categories and track changes
+
+        filtered_updates = dict()
+        for key, value in updates.items():
+            if key in self.category_keys:
+                if not value:
+                    try:
+                        del self.attrs[key]
+                    except IndexError:
+                        if silent:
+                            pass
+                else:
+                    # if there is a value, prepare the pair for update
+                    filtered_updates.update({key: value})
+            else:
+                filtered_updates.update({key: value})
+
+        if expected_updates is None:
+            expected_updates = {}
+
+        command = self.make_update_command(updates)
+
+        try:
+            result = command()
+        except errors.EmptyModlist:
+            if silent:
+                self.attrs.update(filtered_updates)
+                self.attrs.update(expected_updates)
+                self.check_update(result,
+                                  extra_keys=set(self.update_keys) |
+                                  set(expected_updates.keys()))
+
+    def check_update(self, result, extra_keys=()):
+        assert_deepequal(dict(
+            value=self.name,
+            summary=u'Modified CA ACL "{}"'.format(self.name),
+            result=self.filter_attrs(self.update_keys | set(extra_keys))
+        ), result)
+
+    # Helper methods for caacl subcommands. The check methods will be
+    # implemented in standalone test
+    #
+    # The methods implemented here will be:
+    # caacl_{add,remove}_{host, service, certprofile, user [, subca]}
+
+    def _add_acl_component(self, command_name, keys, track):
+        """ Add a resource into ACL rule and track it.
+
+            command_name - the name in the API
+            keys = {
+                'tracker_attr': {
+                    'api_key': 'value'
+                }
+            }
+
+            e.g.
+
+            keys = {
+                'memberhost_host': {
+                    'host': 'hostname'
+                },
+                'memberhost_hostgroup': {
+                    'hostgroup': 'hostgroup_name'
+                }
+            }
+        """
+
+        if not self.exists:
+            raise errors.NotFound(reason="The tracked entry doesn't exist.")
+
+        command = self.make_command(command_name, self.name)
+        command_options = dict()
+
+        # track
+        for tracker_attr in keys:
+            api_options = keys[tracker_attr]
+            if track:
+                for option in api_options:
+                    try:
+                        if type(option) in (list, tuple):
+                            self.attrs[tracker_attr].extend(api_options[option])
+                        else:
+                            self.attrs[tracker_attr].append(api_options[option])
+                    except KeyError:
+                        if type(option) in (list, tuple):
+                            self.attrs[tracker_attr] = api_options[option]
+                        else:
+                            self.attrs[tracker_attr] = [api_options[option]]
+            # prepare options for the command call
+            command_options.update(api_options)
+
+        return command(**command_options)
+
+    def _remove_acl_component(self, command_name, keys, track):
+        """ Remove a resource from ACL rule and track it.
+
+            command_name - the name in the API
+            keys = {
+                'tracker_attr': {
+                    'api_key': 'value'
+                }
+            }
+
+            e.g.
+
+            keys = {
+                'memberhost_host': {
+                    'host': 'hostname'
+                },
+                'memberhost_hostgroup': {
+                    'hostgroup': 'hostgroup_name'
+                }
+            }
+        """
+        command = self.make_command(command_name, self.name)
+        command_options = dict()
+
+        for tracker_attr in keys:
+            api_options = keys[tracker_attr]
+            if track:
+                for option in api_options:
+                    if type(option) in (list, tuple):
+                        for item in option:
+                            self.attrs[tracker_attr].remove(item)
+                    else:
+                        self.attrs[tracker_attr].remove(api_options[option])
+                    if len(self.attrs[tracker_attr]) == 0:
+                        del self.attrs[tracker_attr]
+            command_options.update(api_options)
+
+        return command(**command_options)
+
+    def add_host(self, host=None, hostgroup=None, track=True):
+        """Associates an host or hostgroup entry with the ACL.
+
+           The command takes an unicode string with the name
+           of the entry (RDN).
+
+           It is the responsibility of a test writer to provide
+           the correct value, object type as the method does not
+           verify whether the entry exists.
+
+           The method can add only one entry of each type
+           in one call.
+        """
+
+        options = {
+            u'memberhost_host': {u'host': host},
+            u'memberhost_hostgroup': {u'hostgroup': hostgroup}}
+
+        return self._add_acl_component(u'caacl_add_host', options, track)
+
+    def remove_host(self, host=None, hostgroup=None, track=True):
+        options = {
+            u'memberhost_host': {u'host': host},
+            u'memberhost_hostgroup': {u'hostgroup': hostgroup}}
+
+        return self._remove_acl_component(u'caacl_remove_host', options, track)
+
+    def add_user(self, user=None, group=None, track=True):
+        options = {
+            u'memberuser_user': {u'user': user},
+            u'memberuser_group': {u'group': group}}
+
+        return self._add_acl_component(u'caacl_add_user', options, track)
+
+    def remove_user(self, user=None, group=None, track=True):
+        options = {
+            u'memberuser_user': {u'user': user},
+            u'memberuser_group': {u'group': group}}
+
+        return self._remove_acl_component(u'caacl_remove_user', options, track)
+
+    def add_service(self, service=None, track=True):
+        options = {
+            u'memberservice_service': {u'service': service}}
+
+        return self._add_acl_component(u'caacl_add_service', options, track)
+
+    def remove_service(self, service=None, track=True):
+        options = {
+            u'memberservice_service': {u'service': service}}
+
+        return self._remove_acl_component(u'caacl_remove_service', options, track)
+
+    def add_profile(self, certprofile=None, track=True):
+        options = {
+            u'ipamembercertprofile_certprofile':
+                {u'certprofile': certprofile}}
+
+        return self._add_acl_component(u'caacl_add_profile', options, track)
+
+    def remove_profile(self, certprofile=None, track=True):
+        options = {
+            u'ipamembercertprofile_certprofile':
+                {u'certprofile': certprofile}}
+
+        return self._remove_acl_component(u'caacl_remove_profile', options, track)
+
+    def enable(self):
+        command = self.make_command(u'caacl_enable', self.name)
+        self.attrs.update({u'ipaenabledflag': [u'TRUE']})
+        command()
+
+    def disable(self):
+        command = self.make_command(u'caacl_disable', self.name)
+        self.attrs.update({u'ipaenabledflag': [u'FALSE']})
+        command()
-- 
2.6.1

From d3599a45ecb7d3bc8e5364fde239769291672ca2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Milan=20Kub=C3=ADk?= <mku...@redhat.com>
Date: Fri, 7 Aug 2015 15:54:18 +0200
Subject: [PATCH 3/6] tests: add test to check the default ACL

Also includes basic ACL manipulation and adding
and removing members to/from the acl.

https://fedorahosted.org/freeipa/ticket/57
---
 ipatests/test_xmlrpc/test_caacl_plugin.py | 135 ++++++++++++++++++++++++++++--
 1 file changed, 128 insertions(+), 7 deletions(-)

diff --git a/ipatests/test_xmlrpc/test_caacl_plugin.py b/ipatests/test_xmlrpc/test_caacl_plugin.py
index 6cf835b229f70797e32bcfd2309cfa7be5732f51..33268d6dde115ce040e55eaae08a7fe1299ad112 100644
--- a/ipatests/test_xmlrpc/test_caacl_plugin.py
+++ b/ipatests/test_xmlrpc/test_caacl_plugin.py
@@ -6,20 +6,20 @@
 Test the `ipalib.plugins.caacl` module.
 """
 
-import os
-
 import pytest
 
-from ipapython import ipautil
-from ipalib import errors, x509
-from ipapython.dn import DN
+from ipalib import errors
 from ipatests.test_xmlrpc.ldaptracker import Tracker
 from ipatests.test_xmlrpc.xmlrpc_test import (XMLRPC_test, fuzzy_caacldn,
-                                              fuzzy_uuid, fuzzy_ipauniqueid,
-                                              raises_exact)
+                                              fuzzy_uuid, fuzzy_ipauniqueid)
+
 from ipatests.test_xmlrpc import objectclasses
 from ipatests.util import assert_deepequal
 
+# reuse the fixture
+from ipatests.test_xmlrpc.test_certprofile_plugin import default_profile
+from ipatests.test_xmlrpc.test_stageuser_plugin import StageUserTracker
+
 
 class CAACLTracker(Tracker):
     """Tracker class for CA ACL LDAP object.
@@ -376,3 +376,124 @@ class CAACLTracker(Tracker):
         command = self.make_command(u'caacl_disable', self.name)
         self.attrs.update({u'ipaenabledflag': [u'FALSE']})
         command()
+
+
+@pytest.fixture(scope='class')
+def default_acl(request):
+    name = u'hosts_services_caIPAserviceCert'
+    tracker = CAACLTracker(name, service_category=u'all', host_category=u'all')
+    tracker.track_create()
+    tracker.attrs.update(
+        {u'ipamembercertprofile_certprofile': [u'caIPAserviceCert']})
+    return tracker
+
+
+@pytest.fixture(scope='class')
+def crud_acl(request):
+    name = u'crud-acl'
+    tracker = CAACLTracker(name)
+
+    return tracker.make_fixture(request)
+
+
+@pytest.fixture(scope='class')
+def category_acl(request):
+    name = u'category_acl'
+    tracker = CAACLTracker(name, ipacertprofile_category=u'all',
+                           user_category=u'all', service_category=u'all',
+                           host_category=u'all')
+
+    return tracker.make_fixture(request)
+
+
+@pytest.fixture(scope='class')
+def staged_user(request):
+    name = u'st-user'
+    tracker = StageUserTracker(name, u'stage', u'test')
+
+    return tracker.make_fixture(request)
+
+
+class TestDefaultACL(XMLRPC_test):
+    def test_default_acl_present(self, default_acl):
+        default_acl.retrieve()
+
+
+class TestCAACLbasicCRUD(XMLRPC_test):
+    def test_create(self, crud_acl):
+        crud_acl.create()
+
+    def test_delete(self, crud_acl):
+        crud_acl.delete()
+
+    def test_disable(self, crud_acl):
+        crud_acl.ensure_exists()
+        crud_acl.disable()
+        crud_acl.retrieve()
+
+    def test_disable_twice(self, crud_acl):
+        crud_acl.disable()
+        crud_acl.retrieve()
+
+    def test_enable(self, crud_acl):
+        crud_acl.enable()
+        crud_acl.retrieve()
+
+    def test_enable_twice(self, crud_acl):
+        crud_acl.enable()
+        crud_acl.retrieve()
+
+    def test_find(self, crud_acl):
+        crud_acl.find()
+
+
+class TestCAACLMembers(XMLRPC_test):
+    def test_category_member_exclusivity(self, category_acl, default_profile):
+        category_acl.create()
+        default_profile.ensure_exists()
+        with pytest.raises(errors.MutuallyExclusiveError):
+            category_acl.add_profile(default_profile.name, track=False)
+
+    def test_mod_delete_category(self, category_acl):
+        updates = dict(
+            hostcategory=None,
+            servicecategory=None,
+            ipacertprofilecategory=None,
+            usercategory=None)
+        category_acl.update(updates)
+
+    def test_add_profile(self, category_acl, default_profile):
+        category_acl.add_profile(certprofile=default_profile.name)
+        category_acl.retrieve()
+
+    def test_remove_profile(self, category_acl, default_profile):
+        category_acl.remove_profile(certprofile=default_profile.name)
+        category_acl.retrieve()
+
+    def test_add_invalid_value_service(self, category_acl, default_profile):
+        res = category_acl.add_service(service=default_profile.name, track=False)
+        assert len(res['failed']) == 1
+
+    # the same for other types
+
+    def test_add_invalid_value_user(self, category_acl, default_profile):
+        res = category_acl.add_user(user=default_profile.name, track=False)
+        assert len(res['failed']) == 1
+
+        res = category_acl.add_user(group=default_profile.name, track=False)
+        assert len(res['failed']) == 1
+
+    def test_add_invalid_value_host(self, category_acl, default_profile):
+        res = category_acl.add_host(host=default_profile.name, track=False)
+        assert len(res['failed']) == 1
+
+        res = category_acl.add_host(hostgroup=default_profile.name, track=False)
+        assert len(res['failed']) == 1
+
+    def test_add_invalid_value_profile(self, category_acl):
+        res = category_acl.add_profile(certprofile=category_acl.name, track=False)
+        assert len(res['failed']) == 1
+
+    def test_add_staged_user_to_acl(self, category_acl, staged_user):
+        res = category_acl.add_user(user=staged_user.name, track=False)
+        assert len(res['failed']) == 1
-- 
2.6.1

From 405a9ae0f8cf38e1c0e0477191ed29e3f3123c36 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Milan=20Kub=C3=ADk?= <mku...@redhat.com>
Date: Sun, 23 Aug 2015 01:16:04 +0200
Subject: [PATCH 4/6] ipatests: CA ACL - added config templates

https://fedorahosted.org/freeipa/ticket/57
---
 ipatests/test_xmlrpc/data/smime.cfg.tmpl        | 108 ++++++++++++++++++++++++
 ipatests/test_xmlrpc/data/usercert-priv-key.pem |  27 ++++++
 ipatests/test_xmlrpc/data/usercert.conf.tmpl    |  13 +++
 3 files changed, 148 insertions(+)
 create mode 100644 ipatests/test_xmlrpc/data/smime.cfg.tmpl
 create mode 100644 ipatests/test_xmlrpc/data/usercert-priv-key.pem
 create mode 100644 ipatests/test_xmlrpc/data/usercert.conf.tmpl

diff --git a/ipatests/test_xmlrpc/data/smime.cfg.tmpl b/ipatests/test_xmlrpc/data/smime.cfg.tmpl
new file mode 100644
index 0000000000000000000000000000000000000000..3baf03f0bf1400026b45390e43ec079e42544235
--- /dev/null
+++ b/ipatests/test_xmlrpc/data/smime.cfg.tmpl
@@ -0,0 +1,108 @@
+auth.instance_id=raCertAuth
+classId=caEnrollImpl
+desc=Certificate for S-MIME extension
+enable=true
+enableBy=ipara
+input.i1.class_id=certReqInputImpl
+input.i2.class_id=submitterInfoInputImpl
+input.list=i1,i2
+name=SMIME certificate profile
+output.list=o1
+output.o1.class_id=certOutputImpl
+policyset.list=serverCertSet
+policyset.serverCertSet.1.constraint.class_id=subjectNameConstraintImpl
+policyset.serverCertSet.1.constraint.name=Subject Name Constraint
+policyset.serverCertSet.1.constraint.params.accept=true
+policyset.serverCertSet.1.constraint.params.pattern=CN=[^,]+,.+
+policyset.serverCertSet.1.default.class_id=subjectNameDefaultImpl
+policyset.serverCertSet.1.default.name=Subject Name Default
+policyset.serverCertSet.1.default.params.name=CN=$request.req_subject_name.cn$, O={iparealm}
+policyset.serverCertSet.10.constraint.class_id=noConstraintImpl
+policyset.serverCertSet.10.constraint.name=No Constraint
+policyset.serverCertSet.10.default.class_id=subjectKeyIdentifierExtDefaultImpl
+policyset.serverCertSet.10.default.name=Subject Key Identifier Extension Default
+policyset.serverCertSet.10.default.params.critical=false
+policyset.serverCertSet.11.constraint.class_id=noConstraintImpl
+policyset.serverCertSet.11.constraint.name=No Constraint
+policyset.serverCertSet.11.default.class_id=userExtensionDefaultImpl
+policyset.serverCertSet.11.default.name=User Supplied Extension Default
+policyset.serverCertSet.11.default.params.userExtOID=2.5.29.17
+policyset.serverCertSet.2.constraint.class_id=validityConstraintImpl
+policyset.serverCertSet.2.constraint.name=Validity Constraint
+policyset.serverCertSet.2.constraint.params.notAfterCheck=false
+policyset.serverCertSet.2.constraint.params.notBeforeCheck=false
+policyset.serverCertSet.2.constraint.params.range=740
+policyset.serverCertSet.2.default.class_id=validityDefaultImpl
+policyset.serverCertSet.2.default.name=Validity Default
+policyset.serverCertSet.2.default.params.range=731
+policyset.serverCertSet.2.default.params.startTime=0
+policyset.serverCertSet.3.constraint.class_id=keyConstraintImpl
+policyset.serverCertSet.3.constraint.name=Key Constraint
+policyset.serverCertSet.3.constraint.params.keyParameters=1024,2048,3072,4096
+policyset.serverCertSet.3.constraint.params.keyType=RSA
+policyset.serverCertSet.3.default.class_id=userKeyDefaultImpl
+policyset.serverCertSet.3.default.name=Key Default
+policyset.serverCertSet.4.constraint.class_id=noConstraintImpl
+policyset.serverCertSet.4.constraint.name=No Constraint
+policyset.serverCertSet.4.default.class_id=authorityKeyIdentifierExtDefaultImpl
+policyset.serverCertSet.4.default.name=Authority Key Identifier Default
+policyset.serverCertSet.5.constraint.class_id=noConstraintImpl
+policyset.serverCertSet.5.constraint.name=No Constraint
+policyset.serverCertSet.5.default.class_id=authInfoAccessExtDefaultImpl
+policyset.serverCertSet.5.default.name=AIA Extension Default
+policyset.serverCertSet.5.default.params.authInfoAccessADEnable_0=true
+policyset.serverCertSet.5.default.params.authInfoAccessADLocationType_0=URIName
+policyset.serverCertSet.5.default.params.authInfoAccessADLocation_0=http://ipa-ca.{ipadomain}/ca/ocsp
+policyset.serverCertSet.5.default.params.authInfoAccessADMethod_0=1.3.6.1.5.5.7.48.1
+policyset.serverCertSet.5.default.params.authInfoAccessCritical=false
+policyset.serverCertSet.5.default.params.authInfoAccessNumADs=1
+policyset.serverCertSet.6.constraint.class_id=keyUsageExtConstraintImpl
+policyset.serverCertSet.6.constraint.name=Key Usage Extension Constraint
+policyset.serverCertSet.6.constraint.params.keyUsageCritical=true
+policyset.serverCertSet.6.constraint.params.keyUsageCrlSign=false
+policyset.serverCertSet.6.constraint.params.keyUsageDataEncipherment=true
+policyset.serverCertSet.6.constraint.params.keyUsageDecipherOnly=false
+policyset.serverCertSet.6.constraint.params.keyUsageDigitalSignature=true
+policyset.serverCertSet.6.constraint.params.keyUsageEncipherOnly=false
+policyset.serverCertSet.6.constraint.params.keyUsageKeyAgreement=false
+policyset.serverCertSet.6.constraint.params.keyUsageKeyCertSign=false
+policyset.serverCertSet.6.constraint.params.keyUsageKeyEncipherment=true
+policyset.serverCertSet.6.constraint.params.keyUsageNonRepudiation=true
+policyset.serverCertSet.6.default.class_id=keyUsageExtDefaultImpl
+policyset.serverCertSet.6.default.name=Key Usage Default
+policyset.serverCertSet.6.default.params.keyUsageCritical=true
+policyset.serverCertSet.6.default.params.keyUsageCrlSign=false
+policyset.serverCertSet.6.default.params.keyUsageDataEncipherment=true
+policyset.serverCertSet.6.default.params.keyUsageDecipherOnly=false
+policyset.serverCertSet.6.default.params.keyUsageDigitalSignature=true
+policyset.serverCertSet.6.default.params.keyUsageEncipherOnly=false
+policyset.serverCertSet.6.default.params.keyUsageKeyAgreement=false
+policyset.serverCertSet.6.default.params.keyUsageKeyCertSign=false
+policyset.serverCertSet.6.default.params.keyUsageKeyEncipherment=true
+policyset.serverCertSet.6.default.params.keyUsageNonRepudiation=true
+policyset.serverCertSet.7.constraint.class_id=noConstraintImpl
+policyset.serverCertSet.7.constraint.name=No Constraint
+policyset.serverCertSet.7.default.class_id=extendedKeyUsageExtDefaultImpl
+policyset.serverCertSet.7.default.name=Extended Key Usage Extension Default
+policyset.serverCertSet.7.default.params.exKeyUsageCritical=false
+policyset.serverCertSet.7.default.params.exKeyUsageOIDs=1.3.6.1.5.5.7.3.4
+policyset.serverCertSet.8.constraint.class_id=signingAlgConstraintImpl
+policyset.serverCertSet.8.constraint.name=No Constraint
+policyset.serverCertSet.8.constraint.params.signingAlgsAllowed=SHA1withRSA,SHA256withRSA,SHA512withRSA,MD5withRSA,MD2withRSA,SHA1withDSA,SHA1withEC,SHA256withEC,SHA384withEC,SHA512withEC
+policyset.serverCertSet.8.default.class_id=signingAlgDefaultImpl
+policyset.serverCertSet.8.default.name=Signing Alg
+policyset.serverCertSet.8.default.params.signingAlg=-
+policyset.serverCertSet.9.constraint.class_id=noConstraintImpl
+policyset.serverCertSet.9.constraint.name=No Constraint
+policyset.serverCertSet.9.default.class_id=crlDistributionPointsExtDefaultImpl
+policyset.serverCertSet.9.default.name=CRL Distribution Points Extension Default
+policyset.serverCertSet.9.default.params.crlDistPointsCritical=false
+policyset.serverCertSet.9.default.params.crlDistPointsEnable_0=true
+policyset.serverCertSet.9.default.params.crlDistPointsIssuerName_0=CN=Certificate Authority,o=ipaca
+policyset.serverCertSet.9.default.params.crlDistPointsIssuerType_0=DirectoryName
+policyset.serverCertSet.9.default.params.crlDistPointsNum=1
+policyset.serverCertSet.9.default.params.crlDistPointsPointName_0=http://ipa-ca.{ipadomain}/ipa/crl/MasterCRL.bin
+policyset.serverCertSet.9.default.params.crlDistPointsPointType_0=URIName
+policyset.serverCertSet.9.default.params.crlDistPointsReasons_0=
+policyset.serverCertSet.list=1,2,3,4,5,6,7,8,9,10,11
+visible=false
diff --git a/ipatests/test_xmlrpc/data/usercert-priv-key.pem b/ipatests/test_xmlrpc/data/usercert-priv-key.pem
new file mode 100644
index 0000000000000000000000000000000000000000..af5ea0b1829ba5401aa6a53a25b5768a37b6e8af
--- /dev/null
+++ b/ipatests/test_xmlrpc/data/usercert-priv-key.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAsYQl2pLroHyuZWe6vSd6GsxPRxhGlAT0K87S1vMRr5cfU0Eu
+SZx0jsS4jXfAKqW/BhsVHsGA75iVFhbUY7CRGOZ9b9z1kUyajkd0TiJWxLvSDUma
+muwi/gsRDvCr+ro0zk/v38sddXXrhMKhJBj67P3PesEqobQK59GjFk+tAYn+DROP
+bo9w6bkJuhNqEaWmmYmTQz8EdBLIwGb/9FAD8iDuEFd70Mvdnn8DZ9zi5fJnpTUG
+fT6B/mTgTY/37i7scaH8/3+/dHFk5bTjQHIsj0On9z8/ehs8llgSMawMlIuOB1q6
+i7LuVkoD/zz3d7k8ytiYqOd8wfBIEEIdfULQaQIDAQABAoIBACrnpb6OhCTl/cDE
+sX3GbNzNRNwKIgTkrZ9o/cy2MzAddpTIzEc+aW2YXoLSzr+AEAuJwDEO0/sVBfOw
+0OTHaEp8axT+ctwLh8+btaCs7Avg2YQcpiGLsWl1g0n5IZgYKWs0JuYQUa5yMdqE
+sC3pW7ysG9mvln4+5ePh52kdGNOl/4MNaEbQ+OR0V8mmb3wPPPeUIHFKKzLJj7po
+n2nxdhebX8W/BGUkcV7zx89Bpufnypc8e0nn9cznbGsq/U1LUTCVJ2SzCJ2qDwUG
++0ZM1dprfp3/J3yFxKEBlZRMS//VQ5/DIFCcxFPzFizRonkgrzSF1kapNSsQvuzE
+Wax+rYECgYEA3SZthkGW2lvHjI1mMLQY94+7IB9ixdUB4lUvYmwuoUf7k/fJZ4Nq
+t4BycD9Ttj/Q19akZdXfuJgnVtDV1OgExTuJqV4gZ1jz5ewvP1ILfjcjeMLFfoez
+u0JkkeCqrBVWeCc0Ax5lWtm8JsVGV4Nd5gVLI/nmLaMh9InbClQRGC8CgYEAzX10
+NEqPFvvjEaKe2q7EIkHm4sL6TB30ajvC1Zp50jonj1i0L6Un5WqB3RCX0HAbkuz2
+umi9bxMFRfRVJsTcmhb2UrIDEolYkmIm/ji+JS0tvk7jKwl2fAv2waTBwpBA7K3g
+YUHoY6L2r7eCh/2dZvp7LiVyHrIatMsiL9msYucCgYB5MHfQnNzYKHeAFHStt+P+
+tisrfUeZdhMkPt5Kp1IeW94Hxj/+k8vFZ4RO8sUjGHGP9jX9AGkrNWZJcwPbOpJy
+qx/TSpujRuHRW87AemuF7R1pLgMgRak+szF9p4qf5smN6p3cH6oXUT6EWJMlnf20
+8a2tt2JmHAGdinYYgN0lTQKBgQCZJXWUfzjTTVj2zLcNjhCY43q658t5LR36ip1z
+apR+DF9tYxOvKqxoO4+bfQFYFCVIxBhB50u/W3KjpyxLH461vIVKLmdBymDbgBFF
+iG6V8GzWF58QdRX770KxISRS6AWrHw9KDL+wekTVwrOivG4x0F47jybVH7HtqjLJ
+bLYgYwKBgQCmLVTDie+GB2nUVe+IUOBsLfyQXfbQT1ksgX5l7M+W1tOKq1h5R/SS
+YDhasCpNIuK/yMTIXxKP1B7+Kd0I4Ib7w7Ri4kHdai+Wlf+sCjTytXt8YXUQo5mx
+fEadGM6UR/95ug9S4VbeqZdrPcjnOa4RcdzDUsxrqYRMUIlwWNCy8Q==
+-----END RSA PRIVATE KEY-----
diff --git a/ipatests/test_xmlrpc/data/usercert.conf.tmpl b/ipatests/test_xmlrpc/data/usercert.conf.tmpl
new file mode 100644
index 0000000000000000000000000000000000000000..22804be851c2180d14d299b3bb150077d1a6458e
--- /dev/null
+++ b/ipatests/test_xmlrpc/data/usercert.conf.tmpl
@@ -0,0 +1,13 @@
+[ req ]
+prompt = no
+encrypt_key = no
+
+distinguished_name = dn
+req_extensions = exts
+
+[ dn ]
+commonName = "{username}"
+{ipacertbase}
+
+[ exts ]
+subjectAltName=email:{username}@{ipadomain}
-- 
2.6.2

From f40a415e9765f3ea22eaf312c9462cc94d0e1a62 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Milan=20Kub=C3=ADk?= <mku...@redhat.com>
Date: Tue, 22 Sep 2015 15:21:33 +0200
Subject: [PATCH 5/6] ipatests: added unlock_principal_password and
 change_principal

The unlock_principal_password unlocks the (new) user by running
ldappasswd as the user.

change_principal is an context manager that changes identity
for the supplied api object by disconnecting and reconnecting
the rpcclient in and outside of requested kerberos context.
This context manager allows to run tests that cannot be
executed as an admin user which can for example override
an CA ACL.

https://fedorahosted.org/freeipa/ticket/57
---
 ipatests/util.py | 40 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 40 insertions(+)

diff --git a/ipatests/util.py b/ipatests/util.py
index c6566c930838c444425fb654c3e807c20b1c7530..44165093e4ed8f87f575a0c5b261e337ac569432 100644
--- a/ipatests/util.py
+++ b/ipatests/util.py
@@ -27,6 +27,8 @@ from os import path
 import tempfile
 import shutil
 import re
+import uuid
+from contextlib import contextmanager
 
 import six
 import ldap
@@ -34,9 +36,12 @@ import ldap.sasl
 import ldap.modlist
 
 import ipalib
+from ipalib import api
 from ipalib.plugable import Plugin
 from ipalib.request import context
 from ipapython.dn import DN
+from ipapython.ipautil import private_ccache, kinit_password, run
+from ipaplatform.paths import paths
 
 if six.PY3:
     unicode = str
@@ -666,3 +671,38 @@ def prepare_config(template, values):
         config.write(template.format(**values))
 
     return config.name
+
+
+def unlock_principal_password(user, oldpw, newpw):
+    userdn = "uid={},{},{}".format(
+        user, api.env.container_user, api.env.basedn)
+
+    args = [paths.LDAPPASSWD, '-D', userdn, '-w', oldpw, '-a', oldpw,
+            '-s', newpw, '-x']
+    return run(args)
+
+
+@contextmanager
+def change_principal(user, password, client=None, path=None):
+
+    if path:
+        ccache_name = path
+    else:
+        ccache_name = os.path.join('/tmp', str(uuid.uuid4()))
+
+    if client is None:
+        client = api
+
+
+    client.Backend.rpcclient.disconnect()
+
+    with private_ccache(ccache_name):
+        kinit_password(user, password, ccache_name)
+        client.Backend.rpcclient.connect()
+
+        try:
+            yield
+        finally:
+            client.Backend.rpcclient.disconnect()
+
+    client.Backend.rpcclient.connect()
-- 
2.6.1

From 9c6e7df0be98b837b141862d87bf5fe609f8a373 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Milan=20Kub=C3=ADk?= <mku...@redhat.com>
Date: Tue, 22 Sep 2015 15:22:25 +0200
Subject: [PATCH] ipatests: CA ACL and cert profile functional test

https://fedorahosted.org/freeipa/ticket/57
---
 ipatests/test_xmlrpc/data/smime-mod.cfg.tmpl       | 108 ++++++++++
 .../test_xmlrpc/test_caacl_profile_enforcement.py  | 236 +++++++++++++++++++++
 2 files changed, 344 insertions(+)
 create mode 100644 ipatests/test_xmlrpc/data/smime-mod.cfg.tmpl
 create mode 100644 ipatests/test_xmlrpc/test_caacl_profile_enforcement.py

diff --git a/ipatests/test_xmlrpc/data/smime-mod.cfg.tmpl b/ipatests/test_xmlrpc/data/smime-mod.cfg.tmpl
new file mode 100644
index 0000000000000000000000000000000000000000..da94c25e574af6cfe7230073195a541eb7f04fd8
--- /dev/null
+++ b/ipatests/test_xmlrpc/data/smime-mod.cfg.tmpl
@@ -0,0 +1,108 @@
+auth.instance_id=raCertAuth
+classId=caEnrollImpl
+desc=Certificate for S-MIME extension
+enable=true
+enableBy=ipara
+input.i1.class_id=certReqInputImpl
+input.i2.class_id=submitterInfoInputImpl
+input.list=i1,i2
+name=SMIME certificate profile
+output.list=o1
+output.o1.class_id=certOutputImpl
+policyset.list=serverCertSet
+policyset.serverCertSet.1.constraint.class_id=subjectNameConstraintImpl
+policyset.serverCertSet.1.constraint.name=Subject Name Constraint
+policyset.serverCertSet.1.constraint.params.accept=true
+policyset.serverCertSet.1.constraint.params.pattern=CN=refuse sign
+policyset.serverCertSet.1.default.class_id=subjectNameDefaultImpl
+policyset.serverCertSet.1.default.name=Subject Name Default
+policyset.serverCertSet.1.default.params.name=CN=$request.req_subject_name.cn$, O={iparealm}
+policyset.serverCertSet.2.constraint.class_id=validityConstraintImpl
+policyset.serverCertSet.2.constraint.name=Validity Constraint
+policyset.serverCertSet.2.constraint.params.notAfterCheck=false
+policyset.serverCertSet.2.constraint.params.notBeforeCheck=false
+policyset.serverCertSet.2.constraint.params.range=740
+policyset.serverCertSet.2.default.class_id=validityDefaultImpl
+policyset.serverCertSet.2.default.name=Validity Default
+policyset.serverCertSet.2.default.params.range=731
+policyset.serverCertSet.2.default.params.startTime=0
+policyset.serverCertSet.3.constraint.class_id=keyConstraintImpl
+policyset.serverCertSet.3.constraint.name=Key Constraint
+policyset.serverCertSet.3.constraint.params.keyParameters=1024,2048,3072,4096
+policyset.serverCertSet.3.constraint.params.keyType=RSA
+policyset.serverCertSet.3.default.class_id=userKeyDefaultImpl
+policyset.serverCertSet.3.default.name=Key Default
+policyset.serverCertSet.4.constraint.class_id=noConstraintImpl
+policyset.serverCertSet.4.constraint.name=No Constraint
+policyset.serverCertSet.4.default.class_id=authorityKeyIdentifierExtDefaultImpl
+policyset.serverCertSet.4.default.name=Authority Key Identifier Default
+policyset.serverCertSet.5.constraint.class_id=noConstraintImpl
+policyset.serverCertSet.5.constraint.name=No Constraint
+policyset.serverCertSet.5.default.class_id=authInfoAccessExtDefaultImpl
+policyset.serverCertSet.5.default.name=AIA Extension Default
+policyset.serverCertSet.5.default.params.authInfoAccessADEnable_0=true
+policyset.serverCertSet.5.default.params.authInfoAccessADLocationType_0=URIName
+policyset.serverCertSet.5.default.params.authInfoAccessADLocation_0=http://ipa-ca.{ipadomain}/ca/ocsp
+policyset.serverCertSet.5.default.params.authInfoAccessADMethod_0=1.3.6.1.5.5.7.48.1
+policyset.serverCertSet.5.default.params.authInfoAccessCritical=false
+policyset.serverCertSet.5.default.params.authInfoAccessNumADs=1
+policyset.serverCertSet.6.constraint.class_id=keyUsageExtConstraintImpl
+policyset.serverCertSet.6.constraint.name=Key Usage Extension Constraint
+policyset.serverCertSet.6.constraint.params.keyUsageCritical=true
+policyset.serverCertSet.6.constraint.params.keyUsageCrlSign=false
+policyset.serverCertSet.6.constraint.params.keyUsageDataEncipherment=true
+policyset.serverCertSet.6.constraint.params.keyUsageDecipherOnly=false
+policyset.serverCertSet.6.constraint.params.keyUsageDigitalSignature=true
+policyset.serverCertSet.6.constraint.params.keyUsageEncipherOnly=false
+policyset.serverCertSet.6.constraint.params.keyUsageKeyAgreement=false
+policyset.serverCertSet.6.constraint.params.keyUsageKeyCertSign=false
+policyset.serverCertSet.6.constraint.params.keyUsageKeyEncipherment=true
+policyset.serverCertSet.6.constraint.params.keyUsageNonRepudiation=true
+policyset.serverCertSet.6.default.class_id=keyUsageExtDefaultImpl
+policyset.serverCertSet.6.default.name=Key Usage Default
+policyset.serverCertSet.6.default.params.keyUsageCritical=true
+policyset.serverCertSet.6.default.params.keyUsageCrlSign=false
+policyset.serverCertSet.6.default.params.keyUsageDataEncipherment=true
+policyset.serverCertSet.6.default.params.keyUsageDecipherOnly=false
+policyset.serverCertSet.6.default.params.keyUsageDigitalSignature=true
+policyset.serverCertSet.6.default.params.keyUsageEncipherOnly=false
+policyset.serverCertSet.6.default.params.keyUsageKeyAgreement=false
+policyset.serverCertSet.6.default.params.keyUsageKeyCertSign=false
+policyset.serverCertSet.6.default.params.keyUsageKeyEncipherment=true
+policyset.serverCertSet.6.default.params.keyUsageNonRepudiation=true
+policyset.serverCertSet.7.constraint.class_id=noConstraintImpl
+policyset.serverCertSet.7.constraint.name=No Constraint
+policyset.serverCertSet.7.default.class_id=extendedKeyUsageExtDefaultImpl
+policyset.serverCertSet.7.default.name=Extended Key Usage Extension Default
+policyset.serverCertSet.7.default.params.exKeyUsageCritical=false
+policyset.serverCertSet.7.default.params.exKeyUsageOIDs=1.3.6.1.5.5.7.3.4
+policyset.serverCertSet.8.constraint.class_id=signingAlgConstraintImpl
+policyset.serverCertSet.8.constraint.name=No Constraint
+policyset.serverCertSet.8.constraint.params.signingAlgsAllowed=SHA1withRSA,SHA256withRSA,SHA512withRSA,MD5withRSA,MD2withRSA,SHA1withDSA,SHA1withEC,SHA256withEC,SHA384withEC,SHA512withEC
+policyset.serverCertSet.8.default.class_id=signingAlgDefaultImpl
+policyset.serverCertSet.8.default.name=Signing Alg
+policyset.serverCertSet.8.default.params.signingAlg=-
+policyset.serverCertSet.9.constraint.class_id=noConstraintImpl
+policyset.serverCertSet.9.constraint.name=No Constraint
+policyset.serverCertSet.9.default.class_id=crlDistributionPointsExtDefaultImpl
+policyset.serverCertSet.9.default.name=CRL Distribution Points Extension Default
+policyset.serverCertSet.9.default.params.crlDistPointsCritical=false
+policyset.serverCertSet.9.default.params.crlDistPointsEnable_0=true
+policyset.serverCertSet.9.default.params.crlDistPointsIssuerName_0=CN=Certificate Authority,o=ipaca
+policyset.serverCertSet.9.default.params.crlDistPointsIssuerType_0=DirectoryName
+policyset.serverCertSet.9.default.params.crlDistPointsNum=1
+policyset.serverCertSet.9.default.params.crlDistPointsPointName_0=http://ipa-ca.{ipadomain}/ipa/crl/MasterCRL.bin
+policyset.serverCertSet.9.default.params.crlDistPointsPointType_0=URIName
+policyset.serverCertSet.9.default.params.crlDistPointsReasons_0=
+policyset.serverCertSet.10.constraint.class_id=noConstraintImpl
+policyset.serverCertSet.10.constraint.name=No Constraint
+policyset.serverCertSet.10.default.class_id=subjectKeyIdentifierExtDefaultImpl
+policyset.serverCertSet.10.default.name=Subject Key Identifier Extension Default
+policyset.serverCertSet.10.default.params.critical=false
+policyset.serverCertSet.11.constraint.class_id=noConstraintImpl
+policyset.serverCertSet.11.constraint.name=No Constraint
+policyset.serverCertSet.11.default.class_id=userExtensionDefaultImpl
+policyset.serverCertSet.11.default.name=User Supplied Extension Default
+policyset.serverCertSet.11.default.params.userExtOID=2.5.29.17
+policyset.serverCertSet.list=1,2,3,4,5,6,7,8,9,10,11
+visible=false
diff --git a/ipatests/test_xmlrpc/test_caacl_profile_enforcement.py b/ipatests/test_xmlrpc/test_caacl_profile_enforcement.py
new file mode 100644
index 0000000000000000000000000000000000000000..e9472b07813bcac276397d7a7b61746b6b5a78b6
--- /dev/null
+++ b/ipatests/test_xmlrpc/test_caacl_profile_enforcement.py
@@ -0,0 +1,236 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2015  FreeIPA Contributors see COPYING for license
+#
+
+import os
+import pytest
+import tempfile
+
+from ipalib import api, errors
+from ipatests.util import (
+    prepare_config, unlock_principal_password, change_principal)
+from ipatests.test_xmlrpc.xmlrpc_test import XMLRPC_test
+from ipatests.test_xmlrpc.test_certprofile_plugin import CertprofileTracker
+from ipatests.test_xmlrpc.test_caacl_plugin import CAACLTracker
+
+from ipapython.ipautil import run
+
+BASE_DIR = os.path.dirname(__file__)
+
+SMIME_PROFILE_TEMPLATE = os.path.join(BASE_DIR, 'data/smime.cfg.tmpl')
+SMIME_MOD_CONSTR_PROFILE_TEMPLATE = os.path.join(BASE_DIR, 'data/smime-mod.cfg.tmpl')
+CERT_OPENSSL_CONFIG_TEMPLATE = os.path.join(BASE_DIR, 'data/usercert.conf.tmpl')
+CERT_RSA_PRIVATE_KEY_PATH = os.path.join(BASE_DIR, 'data/usercert-priv-key.pem')
+
+
+CERT_SUBJECT_BASE = (
+    api.Command.config_show()
+    ['result']['ipacertificatesubjectbase'][0]
+)
+
+SMIME_USER_INIT_PW = u'Change123'
+SMIME_USER_PW = u'Secret123'
+
+
+def generate_user_csr(username, domain=None):
+    csr_values = dict(
+        ipacertbase=CERT_SUBJECT_BASE,
+        ipadomain=domain if domain else api.env.domain,
+        username=username)
+
+    with tempfile.NamedTemporaryFile(mode='w') as csr_file:
+        run(['openssl', 'req', '-new', '-key', CERT_RSA_PRIVATE_KEY_PATH,
+             '-out', csr_file.name,
+             '-config', prepare_config(
+                 CERT_OPENSSL_CONFIG_TEMPLATE, csr_values)])
+
+        with open(csr_file.name, 'r') as f:
+            csr = unicode(f.read())
+
+    return csr
+
+
+@pytest.fixture(scope='class')
+def smime_profile(request):
+    profile_path = prepare_config(
+            SMIME_PROFILE_TEMPLATE,
+            dict(ipadomain=api.env.domain, iparealm=api.env.realm))
+
+    tracker = CertprofileTracker(u'smime', store=True,
+                                 desc=u"S/MIME certificate profile",
+                                 profile=profile_path)
+
+    return tracker.make_fixture(request)
+
+
+@pytest.fixture(scope='class')
+def smime_acl(request):
+    tracker = CAACLTracker(u'smime_acl')
+
+    return tracker.make_fixture(request)
+
+
+# TODO: rewrite these into Tracker instances
+# UserTracker has problems while setting passwords.
+# Until fixed, will use this fixture.
+@pytest.fixture(scope='class')
+def smime_user(request):
+    username = u'alice'
+    api.Command.user_add(uid=username, givenname=u'Alice', sn=u'SMIME',
+                         userpassword=SMIME_USER_INIT_PW)
+
+    unlock_principal_password(username, SMIME_USER_INIT_PW, SMIME_USER_PW)
+
+    def fin():
+        api.Command.user_del(username)
+    request.addfinalizer(fin)
+
+    return username
+
+
+@pytest.fixture(scope='class')
+def smime_group(request):
+    api.Command.group_add(u'smime_users')
+
+    def fin():
+        api.Command.group_del(u'smime_users')
+    request.addfinalizer(fin)
+
+    return u'smime_users'
+
+
+class TestCertSignMIME(XMLRPC_test):
+
+    def test_cert_import(self, smime_profile):
+        smime_profile.ensure_exists()
+
+    def test_create_acl(self, smime_acl):
+        smime_acl.ensure_exists()
+
+    def test_add_profile_to_acl(self, smime_acl, smime_profile):
+        smime_acl.add_profile(certprofile=smime_profile.name)
+
+    # rewrite to trackers, prepare elsewhere
+    def test_add_user_to_group(self, smime_group, smime_user):
+        api.Command.group_add_member(smime_group, user=smime_user)
+
+    def test_add_group_to_acl(self, smime_group, smime_acl):
+        smime_acl.add_user(group=smime_group)
+
+    def test_sign_smime_csr(self, smime_profile, smime_user):
+        csr = generate_user_csr(smime_user)
+        with change_principal(smime_user, SMIME_USER_PW):
+            api.Command.cert_request(csr, principal=smime_user,
+                                     profile_id=smime_profile.name)
+
+
+class TestSignWithDisabledACL(XMLRPC_test):
+
+    def test_import_profile_and_acl(self, smime_profile, smime_acl):
+        smime_profile.ensure_exists()
+        smime_acl.ensure_missing()
+        smime_acl.ensure_exists()
+
+    def test_add_profile_to_acl(self, smime_acl, smime_profile):
+        smime_acl.add_profile(certprofile=smime_profile.name)
+
+    # rewrite to trackers, prepare elsewhere
+    def test_add_user_to_group(self, smime_group, smime_user):
+        api.Command.group_add_member(smime_group, user=smime_user)
+
+    def test_add_group_to_acl(self, smime_group, smime_acl):
+        smime_acl.add_user(group=smime_group)
+
+    def test_disable_acl(self, smime_acl):
+        smime_acl.disable()
+
+    def test_signing_with_disabled_acl(self, smime_acl, smime_profile,
+                                       smime_user):
+        csr = generate_user_csr(smime_user)
+        with change_principal(smime_user, SMIME_USER_PW):
+            with pytest.raises(errors.ACIError):
+                api.Command.cert_request(
+                    csr, profile_id=smime_profile.name,
+                    principal=smime_user)
+
+    def test_admin_overrides_disabled_acl(self, smime_acl, smime_profile,
+                                          smime_user):
+        csr = generate_user_csr(smime_user)
+        api.Command.cert_request(
+            csr, profile_id=smime_profile.name,
+            principal=smime_user)
+
+
+class TestSignWithoutGroupMembership(XMLRPC_test):
+
+    def test_import_profile_and_acl(self, smime_profile, smime_acl):
+        smime_profile.ensure_exists()
+        smime_acl.ensure_missing()
+        smime_acl.ensure_exists()
+
+    def test_add_profile_to_acl(self, smime_acl, smime_profile):
+        smime_acl.add_profile(certprofile=smime_profile.name)
+
+    def test_add_group_to_acl(self, smime_group, smime_acl, smime_user):
+        # smime user should not be a member of this group
+        #
+        # adding smime_user fixture to ensure it exists
+        smime_acl.add_user(group=smime_group)
+
+    def test_signing_with_non_member_principal(self, smime_acl, smime_profile,
+                                               smime_user):
+
+        csr = generate_user_csr(smime_user)
+        with change_principal(smime_user, SMIME_USER_PW):
+            with pytest.raises(errors.ACIError):
+                api.Command.cert_request(
+                    csr,
+                    profile_id=smime_profile.name,
+                    principal=smime_user)
+
+    def test_admin_overrides_group_membership(self, smime_acl, smime_profile,
+                                              smime_user):
+        csr = generate_user_csr(smime_user)
+        api.Command.cert_request(
+            csr, profile_id=smime_profile.name,
+            principal=smime_user)
+
+
+class TestSignWithChangedProfile(XMLRPC_test):
+    """ Test to verify that the updated profile is used.
+
+    The profile change requires different CN in CSR
+    than the one configured. This leads to rejection
+    based on not meeting the profile constraints.
+    """
+
+    def test_prepare_env(self, smime_profile, smime_acl):
+        smime_profile.ensure_exists()
+        smime_acl.ensure_exists()
+
+        smime_acl.add_profile(certprofile=smime_profile.name)
+
+    def test_prepare_user_and_group(self, smime_group, smime_user, smime_acl):
+        api.Command.group_add_member(smime_group, user=smime_user)
+        smime_acl.add_user(group=smime_group)
+
+    def test_modify_smime_profile(self, smime_profile):
+        updated_profile_path = prepare_config(SMIME_MOD_CONSTR_PROFILE_TEMPLATE,
+                                              dict(
+                                                   ipadomain=api.env.domain,
+                                                   iparealm=api.env.realm))
+
+        with open(updated_profile_path) as f:
+            updated_profile = unicode(f.read())
+
+        updates = {u'file': updated_profile}
+        update_smime_profile = smime_profile.make_update_command(updates)
+        update_smime_profile()
+
+    def test_sign_smime_csr(self, smime_profile, smime_user):
+        csr = generate_user_csr(smime_user)
+        with change_principal(smime_user, SMIME_USER_PW):
+            with pytest.raises(errors.CertificateOperationError):
+                api.Command.cert_request(csr, principal=smime_user,
+                                         profile_id=smime_profile.name)
-- 
2.6.2

From 3474dcfe9f94e9fce7570c25d5610922fedebe5c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Milan=20Kub=C3=ADk?= <mku...@redhat.com>
Date: Fri, 17 Jul 2015 14:42:23 +0200
Subject: [PATCH 1/6] ipatests: add fuzzy instances for CA ACL DN and RDN

https://fedorahosted.org/freeipa/ticket/57
---
 ipatests/test_xmlrpc/xmlrpc_test.py | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/ipatests/test_xmlrpc/xmlrpc_test.py b/ipatests/test_xmlrpc/xmlrpc_test.py
index 808abae1944f87ac19d419c716658b07942556f0..7019d3b1f1fb92e27297526b63d090eb01124bd4 100644
--- a/ipatests/test_xmlrpc/xmlrpc_test.py
+++ b/ipatests/test_xmlrpc/xmlrpc_test.py
@@ -75,6 +75,14 @@ fuzzy_sudocmddn = Fuzzy(
     '(?i)ipauniqueid=%s,cn=sudocmds,cn=sudo,%s' % (uuid_re, api.env.basedn)
 )
 
+# Matches caacl dn
+fuzzy_caacldn = Fuzzy(
+    '(?i)ipauniqueid=%s,cn=caacls,cn=ca,%s' % (uuid_re, api.env.basedn)
+)
+
+# Matches fuzzy ipaUniqueID DN group (RDN)
+fuzzy_ipauniqueid = Fuzzy('(?i)ipauniqueid=%s' % uuid_re)
+
 # Matches a hash signature, not enforcing length
 fuzzy_hash = Fuzzy('^([a-f0-9][a-f0-9]:)+[a-f0-9][a-f0-9]$', type=basestring)
 
-- 
2.6.1

From d962272948754805ad6c9de087046dd615a1ecd0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Milan=20Kub=C3=ADk?= <mku...@redhat.com>
Date: Tue, 30 Jun 2015 17:00:18 +0200
Subject: [PATCH 2/6] ipatests: Add initial CAACLTracker implementation

The patch implements the tracker for CA ACL feature.
The basic CRUD checkers has been implemented. The methods
for adding and removing the association of the resources
with the ACL do not have the check methods. These will be provided
as a separate test suite.

https://fedorahosted.org/freeipa/ticket/57
---
 ipatests/test_xmlrpc/objectclasses.py     |   5 +
 ipatests/test_xmlrpc/test_caacl_plugin.py | 378 ++++++++++++++++++++++++++++++
 2 files changed, 383 insertions(+)
 create mode 100644 ipatests/test_xmlrpc/test_caacl_plugin.py

diff --git a/ipatests/test_xmlrpc/objectclasses.py b/ipatests/test_xmlrpc/objectclasses.py
index 1cd77c7f885fe408d0d9d48fc6d8284900c91b7f..134a08803f3abca1124c4d26274d9e3fc981b941 100644
--- a/ipatests/test_xmlrpc/objectclasses.py
+++ b/ipatests/test_xmlrpc/objectclasses.py
@@ -217,3 +217,8 @@ certprofile = [
     u'top',
     u'ipacertprofile',
 ]
+
+caacl = [
+    u'ipaassociation',
+    u'ipacaacl'
+]
diff --git a/ipatests/test_xmlrpc/test_caacl_plugin.py b/ipatests/test_xmlrpc/test_caacl_plugin.py
new file mode 100644
index 0000000000000000000000000000000000000000..6cf835b229f70797e32bcfd2309cfa7be5732f51
--- /dev/null
+++ b/ipatests/test_xmlrpc/test_caacl_plugin.py
@@ -0,0 +1,378 @@
+#
+# Copyright (C) 2015  FreeIPA Contributors see COPYING for license
+#
+
+"""
+Test the `ipalib.plugins.caacl` module.
+"""
+
+import os
+
+import pytest
+
+from ipapython import ipautil
+from ipalib import errors, x509
+from ipapython.dn import DN
+from ipatests.test_xmlrpc.ldaptracker import Tracker
+from ipatests.test_xmlrpc.xmlrpc_test import (XMLRPC_test, fuzzy_caacldn,
+                                              fuzzy_uuid, fuzzy_ipauniqueid,
+                                              raises_exact)
+from ipatests.test_xmlrpc import objectclasses
+from ipatests.util import assert_deepequal
+
+
+class CAACLTracker(Tracker):
+    """Tracker class for CA ACL LDAP object.
+
+    The class implements methods required by the base class
+    to help with basic CRUD operations.
+
+    Methods for adding and deleting actual member entries into an ACL
+    do not have check methods as these would make the class
+    unnecessarily complicated. The checks are implemented as
+    a standalone test suite. However, this makes the test crucial
+    in debugging more complicated test cases. Since the add/remove
+    operation won't be checked right away by the tracker, a problem
+    in this operation may propagate into more complicated test case.
+
+    It is possible to pass a list of member uids to these methods.
+
+    The test uses instances of Fuzzy class to compare results as they
+    are in the UUID format. The dn and rdn properties were modified
+    to reflect this as well.
+    """
+
+    member_keys = {
+        u'memberuser_user', u'memberuser_group',
+        u'memberhost_host', u'memberhost_hostgroup',
+        u'memberservice_service',
+        u'ipamembercertprofile_certprofile'}
+    category_keys = {
+        u'ipacacategory', u'ipacertprofilecategory', u'usercategory',
+        u'hostcategory', u'servicecategory'}
+    retrieve_keys = {
+        u'dn', u'cn', u'description', u'ipaenabledflag',
+        u'ipamemberca', u'ipamembercertprofile', u'memberuser',
+        u'memberhost', u'memberservice'} | member_keys | category_keys
+    retrieve_all_keys = retrieve_keys | {u'objectclass', u'ipauniqueid'}
+    create_keys = {u'dn', u'cn', u'description', u'ipacertprofilecategory',
+                   u'usercategory', u'hostcategory', u'ipacacategory',
+                   u'servicecategory', u'ipaenabledflag', u'objectclass',
+                   u'ipauniqueid'}
+    update_keys = create_keys - {u'dn'}
+
+    def __init__(self, name, ipacertprofile_category=None, user_category=None,
+                 service_category=None, host_category=None, description=None,
+                 default_version=None):
+        super(CAACLTracker, self).__init__(default_version=default_version)
+
+        self._name = name
+        self.description = description
+        self._categories = dict(
+            ipacertprofilecategory=ipacertprofile_category,
+            usercategory=user_category,
+            servicecategory=service_category,
+            hostcategory=host_category)
+
+        self.dn = fuzzy_caacldn
+
+    @property
+    def name(self):
+        return self._name
+
+    @property
+    def rdn(self):
+        return fuzzy_ipauniqueid
+
+    @property
+    def categories(self):
+        """To be used in track_create"""
+        return {cat: v for cat, v in self._categories.items() if v}
+
+    @property
+    def create_categories(self):
+        """ Return the categories set on create.
+            Unused categories are left out.
+        """
+        return {cat: [v] for cat, v in self.categories.items() if v}
+
+    def make_create_command(self, force=True):
+        return self.make_command(u'caacl_add', self.name,
+                                 description=self.description,
+                                 **self.categories)
+
+    def check_create(self, result):
+        assert_deepequal(dict(
+            value=self.name,
+            summary=u'Added CA ACL "{}"'.format(self.name),
+            result=dict(self.filter_attrs(self.create_keys))
+        ), result)
+
+    def track_create(self):
+        self.attrs = dict(
+            dn=self.dn,
+            ipauniqueid=[fuzzy_uuid],
+            cn=[self.name],
+            objectclass=objectclasses.caacl,
+            ipaenabledflag=[u'TRUE'])
+
+        self.attrs.update(self.create_categories)
+        if self.description:
+            self.attrs.update({u'description', [self.description]})
+
+        self.exists = True
+
+    def make_delete_command(self):
+        return self.make_command('caacl_del', self.name)
+
+    def check_delete(self, result):
+        assert_deepequal(dict(
+            value=[self.name],
+            summary=u'Deleted CA ACL "{}"'.format(self.name),
+            result=dict(failed=[])
+        ), result)
+
+    def make_retrieve_command(self, all=False, raw=False):
+        return self.make_command('caacl_show', self.name, all=all, raw=raw)
+
+    def check_retrieve(self, result, all=False, raw=False):
+        if all:
+            expected = self.filter_attrs(self.retrieve_all_keys)
+        else:
+            expected = self.filter_attrs(self.retrieve_keys)
+
+        assert_deepequal(dict(
+            value=self.name,
+            summary=None,
+            result=expected
+        ), result)
+
+    def make_find_command(self, *args, **kwargs):
+        return self.make_command('caacl_find', *args, **kwargs)
+
+    def check_find(self, result, all=False, raw=False):
+        if all:
+            expected = self.filter_attrs(self.retrieve_all_keys)
+        else:
+            expected = self.filter_attrs(self.retrieve_keys)
+
+        assert_deepequal(dict(
+            count=1,
+            truncated=False,
+            summary=u'1 CA ACL matched',
+            result=[expected]
+        ), result)
+
+    def make_update_command(self, updates):
+        return self.make_command('caacl_mod', self.name, **updates)
+
+    def update(self, updates, expected_updates=None, silent=False):
+        """If removing a category, delete it from tracker as well"""
+        # filter out empty categories and track changes
+
+        filtered_updates = dict()
+        for key, value in updates.items():
+            if key in self.category_keys:
+                if not value:
+                    try:
+                        del self.attrs[key]
+                    except IndexError:
+                        if silent:
+                            pass
+                else:
+                    # if there is a value, prepare the pair for update
+                    filtered_updates.update({key: value})
+            else:
+                filtered_updates.update({key: value})
+
+        if expected_updates is None:
+            expected_updates = {}
+
+        command = self.make_update_command(updates)
+
+        try:
+            result = command()
+        except errors.EmptyModlist:
+            if silent:
+                self.attrs.update(filtered_updates)
+                self.attrs.update(expected_updates)
+                self.check_update(result,
+                                  extra_keys=set(self.update_keys) |
+                                  set(expected_updates.keys()))
+
+    def check_update(self, result, extra_keys=()):
+        assert_deepequal(dict(
+            value=self.name,
+            summary=u'Modified CA ACL "{}"'.format(self.name),
+            result=self.filter_attrs(self.update_keys | set(extra_keys))
+        ), result)
+
+    # Helper methods for caacl subcommands. The check methods will be
+    # implemented in standalone test
+    #
+    # The methods implemented here will be:
+    # caacl_{add,remove}_{host, service, certprofile, user [, subca]}
+
+    def _add_acl_component(self, command_name, keys, track):
+        """ Add a resource into ACL rule and track it.
+
+            command_name - the name in the API
+            keys = {
+                'tracker_attr': {
+                    'api_key': 'value'
+                }
+            }
+
+            e.g.
+
+            keys = {
+                'memberhost_host': {
+                    'host': 'hostname'
+                },
+                'memberhost_hostgroup': {
+                    'hostgroup': 'hostgroup_name'
+                }
+            }
+        """
+
+        if not self.exists:
+            raise errors.NotFound(reason="The tracked entry doesn't exist.")
+
+        command = self.make_command(command_name, self.name)
+        command_options = dict()
+
+        # track
+        for tracker_attr in keys:
+            api_options = keys[tracker_attr]
+            if track:
+                for option in api_options:
+                    try:
+                        if type(option) in (list, tuple):
+                            self.attrs[tracker_attr].extend(api_options[option])
+                        else:
+                            self.attrs[tracker_attr].append(api_options[option])
+                    except KeyError:
+                        if type(option) in (list, tuple):
+                            self.attrs[tracker_attr] = api_options[option]
+                        else:
+                            self.attrs[tracker_attr] = [api_options[option]]
+            # prepare options for the command call
+            command_options.update(api_options)
+
+        return command(**command_options)
+
+    def _remove_acl_component(self, command_name, keys, track):
+        """ Remove a resource from ACL rule and track it.
+
+            command_name - the name in the API
+            keys = {
+                'tracker_attr': {
+                    'api_key': 'value'
+                }
+            }
+
+            e.g.
+
+            keys = {
+                'memberhost_host': {
+                    'host': 'hostname'
+                },
+                'memberhost_hostgroup': {
+                    'hostgroup': 'hostgroup_name'
+                }
+            }
+        """
+        command = self.make_command(command_name, self.name)
+        command_options = dict()
+
+        for tracker_attr in keys:
+            api_options = keys[tracker_attr]
+            if track:
+                for option in api_options:
+                    if type(option) in (list, tuple):
+                        for item in option:
+                            self.attrs[tracker_attr].remove(item)
+                    else:
+                        self.attrs[tracker_attr].remove(api_options[option])
+                    if len(self.attrs[tracker_attr]) == 0:
+                        del self.attrs[tracker_attr]
+            command_options.update(api_options)
+
+        return command(**command_options)
+
+    def add_host(self, host=None, hostgroup=None, track=True):
+        """Associates an host or hostgroup entry with the ACL.
+
+           The command takes an unicode string with the name
+           of the entry (RDN).
+
+           It is the responsibility of a test writer to provide
+           the correct value, object type as the method does not
+           verify whether the entry exists.
+
+           The method can add only one entry of each type
+           in one call.
+        """
+
+        options = {
+            u'memberhost_host': {u'host': host},
+            u'memberhost_hostgroup': {u'hostgroup': hostgroup}}
+
+        return self._add_acl_component(u'caacl_add_host', options, track)
+
+    def remove_host(self, host=None, hostgroup=None, track=True):
+        options = {
+            u'memberhost_host': {u'host': host},
+            u'memberhost_hostgroup': {u'hostgroup': hostgroup}}
+
+        return self._remove_acl_component(u'caacl_remove_host', options, track)
+
+    def add_user(self, user=None, group=None, track=True):
+        options = {
+            u'memberuser_user': {u'user': user},
+            u'memberuser_group': {u'group': group}}
+
+        return self._add_acl_component(u'caacl_add_user', options, track)
+
+    def remove_user(self, user=None, group=None, track=True):
+        options = {
+            u'memberuser_user': {u'user': user},
+            u'memberuser_group': {u'group': group}}
+
+        return self._remove_acl_component(u'caacl_remove_user', options, track)
+
+    def add_service(self, service=None, track=True):
+        options = {
+            u'memberservice_service': {u'service': service}}
+
+        return self._add_acl_component(u'caacl_add_service', options, track)
+
+    def remove_service(self, service=None, track=True):
+        options = {
+            u'memberservice_service': {u'service': service}}
+
+        return self._remove_acl_component(u'caacl_remove_service', options, track)
+
+    def add_profile(self, certprofile=None, track=True):
+        options = {
+            u'ipamembercertprofile_certprofile':
+                {u'certprofile': certprofile}}
+
+        return self._add_acl_component(u'caacl_add_profile', options, track)
+
+    def remove_profile(self, certprofile=None, track=True):
+        options = {
+            u'ipamembercertprofile_certprofile':
+                {u'certprofile': certprofile}}
+
+        return self._remove_acl_component(u'caacl_remove_profile', options, track)
+
+    def enable(self):
+        command = self.make_command(u'caacl_enable', self.name)
+        self.attrs.update({u'ipaenabledflag': [u'TRUE']})
+        command()
+
+    def disable(self):
+        command = self.make_command(u'caacl_disable', self.name)
+        self.attrs.update({u'ipaenabledflag': [u'FALSE']})
+        command()
-- 
2.6.1

From 7f3f746567a69597167fa0c8c8efb6e89d08dfd3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Milan=20Kub=C3=ADk?= <mku...@redhat.com>
Date: Fri, 7 Aug 2015 15:54:18 +0200
Subject: [PATCH 3/6] tests: add test to check the default ACL

Also includes basic ACL manipulation and adding
and removing members to/from the acl.

https://fedorahosted.org/freeipa/ticket/57
---
 ipatests/test_xmlrpc/test_caacl_plugin.py | 135 ++++++++++++++++++++++++++++--
 1 file changed, 128 insertions(+), 7 deletions(-)

diff --git a/ipatests/test_xmlrpc/test_caacl_plugin.py b/ipatests/test_xmlrpc/test_caacl_plugin.py
index 6cf835b229f70797e32bcfd2309cfa7be5732f51..33268d6dde115ce040e55eaae08a7fe1299ad112 100644
--- a/ipatests/test_xmlrpc/test_caacl_plugin.py
+++ b/ipatests/test_xmlrpc/test_caacl_plugin.py
@@ -6,20 +6,20 @@
 Test the `ipalib.plugins.caacl` module.
 """
 
-import os
-
 import pytest
 
-from ipapython import ipautil
-from ipalib import errors, x509
-from ipapython.dn import DN
+from ipalib import errors
 from ipatests.test_xmlrpc.ldaptracker import Tracker
 from ipatests.test_xmlrpc.xmlrpc_test import (XMLRPC_test, fuzzy_caacldn,
-                                              fuzzy_uuid, fuzzy_ipauniqueid,
-                                              raises_exact)
+                                              fuzzy_uuid, fuzzy_ipauniqueid)
+
 from ipatests.test_xmlrpc import objectclasses
 from ipatests.util import assert_deepequal
 
+# reuse the fixture
+from ipatests.test_xmlrpc.test_certprofile_plugin import default_profile
+from ipatests.test_xmlrpc.test_stageuser_plugin import StageUserTracker
+
 
 class CAACLTracker(Tracker):
     """Tracker class for CA ACL LDAP object.
@@ -376,3 +376,124 @@ class CAACLTracker(Tracker):
         command = self.make_command(u'caacl_disable', self.name)
         self.attrs.update({u'ipaenabledflag': [u'FALSE']})
         command()
+
+
+@pytest.fixture(scope='class')
+def default_acl(request):
+    name = u'hosts_services_caIPAserviceCert'
+    tracker = CAACLTracker(name, service_category=u'all', host_category=u'all')
+    tracker.track_create()
+    tracker.attrs.update(
+        {u'ipamembercertprofile_certprofile': [u'caIPAserviceCert']})
+    return tracker
+
+
+@pytest.fixture(scope='class')
+def crud_acl(request):
+    name = u'crud-acl'
+    tracker = CAACLTracker(name)
+
+    return tracker.make_fixture(request)
+
+
+@pytest.fixture(scope='class')
+def category_acl(request):
+    name = u'category_acl'
+    tracker = CAACLTracker(name, ipacertprofile_category=u'all',
+                           user_category=u'all', service_category=u'all',
+                           host_category=u'all')
+
+    return tracker.make_fixture(request)
+
+
+@pytest.fixture(scope='class')
+def staged_user(request):
+    name = u'st-user'
+    tracker = StageUserTracker(name, u'stage', u'test')
+
+    return tracker.make_fixture(request)
+
+
+class TestDefaultACL(XMLRPC_test):
+    def test_default_acl_present(self, default_acl):
+        default_acl.retrieve()
+
+
+class TestCAACLbasicCRUD(XMLRPC_test):
+    def test_create(self, crud_acl):
+        crud_acl.create()
+
+    def test_delete(self, crud_acl):
+        crud_acl.delete()
+
+    def test_disable(self, crud_acl):
+        crud_acl.ensure_exists()
+        crud_acl.disable()
+        crud_acl.retrieve()
+
+    def test_disable_twice(self, crud_acl):
+        crud_acl.disable()
+        crud_acl.retrieve()
+
+    def test_enable(self, crud_acl):
+        crud_acl.enable()
+        crud_acl.retrieve()
+
+    def test_enable_twice(self, crud_acl):
+        crud_acl.enable()
+        crud_acl.retrieve()
+
+    def test_find(self, crud_acl):
+        crud_acl.find()
+
+
+class TestCAACLMembers(XMLRPC_test):
+    def test_category_member_exclusivity(self, category_acl, default_profile):
+        category_acl.create()
+        default_profile.ensure_exists()
+        with pytest.raises(errors.MutuallyExclusiveError):
+            category_acl.add_profile(default_profile.name, track=False)
+
+    def test_mod_delete_category(self, category_acl):
+        updates = dict(
+            hostcategory=None,
+            servicecategory=None,
+            ipacertprofilecategory=None,
+            usercategory=None)
+        category_acl.update(updates)
+
+    def test_add_profile(self, category_acl, default_profile):
+        category_acl.add_profile(certprofile=default_profile.name)
+        category_acl.retrieve()
+
+    def test_remove_profile(self, category_acl, default_profile):
+        category_acl.remove_profile(certprofile=default_profile.name)
+        category_acl.retrieve()
+
+    def test_add_invalid_value_service(self, category_acl, default_profile):
+        res = category_acl.add_service(service=default_profile.name, track=False)
+        assert len(res['failed']) == 1
+
+    # the same for other types
+
+    def test_add_invalid_value_user(self, category_acl, default_profile):
+        res = category_acl.add_user(user=default_profile.name, track=False)
+        assert len(res['failed']) == 1
+
+        res = category_acl.add_user(group=default_profile.name, track=False)
+        assert len(res['failed']) == 1
+
+    def test_add_invalid_value_host(self, category_acl, default_profile):
+        res = category_acl.add_host(host=default_profile.name, track=False)
+        assert len(res['failed']) == 1
+
+        res = category_acl.add_host(hostgroup=default_profile.name, track=False)
+        assert len(res['failed']) == 1
+
+    def test_add_invalid_value_profile(self, category_acl):
+        res = category_acl.add_profile(certprofile=category_acl.name, track=False)
+        assert len(res['failed']) == 1
+
+    def test_add_staged_user_to_acl(self, category_acl, staged_user):
+        res = category_acl.add_user(user=staged_user.name, track=False)
+        assert len(res['failed']) == 1
-- 
2.6.1

From 8d1466e20d42393b8fb9038043a1b3e73b9decac Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Milan=20Kub=C3=ADk?= <mku...@redhat.com>
Date: Sun, 23 Aug 2015 01:16:04 +0200
Subject: [PATCH 4/6] ipatests: CA ACL - added config templates

https://fedorahosted.org/freeipa/ticket/57
---
 ipatests/test_xmlrpc/data/smime.cfg.tmpl        | 108 ++++++++++++++++++++++++
 ipatests/test_xmlrpc/data/usercert-priv-key.pem |  27 ++++++
 ipatests/test_xmlrpc/data/usercert.conf.tmpl    |  13 +++
 3 files changed, 148 insertions(+)
 create mode 100644 ipatests/test_xmlrpc/data/smime.cfg.tmpl
 create mode 100644 ipatests/test_xmlrpc/data/usercert-priv-key.pem
 create mode 100644 ipatests/test_xmlrpc/data/usercert.conf.tmpl

diff --git a/ipatests/test_xmlrpc/data/smime.cfg.tmpl b/ipatests/test_xmlrpc/data/smime.cfg.tmpl
new file mode 100644
index 0000000000000000000000000000000000000000..3baf03f0bf1400026b45390e43ec079e42544235
--- /dev/null
+++ b/ipatests/test_xmlrpc/data/smime.cfg.tmpl
@@ -0,0 +1,108 @@
+auth.instance_id=raCertAuth
+classId=caEnrollImpl
+desc=Certificate for S-MIME extension
+enable=true
+enableBy=ipara
+input.i1.class_id=certReqInputImpl
+input.i2.class_id=submitterInfoInputImpl
+input.list=i1,i2
+name=SMIME certificate profile
+output.list=o1
+output.o1.class_id=certOutputImpl
+policyset.list=serverCertSet
+policyset.serverCertSet.1.constraint.class_id=subjectNameConstraintImpl
+policyset.serverCertSet.1.constraint.name=Subject Name Constraint
+policyset.serverCertSet.1.constraint.params.accept=true
+policyset.serverCertSet.1.constraint.params.pattern=CN=[^,]+,.+
+policyset.serverCertSet.1.default.class_id=subjectNameDefaultImpl
+policyset.serverCertSet.1.default.name=Subject Name Default
+policyset.serverCertSet.1.default.params.name=CN=$request.req_subject_name.cn$, O={iparealm}
+policyset.serverCertSet.10.constraint.class_id=noConstraintImpl
+policyset.serverCertSet.10.constraint.name=No Constraint
+policyset.serverCertSet.10.default.class_id=subjectKeyIdentifierExtDefaultImpl
+policyset.serverCertSet.10.default.name=Subject Key Identifier Extension Default
+policyset.serverCertSet.10.default.params.critical=false
+policyset.serverCertSet.11.constraint.class_id=noConstraintImpl
+policyset.serverCertSet.11.constraint.name=No Constraint
+policyset.serverCertSet.11.default.class_id=userExtensionDefaultImpl
+policyset.serverCertSet.11.default.name=User Supplied Extension Default
+policyset.serverCertSet.11.default.params.userExtOID=2.5.29.17
+policyset.serverCertSet.2.constraint.class_id=validityConstraintImpl
+policyset.serverCertSet.2.constraint.name=Validity Constraint
+policyset.serverCertSet.2.constraint.params.notAfterCheck=false
+policyset.serverCertSet.2.constraint.params.notBeforeCheck=false
+policyset.serverCertSet.2.constraint.params.range=740
+policyset.serverCertSet.2.default.class_id=validityDefaultImpl
+policyset.serverCertSet.2.default.name=Validity Default
+policyset.serverCertSet.2.default.params.range=731
+policyset.serverCertSet.2.default.params.startTime=0
+policyset.serverCertSet.3.constraint.class_id=keyConstraintImpl
+policyset.serverCertSet.3.constraint.name=Key Constraint
+policyset.serverCertSet.3.constraint.params.keyParameters=1024,2048,3072,4096
+policyset.serverCertSet.3.constraint.params.keyType=RSA
+policyset.serverCertSet.3.default.class_id=userKeyDefaultImpl
+policyset.serverCertSet.3.default.name=Key Default
+policyset.serverCertSet.4.constraint.class_id=noConstraintImpl
+policyset.serverCertSet.4.constraint.name=No Constraint
+policyset.serverCertSet.4.default.class_id=authorityKeyIdentifierExtDefaultImpl
+policyset.serverCertSet.4.default.name=Authority Key Identifier Default
+policyset.serverCertSet.5.constraint.class_id=noConstraintImpl
+policyset.serverCertSet.5.constraint.name=No Constraint
+policyset.serverCertSet.5.default.class_id=authInfoAccessExtDefaultImpl
+policyset.serverCertSet.5.default.name=AIA Extension Default
+policyset.serverCertSet.5.default.params.authInfoAccessADEnable_0=true
+policyset.serverCertSet.5.default.params.authInfoAccessADLocationType_0=URIName
+policyset.serverCertSet.5.default.params.authInfoAccessADLocation_0=http://ipa-ca.{ipadomain}/ca/ocsp
+policyset.serverCertSet.5.default.params.authInfoAccessADMethod_0=1.3.6.1.5.5.7.48.1
+policyset.serverCertSet.5.default.params.authInfoAccessCritical=false
+policyset.serverCertSet.5.default.params.authInfoAccessNumADs=1
+policyset.serverCertSet.6.constraint.class_id=keyUsageExtConstraintImpl
+policyset.serverCertSet.6.constraint.name=Key Usage Extension Constraint
+policyset.serverCertSet.6.constraint.params.keyUsageCritical=true
+policyset.serverCertSet.6.constraint.params.keyUsageCrlSign=false
+policyset.serverCertSet.6.constraint.params.keyUsageDataEncipherment=true
+policyset.serverCertSet.6.constraint.params.keyUsageDecipherOnly=false
+policyset.serverCertSet.6.constraint.params.keyUsageDigitalSignature=true
+policyset.serverCertSet.6.constraint.params.keyUsageEncipherOnly=false
+policyset.serverCertSet.6.constraint.params.keyUsageKeyAgreement=false
+policyset.serverCertSet.6.constraint.params.keyUsageKeyCertSign=false
+policyset.serverCertSet.6.constraint.params.keyUsageKeyEncipherment=true
+policyset.serverCertSet.6.constraint.params.keyUsageNonRepudiation=true
+policyset.serverCertSet.6.default.class_id=keyUsageExtDefaultImpl
+policyset.serverCertSet.6.default.name=Key Usage Default
+policyset.serverCertSet.6.default.params.keyUsageCritical=true
+policyset.serverCertSet.6.default.params.keyUsageCrlSign=false
+policyset.serverCertSet.6.default.params.keyUsageDataEncipherment=true
+policyset.serverCertSet.6.default.params.keyUsageDecipherOnly=false
+policyset.serverCertSet.6.default.params.keyUsageDigitalSignature=true
+policyset.serverCertSet.6.default.params.keyUsageEncipherOnly=false
+policyset.serverCertSet.6.default.params.keyUsageKeyAgreement=false
+policyset.serverCertSet.6.default.params.keyUsageKeyCertSign=false
+policyset.serverCertSet.6.default.params.keyUsageKeyEncipherment=true
+policyset.serverCertSet.6.default.params.keyUsageNonRepudiation=true
+policyset.serverCertSet.7.constraint.class_id=noConstraintImpl
+policyset.serverCertSet.7.constraint.name=No Constraint
+policyset.serverCertSet.7.default.class_id=extendedKeyUsageExtDefaultImpl
+policyset.serverCertSet.7.default.name=Extended Key Usage Extension Default
+policyset.serverCertSet.7.default.params.exKeyUsageCritical=false
+policyset.serverCertSet.7.default.params.exKeyUsageOIDs=1.3.6.1.5.5.7.3.4
+policyset.serverCertSet.8.constraint.class_id=signingAlgConstraintImpl
+policyset.serverCertSet.8.constraint.name=No Constraint
+policyset.serverCertSet.8.constraint.params.signingAlgsAllowed=SHA1withRSA,SHA256withRSA,SHA512withRSA,MD5withRSA,MD2withRSA,SHA1withDSA,SHA1withEC,SHA256withEC,SHA384withEC,SHA512withEC
+policyset.serverCertSet.8.default.class_id=signingAlgDefaultImpl
+policyset.serverCertSet.8.default.name=Signing Alg
+policyset.serverCertSet.8.default.params.signingAlg=-
+policyset.serverCertSet.9.constraint.class_id=noConstraintImpl
+policyset.serverCertSet.9.constraint.name=No Constraint
+policyset.serverCertSet.9.default.class_id=crlDistributionPointsExtDefaultImpl
+policyset.serverCertSet.9.default.name=CRL Distribution Points Extension Default
+policyset.serverCertSet.9.default.params.crlDistPointsCritical=false
+policyset.serverCertSet.9.default.params.crlDistPointsEnable_0=true
+policyset.serverCertSet.9.default.params.crlDistPointsIssuerName_0=CN=Certificate Authority,o=ipaca
+policyset.serverCertSet.9.default.params.crlDistPointsIssuerType_0=DirectoryName
+policyset.serverCertSet.9.default.params.crlDistPointsNum=1
+policyset.serverCertSet.9.default.params.crlDistPointsPointName_0=http://ipa-ca.{ipadomain}/ipa/crl/MasterCRL.bin
+policyset.serverCertSet.9.default.params.crlDistPointsPointType_0=URIName
+policyset.serverCertSet.9.default.params.crlDistPointsReasons_0=
+policyset.serverCertSet.list=1,2,3,4,5,6,7,8,9,10,11
+visible=false
diff --git a/ipatests/test_xmlrpc/data/usercert-priv-key.pem b/ipatests/test_xmlrpc/data/usercert-priv-key.pem
new file mode 100644
index 0000000000000000000000000000000000000000..af5ea0b1829ba5401aa6a53a25b5768a37b6e8af
--- /dev/null
+++ b/ipatests/test_xmlrpc/data/usercert-priv-key.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAsYQl2pLroHyuZWe6vSd6GsxPRxhGlAT0K87S1vMRr5cfU0Eu
+SZx0jsS4jXfAKqW/BhsVHsGA75iVFhbUY7CRGOZ9b9z1kUyajkd0TiJWxLvSDUma
+muwi/gsRDvCr+ro0zk/v38sddXXrhMKhJBj67P3PesEqobQK59GjFk+tAYn+DROP
+bo9w6bkJuhNqEaWmmYmTQz8EdBLIwGb/9FAD8iDuEFd70Mvdnn8DZ9zi5fJnpTUG
+fT6B/mTgTY/37i7scaH8/3+/dHFk5bTjQHIsj0On9z8/ehs8llgSMawMlIuOB1q6
+i7LuVkoD/zz3d7k8ytiYqOd8wfBIEEIdfULQaQIDAQABAoIBACrnpb6OhCTl/cDE
+sX3GbNzNRNwKIgTkrZ9o/cy2MzAddpTIzEc+aW2YXoLSzr+AEAuJwDEO0/sVBfOw
+0OTHaEp8axT+ctwLh8+btaCs7Avg2YQcpiGLsWl1g0n5IZgYKWs0JuYQUa5yMdqE
+sC3pW7ysG9mvln4+5ePh52kdGNOl/4MNaEbQ+OR0V8mmb3wPPPeUIHFKKzLJj7po
+n2nxdhebX8W/BGUkcV7zx89Bpufnypc8e0nn9cznbGsq/U1LUTCVJ2SzCJ2qDwUG
++0ZM1dprfp3/J3yFxKEBlZRMS//VQ5/DIFCcxFPzFizRonkgrzSF1kapNSsQvuzE
+Wax+rYECgYEA3SZthkGW2lvHjI1mMLQY94+7IB9ixdUB4lUvYmwuoUf7k/fJZ4Nq
+t4BycD9Ttj/Q19akZdXfuJgnVtDV1OgExTuJqV4gZ1jz5ewvP1ILfjcjeMLFfoez
+u0JkkeCqrBVWeCc0Ax5lWtm8JsVGV4Nd5gVLI/nmLaMh9InbClQRGC8CgYEAzX10
+NEqPFvvjEaKe2q7EIkHm4sL6TB30ajvC1Zp50jonj1i0L6Un5WqB3RCX0HAbkuz2
+umi9bxMFRfRVJsTcmhb2UrIDEolYkmIm/ji+JS0tvk7jKwl2fAv2waTBwpBA7K3g
+YUHoY6L2r7eCh/2dZvp7LiVyHrIatMsiL9msYucCgYB5MHfQnNzYKHeAFHStt+P+
+tisrfUeZdhMkPt5Kp1IeW94Hxj/+k8vFZ4RO8sUjGHGP9jX9AGkrNWZJcwPbOpJy
+qx/TSpujRuHRW87AemuF7R1pLgMgRak+szF9p4qf5smN6p3cH6oXUT6EWJMlnf20
+8a2tt2JmHAGdinYYgN0lTQKBgQCZJXWUfzjTTVj2zLcNjhCY43q658t5LR36ip1z
+apR+DF9tYxOvKqxoO4+bfQFYFCVIxBhB50u/W3KjpyxLH461vIVKLmdBymDbgBFF
+iG6V8GzWF58QdRX770KxISRS6AWrHw9KDL+wekTVwrOivG4x0F47jybVH7HtqjLJ
+bLYgYwKBgQCmLVTDie+GB2nUVe+IUOBsLfyQXfbQT1ksgX5l7M+W1tOKq1h5R/SS
+YDhasCpNIuK/yMTIXxKP1B7+Kd0I4Ib7w7Ri4kHdai+Wlf+sCjTytXt8YXUQo5mx
+fEadGM6UR/95ug9S4VbeqZdrPcjnOa4RcdzDUsxrqYRMUIlwWNCy8Q==
+-----END RSA PRIVATE KEY-----
diff --git a/ipatests/test_xmlrpc/data/usercert.conf.tmpl b/ipatests/test_xmlrpc/data/usercert.conf.tmpl
new file mode 100644
index 0000000000000000000000000000000000000000..22804be851c2180d14d299b3bb150077d1a6458e
--- /dev/null
+++ b/ipatests/test_xmlrpc/data/usercert.conf.tmpl
@@ -0,0 +1,13 @@
+[ req ]
+prompt = no
+encrypt_key = no
+
+distinguished_name = dn
+req_extensions = exts
+
+[ dn ]
+commonName = "{username}"
+{ipacertbase}
+
+[ exts ]
+subjectAltName=email:{username}@{ipadomain}
-- 
2.6.2

From e055706c6aab3d9d79663ad224347a387d576be9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Milan=20Kub=C3=ADk?= <mku...@redhat.com>
Date: Tue, 22 Sep 2015 15:21:33 +0200
Subject: [PATCH 5/6] ipatests: added unlock_principal_password and
 change_principal

The unlock_principal_password unlocks the (new) user by running
ldappasswd as the user.

change_principal is an context manager that changes identity
for the supplied api object by disconnecting and reconnecting
the rpcclient in and outside of requested kerberos context.
This context manager allows to run tests that cannot be
executed as an admin user which can for example override
an CA ACL.

https://fedorahosted.org/freeipa/ticket/57
---
 ipatests/util.py | 40 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 40 insertions(+)

diff --git a/ipatests/util.py b/ipatests/util.py
index 8a7dccaa7baf15d931e929d179a3863d3b9e1e54..4d99ff6e0a505cd3f75053f97caca9edbc802bcf 100644
--- a/ipatests/util.py
+++ b/ipatests/util.py
@@ -30,10 +30,15 @@ import ldap.modlist
 import tempfile
 import shutil
 import re
+import uuid
+from contextlib import contextmanager
 import ipalib
+from ipalib import api
 from ipalib.plugable import Plugin
 from ipalib.request import context
 from ipapython.dn import DN
+from ipapython.ipautil import private_ccache, kinit_password, run
+from ipaplatform.paths import paths
 
 
 class TempDir(object):
@@ -657,3 +662,38 @@ def prepare_config(template, values):
         config.write(template.format(**values))
 
     return config.name
+
+
+def unlock_principal_password(user, oldpw, newpw):
+    userdn = "uid={},{},{}".format(
+        user, api.env.container_user, api.env.basedn)
+
+    args = [paths.LDAPPASSWD, '-D', userdn, '-w', oldpw, '-a', oldpw,
+            '-s', newpw, '-x']
+    return run(args)
+
+
+@contextmanager
+def change_principal(user, password, client=None, path=None):
+
+    if path:
+        ccache_name = path
+    else:
+        ccache_name = os.path.join('/tmp', str(uuid.uuid4()))
+
+    if client is None:
+        client = api
+
+
+    client.Backend.rpcclient.disconnect()
+
+    with private_ccache(ccache_name):
+        kinit_password(user, password, ccache_name)
+        client.Backend.rpcclient.connect()
+
+        try:
+            yield
+        finally:
+            client.Backend.rpcclient.disconnect()
+
+    client.Backend.rpcclient.connect()
-- 
2.6.1

From db2f35667d8a7b2ca8c3ca90a61bbce63610c9f0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Milan=20Kub=C3=ADk?= <mku...@redhat.com>
Date: Tue, 22 Sep 2015 15:22:25 +0200
Subject: [PATCH] ipatests: CA ACL and cert profile functional test

https://fedorahosted.org/freeipa/ticket/57
---
 ipatests/test_xmlrpc/data/smime-mod.cfg.tmpl       | 108 ++++++++++
 .../test_xmlrpc/test_caacl_profile_enforcement.py  | 236 +++++++++++++++++++++
 2 files changed, 344 insertions(+)
 create mode 100644 ipatests/test_xmlrpc/data/smime-mod.cfg.tmpl
 create mode 100644 ipatests/test_xmlrpc/test_caacl_profile_enforcement.py

diff --git a/ipatests/test_xmlrpc/data/smime-mod.cfg.tmpl b/ipatests/test_xmlrpc/data/smime-mod.cfg.tmpl
new file mode 100644
index 0000000000000000000000000000000000000000..da94c25e574af6cfe7230073195a541eb7f04fd8
--- /dev/null
+++ b/ipatests/test_xmlrpc/data/smime-mod.cfg.tmpl
@@ -0,0 +1,108 @@
+auth.instance_id=raCertAuth
+classId=caEnrollImpl
+desc=Certificate for S-MIME extension
+enable=true
+enableBy=ipara
+input.i1.class_id=certReqInputImpl
+input.i2.class_id=submitterInfoInputImpl
+input.list=i1,i2
+name=SMIME certificate profile
+output.list=o1
+output.o1.class_id=certOutputImpl
+policyset.list=serverCertSet
+policyset.serverCertSet.1.constraint.class_id=subjectNameConstraintImpl
+policyset.serverCertSet.1.constraint.name=Subject Name Constraint
+policyset.serverCertSet.1.constraint.params.accept=true
+policyset.serverCertSet.1.constraint.params.pattern=CN=refuse sign
+policyset.serverCertSet.1.default.class_id=subjectNameDefaultImpl
+policyset.serverCertSet.1.default.name=Subject Name Default
+policyset.serverCertSet.1.default.params.name=CN=$request.req_subject_name.cn$, O={iparealm}
+policyset.serverCertSet.2.constraint.class_id=validityConstraintImpl
+policyset.serverCertSet.2.constraint.name=Validity Constraint
+policyset.serverCertSet.2.constraint.params.notAfterCheck=false
+policyset.serverCertSet.2.constraint.params.notBeforeCheck=false
+policyset.serverCertSet.2.constraint.params.range=740
+policyset.serverCertSet.2.default.class_id=validityDefaultImpl
+policyset.serverCertSet.2.default.name=Validity Default
+policyset.serverCertSet.2.default.params.range=731
+policyset.serverCertSet.2.default.params.startTime=0
+policyset.serverCertSet.3.constraint.class_id=keyConstraintImpl
+policyset.serverCertSet.3.constraint.name=Key Constraint
+policyset.serverCertSet.3.constraint.params.keyParameters=1024,2048,3072,4096
+policyset.serverCertSet.3.constraint.params.keyType=RSA
+policyset.serverCertSet.3.default.class_id=userKeyDefaultImpl
+policyset.serverCertSet.3.default.name=Key Default
+policyset.serverCertSet.4.constraint.class_id=noConstraintImpl
+policyset.serverCertSet.4.constraint.name=No Constraint
+policyset.serverCertSet.4.default.class_id=authorityKeyIdentifierExtDefaultImpl
+policyset.serverCertSet.4.default.name=Authority Key Identifier Default
+policyset.serverCertSet.5.constraint.class_id=noConstraintImpl
+policyset.serverCertSet.5.constraint.name=No Constraint
+policyset.serverCertSet.5.default.class_id=authInfoAccessExtDefaultImpl
+policyset.serverCertSet.5.default.name=AIA Extension Default
+policyset.serverCertSet.5.default.params.authInfoAccessADEnable_0=true
+policyset.serverCertSet.5.default.params.authInfoAccessADLocationType_0=URIName
+policyset.serverCertSet.5.default.params.authInfoAccessADLocation_0=http://ipa-ca.{ipadomain}/ca/ocsp
+policyset.serverCertSet.5.default.params.authInfoAccessADMethod_0=1.3.6.1.5.5.7.48.1
+policyset.serverCertSet.5.default.params.authInfoAccessCritical=false
+policyset.serverCertSet.5.default.params.authInfoAccessNumADs=1
+policyset.serverCertSet.6.constraint.class_id=keyUsageExtConstraintImpl
+policyset.serverCertSet.6.constraint.name=Key Usage Extension Constraint
+policyset.serverCertSet.6.constraint.params.keyUsageCritical=true
+policyset.serverCertSet.6.constraint.params.keyUsageCrlSign=false
+policyset.serverCertSet.6.constraint.params.keyUsageDataEncipherment=true
+policyset.serverCertSet.6.constraint.params.keyUsageDecipherOnly=false
+policyset.serverCertSet.6.constraint.params.keyUsageDigitalSignature=true
+policyset.serverCertSet.6.constraint.params.keyUsageEncipherOnly=false
+policyset.serverCertSet.6.constraint.params.keyUsageKeyAgreement=false
+policyset.serverCertSet.6.constraint.params.keyUsageKeyCertSign=false
+policyset.serverCertSet.6.constraint.params.keyUsageKeyEncipherment=true
+policyset.serverCertSet.6.constraint.params.keyUsageNonRepudiation=true
+policyset.serverCertSet.6.default.class_id=keyUsageExtDefaultImpl
+policyset.serverCertSet.6.default.name=Key Usage Default
+policyset.serverCertSet.6.default.params.keyUsageCritical=true
+policyset.serverCertSet.6.default.params.keyUsageCrlSign=false
+policyset.serverCertSet.6.default.params.keyUsageDataEncipherment=true
+policyset.serverCertSet.6.default.params.keyUsageDecipherOnly=false
+policyset.serverCertSet.6.default.params.keyUsageDigitalSignature=true
+policyset.serverCertSet.6.default.params.keyUsageEncipherOnly=false
+policyset.serverCertSet.6.default.params.keyUsageKeyAgreement=false
+policyset.serverCertSet.6.default.params.keyUsageKeyCertSign=false
+policyset.serverCertSet.6.default.params.keyUsageKeyEncipherment=true
+policyset.serverCertSet.6.default.params.keyUsageNonRepudiation=true
+policyset.serverCertSet.7.constraint.class_id=noConstraintImpl
+policyset.serverCertSet.7.constraint.name=No Constraint
+policyset.serverCertSet.7.default.class_id=extendedKeyUsageExtDefaultImpl
+policyset.serverCertSet.7.default.name=Extended Key Usage Extension Default
+policyset.serverCertSet.7.default.params.exKeyUsageCritical=false
+policyset.serverCertSet.7.default.params.exKeyUsageOIDs=1.3.6.1.5.5.7.3.4
+policyset.serverCertSet.8.constraint.class_id=signingAlgConstraintImpl
+policyset.serverCertSet.8.constraint.name=No Constraint
+policyset.serverCertSet.8.constraint.params.signingAlgsAllowed=SHA1withRSA,SHA256withRSA,SHA512withRSA,MD5withRSA,MD2withRSA,SHA1withDSA,SHA1withEC,SHA256withEC,SHA384withEC,SHA512withEC
+policyset.serverCertSet.8.default.class_id=signingAlgDefaultImpl
+policyset.serverCertSet.8.default.name=Signing Alg
+policyset.serverCertSet.8.default.params.signingAlg=-
+policyset.serverCertSet.9.constraint.class_id=noConstraintImpl
+policyset.serverCertSet.9.constraint.name=No Constraint
+policyset.serverCertSet.9.default.class_id=crlDistributionPointsExtDefaultImpl
+policyset.serverCertSet.9.default.name=CRL Distribution Points Extension Default
+policyset.serverCertSet.9.default.params.crlDistPointsCritical=false
+policyset.serverCertSet.9.default.params.crlDistPointsEnable_0=true
+policyset.serverCertSet.9.default.params.crlDistPointsIssuerName_0=CN=Certificate Authority,o=ipaca
+policyset.serverCertSet.9.default.params.crlDistPointsIssuerType_0=DirectoryName
+policyset.serverCertSet.9.default.params.crlDistPointsNum=1
+policyset.serverCertSet.9.default.params.crlDistPointsPointName_0=http://ipa-ca.{ipadomain}/ipa/crl/MasterCRL.bin
+policyset.serverCertSet.9.default.params.crlDistPointsPointType_0=URIName
+policyset.serverCertSet.9.default.params.crlDistPointsReasons_0=
+policyset.serverCertSet.10.constraint.class_id=noConstraintImpl
+policyset.serverCertSet.10.constraint.name=No Constraint
+policyset.serverCertSet.10.default.class_id=subjectKeyIdentifierExtDefaultImpl
+policyset.serverCertSet.10.default.name=Subject Key Identifier Extension Default
+policyset.serverCertSet.10.default.params.critical=false
+policyset.serverCertSet.11.constraint.class_id=noConstraintImpl
+policyset.serverCertSet.11.constraint.name=No Constraint
+policyset.serverCertSet.11.default.class_id=userExtensionDefaultImpl
+policyset.serverCertSet.11.default.name=User Supplied Extension Default
+policyset.serverCertSet.11.default.params.userExtOID=2.5.29.17
+policyset.serverCertSet.list=1,2,3,4,5,6,7,8,9,10,11
+visible=false
diff --git a/ipatests/test_xmlrpc/test_caacl_profile_enforcement.py b/ipatests/test_xmlrpc/test_caacl_profile_enforcement.py
new file mode 100644
index 0000000000000000000000000000000000000000..e9472b07813bcac276397d7a7b61746b6b5a78b6
--- /dev/null
+++ b/ipatests/test_xmlrpc/test_caacl_profile_enforcement.py
@@ -0,0 +1,236 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2015  FreeIPA Contributors see COPYING for license
+#
+
+import os
+import pytest
+import tempfile
+
+from ipalib import api, errors
+from ipatests.util import (
+    prepare_config, unlock_principal_password, change_principal)
+from ipatests.test_xmlrpc.xmlrpc_test import XMLRPC_test
+from ipatests.test_xmlrpc.test_certprofile_plugin import CertprofileTracker
+from ipatests.test_xmlrpc.test_caacl_plugin import CAACLTracker
+
+from ipapython.ipautil import run
+
+BASE_DIR = os.path.dirname(__file__)
+
+SMIME_PROFILE_TEMPLATE = os.path.join(BASE_DIR, 'data/smime.cfg.tmpl')
+SMIME_MOD_CONSTR_PROFILE_TEMPLATE = os.path.join(BASE_DIR, 'data/smime-mod.cfg.tmpl')
+CERT_OPENSSL_CONFIG_TEMPLATE = os.path.join(BASE_DIR, 'data/usercert.conf.tmpl')
+CERT_RSA_PRIVATE_KEY_PATH = os.path.join(BASE_DIR, 'data/usercert-priv-key.pem')
+
+
+CERT_SUBJECT_BASE = (
+    api.Command.config_show()
+    ['result']['ipacertificatesubjectbase'][0]
+)
+
+SMIME_USER_INIT_PW = u'Change123'
+SMIME_USER_PW = u'Secret123'
+
+
+def generate_user_csr(username, domain=None):
+    csr_values = dict(
+        ipacertbase=CERT_SUBJECT_BASE,
+        ipadomain=domain if domain else api.env.domain,
+        username=username)
+
+    with tempfile.NamedTemporaryFile(mode='w') as csr_file:
+        run(['openssl', 'req', '-new', '-key', CERT_RSA_PRIVATE_KEY_PATH,
+             '-out', csr_file.name,
+             '-config', prepare_config(
+                 CERT_OPENSSL_CONFIG_TEMPLATE, csr_values)])
+
+        with open(csr_file.name, 'r') as f:
+            csr = unicode(f.read())
+
+    return csr
+
+
+@pytest.fixture(scope='class')
+def smime_profile(request):
+    profile_path = prepare_config(
+            SMIME_PROFILE_TEMPLATE,
+            dict(ipadomain=api.env.domain, iparealm=api.env.realm))
+
+    tracker = CertprofileTracker(u'smime', store=True,
+                                 desc=u"S/MIME certificate profile",
+                                 profile=profile_path)
+
+    return tracker.make_fixture(request)
+
+
+@pytest.fixture(scope='class')
+def smime_acl(request):
+    tracker = CAACLTracker(u'smime_acl')
+
+    return tracker.make_fixture(request)
+
+
+# TODO: rewrite these into Tracker instances
+# UserTracker has problems while setting passwords.
+# Until fixed, will use this fixture.
+@pytest.fixture(scope='class')
+def smime_user(request):
+    username = u'alice'
+    api.Command.user_add(uid=username, givenname=u'Alice', sn=u'SMIME',
+                         userpassword=SMIME_USER_INIT_PW)
+
+    unlock_principal_password(username, SMIME_USER_INIT_PW, SMIME_USER_PW)
+
+    def fin():
+        api.Command.user_del(username)
+    request.addfinalizer(fin)
+
+    return username
+
+
+@pytest.fixture(scope='class')
+def smime_group(request):
+    api.Command.group_add(u'smime_users')
+
+    def fin():
+        api.Command.group_del(u'smime_users')
+    request.addfinalizer(fin)
+
+    return u'smime_users'
+
+
+class TestCertSignMIME(XMLRPC_test):
+
+    def test_cert_import(self, smime_profile):
+        smime_profile.ensure_exists()
+
+    def test_create_acl(self, smime_acl):
+        smime_acl.ensure_exists()
+
+    def test_add_profile_to_acl(self, smime_acl, smime_profile):
+        smime_acl.add_profile(certprofile=smime_profile.name)
+
+    # rewrite to trackers, prepare elsewhere
+    def test_add_user_to_group(self, smime_group, smime_user):
+        api.Command.group_add_member(smime_group, user=smime_user)
+
+    def test_add_group_to_acl(self, smime_group, smime_acl):
+        smime_acl.add_user(group=smime_group)
+
+    def test_sign_smime_csr(self, smime_profile, smime_user):
+        csr = generate_user_csr(smime_user)
+        with change_principal(smime_user, SMIME_USER_PW):
+            api.Command.cert_request(csr, principal=smime_user,
+                                     profile_id=smime_profile.name)
+
+
+class TestSignWithDisabledACL(XMLRPC_test):
+
+    def test_import_profile_and_acl(self, smime_profile, smime_acl):
+        smime_profile.ensure_exists()
+        smime_acl.ensure_missing()
+        smime_acl.ensure_exists()
+
+    def test_add_profile_to_acl(self, smime_acl, smime_profile):
+        smime_acl.add_profile(certprofile=smime_profile.name)
+
+    # rewrite to trackers, prepare elsewhere
+    def test_add_user_to_group(self, smime_group, smime_user):
+        api.Command.group_add_member(smime_group, user=smime_user)
+
+    def test_add_group_to_acl(self, smime_group, smime_acl):
+        smime_acl.add_user(group=smime_group)
+
+    def test_disable_acl(self, smime_acl):
+        smime_acl.disable()
+
+    def test_signing_with_disabled_acl(self, smime_acl, smime_profile,
+                                       smime_user):
+        csr = generate_user_csr(smime_user)
+        with change_principal(smime_user, SMIME_USER_PW):
+            with pytest.raises(errors.ACIError):
+                api.Command.cert_request(
+                    csr, profile_id=smime_profile.name,
+                    principal=smime_user)
+
+    def test_admin_overrides_disabled_acl(self, smime_acl, smime_profile,
+                                          smime_user):
+        csr = generate_user_csr(smime_user)
+        api.Command.cert_request(
+            csr, profile_id=smime_profile.name,
+            principal=smime_user)
+
+
+class TestSignWithoutGroupMembership(XMLRPC_test):
+
+    def test_import_profile_and_acl(self, smime_profile, smime_acl):
+        smime_profile.ensure_exists()
+        smime_acl.ensure_missing()
+        smime_acl.ensure_exists()
+
+    def test_add_profile_to_acl(self, smime_acl, smime_profile):
+        smime_acl.add_profile(certprofile=smime_profile.name)
+
+    def test_add_group_to_acl(self, smime_group, smime_acl, smime_user):
+        # smime user should not be a member of this group
+        #
+        # adding smime_user fixture to ensure it exists
+        smime_acl.add_user(group=smime_group)
+
+    def test_signing_with_non_member_principal(self, smime_acl, smime_profile,
+                                               smime_user):
+
+        csr = generate_user_csr(smime_user)
+        with change_principal(smime_user, SMIME_USER_PW):
+            with pytest.raises(errors.ACIError):
+                api.Command.cert_request(
+                    csr,
+                    profile_id=smime_profile.name,
+                    principal=smime_user)
+
+    def test_admin_overrides_group_membership(self, smime_acl, smime_profile,
+                                              smime_user):
+        csr = generate_user_csr(smime_user)
+        api.Command.cert_request(
+            csr, profile_id=smime_profile.name,
+            principal=smime_user)
+
+
+class TestSignWithChangedProfile(XMLRPC_test):
+    """ Test to verify that the updated profile is used.
+
+    The profile change requires different CN in CSR
+    than the one configured. This leads to rejection
+    based on not meeting the profile constraints.
+    """
+
+    def test_prepare_env(self, smime_profile, smime_acl):
+        smime_profile.ensure_exists()
+        smime_acl.ensure_exists()
+
+        smime_acl.add_profile(certprofile=smime_profile.name)
+
+    def test_prepare_user_and_group(self, smime_group, smime_user, smime_acl):
+        api.Command.group_add_member(smime_group, user=smime_user)
+        smime_acl.add_user(group=smime_group)
+
+    def test_modify_smime_profile(self, smime_profile):
+        updated_profile_path = prepare_config(SMIME_MOD_CONSTR_PROFILE_TEMPLATE,
+                                              dict(
+                                                   ipadomain=api.env.domain,
+                                                   iparealm=api.env.realm))
+
+        with open(updated_profile_path) as f:
+            updated_profile = unicode(f.read())
+
+        updates = {u'file': updated_profile}
+        update_smime_profile = smime_profile.make_update_command(updates)
+        update_smime_profile()
+
+    def test_sign_smime_csr(self, smime_profile, smime_user):
+        csr = generate_user_csr(smime_user)
+        with change_principal(smime_user, SMIME_USER_PW):
+            with pytest.raises(errors.CertificateOperationError):
+                api.Command.cert_request(csr, principal=smime_user,
+                                         profile_id=smime_profile.name)
-- 
2.6.2

-- 
Manage your subscription for the Freeipa-devel mailing list:
https://www.redhat.com/mailman/listinfo/freeipa-devel
Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code

Reply via email to