Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding generation of accessors for equivalent claims #1829

Open
wants to merge 16 commits into
base: dev
Choose a base branch
from

Conversation

jmprieur
Copy link
Contributor

@jmprieur jmprieur commented Mar 24, 2022

Adding generation of accessors for claims independently of AAD token version of the token
See #1800

Developer experience:

  1. Add a using:

    using Microsoft.IdentityModel.Tokens.Jwt,
  2. In the code, on a ClaimsPrincipal or an IdentityPrincipal instance, you will get access to extension methods that retrieve the claims in the instance independently of them being long or short claims (which depends on the token version: v1.0 or v2.0 tokens). Developers will also get completion telling them if the claim is safe or not for use in authorization policies, and what privacy category they belong to
    image

See also the description of the accessors:

ClaimsAccessorDoc.md

Design

This PR a bit different from others: It uses code generation
The claims are described in ClaimsKnowledge.tti (Text Template Include file)
This include file is included in:

  • ClaimTypeMapping.tt, which generates ClaimTypeMapping.cs (which was already existing),
  • ClaimsTypeAccessor.tt, which generates ClaimsTypeAccessor.gen.cs, which contains a partial class implementation of ClaimsTypeAccessor, the other partial being in ClaimsTypeAccessor.cs
  • ClaimsAccessorDoc.tt, which generates ClaimsAccessorDoc.md

Copy link
Contributor Author

@jmprieur jmprieur left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a few explanations

@@ -0,0 +1,115 @@
<#@ template debug="false" hostspecific="false" language="C#" #>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a T4 file that generates the ClaimTypeMapping.cs from the ClaimsKnowledge.tti.

@@ -0,0 +1,141 @@
<#+
class ClaimsKnowledge
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class is not product code, but control code to generate the product code.
This data structure describes the claims information.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not understanding why we need a template model.
I have used .tt's when using typescript that will generate different jscript mapped to a release.

How does the tti help us?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It avoids writing 5000 lines of code

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jmprieur i will educate myself about this technique.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can spend 10 minutes with you @brentschmaltz if you wish. Also you might want to install this Visual Studio extension: https://marketplace.visualstudio.com/items?itemName=bricelam.T4Language

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jmprieur Would this require users to install this extension to build?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no. not at all. It's an editor extension so that the syntax coloring help understanding the T4 code

</None>
<None Update="ClaimTypeAccessor.tt">
<LastGenOutput>ClaimTypeAccessor.gen.cs</LastGenOutput>
<Generator>TextTemplatingFileGenerator</Generator>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When you save the ClaimTypeAccessor.tt this generates the ClaimTypeAccessor.gen.cs which contain a partial class implementation of ClaimTypeAccessor.cs

new ClaimsKnowledge("Group", true, false, true, "", "\"http://schemas.xmlsoap.org/claims/Group\"", "\"group\""),
new ClaimsKnowledge("GroupsId", true, false, true, "", "ClaimTypes.GroupSid", "\"groupsid\""),
new ClaimsKnowledge("IdTyp", false, false, true, "", "\"idtyp\""),
new ClaimsKnowledge("Idp", false, false, true, "", "\"http://schemas.microsoft.com/identity/claims/identityprovider\"", "\"idp\""),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do we know 'idp' (or any other claim) is or is not 'usableForAuthorization' ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be. I've started a conversation with a few people about what claim should be. (same for the privacy)

@jmprieur jmprieur changed the title Adding generation of accessors for claims independently of Adding generation of accessors for equivalent claims Mar 25, 2022
string | GetTenantId | True | OII | "tid"<BR/> "http://schemas.microsoft.com/identity/claims/tenantid"
string | GetAcr | False | | "acr"<BR/> "http://schemas.microsoft.com/claims/authnclassreference"
string | GetAdfs1Email | False | EUPI | "adfs1email"<BR/> "http://schemas.xmlsoap.org/claims/EmailAddress"
string | GetAdfs1Upn | False | EUPI | "adfs1upn"<BR/> "http://schemas.xmlsoap.org/claims/UPN"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetAdfs1Upn

Wouldn't this cause confusion as it references a claim like upn which will almost always be present in tokens issued to users?
It might give n incorrect impression that the user authenticated with ADFS, even if they didn't.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know what this claim is. Maybe it's only available when users logged with UPN

string | GetObjectId | True | EUPI | "oid"<BR/> "http://schemas.microsoft.com/identity/claims/objectidentifier"
IEnumerable<string> | GetScopes | True | | "scp"<BR/> "http://schemas.microsoft.com/identity/claims/scope"
string | GetTenantId | True | OII | "tid"<BR/> "http://schemas.microsoft.com/identity/claims/tenantid"
string | GetAcr | False | | "acr"<BR/> "http://schemas.microsoft.com/claims/authnclassreference"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acr

there is also acrs, and these two acr and acrs cause enough confusion. So my suggestion would be to use full name if possible , like GetAuthncClassReference

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kalyankrishna1: do you mean GetAuthenticationClassReference?

IEnumerable<string> | GetRole | True | | "role"<BR/> "roles"<BR/> ClaimTypes.Role
string | GetSid | False | | "sid"
string | GetUpn | False | | "upn"<BR/> ClaimTypes.Upn
string | GetWinaccountname | False | | "winaccountname"<BR/> ClaimTypes.WindowsAccountName

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetWinaccountname

EUPI?

@@ -0,0 +1,196 @@
<#@ template debug="false" hostspecific="false" language="C#" #>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple of general concerns:

  1. some of the categories seems to be AAD specific, IdentityModel has always tried to be neutral?
  2. can users set their own categories?
  3. there is no general spec for the category of claims, how can one say claim 'a' is in a category in general?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Do we want to move this into SAL?
  2. The categories are privacy categories (well defined. could almost be an enum). For 3) will follow-up offline

using System.Security.Claims;
using System.Linq;

namespace System.IdentityModel.Aad
Copy link
Member

@brentschmaltz brentschmaltz Apr 6, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ns is System.IdentityModel.Aad, but folder and file names are Microsoft.IdentityModel.*

All our new work uses Microsoft.IdentityModel.*

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank @brentschmaltz: I fixed the generators (which fixed the code)
a38cc2f

@brentschmaltz
Copy link
Member

@jmprieur this statement needs some adjustment:

that retrieve the claims in the instance independently of them being long or short claims (which depends on the token version: v1.0 or v2.0 tokens)

long or short claim types is not a result of token version, it is a result of the claims mapping in the JwtSecurityTokenHandler.
AAD v1.0 and v2.0 have different claims that represent the same concept see access_tokens..

For example: v1.appid <-> v2.azp, v1.appidacr <-> v2. azpacr

string | GetAuthenticationContextClassReference | False | | acr<BR/> http://schemas.microsoft.com/claims/authnclassreference
string | GetAdfs1Email | False | EUPI | adfs1email<BR/> http://schemas.xmlsoap.org/claims/EmailAddress
string | GetAdfs1Upn | False | EUPI | adfs1upn<BR/> http://schemas.xmlsoap.org/claims/UPN
string | GetAmr | False | | amr<BR/> http://schemas.microsoft.com/claims/authnmethodsreferences
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

amr is definitely used in Authorization as this defines how the subject authenticated with the identity provider.


Type | Accessor | Used for authorization | Privacy | Claims
-- | -- | -- | -- | --
string | GetActor | False | | actort<BR/> http://schemas.xmlsoap.org/ws/2009/09/identity/claims/actor
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think actor is PII as the return is another token.

/// <returns>All the values of the claims or its equivalents.</returns>
public static IEnumerable<string> GetAllEquivalentClaims(this ClaimsPrincipal user, string claimType)
{
if (user is null)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have been using the coding style:

_ = user ?? throw new ArgumentNullException();

{
if (user is null)
{
throw new ArgumentNullException(nameof(user));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How will this plug into our logging model?


if (string.IsNullOrEmpty(claimType))
{
throw new ArgumentException($"'{nameof(claimType)}' cannot be null or empty.", nameof(claimType));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have been using a single location for log messages, each with an identifier that allows for quickly identifying the assembly and location of the issue.
see: LogMessages

/// <summary>
/// Verifies that the token has any of the claims required.
/// </summary>
/// <param name="user">claims identity</param>
Copy link
Member

@brentschmaltz brentschmaltz Apr 11, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this parameter is a ClaimsPrincipal.
we may want to write:

/// <param name="claimsPrincipal"> the <see cref="ClaimsPrincipal"/> to check for a claim 
/// of one of the requiredClaims.</param>

This comment applies to methods below.

/// <param name="user">Claims principal</param>
/// <param name="claimType">Claims type</param>
/// <returns></returns>
public static string GetEquivalentClaim(this ClaimsPrincipal user, string claimType)
Copy link
Member

@brentschmaltz brentschmaltz Apr 11, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is not returning a Claim, but a Claim.Value (which may be a complex type, bool, int, null).
In this case, the user will not know the type of the Value as that is contained in Claim.ValueType.

One option is to return a Claim.
Our JWT -> Claim logic is here.

JsonWebToken has added TryGetPayloadValue to try and deal with this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants