On Fri, 2013-11-15 at 12:34 +0100, Petr Viktorin wrote:
> On 11/12/2013 12:17 AM, Nathaniel McCallum wrote:
> > On Fri, 2013-11-08 at 13:26 +0100, Petr Viktorin wrote:
> >> >On 09/25/2013 10:56 PM, Nathaniel McCallum wrote:
> >>> > >On Fri, 2013-09-20 at 12:38 -0400, Nathaniel McCallum wrote:
> >>>> > >>On Thu, 2013-09-12 at 16:48 -0400, Nathaniel McCallum wrote:
> >>>>> > >>>On Thu, 2013-09-05 at 00:06 -0400, Nathaniel McCallum wrote:
> >>>>>> > >>>>patch attached
> >>>>> > >>>
> >>>>> > >>>Update for ./makeapi attached.
> >>>> > >>
> >>>> > >>Version 3. This should fix all the current review issues, including 
> >>>> > >>the
> >>>> > >>use of the referential integrity plugin. I had to make one schema 
> >>>> > >>change
> >>>> > >>in order to make the referential integrity modification work. Note 
> >>>> > >>also
> >>>> > >>that the command name prefix is changed from radius to radiusproxy.
> >>> > >
> >>> > >Version 4. This patch fixes my failure to increment the minor version
> >>> > >number in the VERSION file.
> >>> > >
> >>> > >Nathaniel
> >> >
> >> >We've since decided that we'll carry LDAP "content" updates only in
> >> >update files, so you can leave indices.ldif & referint-conf.ldif 
> >> >unchanged.
> >> >Schema, on the other hand, will still be in ldif files (and soon*only*
> >> >in ldif files).
> > Fixed.
> >
> >> >The patch needs a rebase.
> > Fixed.
> >
> > Also fixed: two other bugs I found when testing the above fixes. Tests
> > pass.
> >
> > Nathaniel
> 
> Thanks for the patch!
> 
> The design page needs an update: radius-* are renamed to radiusproxy-*, 
> several options marked in the doc as optional are mandatory.

Fixed.

> The password can be retrieved with radiusproxy-show --all, because it is 
> not blocked by LDAP ACIs. Is that intended?

Yes. But I'm torn as to whether or not this is a good idea. Regular
users can't see radius proxy servers at all. Admins can see all
attributes.

It is common in radius server deployments to have a text file readable
by root with the radius secret. The current LDAP policy replicates this
"expected" behavior. It may be wise to block all reads of the secret
though. I'm open to suggestions.

> ipatokenradiusserver is not validated. See validate_searchtimelimit in 
> the config plugin for an example validator. You can use validate_ipaddr 
> and validate_hostname from ipalib.util.

Fixed.

> ipatokenusermapattribute is also not validated. Not sure if it needs to be.

I don't think validation is really possible outside of the permitted
characters for an LDAP attribute.

> The --secret CLI option does nothing (it doesn't take a value, and the 
> secret is prompted for whether or not the option is given). It should be 
> flagged no_option. (Or file an RFE for better Password semantics)

Fixed.

> For the user commands, --radius takes the name on input, but a DN is 
> output. Is that expected?

Fixed.

> I'm attaching tests I used.
> 
> > @@ -640,16 +663,29 @@ class user_mod(LDAPUpdate):
> >               entry_attrs['userpassword'] = 
> > ipa_generate_password(user_pwdchars)
> >               # 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:
> > +        if 'ipasshpubkey' in entry_attrs or 'ipauserauthtype' 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'])
> 
> This form is deprecated. Since you don't need _dn, just do
>      _entry_attrs = ldap.get_entry(dn, ['objectclass'])

Fixed.

> >                   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:
> >                   obj_classes.append('ipauserauthtypeclass')
> 
> That should be `and 'ipauserauthtypeclass' not in obj_classes`, right? 
> It must have slipped an earlier review.

Fixed.

>  > +
>  > +            if 'ipatokenradiusconfiglink' in entry_attrs:
>  > +                cl = entry_attrs['ipatokenradiusconfiglink']
>  > +                if cl:
>  > +                    if 'ipatokenradiusproxyuser' not in 
> entry_attrs['objectclass']:
>  > + 
> entry_attrs['objectclass'].append('ipatokenradiusproxyuser')
> 
> Nitpick: entry_attrs['objectclass'] is stored in obj_classes, you can 
> use that.

Fixed.

>From 838b75c95d30888d72d5120a1b2e5f348deb686c Mon Sep 17 00:00:00 2001
From: Nathaniel McCallum <npmccal...@redhat.com>
Date: Mon, 11 Nov 2013 17:58:02 -0400
Subject: [PATCH] Add RADIUS proxy support to ipalib CLI

