fix(auth): make no-identity representable; stub dev-only (RB-09)
IIdentityProvider.Resolve returned a non-nullable CallerIdentity, so
the interface could not express "no identity" - StubIdentityProvider
was forced to invent one for any request carrying no credential at
all. Consequence: a production behandelportal build sends no
X-Medewerker header (medewerkerInterceptor is dev-only), so it used
to authenticate as the seeded citizen, role drafter - failing closed
on backoffice capabilities but open on every citizen-scoped endpoint,
including CanRevealBigNummer.
Resolve now returns CallerIdentity?. StubIdentityProvider keeps a
non-nullable return type (a valid narrower override) since it never
itself has "no identity" to report - it is registered only under
IsDevelopment() now. Production registers nothing and throws an
InvalidOperationException immediately during startup instead: there
is no real DigiD/employee-SSO provider in this POC yet, so a
misconfigured Production deploy must fail before serving a single
request, not resolve one per request. The identity-resolution
middleware turns a null resolution into a 401 rather than passing it
downstream.
Added StubIdentityProviderTests.Never_returns_null_even_with_no_headers_at_all
and ProductionIdentityProviderTests, which builds its own
WebApplicationFactory<Program> with UseEnvironment("Production") and
asserts startup throws. Verified both new tests fail red against the
pre-fix code.
RB-01's residual (GET /uploads/{id}/content reached via plain browser
navigation, no identity header) is confirmed unchanged in Development
and its Production consequence is written up in
implementation/rb-09.md for whoever lands the real identity provider -
no signed-URL/cookie scheme was designed here, per scope.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
using BigRegister.Api.Data;
|
||||
using BigRegister.Domain.Authorization;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.Testing;
|
||||
|
||||
namespace BigRegister.Tests;
|
||||
|
||||
@@ -93,4 +95,32 @@ public class StubIdentityProviderTests
|
||||
var caller = Resolve(role: "admin", medewerker: "m.jansen");
|
||||
Assert.Equal(PrincipalRole.Admin, caller.Role);
|
||||
}
|
||||
|
||||
/// RB-09/BIO-002: IIdentityProvider.Resolve can now return null ("no identity"), but this
|
||||
/// stub's own contract stays non-nullable — it is a developer convenience that always invents
|
||||
/// a caller, never a source of "no identity" itself. A request with genuinely no headers at
|
||||
/// all still resolves to the seeded citizen, unchanged.
|
||||
[Fact]
|
||||
public void Never_returns_null_even_with_no_headers_at_all()
|
||||
{
|
||||
Assert.NotNull(new StubIdentityProvider().Resolve(new DefaultHttpContext()));
|
||||
}
|
||||
}
|
||||
|
||||
/// RB-09/BIO-002: in Production, StubIdentityProvider is not registered at all (it is
|
||||
/// Development-only) and there is no real DigiD/employee-SSO IIdentityProvider in this POC yet —
|
||||
/// so a Production build must fail at startup rather than silently resolving every request to
|
||||
/// the seeded citizen (the failure mode BIO-002 documents).
|
||||
public class ProductionIdentityProviderTests
|
||||
{
|
||||
[Fact]
|
||||
public void Production_environment_with_no_real_identity_provider_fails_at_startup()
|
||||
{
|
||||
using var factory = new WebApplicationFactory<Program>()
|
||||
.WithWebHostBuilder(builder => builder.UseEnvironment("Production"));
|
||||
|
||||
// The throw happens while the app builds services, before any request can be served —
|
||||
// triggered here by the test host materialising that host to hand out a client.
|
||||
Assert.ThrowsAny<Exception>(() => factory.CreateClient());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user