Repository navigation
feat(core): Global RBAC Initial commit #158
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
RVANDO12
wants to merge
12
commits into
main
Choose a base branch
from
feat/RABC/step_1_global
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
c2ba1e2
feat(core): Global RBAC Initial commit
RVANDO12 0ca53eb
feat(core): Global RBAC - itegrate actions and rules
RVANDO12 573abae
feat(core): Global RBAC - update doc
RVANDO12 8a614d8
feat(core): Global RBAC - prepare step following
RVANDO12 f11fa24
feat(core): Global RBAC - cleaning logic
RVANDO12 5d895a8
feat(core): Global RBAC - vale issue
RVANDO12 c71a220
feat(core): Global RBAC - fix copilot issue
RVANDO12 ec085d8
feat(core): Global RBAC - fix copilot issue
RVANDO12 c2186cb
feat(core): Global RBAC - fix copilot issue
RVANDO12 5a5d669
fix: CBC-999 - remove useless security
RVANDO12 ef89757
feat(core): Global RBAC - fix test fail
RVANDO12 381c0b4
Merge branch 'main' into feat/RABC/step_1_global
RVANDO12 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
22 changes: 22 additions & 0 deletions
22
...om/decathlon/idp_core/domain/exception/authorization/PrincipalNotAuthorizedException.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| package com.decathlon.idp_core.domain.exception.authorization; | ||
|
|
||
| /// Indicates that a principal is not permitted to perform the requested operation. | ||
| public class PrincipalNotAuthorizedException extends RuntimeException { | ||
|
|
||
| private final String identifier; | ||
|
|
||
| /// Creates an authorization failure for the specified principal. | ||
| /// | ||
| /// @param identifier the identifier of the principal denied access | ||
| public PrincipalNotAuthorizedException(String identifier) { | ||
| super("Principal '" + identifier + "' is not authorized to perform this operation"); | ||
| this.identifier = identifier; | ||
| } | ||
|
|
||
| /// Returns the identifier of the principal denied access. | ||
| /// | ||
| /// @return the principal identifier | ||
| public String getIdentifier() { | ||
| return identifier; | ||
| } | ||
| } |
6 changes: 6 additions & 0 deletions
6
src/main/java/com/decathlon/idp_core/domain/model/authorization/AuthorizationAction.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| package com.decathlon.idp_core.domain.model.authorization; | ||
|
|
||
| /// CRUD operation requested against a platform resource. | ||
| public enum AuthorizationAction { | ||
| CREATE, READ, UPDATE, DELETE, UNSUPPORTED | ||
| } |
6 changes: 6 additions & 0 deletions
6
src/main/java/com/decathlon/idp_core/domain/model/authorization/AuthorizationMode.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| package com.decathlon.idp_core.domain.model.authorization; | ||
|
|
||
| /// Authorization strategies supported by the platform. | ||
| public enum AuthorizationMode { | ||
| GLOBAL, RBAC, ABAC | ||
| } |
20 changes: 20 additions & 0 deletions
20
src/main/java/com/decathlon/idp_core/domain/model/authorization/AuthorizationPolicy.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| package com.decathlon.idp_core.domain.model.authorization; | ||
|
|
||
| import java.util.Objects; | ||
| import java.util.Optional; | ||
| import java.util.Set; | ||
|
|
||
| /// Immutable authorization configuration used by domain authorization services. | ||
| public record AuthorizationPolicy(AuthorizationMode mode, Set<String> globalPrincipalIdentifiers, | ||
| Optional<String> requiredPrincipalProperty) { | ||
|
|
||
| public AuthorizationPolicy { | ||
| Objects.requireNonNull(mode, "mode must not be null"); | ||
| globalPrincipalIdentifiers = globalPrincipalIdentifiers != null | ||
| ? Set.copyOf(globalPrincipalIdentifiers) | ||
| : Set.of(); | ||
| Objects.requireNonNull(requiredPrincipalProperty, "requiredPrincipalProperty must not be null"); | ||
| requiredPrincipalProperty = requiredPrincipalProperty.map(String::trim) | ||
| .filter(propertyName -> !propertyName.isBlank()); | ||
| } | ||
| } |
22 changes: 22 additions & 0 deletions
22
src/main/java/com/decathlon/idp_core/domain/model/authorization/AuthorizationRequest.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| package com.decathlon.idp_core.domain.model.authorization; | ||
|
|
||
| import java.util.Objects; | ||
| import java.util.Optional; | ||
|
|
||
| import jakarta.validation.constraints.NotNull; | ||
|
|
||
| import com.decathlon.idp_core.domain.model.entity.Entity; | ||
| import com.decathlon.idp_core.domain.model.principal.PrincipalInfo; | ||
|
|
||
| /// Domain input describing a principal's request without infrastructure types. | ||
| public record AuthorizationRequest(@NotNull PrincipalInfo principal, | ||
| @NotNull Optional<Entity> principalEntity, @NotNull AuthorizationAction action, | ||
| @NotNull AuthorizationResource resource) { | ||
|
|
||
| public AuthorizationRequest { | ||
| Objects.requireNonNull(principal, "principal must not be null"); | ||
| Objects.requireNonNull(principalEntity, "principalEntity must not be null"); | ||
| Objects.requireNonNull(action, "action must not be null"); | ||
| Objects.requireNonNull(resource, "resource must not be null"); | ||
| } | ||
| } |
23 changes: 23 additions & 0 deletions
23
src/main/java/com/decathlon/idp_core/domain/model/authorization/AuthorizationResource.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| package com.decathlon.idp_core.domain.model.authorization; | ||
|
|
||
| import java.util.Objects; | ||
| import java.util.Optional; | ||
|
|
||
| import jakarta.validation.constraints.NotBlank; | ||
| import jakarta.validation.constraints.NotNull; | ||
|
|
||
| /// Identifies the resource and optional parent context for an authorization request. | ||
| public record AuthorizationResource(@NotBlank String type, @NotNull Optional<String> identifier, | ||
| @NotNull Optional<String> parentIdentifier) { | ||
|
|
||
| public static final String INBOUND_WEBHOOK_CONFIGURATION = "inbound_webhook_configuration"; | ||
| public static final String ENTITY_DYNAMIC_MAPPING = "entity_dynamic_mapping"; | ||
| public static final String ENTITY_TEMPLATE = "entity_template"; | ||
| public static final String PRINCIPAL_TEMPLATE_IDENTIFIER = "principal"; | ||
|
|
||
| public AuthorizationResource { | ||
| Objects.requireNonNull(type, "type must not be null"); | ||
| Objects.requireNonNull(identifier, "identifier must not be null"); | ||
| Objects.requireNonNull(parentIdentifier, "parentIdentifier must not be null"); | ||
| } | ||
| } |
15 changes: 15 additions & 0 deletions
15
src/main/java/com/decathlon/idp_core/domain/port/audit/AuditIdentityProviderPort.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| package com.decathlon.idp_core.domain.port.audit; | ||
|
|
||
| /// Provides the authenticated identity associated with an audit revision. | ||
| public interface AuditIdentityProviderPort { | ||
|
|
||
| /// Returns the identifier recorded for the authenticated user. | ||
| /// | ||
| /// @return authenticated user identifier | ||
| String getAuthId(); | ||
|
|
||
| /// Returns the name associated with the authenticated user. | ||
| /// | ||
| /// @return authenticated user name | ||
| String getName(); | ||
| } |
106 changes: 106 additions & 0 deletions
106
.../java/com/decathlon/idp_core/domain/service/authorization/GlobalAuthorizationService.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,106 @@ | ||
| package com.decathlon.idp_core.domain.service.authorization; | ||
|
|
||
| import org.springframework.stereotype.Service; | ||
|
|
||
| import com.decathlon.idp_core.domain.exception.authorization.PrincipalNotAuthorizedException; | ||
| import com.decathlon.idp_core.domain.model.authorization.AuthorizationAction; | ||
| import com.decathlon.idp_core.domain.model.authorization.AuthorizationMode; | ||
| import com.decathlon.idp_core.domain.model.authorization.AuthorizationPolicy; | ||
| import com.decathlon.idp_core.domain.model.authorization.AuthorizationRequest; | ||
| import com.decathlon.idp_core.domain.model.authorization.AuthorizationResource; | ||
| import com.decathlon.idp_core.domain.model.entity.Entity; | ||
| import com.decathlon.idp_core.domain.model.principal.PrincipalKind; | ||
|
|
||
| /// Applies the global authorization policy to a principal request. | ||
| @Service | ||
| public class GlobalAuthorizationService { | ||
|
|
||
| private static final String IS_ADMIN_PROPERTY = "is_admin"; | ||
| private static final String PRINCIPAL_TEMPLATE_IDENTIFIER = AuthorizationResource.PRINCIPAL_TEMPLATE_IDENTIFIER; | ||
| private static final String ENTITY_RESOURCE = "entity"; | ||
| private static final String PRINCIPAL_RESOURCE = "principal"; | ||
| private static final String ENTITY_TEMPLATE_RESOURCE = AuthorizationResource.ENTITY_TEMPLATE; | ||
| private static final String ENTITY_DYNAMIC_MAPPING_RESOURCE = AuthorizationResource.ENTITY_DYNAMIC_MAPPING; | ||
|
|
||
| /// Authorizes break-glass principals, catalog administrators, eligible | ||
| /// service-account operations, and reads. | ||
| /// | ||
| /// @param request the principal and operation being authorized | ||
| /// @param policy the configured global authorization policy | ||
| /// @throws PrincipalNotAuthorizedException when the request is not permitted | ||
| public void authorize(AuthorizationRequest request, AuthorizationPolicy policy) { | ||
| String identifier = request.principal().identifier(); | ||
| if (policy.mode() != AuthorizationMode.GLOBAL | ||
| || request.action() == AuthorizationAction.UNSUPPORTED) { | ||
| throw new PrincipalNotAuthorizedException(identifier); | ||
| } | ||
|
|
||
| if (policy.globalPrincipalIdentifiers().contains(identifier)) { | ||
| return; | ||
| } | ||
|
|
||
| if (request.principalEntity().filter(this::isCatalogAdministrator).isPresent()) { | ||
| return; | ||
| } | ||
|
|
||
| boolean missingConfiguredAccessProperty = request.principal().kind() == PrincipalKind.HUMAN | ||
| && policy.requiredPrincipalProperty() | ||
| .map(propertyName -> request.principalEntity() | ||
| .filter(entity -> hasRequiredPrincipalProperty(entity, propertyName)).isEmpty()) | ||
| .orElse(false); | ||
| if (missingConfiguredAccessProperty) { | ||
| throw new PrincipalNotAuthorizedException(identifier); | ||
| } | ||
|
|
||
| if (request.principal().kind() == PrincipalKind.SERVICE_ACCOUNT) { | ||
| if (isPrincipalManagementWrite(request)) { | ||
| throw new PrincipalNotAuthorizedException(identifier); | ||
| } | ||
| if (!isPlatformAdminOnlyCreation(request)) { | ||
| return; | ||
|
Copilot marked this conversation as resolved.
|
||
| } | ||
| } | ||
|
|
||
| if (request.action() == AuthorizationAction.READ) { | ||
| return; | ||
| } | ||
|
|
||
| throw new PrincipalNotAuthorizedException(identifier); | ||
| } | ||
|
|
||
| private boolean isPlatformAdminOnlyCreation(AuthorizationRequest request) { | ||
| return request.action() == AuthorizationAction.CREATE | ||
| && AuthorizationResource.INBOUND_WEBHOOK_CONFIGURATION.equals(request.resource().type()); | ||
| } | ||
|
|
||
| private boolean isPrincipalManagementWrite(AuthorizationRequest request) { | ||
| if (request.action() == AuthorizationAction.READ) { | ||
| return false; | ||
| } | ||
|
|
||
| AuthorizationResource resource = request.resource(); | ||
| return PRINCIPAL_RESOURCE.equals(resource.type()) | ||
| || (ENTITY_RESOURCE.equals(resource.type()) && resource.parentIdentifier() | ||
| .filter(PRINCIPAL_TEMPLATE_IDENTIFIER::equals).isPresent()) | ||
| || (ENTITY_TEMPLATE_RESOURCE.equals(resource.type()) | ||
| && resource.identifier().filter(PRINCIPAL_TEMPLATE_IDENTIFIER::equals).isPresent()) | ||
| || ((ENTITY_DYNAMIC_MAPPING_RESOURCE.equals(resource.type()) | ||
| || AuthorizationResource.INBOUND_WEBHOOK_CONFIGURATION.equals(resource.type())) | ||
| && resource.parentIdentifier().filter(PRINCIPAL_TEMPLATE_IDENTIFIER::equals) | ||
| .isPresent()); | ||
|
RVANDO12 marked this conversation as resolved.
|
||
| } | ||
|
|
||
| private boolean isCatalogAdministrator(Entity principalEntity) { | ||
| return PRINCIPAL_TEMPLATE_IDENTIFIER.equals(principalEntity.templateIdentifier()) | ||
| && principalEntity.properties().stream() | ||
| .anyMatch(property -> IS_ADMIN_PROPERTY.equals(property.name()) | ||
| && Boolean.parseBoolean(property.value())); | ||
| } | ||
|
|
||
| private boolean hasRequiredPrincipalProperty(Entity principalEntity, String propertyName) { | ||
| return PRINCIPAL_TEMPLATE_IDENTIFIER.equals(principalEntity.templateIdentifier()) | ||
| && principalEntity.properties().stream() | ||
| .anyMatch(property -> propertyName.equals(property.name()) | ||
| && Boolean.parseBoolean(property.value())); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.