diff --git a/Program.cs b/Program.cs index ed30455..d231730 100644 --- a/Program.cs +++ b/Program.cs @@ -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( + 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 -{ - 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 { 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 = + Name = idpConfig.Scope.Name, + Description = idpConfig.Scope.Description, + ClaimMappers = idpConfig.Scope.ClaimMappers.Select(cm => new ScopeClaimMapper { - new ScopeClaimMapper() - { - IncludeInAccessToken = true, - TokenClaimJsonType = TokenClaimJsonTypes.STRING, - TargetClaimPath = "roles", - 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", - }, - - }, + IncludeInAccessToken = true, + TokenClaimJsonType = Enum.Parse(cm.TokenClaimJsonType, ignoreCase: true), + TargetClaimPath = cm.TargetClaimPath, + MapperType = MappingRuleTypes.USERATTRIBUTE, + 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 { 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 + +// ── 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 }; + +// ── Build users ─────────────────────────────────────────────────────────────── +var users = idpConfig.Users.Select(u => { - ClientBuilder - .BuildUserAgentClient("212C9DB96C2A4B6DA0AFDB2222F6EEAA.bighand.com", null, realm, new[] { "http://localhost:4200/loggedin" }) - .SetClientName("BI Portal") - .AddScope(scopes.ToArray()) - .AddRefreshToken() - .Build(), -}; + 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); -clients[0].IsPublic = true; -clients[0].Realms.Add(realm); + foreach (var claim in u.Claims) + userBuilder = userBuilder.AddClaim(claim.Key, claim.Value); -realm.Clients.Add(clients[0]); + 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 Users); + +record ApiConfig( + string Name, + string Audience); + +record ScopeConfig( + string Name, + string Description, + List ClaimMappers); + +record ClaimMapperConfig( + string TargetClaimPath, + string SourceUserAttribute, + string TokenClaimJsonType, + bool IsMultiValued = false); + +record ClientConfig( + string ClientId, + string Name, + List RedirectUris, + bool IsPublic = true); + +record UserConfig( + string Login, + string Password, + string Name, + string Email, + string Firstname, + bool EmailVerified, + List Roles, + Dictionary Claims, + List Consents); + +record ConsentConfig( + string Realm, + string ClientId, + string Scope); diff --git a/SimpleIdp.csproj b/SimpleIdp.csproj index 233ef2b..e69bc2b 100644 --- a/SimpleIdp.csproj +++ b/SimpleIdp.csproj @@ -10,6 +10,11 @@ + + + PreserveNewest + + AccountsResource.resx diff --git a/config/idp-config.json b/config/idp-config.json new file mode 100644 index 0000000..c70f965 --- /dev/null +++ b/config/idp-config.json @@ -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" + } + ] + } + ] +}