Exemple #1
0
    def post(self, request, user_name):
        """
        Create a new user

        Args:
            request: Django rest framework request
            user_name: User name of the user to create

        Returns:
            None

        Note: User's data is passed as json data in the request
        """
        user_data = request.data.copy()

        # Keep track of what has been created, so in the catch block we can
        # delete them when there is an error in another step of create user
        user_created = False

        try:
            with KeyCloakClient('BOSS') as kc:
                # DP NOTE: email also has to be unique, in the current configuration of Keycloak
                data = {
                    "username": user_name,
                    "firstName": user_data.get('first_name'),
                    "lastName": user_data.get('last_name'),
                    "email": user_data.get('email'),
                    "enabled": True
                }
                data = json.dumps(data)
                response = kc.create_user(data)
                user_created = True

                data = {
                    "type": "password",
                    "temporary": False,
                    "value": user_data.get('password')
                }
                kc.reset_password(user_name, data)

                return Response(status=201)
        except KeyCloakError:
            # cleanup created objects
            if True in [user_created]:
                try:
                    with KeyCloakClient('BOSS') as kc:
                        try:
                            if user_created:
                                kc.delete_user(user_name)
                        except:
                            LOG.exception(
                                "Error deleting user '{}'".format(user_name))
                except:
                    LOG.exception(
                        "Error communicating with Keycloak to delete created user and primary group"
                    )

            msg = "Error addng user '{}' to Keycloak".format(user_name)
            return BossKeycloakError(msg)
Exemple #2
0
    def post(self, request, user_name):
        """
        Create a new user if the user does not exist
        Args:
            request: Django rest framework request
            user_name: User name from the request

        Returns:
            Http status of the request

        """
        user_data = request.data.copy()

        # Keep track of what has been created, so in the catch block we can
        # delete them when there is an error in another step of create user
        user_created = False

        try:
            with KeyCloakClient('BOSS') as kc:
                # Create the user account, attached to the default groups
                # DP NOTE: email also has to be unique, in the current configuration of Keycloak
                data = {
                    "username": user_name,
                    "firstName": user_data.get('first_name'),
                    "lastName": user_data.get('last_name'),
                    "email": user_data.get('email'),
                    "enabled": True
                }
                data = json.dumps(data)
                response = kc.create_user(data)
                user_create = True

                data = {
                    "type": "password",
                    "temporary": False,
                    "value": user_data.get('password')
                }
                kc.reset_password(user_name, data)

                return Response(response, status=201)
        except Exception as e:
            # cleanup created objects
            if True in [user_created]:
                try:
                    with KeyCloakClient('BOSS') as kc:
                        try:
                            if user_created:
                                kc.delete_user(user_name)
                        except:
                            LOG.exception(
                                "Error deleting user '{}'".format(user_name))
                except:
                    LOG.exception(
                        "Error communicating with Keycloak to delete created user and primary group"
                    )

            msg = "Error addng user '{}' to Keycloak".format(user_name)
            return BossHTTPError.from_exception(e, 404, msg, 30000)
Exemple #3
0
    def post(self, request, user_name, role_name):
        """
        Assign a role to a user
        Args:
            request: Django rest framework request
            user_name: User name
            role_name : Role name

        Returns:
            Http status of the request

        """
        try:
            if role_name not in ['admin', 'user-manager', 'resource-manager']:
                return BossHTTPError(404,
                                     "Invalid role name {}".format(role_name),
                                     30000)

            with KeyCloakClient('BOSS') as kc:
                response = kc.map_role_to_user(user_name, role_name)
                return Response(serializer.data, status=201)

        except Exception as e:
            return BossHTTPError(
                404, "Unable to map role {} to user {} in keycloak. {}".format(
                    role_name, user_name, e), 30000)
