Compare commits
6 commits
bb4ad4fcb8
...
c354cbab78
| Author | SHA1 | Date | |
|---|---|---|---|
| c354cbab78 | |||
| b1e8d37f21 | |||
| 1b0933c215 | |||
| 6e4b68c60d | |||
| 7fa55a68c9 | |||
| f3bb039d2f |
16 changed files with 153 additions and 111 deletions
22
contrib/bruno/blog post.yml
Normal file
22
contrib/bruno/blog post.yml
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
info:
|
||||||
|
name: blog post
|
||||||
|
type: http
|
||||||
|
seq: 2
|
||||||
|
|
||||||
|
http:
|
||||||
|
method: POST
|
||||||
|
url: "{{Blogs}}/api/v1/blog"
|
||||||
|
body:
|
||||||
|
type: json
|
||||||
|
data: |-
|
||||||
|
{
|
||||||
|
"Title": "lkijlk",
|
||||||
|
"Article": "test"
|
||||||
|
}
|
||||||
|
auth: inherit
|
||||||
|
|
||||||
|
settings:
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
followRedirects: true
|
||||||
|
maxRedirects: 5
|
||||||
|
|
@ -25,7 +25,7 @@ request:
|
||||||
credentials:
|
credentials:
|
||||||
clientId: postit
|
clientId: postit
|
||||||
placement: basic_auth_header
|
placement: basic_auth_header
|
||||||
scope: openid blogs
|
scope: openid blogs profile
|
||||||
pkce: {}
|
pkce: {}
|
||||||
tokenConfig:
|
tokenConfig:
|
||||||
id: credentials
|
id: credentials
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,8 @@ namespace Yavsc
|
||||||
|
|
||||||
public const string StreamingPath = "/api/stream/put";
|
public const string StreamingPath = "/api/stream/put";
|
||||||
|
|
||||||
|
public static string NameClaimType = "http://schemas.microsoft.com/ws/2008/06/identity/claims/name";
|
||||||
|
|
||||||
public static string RoleClaimType = "http://schemas.microsoft.com/ws/2008/06/identity/claims/role";
|
public static string RoleClaimType = "http://schemas.microsoft.com/ws/2008/06/identity/claims/role";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ namespace Yavsc.Abstract.Identity
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public static string AvatarSrc(IApplicationUser? user)
|
public static string AvatarSrc(IApplicationUser? user)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(user?.UserName))
|
if (user==null || string.IsNullOrWhiteSpace(user?.UserName))
|
||||||
return YavscConstants.DefaultAvatar;
|
return YavscConstants.DefaultAvatar;
|
||||||
return $"{YavscConstants.AvatarsPath}/{user!.UserName}.s.png";
|
return $"{YavscConstants.AvatarsPath}/{user!.UserName}.s.png";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ namespace Yavsc.Blogs.Controllers
|
||||||
return new StatusCodeResult(StatusCodes.Status204NoContent);
|
return new StatusCodeResult(StatusCodes.Status204NoContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST: api/BlogApi
|
// POST: api/v1/blog
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public IActionResult PostBlog([FromBody] Models.Blog.BlogPost blog)
|
public IActionResult PostBlog([FromBody] Models.Blog.BlogPost blog)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -37,22 +37,16 @@ internal class Program
|
||||||
.AddYavscCors(builder.Configuration)
|
.AddYavscCors(builder.Configuration)
|
||||||
.AddControllers();
|
.AddControllers();
|
||||||
String authority = builder.Configuration.GetValue<string>("Site:Authority");
|
String authority = builder.Configuration.GetValue<string>("Site:Authority");
|
||||||
String audience = builder.Configuration.GetValue<string>("Site:Audience");
|
|
||||||
if (string.IsNullOrEmpty(authority))
|
if (string.IsNullOrEmpty(authority))
|
||||||
{
|
{
|
||||||
throw new Exception("Site:Authority is not configured in appsettings.json");
|
throw new Exception("Site:Authority is not configured in appsettings.json");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// AuthenticationBuilder
|
// AuthenticationBuilder
|
||||||
services.AddAuthentication("Bearer")
|
services.AddAuthentication("Bearer")
|
||||||
.AddYavscJwtBearer(builder.Configuration,
|
.AddYavscJwtBearer(builder.Configuration);
|
||||||
options =>
|
|
||||||
{
|
|
||||||
options.Authority = authority;
|
|
||||||
options.Audience = builder.Configuration.GetValue<string>
|
|
||||||
("Site:Audience");
|
|
||||||
});
|
|
||||||
|
|
||||||
// DbContextBuilder
|
// DbContextBuilder
|
||||||
services.AddDbContext<ApplicationDbContext>(options =>
|
services.AddDbContext<ApplicationDbContext>(options =>
|
||||||
|
|
@ -100,7 +94,7 @@ internal class Program
|
||||||
.UseCors("default")
|
.UseCors("default")
|
||||||
;
|
;
|
||||||
app.Services.GetRequiredService<ILoggerFactory>().CreateLogger("Program")
|
app.Services.GetRequiredService<ILoggerFactory>().CreateLogger("Program")
|
||||||
.LogInformation($"Yavsc.Blogs started, Authority is '{authority}', Audience is '{audience}'");
|
.LogInformation($"Yavsc.Blogs started, Authority is '{authority}''");
|
||||||
app.MapControllers();
|
app.MapControllers();
|
||||||
app.MapIdentityApi<ApplicationUser>().RequireAuthorization("BlogScope")
|
app.MapIdentityApi<ApplicationUser>().RequireAuthorization("BlogScope")
|
||||||
.WithHttpLogging(Microsoft.AspNetCore.HttpLogging.HttpLoggingFields.All)
|
.WithHttpLogging(Microsoft.AspNetCore.HttpLogging.HttpLoggingFields.All)
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
"Description": "A collection of blogs about software development, technology, and programming.",
|
"Description": "A collection of blogs about software development, technology, and programming.",
|
||||||
"Keywords": "software development, technology, programming, coding, blogs",
|
"Keywords": "software development, technology, programming, coding, blogs",
|
||||||
"Authority": "https://localhost:5001",
|
"Authority": "https://localhost:5001",
|
||||||
|
"Audience": ["blogs"],
|
||||||
"CorsAllowedOrigins": [
|
"CorsAllowedOrigins": [
|
||||||
"https://localhost:5005"
|
"https://localhost:5005"
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -8,32 +8,32 @@ namespace Yavsc.Org.Tests
|
||||||
[Trait("regression", "oui")]
|
[Trait("regression", "oui")]
|
||||||
public class Remoting : BaseTestContext, IClassFixture<WebServerFixture>
|
public class Remoting : BaseTestContext, IClassFixture<WebServerFixture>
|
||||||
{
|
{
|
||||||
|
private readonly ITestOutputHelper _output;
|
||||||
|
|
||||||
public Remoting(WebServerFixture serverFixture, ITestOutputHelper output)
|
public Remoting(WebServerFixture serverFixture, ITestOutputHelper output)
|
||||||
: base(output, serverFixture)
|
: base(output, serverFixture)
|
||||||
{
|
{
|
||||||
|
_output = output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ObtainServiceToken()
|
public async Task ObtainServiceToken()
|
||||||
{
|
{
|
||||||
var serverUrl = _serverFixture.SiteSettings.Authority;
|
var serverUrl = GetServerUrl();
|
||||||
if (string.IsNullOrEmpty(serverUrl))
|
var cancellationToken = TestContext.Current.CancellationToken;
|
||||||
throw new InvalidOperationException("No HTTPS server address found");
|
|
||||||
|
|
||||||
HttpClient client = NewHttpClient();
|
HttpClient client = NewHttpClient();
|
||||||
var disco = await client.GetDiscoveryDocumentAsync(serverUrl);
|
var tokenEndpoint = await ResolveTokenEndpointAsync(client, serverUrl, cancellationToken);
|
||||||
if (disco.IsError) throw new Exception(disco.Error);
|
|
||||||
|
|
||||||
var response = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
|
var response = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
|
||||||
{
|
{
|
||||||
Address = disco.TokenEndpoint,
|
Address = tokenEndpoint,
|
||||||
ClientId = _serverFixture.TestClientId,
|
ClientId = RequireNonEmpty(_serverFixture.TestClientId, nameof(_serverFixture.TestClientId)),
|
||||||
ClientSecret = _serverFixture.TestClientSecret,
|
ClientSecret = RequireNonEmpty(_serverFixture.TestClientSecret, nameof(_serverFixture.TestClientSecret)),
|
||||||
Scope = "test",
|
Scope = "test",
|
||||||
GrantType = "client_credentials"
|
GrantType = "client_credentials"
|
||||||
});
|
}, cancellationToken);
|
||||||
if (response.IsError) throw new Exception(response.Error);
|
if (response.IsError) throw new Exception(response.Error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -45,27 +45,25 @@ namespace Yavsc.Org.Tests
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ObtainResourceOwnerPasswordToken()
|
public async Task ObtainResourceOwnerPasswordToken()
|
||||||
{
|
{
|
||||||
var serverUrl = _serverFixture.SiteSettings.Authority;
|
var serverUrl = GetServerUrl();
|
||||||
if (string.IsNullOrEmpty(serverUrl))
|
var cancellationToken = TestContext.Current.CancellationToken;
|
||||||
throw new InvalidOperationException("No HTTPS server address found");
|
|
||||||
|
|
||||||
var client = NewHttpClient();
|
var client = NewHttpClient();
|
||||||
var disco = await client.GetDiscoveryDocumentAsync(serverUrl);
|
var tokenEndpoint = await ResolveTokenEndpointAsync(client, serverUrl, cancellationToken);
|
||||||
if (disco.IsError) throw new Exception(disco.Error);
|
|
||||||
|
|
||||||
var response = await client.RequestPasswordTokenAsync(new PasswordTokenRequest
|
var response = await client.RequestPasswordTokenAsync(new PasswordTokenRequest
|
||||||
{
|
{
|
||||||
Address = disco.TokenEndpoint,
|
Address = tokenEndpoint,
|
||||||
ClientId = _serverFixture.TestClientId,
|
ClientId = RequireNonEmpty(_serverFixture.TestClientId, nameof(_serverFixture.TestClientId)),
|
||||||
ClientSecret = _serverFixture.TestClientSecret,
|
ClientSecret = RequireNonEmpty(_serverFixture.TestClientSecret, nameof(_serverFixture.TestClientSecret)),
|
||||||
UserName = _serverFixture.TestingUserName,
|
UserName = RequireNonEmpty(_serverFixture.TestingUserName, nameof(_serverFixture.TestingUserName)),
|
||||||
Password = _serverFixture.TestingUserPassword,
|
Password = RequireNonEmpty(_serverFixture.TestingUserPassword, nameof(_serverFixture.TestingUserPassword)),
|
||||||
Scope = "test",
|
Scope = "test",
|
||||||
Parameters =
|
Parameters =
|
||||||
{
|
{
|
||||||
{ "acr_values", "tenant:custom_account_store1 foo bar quux" }
|
{ "acr_values", "tenant:custom_account_store1 foo bar quux" }
|
||||||
}
|
}
|
||||||
});
|
}, cancellationToken);
|
||||||
|
|
||||||
if (response.IsError) throw new Exception(response.Error);
|
if (response.IsError) throw new Exception(response.Error);
|
||||||
|
|
||||||
|
|
@ -76,6 +74,36 @@ namespace Yavsc.Org.Tests
|
||||||
return new object[][] { new object[] { "testuser", "test" } };
|
return new object[][] { new object[] { "testuser", "test" } };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<string> ResolveTokenEndpointAsync(HttpClient client, string serverUrl, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var disco = await client.GetDiscoveryDocumentAsync(serverUrl, cancellationToken);
|
||||||
|
if (!disco.IsError && !string.IsNullOrWhiteSpace(disco.TokenEndpoint))
|
||||||
|
{
|
||||||
|
return disco.TokenEndpoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some full-suite runs intermittently return 500 on the OIDC
|
||||||
|
// discovery document while /connect/token remains available.
|
||||||
|
var fallback = new Uri(new Uri(serverUrl), "/connect/token").ToString();
|
||||||
|
_output.WriteLine($"WARNING: OIDC discovery failed ({disco.Error}). Fallback token endpoint: {fallback}");
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetServerUrl()
|
||||||
|
{
|
||||||
|
return RequireNonEmpty(_serverFixture.SiteSettings?.Authority, "SiteSettings.Authority");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string RequireNonEmpty(string? value, string name)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(value))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Missing required test setting: {name}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class BypassSslValidationHandler : HttpClientHandler
|
internal class BypassSslValidationHandler : HttpClientHandler
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ using Yavsc.Server.Helpers;
|
||||||
|
|
||||||
// For more information on enabling Web API for empty projects, visit http://go.microsoft.com/fwlink/?LinkID=397860
|
// For more information on enabling Web API for empty projects, visit http://go.microsoft.com/fwlink/?LinkID=397860
|
||||||
|
|
||||||
namespace Yavsc.Controllers
|
namespace Yavsc.Org.Controllers
|
||||||
{
|
{
|
||||||
public class BlogSpotController : Controller
|
public class BlogSpotController : Controller
|
||||||
{
|
{
|
||||||
|
|
@ -28,7 +28,7 @@ namespace Yavsc.Controllers
|
||||||
BlogSpotService blogSpotService)
|
BlogSpotService blogSpotService)
|
||||||
{
|
{
|
||||||
_context = context;
|
_context = context;
|
||||||
_logger = loggerFactory.CreateLogger<AccountController>();
|
_logger = loggerFactory.CreateLogger<BlogSpotController>();
|
||||||
_authorizationService = authorizationService;
|
_authorizationService = authorizationService;
|
||||||
_localisationOptions = localisationOptions.Value;
|
_localisationOptions = localisationOptions.Value;
|
||||||
this.blogSpotService = blogSpotService;
|
this.blogSpotService = blogSpotService;
|
||||||
|
|
@ -76,7 +76,7 @@ namespace Yavsc.Controllers
|
||||||
return View(blog);
|
return View(blog);
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (AuthorizationFailureException ex)
|
catch (AuthorizationFailureException)
|
||||||
{
|
{
|
||||||
return Challenge();
|
return Challenge();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -157,8 +157,7 @@ public static class HostingExtensions
|
||||||
|
|
||||||
|
|
||||||
services.AddAuthentication("Bearer")
|
services.AddAuthentication("Bearer")
|
||||||
.AddYavscJwtBearer(builder.Configuration,
|
.AddYavscJwtBearer(builder.Configuration);
|
||||||
configure: o => o.Audience = builder.Configuration.GetSection("Site")["ExternalUrl"]);
|
|
||||||
|
|
||||||
services.AddTransient<RoleManager<IdentityRole>>();
|
services.AddTransient<RoleManager<IdentityRole>>();
|
||||||
services.AddTransient<IRoleStore<IdentityRole>, RoleStore<IdentityRole, ApplicationDbContext>>();
|
services.AddTransient<IRoleStore<IdentityRole>, RoleStore<IdentityRole, ApplicationDbContext>>();
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,24 @@
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.DotNet.Scaffolding.Shared;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Yavsc;
|
using Yavsc;
|
||||||
using Yavsc.Helpers;
|
|
||||||
using Yavsc.Models;
|
using Yavsc.Models;
|
||||||
using Yavsc.Models.Blog;
|
using Yavsc.Models.Blog;
|
||||||
using Yavsc.Server.Exceptions;
|
using Yavsc.Server.Exceptions;
|
||||||
using Yavsc.Server.Helpers;
|
using Yavsc.Server.Helpers;
|
||||||
using Yavsc.Services;
|
using Yavsc.Services;
|
||||||
using Yavsc.ViewModels.Auth;
|
using Yavsc.ViewModels.Auth;
|
||||||
using Yavsc.Abstract.Helpers;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
|
|
||||||
public class BlogSpotService
|
|
||||||
|
[Obsolete]
|
||||||
|
public class OldBlogSpotService
|
||||||
{
|
{
|
||||||
private readonly ApplicationDbContext _context;
|
private readonly ApplicationDbContext _context;
|
||||||
private readonly IAuthorizationService _authorizationService;
|
private readonly IAuthorizationService _authorizationService;
|
||||||
private readonly IFileSystemAuthManager fileSystemAuthManager;
|
private readonly IFileSystemAuthManager fileSystemAuthManager;
|
||||||
|
|
||||||
public BlogSpotService(ApplicationDbContext context,
|
public OldBlogSpotService(ApplicationDbContext context,
|
||||||
IAuthorizationService authorizationService,
|
IAuthorizationService authorizationService,
|
||||||
IFileSystemAuthManager fileSystemAuthManager)
|
IFileSystemAuthManager fileSystemAuthManager)
|
||||||
{
|
{
|
||||||
|
|
@ -93,13 +91,13 @@ public class BlogSpotService
|
||||||
public async Task<BlogPostEditViewModel> GetPostForEdition(ClaimsPrincipal user, long blogPostId)
|
public async Task<BlogPostEditViewModel> GetPostForEdition(ClaimsPrincipal user, long blogPostId)
|
||||||
{
|
{
|
||||||
var blog = await _context.BlogSpot.Include(x => x.Author).Include(x => x.ACL).SingleAsync(m => m.Id == blogPostId);
|
var blog = await _context.BlogSpot.Include(x => x.Author).Include(x => x.ACL).SingleAsync(m => m.Id == blogPostId);
|
||||||
var auth = await _authorizationService.AuthorizeAsync(user, blog, new EditPermission());
|
var auth = await _authorizationService.AuthorizeAsync(user, blog, new EditPermission());
|
||||||
if (!auth.Succeeded)
|
if (!auth.Succeeded)
|
||||||
{
|
{
|
||||||
throw new AuthorizationFailureException(auth);
|
throw new AuthorizationFailureException(auth);
|
||||||
}
|
}
|
||||||
var pub = await _context.blogSpotPublications.AnyAsync(x => x.BlogpostId == blog.Id);
|
var pub = await _context.blogSpotPublications.AnyAsync(x => x.BlogpostId == blog.Id);
|
||||||
|
|
||||||
return new BlogPostEditViewModel(blog, pub);
|
return new BlogPostEditViewModel(blog, pub);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,19 @@
|
||||||
@using Yavsc.Abstract.Identity
|
@using Yavsc.Abstract.Identity
|
||||||
@model ApplicationUser
|
@model ApplicationUser
|
||||||
@{
|
|
||||||
// Le helper défend contre Model null et contre UserName vide
|
|
||||||
// ou whitespace. Sans cette garde, Razor lève
|
|
||||||
// NullReferenceException ici, ce qui propage un 500 et
|
|
||||||
// masque aussi la page d'erreur.
|
|
||||||
var avuri = UserDisplayHelpers.AvatarSrc(Model);
|
|
||||||
}
|
|
||||||
<div class="userinfo">
|
<div class="userinfo">
|
||||||
<a title="Posts" asp-controller="Blogspot" asp-action="Index" asp-route-id="@Model.UserName" class="btn btn-primary">
|
@if (Model != null && !string.IsNullOrWhiteSpace(Model.UserName))
|
||||||
|
{
|
||||||
|
string avuri = UserDisplayHelpers.AvatarSrc(Model);
|
||||||
|
|
||||||
|
<a title="Posts" asp-controller="Blogspot" asp-action="Index" asp-route-id="@Model.UserName" class="btn btn-primary">
|
||||||
<img src="@avuri" asp-append-version="true" class="smalltofhol" alt="@Model.UserName" title="@Model.UserName" />
|
<img src="@avuri" asp-append-version="true" class="smalltofhol" alt="@Model.UserName" title="@Model.UserName" />
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
} else {
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
<strong>Utilisateur inconnu</strong>
|
||||||
|
<img src="@YavscConstants.DefaultAvatar" class="smalltofhol" alt="Utilisateur inconnu" title="Utilisateur inconnu" />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -74,48 +74,59 @@ public static class ServiceExtensions
|
||||||
public static AuthenticationBuilder AddYavscJwtBearer(
|
public static AuthenticationBuilder AddYavscJwtBearer(
|
||||||
this AuthenticationBuilder builder,
|
this AuthenticationBuilder builder,
|
||||||
IConfiguration configuration,
|
IConfiguration configuration,
|
||||||
Action<JwtBearerOptions>? configure = null,
|
|
||||||
string schemeName = "Bearer")
|
string schemeName = "Bearer")
|
||||||
{
|
{
|
||||||
var authority = configuration.GetSection("Site")["Authority"]
|
var authority = configuration.GetSection("Site")["Authority"]
|
||||||
?? throw new InvalidOperationException(
|
?? throw new InvalidOperationException(
|
||||||
"Site:Authority is required to configure Yavsc JWT Bearer authentication.");
|
"Site:Authority is required to configure Yavsc JWT Bearer authentication.");
|
||||||
|
|
||||||
return builder.AddJwtBearer(schemeName, options =>
|
|
||||||
{
|
|
||||||
options.IncludeErrorDetails = true;
|
|
||||||
options.Authority = authority;
|
|
||||||
options.TokenValidationParameters = new TokenValidationParameters
|
|
||||||
{
|
|
||||||
ValidateAudience = false,
|
|
||||||
RoleClaimType = YavscConstants.RoleClaimType
|
|
||||||
};
|
|
||||||
options.MapInboundClaims = true;
|
|
||||||
|
|
||||||
// Dev: every Yavsc resource service (Yavsc.Api, Yavsc.Blogs,
|
string[] audiences = configuration.GetSection("Site").GetSection("Audience").Get<string[]>() ?? Array.Empty<string>();
|
||||||
// Yavsc.Org itself) validates JWTs against the OP that runs
|
AuthenticationBuilder result = builder;
|
||||||
// on https://localhost:5001 with a self-signed dev cert.
|
foreach (var audience in audiences)
|
||||||
// The default .NET HttpClient rejects self-signed certs, so
|
{
|
||||||
// JwtBearer's backchannel silently fails to fetch the OIDC
|
result = builder.AddJwtBearer(schemeName, options =>
|
||||||
// discovery + JWKS. With an empty ValidIssuer, every token
|
|
||||||
// is rejected with IDX10204 ("ValidIssuer is null or
|
|
||||||
// whitespace"). Telling the backchannel to skip TLS
|
|
||||||
// validation unblocks discovery in dev. Production uses a
|
|
||||||
// real CA-signed cert and the default validation path; the
|
|
||||||
// override is gated on HostingEnvironment == Development
|
|
||||||
// and only fires when the consumer opt-in via the
|
|
||||||
// 'Yavsc:Dev:TlsInsecure' configuration flag (default
|
|
||||||
// false), so a misconfigured production environment cannot
|
|
||||||
// silently downgrade TLS.
|
|
||||||
if (configuration.GetValue<string>("ASPNETCORE_ENVIRONMENT") == "Development")
|
|
||||||
{
|
{
|
||||||
options.BackchannelHttpHandler = new HttpClientHandler
|
options.IncludeErrorDetails = true;
|
||||||
|
options.Authority = authority;
|
||||||
|
options.TokenValidationParameters = new TokenValidationParameters
|
||||||
{
|
{
|
||||||
ServerCertificateCustomValidationCallback =
|
ValidateAudience = true,
|
||||||
(_, _, _, _) => true
|
ValidAudience = audience,
|
||||||
|
RoleClaimType = YavscConstants.RoleClaimType,
|
||||||
|
NameClaimType = YavscConstants.NameClaimType,
|
||||||
};
|
};
|
||||||
}
|
options.MapInboundClaims = true;
|
||||||
configure?.Invoke(options);
|
options.ClaimsIssuer = authority;
|
||||||
});
|
options.Audience = audience;
|
||||||
|
|
||||||
|
// Dev: every Yavsc resource service (Yavsc.Api, Yavsc.Blogs,
|
||||||
|
// Yavsc.Org itself) validates JWTs against the OP that runs
|
||||||
|
// on https://localhost:5001 with a self-signed dev cert.
|
||||||
|
// The default .NET HttpClient rejects self-signed certs, so
|
||||||
|
// JwtBearer's backchannel silently fails to fetch the OIDC
|
||||||
|
// discovery + JWKS. With an empty ValidIssuer, every token
|
||||||
|
// is rejected with IDX10204 ("ValidIssuer is null or
|
||||||
|
// whitespace"). Telling the backchannel to skip TLS
|
||||||
|
// validation unblocks discovery in dev. Production uses a
|
||||||
|
// real CA-signed cert and the default validation path; the
|
||||||
|
// override is gated on HostingEnvironment == Development
|
||||||
|
// and only fires when the consumer opt-in via the
|
||||||
|
// 'Yavsc:Dev:TlsInsecure' configuration flag (default
|
||||||
|
// false), so a misconfigured production environment cannot
|
||||||
|
// silently downgrade TLS.
|
||||||
|
if (configuration.GetValue<string>("ASPNETCORE_ENVIRONMENT") == "Development")
|
||||||
|
{
|
||||||
|
options.BackchannelHttpHandler = new HttpClientHandler
|
||||||
|
{
|
||||||
|
ServerCertificateCustomValidationCallback =
|
||||||
|
(_, _, _, _) => true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ namespace Yavsc.Server.Helpers
|
||||||
(x.ACL.Count == 0 || x.ACL.Any(a => readerCirclesMemberships.Contains(a.CircleId))));
|
(x.ACL.Count == 0 || x.ACL.Any(a => readerCirclesMemberships.Contains(a.CircleId))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetUserId(this ClaimsPrincipal user)
|
public static string GetUserId(this ClaimsPrincipal user)
|
||||||
{
|
{
|
||||||
return user.FindFirstValue("sub");
|
return user.FindFirstValue("sub");
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ namespace cli
|
||||||
public string ClientId { get; set; }
|
public string ClientId { get; set; }
|
||||||
public string ClientSecret { get; set; }
|
public string ClientSecret { get; set; }
|
||||||
public string Authority { get; set; }
|
public string Authority { get; set; }
|
||||||
public string Audience { get; set; }
|
public string[] Audience { get; set; }
|
||||||
public string SiteAccessSheme { get; set; } = "http";
|
public string SiteAccessSheme { get; set; } = "http";
|
||||||
public int Port { get; set; }
|
public int Port { get; set; }
|
||||||
public string Scope { get; set; } = "profile";
|
public string Scope { get; set; } = "profile";
|
||||||
|
|
@ -21,14 +21,14 @@ namespace cli
|
||||||
return Port==0 ? $"{SiteAccessSheme}://{Authority}/authorize" :
|
return Port==0 ? $"{SiteAccessSheme}://{Authority}/authorize" :
|
||||||
$"{SiteAccessSheme}://{Authority}:{Port}/authorize" ;
|
$"{SiteAccessSheme}://{Authority}:{Port}/authorize" ;
|
||||||
} }
|
} }
|
||||||
|
|
||||||
[NotMapped]
|
[NotMapped]
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public string RedirectUrl {get {
|
public string RedirectUrl {get {
|
||||||
return Port==0 ? $"{SiteAccessSheme}://{Authority}/oauth/success" :
|
return Port==0 ? $"{SiteAccessSheme}://{Authority}/oauth/success" :
|
||||||
$"{SiteAccessSheme}://{Authority}:{Port}/oauth/success" ;
|
$"{SiteAccessSheme}://{Authority}:{Port}/oauth/success" ;
|
||||||
} }
|
} }
|
||||||
|
|
||||||
[NotMapped]
|
[NotMapped]
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public string AccessTokenUrl { get {
|
public string AccessTokenUrl { get {
|
||||||
|
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
{
|
|
||||||
"UserConnection": {
|
|
||||||
"UserName": "Paul",
|
|
||||||
"AccessToken": "IvvrnJye2b7CSFp2Hj3mEEK1-7EMbLLGJATQHAwOlRt5sIcm9aMH85kJMAxDb4yIzL76G7maoIlaWuSyhhS3qfcIB04WcVnCVdmw22ncMs8rk_L0yGuLdNBnF3HuhZXpjD-AjpLJ1TdE0q3c-yakkP_EWTjM9I00a1gGop4bgE_-LAa2uoLeaCj0dbe95OSvVKJWJ4MU0fjlvqAaUx-EC4HYicUMEQvJviR0FyyMKPYYyx4jyu_bgYbhmKX9LoE5VeEDEMsyXZE0fxqdfnflICduUqrprCvQfLoG4DZ8o7ivjoak3-xiP7CFaF1vou89mO1c9BcJYUjcZXPgNYC7K9QHwFC5sPDkOHdijUo9xfieRaaqge5tEHlZAs3H_u-bGWft7xjxHOVUPSRzzYygR27alVSUIt6rt28FoLsEGYQaVK5QNzIbOE0RVU40vQb5a6JzcTcjTtRxCMn3FneKZtilEHb2TooUy4YbmhbnDIKsa0ZMMBEshyEel8oCWz9SITjm24FbdCmUvSKOlkHhIcnSM8oez9qhDlzzcsKzahbNeO-EBINW-6qBlKISQbFmfy-zO134JEkcpfA9NybGcYPZWLWu1IrF9pDzO66X2Rh-H45pHQWRWFodHcoQh0p9ZBNwo5k_EkU731uwQZF5zgGyZnUU2VpxdjorzrpexZXpStzYnYRbGGK7kGAUZH9v16zL-nB4t55Mu12iK9jsUIS7cmqOSTj4mJWFd03dwe4xkNrLgcGSGPebmsfSWYaIk7qAmWMsPKKv_vnOpi-1sZuOgdVyH-RmuyW83IPCUiEAf63a40PuqpVxFiMmbcRqSKvh5TedjWUiEZvBvz786-atYF_b0dxCj8fDQbyWiMj-4Lj7FV4gUJFvrDX4DsRns6wL6Cs3urL4PmKHLEhRma-i589pEKmzinO7l4S2atB6uqMS_xNU7Y0um_GQ51oPk8PxxCggI2aLR4PJbuBGSJBoTDW1OV9HNa_BybrKamN8cuc8IT3LBJiHH_ImHyW1n94C2oFcaUJ1dVVt6JDJqt4EVvFiwk15SlRJMXSLWQWxPKeOTCGb-So2Gcy_DwGu8_AlVL4mOdQI0GiGRkpaWNIF3N6ek-B_7Lz1zxKX7BCx1ISurAzG7jK1FDydovJ2LOHO6QbqVH1JKRdRDCLOKZl3TpcAyOQHDo5rAl84pwf5UROMb5SfVDJtdekb7SRR",
|
|
||||||
"TokenType": "bearer",
|
|
||||||
"ExpiresIn": "1199",
|
|
||||||
"RefreshToken": "CfDJ8LSG6cXzxTtFuur3SY2RDINpvQVezgrIru8TEIfKUpNmUXe5lA4tCQ_nlkklgarH5UocJ6b95NLRon0uvVy5mE-7EY3ld_-bjLgjDnV9oPxY7Xdgb94_5HdDWcyG_Zaw67T0G_t21bvNEgtw0zewbdisPpjKopfH7avQWxlRrMbN9Wboj2ZXYVCbmu2EkeyG5665-B16UEQFF7dVT6qU4qnSldIfhx5ex8Ii5aGg0pDAEnRUNla_iObls2yAf18bmTAfF-34kmClqqLCr7z4Cy65DwQ8EHNA9oIEvgCEuj04IG5wC79vycZrw0NLQWAz7fTahRreD_WsmbIKdUqkvlDjV1W3Dq7KUCi4bAk7oAJuqdl3qZg4SohmQ31IKD08aKSOPa4_jtJ-eKOlT6fdDhpoaP3DyKS09OkvAwbMA86937IrCCFZsGBhHAm-yszHXjVjPCr7gAJYaBjneAUAoseEYK0GaOVGXfhxQcURXtM9TyH3pgHjccOzq300Djv-BpfQZcSZnQf59kulL_gt--451r8UIw3wxQ6BD5fGVF_MrilUluLyhoqoGZJbiHFyL-miKjW7feGFuogVyee0nIO1Me-7T-aTQnn-LivP9avyHab4eGwg1nfUbEspA21vmz2UXkUvKvT-JRGmSxlYineeJURQMcGGbcvr9GbFy1A_vbImUSw_Tx0u7_7jwxkDwlTt5WvGVsFFHsj9vgtNSnWHRzbGhbOSBocg0eWylgmYy3yZtdajRx6xQluRUewP_K66GsmA6xVmekUo0_ZzUPj61VQzTMR-knYE-pCd6V_qGS4qtUNeQ5nPLWT6hRPvnd3kK7CS22ijEErm3LaOY2DogGYIoBLvmdande226KW48hUgQPxsmOLoUl1tXMYsiRqHKAnWlzg4-e_BfRe3YsMUwlNPcqYf0hQ-mE-j4fVo79XJFFSWF8WxYKed-imDYeK9b6bQPP3ZPaqiMy7_d9Fxnb3i2Oo4XQPmrTpDkHbMovslBfBoJW4RSpXo3cn0QqcCDG2KfFS8HIYr68jXnyx7Ws3MeswdyEFSqx-XWmZa"
|
|
||||||
},
|
|
||||||
"Connection": {
|
|
||||||
"ClientId": "53f4d5da-93a9-4584-82f9-b8fdf243b002",
|
|
||||||
"ClientSecret": "blouh",
|
|
||||||
"Authority": "localhost",
|
|
||||||
"Audience": "localhost",
|
|
||||||
"SiteAccessSheme": "http",
|
|
||||||
"Port": 5000,
|
|
||||||
"Scope": "profile"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue