Skip to content

Commit

Permalink
feat: Add CRUD apis for role, permission, user (#1801)
Browse files Browse the repository at this point in the history
* Add CRUD apis for role, permission, user

* black and flake

* Add schema for user, encrypt password with pre_add

* Add edit model schema

* Add edit model schema

* complete apis

* Flake and Black

* Add tests

* lint

* Add view and permission view apis

* Fixed session error

* Fixed session error

* Fix test

* avoid session leak

* fix api test

* suggestions

* black flake

* Seperate tests

* add nested api for role permissions

* add test for default password validator

* Fix mysql test

* add test for invalid role

* add 1 test for invlid payload for role permission, suggestions

* Remove mutating apis on permissionApi, suggestions

Co-authored-by: Daniel Vaz Gaspar <danielvazgaspar@gmail.com>
  • Loading branch information
mayurnewase and dpgaspar committed Apr 12, 2022
1 parent 06664bd commit 0a6623d
Show file tree
Hide file tree
Showing 16 changed files with 1,627 additions and 0 deletions.
7 changes: 7 additions & 0 deletions flask_appbuilder/security/sqla/apis/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from flask_appbuilder.security.sqla.apis.permission import PermissionApi # noqa: F401
from flask_appbuilder.security.sqla.apis.permission_view_menu import ( # noqa: F401
PermissionViewMenuApi,
)
from flask_appbuilder.security.sqla.apis.role import RoleApi # noqa: F401
from flask_appbuilder.security.sqla.apis.user import UserApi # noqa: F401
from flask_appbuilder.security.sqla.apis.view_menu import ViewMenuApi # noqa: F401
1 change: 1 addition & 0 deletions flask_appbuilder/security/sqla/apis/permission/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .api import PermissionApi # noqa: F401
19 changes: 19 additions & 0 deletions flask_appbuilder/security/sqla/apis/permission/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from flask_appbuilder import ModelRestApi
from flask_appbuilder.models.sqla.interface import SQLAInterface
from flask_appbuilder.security.sqla.models import Permission


class PermissionApi(ModelRestApi):
resource_name = "permissions"
openapi_spec_tag = "Security Permissions"

class_permission_name = "Permission"
datamodel = SQLAInterface(Permission)
allow_browser_login = True
include_route_methods = {"info", "get", "get_list"}

list_columns = ["id", "name"]
show_columns = list_columns
add_columns = ["name"]
edit_columns = add_columns
search_columns = list_columns
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .api import PermissionViewMenuApi # noqa: F401
17 changes: 17 additions & 0 deletions flask_appbuilder/security/sqla/apis/permission_view_menu/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from flask_appbuilder import ModelRestApi
from flask_appbuilder.models.sqla.interface import SQLAInterface
from flask_appbuilder.security.sqla.models import PermissionView


class PermissionViewMenuApi(ModelRestApi):
resource_name = "permissionsviewmenus"
openapi_spec_tag = "Security Permissions View Menus"
class_permission_name = "PermissionViewMenu"
datamodel = SQLAInterface(PermissionView)
allow_browser_login = True

list_columns = ["id", "permission.name", "view_menu.name"]
show_columns = list_columns
add_columns = ["permission_id", "view_menu_id"]
edit_columns = add_columns
search_columns = list_columns
1 change: 1 addition & 0 deletions flask_appbuilder/security/sqla/apis/role/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .api import RoleApi # noqa: F401
154 changes: 154 additions & 0 deletions flask_appbuilder/security/sqla/apis/role/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
from flask import current_app, request
from flask_appbuilder import ModelRestApi
from flask_appbuilder.api import expose, safe
from flask_appbuilder.const import API_RESULT_RES_KEY
from flask_appbuilder.models.sqla.interface import SQLAInterface
from flask_appbuilder.security.decorators import permission_name, protect
from flask_appbuilder.security.sqla.apis.role.schema import (
RolePermissionListSchema,
RolePermissionPostSchema,
)
from flask_appbuilder.security.sqla.models import PermissionView, Role
from marshmallow import ValidationError
from sqlalchemy.exc import IntegrityError


class RoleApi(ModelRestApi):
resource_name = "roles"
openapi_spec_tag = "Security Roles"
class_permission_name = "Role"
datamodel = SQLAInterface(Role)
allow_browser_login = True

list_columns = ["id", "name"]
show_columns = list_columns
add_columns = ["name"]
edit_columns = ["name"]
search_columns = list_columns

list_role_permission_schema = RolePermissionListSchema()
add_role_permission_schema = RolePermissionPostSchema()
openapi_spec_component_schemas = (
RolePermissionListSchema,
RolePermissionPostSchema,
)

@expose("/<int:pk>/permissions", methods=["GET"])
@protect()
@safe
@permission_name("list_role_permissions")
def list_role_permissions(self, pk):
"""list role permissions
---
get:
parameters:
- in: path
schema:
type: integer
name: pk
responses:
200:
description: List of permissions
content:
application/json:
schema:
type: object
properties:
result:
$ref: '#/components/schemas/RolePermissionListSchema'
400:
$ref: '#/components/responses/400'
401:
$ref: '#/components/responses/401'
404:
$ref: '#/components/responses/404'
422:
$ref: '#/components/responses/422'
500:
$ref: '#/components/responses/500'
"""
role = self.datamodel.get(pk, select_columns=["permissions"])
if not role:
return self.response_404()

permissions = [
{
"id": p.id,
"permission_name": p.permission.name,
"view_menu_name": p.view_menu.name,
}
for p in role.permissions
]
return self.response(200, **{API_RESULT_RES_KEY: permissions})

@expose("/<int:role_id>/permissions", methods=["POST"])
@protect()
@safe
@permission_name("add_role_permissions")
def add_role_permissions(self, role_id):
"""add role permissions
---
post:
parameters:
- in: path
schema:
type: integer
name: role_id
requestBody:
description: Add role permissions schema
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/RolePermissionPostSchema'
responses:
200:
description: Permissions added
content:
application/json:
schema:
type: object
properties:
result:
$ref: '#/components/schemas/RolePermissionPostSchema'
400:
$ref: '#/components/responses/400'
401:
$ref: '#/components/responses/401'
404:
$ref: '#/components/responses/404'
422:
$ref: '#/components/responses/422'
500:
$ref: '#/components/responses/500'
"""
try:
item = self.add_role_permission_schema.load(request.json)
role = self.datamodel.get(role_id)
if not role:
return self.response_404()
permissions = []
for id in item["permission_view_menu_ids"]:
permission = (
current_app.appbuilder.get_session.query(PermissionView)
.filter_by(id=id)
.one_or_none()
)
if permission:
permissions.append(permission)

role.permissions = permissions
self.datamodel.edit(role, raise_exception=True)
return self.response(
200,
**{
API_RESULT_RES_KEY: self.add_role_permission_schema.dump(
item, many=False
)
},
)

except ValidationError as error:
return self.response_400(message=error.messages)
except IntegrityError as e:
return self.response_422(message=str(e.orig))
13 changes: 13 additions & 0 deletions flask_appbuilder/security/sqla/apis/role/schema.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from marshmallow import fields, Schema


class RolePermissionPostSchema(Schema):
permission_view_menu_ids = fields.List(
fields.Integer, required=True, description="List of permission view menu id"
)


class RolePermissionListSchema(Schema):
id = fields.Integer()
permission_name = fields.String()
view_menu_name = fields.String()
1 change: 1 addition & 0 deletions flask_appbuilder/security/sqla/apis/user/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .api import UserApi # noqa: F401
Loading

0 comments on commit 0a6623d

Please sign in to comment.