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 8fa8a3e8d3c9532d2cb53b0cc3b75705fd9ad87b Mon Sep 17 00:00:00 2001
From: David Kupka <dku...@redhat.com>
Date: Wed, 1 Feb 2017 11:36:32 +0100
Subject: [PATCH 01/10] 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 d05d99aa2e065f98171405d62b52c363173e1c67 Mon Sep 17 00:00:00 2001
From: David Kupka <dku...@redhat.com>
Date: Wed, 1 Feb 2017 11:37:00 +0100
Subject: [PATCH 02/10] 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 eb45d20876bb0e60f6ea91ecdb9c40f5a0ce8e90 Mon Sep 17 00:00:00 2001
From: David Kupka <dku...@redhat.com>
Date: Thu, 26 Jan 2017 17:11:16 +0100
Subject: [PATCH 03/10] 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 381bf201e0906e7fa0675acbb52129f5c07e4007 Mon Sep 17 00:00:00 2001
From: David Kupka <dku...@redhat.com>
Date: Wed, 1 Feb 2017 11:49:34 +0100
Subject: [PATCH 04/10] 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 36082a0252fbf31d4c6450b3ecba68b649900b9c Mon Sep 17 00:00:00 2001
From: David Kupka <dku...@redhat.com>
Date: Wed, 1 Feb 2017 11:52:00 +0100
Subject: [PATCH 05/10] 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 1781226dee2fd5900cbe79e07bd5329dfd300012 Mon Sep 17 00:00:00 2001
From: David Kupka <dku...@redhat.com>
Date: Tue, 24 Jan 2017 16:21:54 +0100
Subject: [PATCH 06/10] 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 eb8240610c4544bda74bcfce21bc78387cea82df Mon Sep 17 00:00:00 2001
From: David Kupka <dku...@redhat.com>
Date: Thu, 26 Jan 2017 17:20:23 +0100
Subject: [PATCH 07/10] 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 62225edccfa8217c88175844bafc391038e03c1b Mon Sep 17 00:00:00 2001
From: David Kupka <dku...@redhat.com>
Date: Thu, 26 Jan 2017 17:40:42 +0100
Subject: [PATCH 08/10] 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):
     """

From 8622682700b189914e784490ab1c833fef23d6f7 Mon Sep 17 00:00:00 2001
From: David Kupka <dku...@redhat.com>
Date: Thu, 9 Feb 2017 14:01:23 +0100
Subject: [PATCH 09/10] tests: tracker: Add CertmapdataMixin tracker

https://fedorahosted.org/freeipa/ticket/6542
---
 ipatests/test_xmlrpc/tracker/certmapdata.py | 149 ++++++++++++++++++++++++++++
 1 file changed, 149 insertions(+)
 create mode 100644 ipatests/test_xmlrpc/tracker/certmapdata.py

diff --git a/ipatests/test_xmlrpc/tracker/certmapdata.py b/ipatests/test_xmlrpc/tracker/certmapdata.py
new file mode 100644
index 0000000..30cfa1d
--- /dev/null
+++ b/ipatests/test_xmlrpc/tracker/certmapdata.py
@@ -0,0 +1,149 @@
+#
+# Copyright (C) 2015  FreeIPA Contributors see COPYING for license
+#
+from cryptography import x509
+from cryptography.hazmat.backends import default_backend
+from nose.tools import assert_raises
+
+from ipalib.errors import MutuallyExclusiveError, RequirementError
+from ipapython.dn import DN
+from ipatests.util import assert_deepequal
+
+
+class CertmapdataMixin(object):
+    certmapdata_options = {u'issuer', u'subject', u'usercertificate',
+                           u'ipacertmapdata'}
+
+    def _data_from_options(self, **options):
+        issuer = None
+        subject = None
+
+        def any_in(keys, mapping):
+            return any([(key in mapping) for key in keys])
+
+        if not any_in(self.certmapdata_options, options):
+            raise RequirementError(name=u'certmapdata')
+
+        if (
+            any_in((u'ipacertmapdata', u'usercertificate',), options) and
+            any_in((u'issuer', u'subject',), options)
+        ):
+            raise MutuallyExclusiveError(reason=u'Mutually exclusive options '
+                                                u'provided at the same time.')
+
+        try:
+            data = options[u'ipacertmapdata']
+        except KeyError:
+            pass
+        else:
+            if not isinstance(data, list):
+                data = [data]
+            return data
+
+        try:
+            certs = options[u'usercertificate']
+        except KeyError:
+            pass
+        else:
+            if not isinstance(certs, list):
+                certs = [certs]
+
+            certmapdata = []
+
+            for cert in certs:
+                cert = x509.load_pem_x509_certificate(
+                    (b'-----BEGIN CERTIFICATE-----\n'
+                     b'{}-----END CERTIFICATE-----\n'
+                     .format(cert)),
+                    default_backend()
+                )
+                issuer = DN(cert.issuer).x500_text()
+                subject = DN(cert.subject).x500_text()
+
+                certmapdata.append(
+                    u'X509:<I>{i}<S>{s}'.format(i=issuer, s=subject)
+                )
+
+            return certmapdata
+
+        try:
+            issuer = DN(options[u'issuer']).x500_text()
+        except KeyError:
+            pass
+
+        try:
+            subject = DN(options[u'subject']).x500_text()
+        except KeyError:
+            pass
+
+        data = u'X509:'
+        if issuer:
+            data += u'<I>{i}'.format(i=issuer)
+        if subject:
+            data += u'<S>{s}'.format(s=subject)
+
+        return [data]
+
+    def _make_add_certmap(self):
+        raise NotImplementedError("_make_add_certmap method must be "
+                                  "implemented in instance.")
+
+    def _make_remove_certmap(self):
+        raise NotImplementedError("_make_remove_certmap method must be "
+                                  "implemented in instance.")
+
+    def add_certmap(self, **kwargs):
+        cmd = self._make_add_certmap()
+
+        try:
+            expected_certmapdata = self._data_from_options(**kwargs)
+        except Exception as e:
+            with assert_raises(type(e)):
+                cmd(**kwargs)
+        else:
+            result = cmd(**kwargs)
+            self.attrs.setdefault(u'ipacertmapdata', []).extend(
+                expected_certmapdata)
+
+            expected = dict(
+                summary=(u'Added certificate mappings to user '
+                         u'"{}"'.format(self.name)),
+                value=self.name,
+                result=dict(
+                    uid=(self.name,),
+                ),
+            )
+
+            if self.attrs[u'ipacertmapdata']:
+                expected[u'result'][u'ipacertmapdata'] = (
+                    self.attrs[u'ipacertmapdata'])
+
+            assert_deepequal(expected, result)
+
+    def remove_certmap(self, **kwargs):
+        cmd = self._make_remove_certmap()
+
+        try:
+            expected_certmapdata = self._data_from_options(**kwargs)
+        except Exception as e:
+            with assert_raises(type(e)):
+                cmd(**kwargs)
+        else:
+            result = cmd(**kwargs)
+
+            for data in expected_certmapdata:
+                self.attrs[u'ipacertmapdata'].remove(data)
+
+            expected = dict(
+                summary=(u'Removed certificate mappings from user '
+                         u'"{}"'.format(self.name)),
+                value=self.name,
+                result=dict(
+                    uid=(self.name,),
+                ),
+            )
+            if self.attrs[u'ipacertmapdata']:
+                expected[u'result'][u'ipacertmapdata'] = (
+                    self.attrs[u'ipacertmapdata'])
+
+            assert_deepequal(expected, result)

From 5625e955af8893ee4d3a7d1bd63694f80d731f96 Mon Sep 17 00:00:00 2001
From: David Kupka <dku...@redhat.com>
Date: Thu, 9 Feb 2017 14:02:12 +0100
Subject: [PATCH 10/10] tests: certmap: Add test for user-{add,remove}-certmap

https://fedorahosted.org/freeipa/ticket/6542
---
 ipatests/test_xmlrpc/test_certmap_plugin.py | 59 +++++++++++++++++++++++++++++
 ipatests/test_xmlrpc/tracker/user_plugin.py | 10 ++++-
 2 files changed, 68 insertions(+), 1 deletion(-)

diff --git a/ipatests/test_xmlrpc/test_certmap_plugin.py b/ipatests/test_xmlrpc/test_certmap_plugin.py
index 7756415..a0e7efc 100644
--- a/ipatests/test_xmlrpc/test_certmap_plugin.py
+++ b/ipatests/test_xmlrpc/test_certmap_plugin.py
@@ -12,6 +12,7 @@
 from ipatests.test_xmlrpc.xmlrpc_test import XMLRPC_test
 from ipatests.test_xmlrpc.tracker.certmap_plugin import (CertmapruleTracker,
                                                          CertmapconfigTracker)
+from ipatests.test_xmlrpc.tracker.user_plugin import UserTracker
 from ipatests.util import assert_deepequal
 from ipatests.util import change_principal, unlock_principal_password
 
@@ -149,6 +150,64 @@ def test_config_show(self, certmap_config):
         certmap_config.retrieve()
 
 
+certmapdata_create_params = {
+    u'issuer': u'CN=CA,O=EXAMPLE.ORG',
+    u'subject': u'CN={},O=EXAMPLE.ORG'.format(CERTMAP_USER),
+    u'ipacertmapdata': (u'X509:<I>O=EXAMPLE.ORG,CN=CA'
+                        u'<S>O=EXAMPLE.ORG,CN={}'.format(CERTMAP_USER)),
+    u'usercertificate': (
+        u'MIICwzCCAaugAwIBAgICP9wwDQYJKoZIhvcNAQELBQAwIzEUMBIGA1UEChMLRVhB\n\r'
+        'TVBMRS5PUkcxCzAJBgNVBAMTAkNBMB4XDTE3MDIxMDEzMjAyNVoXDTE3MDUxMDEz\n\r'
+        'MjAyNVowJjEUMBIGA1UEChMLRVhBTVBMRS5PUkcxDjAMBgNVBAMTBWN1c2VyMIIB\n\r'
+        'IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlEBxZ6RULSZZ+nW1YfJUfCaX\n\r'
+        'wHIIWJeAoU98m7dbdUtkZMgLCPXiceIkCkcHu0DLS3wYlsL6VDG+0nIpT56Qkxph\n\r'
+        '+qpWGdVptnuVZ5dEthbluoopzxpkAAz3ywM3NqfTCM78G9GQvftUZEOlwnfyEBbY\n\r'
+        'XXs2wBhynrVTZcpL+ORXMpzVAallU63/YUExNvBzHlqdGHy+pPJSw1gsRTpLm75p\n\r'
+        '36r3bn/5cnIih1WUygC0WnjxXQwqOdGUauBp/Z8JVRuLSe8qbPfcl/voQGnSt3u2\n\r'
+        '9CFkDKpMMp6pv/3RbnOmMwSZGO0/n4G13qEdoneIghF1SE9qaxFGWk8mSDYc5QID\n\r'
+        'AQABMA0GCSqGSIb3DQEBCwUAA4IBAQCKrvO7AUCt9YOyJ0RioDxgQZVJqQ0/E877\n\r'
+        'vBP3HPwN0xkiGgASKtEDi3uLWGQfDgJFX5qXtK1qu7yiM86cbKq9PlkLTC6UowjP\n\r'
+        'iaFAaVBwbS3nLVo731gn5cCJqZ0QrDt2NDiXaGxo4tBcqc/Y/taw5Py3f59tMDk6\n\r'
+        'TnTmKmFnI+hB4+oxZhpcFj6bX35XMJXnRyekraE5VWtSbq57SXiRnINW4gNS5B6w\n\r'
+        'dYUGo551tfFq+DOnSl0H7NnnL3q4VzCH/1AMOownorgVsw4LqMgY0NiaD/yqJyKe\n\r'
+        'SggODAjRznNYx/Sk/At/eStqDxMHjP9X8AucGFIt76bEwHAAL2uu\n'
+    ),
+}
+
+
+@pytest.fixture
+def certmap_user(request):
+    user = UserTracker(CERTMAP_USER, u'certmap', u'user')
+    return user.make_fixture(request)
+
+
+def addcertmap_id(options):
+    if options:
+        return u', '.join([k for k in options])
+    else:
+        return u' '
+
+
+class TestAddRemoveCertmap(XMLRPC_test):
+    @pytest.mark.parametrize(
+        'options', [
+            dict(o) for l in range(len(certmapdata_create_params)+1)
+            for o in itertools.combinations(
+                certmapdata_create_params.items(), l)
+        ],
+        ids=addcertmap_id,
+    )
+    def test_add_certmap(self, options, certmap_user):
+        certmap_user.ensure_exists()
+        certmap_user.add_certmap(**options)
+        certmap_user.ensure_missing()
+
+    def test_remove_certmap(self, certmap_user):
+        certmap_user.ensure_exists()
+        certmap_user.add_certmap(ipacertmapdata=u'rawdata')
+        certmap_user.remove_certmap(ipacertmapdata=u'rawdata')
+
+
 @contextmanager
 def execute_with_expected(user, password, perms, exps, ok_expected=None):
     """
