[Freeipa-devel] [PATCH] Password vault

Endi Sukma Dewata edewata at redhat.com
Thu May 21 15:45:04 UTC 2015


Please take a look at the new patch.

On 5/20/2015 1:53 AM, Jan Cholasta wrote:
>> I suppose you meant you're OK with not adding host vaults now?
>
> Yes.
>
>> The only way to know if the design will be future proof is if we have at
>> least some idea how it will be used. Without that there is no guarantee.
>>
>> Host principals have this form: host/<hostname>@<realm>, so with the
>> current code they will be considered a service and will have a service
>> container.
>>
>> Do you want to add a new cn=hosts container just for hosts? Unless we
>> have a specific reason (i.e. use case) I don't see a need to add
>> specific code for hosts now, or at least until we get the core vault
>> functionality working.
>
> The reason is consistency. Private vaults should be available for all
> identities, because anything else would be an arbitrary limitation
> (which is not elegant). If private vaults were available for all
> identities, we would need a container for host vaults. I'm not saying
> the container has to be added now, but there should at least be a check
> to reject requests when the authenticated identity is a host (i.e.
> context.principal.startswith('host/')).

In the previous patch, since a host was considered a service it could 
have private vaults too. But anyway, I added the code to reject host 
principals as you requested.

>>>>> When is the self.api actually initialized? Can we initialize the
>>>>> container_dn (or base_dn as in the original code) attribute
>>>>> immediately
>>>>> after that?
>>>
>>> Not yet, but this will be fixed in the future. (Also, container_dn is
>>> part of the LDAPObject API, unlike base_dn used in the original code.)
>>
>> Is there a ticket for this?
>
> I don't think there is a ticket for this particular issue.

Added TODOs in the code.

>>>> This change is not included. The code will now obtain the values from
>>>> apilib.api.env at init time and store it in class attributes so it can
>>>> be reused.
>>>>
>>>>      container_dn = api.env.container_vault
>>>>      base_dn = DN(container_dn, api.env.basedn)
>>>
>>> Sorry, but no. Please just follow the best practice instead of trying to
>>> invent something new. This is not the right time and place to discuss
>>> this. We should be discussing the vault, not framework idiosyncracies.
>>
>> OK.
>
> Thanks for understanding.

Changed the code as requested.

>> There is an obvious inefficiency here: all containers in the path
>> (including the built-in ones) will be re-added on every vault-add
>> operation.
>>
>> I don't see anything wrong with recursions, especially if it works more
>> efficiently since only the immediate parent will be re-added.
>
> I tend to stick to the "don't use recursion in production code" rule,
> epsecially in simple cases like this one.

I think the rule is kind of misguided. Recursion might be considered bad 
if it goes very deep thus consuming so much stack space, which is not 
the case here given the short and flat tree topology. Or, if it's 
unnecessary such as a tail-recursion, which is not the case here either.

>> So for example, with the loop every time you add a private vault you're
>> guaranteed to have three failed LDAP Add operations whereas with the
>> recursion there's only one failed operation.
>
> This is not about loop vs. recursion, this is about the used approach.
> Your code can be easily transformed into a loop while keeping the same
> approach:
>
>      entries = []
>
>      while dn:
>          assert dn.endswith(self.base_dn)
>
>          rdn = dn[0]
>          entry = self.backend.make_entry(
>              dn,
>              {
>                  'objectclass': ['nsContainer'],
>                  'cn': rdn['cn'],
>              })
>
>          # if entry can be added, return
>          try:
>              self.backend.add_entry(entry)
>              break
>
>          except errors.NotFound:
>              pass
>
>          # otherwise, create parent entry first
>          dn = DN(*dn[1:])
>          entries.insert(0, entry)
>
>      for entry in entries:
>          # then create the entry itself again
>          self.backend.add_entry(entry)
>
>>
>> Do you still want to use the loop?
>
> Yes please.

This algorithm is recursive by nature (start at the bottom, but add the 
parent first). The above code basically emulates recursion with two 
loops and an explicit stack of entries, but the memory requirement is 
pretty much the same because it's still using a stack. With real 
recursion there is no loops and the stack is implicit, so the code is at 
least cleaner.

If you really want to avoid recursion you probably should use an RDN 
iterator instead of a stack, but the code would be either even uglier or 
less efficient.

