URL: https://github.com/freeipa/freeipa/pull/987
Author: tomaskrizek
 Title: #987: Backport PR 974 to ipa-4-5
Action: opened

PR body:
"""
This PR was opened automatically because PR #974 was pushed to master and 
backport to ipa-4-5 is required.
"""

To pull the PR as Git branch:
git remote add ghfreeipa https://github.com/freeipa/freeipa
git fetch ghfreeipa pull/987/head:pr987
git checkout pr987
From a4053652ebb2380939066d955727f9189123e422 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.

https://pagure.io/freeipa/issue/7105
---
 ipatests/test_xmlrpc/tracker/base.py | 286 +++++++++++++++++++++--------------
 1 file changed, 172 insertions(+), 114 deletions(-)

diff --git a/ipatests/test_xmlrpc/tracker/base.py b/ipatests/test_xmlrpc/tracker/base.py
index aa88e6bee2..5c9456c035 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 RetrievalTracker(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 SearchTracker(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 ModificationTracker(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 CreationTracker(BaseTracker):
+    create_keys = None
+
+    def __init__(self, default_version=None):
+        super(CreationTracker, 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,15 +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"""
-        raise NotImplementedError(self._override_me_msg)
+    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(CreationTracker, self).make_fixture(request)
 
     def find(self, all=False, raw=False):
         """Helper function to search for this hosts and check the result"""
@@ -282,6 +290,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(RetrievalTracker, SearchTracker, ModificationTracker,
+              CreationTracker):
+    """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 0db0bf2a0b618d03a9dfc77c082aebaa685cbe56 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

https://pagure.io/freeipa/issue/7105
---
 ipatests/test_xmlrpc/tracker/base.py | 66 ++++++++++++++++++++++++------------
 1 file changed, 44 insertions(+), 22 deletions(-)

diff --git a/ipatests/test_xmlrpc/tracker/base.py b/ipatests/test_xmlrpc/tracker/base.py
index 5c9456c035..61caf10bf2 100644
--- a/ipatests/test_xmlrpc/tracker/base.py
+++ b/ipatests/test_xmlrpc/tracker/base.py
@@ -259,36 +259,58 @@ def cleanup():
 
         return super(CreationTracker, self).make_fixture(request)
 
-    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)
+
+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 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(RetrievalTracker, SearchTracker, ModificationTracker,

From 639f7f13cb9178a0ec62e8b6b5311b6831268698 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 ConfigurationTracker to test
 *config-{mod,show} commands

https://pagure.io/freeipa/issue/7105
---
 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 61caf10bf2..d41b1ac2d1 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 ModificationTracker(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 ConfigurationTracker(RetrievalTracker, ModificationTracker):
+    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(ConfigurationTracker, self).make_fixture(request)
+
+
 class Tracker(RetrievalTracker, SearchTracker, ModificationTracker,
               CreationTracker):
     """Wraps and tracks modifications to a plugin LDAP entry object

From 881b73f7b46501228458a442726f6cbb4c49585d 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://pagure.io/freeipa/issue/7105
---
 ipatests/test_xmlrpc/objectclasses.py          |   5 +
 ipatests/test_xmlrpc/tracker/certmap_plugin.py | 165 +++++++++++++++++++++++++
 2 files changed, 170 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 1ea020b18f..0a15a211cc 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 0000000000..bfedf916df
--- /dev/null
+++ b/ipatests/test_xmlrpc/tracker/certmap_plugin.py
@@ -0,0 +1,165 @@
+#
+# 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'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, 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'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 d4085acdcb295c37926ab967247b524d2f915340 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://pagure.io/freeipa/issue/7105
---
 ipatests/test_xmlrpc/test_certmap_plugin.py | 103 ++++++++++++++++++++++++++++
 1 file changed, 103 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 0000000000..0991871dd9
--- /dev/null
+++ b/ipatests/test_xmlrpc/test_certmap_plugin.py
@@ -0,0 +1,103 @@
+#
+# 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'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': api.env.domain,
+        u'ipacertmappriority': u'1',
+}
+
+certmaprule_update_params = {
+        u'description': u'Changed description',
+        u'ipacertmapmaprule': u'changed arbitrary mapping rule',
+        u'ipacertmapmatchrule': u'changed arbitrary maching rule',
+        u'ipacertmappriority': u'5',
+}
+
+certmaprule_optional_params = (
+    'description',
+    '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 7ab84563aa7dcfecb7bf77400b66b79f7edcd2f8 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://pagure.io/freeipa/issue/7105
---
 ipatests/test_xmlrpc/test_certmap_plugin.py | 304 ++++++++++++++++++++++++++++
 1 file changed, 304 insertions(+)

diff --git a/ipatests/test_xmlrpc/test_certmap_plugin.py b/ipatests/test_xmlrpc/test_certmap_plugin.py
index 0991871dd9..75c4186b50 100644
--- a/ipatests/test_xmlrpc/test_certmap_plugin.py
+++ b/ipatests/test_xmlrpc/test_certmap_plugin.py
@@ -3,11 +3,16 @@
 #
 
 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',
@@ -36,6 +41,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
@@ -101,3 +122,286 @@ def test_disable(self, certmap_rule):
     def test_enable(self, certmap_rule):
         certmap_rule.ensure_exists()
         certmap_rule.enable()
+
+
+class EWE(object):
+    """
+    Context manager that checks the outcome of wrapped statement executed
+    under specified user against specified expected outcome based on permission
+    given to the user.
+
+    """
+    def __init__(self, user, password):
+        """
+        @param user     Change to this user before calling the command
+        @param password Password to use for user change
+        """
+        self.user = user
+        self.password = password
+        self.returned = False
+        self.value = None
+
+    def __call__(self, perms, exps, ok_expected=None):
+        """
+        @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 use send method with the result as the only
+                        parameter
+        @param ok_expected  When no permission is missing command must send
+                            ok_expected
+        """
+        for perm, exception, expected in exps:
+            if perm not in perms:
+                break
+        else:
+            exception = None
+            expected = ok_expected
+
+        self.exception = exception
+        self.expected = expected
+
+        return self
+
+    def send(self, value):
+        """
+        Use send method to report result of executed statement to EWE
+        """
+        self.returned = True
+        self.value = value
+
+    def __enter__(self):
+        self.returned = False
+        self.value = None
+
+        self.change_principal_cm = change_principal(self.user, self.password)
+        self.change_principal_cm.__enter__()  # pylint: disable=no-member
+
+        if self.exception:
+            self.assert_raises_cm = assert_raises(self.exception)
+            self.assert_raises_cm.__enter__()
+
+        return self
+
+    def __exit__(self, exc_type, exc_value, tb):
+        self.change_principal_cm.__exit__(  # pylint: disable=no-member
+            exc_type, exc_value, tb)
+        if self.exception:
+            return self.assert_raises_cm.__exit__(exc_type, exc_value, tb)
+        else:
+            if self.expected and self.returned:
+                assert_deepequal(self.expected, self.value)
+            elif self.expected:
+                assert False, "Value expected but not provided"
+            elif self.returned:
+                assert False, "Value provided but not expected"
+
+
+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):
+    execute_with_expected = EWE(CERTMAP_USER, CERTMAP_PASSWD)
+
+    def test_create(self, certmap_rule, certmap_user_permissions):
+        certmap_rule.ensure_missing()
+
+        with self.execute_with_expected(
+            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 self.execute_with_expected(
+            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 self.execute_with_expected(
+            certmap_user_permissions,
+            [
+                (READ_PERM, None, expected_without_read,),
+            ],
+            expected_ok,
+        ) as ewe:
+            find = certmap_rule.make_find_command()
+            res = find(**{k: v for k, v in certmaprule_create_params.items()
+                          if k is not u'dn'})
+            ewe.send(res)
+
+    def test_update(self, certmap_rule, certmap_user_permissions):
+        certmap_rule.ensure_missing()
+        certmap_rule.ensure_exists()
+
+        with self.execute_with_expected(
+            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 self.execute_with_expected(
+            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 self.execute_with_expected(
+            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 self.execute_with_expected(
+            certmap_user_permissions,
+            [
+                (READ_PERM, errors.NotFound, None,),
+                (UPDATE_PERM, errors.ACIError, None,),
+            ],
+        ):
+            certmap_rule.disable()

From 0c4b133791d7eef62ec2e6c9da952ffc81bc2f03 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://pagure.io/freeipa/issue/7105
---
 ipatests/test_xmlrpc/objectclasses.py          |  6 +++
 ipatests/test_xmlrpc/tracker/certmap_plugin.py | 62 +++++++++++++++++++++++++-
 2 files changed, 67 insertions(+), 1 deletion(-)

diff --git a/ipatests/test_xmlrpc/objectclasses.py b/ipatests/test_xmlrpc/objectclasses.py
index 0a15a211cc..a4f140930b 100644
--- a/ipatests/test_xmlrpc/objectclasses.py
+++ b/ipatests/test_xmlrpc/objectclasses.py
@@ -232,3 +232,9 @@
     u'top',
     u'ipacertmaprule',
 ]
+
+certmapconfig = [
+    u'top',
+    u'nsContainer',
+    u'ipaCertMapConfigObject',
+]
diff --git a/ipatests/test_xmlrpc/tracker/certmap_plugin.py b/ipatests/test_xmlrpc/tracker/certmap_plugin.py
index bfedf916df..1364f5a943 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 ConfigurationTracker, EnableTracker
 from ipatests.test_xmlrpc import objectclasses
+from ipatests.test_xmlrpc.xmlrpc_test import fuzzy_string
 from ipatests.util import assert_deepequal
 
 
@@ -163,3 +165,61 @@ def check_disable(self, result):
             ),
             result
         )
+
+
+class CertmapconfigTracker(ConfigurationTracker):
+    retrieve_keys = {
+        u'dn',
+        u'ipacertmappromptusername',
+    }
+
+    retrieve_all_keys = retrieve_keys | {
+        u'cn',
+        u'objectclass',
+        u'aci',
+    }
+    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'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 562821a2d961cce886b2a7b78f5ebe59b4cabc70 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://pagure.io/freeipa/issue/7105
---
 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 75c4186b50..b1f30aa199 100644
--- a/ipatests/test_xmlrpc/test_certmap_plugin.py
+++ b/ipatests/test_xmlrpc/test_certmap_plugin.py
@@ -9,7 +9,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
 
@@ -41,6 +42,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'
@@ -73,6 +76,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',
@@ -124,6 +133,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()
+
+
 class EWE(object):
     """
     Context manager that checks the outcome of wrapped statement executed

From fdd9d78dffaee154cfe75b29ab1de783d351b976 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://pagure.io/freeipa/issue/7105
---
 ipatests/test_xmlrpc/tracker/certmapdata.py | 135 ++++++++++++++++++++++++++++
 1 file changed, 135 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 0000000000..b869640f88
--- /dev/null
+++ b/ipatests/test_xmlrpc/tracker/certmapdata.py
@@ -0,0 +1,135 @@
+#
+# 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'certificate',
+                           u'ipacertmapdata'}
+
+    def _data_from_options(self, **options):
+        issuer = None
+        subject = None
+
+        if not self.certmapdata_options & set(options):
+            raise RequirementError(name=u'certmapdata')
+
+        if ({u'issuer', u'subject'} & set(options) and
+                {u'ipacertmapdata', u'certificate'} & set(options)):
+            raise MutuallyExclusiveError(reason=u'Mutually exclusive options '
+                                                u'provided at the same time.')
+
+        if u'issuer' in options and u'subject' not in options:
+            raise RequirementError(name=u'subject')
+
+        if u'subject' in options and u'issuer' not in options:
+            raise RequirementError(name=u'issuer')
+
+        if {u'ipacertmapdata', u'certificate'} & set(options):
+            try:
+                data = options[u'ipacertmapdata']
+            except KeyError:
+                data = []
+            else:
+                if not isinstance(data, list):
+                    data = [data]
+
+            try:
+                certs = options[u'certificate']
+            except KeyError:
+                certs = []
+            else:
+                if not isinstance(certs, list):
+                    certs = [certs]
+
+            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()
+
+                data.append(
+                    u'X509:<I>{i}<S>{s}'.format(i=issuer, s=subject)
+                )
+        else:
+            issuer = DN(options[u'issuer']).x500_text()
+            subject = DN(options[u'subject']).x500_text()
+
+            data = [u'X509:<I>{i}<S>{s}'.format(i=issuer, s=subject)]
+
+        return set(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 4eb750bef0fe9812ebc2e760a0cf12fa24198506 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://pagure.io/freeipa/issue/7105
---
 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 b1f30aa199..e927f9d3a2 100644
--- a/ipatests/test_xmlrpc/test_certmap_plugin.py
+++ b/ipatests/test_xmlrpc/test_certmap_plugin.py
@@ -11,6 +11,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
 
@@ -144,6 +145,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'certificate': (
+        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')
+
+
 class EWE(object):
     """
     Context manager that checks the outcome of wrapped statement executed
diff --git a/ipatests/test_xmlrpc/tracker/user_plugin.py b/ipatests/test_xmlrpc/tracker/user_plugin.py
index d57db930dd..e1f2da5a52 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)
_______________________________________________
FreeIPA-devel mailing list -- freeipa-devel@lists.fedorahosted.org
To unsubscribe send an email to freeipa-devel-le...@lists.fedorahosted.org

Reply via email to