Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 33 additions & 3 deletions backend/src/xfd_django/xfd_api/api_methods/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,6 @@ def get_users(current_user):
raise HTTPException(status_code=401, detail="Unauthorized")

users = User.objects.all().prefetch_related("roles__organization")

# Return the updated user details
return [
{
Expand All @@ -212,6 +211,14 @@ 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": 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": [
Expand All @@ -232,9 +239,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}
Expand Down Expand Up @@ -378,6 +387,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,
Expand All @@ -388,6 +398,14 @@ 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": 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": [
{
Expand Down Expand Up @@ -454,6 +472,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)

# Return the updated user details
return {
Expand Down Expand Up @@ -500,6 +522,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.")

Expand All @@ -525,7 +552,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
Expand Down
8 changes: 8 additions & 0 deletions backend/src/xfd_django/xfd_api/schema_models/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ 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: Optional[Dict[str, str]] = None
date_accepted_terms: Optional[datetime]
accepted_terms_version: Optional[str]
last_logged_in: Optional[datetime]
Expand Down Expand Up @@ -161,6 +164,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: Optional[Any] = None


class RegisterUserResponse(BaseModel):
Expand All @@ -186,6 +191,9 @@ 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: Optional[Dict[str, str]] = None
accepted_terms_version: Optional[str] = None
date_accepted_terms: Optional[datetime] = None
last_logged_in: Optional[datetime] = None
Expand Down
74 changes: 14 additions & 60 deletions backend/src/xfd_django/xfd_mini_dl/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1274,6 +1274,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 = models.ForeignKey(
"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,
Expand Down Expand Up @@ -2740,66 +2754,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."""

Expand Down
7 changes: 7 additions & 0 deletions frontend/src/pages/RegionUsers/RegionUsers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export const RegionUsers: React.FC = () => {
const { formattedUserType } = useUserLevel();
const getOrgsURL = `/organizations/region_id/`;
const getUsersURL = `/v2/users?invite_pending=`;

const pendingCols: GridColDef[] = [
{ field: 'full_name', headerName: 'Name', minWidth: 100, flex: 1 },
{ field: 'email', headerName: 'Email', minWidth: 100, flex: 2 },
Expand Down Expand Up @@ -86,6 +87,12 @@ export const RegionUsers: React.FC = () => {
</Stack>
);
}
},
{
field: 'cognito_use_case_description',
headerName: 'Use Case',
minWidth: 100,
flex: 1.5
}
];
const memberCols: GridColDef[] = [
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/pages/Users/UserForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
37 changes: 33 additions & 4 deletions frontend/src/pages/Users/Users.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,24 @@ type ApiErrorStates = {
getOrgsError: string;
};

export interface ApiResponse {
result: User[];
count: number;
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;
}

interface UserType extends User {
lastLoggedInString?: string | null | undefined;
dateToUSigned?: string | null | undefined;
orgs?: string | null | undefined;
full_name: string;
approved_by?: ApprovedBy | null;
date_approved?: string | null;
}

export const Users: React.FC = () => {
Expand Down Expand Up @@ -116,6 +123,28 @@ 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',
headerName: 'Approved By',
minWidth: 100,
flex: 0.75,
valueGetter: (params) =>
params.row.approved_by ? params.row.approved_by.full_name : 'None'
},
{
field: 'dateToUSigned',
headerName: 'Date ToU Signed',
Expand Down
Loading