Get count of users while reading from AAD [Microsoft Graph] - azure-active-directory

I was following this documentation https://learn.microsoft.com/en-us/graph/aad-advanced-queries?tabs=csharp to run some graph query on AAD objects as follows:
await _graphServiceClient
.Users.Request()
.Request(new Option[] { new QueryOption("$count", "true") })
.Header("ConsistencyLevel", "eventual")
.Filter("endsWith(mail,'tenant.com')")
.GetAsync();
I see the following error:
What am I missing and how do I resolve the same?

await _graphServiceClient
.Users.Request(new Option[] { new QueryOption("$count", "true") })
.Header("ConsistencyLevel", "eventual")
.Filter("endsWith(mail,'tenant.com')")
.GetAsync();

I tried to reproduce the same in my environment and got the below results:
To retrieve the list of users with Mail-ID, I executed the below query in Microsoft Graph Explorer:
GET https://graph.microsoft.com/v1.0/users?$count=true&$filter=endsWith(mail,'#tenant.com')
ConsistencyLevel:eventual
Response:
You can make use of the below sample CSharp code:
GraphServiceClient graphClient = new GraphServiceClient( authProvider );
var queryOptions = new List<QueryOption>()
{
new QueryOption("$count", "true")
};
var users = await graphClient.Users
.Request( queryOptions )
.Header("ConsistencyLevel","eventual")
.Filter("endsWith(mail,'#tenant.com')")
.GetAsync();

Related

Get Members from Delta Query on Groups

How do I return only Members from the delta API for group (https://learn.microsoft.com/en-us/graph/delta-query-overview?tabs=http)
Adding .Members & .Select() are not supported
await _graphServiceClient
.Groups
.Members
.Delta()
.Request()
.Filter($"id eq '{groupId}'")
.GetAsync();
On trying this:
var queryOptions = new List<QueryOption>()
{
new QueryOption("select", "members")
};
await _graphServiceClient
.Groups
.Delta()
.Request(queryOptions)
.Filter($"id eq '{groupId}'")
.GetAsync();
I see this error:
Message: Unrecognized query argument specified: 'select'. What am I missing?
Use Select method after Request.
var delta = await _graphServiceClient.Groups
.Delta()
.Request()
.Select("members")
.Filter($"id eq '{groupId}'")
.GetAsync();
in your example with QueryOption you are missing $ before select
var queryOptions = new List<QueryOption>()
{
new QueryOption("$select", "members")
};
await _graphServiceClient
.Groups
.Delta()
.Request(queryOptions)
.Filter($"id eq '{groupId}'")
.GetAsync();
You can try with below code
var user = await graphClient.Groups["{group-id}"].Members
.Request( queryOptions )
.Header("ConsistencyLevel","eventual")
.Select("displayname,id")
.GetAsync();
You can refer docs for more info - https://learn.microsoft.com/en-us/graph/api/group-list-members?view=graph-rest-beta&tabs=csharp#example-4-use-search-and-odata-cast-to-get-user-membership-in-groups-with-display-names-that-contain-the-letters-pr-including-a-count-of-returned-objects
Hope this helps
Thanks

call graph as part of authentication to add claims .net 4.5

i think the correct place is in SecurityTokenValidated but account is always null. i dont know how to set up the graphclient here?
SecurityTokenValidated = async (x) =>
{
IConfidentialClientApplication clientApp2 = MsalAppBuilder.BuildConfidentialClientApplication();
AuthenticationResult result2 = null;
var account = await clientApp2.GetAccountAsync(ClaimsPrincipal.Current.GetMsalAccountId());
string[] scopes = { "User.Read" };
// try to get an already cached token
result2 = await clientApp2.AcquireTokenSilent(scopes, account).ExecuteAsync().ConfigureAwait(false);
var graphClient = new GraphServiceClient(
new DelegateAuthenticationProvider(async (request) =>
{
//var token = await tokenAcquisition
// .GetAccessTokenForUserAsync(GraphConstants.Scopes, user: context.Principal);
var token = result2.AccessToken;
request.Headers.Authorization =
new AuthenticationHeaderValue("Bearer", token);
})
);
var user = await graphClient.Me.Request()
.Select(u => new
{
u.DisplayName,
u.Mail,
u.UserPrincipalName
})
.GetAsync();
var identity = x.AuthenticationTicket.Identity;
identity.AddClaim(new Claim(ClaimTypes.Role, "test"));
}
Please refer to this sample: https://learn.microsoft.com/en-us/samples/azure-samples/active-directory-dotnet-admin-restricted-scopes-v2/active-directory-dotnet-admin-restricted-scopes-v2/
You could follow this sample to get access token with GetGraphAccessToken() and make sure the signed-in user is a user account in your Azure AD tenant. Last thing is using Chrome in incognito mode this helps ensure that the session cookie does not get in the way by automatically logging you in and bypassing authentication.
This sample will not work with a Microsoft account (formerly Windows
Live account). Therefore, if you signed in to the Azure portal with a
Microsoft account and have never created a user account in your
directory before, you need to do that now. You need to have at least
one account which is a directory administrator to test the features
which require an administrator to consent.
var graphserviceClient = new GraphServiceClient(
new DelegateAuthenticationProvider(
(requestMessage) =>
{
// Get a token for the Microsoft Graph
var access_token = await GetGraphAccessToken();
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", access_token);
return Task.FromResult(0);
}));
}
private async Task<string> GetGraphAccessToken()
{
IConfidentialClientApplication cc = MsalAppBuilder.BuildConfidentialClientApplication();
var userAccount = await cc.GetAccountAsync(ClaimsPrincipal.Current.GetMsalAccountId());
AuthenticationResult result = await cc.AcquireTokenSilent(new string[] { "user.read" }, userAccount).ExecuteAsync();
return result.AccessToken;
}

Unable to send email via Microsoft Graph API with Delegated Permission

I created a C# console application to send email using Microsoft Graph API. On adding Mail.Send Application Permission, it works fine. But, because of company requirements, I was asked to use Mail.Send Delegated Permission instead and with that permission I don't see it working and I see this error:
Are there any steps I should consider doing after adding Mail.Send Delegated Permission in order to get this working?
Here is my code:
static void Main(string[] args)
{
// Azure AD APP
string clientId = "<client Key Here>";
string tenantID = "<tenant key here>";
string clientSecret = "<client secret here>";
Task<GraphServiceClient> callTask = Task.Run(() => SendEmail(clientId, tenantID, clientSecret));
// Wait for it to finish
callTask.Wait();
// Get the result
var astr = callTask;
}
public static async Task<GraphServiceClient> SendEmail(string clientId, string tenantID, string clientSecret)
{
var confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithTenantId(tenantID)
.WithClientSecret(clientSecret)
.Build();
var authProvider = new ClientCredentialProvider(confidentialClientApplication);
var graphClient = new GraphServiceClient(authProvider);
var message = new Message
{
Subject = subject,
Body = new ItemBody
{
ContentType = BodyType.Text,
Content = content
},
ToRecipients = new List<Recipient>()
{
new Recipient
{
EmailAddress = new EmailAddress { Address = recipientAddress }
}
}
};
var saveToSentItems = true;
await _graphClient.Users[<userprincipalname>]
.SendMail(message, saveToSentItems)
.Request()
.PostAsync();
return graphClient;
}
UPDATE:
Based on below answer, I updated code as follows:
var publicClientApplication = PublicClientApplicationBuilder
.Create("<client-id>")
.WithTenantId("<tenant-id>")
.Build();
var authProvider = new UsernamePasswordProvider(publicClientApplication);
var secureString = new NetworkCredential("", "<password>").SecurePassword;
User me = await graphClient.Me.Request()
.WithUsernamePassword("<username>", secureString)
.GetAsync();
I enabled "Allow public client flows" to fix an exception.
And now I see another exception: Insufficient privileges to complete the operation.
What am I missing?
UPDATE: Currently I see this exception with no changes in the code:
The code you provided shows you use client credential flow to do the authentication. When you use Mail.Send Application permission, use client credential flow is ok. But if you use Mail.Send Delegated permission, we can not use client credential. You should use username/password flow to do authentication.
=================================Update===================================
Below is my code:
using Microsoft.Graph;
using Microsoft.Graph.Auth;
using Microsoft.Identity.Client;
using System;
using System.Collections.Generic;
using System.Security;
namespace ConsoleApp34
{
class Program
{
static async System.Threading.Tasks.Task Main(string[] args)
{
Console.WriteLine("Hello World!");
var publicClientApplication = PublicClientApplicationBuilder
.Create("client id")
.WithTenantId("tenant id")
.Build();
string[] scopes = new string[] { "mail.send" };
UsernamePasswordProvider authProvider = new UsernamePasswordProvider(publicClientApplication, scopes);
GraphServiceClient graphClient = new GraphServiceClient(authProvider);
var message = new Message
{
Subject = "Meet for lunch?",
Body = new ItemBody
{
ContentType = BodyType.Text,
Content = "The new cafeteria is open."
},
ToRecipients = new List<Recipient>()
{
new Recipient
{
EmailAddress = new EmailAddress
{
Address = "to email address"
}
}
}
};
var securePassword = new SecureString();
foreach (char c in "your password")
securePassword.AppendChar(c);
var saveToSentItems = true;
await graphClient.Me
.SendMail(message, saveToSentItems)
.Request().WithUsernamePassword("your email", securePassword)
.PostAsync();
}
}
}
The reason for your error message Insufficient privileges to complete the operation is you use the code:
User me = await graphClient.Me.Request()
.WithUsernamePassword("<username>", secureString)
.GetAsync();
This code is used to get the user(me)'s information but not send email, you haven't added the permission to the app. So it will show Insufficient privileges to complete the operation. Please remove this code and use the code block in my code instead:
await graphClient.Me.SendMail(message, saveToSentItems)
.Request().WithUsernamePassword("your email", securePassword)
.PostAsync();
==============================Update2====================================

