def get(self, request): """List principals for account.""" user = request.user path = request.path query_params = request.query_params default_limit = StandardResultsSetPagination.default_limit usernames_filter = "" options = {} try: limit = int(query_params.get("limit", default_limit)) offset = int(query_params.get("offset", 0)) options["sort_order"] = validate_and_get_key( query_params, SORTORDER_KEY, VALID_SORTORDER_VALUE, "asc") options["status"] = validate_and_get_key(query_params, STATUS_KEY, VALID_STATUS_VALUE, "enabled") except ValueError: error = { "detail": "Values for limit and offset must be positive numbers.", "source": "principals", "status": str(status.HTTP_400_BAD_REQUEST), } errors = {"errors": [error]} return Response(status=status.HTTP_400_BAD_REQUEST, data=errors) previous_offset = 0 if offset - limit > 0: previous_offset = offset - limit resp, usernames_filter = self.users_from_proxy(user, query_params, options, limit, offset) status_code = resp.get("status_code") response_data = {} if status_code == status.HTTP_200_OK: data = resp.get("data", []) if isinstance(data, dict): count = data.get("userCount") data = data.get("users") elif isinstance(data, list): count = len(data) else: count = None response_data["meta"] = {"count": count} response_data["links"] = { "first": f"{path}?limit={limit}&offset=0{usernames_filter}", "next": f"{path}?limit={limit}&offset={offset + limit}{usernames_filter}", "previous": f"{path}?limit={limit}&offset={previous_offset}{usernames_filter}", "last": None, } response_data["data"] = data else: response_data = resp del response_data["status_code"] return Response(status=status_code, data=response_data)
def retrieve(self, request, *args, **kwargs): """Retrive cross account requests by request_id.""" with tenant_context(Tenant.objects.get(schema_name="public")): result = super().retrieve(request=request, args=args, kwargs=kwargs) if validate_and_get_key(self.request.query_params, QUERY_BY_KEY, VALID_QUERY_BY_KEY, ACCOUNT) == ACCOUNT: user_id = result.data.pop("user_id") principal = PROXY.request_filtered_principals([user_id], account=None, options={ "query_by": "user_id", "return_id": True }).get("data")[0] # Replace the user_id with user's info result.data.update({ "first_name": principal["first_name"], "last_name": principal["last_name"], "email": principal["email"], }) return result
def get_queryset(self): """Get query set based on the queryBy key word.""" if self.request.method in ["PATCH", "PUT"]: return CrossAccountRequest.objects.all() if validate_and_get_key(self.request.query_params, QUERY_BY_KEY, VALID_QUERY_BY_KEY, ACCOUNT) == ACCOUNT: return CrossAccountRequest.objects.filter(target_account=self.request.user.account) return CrossAccountRequest.objects.filter(user_id=self.request.user.user_id)
def name_filter(self, queryset, field, value, name_field="name"): """Filter to lookup name, partial or exact.""" match_criteria = validate_and_get_key(self.request.query_params, NAME_MATCH_KEY, VALID_NAME_MATCHES, "partial") if match_criteria == "partial": return queryset.filter(**{f"{name_field}__icontains": value}) elif match_criteria == "exact": return queryset.filter(**{f"{name_field}__iexact": value})
def list(self, request, *args, **kwargs): """List cross account requests for account/user_id.""" validate_limit_and_offset(self.request.query_params) result = super().list(request=request, args=args, kwargs=kwargs) # The approver's view requires requester's info such as first name, last name, email address. if validate_and_get_key(self.request.query_params, QUERY_BY_KEY, VALID_QUERY_BY_KEY, ACCOUNT) == ACCOUNT: return self.replace_user_id_with_info(result) return result
def validate_and_get_param(self, params): """Validate input parameters and get ordering and sub_key.""" validate_limit_and_offset(params) app = params.get(APPLICATION_KEY) sub_key = app ordering = validate_and_get_key(params, ORDER_FIELD, VALID_ORDER_VALUES, required=False) if ordering: sub_key = f"{app}&order:{ordering}" return sub_key, ordering
def exclude_globals_filter(self, queryset, field, value): """Filter to filter out global permissions from results.""" query_field = validate_and_get_key(self.request.query_params, field, VALID_EXCLUDE_GLOBALS_VALUES, "false") if query_field == "true": exclude_set = Q(application="*") | Q(resource_type="*") | Q( verb="*") return queryset.exclude(exclude_set) return queryset
def users_from_proxy(self, user, query_params, options, limit, offset): """Format principal request for proxy and return prepped result.""" proxy = PrincipalProxy() usernames = query_params.get(USERNAMES_KEY) email = query_params.get(EMAIL_KEY) match_criteria = validate_and_get_key(query_params, MATCH_CRITERIA_KEY, VALID_MATCH_VALUE, "exact") if not usernames and not email: options["admin_only"] = validate_and_get_key( query_params, ADMIN_ONLY_KEY, VALID_ADMIN_ONLY_VALUE, "false") resp = proxy.request_principals(user.account, limit=limit, offset=offset, options=options) return resp, "" proxyInput = {} resp = None if usernames: principals = usernames.split(",") if match_criteria != "partial": resp = proxy.request_filtered_principals( principals, account=user.account, limit=limit, offset=offset, options={"sort_order": options["sort_order"]}, ) usernames_filter = f"&usernames={usernames}" return resp, usernames_filter else: proxyInput["principalStartsWith"] = principals[0] if email: if match_criteria == "partial": proxyInput["emailStartsWith"] = email else: proxyInput["primaryEmail"] = email resp = proxy.request_principals(user.account, input=proxyInput, limit=limit, offset=offset, options=options) return resp, ""
def obtain_roles(self, request, group): """Obtain roles based on request, supports exclusion.""" exclude = validate_and_get_key(request.query_params, EXCLUDE_KEY, VALID_EXCLUDE_VALUES, "false") roles = group.roles_with_access() if exclude == "false" else self.obtain_roles_with_exclusion(request, group) filtered_roles = self.filtered_roles(roles, request) annotated_roles = filtered_roles.annotate(policyCount=Count("policies", distinct=True)) if ORDERING_PARAM in request.query_params: ordered_roles = self.order_queryset( annotated_roles, VALID_ROLE_ORDER_FIELDS, request.query_params.get(ORDERING_PARAM) ) return [RoleMinimumSerializer(role).data for role in ordered_roles] return [RoleMinimumSerializer(role).data for role in annotated_roles]
def roles_filter(self, queryset, field, values): """Filter for group to lookup list of role name.""" if not values: key = "groups_filter" message = "No value of roles provided to filter groups!" error = {key: [_(message)]} raise serializers.ValidationError(error) 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" ) if discriminator == "any": return queryset.filter(policies__roles__name__iregex=r"(" + "|".join(roles_list) + ")") for role_name in roles_list: queryset = queryset.filter(policies__roles__name__icontains=role_name) return queryset
def has_permission(self, request, view): """Check permission based on identity and query by.""" if request._request.path.startswith(reverse("cross-list")): if request.method == "POST": # Only allow associates create the request return request.user.internal query_by = validate_and_get_key(request.query_params, QUERY_BY_KEY, VALID_QUERY_BY_KEY, ACCOUNT) if query_by == ACCOUNT: return request.user.admin elif query_by == USER_ID: return request.user.internal return False if request.method not in permissions.SAFE_METHODS: return False return True
def options(self, request): """Get options of applications.""" """ @api {get} /api/v1/permissions/options/ Get option of permission @apiName getPermissionOption @apiGroup Permission @apiVersion 1.0.0 @apiDescription Get option of permission @apiHeader {String} token User authorization token @apiParam (Query) {String} field The field to return. @apiParam (Query) {String} application Filter by permission name. @apiParam (Query) {String} resource_type Filter by resource_type. @apiParam (Query) {String} verb Filter by verb. @apiSuccess {Object[]} data The array of results. HTTP/1.1 200 OK { "data": [ catalog, approval ] } """ filters = {} query_field = validate_and_get_key(request.query_params, QUERY_FIELD, PERMISSION_FIELD_KEYS, None) for key in PERMISSION_FIELD_KEYS: context = request.query_params.get(key) if context: filters[f"{key}__in"] = context.split(",") query_set = Permission.objects.distinct(query_field).filter( **filters).values_list(query_field, flat=True) if "limit" not in self.request.query_params: self.paginator.default_limit = self.paginator.max_limit page = self.paginate_queryset(query_set) return self.get_paginated_response(page)
def principals(self, request, uuid=None): """Get, add or remove principals from a group.""" """ @api {get} /api/v1/groups/:uuid/principals/ Get principals for a group @apiName getPrincipals @apiGroup Group @apiVersion 1.0.0 @apiDescription Get principals for a group @apiHeader {String} token User authorization token @apiParam (Path) {String} id Group unique identifier @apiSuccess {String} uuid Group unique identifier @apiSuccess {String} name Group name @apiSuccess {Array} principals Array of principals @apiSuccessExample {json} Success-Response: HTTP/1.1 200 OK { "principals": [ { "username": "******" } ] } """ """ @api {post} /api/v1/groups/:uuid/principals/ Add principals to a group @apiName addPrincipals @apiGroup Group @apiVersion 1.0.0 @apiDescription Add principals to a group @apiHeader {String} token User authorization token @apiParam (Path) {String} id Group unique identifier @apiParam (Request Body) {String} username Principal username @apiParamExample {json} Request Body: { "principals": [ { "username": "******" }, { "username": "******" } ] } @apiSuccess {String} uuid Group unique identifier @apiSuccess {String} name Group name @apiSuccess {Array} principals Array of principals @apiSuccessExample {json} Success-Response: HTTP/1.1 200 OK { "uuid": "16fd2706-8baf-433b-82eb-8c7fada847da", "name": "GroupA", "principals": [ { "username": "******" } ] } """ """ @api {delete} /api/v1/groups/:uuid/principals/ Remove principals from group @apiName removePrincipals @apiGroup Group @apiVersion 1.0.0 @apiDescription Remove principals from a group @apiHeader {String} token User authorization token @apiParam (Path) {String} id Group unique identifier @apiParam (Query) {String} usernames List of comma separated principal usernames @apiSuccessExample {json} Success-Response: HTTP/1.1 204 NO CONTENT """ principals = [] validate_uuid(uuid, "group uuid validation") group = self.get_object() account = self.request.user.account if request.method == "POST": serializer = GroupPrincipalInputSerializer(data=request.data) if serializer.is_valid(raise_exception=True): principals = serializer.data.pop("principals") resp = self.add_principals(group, principals, account) if isinstance(resp, dict) and "errors" in resp: return Response(status=resp["status_code"], data=resp["errors"]) output = GroupSerializer(resp) response = Response(status=status.HTTP_200_OK, data=output.data) elif request.method == "GET": principals_from_params = self.filtered_principals(group, request) page = self.paginate_queryset(principals_from_params) serializer = PrincipalSerializer(page, many=True) principal_data = serializer.data if principal_data: username_list = [ principal["username"] for principal in principal_data ] else: username_list = [] proxy = PrincipalProxy() all_valid_fields = VALID_PRINCIPAL_ORDER_FIELDS + [ "-" + field for field in VALID_PRINCIPAL_ORDER_FIELDS ] if request.query_params.get(ORDERING_PARAM): sort_field = validate_and_get_key(request.query_params, ORDERING_PARAM, all_valid_fields, "username") sort_order = "des" if sort_field == "-username" else "asc" else: sort_order = None resp = proxy.request_filtered_principals(username_list, account, sort_order=sort_order) if isinstance(resp, dict) and "errors" in resp: return Response(status=resp.get("status_code"), data=resp.get("errors")) response = self.get_paginated_response(resp.get("data")) else: if USERNAMES_KEY not in request.query_params: key = "detail" message = "Query parameter {} is required.".format( USERNAMES_KEY) raise serializers.ValidationError({key: _(message)}) username = request.query_params.get(USERNAMES_KEY, "") principals = [name.strip() for name in username.split(",")] self.remove_principals(group, principals, account) response = Response(status=status.HTTP_204_NO_CONTENT) return response
def get(self, request): """List principals for account.""" proxy = PrincipalProxy() user = self.request.user path = self.request.path query_params = self.request.query_params default_limit = StandardResultsSetPagination.default_limit usernames = None usernames_filter = "" options = {} try: limit = int(query_params.get("limit", default_limit)) offset = int(query_params.get("offset", 0)) usernames = query_params.get(USERNAMES_KEY) email = query_params.get(EMAIL_KEY) options["sort_order"] = validate_and_get_key( query_params, SORTORDER_KEY, VALID_SORTORDER_VALUE, "asc") options["status"] = validate_and_get_key(query_params, STATUS_KEY, VALID_STATUS_VALUE, "enabled") options["admin_only"] = validate_and_get_key( query_params, ADMIN_ONLY_KEY, VALID_ADMIN_ONLY_VALUE, "false") except ValueError: error = { "detail": "Values for limit and offset must be positive numbers.", "source": "principals", "status": str(status.HTTP_400_BAD_REQUEST), } errors = {"errors": [error]} return Response(status=status.HTTP_400_BAD_REQUEST, data=errors) previous_offset = 0 if offset - limit > 0: previous_offset = offset - limit if usernames: principals = usernames.split(",") resp = proxy.request_filtered_principals( principals, account=user.account, limit=limit, offset=offset, sort_order=options["sort_order"]) usernames_filter = f"&usernames={usernames}" elif email: resp = proxy.request_principals( user.account, email=email, limit=limit, offset=offset, options={"sort_order": options["sort_order"]}) else: resp = proxy.request_principals(user.account, limit=limit, offset=offset, options=options) status_code = resp.get("status_code") response_data = {} if status_code == status.HTTP_200_OK: data = resp.get("data", []) if isinstance(data, dict): count = data.get("userCount") data = data.get("users") elif isinstance(data, list): count = len(data) else: count = None response_data["meta"] = {"count": count} response_data["links"] = { "first": f"{path}?limit={limit}&offset=0{usernames_filter}", "next": f"{path}?limit={limit}&offset={offset + limit}{usernames_filter}", "previous": f"{path}?limit={limit}&offset={previous_offset}{usernames_filter}", "last": None, } response_data["data"] = data else: response_data = resp del response_data["status_code"] return Response(status=status_code, data=response_data)
def get(self, request): """List prinicpals for account.""" proxy = PrincipalProxy() user = self.request.user path = self.request.path query_params = self.request.query_params default_limit = StandardResultsSetPagination.default_limit usernames = None usernames_filter = '' try: limit = int(query_params.get('limit', default_limit)) offset = int(query_params.get('offset', 0)) usernames = query_params.get(USERNAMES_KEY) email = query_params.get(EMAIL_KEY) sort_order = validate_and_get_key(query_params, SORTORDER_KEY, VALID_SORTORDER_VALUE, 'asc') except ValueError: error = { 'detail': 'Values for limit and offset must be positive numbers.', 'source': 'principals', 'status': status.HTTP_400_BAD_REQUEST } errors = {'errors': [error]} return Response(status=status.HTTP_400_BAD_REQUEST, data=errors) previous_offset = 0 if offset - limit > 0: previous_offset = offset - limit if usernames: principals = usernames.split(',') resp = proxy.request_filtered_principals(principals, account=user.account, limit=limit, offset=offset, sort_order=sort_order) usernames_filter = f'&usernames={usernames}' elif email: resp = proxy.request_principals(user.account, email=email, limit=limit, offset=offset, sort_order=sort_order) else: resp = proxy.request_principals(user.account, limit=limit, offset=offset, sort_order=sort_order) status_code = resp.get('status_code') response_data = {} if status_code == status.HTTP_200_OK: data = resp.get('data', []) if isinstance(data, dict): count = data.get('userCount') data = data.get('users') elif isinstance(data, list): count = len(data) else: count = None response_data['meta'] = {'count': count} response_data['links'] = { 'first': f'{path}?limit={limit}&offset=0{usernames_filter}', 'next': f'{path}?limit={limit}&offset={offset + limit}{usernames_filter}', 'previous': f'{path}?limit={limit}&offset={previous_offset}{usernames_filter}', 'last': None, } response_data['data'] = data else: response_data = resp del response_data['status_code'] return Response(status=status_code, data=response_data)