Beispiel #1
0
        class GroupResource(ModelResource):
            class Meta:
                model = Group
                include_id = True
                include_type = True

            members = Relation('user')
Beispiel #2
0
class DomainResource(ModelResource):
    projects = Relation("project")

    class Meta:
        model = Domain
        read_only_fields = (Domain.uuid.key, )

    @ItemRoute.PATCH("/deactivate", rel="deactivate")
    @role_required(["admin"])
    def deactivate(self, domain) -> fields.Boolean():
        domain.is_active = False

        db.session.commit()

        return True

    @ItemRoute.PATCH("/activate", rel="activate")
    @role_required(["admin"])
    def activate(self, domain) -> fields.Boolean():
        domain.is_active = True

        db.session.commit()

        return True

    @Route.POST("",
                rel="create",
                schema=fields.Inline("self"),
                response_schema=fields.Inline("self"))
    @role_required(["admin"])
    def create(self, properties):
        item = self.manager.create(properties)
        return item
Beispiel #3
0
        class UserResource(ModelResource):
            class Meta:
                model = User
                include_id = True
                include_type = True

            children = Relation('self')
Beispiel #4
0
class AuthorResource(ModelResource):
    books = Relation('book')

    class Meta:
        model = Author
        natural_key = ('first_name', 'last_name'
                       )  # natural key declaration added here
Beispiel #5
0
class GatewayResource(PrincipalResource):
    users = Relation('users')
    vouchers = Relation('vouchers')

    class Meta:
        manager = Manager

        model = Gateway
        include_id = True
        id_converter = 'string'
        id_field_class = fields.String
        permissions = {
            'read': 'yes',
            'create': network_or_above,
            'update': network_or_above,
            'delete': network_or_above,
        }
        read_only_fields = ('created_at', 'updated_at')

    class Schema:
        id = fields.String(min_length=3, max_length=20)
        network = fields.ToOne('networks')
        title = fields.String(min_length=3)
        login_ask_name = fields.Boolean(default=False)
        login_require_name = fields.Boolean(default=False)

    @ItemRoute.POST
    def logo(self, gateway):
        if 'file' in flask.request.files:
            filename = logos.save(flask.request.files['file'])

            self.manager.update(gateway, {'logo': filename})

            im = Image.open(logos.path(filename))

            static_folder = os.path.abspath(
                os.path.dirname(__file__) + '/static/logos')
            mkdir_p(static_folder)

            im.thumbnail((300, 300), Image.ANTIALIAS)
            im.save(static_folder + '/' + filename)
Beispiel #6
0
        class Group(ModelResource):
            class Schema:
                name = fields.String()

            class Meta:
                name = 'group'
                model = name
                manager = MemoryManager
                include_id = True
                include_type = True

            members = Relation('person')
Beispiel #7
0
class NetworkResource(PrincipalResource):
    gateways = Relation('gateways')
    users = Relation('users')

    class Meta:
        manager = Manager

        model = Network
        include_id = True
        id_converter = 'string'
        id_field_class = fields.String
        permissions = {
            'read': gateway_or_above,
            'create': super_admin_only,
            'update': super_admin_only,
            'delete': super_admin_only,
        }
        read_only_fields = ('created_at', 'updated_at')

    class Schema:
        id = fields.String(min_length=3, max_length=20)
        title = fields.String(min_length=3)
Beispiel #8
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
Beispiel #9
0
        class GroupResource(ModelResource):
            class Meta:
                model = Group

            members = Relation(UserResource)
Beispiel #10
0
        class UserResource(ModelResource):
            class Meta:
                model = User

            children = Relation('self')
