12

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.

ruffen
  • 1,695
  • 2
  • 25
  • 51
  • When you say "extension" field, is it an extenion property which has a name in the format "extension_guid_someName"? And how are the extension properties put on the user account in the first place? Is that through Graph API? (i.e. the user is created by AD B2C and then Graph API is used to update it?) – Omer Iqbal Oct 22 '17 at 23:09
  • Yes, by extension field I mean extension property with that format. They are created using the graph API, or more correctly, I am using the ActiveDirectoryClient class and I am assuming this is using the Graph API in the background – ruffen Oct 23 '17 at 09:42
  • Yes, ActiveDirectoryClient wraps around Graph API. You can access raw interface throuh ActiveDirectoryClient.Context.ExecuteAsync to perform queries from @nboettcher's answer. However, there is a problem: $filter does not support 'contains' operation, only 'startswith' and 'any' for multi-valued properties (and you cannot create multi-valued extended property) :( Maybe there is hope in the future (but not in Azure AD Graph API - Microsoft announced movement to Microsoft Graph API) – Aloraman Oct 26 '17 at 19:22

3 Answers3

6

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.

nboettcher
  • 455
  • 3
  • 12
  • Finally had the time to test this, and I managed to query the extension properties, but I found that you can't search a property for part of its string (substring), do you have any idea for this? – ruffen Dec 05 '17 at 11:04
2

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();
    }
}
Brandtware
  • 451
  • 4
  • 17
0

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.

IdusOrtus
  • 1,005
  • 1
  • 16
  • 24