From 9e24918c89f30a6d7064844dc0dd848bb35140df Mon Sep 17 00:00:00 2001 From: Florence Blanc-Renaud Date: Tue, 20 Dec 2016 16:21:58 +0100 Subject: Support for Certificate Identity Mapping See design http://www.freeipa.org/page/V4/Certificate_Identity_Mapping https://fedorahosted.org/freeipa/ticket/6542 Reviewed-By: Martin Basti Reviewed-By: Jan Cholasta Reviewed-By: David Kupka --- ACI.txt | 16 +- API.txt | 181 +++++++++++++++++ VERSION.m4 | 4 +- install/share/73certmap.ldif | 14 ++ install/share/Makefile.am | 1 + install/updates/73-certmap.update | 23 +++ install/updates/Makefile.am | 1 + ipalib/constants.py | 2 + ipapython/dn.py | 8 +- ipaserver/install/dsinstance.py | 1 + ipaserver/plugins/baseuser.py | 174 ++++++++++++++++- ipaserver/plugins/certmap.py | 391 +++++++++++++++++++++++++++++++++++++ ipaserver/plugins/stageuser.py | 16 +- ipaserver/plugins/user.py | 23 ++- ipatests/test_ipapython/test_dn.py | 20 ++ 15 files changed, 862 insertions(+), 13 deletions(-) create mode 100644 install/share/73certmap.ldif create mode 100644 install/updates/73-certmap.update create mode 100644 ipaserver/plugins/certmap.py diff --git a/ACI.txt b/ACI.txt index 0b4748953..a36d460b7 100644 --- a/ACI.txt +++ b/ACI.txt @@ -40,6 +40,18 @@ dn: cn=caacls,cn=ca,dc=ipa,dc=example aci: (targetattr = "cn || description || ipaenabledflag")(targetfilter = "(objectclass=ipacaacl)")(version 3.0;acl "permission:System: Modify CA ACL";allow (write) groupdn = "ldap:///cn=System: Modify CA ACL,cn=permissions,cn=pbac,dc=ipa,dc=example";) dn: cn=caacls,cn=ca,dc=ipa,dc=example aci: (targetattr = "cn || createtimestamp || description || entryusn || hostcategory || ipacacategory || ipacertprofilecategory || ipaenabledflag || ipamemberca || ipamembercertprofile || ipauniqueid || member || memberhost || memberservice || memberuser || modifytimestamp || objectclass || servicecategory || usercategory")(targetfilter = "(objectclass=ipacaacl)")(version 3.0;acl "permission:System: Read CA ACLs";allow (compare,read,search) userdn = "ldap:///all";) +dn: cn=certmap,dc=ipa,dc=example +aci: (targetattr = "ipacertmappromptusername")(targetfilter = "(objectclass=ipacertmapconfigobject)")(version 3.0;acl "permission:System: Modify Certmap Configuration";allow (write) groupdn = "ldap:///cn=System: Modify Certmap Configuration,cn=permissions,cn=pbac,dc=ipa,dc=example";) +dn: cn=certmap,dc=ipa,dc=example +aci: (targetattr = "cn || ipacertmappromptusername")(targetfilter = "(objectclass=ipacertmapconfigobject)")(version 3.0;acl "permission:System: Read Certmap Configuration";allow (compare,read,search) userdn = "ldap:///all";) +dn: cn=certmaprules,cn=certmap,dc=ipa,dc=example +aci: (targetfilter = "(objectclass=ipacertmaprule)")(version 3.0;acl "permission:System: Add Certmap Rules";allow (add) groupdn = "ldap:///cn=System: Add Certmap Rules,cn=permissions,cn=pbac,dc=ipa,dc=example";) +dn: cn=certmaprules,cn=certmap,dc=ipa,dc=example +aci: (targetfilter = "(objectclass=ipacertmaprule)")(version 3.0;acl "permission:System: Delete Certmap Rules";allow (delete) groupdn = "ldap:///cn=System: Delete Certmap Rules,cn=permissions,cn=pbac,dc=ipa,dc=example";) +dn: cn=certmaprules,cn=certmap,dc=ipa,dc=example +aci: (targetattr = "associateddomain || cn || description || ipacertmapmaprule || ipacertmapmatchrule || ipacertmappriority || ipaenabledflag || objectclass")(targetfilter = "(objectclass=ipacertmaprule)")(version 3.0;acl "permission:System: Modify Certmap Rules";allow (write) groupdn = "ldap:///cn=System: Modify Certmap Rules,cn=permissions,cn=pbac,dc=ipa,dc=example";) +dn: cn=certmaprules,cn=certmap,dc=ipa,dc=example +aci: (targetattr = "associateddomain || cn || createtimestamp || description || entryusn || ipacertmapmaprule || ipacertmapmatchrule || ipacertmappriority || ipaenabledflag || modifytimestamp || objectclass")(targetfilter = "(objectclass=ipacertmaprule)")(version 3.0;acl "permission:System: Read Certmap Rules";allow (compare,read,search) userdn = "ldap:///all";) dn: cn=certprofiles,cn=ca,dc=ipa,dc=example aci: (targetfilter = "(objectclass=ipacertprofile)")(version 3.0;acl "permission:System: Delete Certificate Profile";allow (delete) groupdn = "ldap:///cn=System: Delete Certificate Profile,cn=permissions,cn=pbac,dc=ipa,dc=example";) dn: cn=certprofiles,cn=ca,dc=ipa,dc=example @@ -337,6 +349,8 @@ aci: (targetfilter = "(objectclass=posixaccount)")(version 3.0;acl "permission:S dn: cn=users,cn=accounts,dc=ipa,dc=example aci: (targetattr = "krbprincipalkey || passwordhistory || sambalmpassword || sambantpassword || userpassword")(targetfilter = "(&(!(memberOf=cn=admins,cn=groups,cn=accounts,dc=ipa,dc=example))(objectclass=posixaccount))")(version 3.0;acl "permission:System: Change User password";allow (write) groupdn = "ldap:///cn=System: Change User password,cn=permissions,cn=pbac,dc=ipa,dc=example";) dn: cn=users,cn=accounts,dc=ipa,dc=example +aci: (targetattr = "ipacertmapdata || objectclass")(targetfilter = "(objectclass=posixaccount)")(version 3.0;acl "permission:System: Manage User Certificate Mappings";allow (write) groupdn = "ldap:///cn=System: Manage User Certificate Mappings,cn=permissions,cn=pbac,dc=ipa,dc=example";) +dn: cn=users,cn=accounts,dc=ipa,dc=example aci: (targetattr = "usercertificate")(targetfilter = "(objectclass=posixaccount)")(version 3.0;acl "permission:System: Manage User Certificates";allow (write) groupdn = "ldap:///cn=System: Manage User Certificates,cn=permissions,cn=pbac,dc=ipa,dc=example";) dn: cn=users,cn=accounts,dc=ipa,dc=example aci: (targetattr = "krbcanonicalname || krbprincipalname")(targetfilter = "(objectclass=posixaccount)")(version 3.0;acl "permission:System: Manage User Principals";allow (write) groupdn = "ldap:///cn=System: Manage User Principals,cn=permissions,cn=pbac,dc=ipa,dc=example";) @@ -347,7 +361,7 @@ aci: (targetattr = "businesscategory || carlicense || cn || departmentnumber || dn: cn=UPG Definition,cn=Definitions,cn=Managed Entries,cn=etc,dc=ipa,dc=example aci: (targetattr = "*")(target = "ldap:///cn=UPG Definition,cn=Definitions,cn=Managed Entries,cn=etc,dc=ipa,dc=example")(version 3.0;acl "permission:System: Read UPG Definition";allow (compare,read,search) groupdn = "ldap:///cn=System: Read UPG Definition,cn=permissions,cn=pbac,dc=ipa,dc=example";) dn: cn=users,cn=accounts,dc=ipa,dc=example -aci: (targetattr = "audio || businesscategory || carlicense || departmentnumber || destinationindicator || employeenumber || employeetype || facsimiletelephonenumber || homephone || homepostaladdress || inetuserhttpurl || inetuserstatus || internationalisdnnumber || jpegphoto || l || labeleduri || mail || mobile || o || ou || pager || photo || physicaldeliveryofficename || postaladdress || postalcode || postofficebox || preferreddeliverymethod || preferredlanguage || registeredaddress || roomnumber || secretary || seealso || st || street || telephonenumber || teletexterminalidentifier || telexnumber || usercertificate || usersmimecertificate || x121address || x500uniqueidentifier")(targetfilter = "(objectclass=posixaccount)")(version 3.0;acl "permission:System: Read User Addressbook Attributes";allow (compare,read,search) userdn = "ldap:///all";) +aci: (targetattr = "audio || businesscategory || carlicense || departmentnumber || destinationindicator || employeenumber || employeetype || facsimiletelephonenumber || homephone || homepostaladdress || inetuserhttpurl || inetuserstatus || internationalisdnnumber || ipacertmapdata || jpegphoto || l || labeleduri || mail || mobile || o || ou || pager || photo || physicaldeliveryofficename || postaladdress || postalcode || postofficebox || preferreddeliverymethod || preferredlanguage || registeredaddress || roomnumber || secretary || seealso || st || street || telephonenumber || teletexterminalidentifier || telexnumber || usercertificate || usersmimecertificate || x121address || x500uniqueidentifier")(targetfilter = "(objectclass=posixaccount)")(version 3.0;acl "permission:System: Read User Addressbook Attributes";allow (compare,read,search) userdn = "ldap:///all";) dn: dc=ipa,dc=example aci: (targetattr = "cn || createtimestamp || entryusn || gecos || gidnumber || homedirectory || loginshell || modifytimestamp || objectclass || uid || uidnumber")(target = "ldap:///cn=users,cn=compat,dc=ipa,dc=example")(version 3.0;acl "permission:System: Read User Compat Tree";allow (compare,read,search) userdn = "ldap:///anyone";) dn: cn=users,cn=accounts,dc=ipa,dc=example diff --git a/API.txt b/API.txt index 3ebebabac..a8f8ff187 100644 --- a/API.txt +++ b/API.txt @@ -824,6 +824,116 @@ option: Str('version?') output: Entry('result') output: Output('summary', type=[, ]) output: PrimaryKey('value') +command: certmapconfig_mod/1 +args: 0,8,3 +option: Str('addattr*', cli_name='addattr') +option: Flag('all', autofill=True, cli_name='all', default=False) +option: Str('delattr*', cli_name='delattr') +option: Bool('ipacertmappromptusername?', autofill=False, cli_name='promptusername') +option: Flag('raw', autofill=True, cli_name='raw', default=False) +option: Flag('rights', autofill=True, default=False) +option: Str('setattr*', cli_name='setattr') +option: Str('version?') +output: Entry('result') +output: Output('summary', type=[, ]) +output: PrimaryKey('value') +command: certmapconfig_show/1 +args: 0,4,3 +option: Flag('all', autofill=True, cli_name='all', default=False) +option: Flag('raw', autofill=True, cli_name='raw', default=False) +option: Flag('rights', autofill=True, default=False) +option: Str('version?') +output: Entry('result') +output: Output('summary', type=[, ]) +output: PrimaryKey('value') +command: certmaprule_add/1 +args: 1,11,3 +arg: Str('cn', cli_name='rulename') +option: Str('addattr*', cli_name='addattr') +option: Flag('all', autofill=True, cli_name='all', default=False) +option: DNSNameParam('associateddomain*', cli_name='domain') +option: Str('description?', cli_name='desc') +option: Str('ipacertmapmaprule?', cli_name='maprule') +option: Str('ipacertmapmatchrule?', cli_name='matchrule') +option: Int('ipacertmappriority?', cli_name='priority') +option: Flag('ipaenabledflag?', autofill=True, default=True) +option: Flag('raw', autofill=True, cli_name='raw', default=False) +option: Str('setattr*', cli_name='setattr') +option: Str('version?') +output: Entry('result') +output: Output('summary', type=[, ]) +output: PrimaryKey('value') +command: certmaprule_del/1 +args: 1,2,3 +arg: Str('cn+', cli_name='rulename') +option: Flag('continue', autofill=True, cli_name='continue', default=False) +option: Str('version?') +output: Output('result', type=[]) +output: Output('summary', type=[, ]) +output: ListOfPrimaryKeys('value') +command: certmaprule_disable/1 +args: 1,1,3 +arg: Str('cn', cli_name='rulename') +option: Str('version?') +output: Output('result', type=[]) +output: Output('summary', type=[, ]) +output: PrimaryKey('value') +command: certmaprule_enable/1 +args: 1,1,3 +arg: Str('cn', cli_name='rulename') +option: Str('version?') +output: Output('result', type=[]) +output: Output('summary', type=[, ]) +output: PrimaryKey('value') +command: certmaprule_find/1 +args: 1,13,4 +arg: Str('criteria?') +option: Flag('all', autofill=True, cli_name='all', default=False) +option: DNSNameParam('associateddomain*', autofill=False, cli_name='domain') +option: Str('cn?', autofill=False, cli_name='rulename') +option: Str('description?', autofill=False, cli_name='desc') +option: Str('ipacertmapmaprule?', autofill=False, cli_name='maprule') +option: Str('ipacertmapmatchrule?', autofill=False, cli_name='matchrule') +option: Int('ipacertmappriority?', autofill=False, cli_name='priority') +option: Bool('ipaenabledflag?', autofill=False, default=True) +option: Flag('pkey_only?', autofill=True, default=False) +option: Flag('raw', autofill=True, cli_name='raw', default=False) +option: Int('sizelimit?', autofill=False) +option: Int('timelimit?', autofill=False) +option: Str('version?') +output: Output('count', type=[]) +output: ListOfEntries('result') +output: Output('summary', type=[, ]) +output: Output('truncated', type=[]) +command: certmaprule_mod/1 +args: 1,13,3 +arg: Str('cn', cli_name='rulename') +option: Str('addattr*', cli_name='addattr') +option: Flag('all', autofill=True, cli_name='all', default=False) +option: DNSNameParam('associateddomain*', autofill=False, cli_name='domain') +option: Str('delattr*', cli_name='delattr') +option: Str('description?', autofill=False, cli_name='desc') +option: Str('ipacertmapmaprule?', autofill=False, cli_name='maprule') +option: Str('ipacertmapmatchrule?', autofill=False, cli_name='matchrule') +option: Int('ipacertmappriority?', autofill=False, cli_name='priority') +option: Flag('ipaenabledflag?', autofill=True, default=True) +option: Flag('raw', autofill=True, cli_name='raw', default=False) +option: Flag('rights', autofill=True, default=False) +option: Str('setattr*', cli_name='setattr') +option: Str('version?') +output: Entry('result') +output: Output('summary', type=[, ]) +output: PrimaryKey('value') +command: certmaprule_show/1 +args: 1,4,3 +arg: Str('cn', cli_name='rulename') +option: Flag('all', autofill=True, cli_name='all', default=False) +option: Flag('raw', autofill=True, cli_name='raw', default=False) +option: Flag('rights', autofill=True, default=False) +option: Str('version?') +output: Entry('result') +output: Output('summary', type=[, ]) +output: PrimaryKey('value') command: certprofile_del/1 args: 1,2,3 arg: Str('cn+', cli_name='id') @@ -4762,6 +4872,20 @@ option: Str('version?') output: Entry('result') output: Output('summary', type=[, ]) output: PrimaryKey('value') +command: stageuser_add_certmapdata/1 +args: 2,7,3 +arg: Str('uid', cli_name='login') +arg: Str('ipacertmapdata*', alwaysask=False, cli_name='certmapdata') +option: Flag('all', autofill=True, cli_name='all', default=False) +option: Bytes('certificate*', cli_name='certificate') +option: DNParam('issuer?', cli_name='issuer') +option: Flag('no_members', autofill=True, default=False) +option: Flag('raw', autofill=True, cli_name='raw', default=False) +option: DNParam('subject?', cli_name='subject') +option: Str('version?') +output: Entry('result') +output: Output('summary', type=[, ]) +output: PrimaryKey('value') command: stageuser_add_manager/1 args: 1,5,3 arg: Str('uid', cli_name='login') @@ -4915,6 +5039,20 @@ option: Str('version?') output: Entry('result') output: Output('summary', type=[, ]) output: PrimaryKey('value') +command: stageuser_remove_certmapdata/1 +args: 2,7,3 +arg: Str('uid', cli_name='login') +arg: Str('ipacertmapdata*', alwaysask=False, cli_name='certmapdata') +option: Flag('all', autofill=True, cli_name='all', default=False) +option: Bytes('certificate*', cli_name='certificate') +option: DNParam('issuer?', cli_name='issuer') +option: Flag('no_members', autofill=True, default=False) +option: Flag('raw', autofill=True, cli_name='raw', default=False) +option: DNParam('subject?', cli_name='subject') +option: Str('version?') +output: Entry('result') +output: Output('summary', type=[, ]) +output: PrimaryKey('value') command: stageuser_remove_manager/1 args: 1,5,3 arg: Str('uid', cli_name='login') @@ -5796,6 +5934,20 @@ option: Str('version?') output: Entry('result') output: Output('summary', type=[, ]) output: PrimaryKey('value') +command: user_add_certmapdata/1 +args: 2,7,3 +arg: Str('uid', cli_name='login') +arg: Str('ipacertmapdata*', alwaysask=False, cli_name='certmapdata') +option: Flag('all', autofill=True, cli_name='all', default=False) +option: Bytes('certificate*', cli_name='certificate') +option: DNParam('issuer?', cli_name='issuer') +option: Flag('no_members', autofill=True, default=False) +option: Flag('raw', autofill=True, cli_name='raw', default=False) +option: DNParam('subject?', cli_name='subject') +option: Str('version?') +output: Entry('result') +output: Output('summary', type=[, ]) +output: PrimaryKey('value') command: user_add_manager/1 args: 1,5,3 arg: Str('uid', cli_name='login') @@ -5968,6 +6120,20 @@ option: Str('version?') output: Entry('result') output: Output('summary', type=[, ]) output: PrimaryKey('value') +command: user_remove_certmapdata/1 +args: 2,7,3 +arg: Str('uid', cli_name='login') +arg: Str('ipacertmapdata*', alwaysask=False, cli_name='certmapdata') +option: Flag('all', autofill=True, cli_name='all', default=False) +option: Bytes('certificate*', cli_name='certificate') +option: DNParam('issuer?', cli_name='issuer') +option: Flag('no_members', autofill=True, default=False) +option: Flag('raw', autofill=True, cli_name='raw', default=False) +option: DNParam('subject?', cli_name='subject') +option: Str('version?') +output: Entry('result') +output: Output('summary', type=[, ]) +output: PrimaryKey('value') command: user_remove_manager/1 args: 1,5,3 arg: Str('uid', cli_name='login') @@ -6351,6 +6517,17 @@ default: cert_request/1 default: cert_revoke/1 default: cert_show/1 default: cert_status/1 +default: certmapconfig/1 +default: certmapconfig_mod/1 +default: certmapconfig_show/1 +default: certmaprule/1 +default: certmaprule_add/1 +default: certmaprule_del/1 +default: certmaprule_disable/1 +default: certmaprule_enable/1 +default: certmaprule_find/1 +default: certmaprule_mod/1 +default: certmaprule_show/1 default: certprofile/1 default: certprofile_del/1 default: certprofile_find/1 @@ -6706,12 +6883,14 @@ default: stageuser/1 default: stageuser_activate/1 default: stageuser_add/1 default: stageuser_add_cert/1 +default: stageuser_add_certmapdata/1 default: stageuser_add_manager/1 default: stageuser_add_principal/1 default: stageuser_del/1 default: stageuser_find/1 default: stageuser_mod/1 default: stageuser_remove_cert/1 +default: stageuser_remove_certmapdata/1 default: stageuser_remove_manager/1 default: stageuser_remove_principal/1 default: stageuser_show/1 @@ -6789,6 +6968,7 @@ default: trustdomain_mod/1 default: user/1 default: user_add/1 default: user_add_cert/1 +default: user_add_certmapdata/1 default: user_add_manager/1 default: user_add_principal/1 default: user_del/1 @@ -6797,6 +6977,7 @@ default: user_enable/1 default: user_find/1 default: user_mod/1 default: user_remove_cert/1 +default: user_remove_certmapdata/1 default: user_remove_manager/1 default: user_remove_principal/1 default: user_show/1 diff --git a/VERSION.m4 b/VERSION.m4 index 8d6671847..8c9327731 100644 --- a/VERSION.m4 +++ b/VERSION.m4 @@ -73,8 +73,8 @@ define(IPA_DATA_VERSION, 20100614120000) # # ######################################################## define(IPA_API_VERSION_MAJOR, 2) -define(IPA_API_VERSION_MINOR, 218) -# Last change: Remove no_option flag for nsaccountlock and add cli_name='disabled' +define(IPA_API_VERSION_MINOR, 219) +# Last change: Support for Certificate Identity Mapping ######################################################## diff --git a/install/share/73certmap.ldif b/install/share/73certmap.ldif new file mode 100644 index 000000000..9c67ccb36 --- /dev/null +++ b/install/share/73certmap.ldif @@ -0,0 +1,14 @@ +## IPA Base OID: +## +## Attributes: 2.16.840.1.113730.3.8.22.1.x +## ObjectClasses: 2.16.840.1.113730.3.8.22.2.y +## +dn: cn=schema +attributeTypes: (2.16.840.1.113730.3.8.22.1.1 NAME 'ipaCertMapPromptUsername' DESC 'Prompt for the username when multiple identities are mapped to a certificate' EQUALITY booleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE X-ORIGIN 'IPA v4.5' ) +attributeTypes: (2.16.840.1.113730.3.8.22.1.2 NAME 'ipaCertMapMapRule' DESC 'Certificate Mapping Rule' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'IPA v4.5' ) +attributeTypes: (2.16.840.1.113730.3.8.22.1.3 NAME 'ipaCertMapMatchRule' DESC 'Certificate Matching Rule' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'IPA v4.5' ) +attributeTypes: (2.16.840.1.113730.3.8.22.1.4 NAME 'ipaCertMapData' DESC 'Certificate Mapping Data' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v4.5' ) +attributeTypes: (2.16.840.1.113730.3.8.22.1.5 NAME 'ipaCertMapPriority' DESC 'Rule priority' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'IPA v4.5' ) +objectClasses: (2.16.840.1.113730.3.8.22.2.1 NAME 'ipaCertMapConfigObject' DESC 'IPA Certificate Mapping global config options' AUXILIARY MAY ipaCertMapPromptUsername X-ORIGIN 'IPA v4.5' ) +objectClasses: (2.16.840.1.113730.3.8.22.2.2 NAME 'ipaCertMapRule' DESC 'IPA Certificate Mapping rule' SUP top STRUCTURAL MUST cn MAY ( description $ ipaCertMapMapRule $ ipaCertMapMatchRule $ associatedDomain $ ipaCertMapPriority $ ipaEnabledFlag ) X-ORIGIN 'IPA v4.5' ) +objectClasses: (2.16.840.1.113730.3.8.22.2.3 NAME 'ipaCertMapObject' DESC 'IPA Object for Certificate Mapping' AUXILIARY MAY ipaCertMapData X-ORIGIN 'IPA v4.5' ) diff --git a/install/share/Makefile.am b/install/share/Makefile.am index c58e1d2dd..bbf6ce10a 100644 --- a/install/share/Makefile.am +++ b/install/share/Makefile.am @@ -27,6 +27,7 @@ dist_app_DATA = \ 70topology.ldif \ 71idviews.ldif \ 72domainlevels.ldif \ + 73certmap.ldif \ anon-princ-aci.ldif \ bootstrap-template.ldif \ ca-topology.uldif \ diff --git a/install/updates/73-certmap.update b/install/updates/73-certmap.update new file mode 100644 index 000000000..ecb3db353 --- /dev/null +++ b/install/updates/73-certmap.update @@ -0,0 +1,23 @@ +# Configuration for Certificate Identity Mapping +dn: cn=certmap,$SUFFIX +default:objectclass: top +default:objectclass: nsContainer +default:objectclass: ipaCertMapConfigObject +default:cn: certmap +default:ipaCertMapPromptUsername: FALSE + +dn: cn=certmaprules,cn=certmap,$SUFFIX +default:objectclass: top +default:objectclass: nsContainer +default:cn: certmaprules + +# Certificate Identity Mapping Administrators +dn: cn=Certificate Identity Mapping Administrators,cn=privileges,cn=pbac,$SUFFIX +default:objectClass: top +default:objectClass: groupofnames +default:objectClass: nestedgroup +default:cn: Certificate Identity Mapping Administrators +default:description: Certificate Identity Mapping Administrators + +dn: $SUFFIX +add:aci: (targetattr = "ipacertmapdata")(targattrfilters="add=objectclass:(objectclass=ipacertmapobject)")(version 3.0;acl "selfservice:Users can manage their own X.509 certificate identity mappings";allow (write) userdn = "ldap:///self";) diff --git a/install/updates/Makefile.am b/install/updates/Makefile.am index e8a55e173..0ff0edb93 100644 --- a/install/updates/Makefile.am +++ b/install/updates/Makefile.am @@ -61,6 +61,7 @@ app_DATA = \ 72-domainlevels.update \ 73-custodia.update \ 73-winsync.update \ + 73-certmap.update \ 90-post_upgrade_plugins.update \ $(NULL) diff --git a/ipalib/constants.py b/ipalib/constants.py index bc78422ea..8789a95d0 100644 --- a/ipalib/constants.py +++ b/ipalib/constants.py @@ -122,6 +122,8 @@ DEFAULT_CONFIG = ( ('container_dnsservers', DN(('cn', 'servers'), ('cn', 'dns'))), ('container_custodia', DN(('cn', 'custodia'), ('cn', 'ipa'), ('cn', 'etc'))), ('container_sysaccounts', DN(('cn', 'sysaccounts'), ('cn', 'etc'))), + ('container_certmap', DN(('cn', 'certmap'))), + ('container_certmaprules', DN(('cn', 'certmaprules'), ('cn', 'certmap'))), # Ports, hosts, and URIs: ('xmlrpc_uri', 'http://localhost:8888/ipa/xml'), diff --git a/ipapython/dn.py b/ipapython/dn.py index 4e8c22bb0..a54629e8b 100644 --- a/ipapython/dn.py +++ b/ipapython/dn.py @@ -1155,9 +1155,15 @@ class DN(object): def _get_rdn(self, rdn): return self.RDN_type(*rdn, **{'raw': True}) - def __str__(self): + def ldap_text(self): return dn2str(self.rdns) + def x500_text(self): + return dn2str(reversed(self.rdns)) + + def __str__(self): + return self.ldap_text() + def __repr__(self): return "%s.%s('%s')" % (self.__module__, self.__class__.__name__, self.__str__()) diff --git a/ipaserver/install/dsinstance.py b/ipaserver/install/dsinstance.py index 99e61903d..733dd4006 100644 --- a/ipaserver/install/dsinstance.py +++ b/ipaserver/install/dsinstance.py @@ -70,6 +70,7 @@ IPA_SCHEMA_FILES = ("60kerberos.ldif", "70topology.ldif", "71idviews.ldif", "72domainlevels.ldif", + "73certmap.ldif", "15rfc2307bis.ldif", "15rfc4876.ldif") diff --git a/ipaserver/plugins/baseuser.py b/ipaserver/plugins/baseuser.py index 75cf7d81a..44adc76ec 100644 --- a/ipaserver/plugins/baseuser.py +++ b/ipaserver/plugins/baseuser.py @@ -19,14 +19,17 @@ import six -from ipalib import api, errors -from ipalib import Flag, Int, Password, Str, Bool, StrEnum, DateTime, Bytes +from ipalib import api, errors, x509 +from ipalib import ( + Flag, Int, Password, Str, Bool, StrEnum, DateTime, Bytes, DNParam) from ipalib.parameters import Principal from ipalib.plugable import Registry from .baseldap import ( DN, LDAPObject, LDAPCreate, LDAPUpdate, LDAPSearch, LDAPDelete, - LDAPRetrieve, LDAPAddAttribute, LDAPRemoveAttribute, LDAPAddMember, - LDAPRemoveMember, LDAPAddAttributeViaOption, LDAPRemoveAttributeViaOption) + LDAPRetrieve, LDAPAddAttribute, LDAPModAttribute, LDAPRemoveAttribute, + LDAPAddMember, LDAPRemoveMember, + LDAPAddAttributeViaOption, LDAPRemoveAttributeViaOption, + add_missing_object_class) from ipaserver.plugins.service import ( validate_certificate, validate_realm, normalize_principal) from ipalib.request import context @@ -134,7 +137,7 @@ class baseuser(LDAPObject): object_class_config = 'ipauserobjectclasses' possible_objectclasses = [ 'meporiginentry', 'ipauserauthtypeclass', 'ipauser', - 'ipatokenradiusproxyuser' + 'ipatokenradiusproxyuser', 'ipacertmapobject' ] disallow_object_classes = ['krbticketpolicyaux'] permission_filter_objectclasses = ['posixaccount'] @@ -146,7 +149,8 @@ class baseuser(LDAPObject): 'memberofindirect', 'ipauserauthtype', 'userclass', 'ipatokenradiusconfiglink', 'ipatokenradiususername', 'krbprincipalexpiration', 'usercertificate;binary', - 'krbprincipalname', 'krbcanonicalname' + 'krbprincipalname', 'krbcanonicalname', + 'ipacertmapdata' ] search_display_attributes = [ 'uid', 'givenname', 'sn', 'homedirectory', 'krbcanonicalname', @@ -360,6 +364,13 @@ class baseuser(LDAPObject): label=_('Certificate'), doc=_('Base-64 encoded user certificate'), ), + Str( + 'ipacertmapdata*', + cli_name='certmapdata', + label=_('Certificate mapping data'), + doc=_('Certificate mapping data'), + flags=['no_create', 'no_update', 'no_search'], + ), ) def normalize_and_validate_email(self, email, config=None): @@ -728,3 +739,154 @@ class baseuser_remove_cert(LDAPRemoveAttributeViaOption): self.obj.convert_usercertificate_post(entry_attrs, **options) return dn + + +class ModCertMapData(LDAPModAttribute): + attribute = 'ipacertmapdata' + takes_options = ( + DNParam( + 'issuer?', + cli_name='issuer', + label=_('Issuer'), + doc=_('Issuer of the certificate'), + flags=['virtual_attribute'] + ), + DNParam( + 'subject?', + cli_name='subject', + label=_('Subject'), + doc=_('Subject of the certificate'), + flags=['virtual_attribute'] + ), + Bytes( + 'certificate*', validate_certificate, + cli_name='certificate', + label=_('Certificate'), + doc=_('Base-64 encoded user certificate'), + flags=['virtual_attribute'] + ), + ) + + @staticmethod + def _build_mapdata(subject, issuer): + return u'X509:{issuer}{subject}'.format( + issuer=issuer.x500_text(), subject=subject.x500_text()) + + @classmethod + def _convert_options_to_certmap(cls, entry_attrs, issuer=None, + subject=None, certificates=()): + """ + Converts options to ipacertmapdata + + When --subject --issuer or --certificate options are used, + the value for ipacertmapdata is built from extracting subject and + issuer, + converting their values to X500 ordering and using the format + X509:issuersubject + For instance: + X509:O=DOMAIN,CN=Certificate AuthorityO=DOMAIN,CN=user + A list of values can be returned if --certificate is used multiple + times, or in conjunction with --subject --issuer. + """ + data = [] + data.extend(entry_attrs.get(cls.attribute, list())) + + if issuer or subject: + data.append(cls._build_mapdata(subject, issuer)) + + for dercert in certificates: + cert = x509.load_certificate(dercert, x509.DER) + issuer = DN(cert.issuer) + subject = DN(cert.subject) + if not subject: + raise errors.ValidationError( + name='certificate', + error=_('cannot have an empty subject')) + data.append(cls._build_mapdata(subject, issuer)) + + entry_attrs[cls.attribute] = data + + def get_args(self): + # ipacertmapdata is not mandatory as it can be built + # from the values subject+issuer or from reading certificate + for arg in super(ModCertMapData, self).get_args(): + if arg.name == 'ipacertmapdata': + yield arg.clone(required=False, alwaysask=False) + else: + yield arg.clone() + + def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, + **options): + # The 3 valid calls are + # ipa user-add-certmapdata LOGIN --subject xx --issuer yy + # ipa user-add-certmapdata LOGIN [DATA] --certificate xx + # ipa user-add-certmapdata LOGIN DATA + # Check that at least one of the 3 formats is used + + try: + certmapdatas = keys[1] or [] + except IndexError: + certmapdatas = [] + issuer = options.get('issuer') + subject = options.get('subject') + certificates = options.get('certificate', []) + + # If only LOGIN is supplied, then we need either subject or issuer or + # certificate + if (not certmapdatas and not issuer and not subject and + not certificates): + raise errors.RequirementError(name='ipacertmapdata') + + # If subject or issuer is provided, other options are not allowed + if subject or issuer: + if certificates: + raise errors.MutuallyExclusiveError( + reason=_('cannot specify both subject/issuer ' + 'and certificate')) + if certmapdatas: + raise errors.MutuallyExclusiveError( + reason=_('cannot specify both subject/issuer ' + 'and ipacertmapdata')) + # If subject or issuer is provided, then the other one is required + if not subject: + raise errors.RequirementError(name='subject') + if not issuer: + raise errors.RequirementError(name='issuer') + + # if the command is called with --subject --issuer or --certificate + # we need to add ipacertmapdata to the attrs_list in order to + # display the resulting value in the command output + if 'ipacertmapdata' not in attrs_list: + attrs_list.append('ipacertmapdata') + + self._convert_options_to_certmap( + entry_attrs, + issuer=issuer, + subject=subject, + certificates=certificates) + + return dn + + +class baseuser_add_certmapdata(ModCertMapData, LDAPAddAttribute): + __doc__ = _("Add one or more certificate mappings to the user entry.") + msg_summary = _('Added certificate mappings to user "%(value)s"') + + def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, + **options): + + dn = super(baseuser_add_certmapdata, self).pre_callback( + ldap, dn, entry_attrs, attrs_list, *keys, **options) + + # The objectclass ipacertmapobject may not be present on + # existing user entries. We need to add it if we define a new + # value for ipacertmapdata + add_missing_object_class(ldap, u'ipacertmapobject', dn) + + return dn + + +class baseuser_remove_certmapdata(ModCertMapData, + LDAPRemoveAttribute): + __doc__ = _("Remove one or more certificate mappings from the user entry.") + msg_summary = _('Removed certificate mappings from user "%(value)s"') diff --git a/ipaserver/plugins/certmap.py b/ipaserver/plugins/certmap.py new file mode 100644 index 000000000..c37eae38e --- /dev/null +++ b/ipaserver/plugins/certmap.py @@ -0,0 +1,391 @@ +# Authors: +# Florence Blanc-Renaud +# +# Copyright (C) 2017 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 . + +import six + +from ipalib import api, errors +from ipalib.parameters import Bool, DNSNameParam, Flag, Int, Str +from ipalib.plugable import Registry +from .baseldap import ( + LDAPCreate, + LDAPDelete, + LDAPObject, + LDAPQuery, + LDAPRetrieve, + LDAPSearch, + LDAPUpdate, + pkey_to_value) +from ipalib import _, ngettext +from ipalib import output + + +if six.PY3: + unicode = str + +__doc__ = _(""" +Certificate Identity Mapping +""") + _(""" +Manage Certificate Identity Mapping configuration and rules. +""") + _(""" +IPA supports the use of certificates for authentication. Certificates can +either be stored in the user entry (full certificate in the usercertificate +attribute), or simply linked to the user entry through a mapping. +This code enables the management of the rules allowing to link a +certificate to a user entry. +""") + _(""" +EXAMPLES: +""") + _(""" + Display the Certificate Identity Mapping global configuration: + ipa certmapconfig-show +""") + _(""" + Modify Certificate Identity Mapping global configuration: + ipa certmapconfig-mod --promptusername=TRUE +""") + _(""" + Create a new Certificate Identity Mapping Rule: + ipa certmaprule-add rule1 --desc="Link certificate with subject and issuer" +""") + _(""" + Modify a Certificate Identity Mapping Rule: + ipa certmaprule-mod rule1 --maprule="" +""") + _(""" + Disable a Certificate Identity Mapping Rule: + ipa certmaprule-disable rule1 +""") + _(""" + Enable a Certificate Identity Mapping Rule: + ipa certmaprule-enable rule1 +""") + _(""" + Display information about a Certificate Identity Mapping Rule: + ipa certmaprule-show rule1 +""") + _(""" + Find all Certificate Identity Mapping Rules with the specified domain: + ipa certmaprule-find --domain example.com +""") + _(""" + Delete a Certificate Identity Mapping Rule: + ipa certmaprule-del rule1 +""") + +register = Registry() + + +def check_associateddomain_is_trusted(api_inst, options): + """ + Check that the associateddomain in options are either IPA domain or + a trusted domain. + + :param api_inst: API instance + :param associateddomain: domains to be checked + + :raises: ValidationError if the domain is neither IPA domain nor trusted + """ + domains = options.get('associateddomain') + if domains: + trust_suffix_namespace = set() + trust_suffix_namespace.add(api_inst.env.domain.lower()) + + trust_objects = api_inst.Command.trust_find(sizelimit=0)['result'] + for obj in trust_objects: + trustdomains = api_inst.Command.trustdomain_find( + obj['cn'][0], sizelimit=0)['result'] + for domain in trustdomains: + trust_suffix_namespace.add(domain['cn'][0].lower()) + + for dom in domains: + if not str(dom).lower() in trust_suffix_namespace: + raise errors.ValidationError( + name=_('domain'), + error=_('The domain %s is neither IPA domain nor a trusted' + 'domain.') % dom + ) + + +@register() +class certmapconfig(LDAPObject): + """ + Certificate Identity Mapping configuration object + """ + object_name = _('Certificate Identity Mapping configuration options') + default_attributes = ['ipacertmappromptusername'] + + container_dn = api.env.container_certmap + + label = _('Certificate Identity Mapping Global Configuration') + label_singular = _('Certificate Identity Mapping Global Configuration') + + takes_params = ( + Bool( + 'ipacertmappromptusername', + cli_name='promptusername', + label=_('Prompt for the username'), + doc=_('Prompt for the username when multiple identities' + ' are mapped to a certificate'), + ), + ) + + permission_filter_objectclasses = ['ipacertmapconfigobject'] + managed_permissions = { + 'System: Read Certmap Configuration': { + 'replaces_global_anonymous_aci': True, + 'ipapermbindruletype': 'all', + 'ipapermright': {'read', 'search', 'compare'}, + 'ipapermdefaultattr': { + 'ipacertmappromptusername', + 'cn', + }, + }, + 'System: Modify Certmap Configuration': { + 'replaces_global_anonymous_aci': True, + 'ipapermright': {'write'}, + 'ipapermdefaultattr': { + 'ipacertmappromptusername', + }, + 'default_privileges': { + 'Certificate Identity Mapping Administrators'}, + }, + } + + +@register() +class certmapconfig_mod(LDAPUpdate): + __doc__ = _('Modify Certificate Identity Mapping configuration.') + + +@register() +class certmapconfig_show(LDAPRetrieve): + __doc__ = _('Show the current Certificate Identity Mapping configuration.') + + +@register() +class certmaprule(LDAPObject): + """ + Certificate Identity Mapping Rules + """ + + label = _('Certificate Identity Mapping Rules') + label_singular = _('Certificate Identity Mapping Rule') + + object_name = _('Certificate Identity Mapping Rule') + object_name_plural = _('Certificate Identity Mapping Rules') + object_class = ['ipacertmaprule'] + + container_dn = api.env.container_certmaprules + default_attributes = [ + 'cn', 'description', + 'ipacertmapmaprule', + 'ipacertmapmatchrule', + 'associateddomain', + 'ipacertmappriority', + 'ipaenabledflag' + ] + search_attributes = [ + 'cn', 'description', + 'ipacertmapmaprule', + 'ipacertmapmatchrule', + 'associateddomain', + 'ipacertmappriority', + 'ipaenabledflag' + ] + + takes_params = ( + Str( + 'cn', + cli_name='rulename', + primary_key=True, + label=_('Rule name'), + doc=_('Certificate Identity Mapping Rule name'), + ), + Str( + 'description?', + cli_name='desc', + label=_('Description'), + doc=_('Certificate Identity Mapping Rule description'), + ), + Str( + 'ipacertmapmaprule?', + cli_name='maprule', + label=_('Mapping rule'), + doc=_('Rule used to map the certificate with a user entry'), + ), + Str( + 'ipacertmapmatchrule?', + cli_name='matchrule', + label=_('Matching rule'), + doc=_('Rule used to check if a certificate can be used for' + ' authentication'), + ), + DNSNameParam( + 'associateddomain*', + cli_name='domain', + label=_('Domain name'), + doc=_('Domain where the user entry will be searched'), + ), + Int( + 'ipacertmappriority?', + cli_name='priority', + label=_('Priority'), + doc=_('Priority of the rule (higher number means lower priority'), + minvalue=0, + ), + Flag( + 'ipaenabledflag?', + label=_('Enabled'), + flags=['no_option'], + default=True + ), + ) + + permission_filter_objectclasses = ['ipacertmaprule'] + managed_permissions = { + 'System: Add Certmap Rules': { + 'replaces_global_anonymous_aci': True, + 'ipapermright': {'add'}, + 'default_privileges': { + 'Certificate Identity Mapping Administrators'}, + }, + 'System: Read Certmap Rules': { + 'replaces_global_anonymous_aci': True, + 'ipapermbindruletype': 'all', + 'ipapermright': {'read', 'search', 'compare'}, + 'ipapermdefaultattr': { + 'objectclass', 'cn', 'description', + 'ipacertmapmaprule', 'ipacertmapmatchrule', 'associateddomain', + 'ipacertmappriority', 'ipaenabledflag', + }, + }, + 'System: Delete Certmap Rules': { + 'replaces_global_anonymous_aci': True, + 'ipapermright': {'delete'}, + 'default_privileges': { + 'Certificate Identity Mapping Administrators'}, + }, + 'System: Modify Certmap Rules': { + 'replaces_global_anonymous_aci': True, + 'ipapermright': {'write'}, + 'ipapermdefaultattr': { + 'objectclass', 'cn', 'description', + 'ipacertmapmaprule', 'ipacertmapmatchrule', 'associateddomain', + 'ipacertmappriority', 'ipaenabledflag', + }, + 'default_privileges': { + 'Certificate Identity Mapping Administrators'}, + }, + } + + +@register() +class certmaprule_add(LDAPCreate): + __doc__ = _('Create a new Certificate Identity Mapping Rule.') + + msg_summary = _('Added Certificate Identity Mapping Rule "%(value)s"') + + def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, + **options): + check_associateddomain_is_trusted(self.api, options) + return dn + + +@register() +class certmaprule_mod(LDAPUpdate): + __doc__ = _('Modify a Certificate Identity Mapping Rule.') + + msg_summary = _('Modified Certificate Identity Mapping Rule "%(value)s"') + + def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, + **options): + check_associateddomain_is_trusted(self.api, options) + return dn + + +@register() +class certmaprule_find(LDAPSearch): + __doc__ = _('Search for Certificate Identity Mapping Rules.') + + msg_summary = ngettext( + '%(count)d Certificate Identity Mapping Rule matched', + '%(count)d Certificate Identity Mapping Rules matched', 0 + ) + + +@register() +class certmaprule_show(LDAPRetrieve): + __doc__ = _('Display information about a Certificate Identity Mapping' + ' Rule.') + + +@register() +class certmaprule_del(LDAPDelete): + __doc__ = _('Delete a Certificate Identity Mapping Rule.') + + msg_summary = _('Deleted Certificate Identity Mapping Rule "%(value)s"') + + +@register() +class certmaprule_enable(LDAPQuery): + __doc__ = _('Enable a Certificate Identity Mapping Rule.') + + msg_summary = _('Enabled Certificate Identity Mapping Rule "%(value)s"') + has_output = output.standard_value + + def execute(self, cn, **options): + ldap = self.obj.backend + + dn = self.obj.get_dn(cn) + try: + entry_attrs = ldap.get_entry(dn, ['ipaenabledflag']) + except errors.NotFound: + self.obj.handle_not_found(cn) + + entry_attrs['ipaenabledflag'] = ['TRUE'] + + try: + ldap.update_entry(entry_attrs) + except errors.EmptyModlist: + pass + + return dict( + result=True, + value=pkey_to_value(cn, options), + ) + + +@register() +class certmaprule_disable(LDAPQuery): + __doc__ = _('Disable a Certificate Identity Mapping Rule.') + + msg_summary = _('Disabled Certificate Identity Mapping Rule "%(value)s"') + has_output = output.standard_value + + def execute(self, cn, **options): + ldap = self.obj.backend + + dn = self.obj.get_dn(cn) + try: + entry_attrs = ldap.get_entry(dn, ['ipaenabledflag']) + except errors.NotFound: + self.obj.handle_not_found(cn) + + entry_attrs['ipaenabledflag'] = ['FALSE'] + + try: + ldap.update_entry(entry_attrs) + except errors.EmptyModlist: + pass + + return dict( + result=True, + value=pkey_to_value(cn, options), + ) diff --git a/ipaserver/plugins/stageuser.py b/ipaserver/plugins/stageuser.py index 560251486..c7ea478ab 100644 --- a/ipaserver/plugins/stageuser.py +++ b/ipaserver/plugins/stageuser.py @@ -44,7 +44,9 @@ from .baseuser import ( baseuser_add_principal, baseuser_remove_principal, baseuser_add_manager, - baseuser_remove_manager) + baseuser_remove_manager, + baseuser_add_certmapdata, + baseuser_remove_certmapdata) from ipalib.request import context from ipalib.util import set_krbcanonicalname from ipalib import _, ngettext @@ -772,3 +774,15 @@ class stageuser_add_principal(baseuser_add_principal): class stageuser_remove_principal(baseuser_remove_principal): __doc__ = _('Remove principal alias from the stageuser entry') msg_summary = _('Removed aliases from stageuser "%(value)s"') + + +@register() +class stageuser_add_certmapdata(baseuser_add_certmapdata): + __doc__ = _("Add one or more certificate mappings to the stage user" + " entry.") + + +@register() +class stageuser_remove_certmapdata(baseuser_remove_certmapdata): + __doc__ = _("Remove one or more certificate mappings from the stage user" + " entry.") diff --git a/ipaserver/plugins/user.py b/ipaserver/plugins/user.py index 88171cf40..2d29dfb11 100644 --- a/ipaserver/plugins/user.py +++ b/ipaserver/plugins/user.py @@ -22,7 +22,6 @@ import time from time import gmtime, strftime import posixpath import os - import six from ipalib import api @@ -46,7 +45,9 @@ from .baseuser import ( baseuser_add_cert, baseuser_remove_cert, baseuser_add_principal, - baseuser_remove_principal) + baseuser_remove_principal, + baseuser_add_certmapdata, + baseuser_remove_certmapdata) from .idviews import remove_ipaobject_overrides from ipalib.plugable import Registry from .baseldap import ( @@ -179,6 +180,7 @@ class user(baseuser): 'secretary', 'usercertificate', 'usersmimecertificate', 'x500uniqueidentifier', 'inetuserhttpurl', 'inetuserstatus', + 'ipacertmapdata', }, 'fixup_function': fix_addressbook_permission_bindrule, }, @@ -366,6 +368,13 @@ class user(baseuser): }, 'default_privileges': {'PassSync Service'}, }, + 'System: Manage User Certificate Mappings': { + 'ipapermright': {'write'}, + 'ipapermdefaultattr': {'ipacertmapdata', 'objectclass'}, + 'default_privileges': { + 'Certificate Identity Mapping Administrators' + }, + }, } takes_params = baseuser.takes_params + ( @@ -1184,6 +1193,16 @@ class user_remove_cert(baseuser_remove_cert): msg_summary = _('Removed certificates from user "%(value)s"') +@register() +class user_add_certmapdata(baseuser_add_certmapdata): + __doc__ = _("Add one or more certificate mappings to the user entry.") + + +@register() +class user_remove_certmapdata(baseuser_remove_certmapdata): + __doc__ = _("Remove one or more certificate mappings from the user entry.") + + @register() class user_add_manager(baseuser_add_manager): __doc__ = _("Add a manager to the user entry") diff --git a/ipatests/test_ipapython/test_dn.py b/ipatests/test_ipapython/test_dn.py index 3ca3b570a..24b609343 100644 --- a/ipatests/test_ipapython/test_dn.py +++ b/ipatests/test_ipapython/test_dn.py @@ -1184,6 +1184,26 @@ class TestDN(unittest.TestCase): self.assertFalse(dn3_a in s) self.assertFalse(dn3_b in s) + def test_x500_text(self): + # null DN x500 ordering and LDAP ordering are the same + nulldn = DN() + self.assertEqual(nulldn.ldap_text(), nulldn.x500_text()) + + # reverse a DN with a single RDN + self.assertEqual(self.dn1.ldap_text(), self.dn1.x500_text()) + + # reverse a DN with 2 RDNs + dn3_x500 = self.dn3.x500_text() + dn3_rev = DN(self.rdn2, self.rdn1) + self.assertEqual(dn3_rev.ldap_text(), dn3_x500) + + # reverse a longer DN + longdn_x500 = self.base_container_dn.x500_text() + longdn_rev = DN(longdn_x500) + l = len(self.base_container_dn) + for i in range(l): + self.assertEquals(longdn_rev[i], self.base_container_dn[l-1-i]) + class TestEscapes(unittest.TestCase): def setUp(self): -- cgit