Access Control for Flask-Admin

本秂侑毒 提交于 2019-12-30 10:34:53

问题


My flask app centers around modifying models based on SQLAlchemy. Hence, I find flask-admin a great plugin because it maps my SQLA models to forms with views already defined with a customizable interface that is tried and tested.

I understand that Flask-admin is intended to be a plugin for administrators managing their site's data. However, I don't see why I can't use FA as a framework for my users to CRUD their data as well.

To do this, I have written the following:

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)

Note: I am using Flask-User which is built on top of Flask-Login.

The code above works. However, it is difficult to abstract as a base class for other models which I would like to implement access control for CRUD operations and Index/Edit/Detail/Delete views.

Mainly, the problems are:

  1. the API method, is_accessible, does not provide the primary key of the model. This key is needed because in almost all cases relationships between users and entities are almost always stored via relationships or in the model table directly (i.e. having user_id in your model table).

  2. some views, such as delete_view, do not provide the instance id that can be retrieve easily. In delete_view, I had to copy the entire function just to add one extra line to check if it belongs to the right user.

Surely someone has thought about these problems.

How should I go about rewriting this to something that is more DRY and maintainable?

来源:https://stackoverflow.com/questions/46914054/access-control-for-flask-admin

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!