Searching Active Directory B2C by custom property on User - c#

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.

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.

List all users in specific role

In ASP.NET Core 2.2 MVC I'm trying to get a list of all users in a specific role.
Fx. list of all users in role named "Admin":
var idsWithPermission = _userManager.GetUsersInRoleAsync("Admin").Result;
var users = _db.ApplicationUser.Where(u => idsWithPermission.Contains(u.Id)).ToListAsync();
return(users);
Compiler fails "u.Id" here: idsWithPermission.Contains(u.Id)
Error: Argument 1: Cannot convert from "string" to Microsoft.AspNetCore.Identity.IdentityUser
This is a newbie questions, so might be very simple for a shark :-)
Thanks a lot in advance...
GetUsersInRoleAsync returns a list of IdentityUser objects. To get a list of IDs, you need to access the Id property of these objects.
// Get a list of users in the role
var usersWithPermission = _userManager.GetUsersInRoleAsync("Admin").Result;
// Then get a list of the ids of these users
var idsWithPermission = usersWithPermission.Select(u => u.Id);
// Now get the users in our database with the same ids
var users = _db.ApplicationUser.Where(u => idsWithPermission.Contains(u.Id)).ToListAsync();
return users;
Note that using .Result on an async method is not advised, because it can lead to deadlocks. Instead use await and make your method async.
Also note that depending on your setup, if ApplicationUser inherits from IdentityUser and the identity system is correctly configured, GetUsersInRoleAsync will already return ApplicationUser objects and you only need to cast them to the correct type:
// Get a list of users in the role
var usersWithPermission = _userManager.GetUsersInRoleAsync("Admin").Result;
var users = usersWithPermission.OfType<ApplicationUser>();
return users;

Get only users with TransitiveMembers Microsoft Graph C# SDK

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);
}

How to get a list of all company accounts?

I'm trying to get list of all company accounts using SAP Business One (B1 or BO) Data Interface API (DI API) for .NET.
An account is represented as ChartOfAccounts SDK's type.
I can't figure out if there is a way to do something like this (it's how I'm getting the list of items):
var oItem = (Items) Company.GetBusinessObject(BoObjectTypes.oItems);
var oSBObob = (SBObob)Company.GetBusinessObject(BoObjectTypes.BoBridge);
var oRecordSet = oSBObob.GetItemList();
But it seems there is no method to similar to GetItemList() for accounts in SBObob type.
Does anybody know how to get list of company accounts?
I implemented this using a Recordset approach by querying OACT DB table directly for the list of account keys (AcctCode DB field) and then using ChartOfAccounts's GetByKey() method to fill other ChartOfAccounts's fields like this:
var sapAccount = (ChartOfAccounts)Company.GetBusinessObject(BoObjectTypes.oChartOfAccounts);
var oRecordSet = (Recordset)company.GetBusinessObject(BoObjectTypes.BoRecordset);
oRecordSet.DoQuery("SELECT AcctCode FROM OACT");
while (!oRecordSet.EoF)
{
var key = oRecordSet.Fields.Item(0).Value.ToString();
sapAccount.GetByKey(key)
// Now sapAccount is filled with current account data - do something with its fields
oRecordSet.MoveNext();
}

How to determine if a user belongs to a certain group using ASP.Net Identity Framework 2.0

Using the new ASP.Net Identity Framework 2.0, I am trying to determine the list of users that belong to a certain group.
The UserManager and RoleManager are properly instantiated and I get the right infos while debugging but I don't understand how to use user.Roles.Contains.
var _listAllUsers = UserManager.Users.ToListAsync().Result;
//var _allUsers = UserManager.Users;
var roleToMatch = RoleManager.FindByNameAsync("MyUserManager").Result;
foreach (var user in _listAllUsers){
var _listGroupAdminCat = user.Roles.Contains((IdentityUserRole)roleToMatch);
}
There's something I am missing in the syntax.
First get your IdentityRole object:
var role = RoleManager.FindByName("MyUserManager");
Then get the users in that role:
var usersInRole = role.Users;
Your question title is asking a slightly different question though, how to determine is a user is in a role. For this, use the UserManager like this:
int userId = 5;
string roleToCheck = "MyRole";
bool userIsInRole = userManager.IsInRole(5, roleToCheck );
It's also worth noting that in the code you posted you are using the asynchronous functions incorrectly. Either use them with the await keyword or use the synchronous versions:
var roleSynchronously = RoleManager.FindByName("MyUserManager");
var roleAsynchronously = await RoleManager.FindByNameAsync("MyUserManager");

Categories

Resources