diff options
Diffstat (limited to 'ipalib/plugins')
-rw-r--r-- | ipalib/plugins/config.py | 2 | ||||
-rw-r--r-- | ipalib/plugins/radiusproxy.py | 168 | ||||
-rw-r--r-- | ipalib/plugins/user.py | 65 |
3 files changed, 226 insertions, 9 deletions
diff --git a/ipalib/plugins/config.py b/ipalib/plugins/config.py index f4e35519f..e20e5e801 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 000000000..4d143c4bf --- /dev/null +++ b/ipalib/plugins/radiusproxy.py @@ -0,0 +1,168 @@ +# Authors: +# Nathaniel McCallum <npmccallum@redhat.com> +# +# 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 <http://www.gnu.org/licenses/>. + +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 a7005faf1..c85514539 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) |