Compare commits

..

2 Commits

Author SHA1 Message Date
administrator 4bbe881285 Merge pull request 'Extract IDP configuration to external JSON file' (#1) from claude/update-set-up-logic into main 2026-03-22 11:26:31 +00:00
Marek Lesko e4c25bfaa7 Extract IDP configuration to external JSON file
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>
2026-03-22 11:22:33 +00:00
3 changed files with 194 additions and 87 deletions
+122 -84
View File
@@ -1,9 +1,10 @@
// Copyright (c) SimpleIdServer. All rights reserved.
// 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.Security.Claims;
using System.Text.Json;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using SimpleIdServer.IdServer.Builders;
@@ -14,83 +15,48 @@ 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();
//api.Audience = "urn:bighand:api:bi:portal";
var users = new List<User>
{
UserBuilder
.Create("administrator", "password", "Administrator")
.SetEmail("adm@mail.com")
.SetFirstname("Administrator")
.AddRole("BI.PORTAL_ADMIN")
.AddRole("BI.TENANT_ADMIN")
.AddClaim("tid", "cbaa13c2-e95b-470a-bbcb-18911d5a6025")
.AddClaim("aud","urn:bighand:api:bi:portal")
.AddConsent("master","212C9DB96C2A4B6DA0AFDB2222F6EEAA.bighand.com","bi.portal")
.SetEmailVerified(true)
.Build(),
};
var rUser = new RealmUser
{
Realm = realm,
User = users[0],
};
users[0].Realms.Add(rUser);
// ── Build API resource ────────────────────────────────────────────────────────
var api = new ApiResource
{
Realms = { realm },
Name = "BI Portal API",
Audience = "urn:bighand:api:bi:portal"
Name = idpConfig.Api.Name,
Audience = idpConfig.Api.Audience
};
//var scopes = new List<Scope> { ScopeBuilder.CreateRoleScope(clients[0], "bi.portal", "").Build() };
var biScope = new Scope()
// ── Build custom scope with claim mappers ─────────────────────────────────────
var biScope = new Scope
{
Realms = { realm },
ApiResources = { api },
Protocol = ScopeProtocols.OAUTH,
Type = ScopeTypes.APIRESOURCE,
Name = "bi.portal",
Description = "BI Portal Scope",
ClaimMappers =
{
new ScopeClaimMapper()
Name = idpConfig.Scope.Name,
Description = idpConfig.Scope.Description,
ClaimMappers = idpConfig.Scope.ClaimMappers.Select(cm => new ScopeClaimMapper
{
IncludeInAccessToken = true,
TokenClaimJsonType = TokenClaimJsonTypes.STRING,
TargetClaimPath = "roles",
TokenClaimJsonType = Enum.Parse<TokenClaimJsonTypes>(cm.TokenClaimJsonType, ignoreCase: true),
TargetClaimPath = cm.TargetClaimPath,
MapperType = MappingRuleTypes.USERATTRIBUTE,
IsMultiValued=true,
SourceUserAttribute = "role",
SourceUserProperty = "role",
},
new ScopeClaimMapper()
{
IncludeInAccessToken = true,
TokenClaimJsonType = TokenClaimJsonTypes.STRING,
TargetClaimPath = "tid",
MapperType = MappingRuleTypes.USERATTRIBUTE,
SourceUserAttribute = "tid",
SourceUserProperty = "tid",
},
new ScopeClaimMapper()
{
IncludeInAccessToken = true,
TokenClaimJsonType = TokenClaimJsonTypes.STRING,
TargetClaimPath = "upn",
MapperType = MappingRuleTypes.USERATTRIBUTE,
SourceUserAttribute = "email",
SourceUserProperty = "email",
},
},
IsMultiValued = cm.IsMultiValued,
SourceUserAttribute = cm.SourceUserAttribute,
SourceUserProperty = cm.SourceUserAttribute,
}).ToList(),
};
api.Scopes.Add(biScope);
@@ -99,41 +65,69 @@ 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 } },
new Scope("profile") { Realms = { realm } },
new Scope("offline_access") { Realms = { realm } },
biScope
};
var clients = new List<Client>
{
ClientBuilder
.BuildUserAgentClient("212C9DB96C2A4B6DA0AFDB2222F6EEAA.bighand.com", null, realm, new[] { "http://localhost:4200/loggedin" })
.SetClientName("BI Portal")
// ── Build client ──────────────────────────────────────────────────────────────
var client = ClientBuilder
.BuildUserAgentClient(
idpConfig.Client.ClientId,
null,
realm,
idpConfig.Client.RedirectUris.ToArray())
.SetClientName(idpConfig.Client.Name)
.AddScope(scopes.ToArray())
.AddRefreshToken()
.Build(),
};
.Build();
client.IsPublic = idpConfig.Client.IsPublic;
client.Realms.Add(realm);
clients[0].IsPublic = true;
clients[0].Realms.Add(realm);
var clients = new List<Client> { client };
realm.Clients.Add(clients[0]);
// ── 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);
realm.Users.Add(rUser);
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();
}
);
policy => policy.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader());
});
builder
@@ -146,7 +140,6 @@ builder
.AddInMemoryLanguages(DefaultLanguages.All)
.AddPwdAuthentication(true);
var app = builder.Build();
using (var sc = app.Services.CreateScope())
@@ -160,3 +153,48 @@ 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);
+5
View File
@@ -10,6 +10,11 @@
<PackageReference Include="SimpleIdServer.IdServer" Version="6.0.*-*" />
<PackageReference Include="Microsoft.Web.LibraryManager.Build" Version="2.1.175" />
</ItemGroup>
<ItemGroup>
<Content Include="config\idp-config.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<Compile Update="Resources\AccountsResource.Designer.cs">
<DependentUpon>AccountsResource.resx</DependentUpon>
+64
View File
@@ -0,0 +1,64 @@
{
"realm": "master",
"api": {
"name": "BI Portal API",
"audience": "urn:bighand:api:bi:portal"
},
"scope": {
"name": "bi.portal",
"description": "BI Portal Scope",
"claimMappers": [
{
"targetClaimPath": "roles",
"sourceUserAttribute": "role",
"tokenClaimJsonType": "STRING",
"isMultiValued": true
},
{
"targetClaimPath": "tid",
"sourceUserAttribute": "tid",
"tokenClaimJsonType": "STRING",
"isMultiValued": false
},
{
"targetClaimPath": "upn",
"sourceUserAttribute": "email",
"tokenClaimJsonType": "STRING",
"isMultiValued": false
}
]
},
"client": {
"clientId": "212C9DB96C2A4B6DA0AFDB2222F6EEAA.bighand.com",
"name": "BI Portal",
"redirectUris": [
"http://localhost:4200/loggedin"
],
"isPublic": true
},
"users": [
{
"login": "administrator",
"password": "password",
"name": "Administrator",
"email": "adm@mail.com",
"firstname": "Administrator",
"emailVerified": true,
"roles": [
"BI.PORTAL_ADMIN",
"BI.TENANT_ADMIN"
],
"claims": {
"tid": "cbaa13c2-e95b-470a-bbcb-18911d5a6025",
"aud": "urn:bighand:api:bi:portal"
},
"consents": [
{
"realm": "master",
"clientId": "212C9DB96C2A4B6DA0AFDB2222F6EEAA.bighand.com",
"scope": "bi.portal"
}
]
}
]
}