Elektronischer Semesterapparat

Einleitung

Übersicht

Für die transparente Anmeldung in den elektronischen Semesterapparat (ESA) des Visual Library Servers (VLS) ist das hier beschriebene Protokoll vorgesehen.

Prozess

Dem im Fremdportal angemeldeten Dozenten <Dozent> wird dort ein Hyperlink angeboten, über den er den ESA der VLS erreichen kann. Dieser Hyperlink enthält eine zeitlich begrenzt gültige und digital signierte Berechtigung, sich bei der VLS-Instanz anzumelden.

Der Hyperlink kann eventuell auch für den Dozenten transparent zum Beispiel innerhalb eines Inlineframes aufgelöst werden.

Anmeldeverfahren

Die Anmeldung des Dozenten für einen Kursraum in einer VLS-Instanz vls.example.com erfolgt direkt über eine speziell formatierte URL:

    http://vls.example.com/order/start?uct=eJytUstu00AUXSEWLPgBkKxZgRRcjx-[...mehr...]

Die URL besteht aus einem statischen Teil und einem dynamischen Teil. Der statische Teil umfasst den Hostnamen der VLS-Instanz und die Route order/start. Der dynamische Teil umfasst den GET Parameter uct (Untrusted Client Transport), dessen Wert ein mehrfach kodierter Datensatz ist.

Der Name „Untrusted Client Transport“ weist darauf hin, dass die Anmeldedaten über einen Benutzer transportiert werden können, dem nicht vertraut werden muss. Technische Maßnahmen sichern das Verfahren dahingehend ab, dass die Anmeldung nur für diesen Dozenten und Kursraum für eine begrenzte Zeit möglich ist.

Konfiguration

In der Domain-Konfiguration der VLS-Instanz sind im Abschnitt [moodle] folgende Einstellungen verfügbar:

passphrase (Zwingend erforderlich)

Pre-shared Key (PSK), der auch im Fremdportal hinterlegt ist, und zur Erzeugung der digitalen Signatur in der Sicherheitsschicht des Protokolls verwendet wird.

Der PSK muss im ASCII Format kodiert sein und darf nur druckbare Zeichen sowie Leerzeichen enthalten (0x20-0x7e).

hashname (Optional, voreingestellt: sha256)

Name des Hash-Algorithmus, der zur Erzeugung der digitalen Signatur verwendet wird. Die folgenden Namen sind möglich (entsprechend dem Python-Modul hashlib):

  • md5 (aus Sicherheitsgründen nicht empfohlen)

  • sha1 (aus Sicherheitsgründen nicht empfohlen)

  • sha224

  • sha256 (Voreinstellung)

  • sha384

  • sha512

Empfohlen ist die Voreinstellung sha256. Da der verwendete Hash-Algorithmus nicht mit übertragen wird, muss in der Domain-Konfiguration und im Fremdportal der gleiche Hash-Algorithmus fest eingestellt werden.

Nutzdatenschicht

Die Nutzdaten werden vom Fremdportal erzeugt und dann im JSON-Format serialisiert. Die zu verwendende Kodierung ist UTF-8.

Das Format der Nutzdaten ist ausführlich im Abschnitt „signindata“ beschrieben.

Sicherheitsschicht

In der Sicherheitsschicht wird dem Datensatz eine digitale Signature angefügt. Um eine aufwändige Public-Key Infrastruktur zu vermeiden, wird diese nach dem HMAC Verfahren aus dem PSK und dem Nutzdatensatz erzeugt. Das verwendete Hash-Verfahren ist konfigurierbar, die empfohlene Voreinstellung ist SHA-256.

Der HMAC-Digest wird im Binärformat ohne Trennzeichen an den Nutzdatensatz angefügt. Da die Länge des Digest durch den Hash-Algorithmus festgelegt ist, kann dieser auf der Empfängerseite wieder eindeutig abgetrennt werden.

Kompressionsschicht

