Seed ApiScopes for ApiResourcesScopes, align PostIt client
EnsureDefaultApplicationScopes was inserting every entry of Constants.ApiResourcesScopes (admin, moderation, performer, client, blogs) into the IdentityResources table, as Profile-derived rows. That made them visible to /connect/discovery's scopes_supported under the identity section, but no API resource would ever issue a token bearing them — IdentityServer then rejected clients that requested any of these scopes with 'invalid_scope' at the token endpoint. The most visible casualty was PostIt, a public PKCE client whose postit-settings.json asks for scope=openid profile offline_access blogs. 'blogs' is the scope that gates the Yavsc.Blogs deployment (blogs.pschneider.fr), so the login flow died at the token step. Fix: - Constants.ApiResourcesScopes entries are now seeded as ApiScope rows (with Name + DisplayName). IdentityResources stays limited to the actual OpenID Connect profile (openid, profile). - EnsureDefaultConfiguration gains an idempotent AlignPostItClientScopes pass that adds any missing scope from PostItScopes to the existing 'postit' client's AllowedScopes. Nothing is removed — manual revocation stays manual. Existing live databases pick up both changes on next startup: missing ApiScope rows are inserted, and the postit client's ClientScope rows catch up.
This commit is contained in:
parent
99f4361e2d
commit
3744017127
1 changed files with 75 additions and 11 deletions
|
|
@ -539,7 +539,10 @@ public static class HostingExtensions
|
|||
var identityResources = context.Set<IdentityServer8.EntityFramework.Entities.IdentityResource>();
|
||||
var apiScopes = context.Set<IdentityServer8.EntityFramework.Entities.ApiScope>();
|
||||
|
||||
// IdentityResources standards
|
||||
// IdentityResources standards (OpenId + Profile only).
|
||||
// Application-defined API scopes from Constants.ApiResourcesScopes
|
||||
// are NOT identity resources — they belong to the ApiScopes table
|
||||
// and are seeded as such further down.
|
||||
if (!identityResources.Any(r => r.Name == "openid"))
|
||||
{
|
||||
var openid = new IdentityResources.OpenId().ToEntity();
|
||||
|
|
@ -551,18 +554,29 @@ public static class HostingExtensions
|
|||
var profile = new IdentityResources.Profile().ToEntity();
|
||||
identityResources.Add(profile);
|
||||
}
|
||||
foreach (var scope in Constants.ApiResourcesScopes)
|
||||
{
|
||||
|
||||
if (!identityResources.Any(s => s.Name == scope.ScopeName))
|
||||
// Application-defined API scopes (admin, moderation, performer,
|
||||
// client, blogs, …). Seeded into ApiScopes, not IdentityResources:
|
||||
// these gate access to API resources (e.g. the Yavsc.Blogs
|
||||
// deployment requires the "blogs" scope) and must therefore be
|
||||
// discoverable through /connect/discovery's
|
||||
// scopes_supported of type resource, not identity.
|
||||
//
|
||||
// NOTE: prior versions inserted these into IdentityResources,
|
||||
// which made them visible to /connect/authorize but unfulfillable
|
||||
// (no API resource recognises an identity-scoped consent as
|
||||
// access to a downstream resource). Clients like PostIt that
|
||||
// request one of these scopes were rejected with "invalid_scope"
|
||||
// at the token endpoint. Keep this in ApiScopes.
|
||||
foreach (var scopeSpec in Constants.ApiResourcesScopes)
|
||||
{
|
||||
if (!apiScopes.Any(s => s.Name == scopeSpec.ScopeName))
|
||||
{
|
||||
identityResources.Add(
|
||||
new IdentityResources.Profile()
|
||||
{
|
||||
Name = scope.ScopeName,
|
||||
DisplayName = scope.Description,
|
||||
Enabled = true
|
||||
}.ToEntity());
|
||||
apiScopes.Add(new IdentityServer8.EntityFramework.Entities.ApiScope
|
||||
{
|
||||
Name = scopeSpec.ScopeName,
|
||||
DisplayName = scopeSpec.Description,
|
||||
});
|
||||
}
|
||||
}
|
||||
context.SaveChanges();
|
||||
|
|
@ -618,6 +632,16 @@ public static class HostingExtensions
|
|||
return;
|
||||
}
|
||||
|
||||
// The PostIt client was already seeded in a previous run.
|
||||
// Make sure its AllowedScopes still match what the server
|
||||
// actually exposes — for instance, "blogs" only exists as an
|
||||
// ApiScope since we fixed the seed (see EnsureDefaultApplicationScopes
|
||||
// above). Without this pass, a client created before the fix
|
||||
// would still ask for a scope the server no longer recognises
|
||||
// and IdentityServer would answer "invalid_scope" at the token
|
||||
// endpoint. Idempotent: missing scopes are added, nothing is
|
||||
// removed (manual revocation stays manual).
|
||||
AlignPostItClientScopes(context, existingClient);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -674,6 +698,46 @@ public static class HostingExtensions
|
|||
context.SaveChanges();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Idempotent reconciliation of the PostIt client's AllowedScopes
|
||||
/// against the scopes the server actually publishes. Adds any missing
|
||||
/// scope as a ClientScope row; never removes anything (revocation is a
|
||||
/// manual operation, not a seed concern). Called on every startup from
|
||||
/// <see cref="EnsureDefaultConfiguration"/> so a client created before
|
||||
/// a scope was introduced (or before the seed was corrected) catches up
|
||||
/// automatically.
|
||||
/// </summary>
|
||||
private static void AlignPostItClientScopes(
|
||||
DbContext context,
|
||||
IdentityServer8.EntityFramework.Entities.Client postitClient
|
||||
)
|
||||
{
|
||||
var clientScopes = context.Set<ClientScope>();
|
||||
var existingScopeNames = clientScopes
|
||||
.Where(s => s.Client == postitClient || s.ClientId == postitClient.Id)
|
||||
.Select(s => s.Scope)
|
||||
.ToHashSet();
|
||||
|
||||
bool changed = false;
|
||||
foreach (var scope in PostItScopes)
|
||||
{
|
||||
if (existingScopeNames.Contains(scope))
|
||||
continue;
|
||||
|
||||
clientScopes.Add(new IdentityServer8.EntityFramework.Entities.ClientScope
|
||||
{
|
||||
Client = postitClient,
|
||||
Scope = scope
|
||||
});
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (changed)
|
||||
{
|
||||
context.SaveChanges();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compose the full set of redirect URIs for the PostIt client. The base
|
||||
/// URIs cover the standalone desktop/mobile flows; the value of
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue