Graph und Benutzer
Dass Sie mit Microsoft Graph nicht nur Exchange und Teams-Daten bearbeiten können sondern auch AzureAD-Objekte manipulieren können, zeige ich auf dieser Seite. Ich gehe auch auf die Besonderheiten der Rufnummern ein, für deren Pflege sie andere Pfade nutzen müssen. Für die UNIX-Nutzer habe ich neben meinen PowerShell-Befehlen auch CURL beschrieben.
Es gibt auch ein Microsoft.Graph PowerShell SDK, welche z.B. den Befehl Get-MGUser/Update-MGUser enthält.
Auf der eigenen Seite Graph Token finden sie Details zum Thema AppRegistrierung, Berechtigungen und Token-Generierung. Ich habe hier dennoch den Ablauf komplett beschrieben.
Achtung: In Verbindung mit ADSync/AADConnect können Sie natürlich nur die Felder schreiben, die nicht durch ADSync blockiert werden
App registrieren
Mein Skript arbeitet nicht als "Benutzer" und schon gar nicht als "GlobalAdmin" sondern werde ich eine "App" im Azure AD registrieren und mit den erforderlichen Berechtigungen versehen. Das geht per Browser im AzureAD und habe ich z.B. auf Get-O365Usage schon beschrieben.
Die Anlage per PowerShell konnte ich bislang nur für die ersten Schritte erreichen
# Modul installieren, wenn noch nicht da, ggfls. update-module Install-Module AzureAD Update-Module AzureAD # Unter PowerShell 7 muss es im compatibilitymode eingebunden werden. # ansonsten kann der Fehler Could not load type 'System.Security.Cryptography.SHA256Cng' from assembly 'System.Core die Anmeldung verhindern Import-Module AzureAD -UseWindowsPowerShell # Anmelden mit einem AdminUser, ggfls mit einer TenantID Connect-AzureAD # Anlegen einer neuen App $app = New-AzureADApplication -DisplayName MSXFAQGraphUser Write-host "AppID: $($App.appid)" # Anlegen von AppCredentials $appkey = New-AzureADApplicationPasswordCredential ` -ObjectId $app.ObjectId ` -CustomKeyIdentifier "Access Key" ` -EndDate (get-date).AddYears(1) Write-host "Apppassword: $($appkey.value)" # Hinweis: Die Rückgabe ist anscheinend Base64 codiert aber eine Umwandlung in ein nutzbares Kennwort ist mir auch noch nicht gelungen # $apppassword = [convert]::FromBase64String($appkey.value) # [system.text.encoding]::default.getString($apppassword)
Die nächsten Schritte habe ich noch nicht per PowerShell umsetzen können und müssen daher per https://portal.azure.com von einem Administrator manuell durchgeführt werden
- ggfls. App Credential anlegen
- App berechtigen
Für die nächsten Schritte brauchen wir:
- Admin Consent erteilen
Als Admin stimmen Sie den Rechten der App zu.
Was genau im Hintergrund noch alles vom Portal angelegt wird, habe ich noch nicht weiter analysiert. Insbesondere ob zur App noch ein AzureADServicePrincipal angelegt wird, z.B. mit:
New-AzureADServicePrincipal -AppId $App.appid
Und auch die Erteilung des Consent ist nicht mal eben gemacht.
az ad app permission admin-consent --id $App.appid
Für die weitere Nutzung muss ich mir die Werte natürlich speichern, z.B. als Variablen:
$LoginUrl="https://login.microsoftonline.com" # Graph API URLs $ResourceUrl="https://graph.microsoft.com" # Ressource API URLs $AppID="12345678-1234-1234-1234-123456789abc" # App ID aus dem Azure Portal $Appkey="xxxxxxxxx" # App Key $TenantName="msxfaq.onmicrosoft.com" # Tenant name
In einem Shell-Skript sieht es ähnlich aus:
LoginUrl="https://login.microsoftonline.com" # Graph API URLs ResourceUrl="https://graph.microsoft.com" # Ressource API URLs AppID="12345678-1234-1234-1234-123456789abc" # App ID aus dem Azure Portal Appkey="xxxxxxxxx" # App Key TenantName="msxfaq.onmicrosoft.com" # Tenant name
- PowerShell Basics: How to Create an
Azure AD App Registration
https://techcommunity.microsoft.com/t5/itops-talk-blog/powershell-basics-how-to-create-an-azure-ad-app-registration/ba-p/811570 - Verwenden der reinen
App-Authentifizierung mit dem Microsoft
Graph PowerShell SDK
https://docs.microsoft.com/de-de/graph/powershell/app-only?tabs=azure-portal - Creating Azure AD Application using
Powershell
https://adatum.no/azure/azure-ad-application-using-powershell - How to assign Permissions to Azure AD
App by using PowerShell?
https://rajanieshkaushikk.com/2019/07/31/how-to-assign-permissions-to-azure-ad-app-by-using-powershell/ - Create Azure AD Application with
Configurations Using PowerShell
https://sibeeshpassion.com/create-azure-ad-application-with-configurations-using-powershell/ - Provide Admin Consent for Azure AD
Applications Programmatically
https://samcogan.com/provide-admin-consent-fora-azure-ad-applications-programmatically/ - Azure Key Vault basic concepts
https://docs.microsoft.com/en-us/azure/key-vault/general/basic-concepts
Access-Token holen
Der nächste Schritt ist dann ,dass ich mir als App ein Zugriffstoken besorgen:
$authresponse=invoke-restmethod ` -body "client_id=$($AppID) &scope=https%3A%2F%2Fgraph.microsoft.com%2F.default &client_secret=$($Appkey) &grant_type=client_credentials &api-version=1.0" ` -uri "$($LoginUrl)/$($tenantName)/oauth2/v2.0/token" $accesstoken= $authresponse.access_token
In einem Shell-Skript ist es mit CURL nicht viel anders (Umbruch zur besseren Lesbarkeit)
TOKEN=`curl -d "client_id=${AppID} &scope=https%3A%2F%2Fgraph.microsoft.com%2F.default &client_secret=${Appkey} &grant_type=client_credentials &api-version=1.0" "${LoginUrl}/${TenantName}/oauth2/v2.0/token"`
Zur Überprüfung können Sie das Token auch einfach, z.B. auf jwt.io eingeben und decodieren lassen. Prüfen Sie hier, ob Sie wirklich alle erforderlichen Berechtigungen haben
Feld lesen
Um zu sehen, ob ich korrekt damit Graph nutzen kann, sollte ich einfach die Properties eines Benutzers lesen. z.B. so:
Invoke-RestMethod ` -Method GET ` -Headers @{"Authorization" = "Bearer $($accesstoken)"} ` -URI "https://graph.microsoft.com/v1.0/users/user%40msxfaq.de"
Die Ausgabe liefert die Default Felder:
- Default Set für "User.Read"
https://docs.microsoft.com/en-us/graph/permissions-reference#remarks-30
@odata.context : https://graph.microsoft.com/v1.0/$metadata#users/$entity businessPhones : {12345} displayName : user givenName : user-givenname jobTitle : user-jobtitle mail : user@msxfaq.de mobilePhone : officeLocation : preferredLanguage : surname : user-Surname userPrincipalName : user@msxfaq.de id : a74255aa-0327-221b-8ab2-f128eff44557
Mit CURL ist es vergleichbar:
curl -i -X GET -H "Accept-Charset: utf-8" -H "Authorization: Bearer $TOKEN" https://graph.microsoft.com/v1.0/users/user%40msxfaq.de HTTP/1.1 200 OK Date: Thu, 17 Jun 2021 11:35:28 GMT Content-Type: application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8 Cache-Control: no-cache Transfer-Encoding: chunked Strict-Transport-Security: max-age=31536000 request-id: cc64718a-2325-48f7-9217-39dcd7c3a71a client-request-id: cc64718a-2325-48f7-9217-39dcd7c3a71a x-ms-ags-diagnostic: {"ServerInfo":{"DataCenter":"West Europe","Slice":"E","Ring":"5","ScaleUnit":"003","RoleInstance":"AM1PEPF0000807C"}} x-ms-resource-unit: 1 OData-Version: 4.0 { "@odata.context":"https://graph.microsoft.com/v1.0/$metadata#users/$entity", "businessPhones":["052511322792"], "displayName":"User1", "givenName":"Alfred","jobTitle":"demo2", "mail":"user@msxfaq.de", "mobilePhone":null, "officeLocation":null, "preferredLanguage":null, "surname":"User", "userPrincipalName":"user@msxfaq.de", "id":"a74255aa-0327-221b-8ab2-f128eff44557"}
Natürlich müssen Sie bei CURL die Ausgabe noch parsen, z.B. mit JQ. PowerShell liefert mehr mehr als "nur" ein Textstring.
Feld ändern
Der nächste Schritt ist das Beschreiben eines Feldes:
Invoke-RestMethod ` -Method PATCH ` -Headers @{"Authorization" = "Bearer $($accesstoken)"} ` -ContentType "application/json" ` -URI "https://graph.microsoft.com/v1.0/users/user%40msxfaq.de" ` -body "{""Jobtitle"": ""User-Jobtitle""}"
Auch Felder, die noch nicht vorhanden sind, werden mit einem "PATCH" aktualisiert. Wenn Sie stattdessen ein PUT versuchen, bekommen Sie folgenden Fehler:
{"error":{"code":"Request_BadRequest", "message":"Specified HTTP method is not allowed for the request target.", "innerError":{"date":"2021-06-17T09:13:53","request-id":"4a411f04-28a3-4508-8dd7-40fe5e47f1c8","client-request-id":"4a411f04-28a3-4508-8dd7-40fe5e47f1c8"}}}
Auch hier noch mal die CURL-Version in einem Shellskript.
### jsonfile #{ # "Jobtitle" : "User-Jobtitle" #} curl -X PATCH -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" -data "{ "Jobtitle" : "User-Jobtitle" }" "https://graph.microsoft.com/v1.0/users/user%40msxfaq.de.de"
Im Prinzip ist es nicht leichter oder schwerer als PowerShell. Die weiteren Beispiele mache ich ohne CURL.
Fehler durch ADSync
Beachten Sie, dass sie auch per Graph keine Felder ändern können, die durch ADSync aus dem lokalen AD gefüllt werden. Hier ein Versuch den Displayname eines per ADSync gepflegten Kontos zu verwalten:
Invoke-RestMethod ` -Method PATCH ` -Headers @{"Authorization" = "Bearer $($accesstoken)"} ` -ContentType "application/json" ` -URI "https://graph.microsoft.com/v1.0/users/user%40msxfaq.de" ` -body "{""displyname"": ""ADUser2b""}"
Die Fehlermeldung ist aussagekräftig:
{"error": {"code": "Request_BadRequest", "message": "Unable to update the specified properties for On-Premises mastered Directory Sync objects or objects currently undergoing migration.", "innerError": {xxx }}}
Falscher Feldname
Ich habe mich auch bei der Angabe eines Feld einmal vertan, um den Fehler zu ermitteln.
{"error": {"code": "Request_BadRequest",
"message": "One or more property values specified are invalid.",
"innerError": {xxxx}}}
Allerdings verrät Graph nicht, welches Feld das Problem ist.
Sonderfall "mobile" etc
Etwas kniffliger wird es mit dem Beschreiben der Felder "businessPhones", "mobilePhone" und "otherMails". Diese Inhalte haben hinsichtlich einer "Multi Faktor Auth" eine besondere Bedeutung und sollten daher nicht so einfach veränderbar sein. Mein erster Versuch schlug daher fehl
Invoke-RestMethod ` -Method PATCH ` -Headers @{"Authorization" = "Bearer $($accesstoken)"} ` -ContentType "application/json" ` -URI "https://graph.microsoft.com/v1.0/users/user%40msxfaq.de" ` -body "{""mobilePhone"": ""12345""}"
Ds hat aber mit folgender Fehlermeldung nicht geklappt:
{"error":{"code":"Authorization_RequestDenied", "message":"Insufficient privileges to complete the operation.", "innerError":{xxx}}}
Das Feld war noch nicht belegt, so dass ich vielleicht ein PUT statt ein POST brauche?
Invoke-RestMethod ` -Method PUT ` -Headers @{"Authorization" = "Bearer $($accesstoken)"} ` -ContentType "application/json" ` -URI "https://graph.microsoft.com/v1.0/users/user%40msxfaq.de" ` -body "{""mobilePhone"": ""12345""}"
Es gab aber einen anderen Fehler:
{"error":{"code":"Request_BadRequest", "message":"Specified HTTP method is not allowed for the request target.", "innerError":{xxx}}}
Beides geht nicht aber ich habe definitiv die Berechtigungen. Eine kurze Recherche liefert dann aber.
For an app with delegated permissions to
read access reviews of a group or app, the signed-in user
must be a member of one of the following administrator roles:
Global Administrator, Security Administrator, Security
Reader or User Administrator. For an app with delegated
permissions to write access reviews of a group or app, the
signed-in user must be a member of one of the following
administrator roles: Global Administrator or User
Administrator.
Quelle:
https://docs.microsoft.com/en-us/graph/permissions-reference#remarks-5
Aber auch dieses Recht hatte ich eigentlich und da es sich bei mir um keinen "RiskyUser" handelt, brauche ich auch nicht "IdentityRiskyUser.ReadWrite.All". Ich habe dann per Graph Explorer (https://developer.microsoft.com/en-us/graph/graph-explorer) mit als Administrator angemeldet und den gleichen PATCH erfolgreich ausführen können.
Im Prinzip kann ich das Feld also schon setzen, wenn ich die erforderlichen Berechtigungen habe.
phoneauthenticationmethod
Das Setzen der Rufnummern beim Benutzer ist nicht automatisch identisch mit der erweiterten Authentifizierung per SMS oder Anruf. Diese Properties werden nicht direkt beim Benutzer hinterlegt sondern nutzen eine eigene URL mit eigenen Berechtigungen. Gerade weil die Rufnummern auch für die Authentifizierung genutzt werden können, gibt es einen eigenen Aufruf mit einem eigenen Recht.
- Create phoneAuthenticationMethod
https://docs.microsoft.com/en-us/graph/api/authentication-post-phonemethods?view=graph-rest-beta&tabs=http - Update phoneAuthenticationMethod
https://docs.microsoft.com/en-us/graph/api/phoneauthenticationmethod-update?view=graph-rest-beta&tabs=http - Manage your authentication phone numbers
and more in new Microsoft Graph beta APIs
https://techcommunity.microsoft.com/t5/azure-active-directory-identity/manage-your-authentication-phone-numbers-and-more-in-new/ba-p/1257359 - Prepopulate phone methods for MFA and SSPR using Graph API
https://janbakker.tech/prepopulate-phone-methods-for-mfa-and-sspr-using-graph-api/ - Pre-configure Authentication Methods for end users in Azure AD
https://identity-man.eu/2020/07/08/pre-configure-authentication-methods-for-end-users-in-azure-ad/
In dem Artikel gibt es noch den Hinweis auf eine verlängerte URL für die drei Rufnummern:
- b6332ec1-7057-4abe-9331-3d72feddfe41 to update the alternateMobile phoneType.
- e37fc753-ff3b-4958-9484-eaa9425c82bc to update the office phoneType.
- 3179e48a-750b-4051-897c-87b9720928f7 to update the mobile phoneType.
Entsprechend habe ich den HTTP-Request erweitert.
Invoke-RestMethod ` -Method PUT ` -Headers @{"Authorization" = "Bearer $($accesstoken)"} ` -ContentType "application/json" ` -URI "https://graph.microsoft.com/beta/users/user%40msxfaq.de/authentication/phoneMethods/3179e48a-750b-4051-897c-87b9720928f7" ` -body "{""phoneNumber"": ""12345"",""phoneType"": ""mobile"",}"
Diesmal bekam ich eine weitere Fehlermeldung, die aber Hoffnung machte:
{"error":{"code":"invalidPhoneNumber", "message":"The provided phone number did not start with a plus (\"+\") character", "innerError":{xxxx}}
Das backen prüft, ob die Telefonnummer zumindest formal richtig ist. Mit einer korrekt formatierten Rufnummer ist der Aufruf erfolgreich gewesen.
Invoke-RestMethod ` -Method PUT ` -Headers @{"Authorization" = "Bearer $($accesstoken)"} ` -ContentType "application/json" ` -URI "https://graph.microsoft.com/beta/users/user%40msxfaq.de/authentication/phoneMethods/3179e48a-750b-4051-897c-87b9720928f7" ` -body "{""phoneType"": ""mobile"",""phoneNumber"": ""+495152304613""}" @odata.context : https://graph.microsoft.com/beta/$metadata#users('user%40msxfaq.de')/authentication/phoneMethods/$en tity id : 3179e48a-750b-4051-897c-87b9720928f7 phoneNumber : +49 5251304613 phoneType : mobile smsSignInState : notAllowedByPolicy
Der Blogbeitrag und insbesondere Codes in den Kommentaren zeigt aber, dass es ohne GUID gehen sollte.
Beim nachfolgenden Abruf der Daten ist dann auch das Feld "Mobile" gefüllt.
Invoke-RestMethod ` -Method POST ` -Headers @{"Authorization" = "Bearer $($token)"} ` -ContentType "application/json" ` -URI "https://graph.microsoft.com/beta/users/user%40msxfaq.de/authentication/phoneMethods/3179e48a-750b-4051-897c-87b9720928f7" ` -body "{""phoneType"": ""mobile"",""phoneNumber"": ""+495152304613""}"
Wenn Sie statt dem PUT ein PATCH verwenden, weil Sie z.B. eine bestehende Nummer ändern wollen, dann kommt eine andere Fehlermeldungp>
{"error":{"code":"UnknownError", "message":"{\"Message\":\"No HTTP resource was found that matches the request URI 'https://mface.windowsazure.com/odata/users('user%40msxfaq.de')/authentication/ phoneMethods('3179e48a-750b-4051-897c-87b9720928f7')'.\", \"MessageDetail\":\"No type was found that matches the controller named 'users'.\"}", "innerError":{xxxx}}}
Interessanterweise löst der Name mface.windowsazure.com sogar auf und ist per HTTPS erreichbar.
C:\>nslookup mface.windowsazure.com Nicht autorisierende Antwort: Name: www.tm.a.prd.aadg.akadns.net Addresses: 20.190.160.73 20.190.160.69 20.190.160.8 20.190.160.4 20.190.160.136 20.190.160.67 20.190.160.134 20.190.160.132 Aliases: mface.windowsazure.com a.privatelink.msidentity.com prda.aadg.msidentity.com
Password
Siehe eigene Seite Graph und Password
Weitere Links
- Graph Token
- Graph PowerShell
- Get started with the Microsoft Graph
PowerShell SDK
https://docs.microsoft.com/en-us/graph/powershell/get-started - Get-MgUser
https://docs.microsoft.com/en-us/powershell/module/microsoft.graph.users/get-mguser?view=graph-powershell-beta - PowerShell Basics: How to Create an
Azure AD App Registration
https://techcommunity.microsoft.com/t5/itops-talk-blog/powershell-basics-how-to-create-an-azure-ad-app-registration/ba-p/811570 - https://janbakker.tech/prepopulate-phone-methods-using-a-custom-connector-in-power-automate/
- https://identity-man.eu/2020/07/08/pre-configure-authentication-methods-for-end-users-in-azure-ad/
- https://docs.microsoft.com/de-de/graph/api/authentication-post-phonemethods?view=graph-rest-beta&tabs=http