Skip to content
This repository has been archived by the owner on Jul 31, 2024. It is now read-only.

Make aspid profile service more extensible #4237

Merged
merged 3 commits into from
Mar 31, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion samples/Clients/src/MvcCode/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public void ConfigureServices(IServiceCollection services)
options.Scope.Add("profile");
options.Scope.Add("email");
options.Scope.Add("feature1");
options.Scope.Add("transaction:123");
//options.Scope.Add("transaction:123");
//options.Scope.Add("transaction");
options.Scope.Add("offline_access");

Expand Down
24 changes: 10 additions & 14 deletions src/AspNetIdentity/host/Configuration/ClientsConsole.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public static IEnumerable<Client> Get()
ClientId = "client",
ClientSecrets = {new Secret("secret".Sha256())},
AllowedGrantTypes = GrantTypes.ClientCredentials,
AllowedScopes = {"api1", "api2.read_only", IdentityServerConstants.LocalApi.ScopeName}
AllowedScopes = { "feature1", "feature2", IdentityServerConstants.LocalApi.ScopeName}
},

///////////////////////////////////////////
Expand All @@ -48,7 +48,7 @@ public static IEnumerable<Client> Get()
},
AccessTokenType = AccessTokenType.Jwt,
AllowedGrantTypes = GrantTypes.ClientCredentials,
AllowedScopes = {"api1", "api2.read_only"}
AllowedScopes = { "feature1", "feature2" }
},

///////////////////////////////////////////
Expand All @@ -73,7 +73,7 @@ public static IEnumerable<Client> Get()
}
},
AllowedGrantTypes = GrantTypes.ClientCredentials,
AllowedScopes = {"api1", "api2.read_only"}
AllowedScopes = { "feature1", "feature2" }
},

///////////////////////////////////////////
Expand All @@ -84,7 +84,7 @@ public static IEnumerable<Client> Get()
ClientId = "client.custom",
ClientSecrets = {new Secret("secret".Sha256())},
AllowedGrantTypes = {"custom", "custom.nosubject"},
AllowedScopes = {"api1", "api2.read_only"}
AllowedScopes = { "feature1", "feature2" }
},

///////////////////////////////////////////
Expand All @@ -100,8 +100,7 @@ public static IEnumerable<Client> Get()
{
IdentityServerConstants.StandardScopes.OpenId,
"custom.profile",
"api1",
"api2.read_only"
"feature1", "feature2"
}
},

Expand All @@ -118,8 +117,7 @@ public static IEnumerable<Client> Get()
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Email,
"api1",
"api2.read_only"
"feature1", "feature2"
}
},

Expand All @@ -140,8 +138,7 @@ public static IEnumerable<Client> Get()
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
"api1",
"api2.read_only"
"feature1", "feature2"
}
},
///////////////////////////////////////////
Expand All @@ -163,8 +160,7 @@ public static IEnumerable<Client> Get()
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
"api1",
"api2.read_only"
"feature1", "feature2"
}
},

Expand All @@ -177,7 +173,7 @@ public static IEnumerable<Client> Get()
ClientId = "roclient.reference",
ClientSecrets = {new Secret("secret".Sha256())},
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
AllowedScopes = {"api1", "api2.read_only"},
AllowedScopes = {"feature1", "feature2"},
AccessTokenType = AccessTokenType.Reference
},

Expand All @@ -201,7 +197,7 @@ public static IEnumerable<Client> Get()
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
"api1", "api2.read_only", "api2.full_access"
"feature1", "feature2"
}
}
};
Expand Down
9 changes: 5 additions & 4 deletions src/AspNetIdentity/host/Configuration/ClientsWeb.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public static IEnumerable<Client> Get()
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
"api1", "api2.read_only", "api2.full_access"
"feature1", "feature2"
}
},

Expand Down Expand Up @@ -73,7 +73,7 @@ public static IEnumerable<Client> Get()
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
"api1", "api2.read_only"
"feature1", "feature2"
}
},

Expand Down Expand Up @@ -105,7 +105,8 @@ public static IEnumerable<Client> Get()
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
"api1", "api2.read_only"
"feature1", "feature2",
"transaction"
}
},

Expand Down Expand Up @@ -137,7 +138,7 @@ public static IEnumerable<Client> Get()
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
"api1", "api2.read_only"
"feature1", "feature2"
}
}
};
Expand Down
32 changes: 7 additions & 25 deletions src/AspNetIdentity/host/Configuration/Resources.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ public class Resources
// simple version with ctor
new ApiResource("api1", "Some API 1")
{
// this is needed for introspection when using reference tokens
ApiSecrets = { new Secret("secret".Sha256()) },
//// this is needed for introspection when using reference tokens
//ApiSecrets = { new Secret("secret".Sha256()) },

//AllowedSigningAlgorithms = { "RS256", "ES256" }

Scopes = { "api1" }
Scopes = { "feature1" }
},

