URL: https://github.com/freeipa/freeipa/pull/2557
Author: tiran
 Title: #2557: [Backport][ipa-4-7] Fix ipa user-add --radius=radiusserver
Action: opened

PR body:
"""
This PR was opened automatically because PR #2530 was pushed to master and 
backport to ipa-4-7 is required.
"""

To pull the PR as Git branch:
git remote add ghfreeipa https://github.com/freeipa/freeipa
git fetch ghfreeipa pull/2557/head:pr2557
git checkout pr2557
From 27011e597638e51fd82b17ea4edc8075dee8ea37 Mon Sep 17 00:00:00 2001
From: Florence Blanc-Renaud <f...@redhat.com>
Date: Tue, 6 Nov 2018 16:43:29 +0100
Subject: [PATCH 1/4] ipa user-add: add optional objectclass for
 radius-username

The command "ipa user-add --radius-username" fails with
ipa: ERROR: attribute "ipatokenRadiusUserName" not allowed
because it does not add the objectclass ipatokenradiusproxyuser
that is required by the attribute ipatokenradiususername.

The issue happens with ipa user-add / stageuser-add / user-mod / stageuser-mod.

The fix adds the objectclass when needed in the pre_common_callback method
of baseuser_add and baseuser_mod (ensuring that user and stageuser commands
are fixed).

Fixes https://pagure.io/freeipa/issue/7569
---
 ipaserver/plugins/baseuser.py | 18 ++++++++++++++++--
 1 file changed, 16 insertions(+), 2 deletions(-)

diff --git a/ipaserver/plugins/baseuser.py b/ipaserver/plugins/baseuser.py
index eed750e68e..1b01758010 100644
--- a/ipaserver/plugins/baseuser.py
+++ b/ipaserver/plugins/baseuser.py
@@ -485,6 +485,9 @@ def pre_common_callback(self, ldap, dn, entry_attrs, attrs_list, *keys,
         assert isinstance(dn, DN)
         set_krbcanonicalname(entry_attrs)
         self.obj.convert_usercertificate_pre(entry_attrs)
+        if entry_attrs.get('ipatokenradiususername', None):
+            add_missing_object_class(ldap, u'ipatokenradiusproxyuser', dn,
+                                     entry_attrs, update=False)
 
     def post_common_callback(self, ldap, dn, entry_attrs, *keys, **options):
         assert isinstance(dn, DN)
@@ -573,8 +576,10 @@ def check_userpassword(self, entry_attrs, **options):
             setattr(context, 'randompassword', entry_attrs['userpassword'])
 
     def check_objectclass(self, ldap, dn, entry_attrs):
-        if ('ipasshpubkey' in entry_attrs or 'ipauserauthtype' in entry_attrs
-            or 'userclass' in entry_attrs or 'ipatokenradiusconfiglink' in entry_attrs):
+        # Some attributes may require additional object classes
+        special_attrs = {'ipasshpubkey', 'ipauserauthtype', 'userclass',
+                         'ipatokenradiusconfiglink', 'ipatokenradiususername'}
+        if special_attrs.intersection(entry_attrs):
             if 'objectclass' in entry_attrs:
                 obj_classes = entry_attrs['objectclass']
             else:
@@ -602,6 +607,15 @@ def check_objectclass(self, ldap, dn, entry_attrs):
                     answer = self.api.Object['radiusproxy'].get_dn_if_exists(cl)
                     entry_attrs['ipatokenradiusconfiglink'] = answer
 
+            # Note: we could have used the method add_missing_object_class
+            # but since the data is already fetched and lowercased in
+            # obj_classes, it is more efficient to use the same approach
+            # as the code right above these lines
+            if 'ipatokenradiususername' in entry_attrs:
+                if 'ipatokenradiusproxyuser' not in obj_classes:
+                    entry_attrs['objectclass'].append(
+                        'ipatokenradiusproxyuser')
+
     def pre_common_callback(self, ldap, dn, entry_attrs, attrs_list, *keys,
                             **options):
         assert isinstance(dn, DN)

From 61344fe63572b50021b73c74ea3adfe962cf3f88 Mon Sep 17 00:00:00 2001
From: Florence Blanc-Renaud <f...@redhat.com>
Date: Wed, 7 Nov 2018 11:22:25 +0100
Subject: [PATCH 2/4] tests: add xmlrpc test for ipa user-add --radius-username

Add a xmlrpc test for ipa user-add/user-mod --radius-username
The command were previously failing because the objectclass
ipatokenradiusproxyuser was not automatically added when the
attribute ipatokenRadiusUserName was added to the entry.

The test ensures that the command is now succeeding.

Related to https://pagure.io/freeipa/issue/7569
---
 ipatests/test_xmlrpc/test_user_plugin.py | 29 ++++++++++++++++++++++++
 1 file changed, 29 insertions(+)

diff --git a/ipatests/test_xmlrpc/test_user_plugin.py b/ipatests/test_xmlrpc/test_user_plugin.py
index 08d73f4b8f..06a67cfb6b 100644
--- a/ipatests/test_xmlrpc/test_user_plugin.py
+++ b/ipatests/test_xmlrpc/test_user_plugin.py
@@ -138,6 +138,19 @@ def user_npg2(request, group):
     return tracker.make_fixture(request)
 
 
+@pytest.fixture(scope='class')
+def user_radius(request):
+    """ User tracker fixture for testing users with radius user name """
+    tracker = UserTracker(name=u'radiususer', givenname=u'radiususer',
+                          sn=u'radiususer1',
+                          ipatokenradiususername=u'radiususer')
+    tracker.track_create()
+    tracker.attrs.update(
+        objectclass=objectclasses.user + [u'ipatokenradiusproxyuser']
+    )
+    return tracker.make_fixture(request)
+
+
 @pytest.fixture(scope='class')
 def group(request):
     tracker = GroupTracker(name=u'group1')
@@ -448,6 +461,15 @@ def test_rename_to_invalid_login(self, user):
                 error=u'may only include letters, numbers, _, -, . and $')):
             command()
 
