Die Authentifizierung am Windows Azure Active Directory haben wir sowohl bereits mit dem Tooling als auch via Code gesehen. Heute geht es darum, wie man Daten lesen und sogar auch schreiben kann.
Windows Azure Active Directory?
Wer keine Ahnung von der Begrifflichkeit hat, dem empfehle ich ebenfalls die beiden vorangegangen Blogposts oder schaut einfach auf dieser Azure Info Seite.
Welche Ressourcen befinden sich da?
Im Azure AD befinden sich folgende Entitäten:
- Users
- Groups
- Contacts
- Roles
Zugriff auf das Directory oder auf den “Directory Graph”
Trotz des “Active Directory” im Namen und den bekannten Entitäten hat dieser Azure Dienst wenig Gemeinsamkeiten zu einem herkömmlichen Active Directory. Der vermutlich grösste Unterschied: Es gibt kein LDAP Zugriff – es ist eine REST API. Die Daten liegen im so genannten Windows Azure Active Directory Graph. Anwendungen die jetzt gegen ein Active Directory funktionieren müssen umgeschrieben werden für Windows Azure AD.
REST … oder auch OData
Als Endpunkt gibt es eine REST bzw. eine OData API. Ob OData der eleganteste Weg ist steht auf einem anderen Papier – allerdings arbeitet das Team daran weitere OData Features mit zu unterstützen.
Grundsätzlich gibt es zwei Arten von Queries:
“Common Queries": Eine recht einfache API mit der man alle Daten aus dem AD rausholen kann.
“Differential Queries”: Diese API wird interessant wenn man grosse Datenmengen zwischen der eigenen Anwendung und dem Azure AD synchronisieren möchte. Über diese API kann man nur die Änderungen an einer Ressource zwischen zwei Requests herausbekommen.
Aktuell: Kein nettes NuGet Package
Jetzt weg von der Theorie, hin zur Praxis. Aktuell gibt es leider kein NuGet Package oder ähnliches was die Arbeit mit der Graph API komplett vereinfacht. Zwar gibt es ein altes NuGet Package, allerdings stammt dies auch nur aus Sample Code und zudem ist die API entsprechend schon recht alt und untersützt nicht alle Funktion die es heute gibt (Gruppen-Management z.B.)
Alternative: Hand Made Requests
Da es eine REST API ist, benötigt man natürlich nur einen HttpClient und man kann dagegen entwickeln. Die MSDN gibt auch genügend Beispiele wie der Request aussehen kann:
GET https://graph.windows.net/contoso.onmicrosoft.com/users/[email protected]?api-version=2013-04-05 HTTP/1.1 Authorization: Bearer eyJ0eX ... FWSXfwtQ Content-Type: application/json Host: graph.windows.net
Da sich die API allerdings recht schnell weiter entwickelt und ich mir die “OData”-like Queries nicht von Hand zusammenschrauben möchte, gibt es noch einen Weg. Dieser scheint wohl mehr oder minder auch der “empfohlene” Weg zu sein.
Zum Code: Wir nutzen Sample Code – uhh oh…
Das Azure Graph Team publiziert auf dieser MSDN Seite verschiedene Beispiele, darunter auch eine “Graph API Helper Library”. Im Einsatz kommt diese Library auch im .NET Sample.
Das Sample ist eine MVC Anwendung, welche ein CRUD auf User und Groups abbildet. Der GraphHelper enthält die generierte “DataService” aus dem OData-Endpunkt und einige Utilities drum herum – so kann man recht einfach sich gegen die Graph API authentifizieren und Requests abschicken.
Das Sample kommt mit voreingestellten Settings – allerdings ist die App nur “lesend” auf das AD berechtigt.
Mal ein paar Screenshots von der Applikation:
Generierter Code… uh…
Der “generierte” Code stammt aus dem OData Endpunkt und ist alles andere als “schön”. Dazu gibt es noch eine “Partial” Klasse, da die generierte Klasse die eigentlichen Entitäten nicht erkennt.
Der eigentliche Code ist nicht sehr komplex, aber vom Syntax nicht ganz so sexy.
So holt man z.B. alle Gruppen ab:
// // GET: /Group/ // Get: /Group?$skiptoken=xxx // Get: /Group?$filter=DisplayName eq 'xxxx' public ActionResult Index(string displayName, string skipToken) { QueryOperationResponseresponse; var groups = DirectoryService.groups; // If a filter query for displayName is submitted, we throw away previous results we were paging. if (displayName != null) { ViewBag.CurrentFilter = displayName; // Linq query for filter for DisplayName property. groups = (DataServiceQuery )(groups.Where(group => group.displayName.Equals(displayName))); response = groups.Execute() as QueryOperationResponse ; } else { // Handle the case for first request vs paged request. if (skipToken == null) { response = groups.Execute() as QueryOperationResponse ; } else { response = DirectoryService.Execute (new Uri(skipToken)) as QueryOperationResponse ; } } List groupList = response.ToList(); // Handle the SkipToken if present in the response. if (response.GetContinuation() != null) { ViewBag.ContinuationToken = response.GetContinuation().NextLinkUri; } return View(groupList); } </pre> Empfehlung: Sample anschauen und nicht direkt auf die generierten Klassen verweisen
Das Sample enthält die “Common Queries” plus CRUD-Operations und bietet einen einfachen Einstieg. Ich würde aber davon abraten direkt die Entitäten daraus zu nutzen, da die generierten Klassen auch “Unschönheiten” wie z.B. kleine Property-Namen mitbringen.
Weitere Informationen findet man in der MSDN auf der Graph API Seite.