up
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled
This commit is contained in:
@@ -1,30 +1,30 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace StellaOps.Signer.WebService.Contracts;
|
||||
|
||||
public sealed record SignDsseSubjectDto(string Name, Dictionary<string, string> Digest);
|
||||
|
||||
public sealed record SignDssePoeDto(string Format, string Value);
|
||||
|
||||
public sealed record SignDsseOptionsDto(string? SigningMode, int? ExpirySeconds, string? ReturnBundle);
|
||||
|
||||
public sealed record SignDsseRequestDto(
|
||||
List<SignDsseSubjectDto> Subject,
|
||||
string PredicateType,
|
||||
JsonElement Predicate,
|
||||
string ScannerImageDigest,
|
||||
SignDssePoeDto Poe,
|
||||
SignDsseOptionsDto? Options);
|
||||
|
||||
public sealed record SignDsseResponseDto(SignDsseBundleDto Bundle, SignDssePolicyDto Policy, string AuditId);
|
||||
|
||||
public sealed record SignDsseBundleDto(SignDsseEnvelopeDto Dsse, IReadOnlyList<string> CertificateChain, string Mode, SignDsseIdentityDto SigningIdentity);
|
||||
|
||||
public sealed record SignDsseEnvelopeDto(string PayloadType, string Payload, IReadOnlyList<SignDsseSignatureDto> Signatures);
|
||||
|
||||
public sealed record SignDsseSignatureDto(string Signature, string? KeyId);
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace StellaOps.Signer.WebService.Contracts;
|
||||
|
||||
public sealed record SignDsseSubjectDto(string Name, Dictionary<string, string> Digest);
|
||||
|
||||
public sealed record SignDssePoeDto(string Format, string Value);
|
||||
|
||||
public sealed record SignDsseOptionsDto(string? SigningMode, int? ExpirySeconds, string? ReturnBundle);
|
||||
|
||||
public sealed record SignDsseRequestDto(
|
||||
List<SignDsseSubjectDto> Subject,
|
||||
string PredicateType,
|
||||
JsonElement Predicate,
|
||||
string ScannerImageDigest,
|
||||
SignDssePoeDto Poe,
|
||||
SignDsseOptionsDto? Options);
|
||||
|
||||
public sealed record SignDsseResponseDto(SignDsseBundleDto Bundle, SignDssePolicyDto Policy, string AuditId);
|
||||
|
||||
public sealed record SignDsseBundleDto(SignDsseEnvelopeDto Dsse, IReadOnlyList<string> CertificateChain, string Mode, SignDsseIdentityDto SigningIdentity);
|
||||
|
||||
public sealed record SignDsseEnvelopeDto(string PayloadType, string Payload, IReadOnlyList<SignDsseSignatureDto> Signatures);
|
||||
|
||||
public sealed record SignDsseSignatureDto(string Signature, string? KeyId);
|
||||
|
||||
public sealed record SignDsseIdentityDto(string Issuer, string Subject, string? CertExpiry);
|
||||
|
||||
public sealed record SignDssePolicyDto(string Plan, int MaxArtifactBytes, int QpsRemaining);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -14,15 +14,15 @@ using StellaOps.Signer.Core;
|
||||
using StellaOps.Signer.WebService.Contracts;
|
||||
|
||||
namespace StellaOps.Signer.WebService.Endpoints;
|
||||
|
||||
public static class SignerEndpoints
|
||||
{
|
||||
public static IEndpointRouteBuilder MapSignerEndpoints(this IEndpointRouteBuilder endpoints)
|
||||
{
|
||||
var group = endpoints.MapGroup("/api/v1/signer")
|
||||
.WithTags("Signer")
|
||||
.RequireAuthorization();
|
||||
|
||||
|
||||
public static class SignerEndpoints
|
||||
{
|
||||
public static IEndpointRouteBuilder MapSignerEndpoints(this IEndpointRouteBuilder endpoints)
|
||||
{
|
||||
var group = endpoints.MapGroup("/api/v1/signer")
|
||||
.WithTags("Signer")
|
||||
.RequireAuthorization();
|
||||
|
||||
group.MapPost("/sign/dsse", SignDsseAsync);
|
||||
group.MapGet("/verify/referrers", VerifyReferrersAsync);
|
||||
return endpoints;
|
||||
@@ -30,40 +30,40 @@ public static class SignerEndpoints
|
||||
|
||||
private static async Task<IResult> SignDsseAsync(
|
||||
HttpContext httpContext,
|
||||
[FromBody] SignDsseRequestDto requestDto,
|
||||
ISignerPipeline pipeline,
|
||||
ILoggerFactory loggerFactory,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (requestDto is null)
|
||||
{
|
||||
[FromBody] SignDsseRequestDto requestDto,
|
||||
ISignerPipeline pipeline,
|
||||
ILoggerFactory loggerFactory,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (requestDto is null)
|
||||
{
|
||||
return CreateProblem("invalid_request", "Request body is required.", StatusCodes.Status400BadRequest);
|
||||
}
|
||||
|
||||
var logger = loggerFactory.CreateLogger("SignerEndpoints.SignDsse");
|
||||
try
|
||||
{
|
||||
var caller = BuildCallerContext(httpContext);
|
||||
ValidateSenderBinding(httpContext, requestDto.Poe, caller);
|
||||
|
||||
using var predicateDocument = JsonDocument.Parse(requestDto.Predicate.GetRawText());
|
||||
var signingRequest = new SigningRequest(
|
||||
ConvertSubjects(requestDto.Subject),
|
||||
requestDto.PredicateType,
|
||||
predicateDocument,
|
||||
requestDto.ScannerImageDigest,
|
||||
new ProofOfEntitlement(
|
||||
ParsePoeFormat(requestDto.Poe.Format),
|
||||
requestDto.Poe.Value),
|
||||
ConvertOptions(requestDto.Options));
|
||||
|
||||
var outcome = await pipeline.SignAsync(signingRequest, caller, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var logger = loggerFactory.CreateLogger("SignerEndpoints.SignDsse");
|
||||
try
|
||||
{
|
||||
var caller = BuildCallerContext(httpContext);
|
||||
ValidateSenderBinding(httpContext, requestDto.Poe, caller);
|
||||
|
||||
using var predicateDocument = JsonDocument.Parse(requestDto.Predicate.GetRawText());
|
||||
var signingRequest = new SigningRequest(
|
||||
ConvertSubjects(requestDto.Subject),
|
||||
requestDto.PredicateType,
|
||||
predicateDocument,
|
||||
requestDto.ScannerImageDigest,
|
||||
new ProofOfEntitlement(
|
||||
ParsePoeFormat(requestDto.Poe.Format),
|
||||
requestDto.Poe.Value),
|
||||
ConvertOptions(requestDto.Options));
|
||||
|
||||
var outcome = await pipeline.SignAsync(signingRequest, caller, cancellationToken).ConfigureAwait(false);
|
||||
var response = ConvertOutcome(outcome);
|
||||
return Json(response);
|
||||
}
|
||||
catch (SignerValidationException ex)
|
||||
{
|
||||
logger.LogWarning(ex, "Validation failure while signing DSSE.");
|
||||
}
|
||||
catch (SignerValidationException ex)
|
||||
{
|
||||
logger.LogWarning(ex, "Validation failure while signing DSSE.");
|
||||
return CreateProblem(ex.Code, ex.Message, StatusCodes.Status400BadRequest);
|
||||
}
|
||||
catch (SignerAuthorizationException ex)
|
||||
@@ -135,155 +135,155 @@ public static class SignerEndpoints
|
||||
var user = context.User ?? throw new SignerAuthorizationException("invalid_caller", "Caller is not authenticated.");
|
||||
|
||||
string subject = user.FindFirstValue(StellaOpsClaimTypes.Subject) ??
|
||||
throw new SignerAuthorizationException("invalid_caller", "Subject claim is required.");
|
||||
string tenant = user.FindFirstValue(StellaOpsClaimTypes.Tenant) ?? subject;
|
||||
|
||||
var scopes = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
if (user.HasClaim(c => c.Type == StellaOpsClaimTypes.Scope))
|
||||
{
|
||||
foreach (var value in user.FindAll(StellaOpsClaimTypes.Scope))
|
||||
{
|
||||
foreach (var scope in value.Value.Split(' ', StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
scopes.Add(scope);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var scopeClaim in user.FindAll(StellaOpsClaimTypes.ScopeItem))
|
||||
{
|
||||
scopes.Add(scopeClaim.Value);
|
||||
}
|
||||
|
||||
var audiences = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var audClaim in user.FindAll(StellaOpsClaimTypes.Audience))
|
||||
{
|
||||
if (audClaim.Value.Contains(' '))
|
||||
{
|
||||
foreach (var aud in audClaim.Value.Split(' ', StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
audiences.Add(aud);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
audiences.Add(audClaim.Value);
|
||||
}
|
||||
}
|
||||
|
||||
if (audiences.Count == 0)
|
||||
{
|
||||
throw new SignerAuthorizationException("invalid_audience", "Audience claim is required.");
|
||||
}
|
||||
|
||||
var sender = context.Request.Headers.TryGetValue("DPoP", out var dpop)
|
||||
? dpop.ToString()
|
||||
: null;
|
||||
|
||||
var clientCert = context.Connection.ClientCertificate?.Thumbprint;
|
||||
|
||||
return new CallerContext(
|
||||
subject,
|
||||
tenant,
|
||||
scopes.ToArray(),
|
||||
audiences.ToArray(),
|
||||
sender,
|
||||
clientCert);
|
||||
}
|
||||
|
||||
private static void ValidateSenderBinding(HttpContext context, SignDssePoeDto poe, CallerContext caller)
|
||||
{
|
||||
if (poe is null)
|
||||
{
|
||||
throw new SignerValidationException("poe_missing", "Proof of entitlement is required.");
|
||||
}
|
||||
|
||||
var format = ParsePoeFormat(poe.Format);
|
||||
if (format == SignerPoEFormat.Jwt)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(caller.SenderBinding))
|
||||
{
|
||||
throw new SignerAuthorizationException("invalid_token", "DPoP proof is required for JWT PoE.");
|
||||
}
|
||||
}
|
||||
else if (format == SignerPoEFormat.Mtls)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(caller.ClientCertificateThumbprint))
|
||||
{
|
||||
throw new SignerAuthorizationException("invalid_token", "Client certificate is required for mTLS PoE.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static IReadOnlyList<SigningSubject> ConvertSubjects(List<SignDsseSubjectDto> subjects)
|
||||
{
|
||||
if (subjects is null || subjects.Count == 0)
|
||||
{
|
||||
throw new SignerValidationException("subject_missing", "At least one subject is required.");
|
||||
}
|
||||
|
||||
return subjects.Select(subject =>
|
||||
{
|
||||
if (subject.Digest is null || subject.Digest.Count == 0)
|
||||
{
|
||||
throw new SignerValidationException("subject_digest_invalid", $"Digest for subject '{subject.Name}' is required.");
|
||||
}
|
||||
|
||||
return new SigningSubject(subject.Name, subject.Digest);
|
||||
}).ToArray();
|
||||
}
|
||||
|
||||
private static SigningOptions ConvertOptions(SignDsseOptionsDto? optionsDto)
|
||||
{
|
||||
if (optionsDto is null)
|
||||
{
|
||||
return new SigningOptions(SigningMode.Kms, null, "dsse+cert");
|
||||
}
|
||||
|
||||
var mode = optionsDto.SigningMode switch
|
||||
{
|
||||
null or "" => SigningMode.Kms,
|
||||
"kms" or "KMS" => SigningMode.Kms,
|
||||
"keyless" or "KEYLESS" => SigningMode.Keyless,
|
||||
_ => throw new SignerValidationException("signing_mode_invalid", $"Unsupported signing mode '{optionsDto.SigningMode}'."),
|
||||
};
|
||||
|
||||
return new SigningOptions(mode, optionsDto.ExpirySeconds, optionsDto.ReturnBundle ?? "dsse+cert");
|
||||
}
|
||||
|
||||
private static SignerPoEFormat ParsePoeFormat(string? format)
|
||||
{
|
||||
return format?.ToLowerInvariant() switch
|
||||
{
|
||||
"jwt" => SignerPoEFormat.Jwt,
|
||||
"mtls" => SignerPoEFormat.Mtls,
|
||||
_ => throw new SignerValidationException("poe_invalid", $"Unsupported PoE format '{format}'."),
|
||||
};
|
||||
}
|
||||
|
||||
private static SignDsseResponseDto ConvertOutcome(SigningOutcome outcome)
|
||||
{
|
||||
var signatures = outcome.Bundle.Envelope.Signatures
|
||||
.Select(signature => new SignDsseSignatureDto(signature.Signature, signature.KeyId))
|
||||
.ToArray();
|
||||
|
||||
var bundle = new SignDsseBundleDto(
|
||||
new SignDsseEnvelopeDto(
|
||||
outcome.Bundle.Envelope.PayloadType,
|
||||
outcome.Bundle.Envelope.Payload,
|
||||
signatures),
|
||||
outcome.Bundle.Metadata.CertificateChain,
|
||||
outcome.Bundle.Metadata.Identity.Mode,
|
||||
new SignDsseIdentityDto(
|
||||
outcome.Bundle.Metadata.Identity.Issuer,
|
||||
outcome.Bundle.Metadata.Identity.Subject,
|
||||
outcome.Bundle.Metadata.Identity.ExpiresAtUtc?.ToString("O")));
|
||||
|
||||
var policy = new SignDssePolicyDto(
|
||||
outcome.Policy.Plan,
|
||||
outcome.Policy.MaxArtifactBytes,
|
||||
outcome.Policy.QpsRemaining);
|
||||
|
||||
return new SignDsseResponseDto(bundle, policy, outcome.AuditId);
|
||||
}
|
||||
}
|
||||
throw new SignerAuthorizationException("invalid_caller", "Subject claim is required.");
|
||||
string tenant = user.FindFirstValue(StellaOpsClaimTypes.Tenant) ?? subject;
|
||||
|
||||
var scopes = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
if (user.HasClaim(c => c.Type == StellaOpsClaimTypes.Scope))
|
||||
{
|
||||
foreach (var value in user.FindAll(StellaOpsClaimTypes.Scope))
|
||||
{
|
||||
foreach (var scope in value.Value.Split(' ', StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
scopes.Add(scope);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var scopeClaim in user.FindAll(StellaOpsClaimTypes.ScopeItem))
|
||||
{
|
||||
scopes.Add(scopeClaim.Value);
|
||||
}
|
||||
|
||||
var audiences = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var audClaim in user.FindAll(StellaOpsClaimTypes.Audience))
|
||||
{
|
||||
if (audClaim.Value.Contains(' '))
|
||||
{
|
||||
foreach (var aud in audClaim.Value.Split(' ', StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
audiences.Add(aud);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
audiences.Add(audClaim.Value);
|
||||
}
|
||||
}
|
||||
|
||||
if (audiences.Count == 0)
|
||||
{
|
||||
throw new SignerAuthorizationException("invalid_audience", "Audience claim is required.");
|
||||
}
|
||||
|
||||
var sender = context.Request.Headers.TryGetValue("DPoP", out var dpop)
|
||||
? dpop.ToString()
|
||||
: null;
|
||||
|
||||
var clientCert = context.Connection.ClientCertificate?.Thumbprint;
|
||||
|
||||
return new CallerContext(
|
||||
subject,
|
||||
tenant,
|
||||
scopes.ToArray(),
|
||||
audiences.ToArray(),
|
||||
sender,
|
||||
clientCert);
|
||||
}
|
||||
|
||||
private static void ValidateSenderBinding(HttpContext context, SignDssePoeDto poe, CallerContext caller)
|
||||
{
|
||||
if (poe is null)
|
||||
{
|
||||
throw new SignerValidationException("poe_missing", "Proof of entitlement is required.");
|
||||
}
|
||||
|
||||
var format = ParsePoeFormat(poe.Format);
|
||||
if (format == SignerPoEFormat.Jwt)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(caller.SenderBinding))
|
||||
{
|
||||
throw new SignerAuthorizationException("invalid_token", "DPoP proof is required for JWT PoE.");
|
||||
}
|
||||
}
|
||||
else if (format == SignerPoEFormat.Mtls)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(caller.ClientCertificateThumbprint))
|
||||
{
|
||||
throw new SignerAuthorizationException("invalid_token", "Client certificate is required for mTLS PoE.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static IReadOnlyList<SigningSubject> ConvertSubjects(List<SignDsseSubjectDto> subjects)
|
||||
{
|
||||
if (subjects is null || subjects.Count == 0)
|
||||
{
|
||||
throw new SignerValidationException("subject_missing", "At least one subject is required.");
|
||||
}
|
||||
|
||||
return subjects.Select(subject =>
|
||||
{
|
||||
if (subject.Digest is null || subject.Digest.Count == 0)
|
||||
{
|
||||
throw new SignerValidationException("subject_digest_invalid", $"Digest for subject '{subject.Name}' is required.");
|
||||
}
|
||||
|
||||
return new SigningSubject(subject.Name, subject.Digest);
|
||||
}).ToArray();
|
||||
}
|
||||
|
||||
private static SigningOptions ConvertOptions(SignDsseOptionsDto? optionsDto)
|
||||
{
|
||||
if (optionsDto is null)
|
||||
{
|
||||
return new SigningOptions(SigningMode.Kms, null, "dsse+cert");
|
||||
}
|
||||
|
||||
var mode = optionsDto.SigningMode switch
|
||||
{
|
||||
null or "" => SigningMode.Kms,
|
||||
"kms" or "KMS" => SigningMode.Kms,
|
||||
"keyless" or "KEYLESS" => SigningMode.Keyless,
|
||||
_ => throw new SignerValidationException("signing_mode_invalid", $"Unsupported signing mode '{optionsDto.SigningMode}'."),
|
||||
};
|
||||
|
||||
return new SigningOptions(mode, optionsDto.ExpirySeconds, optionsDto.ReturnBundle ?? "dsse+cert");
|
||||
}
|
||||
|
||||
private static SignerPoEFormat ParsePoeFormat(string? format)
|
||||
{
|
||||
return format?.ToLowerInvariant() switch
|
||||
{
|
||||
"jwt" => SignerPoEFormat.Jwt,
|
||||
"mtls" => SignerPoEFormat.Mtls,
|
||||
_ => throw new SignerValidationException("poe_invalid", $"Unsupported PoE format '{format}'."),
|
||||
};
|
||||
}
|
||||
|
||||
private static SignDsseResponseDto ConvertOutcome(SigningOutcome outcome)
|
||||
{
|
||||
var signatures = outcome.Bundle.Envelope.Signatures
|
||||
.Select(signature => new SignDsseSignatureDto(signature.Signature, signature.KeyId))
|
||||
.ToArray();
|
||||
|
||||
var bundle = new SignDsseBundleDto(
|
||||
new SignDsseEnvelopeDto(
|
||||
outcome.Bundle.Envelope.PayloadType,
|
||||
outcome.Bundle.Envelope.Payload,
|
||||
signatures),
|
||||
outcome.Bundle.Metadata.CertificateChain,
|
||||
outcome.Bundle.Metadata.Identity.Mode,
|
||||
new SignDsseIdentityDto(
|
||||
outcome.Bundle.Metadata.Identity.Issuer,
|
||||
outcome.Bundle.Metadata.Identity.Subject,
|
||||
outcome.Bundle.Metadata.Identity.ExpiresAtUtc?.ToString("O")));
|
||||
|
||||
var policy = new SignDssePolicyDto(
|
||||
outcome.Policy.Plan,
|
||||
outcome.Policy.MaxArtifactBytes,
|
||||
outcome.Policy.QpsRemaining);
|
||||
|
||||
return new SignDsseResponseDto(bundle, policy, outcome.AuditId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace StellaOps.Signer.WebService.Security;
|
||||
|
||||
public static class StubBearerAuthenticationDefaults
|
||||
{
|
||||
public const string AuthenticationScheme = "StubBearer";
|
||||
}
|
||||
namespace StellaOps.Signer.WebService.Security;
|
||||
|
||||
public static class StubBearerAuthenticationDefaults
|
||||
{
|
||||
public const string AuthenticationScheme = "StubBearer";
|
||||
}
|
||||
|
||||
@@ -1,55 +1,55 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Claims;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Auth.Abstractions;
|
||||
|
||||
namespace StellaOps.Signer.WebService.Security;
|
||||
|
||||
public sealed class StubBearerAuthenticationHandler
|
||||
: AuthenticationHandler<AuthenticationSchemeOptions>
|
||||
{
|
||||
public StubBearerAuthenticationHandler(
|
||||
IOptionsMonitor<AuthenticationSchemeOptions> options,
|
||||
ILoggerFactory logger,
|
||||
UrlEncoder encoder)
|
||||
: base(options, logger, encoder)
|
||||
{
|
||||
}
|
||||
|
||||
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
|
||||
{
|
||||
var authorization = Request.Headers.Authorization.ToString();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(authorization) ||
|
||||
!authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return Task.FromResult(AuthenticateResult.Fail("Missing bearer token."));
|
||||
}
|
||||
|
||||
var token = authorization.Substring("Bearer ".Length).Trim();
|
||||
if (token.Length == 0)
|
||||
{
|
||||
return Task.FromResult(AuthenticateResult.Fail("Bearer token is empty."));
|
||||
}
|
||||
|
||||
var claims = new List<Claim>
|
||||
{
|
||||
new(ClaimTypes.NameIdentifier, "stub-subject"),
|
||||
new(StellaOpsClaimTypes.Subject, "stub-subject"),
|
||||
new(StellaOpsClaimTypes.Tenant, "stub-tenant"),
|
||||
new(StellaOpsClaimTypes.Scope, "signer.sign"),
|
||||
new(StellaOpsClaimTypes.ScopeItem, "signer.sign"),
|
||||
new(StellaOpsClaimTypes.Audience, "signer"),
|
||||
};
|
||||
|
||||
var identity = new ClaimsIdentity(claims, Scheme.Name);
|
||||
var principal = new ClaimsPrincipal(identity);
|
||||
var ticket = new AuthenticationTicket(principal, Scheme.Name);
|
||||
return Task.FromResult(AuthenticateResult.Success(ticket));
|
||||
}
|
||||
}
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Claims;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Auth.Abstractions;
|
||||
|
||||
namespace StellaOps.Signer.WebService.Security;
|
||||
|
||||
public sealed class StubBearerAuthenticationHandler
|
||||
: AuthenticationHandler<AuthenticationSchemeOptions>
|
||||
{
|
||||
public StubBearerAuthenticationHandler(
|
||||
IOptionsMonitor<AuthenticationSchemeOptions> options,
|
||||
ILoggerFactory logger,
|
||||
UrlEncoder encoder)
|
||||
: base(options, logger, encoder)
|
||||
{
|
||||
}
|
||||
|
||||
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
|
||||
{
|
||||
var authorization = Request.Headers.Authorization.ToString();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(authorization) ||
|
||||
!authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return Task.FromResult(AuthenticateResult.Fail("Missing bearer token."));
|
||||
}
|
||||
|
||||
var token = authorization.Substring("Bearer ".Length).Trim();
|
||||
if (token.Length == 0)
|
||||
{
|
||||
return Task.FromResult(AuthenticateResult.Fail("Bearer token is empty."));
|
||||
}
|
||||
|
||||
var claims = new List<Claim>
|
||||
{
|
||||
new(ClaimTypes.NameIdentifier, "stub-subject"),
|
||||
new(StellaOpsClaimTypes.Subject, "stub-subject"),
|
||||
new(StellaOpsClaimTypes.Tenant, "stub-tenant"),
|
||||
new(StellaOpsClaimTypes.Scope, "signer.sign"),
|
||||
new(StellaOpsClaimTypes.ScopeItem, "signer.sign"),
|
||||
new(StellaOpsClaimTypes.Audience, "signer"),
|
||||
};
|
||||
|
||||
var identity = new ClaimsIdentity(claims, Scheme.Name);
|
||||
var principal = new ClaimsPrincipal(identity);
|
||||
var ticket = new AuthenticationTicket(principal, Scheme.Name);
|
||||
return Task.FromResult(AuthenticateResult.Success(ticket));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user