Exemple #1
0
    def resolve_permissions(
        self, user, project_id: fields.Integer(minimum=1)
    ) -> fields.List(fields.String()):
        permissions = (UserPermissionLinker.query.filter_by(
            project_id=project_id, user_id=user.id).join(
                UserPermissionLinker.permission).with_entities(
                    Permission.slug).all())

        permissions = list(map(lambda x: x.slug, permissions))

        return permissions
Exemple #2
0
 class Schema(object):
     users = fields.List(CONCISE_USER_SCHEMA,
                         description='The individual users in a group.',
                         io='r')
     roles = fields.Custom(
         ROLE_MAP_SCHEMA,
         description='The role(s) that all members of the group possess',
         attribute='roles',
         formatter=role_list_as_map,
         default=[],
         io='r',
     )
 class Schema(object):
     name = fields.String(description='The unique name of the resource.')
     resourceType = fields.Custom(
         fields.String(),
         attribute='resource_type',
         converter=None,
         formatter=lambda rsc_type: rsc_type.name.name,
         title='resourceType',
         description='The string representation of the resource type.',
         io='r',
     )
     users = fields.List(
         CONCISE_USER_SCHEMA,
         description=
         'The user(s) that hold one or more roles for this resource.',
         io='r',
     )
     groups = fields.List(
         CONCISE_GROUP_SCHEMA,
         description=
         'The group(s) that hold one or more roles for this resource.',
         io='r',
     )
 class Schema(object):
     permissions = fields.List(
         PERMISSION_SCHEMA,
         title='Role Permissions',
         description='The permissions the role has.',
     )
     resourceType = fields.Custom(
         fields.String(),
         attribute='resource_type',
         converter=None,
         formatter=lambda rsc_type: rsc_type.name.name,
         title='resourceType',
         description='The resource type this role is associated with.',
         io='r',
     )
Exemple #5
0
 def session_timed_log(
     self, session_uuid: fields.UUID()) -> fields.List(fields.String()):
     session = Session.query.filter(Session.uuid == session_uuid).first()
     session_base_path = path.session_logs + session.start.date().isoformat().replace('-', '') + '/' + \
         session.user.username + '_' + session.host.hostname + '_' + \
         session.start.time().isoformat().split('.')[0].replace(':', '') + '_' + \
         session_uuid
     session_log_path = session_base_path + '.log'
     session_timer_path = session_base_path + '.timer'
     results = []
     session_time = 0.
     session_seek = 0
     with open(session_log_path, 'rb') as session_log:
         with open(session_timer_path, 'r') as session_timer:
             session_data = session_log.read()
             # todo improve this (use csv reader?)
             lines = session_timer.readlines()
             for l in lines:
                 rel_time, rel_seek = l.split(' ')
                 rel_time = float(rel_time)
                 rel_seek = int(rel_seek)
                 session_time += rel_time
                 current_seek = session_seek
                 session_seek += rel_seek
                 results.append({
                     'session_time':
                     session_time,
                     'session_seek':
                     session_seek,
                     'session_size':
                     len(session_data),
                     'data_size':
                     rel_seek,
                     'data':
                     base64.b64encode(
                         session_data[current_seek:session_seek]).decode(
                             'utf-8', 'strict')
                 })
     return results
Exemple #6
0
class QueryPolicyResource(PrincipalResource):

    resource = Relation('resource', io='r')

    class Meta(object):
        manager = principals(QueryPolicyManager)
        model = QueryPolicy
        natural_key = 'name'
        excluded_fields = ('id', )
        id_attribute = 'resource_id'

        permissions = {'view_resource': 'view_resource'}

        filters = {'name': True, 'description': True}

    class Schema(object):
        name = fields.String(
            description=
            'A unique human-readable name to denote the query policy.')

        description = fields.String(
            description=
            'A description of what data the query policy is governing the access to.'
        )

        policyFilters = POLICY_FILTERS_SCHEMA

        resource = fields.ItemUri(
            'web.server.api.permission_api_models.BackendResource',
            attribute='resource_id',
        )

    # pylint: disable=R0201
    # pylint: disable=E1101
    # Flask Potion does not allow class methods.

    @Route.GET(
        '/enabled_dimensions',
        rel='getAuthorizableDimensions',
        response_schema=fields.List(
            fields.String(
                description='A dimension for which authorization is enabled.',
                enum=CONFIG_FILTERS.AUTHORIZABLE_DIMENSIONS,
            ),
            description=
            'The list of dimensions for which authorization filters will have effect.',
        ),
    )
    def get_authorizable_dimensions(self):
        return CONFIG_FILTERS.AUTHORIZABLE_DIMENSIONS

    @Route.GET(
        '/dimensions',
        rel='getAllDimensions',
        response_schema=fields.List(
            fields.String(description='A dimension in Druid.',
                          enum=DIMENSIONS),
            description='The list of all Druid dimensions.',
        ),
    )
    def get_all_dimensions(self):
        return DIMENSIONS
