diff --git a/ACI.txt b/ACI.txt index 62ff5ea7c..e6d6e3d15 100644 --- a/ACI.txt +++ b/ACI.txt @@ -387,6 +387,8 @@ aci: (targetattr = "krbpasswordexpiration || krbprincipalkey || passwordhistory dn: cn=users,cn=accounts,dc=ipa,dc=example aci: (targetattr = "krbpasswordexpiration || 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 = "ipapasskey || objectclass")(targetfilter = "(objectclass=posixaccount)")(version 3.0;acl "permission:System: Manage Passkey Mappings";allow (write) groupdn = "ldap:///cn=System: Manage Passkey Mappings,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 = "(&(!(memberOf=cn=admins,cn=groups,cn=accounts,dc=ipa,dc=example))(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";) @@ -403,7 +405,7 @@ aci: (targetattr = "audio || businesscategory || carlicense || departmentnumber 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 -aci: (targetattr = "ipasshpubkey || ipauniqueid || ipauserauthtype || userclass")(targetfilter = "(objectclass=posixaccount)")(version 3.0;acl "permission:System: Read User IPA Attributes";allow (compare,read,search) userdn = "ldap:///all";) +aci: (targetattr = "ipapasskey || ipasshpubkey || ipauniqueid || ipauserauthtype || userclass")(targetfilter = "(objectclass=posixaccount)")(version 3.0;acl "permission:System: Read User IPA Attributes";allow (compare,read,search) userdn = "ldap:///all";) dn: cn=users,cn=accounts,dc=ipa,dc=example aci: (targetattr = "krbcanonicalname || krblastpwdchange || krbpasswordexpiration || krbprincipalaliases || krbprincipalexpiration || krbprincipalname || krbprincipaltype || nsaccountlock")(targetfilter = "(objectclass=posixaccount)")(version 3.0;acl "permission:System: Read User Kerberos Attributes";allow (compare,read,search) userdn = "ldap:///all";) dn: cn=users,cn=accounts,dc=ipa,dc=example diff --git a/API.txt b/API.txt index 78d655a00..e0c4847b7 100644 --- a/API.txt +++ b/API.txt @@ -5282,6 +5282,17 @@ option: Str('version?') output: Output('completed', type=[]) output: Output('failed', type=[]) output: Entry('result') +command: stageuser_add_passkey/1 +args: 2,4,3 +arg: Str('uid', cli_name='login') +arg: Str('ipapasskey+', alwaysask=True, cli_name='passkey') +option: Flag('all', autofill=True, cli_name='all', default=False) +option: Flag('no_members', autofill=True, default=False) +option: Flag('raw', autofill=True, cli_name='raw', default=False) +option: Str('version?') +output: Entry('result') +output: Output('summary', type=[, ]) +output: PrimaryKey('value') command: stageuser_add_principal/1 args: 2,4,3 arg: Str('uid', cli_name='login') @@ -5465,6 +5476,17 @@ option: Str('version?') output: Output('completed', type=[]) output: Output('failed', type=[]) output: Entry('result') +command: stageuser_remove_passkey/1 +args: 2,4,3 +arg: Str('uid', cli_name='login') +arg: Str('ipapasskey+', alwaysask=True, cli_name='passkey') +option: Flag('all', autofill=True, cli_name='all', default=False) +option: Flag('no_members', autofill=True, default=False) +option: Flag('raw', autofill=True, cli_name='raw', default=False) +option: Str('version?') +output: Entry('result') +output: Output('summary', type=[, ]) +output: PrimaryKey('value') command: stageuser_remove_principal/1 args: 2,4,3 arg: Str('uid', cli_name='login') @@ -6469,6 +6491,17 @@ option: Str('version?') output: Output('completed', type=[]) output: Output('failed', type=[]) output: Entry('result') +command: user_add_passkey/1 +args: 2,4,3 +arg: Str('uid', cli_name='login') +arg: Str('ipapasskey+', alwaysask=True, cli_name='passkey') +option: Flag('all', autofill=True, cli_name='all', default=False) +option: Flag('no_members', autofill=True, default=False) +option: Flag('raw', autofill=True, cli_name='raw', default=False) +option: Str('version?') +output: Entry('result') +output: Output('summary', type=[, ]) +output: PrimaryKey('value') command: user_add_principal/1 args: 2,4,3 arg: Str('uid', cli_name='login') @@ -6671,6 +6704,17 @@ option: Str('version?') output: Output('completed', type=[]) output: Output('failed', type=[]) output: Entry('result') +command: user_remove_passkey/1 +args: 2,4,3 +arg: Str('uid', cli_name='login') +arg: Str('ipapasskey+', alwaysask=True, cli_name='passkey') +option: Flag('all', autofill=True, cli_name='all', default=False) +option: Flag('no_members', autofill=True, default=False) +option: Flag('raw', autofill=True, cli_name='raw', default=False) +option: Str('version?') +output: Entry('result') +output: Output('summary', type=[, ]) +output: PrimaryKey('value') command: user_remove_principal/1 args: 2,4,3 arg: Str('uid', cli_name='login') @@ -7445,6 +7489,7 @@ default: stageuser_add/1 default: stageuser_add_cert/1 default: stageuser_add_certmapdata/1 default: stageuser_add_manager/1 +default: stageuser_add_passkey/1 default: stageuser_add_principal/1 default: stageuser_del/1 default: stageuser_find/1 @@ -7452,6 +7497,7 @@ default: stageuser_mod/1 default: stageuser_remove_cert/1 default: stageuser_remove_certmapdata/1 default: stageuser_remove_manager/1 +default: stageuser_remove_passkey/1 default: stageuser_remove_principal/1 default: stageuser_show/1 default: subid/1 @@ -7540,6 +7586,7 @@ default: user_add/1 default: user_add_cert/1 default: user_add_certmapdata/1 default: user_add_manager/1 +default: user_add_passkey/1 default: user_add_principal/1 default: user_del/1 default: user_disable/1 @@ -7549,6 +7596,7 @@ default: user_mod/1 default: user_remove_cert/1 default: user_remove_certmapdata/1 default: user_remove_manager/1 +default: user_remove_passkey/1 default: user_remove_principal/1 default: user_show/1 default: user_stage/1 diff --git a/doc/api/commands.rst b/doc/api/commands.rst index f31837551..560f4d901 100644 --- a/doc/api/commands.rst +++ b/doc/api/commands.rst @@ -382,6 +382,7 @@ IPA API Commands stageuser_add_cert.md stageuser_add_certmapdata.md stageuser_add_manager.md + stageuser_add_passkey.md stageuser_add_principal.md stageuser_del.md stageuser_find.md @@ -389,6 +390,7 @@ IPA API Commands stageuser_remove_cert.md stageuser_remove_certmapdata.md stageuser_remove_manager.md + stageuser_remove_passkey.md stageuser_remove_principal.md stageuser_show.md subid_add.md @@ -466,6 +468,7 @@ IPA API Commands user_add_cert.md user_add_certmapdata.md user_add_manager.md + user_add_passkey.md user_add_principal.md user_del.md user_disable.md @@ -475,6 +478,7 @@ IPA API Commands user_remove_cert.md user_remove_certmapdata.md user_remove_manager.md + user_remove_passkey.md user_remove_principal.md user_show.md user_stage.md diff --git a/doc/api/stageuser_add_passkey.md b/doc/api/stageuser_add_passkey.md new file mode 100644 index 000000000..480096ef3 --- /dev/null +++ b/doc/api/stageuser_add_passkey.md @@ -0,0 +1,32 @@ +[//]: # (THE CONTENT BELOW IS GENERATED. DO NOT EDIT.) +# stageuser_add_passkey +Add one or more passkey mappings to the stage user entry. + +### Arguments +|Name|Type|Required +|-|-|- +|uid|:ref:`Str`|True +|ipapasskey|:ref:`Str`|True + +### Options +* all : :ref:`Flag` **(Required)** + * Default: False +* raw : :ref:`Flag` **(Required)** + * Default: False +* no_members : :ref:`Flag` **(Required)** + * Default: False +* version : :ref:`Str` + +### Output +|Name|Type +|-|- +|result|Entry +|summary|Output +|value|PrimaryKey + +[//]: # (ADD YOUR NOTES BELOW. THESE WILL BE PICKED EVERY TIME THE DOCS ARE REGENERATED. //end) +### Semantics + +### Notes + +### Version differences \ No newline at end of file diff --git a/doc/api/stageuser_remove_passkey.md b/doc/api/stageuser_remove_passkey.md new file mode 100644 index 000000000..11ab8a735 --- /dev/null +++ b/doc/api/stageuser_remove_passkey.md @@ -0,0 +1,32 @@ +[//]: # (THE CONTENT BELOW IS GENERATED. DO NOT EDIT.) +# stageuser_remove_passkey +Remove one or more passkey mappings from the stage user entry. + +### Arguments +|Name|Type|Required +|-|-|- +|uid|:ref:`Str`|True +|ipapasskey|:ref:`Str`|True + +### Options +* all : :ref:`Flag` **(Required)** + * Default: False +* raw : :ref:`Flag` **(Required)** + * Default: False +* no_members : :ref:`Flag` **(Required)** + * Default: False +* version : :ref:`Str` + +### Output +|Name|Type +|-|- +|result|Entry +|summary|Output +|value|PrimaryKey + +[//]: # (ADD YOUR NOTES BELOW. THESE WILL BE PICKED EVERY TIME THE DOCS ARE REGENERATED. //end) +### Semantics + +### Notes + +### Version differences \ No newline at end of file diff --git a/doc/api/user_add_passkey.md b/doc/api/user_add_passkey.md new file mode 100644 index 000000000..61112b42f --- /dev/null +++ b/doc/api/user_add_passkey.md @@ -0,0 +1,32 @@ +[//]: # (THE CONTENT BELOW IS GENERATED. DO NOT EDIT.) +# user_add_passkey +Add one or more passkey mappings to the user entry. + +### Arguments +|Name|Type|Required +|-|-|- +|uid|:ref:`Str`|True +|ipapasskey|:ref:`Str`|True + +### Options +* all : :ref:`Flag` **(Required)** + * Default: False +* raw : :ref:`Flag` **(Required)** + * Default: False +* no_members : :ref:`Flag` **(Required)** + * Default: False +* version : :ref:`Str` + +### Output +|Name|Type +|-|- +|result|Entry +|summary|Output +|value|PrimaryKey + +[//]: # (ADD YOUR NOTES BELOW. THESE WILL BE PICKED EVERY TIME THE DOCS ARE REGENERATED. //end) +### Semantics + +### Notes + +### Version differences \ No newline at end of file diff --git a/doc/api/user_remove_passkey.md b/doc/api/user_remove_passkey.md new file mode 100644 index 000000000..254b14110 --- /dev/null +++ b/doc/api/user_remove_passkey.md @@ -0,0 +1,32 @@ +[//]: # (THE CONTENT BELOW IS GENERATED. DO NOT EDIT.) +# user_remove_passkey +Remove one or more passkey mappings from the user entry. + +### Arguments +|Name|Type|Required +|-|-|- +|uid|:ref:`Str`|True +|ipapasskey|:ref:`Str`|True + +### Options +* all : :ref:`Flag` **(Required)** + * Default: False +* raw : :ref:`Flag` **(Required)** + * Default: False +* no_members : :ref:`Flag` **(Required)** + * Default: False +* version : :ref:`Str` + +### Output +|Name|Type +|-|- +|result|Entry +|summary|Output +|value|PrimaryKey + +[//]: # (ADD YOUR NOTES BELOW. THESE WILL BE PICKED EVERY TIME THE DOCS ARE REGENERATED. //end) +### Semantics + +### Notes + +### Version differences \ No newline at end of file diff --git a/install/updates/73-passkey.update b/install/updates/73-passkey.update index 26d8c0c5f..4693006e6 100644 --- a/install/updates/73-passkey.update +++ b/install/updates/73-passkey.update @@ -12,3 +12,6 @@ default:objectClass: groupofnames default:objectClass: nestedgroup default:cn: Passkey Administrators default:description: Passkey Administrators + +dn: $SUFFIX +add:aci: (targetattr = "ipapasskey")(targattrfilters="add=objectclass:(objectclass=ipapasskeyuser)")(version 3.0;acl "selfservice:Users can manage their own passkey mappings";allow (write) userdn = "ldap:///self";) \ No newline at end of file diff --git a/ipaclient/plugins/baseuser.py b/ipaclient/plugins/baseuser.py new file mode 100644 index 000000000..ea473235e --- /dev/null +++ b/ipaclient/plugins/baseuser.py @@ -0,0 +1,93 @@ +# +# Copyright (C) 2022 FreeIPA Contributors see COPYING for license +# + +import os +import locale +import logging +import subprocess +from ipaclient.frontend import MethodOverride +from ipalib import errors +from ipalib import Bool, Flag, StrEnum +from ipalib.text import _ +from ipaplatform.paths import paths + +logger = logging.getLogger(__name__) + + +class baseuser_add_passkey(MethodOverride): + takes_options = ( + Flag( + 'register', + cli_name='register', + doc=_('Register the passkey'), + ), + Bool( + 'require_user_verification?', + cli_name='require_user_verification', + doc=_('Require user verification during authentication with ' + 'the passkey') + ), + StrEnum( + 'cosetype?', + cli_name='cose_type', + doc=_('COSE type to use for registration'), + values=('es256', 'rs256', 'eddsa'), + ), + ) + + def get_args(self): + # ipapasskey is not mandatory as it can be built + # from the registration step + for arg in super(baseuser_add_passkey, self).get_args(): + if arg.name == 'ipapasskey': + yield arg.clone(required=False, alwaysask=False) + else: + yield arg.clone() + + def forward(self, *args, **options): + if self.api.env.context == 'cli': + # 2 formats are possible for ipa user-add-passkey: + # --register [--require-user-verification] [--cose-type ...] + # or + # passkey:, + for option in super(baseuser_add_passkey, self).get_options(): + if args and option in options: + raise errors.MutuallyExclusiveError( + reason=_("cannot specify both %s and " + "passkey mapping").format(option)) + # if the first format is used, need to register the key first + # and obtained the data + if 'register' in options: + # Ensure the executable exists + if not os.path.exists(paths.PASSKEY_CHILD): + raise errors.ValidationError(name="register", error=_( + "Missing executable %s, use the command with " + "LOGIN PASSKEY instead of LOGIN --register") + % paths.PASSKEY_CHILD) + + options.pop('register') + cosetype = options.pop('cosetype', None) + require_verif = options.pop('require_user_verification', None) + cmd = [paths.PASSKEY_CHILD, "--register", + "--domain", self.api.env.domain, + "--username", args[0]] + if cosetype: + cmd.append("--type") + cmd.append(cosetype) + if require_verif is not None: + cmd.append("--user-verification") + cmd.append(str(require_verif).lower()) + + logger.debug("Executing command: %s", cmd) + subp = subprocess.Popen(cmd, stdout=subprocess.PIPE) + stdout, _stderr = subp.communicate(None) + + if subp.returncode != 0: + raise errors.NotFound(reason="Failed to generate passkey") + + passkey = stdout.decode(locale.getpreferredencoding(), + errors='replace').strip() + args = (args[0], [passkey]) + + return super(baseuser_add_passkey, self).forward(*args, **options) diff --git a/ipaclient/plugins/stageuser.py b/ipaclient/plugins/stageuser.py new file mode 100644 index 000000000..e389ca651 --- /dev/null +++ b/ipaclient/plugins/stageuser.py @@ -0,0 +1,14 @@ +# +# Copyright (C) 2022 FreeIPA Contributors see COPYING for license +# +from ipaclient.plugins.baseuser import baseuser_add_passkey +from ipalib.plugable import Registry +from ipalib import _ + + +register = Registry() + + +@register(override=True, no_fail=True) +class stageuser_add_passkey(baseuser_add_passkey): + __doc__ = _("Add one or more passkey mappings to the user entry.") diff --git a/ipaclient/plugins/user.py b/ipaclient/plugins/user.py index 5af73b50c..c567a3524 100644 --- a/ipaclient/plugins/user.py +++ b/ipaclient/plugins/user.py @@ -19,6 +19,7 @@ # along with this program. If not, see . from ipaclient.frontend import MethodOverride +from ipaclient.plugins.baseuser import baseuser_add_passkey from ipalib import errors from ipalib import Flag from ipalib import util @@ -79,3 +80,8 @@ class user_show(MethodOverride): raise errors.NoCertificateError(entry=keys[-1]) else: return super(user_show, self).forward(*keys, **options) + + +@register(override=True, no_fail=True) +class user_add_passkey(baseuser_add_passkey): + __doc__ = _("Add one or more passkey mappings to the user entry.") diff --git a/ipaplatform/base/paths.py b/ipaplatform/base/paths.py index 729a66a62..e7092dd52 100644 --- a/ipaplatform/base/paths.py +++ b/ipaplatform/base/paths.py @@ -463,6 +463,7 @@ class BasePathNamespace: "/var/lib/gssproxy/ipa_ccache_sweeper.sock" ) PAM_CONFIG = None + PASSKEY_CHILD = '/usr/libexec/sssd/passkey_child' def check_paths(self): """Check paths for missing files diff --git a/ipaserver/plugins/baseuser.py b/ipaserver/plugins/baseuser.py index bae6c54ff..b5ac8be53 100644 --- a/ipaserver/plugins/baseuser.py +++ b/ipaserver/plugins/baseuser.py @@ -17,6 +17,10 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import base64 +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives.serialization import load_pem_public_key +import re import six from ipalib import api, errors, constants @@ -157,6 +161,35 @@ def update_samba_attrs(ldap, dn, entry_attrs, **options): ) +def validate_passkey(ugettext, key): + """ + Validate the format for passkey mappings. + + The expected format is passkey:, + """ + pattern = re.compile(r'passkey:(?P.*),(?P.*)') + result = re.match(pattern, key) + if result is None: + return '"%s" is not a valid passkey mapping' % key + + # Validate the id part + try: + base64.b64decode(result.group('id')) + except Exception: + return '"%s" is not a valid passkey mapping, invalid id' % key + + # Validate the pkey part + try: + pem = "-----BEGIN PUBLIC KEY-----\n" + \ + result.group('pkey') + \ + "\n-----END PUBLIC KEY-----" + load_pem_public_key(data=pem.encode('utf-8'), + backend=default_backend()) + except ValueError: + return '"%s" is not a valid passkey mapping, invalid key' % key + return None + + class baseuser(LDAPObject): """ baseuser object. @@ -170,7 +203,7 @@ class baseuser(LDAPObject): possible_objectclasses = [ 'meporiginentry', 'ipauserauthtypeclass', 'ipauser', 'ipatokenradiusproxyuser', 'ipacertmapobject', - 'ipantuserattrs', 'ipaidpuser', + 'ipantuserattrs', 'ipaidpuser', 'ipapasskeyuser', ] disallow_object_classes = ['krbticketpolicyaux'] permission_filter_objectclasses = ['posixaccount'] @@ -186,6 +219,7 @@ class baseuser(LDAPObject): 'krbprincipalname', 'krbcanonicalname', 'ipacertmapdata', 'ipantlogonscript', 'ipantprofilepath', 'ipanthomedirectory', 'ipanthomedirectorydrive', + 'ipapasskey', ] search_display_attributes = [ 'uid', 'givenname', 'sn', 'homedirectory', 'krbcanonicalname', @@ -451,6 +485,12 @@ class baseuser(LDAPObject): 'J:', 'K:', 'L:', 'M:', 'N:', 'O:', 'P:', 'Q:', 'R:', 'S:', 'T:', 'U:', 'V:', 'W:', 'X:', 'Y:', 'Z:'), ), + Str('ipapasskey*', validate_passkey, + cli_name='passkey', + label=_('Passkey mapping'), + doc=_('Passkey mapping'), + flags=['no_create', 'no_update', 'no_search'], + ), ) def normalize_and_validate_email(self, email, config=None): @@ -1011,3 +1051,30 @@ 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"') + + +class ModPassKey(LDAPModAttribute): + attribute = 'ipapasskey' + + +class baseuser_add_passkey(ModPassKey, LDAPAddAttribute): + __doc__ = _("Add one or more passkey mappings to the user entry.") + msg_summary = _('Added passkey mappings to user "%(value)s"') + + def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, + **options): + + dn = super(baseuser_add_passkey, self).pre_callback( + ldap, dn, entry_attrs, attrs_list, *keys, **options) + + # The objectclass ipafpasskeyuser may not be present on + # existing user entries. We need to add it if we define a new + # value for ipapasskey + add_missing_object_class(ldap, u'ipapasskeyuser', dn) + + return dn + + +class baseuser_remove_passkey(ModPassKey, LDAPRemoveAttribute): + __doc__ = _("Remove one or more passkey mappings from the user entry.") + msg_summary = _('Removed passkey mappings from user "%(value)s"') diff --git a/ipaserver/plugins/stageuser.py b/ipaserver/plugins/stageuser.py index 51438a83a..24697cf58 100644 --- a/ipaserver/plugins/stageuser.py +++ b/ipaserver/plugins/stageuser.py @@ -49,7 +49,9 @@ from .baseuser import ( baseuser_add_manager, baseuser_remove_manager, baseuser_add_certmapdata, - baseuser_remove_certmapdata) + baseuser_remove_certmapdata, + baseuser_add_passkey, + baseuser_remove_passkey) from ipalib.request import context from ipalib.util import set_krbcanonicalname from ipalib import _, ngettext @@ -819,3 +821,15 @@ class stageuser_add_certmapdata(baseuser_add_certmapdata): class stageuser_remove_certmapdata(baseuser_remove_certmapdata): __doc__ = _("Remove one or more certificate mappings from the stage user" " entry.") + + +@register() +class stageuser_add_passkey(baseuser_add_passkey): + __doc__ = _("Add one or more passkey mappings to the stage user" + " entry.") + + +@register() +class stageuser_remove_passkey(baseuser_remove_passkey): + __doc__ = _("Remove one or more passkey mappings from the stage user" + " entry.") diff --git a/ipaserver/plugins/user.py b/ipaserver/plugins/user.py index 643b44f14..9fcf23d73 100644 --- a/ipaserver/plugins/user.py +++ b/ipaserver/plugins/user.py @@ -51,6 +51,8 @@ from .baseuser import ( baseuser_remove_principal, baseuser_add_certmapdata, baseuser_remove_certmapdata, + baseuser_add_passkey, + baseuser_remove_passkey, ) from .idviews import remove_ipaobject_overrides from ipalib.plugable import Registry @@ -210,6 +212,7 @@ class user(baseuser): 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': { 'ipauniqueid', 'ipasshpubkey', 'ipauserauthtype', 'userclass', + 'ipapasskey', }, 'fixup_function': fix_addressbook_permission_bindrule, }, @@ -430,6 +433,14 @@ class user(baseuser): 'Certificate Identity Mapping Administrators' }, }, + 'System: Manage Passkey Mappings': { + 'ipapermright': {'write'}, + 'ipapermdefaultattr': {'ipapasskey', 'objectclass'}, + 'default_privileges': { + 'Passkey Administrators' + }, + }, + } takes_params = baseuser.takes_params + ( @@ -1019,13 +1030,15 @@ class user_stage(LDAPMultiQuery): # ipauniqueid, krbcanonicalname, sshpubkeyfp, krbextradata # are automatically generated # ipacertmapdata can only be provided with user_add_certmapdata + # ipapasskey can only be provided with user_add_passkey ignore_attrs = [u'dn', u'uid', u'has_keytab', u'has_password', u'preserved', u'ipauniqueid', u'krbcanonicalname', u'sshpubkeyfp', u'krbextradata', u'ipacertmapdata', 'ipantsecurityidentifier', - u'nsaccountlock'] + u'nsaccountlock', + u'ipapasskey'] def execute(self, *keys, **options): @@ -1079,6 +1092,12 @@ class user_stage(LDAPMultiQuery): self.api.Command.stageuser_add_certmapdata( *single_keys, ipacertmapdata=certmapdata) + # special handling for passkey + passkey = user.get(u'ipapasskey') + if passkey: + self.api.Command.stageuser_add_passkey( + *single_keys, + ipapasskey=passkey) try: self.api.Command.user_del(*multi_keys, preserve=False) except errors.ExecutionError: @@ -1360,3 +1379,13 @@ class user_add_principal(baseuser_add_principal): class user_remove_principal(baseuser_remove_principal): __doc__ = _('Remove principal alias from the user entry') msg_summary = _('Removed aliases from user "%(value)s"') + + +@register() +class user_add_passkey(baseuser_add_passkey): + __doc__ = _("Add one or more passkey mappings to the user entry.") + + +@register() +class user_remove_passkey(baseuser_remove_passkey): + __doc__ = _("Remove one or more passkey mappings from the user entry.")