In der Kompressionsschicht wird der signierte Datensatz mit dem ZLIB Verfahren komprimiert.

Transportschicht

Der komprimierte Datensatz wird Base64-kodiert. Als Alphabet wird, abweichend von der üblichen Praxis, das Zeichen - (Minus) anstelle von + (Plus) und _ (Unterstrich) anstelle von / (Schrägstrich) verwendet. Diese Konvention erlaubt es, den Datensatz ohne weitere Verarbeitung in die URL einzufügen.

Anmeldedaten

In diesem Abschnitt werden die zu übertragenden Nutzdaten bei der Anmeldung beschrieben.

Die Nutzdaten sind ein Objekt mit folgenden Einträgen:

time (number, erforderlich)

Der Zeitstempel der Anmeldung in Sekunden seit Mitternacht am 1. 1. 1970 (UNIX epoch) für UTC (Coordinated Universal Time, auch bekannt als Greenwich Mean Time, GMT).

Beispiel: "time": 1384349644

Hinweis

Da die Anmeldedaten nur für eine begrenzte Zeit gültig sind, müssen die Uhren auf dem Fremdportal und der VLS-Instanz synchronisiert sein. Eine Angabe der Zeit in localtime ist nicht zulässig und führt zu einem Datenpaket, das in der Vergangenheit gültig war oder erst in der Zukunft gültig sein wird.

token_uid (string, optional)

Eine eindeutige Token-ID des Fremdportals für dieses UCT-Paket. Wird vom VLS ESA ignoriert.

user (object, erforderlich)

Der Dozent, für den die Anmeldung erfolgen soll. Dozenten sind ihrerseits Objekte, deren Nutzdaten weiter unten erklärt sind.

course (object, erforderlich)

Der Kursraum, für den die Anmeldung erfolgen soll. Kursräume sind ihrerseits Objekte, deren Nutzdaten weiter unten erklärt sind.

categories (object, optional)

Kategorien, unter denen der Kursraum abgelegt ist. Kategorien sind ihrerseits Objekte, deren Nutzdaten weiter unten erklärt sind.

Hinweis

Kategorien sind eine optionale Erweiterung. Wird keine Kategorie im Kursraum referenziert, kann das categories Objekt vollständig entfallen.

server (object, optional)

Das Fremdportal, welches die Anmeldung durchführt. Server sind ihrerseits Objekte, deren Nutzdaten weiter unten erklärt sind.

Hinweis

Die Serverdaten sind nicht in jedem Fall erforderlich. Werden keine Serverdaten übertragen, kann das server Objekt vollständig entfallen.

Dozentendaten

Ein Benutzer-Objekt (user) hat folgende Einträge:

id (number, erforderlich)

Eine eindeutige Kenn-Nummer für diesen Dozenten. 0 ist reserviert und keine gültige Kenn-Nummer.

Beispiel: "id": 45

username (string, erforderlich)

Die Benutzerkennung des Dozenten (kodiert in UTF-8).

Beispiel: "username": "rfeynman"

firstname (string, erforderlich)

Der Vorname des Dozenten.

Beispiel: "firstname": "Richard"

lastname (string, erforderlich)

Der Nachname des Dozenten.

Beispiel: "lastname": "Feynman"

email (string, erforderlich)

Die Email-Adresse des Dozenten.

Beispiel: "email": "rf@caltech.example.com"

timemodified (number, optional)

Der Zeitstempel (in Sekunden seit Mitternacht am 1. 1. 1970, UTC) der letzten Änderung an den Kursraumdaten. Wenn der Zeitstempel gegeben ist, wird der Kursraum-Datensatz im VLS ESA nur aktualisiert, wenn die dortige Version älter ist als timemodified.

Beispiel: "timemodified": 1384328462

Kursraumdaten

Ein Kursraum-Objekt (course) hat folgende Einträge:

id (number, erforderlich)

