Merge branch 'master' of git.entrouvert.org:paul-synchro

This commit is contained in:
Paul Marillonnet 2017-09-25 15:49:09 +02:00
commit 8235164ecc
17 changed files with 121 additions and 3 deletions

View File

@ -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 = _(

View File

@ -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)

View File

@ -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'

View File

@ -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/')

View File

@ -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 = ''

View File

@ -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="")

View File

@ -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)

View File

@ -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')

View File

@ -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/'

7
misc/resume_technique Normal file
View File

@ -0,0 +1,7 @@
Rapport de stage chez Entr'ouvert : État de l'art des systèmes d'approvisionnement et de synchronisation des bases d'identités numériques et mise en oeuvre de démonstrateurs
Ce rapport de stage s'incrit dans la thématique de la gestion des identités numériques, en particulier des mécanismes de constitution de référentiels d'identité.
Une étude des mécanismes d'approvisionnement et de synchronisation de ces référentiels a été effectuée. Un examen théorique et pratique des différentes solutions libres assurant ces fonctionnalités a ensuite été réalisé.
Une seconde étude, plus générale, traitant de la littérature relative aux différents mécanismes et technologies de la gestion des identités numériques, a été réalisée et complétée tout au long de ce projet de fin d'études.
Dans le cadre de ces travaux, ce document contient aussi une étude relative à l'outil LSC (LDAP Synchronization Connector).
Après séléction d'un scénario de synchronisation pertinent pour une application effective, ce rapport explique la démarche adoptée pour le développement d'un démonstrateur. Ce démonstrateur, de type 'Preuve de concept' (POC), a été développé sous licence libre à l'aide du framework Web Django. Le déploiement des différentes briques logicielles (annuaire, fournisseur d'identités, connecteurs et gestionnaire de formulaires et de workflows) nécessaires au déploiement du démonstrateur est aussi expliqué dans ce document.

Binary file not shown.

BIN
report/draft_report006.odt Normal file

Binary file not shown.

BIN
report/draft_report007.odt Normal file

Binary file not shown.

BIN
report/draft_report008.odt Normal file

Binary file not shown.

BIN
report/draft_report009.odt Normal file

Binary file not shown.

BIN
report/draft_report010.odt Normal file

Binary file not shown.

13
soutenance/texte Normal file
View File

@ -0,0 +1,13 @@
// 15 min maxi
Tout d'abord une présentation de l'entreprise
C'est une SCOP
Société Coopérative Ouvrière de Produduction
(Société Coopérative et Participative)
Même salaire, décision par vote, actionnaires à part égale de l'entreprise
Editrice de logiciel libre
Culture du libre
transparence, valorisation de la connaissance et de l'expertise des coopérateurs plutôt que le
code source