e4c25bfaa7
Move all hard-coded identity server settings (API resource, scope, client, users/roles) from Program.cs into config/idp-config.json and add parsing logic. The config file is copied to the output directory so it works for local development and is available at /app/config/idp-config.json in the Docker image, making it straightforward to override via a volume mount. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
201 lines
6.8 KiB
C#
201 lines
6.8 KiB
C#
// Copyright (c) SimpleIdServer. All rights reserved.
|
|
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text.Json;
|
|
using Microsoft.AspNetCore.Builder;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using SimpleIdServer.IdServer.Builders;
|
|
using SimpleIdServer.IdServer.Config;
|
|
using SimpleIdServer.IdServer.Domains;
|
|
using SimpleIdServer.IdServer.Helpers;
|
|
using SimpleIdServer.IdServer.Stores;
|
|
|
|
var corsPolicyName = "AllowAll";
|
|
|
|
// ── Load configuration ────────────────────────────────────────────────────────
|
|
var configPath = Path.Combine(AppContext.BaseDirectory, "config", "idp-config.json");
|
|
if (!File.Exists(configPath))
|
|
throw new FileNotFoundException(
|
|
$"IDP configuration file not found at '{configPath}'. " +
|
|
"Mount your config file to /app/config/idp-config.json when running in Docker.");
|
|
|
|
var idpConfig = JsonSerializer.Deserialize<IdpConfig>(
|
|
File.ReadAllText(configPath),
|
|
new JsonSerializerOptions { PropertyNameCaseInsensitive = true })
|
|
?? throw new InvalidOperationException("Failed to deserialize IDP configuration.");
|
|
|
|
// ── Build realm ───────────────────────────────────────────────────────────────
|
|
var realm = RealmBuilder.CreateMaster().Build();
|
|
|
|
// ── Build API resource ────────────────────────────────────────────────────────
|
|
var api = new ApiResource
|
|
{
|
|
Realms = { realm },
|
|
Name = idpConfig.Api.Name,
|
|
Audience = idpConfig.Api.Audience
|
|
};
|
|
|
|
// ── Build custom scope with claim mappers ─────────────────────────────────────
|
|
var biScope = new Scope
|
|
{
|
|
Realms = { realm },
|
|
ApiResources = { api },
|
|
Protocol = ScopeProtocols.OAUTH,
|
|
Type = ScopeTypes.APIRESOURCE,
|
|
Name = idpConfig.Scope.Name,
|
|
Description = idpConfig.Scope.Description,
|
|
ClaimMappers = idpConfig.Scope.ClaimMappers.Select(cm => new ScopeClaimMapper
|
|
{
|
|
IncludeInAccessToken = true,
|
|
TokenClaimJsonType = Enum.Parse<TokenClaimJsonTypes>(cm.TokenClaimJsonType, ignoreCase: true),
|
|
TargetClaimPath = cm.TargetClaimPath,
|
|
MapperType = MappingRuleTypes.USERATTRIBUTE,
|
|
IsMultiValued = cm.IsMultiValued,
|
|
SourceUserAttribute = cm.SourceUserAttribute,
|
|
SourceUserProperty = cm.SourceUserAttribute,
|
|
}).ToList(),
|
|
};
|
|
|
|
api.Scopes.Add(biScope);
|
|
api.Realms.Add(realm);
|
|
|
|
var scopes = new List<Scope>
|
|
{
|
|
new Scope("openid") { Realms = { realm } },
|
|
new Scope("profile") { Realms = { realm } },
|
|
new Scope("offline_access") { Realms = { realm } },
|
|
biScope
|
|
};
|
|
|
|
// ── Build client ──────────────────────────────────────────────────────────────
|
|
var client = ClientBuilder
|
|
.BuildUserAgentClient(
|
|
idpConfig.Client.ClientId,
|
|
null,
|
|
realm,
|
|
idpConfig.Client.RedirectUris.ToArray())
|
|
.SetClientName(idpConfig.Client.Name)
|
|
.AddScope(scopes.ToArray())
|
|
.AddRefreshToken()
|
|
.Build();
|
|
|
|
client.IsPublic = idpConfig.Client.IsPublic;
|
|
client.Realms.Add(realm);
|
|
|
|
var clients = new List<Client> { client };
|
|
|
|
// ── Build users ───────────────────────────────────────────────────────────────
|
|
var users = idpConfig.Users.Select(u =>
|
|
{
|
|
var userBuilder = UserBuilder
|
|
.Create(u.Login, u.Password, u.Name)
|
|
.SetEmail(u.Email)
|
|
.SetFirstname(u.Firstname)
|
|
.SetEmailVerified(u.EmailVerified);
|
|
|
|
foreach (var role in u.Roles)
|
|
userBuilder = userBuilder.AddRole(role);
|
|
|
|
foreach (var claim in u.Claims)
|
|
userBuilder = userBuilder.AddClaim(claim.Key, claim.Value);
|
|
|
|
foreach (var consent in u.Consents)
|
|
userBuilder = userBuilder.AddConsent(consent.Realm, consent.ClientId, consent.Scope);
|
|
|
|
return userBuilder.Build();
|
|
}).ToList();
|
|
|
|
// ── Wire up realm relationships ───────────────────────────────────────────────
|
|
foreach (var user in users)
|
|
{
|
|
var rUser = new RealmUser { Realm = realm, User = user };
|
|
user.Realms.Add(rUser);
|
|
realm.Users.Add(rUser);
|
|
}
|
|
|
|
realm.Clients.Add(client);
|
|
realm.ApiResources.Add(api);
|
|
scopes.ForEach(s => realm.Scopes.Add(s));
|
|
|
|
// ── Configure services ────────────────────────────────────────────────────────
|
|
var builder = WebApplication.CreateBuilder(args);
|
|
|
|
builder.Services.AddCors(options =>
|
|
{
|
|
options.AddPolicy(
|
|
name: corsPolicyName,
|
|
policy => policy.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader());
|
|
});
|
|
|
|
builder
|
|
.AddSidIdentityServer()
|
|
.AddDeveloperSigningCredential()
|
|
.AddInMemoryRealms(new[] { realm }.ToList())
|
|
.AddInMemoryUsers(users)
|
|
.AddInMemoryClients(clients)
|
|
.AddInMemoryScopes(scopes)
|
|
.AddInMemoryLanguages(DefaultLanguages.All)
|
|
.AddPwdAuthentication(true);
|
|
|
|
var app = builder.Build();
|
|
|
|
using (var sc = app.Services.CreateScope())
|
|
{
|
|
var s = sc.ServiceProvider.GetRequiredService<IApiResourceRepository>();
|
|
s.Add(api);
|
|
}
|
|
|
|
app.Services.SeedData();
|
|
app.UseSid();
|
|
app.UseCors(corsPolicyName);
|
|
|
|
await app.RunAsync();
|
|
|
|
// ── Configuration model ───────────────────────────────────────────────────────
|
|
|
|
record IdpConfig(
|
|
ApiConfig Api,
|
|
ScopeConfig Scope,
|
|
ClientConfig Client,
|
|
List<UserConfig> Users);
|
|
|
|
record ApiConfig(
|
|
string Name,
|
|
string Audience);
|
|
|
|
record ScopeConfig(
|
|
string Name,
|
|
string Description,
|
|
List<ClaimMapperConfig> ClaimMappers);
|
|
|
|
record ClaimMapperConfig(
|
|
string TargetClaimPath,
|
|
string SourceUserAttribute,
|
|
string TokenClaimJsonType,
|
|
bool IsMultiValued = false);
|
|
|
|
record ClientConfig(
|
|
string ClientId,
|
|
string Name,
|
|
List<string> RedirectUris,
|
|
bool IsPublic = true);
|
|
|
|
record UserConfig(
|
|
string Login,
|
|
string Password,
|
|
string Name,
|
|
string Email,
|
|
string Firstname,
|
|
bool EmailVerified,
|
|
List<string> Roles,
|
|
Dictionary<string, string> Claims,
|
|
List<ConsentConfig> Consents);
|
|
|
|
record ConsentConfig(
|
|
string Realm,
|
|
string ClientId,
|
|
string Scope);
|