Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,137 +20,117 @@
import lombok.Value;
import lombok.With;

import jakarta.validation.constraints.NotNull;
import java.time.Instant;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

/**
* Immutable backoffice context container for internal portal/backoffice requests.
* Contains information about the backoffice user performing the action and the customer being impersonated.
*
* <p>This class extends the standard application context with customer impersonation capabilities:</p>
* Immutable, product-agnostic backoffice context container for internal portal/backoffice requests.
*
* <p>This context carries information about the backoffice operator performing the action and,
* optionally, about a subject being impersonated. It deliberately carries <strong>no</strong>
* product-domain concepts (no party, contract, or product). The impersonated principal is a generic
* {@code String} subject so the backoffice platform stays neutral across products.</p>
*
* <ul>
* <li><strong>backofficeUserId</strong>: The actual backoffice/admin user performing the action</li>
* <li><strong>impersonatedPartyId</strong>: The customer (party) being impersonated</li>
* <li><strong>contractId</strong>: What contract/agreement is being accessed</li>
* <li><strong>productId</strong>: What product is being accessed/modified</li>
* <li><strong>backofficeUserId</strong>: the authenticated backoffice/admin operator performing the action</li>
* <li><strong>impersonatedSubject</strong>: the generic subject being impersonated (may be {@code null})</li>
* <li><strong>tenantId</strong>: the generic tenant discriminator this context belongs to</li>
* <li><strong>backofficeRoles / backofficePermissions</strong>: authorization data for the operator</li>
* <li><strong>impersonatedSubjectRoles / impersonatedSubjectPermissions</strong>: informational authorization
* data for the impersonated subject</li>
* </ul>
* </p>
*
* <p>The impersonation is tracked for audit purposes and security validation.</p>
*
* <p><strong>Usage:</strong> Backoffice systems must send the X-Impersonate-Party-Id header to indicate
* which customer they are accessing. The X-User-Id header identifies the backoffice user.</p>
*
*
* <p>Validated identity is sourced from the {@code fireflyframework-security} platform
* ({@code SecurityContextPort} / {@code SecurityPrincipal}). Impersonation, when present, is read from
* an optional {@code X-Impersonate-Subject} request header.</p>
*
* @author Firefly Development Team
* @since 1.0.0
*/
@Value
@Builder(toBuilder = true)
@With
public class BackofficeContext {

/**
* Unique identifier of the backoffice user (admin/support) performing the action.
* This is the authenticated user in the backoffice system.
* Comes from X-User-Id header (injected by Istio for backoffice routes).
* Unique identifier of the backoffice operator (admin/support) performing the action.
* Derived from the validated security principal's subject when it is a UUID; otherwise {@code null}
* (the raw subject is then kept under the {@code "backofficeUserSubject"} attribute).
*/
@NotNull
UUID backofficeUserId;

/**
* Unique identifier of the customer (party) being impersonated.
* This is the customer whose data is being accessed or modified.
* Comes from X-Impersonate-Party-Id header (required for all backoffice operations on customer data).
*/
@NotNull
UUID impersonatedPartyId;


/**
* Unique identifier of the contract associated with this request.
* This comes from common-platform-contract-mgmt.
* Optional for operations that don't require a contract context.
* Generic subject being impersonated by the backoffice operator.
* {@code null} when no impersonation is in effect. Read from the optional
* {@code X-Impersonate-Subject} header.
*/
UUID contractId;
String impersonatedSubject;

/**
* Unique identifier of the product being accessed or modified.
* This comes from common-platform-product-mgmt.
* Optional for operations that don't require a product context.
* The generic tenant/organization this context belongs to.
* Parsed from the security principal's tenant identifier when it is a UUID; otherwise {@code null}.
*/
UUID productId;
UUID tenantId;

/**
* Roles that the backoffice user has.
* Used for authorization decisions (e.g., "admin", "support", "analyst").
* Roles that the backoffice operator holds.
* Used for authorization decisions. Sourced from the principal's authorities.
*/
Set<String> backofficeRoles;

/**
* Permissions that the backoffice user has.
* Derived from roles and used for fine-grained authorization.
* Permissions that the backoffice operator holds.
* Used for fine-grained authorization. Sourced from the principal's scopes.
*/
Set<String> backofficePermissions;

/**
* Roles that the impersonated party has in the context of this contract/product.
* These are informational - the backoffice user's permissions take precedence.
*/
Set<String> impersonatedPartyRoles;

/**
* Permissions that the impersonated party has in this context.
* These are informational - the backoffice user's permissions take precedence.
*/
Set<String> impersonatedPartyPermissions;


/**
* The tenant/organization this context belongs to.
* Links to the tenant of the impersonated party.
* Roles that the impersonated subject holds.
* Informational - the backoffice operator's permissions take precedence.
*/
UUID tenantId;
Set<String> impersonatedSubjectRoles;

/**
* Timestamp when the impersonation started (for audit trail).
* Permissions that the impersonated subject holds.
* Informational - the backoffice operator's permissions take precedence.
*/
@Builder.Default
Instant impersonationStartedAt = Instant.now();

Set<String> impersonatedSubjectPermissions;

/**
* Reason for impersonation (optional, for audit purposes).
* Example: "Customer support ticket #12345", "Administrative review"
* Example: "Support ticket #12345", "Administrative review".
*/
String impersonationReason;

/**
* IP address of the backoffice user (for audit trail).
* IP address of the backoffice operator (for audit trail).
*/
String backofficeUserIpAddress;

/**
* Additional context-specific attributes.
* Can be used to store domain-specific context information.
* Can be used to store generic context information (e.g. the raw backoffice user subject).
*/
java.util.Map<String, Object> attributes;
Map<String, Object> attributes;

/**
* Checks if the backoffice user has a specific role
*
* Checks if the backoffice operator has a specific role.
*
* @param role the role to check
* @return true if the role is present
*/
public boolean hasBackofficeRole(String role) {
return backofficeRoles != null && backofficeRoles.contains(role);
}

/**
* Checks if the backoffice user has any of the specified roles
*
* Checks if the backoffice operator has any of the specified roles.
*
* @param roles the roles to check
* @return true if any of the roles are present
*/
public boolean hasAnyBackofficeRole(String... roles) {
public boolean hasBackofficeAnyRole(String... roles) {
if (this.backofficeRoles == null || roles == null) {
return false;
}
Expand All @@ -161,66 +141,39 @@ public boolean hasAnyBackofficeRole(String... roles) {
}
return false;
}

/**
* Checks if the backoffice user has all of the specified roles
*
* @param roles the roles to check
* @return true if all roles are present
*/
public boolean hasAllBackofficeRoles(String... roles) {
if (this.backofficeRoles == null || roles == null) {
return false;
}
for (String role : roles) {
if (!this.backofficeRoles.contains(role)) {
return false;
}
}
return true;
}


/**
* Checks if the backoffice user has a specific permission
*
* Checks if the backoffice operator has a specific permission.
*
* @param permission the permission to check
* @return true if the permission is present
*/
public boolean hasBackofficePermission(String permission) {
return backofficePermissions != null && backofficePermissions.contains(permission);
}

/**
* Checks if the impersonated party has a specific role (informational)
*
* Checks if the impersonated subject has a specific role (informational).
*
* @param role the role to check
* @return true if the role is present for the impersonated party
*/
public boolean impersonatedPartyHasRole(String role) {
return impersonatedPartyRoles != null && impersonatedPartyRoles.contains(role);
}

/**
* Checks if this context has a contract association
*
* @return true if contractId is present
* @return true if the role is present for the impersonated subject
*/
public boolean hasContract() {
return contractId != null;
public boolean impersonatedSubjectHasRole(String role) {
return impersonatedSubjectRoles != null && impersonatedSubjectRoles.contains(role);
}

/**
* Checks if this context has a product association
*
* @return true if productId is present
* Indicates whether this context represents an active impersonation.
*
* @return true if an impersonated subject is set
*/
public boolean hasProduct() {
return productId != null;
public boolean isImpersonating() {
return impersonatedSubject != null;
}

/**
* Gets an attribute from the context
*
* Gets an attribute from the context.
*
* @param key the attribute key
* @param <T> the expected type
* @return the attribute value or null if not present
Expand All @@ -229,13 +182,4 @@ public boolean hasProduct() {
public <T> T getAttribute(String key) {
return attributes != null ? (T) attributes.get(key) : null;
}

/**
* Checks if this is a valid impersonation context
*
* @return true if both backoffice user and impersonated party are set
*/
public boolean isValidImpersonation() {
return backofficeUserId != null && impersonatedPartyId != null;
}
}
Loading
Loading