From 562e2653c94708ccd9ff2c1f7f49712e7d96c568 Mon Sep 17 00:00:00 2001 From: esensar Date: Sun, 23 Sep 2018 01:22:19 +0200 Subject: [PATCH] Add role management routes --- app/accounts/__init__.py | 40 ++++++++++++++++++++++- app/accounts/models.py | 10 +++--- app/api/__init__.py | 6 +++- app/api/resources/account.py | 49 ++++++++++++++++++++++++++++ migrations/versions/55611f1868bd_.py | 44 +++++++++++++++++++++++++ 5 files changed, 143 insertions(+), 6 deletions(-) create mode 100644 migrations/versions/55611f1868bd_.py diff --git a/app/accounts/__init__.py b/app/accounts/__init__.py index dc587af..81afe41 100644 --- a/app/accounts/__init__.py +++ b/app/accounts/__init__.py @@ -1,6 +1,6 @@ from app.core import bcrypt from flask import Blueprint -from .models import Account +from .models import Account, Role accounts_bp = Blueprint('accounts', __name__) @@ -44,6 +44,44 @@ def update_account_role(account_id, role_id): acc.save() +def create_role(display_name, permissions): + """ + Tries to create role + + :param display_name: Name of role - display only + :param permissions: List of strings - permissions that this role has + :type display_name: String + :type permissions: List of String + :returns: True if role is successfully created + :rtype: Boolean + :raises: ValueError if role already exists + """ + role = Role(display_name, permissions) + role.save() + + +def get_role(role_id): + """ + Tries to get role + + :param role_id: Id of role + :type role_id: int + :returns: Role if found + :rtype: Role + """ + return Role.get(role_id) + + +def get_all_roles(): + """ + Gets all roles + + :returns: Role list if found + :rtype: List of Roles + """ + return Role.get_all() + + def create_token(username, password): """ Tries to create token for account with given parameters. diff --git a/app/accounts/models.py b/app/accounts/models.py index eeedcc6..c85fc8b 100644 --- a/app/accounts/models.py +++ b/app/accounts/models.py @@ -122,10 +122,12 @@ class Role(db.Model): __tablename__ = 'roles' id = db.Column(db.Integer, primary_key=True) - display_name = db.Column(db.String) + display_name = db.Column(db.String, unique=True) + permissions = db.Column(db.ARRAY(db.String)) - def __init__(self, name): + def __init__(self, name, permissions): self.display_name = str(name) + self.permissions = permissions def save(self): """ @@ -147,7 +149,7 @@ class Role(db.Model): """ Get role with id = roleId """ - return Role.query.filter_by(id=roleId) + return Role.query.filter_by(id=roleId).first_or_404() def __repr__(self): - return '' % self.display_name + return '' % self.display_name, self.permissions diff --git a/app/api/__init__.py b/app/api/__init__.py index 24fc08f..ba6cbb5 100644 --- a/app/api/__init__.py +++ b/app/api/__init__.py @@ -40,7 +40,9 @@ class ProtectedResource(Resource): def add_resources(): from .resources.account import (AccountResource, AccountListResource, - AccountRoleResource) + AccountRoleResource, + RoleResource, + RolesResource) from .resources.token import TokenResource, ValidateTokenResource from .resources.device import (DeviceResource, DeviceRecordingResource, @@ -53,6 +55,8 @@ def add_resources(): api.add_resource(AccountResource, '/v1/accounts/') api.add_resource(AccountListResource, '/v1/accounts') api.add_resource(AccountRoleResource, '/v1/accounts//role') + api.add_resource(RoleResource, '/v1/roles/') + api.add_resource(RolesResource, '/v1/roles') api.add_resource(TokenResource, '/v1/token') api.add_resource(ValidateTokenResource, '/v1/token/validate') api.add_resource(DeviceResource, '/v1/devices/') diff --git a/app/api/resources/account.py b/app/api/resources/account.py index 2933064..02c8f25 100644 --- a/app/api/resources/account.py +++ b/app/api/resources/account.py @@ -17,6 +17,32 @@ class RoleUpdateSchema(Schema): role_id = fields.Integer(required=True, load_only=True, location='json') +class RoleSchema(Schema): + role_id = fields.Integer(required=True, location='json') + display_name = fields.String(required=True, location='json') + permissions = fields.List(fields.String, required=True, + location='json', many=True) + + +class RoleWrapperSchema(Schema): + role = fields.Nested(RoleSchema, required=True, location='json') + + +class RolesWrapperSchema(Schema): + roles = fields.Nested(RoleSchema, required=True, + location='json', many=True) + + +class RoleCreationSchema(Schema): + display_name = fields.String(required=True, location='json') + permissions = fields.List(fields.String, required=True, + location='json', many=True) + + +class RoleCreationWrapperSchema(Schema): + role = fields.Nested(RoleCreationSchema, required=True, location='json') + + class UserWrapperSchema(Schema): user = fields.Nested(UserSchema, required=True, location='json') @@ -29,6 +55,29 @@ class AccountResource(ProtectedResource): abort(403, message='You can only get your own account', status='error') +class RoleResource(ProtectedResource): + @swag_from('swagger/get_role_spec.yaml') + def get(self, role_id): + return RoleWrapperSchema().dump( + {'role': accounts.get_role(role_id)}), 200 + + +class RolesResource(ProtectedResource): + @use_args(RoleCreationWrapperSchema()) + @swag_from('swagger/create_role_spec.yaml') + def post(self, args): + args = args['role'] + success = accounts.create_role(args['display_name'], + args['permissions']) + if success: + return '', 201 + + @swag_from('swagger/get_roles_spec.yaml') + def get(self): + return RolesWrapperSchema().dump( + {'roles': accounts.get_all_roles()}), 200 + + class AccountRoleResource(ProtectedResource): @use_args(RoleUpdateSchema()) @swag_from('swagger/update_account_role_spec.yaml') diff --git a/migrations/versions/55611f1868bd_.py b/migrations/versions/55611f1868bd_.py new file mode 100644 index 0000000..e788f28 --- /dev/null +++ b/migrations/versions/55611f1868bd_.py @@ -0,0 +1,44 @@ +"""empty message + +Revision ID: 55611f1868bd +Revises: 5aa58dcd7a2c +Create Date: 2018-09-23 00:56:34.193022 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '55611f1868bd' +down_revision = '5aa58dcd7a2c' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('roles', sa.Column('permissions', sa.ARRAY(sa.String()), nullable=True)) + op.create_unique_constraint(None, 'roles', ['display_name']) + role = sa.table('roles', sa.column('id', sa.Integer), + sa.column('permissions', sa.ARRAY(sa.String))) + op.execute(role.update().where(role.c.id == op.inline_literal(1)). + values({'permissions': + ['CREATE_DEVICE_TYPE', 'ASSIGN_ROLE', + 'CREATE_DEVICE', 'CREATE_DASHBOARD', + 'READ_DEVICE_TYPES', 'READ_ROLES']}) + ) + + op.execute(role.update().where(role.c.id == op.inline_literal(2)). + values({'permissions': + ['CREATE_DEVICE', 'CREATE_DASHBOARD', + 'READ_DEVICE_TYPES', 'READ_ROLES']}) + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint(None, 'roles', type_='unique') + op.drop_column('roles', 'permissions') + # ### end Alembic commands ###