Exemple #4
0
    def delete(self, request, user_name, role_name):
        """
        Delete a user
        Args:
            request: Django rest framework request
            user_name: User name from the request
            role_name: Role name from the request

        Returns:
            Http status of the request

        """
        try:

            if role_name not in ['admin', 'user-manager', 'resource-manager']:
                return BossHTTPError(404,
                                     "Invalid role name {}".format(role_name),
                                     30000)
            with KeyCloakClient('BOSS') as kc:
                response = kc.remove_role_from_user(user_name, role_name)
                return Response(status=204)

        except Exception as e:
            return BossHTTPError(
                404,
                "Unable to remove role {} from user {} in keycloak. {}".format(
                    role_name, user_name, e), 30000)
Exemple #5
0
    def get(self, request, user_name, role_name=None):
        """
        Multi-function method
        1) If role_name is None, return all roles assigned to the user
        2) If role_name is not None, return True/False if the user
           is assigned the given role

        Args:
           request: Django rest framework request
           user_name: User name of the user to check
           role_name: Name of the role to check, or None to return all roles

        Returns:
            True if the user has the role or a list of all assigned roles
        """
        try:
            with KeyCloakClient('BOSS') as kc:
                resp = kc.get_realm_roles(user_name)
                roles = [r['name'] for r in resp]
                roles = filter_roles(roles)

                if role_name is None:
                    return Response(roles, status=200)
                else:
                    exists = role_name in roles
                    return Response(exists, status=200)

        except KeyCloakError:
            msg = "Error getting user '{}' role's from Keycloak".format(
                user_name)
            return BossKeycloakError(msg)
Exemple #6
0
    def get(self, request, user_name, role_name=None):
        """
        Check if the user has a specific role
        Args:
           request: Django rest framework request
           user_name: User name
           role_name:

        Returns:
            True if the user has the role
        """
        try:
            with KeyCloakClient('BOSS') as kc:
                resp = kc.get_realm_roles(user_name)
                roles = [r['name'] for r in resp]
                # DP TODO: filter roles array to limit to valid roles??

                if role_name is None:
                    return Response(roles, status=200)
                else:
                    valid = ['admin', 'user-manager', 'resource-manager']
                    if role_name not in valid:
                        return BossHTTPError(
                            404, "Invalid role name {}".format(role_name),
                            30000)

                    exists = role_name in roles
                    return Response(exists, status=200)

        except Exception as e:
            return BossHTTPError(
                404, "Error getting user's {} roles from keycloak. {}".format(
                    user_name, e), 30000)
Exemple #7
0
    def delete(self, request, user_name, role_name):
        """
        Unasign a role from a user

        Args:
            request: Django rest framework request
            user_name: User name of user to unassign role from
            role_name : Role name of role to unassign from user

        Returns:
            None
        """
        # DP NOTE: admin role has to be removed manually in Keycloak
        if role_name == 'admin':
            return BossHTTPError("Cannot remove 'admin' role",
                                 ErrorCodes.INVALID_ROLE)

        # DP NOTE: user-manager role can only be modified by an admin
        if role_name == 'user-manager':
            resp = check_for_admin(request.user)
            if resp is not None:
                return resp

        try:
            with KeyCloakClient('BOSS') as kc:
                response = kc.remove_role_from_user(user_name, role_name)
                return Response(status=204)

        except KeyCloakError:
            msg = "Unable to remove role '{}' from user '{}' in Keycloak".format(
                role_name, user_name)
            return BossKeycloakError(msg)
Exemple #8
0
    def get(self, request, user_name=None):
        """
        Get information about a user

        Args:
           request: Django rest framework request
           user_name: User name to get information about

        Returns:
            JSON dictionary of user data
        """
        try:
            with KeyCloakClient('BOSS') as kc:
                if user_name is None:  # Get all users
                    search = request.GET.get('search')
                    response = kc.get_all_users(search)
                    return Response(response, status=200)
                else:
                    response = kc.get_userdata(user_name)
                    roles = kc.get_realm_roles(user_name)
                    response["realmRoles"] = filter_roles(
                        [r['name'] for r in roles])
                    return Response(response, status=200)
        except KeyCloakError:
            msg = "Error getting user '{}' from Keycloak".format(user_name)
            return BossKeycloakError(msg)
