URL: https://github.com/freeipa/freeipa/pull/399 Author: dkupka Title: #399: Certificate mapping test Action: synchronized
To pull the PR as Git branch: git remote add ghfreeipa https://github.com/freeipa/freeipa git fetch ghfreeipa pull/399/head:pr399 git checkout pr399
From 11ac9cfa85cee324be81bc6dacf2b757f1933d9a Mon Sep 17 00:00:00 2001 From: David Kupka <dku...@redhat.com> Date: Wed, 1 Feb 2017 11:36:32 +0100 Subject: [PATCH 1/8] tests: tracker: Split Tracker into one-purpose Trackers There are multiple types of entries and objects accessible in API and not all of them have the same set methods. Spliting Tracker into multiple trackers should reflect this better. --- ipatests/test_xmlrpc/tracker/base.py | 285 +++++++++++++++++++++-------------- 1 file changed, 172 insertions(+), 113 deletions(-) diff --git a/ipatests/test_xmlrpc/tracker/base.py b/ipatests/test_xmlrpc/tracker/base.py index aa88e6b..8b6e97e 100644 --- a/ipatests/test_xmlrpc/tracker/base.py +++ b/ipatests/test_xmlrpc/tracker/base.py @@ -15,61 +15,7 @@ from ipatests.util import Fuzzy -class Tracker(object): - """Wraps and tracks modifications to a plugin LDAP entry object - - Stores a copy of state of a plugin entry object and allows checking that - the state in the database is the same as expected. - This allows creating independent tests: the individual tests check - that the relevant changes have been made. At the same time - the entry doesn't need to be recreated and cleaned up for each test. - - Two attributes are used for tracking: ``exists`` (true if the entry is - supposed to exist) and ``attrs`` (a dict of LDAP attributes that are - expected to be returned from IPA commands). - - For commonly used operations, there is a helper method, e.g. - ``create``, ``update``, or ``find``, that does these steps: - - * ensure the entry exists (or does not exist, for "create") - * store the expected modifications - * get the IPA command to run, and run it - * check that the result matches the expected state - - Tests that require customization of these steps are expected to do them - manually, using lower-level methods. - Especially the first step (ensure the entry exists) is important for - achieving independent tests. - - The Tracker object also stores information about the entry, e.g. - ``dn``, ``rdn`` and ``name`` which is derived from DN property. - - To use this class, the programer must subclass it and provide the - implementation of following methods: - - * make_*_command -- implementing the API call for particular plugin - and operation (add, delete, ...) - These methods should use the make_command method - * check_* commands -- an assertion for a plugin command (CRUD) - * track_create -- to make an internal representation of the - entry - - Apart from overriding these methods, the subclass must provide the - distinguished name of the entry in `self.dn` property. - - It is also required to override the class variables defining the sets - of ldap attributes/keys for these operations specific to the plugin - being implemented. Take the host plugin test for an example. - - The implementation of these methods is not strictly enforced. - A missing method will cause a NotImplementedError during runtime - as a result. - """ - retrieve_keys = None - retrieve_all_keys = None - create_keys = None - update_keys = None - +class BaseTracker(object): _override_me_msg = "This method needs to be overridden in a subclass" def __init__(self, default_version=None): @@ -78,8 +24,6 @@ def __init__(self, default_version=None): self._dn = None self.attrs = {} - self.exists = False - @property def dn(self): """A property containing the distinguished name of the entry.""" @@ -138,53 +82,33 @@ def make_command(self, name, *args, **options): return functools.partial(self.run_command, name, *args, **options) def make_fixture(self, request): - """Make a pytest fixture for this tracker + """Make fixture for the tracker - The fixture ensures the plugin entry does not exist before - and after the tests that use it. + Don't do anything here. """ - del_command = self.make_delete_command() - try: - del_command() - except errors.NotFound: - pass - - def cleanup(): - existed = self.exists - try: - del_command() - except errors.NotFound: - if existed: - raise - self.exists = False - - request.addfinalizer(cleanup) - return self - def ensure_exists(self): - """If the entry does not exist (according to tracker state), create it - """ - if not self.exists: - self.create() - def ensure_missing(self): - """If the entry exists (according to tracker state), delete it - """ - if self.exists: - self.delete() +class RetrievableTracker(BaseTracker): + retrieve_keys = None + retrieve_all_keys = None - def make_create_command(self): - """Make function that creates the plugin entry object.""" + def make_retrieve_command(self, all=False, raw=False): + """Make function that retrieves the entry using ${CMD}_show""" raise NotImplementedError(self._override_me_msg) - def make_delete_command(self): - """Make function that deletes the plugin entry object.""" + def check_retrieve(self, result, all=False, raw=False): + """Check the plugin's `show` command result""" raise NotImplementedError(self._override_me_msg) - def make_retrieve_command(self, all=False, raw=False): - """Make function that retrieves the entry using ${CMD}_show""" - raise NotImplementedError(self._override_me_msg) + def retrieve(self, all=False, raw=False): + """Helper function to retrieve an entry and check the result""" + command = self.make_retrieve_command(all=all, raw=raw) + result = command() + self.check_retrieve(result, all=all, raw=raw) + + +class SearchableTracker(BaseTracker): def make_find_command(self, *args, **kwargs): """Make function that finds the entry using ${CMD}_find @@ -194,16 +118,62 @@ def make_find_command(self, *args, **kwargs): """ raise NotImplementedError(self._override_me_msg) + def check_find(self, result, all=False, raw=False): + """Check the plugin's `find` command result""" + raise NotImplementedError(self._override_me_msg) + + def find(self, all=False, raw=False): + """Helper function to search for this hosts and check the result""" + command = self.make_find_command(self.name, all=all, raw=raw) + result = command() + self.check_find(result, all=all, raw=raw) + + +class MutableTracker(BaseTracker): + update_keys = None + def make_update_command(self, updates): """Make function that modifies the entry using ${CMD}_mod""" raise NotImplementedError(self._override_me_msg) - def create(self): - """Helper function to create an entry and check the result""" - self.track_create() - command = self.make_create_command() + def check_update(self, result, extra_keys=()): + """Check the plugin's `mod` command result""" + raise NotImplementedError(self._override_me_msg) + + def update(self, updates, expected_updates=None): + """Helper function to update this hosts and check the result + + The ``updates`` are used as options to the *_mod command, + and the self.attrs is updated with this dict. + Additionally, self.attrs is updated with ``expected_updates``. + """ + if expected_updates is None: + expected_updates = {} + + command = self.make_update_command(updates) result = command() - self.check_create(result) + self.attrs.update(updates) + self.attrs.update(expected_updates) + for key, value in self.attrs.items(): + if value is None: + del self.attrs[key] + + self.check_update( + result, + extra_keys=set(updates.keys()) | set(expected_updates.keys()) + ) + + +class CreatableTracker(BaseTracker): + create_keys = None + + def __init__(self, default_version=None): + super(CreatableTracker, self).__init__(default_version=default_version) + self.exists = False + + def make_create_command(self): + """Make function that creates the plugin entry object.""" + raise NotImplementedError(self._override_me_msg) def track_create(self): """Update expected state for host creation @@ -225,12 +195,22 @@ def check_create(self, result): """Check plugin's add command result""" raise NotImplementedError(self._override_me_msg) - def delete(self): - """Helper function to delete a host and check the result""" - self.track_delete() - command = self.make_delete_command() + def create(self): + """Helper function to create an entry and check the result""" + self.track_create() + command = self.make_create_command() result = command() - self.check_delete(result) + self.check_create(result) + + def ensure_exists(self): + """If the entry does not exist (according to tracker state), create it + """ + if not self.exists: + self.create() + + def make_delete_command(self): + """Make function that deletes the plugin entry object.""" + raise NotImplementedError(self._override_me_msg) def track_delete(self): """Update expected state for host deletion""" @@ -241,14 +221,43 @@ def check_delete(self, result): """Check plugin's `del` command result""" raise NotImplementedError(self._override_me_msg) - def retrieve(self, all=False, raw=False): - """Helper function to retrieve an entry and check the result""" - command = self.make_retrieve_command(all=all, raw=raw) + def delete(self): + """Helper function to delete a host and check the result""" + self.track_delete() + command = self.make_delete_command() result = command() - self.check_retrieve(result, all=all, raw=raw) + self.check_delete(result) - def check_retrieve(self, result, all=False, raw=False): - """Check the plugin's `show` command result""" + def ensure_missing(self): + """If the entry exists (according to tracker state), delete it + """ + if self.exists: + self.delete() + + def make_fixture(self, request): + """Make a pytest fixture for this tracker + + The fixture ensures the plugin entry does not exist before + and after the tests that use it. + """ + del_command = self.make_delete_command() + try: + del_command() + except errors.NotFound: + pass + + def cleanup(): + existed = self.exists + try: + del_command() + except errors.NotFound: + if existed: + raise + self.exists = False + + request.addfinalizer(cleanup) + + return super(CreatableTracker, self).make_fixture(request) raise NotImplementedError(self._override_me_msg) def find(self, all=False, raw=False): @@ -282,6 +291,56 @@ def update(self, updates, expected_updates=None): self.check_update(result, extra_keys=set(updates.keys()) | set(expected_updates.keys())) - def check_update(self, result, extra_keys=()): - """Check the plugin's `mod` command result""" - raise NotImplementedError(self._override_me_msg) + +class Tracker(RetrievableTracker, SearchableTracker, MutableTracker, + CreatableTracker): + """Wraps and tracks modifications to a plugin LDAP entry object + + Stores a copy of state of a plugin entry object and allows checking that + the state in the database is the same as expected. + This allows creating independent tests: the individual tests check + that the relevant changes have been made. At the same time + the entry doesn't need to be recreated and cleaned up for each test. + + Two attributes are used for tracking: ``exists`` (true if the entry is + supposed to exist) and ``attrs`` (a dict of LDAP attributes that are + expected to be returned from IPA commands). + + For commonly used operations, there is a helper method, e.g. + ``create``, ``update``, or ``find``, that does these steps: + + * ensure the entry exists (or does not exist, for "create") + * store the expected modifications + * get the IPA command to run, and run it + * check that the result matches the expected state + + Tests that require customization of these steps are expected to do them + manually, using lower-level methods. + Especially the first step (ensure the entry exists) is important for + achieving independent tests. + + The Tracker object also stores information about the entry, e.g. + ``dn``, ``rdn`` and ``name`` which is derived from DN property. + + To use this class, the programer must subclass it and provide the + implementation of following methods: + + * make_*_command -- implementing the API call for particular plugin + and operation (add, delete, ...) + These methods should use the make_command method + * check_* commands -- an assertion for a plugin command (CRUD) + * track_create -- to make an internal representation of the + entry + + Apart from overriding these methods, the subclass must provide the + distinguished name of the entry in `self.dn` property. + + It is also required to override the class variables defining the sets + of ldap attributes/keys for these operations specific to the plugin + being implemented. Take the host plugin test for an example. + + The implementation of these methods is not strictly enforced. + A missing method will cause a NotImplementedError during runtime + as a result. + """ + pass From d4974be385f5967d431cd03b624e5d25351d5ac6 Mon Sep 17 00:00:00 2001 From: David Kupka <dku...@redhat.com> Date: Wed, 1 Feb 2017 11:37:00 +0100 Subject: [PATCH 2/8] tests: tracker: Add EnableTracker to test *-{enable,disable} commands --- ipatests/test_xmlrpc/tracker/base.py | 65 ++++++++++++++++++++++++------------ 1 file changed, 43 insertions(+), 22 deletions(-) diff --git a/ipatests/test_xmlrpc/tracker/base.py b/ipatests/test_xmlrpc/tracker/base.py index 8b6e97e..58a2891 100644 --- a/ipatests/test_xmlrpc/tracker/base.py +++ b/ipatests/test_xmlrpc/tracker/base.py @@ -258,38 +258,59 @@ def cleanup(): request.addfinalizer(cleanup) return super(CreatableTracker, self).make_fixture(request) + + +class EnableTracker(BaseTracker): + def __init__(self, default_version=None, enabled=True): + super(EnableTracker, self).__init__(default_version=default_version) + self.original_enabled = enabled + self.enabled = enabled + + def make_enable_command(self): + """Make function that enables the entry using ${CMD}_enable""" raise NotImplementedError(self._override_me_msg) - def find(self, all=False, raw=False): - """Helper function to search for this hosts and check the result""" - command = self.make_find_command(self.name, all=all, raw=raw) + def enable(self): + self.enabled = True + command = self.make_enable_command() result = command() - self.check_find(result, all=all, raw=raw) + self.check_enable(result) - def check_find(self, result, all=False, raw=False): - """Check the plugin's `find` command result""" + def check_enable(self, result): + """Check the plugin's `enable` command result""" raise NotImplementedError(self._override_me_msg) - def update(self, updates, expected_updates=None): - """Helper function to update this hosts and check the result + def make_disable_command(self): + """Make function that disables the entry using ${CMD}_disable""" + raise NotImplementedError(self._override_me_msg) - The ``updates`` are used as options to the *_mod command, - and the self.attrs is updated with this dict. - Additionally, self.attrs is updated with ``expected_updates``. + def disable(self): + self.enabled = False + command = self.make_disable_command() + result = command() + self.check_disable(result) + + def check_disable(self, result): + """Check the plugin's `disable` command result""" + raise NotImplementedError(self._override_me_msg) + + def make_fixture(self, request): + """Make a pytest fixture for this tracker + + The fixture ensures the plugin entry is in the same state + (enabled/disabled) after the test as it was before it. """ - if expected_updates is None: - expected_updates = {} + def cleanup(): + if self.original_enabled != self.enabled: + if self.original_enabled: + command = self.make_enable_command() + else: + command = self.make_disable_command() + command() - command = self.make_update_command(updates) - result = command() - self.attrs.update(updates) - self.attrs.update(expected_updates) - for key, value in self.attrs.items(): - if value is None: - del self.attrs[key] + request.addfinalizer(cleanup) - self.check_update(result, extra_keys=set(updates.keys()) | - set(expected_updates.keys())) + return super(EnableTracker, self).make_fixture(request) class Tracker(RetrievableTracker, SearchableTracker, MutableTracker, From 2e3d8c4f899204b17b589f66f56104e36b74fb14 Mon Sep 17 00:00:00 2001 From: David Kupka <dku...@redhat.com> Date: Thu, 26 Jan 2017 17:11:16 +0100 Subject: [PATCH 3/8] tests: tracker: Add ConfigTracker to test *config-{mod,show} commands --- ipatests/test_xmlrpc/tracker/base.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/ipatests/test_xmlrpc/tracker/base.py b/ipatests/test_xmlrpc/tracker/base.py index 58a2891..4852ff6 100644 --- a/ipatests/test_xmlrpc/tracker/base.py +++ b/ipatests/test_xmlrpc/tracker/base.py @@ -131,6 +131,7 @@ def find(self, all=False, raw=False): class MutableTracker(BaseTracker): update_keys = None + singlevalue_keys = None def make_update_command(self, updates): """Make function that modifies the entry using ${CMD}_mod""" @@ -313,6 +314,33 @@ def cleanup(): return super(EnableTracker, self).make_fixture(request) +class ConfigTracker(RetrievableTracker, MutableTracker): + def make_fixture(self, request): + """Make a pytest fixture for this tracker + + Make sure that the state of entry in the end is the same + it was in the begining. + """ + retrieve = self.make_retrieve_command(all=True) + res = retrieve()['result'] + original_state = {} + for k, v in res.items(): + if k in self.update_keys: + original_state[k] = v[0] if k in self.singlevalue_keys else v + + def revert(): + update = self.make_update_command(original_state) + try: + update() + except errors.EmptyModlist: + # ignore no change + pass + + request.addfinalizer(revert) + + return super(ConfigTracker, self).make_fixture(request) + + class Tracker(RetrievableTracker, SearchableTracker, MutableTracker, CreatableTracker): """Wraps and tracks modifications to a plugin LDAP entry object From 7472a9665b0e56ce2eb3f33dd2049e1c5e3627a0 Mon Sep 17 00:00:00 2001 From: David Kupka <dku...@redhat.com> Date: Wed, 1 Feb 2017 11:49:34 +0100 Subject: [PATCH 4/8] tests: tracker: Add CertmapTracker for testing certmap-* commands https://fedorahosted.org/freeipa/ticket/6542 --- ipatests/test_xmlrpc/objectclasses.py | 5 + ipatests/test_xmlrpc/tracker/certmap_plugin.py | 167 +++++++++++++++++++++++++ 2 files changed, 172 insertions(+) create mode 100644 ipatests/test_xmlrpc/tracker/certmap_plugin.py diff --git a/ipatests/test_xmlrpc/objectclasses.py b/ipatests/test_xmlrpc/objectclasses.py index 1ea020b..0a15a21 100644 --- a/ipatests/test_xmlrpc/objectclasses.py +++ b/ipatests/test_xmlrpc/objectclasses.py @@ -227,3 +227,8 @@ u'top', u'ipaca', ] + +certmaprule = [ + u'top', + u'ipacertmaprule', +] diff --git a/ipatests/test_xmlrpc/tracker/certmap_plugin.py b/ipatests/test_xmlrpc/tracker/certmap_plugin.py new file mode 100644 index 0000000..d61be5d --- /dev/null +++ b/ipatests/test_xmlrpc/tracker/certmap_plugin.py @@ -0,0 +1,167 @@ +# +# Copyright (C) 2017 FreeIPA Contributors see COPYING for license +# + +from ipapython.dn import DN +from ipatests.test_xmlrpc.tracker.base import Tracker, EnableTracker +from ipatests.test_xmlrpc import objectclasses +from ipatests.util import assert_deepequal + + +class CertmapruleTracker(Tracker, EnableTracker): + """ Tracker for testin certmaprule plugin """ + retrieve_keys = { + u'dn', + u'cn', + u'description', + u'ipacertmapissuer', + u'ipacertmapmaprule', + u'ipacertmapmatchrule', + u'associateddomain', + u'ipacertmappriority', + u'ipaenabledflag' + } + retrieve_all_keys = retrieve_keys | {u'objectclass'} + create_keys = retrieve_keys | {u'objectclass'} + update_keys = retrieve_keys - {u'dn'} + + def __init__(self, cn, description, ipacertmapissuer, ipacertmapmaprule, + ipacertmapmatchrule, associateddomain, ipacertmappriority, + default_version=None): + super(CertmapruleTracker, self).__init__( + default_version=default_version) + + self.dn = DN((u'cn', cn,), + self.api.env.container_certmaprules, + self.api.env.basedn) + self.options = { + u'description': description, + u'ipacertmapissuer': ipacertmapissuer, + u'ipacertmapmaprule': ipacertmapmaprule, + u'ipacertmapmatchrule': ipacertmapmatchrule, + u'associateddomain': associateddomain, + u'ipacertmappriority': ipacertmappriority, + } + + def make_create_command(self, dont_fill=()): + kwargs = {k: v for k, v in self.options.items() if k not in dont_fill} + + return self.make_command('certmaprule_add', self.name, **kwargs) + + def track_create(self, dont_fill=()): + self.attrs = { + 'dn': self.dn, + 'cn': [self.name], + 'ipaenabledflag': [u'TRUE'], + 'objectclass': objectclasses.certmaprule, + } + self.attrs.update({ + k: [v] for k, v in self.options.items() if k not in dont_fill + }) + self.exists = True + + def check_create(self, result): + assert_deepequal(dict( + value=self.name, + summary=u'Added Certificate Identity Mapping Rule "{}"' + u''.format(self.name), + result=self.filter_attrs(self.create_keys), + ), result) + + def create(self, dont_fill=()): + self.track_create(dont_fill) + command = self.make_create_command(dont_fill) + result = command() + self.check_create(result) + + def make_delete_command(self): + return self.make_command('certmaprule_del', self.name) + + def check_delete(self, result): + assert_deepequal( + dict( + value=[self.name], + summary=u'Deleted Certificate Identity Mapping Rule "{}"' + ''.format(self.name), + result=dict(failed=[]), + ), + result + ) + + def make_retrieve_command(self, all=False, raw=False): + return self.make_command('certmaprule_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('certmaprule_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 Certificate Identity Mapping Rule matched', + result=[expected], + ), + result + ) + + def make_update_command(self, updates): + return self.make_command('certmaprule_mod', self.name, **updates) + + def check_update(self, result, extra_keys=()): + assert_deepequal( + dict( + value=self.name, + summary=u'Modified Certificate Identity Mapping Rule "{}"' + u''.format(self.name), + result=self.filter_attrs(self.update_keys | set(extra_keys)), + ), + result + ) + + def make_enable_command(self): + return self.make_command('certmaprule_enable', self.name) + + def check_enable(self, result): + assert_deepequal( + dict( + value=self.name, + summary=u'Enabled Certificate Identity Mapping Rule "{}"' + u''.format(self.name), + result=True, + ), + result + ) + + def make_disable_command(self): + return self.make_command('certmaprule_disable', self.name) + + def check_disable(self, result): + assert_deepequal( + dict( + value=self.name, + summary=u'Disabled Certificate Identity Mapping Rule "{}"' + u''.format(self.name), + result=True, + ), + result + ) From df6af70ee81d3286cafe41d5aefe72e388f82f85 Mon Sep 17 00:00:00 2001 From: David Kupka <dku...@redhat.com> Date: Wed, 1 Feb 2017 11:52:00 +0100 Subject: [PATCH 5/8] tests: certmap: Add basic tests for certmaprule commands https://fedorahosted.org/freeipa/ticket/6542 --- ipatests/test_xmlrpc/test_certmap_plugin.py | 107 ++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 ipatests/test_xmlrpc/test_certmap_plugin.py diff --git a/ipatests/test_xmlrpc/test_certmap_plugin.py b/ipatests/test_xmlrpc/test_certmap_plugin.py new file mode 100644 index 0000000..9343f9a --- /dev/null +++ b/ipatests/test_xmlrpc/test_certmap_plugin.py @@ -0,0 +1,107 @@ +# +# Copyright (C) 2017 FreeIPA Contributors see COPYING for license +# + +import itertools +import pytest + +from ipapython.dn import DN +from ipatests.test_xmlrpc.xmlrpc_test import XMLRPC_test +from ipatests.test_xmlrpc.tracker.certmap_plugin import CertmapruleTracker + +certmaprule_create_params = { + u'cn': u'test_rule', + u'description': u'Certificate mapping and matching rule for test ' + u'purposes', + u'ipacertmapissuer': DN('CN=CA,O=EXAMPLE.ORG'), + u'ipacertmapmaprule': u'arbitrary free-form mapping rule defined and ' + u'consumed by SSSD', + u'ipacertmapmatchrule': u'arbitrary free-form matching rule defined ' + u'and consumed by SSSD', + u'associateddomain': u'example.org', + u'ipacertmappriority': u'1', +} + +certmaprule_update_params = { + u'description': u'Changed description', + u'ipacertmapissuer': DN('CN=Changed CA,O=OTHER.ORG'), + u'ipacertmapmaprule': u'changed arbitrary mapping rule', + u'ipacertmapmatchrule': u'changed arbitrary maching rule', + u'associateddomain': u'changed.example.org', + u'ipacertmappriority': u'5', +} + +certmaprule_optional_params = ( + 'description', + 'ipacertmapissuer', + 'ipacertmapmaprule', + 'ipacertmapmatchrule', + 'ipaassociateddomain', + 'ipacertmappriority', +) + +def dontfill_idfn(dont_fill): + return u"dont_fill=({})".format(', '.join([ + u"{}".format(d) for d in dont_fill + ])) + + +def update_idfn(update): + return ', '.join(["{}: {}".format(k, v) for k, v in update.items()]) + + +@pytest.fixture(scope='class') +def certmap_rule(request): + tracker = CertmapruleTracker(**certmaprule_create_params) + return tracker.make_fixture(request) + + +class TestCRUD(XMLRPC_test): + @pytest.mark.parametrize( + 'dont_fill', + itertools.chain(*[ + itertools.combinations(certmaprule_optional_params, l) + for l in range(len(certmaprule_optional_params)+1) + ]), + ids=dontfill_idfn, + ) + def test_create(self, dont_fill, certmap_rule): + certmap_rule.ensure_missing() + try: + certmap_rule.create(dont_fill) + finally: + certmap_rule.ensure_missing() + + def test_retrieve(self, certmap_rule): + certmap_rule.ensure_exists() + certmap_rule.retrieve() + + def test_find(self, certmap_rule): + certmap_rule.ensure_exists() + certmap_rule.find() + + @pytest.mark.parametrize('update', [ + dict(u) for l in range(1, len(certmaprule_update_params)+1) + for u in itertools.combinations( + certmaprule_update_params.items(), l) + ], + ids=update_idfn, + ) + def test_update(self, update, certmap_rule): + certmap_rule.ensure_missing() + certmap_rule.ensure_exists() + certmap_rule.update(update, {o: [v] for o, v in update.items()}) + + def test_delete(self, certmap_rule): + certmap_rule.ensure_exists() + certmap_rule.delete() + + +class TestEnableDisable(XMLRPC_test): + def test_disable(self, certmap_rule): + certmap_rule.ensure_exists() + certmap_rule.disable() + + def test_enable(self, certmap_rule): + certmap_rule.ensure_exists() + certmap_rule.enable() From 3d3bd8a1b5f29f2331119fbb2fea90c1a81efa12 Mon Sep 17 00:00:00 2001 From: David Kupka <dku...@redhat.com> Date: Tue, 24 Jan 2017 16:21:54 +0100 Subject: [PATCH 6/8] tests: certmap: Test permissions for certmap https://fedorahosted.org/freeipa/ticket/6542 --- ipatests/test_xmlrpc/test_certmap_plugin.py | 278 ++++++++++++++++++++++++++++ 1 file changed, 278 insertions(+) diff --git a/ipatests/test_xmlrpc/test_certmap_plugin.py b/ipatests/test_xmlrpc/test_certmap_plugin.py index 9343f9a..cdf667c 100644 --- a/ipatests/test_xmlrpc/test_certmap_plugin.py +++ b/ipatests/test_xmlrpc/test_certmap_plugin.py @@ -2,12 +2,18 @@ # Copyright (C) 2017 FreeIPA Contributors see COPYING for license # +from contextlib import contextmanager import itertools +from nose.tools import assert_raises import pytest +from ipalib import api, errors from ipapython.dn import DN from ipatests.test_xmlrpc.xmlrpc_test import XMLRPC_test from ipatests.test_xmlrpc.tracker.certmap_plugin import CertmapruleTracker +from ipatests.util import assert_deepequal +from ipatests.util import change_principal, unlock_principal_password + certmaprule_create_params = { u'cn': u'test_rule', @@ -40,6 +46,22 @@ 'ipacertmappriority', ) +CREATE_PERM = u'System: Add Certmap Rules' +READ_PERM = u'System: Read Certmap Rules' +UPDATE_PERM = u'System: Modify Certmap Rules' +DELETE_PERM = u'System: Delete Certmap Rules' + +certmaprule_permissions = { + u'C': CREATE_PERM, + u'R': READ_PERM, + u'U': UPDATE_PERM, + u'D': DELETE_PERM, +} + +CERTMAP_USER = u'cuser' +CERTMAP_PASSWD = 'Secret123' + + def dontfill_idfn(dont_fill): return u"dont_fill=({})".format(', '.join([ u"{}".format(d) for d in dont_fill @@ -105,3 +127,259 @@ def test_disable(self, certmap_rule): def test_enable(self, certmap_rule): certmap_rule.ensure_exists() certmap_rule.enable() + + +@contextmanager +def execute_with_expected(user, password, perms, exps, ok_expected=None): + """ + Run command as specified user. Check exception or return value + according provided rules. + + @param user Change to this user before calling the command + @param password User to change user + @param perms User has those permissions + @param exps Iterable containing tuple + (permission, exception_class, expected_result,) + If permission is missing command must raise exception of + exception_class. If exception class is None command must + raise Result(expected_result) + @param ok_expected When no permission is missing command must raise + Result(ok_expected) + """ + for perm, exception, expected in exps: + if perm not in perms: + break + else: + exception = None + expected = ok_expected + + with change_principal(user, password): + if exception: + with assert_raises(exception): + yield + else: + got = yield + if expected: + if got: + assert_deepequal(expected, got) + else: + assert("Command didn't returned") + + +def permissions_idfn(perms): + i = [] + for short_name, long_name in certmaprule_permissions.items(): + if long_name in perms: + i.append(short_name) + else: + i.append('-') + return ''.join(i) + + +def change_permissions_bindtype(perm, bindtype): + orig = api.Command.permission_show(perm)['result']['ipapermbindruletype'] + if orig != (bindtype,): + api.Command.permission_mod(perm, ipapermbindruletype=bindtype) + + return orig + + +@pytest.fixture(scope='class') +def bindtype_permission(request): + orig_bindtype = {} + # set bindtype to permission to actually test the permission + for perm_name in certmaprule_permissions.values(): + orig_bindtype[perm_name] = change_permissions_bindtype( + perm_name, u'permission') + + def finalize(): + for perm_name, bindtype in orig_bindtype.items(): + change_permissions_bindtype(perm_name, bindtype[0]) + + request.addfinalizer(finalize) + + +@pytest.fixture( + scope='class', + params=itertools.chain(*[ + itertools.combinations(certmaprule_permissions.values(), l) + for l in range(len(certmaprule_permissions.values())+1) + ]), + ids=permissions_idfn, +) +def certmap_user_permissions(request, bindtype_permission): + tmp_password = u'Initial123' + + priv_name = u'test_certmap_privilege' + role_name = u'test_certmap_role' + + api.Command.user_add(CERTMAP_USER, givenname=u'Certmap', sn=u'User', + userpassword=tmp_password) + unlock_principal_password(CERTMAP_USER, tmp_password, + CERTMAP_PASSWD) + + api.Command.privilege_add(priv_name) + for perm_name in request.param: + # add to privilege for user + api.Command.privilege_add_permission(priv_name, permission=perm_name) + api.Command.role_add(role_name) + api.Command.role_add_privilege(role_name, privilege=priv_name) + api.Command.role_add_member(role_name, user=CERTMAP_USER) + + def finalize(): + try: + api.Command.user_del(CERTMAP_USER) + except Exception: + pass + try: + api.Command.role_del(role_name) + except Exception: + pass + try: + api.Command.privilege_del(priv_name) + except Exception: + pass + + request.addfinalizer(finalize) + + return request.param + + +class TestPermission(XMLRPC_test): + def test_create(self, certmap_rule, certmap_user_permissions): + certmap_rule.ensure_missing() + + with execute_with_expected( + CERTMAP_USER, + CERTMAP_PASSWD, + certmap_user_permissions, + [ + (CREATE_PERM, errors.ACIError, None,), + (READ_PERM, errors.NotFound, None,), + ], + ): + certmap_rule.create() + + # Tracker sets 'exists' to True even when the create does not + # succeed so ensure_missing wouldn't be reliable here + try: + certmap_rule.delete() + except Exception: + pass + + def test_retrieve(self, certmap_rule, certmap_user_permissions): + certmap_rule.ensure_exists() + + with execute_with_expected( + CERTMAP_USER, + CERTMAP_PASSWD, + certmap_user_permissions, + [ + (READ_PERM, errors.NotFound, None,), + ], + ): + certmap_rule.retrieve() + + def test_find(self, certmap_rule, certmap_user_permissions): + certmap_rule.ensure_exists() + + expected_without_read = { + u'count': 0, + u'result': (), + u'summary': u'0 Certificate Identity Mapping Rules matched', + u'truncated': False, + } + expected_ok = { + u'count': 1, + u'result': [{ + k: (v,) for k, v in certmaprule_create_params.items() + }], + u'summary': u'1 Certificate Identity Mapping Rule matched', + u'truncated': False, + } + expected_ok[u'result'][0][u'dn'] = DN( + (u'cn', expected_ok[u'result'][0][u'cn'][0]), + api.env.container_certmaprules, + api.env.basedn, + ) + expected_ok[u'result'][0][u'ipaenabledflag'] = (u'TRUE',) + with execute_with_expected( + CERTMAP_USER, + CERTMAP_PASSWD, + certmap_user_permissions, + [ + (READ_PERM, None, expected_without_read,), + ], + expected_ok, + ): + find = certmap_rule.make_find_command() + find(**{k: v for k, v in certmaprule_create_params.items() + if k is not u'dn'}) + + def test_update(self, certmap_rule, certmap_user_permissions): + certmap_rule.ensure_missing() + certmap_rule.ensure_exists() + + with execute_with_expected( + CERTMAP_USER, + CERTMAP_PASSWD, + certmap_user_permissions, + [ + (READ_PERM, errors.NotFound, None,), + (UPDATE_PERM, errors.ACIError, None,), + ], + ): + certmap_rule.update( + certmaprule_update_params, + {o: [v] for o, v in certmaprule_update_params.items()}, + ) + + def test_delete(self, certmap_rule, certmap_user_permissions): + certmap_rule.ensure_exists() + + with execute_with_expected( + CERTMAP_USER, + CERTMAP_PASSWD, + certmap_user_permissions, + [ + (DELETE_PERM, errors.ACIError, None,), + ], + ): + certmap_rule.delete() + + # Tracker sets 'exists' to False even when the delete does not + # succeed so ensure_missing wouldn't be reliable here + try: + certmap_rule.delete() + except Exception: + pass + + def test_enable(self, certmap_rule, certmap_user_permissions): + certmap_rule.ensure_exists() + certmap_rule.disable() + + with execute_with_expected( + CERTMAP_USER, + CERTMAP_PASSWD, + certmap_user_permissions, + [ + (READ_PERM, errors.NotFound, None,), + (UPDATE_PERM, errors.ACIError, None,), + ], + ): + certmap_rule.enable() + + def test_disable(self, certmap_rule, certmap_user_permissions): + certmap_rule.ensure_exists() + certmap_rule.enable() + + with execute_with_expected( + CERTMAP_USER, + CERTMAP_PASSWD, + certmap_user_permissions, + [ + (READ_PERM, errors.NotFound, None,), + (UPDATE_PERM, errors.ACIError, None,), + ], + ): + certmap_rule.disable() From 73b3f807ab1c4158ac36328b58588813f7dc5c05 Mon Sep 17 00:00:00 2001 From: David Kupka <dku...@redhat.com> Date: Thu, 26 Jan 2017 17:20:23 +0100 Subject: [PATCH 7/8] tests: tracker: Add CertmapconfigTracker to tests certmapconfig-* commands https://fedorahosted.org/freeipa/ticket/6542 --- ipatests/test_xmlrpc/objectclasses.py | 8 ++++ ipatests/test_xmlrpc/tracker/certmap_plugin.py | 66 +++++++++++++++++++++++++- 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/ipatests/test_xmlrpc/objectclasses.py b/ipatests/test_xmlrpc/objectclasses.py index 0a15a21..c9ae724 100644 --- a/ipatests/test_xmlrpc/objectclasses.py +++ b/ipatests/test_xmlrpc/objectclasses.py @@ -232,3 +232,11 @@ u'top', u'ipacertmaprule', ] + +certmapconfig = [ + u'top', + u'ipaConfigObject', + u'nsContainer', + u'ipaCertMapContainer', + u'ipaCertMapConfigObject', +] diff --git a/ipatests/test_xmlrpc/tracker/certmap_plugin.py b/ipatests/test_xmlrpc/tracker/certmap_plugin.py index d61be5d..8991e69 100644 --- a/ipatests/test_xmlrpc/tracker/certmap_plugin.py +++ b/ipatests/test_xmlrpc/tracker/certmap_plugin.py @@ -3,8 +3,10 @@ # from ipapython.dn import DN -from ipatests.test_xmlrpc.tracker.base import Tracker, EnableTracker +from ipatests.test_xmlrpc.tracker.base import Tracker +from ipatests.test_xmlrpc.tracker.base import ConfigTracker, EnableTracker from ipatests.test_xmlrpc import objectclasses +from ipatests.test_xmlrpc.xmlrpc_test import fuzzy_string from ipatests.util import assert_deepequal @@ -165,3 +167,65 @@ def check_disable(self, result): ), result ) + + +class CertmapconfigTracker(ConfigTracker): + retrieve_keys = { + u'dn', + u'ipacertmappromptusername', + } + + retrieve_all_keys = retrieve_keys | { + u'cn', + u'objectclass', + u'aci', + u'ipacertmapversion', + u'ipaconfigstring' + } + update_keys = retrieve_keys - {u'dn'} + singlevalue_keys = {u'ipacertmappromptusername'} + + def __init__(self, default_version=None): + super(CertmapconfigTracker, self).__init__( + default_version=default_version) + + self.attrs = { + u'dn': DN(self.api.env.container_certmap, self.api.env.basedn), + u'cn': [self.api.env.container_certmap[0].value], + u'objectclass': objectclasses.certmapconfig, + u'ipacertmapversion': [u'1'], + u'ipaconfigstring': [u'CertMapVersion 1'], + u'aci': [fuzzy_string], + u'ipacertmappromptusername': self.api.Command.certmapconfig_show( + )[u'result'][u'ipacertmappromptusername'] + } + + def make_update_command(self, updates): + return self.make_command('certmapconfig_mod', **updates) + + def check_update(self, result, extra_keys=()): + assert_deepequal( + dict( + value=None, + summary=None, + result=self.filter_attrs(self.update_keys | set(extra_keys)), + ), + result + ) + + def make_retrieve_command(self, all=False, raw=False): + return self.make_command('certmapconfig_show', 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=None, + summary=None, + result=expected, + ), + result + ) From 51765c7e7fe32cb6abafc51ef4da199bd1e7a45a Mon Sep 17 00:00:00 2001 From: David Kupka <dku...@redhat.com> Date: Thu, 26 Jan 2017 17:40:42 +0100 Subject: [PATCH 8/8] tests: certmap: Add test for certmapconfig-{mod,show} https://fedorahosted.org/freeipa/ticket/6542 --- ipatests/test_xmlrpc/test_certmap_plugin.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/ipatests/test_xmlrpc/test_certmap_plugin.py b/ipatests/test_xmlrpc/test_certmap_plugin.py index cdf667c..7756415 100644 --- a/ipatests/test_xmlrpc/test_certmap_plugin.py +++ b/ipatests/test_xmlrpc/test_certmap_plugin.py @@ -10,7 +10,8 @@ from ipalib import api, errors from ipapython.dn import DN from ipatests.test_xmlrpc.xmlrpc_test import XMLRPC_test -from ipatests.test_xmlrpc.tracker.certmap_plugin import CertmapruleTracker +from ipatests.test_xmlrpc.tracker.certmap_plugin import (CertmapruleTracker, + CertmapconfigTracker) from ipatests.util import assert_deepequal from ipatests.util import change_principal, unlock_principal_password @@ -46,6 +47,8 @@ 'ipacertmappriority', ) +certmapconfig_update_params = {u'ipacertmappromptusername': u'TRUE'} + CREATE_PERM = u'System: Add Certmap Rules' READ_PERM = u'System: Read Certmap Rules' UPDATE_PERM = u'System: Modify Certmap Rules' @@ -78,6 +81,12 @@ def certmap_rule(request): return tracker.make_fixture(request) +@pytest.fixture(scope='class') +def certmap_config(request): + tracker = CertmapconfigTracker() + return tracker.make_fixture(request) + + class TestCRUD(XMLRPC_test): @pytest.mark.parametrize( 'dont_fill', @@ -129,6 +138,17 @@ def test_enable(self, certmap_rule): certmap_rule.enable() +class TestConfig(XMLRPC_test): + def test_config_mod(self, certmap_config): + certmap_config.update( + certmapconfig_update_params, + {k: [v] for k, v in certmapconfig_update_params.items()} + ) + + def test_config_show(self, certmap_config): + certmap_config.retrieve() + + @contextmanager def execute_with_expected(user, password, perms, exps, ok_expected=None): """
-- 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