diff --git a/ipatests/test_xmlrpc/tracker/user_plugin.py b/ipatests/test_xmlrpc/tracker/user_plugin.py
index d57db93..e1f2da5 100644
--- a/ipatests/test_xmlrpc/tracker/user_plugin.py
+++ b/ipatests/test_xmlrpc/tracker/user_plugin.py
@@ -13,12 +13,13 @@
     fuzzy_digits, fuzzy_uuid, raises_exact)
 from ipatests.test_xmlrpc.tracker.base import Tracker
 from ipatests.test_xmlrpc.tracker.kerberos_aliases import KerberosAliasMixin
+from ipatests.test_xmlrpc.tracker.certmapdata import CertmapdataMixin
 
 if six.PY3:
     unicode = str
 
 
-class UserTracker(KerberosAliasMixin, Tracker):
+class UserTracker(CertmapdataMixin, KerberosAliasMixin, Tracker):
     """ Class for host plugin like tests """
 
     retrieve_keys = {
@@ -530,3 +531,10 @@ def _make_add_alias_cmd(self):
 
     def _make_remove_alias_cmd(self):
         return self.make_command('user_remove_principal', self.name)
+
+    # Certificate identity mapping methods
+    def _make_add_certmap(self):
+        return self.make_command('user_add_certmapdata', self.name)
+
+    def _make_remove_certmap(self):
+        return self.make_command('user_remove_certmapdata', self.name)
-- 
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