Exemple #9
0
    def delete(self, request, user_name):
        """
        Delete a user

        Args:
            request: Django rest framework request
            user_name: User name of user to delete

        Returns:
            None
        """
        # DP TODO: verify user_name is not an admin
        if user_name == 'bossadmin':
            msg = "Cannot delete user bossadmin from Keycloak".format(
                user_name)
            return BossKeycloakError(msg)
        else:
            try:
                with KeyCloakClient('BOSS') as kc:
                    kc.delete_user(user_name)

                return Response(status=204)
            except KeyCloakError:
                msg = "Error deleting user '{}' from Keycloak".format(
                    user_name)
                return BossKeycloakError(msg)
Exemple #10
0
    def setUp(self):
        """Create the KeyCloakClient object and override login/logout with mock versions"""
        self.client = KeyCloakClient(REALM, BASE)

        # Have to use MethodType to bind the mock method to the specific instance
        # of the KeyCloakClient instead of binding it to all instances of the
        # class
        # See http://stackoverflow.com/questions/972/adding-a-method-to-an-existing-object-instance
        self.client.login = MethodType(mock_login, self.client)
        self.client.logout = MethodType(mock_logout, self.client)
Exemple #11
0
    def user_exist(self, uid):
        """Cache the results of looking up the user in Keycloak"""
        if not LOCAL_KEYCLOAK_TESTING:
            with KeyCloakClient('BOSS') as kc:
                return kc.user_exist(uid)

        # Code for local testing with KeyCloak.
        try:
            kc = KeyCloakClient('BOSS',
                                url_base='http://localhost:8080/auth',
                                https=False)
            kc.login(username=KEYCLOAK_ADMIN_USER,
                     password=KEYCLOAK_ADMIN_PASSWORD,
                     client_id='admin-cli',
                     login_realm='master')
            return kc.user_exist(uid)
        finally:
            kc.logout()
Exemple #12
0
    def delete(self, request, user_name):
        """
        Delete a user
        Args:
            request: Django rest framework request
            user_name: User name from the request
        Returns:
            Http status of the request
        """
        try:
            # Delete from Keycloak
            with KeyCloakClient('BOSS') as kc:
                kc.delete_user(user_name)

            return Response(status=204)
        except Exception as e:
            msg = "Error deleting user '{}' from Keycloak".format(user_name)
            return BossHTTPError.from_exception(e, 404, msg, 30000)
Exemple #13
0
    def get(self, request, user_name):
        """
        Get the user information
        Args:
           request: Django rest framework request
           user_name: User name from the request

        Returns:
            User if the user exists
        """
        try:
            with KeyCloakClient('BOSS') as kc:
                response = kc.get_userdata(user_name)
                roles = kc.get_realm_roles(user_name)
                response["realmRoles"] = [r['name'] for r in roles]
                return Response(response, status=200)
        except Exception as e:
            msg = "Error getting user '{}' from Keycloak".format(user_name)
            return BossHTTPError.from_exception(e, 404, msg, 30000)
Exemple #14
0
    def delete(self, request, user_name):
        """
        Delete a user

        Args:
            request: Django rest framework request
            user_name: User name of user to delete

        Returns:
            None
        """
        try:
            with KeyCloakClient('BOSS') as kc:
                kc.delete_user(user_name)

            return Response(status=204)
        except KeyCloakError:
            msg = "Error deleting user '{}' from Keycloak".format(user_name)
            return BossKeycloakError(msg)
Exemple #15
0
    def post(self, request, user_name, role_name):
        """
        Assign a role to a user

        Args:
            request: Django rest framework request
            user_name: User name of user to assign role to
            role_name : Role name of role to assign to user

        Returns:
            None
        """
        try:
            with KeyCloakClient('BOSS') as kc:
                response = kc.map_role_to_user(user_name, role_name)
                return Response(status=201)

        except KeyCloakError:
            msg = "Unable to map role '{}' to user '{}' in Keycloak".format(
                role_name, user_name)
            return BossKeycloakError(msg)
