Microsoft.Graph.GraphServiceClient how to limit properties returned? - azure-active-directory

I am using Azure AD B2C. I was hoping to get a count of the number of users - however, according to the documentation:
Azure AD B2C currently does not support advanced query capabilities on
directory objects. This means that there is no support for $count ...
Great, so I thought the next best thing to do was to perform a query to get all the ids of the users, i.e.:
var options = new TokenCredentialOptions
{
AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
};
var clientSecretCredential = new ClientSecretCredential( tenant_id, client_id, client_secret, options );
var scopes = new[] { "https://graph.microsoft.com/.default" };
var users = await graphClient.Users
.Request()
.Select( "id" )
.GetAsync();
// This shows other properties returned in addition to "id" ...
if ( users is not null )
{
Console.WriteLine( JsonSerializer.Serialize( users ) );
}
I was under the impression (from the documentation) that using .Select( "id" ) would only return the "id" property, instead, the graph client returns what I assume are a default set of properties per user in addition to the "id", i.e.:
[
{"accountEnabled":null,
"ageGroup":null,
...more properties...,
"id":"xxxx...",
"#odata.type":"microsoft.graph.user"
},
{"accountEnabled":null,
"ageGroup":null,
... more properties ...,
"id":"xxxx...",
"#odata.type":"microsoft.graph.user"
}, ...
]
I am probably doing something wrong, but just how do you return ONLY the property (in this case "id") WITHOUT any other properties? I have yet to find an example or documentation that describes how to do this.
Thanks in advance.

Thanks to #user2250152 and #Rukmini - the "answer" per se is that the graph QUERY returns only the selected / requested properties, that is, when the following code is used:
var users = await graphClient.Users
.Request()
.Select("id")
.GetAsync();
The QUERY returns the "ids" as illustrated in #Rukmini's answer, but then the graphClient populates a list of "User" objects that is returned when the call completes. The User objects will contain the Id property as well as all the other properties associated with a User object as mentioned by #user2250152.
Thanks again for clearing this up.

I tried to reproduce the same in my environment and got the results successfully like below:
To get the Azure AD b2c User IDs I used the below code:
using Microsoft.Graph;
using Azure.Identity;
var scopes = new[] { "https://graph.microsoft.com/.default" };
var tenantId = "b2corg.onmicrosoft.com";
var clientId = "ClientID";
var options = new TokenCredentialOptions
{
AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
};
var userName = "admin#b2corg.onmicrosoft.com";
var password = "password";
var userNamePasswordCredential = new UsernamePasswordCredential(
userName, password, tenantId, clientId, options);
var graphClient = new GraphServiceClient(userNamePasswordCredential, scopes);
var users = await graphClient.Users
.Request()
.Select("id")
.GetAsync();
users.ToList().ForEach(x => { Console.WriteLine("\nid: " + x.Id); });
I also tried in the Microsoft Graph Explorer, and I am able to fetch only the User IDs like below:
https://graph.microsoft.com/v1.0/users?$select=id

Related

Cannot retrieve data from Active Directory

