manager: display templated user info in sidebar (#81389)
gitea/authentic/pipeline/head This commit looks good Details

This commit is contained in:
Paul Marillonnet 2023-10-03 14:35:36 +02:00
parent ae2f54e98b
commit 952072cd0c
11 changed files with 211 additions and 13 deletions

View File

@ -1017,3 +1017,18 @@ class ServicesSettingsForm(forms.Form):
)
if RUNTIME_SETTINGS[setting.key]['type'] == 'colour':
self.fields[setting.key].widget = forms.TextInput(attrs={'type': 'color'})
class UsersAdvancedConfigurationForm(forms.Form):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for setting in Setting.objects.filter_namespace('users'):
self.fields[setting.key] = forms.CharField(
initial=setting.value,
label=RUNTIME_SETTINGS[setting.key]['name'],
required=False,
)
if RUNTIME_SETTINGS[setting.key]['type'] == 'text':
self.fields[setting.key].widget = forms.Textarea()

View File

@ -143,5 +143,11 @@
</div>
{% endif %}
{% if sidebar_advanced_info %}
<div id="advanced-info">
{{ sidebar_advanced_info|safe }}
</div>
{% endif %}
</aside>
{% endblock %}

View File

@ -0,0 +1,12 @@
{% extends "authentic2/manager/form.html" %}
{% load i18n %}
{% block breadcrumb %}
{{ block.super }}
<a href="{% url 'a2-manager-users' %}">{% trans 'Users' %}</a>
<a href="#">{% trans "Edit users management advanced configuration" %}</a>
{% endblock %}
{% block page_title %}
{% trans "Edit users management advanced configuration" %}
{% endblock %}

View File

@ -58,6 +58,11 @@ urlpatterns = required(
user_views.user_import_report,
name='a2-manager-users-import-report',
),
path(
'users/advanced/',
user_views.users_advanced_configuration_view,
name='a2-manager-users-advanced-configuration',
),
path('users/<int:ou_pk>/add/', user_views.user_add, name='a2-manager-user-add'),
path('users/<int:pk>/', user_views.user_detail, name='a2-manager-user-detail'),
path('users/<int:pk>/edit/', user_views.user_edit, name='a2-manager-user-edit'),

View File

