From 46d89ab050c108d31adaca6eb5fe7df99c5759b0 Mon Sep 17 00:00:00 2001
From: Paul Marillonnet <pmarillonnet@entrouvert.com>
Date: Wed, 15 Apr 2020 11:14:36 +0200
Subject: [PATCH] manager: deactivate link for un-viewable roles in user
 details (#41733)

---
 .../authentic2/manager/user_detail.html       |  2 +-
 src/authentic2/manager/user_views.py          |  5 ++
 tests/test_user_manager.py                    | 51 ++++++++++++++++++-
 3 files changed, 56 insertions(+), 2 deletions(-)

diff --git a/src/authentic2/manager/templates/authentic2/manager/user_detail.html b/src/authentic2/manager/templates/authentic2/manager/user_detail.html
index 2acb73e0f..f6dd54746 100644
--- a/src/authentic2/manager/templates/authentic2/manager/user_detail.html
+++ b/src/authentic2/manager/templates/authentic2/manager/user_detail.html
@@ -76,7 +76,7 @@
         {% endif %}
               {% for role in ou_roles %}
                 <li {% if role.description %}title="{{ role.description }}"{% endif %}>
-                <a href="{% url "a2-manager-role-members" pk=role.pk %}">{{ role }}</a></li>
+                {% if role.user_visible %}<a href="{% url "a2-manager-role-members" pk=role.pk %}">{{ role }}</a>{% else %}{{ role }}{% endif %}</li>
               {% endfor %}
         {% if have_roles_on_multiple_ou %}
               </ul>
diff --git a/src/authentic2/manager/user_views.py b/src/authentic2/manager/user_views.py
index 4da67a589..b5e8add54 100644
--- a/src/authentic2/manager/user_views.py
+++ b/src/authentic2/manager/user_views.py
@@ -338,8 +338,13 @@ class UserDetailView(OtherActionsMixin, BaseDetailView):
     def get_context_data(self, **kwargs):
         kwargs['default_ou'] = get_default_ou
         roles = self.object.roles_and_parents().order_by('ou__name', 'name')
+        role_qs = get_role_model().objects.all()
+        if app_settings.ROLE_MEMBERS_FROM_OU and self.object.ou:
+            role_qs = role_qs.filter(ou=self.object.ou)
+        visible_roles = self.request.user.filter_by_perm('a2_rbac.view_role', role_qs)
         roles_by_ou = collections.OrderedDict()
         for role in roles:
+            role.user_visible = bool(role in visible_roles)
             roles_by_ou.setdefault(role.ou.name if role.ou else '', []).append(role)
         kwargs['roles'] = roles
         kwargs['roles_by_ou'] = roles_by_ou
diff --git a/tests/test_user_manager.py b/tests/test_user_manager.py
index 067503cf1..8702d723b 100644
--- a/tests/test_user_manager.py
+++ b/tests/test_user_manager.py
@@ -25,15 +25,23 @@ from urllib.parse import urlparse
 import pytest
 from webtest import Upload
 
+from django.contrib.auth import get_user_model
 from django.contrib.contenttypes.models import ContentType
 from django.urls import reverse
 from django.utils.six import text_type
 
-from django_rbac.utils import get_ou_model
+from django_rbac.models import VIEW_OP
+from django_rbac.utils import (
+        get_operation,
+        get_ou_model,
+        get_permission_model,
+        get_role_model,
+)
 
 from authentic2.custom_user.models import User
 from authentic2.models import Attribute, AttributeValue
 from authentic2.a2_rbac.utils import get_default_ou
+from authentic2.a2_rbac.utils import get_view_user_perm
 from authentic2.manager import user_import
 
 
@@ -782,3 +790,44 @@ def test_manager_user_username_field(app, superuser, simple_user):
     assert resp.html.find('input', {'name': 'username'})
     resp = app.get(reverse('a2-manager-user-edit', kwargs={'pk': simple_user.id}))
     assert resp.html.find('input', {'name': 'username'})
+
+
+def test_manager_user_roles_visibility(app, simple_user, admin, ou1, ou2):
+    Role = get_role_model()
+    role1 = Role.objects.create(name='Role 1', slug='role1', ou=ou1)
+    role2 = Role.objects.create(name='Role 2', slug='role2', ou=ou2)
+    simple_user.roles.add(role1)
+    simple_user.roles.add(role2)
+    simple_user.save()
+
+    login(app, admin, '/manage/')
+
+    resp = app.get(reverse('a2-manager-user-detail', kwargs={'pk': simple_user.id}))
+    assert '/manage/roles/%s/' % role1.pk in resp.text
+    assert 'Role 1' in resp.text
+    assert '/manage/roles/%s/' % role2.pk in resp.text
+    assert 'Role 2' in resp.text
+
+    app.get('/logout/').form.submit()
+
+    other_user = get_user_model().objects.create(
+        username='other_user', ou=ou1)
+    other_user.set_password('auietsrn')
+    other_role = Role.objects.create(name='Other role', slug='other-role', ou=ou1)
+    view_role1_perm = get_permission_model().objects.create(
+            operation=get_operation(VIEW_OP),
+            target_ct=ContentType.objects.get_for_model(Role),
+            target_id=role1.pk)
+    other_role.permissions.add(get_view_user_perm())
+    other_role.permissions.add(view_role1_perm)
+    other_role.save()
+    other_user.roles.add(other_role)
+    other_user.save()
+
+    login(app, other_user, '/manage/', 'auietsrn')
+    resp = app.get(reverse('a2-manager-user-detail', kwargs={'pk': simple_user.id}))
+    assert '/manage/roles/%s/' % role1.pk in resp.text
+    assert 'Role 1' in resp.text
+    assert '/manage/roles/%s/' % role2.pk not in resp.text
+    assert 'Role 2' in resp.text
+    app.get('/manage/roles/%s/' % role2.pk, status=403)
-- 
2.28.0

