Skip to content

Commit

Permalink
Don't show role_only_columns if not role; allow viewing view if role …
Browse files Browse the repository at this point in the history
…in roles_accepted
  • Loading branch information
Brian Peterson committed Aug 21, 2019
1 parent 4c51ebd commit 8d824f1
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 5 deletions.
3 changes: 2 additions & 1 deletion flask_secure_admin/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@

from .base import SecureAdminBlueprint
from .security import (SUPER_ROLE, SecureRedirectIndex, SecureDefaultIndex,
SecureModelView)
SecureModelView, scaffold_form_respecting_roles,
scaffold_list_columns_respecting_roles)
25 changes: 23 additions & 2 deletions flask_secure_admin/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@
from flask_admin import helpers as admin_helpers, AdminIndexView, expose
from flask_security import Security, login_required

from .security import SecureModelView, SecureDefaultIndex
from .security import (
SecureModelView, SecureDefaultIndex,
scaffold_list_columns_respecting_roles,
scaffold_form_respecting_roles, SUPER_ROLE
)
from .contrib.sqlsoup import override___name___on_sqlsoup_model, SQLSoupUserDataStore
from .templates import load_master_template
from .utils import encrypt_password, create_initial_admin_user
Expand Down Expand Up @@ -42,14 +46,15 @@ class SecureAdminBlueprint(Blueprint):
]

def __init__(self, name=None, models=None,
view_options=None, index_url=None,
view_options=None, admin_roles_accepted=None,
*args, **kwargs):
self.app_name = name
assert self.app_name, "Admin instances must have a name value"
self.models = models or []
self.models.extend(self.DEFAULT_MODELS)
self.view_options = view_options or []
self.view_options.extend(self.DEFAULT_VIEW_OPTIONS)
self.admin_roles_accepted = admin_roles_accepted or [SUPER_ROLE]

# Initialize the below as a best practice,
# so they can be referenced before assignment
Expand Down Expand Up @@ -122,10 +127,26 @@ def on_after_add_layout_to_admin(self, admin, app, db, options):
def add_layout_to_admin(self, admin, app, db, options):
""" Add auth views, and for each additional model specified
get the model from the database which must be set on app. """

for model_name, view_options_bag in zip_longest(
self.models, self.view_options, fillvalue={}):

# Add default view options
if not view_options_bag.get('scaffold_list_columns'):
view_options_bag['scaffold_list_columns'] = \
scaffold_list_columns_respecting_roles
if not view_options_bag.get('scaffold_form'):
view_options_bag['scaffold_form'] = \
scaffold_form_respecting_roles
if not view_options_bag.get('role_only_columns'):
view_options_bag['role_only_columns'] = dict()
if not view_options_bag.get('roles_accepted'):
view_options_bag['roles_accepted'] = self.admin_roles_accepted

# TODO: SQLSoup-specific
model = getattr(db, model_name)
model = override___name___on_sqlsoup_model(model)

DerviedModelViewCls = \
type(f'Secure{model.__name__}View',
(SecureModelView,),
Expand Down
4 changes: 4 additions & 0 deletions flask_secure_admin/security/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,7 @@
from .data import SUPER_ROLE
from .model_view import SecureModelView
from .indexes import SecureRedirectIndex, SecureDefaultIndex
from .role_scaffolding import (
scaffold_list_columns_respecting_roles,
scaffold_form_respecting_roles
)
15 changes: 13 additions & 2 deletions flask_secure_admin/security/model_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,22 @@ class SecureModelView(sqla.ModelView):
def __repr__(self):
return f"<'{self.name}' ModelView>"

def rebuild_views_respecting_access(self):
# Rebuild edit & list views based on who is accessing them
self._refresh_forms_cache()
self._list_columns = self.get_list_columns()

def has_one_accepted_role(self, user):
return any(
[current_user.has_role(r)
for r in self.roles_accepted])

def is_accessible(self):
if (current_user.is_active and
current_user.is_authenticated and
current_user.has_role(SUPER_ROLE)):
return True
self.has_one_accepted_role(current_user)):
self.rebuild_views_respecting_access()
return True
else:
user_ref = 'AnonymousUser' if \
current_user.is_anonymous else \
Expand Down
41 changes: 41 additions & 0 deletions flask_secure_admin/security/role_scaffolding.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@

from flask_admin.contrib.sqla.view import ModelView as SQLAModelView
from .data import SUPER_ROLE
from flask_security import current_user
from flask_admin.contrib.sqla.form import get_form as get_sqla_form


def scaffold_list_columns_respecting_roles(self):
""" Respect a new view option, `role_only_columns`,
in the list view. Must be a dictionary mapping
between role names and columns which only users
with this role are allowed to see. """
columns = SQLAModelView.scaffold_list_columns(self)
role_only_columns = self.role_only_columns or dict()
super_only_columns = role_only_columns.get(SUPER_ROLE) or []
if current_user and not current_user.has_role(SUPER_ROLE):
columns = [c for c in columns if c not in super_only_columns]
return columns


def scaffold_form_respecting_roles(self):
""" Just like regular SQLAModelView `scaffold_form()`,
except that we exclude `role_only_columns`
if user does not have the expected role. """
exclude = list(self.form_excluded_columns or [])
role_only_columns = self.role_only_columns or dict()
super_only_columns = role_only_columns.get(SUPER_ROLE) or []
if current_user and not current_user.has_role(SUPER_ROLE):
exclude.extend(role_only_columns)
converter = self.model_form_converter(self.session, self)
form_class = get_sqla_form(self.model, converter,
base_class=self.form_base_class,
only=self.form_columns,
exclude=exclude,
field_args=self.form_args,
ignore_hidden=self.ignore_hidden,
extra_fields=self.form_extra_fields)

if self.inline_models:
form_class = self.scaffold_inline_form_models(form_class)
return form_class

0 comments on commit 8d824f1

Please sign in to comment.