Initial curated itworx-knowledge-hub source from 3b1751d0d9dee551aa6994eee0093704dc334817
This commit is contained in:
commit
ad8ad252a9
825 files changed
+124216
No files matched your search
+218
@@ -0,0 +1,218 @@
|
||||
using System.Net.Http.Json;
|
||||
using System.Security.Claims;
|
||||
using ItWorx.KnowledgeHub.Api.Authorization;
|
||||
using ItWorx.KnowledgeHub.Application.Ingestion;
|
||||
using ItWorx.KnowledgeHub.Contracts.Admin;
|
||||
using ItWorx.KnowledgeHub.Contracts.Auth;
|
||||
using ItWorx.KnowledgeHub.Contracts.Automation;
|
||||
using ItWorx.KnowledgeHub.Domain.Automation;
|
||||
using ItWorx.KnowledgeHub.IntegrationTests.Infrastructure;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace ItWorx.KnowledgeHub.IntegrationTests.Ingestion;
|
||||
|
||||
/// <summary>
|
||||
/// Covers the bounded Task 3 surface without registering proposal routes that Task 4
|
||||
/// owns. The policy probe invokes the application's registered authorization policies
|
||||
/// against the same claims emitted by the service-token handler.
|
||||
/// </summary>
|
||||
[Collection(PostgresTestGroup.Name)]
|
||||
public sealed class IngestionProposalAuthorizationTests : OwnerSetupTestBase
|
||||
{
|
||||
private readonly PostgresContainerFixture _postgres;
|
||||
|
||||
public IngestionProposalAuthorizationTests(PostgresContainerFixture postgres) : base(postgres)
|
||||
{
|
||||
_postgres = postgres;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Ingestion_write_scope_satisfies_submission_but_not_human_review()
|
||||
{
|
||||
using var scope = Factory.Services.CreateScope();
|
||||
var authorization = scope.ServiceProvider.GetRequiredService<IAuthorizationService>();
|
||||
|
||||
var ingestingService = ServicePrincipal(Scopes.IngestionWrite);
|
||||
|
||||
Assert.True((await authorization.AuthorizeAsync(
|
||||
ingestingService, resource: null, AuthorizationPolicies.IngestionWrite)).Succeeded);
|
||||
Assert.False((await authorization.AuthorizeAsync(
|
||||
ingestingService, resource: null, AuthorizationPolicies.IngestionReview)).Succeeded);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Pages_write_scope_cannot_satisfy_ingestion_submission()
|
||||
{
|
||||
using var scope = Factory.Services.CreateScope();
|
||||
var authorization = scope.ServiceProvider.GetRequiredService<IAuthorizationService>();
|
||||
|
||||
var pagesService = ServicePrincipal(Scopes.PagesWrite);
|
||||
|
||||
Assert.False((await authorization.AuthorizeAsync(
|
||||
pagesService, resource: null, AuthorizationPolicies.IngestionWrite)).Succeeded);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task A_delegated_principal_with_ingestion_write_can_submit_but_cannot_review()
|
||||
{
|
||||
using var scope = Factory.Services.CreateScope();
|
||||
var authorization = scope.ServiceProvider.GetRequiredService<IAuthorizationService>();
|
||||
|
||||
var delegatedIdentity = new ClaimsIdentity(
|
||||
[
|
||||
new Claim(ClaimTypes.NameIdentifier, Guid.NewGuid().ToString()),
|
||||
new Claim(ClaimTypes.Name, "delegated-human")
|
||||
], authenticationType: "delegated-cookie");
|
||||
var serviceIdentity = new ClaimsIdentity(
|
||||
[
|
||||
new Claim(ClaimTypes.NameIdentifier, Guid.NewGuid().ToString()),
|
||||
new Claim(ServiceTokenAuthenticationHandler.ServiceAccountClaimType, Guid.NewGuid().ToString()),
|
||||
new Claim(ServiceTokenAuthenticationHandler.ScopeClaimType, Scopes.IngestionWrite)
|
||||
], ServiceTokenAuthenticationDefaults.Scheme);
|
||||
|
||||
var delegatedService = new ClaimsPrincipal([delegatedIdentity, serviceIdentity]);
|
||||
|
||||
Assert.True((await authorization.AuthorizeAsync(
|
||||
delegatedService, resource: null, AuthorizationPolicies.IngestionWrite)).Succeeded);
|
||||
Assert.False((await authorization.AuthorizeAsync(
|
||||
delegatedService, resource: null, AuthorizationPolicies.IngestionReview)).Succeeded);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Default_options_disable_ingestion_and_anonymous_session_has_no_features_or_workspace()
|
||||
{
|
||||
using var scope = Factory.Services.CreateScope();
|
||||
var options = scope.ServiceProvider.GetRequiredService<IOptions<IngestionProposalOptions>>().Value;
|
||||
Assert.False(options.Enabled);
|
||||
|
||||
using var client = Factory.CreateClient();
|
||||
var session = await client.GetFromJsonAsync<SessionResponse>("/api/v1/auth/session");
|
||||
|
||||
Assert.NotNull(session);
|
||||
Assert.False(session.Authenticated);
|
||||
Assert.Null(session.Features);
|
||||
Assert.Null(session.WorkspaceId);
|
||||
Assert.Null(session.Role);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Authenticated_default_configuration_session_exposes_disabled_ingestion_feature()
|
||||
{
|
||||
using var client = Factory.CreateClient();
|
||||
var login = await SetupHelper.LoginAsync(client, OwnerEmail, OwnerPassword);
|
||||
login.EnsureSuccessStatusCode();
|
||||
var session = await login.Content.ReadFromJsonAsync<SessionResponse>();
|
||||
|
||||
Assert.NotNull(session);
|
||||
Assert.True(session.Authenticated);
|
||||
Assert.NotNull(session.Features);
|
||||
Assert.False(session.Features.IngestionProposals);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task An_authenticated_human_editor_can_submit_and_review()
|
||||
{
|
||||
using var administratorClient = Factory.CreateClient();
|
||||
var administratorLogin = await SetupHelper.LoginAsync(administratorClient, OwnerEmail, OwnerPassword);
|
||||
administratorLogin.EnsureSuccessStatusCode();
|
||||
|
||||
const string editorEmail = "ingestion-editor@itworx.test";
|
||||
const string editorPassword = "editor-strong-password";
|
||||
var createEditor = await SetupHelper.PostWithCsrfAsync(
|
||||
administratorClient,
|
||||
"/api/v1/admin/users",
|
||||
new CreateUserRequest("Ingestion Editor", editorEmail, editorPassword, "Editor"));
|
||||
createEditor.EnsureSuccessStatusCode();
|
||||
|
||||
using var editorClient = Factory.CreateClient();
|
||||
var editorLogin = await SetupHelper.LoginAsync(editorClient, editorEmail, editorPassword);
|
||||
editorLogin.EnsureSuccessStatusCode();
|
||||
var session = await editorLogin.Content.ReadFromJsonAsync<SessionResponse>();
|
||||
Assert.NotNull(session?.UserId);
|
||||
Assert.Equal("Editor", session.Role);
|
||||
|
||||
using var scope = Factory.Services.CreateScope();
|
||||
var accessor = scope.ServiceProvider.GetRequiredService<IHttpContextAccessor>();
|
||||
accessor.HttpContext = new DefaultHttpContext { User = HumanPrincipal(session.UserId.Value) };
|
||||
|
||||
try
|
||||
{
|
||||
var authorization = scope.ServiceProvider.GetRequiredService<IAuthorizationService>();
|
||||
var human = HumanPrincipal(session.UserId.Value);
|
||||
|
||||
Assert.True((await authorization.AuthorizeAsync(
|
||||
human, resource: null, AuthorizationPolicies.IngestionWrite)).Succeeded);
|
||||
Assert.True((await authorization.AuthorizeAsync(
|
||||
human, resource: null, AuthorizationPolicies.IngestionReview)).Succeeded);
|
||||
}
|
||||
finally
|
||||
{
|
||||
accessor.HttpContext = null;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Authenticated_session_exposes_only_the_server_configured_ingestion_feature()
|
||||
{
|
||||
var configuredFactory = new KnowledgeHubIntegrationFactory(_postgres,
|
||||
new Dictionary<string, string?> { ["IngestionProposal:Enabled"] = "true" });
|
||||
await ((IAsyncLifetime)configuredFactory).InitializeAsync();
|
||||
|
||||
try
|
||||
{
|
||||
using var client = configuredFactory.CreateClient();
|
||||
var setup = await SetupHelper.CompleteFirstRunSetupAsync(
|
||||
client, "Ingestion test", "Owner", "ingestion-owner@itworx.test", "correct-horse-battery-staple");
|
||||
setup.EnsureSuccessStatusCode();
|
||||
|
||||
var login = await SetupHelper.LoginAsync(client, "ingestion-owner@itworx.test", "correct-horse-battery-staple");
|
||||
login.EnsureSuccessStatusCode();
|
||||
var session = await login.Content.ReadFromJsonAsync<SessionResponse>();
|
||||
|
||||
Assert.NotNull(session);
|
||||
Assert.True(session.Authenticated);
|
||||
Assert.NotNull(session.Features);
|
||||
Assert.True(session.Features.IngestionProposals);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await ((IAsyncLifetime)configuredFactory).DisposeAsync();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Ingestion_write_is_an_accepted_service_account_scope()
|
||||
{
|
||||
using var client = Factory.CreateClient();
|
||||
var login = await SetupHelper.LoginAsync(client, OwnerEmail, OwnerPassword);
|
||||
login.EnsureSuccessStatusCode();
|
||||
|
||||
var response = await SetupHelper.PostWithCsrfAsync(
|
||||
client,
|
||||
"/api/v1/service-accounts",
|
||||
new CreateServiceAccountRequest("ingestion", "trusted intake", [Scopes.IngestionWrite]));
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
}
|
||||
|
||||
private static ClaimsPrincipal ServicePrincipal(string scope)
|
||||
{
|
||||
var identity = new ClaimsIdentity(
|
||||
[
|
||||
new Claim(ClaimTypes.NameIdentifier, Guid.NewGuid().ToString()),
|
||||
new Claim(ServiceTokenAuthenticationHandler.ServiceAccountClaimType, Guid.NewGuid().ToString()),
|
||||
new Claim(ServiceTokenAuthenticationHandler.ScopeClaimType, scope)
|
||||
], ServiceTokenAuthenticationDefaults.Scheme);
|
||||
|
||||
return new ClaimsPrincipal(identity);
|
||||
}
|
||||
|
||||
private static ClaimsPrincipal HumanPrincipal(Guid userId) => new(new ClaimsIdentity(
|
||||
[
|
||||
new Claim(ClaimTypes.NameIdentifier, userId.ToString()),
|
||||
new Claim(ClaimTypes.Name, "human-editor")
|
||||
], authenticationType: "cookie"));
|
||||
}
|
||||
Reference in new issue
Block a user