Плагин PermissionPlugin¶
Плагин Permission позволяет:
Повесить декораторы на роутеры
- Добавить ограничительную логику при выгрузке объектов (GET запрос):
ограничить выгрузку по атрибутам (они не будут даже выгружаться из БД, если только специально к ним не обратиться);
ограничить выгрузку по строкам;
ограничить выгрузку по строкам в зависимости от результатов сложных фильтров, например доступны только те пользователи, которые состоят в группе, в которой данный пользователь является владельцем
Предобработка данных для обновления объекта (PATCH запрос) и создания (POST запрос).
Выполнить проверку на возможность удаления объекта данным пользователем.
Работа с плагином¶
Чтобы создать систему пермишенов для какой-либо модели, нужно:
Создать класс от
combojsonapi.permission.permission_system.PermissionMixin, ниже будет более подробно сказано об этомВ ресурс менеджере в
data_layerуказать, какие методы должны использовать данный класс с пермишенамиЕсли нам нужно, чтобы на конкретный ресурс не накручивались пермишн декораторы, необходимо в ресурсе указать атрибут
disable_global_decoratorsОбщие пермишены можно применять даже не накручивая на каждый ресурс отдельно, для этого есть:code:permission_manager https://flask-combo-jsonapi.readthedocs.io/en/latest/permission.html.Для того, чтобы отключить его, нужно у класса назначить
disable_permission. Например:
class AuthResource(ResourceList):
disable_permission = True
disable_global_decorators = True
...
По умолчанию из БД выгружаются только поля, указанные в запросеи разрешенные в пермишенах. Если в вашей модели есть property, который обращается к незапрошенному полю модели (но при этом это поле не нужно возвращать в запросе), то такие зависимости необходимо указать у модели в классе
Meta`в атрибуте :code:`required_fields. Например:
class User:
class Meta:
required_fields = {
'full_name': ['first_name', 'second_name'],
}
first_name = Column()
second_name = Column()
@property
def full_name(self):
return ' '.join([self.first_name, self.second_name])
...
API класса PermissionMixin¶
Свойства:
permission_for_get: PermissionForGet
Разрешения для пользователя в методе get. Содержит свойства:
filters: List- список фильтров, которые нужно применить при выгрузке объектов. Например можем задать, чтобы пользователь при выгрузке видел только себя
joins: List- список джойнов, которые нужно присоединить к данному запросу на выгрузку. Например пользователь может увидеть только тех пользователей, которые входят в некую группу, в которую он входит.
allow_columns: Dict[str, int]- атрибуты модели, которые доступны и вес доступа, влияет на то что пермишены могут быть разрешающими, а могут быть запрещающими
forbidden_columns: Dict[str, int]- атрибуты модели, которые запрещены и вес доступа, влияет на то что пермишены могут быть разрешающими, а могут быть запрещающими
columns: Set[str]- атрибуты модели, доступны пользователю, после сложения разрешающий и запрещающих массивов по весу
permission_for_patch: PermissionForPatch
Разрешения для пользователя в методе patch. Содержит свойства:
allow_columns: Dict[str, int]- атрибуты модели, которые доступны и вес доступа, влияет на то что пермишены могут быть разрешающими, а могут быть запрещающими
forbidden_columns: Dict[str, int]- атрибуты модели, которые запрещены и вес доступа, влияет на то что пермишены могут быть разрешающими, а могут быть запрещающими
columns: Set[str]- атрибуты модели, доступны пользователю, после сложения разрешающий и запрещающих массивов по весу
permission_for_post: PermissionForPost
Разрешения для пользователя в методе post. Содержит свойства:
allow_columns: Dict[str, int]- атрибуты модели, которые доступны и вес доступа, влияет на то что пермишены могут быть разрешающими, а могут быть запрещающими
forbidden_columns: Dict[str, int]- атрибуты модели, которые запрещены и вес доступа, влияет на то что пермишены могут быть разрешающими, а могут быть запрещающими
columns: Set[str]- атрибуты модели, доступны пользователю, после сложения разрешающий и запрещающих массивов по весу
Методы:
get(self, *args, many=True, user_permission: PermissionUser = None, **kwargs) -> PermissionForGet
Ограничения на элементы описанные в PermissionForGet для данного пользователя в GET запросах
bool many- запрашивают через ResourceList или ResourceDetail
PermissionUser user_permission- ограничения для данного пользователя, можно получить доступ к ограничениям по другим моделям для данного пользователя для разных методов (GET, POST, PATCH).
post_data(self, *args, data=None, user_permission: PermissionUser = None, **kwargs) -> Dict
Предобработка данных в соответствие с ограничениями перед создание объекта. Должен вернуть обработанные данные для нового объекта
Dict data- данные для создания объекта;
PermissionUser user_permission- ограничения для данного пользователя, можно получить доступ к ограничениям по другим моделям для данного пользователя для разных методов (GET, POST, PATCH).
post_permission(self, *args, user_permission: PermissionUser = None, **kwargs) -> PermissionForPost
Ограничения на элементы описанные в PermissionForPost для данного пользователя в POST запросах
PermissionUser user_permission- ограничения для данного пользователя, можно получить доступ к ограничениям по другим моделям для данного пользователя для разных методов (GET, POST, PATCH).
patch_data(self, *args, data=None, obj=None, user_permission: PermissionUser = None, **kwargs) -> Dict
Предобработка данных в соответствие с ограничениями перед обновлением объекта. Должен вернуть обработанные данные для обновления объекта
Dict data- входные данные, прошедшие валидацию через схему marshmallow;
obj- обновляемый объект из БД;
PermissionUser user_permission- ограничения для данного пользователя, можно получить доступ к ограничениям по другим моделям для данного пользователя для разных методов (GET, POST, PATCH).
patch_permission(self, *args, user_permission: PermissionUser = None, **kwargs) -> PermissionForPatch
Ограничения на элементы описанные в PermissionForPatch для данного пользователя в PATCH запросах
PermissionUser user_permission- ограничения для данного пользователя, можно получить доступ к ограничениям по другим моделям для данного пользователя для разных методов (GET, POST, PATCH).
delete(self, *args, obj=None, user_permission: PermissionUser = None, **kwargs) -> bool
Permissions check if user is allowed to delete the
objobject. Object won’t be deleted if anydeletemethod returns False.
Проверка пермишеннов на возможность удалить данный объект (obj). Если хотя бы одна из функций, вернёт False, то удаление не произойдёт
PermissionUser user_permission- ограничения для данного пользователя, можно получить доступ к ограничениям по другим моделям для данного пользователя для разных методов (GET, POST, PATCH).
Описания в ресурс менеджерах¶
В разделе data_layer можно указать следующие типы пермишенов:
permission_get: List- список классов, из которых будет запрашиваться методgetpermission_post: List- список классов, из которых будет запрашиваться методpost_permissionиpost_datapermission_patch: List- список классов, из которых будет запрашиваться методpatch_permissionиpatch_datapermission_delete: List- список классов, из которых будет запрашиваться методdelete
Пример подключения плагина¶
model
from enum import Enum
class Role(Enum):
admin = 1
limited_user = 2
user = 3
block = 4
class User(db.Model):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String)
fullname = Column(String)
email = Column(String)
password = Column(String)
role = Column(Integer)
permission
from combojsonapi.permission.permission_system import PermissionMixin, PermissionForGet, \
PermissionUser, PermissionForPatch
class PermissionListUser(PermissionMixin):
ALL_FIELDS = self_json_api.model.__mapper__.column_attrs.keys()
SHORT_INFO_USER = ['id', 'name']
def get(self, *args, many=True, user_permission: PermissionUser = None, **kwargs) -> PermissionForGet:
"""Setting avatilable columns"""
if current_user.role == Role.admin.value:
self.permission_for_get.allow_columns = (self.ALL_FIELDS, 10)
elif current_user.role in [Role.limited_user.value, Role.user.value]:
# limit attributes and forbid to view blocked users
self.permission_for_get.allow_columns = (self.SHORT_INFO_USER, 0)
self.permission_for_get.filters.append(User.role != Role.block.value)
return self.permission_for_get
class PermissionDetailUser(PermissionMixin):
ALL_FIELDS = self_json_api.model.__mapper__.column_attrs.keys()
AVAILABLE_FIELDS_FOR_PATCH = ['password']
def get(self, *args, many=True, user_permission: PermissionUser = None, **kwargs) -> PermissionForGet:
"""Setting avatilable columns"""
if current_user.role in [Role.limited_user.value, Role.user.value]:
# only current user is allowed to be requested
self.permission_for_get.filters.append(User.id != current_user.id)
return self.permission_for_get
def patch_permission(self, *args, user_permission: PermissionUser = None, **kwargs) -> PermissionForPatch:
"""Only password change is allowed"""
self.permission_for_patch.allow_columns = (self.AVAILABLE_FIELDS_FOR_PATCH, 0)
return self.permission_for_patch
def patch_data(self, *args, data: Dict = None, obj: User = None, user_permission: PermissionUser = None, **kwargs) -> Dict:
# password
password = data.get('password')
if password is not None:
return {'password': hashlib.md5(password.encode()).hexdigest()}
return {}
class PermissionPatchAdminUser(PermissionMixin):
"""Allow admin user to change any field"""
ALL_FIELDS = self_json_api.model.__mapper__.column_attrs.keys()
def patch_permission(self, *args, user_permission: PermissionUser = None, **kwargs) -> PermissionForPatch:
"""Only password change is allowed"""
if current_user.role == Role.admin.value:
self.permission_for_patch.allow_columns = (self.ALL_FIELDS, 10) # задаём вес 10, это будет более приоритетно
return self.permission_for_patch
def patch_data(self, *args, data: Dict = None, obj: User = None, user_permission: PermissionUser = None, **kwargs) -> Dict:
if current_user.role == Role.admin.value:
password = data.get('password')
if password is not None:
data['password'] = hashlib.md5(password.encode()).hexdigest()
return data
return {}
views
class UserResourceList(ResourceList):
schema = UserSchema
method = ['GET']
data_layer = {
'session': db.session,
'model': User,
'short_format': ['id', 'name'],
'permission_get': [PermissionListUser],
}
class UserResourceDetail(ResourceDetail):
schema = UserSchema
method = ['GET']
data_layer = {
'session': db.session,
'model': User,
'short_format': ['id', 'name'],
'permission_get': [PermissionDetailUser],
'permission_patch': [PermissionDetailUser, PermissionPatchAdminUser],
}
__init__
api_json = Api(
app,
decorators=(login_required,),
plugins=[
PermissionPlugin(),
]
)
Пример выгрузки различных атрибутов объекта в зависимости от адреса, по которому был запрошен объект¶
permission
from combojsonapi.permission.permission_system import PermissionMixin, PermissionForGet, \
PermissionUser
class PermissionListUser(PermissionMixin):
SHORT_INFO_USER = ['id', 'name']
EXTENDED_USER_INFO = ['id', 'name', 'fullname', 'email', 'role']
ENDPOINTS_FOR_EXTENDED_INFO = ['computer_list', 'phone_list']
def get(self, *args, many=True, user_permission: PermissionUser = None, **kwargs) -> PermissionForGet:
if request.endpoint in self.ENDPOINTS_FOR_EXTENDED_INFO:
self.permission_for_get.allow_columns = (self.EXTENDED_USER_INFO, 10)
else:
self.permission_for_get.allow_columns = (self.SHORT_INFO_USER, 0)
return self.permission_for_get
computer_list, phone_list - endpoint’ы схемы системы маршрутизации:
api_json.route(<Resource manager>, <endpoint name>, <url_1>, <url_2>, ...)