class BackendResource(PrincipalResource):
    '''The potion class for performing CRUD operations on the `Resource` class.
    '''

    users = Relation('user', io='r')
    groups = Relation('group', io='r')
    resourceType = Relation('resource-type', attribute='resource_type', io='r')

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

        # Marking all fields as read-only so they don't show up in the Hyperschema for any
        # destructive HTTP Methods (e.g. PUT, POST, PATCH, DELETE, etc.)
        read_only_fields = [
            'name',
            'label',
            'users',
            'groups',
            'id',
            'resource_type',
            'resource_type_id',
        ]

        # 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 API users to filter on resources by name, label and
        # resource_type
        filters = {
            'name': True,
            'label': True,
            'resourceType': {
                None: ResourceTypeFilter,
                'eq': ResourceTypeFilter
            },
            'users': True,
            'groups': True,
        }

    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',
        )

    # Flask-Potion requires that these be methods and NOT functions
    # pylint: disable=R0201
    # pylint: disable=E1101
    @Route.POST('', rel='create')
    def post(self, *_):
        return (
            StandardResponse(READ_ONLY_API_MESSAGE, METHOD_NOT_ALLOWED, False),
            METHOD_NOT_ALLOWED,
        )

    @ItemRoute.PATCH('', rel='update')
    def patch(self, *_):
        return (
            StandardResponse(READ_ONLY_API_MESSAGE, METHOD_NOT_ALLOWED, False),
            METHOD_NOT_ALLOWED,
        )

    @ItemRoute.DELETE('', rel='destroy')
    def delete(self, *_):
        return (
            StandardResponse(READ_ONLY_API_MESSAGE, METHOD_NOT_ALLOWED, False),
            METHOD_NOT_ALLOWED,
        )

    @ItemRoute.POST(
        '/roles',
        title='Update Resource Roles',
        description=
        'Updates the roles that the various users and groups hold on this particular '
        'resource.',
        rel='updateRoles',
        schema=RESOURCE_ROLES_SCHEMA,
    )
    def update_roles(self, resource, request):
        resource_name = resource.name
        type_name = resource.resource_type.name.name
        resource_string = get_resource_string(resource_name, type_name)
        with AuthorizedOperation('update_users', type_name, resource.id):
            user_roles = request.get('userRoles')
            group_roles = request.get('groupRoles')
            default_roles = request.get('defaultRoles')
            for (_, role_object) in list(default_roles.items()):
                if role_object['applyToUnregistered']:
                    with AuthorizedOperation('publish_resource', type_name,
                                             resource.id):
                        pass

            (existing_roles,
             new_roles) = update_resource_roles(resource, user_roles,
                                                group_roles, default_roles)
            g.request_logger.info(
                'Updating roles for %s. Existing roles are %s. New roles will be %s.',
                resource_string,
                existing_roles,
                new_roles,
            )
            return None, NO_CONTENT

    @ItemRoute.GET(
        '/roles',
        title='Get Resource Roles',
        description=
        'Gets the roles that the various users and groups hold on this particular '
        'resource.',
        rel='getRoles',
        response_schema=RESOURCE_ROLES_SCHEMA,
    )
    def get_roles(self, resource):
        resource_name = resource.name
        resource_type = resource.resource_type.name.name
        resource_string = get_resource_string(resource_name, resource_type)
        with AuthorizedOperation('view_resource', resource_type, resource.id):
            current_roles = get_current_resource_roles(resource)
            message = (
                'Successfully retrieved a listing of all the roles for %s. ' %
                resource_string)
            g.request_logger.debug(message)
            return current_roles
