a2_rbac: allow management of subclasses by manager roles (#77366) #54

Closed
pmarillonnet wants to merge 1 commits from wip/77366-authn-manager-oidc-claim-edit into main
7 changed files with 60 additions and 4 deletions

View File

@ -19,7 +19,7 @@ from django.utils.text import slugify
from django.utils.translation import gettext
from django.utils.translation import gettext_lazy as _
from authentic2.a2_rbac.models import OrganizationalUnit, Role
from authentic2.a2_rbac.models import ADMIN_OP, OrganizationalUnit, Permission, Role
from authentic2.utils.misc import get_fk_model
from . import app_settings, utils
@ -94,6 +94,10 @@ MANAGED_CT = {
('authenticators', 'baseauthenticator'): {
'name': _('Manager of authenticators'),
'scoped_name': _('Authenticators - {ou}'),
'submanagement_cts': (
('authenticators', 'addroleaction'),
('authentic2_auth_oidc', 'oidcclaimmapping'),
),
},
('authentic2', 'apiclient'): {
'name': _('Manager of API clients'),
@ -140,5 +144,15 @@ def update_content_types_roles():
ct_admin_role.permissions.add(view_user_perm)
if MANAGED_CT[ct_tuple].get('must_manage_authorizations_user'):
ct_admin_role.permissions.add(manage_authorizations_user_perm)
for subclass in MANAGED_CT[ct_tuple].get('submanagement_cts', ()):
subct = ContentType.objects.get_by_natural_key(subclass[0], subclass[1])
perm, dummy = Permission.objects.get_or_create(
operation=utils.get_operation(ADMIN_OP),
target_ct=ContentType.objects.get_for_model(ContentType),
target_id=subct.pk,
ou__isnull=True,
ou=None,
)
ct_admin_role.permissions.add(perm)
ct_admin_role.permissions.add(search_ou_perm)
ct_admin_role.add_child(admin_role)

View File

@ -98,6 +98,10 @@ class BaseAuthenticator(models.Model):
return '%s - %s' % (self._meta.verbose_name, self.name or self.slug)
return str(self._meta.verbose_name)
@property
def related_model_permissions(self):
return {}
@property
def manager_form_classes(self):
return [(_('General'), self.manager_form_class)]

View File

@ -270,8 +270,6 @@ order = AuthenticatorsOrderView.as_view()
class AuthenticatorRelatedObjectMixin(MediaMixin, TitleMixin, PermissionMixin):
permissions = ['authenticators.search_baseauthenticator']
def dispatch(self, request, *args, **kwargs):
self.authenticator = get_object_or_404(
BaseAuthenticator.authenticators.all(), pk=kwargs.get('authenticator_pk')
@ -285,6 +283,10 @@ class AuthenticatorRelatedObjectMixin(MediaMixin, TitleMixin, PermissionMixin):
except LookupError:
self.model = apps.get_model('authenticators', model_name)
self.permissions = self.authenticator.related_model_permissions.get(self.model) or [
'authenticators.search_baseauthenticator'
]
return super().dispatch(request, *args, **kwargs)
def get_form_class(self):

View File

@ -179,6 +179,13 @@ class OIDCProvider(BaseAuthenticator):
AddRoleAction: self.add_role_actions.all(),
}
@property
def related_model_permissions(self):
return {
OIDCClaimMapping: ['authentic2_auth_oidc.admin_oidcclaimmapping'],
AddRoleAction: ['authenticators.admin_addroleaction'],
}
@property
def jwkset(self):
if self.jwkset_json:

View File

@ -221,6 +221,12 @@ class SAMLAuthenticator(BaseAuthenticator):
AddRoleAction: self.add_role_actions.all(),
}
@property
def related_model_permissions(self):
return {
AddRoleAction: ['authenticators.admin_addroleaction'],
}
def clean(self):
if not (self.metadata or self.metadata_url):
raise ValidationError(_('One of the metadata fields must be filled.'))

View File

@ -37,7 +37,7 @@ def test_update_rbac(db):
# and 1 user view permission (for the role administrator)
# and 1 user manage authorizations permission (for the role administrator)
# and 1 ou view permission (for the user and role administrators)
assert Permission.objects.count() == 10
assert Permission.objects.count() == 12
def test_delete_role(db):

View File

@ -393,6 +393,29 @@ def test_authenticators_oidc_import_errors(app, superuser, simple_role):
assert escape("Role not found: {'slug': 'xxx'}.") in resp.text
def test_authenticators_oidc_related_objects_permissions(app, simple_user, simple_role):
authenticator = OIDCProvider.objects.create(slug='idp1', order=42, ou=get_default_ou(), enabled=True)
authenticator.save()
mapping = OIDCClaimMapping.objects.create(authenticator=authenticator, claim='test', attribute='hop')
action = AddRoleAction.objects.create(authenticator=authenticator, role=simple_role)
simple_user.roles.add(simple_role.get_admin_role()) # grant user access to /manage/
role = Role.objects.get(name='Manager of authenticators')
login(app, simple_user, path='/')
app.get(authenticator.get_absolute_url(), status=403)
app.get(f'/manage/authenticators/{authenticator.pk}/oidcclaimmapping/{mapping.pk}/edit/', status=403)
app.get(f'/manage/authenticators/{authenticator.pk}/addroleaction/{action.pk}/delete/', status=403)
app.get(f'/manage/authenticators/{authenticator.pk}/addroleaction/add/', status=403)
simple_user.roles.add(role)
app.get(authenticator.get_absolute_url())
app.get(f'/manage/authenticators/{authenticator.pk}/oidcclaimmapping/{mapping.pk}/edit/')
app.get(f'/manage/authenticators/{authenticator.pk}/addroleaction/{action.pk}/delete/')
app.get(f'/manage/authenticators/{authenticator.pk}/addroleaction/add/')
def test_authenticators_fc(app, superuser):
resp = login(app, superuser, path='/manage/authenticators/')