-
Notifications
You must be signed in to change notification settings - Fork 60
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Log when User/SA/Roles is added to a group #1205
base: master
Are you sure you want to change the base?
Changes from 28 commits
0d285c3
f177dfe
52d2425
1879794
e7f1634
695f3d5
98ec671
7d1b53c
a2b27d7
1082105
c4f2149
d93b2f5
ed8c1fd
2728973
b90c810
6c86869
7a56c67
db76f34
2102012
fc2a1a1
9170246
8c5b494
12754a5
33592e0
559bb7a
c3316c0
2a3d3f8
d87cf2d
0f47ea5
34de838
696480a
5b0ab17
c47931f
8e4504b
8ff3678
a3c06b6
a1d5e22
d421e47
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -80,9 +80,8 @@ def get_resource_item(self, r_type, request, *args, **kwargs): | |
group_object_name = "group: " + group_object.name | ||
return group_object_id, group_object_name | ||
|
||
elif r_type == AuditLog.PERMISSION: | ||
# TODO: update for permission related items | ||
return None | ||
else: | ||
return ValueError("Wrong Resource Type") | ||
|
||
def find_edited_field(self, resource, resource_name, request, object): | ||
"""Add additional information when group/role is edited.""" | ||
|
@@ -98,6 +97,22 @@ def find_edited_field(self, resource, resource_name, request, object): | |
description = description + "edited access (permissions/resources)" | ||
return description | ||
|
||
def find_specific_list_of_users(self, type_dict, user_type): | ||
"""Create list of principals/roles/service accounts for description.""" | ||
names_list = [] | ||
if user_type == "user" or user_type == "User": | ||
for username in type_dict: | ||
names_list.append(username["username"]) | ||
elif user_type == "service_account" or user_type == "Service Account": | ||
for service_account in type_dict: | ||
names_list.append(service_account["clientId"]) | ||
elif user_type == AuditLog.ROLE: | ||
for role in type_dict: | ||
names_list.append(str(role.uuid)) | ||
else: | ||
return ValueError("User type does not exist") | ||
return ", ".join(names_list) | ||
|
||
def log_create(self, request, resource): | ||
"""Audit Log when a role or a group is created.""" | ||
self.principal_username = request.user.username | ||
|
@@ -138,3 +153,17 @@ def log_edit(self, request, resource, object): | |
self.action = AuditLog.EDIT | ||
self.tenant_id = self.get_tenant_id(request) | ||
super(AuditLog, self).save() | ||
|
||
def log_group_assignment(self, request, resource, object, type_dict, user_type): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
"""Audit Log when a role, user/principal, or service account is added to a group.""" | ||
self.principal_username = request.user.username | ||
self.resource_type = resource | ||
self.resource_id = object.id | ||
resource_name = "group: " + object.name | ||
|
||
name_list = self.find_specific_list_of_users(type_dict, user_type) | ||
self.description = f"{user_type} {name_list} added to {resource_name}" | ||
|
||
self.action = AuditLog.ADD | ||
self.tenant_id = self.get_tenant_id(request) | ||
super(AuditLog, self).save() |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -57,7 +57,11 @@ | |
from management.principal.model import Principal | ||
from management.principal.proxy import PrincipalProxy | ||
from management.principal.serializer import ServiceAccountSerializer | ||
from management.principal.view import ADMIN_ONLY_KEY, USERNAME_ONLY_KEY, VALID_BOOLEAN_VALUE | ||
from management.principal.view import ( | ||
ADMIN_ONLY_KEY, | ||
USERNAME_ONLY_KEY, | ||
VALID_BOOLEAN_VALUE, | ||
) | ||
from management.querysets import get_group_queryset, get_role_queryset | ||
from management.relation_replicator.relation_replicator import ReplicationEventType | ||
from management.role.view import RoleViewSet | ||
|
@@ -88,7 +92,12 @@ | |
SERVICE_ACCOUNT_NAME_KEY = "service_account_name" | ||
SERVICE_ACCOUNT_USERNAME_FORMAT = "service-account-{clientId}" | ||
VALID_EXCLUDE_VALUES = ["true", "false"] | ||
VALID_GROUP_ROLE_FILTERS = ["role_name", "role_description", "role_display_name", "role_system"] | ||
VALID_GROUP_ROLE_FILTERS = [ | ||
"role_name", | ||
"role_description", | ||
"role_display_name", | ||
"role_system", | ||
] | ||
VALID_GROUP_PRINCIPAL_FILTERS = ["principal_username"] | ||
VALID_PRINCIPAL_ORDER_FIELDS = ["username"] | ||
VALID_PRINCIPAL_TYPE_VALUE = ["service-account", "user"] | ||
|
@@ -116,7 +125,10 @@ def roles_filter(self, queryset, field, values): | |
roles_list = [value.lower() for value in values.split(",")] | ||
|
||
discriminator = validate_and_get_key( | ||
self.request.query_params, ROLE_DISCRIMINATOR_KEY, VALID_ROLE_ROLE_DISCRIMINATOR, "any" | ||
self.request.query_params, | ||
ROLE_DISCRIMINATOR_KEY, | ||
VALID_ROLE_ROLE_DISCRIMINATOR, | ||
"any", | ||
) | ||
|
||
if discriminator == "any": | ||
|
@@ -432,7 +444,13 @@ def validate_principals_in_proxy_request(self, principals, org_id=None): | |
if len(resp.get("data", [])) == 0: | ||
return { | ||
"status_code": status.HTTP_404_NOT_FOUND, | ||
"errors": [{"detail": "User(s) {} not found.".format(users), "status": "404", "source": "principals"}], | ||
"errors": [ | ||
{ | ||
"detail": "User(s) {} not found.".format(users), | ||
"status": "404", | ||
"source": "principals", | ||
} | ||
], | ||
} | ||
return resp | ||
|
||
|
@@ -689,15 +707,25 @@ def principals(self, request: Request, uuid: Optional[UUID] = None): | |
return Response( | ||
status=status.HTTP_403_FORBIDDEN, | ||
data={ | ||
"errors": [{"detail": str(ipe), "status": status.HTTP_403_FORBIDDEN, "source": "groups"}] | ||
"errors": [ | ||
{ | ||
"detail": str(ipe), | ||
"status": status.HTTP_403_FORBIDDEN, | ||
"source": "groups", | ||
} | ||
] | ||
}, | ||
) | ||
except ServiceAccountNotFoundError as sanfe: | ||
return Response( | ||
status=status.HTTP_400_BAD_REQUEST, | ||
data={ | ||
"errors": [ | ||
{"detail": str(sanfe), "source": "group", "status": str(status.HTTP_400_BAD_REQUEST)} | ||
{ | ||
"detail": str(sanfe), | ||
"source": "group", | ||
"status": str(status.HTTP_400_BAD_REQUEST), | ||
} | ||
] | ||
}, | ||
) | ||
|
@@ -731,14 +759,39 @@ def principals(self, request: Request, uuid: Optional[UUID] = None): | |
# Serialize the group... | ||
output = GroupSerializer(group) | ||
response = Response(status=status.HTTP_200_OK, data=output.data) | ||
|
||
if status.is_success(response.status_code): | ||
if len(principals) > 0: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this should be part of transaction block which start on 715. if operation fails, transaction is rolled back and record into audit log is not created. |
||
auditlog = AuditLog() | ||
auditlog.log_group_assignment( | ||
request, | ||
AuditLog.GROUP, | ||
object=group, | ||
type_dict=principals, | ||
user_type=Principal.Types.USER, | ||
) | ||
if len(service_accounts) > 0: | ||
auditlog = AuditLog() | ||
auditlog.log_group_assignment( | ||
request, | ||
AuditLog.GROUP, | ||
object=group, | ||
type_dict=service_accounts, | ||
user_type=Principal.Types.SERVICE_ACCOUNT, | ||
) | ||
|
||
elif request.method == "GET": | ||
# Check if the request comes with a bunch of service account client IDs that we need to check. Since this | ||
# query parameter is incompatible with any other query parameter, we make the checks first. That way if any | ||
# other query parameter was specified, we simply return early. | ||
if SERVICE_ACCOUNT_CLIENT_IDS_KEY in request.query_params: | ||
# pagination is ignored in this case | ||
for query_param in request.query_params: | ||
if query_param not in [SERVICE_ACCOUNT_CLIENT_IDS_KEY, "limit", "offset"]: | ||
if query_param not in [ | ||
SERVICE_ACCOUNT_CLIENT_IDS_KEY, | ||
"limit", | ||
"offset", | ||
]: | ||
return Response( | ||
status=status.HTTP_400_BAD_REQUEST, | ||
data={ | ||
|
@@ -817,7 +870,11 @@ def principals(self, request: Request, uuid: Optional[UUID] = None): | |
|
||
# Get the "username_only" query parameter. | ||
username_only = validate_and_get_key( | ||
request.query_params, USERNAME_ONLY_KEY, VALID_BOOLEAN_VALUE, "false", required=False | ||
request.query_params, | ||
USERNAME_ONLY_KEY, | ||
VALID_BOOLEAN_VALUE, | ||
"false", | ||
required=False, | ||
) | ||
|
||
# Build the options dict. | ||
|
@@ -827,7 +884,10 @@ def principals(self, request: Request, uuid: Optional[UUID] = None): | |
# parameter. It is important because we need to call BOP for | ||
# the users, and IT for the service accounts. | ||
principalType = validate_and_get_key( | ||
request.query_params, PRINCIPAL_TYPE_KEY, VALID_PRINCIPAL_TYPE_VALUE, required=False | ||
request.query_params, | ||
PRINCIPAL_TYPE_KEY, | ||
VALID_PRINCIPAL_TYPE_VALUE, | ||
required=False, | ||
) | ||
|
||
# Store the principal type in the options dict. | ||
|
@@ -856,7 +916,10 @@ def principals(self, request: Request, uuid: Optional[UUID] = None): | |
service_accounts = it_service.get_service_accounts_group( | ||
group=group, user=request.user, options=options | ||
) | ||
except (requests.exceptions.ConnectionError, UnexpectedStatusCodeFromITError): | ||
except ( | ||
requests.exceptions.ConnectionError, | ||
UnexpectedStatusCodeFromITError, | ||
): | ||
return Response( | ||
status=status.HTTP_500_INTERNAL_SERVER_ERROR, | ||
data={ | ||
|
@@ -1041,8 +1104,19 @@ def roles(self, request, uuid=None, principals=None): | |
if serializer.is_valid(raise_exception=True): | ||
roles = request.data.pop(ROLES_KEY, []) | ||
group = set_system_flag_before_update(group, request.tenant, request.user) | ||
add_roles(group, roles, request.tenant, user=request.user) | ||
log_roles = add_roles(group, roles, request.tenant, user=request.user) | ||
response_data = GroupRoleSerializerIn(group) | ||
response = Response(status=status.HTTP_200_OK, data=response_data.data) | ||
if status.is_success(response.status_code): | ||
auditlog = AuditLog() | ||
auditlog.log_group_assignment( | ||
request, | ||
AuditLog.GROUP, | ||
object=group, | ||
type_dict=log_roles, | ||
user_type=AuditLog.ROLE, | ||
) | ||
|
||
elif request.method == "GET": | ||
serialized_roles = self.obtain_roles(request, group) | ||
page = self.paginate_queryset(serialized_roles) | ||
|
@@ -1142,7 +1216,10 @@ def remove_service_accounts(self, user: User, group: Group, service_accounts: It | |
|
||
# Get the group's service accounts that match the service accounts that the user specified. | ||
valid_service_accounts = Principal.objects.filter( | ||
group=group, tenant=tenant, type="service-account", service_account_id__in=service_accounts | ||
group=group, | ||
tenant=tenant, | ||
type=Principal.Types.SERVICE_ACCOUNT, | ||
service_account_id__in=service_accounts, | ||
) | ||
|
||
# Collect the service account IDs the user specified. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
resource and object names are confusing
it looks that resource is rather
resource_type
and then object can beresource
:)Does it make sense ?