Eine eindeutige Kenn-Nummer für diesen Kursraum. 0 ist reserviert und keine gültige Kenn-Nummer.

Beispiel: "id": 123

fullname (string, erforderlich)

Der vollständige Name des Kursraums (kodiert in UTF-8).

Beispiel: "fullname": "Lectures on Physics, Part I"

shortname (string, optional)

Der Kurz-Name des Kursraums (kodiert in UTF-8). Die Voreinstellung ist fullname.

Beispiel: "shortname": "Physics I"

term (string, erforderlich für alle außer Moodle)

Eine Kennzeichnung für das Semester für diesen Kursraum. Die Kennzeichnung ist:

  • WS<JJ> für das Wintersemester

  • SS<JJ> für das Sommersemester

Hierbei ist <JJ> die 2-stellige Jahresangabe.

Beispiel: "term": "SS61"

Hinweis

Für Moodle wird stattdessen das Feld idnumber verwendet.

url (string, optional)

Rücksprungadresse für den Kursraum im Fremdportal. Diese sollte eine vollständige URL sein, welche dem Dozenten in einem Link zur Verfügung gestellt werden kann.

Beispiel: "url": "https://caltech.example.com:8080/course/123"

Hinweis

Fehlt das Feld, so wird eine Rücksprungadresse aus den Serverdaten ermittelt, falls vorhanden. Die Angabe über das Feld course.url ist aber empfohlen.

timemodified (number, optional)

Der Zeitstempel (in Sekunden seit Mitternacht am 1. 1. 1970, UTC) der letzten Änderung an den Kursraumdaten. Wenn der Zeitstempel gegeben ist, wird der Kursraum-Datensatz im VLS ESA nur aktualisiert, wenn die dortige Version älter ist als timemodified.

Beispiel: "timemodified": 1384328462

category (number, optional)

Die Kenn-Nummer der Kategorie für diesen Kursraum. Wird keine Kategorie angegeben, so werden alle Kurse eines Semesters in einer automatische erzeugten Kategorie gesammelt.

Beispiel: "category": 5

sortorder (number, optional)

Eine Ordnungsnummer zur Sortierung der Kursräume.

idnumber (string, optional)

Dieses Feld ist für die Anbindung an Moodle reserviert, und darf nicht von anderen Fremdportalen verwendet werden.

Beispiel: "idnumber": "LecPhys_SS61_01"

Kategoriendaten

Ein Kategorien-Objekt (categories) hat folgende Einträge (<ID> ist ein Platzhalter für eine beliebige Kenn-Nummer):

<ID> (object)

Das Kategorie-Objekt mit der Kenn-Nummer <ID>. Jede Kategorie ist seinerseits ein Objekt, deren Nutzdaten weiter unten erklärt sind.

Beispiel: "5" : { "id": 5, [...mehr...] }

Im Kategorien-Objekt muss ein Kategorie-Objekt für die Kategorie des Kursraum (course.category), sowie je eins für alle Elternkategorien (<ID>.parent) bis zur Wurzelkategorie (<ID>.parent: 0) enthalten sein.

Beispiel:

"categories": {
   "5" : { "id": 5, "parent": 3, [...mehr...] },
   "3" : { "id": 3, "parent": 0, [...mehr...] }
}

Hinweis

Kategoriedaten sind nur dann erforderlich, wenn course.category angegeben wird. In dem Fall müssen allerdings alle Kategorien bis zur Wurzel-Kategorie in den Nutzdaten enthalten sein.

Kategorie

Ein Kategorie-Objekt hat folgende Einträge:

id (number, erforderlich)

Eine eindeutige Kenn-Nummer für diese Kategorie. 0 ist reserviert und keine gültige Kenn-Nummer.

Beispiel: "id": 5

parent (number), erforderlich)

Die Kenn-Nummer des übergeordneten Kategorie-Objekts. Handelt es sich bei dieser Kategorie um eine Wurzelkategorie, so ist parent: 0 zu setzen.

