Get only users with TransitiveMembers Microsoft Graph C# SDK - c#

I want to get all users that are member of a group (transitive).
This call gets what I want:
https://graph.microsoft.com/v1.0/groups/{guid}/transitiveMembers/microsoft.graph.user
In my C# application I use the Graph API SDK. I know how you specify queryoptions, but I need an url segment instead of a query options.
At the moment I have this:
graphClient.Groups[guid].TransitiveMembers.Request().Select("id").GetAsync();
This returns all members, but not only users. So if someone know how to achieve this with the C# sdk please let me know.

The provided request:
GET https://graph.microsoft.com/v1.0/groups/{group-id}/transitiveMembers/microsoft.graph.user
could be constructed and executed via msgraph-sdk-dotnet like this:
var requestUrl = $"{graphClient.Groups[groupId].TransitiveMembers.Request().RequestUrl}/microsoft.graph.user";
var message = new HttpRequestMessage(HttpMethod.Get, requestUrl);
await graphClient.AuthenticationProvider.AuthenticateRequestAsync(message);
var response = await graphClient.HttpProvider.SendAsync(message);
and the response de-serialized like this:
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
var json = JObject.Parse(content);
var items = json["value"].Where(i => (string)i["#odata.type"] == null);
var members = items.Select(item => item.ToObject<Microsoft.Graph.User>());
foreach (var member in members)
{
Console.WriteLine(member.UserPrincipalName);
}
}
Since the query /groups/{group-id}/transitiveMembers/microsoft.graph.user still returns the collection of all transitive members but only the properties for microsoft.graph.user object are retrieved meaning it is not working precisely the same way as $filter parameter, maybe a more straightforward way would be:
a) retrieve all the transitive members via request:
var members = await graphClient.Groups[groupId].TransitiveMembers.Request().GetAsync();
b) and filter by user type:
var userMembers = members.Where(m => m.ODataType == "#microsoft.graph.user").Cast<User>();
foreach (var member in userMembers)
{
Console.WriteLine(member.UserPrincipalName);
}

Related

Filter group members by displayName does not work

I need to provide an endpoint which allows users to search for members within a group
the below code works fine when no filter is passed in (it returns members of the group), but when passing in a queryString I get this error
Microsoft.Graph.ServiceException: 'Code: Request_UnsupportedQuery
Message: The specified filter to the reference property query is currently not supported.
Is there any sensible way around this?
MS claim that this property is filterable, but is that only through the url API? Does that mean that what I'm trying to do is not possible?
It seems that in their examples they always set ConsistencyLevel: Eventual - but I don't see how we can set that using GraphServiceClient
I tried adding it as a header:
var consistencyLevelHeader = new HeaderOption("consistencylevel", "eventual");
request.Headers.Add(consistencyLevelHeader);
but I get the same results
var groupId = "guid-of-the-group";
var request = _graphServiceClient.Value.Groups[groupId]
.Members
.Request();
if (!string.IsNullOrEmpty(queryString))
{
request = request.Filter($"startswith(displayName,'{queryString}')");
}
var groupMembersCollection = await request
.GetAsync();
var userDtos = groupMembersCollection.Select(member =>
new AzureADAccountDTO
{
ProviderKey = member.Id,
EmailAddress = ((User)member).Mail,
Name = ((User)member).DisplayName,
})
.ToList();
return userDtos;
You need to add $count query parameter as well with ConsistencyLevel header to get the successful response.
The request would be something like below
https://graph.microsoft.com/v1.0/groups/0023c709-3556-4296-a6ab-6df2a0a1113c/members?$count=true&$filter=startswith(displayName, 's')
You can test these graph calls in Graph Explore.

C# MongoDB IAsyncCursor Explain