+    def test_add_radius_username(self, user):
+        """ Test for ticket 7569: Try to add --radius-username """
+        user.ensure_exists()
+        command = user.make_update_command(
+            updates=dict(ipatokenradiususername=u'radiususer')
+        )
+        command()
+        user.delete()
+
 
 @pytest.mark.tier1
 class TestCreate(XMLRPC_test):
@@ -663,6 +685,13 @@ def test_create_with_numeric_only_username(self):
         )):
             testuser.create()
 
+    def test_create_with_radius_username(self, user_radius):
+        """Test for issue 7569: try to create a user with --radius-username"""
+        command = user_radius.make_create_command()
+        result = command()
+        user_radius.check_create(result)
+        user_radius.delete()
+
 
 @pytest.mark.tier1
 class TestUserWithGroup(XMLRPC_test):

From 67def9213fb7dafc68e00832699f950150f7bd3b Mon Sep 17 00:00:00 2001
From: Florence Blanc-Renaud <f...@redhat.com>
Date: Wed, 7 Nov 2018 16:58:04 +0100
Subject: [PATCH 3/4] radiusproxy: add permission for reading radius proxy
 servers

A non-admin user which has the "User Administrator" role cannot
add a user with ipa user-add --radius=<proxy> because the
call needs to read the radius proxy server entries.

The fix adds a System permission for reading radius proxy server
entries (all attributes except the ipatokenradiussecret). This
permission is added to the already existing privileges "User
Administrators" and "Stage User Administrators", so that the role
"User Administrator" can call ipa [stage]user-add|mod --radius=<proxy>

Fixes: https://pagure.io/freeipa/issue/7570
---
 ACI.txt                          |  2 ++
 ipaserver/plugins/radiusproxy.py | 19 +++++++++++++++++++
 2 files changed, 21 insertions(+)

