On 04/19/2016 08:20 AM, Jan Cholasta wrote:
> On 13.4.2016 14:13, Tomas Babej wrote:
>> On 04/13/2016 09:55 AM, Tomas Babej wrote:
>>> On 04/07/2016 01:53 PM, Sumit Bose wrote:
>>>> On Mon, Apr 04, 2016 at 04:27:02PM +0200, Jan Cholasta wrote:
>>>>> Hi,
>>>>>
>>>>> On 1.4.2016 16:53, Tomas Babej wrote:
>>>>>> Hi,
>>>>>>
>>>>>> this extends the user ID overrides with capability to store the user
>>>>>> certificate.
>>>>>>
>>>>>> https://fedorahosted.org/freeipa/ticket/4955
>>>>>
>>>>> The preferred way of managing certificates nowadays is using
>>>>> $OBJ-add-cert
>>>>> and $OBJ-remove-cert commands, you should add them here as well.
>>>>>
>>>>> I would even go as far as not allowing to modify certificates using
>>>>> idoverrideuser-mod - in user-mod and host-mod, it's there just for
>>>>> backward
>>>>> compatibility, which is not the case here. But I don't have a
>>>>> strong opinion
>>>>> on that.
>>>>>
>>>>> For consistency with user-find and host-find, the full certificate
>>>>> blob
>>>>> should not be shown in idoverrideuser-find. You can do that by setting
>>>>> search_display_attributes attribute on the idoverrideuser class
>>>>> appropriately.
>>>>
>>>> I tested the current patch with my related patches for SSSD and all is
>>>> working as expected.
>>>>
>>>> bye,
>>>> Sumit
>>>>
>>>>>
>>>>> Honza
>>>>>
>>>>> -- 
>>>>> Jan Cholasta
>>>>>
>>>>> -- 
>>>>> Manage your subscription for the Freeipa-devel mailing list:
>>>>> https://www.redhat.com/mailman/listinfo/freeipa-devel
>>>>> Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code
>>>>
>>>
>>> Thanks for the reviews,
>>>
>>> attaching a updated patch that addresses Honza's comments.
>>>
>>> Tomas
>>>
>>
>> Sending an improved version addressing a couple of additional issues.
> 
> 1) This bit in idoverrideuser_add.pre_callback() is redundant, as the
> certificate will always be DER here already:
> 
>         # Normalize the certificate to DER format
>         certs = options.get('usercertificate', [])
>         certs_der = [x509.normalize_certificate(c) for c in certs]
>         entry_attrs['usercertificate'] = certs_der
> 
> 
> 2) You need to call convert_usercertificate_pre() in
> idoverrideuser_mod.pre_callback() and convert_usercertificate_post() in
> idoverrideuser_{mod,find,show}.post_callback() as well.
> 
> Honza
> 

Updated patch attached, mentioned issues should be fixed, I also removed
one redundant import which escaped my careful eye.

Tomas
From ecfb6dbfb39120fa1c2caf83fd0d6c22471c212d Mon Sep 17 00:00:00 2001
From: Tomas Babej <tba...@redhat.com>
Date: Thu, 3 Mar 2016 15:14:10 +0100
Subject: [PATCH] idviews: Add user certificate attribute to user ID overrides

---
 ACI.txt                      |  2 +-
 API.txt                      | 30 +++++++++++++++--
 VERSION                      |  4 +--
 install/share/71idviews.ldif |  2 +-
 ipalib/plugins/idviews.py    | 79 ++++++++++++++++++++++++++++++++++++++++++--
 5 files changed, 109 insertions(+), 8 deletions(-)

