8 Minuten
Konfigurierbare API mit Django und Django-Rest-Framework
Ich gebe zu, Konfigurierbare API klingt erst einmal etwas ungewohnt. Tatsächlich meine ich damit aber genau das, was da steht. Das Ziel dieses Posts ist es zu zeigen, wie man eine HTTP API mit Django so erstellen kann, dass man als Anwender oder als Administrator die Ausgabe der Schnittstelle konfigurieren kann, ohne dabei die Applikation neu zu starten.
Eine Frage, die jetzt vielleicht im Raum steht ist: “Wozu das?”.
Der konkrete Use-Case
In meinem Fall ist es so gewesen, dass ich für ein Projekt einen OAuth2 Provider gebaut habe. Ein Client C kann sich also registrieren und erhält eine ClientID und ein ClientSecret. Mit dessen Implementierung kann Client C seinen Usern die Möglichkeit geben seine Applikation zu nutzen, ohne, dass diese sich bei ihm extra “registrieren” müssen. User U klickt also auf zum Beispiel auf einen Button “Login mit Provider P”, gibt seinen Grant ab und wird wieder zur Applikation A geleitet. Applikation A ruft im Hintergrund die User-Daten beim Provider P ab. Und genau hier wird es interessant.
In Deutschland regeln mehrere Gesetze, wie wir mit personenbezogenen Daten umgehen sollen. Und das ist auch gut so. Die wichtigsten sind aber wohl das Telemediengesetz (TMG), die Datenschutzgrundverordnung (DSGVO) und das Bundesdatenschutzgesetz (BDSG). In einigen Dingen überschneiden diese sich thematisch und im Ganzen decken die Normen eine breite Menge ab.
§ 71 BDSG (BDSG) verlangt “[…] angemessene Vorkehrungen zu treffen, die geeingnet sind, Datenschutzgrundsätze, wie etwa Datensparsamkeit wirksam umzusetzen. […].
Als Provider habe ich natürlich keine direkte Kontrolle darüber, was Client C mit den Daten des Users macht, die er erhält. Aber ich kann als Provider dafür Sorge tragen, dass Client C die Grundsätze der Datensparsamkeit einhalten kann, in dem er sich selbst von Daten “befreit”, die er zur Ausübung seines Dienstes nicht benötigt. Ein wenig konkreter: Provider P hat die folgenden Informationen zum User: Vorname, Nachname, E-Mail Adresse, Geburtsdatum. Client C benötigt jedoch nur den Vornamen und die E-Mail Adresse. Wäre es dann im Sinne des Users oder gar im Sinne der Datensparsamkeit, wenn dem Client C auch die Daten gesendet werden, die er gar nicht haben will? Und wäre es im Sinne des Client C, der dann auch sicherstellen muss, dass diese Daten weder gespeichert noch ausgewertet werden?
In der vorligenden Lösung biete ich einfach dem Client C die Möglichkeit an, selbst zu entscheiden, welche Daten er gerne hätte. Jede Änderung wird natürlich auch entsprechend festgehalten, sodass zu jedem Zeitpunkt klar ist, welche Daten vom Client C für die User abgerufen wurden. Eine nachträgliche Änderung führt zum Erlöschen der bisheringen Grants und AccessToken, sodass der User erneut zustimmen muss (diesen Teil zeige ich hier aber nicht).
Die Lösung
Django und Django-Rest-Framework
Als Provider kommt eine Django App zum Einsatz, die durch das Django-Rest-Framework erweitert wird. Als Provider Bibliotheken werden Python Social Auth und die dafür spezialisierte App für Django verwendet. In einem späteren Beitrag werde ich zeigen, wie sich ein OAuth2 Provider recht schnell selbst erstellen lässt mit den oben genannten Tools.
Application Model
Von der Bedinung her soll das Ganze recht einfach sein: Beim Anlegen der Application kann man einfach auswählen, welche Felder man gerne vom User hätte. Die werden dann später ausgelesen und eingebunden.
Daher sieht das Application Model wie folgt aus:
Das ChoiceArrayField
in Zeile 6 ist eine Klasse, die vom ArrayField
erbt und das formfield()
anpasst, damit das ArrayField
als MultipleChoiceField
im Django Admin angezeigt wird. Wer das nicht haben möchte, kann auch ganz einfach darauf verzichten und in Zeile 122 ArrayField
stattdessen verwenden.
Das ist schon mal die halbe Magie, denn nun wird unter Application.user_fields
(Zeile 122) eine Liste von Feldern gespeichert. Diese werden wir nun nutzen, um den Serializer dynamisch anzupassen.
UserSerializer
Dieser sieht dann wie folgt aus:
Der Großteil der Methode get_fields()
stammt direkt aus der original Methode des Serializers. Aber Zeile 46 und Zeile 55-57 sind entscheidend. In Zeile 46 überschreiben wir den Inhalt von self.Meta.fields
mit den Inhalten, die im Feld user_fields
von Application
drin sind. Anschließend müssen wir noch die Felder “säubern”, die zusätzlich zum Serializer hinzugefügt wurden. Das passiert in Zeile 55-57.
Welche Felder da genau entfernt werden, zeige ich an folgendem Beispiel. Nehmen wir einmal an, unser User hätte eine Organisation und diese könnte mit serialisiert werden. Dazu bräuchte der UserSerializer
ein extra Feld organisation
, welches einen OrganisationSerializer
beinhaltet.
Die Methode get_fields
holt sich alle Felder, die in dem Serializer definiert wurden, in Zeile 35 und speichert diese in der Variable declared_fields
. declared_fields
sind also die Felder, die im Serializer deklariert wurden. Das Feld wird nun mitunter dazu verwendet, um die Daten zu validieren und zu prüfen, ob diese auch in Model.fields
enthalten sind. Genau da haben wir dann ein Problem, wenn wir zum Beispiel als Client das Organisationsfeld gar nicht haben wollen. Deswegen müssen wir diese Prüfung in den Zeilen 55-57 vornehmen und das Feld declared_fields
manipulieren.
Fazit
Die Implementierung ist recht simpel und funktional. Dadurch wird kurzerhand gewährleistet, dass dokumentiert (nicht hier gezeigt) festgehalten werden kann, welcher Client welche Daten von welchem User erhält und, dass der Client auch wirklich nur die erhält, die er braucht.
Über Feedback und Anregungen freue ich mich immer gerne. ;)
1565 Wörter
2020-05-05 00:00 +0000 (Letzte Aktualisierung: 2021-01-14 16:12 +0000)
1736eb7 @ 2021-01-14