From 29913ba1c99756b6238ca397d27c01fa793254fd Mon Sep 17 00:00:00 2001 From: Chrtorres Date: Wed, 30 Apr 2025 09:13:16 -0400 Subject: [PATCH 01/10] Added date_approved and approved_by_id to user model --- .../xfd_django/xfd_api/api_methods/user.py | 20 ++++++++++++++++++- .../xfd_django/xfd_api/schema_models/user.py | 2 ++ backend/src/xfd_django/xfd_mini_dl/models.py | 14 +++++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/backend/src/xfd_django/xfd_api/api_methods/user.py b/backend/src/xfd_django/xfd_api/api_methods/user.py index 1ff7620be..820426737 100644 --- a/backend/src/xfd_django/xfd_api/api_methods/user.py +++ b/backend/src/xfd_django/xfd_api/api_methods/user.py @@ -190,7 +190,23 @@ def get_users(current_user): raise HTTPException(status_code=401, detail="Unauthorized") users = User.objects.all().prefetch_related("roles__organization") - + print("Inside user.py: ") + for user in users: + print("id:", str(user.id)) + print("created_at:", user.created_at.isoformat()) + print("updated_at:", user.updated_at.isoformat()) + print("first_name:", user.first_name) + print("last_name:", user.last_name) + print("full_name:", user.full_name) + print("email:", user.email) + print("region_id:", user.region_id) + print("state:", user.state) + print("user_type:", user.user_type) + print("last_logged_in:", user.last_logged_in) + print("date_approved:", user.date_approved) + print("approved_by_id:", user.approved_by_id.id) + print("accepted_terms_version:", user.accepted_terms_version) + print("date_accepted_terms:", user.date_accepted_terms) # Return the updated user details return [ { @@ -205,6 +221,8 @@ def get_users(current_user): "state": user.state, "user_type": user.user_type, "last_logged_in": user.last_logged_in, + "date_approved": user.date_approved, + "approved_by_id": user.approved_by_id.id, "accepted_terms_version": user.accepted_terms_version, "date_accepted_terms": user.date_accepted_terms, "roles": [ diff --git a/backend/src/xfd_django/xfd_api/schema_models/user.py b/backend/src/xfd_django/xfd_api/schema_models/user.py index 0e9f6e3f9..546b256dc 100644 --- a/backend/src/xfd_django/xfd_api/schema_models/user.py +++ b/backend/src/xfd_django/xfd_api/schema_models/user.py @@ -186,6 +186,8 @@ class UserResponseV2(BaseModel): last_name: str full_name: str email: str + date_approved: Optional[datetime] = None + approved_by_id: Optional[UUID] = None accepted_terms_version: Optional[str] = None date_accepted_terms: Optional[datetime] = None last_logged_in: Optional[datetime] = None diff --git a/backend/src/xfd_django/xfd_mini_dl/models.py b/backend/src/xfd_django/xfd_mini_dl/models.py index 717990d78..2395858f9 100644 --- a/backend/src/xfd_django/xfd_mini_dl/models.py +++ b/backend/src/xfd_django/xfd_mini_dl/models.py @@ -1269,6 +1269,20 @@ class User(AutoLengthCheckModel): default=False, help_text="A boolean field flagging if the user's invite is pending.", ) + date_approved = models.DateTimeField( + db_column="date_approved", + blank=True, + null=True, + help_text="Date the user was approved to have access to the cyhy dashboard.", + ) + approved_by_id = models.UUIDField( + "User", + models.DO_NOTHING, + db_column="approved_by_id", + blank=True, + null=True, + help_text="Foreign key to the user who approved the user.", + ) login_blocked_by_maintenance = models.BooleanField( db_column="login_blocked_by_maintenance", default=False, From 1ece78df80aec24fe228cb3ca53a323a8efda9b5 Mon Sep 17 00:00:00 2001 From: Chrtorres Date: Wed, 30 Apr 2025 14:31:54 -0400 Subject: [PATCH 02/10] Added approved_by_id and date_approved frontend user types --- .../xfd_django/xfd_api/api_methods/user.py | 7 +++ backend/src/xfd_django/xfd_mini_dl/models.py | 62 +------------------ .../src/pages/RegionUsers/RegionUsers.tsx | 6 ++ frontend/src/pages/Users/UserForm.tsx | 2 + frontend/src/pages/Users/Users.tsx | 22 +++++++ 5 files changed, 38 insertions(+), 61 deletions(-) diff --git a/backend/src/xfd_django/xfd_api/api_methods/user.py b/backend/src/xfd_django/xfd_api/api_methods/user.py index 820426737..b7c4cfb7e 100644 --- a/backend/src/xfd_django/xfd_api/api_methods/user.py +++ b/backend/src/xfd_django/xfd_api/api_methods/user.py @@ -204,6 +204,13 @@ def get_users(current_user): print("user_type:", user.user_type) print("last_logged_in:", user.last_logged_in) print("date_approved:", user.date_approved) + if user.approved_by_id: + # Check if approved_by_id is not None before accessing its id + # This prevents AttributeError if approved_by_id is None + print("approved_by_id:", user.approved_by_id.id) + else: + # Handle the case where approved_by_id is None + print("approved_by_id: None") print("approved_by_id:", user.approved_by_id.id) print("accepted_terms_version:", user.accepted_terms_version) print("date_accepted_terms:", user.date_accepted_terms) diff --git a/backend/src/xfd_django/xfd_mini_dl/models.py b/backend/src/xfd_django/xfd_mini_dl/models.py index 2395858f9..6623dc62c 100644 --- a/backend/src/xfd_django/xfd_mini_dl/models.py +++ b/backend/src/xfd_django/xfd_mini_dl/models.py @@ -1275,7 +1275,7 @@ class User(AutoLengthCheckModel): null=True, help_text="Date the user was approved to have access to the cyhy dashboard.", ) - approved_by_id = models.UUIDField( + approved_by_id = models.ForeignKey( "User", models.DO_NOTHING, db_column="approved_by_id", @@ -2515,66 +2515,6 @@ class Meta: unique_together = ["id"] -class CisaKevCatalog(models.Model): - """Define CISA KEV Catalog model.""" - - cisa_kev_uid = models.UUIDField( - primary_key=True, - default=uuid.uuid4, - help_text="PK: Unique identifier for each KEV record.", - ) - cve_id = models.CharField( - max_length=50, - db_index=True, - help_text="CVE ID of the known exploited vulnerability.", - ) - vendor_project = models.CharField( - max_length=255, - help_text="Name of the vendor or project associated with the vulnerability.", - ) - product = models.CharField( - max_length=255, help_text="Name of the affected product." - ) - vulnerability_name = models.CharField( - max_length=255, help_text="Name or brief title of the vulnerability." - ) - date_added = models.DateField( - help_text="Date the vulnerability was added to the KEV catalog." - ) - short_description = models.TextField( - help_text="Brief description of the vulnerability." - ) - required_action = models.TextField( - help_text="Recommended remediation or mitigation action." - ) - due_date = models.DateField( - null=True, blank=True, help_text="Due date for action if provided." - ) - notes = models.TextField( - null=True, blank=True, help_text="Additional notes or remarks." - ) - cwe_id = models.CharField( - max_length=50, - null=True, - blank=True, - help_text="Common Weakness Enumeration (CWE) ID related to the CVE.", - ) - known_ransomware_campaign_use = models.BooleanField( - default=False, - help_text="Flag indicating if the CVE is known to be used in ransomware campaigns.", - ) - vulnerability_publish_date = models.DateField( - null=True, blank=True, help_text="Original publish date of the vulnerability." - ) - - class Meta: - """Set model metadata for Mini Data Lake.""" - - app_label = app_label_name - managed = manage_db - db_table = "cisa_kev_catalog" - - class PortScan(AutoLengthCheckModel): """The PortScan model.""" diff --git a/frontend/src/pages/RegionUsers/RegionUsers.tsx b/frontend/src/pages/RegionUsers/RegionUsers.tsx index 69793cc0a..089d637cd 100644 --- a/frontend/src/pages/RegionUsers/RegionUsers.tsx +++ b/frontend/src/pages/RegionUsers/RegionUsers.tsx @@ -86,6 +86,12 @@ export const RegionUsers: React.FC = () => { ); } + }, + { + field: 'cognito_use_case_description', + headerName: 'Use Case', + minWidth: 100, + flex: 1.5 } ]; const memberCols: GridColDef[] = [ diff --git a/frontend/src/pages/Users/UserForm.tsx b/frontend/src/pages/Users/UserForm.tsx index 678aa0399..c56ea1b31 100644 --- a/frontend/src/pages/Users/UserForm.tsx +++ b/frontend/src/pages/Users/UserForm.tsx @@ -42,6 +42,8 @@ interface UserType extends User { dateToUSigned?: string | null | undefined; orgs?: string | null | undefined; full_name: string; + date_approved?: string | null | undefined; + approved_by_id?: string | null | undefined; } type CloseReason = 'backdropClick' | 'escapeKeyDown' | 'closeButtonClick'; diff --git a/frontend/src/pages/Users/Users.tsx b/frontend/src/pages/Users/Users.tsx index a371a9cd5..95559a1bc 100644 --- a/frontend/src/pages/Users/Users.tsx +++ b/frontend/src/pages/Users/Users.tsx @@ -48,6 +48,8 @@ interface UserType extends User { dateToUSigned?: string | null | undefined; orgs?: string | null | undefined; full_name: string; + approved_by_id?: string | null | undefined; + date_approved?: string | null | undefined; } export const Users: React.FC = () => { @@ -116,6 +118,26 @@ export const Users: React.FC = () => { flex: 1 }, { field: 'user_type', headerName: 'User Type', minWidth: 100, flex: 0.75 }, + { + field: 'date_approved', + headerName: 'Approval Date', + minWidth: 100, + flex: 1, + sortComparator: (v1, v2) => { + if (v1 === 'None') return -1; + if (v2 === 'None') return 1; + + const date1 = new Date(v1); + const date2 = new Date(v2); + return date1.getTime() - date2.getTime(); + } + }, + { + field: 'approved_by_id', + headerName: 'Approved By', + minWidth: 100, + flex: 0.75 + }, { field: 'dateToUSigned', headerName: 'Date ToU Signed', From 0e01844d1a424cf871025b575c09d24213403442 Mon Sep 17 00:00:00 2001 From: Chrtorres Date: Wed, 30 Apr 2025 15:25:20 -0400 Subject: [PATCH 03/10] Added approved_by_id and date approved to User Response model; Added field to User Registration for Use case --- .../xfd_django/xfd_api/api_methods/user.py | 43 ++++++++----------- .../xfd_django/xfd_api/schema_models/user.py | 2 + .../src/pages/RegionUsers/RegionUsers.tsx | 31 ++++++++++--- 3 files changed, 44 insertions(+), 32 deletions(-) diff --git a/backend/src/xfd_django/xfd_api/api_methods/user.py b/backend/src/xfd_django/xfd_api/api_methods/user.py index b7c4cfb7e..b2f583832 100644 --- a/backend/src/xfd_django/xfd_api/api_methods/user.py +++ b/backend/src/xfd_django/xfd_api/api_methods/user.py @@ -190,30 +190,23 @@ def get_users(current_user): raise HTTPException(status_code=401, detail="Unauthorized") users = User.objects.all().prefetch_related("roles__organization") - print("Inside user.py: ") - for user in users: - print("id:", str(user.id)) - print("created_at:", user.created_at.isoformat()) - print("updated_at:", user.updated_at.isoformat()) - print("first_name:", user.first_name) - print("last_name:", user.last_name) - print("full_name:", user.full_name) - print("email:", user.email) - print("region_id:", user.region_id) - print("state:", user.state) - print("user_type:", user.user_type) - print("last_logged_in:", user.last_logged_in) - print("date_approved:", user.date_approved) - if user.approved_by_id: - # Check if approved_by_id is not None before accessing its id - # This prevents AttributeError if approved_by_id is None - print("approved_by_id:", user.approved_by_id.id) - else: - # Handle the case where approved_by_id is None - print("approved_by_id: None") - print("approved_by_id:", user.approved_by_id.id) - print("accepted_terms_version:", user.accepted_terms_version) - print("date_accepted_terms:", user.date_accepted_terms) + # print("Inside user.py: ") + # for user in users: + # print("id:", str(user.id)) + # print("created_at:", user.created_at.isoformat()) + # print("updated_at:", user.updated_at.isoformat()) + # print("first_name:", user.first_name) + # print("last_name:", user.last_name) + # print("full_name:", user.full_name) + # print("email:", user.email) + # print("region_id:", user.region_id) + # print("state:", user.state) + # print("user_type:", user.user_type) + # print("last_logged_in:", user.last_logged_in) + # print("date_approved:", user.date_approved) + # print("approved_by_id:", user.approved_by_id) + # print("accepted_terms_version:", user.accepted_terms_version) + # print("date_accepted_terms:", user.date_accepted_terms) # Return the updated user details return [ { @@ -229,7 +222,7 @@ def get_users(current_user): "user_type": user.user_type, "last_logged_in": user.last_logged_in, "date_approved": user.date_approved, - "approved_by_id": user.approved_by_id.id, + "approved_by_id": user.approved_by_id, "accepted_terms_version": user.accepted_terms_version, "date_accepted_terms": user.date_accepted_terms, "roles": [ diff --git a/backend/src/xfd_django/xfd_api/schema_models/user.py b/backend/src/xfd_django/xfd_api/schema_models/user.py index 546b256dc..47077369d 100644 --- a/backend/src/xfd_django/xfd_api/schema_models/user.py +++ b/backend/src/xfd_django/xfd_api/schema_models/user.py @@ -37,6 +37,8 @@ class User(BaseModel): email: str invite_pending: bool login_blocked_by_maintenance: bool + date_approved: Optional[datetime] = None + approved_by_id: Optional[UUID] = None date_accepted_terms: Optional[datetime] accepted_terms_version: Optional[str] last_logged_in: Optional[datetime] diff --git a/frontend/src/pages/RegionUsers/RegionUsers.tsx b/frontend/src/pages/RegionUsers/RegionUsers.tsx index 089d637cd..188243aa6 100644 --- a/frontend/src/pages/RegionUsers/RegionUsers.tsx +++ b/frontend/src/pages/RegionUsers/RegionUsers.tsx @@ -296,13 +296,30 @@ export const RegionUsers: React.FC = () => { }; const handleApproveClick = (row: typeof initializeUser) => { - selectOrg([]); - setDialogStates({ - ...dialogStates, - isOrgDialogOpen: true - }); - selectUser(row); - fetchOrganizations(row); + try { + let currentUser = ''; + selectOrg([]); + setDialogStates({ + ...dialogStates, + isOrgDialogOpen: true + }); + selectUser(row); + fetchOrganizations(row); + } catch (e: any) { + setErrorStates({ ...errorStates, getOrgsError: e.message }); + } finally { + // Make API call to get current user approving a new user request + apiGet('/users/me') + .then((currentUser) => { + console.log(currentUser); + console.log('ID', currentUser.id); + console.log('Name', currentUser.full_name); + console.log('Email', currentUser.email); + }) + .catch((e: any) => { + setErrorStates({ ...errorStates, getOrgsError: e.message }); + }); + } }; const handleDenyClick = (row: typeof initializeUser) => { From 34a8bf82b4b685a96b6a3674f410ef6509abf9ee Mon Sep 17 00:00:00 2001 From: Chrtorres Date: Thu, 1 May 2025 12:47:34 -0400 Subject: [PATCH 04/10] Added logic to RegionUsers.tsx to handle logging of user approval and date --- .../xfd_django/xfd_api/schema_models/user.py | 2 ++ .../src/pages/RegionUsers/RegionUsers.tsx | 36 +++++++++++++------ 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/backend/src/xfd_django/xfd_api/schema_models/user.py b/backend/src/xfd_django/xfd_api/schema_models/user.py index 47077369d..27c898906 100644 --- a/backend/src/xfd_django/xfd_api/schema_models/user.py +++ b/backend/src/xfd_django/xfd_api/schema_models/user.py @@ -163,6 +163,8 @@ class UpdateUserV2(BaseModel): state: Optional[str] = None user_type: Optional[str] = None invite_pending: Optional[bool] = None + date_approved: Optional[datetime] = None + approved_by_id: Optional[UUID] = None class RegisterUserResponse(BaseModel): diff --git a/frontend/src/pages/RegionUsers/RegionUsers.tsx b/frontend/src/pages/RegionUsers/RegionUsers.tsx index 188243aa6..d11ca2d4a 100644 --- a/frontend/src/pages/RegionUsers/RegionUsers.tsx +++ b/frontend/src/pages/RegionUsers/RegionUsers.tsx @@ -52,6 +52,29 @@ export const RegionUsers: React.FC = () => { const { formattedUserType } = useUserLevel(); const getOrgsURL = `/organizations/region_id/`; const getUsersURL = `/v2/users?invite_pending=`; + const logUserApproval = async (approvedUser: any) => { + await apiGet('/users/me') + .then((currentUser) => { + const body = { + first_name: approvedUser.first_name, + last_name: approvedUser.last_name, + email: approvedUser.email, + user_type: approvedUser.user_type, + state: approvedUser.state, + region_id: approvedUser.region_id, + date_approved: new Date().toISOString(), + approved_by_id: currentUser.id + }; + apiPut(`/v2/users/${approvedUser.id}`, { body }); + console.log(currentUser); + console.log('ID', currentUser.id); + console.log('Name', currentUser.full_name); + console.log('Email', currentUser.email); + }) + .catch((e: any) => { + setErrorStates({ ...errorStates, getOrgsError: e.message }); + }); + }; const pendingCols: GridColDef[] = [ { field: 'full_name', headerName: 'Name', minWidth: 100, flex: 1 }, { field: 'email', headerName: 'Email', minWidth: 100, flex: 2 }, @@ -297,28 +320,19 @@ export const RegionUsers: React.FC = () => { const handleApproveClick = (row: typeof initializeUser) => { try { - let currentUser = ''; selectOrg([]); setDialogStates({ ...dialogStates, isOrgDialogOpen: true }); selectUser(row); + console.log('Selected User', row); fetchOrganizations(row); } catch (e: any) { setErrorStates({ ...errorStates, getOrgsError: e.message }); } finally { // Make API call to get current user approving a new user request - apiGet('/users/me') - .then((currentUser) => { - console.log(currentUser); - console.log('ID', currentUser.id); - console.log('Name', currentUser.full_name); - console.log('Email', currentUser.email); - }) - .catch((e: any) => { - setErrorStates({ ...errorStates, getOrgsError: e.message }); - }); + logUserApproval(row); } }; From 8b1ad64d5efb1803c89d3cba092eaa25dfa7db9d Mon Sep 17 00:00:00 2001 From: Chrtorres Date: Fri, 2 May 2025 13:05:50 -0400 Subject: [PATCH 05/10] Added approved_by_id and date approved to /vs\2/user/user_id --- .../xfd_django/xfd_api/api_methods/user.py | 19 +++++++++++++++++++ .../src/pages/RegionUsers/RegionUsers.tsx | 3 +++ 2 files changed, 22 insertions(+) diff --git a/backend/src/xfd_django/xfd_api/api_methods/user.py b/backend/src/xfd_django/xfd_api/api_methods/user.py index b2f583832..aab568954 100644 --- a/backend/src/xfd_django/xfd_api/api_methods/user.py +++ b/backend/src/xfd_django/xfd_api/api_methods/user.py @@ -427,6 +427,8 @@ def get_users_v2(state, region_id, invite_pending, current_user): def update_user_v2(user_id, user_data, current_user): """Update a particular user.""" try: + + # Validate that the user ID is a valid UUID if not user_id or not is_valid_uuid(user_id): raise HTTPException(status_code=404, detail="User not found") @@ -465,6 +467,21 @@ def update_user_v2(user_id, user_data, current_user): updated_user = User.objects.prefetch_related("roles__organization").get( id=user_id ) + print("Full updated user: ", vars(updated_user)) + print("Full user data: ", vars(user_data)) + print("Full current user: ", vars(current_user)) + print("Full user id: ", user_id) + + if user_data.approved_by_id: + try: + approver = User.objects.get(id=user_data.approved_by_id.id) + user.approved_by_id = approver + print("Approver found: {}".format(approver)) + except User.DoesNotExist: + print("Approver not found: {}".format(e)) + raise HTTPException( + status_code=500, detail="An unexpected error occurred." + ) # Return the updated user details return { @@ -479,6 +496,8 @@ def update_user_v2(user_id, user_data, current_user): "state": updated_user.state, "user_type": updated_user.user_type, "last_logged_in": user.last_logged_in, + "date_approved": updated_user.date_approved, + "approved_by_id": approver, "accepted_terms_version": user.accepted_terms_version, "roles": [ { diff --git a/frontend/src/pages/RegionUsers/RegionUsers.tsx b/frontend/src/pages/RegionUsers/RegionUsers.tsx index d11ca2d4a..550787428 100644 --- a/frontend/src/pages/RegionUsers/RegionUsers.tsx +++ b/frontend/src/pages/RegionUsers/RegionUsers.tsx @@ -52,6 +52,7 @@ export const RegionUsers: React.FC = () => { const { formattedUserType } = useUserLevel(); const getOrgsURL = `/organizations/region_id/`; const getUsersURL = `/v2/users?invite_pending=`; + const logUserApproval = async (approvedUser: any) => { await apiGet('/users/me') .then((currentUser) => { @@ -65,6 +66,8 @@ export const RegionUsers: React.FC = () => { date_approved: new Date().toISOString(), approved_by_id: currentUser.id }; + + console.log('Body inside RegionUsers', body); apiPut(`/v2/users/${approvedUser.id}`, { body }); console.log(currentUser); console.log('ID', currentUser.id); From 67f2087cd9f3e15a1ca88021d29a11ac1b244d6a Mon Sep 17 00:00:00 2001 From: Chrtorres Date: Mon, 5 May 2025 13:03:55 -0400 Subject: [PATCH 06/10] Added logic to /users/{user_id}/register/approve to record the date a user is approved as well as the approver; Updated user schema and models to record approved_by field --- .../xfd_django/xfd_api/api_methods/user.py | 35 ++++++-------- .../xfd_django/xfd_api/schema_models/user.py | 4 +- backend/src/xfd_django/xfd_mini_dl/models.py | 2 +- .../src/pages/RegionUsers/RegionUsers.tsx | 47 +++---------------- frontend/src/pages/Users/Users.tsx | 4 +- 5 files changed, 26 insertions(+), 66 deletions(-) diff --git a/backend/src/xfd_django/xfd_api/api_methods/user.py b/backend/src/xfd_django/xfd_api/api_methods/user.py index aab568954..1cfb434fc 100644 --- a/backend/src/xfd_django/xfd_api/api_methods/user.py +++ b/backend/src/xfd_django/xfd_api/api_methods/user.py @@ -222,7 +222,7 @@ def get_users(current_user): "user_type": user.user_type, "last_logged_in": user.last_logged_in, "date_approved": user.date_approved, - "approved_by_id": user.approved_by_id, + "approved_by_id": user.approved_by.full_name, "accepted_terms_version": user.accepted_terms_version, "date_accepted_terms": user.date_accepted_terms, "roles": [ @@ -427,8 +427,6 @@ def get_users_v2(state, region_id, invite_pending, current_user): def update_user_v2(user_id, user_data, current_user): """Update a particular user.""" try: - - # Validate that the user ID is a valid UUID if not user_id or not is_valid_uuid(user_id): raise HTTPException(status_code=404, detail="User not found") @@ -467,21 +465,10 @@ def update_user_v2(user_id, user_data, current_user): updated_user = User.objects.prefetch_related("roles__organization").get( id=user_id ) - print("Full updated user: ", vars(updated_user)) - print("Full user data: ", vars(user_data)) - print("Full current user: ", vars(current_user)) - print("Full user id: ", user_id) - - if user_data.approved_by_id: - try: - approver = User.objects.get(id=user_data.approved_by_id.id) - user.approved_by_id = approver - print("Approver found: {}".format(approver)) - except User.DoesNotExist: - print("Approver not found: {}".format(e)) - raise HTTPException( - status_code=500, detail="An unexpected error occurred." - ) + # print("Full updated user: ", vars(updated_user)) + # print("Full user data: ", vars(user_data)) + # print("Full current user: ", vars(current_user)) + # print("Full user id: ", user_id) # Return the updated user details return { @@ -496,8 +483,6 @@ def update_user_v2(user_id, user_data, current_user): "state": updated_user.state, "user_type": updated_user.user_type, "last_logged_in": user.last_logged_in, - "date_approved": updated_user.date_approved, - "approved_by_id": approver, "accepted_terms_version": user.accepted_terms_version, "roles": [ { @@ -530,6 +515,11 @@ def approve_user_registration(user_id, current_user): try: # Retrieve the user by ID user = User.objects.get(id=user_id) + print("User found: ", vars(user)) + print("Current user: ", vars(current_user)) + user.date_approved = datetime.now() + user.approved_by = current_user + user.save() except ObjectDoesNotExist: raise HTTPException(status_code=404, detail="User not found.") @@ -555,7 +545,10 @@ def approve_user_registration(user_id, current_user): status_code=500, detail="Failed to send email: {}".format(str(e)) ) - return {"status_code": 200, "body": "User registration approved."} + return { + "status_code": 200, + "body": "User registration approved by {}.".format(user.approved_by.email), + } # PUT: /users/{user_id}/register/deny diff --git a/backend/src/xfd_django/xfd_api/schema_models/user.py b/backend/src/xfd_django/xfd_api/schema_models/user.py index 27c898906..1bc3a647e 100644 --- a/backend/src/xfd_django/xfd_api/schema_models/user.py +++ b/backend/src/xfd_django/xfd_api/schema_models/user.py @@ -38,7 +38,7 @@ class User(BaseModel): invite_pending: bool login_blocked_by_maintenance: bool date_approved: Optional[datetime] = None - approved_by_id: Optional[UUID] = None + approved_by_id: Optional[Any] = None date_accepted_terms: Optional[datetime] accepted_terms_version: Optional[str] last_logged_in: Optional[datetime] @@ -191,7 +191,7 @@ class UserResponseV2(BaseModel): full_name: str email: str date_approved: Optional[datetime] = None - approved_by_id: Optional[UUID] = None + approved_by_id: Optional[Any] = None accepted_terms_version: Optional[str] = None date_accepted_terms: Optional[datetime] = None last_logged_in: Optional[datetime] = None diff --git a/backend/src/xfd_django/xfd_mini_dl/models.py b/backend/src/xfd_django/xfd_mini_dl/models.py index 17c0cdd8b..0b66b81be 100644 --- a/backend/src/xfd_django/xfd_mini_dl/models.py +++ b/backend/src/xfd_django/xfd_mini_dl/models.py @@ -1278,7 +1278,7 @@ class User(AutoLengthCheckModel): null=True, help_text="Date the user was approved to have access to the cyhy dashboard.", ) - approved_by_id = models.ForeignKey( + approved_by = models.ForeignKey( "User", models.DO_NOTHING, db_column="approved_by_id", diff --git a/frontend/src/pages/RegionUsers/RegionUsers.tsx b/frontend/src/pages/RegionUsers/RegionUsers.tsx index 550787428..0f2bf4262 100644 --- a/frontend/src/pages/RegionUsers/RegionUsers.tsx +++ b/frontend/src/pages/RegionUsers/RegionUsers.tsx @@ -53,31 +53,6 @@ export const RegionUsers: React.FC = () => { const getOrgsURL = `/organizations/region_id/`; const getUsersURL = `/v2/users?invite_pending=`; - const logUserApproval = async (approvedUser: any) => { - await apiGet('/users/me') - .then((currentUser) => { - const body = { - first_name: approvedUser.first_name, - last_name: approvedUser.last_name, - email: approvedUser.email, - user_type: approvedUser.user_type, - state: approvedUser.state, - region_id: approvedUser.region_id, - date_approved: new Date().toISOString(), - approved_by_id: currentUser.id - }; - - console.log('Body inside RegionUsers', body); - apiPut(`/v2/users/${approvedUser.id}`, { body }); - console.log(currentUser); - console.log('ID', currentUser.id); - console.log('Name', currentUser.full_name); - console.log('Email', currentUser.email); - }) - .catch((e: any) => { - setErrorStates({ ...errorStates, getOrgsError: e.message }); - }); - }; const pendingCols: GridColDef[] = [ { field: 'full_name', headerName: 'Name', minWidth: 100, flex: 1 }, { field: 'email', headerName: 'Email', minWidth: 100, flex: 2 }, @@ -322,21 +297,13 @@ export const RegionUsers: React.FC = () => { }; const handleApproveClick = (row: typeof initializeUser) => { - try { - selectOrg([]); - setDialogStates({ - ...dialogStates, - isOrgDialogOpen: true - }); - selectUser(row); - console.log('Selected User', row); - fetchOrganizations(row); - } catch (e: any) { - setErrorStates({ ...errorStates, getOrgsError: e.message }); - } finally { - // Make API call to get current user approving a new user request - logUserApproval(row); - } + selectOrg([]); + setDialogStates({ + ...dialogStates, + isOrgDialogOpen: true + }); + selectUser(row); + fetchOrganizations(row); }; const handleDenyClick = (row: typeof initializeUser) => { diff --git a/frontend/src/pages/Users/Users.tsx b/frontend/src/pages/Users/Users.tsx index 95559a1bc..01a46d1b0 100644 --- a/frontend/src/pages/Users/Users.tsx +++ b/frontend/src/pages/Users/Users.tsx @@ -48,8 +48,8 @@ interface UserType extends User { dateToUSigned?: string | null | undefined; orgs?: string | null | undefined; full_name: string; - approved_by_id?: string | null | undefined; - date_approved?: string | null | undefined; + approved_by?: string | null; + date_approved?: string | null; } export const Users: React.FC = () => { From c0a2a2ac351b88c67ad47f069e6dd6c3ebffe63f Mon Sep 17 00:00:00 2001 From: Chrtorres Date: Mon, 5 May 2025 13:48:20 -0400 Subject: [PATCH 07/10] Populated Use Case field within RegionUsers.tsx; Added cognito_use_case_description to User schema --- backend/src/xfd_django/xfd_api/api_methods/user.py | 3 +++ backend/src/xfd_django/xfd_api/schema_models/user.py | 2 ++ 2 files changed, 5 insertions(+) diff --git a/backend/src/xfd_django/xfd_api/api_methods/user.py b/backend/src/xfd_django/xfd_api/api_methods/user.py index 1cfb434fc..24915e2b6 100644 --- a/backend/src/xfd_django/xfd_api/api_methods/user.py +++ b/backend/src/xfd_django/xfd_api/api_methods/user.py @@ -389,6 +389,7 @@ def get_users_v2(state, region_id, invite_pending, current_user): return [ { "id": str(user.id), + "cognito_use_case_description": user.cognito_use_case_description, "created_at": user.created_at.isoformat(), "updated_at": user.updated_at.isoformat(), "first_name": user.first_name, @@ -399,6 +400,8 @@ def get_users_v2(state, region_id, invite_pending, current_user): "state": user.state, "user_type": user.user_type, "last_logged_in": user.last_logged_in, + "date_approved": user.date_approved, + "approved_by_id": user.approved_by.full_name, "accepted_terms_version": user.accepted_terms_version, "roles": [ { diff --git a/backend/src/xfd_django/xfd_api/schema_models/user.py b/backend/src/xfd_django/xfd_api/schema_models/user.py index 1bc3a647e..0a4e38c19 100644 --- a/backend/src/xfd_django/xfd_api/schema_models/user.py +++ b/backend/src/xfd_django/xfd_api/schema_models/user.py @@ -37,6 +37,7 @@ class User(BaseModel): email: str invite_pending: bool login_blocked_by_maintenance: bool + cognito_use_case_description: Optional[str] = None date_approved: Optional[datetime] = None approved_by_id: Optional[Any] = None date_accepted_terms: Optional[datetime] @@ -190,6 +191,7 @@ class UserResponseV2(BaseModel): last_name: str full_name: str email: str + cognito_use_case_description: Optional[str] = None date_approved: Optional[datetime] = None approved_by_id: Optional[Any] = None accepted_terms_version: Optional[str] = None From b2c084ad9dde5eae1caf8dd15c5742a53dbfb13d Mon Sep 17 00:00:00 2001 From: Chrtorres Date: Tue, 6 May 2025 14:10:05 -0400 Subject: [PATCH 08/10] Created ApprovedBy Interface within Users.tsx to access user object within approved_by field --- .../xfd_django/xfd_api/api_methods/user.py | 37 +++++++++---------- .../xfd_django/xfd_api/schema_models/user.py | 6 +-- frontend/src/pages/Users/Users.tsx | 20 +++++++++- 3 files changed, 38 insertions(+), 25 deletions(-) diff --git a/backend/src/xfd_django/xfd_api/api_methods/user.py b/backend/src/xfd_django/xfd_api/api_methods/user.py index 24915e2b6..96e80892f 100644 --- a/backend/src/xfd_django/xfd_api/api_methods/user.py +++ b/backend/src/xfd_django/xfd_api/api_methods/user.py @@ -190,23 +190,14 @@ def get_users(current_user): raise HTTPException(status_code=401, detail="Unauthorized") users = User.objects.all().prefetch_related("roles__organization") - # print("Inside user.py: ") - # for user in users: - # print("id:", str(user.id)) - # print("created_at:", user.created_at.isoformat()) - # print("updated_at:", user.updated_at.isoformat()) - # print("first_name:", user.first_name) - # print("last_name:", user.last_name) - # print("full_name:", user.full_name) - # print("email:", user.email) - # print("region_id:", user.region_id) - # print("state:", user.state) - # print("user_type:", user.user_type) - # print("last_logged_in:", user.last_logged_in) - # print("date_approved:", user.date_approved) - # print("approved_by_id:", user.approved_by_id) - # print("accepted_terms_version:", user.accepted_terms_version) - # print("date_accepted_terms:", user.date_accepted_terms) + print("Inside user.py: ") + for user in users: + print("user: ", vars(user)) + print("id:", str(user.id)) + print("full_name:", user.full_name) + print("email:", user.email) + print("date_approved:", user.date_approved) + print("approved_by:", user.approved_by) # Return the updated user details return [ { @@ -222,7 +213,11 @@ def get_users(current_user): "user_type": user.user_type, "last_logged_in": user.last_logged_in, "date_approved": user.date_approved, - "approved_by_id": user.approved_by.full_name, + "approved_by": { + "id": str(user.approved_by.id), + "full_name": str(user.approved_by.full_name), + "email": str(user.approved_by.email), + }, "accepted_terms_version": user.accepted_terms_version, "date_accepted_terms": user.date_accepted_terms, "roles": [ @@ -243,9 +238,11 @@ def get_users(current_user): for user in users ] except HTTPException as http_exc: + print("HTTPException: ", http_exc) raise http_exc except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) + print("Error retrieving users: {}".format(e)) + raise Exception(status_code=500, detail=str(e)) # GET: /users/region_id/{region_id} @@ -401,7 +398,7 @@ def get_users_v2(state, region_id, invite_pending, current_user): "user_type": user.user_type, "last_logged_in": user.last_logged_in, "date_approved": user.date_approved, - "approved_by_id": user.approved_by.full_name, + "approved_by": user.approved_by, "accepted_terms_version": user.accepted_terms_version, "roles": [ { diff --git a/backend/src/xfd_django/xfd_api/schema_models/user.py b/backend/src/xfd_django/xfd_api/schema_models/user.py index 0a4e38c19..e6be92a52 100644 --- a/backend/src/xfd_django/xfd_api/schema_models/user.py +++ b/backend/src/xfd_django/xfd_api/schema_models/user.py @@ -39,7 +39,7 @@ class User(BaseModel): login_blocked_by_maintenance: bool cognito_use_case_description: Optional[str] = None date_approved: Optional[datetime] = None - approved_by_id: Optional[Any] = None + approved_by: Optional[Dict[str, str]] = None date_accepted_terms: Optional[datetime] accepted_terms_version: Optional[str] last_logged_in: Optional[datetime] @@ -165,7 +165,7 @@ class UpdateUserV2(BaseModel): user_type: Optional[str] = None invite_pending: Optional[bool] = None date_approved: Optional[datetime] = None - approved_by_id: Optional[UUID] = None + approved_by: Optional[Any] = None class RegisterUserResponse(BaseModel): @@ -193,7 +193,7 @@ class UserResponseV2(BaseModel): email: str cognito_use_case_description: Optional[str] = None date_approved: Optional[datetime] = None - approved_by_id: Optional[Any] = None + approved_by: Optional[Dict[str, str]] = None accepted_terms_version: Optional[str] = None date_accepted_terms: Optional[datetime] = None last_logged_in: Optional[datetime] = None diff --git a/frontend/src/pages/Users/Users.tsx b/frontend/src/pages/Users/Users.tsx index 01a46d1b0..8f1bf068a 100644 --- a/frontend/src/pages/Users/Users.tsx +++ b/frontend/src/pages/Users/Users.tsx @@ -43,12 +43,27 @@ export interface ApiResponse { url?: string; } +interface ApprovedBy { + id: string; + full_name: string; + first_name: string; + last_name: string; + email: string; + user_type: string; + region_id: string; + state: string; + last_logged_in: string; + accepted_terms_version: string; + date_accepted_terms: string; + // approved_by: string; +} + interface UserType extends User { lastLoggedInString?: string | null | undefined; dateToUSigned?: string | null | undefined; orgs?: string | null | undefined; full_name: string; - approved_by?: string | null; + approved_by?: ApprovedBy | null; date_approved?: string | null; } @@ -92,6 +107,7 @@ export const Users: React.FC = () => { .join(', ') : 'None'; row.full_name = `${row.first_name} ${row.last_name}`; + row.approved_by = row.approved_by ? row.approved_by.full_name : 'None'; // Map approved_by to full_name }); setUsers(rows); setApiErrorStates((prev) => ({ ...prev, getUsersError: '' })); @@ -133,7 +149,7 @@ export const Users: React.FC = () => { } }, { - field: 'approved_by_id', + field: 'approved_by', headerName: 'Approved By', minWidth: 100, flex: 0.75 From e95554b02c3e6fa6aaf5ccc6db450e8c9fa6c2e8 Mon Sep 17 00:00:00 2001 From: "Grayson, Matthew" Date: Tue, 6 May 2025 14:46:57 -0500 Subject: [PATCH 09/10] Use valueGetter to retrieve approved_by.full_name in Users.tsx; make approved_by optional in get_users and remove print statements from api_methods/user.py. --- .../src/xfd_django/xfd_api/api_methods/user.py | 12 +++--------- frontend/src/pages/Users/Users.tsx | 15 +++------------ 2 files changed, 6 insertions(+), 21 deletions(-) diff --git a/backend/src/xfd_django/xfd_api/api_methods/user.py b/backend/src/xfd_django/xfd_api/api_methods/user.py index 96e80892f..307227648 100644 --- a/backend/src/xfd_django/xfd_api/api_methods/user.py +++ b/backend/src/xfd_django/xfd_api/api_methods/user.py @@ -190,14 +190,6 @@ def get_users(current_user): raise HTTPException(status_code=401, detail="Unauthorized") users = User.objects.all().prefetch_related("roles__organization") - print("Inside user.py: ") - for user in users: - print("user: ", vars(user)) - print("id:", str(user.id)) - print("full_name:", user.full_name) - print("email:", user.email) - print("date_approved:", user.date_approved) - print("approved_by:", user.approved_by) # Return the updated user details return [ { @@ -217,7 +209,9 @@ def get_users(current_user): "id": str(user.approved_by.id), "full_name": str(user.approved_by.full_name), "email": str(user.approved_by.email), - }, + } + if user.approved_by + else None, "accepted_terms_version": user.accepted_terms_version, "date_accepted_terms": user.date_accepted_terms, "roles": [ diff --git a/frontend/src/pages/Users/Users.tsx b/frontend/src/pages/Users/Users.tsx index 8f1bf068a..c3b2667c1 100644 --- a/frontend/src/pages/Users/Users.tsx +++ b/frontend/src/pages/Users/Users.tsx @@ -37,12 +37,6 @@ type ApiErrorStates = { getOrgsError: string; }; -export interface ApiResponse { - result: User[]; - count: number; - url?: string; -} - interface ApprovedBy { id: string; full_name: string; @@ -52,10 +46,6 @@ interface ApprovedBy { user_type: string; region_id: string; state: string; - last_logged_in: string; - accepted_terms_version: string; - date_accepted_terms: string; - // approved_by: string; } interface UserType extends User { @@ -107,7 +97,6 @@ export const Users: React.FC = () => { .join(', ') : 'None'; row.full_name = `${row.first_name} ${row.last_name}`; - row.approved_by = row.approved_by ? row.approved_by.full_name : 'None'; // Map approved_by to full_name }); setUsers(rows); setApiErrorStates((prev) => ({ ...prev, getUsersError: '' })); @@ -152,7 +141,9 @@ export const Users: React.FC = () => { field: 'approved_by', headerName: 'Approved By', minWidth: 100, - flex: 0.75 + flex: 0.75, + valueGetter: (params) => + params.row.approved_by ? params.row.approved_by.full_name : 'None' }, { field: 'dateToUSigned', From e61a4c09ffdd9daaa650266d1edfc488a14bc81a Mon Sep 17 00:00:00 2001 From: Chrtorres Date: Wed, 21 May 2025 11:06:54 -0400 Subject: [PATCH 10/10] Added detailed information to approved by field within /v2/users response --- backend/src/xfd_django/xfd_api/api_methods/user.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/backend/src/xfd_django/xfd_api/api_methods/user.py b/backend/src/xfd_django/xfd_api/api_methods/user.py index 7f85fa43b..c30fb47f1 100644 --- a/backend/src/xfd_django/xfd_api/api_methods/user.py +++ b/backend/src/xfd_django/xfd_api/api_methods/user.py @@ -399,7 +399,13 @@ def get_users_v2(state, region_id, invite_pending, current_user): "user_type": user.user_type, "last_logged_in": user.last_logged_in, "date_approved": user.date_approved, - "approved_by": user.approved_by, + "approved_by": { + "id": str(user.approved_by.id), + "full_name": str(user.approved_by.full_name), + "email": str(user.approved_by.email), + } + if user.approved_by + else None, "accepted_terms_version": user.accepted_terms_version, "roles": [ {