Example #1
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
Example #2
0
class UserPermissionLink(ModelResource):
    class Meta:
        model = UserPermissionLinker

    @Route.POST("",
                rel="create",
                schema=fields.Inline("self"),
                response_schema=fields.Inline("self"))
    @role_required(["admin"])
    def create(self, properties):
        return super().create(properties=properties)

    @Route.GET(
        lambda r: "/<{}:id>".format(r.meta.id_converter),
        rel="self",
        attribute="instance",
        response_schema=fields.Inline("self"),
    )
    @role_required(["admin"])
    def read(self, id):
        return super().read(id=id)

    @read.PATCH(
        rel="update",
        schema=fields.Inline("self", patchable=True),
        response_schema=fields.Inline("self", patchable=True),
    )
    @role_required(["admin"])
    def update(self, properties, id):
        item = self.manager.read(id)
        updated_item = self.manager.update(item, properties)
        return updated_item
Example #3
0
 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())
Example #4
0
class ProjectResource(ModelResource):
    class Meta:
        model = Project
        read_only_fields = (Project._uuid.key, )

    @Route.POST("",
                rel="create",
                schema=fields.Inline("self"),
                response_schema=fields.Inline("self"))
    @role_required(["admin"])
    def create(self, properties):
        return super().create(properties=properties)

    @Route.GET(
        lambda r: "/<{}:id>".format(r.meta.id_converter),
        rel="self",
        attribute="instance",
        response_schema=fields.Inline("self"),
    )
    @role_required(["admin"])
    def read(self, id):
        return super().read(id=id)

    @read.PATCH(
        rel="update",
        schema=fields.Inline("self", patchable=True),
        response_schema=fields.Inline("self", patchable=True),
    )
    @role_required(["admin"])
    def update(self, properties, id):
        item = self.manager.read(id)
        updated_item = self.manager.update(item, properties)
        return updated_item

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

        db.session.commit()

        return True

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

        db.session.commit()

        return True

    @ItemRoute.GET("/uuid", rel="uuid")
    @role_required(["admin"])
    def uuid(self, project) -> fields.UUID():
        return project.uuid
Example #5
0
 class Schema:
     province = fields.Inline('province')
     ward = fields.Inline('ward')
     district = fields.Inline('district')
     associate_group = fields.Inline('associate_group')
     province_id = fields.String()
     associate_group_id = fields.String()
     district_id = fields.String()
     ward_id = fields.String()
     _deleted_at = fields.DateString()
     _deleted_at._schema = c.DATETIME_SCHEMA
Example #6
0
 class Schema:
     owner_group = fields.Inline('group')
     owner_farmer = fields.Inline('farmer')
     certificate_start_date = fields.DateString()
     certificate_expiry_date = fields.DateString()
     certificate_expiry_date._schema = c.DATETIME_SCHEMA
     certificate_start_date._schema = c.DATETIME_SCHEMA
     owner_group_id = fields.String()
     owner_farmer_id = fields.String()
     _deleted_at = fields.DateString()
     _deleted_at._schema = c.DATETIME_SCHEMA
Example #7
0
        class Box(ModelResource):
            class Schema:
                description = fields.String()
                is_open = fields.Boolean(default=False)

            class Meta:
                model = 'box'
                manager = MemoryManager
                include_id = True
                include_type = True

            @ItemRoute.GET()
            def verbose_description(self, box):
                return box["description"] + " box"

            verbose_description.response_schema = fields.String()

            @ItemRoute.POST()
            def open(self, box):
                return self.manager.update(box, {"is_open": True})

            @Route.GET(schema=FieldSet({'greeting': fields.String()}))
            def greet(self, greeting):
                return greeting + " box!"

            open.response_schema = fields.Inline('self', attribute='Test')
class LogResource(Resource):
    class Schema:
        level = fields.String(enum=['info', 'warning', 'error'])
        message = fields.String()

    class Meta:
        name = 'log'

    @Route.POST(
        '',
        rel="create",
        schema=fields.Inline('self'),
        response_schema=fields.Inline('self'),
    )
    def create(self, properties):
        print('{level}: {message}'.format(**properties))
        return properties
Example #9
0
        class UserResource(Resource):
            @Route.GET('/<int:id>', rel="self")
            def read(self, id):
                return {"name": "Foo", "age": 123}

            read.response_schema = fields.Inline("self")

            class Schema:
                name = fields.String()
                age = fields.PositiveInteger(nullable=True)

            class Meta:
                name = 'user'
