OpenVOOT-avklaringer

En utredning om bruk av OpenVOOT-protokollen i Cerebrum.

1   Om OpenVOOT

VOOT er en datamodell og protokoll for innhenting av gruppeinformasjon.

Tanken er å ha en standardisert modell for utveksling av gruppeinformasjon i UH-sektoren i europa.

Standarden er under utarbeiding, alstå ikke ferdig. Hvis vi begynner å implementere denne standarden, betyr dette at vi må være åpne for at spesifikasjonen endres.

2   Om grupper i Cerebrum

2.1   Manuelle grupper

Manuelle grupper er enkle gruppe-objekter som består av:

  • gruppenavn
  • beskrivelse

Grupper kan ha ulike medlemstyper (person, bruker, andre grupper). Det finnes bare en type medlemsskap -- medlem.

Rekursjon: I Cerebrum tenker vi ofte at objekter som er medlem av en gruppe A som igjen er medlem av en gruppe B, så er objektet implisitt medlem av gruppe B. Et objekt kan således være medlem av en gruppe B dobbelt opp. Både gjennom direkte meldemsskap i B og implisitt gjennom medlemsskap i A.

2.2   Automatiske grupper

Automatiske grupper er simpelthen grupper i Cerebrum hvor medlemsskap legges til eller fjernes gjennom regelmessige synkroniseringsjobber. I praksis er disse gruppene også manuelle grupper, men endringer som gjøres i medlemslisten i slike grupper utenfor synkroniseringsjobb vil alltid overskrives neste gang en synkronisering kjører.

For å ha automatisk vedlikehold i en gruppe hvor også manuelle endringer kan legges inn, benyttes implisitt medlemsskap:

  1. En manuell gruppe A opprettes

  2. En automatisk gruppe B opprettes

  3. B meldes inn i A, slik at medlemsskap i B gir implisitt medlemsskap

    i A

  4. Manuelt medlemsskap i A gir direkte meldemsskap

Utfordringene med denne løsningen er:

  • Svært kostbart å gjøre rekursive gruppeoppslag
  • Mange medlemsskap og grupper
  • Utfordrende å synkronisere til eksterne systemer som ikke støtter rekursive grupper, eller hvor rekursive gruppemedlemsskap ikke gir implisitt medlemsskap.

2.3   Virtuelle grupper

Virtuelle grupper er en ny gruppetype som lar oss utvikle automatiserte grupper hvor gruppemedlemsskap uttrykkes gjennom database-spørringer mot andre tabeller enn medlemsskapstabellen.

Dette omgår følgende problemstillinger med automatiske grupper:

  • Medlemsskap "dobbeltlagres" (en gruppe som vedlikeholdes på bakgrunn av tilknytning, lager en "skyggekopi" av tilknytninger som medlemsskap). Virtuelle grupper omgår dette med å gjøre oppslag på f.eks. tilknytning når medlemsskap slåes opp.
  • Medlemsskap overskrives (manuelle endringer tar midlertidig effekt, men overskrives ved neste synk). Virtuelle grupper omgår dette ved å ikke tillate endringer i medlemsskap. Medlemsskap er simpelthen read-only, og må eventuelt endres i kildetabellene.
  • Medlemsskap er forsinket (endring i f.eks. affiliation påvirker ikke medlemsskap før neste synk). Igjen løses dette ved å slå opp i kildetabell ved behov.

Utfordringene med denne løsningen er:

  • Omsetting fra endring i kildesystem til endring i gruppe, for events/changelog
  • Tydeliggjøring av virtuelle grupper -- ser ut som vanlige grupper.
  • Vanskelig å implementere (mange fallgruver)

2.4   Medlemsskap i Cerebrum

For en gitt gruppetype (manuelle grupper, automatiske grupper, virtuelle grupper) finnes det bare en medlemstype; medlem.

For medlemsskap i manuelle og automatiske grupper, er medlemsskap uttykt med en enkel tabell som binder sammen gruppe-id, med medlems-id. På UiO finnes ca. ~3 000 000 slike medlemsskap.

