Feide-innlogging

Beskrivelse

For å få Feide-innlogging brukes tjenesten Dataporten, som er en OAuth 2.0-provider som autentiserer brukeren via Feide.

Django er konfigurert til å autentisere via OAuth 2.0 ved hjelp av biblioteket django-allauth (se Django tilleggspakker), og trenger ikke noen spesiell kjennskap til Feide utover de tilpasningene som er beskrevet under.

Funksjonaliteten er avhengig av innstillingen USE_FEIDE_AUTH.

Konfigurasjon av Feide-innlogging

For at Feide-innlogging skal fungere må det defineres en applikasjon i Dataporten. Dette gjøres via Dataporten Dashboard.

Se Registering and managing applications, spesielt Managing applications for framgangsmåte.

Når applikasjonen registreres trengs disse opplysningene:

Basic info

Grunnleggende innstillinger:

  • Navn: Passende beskrivelse, f.eks «Wagtail utviklingsmiljø» for testinstallasjoner.

  • Redirect URI: Vil typisk være http://localhost:8000/accounts/dataporten/login/callback/ for testinstallasjoner og https://wagtail.math.ntnu.no/accounts/dataporten/login/callback/ for produksjonsinstansen.

  • Require user interaction: Ja

  • Client type: Confidential

Auth providers

Innstillinger om hvilke brukere som skal kunne logge inn:

  • Accept all providers: Nei

  • Miscellaneous login providers: Ingen

  • Social network: Ingen

  • Åpne for alle i utdanningssektoren: Ja

Permissions

Hvilken informasjon vi ber om å få om brukeren fra Feide. Vi trenger kun:

  • E-post (email): Brukerens e-postadresse.

  • Bruker-ID (userid): Brukerens ID i dataporten.

  • Feide-navn (userid-feide): Brukerens identifikator i Feide. Tilsvarende eduPersonPrincipalName.

OAuth details

Når applikasjonen er opprettet finner man applikasjonens OAuth credentials under menyvalget «OAuth details»:

  • Client ID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

  • Client Secret: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

Disse må legges inn i Django slik:

  • Gå til /django-admin/

  • Logg inn som brukeren du opprettet med ./manage.py createsuperuser

  • Opprett et nytt Social applications-objekt med følgende innstillinger:

    • Provider: Dataporten

    • Name: Dataporten

    • Client id: Client ID fra Dataporten

    • Secret key: Client Secret fra Dataporten

    • Sites: Dette nettstedet (typisk localhost eller wagtail.math.ntnu.no)

Nettstedet som brukes er nødt til å ha SITE_ID = 1 (dette er hardkodet i src/website/settings.py), så det er enklest å endre navn på default site example.com til ønsket navn, i stedet for å legge inn en ny site i Django admin.

Tilpasninger av django-allauth

Innstillinger

Django-allauth sender i utgangspunktet epost til nye brukerkontoer, slik at de kan verifisere/bekrefte epostadressen sin. Dette er disablet ved hjelp av innstillingene ACCOUNT_EMAIL_VERIFICATION = 'none' og SOCIALACCOUNT_EMAIL_VERIFICATION = 'none' i Django settings.

For at brukeren skal bli logget rett inn uten å måtte bekrefte at man ønsker å logge inn via en tredjepartstjeneste (Dataporten) er SOCIALACCOUNT_LOGIN_ON_GET = True satt.

Bruk fullstendig Feide-brukernavn

Når django-allauth oppretter lokal Django-brukerkonto blir brukernavnet i utgangspunktet satt til første komponent av Feide-brukernavnet (alt før @-tegnet). Vi ønsker at lokalt brukernavn skal være det fullstendige Feide-brukernavnet.

Dette er ikke konfigurerbart i django-allauth hvis man bruker den medfølgende dataporten-modulen, vi har derfor tilpasset denne.

Installasjon av django-allauth skjer normalt ved å legge til følgende i INSTALLED_APPS (se dokumentasjonen til django-allauth):

INSTALLED_APPS = (
    ...
    # The following apps are required:
    'django.contrib.auth',
    'django.contrib.messages',
    'django.contrib.sites',

    'allauth',
    'allauth.account',
    'allauth.socialaccount',
    # ... include the providers you want to enable:
    'allauth.socialaccount.providers.dataporten',
    ...
)

I stedet for å inkludere allauth.socialaccount.providers.dataporten inkluderer vi vår egen feide. Da brukes autentiseringsmodulen fra src/feide/provider.py og urlpatterns fra src/feide/urls.py.