Exemple #7
0
from web.server.api.api_models import PrincipalResource
from web.server.routes.views.query_policy import QueryPolicyManager
from web.server.security.permissions import principals

EMPTY_LIST = []

CONFIG_FILTERS = current_app.zen_config.filters
DIMENSIONS = list(
    current_app.druid_context.dimension_values_lookup.get_dimension_value_map(
        False).keys())

SPECIFIC_VALUES_SCHEMA = fields.List(
    fields.String(description='An individual dimension value.'),
    nullable=True,
    default=EMPTY_LIST,
    description=
    'A list of dimension values that the policy holder will be allowed to or prevented '
    'from querying on. To allow the user to query against all values, you would omit '
    'this field and instead set `allowAllValues` to true.',
)

ALL_VALUES_SCHEMA = fields.Boolean(
    description=
    'Set to true if the policy holder should be allowed to query across ALL values '
    'for the specified dimension.',
    default=False,
    nullable=True,
)

DIMENSION_VALUES_SCHEMA = fields.Object({
    'excludeValues': SPECIFIC_VALUES_SCHEMA,
Exemple #8
0
    fields.String(
        description='The string representation of the resource type.'),
}

ROLE_NAME_SCHEMA = fields.String(
    title='roleName',
    description=
    'The string representation of a role name. (e.g. `dashboard_admin`)',
)

# The schema for a mapping of resource names to role names
# Structure should be
# { <resourceName>: [<roleName>, <otherRoleName>], <otherResourceName>: [<roleName>], ... }
RESOURCE_MAP_SCHEMA = fields.Object(
    properties=fields.List(
        ROLE_NAME_SCHEMA,
        description='A list of all the roles held for a given resource.',
    ),
    pattern_properties={
        fields.String(title='resourceName',
                      description='An individual resource.'):
        fields.List(ROLE_NAME_SCHEMA)
    },
    nullable=False,
    description='A mapping of resource names to role names. ',
)

# The schema representing the role assignments for an individual resource type
# Structure should be
# {
#   'resources': {
#     <resourceName>: [<roleName>, <otherRoleName>],
DETAILED_DASHBOARD_SCHEMA = alchemy_fields.InlineModel(
    DASHBOARD_DETAILED_FIELDS, model=Dashboard, description='The dashboard model.'
)

USER_URI_SCHEMA = fields.ItemUri('web.server.api.user_api_models.UserResource')

HISTORY_CHANGE_SCHEMA = generate_history_record_schema(
    fields.ItemUri(
        'web.server.api.dashboard_api_models.DashboardResource', attribute='object_id'
    ),
    'dashboard',
)

DASHBOARD_CHANGES_SCHEMA = fields.List(
    HISTORY_CHANGE_SCHEMA, description='Dashboard history records'
)

# Schemas for properties that are never sent by the user but must be stubbed
# out to make flask potion happy.
UNUSED_LAST_MODIFIED_SCHEMA = fields.DateTimeString(
    description='The last time the dashboard was modified.',
    attribute='last_modified',
    io='r',
)

UNUSED_TOTAL_VIEWS_SCHEMA = fields.Integer(
    description='The total number of times the dashboard has been viewed.',
    minimum=0,
    attribute='total_views',
    io='r',
Exemple #10
0
 def genres(
         self
 ) -> fields.List(fields.String, description="A list of genres"):
     return ['biography', 'history', 'essay', 'law', 'philosophy']
Exemple #11
0
 def session_by_command(self, cmd: fields.String(),
                        **kwargs) -> fields.List(fields.Inline('self')):
     return Session.query.join(Command).filter(
         Command.cmd.like('%{0}%'.format(cmd)))
class RoleResource(PrincipalResource):
    '''The potion class for performing CRUD operations on the `Role` class.
    '''

    resourceType = Relation('resource-type', attribute='resource_type', io='r')

    class Meta(object):
        model = Role
        natural_key = 'name'

        # Read Permissions are the defaults as defined in
        # `web.server.security.permissions.PERMISSION_DEFAULTS`
        #
        # Create, Update and Delete Permissions are enforced by the
        # Signal Handlers installed when the API for this Resource
        # are initialized in `web.server.security.signal_handlers.py`

        filters = {
            'name': True,
            'resourceType': {
                None: ResourceTypeFilter,
                'eq': ResourceTypeFilter
            },
        }

    class Schema(object):
        permissions = fields.List(
            PERMISSION_SCHEMA,
            title='Role Permissions',
            description='The permissions the role has.',
        )
        resourceType = fields.Custom(
            fields.String(),
            attribute='resource_type',
            converter=None,
            formatter=lambda rsc_type: rsc_type.name.name,
            title='resourceType',
            description='The resource type this role is associated with.',
            io='r',
        )

    # pylint: disable=E1101

    @ItemRoute.PATCH(
        '/permissions',
        title='Update Role Permissions',
        description=
        'Updates the role\'s permissions with the values specified.',
        rel='updatePermissions',
        schema=fields.List(
            PERMISSION_SCHEMA,
            title='Updated Permissions',
            description='The updated role permissions.',
        ),
    )
    def update_role_permissions(self, role, new_permissions):
        with AuthorizedOperation('update_permissions', 'role', role.id):
            self.manager.update(role, {'permissions': new_permissions})
            return None, NO_CONTENT

    @ItemRoute.POST(
        '/permission',
        title='Add Role Permission',
        description='Adds a single permission to a Role.',
        rel='addPermission',
        schema=FieldSet({
            'permissionId':
            fields.Integer(
                description=
                'The id of the new permission to be added to this role.')
        }),
        response_schema=UPDATE_ROLE_PERMISSIONS_RESPONSE_SCHEMA,
    )
    def add_single_permision(self, role, permissionId):
        with AuthorizedOperation('update_permissions', 'role', role.id):
            new_permission = find_by_id(Permission, permissionId)

            if not new_permission:
                raise ItemNotFound('permission', id=permissionId)

            if new_permission.resource_type_id != role.resource_type_id:
                error_message = (
                    'The resource type associated with the permission '
                    'must match the resource type of the role.')
                return (
                    StandardResponse(
                        error_message,
                        BAD_REQUEST,
                        False,
                        roleResourceType=new_permission.resource_type,
                        permissionResourceType=role.resource_type,
                    ),
                    BAD_REQUEST,
                )

            exists = True
            if new_permission not in role.permissions:
                exists = False
                role.permissions.append(new_permission)
                self.manager.update(role, {'permissions': role.permissions})

            added_message = 'already exists for' if exists else 'has been added'
            success_message = 'Permission \'%s\' %s to Role \'%s\'.' % (
                new_permission.permission,
                added_message,
                role.name,
            )
            return StandardResponse(success_message, OK, True)

    @ItemRoute.DELETE(
        '/permission',
        title='Delete Role Permission',
        description='Deletes a single permission from a role.',
        schema=FieldSet({
            'permissionId':
            fields.Integer(
                description=
                'The id of the new permission to be deleted from this role.')
        }),
        response_schema=UPDATE_ROLE_PERMISSIONS_RESPONSE_SCHEMA,
        rel='deletePermission',
    )
    def delete_individual_permission(self, role, permissionId):
        with AuthorizedOperation('update_permissions', 'role', role.id):
            old_permission = find_by_id(Permission, permissionId)

            if not old_permission:
                raise ItemNotFound('permission', id=permissionId)

            if old_permission.resource_type_id != role.resource_type_id:
                error_message = (
                    'The resource type associated with the permission '
                    'must match the resource type of the role.')
                return (
                    StandardResponse(
                        error_message,
                        BAD_REQUEST,
                        False,
                        roleResourceType=old_permission.resource_type,
                        permissionResourceType=role.resource_type,
                    ),
                    BAD_REQUEST,
                )

            exists = True
            try:
                role.permissions.remove(old_permission)
                self.manager.update(role, {'permissions': role.permissions})
            except ValueError:
                exists = False

            deleted_message = ('has been removed from'
                               if exists else 'does not exist for')
            success_message = 'Permission \'%s\' %s Role \'%s\'.' % (
                old_permission.permission,
                deleted_message,
                role.name,
            )
            return StandardResponse(success_message, OK, True)
        nullable=True,
    ),
    'permissionResourceType':
    alchemy_fields.InlineModel(
        RESOURCE_TYPE_FIELDS,
        description='The Resource type corresponding to the Permission.',
        model=ResourceType,
        nullable=True,
    ),
}

