def get_principal(username, account, verify_principal=True): """Get principals from username.""" # First check if principal exist on our side, # if not call BOP to check if user exist in the account. try: principal = Principal.objects.get(username__iexact=username) except Principal.DoesNotExist: if verify_principal: proxy = PrincipalProxy() resp = proxy.request_filtered_principals([username], account) if isinstance(resp, dict) and "errors" in resp: raise Exception( "Dependency error: request to get users from dependent service failed." ) if resp.get("data") == []: key = "detail" message = "No data found for principal with username {}.".format( username) raise serializers.ValidationError({key: _(message)}) # Avoid possible race condition if the user was created while checking BOP principal, created = Principal.objects.get_or_create(username=username) # pylint: disable=unused-variable return principal
def test_request_filtered_principals_empty(self): """Test the call to request filtered principals.""" proxy = PrincipalProxy() result = proxy.request_filtered_principals(principals=[], account="1234") expected = {"status_code": status.HTTP_200_OK, "data": []} self.assertEqual(expected, result)
def test__request_principals_200_count(self): """Test request with 200 and good data including userCount.""" proxy = PrincipalProxy() result = proxy._request_principals( url='http://*****:*****@email.foo', 'first_name': 'test', 'last_name': 'user1', 'is_active': 'true' } user2 = { 'username': '******', 'email': '*****@*****.**', 'first_name': 'test', 'last_name': 'user2', 'is_active': 'true' } expected = { 'data': { "userCount": 2, "users": [user1, user2] }, 'status_code': 200 } self.assertEqual(expected, result)
def test_read_principal_list_account(self, mock_request): """Test that we can handle a request with matching accounts""" url = f'{reverse("principals")}?usernames=test_user&offset=30&sort_order=desc' client = APIClient() proxy = PrincipalProxy() response = client.get(url, **self.headers) mock_request.assert_called_once_with(["test_user"], account=ANY, limit=10, offset=30, options={"sort_order": "desc"}) self.assertEqual(response.status_code, status.HTTP_200_OK) for keyname in ["meta", "links", "data"]: self.assertIn(keyname, response.data) self.assertIsInstance(response.data.get("data"), list) self.assertEqual(response.data.get("meta").get("count"), 1) resp = proxy._process_data(response.data.get("data"), account="1234", account_filter=True, return_id=True) self.assertEqual(len(resp), 1) self.assertEqual(resp[0]["username"], "test_user") self.assertEqual(resp[0]["user_id"], "5678")
def test_read_principal_list_by_email_partial_matching(self, mock_request): """Test that we can handle a request with a partial email address""" url = f'{reverse("principals")}?email=test_use&match_criteria=partial' client = APIClient() proxy = PrincipalProxy() response = client.get(url, **self.headers) self.assertEqual(response.status_code, status.HTTP_200_OK) for keyname in ["meta", "links", "data"]: self.assertIn(keyname, response.data) self.assertIsInstance(response.data.get("data"), list) self.assertEqual(response.data.get("meta").get("count"), 1) resp = proxy._process_data(response.data.get("data"), account="54321", account_filter=False) self.assertEqual(len(resp), 1) mock_request.assert_called_once_with( ANY, input="test_use", limit=10, offset=0, options={ "sort_order": "asc", "status": "enabled", "search_by": "partial_email" }, ) self.assertEqual(resp[0]["username"], "test_user")
def test_request_filtered_principals(self, mock_request): """Test the call to request filtered principals.""" proxy = PrincipalProxy() result = proxy.request_filtered_principals(principals=['test_user'], account='1234') expected = {'status_code': status.HTTP_200_OK, 'data': []} self.assertEqual(expected, result)
def test__request_principals_200_count(self): """Test request with 200 and good data including userCount.""" proxy = PrincipalProxy() result = proxy._request_principals( url="http://*****:*****@email.foo", "first_name": "test", "last_name": "user1", "is_active": "true", } user2 = { "username": "******", "email": "*****@*****.**", "first_name": "test", "last_name": "user2", "is_active": "true", } expected = { "data": { "userCount": 2, "users": [user1, user2] }, "status_code": 200 } self.assertEqual(expected, result)
def to_representation(self, obj): """Convert representation to dictionary object.""" proxy = PrincipalProxy() formatted = super().to_representation(obj) principals = formatted.pop('principals') users = [principal.get('username') for principal in principals] resp = proxy.request_filtered_principals(users, limit=len(users)) if resp.get('status_code') == status.HTTP_200_OK: principals = resp.get('data') formatted['principals'] = principals return formatted
def test__request_principals_200_success(self): """Test request with expected 200 and good data.""" proxy = PrincipalProxy() result = proxy._request_principals( url='http://*****:*****@email.foo', 'first_name': 'test', 'last_name': 'user1' } expected = {'data': [user], 'status_code': 200} self.assertEqual(expected, result)
def test__request_principals_200_success(self): """Test request with expected 200 and good data.""" proxy = PrincipalProxy() result = proxy._request_principals( url="http://*****:*****@email.foo", "first_name": "test", "last_name": "user1", "is_active": "true", } expected = {"data": [user], "status_code": 200} self.assertEqual(expected, result)
def test_read_principal_list_account_fail(self, mock_request): """Test that we can handle a request with matching accounts""" url = f'{reverse("principals")}?usernames=test_user&offset=30' client = APIClient() proxy = PrincipalProxy() response = client.get(url, **self.headers) self.assertEqual(response.status_code, status.HTTP_200_OK) for keyname in ["meta", "links", "data"]: self.assertIn(keyname, response.data) self.assertIsInstance(response.data.get("data"), list) resp = proxy._process_data(response.data.get("data"), account="1234", account_filter=True) self.assertEqual(len(resp), 0) self.assertNotEqual(resp, "test_user")
def test__request_principals_404(self): """Test request with expected 404.""" proxy = PrincipalProxy() result = proxy._request_principals( url="http://localhost:8080/v1/users", method=mocked_requests_get_404_json) expected = { "status_code": 404, "errors": [{ "detail": "Not Found.", "status": 404, "source": "principals" }] } self.assertEqual(expected, result)
def test__request_principals_200_fail(self): """Test request with expected 200 and bad data.""" proxy = PrincipalProxy() result = proxy._request_principals( url='http://localhost:8080/v1/users', method=mocked_requests_get_200_except) expected = { 'status_code': 500, 'errors': [{ 'detail': 'Unexpected error.', 'status': 500, 'source': 'principals' }] } self.assertEqual(expected, result)
def test__request_principals_404(self): """Test request with expected 404.""" proxy = PrincipalProxy() result = proxy._request_principals( url='http://localhost:8080/v1/users', method=mocked_requests_get_404_json) expected = { 'status_code': 404, 'errors': [{ 'detail': 'Not Found.', 'status': 404, 'source': 'principals' }] } self.assertEqual(expected, result)
def test_read_users_with_invalid_admin_only_value(self): """Test that reading user with invalid status value returns 400""" url = f'{reverse("principals")}?admin_only=invalid' client = APIClient() proxy = PrincipalProxy() response = client.get(url, **self.headers) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
def test__request_principals_200_fail(self): """Test request with expected 200 and bad data.""" proxy = PrincipalProxy() result = proxy._request_principals( url="http://localhost:8080/v1/users", method=mocked_requests_get_200_except) expected = { "status_code": 500, "errors": [{ "detail": "Unexpected error.", "status": 500, "source": "principals" }], } self.assertEqual(expected, result)
def test_read_principal_list_account_filter(self, mock_request): """Test that we can handle a request with matching accounts""" url = f'{reverse("principals")}?usernames=test_user&offset=30' client = APIClient() proxy = PrincipalProxy() response = client.get(url, **self.headers) self.assertEqual(response.status_code, status.HTTP_200_OK) for keyname in ['meta', 'links', 'data']: self.assertIn(keyname, response.data) self.assertIsInstance(response.data.get('data'), list) resp = proxy._process_data(response.data.get('data'), account='1234', account_filter=False) self.assertEqual(len(resp), 1) self.assertEqual(resp[0]['username'], 'test_user')
def test_read_principal_list_by_email(self, mock_request): """Test that we can handle a request with an email address""" url = f'{reverse("principals")}[email protected]' client = APIClient() proxy = PrincipalProxy() response = client.get(url, **self.headers) self.assertEqual(response.status_code, status.HTTP_200_OK) for keyname in ["meta", "links", "data"]: self.assertIn(keyname, response.data) self.assertIsInstance(response.data.get("data"), list) self.assertEqual(response.data.get("meta").get("count"), 1) resp = proxy._process_data(response.data.get("data"), account="54321", account_filter=False) self.assertEqual(len(resp), 1) mock_request.assert_called_once_with(ANY, email="*****@*****.**", limit=10, offset=0, sort_order="asc") self.assertEqual(resp[0]["username"], "test_user")
def test_read_principal_list_by_email(self, mock_request): """Test that we can handle a request with an email address""" url = f'{reverse("principals")}[email protected]' client = APIClient() proxy = PrincipalProxy() response = client.get(url, **self.headers) self.assertEqual(response.status_code, status.HTTP_200_OK) for keyname in ['meta', 'links', 'data']: self.assertIn(keyname, response.data) self.assertIsInstance(response.data.get('data'), list) self.assertEqual(response.data.get('meta').get('count'), 1) resp = proxy._process_data(response.data.get('data'), account='54321', account_filter=False) self.assertEqual(len(resp), 1) mock_request.assert_called_once_with(ANY, email='*****@*****.**', limit=10, offset=0, sort_order='asc') self.assertEqual(resp[0]['username'], 'test_user')
def test_read_list_of_admins(self, mock_request): """Test that we can return only org admins within an account""" url = f'{reverse("principals")}?admin_only=true' client = APIClient() proxy = PrincipalProxy() response = client.get(url, **self.headers) self.assertEqual(response.status_code, status.HTTP_200_OK) for keyname in ["meta", "links", "data"]: self.assertIn(keyname, response.data) self.assertIsInstance(response.data.get("data"), list) self.assertEqual(response.data.get("meta").get("count"), "1") mock_request.assert_called_once_with(ANY, limit=10, offset=0, options={ "sort_order": "asc", "status": "enabled", "admin_only": "true" })
# You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <https://www.gnu.org/licenses/>. # """Handler for principal clean up.""" import logging from management.principal.model import Principal from management.principal.proxy import PrincipalProxy from rest_framework import status from tenant_schemas.utils import tenant_context from api.models import Tenant logger = logging.getLogger(__name__) # pylint: disable=invalid-name proxy = PrincipalProxy() # pylint: disable=invalid-name def clean_tenant_principals(tenant): """Check if all the principals in the tenant exist, remove non-existent principals.""" with tenant_context(tenant): removed_principals = [] principals = list(Principal.objects.all()) logger.info("Running clean up on %d principals for tenant %s.", len(principals), tenant.schema_name) for principal in principals: if principal.cross_account: continue logger.debug("Checking for username %s for tenant %s.", principal.username, tenant.schema_name) resp = proxy.request_filtered_principals([principal.username])
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
class GroupViewSet( mixins.CreateModelMixin, mixins.DestroyModelMixin, mixins.ListModelMixin, mixins.RetrieveModelMixin, mixins.UpdateModelMixin, viewsets.GenericViewSet, ): """Group View. A viewset that provides default `create()`, `destroy`, `retrieve()`, and `list()` actions. """ queryset = Group.objects.annotate(principalCount=Count("principals", distinct=True), policyCount=Count("policies", distinct=True)) permission_classes = (GroupAccessPermission, ) lookup_field = "uuid" filter_backends = (filters.DjangoFilterBackend, OrderingFilter) filterset_class = GroupFilter ordering_fields = ("name", "modified", "principalCount", "policyCount") ordering = ("name", ) proxy = PrincipalProxy() def get_queryset(self): """Obtain queryset for requesting user based on access.""" return get_group_queryset(self.request) def get_serializer_class(self): """Get serializer based on route.""" if "principals" in self.request.path: return GroupPrincipalInputSerializer if ROLES_KEY in self.request.path and self.request.method == "GET": return GroupRoleSerializerOut if ROLES_KEY in self.request.path: return GroupRoleSerializerIn if self.request.method in ("POST", "PUT"): return GroupInputSerializer if self.request.path.endswith( "groups/") and self.request.method == "GET": return GroupInputSerializer return GroupSerializer def protect_default_groups(self, action): """Deny modifications on platform_default groups.""" group = self.get_object() if group.platform_default: key = "group" message = "{} cannot be performed on platform default groups.".format( action.upper()) error = {key: [_(message)]} raise serializers.ValidationError(error) def create(self, request, *args, **kwargs): """Create a group. @api {post} /api/v1/groups/ Create a group @apiName createGroup @apiGroup Group @apiVersion 1.0.0 @apiDescription Create a Group @apiHeader {String} token User authorization token @apiParam (Request Body) {String} name Group name @apiParamExample {json} Request Body: { "name": "GroupA" } @apiSuccess {String} uuid Group unique identifier @apiSuccess {String} name Group name @apiSuccessExample {json} Success-Response: HTTP/1.1 201 CREATED { "uuid": "16fd2706-8baf-433b-82eb-8c7fada847da", "name": "GroupA", "principals": [] } """ return super().create(request=request, args=args, kwargs=kwargs) def list(self, request, *args, **kwargs): """Obtain the list of groups for the tenant. @api {get} /api/v1/groups/ Obtain a list of groups @apiName getGroups @apiGroup Group @apiVersion 1.0.0 @apiDescription Obtain a list of groups @apiHeader {String} token User authorization token @apiParam (Query) {String} name Filter by group name. @apiParam (Query) {array} uuid Filter by comma separated list of uuids @apiParam (Query) {Number} offset Parameter for selecting the start of data (default is 0). @apiParam (Query) {Number} limit Parameter for selecting the amount of data (default is 10). @apiSuccess {Object} meta The metadata for pagination. @apiSuccess {Object} links The object containing links of results. @apiSuccess {Object[]} data The array of results. @apiSuccessExample {json} Success-Response: HTTP/1.1 200 OK { 'meta': { 'count': 2 } 'links': { 'first': /api/v1/groups/?offset=0&limit=10, 'next': None, 'previous': None, 'last': /api/v1/groups/?offset=0&limit=10 }, 'data': [ { "uuid": "16fd2706-8baf-433b-82eb-8c7fada847da", "name": "GroupA" }, { "uuid": "20ecdcd0-397c-4ede-8940-f3439bf40212", "name": "GroupB" } ] } """ return super().list(request=request, args=args, kwargs=kwargs) def retrieve(self, request, *args, **kwargs): """Get a group. @api {get} /api/v1/groups/:uuid Get a group @apiName getGroup @apiGroup Group @apiVersion 1.0.0 @apiDescription Get 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 @apiSuccess {Array} roles Array of roles @apiSuccessExample {json} Success-Response: HTTP/1.1 200 OK { "uuid": "16fd2706-8baf-433b-82eb-8c7fada847da", "name": "GroupA", "principals": [ { "username": "******" } ], "roles": [ { "name": "RoleA", "uuid": "4df211e0-2d88-49a4-8802-728630224d15" } ] } """ validate_uuid(kwargs.get("uuid"), "group uuid validation") return super().retrieve(request=request, args=args, kwargs=kwargs) def destroy(self, request, *args, **kwargs): """Delete a group. @api {delete} /api/v1/groups/:uuid Delete a group @apiName deleteGroup @apiGroup Group @apiVersion 1.0.0 @apiDescription Delete a group @apiHeader {String} token User authorization token @apiParam (Path) {String} uuid Group unique identifier @apiSuccessExample {json} Success-Response: HTTP/1.1 204 NO CONTENT """ validate_uuid(kwargs.get("uuid"), "group uuid validation") self.protect_default_groups("delete") return super().destroy(request=request, args=args, kwargs=kwargs) def update(self, request, *args, **kwargs): """Update a group. @api {post} /api/v1/groups/:uuid Update a group @apiName updateGroup @apiGroup Group @apiVersion 1.0.0 @apiDescription Update 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 @apiSuccessExample {json} Success-Response: HTTP/1.1 200 OK { "uuid": "16fd2706-8baf-433b-82eb-8c7fada847da", "name": "GroupA" } """ validate_uuid(kwargs.get("uuid"), "group uuid validation") self.protect_default_groups("update") return super().update(request=request, args=args, kwargs=kwargs) def add_principals(self, group, principals, account): """Process list of principals and add them to the group.""" users = [principal.get("username") for principal in principals] resp = self.proxy.request_filtered_principals(users, account, limit=len(users)) if "errors" in resp: return resp for item in resp.get("data", []): username = item["username"] try: principal = Principal.objects.get(username__iexact=username) except Principal.DoesNotExist: principal = Principal.objects.create(username=username) logger.info("Created new principal %s for account_id %s.", username, account) group.principals.add(principal) group.save() return group def remove_principals(self, group, principals, account): """Process list of principals and remove them from the group.""" for username in principals: try: principal = Principal.objects.get(username__iexact=username) except Principal.DoesNotExist: logger.info("No principal %s found for account %s.", username, account) if principal: group.principals.remove(principal) group.save() return group @action(detail=True, methods=["get", "post", "delete"]) 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 @action(detail=True, methods=["get", "post", "delete"]) def roles(self, request, uuid=None): """Get, add or remove roles from a group.""" """ @api {get} /api/v1/groups/:uuid/roles/ Get roles for a group @apiName getRoles @apiGroup Group @apiVersion 1.0.0 @apiDescription Get roles for a group @apiHeader {String} token User authorization token @apiParam (Path) {String} id Group unique identifier. @apiParam (Query) {String} order_by Determine ordering of returned roles. @apiSuccess {Array} data Array of roles @apiSuccessExample {json} Success-Response: HTTP/1.1 200 OK { "data": [ { "name": "RoleA", "uuid": "4df211e0-2d88-49a4-8802-728630224d15", "description": "RoleA Description", "policyCount: 0, "applications": [], "system": false, "platform_default": false } ] } """ """ @api {post} /api/v1/groups/:uuid/roles/ Add roles to a group @apiName addRoles @apiGroup Group @apiVersion 1.0.0 @apiDescription Add roles to a group @apiHeader {String} token User authorization token @apiParam (Path) {String} id Group unique identifier @apiParam (Request Body) {Array} roles Array of role UUIDs @apiParamExample {json} Request Body: { "roles": [ "4df211e0-2d88-49a4-8802-728630224d15" ] } @apiSuccess {String} uuid Group unique identifier @apiSuccess {String} name Group name @apiSuccess {Array} roles Array of roles @apiSuccessExample {json} Success-Response: HTTP/1.1 200 OK { "data": [ { "name": "RoleA", "uuid": "4df211e0-2d88-49a4-8802-728630224d15", "description": "RoleA Description", "policyCount: 0, "applications": [], "system": false, "platform_default": false } ] } """ """ @api {delete} /api/v1/groups/:uuid/roles/ Remove roles from group @apiName removeRoles @apiGroup Group @apiVersion 1.0.0 @apiDescription Remove roles from a group @apiHeader {String} token User authorization token @apiParam (Path) {String} id Group unique identifier @apiParam (Query) {String} roles List of comma separated role UUIDs @apiSuccessExample {json} Success-Response: HTTP/1.1 204 NO CONTENT """ roles = [] validate_uuid(uuid, "group uuid validation") group = self.get_object() if request.method == "POST": serializer = GroupRoleSerializerIn(data=request.data) if serializer.is_valid(raise_exception=True): roles = request.data.pop(ROLES_KEY, []) add_roles(group, roles) set_system_flag_post_update(group) response_data = GroupRoleSerializerIn(group) elif request.method == "GET": serialized_roles = self.obtain_roles(request, group) page = self.paginate_queryset(serialized_roles) serializer = self.get_serializer(page, many=True) return self.get_paginated_response(serializer.data) else: if ROLES_KEY not in request.query_params: key = "detail" message = "Query parameter {} is required.".format(ROLES_KEY) raise serializers.ValidationError({key: _(message)}) role_ids = request.query_params.get(ROLES_KEY, "").split(",") serializer = GroupRoleSerializerIn(data={"roles": role_ids}) if serializer.is_valid(raise_exception=True): remove_roles(group, role_ids) set_system_flag_post_update(group) return Response(status=status.HTTP_204_NO_CONTENT) return Response(status=status.HTTP_200_OK, data=response_data.data) def order_queryset(self, queryset, valid_fields, order_field): """Return queryset ordered according to order_by query param.""" all_valid_fields = valid_fields + [ "-" + field for field in valid_fields ] if order_field in all_valid_fields: return queryset.order_by(order_field) else: key = "detail" message = f"{order_field} is not a valid ordering field. Valid values are {all_valid_fields}" raise serializers.ValidationError({key: _(message)}) def filtered_roles(self, roles, request): """Return filtered roles for group from query params.""" role_filters = self.filters_from_params(VALID_GROUP_ROLE_FILTERS, "role", request) return roles.filter(**role_filters) def filtered_principals(self, group, request): """Return filtered principals for group from query params.""" principal_filters = self.filters_from_params( VALID_GROUP_PRINCIPAL_FILTERS, "principal", request) return group.principals.filter(**principal_filters) def filters_from_params(self, valid_filters, model_name, request): """Build filters from group params.""" filters = {} for param_name, param_value in request.query_params.items(): if param_name in valid_filters: attr_filter_name = param_name.replace(f"{model_name}_", "") filters[f"{attr_filter_name}__icontains"] = param_value return filters 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 obtain_roles_with_exclusion(self, request, group): """Obtain the queryset for roles based on scope.""" scope = request.query_params.get("scope", "account") # Get roles in principal or account scope roles = (get_object_principal_queryset(request, scope, Role) if scope == "principal" else Role.objects.all().prefetch_related("access")) # Exclude the roles in the group roles_for_group = group.roles().values("uuid") return roles.exclude(uuid__in=roles_for_group)
class GroupViewSet(mixins.CreateModelMixin, mixins.DestroyModelMixin, mixins.ListModelMixin, mixins.RetrieveModelMixin, mixins.UpdateModelMixin, viewsets.GenericViewSet): """Group View. A viewset that provides default `create()`, `destroy`, `retrieve()`, and `list()` actions. """ queryset = Group.objects.annotate(principalCount=Count('principals', distinct=True), policyCount=Count('policies', distinct=True)) permission_classes = (GroupAccessPermission,) lookup_field = 'uuid' filter_backends = (filters.DjangoFilterBackend, OrderingFilter) filterset_class = GroupFilter ordering_fields = ('name', 'modified', 'principalCount', 'policyCount') ordering = ('name',) proxy = PrincipalProxy() def get_queryset(self): """Obtain queryset for requesting user based on access.""" return get_group_queryset(self.request) def get_serializer_class(self): """Get serializer based on route.""" if 'principals' in self.request.path: return GroupPrincipalInputSerializer if ROLES_KEY in self.request.path and self.request.method == 'GET': return GroupRoleSerializerOut if ROLES_KEY in self.request.path: return GroupRoleSerializerIn if self.request.method in ('POST', 'PUT'): return GroupInputSerializer if self.request.path.endswith('groups/') and self.request.method == 'GET': return GroupInputSerializer return GroupSerializer def protect_default_groups(self, action): """Deny modifications on platform_default groups.""" group = self.get_object() if group.platform_default: key = 'group' message = '{} cannot be performed on platform default groups.'.format(action.upper()) error = { key: [_(message)] } raise serializers.ValidationError(error) def create(self, request, *args, **kwargs): """Create a group. @api {post} /api/v1/groups/ Create a group @apiName createGroup @apiGroup Group @apiVersion 1.0.0 @apiDescription Create a Group @apiHeader {String} token User authorization token @apiParam (Request Body) {String} name Group name @apiParamExample {json} Request Body: { "name": "GroupA" } @apiSuccess {String} uuid Group unique identifier @apiSuccess {String} name Group name @apiSuccessExample {json} Success-Response: HTTP/1.1 201 CREATED { "uuid": "16fd2706-8baf-433b-82eb-8c7fada847da", "name": "GroupA", "principals": [] } """ return super().create(request=request, args=args, kwargs=kwargs) def list(self, request, *args, **kwargs): """Obtain the list of groups for the tenant. @api {get} /api/v1/groups/ Obtain a list of groups @apiName getGroups @apiGroup Group @apiVersion 1.0.0 @apiDescription Obtain a list of groups @apiHeader {String} token User authorization token @apiParam (Query) {String} name Filter by group name. @apiParam (Query) {Number} offset Parameter for selecting the start of data (default is 0). @apiParam (Query) {Number} limit Parameter for selecting the amount of data (default is 10). @apiSuccess {Object} meta The metadata for pagination. @apiSuccess {Object} links The object containing links of results. @apiSuccess {Object[]} data The array of results. @apiSuccessExample {json} Success-Response: HTTP/1.1 200 OK { 'meta': { 'count': 2 } 'links': { 'first': /api/v1/groups/?offset=0&limit=10, 'next': None, 'previous': None, 'last': /api/v1/groups/?offset=0&limit=10 }, 'data': [ { "uuid": "16fd2706-8baf-433b-82eb-8c7fada847da", "name": "GroupA" }, { "uuid": "20ecdcd0-397c-4ede-8940-f3439bf40212", "name": "GroupB" } ] } """ return super().list(request=request, args=args, kwargs=kwargs) def retrieve(self, request, *args, **kwargs): """Get a group. @api {get} /api/v1/groups/:uuid Get a group @apiName getGroup @apiGroup Group @apiVersion 1.0.0 @apiDescription Get 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 @apiSuccess {Array} roles Array of roles @apiSuccessExample {json} Success-Response: HTTP/1.1 200 OK { "uuid": "16fd2706-8baf-433b-82eb-8c7fada847da", "name": "GroupA", "principals": [ { "username": "******" } ], "roles": [ { "name": "RoleA", "uuid": "4df211e0-2d88-49a4-8802-728630224d15" } ] } """ return super().retrieve(request=request, args=args, kwargs=kwargs) def destroy(self, request, *args, **kwargs): """Delete a group. @api {delete} /api/v1/groups/:uuid Delete a group @apiName deleteGroup @apiGroup Group @apiVersion 1.0.0 @apiDescription Delete a group @apiHeader {String} token User authorization token @apiParam (Path) {String} uuid Group unique identifier @apiSuccessExample {json} Success-Response: HTTP/1.1 204 NO CONTENT """ self.protect_default_groups('delete') return super().destroy(request=request, args=args, kwargs=kwargs) def update(self, request, *args, **kwargs): """Update a group. @api {post} /api/v1/groups/:uuid Update a group @apiName updateGroup @apiGroup Group @apiVersion 1.0.0 @apiDescription Update 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 @apiSuccessExample {json} Success-Response: HTTP/1.1 200 OK { "uuid": "16fd2706-8baf-433b-82eb-8c7fada847da", "name": "GroupA" } """ self.protect_default_groups('update') return super().update(request=request, args=args, kwargs=kwargs) def add_principals(self, group, principals, account): """Process list of principals and add them to the group.""" users = [principal.get('username') for principal in principals] resp = self.proxy.request_filtered_principals(users, account, limit=len(users)) if 'errors' in resp: return resp for item in resp.get('data', []): username = item['username'] try: principal = Principal.objects.get(username__iexact=username) except Principal.DoesNotExist: principal = Principal.objects.create(username=username) logger.info('Created new principal %s for account_id %s.', username, account) group.principals.add(principal) group.save() return group def remove_principals(self, group, principals, account): """Process list of principals and remove them from the group.""" for username in principals: try: principal = Principal.objects.get(username__iexact=username) except Principal.DoesNotExist: logger.info('No principal %s found for account %s.', username, account) if principal: group.principals.remove(principal) group.save() return group @action(detail=True, methods=['post', 'delete']) def principals(self, request, uuid=None): """Add or remove principals from a group.""" """ @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 = [] 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) return Response(status=status.HTTP_200_OK, data=output.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) return Response(status=status.HTTP_204_NO_CONTENT) @action(detail=True, methods=['get', 'post', 'delete']) def roles(self, request, uuid=None): """Get, add or remove roles from a group.""" """ @api {get} /api/v1/groups/:uuid/roles/ Get roles for a group @apiName getRoles @apiGroup Group @apiVersion 1.0.0 @apiDescription Get roles for a group @apiHeader {String} token User authorization token @apiParam (Path) {String} id Group unique identifier. @apiSuccess {Array} data Array of roles @apiSuccessExample {json} Success-Response: HTTP/1.1 200 OK { "data": [ { "name": "RoleA", "uuid": "4df211e0-2d88-49a4-8802-728630224d15", "description": "RoleA Description", "policyCount: 0, "applications": [], "system": false, "platform_default": false } ] } """ """ @api {post} /api/v1/groups/:uuid/roles/ Add roles to a group @apiName addRoles @apiGroup Group @apiVersion 1.0.0 @apiDescription Add roles to a group @apiHeader {String} token User authorization token @apiParam (Path) {String} id Group unique identifier @apiParam (Request Body) {Array} roles Array of role UUIDs @apiParamExample {json} Request Body: { "roles": [ "4df211e0-2d88-49a4-8802-728630224d15" ] } @apiSuccess {String} uuid Group unique identifier @apiSuccess {String} name Group name @apiSuccess {Array} roles Array of roles @apiSuccessExample {json} Success-Response: HTTP/1.1 200 OK { "data": [ { "name": "RoleA", "uuid": "4df211e0-2d88-49a4-8802-728630224d15", "description": "RoleA Description", "policyCount: 0, "applications": [], "system": false, "platform_default": false } ] } """ """ @api {delete} /api/v1/groups/:uuid/roles/ Remove roles from group @apiName removeRoles @apiGroup Group @apiVersion 1.0.0 @apiDescription Remove roles from a group @apiHeader {String} token User authorization token @apiParam (Path) {String} id Group unique identifier @apiParam (Query) {String} roles List of comma separated role UUIDs @apiSuccessExample {json} Success-Response: HTTP/1.1 204 NO CONTENT """ roles = [] group = self.get_object() if request.method == 'POST': serializer = GroupRoleSerializerIn(data=request.data) if serializer.is_valid(raise_exception=True): roles = request.data.pop(ROLES_KEY, []) add_roles(group, roles) set_system_flag_post_update(group) response_data = GroupRoleSerializerIn(group) elif request.method == 'GET': serialized_roles = self.obtain_roles(request, group) page = self.paginate_queryset(serialized_roles) serializer = self.get_serializer(page, many=True) return self.get_paginated_response(serializer.data) else: if ROLES_KEY not in request.query_params: key = 'detail' message = 'Query parameter {} is required.'.format(ROLES_KEY) raise serializers.ValidationError({key: _(message)}) role_ids = request.query_params.get(ROLES_KEY, '').split(',') serializer = GroupRoleSerializerIn(data={'roles': role_ids}) if serializer.is_valid(raise_exception=True): remove_roles(group, role_ids) set_system_flag_post_update(group) return Response(status=status.HTTP_204_NO_CONTENT) return Response(status=status.HTTP_200_OK, data=response_data.data) def obtain_roles(self, request, group): """Obtain roles based on request, supports exclusion.""" exclude = self.validate_and_get_exclude_key(request.query_params) roles = (group.roles_with_access() if exclude == 'false' else self.obtain_roles_with_exclusion(request, group)) return [RoleMinimumSerializer(role).data for role in roles] def obtain_roles_with_exclusion(self, request, group): """Obtain the queryset for roles based on scope.""" scope = request.query_params.get('scope', 'account') # Get roles in principal or account scope roles = (get_object_principal_queryset(request, scope, Role) if scope == 'principal' else Role.objects.all().prefetch_related('access')) # Exclude the roles in the group roles_for_group = group.roles().values('uuid') return roles.exclude(uuid__in=roles_for_group) def validate_and_get_exclude_key(self, params): """Validate the exclude key.""" exclude = params.get(EXCLUDE_KEY, 'false').lower() if exclude not in VALID_EXCLUDE_VALUES: key = 'detail' message = '{} query parameter value {} is invalid. booleans are valid inputs.'.format( EXCLUDE_KEY, exclude) raise serializers.ValidationError({key: _(message)}) return exclude
class GroupViewSet(mixins.CreateModelMixin, mixins.DestroyModelMixin, mixins.ListModelMixin, mixins.RetrieveModelMixin, mixins.UpdateModelMixin, viewsets.GenericViewSet): """Group View. A viewset that provides default `create()`, `destroy`, `retrieve()`, and `list()` actions. """ queryset = Group.objects.annotate(principalCount=Count('principals', distinct=True), policyCount=Count('policies', distinct=True)) permission_classes = (GroupAccessPermission, ) lookup_field = 'uuid' filter_backends = (filters.DjangoFilterBackend, OrderingFilter) filterset_class = GroupFilter ordering_fields = ('name', 'modified', 'principalCount', 'policyCount') ordering = ('name', ) proxy = PrincipalProxy() def get_queryset(self): """Obtain queryset for requesting user based on access.""" return get_group_queryset(self.request) def get_serializer_class(self): """Get serializer based on route.""" if 'principals' in self.request.path: return GroupPrincipalInputSerializer if self.request.method in ('POST', 'PUT'): return GroupInputSerializer if self.request.path.endswith( 'groups/') and self.request.method == 'GET': return GroupInputSerializer return GroupSerializer def create(self, request, *args, **kwargs): """Create a group. @api {post} /api/v1/groups/ Create a group @apiName createGroup @apiGroup Group @apiVersion 1.0.0 @apiDescription Create a Group @apiHeader {String} token User authorization token @apiParam (Request Body) {String} name Group name @apiParamExample {json} Request Body: { "name": "GroupA" } @apiSuccess {String} uuid Group unique identifier @apiSuccess {String} name Group name @apiSuccessExample {json} Success-Response: HTTP/1.1 201 CREATED { "uuid": "16fd2706-8baf-433b-82eb-8c7fada847da", "name": "GroupA", "principals": [] } """ return super().create(request=request, args=args, kwargs=kwargs) def list(self, request, *args, **kwargs): """Obtain the list of groups for the tenant. @api {get} /api/v1/groups/ Obtain a list of groups @apiName getGroups @apiGroup Group @apiVersion 1.0.0 @apiDescription Obtain a list of groups @apiHeader {String} token User authorization token @apiParam (Query) {String} name Filter by group name. @apiParam (Query) {Number} offset Parameter for selecting the start of data (default is 0). @apiParam (Query) {Number} limit Parameter for selecting the amount of data (default is 10). @apiSuccess {Object} meta The metadata for pagination. @apiSuccess {Object} links The object containing links of results. @apiSuccess {Object[]} data The array of results. @apiSuccessExample {json} Success-Response: HTTP/1.1 200 OK { 'meta': { 'count': 2 } 'links': { 'first': /api/v1/groups/?offset=0&limit=10, 'next': None, 'previous': None, 'last': /api/v1/groups/?offset=0&limit=10 }, 'data': [ { "uuid": "16fd2706-8baf-433b-82eb-8c7fada847da", "name": "GroupA" }, { "uuid": "20ecdcd0-397c-4ede-8940-f3439bf40212", "name": "GroupB" } ] } """ return super().list(request=request, args=args, kwargs=kwargs) def retrieve(self, request, *args, **kwargs): """Get a group. @api {get} /api/v1/groups/:uuid Get a group @apiName getGroup @apiGroup Group @apiVersion 1.0.0 @apiDescription Get 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 { "uuid": "16fd2706-8baf-433b-82eb-8c7fada847da", "name": "GroupA", "principals": [ { "username": "******" } ] } """ return super().retrieve(request=request, args=args, kwargs=kwargs) def destroy(self, request, *args, **kwargs): """Delete a group. @api {delete} /api/v1/groups/:uuid Delete a group @apiName deleteGroup @apiGroup Group @apiVersion 1.0.0 @apiDescription Delete a group @apiHeader {String} token User authorization token @apiParam (Path) {String} uuid Group unique identifier @apiSuccessExample {json} Success-Response: HTTP/1.1 204 NO CONTENT """ return super().destroy(request=request, args=args, kwargs=kwargs) def update(self, request, *args, **kwargs): """Update a group. @api {post} /api/v1/groups/:uuid Update a group @apiName updateGroup @apiGroup Group @apiVersion 1.0.0 @apiDescription Update 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 @apiSuccessExample {json} Success-Response: HTTP/1.1 200 OK { "uuid": "16fd2706-8baf-433b-82eb-8c7fada847da", "name": "GroupA" } """ return super().update(request=request, args=args, kwargs=kwargs) def add_principals(self, group, principals, account): """Process list of principals and add them to the group.""" users = [principal.get('username') for principal in principals] resp = self.proxy.request_filtered_principals(users, account, limit=len(users)) for item in resp.get('data', []): username = item['username'] try: principal = Principal.objects.get(username__iexact=username) except Principal.DoesNotExist: principal = Principal.objects.create(username=username) logger.info('Created new principal %s for account_id %s.', username, account) group.principals.add(principal) group.save() return group def remove_principals(self, group, principals, account): """Process list of principals and remove them from the group.""" for username in principals: try: principal = Principal.objects.get(username__iexact=username) except Principal.DoesNotExist: logger.info('No principal %s found for account %s.', username, account) if principal: group.principals.remove(principal) group.save() return group @action(detail=True, methods=['post', 'delete']) def principals(self, request, uuid=None): """Add or remove principals from a group. @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 = [] 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') group = self.add_principals(group, principals, account) output = GroupSerializer(group) return Response(status=status.HTTP_200_OK, data=output.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) return Response(status=status.HTTP_204_NO_CONTENT)
def test_request_principals(self, mock_request): """Test the call to request principals.""" proxy = PrincipalProxy() result = proxy.request_principals(account="1234", limit=20, offset=10) expected = {"status_code": status.HTTP_200_OK, "data": []} self.assertEqual(expected, result)
from rest_framework.serializers import ValidationError from tenant_schemas.utils import tenant_context from api.cross_access.access_control import CrossAccountRequestAccessPermission from api.cross_access.serializer import CrossAccountRequestDetailSerializer, CrossAccountRequestSerializer from api.models import CrossAccountRequest, Tenant from api.serializers import create_schema_name QUERY_BY_KEY = "query_by" ACCOUNT = "target_account" USER_ID = "user_id" VALID_QUERY_BY_KEY = [ACCOUNT, USER_ID] PARAMS_FOR_CREATION = ["target_account", "start_date", "end_date", "roles"] VALID_PATCH_FIELDS = ["start_date", "end_date", "roles", "status"] PROXY = PrincipalProxy() class CrossAccountRequestFilter(filters.FilterSet): """Filter for cross account request.""" def account_filter(self, queryset, field, values): """Filter to lookup requests by target_account.""" accounts = values.split(",") return queryset.filter(target_account__in=accounts) def approved_filter(self, queryset, field, value): """Filter to lookup requests by status of approved.""" if value: return queryset.filter(status="approved").filter( start_date__lt=timezone.now(), end_date__gt=timezone.now()) return queryset