feide.provider arver fra allauth.socialaccount.providers.dataporten og overstyrer kun brukernavnet:

class FeideProvider(dataporten.DataportenProvider):
    """
    allauth.socialaccount.providers-klasse brukt i settings.INSTALLED_APPS.
    i stedet for allauth.socialaccount.providers.dataporten.
    """
    def extract_common_fields(self, data):
        """
        Overstyr data['username'] men bruk resten av verdiene as-is.
        """
        data = super().extract_common_fields(data)
        for userid in data.get('userid_sec'):
            usertype, username = userid.split(':')
            if usertype == 'feide':
                data['username'] = username
                break
        return data

feide.urls arver fra allauth.socialaccount.providers.dataporten.urls og installerer de nødvendige urlpatterns hvis USE_FEIDE_AUTH er påskrudd:

from django.conf import settings
import allauth.socialaccount.providers.dataporten.urls

if settings.USE_FEIDE_AUTH:
    urlpatterns = allauth.socialaccount.providers.dataporten.urls.urlpatterns  # pylint: disable=invalid-name
else:
    urlpatterns = []

Tilpassing av hvordan nye brukerkontoer opprettes

I utgangspunktet må en ny bruker logge inn én gang slik at brukerkontoen blir opprettet, før det er mulig å tildele redigeringsrettigheter. Vi ønsker å kunne opprette brukerkontoer med riktige rettigheter på forhånd, slik at alt er klart til bruk første gang vedkommende logger inn i Wagtail.

Hvis vi oppretter en Django-brukerkonto med et gitt Feide-brukernavn på forhånd, vet ikke django-allauth (og kan ikke vite/anta) at dette er snakk om samme bruker, og django-allauth vil i utgangspunktet da opprette en annen Django-brukerkonto med annet/tilfeldig/udefinert brukernavn som er koblet til Feide-kontoen.

Vi har bedt django-allauth om å gjøre en slik antakelse - ved første gangs innlogging blir Feide-kontoen koblet mot eksisterende Django-konto hvis det finnes en Django-konto med samme brukernavn som Feide-kontoen.

Dette er gjort ved å bruke vår egen SocialAccountAdapter feide.adapter.SocialAccountAdapter. Denne arver fra allauth.socialaccount.adapter.DefaultSocialAccountAdapter men overstyrer logikken rundt oppretting av brukerkonto. Hvilken adapter som skal brukes styres av innstillingen SOCIALACCOUNT_ADAPTER i Django settings.

Uønsket funksjonalitet i django-allauth

Django-allauth har ekstra funksjonalitet som vi ikke ønsker å bruke:

  • signup: Det er mulig å opprette lokal brukerkonto direkte, uten å gå via Feide-innlogging.

  • passordendring: En bruker kan sette lokalt passord på den lokale django-brukerkontoen sin, og dermed klare å logge inn selv om Feide-brukerkontoen blir stengt.

Vi ønsker ikke slike bakveier inn i systemet, og har disablet disse funksjonene.

Django-allauth har ingen mekanisme for å disable disse (dokumentasjonen nevner ingen ting om denne problemstillingen), så vi har brukt en workaround som kan komme til å slutte å fungere i nyere versjoner av django-allauth. I stedet for å la vår src/website/urls.py inkludere hele allauth.urls, inkluderer vi kun de entrypointene som har med dataporten å gjøre. Dette gjøres via vår egen modul feide.urls.

En annen mulig løsning kunne vært å inkludere hele allauth.urls for så å overstyre/maskere de entrypointene vi ikke ønsker skal bli brukt. Denne måten er også sårbar for eventuelle endringer i nye versjoner av django-allauth (som tilføyer nye eller bytter navn på de endepunktene vi har overstyrt.

Det er derfor lagt til automatiserte tester i src/feide/tests.py som sjekker at alt fremdeles er OK etter eventuelle oppgraderinger av django-allauth: Testene sjekker at de endepunktene og viewene vi har overstyrt faktisk er de som brukes, og at det ikke har dukket opp nye endepunkter under /accounts som ikke er maskert (bortsett fra /accounts/dataporten).

Se src/feide/tests.py.

Inn- og utlogging

Utlogging fra Dataporten er ikke implementert i django-allauth. Vi har derfor implementert noen egne views i feide-modulen:

Logging av inn/utlogginger og feilede innloggingsforsøk gjøres via signalhåndterere/callbacks i feide.signals. Disse installeres via AppConfig-klassen i feide.apps, se src/feide/__init__.py.