backoffice: allow limited access to cards via dispatched functions (#66784) #615
|
@ -173,6 +173,32 @@ def test_carddata_management(pub):
|
|||
resp = resp.form.submit('cancel')
|
||||
assert resp.location.endswith('/backoffice/data/foo/')
|
||||
|
||||
# check access to a single card
|
||||
carddef.workflow_roles = {'_editor': None}
|
||||
carddef.backoffice_submission_roles = []
|
||||
carddef.store()
|
||||
assert app.get('/backoffice/data/', status=403)
|
||||
assert app.get('/backoffice/data/foo/', status=403)
|
||||
assert app.get(f'/backoffice/data/foo/{carddata.id}/', status=403)
|
||||
|
||||
carddata = carddef.data_class()()
|
||||
carddata.just_created()
|
||||
carddata.store()
|
||||
assert app.get(f'/backoffice/data/foo/{carddata.id}/', status=403)
|
||||
|
||||
# simulate dynamic dispatch
|
||||
carddata.workflow_roles = {'_editor': user.roles}
|
||||
carddata.store()
|
||||
assert app.get(f'/backoffice/data/foo/{carddata.id}/', status=200)
|
||||
assert app.get('/backoffice/data/foo/', status=200)
|
||||
assert app.get('/backoffice/data/', status=403)
|
||||
|
||||
# attach carddata to user (should not give access)
|
||||
carddata.workflow_roles = {}
|
||||
carddata.user_id = user.id
|
||||
carddata.store()
|
||||
assert app.get(f'/backoffice/data/foo/{carddata.id}/', status=403)
|
||||
|
||||
|
||||
def test_carddata_management_categories(pub):
|
||||
user = create_user(pub)
|
||||
|
|
|
@ -1765,7 +1765,7 @@ class FormsDirectory(AccessControlled, Directory):
|
|||
get_response().set_backoffice_section(self.section)
|
||||
return super()._q_traverse(path)
|
||||
|
||||
def is_accessible(self, user):
|
||||
def is_accessible(self, user, traversal=False):
|
||||
if is_global_accessible(self.section):
|
||||
return True
|
||||
|
||||
|
|
|
@ -1928,7 +1928,7 @@ class WorkflowsDirectory(Directory):
|
|||
get_response().set_backoffice_section('workflows')
|
||||
return super()._q_traverse(path)
|
||||
|
||||
def is_accessible(self, user):
|
||||
def is_accessible(self, user, traversal=False):
|
||||
if is_global_accessible():
|
||||
return True
|
||||
|
||||
|
|
|
@ -77,7 +77,9 @@ class DataManagementDirectory(ManagementDirectory):
|
|||
def add_breadcrumb(self):
|
||||
get_response().breadcrumb.append(('data/', _('Cards')))
|
||||
|
||||
def is_accessible(self, user):
|
||||
def is_accessible(self, user, traversal=False):
|
||||
if traversal:
|
||||
return super().is_accessible(user=user, traversal=traversal)
|
||||
|
||||
if not user.can_go_in_backoffice():
|
||||
return False
|
||||
if get_publisher().get_backoffice_root().is_global_accessible('cards') and CardDef.keys():
|
||||
|
@ -111,6 +113,8 @@ class DataManagementDirectory(ManagementDirectory):
|
|||
get_response().set_title(_('Cards'))
|
||||
if not (CardDef.exists()):
|
||||
return self.empty_site_message(_('Cards'))
|
||||
if not self.is_accessible(get_request().user, traversal=False):
|
||||
raise errors.AccessForbiddenError()
|
||||
fpeters
commented
Vérification ajoutée sur la page d'index pour lever une erreur si l'utilisateur n'avait pas accès (puisque lors de la traversée tous les cas n'auront pas été attrapés). Vérification ajoutée sur la page d'index pour lever une erreur si l'utilisateur n'avait pas accès (puisque lors de la traversée tous les cas n'auront pas été attrapés).
|
||||
return template.QommonTemplateResponse(
|
||||
templates=['wcs/backoffice/data-management.html'], context={'view': self}
|
||||
)
|
||||
|
|
|
@ -116,7 +116,7 @@ class JournalDirectory(Directory):
|
|||
templates=['wcs/backoffice/journal.html'], context=context, is_django_native=True
|
||||
)
|
||||
|
||||
def is_accessible(self, user):
|
||||
def is_accessible(self, user, traversal=False):
|
||||
return user.can_go_in_admin()
|
||||
|
||||
def get_filter_form(self):
|
||||
|
|
|
@ -168,7 +168,7 @@ class ManagementDirectory(Directory):
|
|||
def add_breadcrumb(self):
|
||||
get_response().breadcrumb.append(('management/', _('Management')))
|
||||
|
||||
def is_accessible(self, user):
|
||||
def is_accessible(self, user, traversal=False):
|
||||
return user.can_go_in_backoffice()
|
||||
|
||||
def _q_traverse(self, path):
|
||||
|
|
|
@ -88,7 +88,7 @@ class RootDirectory(AccessControlled, Directory):
|
|||
return super()._q_traverse(path)
|
||||
|
||||
@classmethod
|
||||
def is_accessible(cls, subdirectory):
|
||||
def is_accessible(cls, subdirectory, traversal=False):
|
||||
# check a backoffice directory is accessible to the current user
|
||||
|
||||
if getattr(get_response(), 'filter', {}) and get_response().filter.get('admin_for_all'):
|
||||
|
@ -104,7 +104,7 @@ class RootDirectory(AccessControlled, Directory):
|
|||
|
||||
# if the directory defines a is_accessible method, use it.
|
||||
if hasattr(getattr(cls, subdirectory, None), 'is_accessible'):
|
||||
return getattr(cls, subdirectory).is_accessible(get_request().user)
|
||||
return getattr(cls, subdirectory).is_accessible(get_request().user, traversal=traversal)
|
||||
|
||||
return cls.is_global_accessible(subdirectory)
|
||||
|
||||
|
@ -182,7 +182,7 @@ class RootDirectory(AccessControlled, Directory):
|
|||
|
||||
def _q_index(self):
|
||||
for directory in ('studio', 'management'):
|
||||
if self.is_accessible(directory):
|
||||
if self.is_accessible(directory, traversal=True):
|
||||
pmarillonnet
commented
Pigé la PR dans son ensemble, sauf ici, pas compris en quoi Pigé la PR dans son ensemble, sauf ici, pas compris en quoi `traversal=True` ajouté ici n’est pas trop permissif ?
fpeters
commented
c'est très permissif mais justement derrière les vues en elles-mêmes font le contrôle et donc c'est ok si on laisse passer ici. c'est très permissif mais justement derrière les vues en elles-mêmes font le contrôle et donc c'est ok si on laisse passer ici.
|
||||
return redirect(directory + '/')
|
||||
raise errors.AccessForbiddenError()
|
||||
|
||||
|
@ -198,7 +198,9 @@ class RootDirectory(AccessControlled, Directory):
|
|||
|
||||
def _q_lookup(self, component):
|
||||
if component in [str(x[0]).strip('/') for x in self.menu_items]:
|
||||
if not self.is_accessible(component):
|
||||
if not self.is_accessible(component, traversal=True):
|
||||
# traversal=True will make it skip some expensive checks and
|
||||
# let directories/views further down apply their own checks.
|
||||
fpeters
commented
Ajout d'un paramètre traversal aux méthodes is_accessible(), qui permet d'indiquer quand l'accès se fait juste pour la traversée d'URL, et qu'un contrôle d'accès plus poussé aura lieu plus loin. Ajout d'un paramètre traversal aux méthodes is_accessible(), qui permet d'indiquer quand l'accès se fait juste pour la traversée d'URL, et qu'un contrôle d'accès plus poussé aura lieu plus loin.
|
||||
raise errors.AccessForbiddenError()
|
||||
return getattr(self, component)
|
||||
return super()._q_lookup(component)
|
||||
|
@ -218,7 +220,7 @@ class RootDirectory(AccessControlled, Directory):
|
|||
display_function = options.get('check_display_function')
|
||||
if display_function and not display_function(slug):
|
||||
continue
|
||||
if not self.is_accessible(slug):
|
||||
if not self.is_accessible(slug, traversal=False):
|
||||
continue
|
||||
if callable(v):
|
||||
label = v()
|
||||
|
|
|
@ -74,7 +74,7 @@ class ChangesDirectory(Directory):
|
|||
},
|
||||
)
|
||||
|
||||
def is_accessible(self, user):
|
||||
def is_accessible(self, user, traversal=False):
|
||||
return user.is_admin
|
||||
|
||||
|
||||
|
@ -138,7 +138,7 @@ class StudioDirectory(Directory):
|
|||
templates=['wcs/backoffice/studio.html'], context=context, is_django_native=True
|
||||
)
|
||||
|
||||
def is_accessible(self, user):
|
||||
def is_accessible(self, user, traversal=False):
|
||||
backoffice_root = get_publisher().get_backoffice_root()
|
||||
return (
|
||||
backoffice_root.is_accessible('forms')
|
||||
|
|
|
@ -408,7 +408,7 @@ class SubmissionDirectory(Directory):
|
|||
get_response().set_backoffice_section('submission')
|
||||
return super()._q_traverse(path)
|
||||
|
||||
def is_accessible(self, user):
|
||||
def is_accessible(self, user, traversal=False):
|
||||
if not user.can_go_in_backoffice():
|
||||
return False
|
||||
# check user has at least one role set for backoffice submission
|
||||
|
|
|
@ -50,6 +50,9 @@ class CardDef(FormDef):
|
|||
|
||||
confirmation = False
|
||||
|
||||
# users are not allowed to access carddata where they're submitter.
|
||||
user_allowed_to_access_own_data = False
|
||||
|
||||
category_class = CardDefCategory
|
||||
|
||||
def data_class(self, mode=None):
|
||||
|
|
|
@ -184,6 +184,9 @@ class FormDef(StorableObject):
|
|||
# prefixes for formdata variables
|
||||
var_prefixes = ['form']
|
||||
|
||||
# users are allowed to access formdata where they're submitter.
|
||||
user_allowed_to_access_own_data = True
|
||||
|
||||
# declarations for serialization
|
||||
TEXT_ATTRIBUTES = [
|
||||
'name',
|
||||
|
@ -1731,7 +1734,7 @@ class FormDef(StorableObject):
|
|||
|
||||
user_roles = ensure_role_are_strings(user_roles)
|
||||
|
||||
if formdata and formdata.is_submitter(user):
|
||||
if self.user_allowed_to_access_own_data and formdata and formdata.is_submitter(user):
|
||||
fpeters
commented
Comme tous les agents peuvent désormais traverser jusque /cards/foo/id il faut cette modification pour qu'un agent ne puisse pas voir une fiche qui serait liée à son compte. Comme tous les agents peuvent désormais traverser jusque /cards/foo/id il faut cette modification pour qu'un agent ne puisse pas voir une fiche qui serait liée à son compte.
|
||||
return True
|
||||
if self.is_of_concern_for_user(user):
|
||||
if not formdata:
|
||||
|
|
Quand on est en mode "traversal" (voir plus bas), on court-circuite pour envoyer sur la méthode parente qui se contentera de vérifier que l'accès backoffice est ok.