diff --git a/ACI.txt b/ACI.txt
index e5134a55f8..82820ca210 100644
--- a/ACI.txt
+++ b/ACI.txt
@@ -234,6 +234,8 @@ dn: cn=IPA.EXAMPLE,cn=kerberos,dc=ipa,dc=example
 aci: (targetattr = "krbmaxpwdlife || krbminpwdlife || krbpwdfailurecountinterval || krbpwdhistorylength || krbpwdlockoutduration || krbpwdmaxfailure || krbpwdmindiffchars || krbpwdminlength")(targetfilter = "(objectclass=krbpwdpolicy)")(version 3.0;acl "permission:System: Modify Group Password Policy";allow (write) groupdn = "ldap:///cn=System: Modify Group Password Policy,cn=permissions,cn=pbac,dc=ipa,dc=example";)
 dn: cn=IPA.EXAMPLE,cn=kerberos,dc=ipa,dc=example
 aci: (targetattr = "cn || cospriority || createtimestamp || entryusn || krbmaxpwdlife || krbminpwdlife || krbpwdfailurecountinterval || krbpwdhistorylength || krbpwdlockoutduration || krbpwdmaxfailure || krbpwdmindiffchars || krbpwdminlength || modifytimestamp || objectclass")(targetfilter = "(objectclass=krbpwdpolicy)")(version 3.0;acl "permission:System: Read Group Password Policy";allow (compare,read,search) groupdn = "ldap:///cn=System: Read Group Password Policy,cn=permissions,cn=pbac,dc=ipa,dc=example";)
+dn: cn=radiusproxy,dc=ipa,dc=example
+aci: (targetattr = "cn || createtimestamp || description || entryusn || ipatokenradiusretries || ipatokenradiusserver || ipatokenradiustimeout || ipatokenusermapattribute || modifytimestamp || objectclass")(targetfilter = "(objectclass=ipatokenradiusconfiguration)")(version 3.0;acl "permission:System: Read Radius Servers";allow (compare,read,search) groupdn = "ldap:///cn=System: Read Radius Servers,cn=permissions,cn=pbac,dc=ipa,dc=example";)
 dn: cn=Realm Domains,cn=ipa,cn=etc,dc=ipa,dc=example
 aci: (targetattr = "associateddomain")(targetfilter = "(objectclass=domainrelatedobject)")(version 3.0;acl "permission:System: Modify Realm Domains";allow (write) groupdn = "ldap:///cn=System: Modify Realm Domains,cn=permissions,cn=pbac,dc=ipa,dc=example";)
 dn: cn=Realm Domains,cn=ipa,cn=etc,dc=ipa,dc=example
diff --git a/ipaserver/plugins/radiusproxy.py b/ipaserver/plugins/radiusproxy.py
index f638431f69..7b853555f0 100644
--- a/ipaserver/plugins/radiusproxy.py
+++ b/ipaserver/plugins/radiusproxy.py
@@ -29,6 +29,7 @@
 from ipalib.plugable import Registry
 from ipalib.util import validate_hostname, validate_ipaddr
 from ipalib.errors import ValidationError