UPDATE_ROLE_PERMISSIONS_RESPONSE_SCHEMA = augment_standard_schema(
    ROLE_PERMISSIONS_FIELDS)
ROLE_LIST_SCHEMA = fields.List(
    ROLE_NAME_SCHEMA,
    title='roles',
    description='A listing of roles held by a user or security group on an '
    'indvidual resource or all resources of a specific type.',
)

DEFAULT_ROLE_SCHEMA = fields.Object({
    'roleName':
    ROLE_NAME_SCHEMA,
    'applyToUnregistered':
    fields.Boolean(
        nullable=False,
        description=
        'Indicates whether or not this role only applies to registered users. If '
        'set to `false` it only applies to registered users. If set to `false`, it '
        'applies to unregistered/anonymous users as well as long as public access '
        'is enabled.',
    ),
Exemple #14
0
class GroupResource(PrincipalResource):
    '''The potion class for performing CRUD operations on the `Group` class.
    '''

    users = Relation('user', io='r')

    class Meta(object):
        name = 'group'
        title = 'Groups API'
        description = (
            'The API through which CRUD operations can be performed on User Groups.'
        )
        model = Group
        natural_key = 'name'
        filters = {'name': True}

        # Read Permissions are the defaults as defined in
        # `web.server.security.permissions.PERMISSION_DEFAULTS`
        #
        # Create, Update and Delete Permissions are enforced by the
        # Signal Handlers installed when the API for this Resource
        # are initialized in `web.server.security.signal_handlers.py`

    class Schema(object):
        users = fields.List(CONCISE_USER_SCHEMA,
                            description='The individual users in a group.',
                            io='r')
        roles = fields.Custom(
            ROLE_MAP_SCHEMA,
            description='The role(s) that all members of the group possess',
            attribute='roles',
            formatter=role_list_as_map,
            default=[],
            io='r',
        )

    # Flask-Potion requires that these be methods and NOT functions
    # pylint: disable=R0201
    @ItemRoute.POST(
        '/roles',
        title='Add Group Role',
        description='Adds a single role to a group.',
        schema=GROUP_ROLES_SCHEMA,
        response_schema=STANDARD_RESPONSE_SCHEMA,
        rel='addRole',
    )
    def add_role_by_name(self, group, request):
        with AuthorizedOperation('edit_resource', 'group', group.id):
            role_name = request['roleName']
            resource_name = request.get('resourceName')
            resource_type = request['resourceType']
            (_, exists) = add_group_role(group, role_name, resource_type,
                                         resource_name)
            action = 'already exists' if exists else 'has been added'
            message = 'Role \'%s\' %s for %s' % (
                role_name,
                action,
                get_resource_string(resource_name, resource_type),
            )
            g.request_logger.info(message)
            response_code = OK if exists else CREATED
            return (StandardResponse(message, response_code,
                                     True), response_code)

    # Flask-Potion requires that these be methods and NOT functions
    # pylint: disable=R0201
    @ItemRoute.PATCH(
        '/roles',
        title='Update Group Roles',
        description=
        'Updates all the roles for a group with the values specified.',
        schema=ROLE_MAP_SCHEMA,
        response_schema=STANDARD_RESPONSE_SCHEMA,
        rel='updateRoles',
    )
    def update_roles(self, group, request):
        with AuthorizedOperation('edit_resource', 'group', group.id):
            roles = update_group_roles_from_map(group, request)
            message = (
                'Successfully updated the roles attached to group \'%s\'. '
                'New roles are now: \'%s\'' %
                (group.name,
                 [group_role_as_dictionary(role) for role in roles]))
            g.request_logger.info(message)
            return StandardResponse(message, OK, True)

    # Flask-Potion requires that these be methods and NOT functions
    # pylint: disable=R0201
    @ItemRoute.DELETE(
        '/roles',
        title='Delete Group Role',
        description='Deletes a single role from a group.',
        schema=GROUP_ROLES_SCHEMA,
        response_schema=STANDARD_RESPONSE_SCHEMA,
        rel='deleteRole',
    )
    def delete_role_by_name(self, group, request):
        with AuthorizedOperation('edit_resource', 'group', group.id):
            role_name = request['roleName']
            resource_name = request['resourceName']
            resource_type = request['resourceType']
            (_, exists) = delete_group_role(group, role_name, resource_type,
                                            resource_name)
            action = 'has been deleted' if exists else 'does not exist'
            message = 'Role \'%s\' %s for %s' % (
                role_name,
                action,
                get_resource_string(resource_name, resource_type),
            )
            g.request_logger.info(message)
            return StandardResponse(message, OK, True)

    @ItemRoute.PATCH(
        '/users',
        title='Update Group Users',
        description=
        'Updates the users that are currently in the group with the values specified.',
        schema=fields.List(
            fields.String(title='username',
                          description='The user\'s username')),
        response_schema=STANDARD_RESPONSE_SCHEMA,
        rel='updateUsers',
    )
    def update_users(self, group, request):
        with AuthorizedOperation('edit_resource', 'group', group.id):
            users = update_group_users(group, request)
            directory_listing = [
                get_user_string(user, include_ip=False) for user in users
            ]
            message = (
                'Updated the member list for group \'%s\'. The current users are \'%s\'.'
                % (group.name, directory_listing))
            g.request_logger.info(message)
            return StandardResponse(message, OK, True)

    @ItemRoute.POST(
        '/users',
        title='Add Group User',
        description='Adds a user to the group.',
        schema=fields.String(title='username',
                             description='The user\'s username'),
        response_schema=STANDARD_RESPONSE_SCHEMA,
        rel='addUser',
    )
    def add_user_by_username(self, group, request):
        with AuthorizedOperation('edit_resource', 'group', group.id):
            user, exists = add_group_user(group, request)
            action = ('has been added to the member list of'
                      if exists else 'is already a member of')
            message = 'User \'%s\' %s group \'%s\'. ' % (
                get_user_string(user, include_ip=False),
                action,
                group.name,
            )
            g.request_logger.info(message)
            return StandardResponse(message, OK if exists else CREATED, True)

    @ItemRoute.DELETE(
        '/users',
        title='Delete Group User',
        description='Deletes a user from the group.',
        schema=fields.String(title='username',
                             description='The user\'s username'),
        response_schema=STANDARD_RESPONSE_SCHEMA,
        rel='deleteUser',
    )
    def delete_user_by_username(self, group, request):
        with AuthorizedOperation('edit_resource', 'group', group.id):
            user, exists = delete_group_user(group, request)
            action = ('has been deleted from the member list of'
                      if exists else 'is not a member of')
            message = 'User \'%s\' %s group \'%s\'. ' % (
                get_user_string(user, include_ip=False),
                action,
                group.name,
            )
            g.request_logger.info(message)
            return StandardResponse(message, OK, True)
Exemple #15
0
class UserResource(PrincipalResource):
    '''The potion class for performing CRUD operations on the `User` class.
    '''
    class Meta(object):
        title = 'Users API'
        description = (
            'The API through which CRUD operations can be performed on User(s).'
        )
        model = User
        natural_key = 'username'

        permissions = {'read': 'yes'}
        # Read Permissions are the defaults as defined in
        # `web.server.security.permissions.PERMISSION_DEFAULTS`
        #
        # Create, Update and Delete Permissions are enforced by the
        # Signal Handlers installed when the API for this Resource
        # are initialized in `web.server.security.signal_handlers.py`

        # Allow users to filter resources by name, username, status, and phone
        filters = {
            'username': True,
            'status': True,
            'firstName': True,
            'lastName': True,
            'phoneNumber': True,
        }

        exclude_fields = ('password', 'reset_password_token')

    class Schema(object):
        username = USERNAME_SCHEMA
        roles = fields.Custom(
            ROLE_MAP_SCHEMA,
            description='The role(s) that the user currently possesses.',
            attribute='roles',
            formatter=role_list_as_map,
            default=[],
        )
        firstName = fields.String(
            description='The first name of the user.',
            attribute='first_name',
            pattern=INTERNATIONALIZED_ALPHANUMERIC_AND_DELIMITER,
        )
        lastName = fields.String(
            description='The last name of the user.',
            attribute='last_name',
            pattern=INTERNATIONALIZED_ALPHANUMERIC_AND_DELIMITER_OR_EMPTY,
        )
        phoneNumber = fields.String(
            description='The phone number of the user.',
            attribute='phone_number')
        # Disabling this warning because Hyper-Schema is enforcing
        # that the value of this field MUST match one of the values
        # of the Enum.
        # pylint:disable=E1136
        # pylint: disable=E1101
        status = fields.Custom(
            fields.String(enum=USER_STATUSES),
            attribute='status_id',
            formatter=lambda status_id: UserStatusEnum(status_id).name.lower(),
            converter=lambda status_value: UserStatusEnum[status_value.upper()]
            .value,
            default=UserStatusEnum.ACTIVE.value,
            nullable=False,
        )

    # The parameter is coming directly from the API which uses camelCase instead of
    # snake_case
    # pylint: disable=C0103
    # pylint: disable=E1101
    @ItemRoute.POST(
        '/password',
        title='Update User Password',
        description=
        'Updates the user\'s password with the new value specified.',
        rel='updatePassword',
        schema=FieldSet({
            'newPassword':
            fields.String(
                min_length=10,
                max_length=255,
                description='The new password for the user.',
            )
        }),
    )
    def update_password(self, user, newPassword):
        # TODO(vedant) Refactor this into a separate module like
        # we do for the Groups API
        with AuthorizedOperation('change_password', 'user', user.id):
            hashed_password = current_app.user_manager.hash_password(
                newPassword)
            self.manager.update(user, {'password': hashed_password})
            return None, NO_CONTENT

    @ItemRoute.POST(
        '/reset_password',
        title='Reset User Password',
        description=
        'Resets the user\'s password and sends them a password reset email.',
        rel='resetPassword',
        schema=None,
        response_schema=None,
    )
    def reset_password(self, user):
        with AuthorizedOperation('reset_password', 'user', user.id):
            # TODO(vedant) - Change the return value of `reset_password` to convey a value
            # with more resolution than True/False
            username = user.username

            # TODO(vedant) - This is for legacy users who signed up with usernames that do
            # not represent an e-mail address
            if not EMAIL_REGEX.match(username):
                message = 'User {username} does not have a valid e-mail address.'.format(
                    username=username)
                return StandardResponse(message, BAD_REQUEST,
                                        False), BAD_REQUEST

            send_reset_password(username)

            message = ('User password has been reset and instructions '
                       'e-mailed to {username}.'.format(username=username))
            g.request_logger.info(message)
            return None, NO_CONTENT

    @ItemRoute.DELETE(
        '/force',
        rel='forceDestroy',
        title='Force Delete User',
        description=
        'Force delete a User and ALL their created artifacts (Dashboards, etc.)',
    )
    def force_delete(self, user):
        with AuthorizedOperation('delete_resource', 'user', user.id):
            before_delete.send(user)
            force_delete_user(user)
            after_delete.send(user)
            return None, NO_CONTENT

    @Route.POST(
        '/invite',
        title='Invite New User',
        description='',
        rel='inviteUser',
        schema=fields.List(INVITE_OBJECT_SCHEMA),
        response_schema=fields.List(
            fields.Inline('self'),
            description='A listing of all the invited users'),
    )
    def invite_user(self, invitees):
        with AuthorizedOperation('invite_user', 'site', ROOT_SITE_RESOURCE_ID):
            invited_users = [invitee.email for invitee in invitees]
            try:
                pending_users = invite_users(invitees)
                emails = [user.username for user in pending_users]
                g.request_logger.info(
                    'Successfully invited the following users: \'%s\'.',
                    emails)
                return pending_users
            except UserAlreadyInvited as e:
                g.request_logger.error(
                    'Attempt to invite the following users has failed: \'%s\'. '
                    'The following users already have platform accounts: \'%s\'',
                    invited_users,
                    e.already_registered_users,
                )
                raise

    # Flask-Potion requires that these be methods and NOT functions
    # pylint: disable=R0201
    # pylint: disable=E1101
    @ItemRoute.POST(
        '/roles',
        title='Add User Role',
        description='Adds a single role to a user.',
        schema=USER_ROLES_SCHEMA,
        response_schema=STANDARD_RESPONSE_SCHEMA,
        rel='addRole',
    )
    def add_role_by_name(self, user, request):
        with AuthorizedOperation('edit_resource', 'user', user.id):
            # TODO(vedant) Refactor this into a separate module like
            # we do for the Groups API
            role_name = request['roleName']
            resource_type = request['resourceType']
            resource_name = request.get('resourceName')

            result = add_user_role_api(user,
                                       role_name,
                                       resource_type,
                                       resource_name,
                                       commit=True)
            success = False
            if isinstance(result, Success):
                success = True

            response_code = OK if success else BAD_REQUEST
            return (
                StandardResponse(result['data']['message'], response_code,
                                 success),
                response_code,
            )

    # Flask-Potion requires that these be methods and NOT functions
    # pylint: disable=R0201
    @ItemRoute.DELETE(
        '/roles',
        title='Delete User Role',
        description='Deletes a single role from a user.',
        schema=USER_ROLES_SCHEMA,
        response_schema=STANDARD_RESPONSE_SCHEMA,
        rel='deleteRole',
    )
    def delete_role_by_name(self, user, request):
        with AuthorizedOperation('edit_resource', 'user', user.id):
            # TODO(vedant) Refactor this into a separate module like
            # we do for the Groups API
            role_name = request['roleName']
            resource_type = request['resourceType']
            resource_name = request.get('resourceName')

            result = delete_user_role_api(user,
                                          role_name,
                                          resource_type,
                                          resource_name,
                                          commit=True)

            return StandardResponse(result['data']['message'], OK, True)

    @ItemRoute.PATCH(
        '/roles',
        title='Update User Roles',
        description=
        'Updates all the roles for a user with the values specified.',
        schema=ROLE_MAP_SCHEMA,
        response_schema=fields.Inline('self'),
        rel='updateRoles',
    )
    def update_roles(self, user, request):
        with AuthorizedOperation('edit_resource', 'user', user.id):
            roles = update_user_roles_from_map(user, request)
            message = (
                'Successfully updated the roles attached to user \'%s\'. '
                'New roles are now: \'%s\'' %
                (user.username,
                 [user_role_as_dictionary(role) for role in roles]))
            g.request_logger.info(message)
            return self.manager.read(user.id)

    @Route.GET(
        '/default/roles',
        title='Get Default User Role(s)',
        description=
        'Gets all role(s) that are possessed by all registered users and optionally, '
        'unregistered users.',
        rel='getDefaultRoles',
    )
    @authorization_required('view_resource', 'user', is_api_request=True)
    def list_default_roles(self):
        return StandardResponse(
            'Successfully retrieved a listing of all public roles. ',
            OK,
            True,
            roles=role_list_as_map(list_default_roles()),
        )

    @Route.POST(
        '/default/roles',
        title='Add Default User Role',
        description=
        'Adds a single role to a user that will apply to all registered users and '
        'optionally, unregistered users.',
        schema=DEFAULT_ROLES_SCHEMA,
        rel='addDefaultRole',
    )
    @authorization_required('edit_resource', 'user', is_api_request=True)
    def add_default_role_by_name(self, request):
        role_name = request['roleName']
        resource_type = request['resourceType']
        resource_name = request.get('resourceName')
        (_, exists) = add_default_role(role_name, resource_type, resource_name)
        action = 'has been added' if exists else 'already exists'
        message = 'Role \'%s\' %s for %s' % (
            role_name,
            action,
            get_resource_string(resource_name, resource_type),
        )
        g.request_logger.info(message)
        response_code = OK if exists else CREATED
        return (StandardResponse(message, response_code, True), response_code)

    @Route.DELETE(
        '/default/roles',
        title='Delete Default User Role',
        description=
        'Deletes a single role from a user that will apply to both anonymous '
        '(unregistered) as well as registered users.',
        schema=DEFAULT_ROLES_SCHEMA,
        response_schema=STANDARD_RESPONSE_SCHEMA,
        rel='deletePublicRole',
    )
    @authorization_required('delete_resource', 'user', is_api_request=True)
    def delete_default_role_by_name(self, request):
        role_name = request['roleName']
        resource_type = request['resourceType']
        resource_name = request.get('resourceName')
        (_, exists) = delete_default_role(role_name, resource_type,
                                          resource_name)
        action = 'has been deleted' if exists else 'does not exist'
        message = 'Role \'%s\' %s for %s' % (
            role_name,
            action,
            get_resource_string(resource_name, resource_type),
        )
        g.request_logger.info(message)
        return StandardResponse(message, OK, True)