I tillegg har vi virtuelle grupper, hvor medlemsskapsliste genereres ved oppslag på andre tabeller i Cerebrum, f.eks. tilknytninger.

Alle andre roller uttrykkes i bofhd_auth-modulen, og gjennom opsets, som benyttes for å teste om brukere har tilgang til å gjøre endringer på grupper i bofh.

opsets definerer ulike roller i ulike Cerebrum-miljø -- her finnes det lite som er standardisert. UiO har f.eks. veldig mange sære roller (DigitalEksamen, Expand-Group, Group-Admin, Netgroup-Owner).

Den ryddigste implementasjonen av roller, er å finne i WebID. Her har man rollene Group-Moderator, og Group-Owner, som matcher ganske bra med medlemsrollene i VOOT.

3   OpenVOOT-spesifikasjon

3.1   Datatyper

3.1.1   SCIM

VOOT benytter seg av datatyper fra SCIM (https://tools.ietf.org/html/rfc7643#section-2.3).

I tillegg defineres en spesiell datatype, translatable-string.

3.1.2   translatable-string

VOOT definerer denne datatypen, som er en lokalisert tekststreng. Verdien styres av request-parametere, og vil enten være et dataobjekt med samtlige oversettelser for attributtet, eller en tekststreng med en utvalgt oversettelse.

Dette benyttes typisk for å tilby oversettelser for attributter som displayName og description.

Dersom request inneholder ?translate=false, skal objekt returnere alle oversettelser i attributter med denne datatypen. Verdien er da et objekt:

{
    "en": "Organization",
    "nb": "Organisasjon"
}

Språkkode skal være et gyldig kortnavn i ISO639-1. Det ser ikke ut til å være krav om hvilke språk som skal støttes.

Det er uklart hvorvidt språkstøtte for objekter må være implementert konsistent (altså om oversettelser må være tilstede for samtlige språk på samtlige objekter).

Dersom query-parameteret ikke er tilstede, eller satt til true, skal feltene kun innholde en utvalgt oversettelse, som velges ut i fra request-header Accept-Language.

Det også uklart hvilket språk som skal velges i en del tilfeller:

  • Hva skjer dersom man ikke har med Accept-Language-header?
  • Hva skjer dersom ingen av språkene fra Accept-Language er tilgjengelige for et objekt? For description er det kanskje greit med en tom verdi, men det er værre for påkrevde felt som displayName.
  • Skal man blande språk dersom oversettelser ikke er implementert konsistent? Er det m.a.o. greit å returnere norsk description og engelsk displayName i ett og samme objekt, dersom Accept-Language: en, no;q=0.8 og det mangler description på engelsk?
  • Hvilket språk velges dersom man spør etter makrospråk (f.eks. no), mens flere medlemmer av makrospråket er implementert (f.eks. nb og nn)

3.2   Datamodell

VOOT definerer flere data-objekter som skal benyttes i resultat fra API-et, og som potensielt kan benyttes til andre formål.

Spesifikasjon: https://openvoot.org/datamodel/

Modellen består overordnet av objektene:

Et «user»-objekt kan ha en «membership»-tilknytning til et «group»-objekt. Et «group»-objekt kan ha en «group type».

3.2.1   group

Objekt som beskriver en enkeltgruppe.

Minimalt eksempel:

{
    "id": "8878ae43-965a-412a-87b5-38c398a76569",
    "displayName": "Project on group APIs"
}
id → string

En unik ID for gruppe-objektet. I eksempelet er dette en UUID, som også er det Dataporten benytter i sitt gruppe-API.

Kan være en vilkårlig streng, så i Cerebrum kunne vi benyttet f.eks. gruppens entity_id.
displayNametranslatable-string

Navn eller beskrivelse av gruppen.

Vi har ikke navn eller beskrivelse med flere språk i Cerebrum. I tillegg vil ikke beskrivelse være unikt nok til å brukes som displayName. Her vil nok gruppenavnet være mest meningsbærende - men dette kan ikke ha oversettelser?

Her kan det tenkes at vi burde implementere displayName-støtte for grupper (typ. EntityNameWithLanguage, hvor f.eks. group_name benyttes for alle språk som ikke har satt et displayName).

I tillegg kan objektet inneholde en serie med «valgfrie» attributter:

Er attributter egentlig valgfrie? Eller bare kondisjonelle; altså at de kan/må returneres i enkelte tilfeller? Savner evt. informasjon om dette i protokoll-spesifikasjonen!
description → string, translatable-string?

Beskrivelse av gruppen, kan valgfritt være translatable-string?

Her kan vi f.eks. eksponere description uavhengig av språk, evt. implementere en description med språkstøtte.
type → string
Spesifikasjonen sier «a pointer to a group type», men gir voot:default som eksempel. Så her er vel verdien ID-en til et gitt group type-objekt?
notBefore → DateTime, ISO8601-string?
> «the group did not exist before this date».

Grupper med notBefore dagens dato regnes som ugyldige, og vil typisk ekskluderes i en del utlistinger i API-et.

Her kan vi velge å eksponere created_date fra Cerebrum.
notAfter → DateTime, ISO8601-string?
> «the group is deleted or not valid after this date.»

Grupper med notAfter dagens dato regnes som ugyldige, og vil typisk ekskluderes i en del utlistinger i API-et.

Her kan vi velge å eksponere expire_date fra Cerebrum.
public → boolean
> «a boolean flag indicating whether the existence of this group and the
> basic group information is publicly available. Users may search and
> use groups in setting access control, even if they are not member of a
> group.»

Gjør gruppen synlig i søk/oppslag for brukere som ikke er medlem. Uklart om brukere som ikke er autentisert i det hele tatt skal få se disse gruppene.

Vi har et konsept om synlighet i Cerebrum, men dette bør nok ikke benyttes, hverken til tilgangsstyring eller til noe som helst annet. Det er brukt noe ukonsistent.

Tilgangskontroll må vel implementeres i VOOT-apiet? Det fremstår som litt uklart hva bruksområdet til dette attributtet er, eller hva som skal styre hvorvidt en gruppe er public.

active → boolean
> «may be set to false to indicate that the group is currently inactive.
> It will cause the group to not show up on compact group lists.»

Ser ut til å være et hint til grensesnitt om hvorvidt gruppen kan brukes / skal vises. Inaktive grupper skal ekskluderes i en del utlistinger i API-et.

sourceID → string
> «is an identifier that refer to the authorative source defining this
> group. This is useful when the VOOT Provider is merging information
> from a lot of different systems and passes this information on to the
> clients.»

Ser ut til å være et attributt hvor man kan eksponere kildesystem e.l. for grupper. Et eksempel bruker: "sourceID": "voot:sources:uninett:fs"

Kan være interessant f.eks. hvis vi bygger automatiske kurs-grupper o.l. fra FS, eller hvis vi ender opp med å representere roller i nytt ERP-system som grupper. Kan også være interessant for f.eks. virtuelle affiliations-grupper.
membership → list<membership>

I enkelte endepunkt vil gruppe-objektet kunne inneholde en liste over medlemsskap.

Spesifikasjonen sier ikke noe om når dette skal være inkludert.

3.2.2   membership

Objekt som beskriver et gruppemedlemsskap.

Minimalt eksempel:

{}

Objektet kan være tomt, i så fall betyr dette simpelthen at bruker er et ordinært medlem av gruppen (rolle member).

Valgfrie attributter:

basic → string

Medlemsrolle. Hvis ikke oppgitt, betyr dette at medlemmet er ordinært medlem.

Må være en av:

  • member
  • admin
  • owner

Det legges ingen føringer på hvilke tilganger admin eller owner tilordnes.

Det skaper litt problemer for oss at andre roller enn member ser ut til å implisitt bety at man også er member. I Cerebrum kan man nemlig bli tilordnet tilganger (være admin for en gruppe) uten å selv være medlem av gruppen. Det er også uklart om rollen owner implisitt betyr at man er admin.
displayNametranslatable-string
> «A human readable description of the memership role.»

En forklaring på hva rollen innebærer. Her er det ikke klart hvorvidt displayName må være konsistent med rolletypen

  • Må displayName for en gitt rolle være lik i alle medlemsskap i et og samme API?
  • eller kan man benytte ulikt displayName på en gitt rolle: - i ulike gruppetyper? - i ulike grupper av samme type? - i ulike medlemsskap i en og samme gruppe?
active → boolean
> «may be set to false to indicate that the user is a passive member of
> the group»

Noe uklart hva dette skal bety. I videre klarifisering kan det se ut til at dette kan benyttes for tidligere medlemmer av en gitt gruppe, for å f.eks. implementere støtte for en slags uniform grace-periode i eksterne systemer.

Dette er et punkt som bør avklares nærmere hvis det skal taes i bruk.
notBefore → DateTime, ISO8601-string?
> «the group did not exist before this date.»

Fra dokumentasjon så skal dette feltet evt. bare speile gruppens notBefore-felt.

Usikker på om dette er riktig, eller om meningen her er å støtte notBefore/notAfter på medlemsskap, separat fra selve gruppen…
notAfter → DateTime, ISO8601-string?
> «the group is deleted or not valid after this date.»

Se notBefore.

may → ?
> «An object of permissions that a user may or may not have related to
> the group. In example a user may be allowed to edit, manage, invite or
> delete a group.»

Man kan altså tilordne tilganger til et medlemsskap. Dette attributtet ser ikke ut til å være definert i spesifikasjonen, men et eksempel gies:

"may": {
    "listMembers": true
}
userId → string
> «If the membership object is used stand alone, and there is no
> implicit relation to a given user, the user may be referred to by
> using this property.»

Identifikator for hvilken bruker medlemsskapet gjelder.

Det må nok inkuderes i enkelte tilfeller/endepunkter. I enkelte endepunkt er det ikke gitt hvilken bruker man ser medlemsskap for, så det kan være hensiktsmessig å inkludere dette i objektet.

Mistenker at dette feltet egentlig skulle vært brukt i endepunktet /groups/{groupid}/members, i stedet for å returnere et uspesifisert objekt.

Verdien av feltet bør nok være noe som kan dyttes inn som userid i f.eks. GET /user/{userid}/groups, men det er lite som definerer bruker-objekter i VOOT.

groupID → string
> «If the membership object is used stand alone, and there is no
> implicit relation to a given group, the group may be referred to by
> using this property.»

Se userid. Merk ulik casing her?

3.2.3   group type

Objekt som beskriver sett av grupper. En gruppe kan kun ha en gruppetype.

Minimalt eksempel:

{
    "id": "voot:adhoc",
    "displayName": "foo"
}
id → string

En unik ID for gruppetypen.

Burde vi bruke en registrert urn, f.eks. vår egen urn:mace:uio.no (urn:mace:uio.no:voot:group-type:example)

displayNametranslatable-string
Navn eller beskrivelse av gruppetypen.

Objektet skal si noe om hvilket bruksområde en gitt gruppe har.

I følge beskrivelsen kan man også utvide «Group»-objekter med egne attributter. Da kan gruppetypen si noe om hvilke attributter man kan forvente å finne i gruppeobjektet.

Gruppetyper er ikke en del av spesifikasjon, men Dataporten definerer et par:

eduperson:orgunit
OU-er i Feide. Dataporten genererer allerede disse for oss, uklart om en eventuell VOOT-integrasjon med Dataporten betyr at vi må begynne å eksponere disse gruppene selv.
eduperson:org

Feide-organisasjon -- Dataporten genererer allerede disse for oss.

TODO: Her er det vel bare en enkeltgruppe for f.eks. UiO / et gitt Cerebrum-miljø.

voot:adhoc
Manuelle grupper som opprettes og vedlikeholdes i f.eks. Dataporten.

Her må vi tenke oss godt om før vi begynner å klassifisere gruppene våre inn i gruppetyper. Vi kan dele inn grupper i Cerebrum i typer etter flere kriterier:

  • manuell vs. automatisk vs. virtuell (kun en av disse er ikke som de andre)?
  • posix vs ikke-posix?
  • basert på spreads / hvilke systemer gruppen finnes i?
  • visibility?

Eksempel-gruppene taler for å f.eks. benytte:

  • en felles gruppetype for manuelle og automatiske grupper.
  • en gruppetype per implementasjon av virtuelle grupper (kurs, affiliations, kull, studieemne)

For å kunne gjøre dette bør vi nok ha på plass litt flere gruppetyper før vi kan benynne å tenke på dette.

3.3   Tilgangskontroll

Protokollen spesifiserer at all autentisering skal skje vha. OAuth 2.0 Bearer Tokens. Det er imidlertid uklart hvorvidt enkelte endepunkter skal kreve autentisering.

Endepunktene under path /me krever at vi kan indentifisere en gitt bruker. Her må man alstå enten bruke OIDC eller et OAuth 2.0 userinfo-endepunkt, og request må utføres av en autentisert og autorisert bruker.

3.4   Protokoll

VOOT-protokollen definerer et HTTP-API, type REST/JSON, og et sett med endepunkter som lar brukere og tjenester hente gruppeinformasjon.

Spesifikasjon: https://openvoot.org/protocol/

GET /me/groups → list<group>

Henter en liste av grupper som den autentiserte brukeren er medlem av.

Med query-parameter ?showAll=true, skal man også inkludere alle grupper brukeren er «assosiert» med. Dette inkluderer:

  • inaktive medlemsskap (membership: active, notBefore?, notAfter?)
  • inaktive grupper (group: notBefore, notAfter)
GET /me/groups/{groupid}membership

Henter informasjon om eventuelt medlemsskap i en spesifikk gruppe for den autentiserte brukeren.

Merk at medlemsskap i en gruppe for en gitt bruker er ett, og bare ett objekt. Man kan altså ikke ha rollene member og admin i en og samme gruppe.

Dette skaper noe problemer i Cerebrum, hvor det at man er admin i en gruppe ikke implisitt betyr at man er medlem av en gruppe.

Skal vi implementere dette i Cerebrum, vil vi altså ikke uten videre kunne gi andre roller enn member.

GET /groups/{groupid}group
Henter informasjon om gruppe.
GET /groups/{groupid}/members → list<?>

Henter liste med medlemmer i en gruppe.

Med query-parameter ?showAll=true, skal man også inkludere alle medlemmer som er «assosiert» med gruppen (se /me/groups)

Her er det besnærende å tenke seg at resultatet skulle være en liste av membership-objekter, hvor userid var satt. I stedet definerer ikke protokollen noen datastruktur på resultatet som returneres!

Eksempelet fra spesifikasjonen viser en udefinert bruker-struktur som inkluderer et membership-objekt uten ``userid``…

System Message: WARNING/2 ({DAVSYNC3}openvoot/openvoot-notat.rst, line 591); backlink

Inline literal start-string without end-string.
GET /groups → list<?>

Henter en liste med navn og id på grupper.

Søk og søkeresultat er uklart spesifisert, men er uttrykt med et eksempel:

[
  {"id": "0", "displayName": "foo"},
  {"id": "1", "displayName": "bar"}
]

Listen kan filtreres med query-parameter query={search}. Hvordan dette søket skal fungere er heller ikke definert i spesifikasjon.

  • Hva skal søkes på? displayName? Hvilket språk?
  • Det kan bli svært kostbart å gjøre slike søk i Cerebrum.
  • Det blir for mye data i resultatet av et slikt søk. En fullstendig liste i Cerebrum (UiO) vil fort inkludere ~700 000 grupper. Med en simpel datastruktur som i eksempelet over, med en snittlengde på 8 tegn i navn, vil dette gi en ~30 MB json-dump i retur. Mer hvis man har ulike navn på ulike språk, eller skal inkludere mer i resultatet.
GET /grouptypes → list<group type>
Henter en liste med alle gruppetyper som er definert i systemet.
GET /user/{userid}/groups -> list<group>

Likt som /me/groups, men for en vilkårlig bruker. Valgfri funksjonalitet.

userid er ikke definert i spesifikasjon for datamodell eller protokoll.

Kan en eventuell implementasjon av /me/groups bare omdirigere til denne, men med riktig userid?:

HTTP/1.1 302 Found
Location: <host>/<prefix>/user/<brukernavn>/groups``
GET /user/{userid}/groups/{groupid}membership
Likt som /me/groups/{groupid}, men for en vilkårlig bruker. Valgfri funksjonalitet.

4   Uklarheter

OpenVOOT ser ut som en noe umoden spesifikasjon. Det er en del mangler og uklarheter i VOOT-spesifikasjonen:

Manglende datamodeller

Resultatet av søk har ikke noe definert resultat. Det er gitt et eksempel-resultat, men dette er ikke nok.

I tillegg returnerer også /groups/{groupid}/members et udefinert objekt i eksempelet. Burde dette endepunktet egentlig returnere bare et membership-objekt? I så fall burde jo i det minste userId være påkrevd i dette tilfellet?

Dokumentasjon av protokollen burde også referere til datamodellen, og ikke bare oppgi eksempler.

Valgfrie attributter

Mange objekter i datamodellen spesifiserer en rekke valgfrie attributter. Enkelte av disse virker ikke som om de burde være valgfrie, men heller conditional, altså påkrevt i enkelte sammenhenger.

I tillegg er det ikke klart hvorvidt det må være konsistens i valgfrie attributter. Dersom group.notBefore inkluderes for en gruppe, må det da inkluderes i alle?

Søkefunksjonalitet
Filtrering/matching av grupper er ikke spesifisert. Hvordan skal søk fungere? Hva skal søkes på?
Membership
  • userId vs. groupID: Skal disse ha ulik casing?
  • userId og groupID: Er disse virkelig valgfrie?
  • notBefore og notAfter: Er dette virkelig en speiling av gruppens attributter, eller er beskrivelsen feil?
  • displayName: Må beskrivelser være konsistente?
  • basic: Hvordan skal rollene benyttes?
  • may: Verdien er uspesifisert
Oversettelser
Må språk være implementert konsistent? Hva skjer hvis en oversettelse mangler?
Andre ting
  • group.type: Spesifikasjon kaller det en referanse, kanskje tydeliggjøre at dette er en group type.id
  • /user/{userid} vs. /groups/{groupid}

5   Anbefaling

OpenVOOT fremstår som en noe umoden spesifikasjon. Vi bør:

  1. Melde inn spørsmål til Uninett, slik at:
    • Vi kan få avklart usikkerhetsmoment som fremkommer av denne teksten
    • Spesifikasjonen kan rettes opp der den er uklar
    • Vi kan være med å utforme denne protokollen
  2. Vente med implementasjon til arbeidet med VOOT er kommet litt videre
  3. Gjøre nødvendige endringer i Cerebrum for å støtte en eventuell fremtidig VOOT-implementasjon.

En eventuell VOOT-implementasjon bør være separat fra Cerebrum-APIet. Skal vi ha VOOT, bør dette implementeres som et eget API, et slags view mot gruppestrukturen i Cerebrum.

Dette fordi:

  1. VOOT definerer et API og datamodeller som er inkompatible med Cerebrum-APIet.
  2. VOOT er til syvende og sist noe vil ønsker for å eksponere grupper i Dataporten. Det bør dermed være et API som også nåes gjennom Dataporten.
  3. VOOT spesifiserer ingen måte å gjøre endringer i grupper. Dette er noe Cerebrum-APIet må ha.

6   User Stories

TODO:

  • implementere støtte for gruppe-typer i Cerebrum?
  • implementere støtte for medlemstyper i Cerebrum?
  • implementere egen OpenVOOT-tjeneste for Cerebrum?
  • implementere språkstøtte i gruppenavn / displayName?
  • implementere språkstøtte i gruppe-beskrivelse?
Av fhl
Publisert 30. nov. 2017 15:12