From 30c3735dc7660beb1a81a9c19f8d810bb0df7499 Mon Sep 17 00:00:00 2001 From: Elizabeth Healy Date: Thu, 10 Oct 2024 16:23:56 -0400 Subject: [PATCH 1/5] dummy ers mode --- .../dummy/dummy_entity_resolution.go | 151 ++++++++++++++++++ .../dummy/dummy_entity_resolution_test.go | 128 +++++++++++++++ service/entityresolution/entityresolution.go | 43 +++-- .../keycloak/keycloak_entity_resolution.go | 32 ++++ 4 files changed, 329 insertions(+), 25 deletions(-) create mode 100644 service/entityresolution/dummy/dummy_entity_resolution.go create mode 100644 service/entityresolution/dummy/dummy_entity_resolution_test.go diff --git a/service/entityresolution/dummy/dummy_entity_resolution.go b/service/entityresolution/dummy/dummy_entity_resolution.go new file mode 100644 index 000000000..e87fd8b1c --- /dev/null +++ b/service/entityresolution/dummy/dummy_entity_resolution.go @@ -0,0 +1,151 @@ +package entityresolution + +import ( + "context" + "errors" + "fmt" + "log/slog" + + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "github.com/lestrrat-go/jwx/v2/jwt" + "github.com/opentdf/platform/protocol/go/authorization" + "github.com/opentdf/platform/protocol/go/entityresolution" + auth "github.com/opentdf/platform/service/authorization" + "github.com/opentdf/platform/service/logger" + "github.com/opentdf/platform/service/pkg/serviceregistry" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/types/known/anypb" + "google.golang.org/protobuf/types/known/structpb" +) + +type DummyEntityResolutionService struct { //nolint:revive // allow for simple naming + entityresolution.UnimplementedEntityResolutionServiceServer + logger *logger.Logger +} + +func RegisterDummyERS(config serviceregistry.ServiceConfig, logger *logger.Logger) (any, serviceregistry.HandlerServer) { + return &DummyEntityResolutionService{logger: logger}, + func(ctx context.Context, mux *runtime.ServeMux, server any) error { + return entityresolution.RegisterEntityResolutionServiceHandlerServer(ctx, mux, server.(entityresolution.EntityResolutionServiceServer)) //nolint:forcetypeassert // allow type assert, following other services + } +} + +func (s DummyEntityResolutionService) ResolveEntities(ctx context.Context, req *entityresolution.ResolveEntitiesRequest) (*entityresolution.ResolveEntitiesResponse, error) { + resp, err := EntityResolution(ctx, req, s.logger) + return &resp, err +} + +func (s DummyEntityResolutionService) CreateEntityChainFromJwt(ctx context.Context, req *entityresolution.CreateEntityChainFromJwtRequest) (*entityresolution.CreateEntityChainFromJwtResponse, error) { + resp, err := CreateEntityChainFromJwt(ctx, req, s.logger) + return &resp, err +} + +func CreateEntityChainFromJwt( + ctx context.Context, + req *entityresolution.CreateEntityChainFromJwtRequest, + logger *logger.Logger, +) (entityresolution.CreateEntityChainFromJwtResponse, error) { + entityChains := []*authorization.EntityChain{} + // for each token in the tokens form an entity chain + for _, tok := range req.GetTokens() { + entities, err := getEntitiesFromToken(ctx, tok.GetJwt(), logger) + if err != nil { + return entityresolution.CreateEntityChainFromJwtResponse{}, err + } + entityChains = append(entityChains, &authorization.EntityChain{Id: tok.GetId(), Entities: entities}) + } + + return entityresolution.CreateEntityChainFromJwtResponse{EntityChains: entityChains}, nil +} + +func EntityResolution(ctx context.Context, + req *entityresolution.ResolveEntitiesRequest, logger *logger.Logger, +) (entityresolution.ResolveEntitiesResponse, error) { + + payload := req.GetEntities() + + var resolvedEntities []*entityresolution.EntityRepresentation + + for idx, ident := range payload { + var entityStruct = &structpb.Struct{} + switch ident.GetEntityType().(type) { + case *authorization.Entity_Claims: + claims := ident.GetClaims() + if claims != nil { + err := claims.UnmarshalTo(entityStruct) + if err != nil { + return entityresolution.ResolveEntitiesResponse{}, fmt.Errorf("error unpacking anypb.Any to structpb.Struct: %w", err) + } + } + default: + retrievedStruct, err := entityToStructPb(ident) + if err != nil { + logger.Error("unable to make entity struct", slog.String("error", err.Error())) + return entityresolution.ResolveEntitiesResponse{}, fmt.Errorf("unable to make entity struct: %w", err) + } + entityStruct = retrievedStruct + + } + + // make sure the id field is populated + originialID := ident.GetId() + if originialID == "" { + originialID = auth.EntityIDPrefix + fmt.Sprint(idx) + } + resolvedEntities = append( + resolvedEntities, + &entityresolution.EntityRepresentation{ + OriginalId: originialID, + AdditionalProps: []*structpb.Struct{entityStruct}, + }, + ) + } + return entityresolution.ResolveEntitiesResponse{EntityRepresentations: resolvedEntities}, nil +} + +func getEntitiesFromToken(ctx context.Context, jwtString string, logger *logger.Logger) ([]*authorization.Entity, error) { + token, err := jwt.ParseString(jwtString, jwt.WithVerify(false), jwt.WithValidate(false)) + if err != nil { + return nil, errors.New("error parsing jwt " + err.Error()) + } + // claims, err := token.AsMap(context.Background()) ///nolint:contextcheck // Do not want to include keys from context in map + // if err != nil { + // return nil, errors.New("error getting claims from jwt") + // } + claims := token.PrivateClaims() + + entities := []*authorization.Entity{} + + // Convert map[string]interface{} to *structpb.Struct + structClaims, err := structpb.NewStruct(claims) + if err != nil { + return nil, fmt.Errorf("error converting to structpb.Struct: %w", err) + } + + // Wrap the struct in an *anypb.Any message + anyClaims, err := anypb.New(structClaims) + if err != nil { + return nil, fmt.Errorf("error wrapping in anypb.Any: %w", err) + } + + entities = append(entities, &authorization.Entity{ + EntityType: &authorization.Entity_Claims{Claims: anyClaims}, + // EntityType: &authorization.Entity_ClientId{ClientId: extractedValueCasted}, + Id: "jwtentity-claims", + Category: authorization.Entity_CATEGORY_SUBJECT, + }) + return entities, nil +} + +func entityToStructPb(ident *authorization.Entity) (*structpb.Struct, error) { + entityBytes, err := protojson.Marshal(ident) + if err != nil { + return nil, err + } + var entityStruct structpb.Struct + err = entityStruct.UnmarshalJSON(entityBytes) + if err != nil { + return nil, err + } + return &entityStruct, nil +} diff --git a/service/entityresolution/dummy/dummy_entity_resolution_test.go b/service/entityresolution/dummy/dummy_entity_resolution_test.go new file mode 100644 index 000000000..e7d1ea431 --- /dev/null +++ b/service/entityresolution/dummy/dummy_entity_resolution_test.go @@ -0,0 +1,128 @@ +package entityresolution_test + +import ( + "context" + "testing" + + "github.com/opentdf/platform/protocol/go/authorization" + "github.com/opentdf/platform/protocol/go/entityresolution" + dummy "github.com/opentdf/platform/service/entityresolution/dummy" + "github.com/opentdf/platform/service/logger" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/types/known/anypb" + "google.golang.org/protobuf/types/known/structpb" +) + +const samplejwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6ImhlbGxvd29ybGQiLCJpYXQiOjE1MTYyMzkwMjJ9.EAOittOMzKENEAs44eaMuZe-xas7VNVsgBxhwmxYiIw" //"eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI0OXRmSjByRUo4c0YzUjJ3Yi05eENHVXhYUEQ4RTZldmNsRG1hZ05EM3lBIn0.eyJleHAiOjE3MTUwOTE2MDQsImlhdCI6MTcxNTA5MTMwNCwianRpIjoiMTE3MTYzMjYtNWQyNS00MjlmLWFjMDItNmU0MjE2OWFjMGJhIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4ODg4L2F1dGgvcmVhbG1zL29wZW50ZGYiLCJhdWQiOlsiaHR0cDovL2xvY2FsaG9zdDo4ODg4IiwicmVhbG0tbWFuYWdlbWVudCIsImFjY291bnQiXSwic3ViIjoiOTljOWVlZDItOTM1Ni00ZjE2LWIwODQtZTgyZDczZjViN2QyIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoidGRmLWVudGl0eS1yZXNvbHV0aW9uIiwiYWNyIjoiMSIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJkZWZhdWx0LXJvbGVzLW9wZW50ZGYiLCJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsicmVhbG0tbWFuYWdlbWVudCI6eyJyb2xlcyI6WyJ2aWV3LXVzZXJzIiwidmlldy1jbGllbnRzIiwicXVlcnktY2xpZW50cyIsInF1ZXJ5LWdyb3VwcyIsInF1ZXJ5LXVzZXJzIl19LCJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6InByb2ZpbGUgZW1haWwiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsImNsaWVudEhvc3QiOiIxOTIuMTY4LjI0MC4xIiwicHJlZmVycmVkX3VzZXJuYW1lIjoic2VydmljZS1hY2NvdW50LXRkZi1lbnRpdHktcmVzb2x1dGlvbiIsImNsaWVudEFkZHJlc3MiOiIxOTIuMTY4LjI0MC4xIiwiY2xpZW50X2lkIjoidGRmLWVudGl0eS1yZXNvbHV0aW9uIn0.h29QLo-QvIc67KKqU_e1-x6G_o5YQccOyW9AthMdB7xhn9C1dBrcScytaWq1RfETPmnM8MXGezqN4OpXrYr-zbkHhq9ha0Ib-M1VJXNgA5sbgKW9JxGQyudmYPgn4fimDCJtAsXo7C-e3mYNm6DJS0zhGQ3msmjLTcHmIPzWlj7VjtPgKhYV75b7yr_yZNBdHjf3EZqfynU2sL8bKa1w7DYDNQve7ThtD4MeKLiuOQHa3_23dECs_ptvPVks7pLGgRKfgGHBC-KQuopjtxIhwkz2vOWRzugDl0aBJMHfwBajYhgZ2YRlV9dqSxmy8BOj4OEXuHbiyfIpY0rCRpSrGg" + +func Test_ClientResolveEntity(t *testing.T) { + + var validBody []*authorization.Entity + validBody = append(validBody, &authorization.Entity{Id: "1234", EntityType: &authorization.Entity_ClientId{ClientId: "random"}}) + + var ctxb = context.Background() + + var req = entityresolution.ResolveEntitiesRequest{} + req.Entities = validBody + + var resp, reserr = dummy.EntityResolution(ctxb, &req, logger.CreateTestLogger()) + + require.NoError(t, reserr) + + var entityRepresentations = resp.GetEntityRepresentations() + assert.NotNil(t, entityRepresentations) + assert.Len(t, entityRepresentations, 1) + + assert.Equal(t, "1234", entityRepresentations[0].GetOriginalId()) + assert.Len(t, entityRepresentations[0].GetAdditionalProps(), 1) + var propMap = entityRepresentations[0].GetAdditionalProps()[0].AsMap() + assert.Equal(t, "random", propMap["clientId"]) + assert.Equal(t, "1234", propMap["id"]) +} + +func Test_EmailResolveEntity(t *testing.T) { + + var validBody []*authorization.Entity + validBody = append(validBody, &authorization.Entity{Id: "1234", EntityType: &authorization.Entity_EmailAddress{EmailAddress: "random"}}) + + var ctxb = context.Background() + + var req = entityresolution.ResolveEntitiesRequest{} + req.Entities = validBody + + var resp, reserr = dummy.EntityResolution(ctxb, &req, logger.CreateTestLogger()) + + require.NoError(t, reserr) + + var entityRepresentations = resp.GetEntityRepresentations() + assert.NotNil(t, entityRepresentations) + assert.Len(t, entityRepresentations, 1) + + assert.Equal(t, "1234", entityRepresentations[0].GetOriginalId()) + assert.Len(t, entityRepresentations[0].GetAdditionalProps(), 1) + var propMap = entityRepresentations[0].GetAdditionalProps()[0].AsMap() + assert.Equal(t, "random", propMap["emailAddress"]) + assert.Equal(t, "1234", propMap["id"]) +} + +func Test_ClaimsResolveEntity(t *testing.T) { + + claims := map[string]interface{}{ + "foo": "bar", + "baz": 42, + } + // Convert map[string]interface{} to *structpb.Struct + structClaims, err := structpb.NewStruct(claims) + require.NoError(t, err) + + // Wrap the struct in an *anypb.Any + anyClaims, err := anypb.New(structClaims) + require.NoError(t, err) + + var validBody []*authorization.Entity + validBody = append(validBody, &authorization.Entity{Id: "1234", EntityType: &authorization.Entity_Claims{Claims: anyClaims}}) + + var ctxb = context.Background() + + var req = entityresolution.ResolveEntitiesRequest{} + req.Entities = validBody + + var resp, reserr = dummy.EntityResolution(ctxb, &req, logger.CreateTestLogger()) + + require.NoError(t, reserr) + + var entityRepresentations = resp.GetEntityRepresentations() + assert.NotNil(t, entityRepresentations) + assert.Len(t, entityRepresentations, 1) + + assert.Equal(t, "1234", entityRepresentations[0].GetOriginalId()) + assert.Len(t, entityRepresentations[0].GetAdditionalProps(), 1) + var propMap = entityRepresentations[0].GetAdditionalProps()[0].AsMap() + assert.Equal(t, "bar", propMap["foo"]) + assert.EqualValues(t, 42, propMap["baz"]) +} + +func Test_JWTToEntityChainClaims(t *testing.T) { + var ctxb = context.Background() + + validBody := []*authorization.Token{{Jwt: samplejwt}} + + var resp, reserr = dummy.CreateEntityChainFromJwt(ctxb, &entityresolution.CreateEntityChainFromJwtRequest{Tokens: validBody}, logger.CreateTestLogger()) + + require.NoError(t, reserr) + + assert.Len(t, resp.GetEntityChains(), 1) + assert.Len(t, resp.GetEntityChains()[0].GetEntities(), 1) + assert.IsType(t, &authorization.Entity_Claims{}, resp.GetEntityChains()[0].GetEntities()[0].GetEntityType()) + assert.Equal(t, authorization.Entity_CATEGORY_SUBJECT, resp.GetEntityChains()[0].GetEntities()[0].GetCategory()) + + var unpackedStruct structpb.Struct + err := resp.GetEntityChains()[0].GetEntities()[0].GetClaims().UnmarshalTo(&unpackedStruct) + require.NoError(t, err) + + // Convert structpb.Struct to map[string]interface{} + claimsMap := unpackedStruct.AsMap() + + assert.Equal(t, "helloworld", claimsMap["name"]) +} diff --git a/service/entityresolution/entityresolution.go b/service/entityresolution/entityresolution.go index a539e57f6..9cfdaf830 100644 --- a/service/entityresolution/entityresolution.go +++ b/service/entityresolution/entityresolution.go @@ -1,48 +1,41 @@ package entityresolution import ( - "context" - - "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" "github.com/mitchellh/mapstructure" "github.com/opentdf/platform/protocol/go/entityresolution" + dummy "github.com/opentdf/platform/service/entityresolution/dummy" keycloak "github.com/opentdf/platform/service/entityresolution/keycloak" - "github.com/opentdf/platform/service/logger" "github.com/opentdf/platform/service/pkg/serviceregistry" ) -type EntityResolutionService struct { //nolint:revive // allow for simple naming - entityresolution.UnimplementedEntityResolutionServiceServer - idpConfig keycloak.KeycloakConfig - logger *logger.Logger +// type EntityResolutionService struct { //nolint:revive // allow for simple naming +// entityresolution.UnimplementedEntityResolutionServiceServer +// idpConfig map[string]any //keycloak.KeycloakConfig +// logger *logger.Logger +// } + +type ERSConfig struct { + Mode string `mapstructure:"mode" json:"mode"` } +const KeycloakMode = "keycloak" +const DummyMode = "dummy" + func NewRegistration() serviceregistry.Registration { return serviceregistry.Registration{ Namespace: "entityresolution", ServiceDesc: &entityresolution.EntityResolutionService_ServiceDesc, RegisterFunc: func(srp serviceregistry.RegistrationParams) (any, serviceregistry.HandlerServer) { - var inputIdpConfig keycloak.KeycloakConfig + var inputConfig ERSConfig - if err := mapstructure.Decode(srp.Config, &inputIdpConfig); err != nil { + if err := mapstructure.Decode(srp.Config, &inputConfig); err != nil { panic(err) } - - srp.Logger.Debug("entity_resolution configuration", "config", inputIdpConfig) - - return &EntityResolutionService{idpConfig: inputIdpConfig, logger: srp.Logger}, func(ctx context.Context, mux *runtime.ServeMux, server any) error { - return entityresolution.RegisterEntityResolutionServiceHandlerServer(ctx, mux, server.(entityresolution.EntityResolutionServiceServer)) //nolint:forcetypeassert // allow type assert, following other services + if inputConfig.Mode == DummyMode { + return dummy.RegisterDummyERS(srp.Config, srp.Logger) } + + return keycloak.RegisterKeycloakERS(srp.Config, srp.Logger) }, } } - -func (s EntityResolutionService) ResolveEntities(ctx context.Context, req *entityresolution.ResolveEntitiesRequest) (*entityresolution.ResolveEntitiesResponse, error) { - resp, err := keycloak.EntityResolution(ctx, req, s.idpConfig, s.logger) - return &resp, err -} - -func (s EntityResolutionService) CreateEntityChainFromJwt(ctx context.Context, req *entityresolution.CreateEntityChainFromJwtRequest) (*entityresolution.CreateEntityChainFromJwtResponse, error) { - resp, err := keycloak.CreateEntityChainFromJwt(ctx, req, s.idpConfig, s.logger) - return &resp, err -} diff --git a/service/entityresolution/keycloak/keycloak_entity_resolution.go b/service/entityresolution/keycloak/keycloak_entity_resolution.go index c282e3c7e..ab01bb71c 100644 --- a/service/entityresolution/keycloak/keycloak_entity_resolution.go +++ b/service/entityresolution/keycloak/keycloak_entity_resolution.go @@ -9,11 +9,14 @@ import ( "strings" "github.com/Nerzal/gocloak/v13" + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" "github.com/lestrrat-go/jwx/v2/jwt" + "github.com/mitchellh/mapstructure" "github.com/opentdf/platform/protocol/go/authorization" "github.com/opentdf/platform/protocol/go/entityresolution" auth "github.com/opentdf/platform/service/authorization" "github.com/opentdf/platform/service/logger" + "github.com/opentdf/platform/service/pkg/serviceregistry" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/encoding/protojson" @@ -33,6 +36,12 @@ const ( const serviceAccountUsernamePrefix = "service-account-" +type KeycloakEntityResolutionService struct { //nolint:revive // allow for simple naming + entityresolution.UnimplementedEntityResolutionServiceServer + idpConfig KeycloakConfig + logger *logger.Logger +} + type KeycloakConfig struct { URL string `mapstructure:"url" json:"url"` Realm string `mapstructure:"realm" json:"realm"` @@ -43,6 +52,29 @@ type KeycloakConfig struct { InferID InferredIdentityConfig `mapstructure:"inferid,omitempty" json:"inferid,omitempty"` } +func RegisterKeycloakERS(config serviceregistry.ServiceConfig, logger *logger.Logger) (any, serviceregistry.HandlerServer) { + var inputIdpConfig KeycloakConfig + if err := mapstructure.Decode(config, &inputIdpConfig); err != nil { + panic(err) + } + logger.Debug("entity_resolution configuration", "config", inputIdpConfig) + + return &KeycloakEntityResolutionService{idpConfig: inputIdpConfig, logger: logger}, + func(ctx context.Context, mux *runtime.ServeMux, server any) error { + return entityresolution.RegisterEntityResolutionServiceHandlerServer(ctx, mux, server.(entityresolution.EntityResolutionServiceServer)) //nolint:forcetypeassert // allow type assert, following other services + } +} + +func (s KeycloakEntityResolutionService) ResolveEntities(ctx context.Context, req *entityresolution.ResolveEntitiesRequest) (*entityresolution.ResolveEntitiesResponse, error) { + resp, err := EntityResolution(ctx, req, s.idpConfig, s.logger) + return &resp, err +} + +func (s KeycloakEntityResolutionService) CreateEntityChainFromJwt(ctx context.Context, req *entityresolution.CreateEntityChainFromJwtRequest) (*entityresolution.CreateEntityChainFromJwtResponse, error) { + resp, err := CreateEntityChainFromJwt(ctx, req, s.idpConfig, s.logger) + return &resp, err +} + func (c KeycloakConfig) LogValue() slog.Value { return slog.GroupValue( slog.String("url", c.URL), From 4cbd0b2c825b7926985a00969090777b9f6c4f56 Mon Sep 17 00:00:00 2001 From: Elizabeth Healy Date: Thu, 10 Oct 2024 16:51:16 -0400 Subject: [PATCH 2/5] linting --- .../dummy/dummy_entity_resolution.go | 26 +++++++------------ .../dummy/dummy_entity_resolution_test.go | 5 +--- .../keycloak/keycloak_entity_resolution.go | 2 +- 3 files changed, 11 insertions(+), 22 deletions(-) diff --git a/service/entityresolution/dummy/dummy_entity_resolution.go b/service/entityresolution/dummy/dummy_entity_resolution.go index e87fd8b1c..ae30f0ef1 100644 --- a/service/entityresolution/dummy/dummy_entity_resolution.go +++ b/service/entityresolution/dummy/dummy_entity_resolution.go @@ -18,12 +18,12 @@ import ( "google.golang.org/protobuf/types/known/structpb" ) -type DummyEntityResolutionService struct { //nolint:revive // allow for simple naming +type DummyEntityResolutionService struct { entityresolution.UnimplementedEntityResolutionServiceServer logger *logger.Logger } -func RegisterDummyERS(config serviceregistry.ServiceConfig, logger *logger.Logger) (any, serviceregistry.HandlerServer) { +func RegisterDummyERS(_ serviceregistry.ServiceConfig, logger *logger.Logger) (any, serviceregistry.HandlerServer) { return &DummyEntityResolutionService{logger: logger}, func(ctx context.Context, mux *runtime.ServeMux, server any) error { return entityresolution.RegisterEntityResolutionServiceHandlerServer(ctx, mux, server.(entityresolution.EntityResolutionServiceServer)) //nolint:forcetypeassert // allow type assert, following other services @@ -41,14 +41,14 @@ func (s DummyEntityResolutionService) CreateEntityChainFromJwt(ctx context.Conte } func CreateEntityChainFromJwt( - ctx context.Context, + _ context.Context, req *entityresolution.CreateEntityChainFromJwtRequest, logger *logger.Logger, ) (entityresolution.CreateEntityChainFromJwtResponse, error) { entityChains := []*authorization.EntityChain{} // for each token in the tokens form an entity chain for _, tok := range req.GetTokens() { - entities, err := getEntitiesFromToken(ctx, tok.GetJwt(), logger) + entities, err := getEntitiesFromToken(tok.GetJwt(), logger) if err != nil { return entityresolution.CreateEntityChainFromJwtResponse{}, err } @@ -58,12 +58,10 @@ func CreateEntityChainFromJwt( return entityresolution.CreateEntityChainFromJwtResponse{EntityChains: entityChains}, nil } -func EntityResolution(ctx context.Context, +func EntityResolution(_ context.Context, req *entityresolution.ResolveEntitiesRequest, logger *logger.Logger, ) (entityresolution.ResolveEntitiesResponse, error) { - payload := req.GetEntities() - var resolvedEntities []*entityresolution.EntityRepresentation for idx, ident := range payload { @@ -86,7 +84,6 @@ func EntityResolution(ctx context.Context, entityStruct = retrievedStruct } - // make sure the id field is populated originialID := ident.GetId() if originialID == "" { @@ -103,17 +100,13 @@ func EntityResolution(ctx context.Context, return entityresolution.ResolveEntitiesResponse{EntityRepresentations: resolvedEntities}, nil } -func getEntitiesFromToken(ctx context.Context, jwtString string, logger *logger.Logger) ([]*authorization.Entity, error) { +func getEntitiesFromToken(jwtString string, logger *logger.Logger) ([]*authorization.Entity, error) { token, err := jwt.ParseString(jwtString, jwt.WithVerify(false), jwt.WithValidate(false)) if err != nil { return nil, errors.New("error parsing jwt " + err.Error()) } - // claims, err := token.AsMap(context.Background()) ///nolint:contextcheck // Do not want to include keys from context in map - // if err != nil { - // return nil, errors.New("error getting claims from jwt") - // } - claims := token.PrivateClaims() + claims := token.PrivateClaims() entities := []*authorization.Entity{} // Convert map[string]interface{} to *structpb.Struct @@ -130,9 +123,8 @@ func getEntitiesFromToken(ctx context.Context, jwtString string, logger *logger. entities = append(entities, &authorization.Entity{ EntityType: &authorization.Entity_Claims{Claims: anyClaims}, - // EntityType: &authorization.Entity_ClientId{ClientId: extractedValueCasted}, - Id: "jwtentity-claims", - Category: authorization.Entity_CATEGORY_SUBJECT, + Id: "jwtentity-claims", + Category: authorization.Entity_CATEGORY_SUBJECT, }) return entities, nil } diff --git a/service/entityresolution/dummy/dummy_entity_resolution_test.go b/service/entityresolution/dummy/dummy_entity_resolution_test.go index e7d1ea431..d108c72eb 100644 --- a/service/entityresolution/dummy/dummy_entity_resolution_test.go +++ b/service/entityresolution/dummy/dummy_entity_resolution_test.go @@ -14,10 +14,9 @@ import ( "google.golang.org/protobuf/types/known/structpb" ) -const samplejwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6ImhlbGxvd29ybGQiLCJpYXQiOjE1MTYyMzkwMjJ9.EAOittOMzKENEAs44eaMuZe-xas7VNVsgBxhwmxYiIw" //"eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI0OXRmSjByRUo4c0YzUjJ3Yi05eENHVXhYUEQ4RTZldmNsRG1hZ05EM3lBIn0.eyJleHAiOjE3MTUwOTE2MDQsImlhdCI6MTcxNTA5MTMwNCwianRpIjoiMTE3MTYzMjYtNWQyNS00MjlmLWFjMDItNmU0MjE2OWFjMGJhIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4ODg4L2F1dGgvcmVhbG1zL29wZW50ZGYiLCJhdWQiOlsiaHR0cDovL2xvY2FsaG9zdDo4ODg4IiwicmVhbG0tbWFuYWdlbWVudCIsImFjY291bnQiXSwic3ViIjoiOTljOWVlZDItOTM1Ni00ZjE2LWIwODQtZTgyZDczZjViN2QyIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoidGRmLWVudGl0eS1yZXNvbHV0aW9uIiwiYWNyIjoiMSIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJkZWZhdWx0LXJvbGVzLW9wZW50ZGYiLCJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsicmVhbG0tbWFuYWdlbWVudCI6eyJyb2xlcyI6WyJ2aWV3LXVzZXJzIiwidmlldy1jbGllbnRzIiwicXVlcnktY2xpZW50cyIsInF1ZXJ5LWdyb3VwcyIsInF1ZXJ5LXVzZXJzIl19LCJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6InByb2ZpbGUgZW1haWwiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsImNsaWVudEhvc3QiOiIxOTIuMTY4LjI0MC4xIiwicHJlZmVycmVkX3VzZXJuYW1lIjoic2VydmljZS1hY2NvdW50LXRkZi1lbnRpdHktcmVzb2x1dGlvbiIsImNsaWVudEFkZHJlc3MiOiIxOTIuMTY4LjI0MC4xIiwiY2xpZW50X2lkIjoidGRmLWVudGl0eS1yZXNvbHV0aW9uIn0.h29QLo-QvIc67KKqU_e1-x6G_o5YQccOyW9AthMdB7xhn9C1dBrcScytaWq1RfETPmnM8MXGezqN4OpXrYr-zbkHhq9ha0Ib-M1VJXNgA5sbgKW9JxGQyudmYPgn4fimDCJtAsXo7C-e3mYNm6DJS0zhGQ3msmjLTcHmIPzWlj7VjtPgKhYV75b7yr_yZNBdHjf3EZqfynU2sL8bKa1w7DYDNQve7ThtD4MeKLiuOQHa3_23dECs_ptvPVks7pLGgRKfgGHBC-KQuopjtxIhwkz2vOWRzugDl0aBJMHfwBajYhgZ2YRlV9dqSxmy8BOj4OEXuHbiyfIpY0rCRpSrGg" +const samplejwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6ImhlbGxvd29ybGQiLCJpYXQiOjE1MTYyMzkwMjJ9.EAOittOMzKENEAs44eaMuZe-xas7VNVsgBxhwmxYiIw" func Test_ClientResolveEntity(t *testing.T) { - var validBody []*authorization.Entity validBody = append(validBody, &authorization.Entity{Id: "1234", EntityType: &authorization.Entity_ClientId{ClientId: "random"}}) @@ -42,7 +41,6 @@ func Test_ClientResolveEntity(t *testing.T) { } func Test_EmailResolveEntity(t *testing.T) { - var validBody []*authorization.Entity validBody = append(validBody, &authorization.Entity{Id: "1234", EntityType: &authorization.Entity_EmailAddress{EmailAddress: "random"}}) @@ -67,7 +65,6 @@ func Test_EmailResolveEntity(t *testing.T) { } func Test_ClaimsResolveEntity(t *testing.T) { - claims := map[string]interface{}{ "foo": "bar", "baz": 42, diff --git a/service/entityresolution/keycloak/keycloak_entity_resolution.go b/service/entityresolution/keycloak/keycloak_entity_resolution.go index ab01bb71c..a2a9b484c 100644 --- a/service/entityresolution/keycloak/keycloak_entity_resolution.go +++ b/service/entityresolution/keycloak/keycloak_entity_resolution.go @@ -36,7 +36,7 @@ const ( const serviceAccountUsernamePrefix = "service-account-" -type KeycloakEntityResolutionService struct { //nolint:revive // allow for simple naming +type KeycloakEntityResolutionService struct { entityresolution.UnimplementedEntityResolutionServiceServer idpConfig KeycloakConfig logger *logger.Logger From 67ba84dc028d63cbfd7293c0e3102cfac44c331a Mon Sep 17 00:00:00 2001 From: Elizabeth Healy Date: Thu, 10 Oct 2024 16:54:28 -0400 Subject: [PATCH 3/5] linting --- service/entityresolution/dummy/dummy_entity_resolution.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/service/entityresolution/dummy/dummy_entity_resolution.go b/service/entityresolution/dummy/dummy_entity_resolution.go index ae30f0ef1..b1c77f77e 100644 --- a/service/entityresolution/dummy/dummy_entity_resolution.go +++ b/service/entityresolution/dummy/dummy_entity_resolution.go @@ -2,7 +2,6 @@ package entityresolution import ( "context" - "errors" "fmt" "log/slog" @@ -48,7 +47,7 @@ func CreateEntityChainFromJwt( entityChains := []*authorization.EntityChain{} // for each token in the tokens form an entity chain for _, tok := range req.GetTokens() { - entities, err := getEntitiesFromToken(tok.GetJwt(), logger) + entities, err := getEntitiesFromToken(tok.GetJwt()) if err != nil { return entityresolution.CreateEntityChainFromJwtResponse{}, err } @@ -82,7 +81,6 @@ func EntityResolution(_ context.Context, return entityresolution.ResolveEntitiesResponse{}, fmt.Errorf("unable to make entity struct: %w", err) } entityStruct = retrievedStruct - } // make sure the id field is populated originialID := ident.GetId() @@ -100,10 +98,10 @@ func EntityResolution(_ context.Context, return entityresolution.ResolveEntitiesResponse{EntityRepresentations: resolvedEntities}, nil } -func getEntitiesFromToken(jwtString string, logger *logger.Logger) ([]*authorization.Entity, error) { +func getEntitiesFromToken(jwtString string) ([]*authorization.Entity, error) { token, err := jwt.ParseString(jwtString, jwt.WithVerify(false), jwt.WithValidate(false)) if err != nil { - return nil, errors.New("error parsing jwt " + err.Error()) + return nil, fmt.Errorf("error parsing jwt: %w", err) } claims := token.PrivateClaims() From 086952451dd88b677777b1a5ca461ab4c0d283dc Mon Sep 17 00:00:00 2001 From: Elizabeth Healy Date: Thu, 10 Oct 2024 17:01:32 -0400 Subject: [PATCH 4/5] linting --- service/entityresolution/dummy/dummy_entity_resolution.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service/entityresolution/dummy/dummy_entity_resolution.go b/service/entityresolution/dummy/dummy_entity_resolution.go index b1c77f77e..c7423c8ea 100644 --- a/service/entityresolution/dummy/dummy_entity_resolution.go +++ b/service/entityresolution/dummy/dummy_entity_resolution.go @@ -42,7 +42,7 @@ func (s DummyEntityResolutionService) CreateEntityChainFromJwt(ctx context.Conte func CreateEntityChainFromJwt( _ context.Context, req *entityresolution.CreateEntityChainFromJwtRequest, - logger *logger.Logger, + _ *logger.Logger, ) (entityresolution.CreateEntityChainFromJwtResponse, error) { entityChains := []*authorization.EntityChain{} // for each token in the tokens form an entity chain From 0cd2dc841d90e6740ed2aa40312eb95b8d962404 Mon Sep 17 00:00:00 2001 From: Elizabeth Healy Date: Thu, 31 Oct 2024 09:20:45 -0400 Subject: [PATCH 5/5] rename to claims ERS --- .../claims_entity_resolution.go} | 10 +++++----- .../claims_entity_resolution_test.go} | 14 +++++++------- service/entityresolution/entityresolution.go | 15 +++++---------- 3 files changed, 17 insertions(+), 22 deletions(-) rename service/entityresolution/{dummy/dummy_entity_resolution.go => claims/claims_entity_resolution.go} (88%) rename service/entityresolution/{dummy/dummy_entity_resolution_test.go => claims/claims_entity_resolution_test.go} (87%) diff --git a/service/entityresolution/dummy/dummy_entity_resolution.go b/service/entityresolution/claims/claims_entity_resolution.go similarity index 88% rename from service/entityresolution/dummy/dummy_entity_resolution.go rename to service/entityresolution/claims/claims_entity_resolution.go index c7423c8ea..cde559cc2 100644 --- a/service/entityresolution/dummy/dummy_entity_resolution.go +++ b/service/entityresolution/claims/claims_entity_resolution.go @@ -17,24 +17,24 @@ import ( "google.golang.org/protobuf/types/known/structpb" ) -type DummyEntityResolutionService struct { +type ClaimsEntityResolutionService struct { entityresolution.UnimplementedEntityResolutionServiceServer logger *logger.Logger } -func RegisterDummyERS(_ serviceregistry.ServiceConfig, logger *logger.Logger) (any, serviceregistry.HandlerServer) { - return &DummyEntityResolutionService{logger: logger}, +func RegisterClaimsERS(_ serviceregistry.ServiceConfig, logger *logger.Logger) (any, serviceregistry.HandlerServer) { + return &ClaimsEntityResolutionService{logger: logger}, func(ctx context.Context, mux *runtime.ServeMux, server any) error { return entityresolution.RegisterEntityResolutionServiceHandlerServer(ctx, mux, server.(entityresolution.EntityResolutionServiceServer)) //nolint:forcetypeassert // allow type assert, following other services } } -func (s DummyEntityResolutionService) ResolveEntities(ctx context.Context, req *entityresolution.ResolveEntitiesRequest) (*entityresolution.ResolveEntitiesResponse, error) { +func (s ClaimsEntityResolutionService) ResolveEntities(ctx context.Context, req *entityresolution.ResolveEntitiesRequest) (*entityresolution.ResolveEntitiesResponse, error) { resp, err := EntityResolution(ctx, req, s.logger) return &resp, err } -func (s DummyEntityResolutionService) CreateEntityChainFromJwt(ctx context.Context, req *entityresolution.CreateEntityChainFromJwtRequest) (*entityresolution.CreateEntityChainFromJwtResponse, error) { +func (s ClaimsEntityResolutionService) CreateEntityChainFromJwt(ctx context.Context, req *entityresolution.CreateEntityChainFromJwtRequest) (*entityresolution.CreateEntityChainFromJwtResponse, error) { resp, err := CreateEntityChainFromJwt(ctx, req, s.logger) return &resp, err } diff --git a/service/entityresolution/dummy/dummy_entity_resolution_test.go b/service/entityresolution/claims/claims_entity_resolution_test.go similarity index 87% rename from service/entityresolution/dummy/dummy_entity_resolution_test.go rename to service/entityresolution/claims/claims_entity_resolution_test.go index d108c72eb..3354d8f33 100644 --- a/service/entityresolution/dummy/dummy_entity_resolution_test.go +++ b/service/entityresolution/claims/claims_entity_resolution_test.go @@ -6,7 +6,7 @@ import ( "github.com/opentdf/platform/protocol/go/authorization" "github.com/opentdf/platform/protocol/go/entityresolution" - dummy "github.com/opentdf/platform/service/entityresolution/dummy" + claims "github.com/opentdf/platform/service/entityresolution/claims" "github.com/opentdf/platform/service/logger" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -25,7 +25,7 @@ func Test_ClientResolveEntity(t *testing.T) { var req = entityresolution.ResolveEntitiesRequest{} req.Entities = validBody - var resp, reserr = dummy.EntityResolution(ctxb, &req, logger.CreateTestLogger()) + var resp, reserr = claims.EntityResolution(ctxb, &req, logger.CreateTestLogger()) require.NoError(t, reserr) @@ -49,7 +49,7 @@ func Test_EmailResolveEntity(t *testing.T) { var req = entityresolution.ResolveEntitiesRequest{} req.Entities = validBody - var resp, reserr = dummy.EntityResolution(ctxb, &req, logger.CreateTestLogger()) + var resp, reserr = claims.EntityResolution(ctxb, &req, logger.CreateTestLogger()) require.NoError(t, reserr) @@ -65,12 +65,12 @@ func Test_EmailResolveEntity(t *testing.T) { } func Test_ClaimsResolveEntity(t *testing.T) { - claims := map[string]interface{}{ + customclaims := map[string]interface{}{ "foo": "bar", "baz": 42, } // Convert map[string]interface{} to *structpb.Struct - structClaims, err := structpb.NewStruct(claims) + structClaims, err := structpb.NewStruct(customclaims) require.NoError(t, err) // Wrap the struct in an *anypb.Any @@ -85,7 +85,7 @@ func Test_ClaimsResolveEntity(t *testing.T) { var req = entityresolution.ResolveEntitiesRequest{} req.Entities = validBody - var resp, reserr = dummy.EntityResolution(ctxb, &req, logger.CreateTestLogger()) + var resp, reserr = claims.EntityResolution(ctxb, &req, logger.CreateTestLogger()) require.NoError(t, reserr) @@ -105,7 +105,7 @@ func Test_JWTToEntityChainClaims(t *testing.T) { validBody := []*authorization.Token{{Jwt: samplejwt}} - var resp, reserr = dummy.CreateEntityChainFromJwt(ctxb, &entityresolution.CreateEntityChainFromJwtRequest{Tokens: validBody}, logger.CreateTestLogger()) + var resp, reserr = claims.CreateEntityChainFromJwt(ctxb, &entityresolution.CreateEntityChainFromJwtRequest{Tokens: validBody}, logger.CreateTestLogger()) require.NoError(t, reserr) diff --git a/service/entityresolution/entityresolution.go b/service/entityresolution/entityresolution.go index 9cfdaf830..9fd6c81e2 100644 --- a/service/entityresolution/entityresolution.go +++ b/service/entityresolution/entityresolution.go @@ -3,23 +3,17 @@ package entityresolution import ( "github.com/mitchellh/mapstructure" "github.com/opentdf/platform/protocol/go/entityresolution" - dummy "github.com/opentdf/platform/service/entityresolution/dummy" + claims "github.com/opentdf/platform/service/entityresolution/claims" keycloak "github.com/opentdf/platform/service/entityresolution/keycloak" "github.com/opentdf/platform/service/pkg/serviceregistry" ) -// type EntityResolutionService struct { //nolint:revive // allow for simple naming -// entityresolution.UnimplementedEntityResolutionServiceServer -// idpConfig map[string]any //keycloak.KeycloakConfig -// logger *logger.Logger -// } - type ERSConfig struct { Mode string `mapstructure:"mode" json:"mode"` } const KeycloakMode = "keycloak" -const DummyMode = "dummy" +const ClaimsMode = "claims" func NewRegistration() serviceregistry.Registration { return serviceregistry.Registration{ @@ -31,10 +25,11 @@ func NewRegistration() serviceregistry.Registration { if err := mapstructure.Decode(srp.Config, &inputConfig); err != nil { panic(err) } - if inputConfig.Mode == DummyMode { - return dummy.RegisterDummyERS(srp.Config, srp.Logger) + if inputConfig.Mode == ClaimsMode { + return claims.RegisterClaimsERS(srp.Config, srp.Logger) } + // Default to keyclaok ERS return keycloak.RegisterKeycloakERS(srp.Config, srp.Logger) }, }