// expanded version if more control is needed
Expand All @@ -54,35 +54,17 @@ public class Resources
JwtClaimTypes.Email
},

Scopes = { "api2.full_access", "api2.read_only", "api2.internal" }
Scopes = { "feature2", "feature3" }
}
};

public static IEnumerable<ApiScope> ApiScopes = new[]
{
// local API
// todo: dom, should we also use a resource id for this?
new ApiScope(LocalApi.ScopeName),
new ApiScope("api1"),
new ApiScope
{
Name = "api2.full_access",
DisplayName = "Full access to API 2"
},
new ApiScope
{
Name = "api2.read_only",
DisplayName = "Read only access to API 2"
},
new ApiScope
{
Name = "api2.internal",
ShowInDiscoveryDocument = false,
UserClaims =
{
"internal_id"
}
},
new ApiScope("feature1"),
new ApiScope("feature2"),
new ApiScope("feature3"),
new ApiScope
{
Name = "transaction"
Expand Down
104 changes: 92 additions & 12 deletions src/AspNetIdentity/src/ProfileService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using System;
using System.Security.Claims;
using System.Threading.Tasks;

namespace IdentityServer4.AspNetIdentity
Expand Down Expand Up @@ -72,18 +73,47 @@ public virtual async Task GetProfileDataAsync(ProfileDataRequestContext context)
var sub = context.Subject?.GetSubjectId();
if (sub == null) throw new Exception("No sub claim present");

var user = await UserManager.FindByIdAsync(sub);
if (user == null)
await GetProfileDataAsync(context, sub);
}

/// <summary>
/// Called to get the claims for the subject based on the profile request.
/// </summary>
/// <param name="context"></param>
/// <param name="subjectId"></param>
/// <returns></returns>
protected virtual async Task GetProfileDataAsync(ProfileDataRequestContext context, string subjectId)
{
var user = await FindUserAsync(subjectId);
if (user != null)
{
Logger?.LogWarning("No user found matching subject Id: {0}", sub);
await GetProfileDataAsync(context, user);
}
else
{
var principal = await ClaimsFactory.CreateAsync(user);
if (principal == null) throw new Exception("ClaimsFactory failed to create a principal");
}

context.AddRequestedClaims(principal.Claims);
}
/// <summary>
/// Called to get the claims for the user based on the profile request.
/// </summary>
/// <param name="context"></param>
/// <param name="user"></param>
/// <returns></returns>
protected virtual async Task GetProfileDataAsync(ProfileDataRequestContext context, TUser user)
{
var principal = await GetUserClaimsAsync(user);
context.AddRequestedClaims(principal.Claims);
}

/// <summary>
/// Gets the claims for a user.
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
protected virtual async Task<ClaimsPrincipal> GetUserClaimsAsync(TUser user)
{
var principal = await ClaimsFactory.CreateAsync(user);
if (principal == null) throw new Exception("ClaimsFactory failed to create a principal");

return principal;
}

/// <summary>
Expand All @@ -97,13 +127,63 @@ public virtual async Task IsActiveAsync(IsActiveContext context)
var sub = context.Subject?.GetSubjectId();
if (sub == null) throw new Exception("No subject Id claim present");

var user = await UserManager.FindByIdAsync(sub);
await IsActiveAsync(context, sub);
}

/// <summary>
/// Determines if the subject is active.
/// </summary>
/// <param name="context"></param>
/// <param name="subjectId"></param>
/// <returns></returns>
protected virtual async Task IsActiveAsync(IsActiveContext context, string subjectId)
{
var user = await FindUserAsync(subjectId);
if (user != null)
{
await IsActiveAsync(context, user);
}
else
{
context.IsActive = false;
}
}

/// <summary>
/// Determines if the user is active.
/// </summary>
/// <param name="context"></param>
/// <param name="user"></param>
/// <returns></returns>
protected virtual async Task IsActiveAsync(IsActiveContext context, TUser user)
{
context.IsActive = await IsUserActiveAsync(user);
}

/// <summary>
/// Returns if the user is active.
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
public virtual Task<bool> IsUserActiveAsync(TUser user)
{
return Task.FromResult(true);
}

/// <summary>
/// Loads the user by the subject id.
/// </summary>
/// <param name="subjectId"></param>
/// <returns></returns>
protected virtual async Task<TUser> FindUserAsync(string subjectId)
{
var user = await UserManager.FindByIdAsync(subjectId);
if (user == null)
{
Logger?.LogWarning("No user found matching subject Id: {0}", sub);
Logger?.LogWarning("No user found matching subject Id: {subjectId}", subjectId);
}

context.IsActive = user != null;
return user;
}
}
}