Example #10
0
    def test_inline_schema(self):
        class FooResource(ModelResource):
            class Meta:
                name = "foo"

        class BarResource(ModelResource):
            class Meta:
                name = "bar"

        self.api.add_resource(FooResource)
        self.api.add_resource(BarResource)

        foo = fields.Inline(FooResource)
        foo.bind(BarResource)

        self.assertEqual({'$ref': '/foo/schema'}, foo.response)
Example #11
0
class UserQuerySessionResource(PrincipalResource):
    '''Potion class for interacting with saved queries.
    '''
    class Meta:
        model = UserQuerySession
        filters = {'queryUuid': True}

        id_attribute = 'query_uuid'
        id_field_class = fields.String()
        permissions = {'read': 'view_resource'}

    class Schema:
        queryUuid = fields.String(attribute='query_uuid', nullable=True)
        userId = fields.Integer(attribute='user_id')
        queryBlob = fields.Any(attribute='query_blob')

    # pylint: disable=E1101
    @ItemRoute.GET('/by_query_uuid')
    # pylint: disable=R0201
    def by_query_uuid(self, user_query_session):
        return {
            'queryBlob': user_query_session.query_blob,
            'queryUuid': user_query_session.query_uuid,
            'username': user_query_session.user.username,
        }

    # pylint: disable=E1101
    @Route.POST(
        '/generate_link',
        rel='generateLink',
        schema=fields.Inline('self'),
        response_schema=fields.String(),
    )
    # pylint: disable=R0201
    def generate_link(self, query_session):
        return store_query_and_generate_link(query_session)
Example #12
0
 def create(self, value: fields.Number()) -> fields.Inline('self'):
     return {"name": "foo", "value": value}
Example #13
0
class UserResource(ModelResource):
    class Meta:
        model = User
        exclude_fields = [User._password_hash.key]
        read_only_fields = [User.uuid.key]

    @Route.GET("/profile/permissions")
    @auth_required
    def resolve_profile_permissions(
        self, project_id: fields.Integer(minimum=1)
    ) -> fields.List(fields.String()):
        permissions = (UserPermissionLinker.query.filter_by(
            project_id=project_id, user_id=current_identity.id).join(
                UserPermissionLinker.permission).with_entities(
                    Permission.slug).all())

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

        return permissions

    @Route.GET("/profile")
    @auth_required
    def resolve_profile(self) -> fields.Inline("self"):
        return self.manager.read(current_identity.id)

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

        db.session.commit()

        return True

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

        db.session.commit()

        return True

    @ItemRoute.GET("/permissions")
    @role_required(["admin"])
    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

    @Route.POST("",
                rel="create",
                schema=fields.Inline("self"),
                response_schema=fields.Inline("self"))
    @role_required(["admin"])
    def create(self, properties):
        return super().create(properties=properties)

    @Route.GET(
        lambda r: "/<{}:id>".format(r.meta.id_converter),
        rel="self",
        attribute="instance",
        response_schema=fields.Inline("self"),
    )
    @role_required(["admin"])
    def read(self, id):
        return super().read(id=id)

    @read.PATCH(
        rel="update",
        schema=fields.Inline("self", patchable=True),
        response_schema=fields.Inline("self", patchable=True),
    )
    @role_required(["admin"])
    def update(self, properties, id):
        item = self.manager.read(id)
        updated_item = self.manager.update(item, properties)
        return updated_item

    @ItemRoute.PATCH("/changePassword", rel="change_password")
    @role_required(["admin"])
    def change_password(
        self, user, new_password: fields.String()) -> fields.Boolean():
        user.password_hash = new_password
        db.session.commit()

        return True
Example #14
0
 def add_view(self, video) -> FieldSet({'count': fields.Integer(), 'view': fields.Inline(ViewResource)}):
     '''Create view and return view and count'''
     view = ViewResource().manager.create({
             'video_id': video.id
     })
     return {'count': video.count, 'view': view}
Example #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)
Example #16
0
 def resolve_profile(self) -> fields.Inline("self"):
     return self.manager.read(current_identity.id)
Example #17
0
 class Schema:
     roles = fields.ToMany('role')
     associate_group = fields.Inline('associate_group')
     associate_group_id = fields.String()
     last_login_at = fields.DateTimeString()
     current_login_at = fields.DateTimeString()