I created a new Blazor application with Azure AD authentication. Login functionality works as expected. Now I need to retrieve a list of users of a particular group. On Azure AD, I created a Client Secret. Also, I added Microsoft.Graph permissions Directory.Read.All, Group.Read.All, and
GroupMember.Read.All, and granted admin consent for the permissions.
Here are the permissions:
Here is my code I use to retrieve the users of a group:
var scopes = new string[] { "https://graph.microsoft.com/.default" };
var confidentialClient = ConfidentialClientApplicationBuilder
.Create(_adSettings.ClientId)
.WithAuthority($"{_adSettings.Instance}/{_adSettings.TenantId}/ v2.0")
.WithClientSecret(_adSettings.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
requestMessage.Headers.Authorization =
new AuthenticationHeaderValue("Bearer", authResult.AccessToken);
}));
var members = await graphServiceClient.Groups["mygroup#mydomain.com"]
.Members
.Request()
.GetAsync();
The last statement throws an exception:
An error occurred sending the request. HttpStatusCode: 404: NotFound
I tried to replace it with
var users = await graphServiceClient.Users.Request().GetAsync();
But result was the same.
You should specify your group object ID instead of the group name:
My test group:
Test code and result:
UPDATE:
This is a C# console app code for this test, hope it helps :
using Microsoft.Graph;
using Microsoft.Graph.Auth;
using Microsoft.Identity.Client;
using System;
namespace graphsdktest
{
class Program
{
static void Main(string[] args)
{
var clientId = "<Azure AD App ID>";
var clientSecret = "<App secret>";
var tenantID = "<tenant ID>";
IConfidentialClientApplication confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithTenantId(tenantID)
.WithClientSecret(clientSecret)
.Build();
ClientCredentialProvider authenticationProvider = new ClientCredentialProvider(confidentialClientApplication);
var graphClient = new GraphServiceClient(authenticationProvider);
var result = graphClient.Groups["<group ID>"].Members.Request().GetAsync().GetAwaiter().GetResult();
foreach (var user in result) {
Console.WriteLine(user.Id);
}
}
}
}
UPDATE 2:
If you get some permission exception while you query members of a group, pls go to Azure AD => App registrations => find your app => API permissions => Add a permission => Microsoft graph api => application permission => GroupMember.Read.All :
And click This button to finish the grant process :

Use Azure AD Graph to update values on the `AdditionalValues` dictionary for a user

How do I use Azure AD Graph to update values on the AdditionalValues dictionary for a user? The test below returns 400 Bad Response.
Background:
The rest of my application uses MSGraph. However, since a federated user can not be updated using MSGraph I am searching for alternatives before I ditch every implementation and version of Graph and implement my own database.
This issue is similar to this one however in my case I am trying to update the AdditionalData property.
Documentation
[TestMethod]
public async Task UpdateUserUsingAzureADGraphAPI()
{
string userID = "a880b5ac-d3cc-4e7c-89a1-123b1bd3bdc5"; // A federated user
// Get the user make sure IsAdmin is false.
User user = (await graphService.FindUser(userID)).First();
Assert.IsNotNull(user);
if (user.AdditionalData == null)
{
user.AdditionalData = new Dictionary<string, object>();
}
else
{
user.AdditionalData.TryGetValue(UserAttributes.IsCorporateAdmin, out object o);
Assert.IsNotNull(o);
Assert.IsFalse(Convert.ToBoolean(o));
}
string tenant_id = "me.onmicrosoft.com";
string resource_path = "users/" + userID;
string api_version = "1.6";
string apiUrl = $"https://graph.windows.net/{tenant_id}/{resource_path}?{api_version}";
// Set the field on the extended attribute
user.AdditionalData.TryAdd(UserAttributes.IsCorporateAdmin, true);
// Serialize the dictionary and put it in the content of the request
string content = JsonConvert.SerializeObject(user.AdditionalData);
string additionalData = "{\"AdditionalData\"" + ":" + $"[{content}]" + "}";
//additionalData: {"AdditionalData":[{"extension_myID_IsCorporateAdmin":true}]}
HttpClient httpClient = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage
{
Method = HttpMethod.Patch,
RequestUri = new Uri(apiUrl),
Content = new StringContent(additionalData, Encoding.UTF8, "application/json")
};
var response = await httpClient.SendAsync(request); // 400 Bad Request
}
Make sure that the Request URL looks like: https://graph.windows.net/{tenant}/users/{user_id}?api-version=1.6. You need to change the api_version to "api-version=1.6".
You cannot directly add extensions in AdditionalData and it will return the error(400).
Follow the steps to register an extension then write an extension value to user.
Register an extension:
POST https://graph.windows.net/{tenant}/applications/<applicationObjectId>/extensionProperties?api-version=1.6
{
"name": "<extensionPropertyName like 'extension_myID_IsCorporateAdmin>'",
"dataType": "<String or Binary>",
"targetObjects": [
"User"
]
}
Write an extension value:
PATCH https://graph.windows.net/{tenant}/users/{user-id}?api-version=1.6
{
"<extensionPropertyName>": <value>
}

Not able to view custom data added (used Schema Extension) for a user

