I have the use case to query multiple b2c user from the microsoft graph api to get the display name and get the information about the last login. I am aware that the last login is just available via the beta route.
I am using the microsoft graph api beta client and try to get a user via an email address.
My b2c users do not have any mail or otherMail values, only information about the email is placed in the identities list.
var result = await client.Users
.Request()
.Select(e => new
{
e.DisplayName,
e.UserType,
e.OtherMails,
e.UserPrincipalName,
e.Mail,
e.Identities,
e.SignInActivity
}).GetAsync();
This Call returns all user, so I would have to filter in memory which would be bad.
.Filter("identities/any(id:id/issuer eq 'xxx.onmicrosoft.com' and id/issuerAssignedId eq 'superUser#mail.com')")
This filter function returns exactly one specific user, but I wasn't able to query multiple users via a single request. Something like
.Filter("identities/any(id:id/issuer eq 'xxx.onmicrosoft.com' and id/issuerAssignedId eq 'superUser#mail.com') or identities/any(id:id/issuer eq 'xxx.onmicrosoft.com' and id/issuerAssignedId eq 'superUser2#mail.com')")
Return query is to complex an replace the 'eq' with an 'in' returns not supported query, because looks like lambda operators do not support 'in'.
Has someone an idea how to query for e.g. 2 emails addresses with an single request?
I also can provide an rosly pad script were you just have to set your specific values like client id, secret and so on.
#r "nuget:Microsoft.Graph.Auth/1.0.0-preview.7"
#r "nuget:Microsoft.Graph.Beta/4.28.0-preview"
#r "nuget:RestSharp/107.1.1"
#r "nuget:RestRequest/1.2.0"
#r "nuget:Microsoft.Azure.Services.AppAuthentication/1.6.2"
#r "nuget:Azure.Core/1.22.0"
#r "nuget:Azure.Identity/1.5.0"
using Microsoft.Graph;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using System.Net.Http.Headers;
using Azure.Identity;
using System.Linq;
using Azure.Core;
using Microsoft.Graph.Auth;
using Microsoft.Identity.Client;
using System.Net.Http;
var client = await GetGraphApiClient();
var emails = new []{ "email1#example.de","email2#example.de","email3#example.de","email4#example.de","email5#example.de"};
// Build the batch
var batchRequestContent = new BatchRequestContent();
// Using AddBatchRequestStep adds each request as a step
foreach (var element in emails)
{
var userRequest2 = client.Users
.Request()
.Select(e => new
{
e.DisplayName,
e.UserType,
e.OtherMails,
e.UserPrincipalName,
e.Mail,
e.Identities,
e.SignInActivity // just provided in the Microsoft.Graph.Beta package
})
.Filter($"identities/any(id:id/issuer eq ' ' and id/issuerAssignedId eq '{element}')");
batchRequestContent.AddBatchRequestStep(userRequest2);
}
var returnedResponse = await client.Batch.Request().PostAsync(batchRequestContent);
try
{
var user = await returnedResponse
.GetResponsesAsync();
user.Dump();
}
catch (ServiceException ex)
{
Console.WriteLine($"Failed to get user: {ex.Error.Message}");
}
private static async Task<GraphServiceClient> GetGraphApiClient()
{
var clientId = "<Client-Id-Of-your-app-with-graph-access>";
var secret = "<Client-Secret-Of-your-app-with-graph-access>";
var tenant = "<tenant-id>";
string[] scopes = new string[] { "AuditLog.Read.All", "User.Read.All" };
IConfidentialClientApplication confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithTenantId(tenant)
.WithClientSecret(secret)
.Build();
ClientCredentialProvider authProvider = new ClientCredentialProvider(confidentialClientApplication);
var serviceToken = await authProvider.ClientApplication.AcquireTokenForClient(new string[] { "https://graph.microsoft.com/.default" }).ExecuteAsync();
return new GraphServiceClient(authProvider);
}
Thanks Danstan,
with the a batch request it works to get with single request up to 20 accounts at once. https://learn.microsoft.com/en-us/graph/sdks/batch-requests?tabs=csharp#simple-batching-example
The API still limited it to 20 request in a single batch request. 'Code: MaximumValueExceeded
Message: Number of batch request steps exceeds the maximum value of 20.'
This makes it possible to query all the data by view requests.
Related
I am trying to upload file on onedrive by using microsoft graph onedrive api.
I am using the method for authentication
Client credentials provider
https://learn.microsoft.com/en-us/graph/sdks/choose-authentication-providers?tabs=CS#client-credentials-provider
Like:
// /.default scope, and preconfigure your permissions on the
// app registration in Azure. An administrator must grant consent
// to those permissions beforehand.
var scopes = new[] { "https://graph.microsoft.com/.default" };
// Multi-tenant apps can use "common",
// single-tenant apps must use the tenant ID from the Azure portal
var tenantId = "my-tenantid";
// Values from app registration
var clientId = "YOUR_CLIENT_ID";
var clientSecret = "YOUR_CLIENT_SECRET";
// using Azure.Identity;
var options = new TokenCredentialOptions
{
AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
};
// https://learn.microsoft.com/dotnet/api/azure.identity.clientsecretcredential
var clientSecretCredential = new ClientSecretCredential(
tenantId, clientId, clientSecret, options);
var graphClient = new GraphServiceClient(clientSecretCredential, scopes);
HttpPostedFileBase file = Request.Files;[0];
int fileSize = file.ContentLength;
string fileName = file.FileName;
string mimeType = file.ContentType;
Stream fileContent = file.InputStream;
var res = await graphClient.Me.Drive.Root.ItemWithPath(fileName).Content
.Request()
.PutAsync<DriveItem>(fileContent);
After executing this code then it gives an error in response.
Message: /me request is only valid with delegated authentication flow.
Inner error:
AdditionalData:
date: 2021-12-29T05:30:08
request-id: b51e50ea-4a62-4dc7-b8d2-b26d75268cdc
client-request-id: b51e50ea-4a62-4dc7-b8d2-b26d75268cdc
ClientRequestId: b51e50ea-4a62-4dc7-b8d2-b26d75268cdc
Client credential flow will generate the token on behalf the app itself, so in this scenario, users don't need to sign in first to generate the token stand for the user and then call the api. And because of the design,when you used Me in the graph SDK, your code/app don't know who is Me so it can't work. You should know the user_id first and use /users/{id | userPrincipalName} instead of /Me, in the SDK, that is graphClient.Users["your_user_id"] instead of graphClient.Me
In your scenario, there're 2 solutions, one way is using delegated authentication flow like what you said in your title, another way is get the user id before calling the graph api so that you can use Users["id"] but not Me
===================== Update=========================
I haven't finished the code yet but I found the correct solution now.
Firstly, we can upload file to one drive by this api, you may check the screenshot if this is one drive or sharepoint:
https://graph.microsoft.com/v1.0/users/user_id/drive/items/root:/testupload2.txt:/content
If it is, then the next is easy, using the code below to get an access token and send http request to calling the api:
var scopes = new[] { "https://graph.microsoft.com/.default" };
var tenantId = "tenant_name.onmicrosoft.com";
var clientId = "your_azuread_clientid";
var clientSecret = "corresponding_client_secret";
var clientSecretCredential = new ClientSecretCredential(
tenantId, clientId, clientSecret);
var tokenRequestContext = new TokenRequestContext(scopes);
var token = clientSecretCredential.GetTokenAsync(tokenRequestContext).Result.Token;
I know it's complex because the api is not the same as this one which has SDK sample, but I think it also deserves to try if they are similar.
I need to get all users and their roles(including the roles names and the roles values) in an Azure Application.
What I've done is to retrieve all users and include the appRoleAssignments. The problem is that in the array of appRoleAssignment objects there is only the appRoleId for each role.
Since it would take a lot of http calls to first get all users and then for each appRoleAssignment in each user to retrieve the needed data for the roles by appRoleAssignment Id.
How can I optimize the retrieval of all users and their roles from Azure ?
I think it's possible to use batching and combine the logic for getting all users and their roles(including role name and role value) in to a single API Call, but not sure how to do it.
This is what I have right now:
var users = await graphClient.Users
.Request()
.Expand("appRoleAssignments")
.GetAsync();
As far as I know, there is no way to get user role assignment records with app role names together by one API calling.
I can understand that if you want to get the information above for all of your users, that will be a lot of requests and leads to bad performance. I think you can get all app role information in your directory and get all role assignment records, match them one by one by using AppRoleId.I write a simple console app for you, just try the code below:
using Microsoft.Graph;
using Microsoft.Graph.Auth;
using Microsoft.Identity.Client;
using System;
using System.Collections.Generic;
namespace graphsdktest
{
class Program
{
static void Main(string[] args)
{
var clientId = "";
var clientSecret = "";
var tenantID = "";
IConfidentialClientApplication confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithTenantId(tenantID)
.WithClientSecret(clientSecret)
.Build();
ClientCredentialProvider authenticationProvider = new ClientCredentialProvider(confidentialClientApplication);
var graphClient = new GraphServiceClient(authenticationProvider);
var roleResult = graphClient.ServicePrincipals.Request().Select(app => new { app.AppRoles }).Top(999).GetAsync().GetAwaiter().GetResult();
var appRoleList = new List<AppRole>();
var userRoleAssigments = new List<User>();
var RolePageIterator = PageIterator<ServicePrincipal>
.CreatePageIterator(graphClient, roleResult, (app) =>
{
if (app.AppRoles.GetEnumerator().MoveNext())
{
foreach (var appRole in app.AppRoles)
{
appRoleList.Add(appRole);
}
}
return true;
});
//get all app role information
RolePageIterator.IterateAsync().GetAwaiter().GetResult();
var roleAssigmentResult = graphClient.Users.Request().Expand("appRoleAssignments").GetAsync().GetAwaiter().GetResult();
var RoleAssigmentPageIterator = PageIterator<User>
.CreatePageIterator(graphClient, roleAssigmentResult, (user) =>
{
userRoleAssigments.Add(user);
return true;
});
//get all role assigment records
RoleAssigmentPageIterator.IterateAsync().GetAwaiter().GetResult();
foreach (var user in userRoleAssigments)
{
if (user.AppRoleAssignments.Count > 0)
{
Console.WriteLine("app role assigment of user :" + user.DisplayName);
foreach (var ras in user.AppRoleAssignments)
{
var roleName = (ras.AppRoleId.ToString().Equals("00000000-0000-0000-0000-000000000000") ? "Default Access" : appRoleList.Find(item => item.Id == ras.AppRoleId).DisplayName);
Console.WriteLine("roleID:" + ras.AppRoleId + " appName:" + ras.ResourceDisplayName + " roleName:" + roleName);
}
}
}
}
}
}
Result:
Per my test, the whole request spends about 9 seconds.
Hi I'm trying to write a simple console app which I intend to make into a batch file and get a list of external users who were invited by email have and now they have guest accounts in our Azure tenant and they have redeemed the url that was sent to them in email. When they redeem, their extenalUserState sets to "Accepted". I want to find which ones have that status.
I was told that I have to point to beta version of the API and not v.1.0 of the graph endpoint.
I have the following rudimentary code I have written looking at various examples I could find on GitHub/MS documentation for API etc.
using Microsoft.Graph;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
namespace CreateAzureADUser
{
class Program
{
static string TenantDomain;
static string TenantId;
static string ClientId;
static string ClientSecret;
static void Main(string[] args)
{
GetUsers();
//Console.WriteLine("------------------------------------------\n\n");
//GetGroupsAndMembers();
//CreateAzureADUserNow();
}
private static void GetUsers()
{
var graphServiceClient = CreateGraphServiceClient();
var users = graphServiceClient.Users.Request().Filter("userType eq 'Guest' and startswith(mail,'phs')")
.Select("id,mail,OnPremisesExtensionAttributes,userType,displayName,externalUserState")
.GetAsync()
.Result;
Console.WriteLine("Users found: {0}", users.Count);
Console.WriteLine();
foreach (var item in users)
{
Console.WriteLine("displayName: {3} \nuser id: {0} \nuser email: {1} \nExtensionAttribute8: {2}\n", item.Id, item.Mail, item.OnPremisesExtensionAttributes.ExtensionAttribute8, item.DisplayName);
}
}
public static GraphServiceClient CreateGraphServiceClient()
{
TenantDomain = "mycompanytenant.onmicrosoft.com";
TenantId = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
ClientId = "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy";
ClientSecret = "zzzzzzzzzzzz";
var clientCredential = new ClientCredential(ClientId, ClientSecret);
var authenticationContext = new AuthenticationContext($"https://login.microsoftonline.com/mycompanytenant.onmicrosoft.com");
var authenticationResult = authenticationContext.AcquireTokenAsync("https://graph.microsoft.com", clientCredential).Result;
var delegateAuthProvider = new DelegateAuthenticationProvider((requestMessage) =>
{
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", authenticationResult.AccessToken);
return Task.FromResult(0);
});
// Use this for v.1.0 endpoint
//return new GraphServiceClient(delegateAuthProvider);
// Use this for connecting to beta endpoint
return new GraphServiceClient("https://graph.microsoft.com/beta", delegateAuthProvider);
}
}
}
When I run through the debugger, I do not see "ExternalUserState" as an attribute on the users that are returned.
How to access ExternalUserState attribute on the guest user object?
You're using the SDK so you're using Graph v1.0, not the Beta. The SDKs are all generated from v1.0 metadata so beta properties and methods simply do not exist in the models.
From time to time there is a beta build pushed out to GitHub but it is generally a few versions behind. Currently, the latest beta SDK available seems to be v1.12.0 (for reference, the current SDK is v1.15).
I am trying to connect to my SQL server in azure and list the dbs from a .net application, but I keep getting
ForbiddenError: The server failed to authenticate the request. Verify that the certificate is valid and is associated with this subscription.
even though i am trying to use the Sql Management client with TokenCloudCredentials.
var authContext = new AuthenticationContext(authority);
var clientCredential = new ClientCredential(clientId, appKey);
var result = authContext.AcquireTokenAsync(resource, clientCredential).Result;
var credentials = new Microsoft.Azure.TokenCloudCredentials(subscriptionId, result.AccessToken);
var client = new SqlManagementClient(credentials);
try
{
var servers = await client.Servers.ListAsync();
}
catch (CloudException c)
{
Console.WriteLine(c.Message);
throw;
}
The AD application have permissions to access the resource group and the Azure Management API.
Any ideas why it keeps complaining about a certificate, while using token?
EDIT: I managed to do it using the "new" fluent management API. You need to create an AD application associated with the subscription and have access to the resource group. Then just create credentials and initialize the fluent API.
using Microsoft.Azure.Management.Fluent;
using Microsoft.Azure.Management.ResourceManager.Fluent;
using Microsoft.Azure.Management.ResourceManager.Fluent.Authentication;
using Microsoft.Azure.Management.ResourceManager.Fluent.Core;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AzureManagement
{
public class Program
{
public static void Main(string[] args)
{
var azureCredentials = new AzureCredentials(new
ServicePrincipalLoginInformation
{
ClientId = "clientId",
ClientSecret = "clientSecret="
}, "tenantId", AzureEnvironment.AzureGlobalCloud);
var _azure = Azure
.Configure()
.WithLogLevel(HttpLoggingDelegatingHandler.Level.Basic)
.Authenticate(azureCredentials)
.WithSubscription("subscriptionId");
var sql = _azure.SqlServers.List().ToList();
foreach (var s in sql)
{
var dbs = s.Databases.List().ToList();
}
Console.ReadLine();
}
}
}
I am not sure if this is supported. Please create a support case for this problem.
The code works like this.
You have to assign a role - in my use case I assigned the contributor role to the App registration, on to the Ressource Group
I would like to have some kind of people picker functionality with auto complete features in my asp.net mvc 5 app to search for a user in a specific Azure AD group. It's a demo "todo app" that allows to assign a todo to a user that is member of a the group.
I tried with both the Graph API directly and the Azure Graph Client library but I don't seem to find a way to achieve what I want. The graph api allows to get the members of a group but adding filter "startswith" fails as when adding the filter the api returns only directory object which don't include for example DisplayName property... the client library doesn't help much either except for the batch functionality which offers a way but with a lot of overhead... I then would have to get a filtered resultset of user regardless of group membership (using User List stuff in the api), all members of the group and then fish out using Linq the correct result set.... would work fine for dev/testing but in production with a couple of hundred users this would be insane...
Any ideas or suggestions would be much appreciated. Thanks!
EDIT
Below my code that is called from client side Javascript to search for user;
AccessGroupId is the Azure AD group used to authorize users. Only
members of this group can access the web app which I handle in custom
OWin Middleware
The method is intented to be used to find a user in that group
Code works fine as below only there is no filtering applied which is the intentaion with the input parameter pre (which comes from a textbox in the ui). I get all the members of the access group.
public async Task<JsonResult> FindUser(string pre)
{
string AccessGroupId = ConfigurationManager.AppSettings["AccessGroupId"];
AuthenticationContext authCtx = new AuthenticationContext(String.Format(CultureInfo.InvariantCulture, "{0}/{1}", SecurityConfiguration.LoginUrl, SecurityConfiguration.Tenant));
ClientCredential credential = new ClientCredential(SecurityConfiguration.ClientId, SecurityConfiguration.AppKey);
AuthenticationResult assertionCredential = await authCtx.AcquireTokenAsync(SecurityConfiguration.GraphUrl, credential);
var accessToken = assertionCredential.AccessToken;
var graphUrl = string.Format("https://graph.windows.net/mytenant.onmicrosoft.com/groups/{0}/members?api-version=2013-11-08, AccessGroupId );
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, graphUrl);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
HttpResponseMessage response = await client.SendAsync(request);
String responseString = await response.Content.ReadAsStringAsync();
JObject jsonReponse = JObject.Parse(responseString);
var l = from r in jsonReponse["value"].Children()
select new
{
UserObjectId = r["objectId"].ToString(),
UserPrincipalName = r["userPrincipalName"].ToString(),
DisplayName = r["displayName"].ToString()
};
//users = Newtonsoft.Json.JsonConvert.DeserializeObject<List<User>>(responseString);
return Json(l, JsonRequestBehavior.AllowGet);
}
When I add a filter to the same api call instead of returning the members (users, groups and/or contacts), it returns directory objects (that doesn't have displayName) which are not really usefull in the above code, unless I would query the api again (in batch) to retrieve the users displayname but that looks like a lot of overhead to me.
var graphUrl = string.Format("https://graph.windows.net/mytenant.onmicrosoft.com/groups/{0}/members?api-version=2013-11-08&$filter=startswith(displayName,'{1}')", AccessGroupId, pre);
I'd highlight two possible approaches:
Execute requests to Graph API using a custom JS library.
You'd need still need to care for accesstokens and have a look at ADAL.js
A sample app (not finalized as of this writing) available at:
AzureADSamples WebApp-GroupClaims-DotNet
Have a look at AadPickerLibrary.js
Try using ActiveDirectoryClient
It would look something like:
public async Task<JsonResult> FindUser(string pre) {
ActiveDirectoryClient client = AADHelper.GetActiveDirectoryClient();
IPagedCollection<IUser> pagedCollection = await client.Users.Where(u => u.UserPrincipalName.StartsWith(pre, StringComparison.CurrentCultureIgnoreCase)).ExecuteAsync();
if (pagedCollection != null)
{
do
{
List<IUser> usersList = pagedCollection.CurrentPage.ToList();
foreach (IUser user in usersList)
{
userList.Add((User)user);
}
pagedCollection = await pagedCollection.GetNextPageAsync();
} while (pagedCollection != null);
}
return Json(userList, JsonRequestBehavior.AllowGet);
}
More detailed sample is available at:
AzureADSamples WebApp-GraphAPI-DotNet