Exemple #16
0
    def delete(self, request, user_name, role_name):
        """
        Unasign a role from a user

        Args:
            request: Django rest framework request
            user_name: User name of user to unassign role from
            role_name : Role name of role to unassign from user

        Returns:
            None
        """
        try:
            with KeyCloakClient('BOSS') as kc:
                response = kc.remove_role_from_user(user_name, role_name)
                return Response(status=204)

        except KeyCloakError:
            msg = "Unable to remove role '{}' from user '{}' in Keycloak".format(
                role_name, user_name)
            return BossKeycloakError(msg)
Exemple #17
0
    def get_keycloak_client(self):
        """
        Get the KeyCloak client.

        If LOCAL_KEYCLOAK_TESTING set in the Django settings, logs in using to the
        local test KeyCloak server.

        Returns:
            (KeyCloakClient)
        """
        if LOCAL_KEYCLOAK_TESTING:
            kc = KeyCloakClient('BOSS',
                                url_base='http://localhost:8080/auth',
                                https=False)
            kc.login(username=KEYCLOAK_ADMIN_USER,
                     password=KEYCLOAK_ADMIN_PASSWORD,
                     client_id='admin-cli',
                     login_realm='master')
        else:
            kc = KeyCloakClient('BOSS')

        return kc
Exemple #18
0
 def setUp(self):
     """Create the KeyCloakClient object and override previously tested internal methods with mock versions"""
     self.client = KeyCloakClient(REALM, BASE)
Exemple #19
0
class TestAuthentication(unittest.TestCase):
    """Test the login/logout methods and the context manager that wraps login/logout"""

    def setUp(self):
        self.client = KeyCloakClient(REALM, BASE)

    @mock.patch(prefix + 'keycloak.Vault', autospec = True)
    @mock.patch(prefix + 'keycloak.requests.post', autospec = True)
    def test_login(self, mPost, mVault):
        """Test that login() make the correct POST call with data from Vault and created the internal token variable"""
        mPost.return_value = MockResponse(200, json.dumps(TOKEN))
        mVault.return_value.read = lambda p, k: k

        self.assertIsNone(self.client.token)

        self.client.login()

        self.assertEqual(self.client.token, TOKEN)

        # DP NOTE: since the post call contains information read from Vault
        #          I don't explicitly check for the calls to Vault, because
        #          if they didn't happen, they the post call assert will fail

        url = BASE + '/realms/realm/protocol/openid-connect/token'
        data = {'grant_type': 'password',
                'client_id': 'client_id',
                'username': '******',
                'password': '******' }
        call = mock.call(url, data = data, verify = True)
        self.assertEqual(mPost.mock_calls, [call])

    @mock.patch(prefix + 'keycloak.Vault', autospec = True)
    @mock.patch(prefix + 'keycloak.requests.post', autospec = True)
    def test_failed_login(self, mPost, mVault):
        """Test that if there is an error from Keycloak when logging in that a KeyCloakError is raised"""
        mPost.return_value = MockResponse(500, '[]')
        mVault.return_value.read = lambda p, k: k

        with self.assertRaises(KeyCloakError):
            self.client.login()

    @mock.patch(prefix + 'keycloak.requests.post', autospec = True)
    def test_logout(self, mPost):
        """Test that logout() makes the correct POST call with data used in login and clears the token variable"""
        mPost.return_value = MockResponse(200, '[]')

        self.client.login = MethodType(mock_login, self.client) # See comment in TestRequests.setUp()
        self.client.login()

        self.assertEqual(self.client.token, TOKEN)

        self.client.logout()

        self.assertIsNone(self.client.token)

        url = BASE + '/realms/realm/protocol/openid-connect/logout'
        data = {'refresh_token': 'refresh_token',
                'client_id': 'client_id' }
        call = mock.call(url, data = data, verify = True)
        self.assertEqual(mPost.mock_calls, [call])

    @mock.patch(prefix + 'keycloak.requests.post', autospec = True)
    def test_failed_logout(self, mPost):
        """Test that if there is an error from Keycloak when logging out that a KeyCloakError is raised"""
        mPost.return_value = MockResponse(500, '[]')

        self.client.login = MethodType(mock_login, self.client) # See comment in TestRequests.setUp()
        self.client.login()

        with self.assertRaises(KeyCloakError):
            self.client.logout()

    def test_with(self):
        """Test that that when in a 'using' block that login and logout are called"""
        self.client.loginCalled = False
        self.client.logoutCalled = False

        def call_login(self):
            self.loginCalled = True

        def call_logout(self):
            self.logoutCalled = True

        self.client.login = MethodType(call_login, self.client)
        self.client.logout = MethodType(call_logout, self.client)

        with self.client as kc:
            pass

        self.assertTrue(self.client.loginCalled)
        self.assertTrue(self.client.logoutCalled)

    def test_failed_with(self):
        """Test that if there is an exception in a 'using' block that it is propogated correctly"""
        self.client.loginCalled = False
        self.client.logoutCalled = False

        def call_login(self):
            self.loginCalled = True

        def call_logout(self):
            self.logoutCalled = True

        self.client.login = MethodType(call_login, self.client)
        self.client.logout = MethodType(call_logout, self.client)

        with self.assertRaises(Exception):
            with self.client as kc:
                raise Exception()

        self.assertTrue(self.client.loginCalled)
        self.assertTrue(self.client.logoutCalled)

    def test_failed_with_login(self):
        """Test that when in a 'using' block if login() fails logout() is not called"""
        self.client.loginCalled = False
        self.client.logoutCalled = False

        def call_login(self):
            self.loginCalled = True
            raise Exception()

        def call_logout(self):
            self.logoutCalled = True

        self.client.login = MethodType(call_login, self.client)
        self.client.logout = MethodType(call_logout, self.client)

        with self.assertRaises(Exception):
            with self.client as kc:
                pass

        self.assertTrue(self.client.loginCalled)
        self.assertFalse(self.client.logoutCalled)

    def test_failed_with_logout(self):
        """Test that when in a 'using' block if logout() fails no exception is propogated"""
        self.client.loginCalled = False
        self.client.logoutCalled = False

        def call_login(self):
            self.loginCalled = True

        def call_logout(self):
            self.logoutCalled = True
            raise Exception()

        self.client.login = MethodType(call_login, self.client)
        self.client.logout = MethodType(call_logout, self.client)

        with self.client as kc:
            pass

        self.assertTrue(self.client.loginCalled)
        self.assertTrue(self.client.logoutCalled)
