Exemple #1
0
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
Exemple #2
0
 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)
Exemple #3
0
 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)
Exemple #4
0
    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")
Exemple #5
0
    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")
Exemple #6
0
 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)
Exemple #7
0
 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
Exemple #9
0
 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)
Exemple #10
0
 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)
Exemple #11
0
    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")
Exemple #12
0
 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)
Exemple #13
0
 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)
Exemple #14
0
 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)
Exemple #15
0
 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)
Exemple #16
0
 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)
Exemple #17
0
    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')
Exemple #18
0
    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")
Exemple #19
0
    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')
Exemple #20
0
 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"
                                          })
Exemple #21
0
# 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])
Exemple #22
0
    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
Exemple #23
0
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)
Exemple #24
0
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
Exemple #25
0
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)
Exemple #26
0
 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)
Exemple #27
0
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