diff --git a/ACI.txt b/ACI.txt
index 24cb332ce6e10c82a5bfab76d084fb6c0277800d..ae00cf7a1b8e2ea0e33798993bb24dc5f06127e3 100644
--- a/ACI.txt
+++ b/ACI.txt
@@ -149,7 +149,7 @@ aci: (targetfilter = "(objectclass=ipahostgroup)")(version 3.0;acl "permission:S
 dn: cn=views,cn=accounts,dc=ipa,dc=example
 aci: (targetattr = "cn || createtimestamp || description || entryusn || gidnumber || ipaanchoruuid || modifytimestamp || objectclass")(targetfilter = "(objectclass=ipaGroupOverride)")(version 3.0;acl "permission:System: Read Group ID Overrides";allow (compare,read,search) userdn = "ldap:///all";;)
 dn: cn=views,cn=accounts,dc=ipa,dc=example
-aci: (targetattr = "createtimestamp || description || entryusn || gecos || gidnumber || homedirectory || ipaanchoruuid || ipaoriginaluid || ipasshpubkey || loginshell || modifytimestamp || objectclass || uid || uidnumber")(targetfilter = "(objectclass=ipaUserOverride)")(version 3.0;acl "permission:System: Read User ID Overrides";allow (compare,read,search) userdn = "ldap:///all";;)
+aci: (targetattr = "createtimestamp || description || entryusn || gecos || gidnumber || homedirectory || ipaanchoruuid || ipaoriginaluid || ipasshpubkey || loginshell || modifytimestamp || objectclass || uid || uidnumber || usercertificate")(targetfilter = "(objectclass=ipaUserOverride)")(version 3.0;acl "permission:System: Read User ID Overrides";allow (compare,read,search) userdn = "ldap:///all";;)
 dn: cn=ranges,cn=etc,dc=ipa,dc=example
 aci: (targetattr = "cn || createtimestamp || entryusn || ipabaseid || ipabaserid || ipaidrangesize || ipanttrusteddomainsid || iparangetype || ipasecondarybaserid || modifytimestamp || objectclass")(targetfilter = "(objectclass=ipaidrange)")(version 3.0;acl "permission:System: Read ID Ranges";allow (compare,read,search) userdn = "ldap:///all";;)
 dn: cn=views,cn=accounts,dc=ipa,dc=example
diff --git a/API.txt b/API.txt
index 3598b08198cae536754259f7463669052efa3f86..b2aec7313b6b9496179beddb68e4a0f5a09608bf 100644
--- a/API.txt
+++ b/API.txt
@@ -2429,7 +2429,7 @@ output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDA
 output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: PrimaryKey('value', None, None)
 command: idoverrideuser_add
-args: 2,15,3
+args: 2,16,3
 arg: Str('idviewcn', cli_name='idview', multivalue=False, primary_key=True, query=True, required=True)
 arg: Str('ipaanchoruuid', attribute=True, cli_name='anchor', multivalue=False, primary_key=True, required=True)
 option: Str('addattr*', cli_name='addattr', exclude='webui')
@@ -2446,6 +2446,19 @@ option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui
 option: Str('setattr*', cli_name='setattr', exclude='webui')
 option: Str('uid', attribute=True, cli_name='login', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.][a-zA-Z0-9_.-]{0,252}[a-zA-Z0-9_.$-]?$', required=False)
 option: Int('uidnumber', attribute=True, cli_name='uid', minvalue=1, multivalue=False, required=False)
+option: Bytes('usercertificate', attribute=True, cli_name='certificate', multivalue=True, required=False)
+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: idoverrideuser_add_cert
+args: 2,5,3
+arg: Str('idviewcn', cli_name='idview', multivalue=False, primary_key=True, query=True, required=True)
+arg: Str('ipaanchoruuid', attribute=True, cli_name='anchor', multivalue=False, primary_key=True, query=True, required=True)
+option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
+option: Flag('fallback_to_ldap?', autofill=True, default=False)
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Bytes('usercertificate', alwaysask=True, attribute=True, cli_name='certificate', multivalue=True, required=False)
 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)