Exemple #20
0
 def setUp(self):
     self.client = KeyCloakClient(REALM, BASE)
Exemple #21
0
class TestClient(unittest.TestCase):
    """Test the client methods used to interact with Keycloak"""

    def setUp(self):
        """Create the KeyCloakClient object and override previously tested internal methods with mock versions"""
        self.client = KeyCloakClient(REALM, BASE)

        # DP NOTE: login / logout are not mocked, because the methods are called without logging in or out

    @staticmethod
    def makeMM(arg):
        """Make a mock object and set the return value(s)

        Args:
            arg : Vale or list of Values for mock object to return when called

        Returns:
            mock.MagicMock : mock object
        """

        mm = mock.MagicMock()
        if type(arg) == type([]):
            mm.side_effect = arg
        else:
            mm.return_value = arg

        return mm

    def setReturn(self, get=None, post=None, put=None, delete=None, get_user_id=False, get_role_by_name=False):
        """Mock up the return values for the different HTTP methods of KeyCloakClient

        Args:
            get : Value or Iterable of values to return for GETs
            post : Value or Iterable of values to return for POSTs
            put : Value or Iterable of values to return for PUTs
            delete : Value or Iterable of values to return for DELETEs
            get_user_id (bool) : If KeyCloakClient.get_user_id should be stubbed out
            get_role_by_name (bool) : If KeyCloakClient.get_role_by_name should be stubbed out
        """
        make = lambda m: TestClient.makeMM(m)

        self.client._get = make(get)
        self.client._post = make(post)
        self.client._put = make(put)
        self.client._delete = make(delete)

        if get_user_id:
            self.client.get_user_id = make(0)

        if get_role_by_name:
            self.client.get_role_by_name = make({})

    def test_get_userdata(self):
        username = '******'
        data = {'username': username}
        self.setReturn(get=MockResponse(200, json.dumps(data)), get_user_id=True)

        response = self.client.get_userdata(username)

        self.assertEqual(data, response)

        url = 'users/0' # user id set in setReturn
        call = mock.call(url)
        self.assertEqual(self.client._get.mock_calls, [call])
        self.assertEqual(self.client.get_user_id.call_count, 1)

    def test_get_userinfo(self):
        data = {'test': 'test'}
        self.setReturn(get=MockResponse(200, json.dumps(data)))

        response = self.client.get_userinfo()

        self.assertEqual(data, response)

        url = 'protocol/openid-connect/userinfo'
        call = mock.call(url)
        self.assertEqual(self.client._get.mock_calls, [call])

    def test_create_user(self):
        data = {'test': 'test'}
        self.setReturn()

        response = self.client.create_user(data)

        self.assertIsNone(response)

        call = mock.call('users', data)
        self.assertEqual(self.client._post.mock_calls, [call])

    def test_reset_password(self):
        data = {'test': 'test'}
        self.setReturn(get_user_id = True)

        response = self.client.reset_password('test', data)

        self.assertIsNone(response)

        call = mock.call('users/0/reset-password', data)
        self.assertEqual(self.client._put.mock_calls, [call])
        self.assertEqual(self.client.get_user_id.call_count, 1)

    def test_delete_user(self):
        self.setReturn(get_user_id = True)

        response = self.client.delete_user('test')

        self.assertIsNone(response)

        call = mock.call('users/0')
        self.assertEqual(self.client._delete.mock_calls, [call])
        self.assertEqual(self.client.get_user_id.call_count, 1)

    def test_get_user_id(self):
        username = '******'
        params = {'username': username}
        self.setReturn(get=MockResponse(200, '[{"id": 0}]'))

        response = self.client.get_user_id(username)

        self.assertEqual(0, response)

        call = mock.call('users', params)
        self.assertEqual(self.client._get.mock_calls, [call])

    def test_failed_get_user_id(self):
        username = '******'
        params = {'username': username}
        self.setReturn(get=MockResponse(500, '[]'))

        with self.assertRaises(KeyCloakError):
            self.client.get_user_id(username)

        call = mock.call('users', params)
        self.assertEqual(self.client._get.mock_calls, [call])

    def test_get_realm_roles(self):
        data = {'test': 'test'}
        self.setReturn(get=MockResponse(200, json.dumps(data)), get_user_id=True)

        response = self.client.get_realm_roles('test')

        self.assertEqual(data, response)

        call = mock.call('users/0/role-mappings/realm')
        self.assertEqual(self.client._get.mock_calls, [call])
        self.assertEqual(self.client.get_user_id.call_count, 1)

    def test_get_role_by_name(self):
        data = {'test': 'test'}
        self.setReturn(get=MockResponse(200, json.dumps(data)))

        response = self.client.get_role_by_name('test')

        self.assertEqual(data, response)

        call = mock.call('roles/test')
        self.assertEqual(self.client._get.mock_calls, [call])

    def test_failed_get_role_by_name(self):
        self.setReturn(get=MockResponse(500, '[]'))

        with self.assertRaises(KeyCloakError):
            self.client.get_role_by_name('test')

        call = mock.call('roles/test')
        self.assertEqual(self.client._get.mock_calls, [call])

    def test_map_role_to_user(self):
        self.setReturn(get_user_id = True, get_role_by_name = True)

        response = self.client.map_role_to_user('test', 'test')

        self.assertIsNone(response)

        call = mock.call('users/0/role-mappings/realm', '[{}]')
        self.assertEqual(self.client._post.mock_calls, [call])

    def test_remove_role_from_user(self):
        self.setReturn(get_user_id = True, get_role_by_name = True)

        response = self.client.remove_role_from_user('test', 'test')

        self.assertIsNone(response)

        call = mock.call('users/0/role-mappings/realm', '[{}]')
        self.assertEqual(self.client._delete.mock_calls, [call])
 def user_exist(self, uid):
     """Cache the results of looking up the user in Keycloak"""
     with KeyCloakClient('BOSS') as kc:
         return kc.user_exist(uid)