diff --git a/service/authorization/v2/authorization_test.go b/service/authorization/v2/authorization_test.go index 567bb2035..dc1f3ba98 100644 --- a/service/authorization/v2/authorization_test.go +++ b/service/authorization/v2/authorization_test.go @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/wrapperspb" ) var ( @@ -141,6 +142,27 @@ var ( }, expectedValidationError: "entity_identifier", }, + { + name: "entity identifier - request token invalid", + request: &authzV2.GetDecisionMultiResourceRequest{ + EntityIdentifier: &authzV2.EntityIdentifier{ + Identifier: &authzV2.EntityIdentifier_WithRequestToken{ + WithRequestToken: wrapperspb.Bool(false), + }, + }, + Action: sampleActionCreate, + Resources: []*authzV2.Resource{ + { + Resource: &authzV2.Resource_AttributeValues_{ + AttributeValues: &authzV2.Resource_AttributeValues{ + Fqns: []string{sampleResourceFQN}, + }, + }, + }, + }, + }, + expectedValidationError: "entity_identifier", + }, { name: "missing action", request: &authzV2.GetDecisionMultiResourceRequest{ @@ -505,6 +527,24 @@ func Test_GetDecisionRequest_Succeeds(t *testing.T) { }, }, }, + { + name: "entity: use request token, action: create, resource: attribute values", + request: &authzV2.GetDecisionRequest{ + EntityIdentifier: &authzV2.EntityIdentifier{ + Identifier: &authzV2.EntityIdentifier_WithRequestToken{ + WithRequestToken: wrapperspb.Bool(true), + }, + }, + Action: sampleActionCreate, + Resource: &authzV2.Resource{ + Resource: &authzV2.Resource_AttributeValues_{ + AttributeValues: &authzV2.Resource_AttributeValues{ + Fqns: []string{sampleResourceFQN}, + }, + }, + }, + }, + }, { name: "entity: token, action: create, resource: registered", request: &authzV2.GetDecisionRequest{ @@ -685,6 +725,44 @@ func Test_GetDecisionRequest_Fails(t *testing.T) { }, expectedValidationError: "entity_identifier", }, + { + name: "entity identifier (request token) but nil", + request: &authzV2.GetDecisionRequest{ + EntityIdentifier: &authzV2.EntityIdentifier{ + Identifier: &authzV2.EntityIdentifier_WithRequestToken{ + WithRequestToken: nil, + }, + }, + Action: sampleActionCreate, + Resource: &authzV2.Resource{ + Resource: &authzV2.Resource_AttributeValues_{ + AttributeValues: &authzV2.Resource_AttributeValues{ + Fqns: []string{sampleResourceFQN}, + }, + }, + }, + }, + expectedValidationError: "entity_identifier", + }, + { + name: "entity identifier (request token) but false", + request: &authzV2.GetDecisionRequest{ + EntityIdentifier: &authzV2.EntityIdentifier{ + Identifier: &authzV2.EntityIdentifier_WithRequestToken{ + WithRequestToken: wrapperspb.Bool(false), + }, + }, + Action: sampleActionCreate, + Resource: &authzV2.Resource{ + Resource: &authzV2.Resource_AttributeValues_{ + AttributeValues: &authzV2.Resource_AttributeValues{ + Fqns: []string{sampleResourceFQN}, + }, + }, + }, + }, + expectedValidationError: "entity_identifier", + }, { name: "missing action", request: &authzV2.GetDecisionRequest{ diff --git a/service/internal/access/v2/just_in_time_pdp.go b/service/internal/access/v2/just_in_time_pdp.go index f5c19d159..6e019fb71 100644 --- a/service/internal/access/v2/just_in_time_pdp.go +++ b/service/internal/access/v2/just_in_time_pdp.go @@ -14,14 +14,20 @@ import ( "github.com/opentdf/platform/protocol/go/policy" "github.com/opentdf/platform/protocol/go/policy/subjectmapping" otdfSDK "github.com/opentdf/platform/sdk" + ctxAuth "github.com/opentdf/platform/service/pkg/auth" + "google.golang.org/protobuf/types/known/wrapperspb" "github.com/opentdf/platform/service/internal/access/v2/obligations" "github.com/opentdf/platform/service/logger" ) var ( - ErrMissingRequiredSDK = errors.New("access: missing required SDK") - ErrInvalidEntityType = errors.New("access: invalid entity type") + ErrMissingRequiredSDK = errors.New("access: missing required SDK") + ErrInvalidEntityType = errors.New("access: invalid entity type") + ErrFailedToWithRequestTokenEntityIdentifier = errors.New("access: failed to use request token as entity identifier - none found in context") + ErrInvalidWithRequestTokenEntityIdentifier = errors.New("access: invalid use request token as entity identifier - must be true if provided") + + requestAuthTokenEphemeralID = "with-request-token-auth-entity" ) type JustInTimePDP struct { @@ -153,6 +159,9 @@ func (p *JustInTimePDP) GetDecision( case *authzV2.EntityIdentifier_Token: entityRepresentations, err = p.resolveEntitiesFromToken(ctx, entityIdentifier.GetToken(), skipEnvironmentEntities) + case *authzV2.EntityIdentifier_WithRequestToken: + entityRepresentations, err = p.resolveEntitiesFromRequestToken(ctx, entityIdentifier.GetWithRequestToken(), skipEnvironmentEntities) + case *authzV2.EntityIdentifier_RegisteredResourceValueFqn: regResValueFQN := strings.ToLower(entityIdentifier.GetRegisteredResourceValueFqn()) // Registered resources do not have entity representations, so only one decision is made @@ -246,6 +255,9 @@ func (p *JustInTimePDP) GetEntitlements( // registered resources do not have entity representations, so we can skip the remaining logic return p.pdp.GetEntitlementsRegisteredResource(ctx, regResValueFQN, withComprehensiveHierarchy) + case *authzV2.EntityIdentifier_WithRequestToken: + entityRepresentations, err = p.resolveEntitiesFromRequestToken(ctx, entityIdentifier.GetWithRequestToken(), skipEnvironmentEntities) + default: return nil, fmt.Errorf("entity type %T: %w", entityIdentifier.GetIdentifier(), ErrInvalidEntityType) } @@ -366,3 +378,25 @@ func (p *JustInTimePDP) resolveEntitiesFromToken( } return p.resolveEntitiesFromEntityChain(ctx, entityChains[0], skipEnvironmentEntities) } + +// resolveEntitiesFromRequestToken pulls the request token off the context where it has been set upstream +// by an interceptor and builds an entity.Token that it then resolves +func (p *JustInTimePDP) resolveEntitiesFromRequestToken( + ctx context.Context, + withRequestToken *wrapperspb.BoolValue, + skipEnvironmentEntities bool, +) ([]*entityresolutionV2.EntityRepresentation, error) { + if !withRequestToken.GetValue() { + return nil, ErrInvalidWithRequestTokenEntityIdentifier + } + rawToken := ctxAuth.GetRawAccessTokenFromContext(ctx, p.logger) + if rawToken == "" { + return nil, ErrFailedToWithRequestTokenEntityIdentifier + } + token := &entity.Token{ + Jwt: rawToken, + EphemeralId: requestAuthTokenEphemeralID, + } + + return p.resolveEntitiesFromToken(ctx, token, skipEnvironmentEntities) +}