Can not create a team from scratch via microsoft graph api

I follow this document and tried to create a team in the code
https://learn.microsoft.com/en-us/graph/api/team-post?view=graph-rest-1.0&tabs=csharp%2Chttp.
here is my code snippets:
var scopes = new string[] { "https://graph.microsoft.com/.default" };
// Configure the MSAL client as a confidential client
var confidentialClient = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithTenantId(tenantId)
.WithClientSecret(clientSecret)
.Build();
GraphServiceClient graphServiceClient =
new GraphServiceClient(new DelegateAuthenticationProvider(async (requestMessage) =>
{
// Retrieve an access token for Microsoft Graph (gets a fresh token if needed).
var authResult = await confidentialClient
.AcquireTokenForClient(scopes)
.ExecuteAsync();
// Add the access token in the Authorization header of the API request.
requestMessage.Headers.Authorization =
new AuthenticationHeaderValue("Bearer", authResult.AccessToken);
})
);
// Make a Microsoft Graph API call
var team = new Team
{
DisplayName = "My Sample Team",
Description = "My Sample Team’s Description",
AdditionalData = new Dictionary<string, object>()
{
{"template#odata.bind", "https://graph.microsoft.com/v1.0/teamsTemplates('standard')"},
{"members#odata.bind", "[{\"#odata.type\":\"#microsoft.graph.aadUserConversationMember\",\"roles\":[\"owner\"],\"userId\":\"57d4fc1c-f0a3-1111-b41e-22229f05911c\"}]"}
}
};
GraphServiceClient graphServiceClient =
new GraphServiceClient(new DelegateAuthenticationProvider(async (requestMessage) =>
{
// Retrieve an access token for Microsoft Graph (gets a fresh token if needed).
var authResult = await confidentialClient
.AcquireTokenForClient(scopes)
.ExecuteAsync();
// Add the access token in the Authorization header of the API request.
requestMessage.Headers.Authorization =
new AuthenticationHeaderValue("Bearer", authResult.AccessToken);
})
);
// Make a Microsoft Graph API call
var team = new Team
{
DisplayName = "My Sample Team",
Description = "My Sample Team’s Description",
AdditionalData = new Dictionary<string, object>()
{
{"template#odata.bind", "https://graph.microsoft.com/v1.0/teamsTemplates('standard')"},
{"members#odata.bind", "[{\"#odata.type\":\"#microsoft.graph.aadUserConversationMember\",\"roles\":[\"owner\"],\"userId\":\"57d4fc1c-f0a3-4105-b41e-1ba89f05911c\"}]"}
}
};
but get this error:
"message": "Bind requests not supported for containment navigation property.",\r\n
I'm using the latest Microsoft.Graph library and version is V3.1.8
does anyone have some ideas on this issue or the odata format error?
It seems that the members#odata.bind is still in change. It doesn't work currently.
You need to use members property.
POST https://graph.microsoft.com/v1.0/teams
{
"template#odata.bind":"https://graph.microsoft.com/v1.0/teamsTemplates('standard')",
"displayName":"My Sample Team555",
"description":"My Sample Team’s Description555",
"members":[
{
"#odata.type":"#microsoft.graph.aadUserConversationMember",
"roles":[
"owner"
],
"userId":"9xxxxxc9-f062-48e2-8ced-22xxxxx6dfce"
}
]
}
The corresponding C# code should be:
var team = new Team
{
DisplayName = "My Sample Team557",
Description = "My Sample Team’s Description557",
Members = (ITeamMembersCollectionPage)new List<ConversationMember>()
{
new AadUserConversationMember
{
Roles = new List<String>()
{
"owner"
},
UserId = "9xxxxxc9-f062-48e2-8ced-22xxxxx6dfce"
}
},
AdditionalData = new Dictionary<string, object>()
{
{"template#odata.bind", "https://graph.microsoft.com/v1.0/teamsTemplates('standard')"}
}
};
Unfortunately, when I run the code, it shows:
System.InvalidCastException: 'Unable to cast object of type 'System.Collections.Generic.List`1[Microsoft.Graph.ConversationMember]' to type 'Microsoft.Graph.ITeamMembersCollectionPage'.'
I cannot make it work. The workaround is to use httpClient to send the request in your code.
See a similar question here.
UPDATE:
I have figured it out.
You can try the following code:
var team = new Team
{
DisplayName = "My Sample Team558",
Description = "My Sample Team’s Description558",
Members = new TeamMembersCollectionPage() {
new AadUserConversationMember
{
Roles = new List<String>()
{
"owner"
},
UserId = "9xxxxxc9-f062-48e2-8ced-22xxxxx6dfce"
}
},
AdditionalData = new Dictionary<string, object>()
{
{"template#odata.bind", "https://graph.microsoft.com/v1.0/teamsTemplates('standard')"}
}
};
If you prefer httpClient method, refer to this:
string str = "{\"template#odata.bind\":\"https://graph.microsoft.com/v1.0/teamsTemplates('standard')\",\"displayName\":\"My Sample Team999\",\"description\":\"My Sample Team’s Description555\",\"members\":[{\"#odata.type\":\"#microsoft.graph.aadUserConversationMember\",\"roles\":[\"owner\"],\"userId\":\"9xxxxxc9-f062-48e2-8ced-22xxxxx6dfce\"}]}";
var content = new StringContent(str, Encoding.UTF8, "application/json");
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var response = client.PostAsync("https://graph.microsoft.com/v1.0/teams", content).Result;
UPDATE 2:
If you need to call it in Postman, use this format:
{
"template#odata.bind":"https://graph.microsoft.com/v1.0/teamsTemplates('standard')",
"displayName":"My Sample Team555",
"description":"My Sample Team’s Description555",
"members":[
{
"#odata.type":"#microsoft.graph.aadUserConversationMember",
"roles":[
"owner"
],
"userId":"9xxxxxc9-f062-48e2-8ced-22xxxxx6dfce"
}
]
}

SAML2.0 Access token using 'itfoxtec-identity-saml2'

I am trying to use your Nuget package for dotnet core and I get little bit success also I can login to SAML identity providers like Onelogin,Okta and I got loggin user information also But I am confuse while generating access token(Bearer token to call APIs of SAML identity providers). How will I get that token?
I can see securitytoken object in saml2AuthnResponse but don’t know how to that token and in that object security key and singin key is null.
I am totally new to this so may be I misunderstand something.
Please help me.
[Route("AssertionConsumerService")]
public async Task<IActionResult> AssertionConsumerService()
{
var binding = new Saml2PostBinding();
var saml2AuthnResponse = new Saml2AuthnResponse(config);
binding.ReadSamlResponse(Request.ToGenericHttpRequest(), saml2AuthnResponse);
if (saml2AuthnResponse.Status != Saml2StatusCodes.Success)
{
throw new AuthenticationException($"SAML Response status: {saml2AuthnResponse.Status}");
}
binding.Unbind(Request.ToGenericHttpRequest(), saml2AuthnResponse);
await saml2AuthnResponse.CreateSession(HttpContext, claimsTransform: (claimsPrincipal) => ClaimsTransform.Transform(claimsPrincipal));
var relayStateQuery = binding.GetRelayStateQuery();
var returnUrl = relayStateQuery.ContainsKey(relayStateReturnUrl) ? relayStateQuery[relayStateReturnUrl] : Url.Content("~/");
return Redirect(returnUrl);
}
You can get access to the SAML 2.0 token as a XML string by setting Saml2Configuration.SaveBootstrapContext = true in appsettings.json:
...
"Saml2": {
"SaveBootstrapContext": true,
"IdPMetadata": "https://localhost:44305/metadata",
"Issuer": "itfoxtec-testwebappcore",
...
}
Alternatively you can set the configuration in code:
config.SaveBootstrapContext = true;
Then you can read the SAML 2.0 token as a XML string in the saml2AuthnResponse.ClaimsIdentity.BootstrapContext:
public async Task<IActionResult> AssertionConsumerService()
{
var binding = new Saml2PostBinding();
var saml2AuthnResponse = new Saml2AuthnResponse(config);
binding.ReadSamlResponse(Request.ToGenericHttpRequest(), saml2AuthnResponse);
if (saml2AuthnResponse.Status != Saml2StatusCodes.Success)
{
throw new AuthenticationException($"SAML Response status: {saml2AuthnResponse.Status}");
}
binding.Unbind(Request.ToGenericHttpRequest(), saml2AuthnResponse);
await saml2AuthnResponse.CreateSession(HttpContext, claimsTransform: (claimsPrincipal) => ClaimsTransform.Transform(claimsPrincipal));
var samlTokenXml = saml2AuthnResponse.ClaimsIdentity.BootstrapContext as string;
var relayStateQuery = binding.GetRelayStateQuery();
var returnUrl = relayStateQuery.ContainsKey(relayStateReturnUrl) ? relayStateQuery[relayStateReturnUrl] : Url.Content("~/");
return Redirect(returnUrl);
}

Resources