Api Resources seed
This commit is contained in:
parent
f3a3b63595
commit
99f4361e2d
5 changed files with 75 additions and 40 deletions
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
|
|
@ -10,6 +10,7 @@
|
||||||
"DESTDIR",
|
"DESTDIR",
|
||||||
"dotnet",
|
"dotnet",
|
||||||
"DOTNET",
|
"DOTNET",
|
||||||
|
"ecdsa",
|
||||||
"envsubst",
|
"envsubst",
|
||||||
"Newtonsoft",
|
"Newtonsoft",
|
||||||
"Npgsql",
|
"Npgsql",
|
||||||
|
|
|
||||||
|
|
@ -64,8 +64,6 @@ public partial class App : Application
|
||||||
DataContext = new MainPageViewModel(client, settings)
|
DataContext = new MainPageViewModel(client, settings)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else
|
|
||||||
throw new NotSupportedException("ApplicationLifetime not supported.");
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,20 @@ public partial class Settings : ObservableObject
|
||||||
internal void Load()
|
internal void Load()
|
||||||
{
|
{
|
||||||
if (Loaded) return;
|
if (Loaded) return;
|
||||||
|
|
||||||
|
// Trust an already-populated Authority: tests pre-fill Settings
|
||||||
|
// with the OIDC stub's random loopback port, and programmatic
|
||||||
|
// callers (CLI flags, integration tests) wire their own. If we
|
||||||
|
// fall through to the disk / embedded read here we'd silently
|
||||||
|
// overwrite their value with the bundled default
|
||||||
|
// (yavsc.pschneider.fr), break the stubbed discovery URL, and
|
||||||
|
// turn a passing login into an invalid_grant.
|
||||||
|
if (!string.IsNullOrWhiteSpace(Authentication?.Authority))
|
||||||
|
{
|
||||||
|
Loaded = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
string configDir = Path.Combine(
|
string configDir = Path.Combine(
|
||||||
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
||||||
"PostIt"
|
"PostIt"
|
||||||
|
|
@ -109,16 +123,14 @@ public partial class Settings : ObservableObject
|
||||||
if (!configFileInfo.Exists)
|
if (!configFileInfo.Exists)
|
||||||
{
|
{
|
||||||
Console.Error.WriteLine($"🩎 Settings file not found at {configFileInfo.FullName}");
|
Console.Error.WriteLine($"🩎 Settings file not found at {configFileInfo.FullName}");
|
||||||
// Only fall back to the embedded default when the in-memory
|
// No user-level config: fall back to the embedded default.
|
||||||
// settings haven't been populated yet. This protects callers
|
// We only get here when Authentication.Authority is empty
|
||||||
// (notably tests) that pre-load Settings with explicit values
|
// (the early-return above) so the redundant guard is gone.
|
||||||
// from being silently overwritten by the bundled default.
|
if (!TryLoadEmbeddedFallback())
|
||||||
if (string.IsNullOrWhiteSpace(this.Authentication?.Authority)
|
|
||||||
&& !TryLoadEmbeddedFallback())
|
|
||||||
{
|
{
|
||||||
Console.Error.WriteLine("🩎 No embedded default settings; running with empty configuration.");
|
Console.Error.WriteLine("🩎 No embedded default settings; running with empty configuration.");
|
||||||
}
|
}
|
||||||
return; // no user settings file
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Console.WriteLine($"🔎 Loading settings from {configFileInfo.FullName}");
|
Console.WriteLine($"🔎 Loading settings from {configFileInfo.FullName}");
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,25 @@
|
||||||
|
|
||||||
|
|
||||||
|
using IdentityServer8.EntityFramework.Entities;
|
||||||
|
|
||||||
public static class Constants
|
public static class Constants
|
||||||
{
|
{
|
||||||
public static readonly string[] BuildInApiScopes = {
|
public static readonly string[] BuildInApiScopes = {
|
||||||
"profile", "openid", "offline_access",
|
"profile", "openid", "offline_access",
|
||||||
"blogs", "admin", "moderation", "performer", "client" };
|
"admin", "moderation", "performer", "client" };
|
||||||
|
|
||||||
|
public static readonly ApiResourceScopeSpecification[] ApiResourcesScopes = {
|
||||||
|
|
||||||
|
new ApiResourceScopeSpecification { ScopeName = "admin", Description = "Admin access" },
|
||||||
|
new ApiResourceScopeSpecification { ScopeName = "moderation", Description = "Moderation access" },
|
||||||
|
new ApiResourceScopeSpecification { ScopeName = "performer", Description = "Performer access" },
|
||||||
|
new ApiResourceScopeSpecification { ScopeName = "client", Description = "Client access" },
|
||||||
|
new ApiResourceScopeSpecification { ScopeName = "blogs", Description = "Blogs access" }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ApiResourceScopeSpecification
|
||||||
|
{
|
||||||
|
public string ScopeName { get; set; }
|
||||||
|
public string Description { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -102,10 +102,10 @@ public static class HostingExtensions
|
||||||
options.ResourcesPath = "Resources";
|
options.ResourcesPath = "Resources";
|
||||||
}).AddDataAnnotationsLocalization();
|
}).AddDataAnnotationsLocalization();
|
||||||
|
|
||||||
services.AddTransient<ITrueEmailSender, MailSender>()
|
services.AddTransient<ITrueEmailSender, MailSender>()
|
||||||
.AddTransient<Microsoft.AspNetCore.Identity.UI.Services.IEmailSender, MailSender>();
|
.AddTransient<Microsoft.AspNetCore.Identity.UI.Services.IEmailSender, MailSender>();
|
||||||
|
|
||||||
services.TryAddSingleton<ISmtpClientFactory, SmtpClientFactory>();
|
services.TryAddSingleton<ISmtpClientFactory, SmtpClientFactory>();
|
||||||
|
|
||||||
|
|
||||||
services.AddTransient<IYavscMessageSender, YavscMessageSender>()
|
services.AddTransient<IYavscMessageSender, YavscMessageSender>()
|
||||||
|
|
@ -342,7 +342,7 @@ public static class HostingExtensions
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Skip the production signing-cert requirement when running with
|
// Skip the production signing-cert requirement when running with
|
||||||
// an in-memory database (test fixtures) or in the Development
|
// an in-memory database (test fixtures) or in the Development
|
||||||
// environment. In those cases IdentityServer8 falls back to
|
// environment. In those cases IdentityServer8 falls back to
|
||||||
// AddDeveloperSigningCredential which mints an ephemeral key
|
// AddDeveloperSigningCredential which mints an ephemeral key
|
||||||
|
|
@ -438,7 +438,7 @@ public static class HostingExtensions
|
||||||
{
|
{
|
||||||
// Validate the cert is readable (used downstream for token
|
// Validate the cert is readable (used downstream for token
|
||||||
// audience/subject validation; signing itself uses the key).
|
// audience/subject validation; signing itself uses the key).
|
||||||
_ = new X509Certificate2(certPath);
|
|
||||||
string keyPem = File.ReadAllText(keyPath);
|
string keyPem = File.ReadAllText(keyPath);
|
||||||
|
|
||||||
// BouncyCastle's PemReader accepts every flavour of unencrypted
|
// BouncyCastle's PemReader accepts every flavour of unencrypted
|
||||||
|
|
@ -471,7 +471,9 @@ public static class HostingExtensions
|
||||||
{
|
{
|
||||||
case RsaPrivateCrtKeyParameters rsa:
|
case RsaPrivateCrtKeyParameters rsa:
|
||||||
{
|
{
|
||||||
|
#pragma warning disable CA1416 // Valider la compatibilité de la plateforme
|
||||||
var rsaDotNet = DotNetUtilities.ToRSA(rsa);
|
var rsaDotNet = DotNetUtilities.ToRSA(rsa);
|
||||||
|
#pragma warning restore CA1416 // Valider la compatibilité de la plateforme
|
||||||
var key = new RsaSecurityKey(rsaDotNet);
|
var key = new RsaSecurityKey(rsaDotNet);
|
||||||
return new SigningCredentials(key, SecurityAlgorithms.RsaSha256);
|
return new SigningCredentials(key, SecurityAlgorithms.RsaSha256);
|
||||||
}
|
}
|
||||||
|
|
@ -534,32 +536,36 @@ public static class HostingExtensions
|
||||||
context.SaveChanges();
|
context.SaveChanges();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var identityResources = context.Set<IdentityServer8.EntityFramework.Entities.IdentityResource>();
|
var identityResources = context.Set<IdentityServer8.EntityFramework.Entities.IdentityResource>();
|
||||||
var apiScopes = context.Set<IdentityServer8.EntityFramework.Entities.ApiScope>();
|
var apiScopes = context.Set<IdentityServer8.EntityFramework.Entities.ApiScope>();
|
||||||
|
|
||||||
// IdentityResources standards
|
// IdentityResources standards
|
||||||
if (!identityResources.Any(r => r.Name == "openid"))
|
if (!identityResources.Any(r => r.Name == "openid"))
|
||||||
{
|
|
||||||
var openid = new IdentityResources.OpenId().ToEntity();
|
|
||||||
identityResources.Add(openid);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!identityResources.Any(r => r.Name == "profile"))
|
|
||||||
{
|
|
||||||
var profile = new IdentityResources.Profile().ToEntity();
|
|
||||||
identityResources.Add(profile);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApiScope custom
|
|
||||||
if (!apiScopes.Any(s => s.Name == "blogs"))
|
|
||||||
{
|
|
||||||
apiScopes.Add(new IdentityServer8.EntityFramework.Entities.ApiScope
|
|
||||||
{
|
{
|
||||||
Name = "blogs",
|
var openid = new IdentityResources.OpenId().ToEntity();
|
||||||
DisplayName = "Yavsc Blogs API",
|
identityResources.Add(openid);
|
||||||
Enabled = true
|
}
|
||||||
});
|
|
||||||
}
|
if (!identityResources.Any(r => r.Name == "profile"))
|
||||||
|
{
|
||||||
|
var profile = new IdentityResources.Profile().ToEntity();
|
||||||
|
identityResources.Add(profile);
|
||||||
|
}
|
||||||
|
foreach (var scope in Constants.ApiResourcesScopes)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (!identityResources.Any(s => s.Name == scope.ScopeName))
|
||||||
|
{
|
||||||
|
identityResources.Add(
|
||||||
|
new IdentityResources.Profile()
|
||||||
|
{
|
||||||
|
Name = scope.ScopeName,
|
||||||
|
DisplayName = scope.Description,
|
||||||
|
Enabled = true
|
||||||
|
}.ToEntity());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
context.SaveChanges();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -720,7 +726,7 @@ public static class HostingExtensions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public async static Task<WebApplication> ConfigurePipeline(this WebApplication app, string staticAssetsManifestPath=null)
|
public async static Task<WebApplication> ConfigurePipeline(this WebApplication app, string staticAssetsManifestPath = null)
|
||||||
{
|
{
|
||||||
ILoggerFactory loggerFactory = app.Services.GetRequiredService<ILoggerFactory>();
|
ILoggerFactory loggerFactory = app.Services.GetRequiredService<ILoggerFactory>();
|
||||||
var logger = loggerFactory.CreateLogger<Program>();
|
var logger = loggerFactory.CreateLogger<Program>();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue