Missing Roles Claims in the ASP.NET Core 2 and IdentityServer4 - asp.net-core-2.0

I read https://leastprivilege.com/2017/11/15/missing-claims-in-the-asp-net-core-2-openid-connect-handler/ about mapping custom claims with this code line:
options.ClaimActions.MapUniqueJsonKey("website", "website");
I need to map roles and it works until I have just one role like "User".
options.ClaimActions.MapUniqueJsonKey("role", "role");
The problem is when I have more than one role like "User" and "Superadmin"
That code line throws an exception:
InvalidCastException: Cannot cast Newtonsoft.Json.Linq.JArray to Newtonsoft.Json.Linq.JToken.
Anyone has any idea? Am i wrong something or it could be a bug?

There is a discussion of this issue here:
https://github.com/aspnet/Security/issues/1383
and in the same issue a potential solution to your problem with role:
https://github.com/aspnet/Security/issues/1383#issuecomment-361505163 :
oidcOptions.Events = new OpenIdConnectEvents()
{
OnUserInformationReceived = async context =>
{
// IDS4 returns multiple claim values as JSON arrays, which break the authentication handler
if (context.User.TryGetValue(JwtClaimTypes.Role, out JToken role))
{
var claims = new List<Claim>();
if (role.Type != JTokenType.Array) {
claims.Add(new Claim(JwtClaimTypes.Role, (string)role));
}
else {
foreach (var r in role)
claims.Add(new Claim(JwtClaimTypes.Role, (string)r));
}
var id = context.Principal.Identity as ClaimsIdentity;
id.AddClaims(claims);
}
...
}

Related

How to find user principal name not a member of particular group (which contain "AVD" in its name)

From Azure AD, Are there any ways to find users who does not have a speciifc group assigned ( the name contain %AVD% in it) ?
This is what I have tried:
https://graph.microsoft.com/beta/users?$expand=memberOf
https://graph.microsoft.com/v1.0/users/groups?$search="AVD"
https://graph.microsoft.com/v1.0/users?$select=memberOf eq '%AVD%'
unable to get expected result. That is user principle name not a member of perticuler group which contain "AVD" in its name.
Thanks.
To find users who does not have a specific group assigned, please try the below PowerShell script by Marilee Turscak-MSFT:
$groupids = #("Group_Id1", "Group_Id2")
$userht = #{}
Get-AzureADUser -SearchString 'AVD' | foreach-object {$userht.Add($_.ObjectId,$_)}
ForEach($id in $groupids){
Get-AzureADGroupMember -all $true -ObjectId $id | foreach-object { $userht.Remove($_.ObjectId) }
}
I tried to reproduce the same in my environment like below:
Initially, I executed below command to get the users with Jo in their names.
Get-AzureADUser -SearchString 'Jo'
The user ObjectId "afcfad54xxxxxxxxxxx" is a member of one group like below:
After executing the PowerShell script, the ObjectId with "f1e72629xxxxxxxxxxx" returned as it is not a member of any specified groups:
Firstly, Odata eq doesn't support % to execute fuzzy query, and displayName property not support contains function, so there's actually no solution for graph api to return your idea result.
Per my test, I think this request should work but it didn't execute the filter actually.
https://graph.microsoft.com/v1.0/users?$expand=memberOf($select=displayName;$filter=displayName eq 'xxx';)&$select=displayName,id,memberOf
So I'm afraid you have to execute the api first and then do the filter by your code. And I wrote a sample like this:
using Microsoft.Graph;
using Azure.Identity;
var scopes = new[] { "https://graph.microsoft.com/.default" };
var tenantId = "your_tenant_name.onmicrosoft.com";
var clientId = "azure_ad_client_id";
var clientSecret = "client_secret";
var clientSecretCredential = new ClientSecretCredential(
tenantId, clientId, clientSecret);
var graphClient = new GraphServiceClient(clientSecretCredential, scopes);
//$filter=displayName eq 'xxx' doesn't work
var a = await graphClient.Users.Request().Expand("memberOf($select=displayName;$filter=displayName eq 'xxx')").Select("displayName,id,memberOf").GetAsync();
List<User> users = a.ToList();
List<User> res = new List<User>();
foreach (User user in users)
{
List<DirectoryObject> memberOf = user.MemberOf.ToList();
foreach (DirectoryObject obj in memberOf) {
if (obj.ODataType == "#microsoft.graph.group") {
Group temp = (Group)obj;
if (temp.DisplayName.Contains("Admin")) {
res.Add(user);
continue;
}
}
}
}

Firebase Admin SDK: Set / Merge Custom User Claims

Does Firebase have any trick like { merge: true } to set extra/more custom claims without delete/override the old variables?
Step to reproduce:
admin.auth().setCustomUserClaims(uid, { a: 'value' }) // Run this first
admin.auth().setCustomUserClaims(uid, { b: 'value' }) // Then run this after
Result:
{ b: 'value'}
Expected result:
{ a: 'value', b: 'value' }
Or I did something wrong?
The Firebase documentation for setCustomUserClaims states:
customUserClaims: Object
The developer claims to set. If null is passed, existing custom claims are deleted. Passing a custom claims payload larger than 1000 bytes will throw an error. Custom claims are added to the user's ID token which is transmitted on every authenticated request. For profile non-access related user attributes, use database or other separate storage systems.
It isn't entirely clear from this description, but the statement, "If null is passed, existing custom claims are deleted," provides a hint that the custom claims are completely overwritten with each call to setCustomUserClaims.
Therefore, custom claims need to be set as follows:
claims = {
a: 'value',
b: 'value'
}
admin.auth().setCustomUserClaims(uid, claims)
Workaround: addCustomUserClaims
A helper function could be created to merge in new claims.
async function addCustomUserClaims(uid, claims) {
const user = await admin.auth().getUser(uid)
let updated_claims = user.customClaims || {}
for (let property in claims) {
if (Object.prototype.hasOwnProperty.call(claims, property)) {
updated_claims[property] = claims[property]
}
}
await admin.auth().setCustomUserClaims(uid, updated_claims)
}
Christopher Peisert's answer is correct, but it can be done much more cleanly as
admin.auth().getUser(uid).then(({customClaims: oldClaims}) =>
admin.auth().setCustomUserClaims(uid, { ...oldClaims, b: 'value' }))
If you want to abstract this logic into a function, it can be done as
function addCustomUserClaims(uid, claims) {
return admin.auth().getUser(uid).then(({customClaims}) =>
admin.auth().setCustomUserClaims(uid, { ...customClaims, ...claims }))
}
or equivalently* as
const addCustomUserClaims = (uid, claims) =>
admin.auth().getUser(uid).then(({customClaims}) =>
admin.auth().setCustomUserClaims(uid, { ...customClaims, ...claims }))

AppRoleAssignments Errors With Null

I am trying to add and and AppRoleAssignment using ActiveDirectoryClient. I am able to add the user with no issues to the Azure AD, but when trying to add the Application I receive the the following:
A null value was found for the property named 'id', which has the expected type 'Edm.Guid[Nullable=False]'. The expected type 'Edm.Guid[Nullable=False]' does not allow null values.
I am using the following:
var servicePrincipal = (await
activeDirectoryClient.ServicePrincipals.Where(
s => s.DisplayName == "Tracker.Web").ExecuteAsync()).CurrentPage
.First();
var appRoleAssignment = new AppRoleAssignment
{
Id = Guid.Empty,
ResourceDisplayName = "Tracker.Web",
// Service principal id go here
ResourceId = Guid.Parse(servicePrincipal.ObjectId),
PrincipalType = "User",
PrincipalId = Guid.Parse(newUser.ObjectId)
};
newUser.AppRoleAssignments.Add(appRoleAssignment);
await newUser.UpdateAsync();
I have also tried various other iterations of this with and received the same results. I found this:
Try to adding AppRoleAssignment
But is over 2 years old. Is this still an issue after 2 years or am I doing something wrong? Any help would be appreciated.
Thanks in advance...
The expected type 'Edm.Guid[Nullable=False]' does not allow null values
According to the error information, it indicates that the null value is not allow for Id. We could generate Guid for the AppRoleAssignment Id.
please change Id = Guid.Empty to Id = Guid.NewGuid()
Demo code:
var appRoleAssignment = new AppRoleAssignment
{
Id = Guid.NewGuid(), //null value for Id is not allowed
ResourceDisplayName = "Tracker.Web",
// Service principal id go here
ResourceId = Guid.Parse(servicePrincipal.ObjectId),
PrincipalType = "User",
PrincipalId = Guid.Parse(newUser.ObjectId)
};
For more demo code, you refer to another SO thread.

Not all custom claims add in GetProfileDataAsync appear in identity

I am trying to add some custom claims through ProfileService GetProfileDataAsync. However, some of them come out others don't.
I have custom profile service that's derived from ProfileServic<ApplicationUser>. I override the GetProfileDataAsync as below:
public override async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
await base.GetProfileDataAsync(context);
context.IssuedClaims.Add(new Claim("given_name", "Joe"));
context.IssuedClaims.Add(new Claim("nickname", "jd"));
context.IssuedClaims.Add(new Claim("preferred_username", "Doe"));
context.IssuedClaims.Add(new Claim("sessionLife", "30"));
}
I expect to have four additional claims in my identity. But I just got one: given_name. In my client app configuration, I requested profile scope. I expect the nickname and preferred_username both come out since they are in the profile's resource group. The same as given_name does. nickname and preferred_username just don't come out. Is there some other filters that I need to overcome? Also, note that the last claim: sessionLife is not defined in the IdentityClaims table. Can I get it as well. Do I need to define a new identityClaim and identity resource for it?
For aaronR's request, here is the client side code:
services.AddAuthentication(options => {
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
.AddOpenIdConnect(o =>
{
o.Authority = idpUrl;
o.RequireHttpsMetadata = false;
o.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
o.ClientId = "mvc";
o.ClientSecret = "secret";
o.Scope.Add("email");
o.Scope.Add("api1");
o.Scope.Add("profile"); //Your user profile information(first name, last name, etc.)
o.Scope.Add("offline_access");
o.ResponseType = "code id_token";
o.GetClaimsFromUserInfoEndpoint = true;
o.SaveTokens = true;
});

Drupal: using query string data in Views

i have several moderator roles in my drupal site. the users with this roles can create content of specific content-type called News. let's call the roles the following: role_a, role_b, role_c, ...
now i have a View that shows the last 5 News elements.
my question is how to granulate the News elements in View according to the query string?
i mean on page http://mysite.com/a i want to see only the news that was added by the user with the "a" role. http://mysite.com/b is for the "b"-roled user. etc.
how can i use the query string parameters in the Views filter?
I think you mean you want to use an Argument, rather than the query string. In any case, I don't think Views can handle rolenames by default (it can handle role IDs just fine), so you'll have to modify your view query in order to achieve what you want.
First, add User: Roles as an argument in your View. Then, in a custom module, implement hook_views_query_alter() and modify the query by replacing the rolename with its role ID.
function MYMODULE_views_query_alter(&$view, &$query) {
if ($view->name == 'my_view') {
$rolename = '';
foreach ($query->where as $where_index => $where) {
// find the role ID clause
$clause_index = array_search('users_roles.rid = %d', $where['clauses']);
if ($clause_index !== FALSE) {
// found it, so get the rolename
$rolename = $where['args'][$clause_index];
break;
}
}
// if the rolename argument was found
if (!empty($rolename)) {
// get the role ID
$user_roles = user_roles();
$rid = array_search($rolename, $user_roles);
// if the role exists, then replace the argument
if ($rid !== FALSE) {
$query->where[$where_index]['args'][$clause_index] = $rid;
}
}
}
}
So, for example, if your url is http://mysite.com/a, then it will look up the ID of role 'a', then find all nodes by an author with that role. It will also take the actual role ID - for example, if the ID of role 'a' is 10, then http://mysite.com/10 will also return the same result.
If you want it only to look up rolenames, you can modify the hook to fail when it doesn't find the role (just make $rid = 0 and you shouldn't get any results).
function MYMODULE_views_query_alter(&$view, &$query) {
if ($view->name == 'my_view') {
$rolename = '';
foreach ($query->where as $where_index => $where) {
// find the role ID clause
$clause_index = array_search('users_roles.rid = %d', $where['clauses']);
if ($clause_index !== FALSE) {
// found it, so get the rolename
$rolename = $where['args'][$clause_index];
break;
}
}
// if the rolename argument was found
if (!empty($rolename)) {`enter code here`
// get the role ID
$user_roles = user_roles();
$rid = array_search($rolename, $user_roles);
// if the role exists, then replace the argument
if ($rid !== FALSE) {
$query->where[$where_index]['args'][$clause_index] = $rid;
}
}
}
}

Resources