In the Azure AD, I have created a Native application and assigned users to it. I want to create an extension property using Microsoft Graph's SchemaExtension. I have written the code for the same and it does not throw any errors and the extension is also added(able to see the Id) through code. I then add the value of the custom property for a user under the application but am not able to view in the Azure portal or in the code when I do the below:
I have supplied the value for the custom property after updating the status of the schema extensions to "Available" but still not able to fetch the custom property value in code or in the UI. Please provide inputs if any.
Below is the code i am using:
var aap = new AzureAuthenticationProvider();
var graphserviceClient = new GraphServiceClient(aap);
IEnumerable<string> targetType = new string[] { "User" };
SchemaExtension extensionDefinition = new SchemaExtension
{
ODataType = "microsoft.graph.ComplexExtensionValue",
Id = $"crmCompanyId",
Description = "This extension is for company id",
Owner = "fac4cdd3-1015-4ed5-8a7b-6008095750e6",
Properties = new List<ExtensionSchemaProperty>()
{
new ExtensionSchemaProperty() { Name = "cid", Type = "String" }
},
Status = "InDevelopment",
TargetTypes = targetType
};
SchemaExtension schemaExtension = //code to add extensionDefinition
var updateSchemaExtension = new SchemaExtension
{
Status = "Available"
};
//update schema to available status
var updatedSchema = code to update extension
IDictionary<string, object> extensionInstance = new Dictionary<string,object>();
extensionInstance.Add(schemaExtension.Id, new
MyDBExtensionClass("testMyExtension"));
User updateUser = new User()
{
AdditionalData = extensionInstance
};
var updatedUser = await graphserviceClient.Users["9ca2bb42-a7f8-487c-
87a0 - 38513057886d"].Request().UpdateAsync(updateUser);//AzureADGraphUser3
- 9ca2bb42 - a7f8 - 487c - 87a0 - 38513057886d
await Task.Delay(10000);
Could you please review the code and let me know what I am missing...I have referred the comments at https://github.com/microsoftgraph/msgraph-sdk-dotnet/issues/151 enter code hereby Michael Maier. Let me know in case I need to provide any further info.
Thanks
I was finally able to view the property i had set once i found this link:https://github.com/microsoftgraph/msgraph-sdk-dotnet/issues/238
I queried the extension property on a user this way and was able to view the property value i set.

Microsoft graph API: getting 403 while trying to read user groups

I am trying to get user's group information who log-Ins into the application.
Using below code, when I am hitting https://graph.microsoft.com/v1.0/users/{user}, then I am able to see that user is exist (200), but when trying to hit https://graph.microsoft.com/v1.0/users/{user}/memberOf, then I am getting 403.
private static async Task Test()
{
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "TOKEN HERE");
var user = "testuser#onmicrosoft.com";
var userExist = await DoesUserExistsAsync(client, user);
Console.WriteLine($"Does user exists? {userExist}");
if (userExist)
{
var groups = await GetUserGroupsAsync(client, user);
foreach (var g in groups)
{
Console.WriteLine($"Group: {g}");
}
}
}
}
private static async Task<bool> DoesUserExistsAsync(HttpClient client, string user)
{
var payload = await client.GetStringAsync($"https://graph.microsoft.com/v1.0/users/{user}");
return true;
}
private static async Task<string[]> GetUserGroupsAsync(HttpClient client, string user)
{
var payload = await client.GetStringAsync($"https://graph.microsoft.com/v1.0/users/{user}/memberOf");
var obj = JsonConvert.DeserializeObject<JObject>(payload);
var groupDescription = from g in obj["value"]
select g["displayName"].Value<string>();
return groupDescription.ToArray();
}
Is this something related to permission issue, my token has below scope now,
Note - Over here I am not trying to access other user/group information, only who log-ins. Thanks!
Calling /v1.0/users/[a user]/memberOf requires your access token to have either Directory.Read.All, Directory.ReadWrite.All or Directory.AccessAsUser.All and this is
documented at https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/user_list_memberof.
A great way to test this API call before implementing it in code is to use the Microsoft Graph explorer where you can change which permissions your token has by using the "modify permissions" dialog.

