-
Notifications
You must be signed in to change notification settings - Fork 0
0.1
Marius Kehl
Die Abfrage geschieht über die öffentliche Graph-API-REST-Schnittstelle von Microsoft, die per HTTPS angesprochen wird. Dazu ist eine Authentifizierung gegenüber Microsoft nötig. Das Modul ist in Delphi/Pascal geschrieben.
Zur Verwendung kamen Delphi-Systemeigene Pakete wie beispielsweise der HTTP-Client. Des Weiteren habe ich einen HTTP-Server-Socket aus einem Paket namens INDY (Internet Direct) für das OAuth verfahren genutzt. Ein Ziel meinerseits war es, den Code sprachlich gesehen möglichst modular und leicht erweiterbar, zeitgleich aber nicht die Effizienz aus den Augen zu verlieren.
Die Authentifizierung funktioniert mit einem “OAuth 2.0 authorization code grant flow”. Das bedeutet, dass der Client eine microsoft-login-page öffnet, über die der User sich einloggen kann.
Beispiel einer URL zur Login-page:
https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize
?client_id=11111111-1111-1111-1111-111111111111
&response_type=code
&redirect_uri=http%3A%2F%2Flocalhost%2Fmyapp%2F
&response_mode=query
&scope=offline_access%20user.read%20mail.read
&state=12345
Parameter | Description |
---|---|
{tenant} | Dies muss mit der Tenant-ID ersetzt werden. |
client_id | Die Client-ID, die im der "app" im registration portal zugewisen wurde. |
response_type | Muss code sein, um den authorization code flow zu nutzen (was wir machen wollen)
|
redirect_uri | Die/eine der redirect-uri/-s, die man im registration portal gesetzt hat. |
scope | Dieser Query-Parameter ist eine Space( )-seperated Liste aus allen scopes die die "App" benötigt. |
response_mode | Kann query oder form_post sein. Legt die methode fest, die genutzt werden soll um den code an den client zu übermitteln. In meinem fall habe ich immer query benutzt. (query =GET-req mit query parametern, form_post =POST-req mit form-data als payload) |
state | Ein frei wählbarer string, solle definitiv randomness beinhalten, um vor gewissen attacken zu schützen. |
Mehr dazu: hier
Dann leitet (redirect) microsoft den browser an eine "redirect" uri. Diese Redirect Uri kann von Systemadministartoren im azure portal selbständig festgelegt werden. In meinem fall habe ich eine redirect url auf http://localhost:8080/<some other stuff :) >
festgelegt - mehr dazu später.
Im redirect (bzw. der redirect URI), der von microsoft geschickt wird, befindet sich ein authorisation code
. Dieser kann später zur weiteren Autorisierung genutzt werden.
Beispiel einer redirect-URI:
GET https://localhost/myapp/?
code=M0ab92efe-b6fd-df08-87dc-2c6500a7f84d
&state=12345
Parameter | Description |
---|---|
code | Der code der für die weitere auth verwendet wird |
state | Sollte der gleiche wert sein, der oben gesetzt wurde. Wenn nicht, dann ist man vermutlich einer Attacke zum Opfer gefallen. Also Lohnt es sich, diesen Parameter zu testen :) |
Für den Redirect ist in meinem Fall kein TLS nötig, da der Redirect auf localhost
zeigt, also vom weg zwischen Browser und der Anwendung (also dem HTTP-Socket) niemand ist, der die Auth-daten abhören könnte. (zumindest sollte das der Fall sein. Nur ein Virus, der lokal installiert ist, könnte den
code-flow
abhören.)
Wenn man jetzt auf eine Web-App übergeht, Der Redirect also auf einen Server außerhalb des internal-loopback
des Endgeräts zeigt, dann sollte der Redirect IMMER mit TLS abgesichert sein.
Nun haben wir den code
.
Wir stellen einen Request an Mircosoft:
POST /{tenant}/oauth2/v2.0/token HTTP/1.1
Host: https://login.microsoftonline.com
Content-Type: application/x-www-form-urlencoded
client_id=11111111-1111-1111-1111-111111111111
&scope=user.read%20mail.read
&code=OAAABAAAAiL9Kn2Z27UubvWFPbm0gLWQJVzCTE9UkP3pSx1aXxUjq3n8b2JRLk4OxVXr...
&redirect_uri=http%3A%2F%2Flocalhost%2Fmyapp%2F
&grant_type=authorization_code
Parameter | Description |
---|---|
tenant | Siehe oben. |
client_id | Siehe oben. |
grant_type | Muss authorization_code für den authorization code flow sein. |
scope | Siehe oben. |
code | Den code den wir eben bekommen haben |
redirect_uri | Siehe oben. Es gibt keinen weiteren Redirect, dient nur zur weiteren Absicherung :) |
Dieser Request gibt uns folgende Response-Payload:
{
"token_type": "Bearer",
"scope": "user.read%20Fmail.read",
"expires_in": 3600,
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik5HVEZ2ZEstZnl0aEV1Q...",
"refresh_token": "AwABAAAAvPM1KaPlrEqdFSBzjqfTGAMxZGUTdM0t4B4..."
}
Parameter | Description |
---|---|
token_type | Zitat: Indicates the token type value. The only type that Azure AD supports is Bearer. |
scope | Nochmal der Scope. Sollte der gleiche sein wie oben. |
expires_in | Die Dauer, die der access_token gültig ist. (in Sekunden) |
access_token | Dieser Token wird benutzt, um Requests an die Graph-API zu stellen. |
refresh_token | Dieser Token hat typischerweise eine Gültigkeit von 90 Tagen. Mit ihm kann ein neuer acces_token sowie ein neuer refresh_token angefordert werden. Mehr dazu später. |
Die Userdaten werden mit einem GET-Request abgefragt:
GET /v1.0/{tenant}/users HTTP/1.1
Host: https://graph.microsoft.com
Accept: application/json
ConsistencyLevel: eventual
$filter=onPremisesSamAccountName eq ''{SysLogin}''
&$select=businessPhones,displayName,faxNumber,givenName,mail,mobilePhone,surname
&$count=true
Parameter | Description |
---|---|
tenant | Wieder die TenantID (in dem Fall von der MAC). |
SysLogin | Der Login-Name, der im LMPS hinterlegt ist. |
Die Parameter $filter
und $select
sind nach dem OData Standart definiert. OData ist, in eigenen worten "so etwas wie SQL für rest-Schnittstellen".
Mit diesem Parameter wird aus einer Ressourcen-Collection, in dem Fall eine Liste an Usern nach einem oder mehreren Feldern/Parametern gefiltert. Mehr dazu: hier und hier
Dieser Parameter nimmt Komma(,
)-Seperierte Feld-/Parameternamen entgegen. Dies bestimmt, welche daten die Graph-Schnittstelle bei diesem Request zurückgibt.
{
"displayName": "Marius Kehl",
"businessPhones": ["+49123459876"],
"givenName": "Marius",
"surname": "Kehl",
"mail": "[email protected]",
"mobilePhone": "+49123098456",
"faxNumber": "+49123456789"
}
Die Felder sollten an sich selbsterklärend sein, Dokumentation zu den einzelnen Feldern befindet sich: hier.
Diese Daten parse ich dann und packe sie in ein für das LMPS lesbares Format (xml).
Grundsätzlich ist das Prinzip recht einfach. Es gibt fünf Bereiche, in die alle HTTP-Status-Codes eingeteilt werden können. Vier davon liste ich hier auf:
Bereich | Description |
---|---|
200 - 299 | Request war erfolgreich, ohne Fehler. |
300 - 399 | Besondere Ereignisse wie Redirects, etc. |
400 - 499 | Der Client, also der Sender des Requests hat etwas "Falsch gemacht". Authorisierung nicht gültig/vergessen, eine Ressource angefragt die es nicht gibt, etc. |
500 - 599 | Der Host hat ein Problem/Fehler. InternalServerError, BadGateway, ServiceUnavailable, etc. |
Nur wenn ein Response einen Statuscode außerhalb der 200-range hat, dann wird versucht eine Fehlermeldung zu parsen, anschließend wird eine Callback Funktion aufgerufen, die beim initialisieren des Auth-Objekts festgelegt werden kann.
Mit einem Refresh-Token kann ein neues Set aus Acces- und Refresh-Tokens angefragt werden. Ein Request dazu sieht Folgendermaßen aus:
POST /{tenant}/oauth2/v2.0/token HTTP/1.1
Host: https://login.microsoftonline.com
Content-Type: application/x-www-form-urlencoded
client_id=11111111-1111-1111-1111-111111111111
&scope=user.read%20mail.read
&refresh_token=OAAABAAAAiL9Kn2Z27UubvWFPbm0gLWQJVzCTE9UkP3pSx1aXxUjq...
&grant_type=refresh_token
Parameter | Description |
---|---|
tenant | Siehe oben. |
client_id | Siehe oben. |
grant_type | Muss refresh_token sein. |
refresh_token | Der Refresh-Token, den wir oben bekommen haben. |
Der Response ist Identisch zum Response von Einen Token bekommen.