Chapter 07 / 09
ENSK

SmartExchange · .NET 10 · Chapter 07

Intaker and Web API integration

The same Reader → Resolver → Client → SQL adapter serves both consumers. Only query construction and operation orchestration stay application-specific.

Shared code · Intaker + Web APIReviewed · 2026-10-27

Already wired in Intaker source

MicroApp.Core.Intaker · OnMessageAxtionExportXlsx.cs
// OnMessageAxtionExportXlsx · current source excerpts
authorizationMapping = ExportAuthorizationResourceCatalog.GetMapping(
    importTemplateSheet.IntakeSheet);

var reader = new LegacySqlAuthorizationGrantAdapter(
    (System.Data.Common.DbConnection)kata.Db.Connection, msg.Header.Product);
IAuthorizationContextResolver resolver =
    new SmartExchangeAuthorizationContextResolver(reader);
var principal = await resolver.ResolveAsync(
    new(userId, msg.Header.Product, authorizationMapping.ResourceKind));

authorizationPlan = await exportAuthorizationClient.PlanResourcesAsync(new(
    requestId, principal,
    new AuthorizationResource { Kind = authorizationMapping.ResourceKind },
    "export"));

// After PrepareQueryForExport; before filters, paging and Execute:
exportQueryBuilder.ApplyAuthorizationPlan(
    authorizationPlan, authorizationMapping,
    new SqlKataAuthorizationQueryAdapter());

MicroApplication.AuthorizeExports is an application-owned switch, currently true by default. The handler requires ActionBy and uses the database resolved for Header.Product. It logs request/decision IDs and rejects AlwaysDenied before query execution.

ExportAuthorizationResourceCatalog.GetMapping(intakeSheet) resolves the CRM entity and GUID key, then delegates to the shared factory/registry. Its current code uses Company to locate the CRM assembly; it has no per-entity relationship switch. ExportQueryBuilder.ApplyAuthorizationPlan() simply calls adapter.Apply().

One integration for either resource

Proposed Web API integration · shared code; trusted operation parameters
// resourceKind, action, policyVersion and rootIdColumn are server-owned.
// The database connection must match the authenticated product instance.
var reader = new LegacySqlAuthorizationGrantAdapter(connection, product);
var resolver = new SmartExchangeAuthorizationContextResolver(reader);
var principal = await resolver.ResolveAsync(
    new(userId, product, resourceKind), cancellationToken);
var plan = await policyClient.PlanResourcesAsync(new(
    requestId, principal,
    new AuthorizationResource { Kind = resourceKind, PolicyVersion = policyVersion },
    action), cancellationToken);
var mapping = AuthorizationSqlMappingFactory.Create(
    resourceKind, action,
    new AuthorizationSqlAttribute(rootIdColumn, AuthorizationSqlValueType.Guid),
    SmartExchangeAuthorizationRelationshipRegistry.GetRelationships(resourceKind));
query = queryAdapter.Apply(query, plan, mapping);
// Then count/page/execute; never ignore an authorization failure.
Operation parameterPerson exportInterestedParty export
resourceKind / actionPerson / exportInterestedParty / export
rootIdColumnPerson.IdInterestedParty.Id
Demo policy versionpresentation-demopresentation-demo
Relationship resolved by shared metadatacompanyIds → CompanyprojectIds → Project

Intaker already chooses its resource from the template and always plans export. The API supplies its own query alias and action. Neither needs separate Person/InterestedParty authorization code. The demo policies are not deployed; the real application currently defaults to policy version default.

Concrete records: remaining shared work

Cerbos CheckResources is available and the Project Scope policy was verified through it. Our current IAuthorizationPolicyClient only exposes PlanResources; a shared CheckResources contract/client method is still missing.

A shared resource-attribute provider must load trusted facts such as an InterestedParty’s projectIds or a Person’s companyIds. Define required attributes per resource first; optionally narrow them by action later to simplify read/update/delete queries. This provider is proposed, not implemented.

Coverage limit: the current export POC filters root rows. It does not independently authorize every related entity/column included by a template. That requires a separate explicit design and validation.