I am writing code to set optional claims. I was successful in setting the claim using PowerShell Set-AzureADUserExtension and validate it with Get-AzureADUserExtension. But we need to set the value using the graphApi.
The graphApi call that I tried with some variations are:
var application = (await graphClient.Applications.Request().Filter($"appId eq '{appClientId}'").GetAsync()).SingleOrDefault();
var schemas = await graphClient.Applications[application.Id].ExtensionProperties.Request().GetAsync();
var schema = schemas.CurrentPage.Single(x => x.Name == $"extension_{appClientId.Replace("-", string.Empty)}_clShops");
await graphClient.Users[userId].Extensions.Request().AddAsync (new OpenTypeExtension
{
ODataType = "Microsoft.DirectoryServices.User",
AdditionalData = new Dictionary<string, object> { { schema.Name, "1,2,3" } },
ExtensionName = schema.Id
});
I tried changing the values in OpenTypeExtension but I have not been able to alter the value for that user (checking with the Get-AzureADUserExtension powershell command).
My question is what graphApi function do i need to call so set the optional claim to get the same result as calling Set-AzureADUserExtension?
c# graphApi sdk version is Microsoft.Graph Version=3.35.0
figured it out after some more tests:
optionly you can get the name using:
var application = (await graphClient.Applications.Request().Filter($"appId eq '{appClientId}'").GetAsync()).SingleOrDefault();
var schemas = await graphClient.Applications[application.Id].ExtensionProperties.Request().GetAsync();
var schema = schemas.CurrentPage.Single(x => x.Name.EndsWith("name of claim"));
or by convention (replace schema.Name with): $"extension_{appId}_{'name of claim'}"
await graphClient.Users[userId].Request().UpdateAsync(new Microsoft.Graph.User
{
AdditionalData = new Dictionary<string, object>
{
{ schema.Name,value}
}
});
Related
I have a basic Pulumi build for keycloak where I set up a realm, create a scope, create a client, and update teh scopes for my client.
class RealmBuild : Stack
{
public RealmBuild()
{
var realm = new Realm("ExampleRealm-realm", new RealmArgs
{
RealmName = "ExampleRealm"
});
var recipemanagementScope = ScopeFactory.CreateScope(realm.Id, "recipe_management");
var recipeManagementPostmanMachineClient = ClientFactory.CreateClientCredentialsFlowClient(realm.Id,
"recipe_management.postman.machine",
"974d6f71-d41b-4601-9a7a-a33084484682",
"RecipeManagement Postman Machine",
"https://oauth.pstmn.io");
recipeManagementPostmanMachineClient.ExtendDefaultScopes(recipemanagementScope.Name);
}
}
public static class ClientExtensions
{
public static void ExtendDefaultScopes(this Client client, params Output<string>[] scopeNames)
{
var defaultScopeName = $"default-scopes-for-{client.Name.Apply(x => x)}";
var defaultScopes = new ClientDefaultScopes(defaultScopeName, new ClientDefaultScopesArgs()
{
RealmId = client.RealmId,
ClientId = client.Id,
DefaultScopes =
{
"openid",
"profile",
"email",
"roles",
"web-origins",
scopeNames,
},
});
}
}
public class ClientFactory
{
public static Client CreateClientCredentialsFlowClient(Output<string> realmId,
string clientId,
string clientSecret,
string clientName,
string baseUrl)
{
return new Client($"{clientName.ToLower()}-client", new ClientArgs()
{
RealmId = realmId,
ClientId = clientId,
Name = clientName,
StandardFlowEnabled = false,
Enabled = true,
ServiceAccountsEnabled = true,
AccessType = "CONFIDENTIAL",
BaseUrl = baseUrl,
AdminUrl = baseUrl,
ClientSecret = clientSecret,
BackchannelLogoutSessionRequired = true,
BackchannelLogoutUrl = baseUrl
});
}
}
The problem is, I am getting this error around my scopes:
Diagnostics:
keycloak:openid:ClientDefaultScopes (default-scopes-for-Calling [ToString] on an [Output<T>] is not supported.
To get the value of an Output<T> as an Output<string> consider:
1. o.Apply(v => $"prefix{v}suffix")
2. Output.Format($"prefix{hostname}suffix");
See https://pulumi.io/help/outputs for more details.
This function may throw in a future version of Pulumi.):
error: Duplicate resource URN 'urn:pulumi:dev::KeycloakPulumiStack::keycloak:openid/clientDefaultScopes:ClientDefaultScopes::default-scopes-for-Calling [ToString] on an [Output<T>] is not supported.
To get the value of an Output<T> as an Output<string> consider:
1. o.Apply(v => $"prefix{v}suffix")
2. Output.Format($"prefix{hostname}suffix");
See https://pulumi.io/help/outputs for more details.
This function may throw in a future version of Pulumi.'; try giving it a unique name
I tried something like this as well var defaultScopeName = Output.Format($"default-scopes-for-{client.Name}");, but I can't pass that into the name for ClientDefaultScopes
I did look at the docs to see if anything stuck out as an issue, but I'm clearly missing something.
Rule number 1 with Pulumi outputs: Anything you return from an apply() will still be an Output, even if it looks like it should be a string.
In other words, on this line of code:
var defaultScopeName = $"default-scopes-for-{client.Name.Apply(x => x)}";
defaultScopeName is Output<string>.
However, the x variable in the lambda is in fact a string rather than an output.
The other item to note is that the name of a resource (so the first argument) cannot be an Output. So in your code:
var defaultScopeName = $"default-scopes-for-{client.Name.Apply(x => x)}";
var defaultScopes = new ClientDefaultScopes(defaultScopeName, new ClientDefaultScopesArgs()
{
RealmId = client.RealmId,
ClientId = client.Id,
DefaultScopes =
{
"openid",
"profile",
"email",
"roles",
"web-origins",
scopeNames,
},
});
because defaultScopeName is an Output, this won't work.
You could create the resource inside of the apply():
var defaultScopea = $"default-scopes-for-{client.Name.Apply(x =>
return new ClientDefaultScopes(x, new ClientDefaultScopesArgs()
{
RealmId = client.RealmId,
ClientId = client.Id,
DefaultScopes =
{
"openid",
"profile",
"email",
"roles",
"web-origins",
scopeNames,
},
});
)}";
however, this may mean that the resource won't appear in any previews (see the note in the Apply section of the Inputs and Outputs page in the Pulumi docs).
So what's the answer here? it looks like you're setting the ClientName to be a string value earlier in the code, so I'd use the same variable that you're setting there.
You can't mix and match string and Output<string> values. Instead, you need to transform any output and append your static list to the list of resolved values:
var defaultScopeName = Output.Format($"default-scopes-for-{client.Name}");
var defaultScopes = new ClientDefaultScopes("some-scope-name", new ClientDefaultScopesArgs()
{
RealmId = client.RealmId,
ClientId = client.Id,
DefaultScopes = Output.All(scopeNames).Apply(names =>
new[] { "openid", "profile", "email", "roles", "web-origins", }
.Concat(names)),
});
Note that Output.Format is used for string formatting, Output.All is used to convert to Output<string[]> and .Apply is used to transform the array. You can learn more in Inputs and Outputs.
Currently, Pulumi only supports string types for the name of a resource.
Since
var defaultScopeName = $"default-scopes-for-{client.Name.Apply(x => x)}";
is using an output of a resource, defaultScopeName is type Output<string> and can't be used for the resource name in the line,
var defaultScopes = new ClientDefaultScopes(defaultScopeName, new ClientDefaultScopesArgs()
If I'm reading the code correctly, you specify clientName and use it to set client.Name. So, I would just pass in clientName and use that instead of client.Name. And, that should work since it's a basic type all the way through.
I want to unit test my azure function API by sending mock request and response data. But my test is getting failed even if i pass same Json data on both request and response.
TestCode
[TestMethod]
public async Task ClinicReadTestMethod()
{
//Arrange
//var clinicRequest = new
//{
// Id = "1",
// OpenIdProvider = "Google",
// Subject = "Test",
// Name = "Test",
// Address = "Test",
// Email = "Test",
// Phone = "Test",
// Notes = "Test"
//};
var query = new Dictionary<string, StringValues>();
query.Add("openIdProvider", "Google");
query.Add("subject", "Test");
//var body = JsonSerializer.Serialize(clinicRequest);
var logger = Mock.Of<ILogger>();
var client = Mock.Of<CosmosClient>();
ContentResultFactory contentResultFactory = new ContentResultFactory();
//Act
var testFunction = new ClinicReadFunction(contentResultFactory);
var result = await testFunction.Run(TestFactory.HttpRequestSetup(query), client, logger); //fixme
var resultObject = JsonSerializer.Serialize(result as ContentResult);
//Assert
var clinicResponse = new
{
Id = "1",
openIdProvider = "Google",
subject = "Test",
Name = "Test",
Address = "Test",
Email = "Test",
Phone = "Test",
Notes = "Test"
};
var resultBody = JsonSerializer.Serialize(clinicResponse);
//var res = contentResultFactory.CreateContentResult(HttpStatusCode.OK);
Assert.AreEqual(resultBody, resultObject);
}
}
This is how my azure function looks like. It is taking two parameters and returning the response. I have tried to mock the data for unit test still no success. If anyone have idea how to unit test this azure function please let me know.
//AzureFunction
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", Route = "")] HttpRequest req,
[CosmosDB(
databaseName: "",
containerName: "",
Connection = ""
)] CosmosClient client,
ILogger log)
{
string subject = req.Query["sub"];
if (!Enum.TryParse(req.Query["idp"], out OpenIdProvider openIdProvider) || string.IsNullOrEmpty(subject))
{
var message = "";
log.LogWarning();
return _contentResultFactory.CreateContentResult(message, HttpStatusCode.BadRequest);
}
var query = client.GetContainer("", "").GetItemLinqQueryable<Clinic>()
.Where(x => x.OpenIdProvider == openIdProvider && x.Subject == subject);
Clinic clinic;
using (var iterator = query.ToFeedIterator())
clinic = (await iterator.ReadNextAsync()).FirstOrDefault();
if (clinic == null)
{
log.LogWarning();
return _contentResultFactory.CreateContentResult();
}
var response = new ClinicReadResponse(clinic);
return _contentResultFactory.CreateContentResult(response, HttpStatusCode.OK);
}
//TestFactory
public static HttpRequest HttpRequestSetup(Dictionary<string, StringValues> query)
{
var context = new DefaultHttpContext();
var request = context.Request;
request.Query = new QueryCollection(query);
request.Method = "GET";
return request;
}
In both your Clinic objects, your are generating a new GUID for the ID by calling System.Guid.NewGuid. Assuming the JSON generated from each object is the same shape (they will need to be if you want them to match), the values of each ID property will be different. Since the IDs are different, your JSON strings are not equal, therefore causing the failure.
Here is a post that will show you how to manually create a Guid. You can use this to ensure your IDs are of the same value when testing.
Assigning a GUID in C#
I don't know what your Azure Function code looks like, but your test's setup to make an HTTP request tells me you're calling the method tied to the Http Trigger. Consider the scope of what your method is doing; if it is large (or is calling other methods), this will increase the chances of your test breaking as you change the Azure Function over time. To help future-proof your test make sure the method it's calling has a single responsibility. This will make debugging your code easier to do if a change does make your test fail, and will lessen the likelihood of needing to edit your test to accommodate for code changes.
Currently trying to create a user in a Azure AD B2C over the Graph API but keep getting following error: (I did not delete the property name between the '' there is none...)
Code: Request_ResourceNotFound
Message: Resource '' does not exist or one of its queried reference-property objects are not present.
Inner error:
AdditionalData:
date: 2021-10-12T11:22:34
request-id: f45d65b3-61b1-492c-b6cb-fc43bb3805dd
client-request-id: f45d65b3-61b1-492c-b6cb-fc43bb3805dd
ClientRequestId: f45d65b3-61b1-492c-b6cb-fc43bb3805dd
Following Code is used to create the User
IDictionary<string, object> extensionInstance = new Dictionary<string, object>();
extensionInstance.Add($"extension_{AadB2CConfiguration.ExtensionsID}_GUID", guid);
new User
{
DisplayName = username,
AccountEnabled = true,
Identities = new List<ObjectIdentity>
{
new ObjectIdentity()
{
SignInType = valueSignInType,
Issuer = AadB2CConfiguration.TenantId,
IssuerAssignedId = username,
}
},
PasswordPolicies = valuePasswordPolicies,
PasswordProfile = new PasswordProfile()
{
Password = password,
ForceChangePasswordNextSignIn = false,
}
,AdditionalData = extensionInstance
};
GraphClient Method
public static Microsoft.Graph.GraphServiceClient GraphClient
{
get
{
var app = ConfidentialClientApplicationBuilder.Create(AadB2CConfiguration.ClientId)
.WithClientSecret(AadB2CConfiguration.ClientSecret)
.WithAuthority(AadB2CConfiguration.AadB2cGraphAuthority)
.Build();
string[] scopes = new string[] { AadB2CConfiguration.GraphScope };
var token = app.AcquireTokenForClient(scopes).ExecuteAsync().Result;
Microsoft.Graph.GraphServiceClient graphClient = new Microsoft.Graph.GraphServiceClient(AadB2CConfiguration.GraphBaseUrl, new Microsoft.Graph.DelegateAuthenticationProvider(async (requestMessage) =>
{
requestMessage.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("bearer", token.AccessToken);
}));
return graphClient;
}
The User will then be created via the GraphClient
User result = await GraphClient.Users.Request().AddAsync(newUser);
return ResponseMessage(result);
How can I find out which property is not present?
Check that AadB2CConfiguration.ExtensionsID is correct, it should be the application (client) ID of the application the GUID extension property was created against with the - removed.
If it's wrong, and so Graph can't find the application, then you'll get the error Resource '' does not exist or one of its queried reference-property objects are not present.
I'm trying to add some custom attributes to my newly created user objects in my Azure Active Directory with Microsoft Graph.
My code looks like this:
public async Task addUser(string firstName, string lastName)
{
var mail = firstName.ToLower() + "." + lastName.ToLower() + "#mail.com";
var user = new User
{
AccountEnabled = true,
UserPrincipalName = mail,
PasswordProfile = new PasswordProfile
{
ForceChangePasswordNextSignIn = true,
Password = randomString(10) // I wrote a dedicated function here
},
};
var res = await graphServiceClient
.Users
.Request()
.AddAsync(user);
await addExtension(res.Id);
}
public async Task addExtension(string id)
{
var extension = new OpenTypeExtension
{
ExtensionName = "com.test.test", // necessary I guess
AdditionalData = new Dictionary<string, object>
{
{"NewAttribute1", "Batman"},
{"NewAttribute2" , "Spiderman"}
}
};
await graphServiceClient
.Users[id]
.Extensions
.Request()
.AddAsync(extension);
}
The error message, that I'm currently receiving is:
Message: One or more properties contains invalid values.
Inner error:
AdditionalData:
...
I've orientated myself here:
https://learn.microsoft.com/en-us/graph/api/opentypeextension-post-opentypeextension?view=graph-rest-1.0&tabs=http
I hope someone can help,
thanks!
I managed to make my program work. The code above is actually correct and is now doing fine for me, after some adjustments.
I had to read the extension seperately, as seen below. Accessing the user.Extensions property didn't work for me. Hope this helps someone in the future :)
public async Task getUserExtensions(string userID, string extensionID)
{
var extension = await configuration
.configure()
.Users[userID]
.Extensions[extensionID]
.Request()
.GetAsync();
foreach (var item in extension.AdditionalData)
{
Console.WriteLine($"{item.Key} | {item.Value}");
}
}
I need to get all users and their roles(including the roles names and the roles values) in an Azure Application.
What I've done is to retrieve all users and include the appRoleAssignments. The problem is that in the array of appRoleAssignment objects there is only the appRoleId for each role.
Since it would take a lot of http calls to first get all users and then for each appRoleAssignment in each user to retrieve the needed data for the roles by appRoleAssignment Id.
How can I optimize the retrieval of all users and their roles from Azure ?
I think it's possible to use batching and combine the logic for getting all users and their roles(including role name and role value) in to a single API Call, but not sure how to do it.
This is what I have right now:
var users = await graphClient.Users
.Request()
.Expand("appRoleAssignments")
.GetAsync();
As far as I know, there is no way to get user role assignment records with app role names together by one API calling.
I can understand that if you want to get the information above for all of your users, that will be a lot of requests and leads to bad performance. I think you can get all app role information in your directory and get all role assignment records, match them one by one by using AppRoleId.I write a simple console app for you, just try the code below:
using Microsoft.Graph;
using Microsoft.Graph.Auth;
using Microsoft.Identity.Client;
using System;
using System.Collections.Generic;
namespace graphsdktest
{
class Program
{
static void Main(string[] args)
{
var clientId = "";
var clientSecret = "";
var tenantID = "";
IConfidentialClientApplication confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithTenantId(tenantID)
.WithClientSecret(clientSecret)
.Build();
ClientCredentialProvider authenticationProvider = new ClientCredentialProvider(confidentialClientApplication);
var graphClient = new GraphServiceClient(authenticationProvider);
var roleResult = graphClient.ServicePrincipals.Request().Select(app => new { app.AppRoles }).Top(999).GetAsync().GetAwaiter().GetResult();
var appRoleList = new List<AppRole>();
var userRoleAssigments = new List<User>();
var RolePageIterator = PageIterator<ServicePrincipal>
.CreatePageIterator(graphClient, roleResult, (app) =>
{
if (app.AppRoles.GetEnumerator().MoveNext())
{
foreach (var appRole in app.AppRoles)
{
appRoleList.Add(appRole);
}
}
return true;
});
//get all app role information
RolePageIterator.IterateAsync().GetAwaiter().GetResult();
var roleAssigmentResult = graphClient.Users.Request().Expand("appRoleAssignments").GetAsync().GetAwaiter().GetResult();
var RoleAssigmentPageIterator = PageIterator<User>
.CreatePageIterator(graphClient, roleAssigmentResult, (user) =>
{
userRoleAssigments.Add(user);
return true;
});
//get all role assigment records
RoleAssigmentPageIterator.IterateAsync().GetAwaiter().GetResult();
foreach (var user in userRoleAssigments)
{
if (user.AppRoleAssignments.Count > 0)
{
Console.WriteLine("app role assigment of user :" + user.DisplayName);
foreach (var ras in user.AppRoleAssignments)
{
var roleName = (ras.AppRoleId.ToString().Equals("00000000-0000-0000-0000-000000000000") ? "Default Access" : appRoleList.Find(item => item.Id == ras.AppRoleId).DisplayName);
Console.WriteLine("roleID:" + ras.AppRoleId + " appName:" + ras.ResourceDisplayName + " roleName:" + roleName);
}
}
}
}
}
}
Result:
Per my test, the whole request spends about 9 seconds.