Beispiel #12
0
class DashboardResource(PrincipalResource):
    '''The potion class for performing CRUD operations on the `Dashboard` class.
    '''

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

    class Meta(object):
        manager = principals(DashboardManager)
        model = Dashboard
        natural_key = 'slug'
        excluded_fields = ('id',)
        id_attribute = 'resource_id'

        permissions = {'read': 'view_resource'}

        filters = {
            'slug': True,
            'title': True,
            'created': True,
            'author': {'eq': UserFilter, None: UserFilter},
            'isOfficial': True,
        }

    class Schema(object):
        title = TITLE_SCHEMA

        slug = NULLABLE_SLUG_SCHEMA

        description = DESCRIPTION_SCHEMA

        specification = SPECIFICATION_SCHEMA

        authorUsername = AUTHOR_USERNAME_SCHEMA

        author = AUTHOR_URI_SCHEMA

        resource = RESOURCE_URI_SCHEMA

        created = CREATED_SCHEMA

        isOfficial = IS_OFFICIAL_SCHEMA

        # NOTE(stephen): These fields are now pulled **from metadata** not from
        # the Dashboard model directly. They are only represented here to make
        # flask potion happy EVEN THOUGH THEY WILL NEVER EVER BE SENT BY THE
        # CLIENT, FLASK POTION STILL COMPLAINS.
        lastModified = UNUSED_LAST_MODIFIED_SCHEMA
        totalViews = UNUSED_TOTAL_VIEWS_SCHEMA

    # HACK(stephen): Attaching dashboard metadata to the base dashboard model
    # response is like fitting a square peg into a round hole. To add that
    # information in ways that Flask-Potion would naturally work causes us to
    # issue a huge number of queries per dashboard (previously 14 queries per
    # dashboard with a naive implementation). This query encapsulates all the
    # information needed for the dashboard response into a single query.
    # TODO(stephen): If we have to write workarounds like this, it probably
    # means we shouldn't be jamming too much information into a single API.
    def _attach_metadata_to_query(self, query):
        # NOTE(stephen): I don't think a transaction is necessary for this
        # read only query, but it is an easy way to access the session.
        with Transaction() as transaction:
            session = transaction._session
            subquery = DashboardUserMetadata.summary_by_dashboard_for_user(
                session, current_user.id
            ).subquery()
            return (
                query
                # Join in the summarized metadata for each dashboard.
                .outerjoin(subquery, Dashboard.id == subquery.c.dashboard_id)
                # Attach user info so we can extract the author username.
                .outerjoin(User, Dashboard.author_id == User.id)
                # Make sure all the metadata columns are included.
                .add_columns(subquery)
                # Also include all dashboard columns since otherwise a new query
                # will be issued EACH TIME we access a dashboard in the query
                # result.
                .add_columns(Dashboard.__table__)
                # Manually set up author_username since hybrid properties weren't
                # transferring.
                .add_columns(User.username.label('author_username'))
            )

    # HACK(stephen): To ensure endpoints that return a single dashboard also
    # include the appropriate metadata, we must join in the dashboard metadata
    # to our single dashboard query.
    def _get_single_dashboard_with_metadata(self, resource_id):
        # NOTE(stephen): The resource_id being filtered on here is **not**
        # Dashboard.id. This is because we use the `resource_id` column for
        # lookups and reference.
        query = self.manager._query().filter(self.manager.id_column == resource_id)
        return self._attach_metadata_to_query(query).one()

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

    # Override the default "get all dashboards" route to augment the response
    # with dashboard specific metadata.
    @Route.GET(
        '',
        rel='instances',
        title='Something',
        description='Something',
        schema=Instances(),
        response_schema=fields.Array(fields.Object(DASHBOARD_SIMPLE_FIELDS)),
    )
    def get_instances(self, page, per_page, where, sort):
        base_query = self.manager.instances(where, sort)

        # NOTE(stephen): I'm not sure why this wouldn't exist, but I think it
        # only happens when there are no dashboards in the DB.
        if not base_query:
            return []

        return self._attach_metadata_to_query(base_query).paginate(page, per_page).items

    @Route.POST(
        '/upgrade',
        title='Upgrade Dashboard Specification',
        description='Upgrades the provided dashboard specification to the '
        'latest schema version supported by the server.',
        schema=fields.Any(),
        rel='upgrade',
    )
    def upgrade_dashboard(self, dashboard_specification):
        # The validation is being done in the conversion defined by SPECIFICATION_SCHEMA. If there
        # are any errors, they will be thrown as an exception to the client. No action needs to be
        # taken here.
        return format_and_upgrade_specification(dashboard_specification)

    @ItemRoute.POST(
        '/visualization',
        title='Add Simple Query Visualization',
        description='Adds an item from Simple Query Tool to the dashboard.',
        schema=ADD_QUERY_TO_DASHBOARD_SCHEMA,
        response_schema=DETAILED_DASHBOARD_SCHEMA,
        rel='addVisualization',
    )
    def add_visualization(self, dashboard, request):
        resource_id = dashboard.resource.id
        with AuthorizedOperation('edit_resource', 'dashboard', resource_id):
            _add_visualization(dashboard, request, False)
            track_dashboard_access(dashboard.id, True)
            return self._get_single_dashboard_with_metadata(resource_id)

    @ItemRoute.POST(
        '/visualization/advanced',
        title='Add Advanced Query Visualization',
        description='Adds an item from Advanced Query Tool to the dashboard.',
        schema=ADD_QUERY_TO_DASHBOARD_SCHEMA,
        response_schema=DETAILED_DASHBOARD_SCHEMA,
        rel='addAdvancedVisualization',
    )
    def add_advanced_visualization(self, dashboard, request):
        resource_id = dashboard.resource.id
        with AuthorizedOperation('edit_resource', 'dashboard', resource_id):
            _add_visualization(dashboard, request, True)
            track_dashboard_access(dashboard.id, True)
            return self._get_single_dashboard_with_metadata(resource_id)

    @ItemRoute.POST(
        '/transfer',
        title='Transfer Ownership',
        description='Transfers the ownership of this dashboard from the current'
        'owner to the one specified. ',
        schema=USER_URI_SCHEMA,
    )
    def transfer_ownership(self, dashboard, new_author):
        with AuthorizedOperation(
            'update_users', 'dashboard', dashboard.resource_id
        ), AuthorizedOperation('view_resource', 'user', dashboard.author.id):
            new_author = lookup_author(author_id=new_author)
            api_transfer_dashboard_ownership(dashboard, new_author)
            return None, NO_CONTENT

    @ItemRoute.POST(
        '/transfer/username',
        title='Transfer Ownership',
        description='Transfers the ownership of this dashboard from the current'
        'owner to the one specified. ',
        schema=USERNAME_SCHEMA,
    )
    def transfer_ownership_by_username(self, dashboard, new_author):
        with AuthorizedOperation(
            'update_users', 'dashboard', dashboard.resource_id
        ), AuthorizedOperation('view_resource', 'user', dashboard.author.id):
            new_author = lookup_author(author_username=new_author)
            api_transfer_dashboard_ownership(dashboard, new_author)
            return None, NO_CONTENT

    @Route.POST(
        '/transfer',
        title='Transfer Ownership',
        description='Transfers the ownership of ALL dashboards from one user '
        'to another.',
        schema=fields.Object(
            {'sourceAuthor': USER_URI_SCHEMA, 'targetAuthor': USER_URI_SCHEMA}
        ),
    )
    @authorization_required('update_users', 'dashboard')
    def transfer_bulk_ownership(self, request):
        source_author = lookup_author(author_id=request['sourceAuthor'])
        target_author = lookup_author(author_id=request['targetAuthor'])
        api_bulk_transfer_dashboard_ownership(source_author, target_author)
        return None, NO_CONTENT

    @Route.POST(
        '/transfer/username',
        title='Transfer Ownership',
        description='Transfers the ownership of ALL dashboards from one user '
        'to another.',
        schema=fields.Object(
            {'sourceAuthor': USERNAME_SCHEMA, 'targetAuthor': USERNAME_SCHEMA}
        ),
    )
    @authorization_required('update_users', 'dashboard')
    def transfer_bulk_ownership_by_username(self, request):
        source_author = lookup_author(author_username=request['sourceAuthor'])
        target_author = lookup_author(author_username=request['targetAuthor'])
        api_bulk_transfer_dashboard_ownership(source_author, target_author)
        return None, NO_CONTENT

    @ItemRoute.POST(
        '/official',
        title='Update Dashboard \'official\' flag',
        description='Marks a Dashboard as official or not.',
        schema=fields.Boolean(
            description='The updated value of the "isOfficial" flag for the '
            'dashboard.'
        ),
    )
    @authorization_required('publish_resource', 'dashboard')
    def set_official(self, dashboard, is_official):
        self.manager.update(dashboard, {'is_official': is_official})
        return None, NO_CONTENT

    @ItemRoute.POST(
        '/favorite',
        title='Update Dashboard \'favorite\' flag',
        description='Marks a Dashboard as a user favorite or not.',
        schema=fields.Boolean(
            description='The updated value of the "isFavorite" flag for the '
            'dashboard.'
        ),
    )
    def set_favorite(self, dashboard, is_favorite):
        with AuthorizedOperation(
            'view_resource', 'dashboard', dashboard.id
        ), Transaction() as transaction:
            metadata = get_or_create_metadata(transaction, dashboard.id)
            metadata.is_favorite = is_favorite
            transaction.add_or_update(metadata)

        return None, NO_CONTENT

    # NOTE(vedant): Why are we overriding the default potion route for GET <{}:id> and
    # PATCH <{}:id>?
    #
    # We want to only display the dashboard specification when an individual dashboard is
    # requested. To do this, we have to tailor the response schema to include the specification
    # field which would otherwise NOT be included in the default Schema specified in the `Schema`
    # subclass of `DashboardResource`.
    #
    # The rationale for only display the complete dashboard specification is that specifications
    # have a tendency to be very large and we don't want to send a lot of useless data over the
    # wire unless it is specifically asked for by the client. It is also very unlikely that a
    # client will ever be loading more than one dashboard specification at a given time.
    @Route.GET(
        lambda r: '/<{}:id>'.format(r.meta.id_converter),
        rel='self',
        attribute='instance',
        response_schema=DETAILED_DASHBOARD_SCHEMA,
    )
    def read(self, id):
        with Transaction() as transaction:
            dashboard = super(DashboardResource, self).read(id)
            dashboard.total_views += 1
            track_dashboard_access(dashboard.id)
            dashboard = transaction.add_or_update(dashboard, flush=True)
        return self._get_single_dashboard_with_metadata(id)

    @Route.PATCH(
        lambda r: '/<{}:id>'.format(r.meta.id_converter),
        rel='update',
        schema=fields.Inline('self', patchable=True),
        response_schema=DETAILED_DASHBOARD_SCHEMA,
    )
    def update(self, properties, id):
        dashboard = super(DashboardResource, self).update(properties, id)
        track_dashboard_access(dashboard.id, edited=True)
        return self._get_single_dashboard_with_metadata(id)

    @ItemRoute.GET(
        '/history',
        title='Dashboard Update History',
        schema=Instances(),
        description='Gets dashboard history data',
        rel='getDashboardHistory',
        response_schema=DASHBOARD_CHANGES_SCHEMA,
    )
    # pylint: disable=W0613
    # Method signature requires where and sort params
    def get_history(self, dashboard, page, per_page, where, sort):
        records = []
        with Transaction() as transaction:
            records = (
                transaction.find_all_by_fields(
                    HistoryRecord,
                    {'object_id': dashboard.resource_id, 'object_type': self.meta.name},
                )
                .paginate(page, per_page)
                .items
            )
        return records
