From c5326e08ccea55cda388aa5db4c7c3ef5c45c7e7 Mon Sep 17 00:00:00 2001 From: catherine meng Date: Mon, 12 Aug 2024 14:34:36 -0700 Subject: [PATCH 1/8] feat(1469: added gc notify integration to admin management, refs: #1469 --- .../api/app/integration/gc_notify.py | 57 +++++++++++++++++++ server/admin_management/api/app/schemas.py | 15 ++++- .../api/app/services/utils_service.py | 10 +--- server/admin_management/api/config/config.py | 20 ++++++- server/admin_management/local-dev.env | 4 +- server/admin_management/requirements.txt | 1 + 6 files changed, 94 insertions(+), 13 deletions(-) create mode 100644 server/admin_management/api/app/integration/gc_notify.py diff --git a/server/admin_management/api/app/integration/gc_notify.py b/server/admin_management/api/app/integration/gc_notify.py new file mode 100644 index 000000000..65109f6a4 --- /dev/null +++ b/server/admin_management/api/app/integration/gc_notify.py @@ -0,0 +1,57 @@ +import logging + +import requests +from api.app.schemas import GCNotifyGrantAccessEmailParam +from api.config import config + +LOGGER = logging.getLogger(__name__) + +GC_NOTIFY_EMAIL_BASE_URL = "https://api.notification.canada.ca" +GC_NOTIFY_GRANT_DELEGATED_ADMIN_EMAIL_TEMPLATE_ID = "9abff613-e507-4562-aae0-008317dfe3b9" +GC_NOTIFY_GRANT_APP_ADMIN_EMAIL_TEMPLATE_ID = "230bca59-4906-40b2-8f2b-2f6186a98663" + + +class GCNotifyEmailService: + """ + The class is used for sending email + """ + + TIMEOUT = (5, 10) # Timeout (connect, read) in seconds. + + def __init__(self, email_template_id = GC_NOTIFY_GRANT_DELEGATED_ADMIN_EMAIL_TEMPLATE_ID): + self.API_KEY = config.get_gc_notify_email_api_key() + self.grant_access_email_template_id = email_template_id + self.email_base_url = GC_NOTIFY_EMAIL_BASE_URL + self.headers = { + "Content-Type": "application/json", + "Authorization": "ApiKey-v1 " + self.API_KEY, + } + + self.session = requests.Session() + self.session.headers.update(self.headers) + + def send_granted_access_email(self, params: GCNotifyGrantAccessEmailParam): + """ + Send grant access email + """ + email_params = { + "email_address": params.send_to_email_address, + "template_id": self.grant_access_email_template_id, + "personalisation": { + "first_name": params.first_name, + "last_name": params.last_name, + "application_name": params.application_name, + "role_list_string": params.role_list_string, + "application_team_contact_email": params.application_team_contact_email + }, + } + gc_notify_email_send_url = f"{self.email_base_url}/v2/notifications/email" + + r = self.session.post( + gc_notify_email_send_url, timeout=self.TIMEOUT, json=email_params + ) + r.raise_for_status() # There is a general error handler, see: requests_http_error_handler + send_email_result = r.json() + + LOGGER.debug(f"Send Email result: {send_email_result}") + return send_email_result diff --git a/server/admin_management/api/app/schemas.py b/server/admin_management/api/app/schemas.py index bb53ff463..24a39c388 100644 --- a/server/admin_management/api/app/schemas.py +++ b/server/admin_management/api/app/schemas.py @@ -1,7 +1,7 @@ import logging from typing import List, Optional, Union -from pydantic import BaseModel, ConfigDict, Field, StringConstraints +from pydantic import BaseModel, ConfigDict, Field, StringConstraints, EmailStr from typing_extensions import Annotated from . import constants as famConstants @@ -316,6 +316,7 @@ class IdimProxyIdirInfo(BaseModel): guid: Optional[Annotated[str, StringConstraints(max_length=32)]] = None firstName: Optional[Annotated[str, StringConstraints(max_length=20)]] = None lastName: Optional[Annotated[str, StringConstraints(max_length=20)]] = None + email: Optional[Annotated[str, StringConstraints(max_length=250)]] = None class IdimProxyBceidInfo(BaseModel): @@ -326,3 +327,15 @@ class IdimProxyBceidInfo(BaseModel): businessLegalName: Optional[Annotated[str, StringConstraints(max_length=60)]] = None firstName: Optional[Annotated[str, StringConstraints(max_length=20)]] = None lastName: Optional[Annotated[str, StringConstraints(max_length=20)]] = None + email: Optional[Annotated[str, StringConstraints(max_length=250)]] = None + + +# ------------------------------------- GC Notify Integraion ---------------------------------------- # +class GCNotifyGrantAccessEmailParam(BaseModel): + send_to_email_address: EmailStr + application_name: Annotated[str, StringConstraints(max_length=100)] + first_name: Annotated[str, StringConstraints(max_length=20)] + last_name: Annotated[str, StringConstraints(max_length=20)] + role_list_string: Annotated[str, StringConstraints(max_length=200)] + application_team_contact_email: Optional[EmailStr] = None + diff --git a/server/admin_management/api/app/services/utils_service.py b/server/admin_management/api/app/services/utils_service.py index e73e08df4..ae8bc22ff 100644 --- a/server/admin_management/api/app/services/utils_service.py +++ b/server/admin_management/api/app/services/utils_service.py @@ -2,20 +2,12 @@ import logging from api.app.constants import AppEnv, ApiInstanceEnv, AwsTargetEnv +from api.config.config import is_on_aws_prod LOGGER = logging.getLogger(__name__) -def get_aws_target_env() -> AwsTargetEnv: - # TARGET_ENV is assigned from gov's AWS platform, does not exist in local (None). - return os.environ.get("TARGET_ENV") - - -def is_on_aws_prod() -> bool: - return get_aws_target_env() == AwsTargetEnv.PROD - - def use_api_instance_by_app_env(app_env: str) -> ApiInstanceEnv: """ FAM PROD environment supports (DEV/TET/PROD) integrated applications. diff --git a/server/admin_management/api/config/config.py b/server/admin_management/api/config/config.py index e9f879126..3779a4670 100644 --- a/server/admin_management/api/config/config.py +++ b/server/admin_management/api/config/config.py @@ -3,7 +3,7 @@ import os import boto3 -from api.app.constants import ApiInstanceEnv +from api.app.constants import ApiInstanceEnv, AwsTargetEnv LOGGER = logging.getLogger(__name__) @@ -23,6 +23,11 @@ def get_env_var(env_var_name): return os.environ.get(env_var_name) +def get_aws_target_env() -> AwsTargetEnv: + # TARGET_ENV is assigned from gov's AWS platform, does not exist in local (None). + return os.environ.get("TARGET_ENV") + + def get_root_path(): root_path = "" @@ -37,6 +42,10 @@ def is_on_aws(): return os.environ.get("DB_SECRET") is not None # This key only presents on aws. +def is_on_aws_prod() -> bool: + return get_aws_target_env() == AwsTargetEnv.PROD + + def get_allow_origins(): allow_origins = [get_env_var("ALLOW_ORIGIN")] if is_on_aws() else ["*"] LOGGER.info(f"allow_origins -- {allow_origins}") @@ -164,7 +173,14 @@ def get_idim_proxy_api_baseurl(api_instance_env: ApiInstanceEnv): return idim_proxy_api_baseurl - def get_idim_proxy_api_key(): idim_proxy_api_key = get_env_var("IDIM_PROXY_API_KEY") return idim_proxy_api_key + + +def get_gc_notify_email_api_key(): + if is_on_aws_prod(): + gc_notify_email_api_key = get_env_var("GC_NOTIFY_EMAIL_API_KEY_LIVE") + else: + gc_notify_email_api_key = get_env_var("GC_NOTIFY_EMAIL_API_KEY_TEAM") + return gc_notify_email_api_key \ No newline at end of file diff --git a/server/admin_management/local-dev.env b/server/admin_management/local-dev.env index d6a5c50db..27f3a7b8c 100644 --- a/server/admin_management/local-dev.env +++ b/server/admin_management/local-dev.env @@ -8,4 +8,6 @@ COGNITO_USER_POOL_ID=ca-central-1_p8X8GdjKW COGNITO_CLIENT_ID=6jfveou69mgford233or30hmta COGNITO_USER_POOL_DOMAIN=dev-fam-user-pool-domain FC_API_TOKEN_TEST=thisisasecret -IDIM_PROXY_API_KEY=thisisasecret \ No newline at end of file +IDIM_PROXY_API_KEY=thisisasecret +GC_NOTIFY_EMAIL_API_KEY_TEAM=thisisasecret +GC_NOTIFY_EMAIL_API_KEY_LIVE=thisisasecret \ No newline at end of file diff --git a/server/admin_management/requirements.txt b/server/admin_management/requirements.txt index 946290139..98badfa30 100644 --- a/server/admin_management/requirements.txt +++ b/server/admin_management/requirements.txt @@ -7,6 +7,7 @@ psycopg2-binary==2.9.9 httpx==0.27.0 pydantic==2.5.3 pydantic_core==2.14.6 +email-validator==2.1.1 requests==2.31.0 cryptography==42.0.7 pyjwt==2.8.0 \ No newline at end of file From fb545886cfd190c3093ee02a51e46127639950a5 Mon Sep 17 00:00:00 2001 From: catherine meng Date: Mon, 12 Aug 2024 15:34:14 -0700 Subject: [PATCH 2/8] fix(1469): simplify gc notify api key, refs: #1469 --- .../api/app/services/utils_service.py | 10 +++++++++- server/admin_management/api/config/config.py | 16 ++-------------- server/admin_management/local-dev.env | 3 +-- 3 files changed, 12 insertions(+), 17 deletions(-) diff --git a/server/admin_management/api/app/services/utils_service.py b/server/admin_management/api/app/services/utils_service.py index ae8bc22ff..e73e08df4 100644 --- a/server/admin_management/api/app/services/utils_service.py +++ b/server/admin_management/api/app/services/utils_service.py @@ -2,12 +2,20 @@ import logging from api.app.constants import AppEnv, ApiInstanceEnv, AwsTargetEnv -from api.config.config import is_on_aws_prod LOGGER = logging.getLogger(__name__) +def get_aws_target_env() -> AwsTargetEnv: + # TARGET_ENV is assigned from gov's AWS platform, does not exist in local (None). + return os.environ.get("TARGET_ENV") + + +def is_on_aws_prod() -> bool: + return get_aws_target_env() == AwsTargetEnv.PROD + + def use_api_instance_by_app_env(app_env: str) -> ApiInstanceEnv: """ FAM PROD environment supports (DEV/TET/PROD) integrated applications. diff --git a/server/admin_management/api/config/config.py b/server/admin_management/api/config/config.py index 3779a4670..18e3b5628 100644 --- a/server/admin_management/api/config/config.py +++ b/server/admin_management/api/config/config.py @@ -3,7 +3,7 @@ import os import boto3 -from api.app.constants import ApiInstanceEnv, AwsTargetEnv +from api.app.constants import ApiInstanceEnv LOGGER = logging.getLogger(__name__) @@ -23,11 +23,6 @@ def get_env_var(env_var_name): return os.environ.get(env_var_name) -def get_aws_target_env() -> AwsTargetEnv: - # TARGET_ENV is assigned from gov's AWS platform, does not exist in local (None). - return os.environ.get("TARGET_ENV") - - def get_root_path(): root_path = "" @@ -42,10 +37,6 @@ def is_on_aws(): return os.environ.get("DB_SECRET") is not None # This key only presents on aws. -def is_on_aws_prod() -> bool: - return get_aws_target_env() == AwsTargetEnv.PROD - - def get_allow_origins(): allow_origins = [get_env_var("ALLOW_ORIGIN")] if is_on_aws() else ["*"] LOGGER.info(f"allow_origins -- {allow_origins}") @@ -179,8 +170,5 @@ def get_idim_proxy_api_key(): def get_gc_notify_email_api_key(): - if is_on_aws_prod(): - gc_notify_email_api_key = get_env_var("GC_NOTIFY_EMAIL_API_KEY_LIVE") - else: - gc_notify_email_api_key = get_env_var("GC_NOTIFY_EMAIL_API_KEY_TEAM") + gc_notify_email_api_key = get_env_var("GC_NOTIFY_EMAIL_API_KEY") return gc_notify_email_api_key \ No newline at end of file diff --git a/server/admin_management/local-dev.env b/server/admin_management/local-dev.env index 27f3a7b8c..8e6860a31 100644 --- a/server/admin_management/local-dev.env +++ b/server/admin_management/local-dev.env @@ -9,5 +9,4 @@ COGNITO_CLIENT_ID=6jfveou69mgford233or30hmta COGNITO_USER_POOL_DOMAIN=dev-fam-user-pool-domain FC_API_TOKEN_TEST=thisisasecret IDIM_PROXY_API_KEY=thisisasecret -GC_NOTIFY_EMAIL_API_KEY_TEAM=thisisasecret -GC_NOTIFY_EMAIL_API_KEY_LIVE=thisisasecret \ No newline at end of file +GC_NOTIFY_EMAIL_API_KEY=thisisasecret \ No newline at end of file From ccae93a1b7eced10df828d7baaf0687854da4bfc Mon Sep 17 00:00:00 2001 From: catherine meng Date: Tue, 13 Aug 2024 13:23:31 -0700 Subject: [PATCH 3/8] feat(1469): enable sending email notification for delegated admin, refs: #1469 --- .../api/app/integration/gc_notify.py | 11 ++--- .../router_access_control_privilege.py | 14 ++++-- server/admin_management/api/app/schemas.py | 2 +- .../access_control_privilege_service.py | 48 +++++++++++++++---- 4 files changed, 57 insertions(+), 18 deletions(-) diff --git a/server/admin_management/api/app/integration/gc_notify.py b/server/admin_management/api/app/integration/gc_notify.py index 65109f6a4..b9e66ce36 100644 --- a/server/admin_management/api/app/integration/gc_notify.py +++ b/server/admin_management/api/app/integration/gc_notify.py @@ -1,7 +1,7 @@ import logging import requests -from api.app.schemas import GCNotifyGrantAccessEmailParam +from api.app.schemas import GCNotifyGrantDelegatedAdminEmailParam from api.config import config LOGGER = logging.getLogger(__name__) @@ -18,9 +18,8 @@ class GCNotifyEmailService: TIMEOUT = (5, 10) # Timeout (connect, read) in seconds. - def __init__(self, email_template_id = GC_NOTIFY_GRANT_DELEGATED_ADMIN_EMAIL_TEMPLATE_ID): + def __init__(self): self.API_KEY = config.get_gc_notify_email_api_key() - self.grant_access_email_template_id = email_template_id self.email_base_url = GC_NOTIFY_EMAIL_BASE_URL self.headers = { "Content-Type": "application/json", @@ -30,13 +29,13 @@ def __init__(self, email_template_id = GC_NOTIFY_GRANT_DELEGATED_ADMIN_EMAIL_TEM self.session = requests.Session() self.session.headers.update(self.headers) - def send_granted_access_email(self, params: GCNotifyGrantAccessEmailParam): + def send_delegated_admin_granted_email(self, params: GCNotifyGrantDelegatedAdminEmailParam): """ - Send grant access email + Send email notification for new delegated admin """ email_params = { "email_address": params.send_to_email_address, - "template_id": self.grant_access_email_template_id, + "template_id": GC_NOTIFY_GRANT_DELEGATED_ADMIN_EMAIL_TEMPLATE_ID, "personalisation": { "first_name": params.first_name, "last_name": params.last_name, diff --git a/server/admin_management/api/app/routers/router_access_control_privilege.py b/server/admin_management/api/app/routers/router_access_control_privilege.py index 9a610c838..f52115817 100644 --- a/server/admin_management/api/app/routers/router_access_control_privilege.py +++ b/server/admin_management/api/app/routers/router_access_control_privilege.py @@ -82,8 +82,10 @@ def create_access_control_privilege_many( ) audit_event_log.application = audit_event_log.role.application - response = access_control_privilege_service.create_access_control_privilege_many( - access_control_privilege_request, requester.cognito_user_id, target_user + response = ( + access_control_privilege_service.create_access_control_privilege_many( + access_control_privilege_request, requester.cognito_user_id, target_user + ) ) # get target user from database, so for existing user, we can get the cognito user id audit_event_log.target_user = user_service.get_user_by_domain_and_guid( @@ -91,7 +93,13 @@ def create_access_control_privilege_many( access_control_privilege_request.user_guid, ) - return response + # Send email notification if required + if request.requires_send_user_email: + access_control_privilege_service.send_email_notification( + target_user, audit_event_log.application.application_name, response + ) + + return response except Exception as e: audit_event_log.event_outcome = AuditEventOutcome.FAIL diff --git a/server/admin_management/api/app/schemas.py b/server/admin_management/api/app/schemas.py index 24a39c388..3e462dc24 100644 --- a/server/admin_management/api/app/schemas.py +++ b/server/admin_management/api/app/schemas.py @@ -331,7 +331,7 @@ class IdimProxyBceidInfo(BaseModel): # ------------------------------------- GC Notify Integraion ---------------------------------------- # -class GCNotifyGrantAccessEmailParam(BaseModel): +class GCNotifyGrantDelegatedAdminEmailParam(BaseModel): send_to_email_address: EmailStr application_name: Annotated[str, StringConstraints(max_length=100)] first_name: Annotated[str, StringConstraints(max_length=20)] diff --git a/server/admin_management/api/app/services/access_control_privilege_service.py b/server/admin_management/api/app/services/access_control_privilege_service.py index 216d1990f..0c2309d48 100644 --- a/server/admin_management/api/app/services/access_control_privilege_service.py +++ b/server/admin_management/api/app/services/access_control_privilege_service.py @@ -4,16 +4,19 @@ from api.app import constants as famConstants from api.app import schemas -from api.app.integration.forest_client_integration import \ - ForestClientIntegrationService -from api.app.repositories.access_control_privilege_repository import \ - AccessControlPrivilegeRepository +from api.app.integration.forest_client_integration import ForestClientIntegrationService +from api.app.integration.gc_notify import GCNotifyEmailService +from api.app.repositories.access_control_privilege_repository import ( + AccessControlPrivilegeRepository, +) from api.app.services import utils_service from api.app.services.role_service import RoleService from api.app.services.user_service import UserService from api.app.services.validator.forest_client_validator import ( - forest_client_active, forest_client_number_exists, - get_forest_client_status) + forest_client_active, + forest_client_number_exists, + get_forest_client_status, +) from api.app.utils import utils from sqlalchemy.orm import Session @@ -100,7 +103,9 @@ def create_access_control_privilege_many( ) forest_client_integration_service = ForestClientIntegrationService( - utils_service.use_api_instance_by_app_env(fam_role.application.app_environment) + utils_service.use_api_instance_by_app_env( + fam_role.application.app_environment + ) ) for forest_client_number in request.forest_client_numbers: # validate the forest client number @@ -138,7 +143,6 @@ def create_access_control_privilege_many( fam_user.user_id, child_role.role_id, requester ) create_return_list.append(handle_create_return) - else: handle_create_return = self.grant_privilege( fam_user.user_id, fam_role.role_id, requester @@ -209,3 +213,31 @@ def grant_privilege( ) return access_control_privilege_return + + def send_email_notification( + self, + target_user: schemas.TargetUser, + application_name: str, + roles_assigned: List[schemas.FamAccessControlPrivilegeCreateResponse], + ): + try: + gc_notify_email_service = GCNotifyEmailService() + email_response = gc_notify_email_service.send_delegated_admin_granted_email( + schemas.GCNotifyGrantDelegatedAdminEmailParam( + **{ + "send_to_email_address": target_user.email, + "application_name": application_name, + "first_name": target_user.first_name, + "lastName": target_user.last_name, + "role_list_string": ", ".join( + item.detail.role.role_name for item in roles_assigned + ), + } + ) + ) + return email_response + except Exception as e: + LOGGER.debug( + f"Failure sending email to the new delegated admin {target_user.email}." + ) + LOGGER.debug(f"Failure reason : {e}.") From ecd075c384bbe1a2fe9c42ada69f618c126b8aa1 Mon Sep 17 00:00:00 2001 From: catherine meng Date: Tue, 13 Aug 2024 14:18:32 -0700 Subject: [PATCH 4/8] fix(1469): fix gc notify parameters and add logs, refs: #1469 --- .../admin_management/api/app/integration/gc_notify.py | 11 ++++++++++- .../app/routers/router_access_control_privilege.py | 2 +- .../app/services/access_control_privilege_service.py | 3 ++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/server/admin_management/api/app/integration/gc_notify.py b/server/admin_management/api/app/integration/gc_notify.py index b9e66ce36..2950f5b88 100644 --- a/server/admin_management/api/app/integration/gc_notify.py +++ b/server/admin_management/api/app/integration/gc_notify.py @@ -11,6 +11,7 @@ GC_NOTIFY_GRANT_APP_ADMIN_EMAIL_TEMPLATE_ID = "230bca59-4906-40b2-8f2b-2f6186a98663" + class GCNotifyEmailService: """ The class is used for sending email @@ -33,6 +34,14 @@ def send_delegated_admin_granted_email(self, params: GCNotifyGrantDelegatedAdmin """ Send email notification for new delegated admin """ + # GC Notify does not have sufficient conditional rendering, cannot send None to variable, and does not support + # 'variable' within coditional text. Easier to do this in code. + contact_message = ( + f"Please contact your administrator {params.application_team_contact_email} if you have any issues accessing the application." + if params.application_team_contact_email is not None + else "Please contact your administrator if you have any issues accessing the application." + ) + email_params = { "email_address": params.send_to_email_address, "template_id": GC_NOTIFY_GRANT_DELEGATED_ADMIN_EMAIL_TEMPLATE_ID, @@ -41,7 +50,7 @@ def send_delegated_admin_granted_email(self, params: GCNotifyGrantDelegatedAdmin "last_name": params.last_name, "application_name": params.application_name, "role_list_string": params.role_list_string, - "application_team_contact_email": params.application_team_contact_email + "contact_message": contact_message }, } gc_notify_email_send_url = f"{self.email_base_url}/v2/notifications/email" diff --git a/server/admin_management/api/app/routers/router_access_control_privilege.py b/server/admin_management/api/app/routers/router_access_control_privilege.py index f52115817..9f996d6bb 100644 --- a/server/admin_management/api/app/routers/router_access_control_privilege.py +++ b/server/admin_management/api/app/routers/router_access_control_privilege.py @@ -94,7 +94,7 @@ def create_access_control_privilege_many( ) # Send email notification if required - if request.requires_send_user_email: + if access_control_privilege_request.requires_send_user_email: access_control_privilege_service.send_email_notification( target_user, audit_event_log.application.application_name, response ) diff --git a/server/admin_management/api/app/services/access_control_privilege_service.py b/server/admin_management/api/app/services/access_control_privilege_service.py index 0c2309d48..4f2750d34 100644 --- a/server/admin_management/api/app/services/access_control_privilege_service.py +++ b/server/admin_management/api/app/services/access_control_privilege_service.py @@ -228,13 +228,14 @@ def send_email_notification( "send_to_email_address": target_user.email, "application_name": application_name, "first_name": target_user.first_name, - "lastName": target_user.last_name, + "last_name": target_user.last_name, "role_list_string": ", ".join( item.detail.role.role_name for item in roles_assigned ), } ) ) + LOGGER.debug(f"Email is sent to {target_user.email}: {email_response}") return email_response except Exception as e: LOGGER.debug( From f2e2229105fd09bfae80bc0260c5f6659901fe8f Mon Sep 17 00:00:00 2001 From: catherine meng Date: Tue, 13 Aug 2024 14:44:01 -0700 Subject: [PATCH 5/8] fix(1469): only send email for successful grants, refs: #1469 --- client-code-gen/admin-management-openapi.json | 2 +- .../model/fam-role-with-client-dto.ts | 9 ++++++++ .../router_access_control_privilege.py | 2 +- server/admin_management/api/app/schemas.py | 1 + .../access_control_privilege_service.py | 21 +++++++++++++------ 5 files changed, 27 insertions(+), 8 deletions(-) diff --git a/client-code-gen/admin-management-openapi.json b/client-code-gen/admin-management-openapi.json index fcf925a83..25bd49b1d 100644 --- a/client-code-gen/admin-management-openapi.json +++ b/client-code-gen/admin-management-openapi.json @@ -1 +1 @@ -{"openapi":"3.0.3","info":{"title":"Forest Access Management - FAM - Admin Management API","description":"\nForest Access Management Admin Management API used by the Forest Access Management application\nto define admin access to forest applications.\n","contact":{"name":"Team Heartwood","url":"https://apps.nrs.gov.bc.ca/int/confluence/display/FSAST1/Team+Heartwood","email":"SIBIFSAF@victoria1.gov.bc.ca"},"license":{"name":"Apache 2.0","url":"https://www.apache.org/licenses/LICENSE-2.0.html"},"version":"0.0.1"},"paths":{"/smoke_test":{"get":{"tags":["Smoke Test"],"summary":"Smoke Test","description":"List of different applications that are administered by FAM","operationId":"smoke_test","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"6jfveou69mgford233or30hmta":[]}]}},"/application_admins":{"get":{"tags":["FAM Application Admin"],"summary":"Get Application Admins","operationId":"get_application_admins","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/FamAppAdminGetResponse"},"type":"array","title":"Response Get Application Admins"}}}}},"security":[{"6jfveou69mgford233or30hmta":[]}]},"post":{"tags":["FAM Application Admin"],"summary":"Create Application Admin","operationId":"create_application_admin","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FamAppAdminCreateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FamAppAdminGetResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"6jfveou69mgford233or30hmta":[]}]}},"/application_admins/{application_admin_id}":{"delete":{"tags":["FAM Application Admin"],"summary":"Delete Application Admin","operationId":"delete_application_admin","security":[{"6jfveou69mgford233or30hmta":[]}],"parameters":[{"name":"application_admin_id","in":"path","required":true,"schema":{"type":"integer","title":"Application Admin Id"}}],"responses":{"200":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/access_control_privileges":{"post":{"tags":["FAM Access Control Privileges"],"summary":"Create Access Control Privilege Many","description":"Grant Delegated Admin Privileges","operationId":"create_access_control_privilege_many","security":[{"6jfveou69mgford233or30hmta":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FamAccessControlPrivilegeCreateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/FamAccessControlPrivilegeCreateResponse"},"title":"Response Create Access Control Privilege Many"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["FAM Access Control Privileges"],"summary":"Get Access Control Privileges By Application Id","description":"Get Delegated Admin Privileges For an Application","operationId":"get_access_control_privileges_by_application_id","security":[{"6jfveou69mgford233or30hmta":[]}],"parameters":[{"name":"application_id","in":"query","required":true,"schema":{"type":"integer","title":"Application Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/FamAccessControlPrivilegeGetResponse"},"title":"Response Get Access Control Privileges By Application Id"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/access_control_privileges/{access_control_privilege_id}":{"delete":{"tags":["FAM Access Control Privileges"],"summary":"Delete Access Control Privilege","operationId":"delete_access_control_privilege","security":[{"6jfveou69mgford233or30hmta":[]}],"parameters":[{"name":"access_control_privilege_id","in":"path","required":true,"schema":{"type":"integer","title":"Access Control Privilege Id"}}],"responses":{"200":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin-user-accesses":{"get":{"tags":["Admin User Accesses"],"summary":"Admin User Access Privilege","description":"Access privilege for logged on admin user for what applications/roles(scoped) the user can grant.","operationId":"Admin user access privilege","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminUserAccessResponse"}}}}},"security":[{"6jfveou69mgford233or30hmta":[]}]}}},"components":{"schemas":{"AdminRoleAuthGroup":{"type":"string","enum":["FAM_ADMIN","APP_ADMIN","DELEGATED_ADMIN"],"title":"AdminRoleAuthGroup","description":"FAM data model does not explicitly have these role group of admins.\nHowever, business rules do differentiate purpose of admins as:\n (FAM_ADMIN, [APP]_ADMIN, DELEGATED_ADMIN)\n# Referencing to FAM confluence for design:\n https://apps.nrs.gov.bc.ca/int/confluence/display/FSAST1/Delegated+Access+Administration+Design (Auth Function)"},"AdminUserAccessResponse":{"properties":{"access":{"items":{"$ref":"#/components/schemas/FamAuthGrantDto"},"type":"array","title":"Access"}},"type":"object","required":["access"],"title":"AdminUserAccessResponse"},"AppEnv":{"type":"string","enum":["DEV","TEST","PROD"],"title":"AppEnv"},"FamAccessControlPrivilegeCreateRequest":{"properties":{"user_name":{"type":"string","maxLength":20,"minLength":3,"title":"User Name"},"user_guid":{"type":"string","maxLength":32,"minLength":32,"title":"User Guid"},"user_type_code":{"$ref":"#/components/schemas/UserType"},"role_id":{"type":"integer","title":"Role Id"},"forest_client_numbers":{"anyOf":[{"items":{"type":"string","maxLength":8,"minLength":1},"type":"array"},{"type":"null"}],"title":"Forest Client Numbers"},"requires_send_user_email":{"type":"boolean","title":"Requires Send User Email","default":false}},"type":"object","required":["user_name","user_guid","user_type_code","role_id"],"title":"FamAccessControlPrivilegeCreateRequest","description":"This is used at router level, the data we receive from frontend.\nUse username and user_type_code to get user_id,\nand for concrete role, can use its role_id directly,\nbut for abstract role, need to create/get child role_id based on the forest client number,\nand then use schema FamAccessControlPrivilegeCreateDto to insert into the database"},"FamAccessControlPrivilegeCreateResponse":{"properties":{"status_code":{"type":"integer","title":"Status Code"},"detail":{"$ref":"#/components/schemas/FamAccessControlPrivilegeGetResponse"},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message"}},"type":"object","required":["status_code","detail"],"title":"FamAccessControlPrivilegeCreateResponse"},"FamAccessControlPrivilegeGetResponse":{"properties":{"access_control_privilege_id":{"type":"integer","title":"Access Control Privilege Id"},"user_id":{"type":"integer","title":"User Id"},"role_id":{"type":"integer","title":"Role Id"},"user":{"$ref":"#/components/schemas/FamUserInfoDto"},"role":{"$ref":"#/components/schemas/FamRoleWithClientDto"}},"type":"object","required":["access_control_privilege_id","user_id","role_id","user","role"],"title":"FamAccessControlPrivilegeGetResponse"},"FamAppAdminCreateRequest":{"properties":{"user_name":{"type":"string","maxLength":20,"minLength":3,"title":"User Name"},"user_guid":{"type":"string","maxLength":32,"minLength":32,"title":"User Guid"},"user_type_code":{"$ref":"#/components/schemas/UserType"},"application_id":{"type":"integer","title":"Application Id"}},"type":"object","required":["user_name","user_guid","user_type_code","application_id"],"title":"FamAppAdminCreateRequest"},"FamAppAdminGetResponse":{"properties":{"application_admin_id":{"type":"integer","title":"Application Admin Id"},"user_id":{"type":"integer","title":"User Id"},"application_id":{"type":"integer","title":"Application Id"},"user":{"$ref":"#/components/schemas/FamUserInfoDto"},"application":{"$ref":"#/components/schemas/FamApplicationBase"}},"type":"object","required":["application_admin_id","user_id","application_id","user","application"],"title":"FamAppAdminGetResponse"},"FamApplicationBase":{"properties":{"application_name":{"type":"string","maxLength":100,"title":"Application Name"},"application_description":{"type":"string","maxLength":200,"title":"Application Description"},"app_environment":{"anyOf":[{"$ref":"#/components/schemas/AppEnv"},{"type":"null"}]}},"type":"object","required":["application_name","application_description"],"title":"FamApplicationBase"},"FamApplicationDto":{"properties":{"id":{"type":"integer","title":"Id"},"name":{"type":"string","maxLength":100,"title":"Name"},"description":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Description"},"env":{"anyOf":[{"$ref":"#/components/schemas/AppEnv"},{"type":"null"}]}},"type":"object","required":["id","name"],"title":"FamApplicationDto"},"FamAuthGrantDto":{"properties":{"auth_key":{"$ref":"#/components/schemas/AdminRoleAuthGroup"},"grants":{"items":{"$ref":"#/components/schemas/FamGrantDetailDto"},"type":"array","title":"Grants"}},"type":"object","required":["auth_key","grants"],"title":"FamAuthGrantDto"},"FamForestClientBase":{"properties":{"forest_client_number":{"type":"string","maxLength":8,"title":"Forest Client Number"}},"type":"object","required":["forest_client_number"],"title":"FamForestClientBase"},"FamGrantDetailDto":{"properties":{"application":{"$ref":"#/components/schemas/FamApplicationDto"},"roles":{"anyOf":[{"items":{"$ref":"#/components/schemas/FamRoleDto"},"type":"array"},{"type":"null"}],"title":"Roles"}},"type":"object","required":["application"],"title":"FamGrantDetailDto"},"FamRoleBase":{"properties":{"role_name":{"type":"string","maxLength":100,"title":"Role Name"},"role_type_code":{"$ref":"#/components/schemas/RoleType"}},"type":"object","required":["role_name","role_type_code"],"title":"FamRoleBase"},"FamRoleDto":{"properties":{"id":{"type":"integer","title":"Id"},"name":{"type":"string","maxLength":100,"title":"Name"},"description":{"type":"string","maxLength":300,"title":"Description"},"type_code":{"$ref":"#/components/schemas/RoleType"},"forest_clients":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Forest Clients"}},"type":"object","required":["id","name","description","type_code"],"title":"FamRoleDto"},"FamRoleWithClientDto":{"properties":{"role_id":{"type":"integer","title":"Role Id"},"role_name":{"type":"string","maxLength":100,"title":"Role Name"},"client_number":{"anyOf":[{"$ref":"#/components/schemas/FamForestClientBase"},{"type":"null"}]},"parent_role":{"anyOf":[{"$ref":"#/components/schemas/FamRoleBase"},{"type":"null"}]}},"type":"object","required":["role_id","role_name"],"title":"FamRoleWithClientDto"},"FamUserInfoDto":{"properties":{"user_name":{"type":"string","maxLength":20,"title":"User Name"},"user_type":{"$ref":"#/components/schemas/FamUserTypeDto"},"first_name":{"anyOf":[{"type":"string","maxLength":50},{"type":"null"}],"title":"First Name"},"last_name":{"anyOf":[{"type":"string","maxLength":50},{"type":"null"}],"title":"Last Name"},"email":{"anyOf":[{"type":"string","maxLength":250},{"type":"null"}],"title":"Email"}},"type":"object","required":["user_name","user_type"],"title":"FamUserInfoDto"},"FamUserTypeDto":{"properties":{"code":{"$ref":"#/components/schemas/UserType"},"description":{"type":"string","maxLength":35,"title":"Description"}},"type":"object","required":["code","description"],"title":"FamUserTypeDto"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"RoleType":{"type":"string","enum":["A","C"],"title":"RoleType"},"UserType":{"type":"string","enum":["I","B"],"title":"UserType"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}},"securitySchemes":{"6jfveou69mgford233or30hmta":{"type":"oauth2","flows":{"authorizationCode":{"scopes":{},"authorizationUrl":"https://dev-fam-user-pool-domain.auth.ca-central-1.amazoncognito.com/authorize","tokenUrl":"https://dev-fam-user-pool-domain.auth.ca-central-1.amazoncognito.com/token"}}}}}} \ No newline at end of file +{"openapi":"3.0.3","info":{"title":"Forest Access Management - FAM - Admin Management API","description":"\nForest Access Management Admin Management API used by the Forest Access Management application\nto define admin access to forest applications.\n","contact":{"name":"Team Heartwood","url":"https://apps.nrs.gov.bc.ca/int/confluence/display/FSAST1/Team+Heartwood","email":"SIBIFSAF@victoria1.gov.bc.ca"},"license":{"name":"Apache 2.0","url":"https://www.apache.org/licenses/LICENSE-2.0.html"},"version":"0.0.1"},"paths":{"/smoke_test":{"get":{"tags":["Smoke Test"],"summary":"Smoke Test","description":"List of different applications that are administered by FAM","operationId":"smoke_test","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"6jfveou69mgford233or30hmta":[]}]}},"/application_admins":{"get":{"tags":["FAM Application Admin"],"summary":"Get Application Admins","operationId":"get_application_admins","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/FamAppAdminGetResponse"},"type":"array","title":"Response Get Application Admins"}}}}},"security":[{"6jfveou69mgford233or30hmta":[]}]},"post":{"tags":["FAM Application Admin"],"summary":"Create Application Admin","operationId":"create_application_admin","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FamAppAdminCreateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FamAppAdminGetResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"6jfveou69mgford233or30hmta":[]}]}},"/application_admins/{application_admin_id}":{"delete":{"tags":["FAM Application Admin"],"summary":"Delete Application Admin","operationId":"delete_application_admin","security":[{"6jfveou69mgford233or30hmta":[]}],"parameters":[{"name":"application_admin_id","in":"path","required":true,"schema":{"type":"integer","title":"Application Admin Id"}}],"responses":{"200":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/access_control_privileges":{"post":{"tags":["FAM Access Control Privileges"],"summary":"Create Access Control Privilege Many","description":"Grant Delegated Admin Privileges","operationId":"create_access_control_privilege_many","security":[{"6jfveou69mgford233or30hmta":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FamAccessControlPrivilegeCreateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/FamAccessControlPrivilegeCreateResponse"},"title":"Response Create Access Control Privilege Many"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["FAM Access Control Privileges"],"summary":"Get Access Control Privileges By Application Id","description":"Get Delegated Admin Privileges For an Application","operationId":"get_access_control_privileges_by_application_id","security":[{"6jfveou69mgford233or30hmta":[]}],"parameters":[{"name":"application_id","in":"query","required":true,"schema":{"type":"integer","title":"Application Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/FamAccessControlPrivilegeGetResponse"},"title":"Response Get Access Control Privileges By Application Id"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/access_control_privileges/{access_control_privilege_id}":{"delete":{"tags":["FAM Access Control Privileges"],"summary":"Delete Access Control Privilege","operationId":"delete_access_control_privilege","security":[{"6jfveou69mgford233or30hmta":[]}],"parameters":[{"name":"access_control_privilege_id","in":"path","required":true,"schema":{"type":"integer","title":"Access Control Privilege Id"}}],"responses":{"200":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin-user-accesses":{"get":{"tags":["Admin User Accesses"],"summary":"Admin User Access Privilege","description":"Access privilege for logged on admin user for what applications/roles(scoped) the user can grant.","operationId":"Admin user access privilege","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminUserAccessResponse"}}}}},"security":[{"6jfveou69mgford233or30hmta":[]}]}}},"components":{"schemas":{"AdminRoleAuthGroup":{"type":"string","enum":["FAM_ADMIN","APP_ADMIN","DELEGATED_ADMIN"],"title":"AdminRoleAuthGroup","description":"FAM data model does not explicitly have these role group of admins.\nHowever, business rules do differentiate purpose of admins as:\n (FAM_ADMIN, [APP]_ADMIN, DELEGATED_ADMIN)\n# Referencing to FAM confluence for design:\n https://apps.nrs.gov.bc.ca/int/confluence/display/FSAST1/Delegated+Access+Administration+Design (Auth Function)"},"AdminUserAccessResponse":{"properties":{"access":{"items":{"$ref":"#/components/schemas/FamAuthGrantDto"},"type":"array","title":"Access"}},"type":"object","required":["access"],"title":"AdminUserAccessResponse"},"AppEnv":{"type":"string","enum":["DEV","TEST","PROD"],"title":"AppEnv"},"FamAccessControlPrivilegeCreateRequest":{"properties":{"user_name":{"type":"string","maxLength":20,"minLength":3,"title":"User Name"},"user_guid":{"type":"string","maxLength":32,"minLength":32,"title":"User Guid"},"user_type_code":{"$ref":"#/components/schemas/UserType"},"role_id":{"type":"integer","title":"Role Id"},"forest_client_numbers":{"anyOf":[{"items":{"type":"string","maxLength":8,"minLength":1},"type":"array"},{"type":"null"}],"title":"Forest Client Numbers"},"requires_send_user_email":{"type":"boolean","title":"Requires Send User Email","default":false}},"type":"object","required":["user_name","user_guid","user_type_code","role_id"],"title":"FamAccessControlPrivilegeCreateRequest","description":"This is used at router level, the data we receive from frontend.\nUse username and user_type_code to get user_id,\nand for concrete role, can use its role_id directly,\nbut for abstract role, need to create/get child role_id based on the forest client number,\nand then use schema FamAccessControlPrivilegeCreateDto to insert into the database"},"FamAccessControlPrivilegeCreateResponse":{"properties":{"status_code":{"type":"integer","title":"Status Code"},"detail":{"$ref":"#/components/schemas/FamAccessControlPrivilegeGetResponse"},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message"}},"type":"object","required":["status_code","detail"],"title":"FamAccessControlPrivilegeCreateResponse"},"FamAccessControlPrivilegeGetResponse":{"properties":{"access_control_privilege_id":{"type":"integer","title":"Access Control Privilege Id"},"user_id":{"type":"integer","title":"User Id"},"role_id":{"type":"integer","title":"Role Id"},"user":{"$ref":"#/components/schemas/FamUserInfoDto"},"role":{"$ref":"#/components/schemas/FamRoleWithClientDto"}},"type":"object","required":["access_control_privilege_id","user_id","role_id","user","role"],"title":"FamAccessControlPrivilegeGetResponse"},"FamAppAdminCreateRequest":{"properties":{"user_name":{"type":"string","maxLength":20,"minLength":3,"title":"User Name"},"user_guid":{"type":"string","maxLength":32,"minLength":32,"title":"User Guid"},"user_type_code":{"$ref":"#/components/schemas/UserType"},"application_id":{"type":"integer","title":"Application Id"}},"type":"object","required":["user_name","user_guid","user_type_code","application_id"],"title":"FamAppAdminCreateRequest"},"FamAppAdminGetResponse":{"properties":{"application_admin_id":{"type":"integer","title":"Application Admin Id"},"user_id":{"type":"integer","title":"User Id"},"application_id":{"type":"integer","title":"Application Id"},"user":{"$ref":"#/components/schemas/FamUserInfoDto"},"application":{"$ref":"#/components/schemas/FamApplicationBase"}},"type":"object","required":["application_admin_id","user_id","application_id","user","application"],"title":"FamAppAdminGetResponse"},"FamApplicationBase":{"properties":{"application_name":{"type":"string","maxLength":100,"title":"Application Name"},"application_description":{"type":"string","maxLength":200,"title":"Application Description"},"app_environment":{"anyOf":[{"$ref":"#/components/schemas/AppEnv"},{"type":"null"}]}},"type":"object","required":["application_name","application_description"],"title":"FamApplicationBase"},"FamApplicationDto":{"properties":{"id":{"type":"integer","title":"Id"},"name":{"type":"string","maxLength":100,"title":"Name"},"description":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Description"},"env":{"anyOf":[{"$ref":"#/components/schemas/AppEnv"},{"type":"null"}]}},"type":"object","required":["id","name"],"title":"FamApplicationDto"},"FamAuthGrantDto":{"properties":{"auth_key":{"$ref":"#/components/schemas/AdminRoleAuthGroup"},"grants":{"items":{"$ref":"#/components/schemas/FamGrantDetailDto"},"type":"array","title":"Grants"}},"type":"object","required":["auth_key","grants"],"title":"FamAuthGrantDto"},"FamForestClientBase":{"properties":{"forest_client_number":{"type":"string","maxLength":8,"title":"Forest Client Number"}},"type":"object","required":["forest_client_number"],"title":"FamForestClientBase"},"FamGrantDetailDto":{"properties":{"application":{"$ref":"#/components/schemas/FamApplicationDto"},"roles":{"anyOf":[{"items":{"$ref":"#/components/schemas/FamRoleDto"},"type":"array"},{"type":"null"}],"title":"Roles"}},"type":"object","required":["application"],"title":"FamGrantDetailDto"},"FamRoleBase":{"properties":{"role_name":{"type":"string","maxLength":100,"title":"Role Name"},"role_type_code":{"$ref":"#/components/schemas/RoleType"}},"type":"object","required":["role_name","role_type_code"],"title":"FamRoleBase"},"FamRoleDto":{"properties":{"id":{"type":"integer","title":"Id"},"name":{"type":"string","maxLength":100,"title":"Name"},"description":{"type":"string","maxLength":300,"title":"Description"},"type_code":{"$ref":"#/components/schemas/RoleType"},"forest_clients":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Forest Clients"}},"type":"object","required":["id","name","description","type_code"],"title":"FamRoleDto"},"FamRoleWithClientDto":{"properties":{"role_id":{"type":"integer","title":"Role Id"},"role_name":{"type":"string","maxLength":100,"title":"Role Name"},"client_number":{"anyOf":[{"$ref":"#/components/schemas/FamForestClientBase"},{"type":"null"}]},"parent_role":{"anyOf":[{"$ref":"#/components/schemas/FamRoleBase"},{"type":"null"}]},"application":{"$ref":"#/components/schemas/FamApplicationBase"}},"type":"object","required":["role_id","role_name","application"],"title":"FamRoleWithClientDto"},"FamUserInfoDto":{"properties":{"user_name":{"type":"string","maxLength":20,"title":"User Name"},"user_type":{"$ref":"#/components/schemas/FamUserTypeDto"},"first_name":{"anyOf":[{"type":"string","maxLength":50},{"type":"null"}],"title":"First Name"},"last_name":{"anyOf":[{"type":"string","maxLength":50},{"type":"null"}],"title":"Last Name"},"email":{"anyOf":[{"type":"string","maxLength":250},{"type":"null"}],"title":"Email"}},"type":"object","required":["user_name","user_type"],"title":"FamUserInfoDto"},"FamUserTypeDto":{"properties":{"code":{"$ref":"#/components/schemas/UserType"},"description":{"type":"string","maxLength":35,"title":"Description"}},"type":"object","required":["code","description"],"title":"FamUserTypeDto"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"RoleType":{"type":"string","enum":["A","C"],"title":"RoleType"},"UserType":{"type":"string","enum":["I","B"],"title":"UserType"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}},"securitySchemes":{"6jfveou69mgford233or30hmta":{"type":"oauth2","flows":{"authorizationCode":{"scopes":{},"authorizationUrl":"https://dev-fam-user-pool-domain.auth.ca-central-1.amazoncognito.com/authorize","tokenUrl":"https://dev-fam-user-pool-domain.auth.ca-central-1.amazoncognito.com/token"}}}}}} \ No newline at end of file diff --git a/client-code-gen/gen/admin-management-api/model/fam-role-with-client-dto.ts b/client-code-gen/gen/admin-management-api/model/fam-role-with-client-dto.ts index b53c9132e..d63384271 100644 --- a/client-code-gen/gen/admin-management-api/model/fam-role-with-client-dto.ts +++ b/client-code-gen/gen/admin-management-api/model/fam-role-with-client-dto.ts @@ -13,6 +13,9 @@ */ +// May contain unused imports in some cases +// @ts-ignore +import { FamApplicationBase } from './fam-application-base'; // May contain unused imports in some cases // @ts-ignore import { FamForestClientBase } from './fam-forest-client-base'; @@ -50,5 +53,11 @@ export interface FamRoleWithClientDto { * @memberof FamRoleWithClientDto */ 'parent_role'?: FamRoleBase | null; + /** + * + * @type {FamApplicationBase} + * @memberof FamRoleWithClientDto + */ + 'application': FamApplicationBase; } diff --git a/server/admin_management/api/app/routers/router_access_control_privilege.py b/server/admin_management/api/app/routers/router_access_control_privilege.py index 9f996d6bb..fca7ba146 100644 --- a/server/admin_management/api/app/routers/router_access_control_privilege.py +++ b/server/admin_management/api/app/routers/router_access_control_privilege.py @@ -96,7 +96,7 @@ def create_access_control_privilege_many( # Send email notification if required if access_control_privilege_request.requires_send_user_email: access_control_privilege_service.send_email_notification( - target_user, audit_event_log.application.application_name, response + target_user, response ) return response diff --git a/server/admin_management/api/app/schemas.py b/server/admin_management/api/app/schemas.py index 3e462dc24..1adac4f95 100644 --- a/server/admin_management/api/app/schemas.py +++ b/server/admin_management/api/app/schemas.py @@ -160,6 +160,7 @@ class FamRoleWithClientDto(BaseModel): role_name: Annotated[str, StringConstraints(max_length=100)] client_number: Optional[FamForestClientBase] = None parent_role: Optional[FamRoleBase] = None + application: FamApplicationBase model_config = ConfigDict(from_attributes=True) diff --git a/server/admin_management/api/app/services/access_control_privilege_service.py b/server/admin_management/api/app/services/access_control_privilege_service.py index 4f2750d34..b0b8038d8 100644 --- a/server/admin_management/api/app/services/access_control_privilege_service.py +++ b/server/admin_management/api/app/services/access_control_privilege_service.py @@ -217,21 +217,30 @@ def grant_privilege( def send_email_notification( self, target_user: schemas.TargetUser, - application_name: str, - roles_assigned: List[schemas.FamAccessControlPrivilegeCreateResponse], + access_control_priviliege_response: List[ + schemas.FamAccessControlPrivilegeCreateResponse + ], ): try: + granted_roles = ", ".join( + item.detail.role.role_name + for item in filter( + lambda res: res.status_code == HTTPStatus.OK, + access_control_priviliege_response, + ) + ) + gc_notify_email_service = GCNotifyEmailService() email_response = gc_notify_email_service.send_delegated_admin_granted_email( schemas.GCNotifyGrantDelegatedAdminEmailParam( **{ "send_to_email_address": target_user.email, - "application_name": application_name, + "application_name": access_control_priviliege_response[ + 0 + ].detail.role.application.application_description, "first_name": target_user.first_name, "last_name": target_user.last_name, - "role_list_string": ", ".join( - item.detail.role.role_name for item in roles_assigned - ), + "role_list_string": granted_roles, } ) ) From 9f4a86cdb09268cb58998990676cdc3589bf8434 Mon Sep 17 00:00:00 2001 From: catherine meng Date: Tue, 13 Aug 2024 14:46:25 -0700 Subject: [PATCH 6/8] fix(1469): add gc notify key to admin management terraform config, refs: #1469 --- infrastructure/server/fam_admin_management_api.tf | 1 + 1 file changed, 1 insertion(+) diff --git a/infrastructure/server/fam_admin_management_api.tf b/infrastructure/server/fam_admin_management_api.tf index a1ebe2e70..411089d40 100644 --- a/infrastructure/server/fam_admin_management_api.tf +++ b/infrastructure/server/fam_admin_management_api.tf @@ -136,6 +136,7 @@ resource "aws_lambda_function" "fam_admin_management_api_function" { IDIM_PROXY_BASE_URL_PROD = "${var.idim_proxy_api_base_url_prod}" IDIM_PROXY_API_KEY = "${var.idim_proxy_api_api_key}" + GC_NOTIFY_EMAIL_API_KEY = "${var.gc_notify_email_api_key}" TARGET_ENV = "${var.target_env}" } From bff7e412324138cdec54954fc4e701e2bf71720d Mon Sep 17 00:00:00 2001 From: catherine meng Date: Tue, 13 Aug 2024 16:06:26 -0700 Subject: [PATCH 7/8] fix(1469): update email message, refs: #1469 --- server/admin_management/api/app/integration/gc_notify.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/server/admin_management/api/app/integration/gc_notify.py b/server/admin_management/api/app/integration/gc_notify.py index 2950f5b88..5aba9b307 100644 --- a/server/admin_management/api/app/integration/gc_notify.py +++ b/server/admin_management/api/app/integration/gc_notify.py @@ -11,7 +11,6 @@ GC_NOTIFY_GRANT_APP_ADMIN_EMAIL_TEMPLATE_ID = "230bca59-4906-40b2-8f2b-2f6186a98663" - class GCNotifyEmailService: """ The class is used for sending email @@ -37,9 +36,9 @@ def send_delegated_admin_granted_email(self, params: GCNotifyGrantDelegatedAdmin # GC Notify does not have sufficient conditional rendering, cannot send None to variable, and does not support # 'variable' within coditional text. Easier to do this in code. contact_message = ( - f"Please contact your administrator {params.application_team_contact_email} if you have any issues accessing the application." + f"Please contact your administrator {params.application_team_contact_email} if you have any questions." if params.application_team_contact_email is not None - else "Please contact your administrator if you have any issues accessing the application." + else "Please contact your administrator if you have any questions." ) email_params = { From 172ae33c1963606a380d1d05bdeb71083d78fb73 Mon Sep 17 00:00:00 2001 From: catherine meng Date: Wed, 14 Aug 2024 10:18:19 -0700 Subject: [PATCH 8/8] fix(1469): handle the situation when all roles failed to grant, no email should send, refs: #1469 --- server/admin_management/api/app/integration/gc_notify.py | 1 + .../api/app/services/access_control_privilege_service.py | 3 +++ 2 files changed, 4 insertions(+) diff --git a/server/admin_management/api/app/integration/gc_notify.py b/server/admin_management/api/app/integration/gc_notify.py index 5aba9b307..35b5e7e10 100644 --- a/server/admin_management/api/app/integration/gc_notify.py +++ b/server/admin_management/api/app/integration/gc_notify.py @@ -8,6 +8,7 @@ GC_NOTIFY_EMAIL_BASE_URL = "https://api.notification.canada.ca" GC_NOTIFY_GRANT_DELEGATED_ADMIN_EMAIL_TEMPLATE_ID = "9abff613-e507-4562-aae0-008317dfe3b9" +# Template id for granting application admin, we will use this later GC_NOTIFY_GRANT_APP_ADMIN_EMAIL_TEMPLATE_ID = "230bca59-4906-40b2-8f2b-2f6186a98663" diff --git a/server/admin_management/api/app/services/access_control_privilege_service.py b/server/admin_management/api/app/services/access_control_privilege_service.py index b0b8038d8..d7f7028bc 100644 --- a/server/admin_management/api/app/services/access_control_privilege_service.py +++ b/server/admin_management/api/app/services/access_control_privilege_service.py @@ -230,6 +230,9 @@ def send_email_notification( ) ) + if granted_roles == "": # no role is granted + return + gc_notify_email_service = GCNotifyEmailService() email_response = gc_notify_email_service.send_delegated_admin_granted_email( schemas.GCNotifyGrantDelegatedAdminEmailParam(