Controle de acesso para Flask-Admin
Meu aplicativo de balão gira em torno da modificação de modelos com base no SQLAlchemy. Portanto, considero o flask-admin um ótimo plug-in, pois mapeia meus modelos SQLA para formulários com visualizações já definidas com uma interface personalizável que é experimentada e testada.
Entendo que o Flask-admin se destina a ser um plugin paraadministradores gerenciar os dados de seus sites. No entanto, não vejo por que não posso usar a FA como uma estrutura para meus usuários CRUD seus dados também.
Para fazer isso, escrevi o seguinte:
class AuthorizationRequiredView(BaseView):
def get_roles(self):
raise NotImplemented("Override AuthorizationRequiredView.get_roles not set.")
def is_accessible(self):
if not is_authenticated():
return False
if not current_user.has_role(*self.get_roles()):
return False
return True
def inaccessible_callback(self, name, **kwargs):
if not is_authenticated():
return current_app.user_manager.unauthenticated_view_function()
if not current_user.has_role(*self.get_roles()):
return current_app.user_manager.unauthorized_view_function()
class InstructionModelView(DefaultModelView, AuthorizationRequiredView):
def get_roles(self):
return ["admin", "member"]
def get_query(self):
"""Jails the user to only see their instructions.
"""
base = super(InstructionModelView, self).get_query()
if current_user.has_role('admin'):
return base
else:
return base.filter(Instruction.user_id == current_user.id)
@expose('/edit/', methods=('GET', 'POST'))
def edit_view(self):
if not current_user.has_role('admin'):
instruction_id = request.args.get('id', None)
if instruction_id:
m = self.get_one(instruction_id)
if m.user_id != current_user.id:
return current_app.user_manager.unauthorized_view_function()
return super(InstructionModelView, self).edit_view()
@expose('/delete/', methods=('POST',))
def delete_view(self):
return_url = get_redirect_target() or self.get_url('.index_view')
if not self.can_delete:
return redirect(return_url)
form = self.delete_form()
if self.validate_form(form):
# id is InputRequired()
id = form.id.data
model = self.get_one(id)
if model is None:
flash(gettext('Record does not exist.'), 'error')
return redirect(return_url)
# message is flashed from within delete_model if it fails
if self.delete_model(model):
if not current_user.has_role('admin') \
and model.user_id != current_user.id:
# Denial: NOT admin AND NOT user_id match
return current_app.user_manager.unauthorized_view_function()
flash(gettext('Record was successfully deleted.'), 'success')
return redirect(return_url)
else:
flash_errors(form, message='Failed to delete record. %(error)s')
return redirect(return_url)
Nota: Estou usando o Flask-User, construído sobre o Flask-Login.
O código acima funciona. No entanto, é difícil abstrair como classe base para outros modelos que eu gostaria de implementar o controle de acesso para operações CRUD e exibições Index / Edit / Detail / Delete.
Principalmente, os problemas são:
o método da API,is_accessible
, não fornece a chave primária do modelo. Essa chave é necessária porque, em quase todos os casos, os relacionamentos entre usuários e entidades quase sempre são armazenados por meio de relacionamentos ou diretamente na tabela de modelos (ou seja, ter user_id na tabela de modelos).
algumas visualizações, comodelete_view
, não forneça o ID da instância que pode ser recuperado facilmente. Nodelete_view
, Tive que copiar a função inteira apenas para adicionar uma linha extra para verificar se ela pertence ao usuário certo.
Certamente alguém pensou sobre esses problemas.
Como devo reescrever isso para algo mais SECO e sustentável?