Azure AD issues claims security groups names - azure-active-directory

I need my Azure AD to issue a claim with security group names.
But there are only group object ids come out in the JWT token.
How to get security group names?
What I did so far:
1. Created a test security group and assigned a user to it. This is the only group for this user.
Set the groupMembershipClaims to All (integer 7) as it is in this official document https://learn.microsoft.com/en-us/azure/active-directory/develop/reference-app-manifest
here is the relevant part of the application manifest:
{
...
"appRoles": [],
"availableToOtherTenants": false,
"displayName": "Azure AD B2C sandbox App ",
"errorUrl": null,
"groupMembershipClaims": "All",
"optionalClaims": null,
"acceptMappedClaims": null,...

You cannot get them in tokens. As you noticed, you only get the ids.
Usually this is good, since the id cannot be changed, unlike the name which can change.
If you want to do authorization based on groups, you can set the ids in a configuration file and then check with the id.
If you want the names for some other purpose, you'll need query the groups from Microsoft Graph API.
You can find the API documentation here: https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/resources/groups-overview

You can get the AD group name thru Token configuration. By default, it is return Group ID but you can change it to sAMAccountName.

You can not receive group display names inside your id_token.
But you can query group properties, like group display name from another api, in this case ms graph api.
Here is what I did to query groups display name from ms graph api..
Thanks
/// <summary>
/// Translate group.Id list received on id_token into group.DisplayName list
/// </summary>
/// <param name="groupIdList"></param>
/// <returns></returns>
public override List<string> TranslateGroupNames(List<string> groupIdList)
{
// validations
if (groupIdList == null || groupIdList.Count == 0)
return groupIdList;
if (string.IsNullOrEmpty(Configuration.ClientID))
throw new InvalidOperationException("A configuração 'ClientID' não pode ser vazia.");
if (string.IsNullOrEmpty(Configuration.ClientSecret))
throw new InvalidOperationException("A configuração 'ClientSecret' não pode ser vazia.");
if (string.IsNullOrEmpty(Configuration.TokenEndpoint))
throw new InvalidOperationException("A configuração 'TokenEndpoint' não pode ser vazia.");
if (string.IsNullOrEmpty(Configuration.TenantID))
throw new InvalidOperationException("A configuração 'TenantID' não pode ser vazia.");
// acquire a brand new access_token via client_credentials, especificly to ms graph api
var clientCredentialsRequest = new ClientCredentialsTokenRequest();
clientCredentialsRequest.Address = Configuration.TokenEndpoint;
clientCredentialsRequest.ClientId = Configuration.ClientID;
clientCredentialsRequest.Scope = "https://graph.microsoft.com/.default";
clientCredentialsRequest.ClientSecret = Configuration.ClientSecret;
var accessTokenResponse = _httpClient.RequestClientCredentialsTokenAsync(clientCredentialsRequest).Result;
if (accessTokenResponse.IsError)
throw new InvalidOperationException($"Falha ao recuperar AcessToken. {accessTokenResponse.Error}: {accessTokenResponse.ErrorDescription}");
// set access_token on httpclient
_httpClient.SetBearerToken(accessTokenResponse.AccessToken);
var result = new List<string>(groupIdList.Count);
// query ms graph api to recover group info
foreach (var groupId in groupIdList)
{
var url = $"https://graph.microsoft.com/v1.0/{Configuration.TenantID}/groups/{groupId}";
var groupResponse = _httpClient.GetAsync(url).Result;
if (!groupResponse.IsSuccessStatusCode)
throw new InvalidOperationException($"Falha ao recuperar grupo. {groupResponse.ReasonPhrase}");
var jsonString = groupResponse.Content.ReadAsStringAsync().Result;
var group = JsonConvert.DeserializeObject<dynamic>(jsonString);
if (group?.displayName?.Value == null)
throw new InvalidOperationException($"Grupo inválido");
// get group display name
result.Add(group.displayName.Value);
}
return result;
}

Related

What is the best way to update a department name in Active Directory and Azure for 100+ users?

Azure is currently setup to sync from on
prem. Active Directory. What is the best way to update the department name
in Active Directory and Azure for these users? Below is a sample of the list.
Name
Old Department Name
New Department Name
Larry Lue
Collections
Collector Members
Erica Anderson
Collections
Collector Members
Mary Lee
Collections
Collector Members
You can use ms graph api to do the update by code. But this api doesn't provide a batch operation, so you have to update user profile one by one. Here's my test result:
Before updating the department, the original department look like this:
After update it by code. it will be changed to the new value.
Here's the code snippet:
using Microsoft.Graph;
using Azure.Identity;
public async Task<IActionResult> IndexAsync()
{
var scopes = new[] { "https://graph.microsoft.com/.default" };
var tenantId = "tenant_name.onmicrosoft.com";
var clientId = "azure_ad_app_id";
var clientSecret = "azure_ad_app_client_secret";
var clientSecretCredential = new ClientSecretCredential(
tenantId, clientId, clientSecret);
var graphClient = new GraphServiceClient(clientSecretCredential, scopes);
//query user information
var user = await graphClient.Users["user_id"]
.Request().Select("displayname, department").GetAsync();
//update user information
var userDept = new User
{
Department = "Collector Members"
};
await graphClient.Users["9a8ae89f-711a-434a-8d08-43e1f7d29af2"]
.Request()
.UpdateAsync(userDept);
}

Azure AD/Office 365: Create similar groups for two different domains

I have two domains registered in Azure AD, domainA.com and domainB.com, and I want to create two similar Groups for them:
support#domainA.com
support#domainB.com
How can I do that?
Im trying code like below via Microsoft Graph, and first Group is created successfully. However there is an error when adding the second group for domain B:
"Another object with the same value for property mailNickname already
exists"
public async Task CreateSupportGroups()
{
// Create the group for domain A
await UpdateDefaultDomain("domainA.com");
await AddNewGroup("Support group for Domain A");
// Create the group for domain B
await UpdateDefaultDomain("domainB.com");
await AddNewGroup("Support group for Domain B");
}
public async Task AddNewGroup(String groupName)
{
var group = new Group
{
Description = "Testgroup",
DisplayName = groupName,
GroupTypes = new List<String>()
{
"Unified"
},
MailEnabled = true,
MailNickname = "support",
SecurityEnabled = false,
Visibility = "Private"
};
await graphClient.Groups
.Request()
.AddAsync(group);
}
public async Task UpdateDefaultDomain(String domainID)
{
var domain = new Domain
{
IsDefault = true
};
await graphClient.Domains[domainID]
.Request()
.UpdateAsync(domain);
}
I have tested in my environment.
Please make domainA.com as primary and create a group with displayname support A and mailnickname as support
Then make domainB.com as primary and create a group with displayname support B and mailnickname as supportB because if you create with mailnickname as support, it will throw below error :
After creation of Support B Group, edit the primary mail of the group to support#domainB.com. Even though it throws error, the primary mail of the group is updated.
Please refer below screenshot :

Integration Salesforce with Docusign Invalid type: dfsle.Envelope

I'm trying to integrate the Salesforce with DocuSign with Docusign Apex Toolkit, but dfsle class is not available in my org. I installed the Apex Tool Kit (https://developers.docusign.com/docs/salesforce/how-to/apex-toolkit-install/)
Id MySourceId = '00Q0m00000884XXXXX';
dfsle.Envelope myEnvelope = dfsle.EnvelopeService.getEmptyEnvelope(new dfsle.Entity(mySourceId));
Lead myContact = [SELECT Id, Name, Email FROM lead where id = '00Q0m00000884XXXX'];
//use the Recipient.fromSource method to create the Recipient
dfsle.Recipient myRecipient = dfsle.Recipient.fromSource(
myContact.Name, // Recipient name
myContact.Email, // Recipient email
null, //Optional phone number
'Signer 1', //Role Name. Specify the exact role name from template
new dfsle.Entity(myContact.Id)); //source object for the Recipient
dfsle.UUID myTemplateId = dfsle.UUID.parse('28386dbc-2576-4637-bb77-c86938fe080f');
//create a new document for the Envelope
dfsle.Document myDocument = dfsle.Document.fromTemplate(
myTemplateId, // templateId in dfsle.UUID format
'Self Sales Teste'); // name of the template
// Send the envelope.
myEnvelope = dfsle.EnvelopeService.sendEnvelope(
myEnvelope, // The envelope to send
true); // Send now?
try {
dfsle.EnvelopeService.sendEnvelope(envelope, true);
} catch (dfsle.APIException ex) {
if (ex.error.code == dfsle.APIErrorCode.CONSENT_REQUIRED) {
// user is a valid member of the DocuSign account, but has not granted consent to this application
} else {
// handle other errors
}
}
Error: Line: 3, Column: 1
Error: Invalid type: dfsle.Envelope
Have same issue few weeks ago :) Go to your Salesforce Setup. Type Installed Packages in quick find box. Make sure the DocuSign App Launcher is installed. Check package prefix.