Beispiel: "parent": 3

name (string, erforderlich)

Der Name der Kategorie.

sortorder (number, optional)

Eine Ordnungsnummer zur Sortierung der Kategorien.

timemodified (number, optional)

Der Zeitstempel (in Sekunden seit Mitternacht am 1. 1. 1970 für UTC) der letzten Änderung an den Kategoriedaten. Wenn der Zeitstempel gegeben ist, wird der Kategorie-Datensatz im VLS ESA nur aktualisiert, wenn die dortige Version älter ist als timemodified.

Beispiel: "timemodified": 1384328462

Serverdaten

Ein Server-Objekt (server) hat folgende Einträge:

Rücksprungadresse

Die Serverdaten dieses Abschnitts werden nur dann verwendet, wenn keine Rücksprungaddresse unter course.url angegeben wird. In dem Fall kann die Rücksprungadresse aus den folgenden Serverdaten gewonnen werden.

Hinweis

Die folgenden Einträge sind als Gruppe optional. Dies bedeutet, dass entweder kein Eintrag oder alle Einträge vorhanden sein müssen.

HTTPS (boolean)

Wahr, wenn die Anmeldedaten über eine HTTPS-Verbindung bereitgestellt wurden. Ansonsten falsch.

REQUEST_URI (string)

Der (absolute) URL-Pfad, für welche die Anmeldedaten bereitgestellt wurden. Diese kann (zusammen mit den anderen Daten) verwendet werden, um vom VLS ESA zurück in das Fremdportal zu navigieren.

Beispiel: "REQUEST_URI": "/esa/portal.php?id=456"

SERVER_ADDR (string)

Die IP-Adresse des Fremdportals.

Beispiel: "SERVER_ADDR": "192.168.123.45"

SERVER_NAME (string)

Der Host-Name des Fremdportals.

Beispiel: "SERVER_ADDR": "moodle.example.com"

SERVER_PORT (number)

Die Port-Nummer des Fremdportals.

Beispiel: "SERVER_PORT": 80

Beispiele

Anmeldungsdaten

Minimale Nutzdaten zur Dozentenanmeldung an einen Kursraum in VLS:

{
    "time": 1384349644,
    "user": {
        "id": 45,
        "username": "rfeynman",
        "firstname": "Richard",
        "lastname": "Feynman"
        "email": "rf@caltech.example.com",
    },
    "course": {
        "id": 123,
        "fullname": "Lectures on Physics, Part I",
        "term": "SS61",
        "url": "https://caltech.example.com:8080/course/123"
    }
}

Python-Programm zur Übersetzung der Nutzdaten in das UCT-Format:

import json, hashlib, hmac, zlib, base64

def data2uct(data, passphrase, hashname="sha256"):
    # Nutzdatenschicht
    data = json.dumps(data)

    # Sicherheitsschicht
    hash_factory = lambda: hashlib.new(hashname)
    sig = hmac.HMAC(passphrase, data, hash_factory).digest()
    data = data + sig

    # Kompressionsschicht
    data = zlib.compress(data)

    # Transportschicht
    return base64.b64encode(data, "-_")

Python-Programm zur Überprüfung des UCT-Format und zur Extraktion der Nutzdaten:

import json, hashlib, hmac, zlib, base64
def uct2data(uct, passphrase, hashname="sha256"):
    # Transportschicht
    data = base64.b64decode(uct, "-_")

    # Kompressionsschicht
    data = zlib.decompress(data)

    # Sicherheitsschicht
    hash_factory = lambda: hashlib.new(hashname)
    sig_len = hash_factory().digest_size
    data, orig_sig = data[:-sig_len], data[-sig_len:]

    # Validierung
    sig = hmac.HMAC(passphrase, data, hash_factory).digest()
    if sig != orig_sig:
      raise ValueError("invalid signature")

    # Nutzdatenschicht
    return json.loads(data)