https://fedorahosted.org/freeipa/ticket/3368
---
 API.txt                            |  95 ++++++++++++++++++++++--
 VERSION                            |   2 +-
 install/share/70ipaotp.ldif        |   2 +-
 install/updates/20-indices.update  |   7 ++
 install/updates/25-referint.update |   1 +
 install/updates/40-otp.update      |   5 ++
 ipalib/constants.py                |   1 +
 ipalib/plugins/config.py           |   2 +-
 ipalib/plugins/radiusproxy.py      | 146 +++++++++++++++++++++++++++++++++++++
 ipalib/plugins/user.py             |  65 +++++++++++++++--
 10 files changed, 308 insertions(+), 18 deletions(-)
 create mode 100644 ipalib/plugins/radiusproxy.py

diff --git a/API.txt b/API.txt
index c29efad3382ff59e4753eff5354cba72bc1fe027..328fcf76ac6c8a8f5349e9f5631ec61bae7b3ea4 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')
@@ -2551,6 +2551,81 @@ option: Str('version?', exclude='webui')
 output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
 output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: Output('value', <type 'unicode'>, 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', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: Output('value', <type 'unicode'>, 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', <type 'dict'>, None)
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: Output('value', <type 'unicode'>, 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', <type 'int'>, None)
+output: ListOfEntries('result', (<type 'list'>, <type 'tuple'>), Gettext('A list of LDAP entries', domain='ipa', localedir=None))
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: Output('truncated', <type 'bool'>, 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', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: Output('value', <type 'unicode'>, 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', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: Output('value', <type 'unicode'>, None)
 command: realmdomains_mod
 args: 0,11,3
 option: Str('add_domain', attribute=True, autofill=False, cli_name='add_domain', multivalue=False, required=False)
@@ -3596,7 +3671,7 @@ output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDA
 output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: Output('value', <type 'unicode'>, 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')
@@ -3610,7 +3685,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)
@@ -3661,7 +3738,7 @@ output: Output('result', <type 'bool'>, None)
 output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: Output('value', <type 'unicode'>, 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)
@@ -3678,7 +3755,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)
@@ -3715,7 +3794,7 @@ output: ListOfEntries('result', (<type 'list'>, <type 'tuple'>), Gettext('A list
 output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: Output('truncated', <type 'bool'>, 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')
@@ -3730,7 +3809,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 c036dc5677aafeae69e967876063c8cc1e2d7545..dc029a286481890ba663e19059acd65cecfa63b8 100644
--- a/VERSION
+++ b/VERSION
@@ -89,4 +89,4 @@ IPA_DATA_VERSION=20100614120000
 #                                                      #
 ########################################################
 IPA_API_VERSION_MAJOR=2
-IPA_API_VERSION_MINOR=68
+IPA_API_VERSION_MINOR=69
diff --git a/install/share/70ipaotp.ldif b/install/share/70ipaotp.ldif
index 824be6e9d1d284e966f05d0ff10f82d0f5cac4af..d257a46c38d2e776147e6c2a5c997a33cd100ef1 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 b966a4f7ca5d8d9fc489d94a2e09632fe562a580..5ff6d713d4290d0548751e7ee0d3efa7218c7aae 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 54f3492fae38dbc07c081678f957aaa86152294f..65af05128e433d683d61272cad6145fa8f084b04 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 ff36c87a60c071efc3e2aaee59747635a2477740..83dfab7c03bfeb121756410d79143c3812146de6 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 79885a33a3008bd83908fc34a7340e78ab25e31f..d15babb2fe742dff7ba1f015a8cda406a104cb34 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:
     # FIXME: let's renamed xmlrpc_uri to rpc_xml_uri
diff --git a/ipalib/plugins/config.py b/ipalib/plugins/config.py
index f4e35519f00112c5f3c878b67ebad1c0ab77d01f..e20e5e8016748f063e1a6240a250e1c27986c5cc 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 0000000000000000000000000000000000000000..9bc0705af7c8555ee62d9763fb0b65a8c6273e56
--- /dev/null
+++ b/ipalib/plugins/radiusproxy.py
@@ -0,0 +1,146 @@
+# Authors:
+#   Nathaniel McCallum <npmccal...@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 import Command
+from ipalib.plugins import privilege
+from ipalib.plugable import Registry
+from ipalib.util import validate_hostname, validate_ipaddr
+
+__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()
+
+def validate_radiusserver(ugettext, server):
+    if validate_ipaddr(server):
+        return None
+
+    validate_hostname(server, False, True)
+
+@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?',
+            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 a7005faf16821e7aba0df0ed1ee04ae23853f17d..ab7008981293138eda05960845eec4297d2cffbc 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)
-- 
1.8.4.2

_______________________________________________
Freeipa-devel mailing list
Freeipa-devel@redhat.com
https://www.redhat.com/mailman/listinfo/freeipa-devel

Reply via email to