Skip to content

feat(core): Global RBAC Initial commit - #158

Draft
RVANDO12 wants to merge 6 commits into
mainfrom
feat/RABC/step_1_global
Draft

RVANDO12 wants to merge 6 commits into
mainfrom
feat/RABC/step_1_global

Conversation

@RVANDO12

@RVANDO12 RVANDO12 commented Sep 29, 2026 •

Copy link
Copy Markdown
Collaborator

PR Description

Title: feat(security)!: enforce global authorization for catalog APIs

What this PR Provides

  • Adds Phase 1 GLOBAL authorization to the JWT, OAuth2 login, and mock API security chains.
  • Grants platform-wide CRUD access to configured break-glass principals and principals whose principal entity has is_admin: true.
  • Restricts human principals to read operations by default. An optional property-based gate can further restrict human access: when configured, the named principal property must be true for access to protected API endpoints, including reads. When unset, the gate is disabled.
  • Allows service accounts CRUD operations except for creating inbound webhook configurations and writing principal records or the principal template through the regular API.
  • Keeps webhook ingestion on its existing connector-level security. Trusted ingestion mappings can update principal properties such as is_admin and the configured human-access property.
  • Provisions new principals with is_admin: false; JIT does not grant admin or access-gate status from token attributes, and it does not overwrite existing principal data.
  • Ignores entity-template permission and ownership settings in this phase. Removes the permissive wildcard baseline authority; JWT scopes do not grant platform permissions.
  • Uses a configurable claim as the human principal identifier. IDP_PRINCIPAL_IDENTIFIER_CLAIM defaults to sub; service accounts continue to use client_id, then azp, then sub.
  • Removes automatic lookup, aliasing, and migration of principals previously keyed by another identifier. Principal entities are provisioned directly under the configured identifier.
  • Adds action/resource authorization context to support a future generic RBAC implementation without implementing roles or granular permissions in this PR. The resource is declared on controllers with @AuthorizedResource and resolved from Spring MVC's matched handler and path variables, so the security layer does not parse request URLs. A request that matches no annotated handler gets the resource type unknown.

Fixes

N/A — no issue number was provided.

Review

The reviewer must double-check these points:

  • The reviewer has tested the feature
  • The reviewer has reviewed the implementation of the feature
  • The documentation has been updated
  • The feature implementation respects the Technical Doc / ADR previously produced
  • The Pull Request title has a ! after the type/scope to identify the breaking change in the release note and ensure we will release a major version.

How to test

Initial state

  • Start IDP-Core with a database and the required catalog templates, including a principal template with a Boolean is_admin property.
  • Configure a human token with the selected identifier claim. The default is sub; to use a different claim, set IDP_PRINCIPAL_IDENTIFIER_CLAIM to its name, such as uuid.
  • Configure IDP_PLATFORM_ADMIN_IDENTIFIER or IDP_SUPER_ADMIN_IDENTIFIER with the exact value of the selected human identifier claim for a break-glass admin. These identifiers are checked even when JIT has not created a principal entity; use immutable, globally unique values.
  • To test the optional human-access gate, set IDP_REQUIRED_PRINCIPAL_PROPERTY to a property name, such as is_idp_user. Add that optional Boolean property to the principal template and configure a trusted ingestion mapping to populate it. If the variable is unset, the additional gate is disabled.
  • Prepare a standard human token, a token recognized as a service account, and a webhook connector configured with its own ingestion security.

Below is an example using is_idp_user as a chosen property name, not a default. The gate is disabled unless IDP_REQUIRED_PRINCIPAL_PROPERTY is set.

1. Configure the gate

Base configuration:

app:
  security:
    authorization:
      mode: GLOBAL
      global-principal-identifiers: ${IDP_SUPER_ADMIN_IDENTIFIER:},${IDP_PLATFORM_ADMIN_IDENTIFIER:}
      required-principal-property: ${IDP_REQUIRED_PRINCIPAL_PROPERTY:}

To enable it, before starting the app:

$env:IDP_REQUIRED_PRINCIPAL_PROPERTY = "is_idp_user"
$env:IDP_PRINCIPAL_IDENTIFIER_CLAIM = "sub"
$env:IDP_PLATFORM_ADMIN_IDENTIFIER = "00000000-0000-4000-8000-000000000001"

Before enabling the gate, add is_idp_user as an optional Boolean property on the principal template, then configure the trusted ingestion webhook mapping to populate it. There is no production migration for this optional property.

To disable the gate, leave IDP_REQUIRED_PRINCIPAL_PROPERTY unset or set it to an empty value.

2. Test access with curl

Set a human token:

$env:HUMAN_TOKEN = "<human-access-token>"

Assuming the web-service template exists, request its entities:

curl.exe -i -H "Authorization: Bearer $env:HUMAN_TOKEN" "http://localhost:8084/api/v1/entities/web-service"
Configuration and principal Expected result
Gate unset 200 OK for the normal human read-only policy
Gate set; principal has is_idp_user: true 200 OK
Gate set; property is missing, false, or invalid 403 Forbidden
Principal is is_admin: true or is on the break-glass allow-list 200 OK, regardless of the gate property

The principal’s access property comes from the catalog entity, so after ingestion updates it, use a newly authenticated request to observe the change.

With the gate enabled, a regular human still cannot write, even when the property is true:

curl.exe -i -X POST "http://localhost:8084/api/v1/entities/web-service" `
  -H "Authorization: Bearer $env:HUMAN_TOKEN" `
  -H "Content-Type: application/json" `
  --data-raw '{"identifier":"curl-test","name":"curl-test","properties":{},"relations":[]}'

Expected result: 403 Forbidden. A platform admin or break-glass principal can perform writes.

3. Update a principal for a local test

For manual testing, an authorized platform admin can set the property using the entity API, provided it has been added to the principal template:

$env:ADMIN_TOKEN = "<platform-admin-token>"
curl.exe -i -X PUT "http://localhost:8084/api/v1/entities/principal/alice" `
  -H "Authorization: Bearer $env:ADMIN_TOKEN" `
  -H "Content-Type: application/json" `
  --data-raw '{"name":"Alice","properties":{"kind":"HUMAN","is_admin":"false","is_idp_user":"true"},"relations":[]}'

Expected result: 200 OK. This PUT replaces the entity’s properties and relations, so preserve any existing values you need. In production, use the trusted ingestion mapping as the source of this property rather than relying on a manual update.

Authorization scenarios

  1. Platform admin: Call catalog APIs to read, create, update, and delete resources. Confirm the operations are allowed. Confirm the admin can create webhook configurations and manage principal records, subject to the normal domain validation rules.
  2. Dynamic admin: Through an authorized admin operation or trusted ingestion mapping, set is_admin: true on another principal entity. Authenticate as that principal and confirm it receives platform-wide CRUD access.
  3. Human without the optional gate: Leave IDP_REQUIRED_PRINCIPAL_PROPERTY unset. Confirm reads are allowed and catalog mutations are denied.
  4. Human with the optional gate: Set IDP_REQUIRED_PRINCIPAL_PROPERTY to a Boolean property name. Confirm a human with the property set to true can read, while a missing or false property results in denial, including for read endpoints. Confirm an is_admin principal and a break-glass principal bypass the gate.
  5. Service account: Confirm catalog CRUD is allowed except for principal-record/template writes and POST /api/v1/inbound_webhooks. Confirm webhook delivery continues to use connector-level security independently and trusted mappings can update principal properties.
  6. Identifier configuration: Set IDP_PRINCIPAL_IDENTIFIER_CLAIM=uuid and authenticate with a token containing a unique uuid claim. Confirm the principal is provisioned under that value. Confirm authentication does not proceed successfully when the configured claim is absent.
  7. RBAC rollout behavior: Confirm template-level permission and ownership settings do not change Phase 1 decisions, and that an unimplemented authorization mode fails closed.

Automated validation

The focused authorization, configuration, filter, and principal-provisioning tests pass (34 tests). AuthorizationRequestFactoryTest and GlobalAuthorizationFilterTest exercise request mapping against the annotated controllers, including read-only POST endpoints, named path variables, and unmatched routes. The Testcontainers-backed integration tests could not start in the previous environment because Docker was unavailable.

Markdown lint passes. The strict documentation build is blocked by an existing broken link in docs/src/contributing/adrs/_template.md to 0005-example.md.

Breaking changes

  • Behavior modification: Authenticated humans no longer receive write access through a wildcard baseline authority. They are read-only by default unless promoted to platform admin.
  • Optional access gate: When IDP_REQUIRED_PRINCIPAL_PROPERTY is set, humans need the named principal property set to true to access protected API endpoints. When unset, this extra condition is disabled.
  • Behavior modification: Service accounts cannot create webhook configurations or write principal records/templates through the regular API.
  • Principal identifiers: Human principals now use the configured identifier claim. Existing entities keyed by another claim are not renamed or merged, and their is_admin property is not carried over automatically.
  • Configuration: Break-glass variables use IDP_SUPER_ADMIN_IDENTIFIER and IDP_PLATFORM_ADMIN_IDENTIFIER; deployments must provide values matching the resulting principal identifiers.
  • API JSON schema modification: N/A.

Context of the Breaking Change

The previous permissive baseline did not enforce the Phase 1 global authorization matrix. Human identifier extraction also depended on implicit claim behavior and included optional historical-principal migration support. This PR removes those behaviors and makes the chosen human identifier claim explicit.

Result of the Breaking Change

Humans can read catalog data but cannot mutate it unless their principal entity has is_admin: true or their identifier is configured for break-glass access. If an administrator configures the optional human-access gate, a missing or false configured property also blocks access to protected APIs. Applications must configure the identifier claim and break-glass values to match their IdP tokens. Existing principals are not automatically migrated when the identifier changes.

@RVANDO12
RVANDO12 marked this pull request as draft September 29, 2026 08:11
@github-code-quality

github-code-quality Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

Code Coverage Overview

Languages: Java

Java / code-coverage/jacoco

The overall line coverage in commit 5d895a8 in the feat/RABC/step_1_glo... branch remains at 91%, unchanged from commit 98f5452 in the main branch.

Show a line coverage summary of the most impacted files.
File main 98f5452 feat/RABC/step_1_glo... 5d895a8 +/-
com/decathlon/i...dException.java 0% 75% +75%
com/decathlon/i...estFactory.java 0% 92% +92%
com/decathlon/i...ionService.java 0% 100% +100%
com/decathlon/i...tionFilter.java 0% 100% +100%
com/decathlon/i...tionPolicy.java 0% 100% +100%
com/decathlon/i...Properties.java 0% 100% +100%
com/decathlon/i...figuration.java 0% 100% +100%
com/decathlon/i...ionRequest.java 0% 100% +100%
com/decathlon/i...onResource.java 0% 100% +100%
com/decathlon/i...tionAction.java 0% 100% +100%

Updated October 02, 2026 12:02 UTC

@sonarqubecloud

sonarqubecloud Bot commented Oct 2, 2026

Copy link
Copy Markdown

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Unresolved authorization bypasses can grant service accounts unintended privileges, including platform-admin access.

Review effort: Balanced
Findings: 3 High severity · 4 Medium severity

Open (7)
What changed in this PR

Introduces Phase 1 global authorization for IDP-Core’s catalog APIs, replacing permissive baseline access with principal-based policies.

Changes:

  • Adds authorization policies, controller resource metadata, and security-chain enforcement.
  • Configures human identifiers and provisions principals without admin privileges.
  • Updates security tests, integration fixtures, and authentication documentation.
