summaryrefslogtreecommitdiffstats
path: root/ipalib/plugins
diff options
context:
space:
mode:
authorNathaniel McCallum <npmccallum@redhat.com>2013-11-11 17:58:02 -0400
committerPetr Viktorin <pviktori@redhat.com>2013-12-03 14:49:10 +0100
commit4cb2c2813d5787f8ebee6eba2ea0be756507b58b (patch)
tree481bbd93203634db2fdbba38e8e0cedca57fe384 /ipalib/plugins
parentefffcfdbc24ad81d48c7b65443a75928b156cc49 (diff)
downloadfreeipa-4cb2c2813d5787f8ebee6eba2ea0be756507b58b.tar.gz
freeipa-4cb2c2813d5787f8ebee6eba2ea0be756507b58b.tar.xz
freeipa-4cb2c2813d5787f8ebee6eba2ea0be756507b58b.zip
Add RADIUS proxy support to ipalib CLI
https://fedorahosted.org/freeipa/ticket/3368
Diffstat (limited to 'ipalib/plugins')
-rw-r--r--ipalib/plugins/config.py2
-rw-r--r--ipalib/plugins/radiusproxy.py168
-rw-r--r--ipalib/plugins/user.py65
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)