Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/fwf3399 user endpoint changes #2164

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions forms-flow-api/src/formsflow_api/resources/formio.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,18 +132,13 @@ def add_jwt_token_as_header(response):
) # Email is not mandatory in keycloak
project_id: str = current_app.config.get("FORMIO_PROJECT_URL")
groups = user.groups
if user.tenant_key:
groups = [
group for group in groups if group.startswith(f"/{user.tenant_key}")
]

payload: Dict[str, any] = {
"external": True,
"form": {"_id": _resource_id},
"user": {
"_id": unique_user_id,
"roles": _role_ids,
"customRoles": user.groups,
"customRoles": groups,
},
}
if project_id:
Expand Down
26 changes: 14 additions & 12 deletions forms-flow-api/src/formsflow_api/resources/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ class KeycloakUsersList(Resource):
params={
"memberOfGroup": {
"in": "query",
"description": "Group/Role name for fetching users.",
"description": "Group name for fetching users.",
"default": "",
},
"search": {
Expand Down Expand Up @@ -186,6 +186,11 @@ class KeycloakUsersList(Resource):
"description": "Boolean which defines whether count is returned.",
"default": "false",
},
"permission": {
"in": "query",
"description": "A string to filter user by permission.",
"default": "",
},
}
)
@API.response(200, "OK:- Successful request.", model=user_list_count_model)
Expand All @@ -200,13 +205,15 @@ class KeycloakUsersList(Resource):
def get(): # pylint: disable=too-many-locals
"""Get users list."""
group_name = request.args.get("memberOfGroup")
permission = request.args.get("permission")
search = request.args.get("search")
page_no = int(request.args.get("pageNo", 0))
limit = int(request.args.get("limit", 0))
role = request.args.get("role") == "true"
count = request.args.get("count") == "true"
kc_admin = KeycloakGroupService()
kc_admin = KeycloakFactory.get_instance()
if group_name:

(users_list, users_count) = kc_admin.get_users(
page_no, limit, role, group_name, count, search
)
Expand All @@ -217,12 +224,9 @@ def get(): # pylint: disable=too-many-locals
}
else:
(user_list, user_count) = kc_admin.search_realm_users(
search, page_no, limit, role, count
search, page_no, limit, role, count, permission
)
user_list_response = []
for user in user_list:
user = UsersListSchema().dump(user)
user_list_response.append(user)
user_list_response = UsersListSchema().dump(user_list, many=True)
response = {"data": user_list_response, "count": user_count}
return response, HTTPStatus.OK