I am currently having a text index in my data. I am performing a regex search and I want to make sure that the correct index is used. In the mongo shell I just used explain but how could I use explain in the C# driver?
Here's how I perform my query:
public async Task<IEnumerable<GridFSFileInfo>> Find(string fileName)
{
var filter = Builders<GridFSFileInfo>.Filter.Regex(x => x.Filename, $"/.*{fileName}.*/i");
var options = new FindOptions
{
Modifiers = new BsonDocument("$hint", "filename_text")
};
var result = new List<GridFSFileInfo>();
using (var cursor = await Collection.Find(filter, options).ToCursorAsync())
{
while (await cursor.MoveNextAsync())
{
var batch = cursor.Current;
result.AddRange(batch);
}
}
return result;
}
Is there something like Collection.Find(filter, options).Explain() that returns a string or something? I searched on the web and found this: Is there an "Explain Query" for MongoDB Linq? However is seems there is no Explain method...
The modern mongo c# drivers support only one way to configure $explain. You can do it via FindOptions.Modifiers in the same was as you configured $hint above.
var options = new FindOptions
{
Modifiers = new BsonDocument
{
{ "$hint", "filename_text" },
{ "$explain", 1 }
}
};
var cursor = coll.Find(filter, options).As<BsonDocument>().ToCursor();
Pay attention, since $explain completely changes the server response, you should change your output type to BsonDocument(or a type with fields that matches to what explain returns) via As (if it's already not BsonDocument)

Searching Active Directory B2C by custom property on User

We are using B2C and storing customer numbers as a Extension field on users. A single user can have one or more customers and they are stored in a comma separated string.
What I am doing now is highly inefficient:
1. Get all Users
2. Get extension properties on each user
3. Check if they have the desired extension property and if it contains the customer I want.
4. Build a list of the users I want.
Adclient is IActiveDirectoryClient
var users = (await GetAllElementsInPagedCollection(await AdClient.Users.ExecuteAsync())).ToList();
var customersUsers = users.Where(user => user.AccountEnabled.HasValue && user.AccountEnabled.Value).Where(user =>
{
var extendedProperty = ((User) user).GetExtendedProperties().FirstOrDefault(extProp => extProp.Key == customersExtendedProperty.Name).Value?.ToString();
return extendedProperty != null && extendedProperty.Contains(customerId);
}).ToList();
I want to be able to do this in one query to ActiveDirectory using the AdClient. If I try this I get errors that the methods are not supported, which makes sense as I am assuming a query is being built behind the scenes to query Active Directory.
Edit - additional info:
I was able to query Graph API like this:
var authContext = await ActiveDirectoryClientFactory.GetAuthenticationContext(AuthConfiguration.Tenant,
AuthConfiguration.GraphUrl, AuthConfiguration.ClientId, AuthConfiguration.ClientSecret);
var url = $"https://graph.windows.net:443/hansaborgb2c.onmicrosoft.com/users?api-version=1.6&$filter={customersExtendedProperty.Name} eq '{customerId}'";
var users = await _graphApiHttpService.GetAll<User>(url, authContext.AccessToken);
However, in my example I need to use substringof to filter, but this is not supported by Azure Graph API.
I am not using that library, but we are doing a very similar search using the Graph API. I have constructed a filter that will look for users that match two extension attribute values I am looking for. The filter looks like this:
var filter = $"$filter={idpExtensionAttribute} eq '{userType.ToString()}' and {emailExtensionAttribute} eq '{emailAddress}'";
We have also used REST calls via PowerShell to the Graph API that will return the desired users. The URI with the associated filter looks like this:
https://graph.windows.net/$AzureADDomain/users?`$filter=extension_d2fbadd878984184ad5eab619d33d016_idp eq '$idp' and extension_d2fbadd878984184ad5eab619d33d016_email eq '$email'&api-version=1.6
Both of these options will return any users that match the filter criteria.
I would use normal DirectorySearcher Class from System.DirectoryServices
private void Search()
{
// GetDefaultDomain as start point is optional, you can also pass a specific
// root object like new DirectoryEntry ("LDAP://OU=myOrganisation,DC=myCompany,DC=com");
// not sure if GetDefaultDomain() works in B2C though :(
var results = FindUser("extPropName", "ValueYouAreLookingFor", GetDefaultDomain());
foreach (SearchResult sr in results)
{
// query the other properties you want for example Accountname
Console.WriteLine(sr.Properties["sAMAccountName"][0].ToString());
}
Console.ReadKey();
}
private DirectoryEntry GetDefaultDomain()
{ // Find the default domain
using (var dom = new DirectoryEntry("LDAP://rootDSE"))
{
return new DirectoryEntry("LDAP://" + dom.Properties["defaultNamingContext"][0].ToString());
}
}
private SearchResultCollection FindUser(string extPropName, string searchValue, DirectoryEntry startNode)
{
using (DirectorySearcher dsSearcher = new DirectorySearcher(startNode))
{
dsSearcher.Filter = $"(&(objectClass=user)({extPropName}={searchValue}))";
return dsSearcher.FindAll();
}
}
I came across this post looking to retrieve all users with a specific custom attribute value. Here's the implementation I ended up with:
var filter = $"{userOrganizationName} eq '{organizationName}'";
// Get all users (one page)
var result = await graphClient.Users
.Request()
.Filter(filter)
.Select($"id,surname,givenName,mail,{userOrganizationName}")
.GetAsync();
Where userOrganizationName is the b2c_extension full attribute name.

convert dynamic object to C# object

I’m looking to take a dynamic object from a third party API & convert to my C# object with minimal code.
Sort of like what Dapper does with a SQL statement results to a C# object. Is there a library that would do this?
I can do it manually like below, but it seems like alot of code to write everytime I want to retrieve from this API.
public IEnumerable<Requirement> Get()
{
dynamic requirementsSelect;
requirementsSelect = _RequirementsRepository.GetRequirements();
IList<Requirement> Requirements = new List<Requirement>();
foreach (var req in requirementsSelect)
{
Requirement requirement = new Requirement();
requirement.Text = req.Text;
requirement.Number = req.Number;
Requirements.Add(requirement);
foreach (var node in req.Phrases)
{
Requirement reqNode = new Requirement();
reqNode.Text = node.Text;
reqNode.Number = node.Number;
requirement.Nodes.Add(reqNode);
}
return Requirements;
}
Thanks
I ended up returning the third party as JSON as follows. In my specific case the JSON wasn't showing the properties. I resolved by changing the dynamic object to IEnumerable and I was then able to specify which properties I wanted to retrieve using .Select.
IEnumerable<dynamic> requirements = _RequirementsRepository.GetRequirements();
return JsonConvert.SerializeObject(new
{
Requirement = requirements.Select(x => x.Text),
Number = requirements.Select(x => x.Number),
});
Thanks!

Filtering JSON based on value

I have this object and would like to filter it based on the value of customer_email. For example, I only want to return commissions where the customer email is test#test.com. This is a simplified example of what a response would look like. Below is the method where I grab all the data.
public RootObject GetData(string customerEmail)
{
var data = new Commission();
using (var httpClient = new HttpClient())
{
var requestContent = JObject.FromObject(new
{
commission_id = data.id,
customer_email = customerEmail
}).ToString();
......
RootObject response = JsonConvert.DeserializeObject<RootObject>(responseContent);
return response;
}
}
When I get the response, the information looks like this:
{"response":{
"code":"200",
"message":"OK: The request was successful. See response body for additional data.",
"data":{
"commissions":
[{
"commission_id":"12345",
"customer_email":"test#test.com"
},
{
"commission_id":"67890",
"customer_email":"fake#fake.com"
}]
You can achieve it using simple LINQ right after serialization of response to RootObject.
return response
.Response.Data.Commissions
.Where(commission => commission.CustomerEmail == customerEmail);
Then you can use a lambda expression to easily filter the contents of that list.
return response.Response.Data.Commissions.Where(c => c.customerEmail==customerEmail);
This will return only the instances of Commission in the response's commissions collection.
A side effect of this solution would be changing the return type of the method from RootObject to IEnumerable<Commission>. Which would mean that callers of this method would no longer have access to any other data in the response object (besides the commissions data).

Categories

Resources