File Description
src/​test/​resources/​db/​test/​R__6_insert_principal_user_for_test.sql Adds principal fixtures for authorization tests.
src/​test/​resources/​db/​test/​R__1_Insert_test_data.sql Adds an optional access-gate property.
src/​test/​resources/​application-test.yml Removes wildcard baseline configuration.
src/​test/​java/​com/​decathlon/​idp_core/​SecurityConfigurationTest.java Checks removal of universal authority.
src/​test/​java/​com/​decathlon/​idp_core/​infrastructure/​adapters/​api/​handler/​ApiExceptionHandlerTest.java Tests authorization exception mapping.
src/​test/​java/​com/​decathlon/​idp_core/​infrastructure/​adapters/​api/​controller/​PrincipalControllerTest.java Tests standard human read access.
src/​test/​java/​com/​decathlon/​idp_core/​infrastructure/​adapters/​api/​controller/​InboundWebhookManagementControllerTest.java Tests denied human webhook creation.
src/​test/​java/​com/​decathlon/​idp_core/​infrastructure/​adapters/​api/​controller/​EntityTemplateControllerTest.java Seeds authorized principals for integration tests.
src/​test/​java/​com/​decathlon/​idp_core/​infrastructure/​adapters/​api/​controller/​EntityDynamicMappingControllerTest.java Adds principal fixtures and adjusts payload escaping.
src/​test/​java/​com/​decathlon/​idp_core/​infrastructure/​adapters/​api/​controller/​AuditControllerTest.java Uses admin principals for lifecycle tests.
src/​test/​java/​com/​decathlon/​idp_core/​infrastructure/​adapters/​api/​configuration/​SecurityRolePropertiesTest.java Deletes obsolete baseline-role tests.
src/​test/​java/​com/​decathlon/​idp_core/​infrastructure/​adapters/​api/​configuration/​security/​chains/​PublicFilterChainConfigTest.java Updates authentication configuration construction.
src/​test/​java/​com/​decathlon/​idp_core/​infrastructure/​adapters/​api/​configuration/​security/​chains/​OAuth2LoginFilterChainConfigTest.java Checks authorization filter placement.
src/​test/​java/​com/​decathlon/​idp_core/​infrastructure/​adapters/​api/​configuration/​security/​chains/​MockFilterChainConfigTest.java Checks mock-chain authorization wiring.
src/​test/​java/​com/​decathlon/​idp_core/​infrastructure/​adapters/​api/​configuration/​security/​chains/​JwtFilterChainConfigTest.java Checks JWT-chain authorization wiring.
src/​test/​java/​com/​decathlon/​idp_core/​infrastructure/​adapters/​api/​configuration/​AuthorizationConfigurationTest.java Tests policy binding and filter registration.
src/​test/​java/​com/​decathlon/​idp_core/​infrastructure/​adapters/​api/​configuration/​AuthenticationPropertiesTest.java Tests identifier-claim configuration.
src/​test/​java/​com/​decathlon/​idp_core/​infrastructure/​adapters/​api/​auth/​UnifiedUserProviderTest.java Tests shared identity extraction.
src/​test/​java/​com/​decathlon/​idp_core/​infrastructure/​adapters/​api/​auth/​TestHandlerMappings.java Supplies controller mappings for authorization tests.
src/​test/​java/​com/​decathlon/​idp_core/​infrastructure/​adapters/​api/​auth/​JitProvisioningFilterTest.java Checks request-scoped principal sharing.
src/​test/​java/​com/​decathlon/​idp_core/​infrastructure/​adapters/​api/​auth/​GlobalAuthorizationFilterTest.java Tests authorization filtering and denial behavior.
src/​test/​java/​com/​decathlon/​idp_core/​infrastructure/​adapters/​api/​auth/​AuthorizationRequestFactoryTest.java Tests route-derived actions and resources.
src/​test/​java/​com/​decathlon/​idp_core/​domain/​service/​principal/​PrincipalProvisioningServiceTest.java Checks non-admin provisioning defaults.
src/​test/​java/​com/​decathlon/​idp_core/​domain/​service/​principal/​PrincipalExtractorTest.java Tests configured human identifier extraction.
src/​test/​java/​com/​decathlon/​idp_core/​domain/​service/​authorization/​GlobalAuthorizationServiceTest.java Tests the global authorization matrix.
src/​main/​resources/​application.yml Adds authorization and identifier settings.
src/​main/​resources/​application-local.yml Selects GitHub’s identifier claim locally.
src/​main/​java/​com/​decathlon/​idp_core/​infrastructure/​adapters/​api/​principal/​strategies/​OAuth2UserPrincipalExtractionStrategy.java Uses configured OAuth2/OIDC identifiers.
src/​main/​java/​com/​decathlon/​idp_core/​infrastructure/​adapters/​api/​principal/​strategies/​JwtPrincipalExtractionStrategy.java Uses configured human JWT identifiers.
src/​main/​java/​com/​decathlon/​idp_core/​infrastructure/​adapters/​api/​handler/​ApiExceptionHandler.java Maps authorization exceptions to 403.
src/​main/​java/​com/​decathlon/​idp_core/​infrastructure/​adapters/​api/​controller/​PrincipalController.java Declares the principal resource.
src/​main/​java/​com/​decathlon/​idp_core/​infrastructure/​adapters/​api/​controller/​InboundWebhookConfigurationController.java Declares webhook configuration resources.
src/​main/​java/​com/​decathlon/​idp_core/​infrastructure/​adapters/​api/​controller/​EntityTemplateController.java Declares template resources.
src/​main/​java/​com/​decathlon/​idp_core/​infrastructure/​adapters/​api/​controller/​EntityGraphController.java Declares entity graph resources.
src/​main/​java/​com/​decathlon/​idp_core/​infrastructure/​adapters/​api/​controller/​EntityDynamicMappingController.java Marks mapping resources and read-only dry runs.
src/​main/​java/​com/​decathlon/​idp_core/​infrastructure/​adapters/​api/​controller/​EntityController.java Marks entity resources and read-only searches.
src/​main/​java/​com/​decathlon/​idp_core/​infrastructure/​adapters/​api/​controller/​AuditController.java Declares audit resources.
src/​main/​java/​com/​decathlon/​idp_core/​infrastructure/​adapters/​api/​configuration/​SecurityRoleProperties.java Deletes baseline-role configuration.
src/​main/​java/​com/​decathlon/​idp_core/​infrastructure/​adapters/​api/​configuration/​SecurityConfiguration.java Removes wildcard authority assignment.
src/​main/​java/​com/​decathlon/​idp_core/​infrastructure/​adapters/​api/​configuration/​security/​chains/​OAuth2LoginFilterChainConfig.java Enforces authorization after provisioning.
src/​main/​java/​com/​decathlon/​idp_core/​infrastructure/​adapters/​api/​configuration/​security/​chains/​MockFilterChainConfig.java Adds mock-chain authorization enforcement.
src/​main/​java/​com/​decathlon/​idp_core/​infrastructure/​adapters/​api/​configuration/​security/​chains/​JwtFilterChainConfig.java Adds JWT-chain authorization enforcement.
src/​main/​java/​com/​decathlon/​idp_core/​infrastructure/​adapters/​api/​configuration/​AuthorizationProperties.java Binds external authorization settings.
src/​main/​java/​com/​decathlon/​idp_core/​infrastructure/​adapters/​api/​configuration/​AuthorizationConfiguration.java Builds the domain policy and controls registration.
src/​main/​java/​com/​decathlon/​idp_core/​infrastructure/​adapters/​api/​configuration/​AuthenticationProperties.java Adds the human identifier-claim setting.
src/​main/​java/​com/​decathlon/​idp_core/​infrastructure/​adapters/​api/​auth/​UnifiedUserProvider.java Aligns audit identities with principal extraction.
src/​main/​java/​com/​decathlon/​idp_core/​infrastructure/​adapters/​api/​auth/​ProvisionedPrincipalContext.java Shares provisioned principals within requests.
src/​main/​java/​com/​decathlon/​idp_core/​infrastructure/​adapters/​api/​auth/​JitProvisioningFilter.java Exposes provisioning results to authorization.
src/​main/​java/​com/​decathlon/​idp_core/​infrastructure/​adapters/​api/​auth/​GlobalAuthorizationFilter.java Enforces the global policy on authenticated requests.
src/​main/​java/​com/​decathlon/​idp_core/​infrastructure/​adapters/​api/​auth/​AuthorizedResource.java Defines controller resource metadata.
src/​main/​java/​com/​decathlon/​idp_core/​infrastructure/​adapters/​api/​auth/​AuthorizationRequestFactory.java Derives authorization context from MVC mappings.
src/​main/​java/​com/​decathlon/​idp_core/​domain/​service/​principal/​PrincipalProvisioningService.java Defaults new principals to non-admin.
src/​main/​java/​com/​decathlon/​idp_core/​domain/​service/​authorization/​GlobalAuthorizationService.java Implements global access decisions.
src/​main/​java/​com/​decathlon/​idp_core/​domain/​model/​authorization/​AuthorizationResource.java Models resource identifiers and parent context.
src/​main/​java/​com/​decathlon/​idp_core/​domain/​model/​authorization/​AuthorizationRequest.java Models principal, action, and resource input.
src/​main/​java/​com/​decathlon/​idp_core/​domain/​model/​authorization/​AuthorizationPolicy.java Stores immutable authorization policy settings.
src/​main/​java/​com/​decathlon/​idp_core/​domain/​model/​authorization/​AuthorizationMode.java Defines current and future policy modes.
src/​main/​java/​com/​decathlon/​idp_core/​domain/​model/​authorization/​AuthorizationAction.java Defines supported authorization actions.
src/​main/​java/​com/​decathlon/​idp_core/​domain/​exception/​authorization/​PrincipalNotAuthorizedException.java Represents denied domain operations.
docs/​src/​concepts/​authentication.md Documents global policies and identifier configuration.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