+from ipapython.dn import DN
 import re
 
 __doc__ = _("""
@@ -147,6 +148,24 @@ class radiusproxy(LDAPObject):
         ),
     )
 
+    managed_permissions = {
+        'System: Read Radius Servers': {
+            'replaces_global_anonymous_aci': True,
+            'ipapermright': {'read', 'search', 'compare'},
+            'ipapermdefaultattr': {
+                'cn', 'objectclass', 'ipatokenradiusserver', 'description',
+                'ipatokenradiustimeout', 'ipatokenradiusretries',
+                'ipatokenusermapattribute'
+            },
+            'ipapermlocation': DN(container_dn, api.env.basedn),
+            'ipapermtargetfilter': {
+                '(objectclass=ipatokenradiusconfiguration)'},
+            'default_privileges': {
+                'User Administrators',
+                'Stage User Administrators'},
+        }
+    }
+
 @register()
 class radiusproxy_add(LDAPCreate):
     __doc__ = _('Add a new RADIUS proxy server.')

From 75ac01dacbeb37d527f30c55cf8b75fe17340389 Mon Sep 17 00:00:00 2001
From: Florence Blanc-Renaud <f...@redhat.com>
Date: Wed, 7 Nov 2018 17:02:31 +0100
Subject: [PATCH 4/4] ipatests: add integration test for "Read radius servers"
 perm

Add a new integration test for the following scenario:
- create a user with the "User Administrator" role
- as this user, create a user with a --radius=<radius_proxy_server>

This scenario was previously failing because ipa user-add --radius
requires read access to the radius server entries, and there was no
permission granting this access.

Related to https://pagure.io/freeipa/issue/7570
---
 .../test_integration/test_user_permissions.py | 43 +++++++++++++++++++
 1 file changed, 43 insertions(+)

diff --git a/ipatests/test_integration/test_user_permissions.py b/ipatests/test_integration/test_user_permissions.py
index 38e72fd9d2..13a0c983a2 100644
--- a/ipatests/test_integration/test_user_permissions.py
+++ b/ipatests/test_integration/test_user_permissions.py
@@ -98,6 +98,49 @@ def test_stageuser_show_as_alternate_admin(self):
         result = self.master.run_command(['ipa', 'stageuser-show', stageuser])
         assert 'Kerberos keys available: True' in result.stdout_text
 
+    def test_user_add_withradius(self):
+        """
+        Test that a user with User Administrator role can call
+        ipa user-add --radius myradius
+        to create a user with an assigned Radius Proxy Server.
+
+        This is a test case for issue 7570
+        """
+        # kinit admin
+        tasks.kinit_admin(self.master)
+
+        # Create a radius proxy server
+        radiusproxy = 'myradius'
+        secret = 'Secret123'
+        radius_secret_confirmation = "%s\n%s\n" % (secret, secret)
+        self.master.run_command(
+            ['ipa', 'radiusproxy-add', radiusproxy,
+             '--server', 'radius.example.com', '--secret'],
+            stdin_text=radius_secret_confirmation)
+
+        # Create a user with 'User Administrator' role
+        altuser = 'specialuser'
+        password = 'SpecialUser123'
+        password_confirmation = "%s\n%s\n" % (password, password)
+        self.master.run_command(
+            ['ipa', 'user-add', altuser, '--first', altuser, '--last', altuser,
+             '--password'],
+            stdin_text=password_confirmation)
+        self.master.run_command(
+            ['ipa', 'role-add-member', "User Administrator",
+             '--user', altuser])
+
+        # kinit as altuser to initialize the password
+        altuser_kinit = "%s\n%s\n%s\n" % (password, password, password)
+        self.master.run_command(['kinit', altuser], stdin_text=altuser_kinit)
+        # call ipa user-add with --radius=...
+        # this call requires read access to radius proxy servers
+        self.master.run_command(
+            ['ipa', 'user-add', '--first', 'test', '--last', 'test',
+             '--user-auth-type', 'radius', '--radius-username', 'testradius',
+             'testradius', '--radius', radiusproxy])
+
+
 
 class TestInstallClientNoAdmin(IntegrationTest):
     num_clients = 1
_______________________________________________
FreeIPA-devel mailing list -- freeipa-devel@lists.fedorahosted.org
To unsubscribe send an email to freeipa-devel-le...@lists.fedorahosted.org
Fedora Code of Conduct: https://getfedora.org/code-of-conduct.html
List Guidelines: https://fedoraproject.org/wiki/Mailing_list_guidelines
List Archives: 
https://lists.fedorahosted.org/archives/list/freeipa-devel@lists.fedorahosted.org

Reply via email to