From 30f70142f0b573b2cb9685a5026ffeb8b33751c9 Mon Sep 17 00:00:00 2001 From: Travis Semple Date: Tue, 24 Sep 2024 13:32:02 -0700 Subject: [PATCH 1/3] Add in new route to get emails and names for certain roles. --- .../src/auth_api/resources/v1/__init__.py | 2 + .../src/auth_api/resources/v1/keycloak.py | 41 +++++++++++++++++++ auth-api/src/auth_api/services/keycloak.py | 26 ++++++++++++ 3 files changed, 69 insertions(+) create mode 100644 auth-api/src/auth_api/resources/v1/keycloak.py diff --git a/auth-api/src/auth_api/resources/v1/__init__.py b/auth-api/src/auth_api/resources/v1/__init__.py index 3b9edb5440..5d64fb3a02 100644 --- a/auth-api/src/auth_api/resources/v1/__init__.py +++ b/auth-api/src/auth_api/resources/v1/__init__.py @@ -26,6 +26,7 @@ from .documents_affidavit import bp as documents_affidavit_bp from .entity import bp as entity_bp from .invitation import bp as invitation_bp +from .keycloak import bp as keycloak_bp from .meta import bp as meta_bp from .notifications import bp as notifications_bp from ..ops import bp as ops_bp @@ -63,6 +64,7 @@ def init_app(self, app): self.app.register_blueprint(documents_affidavit_bp) self.app.register_blueprint(entity_bp) self.app.register_blueprint(invitation_bp) + self.app.register_blueprint(keycloak_bp) self.app.register_blueprint(meta_bp) self.app.register_blueprint(notifications_bp) self.app.register_blueprint(ops_bp) diff --git a/auth-api/src/auth_api/resources/v1/keycloak.py b/auth-api/src/auth_api/resources/v1/keycloak.py new file mode 100644 index 0000000000..9440ff7d4b --- /dev/null +++ b/auth-api/src/auth_api/resources/v1/keycloak.py @@ -0,0 +1,41 @@ +# Copyright © 2024 Province of British Columbia +# +# Licensed under the Apache License, Version 2.0 (the 'License'); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an 'AS IS' BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Note this will be depreciated and replaced shortly. + +from flask import Blueprint, jsonify, request +from flask_cors import cross_origin + +from auth_api import status as http_status +from auth_api.auth import jwt as _jwt +from auth_api.exceptions import BusinessException +from auth_api.services.keycloak import KeycloakService +from auth_api.utils.endpoints_enums import EndpointEnum +from auth_api.utils.roles import Role + +bp = Blueprint('KEYCLOAK', __name__, url_prefix=f'{EndpointEnum.API_V1.value}/keycloak') + +@bp.route('/users', methods=['GET', 'OPTIONS']) +@cross_origin(origins='*', methods=['GET']) +@_jwt.has_one_of_roles([Role.SYSTEM.value]) +def get_keycloak_users_by_role(): + """Return keycloak name + email by role.""" + role = request.args.get('role', None) + if role is None: + response, status = {'message': 'Role query parameter is required'}, http_status.HTTP_400_BAD_REQUEST + try: + response, status = KeycloakService.get_user_emails_with_role(role), http_status.HTTP_200_OK + except BusinessException as exception: + response, status = {'code': exception.code, 'message': exception.message}, exception.status_code + return jsonify(response), status diff --git a/auth-api/src/auth_api/services/keycloak.py b/auth-api/src/auth_api/services/keycloak.py index 229f166011..efa3a70cfb 100644 --- a/auth-api/src/auth_api/services/keycloak.py +++ b/auth-api/src/auth_api/services/keycloak.py @@ -292,6 +292,32 @@ async def add_or_remove_users_from_group(kgs: List[KeycloakGroupSubscription]): elif task.status != 204: current_app.logger.error(f'Returned non 204: {task.method} - {task.url} - {task.status}') + @staticmethod + def get_user_emails_with_role(role: str): + """Get user emails with the role name.""" + config = current_app.config + base_url = config.get('KEYCLOAK_BASE_URL') + realm = config.get('KEYCLOAK_REALMNAME') + timeout = config.get('CONNECT_TIMEOUT', 60) + admin_token = KeycloakService._get_admin_token() + + headers = { + 'Content-Type': ContentType.JSON.value, + 'Authorization': f'Bearer {admin_token}' + } + + users = [] + get_role_users = f'{base_url}/auth/admin/realms/{realm}/roles/{role}/users' + response = requests.get(get_role_users, headers=headers, timeout=timeout) + if response.status_code == 404: + raise BusinessException(Error.DATA_NOT_FOUND, None) + response.raise_for_status() + for user in response.json(): + users.append({'firstName': user['firstName'], + 'lastName': user['lastName'], + 'email': user['email']}) + return users + @staticmethod def add_user_to_group(user_id: str, group_name: str): """Add user to the keycloak group.""" From 254471bc0e4de2a642c294646ecb024e41244bf1 Mon Sep 17 00:00:00 2001 From: Travis Semple Date: Tue, 24 Sep 2024 13:32:39 -0700 Subject: [PATCH 2/3] lint --- auth-api/src/auth_api/resources/v1/keycloak.py | 1 + 1 file changed, 1 insertion(+) diff --git a/auth-api/src/auth_api/resources/v1/keycloak.py b/auth-api/src/auth_api/resources/v1/keycloak.py index 9440ff7d4b..59b210c423 100644 --- a/auth-api/src/auth_api/resources/v1/keycloak.py +++ b/auth-api/src/auth_api/resources/v1/keycloak.py @@ -1,3 +1,4 @@ +"""Keycloak resource, will ultimately get swapped out.""" # Copyright © 2024 Province of British Columbia # # Licensed under the Apache License, Version 2.0 (the 'License'); From bac97249d63c26fcd170a2b9a7379edad5fba58c Mon Sep 17 00:00:00 2001 From: Travis Semple Date: Tue, 24 Sep 2024 13:36:56 -0700 Subject: [PATCH 3/3] fix lint --- auth-api/src/auth_api/resources/v1/keycloak.py | 1 + 1 file changed, 1 insertion(+) diff --git a/auth-api/src/auth_api/resources/v1/keycloak.py b/auth-api/src/auth_api/resources/v1/keycloak.py index 59b210c423..a71dcea743 100644 --- a/auth-api/src/auth_api/resources/v1/keycloak.py +++ b/auth-api/src/auth_api/resources/v1/keycloak.py @@ -27,6 +27,7 @@ bp = Blueprint('KEYCLOAK', __name__, url_prefix=f'{EndpointEnum.API_V1.value}/keycloak') + @bp.route('/users', methods=['GET', 'OPTIONS']) @cross_origin(origins='*', methods=['GET']) @_jwt.has_one_of_roles([Role.SYSTEM.value])