Create OAuth2PermissionGrant for Resources via Graph

My scenario is to create app & spn via AAD Graph. That ist rather easy (with a redirect for browser-based consent), what I now want to do is consent the spn right away (like you can do in the portal). The code itself is straight-forward:
var g = new OAuth2PermissionGrant();
g.ClientId = thePrincipal.ObjectId;
g.ConsentType = "AllPrincipals";
g.PrincipalId = null;
g.ResourceId = ##resourceId##;
g.ExpiryTime = DateTime.Now.AddYears(10);
g.Scope = "User.Read";
await client.Oauth2PermissionGrants.AddOAuth2PermissionGrantAsync(g);
Now the part that I haven't figured out properly is ##resourceId##. This is supposed to be the resourceId - in the code sample, it should be Windows Azure Active Directory. How do I get the resourceId for eg the following required resource access (00000002-0000-0000-c000-000000000000):
RequiredResourceAccess =
new [] {
new RequiredResourceAccess() {
ResourceAppId = "00000002-0000-0000-c000-000000000000",
ResourceAccess = new [] {
new ResourceAccess() {
Id = new Guid("311a71cc-e848-46a1-bdf8-97ff7156d8e6"), // sign in and read profile (delegated perm)
Type = "Scope"
},
The lookup ResourceAppId -> resourceId (app to spn) is what I am missing. For eg AAD, Graph, manage.office.com et al.
From the documentation for the OAuth2PermissionGrant entity, the resourceId field of an OAuth2PermissionGrant is the objectId of the ServicePrincipal object for the resource:
Specifies the objectId of the resource service principal to which access has been granted.
So, from the tenant in which you are creating the OAuth2PemrissionGrant, you need to retrieve the ServicePrincipal object corresponding to the resource app you would like to grant permission to, and from that object, read the objectId property.
If you have the resource app's AppId, you can retrieve the corresponding ServicePrincipal object (if one exists) with:
GET https://graph.windows.net/{tenant}/servicePrincipals
?$filter=appId eq '{app-id-guid}'
&api-version=1.6
With Microsoft.Azure.ActiveDirectory.GraphClient (which I think is what you're using in your code), you would do this with:
graphClient.ServicePrincipals.Where(sp => sp.AppId == "{app-id-guid}")
If what you have to identify the resource app is not the Guid app ID, but a (somewhat) friendly identifier URI (e.g. "https://graph.microsoft.com"), you can retrieve the matching ServicePrincipal object by filtering on servicePrincipalNames.
With Azure AD Graph:
GET https://graph.windows.net/{tenant}/servicePrincipals
?$filter=servicePrincipalNames/any(n:n eq 'https://graph.microsoft.com'))
&api-version=1.6
With Microsoft.Azure.ActiveDirectory.GraphClient:
graphClient.ServicePrincipals
.Where(sp => sp.ServicePrincipalNames.Any(n => n == "https://graph.microsoft.com"))

Update user with DirectoryService

I'm writing a small application which updates my AD with info from a DB table, but are having trouble finding examples of best practices.
As far as I understand, I'll need to:
create a DirectorySearcher with a filter objectClass=user and search for the given cn
if found, I need to use result.getDirectoryEntry to get a handle to the actual object,
Update all the values to my entryobject with the one's from the db and then commit changes
Is that it or am I totally lost, any hints or examples are welcome
If you're on .NET 3.5 and up, you should check out the System.DirectoryServices.AccountManagement (S.DS.AM) namespace. Read all about it here:
Managing Directory Security Principals in the .NET Framework 3.5
MSDN docs on System.DirectoryServices.AccountManagement
Basically, you can define a domain context and easily find users and/or groups in AD:
// set up domain context
PrincipalContext ctx = new PrincipalContext(ContextType.Domain);
// find a user
UserPrincipal user = UserPrincipal.FindByIdentity(ctx, "SomeUserName");
if(user != null)
{
// update the properties you need to
user.DisplayName = "Joe Blow";
user.Description = "Some description";
// save back your changes
user.Save();
}
The new S.DS.AM makes it really easy to play around with users and groups in AD!
If you need to search a whole bunch of users, you can use a PrincipalSearcher and a "query-by-example" principal to do your searching:
// create your domain context
PrincipalContext ctx = new PrincipalContext(ContextType.Domain);
// define a "query-by-example" principal - here, we search for a UserPrincipal
// and with the first name (GivenName) of "Bruce"
UserPrincipal qbeUser = new UserPrincipal(ctx);
qbeUser.GivenName = "Bruce";
// create your principal searcher passing in the QBE principal
PrincipalSearcher srch = new PrincipalSearcher(qbeUser);
// find all matches
foreach(var found in srch.FindAll())
{
// do whatever here - "found" is of type "Principal" - it could be user, group, computer.....
UserPrincipal user = found as UserPrincipal;
if(user != null)
{
// update the properties you need to
user.DisplayName = "Joe Blow";
user.Description = "Some description";
// save back your changes
user.Save();
}
}
You can specify any of the properties on the UserPrincipal and use those as "query-by-example" for your PrincipalSearcher.

Resources