Expand Down Expand Up @@ -255,7 +259,7 @@ def put(user_id, group_id):
current_app.logger.debug("Initializing admin API service...")
service = KeycloakGroupService()
current_app.logger.debug("Successfully initialized admin API service !")
response = service.add_user_to_group_role(user_id, group_id, user_and_group)
response = service.add_user_to_group(user_id, group_id, user_and_group)
if not response:
current_app.logger.error(f"Failed to add {user_id} to group {group_id}")
return {
Expand All @@ -282,11 +286,9 @@ def delete(user_id, group_id):
json_payload = request.get_json()
user_and_group = UserPermissionUpdateSchema().load(json_payload)
current_app.logger.debug("Initializing admin API service...")
service = KeycloakGroupService()
service = KeycloakFactory.get_instance()
current_app.logger.debug("Successfully initialized admin API service !")
response = service.remove_user_from_group_role(
user_id, group_id, user_and_group
)
response = service.remove_user_from_group(user_id, group_id, user_and_group)
if not response:
current_app.logger.error(
f"Failed to remove {user_id} from group {group_id}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,20 +51,34 @@ def create_group_role(self, data: Dict):
raise NotImplementedError("Method not implemented")

@abstractmethod
def add_user_to_group_role(self, user_id: str, group_id: str, payload: Dict):
"""Add user to role / group."""
def add_user_to_group(self, user_id: str, group_id: str, payload: Dict):
"""Add user to group."""
raise NotImplementedError("Method not implemented")

@abstractmethod
def remove_user_from_group_role(
self, user_id: str, group_id: str, payload: Dict = None
):
"""Remove user to role / group."""
def add_role_to_user(self, user_id: str, role_id: str, payload: Dict):
"""Add role to user."""
raise NotImplementedError("Method not implemented")

@abstractmethod
def remove_user_from_group(self, user_id: str, group_id: str, payload: Dict = None):
"""Remove group from user."""
raise NotImplementedError("Method not implemented")

@abstractmethod
def remove_role_from_user(self, user_id: str, group_id: str, payload: Dict = None):
"""Remove role from user."""
raise NotImplementedError("Method not implemented")

@abstractmethod
def search_realm_users( # pylint: disable-msg=too-many-arguments
self, search: str, page_no: int, limit: int, role: bool, count: bool
self,
search: str,
page_no: int,
limit: int,
role: bool,
count: bool,
permission: str,
):
"""Get users in a realm."""
raise NotImplementedError("Method not implemented")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import re
from http import HTTPStatus
from typing import Dict, List
from typing import Dict

from flask import current_app
from formsflow_api_utils.exceptions import BusinessException
Expand All @@ -16,45 +16,10 @@
class KeycloakClientService(KeycloakGroupService):
"""Keycloak Admin implementation for client related operations."""

def __populate_user_roles(self, user_list: List) -> List:
"""Collects roles for a user list and populates the role attribute."""
for user in user_list:
user["role"] = (
self.client.get_user_roles(user.get("id")) if user.get("id") else []
)
return user_list

def get_analytics_groups(self, page_no: int, limit: int):
"""Get analytics roles."""
return self.client.get_analytics_roles(page_no, limit)

def get_users( # pylint: disable-msg=too-many-arguments
self,
page_no: int,
limit: int,
role: bool,
group_name: str,
count: bool,
search: str,
):
"""Get users under this client with formsflow-reviewer role."""
# group_name was hardcoded before as `formsflow-reviewer` make sure
# neccessary changes in the client side are made for role based env
current_app.logger.debug(
"Fetching client based users from keycloak with formsflow-reviewer role..."
)
client_id = self.client.get_client_id()
url = f"clients/{client_id}/roles/{group_name}/users"
users_list = self.client.get_request(url)
if search:
users_list = self.user_service.user_search(search, users_list)
users_count = len(users_list) if count else None
if page_no and limit:
users_list = self.user_service.paginate(users_list, page_no, limit)
if role:
users_list = self.__populate_user_roles(users_list)
return (users_list, users_count)

def update_group(self, group_id: str, data: Dict):
"""Update keycloak group."""
data = self.append_tenant_key(data)
Expand All @@ -66,47 +31,14 @@ def create_group_role(self, data: Dict):
self.append_tenant_key(data)
return super().create_group_role(data)

def add_user_to_group_role(self, user_id: str, group_id: str, payload: Dict):
"""Add user to role."""
client_id = self.client.get_client_id()
data = {
"containerId": client_id,
"id": group_id,
"name": payload.get("name"),
}
return self.client.create_request(
url_path=f"users/{user_id}/role-mappings/clients/{client_id}", data=[data]
)

def remove_user_from_group_role(
self, user_id: str, group_id: str, payload: Dict = None
):
"""Remove user to role."""
client_id = self.client.get_client_id()
data = {
"containerId": client_id,
"id": group_id,
"name": payload.get("name"),
}
return self.client.delete_request(
url_path=f"users/{user_id}/role-mappings/clients/{client_id}", data=[data]
)

def search_realm_users( # pylint: disable-msg=too-many-arguments
self, search: str, page_no: int, limit: int, role: bool, count: bool
):
"""Search users in a realm."""
if not page_no or not limit:
raise BusinessException(BusinessErrorCode.MISSING_PAGINATION_PARAMETERS)

user_list, users_count = self.get_tenant_users(search, page_no, limit, count)
if role:
user_list = self.__populate_user_roles(user_list)
return (user_list, users_count)

@user_context
def get_tenant_users(
self, search: str, page_no: int, limit: int, count: bool, **kwargs
self,
search: str,
page_no: int,
limit: int,
count: bool,
**kwargs,
): # pylint: disable=too-many-arguments
"""Return list of users in the tenant."""
# Search and attribute search (q) in Keycloak doesn't work together.
Expand Down
Loading
Loading