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

>From aab00ba19773064a5ec43b73d5761c7145117ed8 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/10-70ipaotp.update |   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      | 138 +++++++++++++++++++++++++++++++++++++
 ipalib/plugins/user.py             |  50 ++++++++++++--
 11 files changed, 290 insertions(+), 15 deletions(-)
 create mode 100644 ipalib/plugins/radiusproxy.py

diff --git a/API.txt b/API.txt
index cddb9d719f38dd3d89e2633148311e437aed0bc9..bee8db1fc218cd63dbb2196a38ce4086f9a4d7bb 100644
--- a/API.txt
+++ b/API.txt
@@ -514,7 +514,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')
@@ -2542,6 +2542,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)
@@ -3587,7 +3662,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,36,3
+args: 1,38,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')
@@ -3601,7 +3676,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)
@@ -3651,7 +3728,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,46,4
+args: 1,48,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)
@@ -3668,7 +3745,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)
@@ -3704,7 +3783,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,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, query=True, required=True)
 option: Str('addattr*', cli_name='addattr', exclude='webui')
 option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
@@ -3719,7 +3798,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 32f6efbc4d4768c77c514a3367cb9feb039205e5..0dacb97041d903733d3005045cac850e21f56d65 100644
--- a/VERSION
+++ b/VERSION
@@ -89,4 +89,4 @@ IPA_DATA_VERSION=20100614120000
 #                                                      #
 ########################################################
 IPA_API_VERSION_MAJOR=2
-IPA_API_VERSION_MINOR=66
+IPA_API_VERSION_MINOR=67
diff --git a/install/share/70ipaotp.ldif b/install/share/70ipaotp.ldif
index d9ae251905d14648f61c6ee61834cd92608b97f8..384ccff1e7aee1b98fd334ae7cd7557c19c88db6 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/10-70ipaotp.update b/install/updates/10-70ipaotp.update
index ad9e45ba5bd354fdcf9146be63ca5aeba67c0a4e..777019db81b596a9f5e5015c7e66b9121d6b7075 100644
--- a/install/updates/10-70ipaotp.update
+++ b/install/updates/10-70ipaotp.update
@@ -21,5 +21,5 @@ add:attributeTypes: (2.16.840.1.113730.3.8.16.1.19 NAME 'ipatokenRadiusRetries'
 add: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')
 add: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')
 add: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')
-add: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')
+add: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')
 add: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..33317930a24e109fa5e0482f7838b5e2277bf3b1
--- /dev/null
+++ b/ipalib/plugins/radiusproxy.py
@@ -0,0 +1,138 @@
+# 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
+
+__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()
+
+@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+',
+            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,
+        ),
+        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 54d11c229206034421b92b422932139d4c54c95a..919231658521b691fb7e9a46ef788a2008b067f0 100644
--- a/ipalib/plugins/user.py
+++ b/ipalib/plugins/user.py
@@ -198,14 +198,16 @@ class user(LDAPObject):
     object_name_plural = _('users')
     object_class = ['posixaccount']
     object_class_config = 'ipauserobjectclasses'
-    possible_objectclasses = ['meporiginentry', 'ipauserauthtypeclass']
+    possible_objectclasses = ['meporiginentry', 'ipauserauthtypeclass',
+                              'ipatokenradiusproxyuser']
     disallow_object_classes = ['krbticketpolicyaux']
     search_attributes_config = 'ipausersearchfields'
     default_attributes = [
         'uid', 'givenname', 'sn', 'homedirectory', 'loginshell',
         'uidnumber', 'gidnumber', 'mail', 'ou',
         'telephonenumber', 'title', 'memberof', 'nsaccountlock',
-        'memberofindirect', 'ipauserauthtype'
+        'memberofindirect', 'ipauserauthtype', 'ipatokenradiusconfiglink',
+        'ipatokenradiususername'
     ]
     search_display_attributes = [
         'uid', 'givenname', 'sn', 'homedirectory', 'loginshell',
@@ -369,9 +371,17 @@ 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('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):
@@ -547,6 +557,19 @@ class user_add(LDAPCreate):
         if 'manager' in entry_attrs:
             entry_attrs['manager'] = self.obj._normalize_manager(entry_attrs['manager'])
 
+        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):
@@ -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'])
                 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')
+
+            if 'ipatokenradiusconfiglink' in entry_attrs:
+                cl = entry_attrs['ipatokenradiusconfiglink']
+                if cl:
+                    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):
@@ -687,6 +723,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):
-- 
1.8.4.2

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

Reply via email to