Anyway, it's not a big deal, I included this change.

-- 
Endi S. Dewata
-------------- next part --------------
>From cc738ccba3cba23d83b9a6f459d4ea6bcf7c341d Mon Sep 17 00:00:00 2001
From: "Endi S. Dewata" <edewata at redhat.com>
Date: Tue, 21 Oct 2014 10:57:08 -0400
Subject: [PATCH] Added vault plugin.

A new plugin has been added to manage vaults. Test scripts have
also been added to verify the functionality.

https://fedorahosted.org/freeipa/ticket/3872
---
 API.txt                                   |  74 +++++
 VERSION                                   |   4 +-
 install/share/60basev3.ldif               |   1 +
 install/updates/40-vault.update           |  19 ++
 install/updates/Makefile.am               |   1 +
 ipa-client/man/default.conf.5             |   1 +
 ipalib/constants.py                       |   1 +
 ipalib/plugins/vault.py                   | 321 +++++++++++++++++++++
 ipatests/test_xmlrpc/test_vault_plugin.py | 445 ++++++++++++++++++++++++++++++
 9 files changed, 865 insertions(+), 2 deletions(-)
 create mode 100644 install/updates/40-vault.update
 create mode 100644 ipalib/plugins/vault.py
 create mode 100644 ipatests/test_xmlrpc/test_vault_plugin.py

diff --git a/API.txt b/API.txt
index 0808f3c64595495c8a9e60da5cbd689d5cdc6224..38deafefa57942bf242f989d79b1e93ee2c2013e 100644
--- a/API.txt
+++ b/API.txt
@@ -4717,6 +4717,80 @@ option: Str('version?', exclude='webui')
 output: Output('result', <type 'bool'>, None)
 output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: PrimaryKey('value', None, None)
+command: vault_add
+args: 1,9,3
+arg: Str('cn', attribute=True, cli_name='name', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-]+$', primary_key=True, required=True)
+option: Str('addattr*', cli_name='addattr', exclude='webui')
+option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
+option: Str('description', attribute=True, cli_name='desc', multivalue=False, required=False)
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Str('service?')
+option: Str('setattr*', cli_name='setattr', exclude='webui')
+option: Flag('shared?', autofill=True, default=False)
+option: Str('user?')
+option: Str('version?', exclude='webui')
+output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: PrimaryKey('value', None, None)
+command: vault_del
+args: 1,5,3
+arg: Str('cn', attribute=True, cli_name='name', maxlength=255, multivalue=True, pattern='^[a-zA-Z0-9_.-]+$', primary_key=True, query=True, required=True)
+option: Flag('continue', autofill=True, cli_name='continue', default=False)
+option: Str('service?')
+option: Flag('shared?', autofill=True, default=False)
+option: Str('user?')
+option: Str('version?', exclude='webui')
+output: Output('result', <type 'dict'>, None)
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: ListOfPrimaryKeys('value', None, None)
+command: vault_find
+args: 1,11,4
+arg: Str('criteria?', noextrawhitespace=False)
+option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
+option: Str('cn', attribute=True, autofill=False, cli_name='name', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-]+$', primary_key=True, query=True, required=False)
+option: Str('description', attribute=True, autofill=False, cli_name='desc', multivalue=False, query=True, required=False)
+option: Flag('pkey_only?', autofill=True, default=False)
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Str('service?')
+option: Flag('shared?', autofill=True, default=False)
+option: Int('sizelimit?', autofill=False, minvalue=0)
+option: Int('timelimit?', autofill=False, minvalue=0)
+option: Str('user?')
+option: Str('version?', exclude='webui')
+output: Output('count', <type 'int'>, None)
+output: ListOfEntries('result', (<type 'list'>, <type 'tuple'>), Gettext('A list of LDAP entries', domain='ipa', localedir=None))
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: Output('truncated', <type 'bool'>, None)
+command: vault_mod
+args: 1,11,3
+arg: Str('cn', attribute=True, cli_name='name', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-]+$', primary_key=True, query=True, required=True)
+option: Str('addattr*', cli_name='addattr', exclude='webui')
+option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
+option: Str('delattr*', cli_name='delattr', exclude='webui')
+option: Str('description', attribute=True, autofill=False, cli_name='desc', multivalue=False, required=False)
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Flag('rights', autofill=True, default=False)
+option: Str('service?')
+option: Str('setattr*', cli_name='setattr', exclude='webui')
+option: Flag('shared?', autofill=True, default=False)
+option: Str('user?')
+option: Str('version?', exclude='webui')
+output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: PrimaryKey('value', None, None)
+command: vault_show
+args: 1,7,3
+arg: Str('cn', attribute=True, cli_name='name', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-]+$', primary_key=True, query=True, required=True)
+option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Flag('rights', autofill=True, default=False)
+option: Str('service?')
+option: Flag('shared?', autofill=True, default=False)
+option: Str('user?')
+option: Str('version?', exclude='webui')
+output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: PrimaryKey('value', None, None)
 capability: messages 2.52
 capability: optional_uid_params 2.54
 capability: permissions2 2.69
diff --git a/VERSION b/VERSION
index c207558504e645dec73d105189d4b862877b4e26..33e7bebe4c673279ae92395a2575dc11e9fd4956 100644
--- a/VERSION
+++ b/VERSION
@@ -90,5 +90,5 @@ IPA_DATA_VERSION=20100614120000
 #                                                      #
 ########################################################
 IPA_API_VERSION_MAJOR=2
-IPA_API_VERSION_MINOR=118
-# Last change: tbordaz - Add stageuser_find, stageuser_mod, stageuser_del, stageuser_show
+IPA_API_VERSION_MINOR=119
+# Last change: edewata - Added vault plugin
diff --git a/install/share/60basev3.ldif b/install/share/60basev3.ldif
index eb1c1298b9de3295695745ba3311bb772dec70e9..33f4804e30ff1b3814ecf295bb41f07e2a8cd12f 100644
--- a/install/share/60basev3.ldif
+++ b/install/share/60basev3.ldif
@@ -79,3 +79,4 @@ objectClasses: (2.16.840.1.113730.3.8.12.24 NAME 'ipaPublicKeyObject' DESC 'Wrap
 objectClasses: (2.16.840.1.113730.3.8.12.25 NAME 'ipaPrivateKeyObject' DESC 'Wrapped private keys' SUP top AUXILIARY MUST ( ipaPrivateKey $ ipaWrappingKey $ ipaWrappingMech ) X-ORIGIN 'IPA v4.1' )
 objectClasses: (2.16.840.1.113730.3.8.12.26 NAME 'ipaSecretKeyObject' DESC 'Wrapped secret keys' SUP top AUXILIARY MUST ( ipaSecretKey $ ipaWrappingKey $ ipaWrappingMech ) X-ORIGIN 'IPA v4.1' )
 objectClasses: (2.16.840.1.113730.3.8.12.34 NAME 'ipaSecretKeyRefObject' DESC 'Indirect storage for encoded key material' SUP top AUXILIARY MUST ( ipaSecretKeyRef ) X-ORIGIN 'IPA v4.1' )
+objectClasses: (2.16.840.1.113730.3.8.18.1.1 NAME 'ipaVault' DESC 'IPA vault' SUP top STRUCTURAL MUST ( cn ) MAY ( description ) X-ORIGIN 'IPA v4.2' )
diff --git a/install/updates/40-vault.update b/install/updates/40-vault.update
new file mode 100644
index 0000000000000000000000000000000000000000..5a6b8c6a022fa56e5a5bc05369ce143d39644092
--- /dev/null
+++ b/install/updates/40-vault.update
@@ -0,0 +1,19 @@
+dn: cn=vaults,$SUFFIX
+default: objectClass: top
+default: objectClass: nsContainer
+default: cn: vaults
+
+dn: cn=services,cn=vaults,$SUFFIX
+default: objectClass: top
+default: objectClass: nsContainer
+default: cn: services
+
+dn: cn=shared,cn=vaults,$SUFFIX
+default: objectClass: top
+default: objectClass: nsContainer
+default: cn: shared
+
+dn: cn=users,cn=vaults,$SUFFIX
+default: objectClass: top
+default: objectClass: nsContainer
+default: cn: users
diff --git a/install/updates/Makefile.am b/install/updates/Makefile.am
index 0d63d9ea8d85f1add5f036e7a39f89543586d33b..66f6b9d37971f8b8501d73fc6ddca21b6686ff4b 100644
--- a/install/updates/Makefile.am
+++ b/install/updates/Makefile.am
@@ -33,6 +33,7 @@ app_DATA =				\
 	40-dns.update			\
 	40-automember.update		\
 	40-otp.update			\
+	40-vault.update			\
 	45-roles.update			\
 	50-7_bit_check.update	        \
 	50-dogtag10-migration.update	\
diff --git a/ipa-client/man/default.conf.5 b/ipa-client/man/default.conf.5
index dbc8a5b4647439de4de7c01152d098eb0561e236..0973f1a07179ad64daa326a02803cdc9ba1870aa 100644
--- a/ipa-client/man/default.conf.5
+++ b/ipa-client/man/default.conf.5
@@ -221,6 +221,7 @@ The following define the containers for the IPA server. Containers define where
   container_sudocmdgroup: cn=sudocmdgroups,cn=sudo
   container_sudorule: cn=sudorules,cn=sudo
   container_user: cn=users,cn=accounts
+  container_vault: cn=vaults
   container_virtual: cn=virtual operations,cn=etc
 
 .SH "FILES"
diff --git a/ipalib/constants.py b/ipalib/constants.py
index f1e14702ffdf5a3bd23a62b1fdd2ee3cd95d84f8..195938a355d1b24c02aa0a5833c1725c76e85c76 100644
--- a/ipalib/constants.py
+++ b/ipalib/constants.py
@@ -99,6 +99,7 @@ DEFAULT_CONFIG = (
     ('container_hbacservice', DN(('cn', 'hbacservices'), ('cn', 'hbac'))),
     ('container_hbacservicegroup', DN(('cn', 'hbacservicegroups'), ('cn', 'hbac'))),
     ('container_dns', DN(('cn', 'dns'))),
+    ('container_vault', DN(('cn', 'vaults'))),
     ('container_virtual', DN(('cn', 'virtual operations'), ('cn', 'etc'))),
     ('container_sudorule', DN(('cn', 'sudorules'), ('cn', 'sudo'))),
     ('container_sudocmd', DN(('cn', 'sudocmds'), ('cn', 'sudo'))),
diff --git a/ipalib/plugins/vault.py b/ipalib/plugins/vault.py
new file mode 100644
index 0000000000000000000000000000000000000000..ebb9f9fd3cf3b5a7d6b44ac9e63e122e8f71aa1a
--- /dev/null
+++ b/ipalib/plugins/vault.py
@@ -0,0 +1,321 @@
+# Authors:
+#   Endi S. Dewata <edewata at redhat.com>
+#
+# Copyright (C) 2015  Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from ipalib import api, errors
+from ipalib import Str, Flag
+from ipalib import output
+from ipalib.plugable import Registry
+from ipalib.plugins.baseldap import LDAPObject, LDAPCreate, LDAPDelete,\
+    LDAPSearch, LDAPUpdate, LDAPRetrieve
+from ipalib.request import context
+from ipalib.plugins.user import split_principal
+from ipalib import _, ngettext
+from ipapython.dn import DN
+
+__doc__ = _("""
+Vaults
+""") + _("""
+Manage vaults.
+""") + _("""
+EXAMPLES:
+""") + _("""
+ List private vaults:
+   ipa vault-find
+""") + _("""
+ List service vaults:
+   ipa vault-find --service <service name>
+""") + _("""
+ List shared vaults:
+   ipa vault-find --shared
+""") + _("""
+ List user vaults:
+   ipa vault-find --user <username>
+""") + _("""
+ Add a private vault:
+   ipa vault-add <name>
+""") + _("""
+ Add a service vault:
+   ipa vault-add <name> --service <service name>
+""") + _("""
+ Add a shared vault:
+   ipa vault-add <ame> --shared
+""") + _("""
+ Add a user vault:
+   ipa vault-add <name> --user <username>
+""") + _("""
+ Show a private vault:
+   ipa vault-show <name>
+""") + _("""
+ Show a service vault:
+   ipa vault-show <name> --service <service name>
+""") + _("""
+ Show a shared vault:
+   ipa vault-show <name> --shared
+""") + _("""
+ Show a user vault:
+   ipa vault-show <name> --user <username>
+""") + _("""
+ Modify a private vault:
+   ipa vault-mod <name> --desc <description>
+""") + _("""
+ Modify a service vault:
+   ipa vault-mod <name> --service <service name> --desc <description>
+""") + _("""
+ Modify a shared vault:
+   ipa vault-mod <name> --shared --desc <description>
+""") + _("""
+ Modify a user vault:
+   ipa vault-mod <name> --user <username> --desc <description>
+""") + _("""
+ Delete a private vault:
+   ipa vault-del <name>
+""") + _("""
+ Delete a service vault:
+   ipa vault-del <name> --service <service name>
+""") + _("""
+ Delete a shared vault:
+   ipa vault-del <name> --shared
+""") + _("""
+ Delete a user vault:
+   ipa vault-del <name> --user <username>
+""")
+
+register = Registry()
+
+
+vault_options = (
+    Str(
+        'service?',
+        doc=_('Service name'),
+    ),
+    Flag(
+        'shared?',
+        doc=_('Shared vault'),
+    ),
+    Str(
+        'user?',
+        doc=_('Username'),
+    ),
+)
+
+
+ at register()
+class vault(LDAPObject):
+    __doc__ = _("""
+    Vault object.
+    """)
+
+    container_dn = api.env.container_vault
+
+    object_name = _('vault')
+    object_name_plural = _('vaults')
+
+    object_class = ['ipaVault']
+    default_attributes = [
+        'cn',
+        'description',
+    ]
+
+    label = _('Vaults')
+    label_singular = _('Vault')
+
+    takes_params = (
+        Str(
+            'cn',
+            cli_name='name',
+            label=_('Vault name'),
+            primary_key=True,
+            pattern='^[a-zA-Z0-9_.-]+$',
+            pattern_errmsg='may only include letters, numbers, _, ., and -',
+            maxlength=255,
+        ),
+        Str(
+            'description?',
+            cli_name='desc',
+            label=_('Description'),
+            doc=_('Vault description'),
+        ),
+    )
+
+    def get_dn(self, *keys, **options):
+        """
+        Generates vault DN from parameters.
+        """
+
+        service = options.get('service')
+        shared = options.get('shared')
+        user = options.get('user')
+
+        count = 0
+        if service:
+            count += 1
+
+        if shared:
+            count += 1
+
+        if user:
+            count += 1
+
+        if count > 1:
+            raise errors.MutuallyExclusiveError(
+                reason=_('Service, shared, and user options ' +
+                         'cannot be specified simultaneously'))
+
+        # TODO: create container_dn after object initialization then reuse it
+        container_dn = DN(self.container_dn, self.api.env.basedn)
+
+        dn = super(vault, self).get_dn(*keys, **options)
+        assert dn.endswith(container_dn)
+        rdns = DN(*dn[:-len(container_dn)])
+
+        if not count:
+            principal = getattr(context, 'principal')
+
+            if principal.startswith('host/'):
+                raise errors.NotImplementedError(
+                    reason=_('Host is not supported'))
+
+            (name, realm) = split_principal(principal)
+            if '/' in name:
+                service = name
+            else:
+                user = name
+
+        if service:
+            parent_dn = DN(('cn', service), ('cn', 'services'), container_dn)
+        elif shared:
+            parent_dn = DN(('cn', 'shared'), container_dn)
+        else:
+            parent_dn = DN(('cn', user), ('cn', 'users'), container_dn)
+
+        return DN(rdns, parent_dn)
+
+    def create_container(self, dn):
+        """
+        Creates vault container and its parents.
+        """
+
+        # TODO: create container_dn after object initialization then reuse it
+        container_dn = DN(self.container_dn, self.api.env.basedn)
+
+        entries = []
+
+        while dn:
+            assert dn.endswith(container_dn)
+
+            rdn = dn[0]
+            entry = self.backend.make_entry(
+                dn,
+                {
+                    'objectclass': ['nsContainer'],
+                    'cn': rdn['cn'],
+                })
+
+            # if entry can be added, return
+            try:
+                self.backend.add_entry(entry)
+                break
+
+            except errors.NotFound:
+                pass
+
+            # otherwise, create parent entry first
+            dn = DN(*dn[1:])
+            entries.insert(0, entry)
+
+        # then create the entries again
+        for entry in entries:
+            self.backend.add_entry(entry)
+
+
+ at register()
+class vault_add(LDAPCreate):
+    __doc__ = _('Create a new vault.')
+
+    takes_options = LDAPCreate.takes_options + vault_options
+
+    msg_summary = _('Added vault "%(value)s"')
+
+    def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys,
+                     **options):
+        assert isinstance(dn, DN)
+
+        try:
+            parent_dn = DN(*dn[1:])
+            self.obj.create_container(parent_dn)
+        except errors.DuplicateEntry, e:
+            pass
+
+        return dn
+
+
+ at register()
+class vault_del(LDAPDelete):
+    __doc__ = _('Delete a vault.')
+
+    takes_options = LDAPDelete.takes_options + vault_options
+
+    msg_summary = _('Deleted vault "%(value)s"')
+
+
+ at register()
+class vault_find(LDAPSearch):
+    __doc__ = _('Search for vaults.')
+
+    takes_options = LDAPSearch.takes_options + vault_options
+
+    msg_summary = ngettext(
+        '%(count)d vault matched',
+        '%(count)d vaults matched',
+        0,
+    )
+
+    def pre_callback(self, ldap, filter, attrs_list, base_dn, scope, *args,
+                     **options):
+        assert isinstance(base_dn, DN)
+
+        base_dn = self.obj.get_dn(*args, **options)
+
+        return (filter, base_dn, scope)
+
+    def exc_callback(self, args, options, exc, call_func, *call_args,
+                     **call_kwargs):
+        if call_func.__name__ == 'find_entries':
+            if isinstance(exc, errors.NotFound):
+                # ignore missing containers since they will be created
+                # automatically on vault creation.
+                raise errors.EmptyResult(reason=str(exc))
+
+        raise exc
+
+
+ at register()
+class vault_mod(LDAPUpdate):
+    __doc__ = _('Modify a vault.')
+
+    takes_options = LDAPUpdate.takes_options + vault_options
+
+    msg_summary = _('Modified vault "%(value)s"')
+
+
+ at register()
+class vault_show(LDAPRetrieve):
+    __doc__ = _('Display information about a vault.')
+
+    takes_options = LDAPRetrieve.takes_options + vault_options
diff --git a/ipatests/test_xmlrpc/test_vault_plugin.py b/ipatests/test_xmlrpc/test_vault_plugin.py
new file mode 100644
index 0000000000000000000000000000000000000000..44d397c583928d98ec252899398ae6c3a83c207c
--- /dev/null
+++ b/ipatests/test_xmlrpc/test_vault_plugin.py
@@ -0,0 +1,445 @@
+# Authors:
+#   Endi S. Dewata <edewata at redhat.com>
+#
+# Copyright (C) 2015  Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+"""
+Test the `ipalib/plugins/vault.py` module.
+"""
+
+from ipalib import api, errors
+from xmlrpc_test import Declarative, fuzzy_string
+
+vault_name = u'test_vault'
+service_name = u'HTTP/server.example.com'
+user_name = u'testuser'
+
+
+class test_vault_plugin(Declarative):
+
+    cleanup_commands = [
+        ('vault_del', [vault_name], {'continue': True}),
+        ('vault_del', [vault_name], {
+            'service': service_name,
+            'continue': True
+        }),
+        ('vault_del', [vault_name], {'shared': True, 'continue': True}),
+        ('vault_del', [vault_name], {'user': user_name, 'continue': True}),
+    ]
+
+    tests = [
+
+        {
+            'desc': 'Create private vault',
+            'command': (
+                'vault_add',
+                [vault_name],
+                {},
+            ),
+            'expected': {
+                'value': vault_name,
+                'summary': 'Added vault "%s"' % vault_name,
+                'result': {
+                    'dn': u'cn=%s,cn=admin,cn=users,cn=vaults,%s'
+                          % (vault_name, api.env.basedn),
+                    'objectclass': [u'top', u'ipaVault'],
+                    'cn': [vault_name],
+                },
+            },
+        },
+
+        {
+            'desc': 'Find private vaults',
+            'command': (
+                'vault_find',
+                [],
+                {},
+            ),
+            'expected': {
+                'count': 1,
+                'truncated': False,
+                'summary': u'1 vault matched',
+                'result': [
+                    {
+                        'dn': u'cn=%s,cn=admin,cn=users,cn=vaults,%s'
+                              % (vault_name, api.env.basedn),
+                        'cn': [vault_name],
+                    },
+                ],
+            },
+        },
+
+        {
+            'desc': 'Show private vault',
+            'command': (
+                'vault_show',
+                [vault_name],
+                {},
+            ),
+            'expected': {
+                'value': vault_name,
+                'summary': None,
+                'result': {
+                    'dn': u'cn=%s,cn=admin,cn=users,cn=vaults,%s'
+                          % (vault_name, api.env.basedn),
+                    'cn': [vault_name],
+                },
+            },
+        },
+
+        {
+            'desc': 'Modify private vault',
+            'command': (
+                'vault_mod',
+                [vault_name],
+                {
+                    'description': u'Test vault',
+                },
+            ),
+            'expected': {
+                'value': vault_name,
+                'summary': u'Modified vault "%s"' % vault_name,
+                'result': {
+                    'cn': [vault_name],
+                    'description': [u'Test vault'],
+                },
+            },
+        },
+
+        {
+            'desc': 'Delete private vault',
+            'command': (
+                'vault_del',
+                [vault_name],
+                {},
+            ),
+            'expected': {
+                'value': [vault_name],
+                'summary': u'Deleted vault "%s"' % vault_name,
+                'result': {
+                    'failed': (),
+                },
+            },
+        },
+
+        {
+            'desc': 'Create service vault',
+            'command': (
+                'vault_add',
+                [vault_name],
+                {
+                    'service': service_name,
+                },
+            ),
+            'expected': {
+                'value': vault_name,
+                'summary': u'Added vault "%s"' % vault_name,
+                'result': {
+                    'dn': u'cn=%s,cn=%s,cn=services,cn=vaults,%s'
+                          % (vault_name, service_name, api.env.basedn),
+                    'objectclass': [u'top', u'ipaVault'],
+                    'cn': [vault_name],
+                },
+            },
+        },
+
+        {
+            'desc': 'Find service vaults',
+            'command': (
+                'vault_find',
+                [],
+                {
+                    'service': service_name,
+                },
+            ),
+            'expected': {
+                'count': 1,
+                'truncated': False,
+                'summary': u'1 vault matched',
+                'result': [
+                    {
+                        'dn': u'cn=%s,cn=%s,cn=services,cn=vaults,%s'
+                              % (vault_name, service_name, api.env.basedn),
+                        'cn': [vault_name],
+                    },
+                ],
+            },
+        },
+
+        {
+            'desc': 'Show service vault',
+            'command': (
+                'vault_show',
+                [vault_name],
+                {
+                    'service': service_name,
+                },
+            ),
+            'expected': {
+                'value': vault_name,
+                'summary': None,
+                'result': {
+                    'dn': u'cn=%s,cn=%s,cn=services,cn=vaults,%s'
+                          % (vault_name, service_name, api.env.basedn),
+                    'cn': [vault_name],
+                },
+            },
+        },
+
+        {
+            'desc': 'Modify service vault',
+            'command': (
+                'vault_mod',
+                [vault_name],
+                {
+                    'service': service_name,
+                    'description': u'Test vault',
+                },
+            ),
+            'expected': {
+                'value': vault_name,
+                'summary': u'Modified vault "%s"' % vault_name,
+                'result': {
+                    'cn': [vault_name],
+                    'description': [u'Test vault'],
+                },
+            },
+        },
+
+        {
+            'desc': 'Delete service vault',
+            'command': (
+                'vault_del',
+                [vault_name],
+                {
+                    'service': service_name,
+                },
+            ),
+            'expected': {
+                'value': [vault_name],
+                'summary': u'Deleted vault "%s"' % vault_name,
+                'result': {
+                    'failed': (),
+                },
+            },
+        },
+
+        {
+            'desc': 'Create shared vault',
+            'command': (
+                'vault_add',
+                [vault_name],
+                {
+                    'shared': True
+                },
+            ),
+            'expected': {
+                'value': vault_name,
+                'summary': u'Added vault "%s"' % vault_name,
+                'result': {
+                    'dn': u'cn=%s,cn=shared,cn=vaults,%s'
+                          % (vault_name, api.env.basedn),
+                    'objectclass': [u'top', u'ipaVault'],
+                    'cn': [vault_name],
+                },
+            },
+        },
+
+        {
+            'desc': 'Find shared vaults',
+            'command': (
+                'vault_find',
+                [],
+                {
+                    'shared': True
+                },
+            ),
+            'expected': {
+                'count': 1,
+                'truncated': False,
+                'summary': u'1 vault matched',
+                'result': [
+                    {
+                        'dn': u'cn=%s,cn=shared,cn=vaults,%s'
+                              % (vault_name, api.env.basedn),
+                        'cn': [vault_name],
+                    },
+                ],
+            },
+        },
+
+        {
+            'desc': 'Show shared vault',
+            'command': (
+                'vault_show',
+                [vault_name],
+                {
+                    'shared': True
+                },
+            ),
+            'expected': {
+                'value': vault_name,
+                'summary': None,
+                'result': {
+                    'dn': u'cn=%s,cn=shared,cn=vaults,%s'
+                          % (vault_name, api.env.basedn),
+                    'cn': [vault_name],
+                },
+            },
+        },
+
+        {
+            'desc': 'Modify shared vault',
+            'command': (
+                'vault_mod',
+                [vault_name],
+                {
+                    'shared': True,
+                    'description': u'Test vault',
+                },
+            ),
+            'expected': {
+                'value': vault_name,
+                'summary': u'Modified vault "%s"' % vault_name,
+                'result': {
+                    'cn': [vault_name],
+                    'description': [u'Test vault'],
+                },
+            },
+        },
+
+        {
+            'desc': 'Delete shared vault',
+            'command': (
+                'vault_del',
+                [vault_name],
+                {
+                    'shared': True
+                },
+            ),
+            'expected': {
+                'value': [vault_name],
+                'summary': u'Deleted vault "%s"' % vault_name,
+                'result': {
+                    'failed': (),
+                },
+            },
+        },
+
+        {
+            'desc': 'Create user vault',
+            'command': (
+                'vault_add',
+                [vault_name],
+                {
+                    'user': user_name,
+                },
+            ),
+            'expected': {
+                'value': vault_name,
+                'summary': u'Added vault "%s"' % vault_name,
+                'result': {
+                    'dn': u'cn=%s,cn=%s,cn=users,cn=vaults,%s'
+                          % (vault_name, user_name, api.env.basedn),
+                    'objectclass': [u'top', u'ipaVault'],
+                    'cn': [vault_name],
+                },
+            },
+        },
+
+        {
+            'desc': 'Find user vaults',
+            'command': (
+                'vault_find',
+                [],
+                {
+                    'user': user_name,
+                },
+            ),
+            'expected': {
+                'count': 1,
+                'truncated': False,
+                'summary': u'1 vault matched',
+                'result': [
+                    {
+                        'dn': u'cn=%s,cn=%s,cn=users,cn=vaults,%s'
+                              % (vault_name, user_name, api.env.basedn),
+                        'cn': [vault_name],
+                    },
+                ],
+            },
+        },
+
+        {
+            'desc': 'Show user vault',
+            'command': (
+                'vault_show',
+                [vault_name],
+                {
+                    'user': user_name,
+                },
+            ),
+            'expected': {
+                'value': vault_name,
+                'summary': None,
+                'result': {
+                    'dn': u'cn=%s,cn=%s,cn=users,cn=vaults,%s'
+                          % (vault_name, user_name, api.env.basedn),
+                    'cn': [vault_name],
+                },
+            },
+        },
+
+        {
+            'desc': 'Modify user vault',
+            'command': (
+                'vault_mod',
+                [vault_name],
+                {
+                    'user': user_name,
+                    'description': u'Test vault',
+                },
+            ),
+            'expected': {
+                'value': vault_name,
+                'summary': u'Modified vault "%s"' % vault_name,
+                'result': {
+                    'cn': [vault_name],
+                    'description': [u'Test vault'],
+                },
+            },
+        },
+
+        {
+            'desc': 'Delete user vault',
+            'command': (
+                'vault_del',
+                [vault_name],
+                {
+                    'user': user_name,
+                },
+            ),
+            'expected': {
+                'value': [vault_name],
+                'summary': u'Deleted vault "%s"' % vault_name,
+                'result': {
+                    'failed': (),
+                },
+            },
+        },
+
+    ]
-- 
1.9.3



More information about the Freeipa-devel mailing list