AngularFire: Getting the ID of Users Data from email in SimpleLogin

I'm trying to implement some simple using registration using Firebase through AngularFire and Angular.js. I'm using the SimpleLogin tool to manage the users. I can create users just fine.
var firebaseRef = new Firebase(FIREBASE_URL);
var simpleLogin = $firebaseSimpleLogin(firebaseRef);
var firebaseUsersRef = new Firebase(FIREBASE_URL + 'users');
var firebaseUsers = $firebase(firebaseUsersRef);
var myObject = {
register: function(user) {
var myDate = new Date().getTime();
return simpleLogin.$createUser(
user.email, user.password)
.then(function(regUser) {
var userInfo = {
date: myDate,
md5: regUser.md5_hash,
firstname: user.firstname,
lastname: user.lastname,
email: user.email
}
firebaseUsers.$push(userInfo).then(function(ref) {
userInfo.uid = ref.name();
$rootScope.currentUser = userInfo;
});
}); //push user
}, //register
Works like a charm. In order to get at this information when the user logs in, I've tried implementing an event handler on the $rootscope. I would like it to search through the uid that I stored and then get me record with the right user information.
$rootScope.$on('$firebaseSimpleLogin:login', function (e, authUser) {
var query = $firebase(firebaseRef.startAt(authUser.uid).endAt(authUser.uid));
console.log(query);
$location.path('/meetings');
});
In order to use startAt and endAt, do I have to establish $priority. When I try, I get an error stating that I can't have any special characters. So that never works. I don't really care about how this data stored, I just want to get the index of the data so that I can retrieve the right user.
By using $push you tell Firebase to generate a key for you.
This is great for collections where you normally access all children at the same time. But that is not the case for your user info: you want to access the info for the current user.
So instead of using $push to add your user's info, I would use the uid of the user.
In the regular Firebase JavaScript API this can be accomplish with:
firebaseUsersRef.child(reguser.uid).set(userInfo);
The equivalent in AngularFire probably uses $set, but I don't think you have any need for that in your $createUser callback.
Update
It looks like you're trying to add your info to the existing user node that Firebase creates for you. This is the example from that from the Firebase documentation on storing user data:
myRef.child('users').child(user.uid).set({
displayName: user.displayName,
provider: user.provider,
provider_id: user.id
});
You can see that they do access the user's node using child(user.uid) similar to what I proposed.
Lessons
Two relatively small mistakes here as far as I can see:
when you use push/$push, you let Firebase generate the node name for you. In cases where there already is a globally unique ID (such as the uid of a user), you're often better off using that as the node name.
If you know the name of the node you want to retrieve, you don't need a query. You can simply access the node as ref.child(user.uid).
Thanks to Frank, I was able to figure out the right way to do this. In order to make my own users object searchable, I can use the uid from the simpleLogin object. So my register function works like this:
var firebaseRef = new Firebase(FIREBASE_URL);
var simpleLogin = $firebaseSimpleLogin(firebaseRef);
var myObject = {
register: function(user) {
var myDate = new Date().getTime();
return simpleLogin.$createUser(user.email, user.password)
.then(function(regUser) {
var userInfo = {
date: myDate,
md5: regUser.md5_hash,
firstname: user.firstname,
lastname: user.lastname,
email: user.email
}
firebaseUsers.$set(regUser.uid, userInfo);
}); //add user
}, //register
} //my Object
Using set instead of push, I can store the uid from the registered user into the object and then pass along what I want to add as the second parameter. My database will now have the users organized by uid, which can be accessed via a url.
Then, when users log in, Firebase will throw up a login event along with the authenticated user, which I can catch and use to add the current user to the $rootScope that's accessible throughout my application.
$rootScope.$on('$firebaseSimpleLogin:login', function (e, authUser) {
var ref = new Firebase(FIREBASE_URL + '/users/' + authUser.uid);
var user = $firebase(ref).$asObject();
user.$loaded().then(function() {
$rootScope.currentUser = user;
});
$location.path('/meetings');
});
Thanks for pointing me in the right direction Frank.

Resources