@ -43,7 +43,7 @@ from authentic2.a2_rbac.models import OrganizationalUnit, Role, RoleParenting
from authentic2.a2_rbac.utils import get_default_ou
from authentic2.apps.journal.views import JournalViewWithContext
from authentic2.backends.ldap_backend import LDAPBackend
from authentic2.models import Attribute, PasswordReset
from authentic2.models import Attribute, PasswordReset, Setting
from authentic2.utils import hooks, spooler, switch_user
from authentic2.utils.misc import (
get_password_authenticator,
@ -53,6 +53,7 @@ from authentic2.utils.misc import (
select_next_url,
send_password_reset_mail,
)
from authentic2.utils.template import Template
from authentic2_idp_oidc.models import OIDCAuthorization, OIDCClient
from . import app_settings
@ -67,6 +68,7 @@ from .forms import (
UserEditForm,
UserNewImportForm,
UserRoleSearchForm,
UsersAdvancedConfigurationForm,
UserSearchForm,
)
from .journal_views import BaseJournalView
@ -153,11 +155,17 @@ class UsersView(HideOUColumnMixin, BaseTableView):
self.can_add = False
extra_actions = ctx['extra_actions'] = []
if self.request.user.has_perm('custom_user.admin_user'):
extra_actions.append(
{
'url': reverse('a2-manager-users-imports'),
'label': _('Import users'),
}
extra_actions.extend(
[
{
'url': reverse('a2-manager-users-imports'),
'label': _('Import users'),
},
{
'url': reverse('a2-manager-users-advanced-configuration'),
'label': _('Users management advanced configuration'),
},
]
)
return ctx
@ -426,6 +434,15 @@ class UserDetailView(OtherActionsMixin, BaseDetailView):
role_qs = role_qs.filter(ou=instance.ou)
return user.filter_by_perm('a2_rbac.manage_members_role', role_qs).exists()
def render_sidebar_advanced_info(self, context=None):
value = ''
if setting := Setting.objects.filter(key='users:backoffice_sidebar_template').first():
value = setting.value
if value and ('{{' in value or '{%' in value):
template = Template(value)
value = template.render(context=context)
return value
def get_context_data(self, **kwargs):
kwargs['default_ou'] = get_default_ou
roles = self.object.roles_and_parents().order_by('ou__name', 'name')
@ -478,6 +495,7 @@ class UserDetailView(OtherActionsMixin, BaseDetailView):
days=self.object.ou.clean_unused_accounts_deletion
)
ctx = super().get_context_data(**kwargs)
ctx['sidebar_advanced_info'] = self.render_sidebar_advanced_info(ctx)
return ctx
@ -1041,3 +1059,24 @@ class UserJournal(PermissionMixin, JournalViewWithContext, BaseJournalView):
user_journal = UserJournal.as_view()
class UsersAdvancedConfigurationView(FormView):
template_name = 'authentic2/manager/users_advanced_configuration.html'
form_class = UsersAdvancedConfigurationForm
title = _('Edit users management advanced configuration')
success_url = '..'
permissions = ['authentic2.change_user']
def form_valid(self, form):
for key, value in form.cleaned_data.items():
try:
setting = Setting.objects.get(key=key)
except Setting.DoesNotExist:
continue
setting.value = value
setting.save()
return super().form_valid(form)
users_advanced_configuration_view = UsersAdvancedConfigurationView.as_view()

View File

@ -9,12 +9,13 @@ def initialize_services_runtime_settings(apps, schema_editor):
if Setting.objects.filter(key__startswith='sso:').count() == 4:
return
for key, data in RUNTIME_SETTINGS.items():
Setting.objects.get_or_create(
key=key,
defaults={
'value': data['value'],
},
)
if key.startswith('sso:'):
Setting.objects.get_or_create(
key=key,
defaults={
'value': data['value'],
},
)
def clear_services_runtime_settings(apps, schema_editor):

View File

@ -0,0 +1,38 @@
from django.db import migrations
def initialize_users_advanced_config(apps, schema_editor):
from authentic2.utils.misc import RUNTIME_SETTINGS
Setting = apps.get_model('authentic2', 'Setting')
if Setting.objects.filter(key__startswith='users:').count() == 1:
return
for key, data in RUNTIME_SETTINGS.items():
if key.startswith('users:'):
Setting.objects.get_or_create(
key=key,
defaults={
'value': data['value'],
},
)
def clear_users_advanced_config(apps, schema_editor):
Setting = apps.get_model('authentic2', 'Setting')
# default config has been extended, do not try to revert it
if Setting.objects.filter(key__startswith='users:').count() != 1:
return
Setting.objects.filter(key__startswith='users:').delete()
class Migration(migrations.Migration):
dependencies = [
('authentic2', '0049_apiclient_allowed_user_attributes'),
]
operations = [
migrations.RunPython(initialize_users_advanced_config, reverse_code=clear_users_advanced_config),
]

View File

@ -1386,4 +1386,9 @@ RUNTIME_SETTINGS = {
'value': '',
'type': 'url',
},
'users:backoffice_sidebar_template': {
'name': _('Backoffice sidebar templated information'),
'value': '',
'type': 'text',
},
}

View File

@ -1404,6 +1404,28 @@ def test_manager_services_settings(app, admin):
assert Setting.objects.get(key='sso:generic_service_name').value == 'Some other name'
def test_manager_users_advanced_configuration_settings(app, admin):
for setting in Setting.objects.filter_namespace('users'):
assert setting.value == ''
resp = login(app, admin, 'a2-manager-services-settings')
resp.form.submit()
for setting in Setting.objects.filter_namespace('sso'):
assert setting.value == ''
# test value is saved correctly
resp = app.get(reverse('a2-manager-users-advanced-configuration'))
resp.form.set('users:backoffice_sidebar_template', 'Foo {{ user }}')
resp.form.submit()
assert Setting.objects.get(key='users:backoffice_sidebar_template').value == 'Foo {{ user }}'
# test form is prefilled with the right value
resp = app.get(reverse('a2-manager-users-advanced-configuration'))
assert resp.form['users:backoffice_sidebar_template'].value == 'Foo {{ user }}'
def test_manager_menu_json(app, admin):
expected = [
{

View File

@ -72,3 +72,15 @@ def test_migration_custom_user_0047_initialize_services_runtime_settings(transac
assert Setting.objects.filter(key__startswith='sso:').count() == 4
for setting in Setting.objects.filter(key__startswith='sso:'):
assert setting.value == ''
def test_migration_custom_user_0050_initialize_users_advanced_configuration(transactional_db, migration):
old_apps = migration.before([('authentic2', '0049_apiclient_allowed_user_attributes')])
Setting = old_apps.get_model('authentic2', 'Setting')
before = Setting.objects.count()
new_apps = migration.apply([('authentic2', '0050_initialize_users_advanced_configuration')])
Setting = new_apps.get_model('authentic2', 'Setting')
assert Setting.objects.count() == before + 1
assert Setting.objects.filter(key__startswith='users:').count() == 1
assert Setting.objects.get(key='users:backoffice_sidebar_template').value == ''

View File

@ -35,7 +35,7 @@ from authentic2.a2_rbac.utils import get_default_ou, get_operation, get_view_use
from authentic2.apps.journal.models import Event
from authentic2.custom_user.models import User
from authentic2.manager import user_import
from authentic2.models import Attribute, AttributeValue, UserExternalId
from authentic2.models import Attribute, AttributeValue, Setting, UserExternalId
from authentic2_idp_oidc.models import OIDCAuthorization, OIDCClient
from .utils import get_link_from_mail, login, logout
@ -1331,6 +1331,49 @@ def test_manager_user_authorizations_breadcrumb(app, superuser, simple_user):
]
def test_manager_user_sidebar_template_value(app, superuser, simple_user):
resp = login(app, superuser)
user_detail_url = reverse('a2-manager-user-detail', kwargs={'pk': simple_user.id})
setting = Setting.objects.get(key='users:backoffice_sidebar_template')
# correct template
setting.value = 'User {{ object }} may have temporary roles.'
setting.save()
resp = app.get(user_detail_url, status=200)
assert 'User Jôhn Dôe may have temporary roles.' in resp.pyquery('#advanced-info')[0].text
# condition correctly evaluated
setting.value = '{% if not object %}Foo{% else %}Bar{% endif %}'
setting.save()
resp = app.get(user_detail_url, status=200)
assert 'Bar' in resp.pyquery('#advanced-info')[0].text
assert 'Foo' not in resp.pyquery('#advanced-info')[0].text
# missing context key rendered to empty value, no error
setting.value = 'User {{ user }} may have temporary roles.'
setting.save()
resp = app.get(user_detail_url, status=200)
assert 'User may have temporary roles.' in resp.pyquery('#advanced-info')[0].text
# erroneous template not rendered
setting.value = 'User {{ user %} may have temporary roles.'
setting.save()
resp = app.get(user_detail_url, status=200)
assert 'User {{ user %} may have temporary roles.' in resp.pyquery('#advanced-info')[0].text
# html is also rendered
setting.value = '<strong>User {{ object.email }} may have temporary roles.</strong>'
setting.save()
resp = app.get(user_detail_url, status=200)
assert 'User user@example.net may have temporary roles.' in resp.pyquery('#advanced-info strong')[0].text
def test_manager_user_roles_breadcrumb(app, superuser, simple_user):
resp = login(app, superuser)
user_roles_url = reverse('a2-manager-user-roles', kwargs={'pk': simple_user.id})