Skip to content

Commit

Permalink
Merge pull request #549 from EsupPortail/dev
Browse files Browse the repository at this point in the history
DEV #2.8.3
  • Loading branch information
ptitloup authored Nov 17, 2021
2 parents aa01bce + 1a6453c commit 21105c4
Show file tree
Hide file tree
Showing 96 changed files with 8,887 additions and 13,369 deletions.
7 changes: 6 additions & 1 deletion AUTHORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@ A list of much-appreciated contributors who have submitted patches and reported
* Loic Bonavent, University of Montpellier
* Guillaume Condesse, University of Bordeaux
* Franck Charneau and Joshua Baubry, University of La Rochelle
* Olivier Bado, University Cote d'Azur
* Olivier Bado-Faustin, University Cote d'Azur
* Frederic Sene, INSA Rennes
* Nicolas Lahoche, University of Lille (design and template)
* Charlotte Benard (Logo and color)

Pictures credits
----------------
* default.svg: adapted from Play button Icon by [Freepik](https://www.freepik.com/free-vector) - Freepik License
* cookie.svg: [Broken oatmeal cookie created by pch.vector](https://www.freepik.com/vectors/logo) - Freepik License
3 changes: 0 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,3 @@ Le projet et la plateforme qui porte le même nom ont pour but de faciliter la m
<img src="https://www.univ-lille.fr/typo3conf/ext/ul2fpfb/Resources/Public/assets/img/UL-ROSE-dark-2014.svg" height="50"> | <img src="https://www.esup-portail.org/sites/default/files/logo-esupportail_1.png" height="50"> | <img src="http://cache.media.enseignementsup-recherche.gouv.fr/image/Global/35/8/Marianne_seule_MESRI_head_www_766358.jpg" height="50"> Ministère de lʼEnseignement supérieur, de la Recherche et de lʼInnovation
:-----:|:-----:|:----:


## Crédits tiers
* static/default.svg : Icon made by [Freepik](https://www.freepik.com)
87 changes: 87 additions & 0 deletions pod/authentication/shibmiddleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,93 @@

REMOTE_USER_HEADER = getattr(settings, "REMOTE_USER_HEADER", "REMOTE_USER")

SHIBBOLETH_ATTRIBUTE_MAP = getattr(
settings,
"SHIBBOLETH_ATTRIBUTE_MAP",
{
"REMOTE_USER": (True, "username"),
"Shibboleth-givenName": (True, "first_name"),
"Shibboleth-sn": (False, "last_name"),
"Shibboleth-mail": (False, "email"),
"Shibboleth-primary-affiliation": (False, "affiliation"),
"Shibboleth-unscoped-affiliation": (False, "affiliations"),
},
)

SHIBBOLETH_STAFF_ALLOWED_DOMAINS = getattr(
settings, "SHIBBOLETH_STAFF_ALLOWED_DOMAINS", None
)

AFFILIATION = getattr(
settings,
"AFFILIATION",
(
("student", ""),
("faculty", ""),
("staff", ""),
("employee", ""),
("member", ""),
("affiliate", ""),
("alum", ""),
("library-walk-in", ""),
("researcher", ""),
("retired", ""),
("emeritus", ""),
("teacher", ""),
("registered-reader", ""),
),
)

AFFILIATION_STAFF = getattr(
settings, "AFFILIATION_STAFF", ("employee", "faculty", "staff")
)


class ShibbMiddleware(ShibbolethRemoteUserMiddleware):
header = REMOTE_USER_HEADER

def check_user_meta(self, user, shib_meta):
"""Check shibboleth access rights with user's meta
Args:
user: User,
shib_meta dict
Returns:
bool
"""
return (
user
and user.owner
and shib_meta["affiliation"] in [A[0] for A in AFFILIATION]
and user.owner.affiliation != shib_meta["affiliation"]
)

def is_staffable(self, user):
"""Check that given user, his domain is in authorized domains of shibboleth staff
Args:
user: User
Returns:
bool
"""
if (
SHIBBOLETH_STAFF_ALLOWED_DOMAINS is None
or len(SHIBBOLETH_STAFF_ALLOWED_DOMAINS) == 0
):
return True
for d in SHIBBOLETH_STAFF_ALLOWED_DOMAINS:
if user.username.endswith("@" + d):
return True
return False

def make_profile(self, user, shib_meta):
if ("affiliation" in shib_meta) and self.check_user_meta(user, shib_meta):
user.owner.affiliation = shib_meta["affiliation"]
user.owner.save()
if self.is_staffable(user) and "affiliations" in shib_meta:
for affiliation in shib_meta["affiliations"].split(";"):
if affiliation in AFFILIATION_STAFF:
user.is_staff = True
user.save()
break
return
2 changes: 1 addition & 1 deletion pod/authentication/templates/authentication/login.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{% extends "base.html" %}
{% load i18n static custom_tags %}
{% block page_extra_head %}

<meta name="robots" content="NONE,NOARCHIVE">
{% endblock %}

{% block page_title %}{% trans 'Authentication' %}{% endblock %}
Expand Down
6 changes: 4 additions & 2 deletions pod/authentication/templates/registration/login.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
{% load i18n static %}

{% block page_extra_head %}
{{ block.super }}<link rel="stylesheet" type="text/css" href="{% static 'admin/css/login.css' %}?ver={{VERSION}}">
{{ form.media }}
{{ block.super }}
<link rel="stylesheet" type="text/css" href="{% static 'admin/css/login.css' %}?ver={{VERSION}}">
<meta name="robots" content="NONE,NOARCHIVE">
{{ form.media }}
{% endblock %}

{% block page_title %}{% trans 'Log in' %}{% endblock %}
Expand Down
166 changes: 166 additions & 0 deletions pod/authentication/tests/test_populated.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
from django.test import TestCase, override_settings
from pod.authentication.models import Owner, AccessGroup
from pod.authentication import populatedCASbackend
from pod.authentication import shibmiddleware
from pod.authentication.backends import ShibbBackend
from django.test import RequestFactory
from django.contrib.auth.models import User
from importlib import reload
from xml.etree import ElementTree as ET
Expand Down Expand Up @@ -46,6 +49,35 @@
},
)

REMOTE_USER_HEADER = getattr(settings, "REMOTE_USER_HEADER", "REMOTE_USER")

SHIBBOLETH_ATTRIBUTE_MAP = getattr(
settings,
"SHIBBOLETH_ATTRIBUTE_MAP",
{
"REMOTE_USER": (True, "username"),
"Shibboleth-givenName": (True, "first_name"),
"Shibboleth-sn": (False, "last_name"),
"Shibboleth-mail": (False, "email"),
"Shibboleth-primary-affiliation": (False, "affiliation"),
"Shibboleth-unscoped-affiliation": (False, "affiliations"),
},
)

SHIBBOLETH_STAFF_ALLOWED_DOMAINS = getattr(
settings, "SHIBBOLETH_STAFF_ALLOWED_DOMAINS", None
)

AFFILIATION_STAFF = getattr(
settings, "AFFILIATION_STAFF", ("employee", "faculty", "staff")
)

SHIB_URL = getattr(settings, "SHIB_URL", "https://univ.fr/Shibboleth.sso/WAYF")

SHIB_LOGOUT_URL = getattr(
settings, "SHIB_LOGOUT_URL", "https://univ.fr/Shibboleth.sso/Logout"
)


class PopulatedCASTestCase(TestCase):
# populate_user_from_tree(user, owner, tree)
Expand Down Expand Up @@ -276,3 +308,137 @@ def test_populate_user_from_entry_affiliation_group(self):
" ---> test_populate_user_from_entry_affiliation_group"
" of PopulatedLDAPTestCase : OK !"
)


class PopulatedShibTestCase(TestCase):
def setUp(self):
"""setUp PopulatedShibTestCase create user pod"""
self.hmap = {}
for a in SHIBBOLETH_ATTRIBUTE_MAP:
self.hmap[SHIBBOLETH_ATTRIBUTE_MAP[a][1]] = a
# print(SHIBBOLETH_ATTRIBUTE_MAP[a][1] + ' > ' + a)

print(" ---> SetUp of PopulatedShibTestCase : OK !")

def _authenticate_shib_user(self, u):
"""Simulate shibboleth header"""
fake_shib_header = {
"REMOTE_USER": u["username"],
self.hmap["username"]: u["username"],
self.hmap["email"]: u["email"],
self.hmap["first_name"]: u["first_name"],
self.hmap["last_name"]: u["last_name"],
}
if "groups" in u.keys():
fake_shib_header[self.hmap["groups"]] = u["groups"]
if "affiliations" in u.keys():
fake_shib_header[self.hmap["affiliation"]] = u["affiliations"].split(";")[0]
fake_shib_header[self.hmap["affiliations"]] = u["affiliations"]

""" Get valid shib_meta from simulated shibboleth header """
request = RequestFactory().get("/", REMOTE_USER=u["username"])
request.META.update(**fake_shib_header)
shib_meta, error = shibmiddleware.ShibbMiddleware.parse_attributes(request)
self.assertFalse(error, "Generating shibboleth attribute mapping contains errors")

""" Check user authentication """
user = ShibbBackend.authenticate(
ShibbBackend(),
request=request,
remote_user=u["username"],
shib_meta=shib_meta,
)
self.assertTrue(user.is_authenticated())

return (user, shib_meta)

@override_settings(DEBUG=False)
def test_make_profile(self):
"""Test if user attributes are retreived"""
user, shib_meta = self._authenticate_shib_user(
{
"username": "[email protected]",
"first_name": "John",
"last_name": "Do",
"email": "[email protected]",
"affiliations": "teacher;staff;member",
}
)
self.assertEqual(user.username, "[email protected]")
self.assertEqual(user.email, "[email protected]")
self.assertEqual(user.first_name, "John")
self.assertEqual(user.last_name, "Do")

""" Test if user can be staff if SHIBBOLETH_STAFF_ALLOWED_DOMAINS is None """
settings.SHIBBOLETH_STAFF_ALLOWED_DOMAINS = None
reload(shibmiddleware)
shibmiddleware.ShibbMiddleware.make_profile(
shibmiddleware.ShibbMiddleware(), user, shib_meta
)
self.assertTrue(user.is_staff)

owner = Owner.objects.get(user__username="[email protected]")
self.assertEqual(owner.affiliation, "teacher")

""" Test if user can be staff when SHIBBOLETH_STAFF_ALLOWED_DOMAINS
is restricted """
settings.SHIBBOLETH_STAFF_ALLOWED_DOMAINS = (
"univ-a.fr",
"univ-b.fr",
)
user.is_staff = False # Staff status is not remove
user.save()
reload(shibmiddleware)
shibmiddleware.ShibbMiddleware.make_profile(
shibmiddleware.ShibbMiddleware(), user, shib_meta
)
self.assertFalse(user.is_staff)

""" Test if user become staff when SHIBBOLETH_STAFF_ALLOWED_DOMAINS
is restrict and contains his domain """
settings.SHIBBOLETH_STAFF_ALLOWED_DOMAINS = ("univ.fr",)
reload(shibmiddleware)
shibmiddleware.ShibbMiddleware.make_profile(
shibmiddleware.ShibbMiddleware(), user, shib_meta
)
self.assertTrue(user.is_staff)

""" Test if same user with new unstaffable affiliation keep his staff status """
unstaffable_affiliation = ["member", "unprobable"]
for a in unstaffable_affiliation:
self.assertFalse(a in AFFILIATION_STAFF)
user, shib_meta = self._authenticate_shib_user(
{
"username": "[email protected]",
"first_name": "Jean",
"last_name": "Do",
"email": "[email protected]",
"affiliations": ";".join(unstaffable_affiliation),
}
)
shibmiddleware.ShibbMiddleware.make_profile(
shibmiddleware.ShibbMiddleware(), user, shib_meta
)
self.assertTrue(user.is_staff) # Staff status is not remove

""" Test if the main affiliation of this same user
with new unstaffable affiliation has changed """
owner = Owner.objects.get(user__username="[email protected]")
self.assertEqual(owner.affiliation, "member")

""" Test if a new user with same unstaffable affiliations has no staff status"""
user, shib_meta = self._authenticate_shib_user(
{
"username": "[email protected]",
"first_name": "Ada",
"last_name": "Da",
"email": "[email protected]",
"affiliations": ";".join(unstaffable_affiliation),
}
)
shibmiddleware.ShibbMiddleware.make_profile(
shibmiddleware.ShibbMiddleware(), user, shib_meta
)
self.assertFalse(user.is_staff)

print(" ---> test_make_profile" " of PopulatedShibTestCase : OK !")
25 changes: 15 additions & 10 deletions pod/authentication/tests/test_views.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
"""
Unit tests for authentication views
"""Unit tests for authentication views.
* run with 'python manage.py test pod.authentication.tests.test_views'
"""
from django.test import TestCase
from django.test import Client
from django.contrib.auth.models import User
from django.conf import settings
from django.utils.translation import ugettext_lazy as _


class authenticationViewsTestCase(TestCase):
Expand All @@ -13,7 +16,7 @@ class authenticationViewsTestCase(TestCase):

def setUp(self):
User.objects.create(username="pod", password="podv2")
print(" ---> SetUp of authenticationViewsTestCase : OK !")
print(" ---> SetUp of authenticationViewsTestCase: OK!")

def test_authentication_login_gateway(self):
self.client = Client()
Expand All @@ -27,26 +30,28 @@ def test_authentication_login_gateway(self):

print(
" ---> test_authentication_login_gateway \
of authenticationViewsTestCase : OK !"
of authenticationViewsTestCase: OK!"
)

def test_authentication_login(self):
"""Test authentication login page."""
self.client = Client()
self.user = User.objects.get(username="pod")
login_url = settings.LOGIN_URL

# User already authenticated
self.client.force_login(self.user)
response = self.client.get("/authentication_login/")
response = self.client.get(login_url)
self.assertRedirects(response, "/")

# User not authenticated and CAS are valued to False
self.client.logout()
response = self.client.get("/authentication_login/")
response = self.client.get(login_url)
self.assertRedirects(response, "/accounts/login/?next=/")

print(
" ---> test_authentication_login \
of authenticationViewsTestCase : OK !"
of authenticationViewsTestCase: OK!"
)

def test_authentication_logout(self):
Expand All @@ -57,7 +62,7 @@ def test_authentication_logout(self):

print(
" ---> test_authentication_logout \
of authenticationViewsTestCase : OK !"
of authenticationViewsTestCase: OK!"
)

def test_userpicture(self):
Expand Down Expand Up @@ -89,9 +94,9 @@ def test_userpicture(self):
messages = list(response.wsgi_request._messages)
self.assertEqual(len(messages), 1)
self.assertEqual(
str(messages[0]), "One or more errors have been found in the form."
str(messages[0]), _("One or more errors have been found in the form.")
)
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "userpicture/userpicture.html")

print(" ---> test_userpicture of authenticationViewsTestCase : OK !")
print(" ---> test_userpicture of authenticationViewsTestCase: OK!")
Loading

0 comments on commit 21105c4

Please sign in to comment.