Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e4c25bfaa7 | |||
| 0a585dd5a2 | |||
| 54e888e240 | |||
| 3b353c6660 | |||
| ca6c565b2b | |||
| 4ac1f94249 | |||
| 8cc3f8e308 | |||
| 1250160b91 | |||
| b6c52c502d | |||
| a3eaf0f5b7 | |||
| b18a89b087 |
@@ -0,0 +1,4 @@
|
|||||||
|
FROM mcr.microsoft.com/dotnet/sdk:8.0-alpine
|
||||||
|
|
||||||
|
# Optional: install Docker CLI (not Docker daemon)
|
||||||
|
RUN apk add --no-cache docker-cli bash git
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
// The Dev Container format allows you to configure your environment. At the heart of it
|
||||||
|
// is a Docker image or Dockerfile which controls the tools available in your environment.
|
||||||
|
//
|
||||||
|
// See https://aka.ms/devcontainer.json for more information.
|
||||||
|
{
|
||||||
|
"name": "Simple Id Server",
|
||||||
|
// Use "image": "mcr.microsoft.com/devcontainers/base:ubuntu-24.04",
|
||||||
|
// instead of the build to use a pre-built image.
|
||||||
|
"build": {
|
||||||
|
"context": ".",
|
||||||
|
"dockerfile": "Dockerfile"
|
||||||
|
},
|
||||||
|
"runArgs": [
|
||||||
|
"--privileged"
|
||||||
|
],
|
||||||
|
"mounts": [
|
||||||
|
"source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind"
|
||||||
|
],
|
||||||
|
"remoteUser": "root"
|
||||||
|
// Features add additional features to your environment. See https://containers.dev/features
|
||||||
|
// Beware: features are not supported on all platforms and may have unintended side-effects.
|
||||||
|
// "features": {
|
||||||
|
// "ghcr.io/devcontainers/features/docker-in-docker": {
|
||||||
|
// "moby": false
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
.vsCode
|
.vsCode
|
||||||
bin
|
bin
|
||||||
obj
|
obj
|
||||||
|
lib
|
||||||
*.vssscc
|
*.vssscc
|
||||||
*.vspscc
|
*.vspscc
|
||||||
*.user
|
*.user
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
|
||||||
|
stages: # Define the stages of the pipeline.
|
||||||
|
- build
|
||||||
|
|
||||||
|
docker-build:
|
||||||
|
stage: build
|
||||||
|
image: docker:latest
|
||||||
|
tags:
|
||||||
|
- shared
|
||||||
|
services:
|
||||||
|
- name: docker:dind
|
||||||
|
alias: docker
|
||||||
|
variables:
|
||||||
|
DOCKER_DRIVER: overlay2
|
||||||
|
DOCKER_HOST: tcp://docker:2375
|
||||||
|
DOCKER_TLS_CERTDIR: ""
|
||||||
|
before_script:
|
||||||
|
- docker info
|
||||||
|
script:
|
||||||
|
- docker build -t $CI_REGISTRY_IMAGE:latest -t $CI_REGISTRY_IMAGE:${CI_PIPELINE_IID} -t mareklesko/simpleidp:latest -t mareklesko/simpleidp:${CI_PIPELINE_IID} -f Dockerfile .
|
||||||
|
- echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin $CI_REGISTRY
|
||||||
|
- docker push $CI_REGISTRY_IMAGE:latest
|
||||||
|
- docker push $CI_REGISTRY_IMAGE:${CI_PIPELINE_IID}
|
||||||
|
- docker login -u mareklesko --password $DOCKER_HUB_PASSWORD
|
||||||
|
- docker push mareklesko/simpleidp:latest
|
||||||
|
- docker push mareklesko/simpleidp:${CI_PIPELINE_IID}
|
||||||
|
only:
|
||||||
|
- dev
|
||||||
|
- main
|
||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
# =========================
|
||||||
|
# Build stage
|
||||||
|
# =========================
|
||||||
|
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
||||||
|
ARG BUILD_CONFIGURATION=Release
|
||||||
|
WORKDIR /src
|
||||||
|
|
||||||
|
# Copy everything (simple + reliable for single-project Razor Pages apps)
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Restore & publish (self-contained trimming can be added later if desired)
|
||||||
|
RUN dotnet restore
|
||||||
|
RUN dotnet publish -c $BUILD_CONFIGURATION -o /app/publish --no-restore
|
||||||
|
|
||||||
|
# =========================
|
||||||
|
# Runtime stage
|
||||||
|
# =========================
|
||||||
|
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS final
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# (Optional) Create non-root user for better security
|
||||||
|
RUN useradd -m appuser
|
||||||
|
ENV ASPNETCORE_URLS=http://+:8080 \
|
||||||
|
ASPNETCORE_ENVIRONMENT=Production \
|
||||||
|
DOTNET_RUNNING_IN_CONTAINER=true
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
# Copy published output
|
||||||
|
COPY --from=build /app/publish ./
|
||||||
|
|
||||||
|
# Switch to non-root
|
||||||
|
USER appuser
|
||||||
|
|
||||||
|
# Start the Razor Pages app
|
||||||
|
ENTRYPOINT ["dotnet", "SimpleIdp.dll"]
|
||||||
+157
-63
@@ -1,106 +1,200 @@
|
|||||||
// 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.
|
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Security.Claims;
|
using System.Text.Json;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using SimpleIdServer.IdServer.Builders;
|
using SimpleIdServer.IdServer.Builders;
|
||||||
using SimpleIdServer.IdServer.Config;
|
using SimpleIdServer.IdServer.Config;
|
||||||
using SimpleIdServer.IdServer.Domains;
|
using SimpleIdServer.IdServer.Domains;
|
||||||
|
using SimpleIdServer.IdServer.Helpers;
|
||||||
|
using SimpleIdServer.IdServer.Stores;
|
||||||
|
|
||||||
var corsPolicyName = "AllowAll";
|
var corsPolicyName = "AllowAll";
|
||||||
|
|
||||||
var users = new List<User>
|
// ── 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
|
||||||
{
|
{
|
||||||
UserBuilder
|
Realms = { realm },
|
||||||
.Create("administrator", "password", "Administrator")
|
Name = idpConfig.Api.Name,
|
||||||
.SetEmail("adm@mail.com")
|
Audience = idpConfig.Api.Audience
|
||||||
.SetFirstname("Administrator")
|
|
||||||
.AddRole("BI.PORTAL_ADMIN")
|
|
||||||
.AddRole("BI.TENANT_ADMIN")
|
|
||||||
.AddClaim("tid", "cbaa13c2-e95b-470a-bbcb-18911d5a6025")
|
|
||||||
.Build(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var api = ApiResourceBuilder.Create("urn:bighand:api:bi:portal", "BI Portal API").Build();
|
// ── Build custom scope with claim mappers ─────────────────────────────────────
|
||||||
|
var biScope = new Scope
|
||||||
var clients = new List<Client>
|
|
||||||
{
|
|
||||||
ClientBuilder
|
|
||||||
.BuildUserAgentClient("foo", null, null, new[] { "http://localhost:4200/loggedin" })
|
|
||||||
.AddScope(new Scope("openid"), new Scope("profile"), new Scope("offline_access"))
|
|
||||||
.AddRefreshToken()
|
|
||||||
.Build(),
|
|
||||||
};
|
|
||||||
|
|
||||||
var scopes = new List<Scope> { ScopeBuilder.CreateRoleScope(clients[0], "bi.portal", "").Build() };
|
|
||||||
var biScope = new Scope()
|
|
||||||
{
|
{
|
||||||
|
Realms = { realm },
|
||||||
ApiResources = { api },
|
ApiResources = { api },
|
||||||
Name = "bi.portal",
|
Protocol = ScopeProtocols.OAUTH,
|
||||||
Clients = { clients[0] },
|
Type = ScopeTypes.APIRESOURCE,
|
||||||
Description = "BI Portal Scope",
|
Name = idpConfig.Scope.Name,
|
||||||
ClaimMappers =
|
Description = idpConfig.Scope.Description,
|
||||||
|
ClaimMappers = idpConfig.Scope.ClaimMappers.Select(cm => new ScopeClaimMapper
|
||||||
{
|
{
|
||||||
new ScopeClaimMapper()
|
IncludeInAccessToken = true,
|
||||||
{
|
TokenClaimJsonType = Enum.Parse<TokenClaimJsonTypes>(cm.TokenClaimJsonType, ignoreCase: true),
|
||||||
IncludeInAccessToken = true,
|
TargetClaimPath = cm.TargetClaimPath,
|
||||||
TokenClaimJsonType = TokenClaimJsonTypes.STRING,
|
MapperType = MappingRuleTypes.USERATTRIBUTE,
|
||||||
TargetClaimPath = "role",
|
IsMultiValued = cm.IsMultiValued,
|
||||||
MapperType = MappingRuleTypes.USERATTRIBUTE,
|
SourceUserAttribute = cm.SourceUserAttribute,
|
||||||
SourceUserAttribute = "role",
|
SourceUserProperty = cm.SourceUserAttribute,
|
||||||
SourceUserProperty = "role",
|
}).ToList(),
|
||||||
},
|
|
||||||
new ScopeClaimMapper()
|
|
||||||
{
|
|
||||||
IncludeInAccessToken = true,
|
|
||||||
TokenClaimJsonType = TokenClaimJsonTypes.STRING,
|
|
||||||
TargetClaimPath = "tid",
|
|
||||||
MapperType = MappingRuleTypes.USERATTRIBUTE,
|
|
||||||
SourceUserAttribute = "tid",
|
|
||||||
SourceUserProperty = "tid",
|
|
||||||
},
|
|
||||||
new ScopeClaimMapper()
|
|
||||||
{
|
|
||||||
IncludeInAccessToken = true,
|
|
||||||
TokenClaimJsonType = TokenClaimJsonTypes.STRING,
|
|
||||||
TargetClaimPath = "email",
|
|
||||||
MapperType = MappingRuleTypes.USERATTRIBUTE,
|
|
||||||
SourceUserAttribute = "email",
|
|
||||||
SourceUserProperty = "email",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
clients[0].Scopes.Add(biScope);
|
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);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
builder.Services.AddCors(options =>
|
builder.Services.AddCors(options =>
|
||||||
{
|
{
|
||||||
options.AddPolicy(
|
options.AddPolicy(
|
||||||
name: corsPolicyName,
|
name: corsPolicyName,
|
||||||
policy =>
|
policy => policy.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader());
|
||||||
{
|
|
||||||
policy.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
builder
|
builder
|
||||||
.AddSidIdentityServer()
|
.AddSidIdentityServer()
|
||||||
.AddDeveloperSigningCredential()
|
.AddDeveloperSigningCredential()
|
||||||
|
.AddInMemoryRealms(new[] { realm }.ToList())
|
||||||
.AddInMemoryUsers(users)
|
.AddInMemoryUsers(users)
|
||||||
.AddInMemoryClients(clients)
|
.AddInMemoryClients(clients)
|
||||||
.AddInMemoryScopes([biScope])
|
.AddInMemoryScopes(scopes)
|
||||||
.AddInMemoryLanguages(DefaultLanguages.All)
|
.AddInMemoryLanguages(DefaultLanguages.All)
|
||||||
.AddPwdAuthentication(true);
|
.AddPwdAuthentication(true);
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
|
using (var sc = app.Services.CreateScope())
|
||||||
|
{
|
||||||
|
var s = sc.ServiceProvider.GetRequiredService<IApiResourceRepository>();
|
||||||
|
s.Add(api);
|
||||||
|
}
|
||||||
|
|
||||||
app.Services.SeedData();
|
app.Services.SeedData();
|
||||||
app.UseSid();
|
app.UseSid();
|
||||||
app.UseCors(corsPolicyName);
|
app.UseCors(corsPolicyName);
|
||||||
|
|
||||||
await app.RunAsync();
|
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);
|
||||||
|
|||||||
@@ -10,6 +10,11 @@
|
|||||||
<PackageReference Include="SimpleIdServer.IdServer" Version="6.0.*-*" />
|
<PackageReference Include="SimpleIdServer.IdServer" Version="6.0.*-*" />
|
||||||
<PackageReference Include="Microsoft.Web.LibraryManager.Build" Version="2.1.175" />
|
<PackageReference Include="Microsoft.Web.LibraryManager.Build" Version="2.1.175" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include="config\idp-config.json">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Update="Resources\AccountsResource.Designer.cs">
|
<Compile Update="Resources\AccountsResource.Designer.cs">
|
||||||
<DependentUpon>AccountsResource.resx</DependentUpon>
|
<DependentUpon>AccountsResource.resx</DependentUpon>
|
||||||
|
|||||||
@@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user