@@ -2485,7 +2498,7 @@ output: ListOfEntries('result', (<type 'list'>, <type 'tuple'>), Gettext('A list
 output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: Output('truncated', <type 'bool'>, None)
 command: idoverrideuser_mod
-args: 2,18,3
+args: 2,19,3
 arg: Str('idviewcn', cli_name='idview', multivalue=False, primary_key=True, query=True, required=True)
 arg: Str('ipaanchoruuid', attribute=True, cli_name='anchor', multivalue=False, primary_key=True, query=True, required=True)
 option: Str('addattr*', cli_name='addattr', exclude='webui')
@@ -2505,6 +2518,19 @@ option: Flag('rights', autofill=True, default=False)
 option: Str('setattr*', cli_name='setattr', exclude='webui')
 option: Str('uid', attribute=True, autofill=False, cli_name='login', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.][a-zA-Z0-9_.-]{0,252}[a-zA-Z0-9_.$-]?$', required=False)
 option: Int('uidnumber', attribute=True, autofill=False, cli_name='uid', minvalue=1, multivalue=False, required=False)
+option: Bytes('usercertificate', attribute=True, autofill=False, cli_name='certificate', multivalue=True, required=False)
+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: idoverrideuser_remove_cert
+args: 2,5,3
+arg: Str('idviewcn', cli_name='idview', multivalue=False, primary_key=True, query=True, required=True)
+arg: Str('ipaanchoruuid', attribute=True, cli_name='anchor', multivalue=False, primary_key=True, query=True, required=True)
+option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
+option: Flag('fallback_to_ldap?', autofill=True, default=False)
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Bytes('usercertificate', alwaysask=True, attribute=True, cli_name='certificate', multivalue=True, required=False)
 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)
diff --git a/VERSION b/VERSION
index aedebd185821d42fa48608f4c5fdf9ff510ace3f..36d3fe267d9d8e8da54f3baa9f0038bcb12bfaae 100644
--- a/VERSION
+++ b/VERSION
@@ -90,5 +90,5 @@ IPA_DATA_VERSION=20100614120000
 #                                                      #
 ########################################################
 IPA_API_VERSION_MAJOR=2
-IPA_API_VERSION_MINOR=165
-# Last change: mbasti - limit ipamaxusernamelength value to 255
+IPA_API_VERSION_MINOR=166
+# Last change: tbabej - idviews: Add user certificate attribute to user ID overrides
diff --git a/install/share/71idviews.ldif b/install/share/71idviews.ldif
index 33db9840bf39ab6f0f2e0eb04de8568d47460f21..b2fa1f414874b432f874dcb10c8d7f4463aec617 100644
--- a/install/share/71idviews.ldif
+++ b/install/share/71idviews.ldif
@@ -3,6 +3,6 @@ attributeTypes: (2.16.840.1.113730.3.8.11.62 NAME 'ipaAnchorUUID' DESC 'Unique A
 attributeTypes: (2.16.840.1.113730.3.8.11.63 NAME 'ipaOriginalUid' DESC 'Original UID of overriden user' EQUALITY caseIgnoreMatch ORDERING caseIgnoreOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'IPA v4')
 objectClasses: (2.16.840.1.113730.3.8.12.29 NAME 'ipaIDView' SUP nsContainer STRUCTURAL MAY ( description ) X-ORIGIN 'IPA v4' )
 objectClasses: (2.16.840.1.113730.3.8.12.30 NAME 'ipaOverrideAnchor' SUP top STRUCTURAL MUST ( ipaAnchorUUID ) MAY ( description ) X-ORIGIN 'IPA v4' )
-objectClasses: (2.16.840.1.113730.3.8.12.31 NAME 'ipaUserOverride' DESC 'Override for User Attributes' SUP ipaOverrideAnchor STRUCTURAL MAY ( uid $ uidNumber $ gidNumber $ homeDirectory $ loginShell $ gecos $ ipaOriginalUid ) X-ORIGIN 'IPA v4' )
+objectClasses: (2.16.840.1.113730.3.8.12.31 NAME 'ipaUserOverride' DESC 'Override for User Attributes' SUP ipaOverrideAnchor STRUCTURAL MAY ( uid $ uidNumber $ gidNumber $ homeDirectory $ loginShell $ gecos $ ipaOriginalUid $ userCertificate ) X-ORIGIN 'IPA v4' )
 objectClasses: (2.16.840.1.113730.3.8.12.32 NAME 'ipaGroupOverride' DESC 'Override for Group Attributes' SUP ipaOverrideAnchor STRUCTURAL MAY ( gidNumber $ cn ) X-ORIGIN 'IPA v4' )
 objectClasses: (2.16.840.1.113730.3.8.12.35 NAME 'ipaOverrideTarget' SUP top STRUCTURAL MUST ( ipaAnchorUUID ) X-ORIGIN 'IPA v4' )
diff --git a/ipalib/plugins/idviews.py b/ipalib/plugins/idviews.py
index bfbec56457bc0122fbb223fe26f5cf09708bdd3e..851a6cb91155f81756f5af668e5040bea728087b 100644
--- a/ipalib/plugins/idviews.py
+++ b/ipalib/plugins/idviews.py
@@ -23,9 +23,11 @@ import six
 
 from ipalib.plugins.baseldap import (LDAPQuery, LDAPObject, LDAPCreate,
                                      LDAPDelete, LDAPUpdate, LDAPSearch,
+                                     LDAPAddAttribute, LDAPRemoveAttribute,
                                      LDAPRetrieve, global_output_params)
 from ipalib.plugins.hostgroup import get_complete_hostgroup_member_list
-from ipalib import api, Str, Int, Flag, _, ngettext, errors, output
+from ipalib.plugins.service import validate_certificate
+from ipalib import api, Str, Int, Bytes, Flag, _, ngettext, errors, output
 from ipalib.constants import IPA_ANCHOR_PREFIX, SID_ANCHOR_PREFIX
 from ipalib.plugable import Registry
 from ipalib.util import (normalize_sshpubkey, validate_sshpubkey,
@@ -817,7 +819,7 @@ class idoverrideuser(baseidoverride):
             'ipapermdefaultattr': {
                 'objectClass', 'ipaAnchorUUID', 'uidNumber', 'description',
                 'homeDirectory', 'uid', 'ipaOriginalUid', 'loginShell', 'gecos',
-                'gidNumber', 'ipaSshPubkey',
+                'gidNumber', 'ipaSshPubkey', 'usercertificate'
             },
         },
     }
@@ -826,6 +828,11 @@ class idoverrideuser(baseidoverride):
     possible_objectclasses = ['ipasshuser', 'ipaSshGroupOfPubKeys']
     default_attributes = baseidoverride.default_attributes + [
        'homeDirectory', 'uidNumber', 'uid', 'ipaOriginalUid', 'loginShell',
+       'ipaSshPubkey', 'gidNumber', 'gecos', 'usercertificate;binary',
+    ]
+
+    search_display_attributes = baseidoverride.default_attributes + [
+       'homeDirectory', 'uidNumber', 'uid', 'ipaOriginalUid', 'loginShell',
        'ipaSshPubkey', 'gidNumber', 'gecos',
     ]
 
@@ -870,6 +877,12 @@ class idoverrideuser(baseidoverride):
             csv=True,
             flags=['no_search'],
         ),
+        Bytes('usercertificate*', validate_certificate,
+              cli_name='certificate',
+              label=_('Certificate'),
+              doc=_('Base-64 encoded user certificate'),
+              flags=['no_search',],
+        ),
     )
 
     override_object = 'user'
@@ -888,6 +901,17 @@ class idoverrideuser(baseidoverride):
             # we have no way to update the original_uid
             pass
 
+    def convert_usercertificate_pre(self, entry_attrs):
+        if 'usercertificate' in entry_attrs:
+            entry_attrs['usercertificate;binary'] = entry_attrs.pop(
+                'usercertificate')
+
+    def convert_usercertificate_post(self, entry_attrs, **options):
+        if 'usercertificate;binary' in entry_attrs:
+            entry_attrs['usercertificate'] = entry_attrs.pop(
+                'usercertificate;binary')
+
+
 
 @register()
 class idoverridegroup(baseidoverride):
@@ -935,6 +959,50 @@ class idoverridegroup(baseidoverride):
 
     override_object = 'group'
 
+@register()
+class idoverrideuser_add_cert(LDAPAddAttribute):
+    __doc__ = _('Add one or more certificates to the idoverrideuser entry')
+    msg_summary = _('Added certificates to idoverrideuser "%(value)s"')
+    attribute = 'usercertificate'
+
+    takes_options = LDAPAddAttribute.takes_options + (fallback_to_ldap_option,)
+
+    def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys,
+                     **options):
+        dn = self.obj.get_dn(*keys, **options)
+        self.obj.convert_usercertificate_pre(entry_attrs)
+
+        return dn
+
+    def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
+        assert isinstance(dn, DN)
+        self.obj.convert_usercertificate_post(entry_attrs, **options)
+        self.obj.convert_anchor_to_human_readable_form(entry_attrs, **options)
+        return dn
+
+
+@register()
+class idoverrideuser_remove_cert(LDAPRemoveAttribute):
+    __doc__ = _('Remove one or more certificates to the idoverrideuser entry')
+    msg_summary = _('Removed certificates from idoverrideuser "%(value)s"')
+    attribute = 'usercertificate'
+
+    takes_options = LDAPRemoveAttribute.takes_options + (fallback_to_ldap_option,)
+
+    def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys,
+                     **options):
+        dn = self.obj.get_dn(*keys, **options)
+        self.obj.convert_usercertificate_pre(entry_attrs)
+
+        return dn
+
+    def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
+        assert isinstance(dn, DN)
+        self.obj.convert_usercertificate_post(entry_attrs, **options)
+        self.obj.convert_anchor_to_human_readable_form(entry_attrs, **options)
+
+        return dn
+
 
 @register()
 class idoverrideuser_add(baseidoverride_add):
@@ -946,6 +1014,7 @@ class idoverrideuser_add(baseidoverride_add):
                  entry_attrs, attrs_list, *keys, **options)
 
         entry_attrs['objectclass'].append('ipasshuser')
+        self.obj.convert_usercertificate_pre(entry_attrs)
 
         # Update the ipaOriginalUid
         self.obj.update_original_uid_reference(entry_attrs)
@@ -955,6 +1024,7 @@ class idoverrideuser_add(baseidoverride_add):
         dn = super(idoverrideuser_add, self).post_callback(ldap, dn,
                  entry_attrs, *keys, **options)
         convert_sshpubkey_post(entry_attrs)
+        self.obj.convert_usercertificate_post(entry_attrs, **options)
         return dn
 
 
@@ -985,12 +1055,15 @@ class idoverrideuser_mod(baseidoverride_mod):
 
         if 'ipasshpubkey' in entry_attrs and 'ipasshuser' not in obj_classes:
             obj_classes.append('ipasshuser')
+
+        self.obj.convert_usercertificate_pre(entry_attrs)
         return dn
 
     def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
         dn = super(idoverrideuser_mod, self).post_callback(ldap, dn,
                  entry_attrs, *keys, **options)
         convert_sshpubkey_post(entry_attrs)
+        self.obj.convert_usercertificate_post(entry_attrs, **options)
         return dn
 
 
@@ -1005,6 +1078,7 @@ class idoverrideuser_find(baseidoverride_find):
             ldap, entries, truncated, *args, **options)
         for entry in entries:
             convert_sshpubkey_post(entry)
+            self.obj.convert_usercertificate_post(entry, **options)
         return truncated
 
 
@@ -1016,6 +1090,7 @@ class idoverrideuser_show(baseidoverride_show):
         dn = super(idoverrideuser_show, self).post_callback(ldap, dn,
                  entry_attrs, *keys, **options)
         convert_sshpubkey_post(entry_attrs)
+        self.obj.convert_usercertificate_post(entry_attrs, **options)
         return dn
 
 
-- 
2.5.5

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

Reply via email to