Beispiel #13
0
class UserResource(ModelResource):
    sessions = Relation('session')

    class Meta:
        model = User
        natural_key = 'username'
Beispiel #14
0
class SessionResource(ModelResource):
    commands = Relation('command')

    class Meta:
        model = Session
        postgres_text_search_fields = ('uuid')

    class Schema:
        uuid = fields.UUID()
        user = fields.Inline('user')
        host = fields.Inline('host')
        start = fields.DateTimeString()
        end = fields.DateTimeString()
        duration = fields.Custom('{"type": "integer"}',
                                 io="r",
                                 formatter=lambda x: x.total_seconds())

    @Route.GET('/by_command')
    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)))

    @Route.GET('/log')
    def session_log(self, session_uuid: fields.UUID()) -> fields.String():
        session = Session.query.filter(Session.uuid == session_uuid).first()
        session_log_path = path.session_logs + session.start.date().isoformat().replace('-', '') + '/' + \
            session.user.username + '_' + session.host.hostname + '_' + \
            session.start.time().isoformat().split('.')[0].replace(':', '') + '_' + \
            session_uuid + '.log'
        with open(session_log_path, 'rb') as session_log:
            return base64.b64encode(session_log.read()).decode(
                'utf-8', 'strict')

    @Route.GET('/timed_log')
    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
Beispiel #15
0
class HostResource(ModelResource):
    sessions = Relation('session')

    class Meta:
        model = Host
        natural_key = 'hostname'
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)
        class UserResource(PrincipalResource):
            books = Relation(BookResource)

            class Meta:
                model = self.USER
                permissions = {'create': 'admin'}
Beispiel #18
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)