throw new PrincipalNotAuthorizedException(identifier);
}

if (request.principal().kind() == PrincipalKind.SERVICE_ACCOUNT) {
Comment on lines +58 to +59
if (!isPlatformAdminOnlyCreation(request)) {
return;
Comment on lines +84 to +85
|| (ENTITY_TEMPLATE_RESOURCE.equals(resource.type())
&& resource.identifier().filter(PRINCIPAL_TEMPLATE_IDENTIFIER::equals).isPresent());
Comment on lines +73 to +75
var principal = principalContext != null
? principalContext.principal()
: principalExtractor.extractPrincipalInfo(authentication);
} catch (PrincipalNotAuthorizedException _) {
log.warn("Authorization denied for principal {} on {} {}", principal.identifier(),
request.getMethod(), request.getRequestURI());
response.sendError(HttpServletResponse.SC_FORBIDDEN);
.addFilterAfter(jitProvisioningFilter, MockJwtAuthenticationFilter.class);
.addFilterAfter(jitProvisioningFilter, MockJwtAuthenticationFilter.class)
// 3. Apply the global authorization policy after the principal is provisioned
.addFilterAfter(globalAuthorizationFilter, JitProvisioningFilter.class);
enabled: true
mock:
enabled: false
principal-identifier-claim: id

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants