summaryrefslogtreecommitdiffstats
path: root/django/sp_sso
diff options
context:
space:
mode:
authorPaul Marillonnet <pmarillonnet@entrouvert.com>2017-09-25 13:49:09 (GMT)
committerPaul Marillonnet <pmarillonnet@entrouvert.com>2017-09-25 13:49:09 (GMT)
commit8235164ecce89e254b978dbc6403980c062a7c0d (patch)
tree301bfb7a818af8511675c60b5affd3b3b7a50fa0 /django/sp_sso
parentd438c5f45477033feb145eae871c3ca509a3901c (diff)
parentd0a6042d3d4b7fd0ce5500621a5f1815dfc70ff5 (diff)
downloadpaul-synchro-8235164ecce89e254b978dbc6403980c062a7c0d.zip
paul-synchro-8235164ecce89e254b978dbc6403980c062a7c0d.tar.gz
paul-synchro-8235164ecce89e254b978dbc6403980c062a7c0d.tar.bz2
Merge branch 'master' of git.entrouvert.org:paul-synchro
Diffstat (limited to 'django/sp_sso')
-rw-r--r--django/sp_sso/invite/forms.py16
-rw-r--r--django/sp_sso/invite/utils.py13
-rw-r--r--django/sp_sso/invite/views.py10
-rw-r--r--django/sp_sso/saml/decorators.py16
-rw-r--r--django/sp_sso/saml/forms.py6
-rw-r--r--django/sp_sso/saml/models.py8
-rw-r--r--django/sp_sso/saml/tests.py1
-rw-r--r--django/sp_sso/saml/utils.py17
-rw-r--r--django/sp_sso/sp_sso/views.py17
9 files changed, 101 insertions, 3 deletions
diff --git a/django/sp_sso/invite/forms.py b/django/sp_sso/invite/forms.py
index 1b5f3bb..0736a3c 100644
--- a/django/sp_sso/invite/forms.py
+++ b/django/sp_sso/invite/forms.py
@@ -5,7 +5,23 @@ from .utils import get_invitaton_attributes_mapping, get_additional_prefilled_fi
class InvitationForm(forms.Form):
+ """ Used when sending invites to the Campus
+
+ Invites are sent to the `email` field. Multiple adresses are handled if
+ they are separated by a blank space ` ` character.
+
+ This form is restricted to users having previously registered to the
+ Campus ; therefore they must log in to their RENATER-federated account
+ before accessing the form.
+ """
def __init__(self, *args, **kwargs):
+ """
+ Attributes fetched from the identity provider are used to pre-fill
+ the fields.
+ An extra hidden field is used to store the user's
+ eduPersonPrincipalName, used as key identity attribute in the
+ corresponding LDAP entry.
+ """
super(InvitationForm, self).__init__(*args, **kwargs)
# Add a help text for sending multiple invitations
self.fields['email'].help_text = _(
diff --git a/django/sp_sso/invite/utils.py b/django/sp_sso/invite/utils.py
index 5cc40fd..4f49b39 100644
--- a/django/sp_sso/invite/utils.py
+++ b/django/sp_sso/invite/utils.py
@@ -21,12 +21,21 @@ PASSERELLE_PEOPLE_QUERY = 'http://dir-condorcet.dev.entrouvert.org/ldapquery/con
logger = logging.getLogger('django')
def get_invitaton_attributes_mapping():
+ """ Full copy of the invitation attributes mapping dictionary """
return INVITATION_ATTRIBUTES_MAPPING.copy()
def get_additional_prefilled_fields():
+ """
+ Returns a full copy of the additional fields to be prefilled with the
+ user's fetched attributes.
+ """
return ADDITIONAL_PREFILLED_FIELDS[:]
def do_invite(invitation):
+ """
+ Sends invitation data to the invitation w.c.s. form.
+ Invitations will then be sent as part of the w.c.s. workflow.
+ """
opener = build_opener(HTTPHandler)
# Generate a JSON to bind against the wcs ReST API
form = {}
@@ -48,6 +57,10 @@ def do_invite(invitation):
def get_affectations_from_eppn(eppn):
+ """
+ Returns a tuple (<Institution identifier>, <Researcher unit identifier>)
+ for a given eduPersonPrincipalName (a.k.a. `eppn`)
+ """
if eppn:
try:
ldapquery = urlopen(PASSERELLE_PEOPLE_QUERY)
diff --git a/django/sp_sso/invite/views.py b/django/sp_sso/invite/views.py
index e5b21fd..ce85b63 100644
--- a/django/sp_sso/invite/views.py
+++ b/django/sp_sso/invite/views.py
@@ -15,10 +15,20 @@ MSG_INVITATION_SENT = _("Your invitation has been sent.")
def invitation_sent(request):
+ """
+ Notifies the user about her invitation(s) by displaying a validation
+ message.
+ """
return render_message(request, MSG_INVITATION_SENT)
class InvitationFormView(FormView):
+ """
+ Main FormView for the invitation process.
+
+ Gathers SSO attributes from the identity provider, used to pre-fill and
+ set as readonly the `InvitationForm` fields.
+ """
form_class = InvitationForm
template_name = 'invite/invitation_form.html'
success_url = '/invite/sent'
diff --git a/django/sp_sso/saml/decorators.py b/django/sp_sso/saml/decorators.py
index 518ee87..91eda4f 100644
--- a/django/sp_sso/saml/decorators.py
+++ b/django/sp_sso/saml/decorators.py
@@ -18,6 +18,10 @@ MSG_USER_NOT_REGISTERED = _("Please register to the campus before sending "
"invites.")
def user_not_in_ldap(function):
+ """
+ Restricts access to users whose eduPersonPrincipalName attribute value
+ doesn't appear in a ou=people sub-entry in the Campus LDAP.
+ """
def wrapped(request, *args, **kwargs):
if 'type' in kwargs and kwargs['type'] == 'mellon':
user_data = saml_collect_data(request)
@@ -28,6 +32,10 @@ def user_not_in_ldap(function):
return wrapped
def user_in_ldap(function):
+ """
+ Restricts access to users whose eduPersonPrincipalName attribute value
+ appear in a `ou=people...` sub-entry in the Campus LDAP.
+ """
def wrapped(request, *args, **kwargs):
if not 'mellon_session' in request.session:
return redirect(reverse('auth_login') + "?next=/invite/")
@@ -39,6 +47,14 @@ def user_in_ldap(function):
return wrapped
def user_can_declare(function):
+ """
+ Ensure that all conditions are met for a user to self-subscribe to the
+ Campus. At the moment, these two conditions are:
+ - the user's EduPersonPrincipalName attribute value mustn't appear in the
+ Campus LDAP base
+ - the user's institution or research unit should appear as registered
+ structures in the Campus LDAP base
+ """
def wrapped(request, *args, **kwargs):
if not request.session.get('mellon_session'):
return redirect(reverse('auth_login') + '?next=/declare/')
diff --git a/django/sp_sso/saml/forms.py b/django/sp_sso/saml/forms.py
index dc1c583..90c452f 100644
--- a/django/sp_sso/saml/forms.py
+++ b/django/sp_sso/saml/forms.py
@@ -53,7 +53,13 @@ ETABLISSEMENT_CHOICES = ()
UNITE_CHOICES = ()
class RegistrationForm(forms.Form):
+ """
+ Main registration form when requesting access to the Campus.
+ User data may be fetched for a single sign-on (SSO) procedure and used to
+ pre-fill the fields. Pre-filled fields are also set as readonly to ensure
+ that the data fetched from the SSO process and sent to w.c.s. is genuine.
+ """
user_help_msg = ''
user_nickname = ''
diff --git a/django/sp_sso/saml/models.py b/django/sp_sso/saml/models.py
index 0911263..ea5429b 100644
--- a/django/sp_sso/saml/models.py
+++ b/django/sp_sso/saml/models.py
@@ -3,6 +3,14 @@ from django.contrib.auth.models import AbstractUser
class SupAnnUser(AbstractUser):
+ """ Base user of the SSO scheme. OBSOLETE
+
+ eduPerson and supann2009 attributes have been added so that theses
+ attributes can be retrieved in mellon.
+
+ XXX mellon attributes are also stored in the session.
+ (see the request.session['mellon_session'] dict)
+ """
# eduPerson attributes:
ep_principal_name = models.CharField(max_length=100,default='user_eppn')
ep_primary_affiliation = models.CharField(max_length=100, default="")
diff --git a/django/sp_sso/saml/tests.py b/django/sp_sso/saml/tests.py
index e94c8ec..8134add 100644
--- a/django/sp_sso/saml/tests.py
+++ b/django/sp_sso/saml/tests.py
@@ -5,6 +5,7 @@ import requests
# Create your tests here.
class ServicesTestCase(TestCase):
+ """Ensure that all four services are up and running."""
def test_ldap_connection(self):
l = ldap_init()
self.assertTrue(l)
diff --git a/django/sp_sso/saml/utils.py b/django/sp_sso/saml/utils.py
index 5ff3502..5e5768b 100644
--- a/django/sp_sso/saml/utils.py
+++ b/django/sp_sso/saml/utils.py
@@ -29,9 +29,17 @@ supann_host_role_value = '{SUPANN}R10' # 'Responsable de mission'
def render_message(request, message):
+ """Renders a simple message to a base template"""
return render(request, 'simple_message.html', {'message': message})
def generate_eppn(lastname):
+ """
+ Used when no eduPersonPrincipalName attribute is fetched from the
+ identity provider during the single sign-on process.
+
+ Returns a randomly generated EPPN value in the form of a valid Campus
+ Condorcet email address.
+ """
return "%s-%06d@campus-condorcet.fr"%(lastname, randint(0,pow(10,6)))
@@ -59,19 +67,22 @@ def ldap_init():
def ldap_get_etablissements():
-# Used to fill the choices in hote_etablissemnt form ChoiceField:
+ """Used to fill the choices in hote_etablissemnt form ChoiceField."""
return ldap_get_attribute_from_subtree_nodes(
structures_base, '(objectClass=supannOrg)', 'ou')
def ldap_get_unites():
-# Used to fill the choices in hote_unite form ChoiceField:
+ """Used to fill the choices in hote_unite form ChoiceField."""
return ldap_get_attribute_from_subtree_nodes(
structures_base, '(supannTypeEntite=*)', 'ou')
def ldap_get_affectations():
-# Used to fill the choices in s_entite_affectation_principale form ChoiceField:
+ """
+ Used to fill the choices in s_entite_affectation_principale form
+ ChoiceField.
+ """
return ldap_get_attribute_from_subtree_nodes(
structures_base, '(objectClass=supannEntite)', 'supannCodeEntite')
diff --git a/django/sp_sso/sp_sso/views.py b/django/sp_sso/sp_sso/views.py
index e87aea8..3448150 100644
--- a/django/sp_sso/sp_sso/views.py
+++ b/django/sp_sso/sp_sso/views.py
@@ -20,6 +20,7 @@ logger = logging.getLogger('sp_sso.resource')
@csrf_exempt
def index(request):
+ """Main view for the service provider"""
if request.method != 'GET':
return HttpResponse('Bad method.', status=405)
request.session['tracking_code'] = request.GET.get('tracking_code')
@@ -27,6 +28,7 @@ def index(request):
return render(request, 'index.html', locals())
def login(request, *args, **kwargs):
+ """Defers the login scheme to the identity provider thanks to mellon"""
if any(get_idps()):
if not 'next' in request.GET:
return HttpResponseRedirect(resolve_url('mellon_login'))
@@ -35,6 +37,7 @@ def login(request, *args, **kwargs):
return auth_views.login(request, *args, **kwargs)
def logout(request, next_page=None):
+ """Defers the logout scheme to the identity provider thanks to mellon"""
if any(get_idps()):
return HttpResponseRedirect(resolve_url('mellon_logout'))
auth_logout(request)
@@ -42,10 +45,24 @@ def logout(request, next_page=None):
return HttpResponseRedirect(next_page)
def subscribed(request):
+ """Success view for the self-subscription process"""
logger.info(u'Processing request %s', request)
return render_message(request, _("Subscribed."))
class Declare(FormView):
+ """Self-subscription FormView
+
+ Users allowed to self-subscribe to the Campus MUSTN'T be registered to the
+ Campus LDAP directory yet. (Presence checking is performed against their
+ eduPersonPrincipalName).
+
+ Additionnally, their institution (supannEtablissement) or research unit
+ (supannEntiteAffectation) MUST be declared in the Campus database.
+
+ These restrictions are processed thanks to the `user_can_declare`
+ decorator.
+
+ """
form_class = RegistrationForm
template_name = 'declare_form.html'
success_url = '/declare/subscribed/'