From 4cb2c2813d5787f8ebee6eba2ea0be756507b58b Mon Sep 17 00:00:00 2001 From: Nathaniel McCallum Date: Dec 03 2013 13:49:10 +0000 Subject: Add RADIUS proxy support to ipalib CLI https://fedorahosted.org/freeipa/ticket/3368 --- diff --git a/API.txt b/API.txt index 107827c..f1103e0 100644 --- a/API.txt +++ b/API.txt @@ -523,7 +523,7 @@ option: Int('ipasearchrecordslimit', attribute=True, autofill=False, cli_name='s option: Int('ipasearchtimelimit', attribute=True, autofill=False, cli_name='searchtimelimit', minvalue=-1, multivalue=False, required=False) option: Str('ipaselinuxusermapdefault', attribute=True, autofill=False, cli_name='ipaselinuxusermapdefault', multivalue=False, required=False) option: Str('ipaselinuxusermaporder', attribute=True, autofill=False, cli_name='ipaselinuxusermaporder', multivalue=False, required=False) -option: StrEnum('ipauserauthtype', attribute=True, autofill=False, cli_name='user_auth_type', csv=True, multivalue=True, required=False, values=(u'password',)) +option: StrEnum('ipauserauthtype', attribute=True, autofill=False, cli_name='user_auth_type', csv=True, multivalue=True, required=False, values=(u'password', u'radius')) option: Str('ipauserobjectclasses', attribute=True, autofill=False, cli_name='userobjectclasses', csv=True, multivalue=True, required=False) option: IA5Str('ipausersearchfields', attribute=True, autofill=False, cli_name='usersearch', multivalue=False, required=False) option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui') @@ -2552,6 +2552,81 @@ option: Str('version?', exclude='webui') output: Entry('result', , Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None)) output: Output('summary', (, ), None) output: Output('value', , None) +command: radiusproxy_add +args: 1,11,3 +arg: Str('cn', attribute=True, cli_name='name', multivalue=False, 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: Int('ipatokenradiusretries', attribute=True, cli_name='retries', maxvalue=10, minvalue=0, multivalue=False, required=False) +option: Password('ipatokenradiussecret', attribute=True, cli_name='secret', confirm=True, multivalue=False, required=True) +option: Str('ipatokenradiusserver', attribute=True, cli_name='server', multivalue=True, required=True) +option: Int('ipatokenradiustimeout', attribute=True, cli_name='timeout', minvalue=1, multivalue=False, required=False) +option: Str('ipatokenusermapattribute', attribute=True, cli_name='userattr', multivalue=False, required=False) +option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui') +option: Str('setattr*', cli_name='setattr', exclude='webui') +option: Str('version?', exclude='webui') +output: Entry('result', , Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None)) +output: Output('summary', (, ), None) +output: Output('value', , None) +command: radiusproxy_del +args: 1,2,3 +arg: Str('cn', attribute=True, cli_name='name', multivalue=True, primary_key=True, query=True, required=True) +option: Flag('continue', autofill=True, cli_name='continue', default=False) +option: Str('version?', exclude='webui') +output: Output('result', , None) +output: Output('summary', (, ), None) +output: Output('value', , None) +command: radiusproxy_find +args: 1,13,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', multivalue=False, primary_key=True, query=True, required=False) +option: Str('description', attribute=True, autofill=False, cli_name='desc', multivalue=False, query=True, required=False) +option: Int('ipatokenradiusretries', attribute=True, autofill=False, cli_name='retries', maxvalue=10, minvalue=0, multivalue=False, query=True, required=False) +option: Password('ipatokenradiussecret', attribute=True, autofill=False, cli_name='secret', confirm=True, multivalue=False, query=True, required=False) +option: Str('ipatokenradiusserver', attribute=True, autofill=False, cli_name='server', multivalue=True, query=True, required=False) +option: Int('ipatokenradiustimeout', attribute=True, autofill=False, cli_name='timeout', minvalue=1, multivalue=False, query=True, required=False) +option: Str('ipatokenusermapattribute', attribute=True, autofill=False, cli_name='userattr', 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: Int('sizelimit?', autofill=False, minvalue=0) +option: Int('timelimit?', autofill=False, minvalue=0) +option: Str('version?', exclude='webui') +output: Output('count', , None) +output: ListOfEntries('result', (, ), Gettext('A list of LDAP entries', domain='ipa', localedir=None)) +output: Output('summary', (, ), None) +output: Output('truncated', , None) +command: radiusproxy_mod +args: 1,14,3 +arg: Str('cn', attribute=True, cli_name='name', multivalue=False, 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: Int('ipatokenradiusretries', attribute=True, autofill=False, cli_name='retries', maxvalue=10, minvalue=0, multivalue=False, required=False) +option: Password('ipatokenradiussecret', attribute=True, autofill=False, cli_name='secret', confirm=True, multivalue=False, required=False) +option: Str('ipatokenradiusserver', attribute=True, autofill=False, cli_name='server', multivalue=True, required=False) +option: Int('ipatokenradiustimeout', attribute=True, autofill=False, cli_name='timeout', minvalue=1, multivalue=False, required=False) +option: Str('ipatokenusermapattribute', attribute=True, autofill=False, cli_name='userattr', multivalue=False, required=False) +option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui') +option: Str('rename', cli_name='rename', multivalue=False, primary_key=True, required=False) +option: Flag('rights', autofill=True, default=False) +option: Str('setattr*', cli_name='setattr', exclude='webui') +option: Str('version?', exclude='webui') +output: Entry('result', , Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None)) +output: Output('summary', (, ), None) +output: Output('value', , None) +command: radiusproxy_show +args: 1,4,3 +arg: Str('cn', attribute=True, cli_name='name', multivalue=False, 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('version?', exclude='webui') +output: Entry('result', , Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None)) +output: Output('summary', (, ), None) +output: Output('value', , None) command: realmdomains_mod args: 0,11,3 option: Str('add_domain', attribute=True, autofill=False, cli_name='add_domain', multivalue=False, required=False) @@ -3597,7 +3672,7 @@ output: Entry('result', , Gettext('A dictionary representing an LDA output: Output('summary', (, ), None) output: Output('value', , None) command: user_add -args: 1,37,3 +args: 1,39,3 arg: 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_.$-]?$', 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') @@ -3611,7 +3686,9 @@ option: Str('givenname', attribute=True, cli_name='first', multivalue=False, req option: Str('homedirectory', attribute=True, cli_name='homedir', multivalue=False, required=False) option: Str('initials', attribute=True, autofill=True, cli_name='initials', multivalue=False, required=False) option: Str('ipasshpubkey', attribute=True, cli_name='sshpubkey', csv=True, multivalue=True, required=False) -option: StrEnum('ipauserauthtype', attribute=True, cli_name='user_auth_type', csv=True, multivalue=True, required=False, values=(u'password',)) +option: Str('ipatokenradiusconfiglink', attribute=True, cli_name='radius', multivalue=False, required=False) +option: Str('ipatokenradiususername', attribute=True, cli_name='radius_username', multivalue=False, required=False) +option: StrEnum('ipauserauthtype', attribute=True, cli_name='user_auth_type', csv=True, multivalue=True, required=False, values=(u'password', u'radius')) option: Str('krbprincipalname', attribute=True, autofill=True, cli_name='principal', multivalue=False, required=False) option: Str('l', attribute=True, cli_name='city', multivalue=False, required=False) option: Str('loginshell', attribute=True, cli_name='shell', multivalue=False, required=False) @@ -3662,7 +3739,7 @@ output: Output('result', , None) output: Output('summary', (, ), None) output: Output('value', , None) command: user_find -args: 1,47,4 +args: 1,49,4 arg: Str('criteria?', noextrawhitespace=False) option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui') option: Str('carlicense', attribute=True, autofill=False, cli_name='carlicense', multivalue=False, query=True, required=False) @@ -3679,7 +3756,9 @@ option: Str('in_netgroup*', cli_name='in_netgroups', csv=True) option: Str('in_role*', cli_name='in_roles', csv=True) option: Str('in_sudorule*', cli_name='in_sudorules', csv=True) option: Str('initials', attribute=True, autofill=False, cli_name='initials', multivalue=False, query=True, required=False) -option: StrEnum('ipauserauthtype', attribute=True, autofill=False, cli_name='user_auth_type', csv=True, multivalue=True, query=True, required=False, values=(u'password',)) +option: Str('ipatokenradiusconfiglink', attribute=True, autofill=False, cli_name='radius', multivalue=False, query=True, required=False) +option: Str('ipatokenradiususername', attribute=True, autofill=False, cli_name='radius_username', multivalue=False, query=True, required=False) +option: StrEnum('ipauserauthtype', attribute=True, autofill=False, cli_name='user_auth_type', csv=True, multivalue=True, query=True, required=False, values=(u'password', u'radius')) option: Str('krbprincipalname', attribute=True, autofill=False, cli_name='principal', multivalue=False, query=True, required=False) option: Str('l', attribute=True, autofill=False, cli_name='city', multivalue=False, query=True, required=False) option: Str('loginshell', attribute=True, autofill=False, cli_name='shell', multivalue=False, query=True, required=False) @@ -3716,7 +3795,7 @@ output: ListOfEntries('result', (, ), Gettext('A list output: Output('summary', (, ), None) output: Output('truncated', , None) command: user_mod -args: 1,38,3 +args: 1,40,3 arg: 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_.$-]?$', 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') @@ -3731,7 +3810,9 @@ option: Str('givenname', attribute=True, autofill=False, cli_name='first', multi option: Str('homedirectory', attribute=True, autofill=False, cli_name='homedir', multivalue=False, required=False) option: Str('initials', attribute=True, autofill=False, cli_name='initials', multivalue=False, required=False) option: Str('ipasshpubkey', attribute=True, autofill=False, cli_name='sshpubkey', csv=True, multivalue=True, required=False) -option: StrEnum('ipauserauthtype', attribute=True, autofill=False, cli_name='user_auth_type', csv=True, multivalue=True, required=False, values=(u'password',)) +option: Str('ipatokenradiusconfiglink', attribute=True, autofill=False, cli_name='radius', multivalue=False, required=False) +option: Str('ipatokenradiususername', attribute=True, autofill=False, cli_name='radius_username', multivalue=False, required=False) +option: StrEnum('ipauserauthtype', attribute=True, autofill=False, cli_name='user_auth_type', csv=True, multivalue=True, required=False, values=(u'password', u'radius')) option: Str('l', attribute=True, autofill=False, cli_name='city', multivalue=False, required=False) option: Str('loginshell', attribute=True, autofill=False, cli_name='shell', multivalue=False, required=False) option: Str('mail', attribute=True, autofill=False, cli_name='email', multivalue=True, required=False) diff --git a/VERSION b/VERSION index dc029a2..e7d7bc3 100644 --- a/VERSION +++ b/VERSION @@ -89,4 +89,4 @@ IPA_DATA_VERSION=20100614120000 # # ######################################################## IPA_API_VERSION_MAJOR=2 -IPA_API_VERSION_MINOR=69 +IPA_API_VERSION_MINOR=70 diff --git a/install/share/70ipaotp.ldif b/install/share/70ipaotp.ldif index 824be6e..d257a46 100644 --- a/install/share/70ipaotp.ldif +++ b/install/share/70ipaotp.ldif @@ -24,5 +24,5 @@ attributeTypes: (2.16.840.1.113730.3.8.16.1.19 NAME 'ipatokenRadiusRetries' DESC attributeTypes: (2.16.840.1.113730.3.8.16.1.20 NAME 'ipatokenUserMapAttribute' DESC 'Attribute to map from the user entry for RADIUS server authentication' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'IPA OTP') objectClasses: (2.16.840.1.113730.3.8.16.2.1 NAME 'ipaToken' SUP top ABSTRACT DESC 'Abstract token class for tokens' MUST (ipatokenUniqueID) MAY (description $ ipatokenOwner $ ipatokenDisabled $ ipatokenNotBefore $ ipatokenNotAfter $ ipatokenVendor $ ipatokenModel $ ipatokenSerial) X-ORIGIN 'IPA OTP') objectClasses: (2.16.840.1.113730.3.8.16.2.2 NAME 'ipatokenTOTP' SUP ipaToken STRUCTURAL DESC 'TOTP Token Type' MAY (ipatokenOTPkey $ ipatokenOTPalgorithm $ ipatokenOTPdigits $ ipatokenTOTPclockOffset $ ipatokenTOTPtimeStep) X-ORIGIN 'IPA OTP') -objectClasses: (2.16.840.1.113730.3.8.16.2.3 NAME 'ipatokenRadiusProxyUser' SUP top AUXILIARY DESC 'Radius Proxy User' MUST (ipatokenRadiusConfigLink) MAY (ipatokenRadiusUserName) X-ORIGIN 'IPA OTP') +objectClasses: (2.16.840.1.113730.3.8.16.2.3 NAME 'ipatokenRadiusProxyUser' SUP top AUXILIARY DESC 'Radius Proxy User' MAY (ipatokenRadiusConfigLink $ ipatokenRadiusUserName) X-ORIGIN 'IPA OTP') objectClasses: (2.16.840.1.113730.3.8.16.2.4 NAME 'ipatokenRadiusConfiguration' SUP top STRUCTURAL DESC 'Proxy Radius Configuration' MUST (cn $ ipatokenRadiusServer $ ipatokenRadiusSecret) MAY (description $ ipatokenRadiusTimeout $ ipatokenRadiusRetries $ ipatokenUserMapAttribute) X-ORIGIN 'IPA OTP') diff --git a/install/updates/20-indices.update b/install/updates/20-indices.update index b966a4f..5ff6d71 100644 --- a/install/updates/20-indices.update +++ b/install/updates/20-indices.update @@ -136,3 +136,10 @@ default:ObjectClass: top default:ObjectClass: nsIndex default:nsSystemIndex: false default:nsIndexType: eq + +dn: cn=ipatokenradiusconfiglink,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config +default:cn: ipatokenradiusconfiglink +default:ObjectClass: top +default:ObjectClass: nsIndex +default:nsSystemIndex: false +only:nsIndexType: eq,pres,sub diff --git a/install/updates/25-referint.update b/install/updates/25-referint.update index 54f3492..65af051 100644 --- a/install/updates/25-referint.update +++ b/install/updates/25-referint.update @@ -11,3 +11,4 @@ add: nsslapd-pluginArg14: memberallowcmd add: nsslapd-pluginArg15: memberdenycmd add: nsslapd-pluginArg16: ipasudorunas add: nsslapd-pluginArg17: ipasudorunasgroup +add: nsslapd-pluginArg18: ipatokenradiusconfiglink diff --git a/install/updates/40-otp.update b/install/updates/40-otp.update index ff36c87..83dfab7 100644 --- a/install/updates/40-otp.update +++ b/install/updates/40-otp.update @@ -7,3 +7,8 @@ dn: $SUFFIX add: aci:'(targetfilter = "(objectClass=ipaToken)")(targetattrs = "objectclass || ipatokenUniqueID || description || ipatokenOwner || ipatokenNotBefore || ipatokenNotAfter || ipatokenVendor || ipatokenModel || ipatokenSerial")(version 3.0; acl "Users can read basic token info"; allow (read, search, compare) userattr = "ipatokenOwner#USERDN";)' add: aci:'(targetfilter = "(objectClass=ipaToken)")(targetattrs = "ipatokenUniqueID || description || ipatokenOwner || ipatokenNotBefore || ipatokenNotAfter || ipatokenVendor || ipatokenModel || ipatokenSerial")(version 3.0; acl "Users can write basic token info"; allow (write) userattr = "ipatokenOwner#USERDN";)' add: aci:'(targetfilter = "(objectClass=ipatokenTOTP)")(targetattrs = "ipatokenOTPkey || ipatokenOTPalgorithm || ipatokenOTPdigits || ipatokenTOTPclockOffset || ipatokenTOTPtimeStep")(version 3.0; acl "Users can add TOTP token secrets"; allow (write, search) userattr = "ipatokenOwner#USERDN";)' + +dn: cn=radiusproxy,$SUFFIX +default: objectClass: nsContainer +default: objectClass: top +default: cn: radiusproxy diff --git a/ipalib/constants.py b/ipalib/constants.py index 5f9f03a..d3e61ca 100644 --- a/ipalib/constants.py +++ b/ipalib/constants.py @@ -109,6 +109,7 @@ DEFAULT_CONFIG = ( ('container_dna_posix_ids', DN(('cn', 'posix-ids'), ('cn', 'dna'), ('cn', 'ipa'), ('cn', 'etc'))), ('container_realm_domains', DN(('cn', 'Realm Domains'), ('cn', 'ipa'), ('cn', 'etc'))), ('container_otp', DN(('cn', 'otp'))), + ('container_radiusproxy', DN(('cn', 'radiusproxy'))), # Ports, hosts, and URIs: ('xmlrpc_uri', 'http://localhost:8888/ipa/xml'), diff --git a/ipalib/plugins/config.py b/ipalib/plugins/config.py index f4e3551..e20e5e8 100644 --- a/ipalib/plugins/config.py +++ b/ipalib/plugins/config.py @@ -202,7 +202,7 @@ class config(LDAPObject): cli_name='user_auth_type', label=_('Default user authentication types'), doc=_('Default types of supported user authentication'), - values=(u'password',), + values=(u'password', u'radius'), csv=True, ), ) diff --git a/ipalib/plugins/radiusproxy.py b/ipalib/plugins/radiusproxy.py new file mode 100644 index 0000000..4d143c4 --- /dev/null +++ b/ipalib/plugins/radiusproxy.py @@ -0,0 +1,168 @@ +# Authors: +# Nathaniel McCallum +# +# Copyright (C) 2013 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 . + +from ipalib.plugins.baseldap import * +from ipalib import api, Str, Int, Password, _, ngettext +from ipalib.plugable import Registry +from ipalib.util import validate_hostname, validate_ipaddr +from ipalib.errors import ValidationError +import re + +__doc__ = _(""" +RADIUS Proxy Servers + +Manage RADIUS Proxy Servers. + +IPA supports the use of an external RADIUS proxy server for krb5 OTP +authentications. This permits a great deal of flexibility when +integrating with third-party authentication services. + +EXAMPLES: + + Add a new server: + ipa radiusproxy-add MyRADIUS --server=radius.example.com:1812 + + Find all servers whose entries include the string "example.com": + ipa radiusproxy-find example.com + + Examine the configuration: + ipa radiusproxy-show MyRADIUS + + Change the secret: + ipa radiusproxy-mod MyRADIUS --secret + + Delete a configuration: + ipa radiusproxy-del MyRADIUS +""") + +register = Registry() + +LDAP_ATTRIBUTE = re.compile("^[a-zA-Z][a-zA-Z0-9-]*$") +def validate_attributename(ugettext, attr): + if not LDAP_ATTRIBUTE.match(attr): + raise ValidationError(name="ipatokenusermapattribute", + error=_('invalid attribute name')) + +def validate_radiusserver(ugettext, server): + split = server.rsplit(':', 1) + server = split[0] + if len(split) == 2: + try: + port = int(split[1]) + if (port < 0 or port > 65535): + raise ValueError() + except ValueError: + raise ValidationError(name="ipatokenradiusserver", + error=_('invalid port number')) + + if validate_ipaddr(server): + return + + try: + validate_hostname(server, check_fqdn=True, allow_underscore=True) + except ValueError, e: + raise errors.ValidationError(name="ipatokenradiusserver", + error=e.message) + + +@register() +class radiusproxy(LDAPObject): + """ + RADIUS Server object. + """ + container_dn = api.env.container_radiusproxy + object_name = _('RADIUS proxy server') + object_name_plural = _('RADIUS proxy servers') + object_class = ['ipatokenradiusconfiguration'] + default_attributes = ['cn', 'description', 'ipatokenradiusserver', + 'ipatokenradiustimeout', 'ipatokenradiusretries', 'ipatokenusermapattribute' + ] + search_attributes = ['cn', 'description', 'ipatokenradiusserver'] + rdn_is_primary_key = True + label = _('RADIUS Servers') + label_singular = _('RADIUS Server') + + takes_params = ( + Str('cn', + cli_name='name', + label=_('RADIUS proxy server name'), + primary_key=True, + ), + Str('description?', + cli_name='desc', + label=_('Description'), + doc=_('A description of this RADIUS proxy server'), + ), + Str('ipatokenradiusserver+', validate_radiusserver, + cli_name='server', + label=_('Server'), + doc=_('The hostname or IP (with or without port)'), + ), + Password('ipatokenradiussecret', + cli_name='secret', + label=_('Secret'), + doc=_('The secret used to encrypt data'), + confirm=True, + flags=['no_option'], + ), + Int('ipatokenradiustimeout?', + cli_name='timeout', + label=_('Timeout'), + doc=_('The total timeout across all retries (in seconds)'), + minvalue=1, + ), + Int('ipatokenradiusretries?', + cli_name='retries', + label=_('Retries'), + doc=_('The number of times to retry authentication'), + minvalue=0, + maxvalue=10, + ), + Str('ipatokenusermapattribute?', validate_attributename, + cli_name='userattr', + label=_('User attribute'), + doc=_('The username attribute on the user object'), + ), + ) + +@register() +class radiusproxy_add(LDAPCreate): + __doc__ = _('Add a new RADIUS proxy server.') + msg_summary = _('Added RADIUS proxy server "%(value)s"') + +@register() +class radiusproxy_del(LDAPDelete): + __doc__ = _('Delete a RADIUS proxy server.') + msg_summary = _('Deleted RADIUS proxy server "%(value)s"') + +@register() +class radiusproxy_mod(LDAPUpdate): + __doc__ = _('Modify a RADIUS proxy server.') + msg_summary = _('Modified RADIUS proxy server "%(value)s"') + +@register() +class radiusproxy_find(LDAPSearch): + __doc__ = _('Search for RADIUS proxy servers.') + msg_summary = ngettext( + '%(count)d RADIUS proxy server matched', '%(count)d RADIUS proxy servers matched', 0 + ) + +@register() +class radiusproxy_show(LDAPRetrieve): + __doc__ = _('Display information about a RADIUS proxy server.') diff --git a/ipalib/plugins/user.py b/ipalib/plugins/user.py index a7005fa..c855145 100644 --- a/ipalib/plugins/user.py +++ b/ipalib/plugins/user.py @@ -124,6 +124,12 @@ def validate_nsaccountlock(entry_attrs): raise errors.ValidationError(name='nsaccountlock', error=_('must be TRUE or FALSE')) +def radius_dn2pk(api, entry_attrs): + cl = entry_attrs.get('ipatokenradiusconfiglink', None) + if cl: + pk = api.Object['radiusproxy'].get_primary_key_from_dn(cl[0]) + entry_attrs['ipatokenradiusconfiglink'] = [pk] + def convert_nsaccountlock(entry_attrs): if not 'nsaccountlock' in entry_attrs: entry_attrs['nsaccountlock'] = False @@ -199,7 +205,8 @@ class user(LDAPObject): object_class = ['posixaccount'] object_class_config = 'ipauserobjectclasses' possible_objectclasses = [ - 'meporiginentry', 'ipauserauthtypeclass', 'ipauser' + 'meporiginentry', 'ipauserauthtypeclass', 'ipauser', + 'ipatokenradiusproxyuser' ] disallow_object_classes = ['krbticketpolicyaux'] search_attributes_config = 'ipausersearchfields' @@ -207,7 +214,8 @@ class user(LDAPObject): 'uid', 'givenname', 'sn', 'homedirectory', 'loginshell', 'uidnumber', 'gidnumber', 'mail', 'ou', 'telephonenumber', 'title', 'memberof', 'nsaccountlock', - 'memberofindirect', 'ipauserauthtype', 'userclass' + 'memberofindirect', 'ipauserauthtype', 'userclass', + 'ipatokenradiusconfiglink', 'ipatokenradiususername' ] search_display_attributes = [ 'uid', 'givenname', 'sn', 'homedirectory', 'loginshell', @@ -371,7 +379,7 @@ class user(LDAPObject): cli_name='user_auth_type', label=_('User authentication types'), doc=_('Types of supported user authentication'), - values=(u'password',), + values=(u'password', u'radius'), csv=True, ), Str('userclass*', @@ -380,6 +388,14 @@ class user(LDAPObject): doc=_('User category (semantics placed on this attribute are for ' 'local interpretation)'), ), + Str('ipatokenradiusconfiglink?', + cli_name='radius', + label=_('RADIUS proxy configuration'), + ), + Str('ipatokenradiususername?', + cli_name='radius_username', + label=_('RADIUS proxy username'), + ), ) def _normalize_and_validate_email(self, email, config=None): @@ -560,6 +576,19 @@ class user_add(LDAPCreate): and 'ipauser' not in entry_attrs['objectclass']): entry_attrs['objectclass'].append('ipauser') + if 'ipatokenradiusconfiglink' in entry_attrs: + cl = entry_attrs['ipatokenradiusconfiglink'] + if cl: + if 'objectclass' not in entry_attrs: + _entry = ldap.get_entry(dn, ['objectclass']) + entry_attrs['objectclass'] = _entry['objectclass'] + + if 'ipatokenradiusproxyuser' not in entry_attrs['objectclass']: + entry_attrs['objectclass'].append('ipatokenradiusproxyuser') + + answer = self.api.Object['radiusproxy'].get_dn_if_exists(cl) + entry_attrs['ipatokenradiusconfiglink'] = answer + return dn def post_callback(self, ldap, dn, entry_attrs, *keys, **options): @@ -604,9 +633,8 @@ class user_add(LDAPCreate): pass self.obj.get_password_attributes(ldap, dn, entry_attrs) - convert_sshpubkey_post(ldap, dn, entry_attrs) - + radius_dn2pk(self.api, entry_attrs) return dn api.register(user_add) @@ -654,18 +682,31 @@ class user_mod(LDAPUpdate): # save the password so it can be displayed in post_callback setattr(context, 'randompassword', entry_attrs['userpassword']) if ('ipasshpubkey' in entry_attrs or 'ipauserauthtype' in entry_attrs - or 'userclass' in entry_attrs): + or 'userclass' in entry_attrs or 'ipatokenradiusconfiglink' in entry_attrs): if 'objectclass' in entry_attrs: obj_classes = entry_attrs['objectclass'] else: - (_dn, _entry_attrs) = ldap.get_entry(dn, ['objectclass']) + _entry_attrs = ldap.get_entry(dn, ['objectclass']) obj_classes = entry_attrs['objectclass'] = _entry_attrs['objectclass'] + if 'ipasshpubkey' in entry_attrs and 'ipasshuser' not in obj_classes: obj_classes.append('ipasshuser') - if 'ipauserauthtype' in entry_attrs and 'ipauserauthtype' not in obj_classes: + + if 'ipauserauthtype' in entry_attrs and 'ipauserauthtypeclass' not in obj_classes: obj_classes.append('ipauserauthtypeclass') + if 'userclass' in entry_attrs and 'ipauser' not in obj_classes: obj_classes.append('ipauser') + + if 'ipatokenradiusconfiglink' in entry_attrs: + cl = entry_attrs['ipatokenradiusconfiglink'] + if cl: + if 'ipatokenradiusproxyuser' not in obj_classes: + obj_classes.append('ipatokenradiusproxyuser') + + answer = self.api.Object['radiusproxy'].get_dn_if_exists(cl) + entry_attrs['ipatokenradiusconfiglink'] = answer + return dn def post_callback(self, ldap, dn, entry_attrs, *keys, **options): @@ -680,6 +721,7 @@ class user_mod(LDAPUpdate): self.obj._convert_manager(entry_attrs, **options) self.obj.get_password_attributes(ldap, dn, entry_attrs) convert_sshpubkey_post(ldap, dn, entry_attrs) + radius_dn2pk(self.api, entry_attrs) return dn api.register(user_mod) @@ -703,6 +745,12 @@ class user_find(LDAPSearch): manager = options.get('manager') if manager is not None: options['manager'] = self.obj._normalize_manager(manager) + + # Ensure that the RADIUS config link is a dn, not just the name + cl = 'ipatokenradiusconfiglink' + if cl in options: + options[cl] = self.api.Object['radiusproxy'].get_dn(options[cl]) + return super(user_find, self).execute(self, *args, **options) def pre_callback(self, ldap, filter, attrs_list, base_dn, scope, *keys, **options): @@ -742,6 +790,7 @@ class user_show(LDAPRetrieve): self.obj._convert_manager(entry_attrs, **options) self.obj.get_password_attributes(ldap, dn, entry_attrs) convert_sshpubkey_post(ldap, dn, entry_attrs) + radius_dn2pk(self.api, entry_attrs) return dn api.register(user_show)