Example #18
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)))
Example #19
0
 class Schema:
     province = fields.Inline('province')
     id = fields.String()
     province_id = fields.String()
     _deleted_at = fields.DateString()
     _deleted_at._schema = c.DATETIME_SCHEMA
Example #20
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
Example #21
0
 class Schema:
     group = fields.Inline('group')
     group_id = fields.String()
     _deleted_at = fields.DateString()
     _deleted_at._schema = c.DATETIME_SCHEMA
class ConfigurationResource(PrincipalResource):
    '''The potion class for performing CRUD operations on the `Configuration` class.
    '''
    class Meta(object):
        model = Configuration
        id_attribute = 'key'
        id_field_class = fields.String

        # Read Permissions are conferred upon all users.
        #
        # 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`
        permissions = {'read': 'yes'}
        read_only_fields = ('description', 'key')
        exclude_fields = ('overwritten_value', 'overwritten')

        # Allow API users to filter on resources by name and value
        filters = {'key': True, 'value': True}

    class Schema(object):
        key = fields.String(description='The unique name of the resource.',
                            enum=CONFIGURATION_KEYS)
        value = fields.Custom(
            fields.Any(),
            attribute='key',
            description='The current value of the configuration setting. ',
            io='r',
            formatter=get_configuration,
        )
        description = fields.Custom(
            fields.String(),
            attribute='key',
            description=
            'An explanation of what the configuration setting does.',
            io='r',
            formatter=lambda key: _DEFAULT_CONFIGURATION_STORE[key][
                'description'],
        )
        defaultValue = fields.Custom(
            fields.Any(),
            attribute='key',
            description='The default value of the setting.',
            io='r',
            formatter=lambda key: _DEFAULT_CONFIGURATION_STORE[key]['value'],
        )

    # Flask Potion requires this to be an instance method.
    # pylint: disable=R0201, E1101
    @ItemRoute.POST(
        '/reset',
        title='Reset configuration',
        description='Resets the value of the configuration to its default.',
        schema=None,
        response_schema=fields.Inline('self'),
        rel='reset',
    )
    def reset_configuration(self, configuration):
        with AuthorizedOperation(
                'edit_resource', 'configuration',
                configuration.id), Transaction() as transaction:

            key = configuration.key
            default_value = _DEFAULT_CONFIGURATION_STORE[key]['value']
            old_value = get_configuration(key)

            message = (
                'The configuration for \'%s\' is being reset to its default value. '
                'The existing value is \'%s\'. '
                'The new (and default) value is \'%s\'. ') % (key, old_value,
                                                              default_value)
            g.request_logger.info(message)

            # By setting `overwritten` to False, we are signifying that we want to have the default
            # value apply. For housekeeping reasons, we also set `overwritten_value` back to None.
            configuration.overwritten_value = None
            configuration.overwritten = False
            transaction.add_or_update(configuration, flush=True)

            # Restart gunicorn when a datasource is selected.
            restart_gunicorn_on_datasource_change(key, old_value,
                                                  default_value)

        return configuration

    # Flask Potion requires this to be an instance method.
    # pylint: disable=R0201, E1101
    @ItemRoute.POST(
        '/set',
        title='Set configuration',
        description=
        'Updates the value of the configuration to the value provided.',
        schema=fields.Any(),
        response_schema=fields.Inline('self'),
        rel='set',
    )
    def set_configuration(self, configuration, updated_value):
        with AuthorizedOperation(
                'edit_resource', 'configuration',
                configuration.id), Transaction() as transaction:
            key = configuration.key
            try:
                old_value = get_configuration(key)
                assert_valid_configuration(key, updated_value)

                message = ('The configuration for \'%s\' is being updated. '
                           'The existing value is \'%s\'. '
                           'The new value is \'%s\'. ') % (key, old_value,
                                                           updated_value)
                g.request_logger.info(message)
            except Exception as e:
                raise BadRequest(description=e.message)

            configuration.overwritten_value = (updated_value, )
            configuration.overwritten = True
            transaction.add_or_update(configuration, flush=True)

            # NOTE(vedant): I have NO idea why the object is stored by default as an array so we
            # always unpack it and reset the value.
            configuration.overwritten_value = configuration.overwritten_value[
                0]
            transaction.add_or_update(configuration)

            # Restart gunicorn when a new datasource is selected.
            restart_gunicorn_on_datasource_change(key, old_value,
                                                  updated_value)

        return configuration
Example #23
0
 class Schema:
     projects = fields.Array(fields.Inline('project'))