diff --git a/docs/grpc/index.html b/docs/grpc/index.html index 3e393d4b72..f4fe1703dc 100644 --- a/docs/grpc/index.html +++ b/docs/grpc/index.html @@ -2400,6 +2400,13 @@

Entity

+ + client_id + string + +

+ + diff --git a/docs/openapi/authorization/authorization.swagger.json b/docs/openapi/authorization/authorization.swagger.json index 49d54aeaf0..25f72e53bb 100644 --- a/docs/openapi/authorization/authorization.swagger.json +++ b/docs/openapi/authorization/authorization.swagger.json @@ -175,6 +175,9 @@ }, "custom": { "$ref": "#/definitions/authorizationEntityCustom" + }, + "clientId": { + "type": "string" } }, "title": "PE (Person Entity) or NPE (Non-Person Entity)" diff --git a/examples/cmd/encrypt.go b/examples/cmd/encrypt.go index 4df936d706..ca2dc8495c 100644 --- a/examples/cmd/encrypt.go +++ b/examples/cmd/encrypt.go @@ -46,7 +46,7 @@ func encrypt(cmd *cobra.Command, args []string) error { defer tdfFile.Close() tdf, err := client.CreateTDF(tdfFile, strReader, - //sdk.WithDataAttributes("https://example.com/attributes/1", "https://example.com/attributes/2"), + //sdk.WithDataAttributes("https://example.com/attr/attr1/value/value1"), sdk.WithKasInformation( sdk.KASInfo{ URL: "http://localhost:8080", diff --git a/go.mod b/go.mod index bc51371af2..f3ef323771 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,6 @@ require ( github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa github.com/jackc/pgx/v5 v5.5.5 - github.com/jarcoal/httpmock v1.3.1 github.com/lestrrat-go/jwx/v2 v2.0.21 github.com/miekg/pkcs11 v1.1.1 github.com/open-policy-agent/opa v0.62.1 @@ -28,7 +27,6 @@ require ( github.com/stretchr/testify v1.9.0 github.com/testcontainers/testcontainers-go v0.28.0 github.com/valyala/fasthttp v1.52.0 - github.com/virtru/access-pdp v1.11.0 golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 golang.org/x/oauth2 v0.18.0 google.golang.org/grpc v1.62.1 @@ -139,7 +137,6 @@ require ( go.opentelemetry.io/otel/sdk v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.27.0 // indirect golang.org/x/mod v0.15.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.18.0 // indirect diff --git a/go.sum b/go.sum index 3313510322..a96f07f104 100644 --- a/go.sum +++ b/go.sum @@ -190,8 +190,6 @@ github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw= github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= -github.com/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInww= -github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg= github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 h1:rp+c0RAYOWj8l6qbCUTSiRLG/iKnW3K3/QfPPuSsBt4= github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901/go.mod h1:Z86h9688Y0wesXCyonoVr47MasHilkuLMqGhRZ4Hpak= github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4= @@ -232,8 +230,6 @@ github.com/marcboeker/go-duckdb v1.5.6 h1:5+hLUXRuKlqARcnW4jSsyhCwBRlu4FGjM0UTf2 github.com/marcboeker/go-duckdb v1.5.6/go.mod h1:wm91jO2GNKa6iO9NTcjXIRsW+/ykPoJbQcHSXhdAl28= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g= -github.com/maxatome/go-testdeep v1.12.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM= github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY= github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg= github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM= @@ -363,8 +359,6 @@ github.com/valyala/fasthttp v1.52.0 h1:wqBQpxH71XW0e2g+Og4dzQM8pk34aFYlA1Ga8db7g github.com/valyala/fasthttp v1.52.0/go.mod h1:hf5C4QnVMkNXMspnsUlfM3WitlgYflyhHYoKol/szxQ= github.com/vertica/vertica-sql-go v1.3.3 h1:fL+FKEAEy5ONmsvya2WH5T8bhkvY27y/Ik3ReR2T+Qw= github.com/vertica/vertica-sql-go v1.3.3/go.mod h1:jnn2GFuv+O2Jcjktb7zyc4Utlbu9YVqpHH/lx63+1M4= -github.com/virtru/access-pdp v1.11.0 h1:JmMUo5EExOg99TKNQ6AdwUDLdBwVvSpjxUVVJxJ0RR4= -github.com/virtru/access-pdp v1.11.0/go.mod h1:7OkDvrJX9qtzZ8KYFv7uvbp3IuhJZBqjVaPcH+Irnc0= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= @@ -402,12 +396,8 @@ go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= -go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= -go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= -go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= diff --git a/internal/entitlements/pdp.go b/internal/entitlements/pdp.go index 4098277245..1b515fdf51 100644 --- a/internal/entitlements/pdp.go +++ b/internal/entitlements/pdp.go @@ -13,8 +13,15 @@ func OpaInput(entity *authorization.Entity, sms map[string]*attributes.GetAttrib inputUnstructured["attribute_mappings"] = sms // Entity ea := make(map[string]interface{}) - ea["id"] = entity.Id - ea["email_address"] = entity.GetEmailAddress() + ea["id"] = entity.GetId() + switch entity.GetEntityType().(type) { + case *authorization.Entity_ClientId: + ea["client_id"] = entity.GetClientId() + case *authorization.Entity_EmailAddress: + ea["email_address"] = entity.GetEmailAddress() + case *authorization.Entity_Jwt: + ea["jwt"] = entity.GetJwt() + } inputUnstructured["entity"] = ea inputUnstructured["idp"] = config diff --git a/internal/idpplugin/keycloak_plugin.go b/internal/idpplugin/keycloak_plugin.go index e79790b074..393f3a845a 100644 --- a/internal/idpplugin/keycloak_plugin.go +++ b/internal/idpplugin/keycloak_plugin.go @@ -31,9 +31,9 @@ type KeyCloakConnector struct { func EntityResolution(ctx context.Context, req *authorization.IdpPluginRequest, config *authorization.IdpConfig) (*authorization.IdpPluginResponse, error) { - slog.Info(fmt.Sprintf("req: %+v", req)) - slog.Info(fmt.Sprintf("config: %+v", config)) - jsonString, err := json.Marshal(config.Config.AsMap()) + // note this only logs when run in test not when running in the OPE engine. + slog.DebugContext(ctx, "EntityResolution", "req", fmt.Sprintf("%+v", req), "config", fmt.Sprintf("%+v", config)) + jsonString, err := json.Marshal(config.GetConfig().AsMap()) if err != nil { slog.Error("Error marshalling keycloak config!", "error", err) return nil, err @@ -52,26 +52,55 @@ func EntityResolution(ctx context.Context, payload := req.GetEntities() var resolvedEntities []*authorization.IdpEntityRepresentation - slog.Debug("EntityResolution invoked with", "payload", payload) + slog.DebugContext(ctx, "EntityResolution invoked", "payload", payload) - for i, ident := range payload { - slog.Debug("Lookup", "entity", ident.GetEntityType()) + for _, ident := range payload { + slog.DebugContext(ctx, "Lookup", "entity", ident.GetEntityType()) var keycloakEntities []*gocloak.User var getUserParams gocloak.GetUsersParams - exactMatch := true - switch ident.EntityType.(type) { + switch ident.GetEntityType().(type) { + case *authorization.Entity_ClientId: + slog.DebugContext(ctx, "GetClient", "client_id", ident.GetClientId()) + clientID := ident.GetClientId() + clients, err := connector.client.GetClients(ctx, connector.token.AccessToken, kcConfig.Realm, gocloak.GetClientsParams{ + ClientID: &clientID, + }) + if err != nil { + slog.Error(err.Error()) + return &authorization.IdpPluginResponse{}, + status.Error(codes.Internal, services.ErrGetRetrievalFailed) + } + var jsonEntities []*structpb.Struct + for _, client := range clients { + json, err := typeToGenericJSONMap(client) + if err != nil { + slog.Error("Error serializing entity representation!", "error", err) + return &authorization.IdpPluginResponse{}, + status.Error(codes.Internal, services.ErrCreationFailed) + } + var mystruct, struct_err = structpb.NewStruct(json) + if struct_err != nil { + slog.Error("Error making struct!", "error", err) + return &authorization.IdpPluginResponse{}, + status.Error(codes.Internal, services.ErrCreationFailed) + } + jsonEntities = append(jsonEntities, mystruct) + } + resolvedEntities = append( + resolvedEntities, + &authorization.IdpEntityRepresentation{ + OriginalId: ident.GetId(), + AdditionalProps: jsonEntities, + }, + ) + return &authorization.IdpPluginResponse{ + EntityRepresentations: resolvedEntities, + }, nil case *authorization.Entity_EmailAddress: - getUserParams = gocloak.GetUsersParams{Email: func() *string { t := payload[i].GetEmailAddress(); return &t }(), Exact: &exactMatch} + getUserParams = gocloak.GetUsersParams{Email: func() *string { t := ident.GetEmailAddress(); return &t }(), Exact: &exactMatch} case *authorization.Entity_UserName: - getUserParams = gocloak.GetUsersParams{Username: func() *string { t := payload[i].GetUserName(); return &t }(), Exact: &exactMatch} - // case "": - // return &authorization.IdpPluginResponse{}, - // status.Error(codes.InvalidArgument, services.ErrNotFound) - default: - typeErr := fmt.Errorf("Unsupported/unknown type for entity %s", ident.String()) - return &authorization.IdpPluginResponse{}, - status.Error(codes.InvalidArgument, typeErr.Error()) + getUserParams = gocloak.GetUsersParams{Username: func() *string { t := ident.GetUserName(); return &t }(), Exact: &exactMatch} } users, err := connector.client.GetUsers(ctx, connector.token.AccessToken, kcConfig.Realm, getUserParams) @@ -93,7 +122,7 @@ func EntityResolution(ctx context.Context, ctx, connector.token.AccessToken, kcConfig.Realm, - gocloak.GetGroupsParams{Search: func() *string { t := payload[i].GetEmailAddress(); return &t }()}, + gocloak.GetGroupsParams{Search: func() *string { t := ident.GetEmailAddress(); return &t }()}, ) if groupErr != nil { slog.Error("Error getting group", "group", groupErr) @@ -153,7 +182,7 @@ func EntityResolution(ctx context.Context, OriginalId: ident.GetId(), AdditionalProps: jsonEntities}, ) - slog.Debug("Entities", "resolved", fmt.Sprintf("%+v", resolvedEntities)) + slog.DebugContext(ctx, "Entities", "resolved", fmt.Sprintf("%+v", resolvedEntities)) } return &authorization.IdpPluginResponse{ diff --git a/internal/idpplugin/keycloak_plugin_test.go b/internal/idpplugin/keycloak_plugin_test.go index d3bd396520..92c4bbe490 100644 --- a/internal/idpplugin/keycloak_plugin_test.go +++ b/internal/idpplugin/keycloak_plugin_test.go @@ -7,6 +7,7 @@ import ( "io" "net/http" "net/http/httptest" + "os" "strings" "testing" @@ -76,13 +77,15 @@ func test_server_resp(t *testing.T, w http.ResponseWriter, r *http.Request, k st } } func test_server(t *testing.T, userSearchQueryAndResp map[string]string, groupSearchQueryAndResp map[string]string, - groupByIdAndResponse map[string]string, groupMemberQueryAndResponse map[string]string) *httptest.Server { + groupByIdAndResponse map[string]string, groupMemberQueryAndResponse map[string]string, clientsSearchQueryAndResp map[string]string) *httptest.Server { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path == "/realms/tdf/protocol/openid-connect/token" { _, err := io.WriteString(w, token_resp) if err != nil { t.Error(err) } + } else if r.URL.Path == "/admin/realms/tdf/clients" { + test_server_resp(t, w, r, r.URL.RawQuery, clientsSearchQueryAndResp) } else if r.URL.Path == "/admin/realms/tdf/users" { test_server_resp(t, w, r, r.URL.RawQuery, userSearchQueryAndResp) } else if r.URL.Path == "/admin/realms/tdf/groups" && groupSearchQueryAndResp != nil { @@ -101,12 +104,44 @@ func test_server(t *testing.T, userSearchQueryAndResp map[string]string, groupSe return server } +func Test_KCEntityResolutionByClientId(t *testing.T) { + + var validBody []*authorization.Entity + validBody = append(validBody, &authorization.Entity{Id: "1234", EntityType: &authorization.Entity_ClientId{ClientId: "opentdf"}}) + + var ctxb = context.Background() + + var req = authorization.IdpPluginRequest{} + req.Entities = validBody + csqr := map[string]string{ + "clientId=opentdf": by_email_bob_resp, + } + server := test_server(t, nil, nil, nil, nil, csqr) + defer server.Close() + var kcconfig = test_keycloakConfig(server) + var kcConfigInterface map[string]interface{} + inrec, err := json.Marshal(kcconfig) + assert.Nil(t, err) + + require.NoError(t, json.Unmarshal(inrec, &kcConfigInterface)) + kcConfigStruct, err := structpb.NewStruct(kcConfigInterface) + var resp, reserr = idpplugin.EntityResolution(ctxb, &req, &authorization.IdpConfig{ + Config: kcConfigStruct, + }) + + assert.Nil(t, reserr) + _ = json.NewEncoder(os.Stdout).Encode(resp) + var entity_representations = resp.GetEntityRepresentations() + assert.NotNil(t, entity_representations) + assert.Equal(t, 1, len(entity_representations)) +} + func Test_KCEntityResolutionByEmail(t *testing.T) { server := test_server(t, map[string]string{ "email=bob%40sample.org&exact=true": by_email_bob_resp, "email=alice%40sample.org&exact=true": by_email_alice_resp, - }, nil, nil, nil) + }, nil, nil, nil, nil) defer server.Close() var validBody []*authorization.Entity @@ -150,7 +185,7 @@ func Test_KCEntityResolutionByUsername(t *testing.T) { server := test_server(t, map[string]string{ "exact=true&username=bob.smith": by_username_bob_resp, "exact=true&username=alice.smith": by_username_alice_resp, - }, nil, nil, nil) + }, nil, nil, nil, nil) defer server.Close() // validBody := `{"entity_identifiers": [{"type": "username","identifier": "bob.smith"}]}` @@ -200,7 +235,8 @@ func Test_KCEntityResolutionByGroupEmail(t *testing.T) { "group1-uuid": group_resp, }, map[string]string{ "group1-uuid": group_submember_resp, - }) + }, + nil) defer server.Close() var validBody []*authorization.Entity @@ -246,7 +282,7 @@ func Test_KCEntityResolutionNotFoundError(t *testing.T) { "group1-uuid": group_resp, }, map[string]string{ "group1-uuid": group_submember_resp, - }) + }, nil) defer server.Close() var validBody []*authorization.Entity diff --git a/internal/server/server.go b/internal/server/server.go index 31b943b18b..25556ac1ce 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -48,7 +48,7 @@ type Config struct { HSM security.HSMConfig `yaml:"hsm"` TLS TLSConfig `yaml:"tls"` WellKnownConfigRegister func(namespace string, config any) error - Port int `yaml:"port" default:"9000"` + Port int `yaml:"port" default:"8080"` Host string `yaml:"host,omitempty"` } diff --git a/opentdf-example-no-kas.yaml b/opentdf-example-no-kas.yaml index 295c1657a0..3c39cf29c0 100644 --- a/opentdf-example-no-kas.yaml +++ b/opentdf-example-no-kas.yaml @@ -43,7 +43,7 @@ server: auth: enabled: false audience: "http://localhost:8080" - issuer: http://localhost:8888/auth/realms/opentdf + issuer: http://localhost:8888/auth/realms/tdf clients: - "opentdf" grpc: diff --git a/policies/entitlements/README.md b/policies/entitlements/README.md new file mode 100644 index 0000000000..159b92c25d --- /dev/null +++ b/policies/entitlements/README.md @@ -0,0 +1,11 @@ +# OpenTDF Platform OPA rego policies + +## entitlements.rego + +This is the default rego policy that will parse a JWT, +traverse the subject mappings, and return the entitlements. + +## entitlements-keycloak.rego + +This is a rego policy for calling Keycloak to get an entity representation, +traverse the subject mappings, and return the entitlements. diff --git a/policies/entitlements/entitlements-keycloak.rego b/policies/entitlements/entitlements-keycloak.rego new file mode 100644 index 0000000000..339667c875 --- /dev/null +++ b/policies/entitlements/entitlements-keycloak.rego @@ -0,0 +1,57 @@ +package opentdf.entitlements + +import rego.v1 + + idp_config = {"config": { + "url": input.idp.url, + "realm": input.idp.realm, + "clientid": input.idp.client, + "clientsecret": input.idp.secret, + "legacykeycloak": input.idp.legacy, + }} + +idp_request := {"entities": [{ + "id": input.entity.id, + "emailAddress": input.entity.email_address, + "clientId": input.entity.client_id, +}]} + +attributes := [attribute | + # external entity + response := keycloak.resolve.entities(idp_request, idp_config) + entity_representations := response.entityRepresentations + some entity_representation in entity_representations + + # mappings + some subject_mapping in input.attribute_mappings[attribute].value.subject_mappings + some subject_set in subject_mapping.subject_condition_set.subject_sets + some condition_group in subject_set.condition_groups + condition_group_evaluate(entity_representation.additionalProps, condition_group.boolean_operator, condition_group.conditions) +] + +# condition_group +condition_group_evaluate(payload, boolean_operator, conditions) if { + # AND + boolean_operator == 1 + some condition in conditions + condition_evaluate(payload[condition.subject_external_field], condition.operator, condition.subject_external_values) +} else if { + # OR + boolean_operator == 2 + payload[key] + some condition in conditions + condition_evaluate(payload[condition.subject_external_field], condition.operator, condition.subject_external_values) +} + +# condition +condition_evaluate(property_values, operator, values) if { + # IN + operator == 1 + some property_value in property_values + property_value in values +} else if { + # NOT IN + operator == 2 + some property_value in property_values + not property_value in values +} diff --git a/policies/entitlements/entitlements.rego b/policies/entitlements/entitlements.rego index ab687175c1..218e0ff71d 100644 --- a/policies/entitlements/entitlements.rego +++ b/policies/entitlements/entitlements.rego @@ -2,64 +2,43 @@ package opentdf.entitlements import rego.v1 -idp_config = {"config": { - "url": input.idp.url, - "realm": input.idp.realm, - "clientid": input.idp.client, - "clientsecret": input.idp.secret, - "legacykeycloak": input.idp.legacy, -}} -idp_request = {"entities": [{ - "id": input.entity.id, - "emailAddress": input.entity.email_address, -}]} - - attributes := [attribute | - # external entity - response := keycloak.resolve.entities(idp_request, idp_config) - entity_representations := response.entityRepresentations - some entity_representation in entity_representations - some prop in entity_representation.additionalProps - - # mapppings - some subject_mapping in input.attribute_mappings[attribute].value.subject_mappings - some subject_set in subject_mapping.subject_condition_set.subject_sets + # JWT + # This statement invokes the built-in function `io.jwt.decode` passing the + # parsed bearer_token as a parameter. The `io.jwt.decode` function returns an + # array: [header, payload, signature] + [_, payload, _] := io.jwt.decode(input.entity.jwt) + + # mappings + some subject_mapping in input.attribute_mappings[attribute].value.subject_mappings + some subject_set in subject_mapping.subject_condition_set.subject_sets some condition_group in subject_set.condition_groups - cgbool_evaluate(prop.attributes, condition_group.boolean_operator, condition_group.conditions) + condition_group_evaluate(payload, condition_group.boolean_operator, condition_group.conditions) ] # condition_group -cgbool_evaluate(external_property, boolean_operator, conditions) if { +condition_group_evaluate(payload, boolean_operator, conditions) if { # AND boolean_operator == 1 - external_property[key] some condition in conditions - condition.subject_external_field == key - external_property[key] in condition.subject_external_values + condition_evaluate(payload[condition.subject_external_field], condition.operator, condition.subject_external_values) } else if { # OR boolean_operator == 2 - external_property[key] + payload[key] some condition in conditions - condition.subject_external_field == key - cbool_evaluate(external_property[key], condition.operator, condition.subject_external_values) + condition_evaluate(payload[condition.subject_external_field], condition.operator, condition.subject_external_values) } # condition -cbool_evaluate(properties, operator, values) if { - # AND +condition_evaluate(property_values, operator, values) if { + # IN operator == 1 - some property in properties - some value in values - property == value + some property_value in property_values + property_value in values } else if { - # OR + # NOT IN operator == 2 - some property in properties - some value in values - property == value + some property_value in property_values + not property_value in values } - -# get IdP entity -resolve_entities := keycloak.resolve.entities(idp_request, idp_config) diff --git a/policies/entitlements/input.json b/policies/entitlements/input.json index ecc741ed70..ef40a4a137 100644 --- a/policies/entitlements/input.json +++ b/policies/entitlements/input.json @@ -1,28 +1,85 @@ { - "entity": { - "claims": [ - "ec11", - "ec12", - "ec13" - ], - "email_address": "a@a.af", - "id": "email_address:\"a@a.af\"" - }, - "subjectset": { - "id": "abc", - "condition_groups": [ - { - "conditions": [ + "attribute_mappings": { + "https://brightonhealthclinic.org/attr/healthrecordtype/value/basicpatientinfo": { + "attribute": { + "id": "46fd500e-6839-4cc0-8b29-75665bf98e3a", + "namespace": { + "id": "f1f12166-8b22-47d6-829a-66e68b533eb2", + "name": "brightonhealthclinic.org" + }, + "name": "healthrecordtype", + "rule": 2, + "values": [ { - "subject_attribute": "https://example.com/attr/attr1/value/value1", - "operator": 1, - "subject_values": [ - "ec11" - ] + "id": "356b7dd3-6abb-453c-8354-6915705fabcb", + "value": "basicpatientinfo", + "fqn": "https://brightonhealthclinic.org/attr/healthrecordtype/value/basicpatientinfo", + "active": { + "value": true + } } ], - "boolean_type": 1 + "active": { + "value": true + }, + "metadata": {} + }, + "value": { + "id": "356b7dd3-6abb-453c-8354-6915705fabcb", + "value": "basicpatientinfo", + "fqn": "https://brightonhealthclinic.org/attr/healthrecordtype/value/basicpatientinfo", + "active": { + "value": true + }, + "subject_mappings": [ + { + "id": "5fb9f643-b7ea-4d53-8b23-f2b61a7ca38a", + "subject_condition_set": { + "id": "eb688795-fa0a-4014-8f4e-0f770c7bdab6", + "subject_sets": [ + { + "condition_groups": [ + { + "conditions": [ + { + "subject_external_field": "groups", + "operator": 1, + "subject_external_values": [ + "/medical" + ] + }, + { + "subject_external_field": "roles", + "operator": 1, + "subject_external_values": [ + "nurse", + "doctor" + ] + } + ], + "boolean_operator": 1 + } + ] + } + ], + "metadata": {} + }, + "actions": [ + { + "Value": { + "Standard": 1 + } + } + ], + "metadata": {} + } + ] } - ] - } + } + }, + "entity": { + "client_id": "opentdf-sdk", + "id": "" + }, + "idp": null } diff --git a/protocol/go/authorization/authorization.pb.go b/protocol/go/authorization/authorization.pb.go index 39ff837486..2214498bdb 100644 --- a/protocol/go/authorization/authorization.pb.go +++ b/protocol/go/authorization/authorization.pb.go @@ -89,6 +89,7 @@ type Entity struct { // *Entity_Jwt // *Entity_Claims // *Entity_Custom + // *Entity_ClientId EntityType isEntity_EntityType `protobuf_oneof:"entity_type"` } @@ -180,6 +181,13 @@ func (x *Entity) GetCustom() *EntityCustom { return nil } +func (x *Entity) GetClientId() string { + if x, ok := x.GetEntityType().(*Entity_ClientId); ok { + return x.ClientId + } + return "" +} + type isEntity_EntityType interface { isEntity_EntityType() } @@ -208,6 +216,10 @@ type Entity_Custom struct { Custom *EntityCustom `protobuf:"bytes,7,opt,name=custom,proto3,oneof"` } +type Entity_ClientId struct { + ClientId string `protobuf:"bytes,8,opt,name=client_id,json=clientId,proto3,oneof"` +} + func (*Entity_EmailAddress) isEntity_EntityType() {} func (*Entity_UserName) isEntity_EntityType() {} @@ -220,6 +232,8 @@ func (*Entity_Claims) isEntity_EntityType() {} func (*Entity_Custom) isEntity_EntityType() {} +func (*Entity_ClientId) isEntity_EntityType() {} + // Entity type for custom entities beyond the standard types type EntityCustom struct { state protoimpl.MessageState @@ -889,7 +903,7 @@ var file_authorization_authorization_proto_rawDesc = []byte{ 0x1a, 0x19, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x61, 0x6e, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x14, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2f, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x22, 0x96, 0x02, 0x0a, 0x06, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x0e, 0x0a, 0x02, + 0x6f, 0x22, 0xb5, 0x02, 0x0a, 0x06, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x25, 0x0a, 0x0d, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0c, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x41, 0x64, 0x64, 0x72, @@ -905,119 +919,121 @@ var file_authorization_authorization_proto_rawDesc = []byte{ 0x61, 0x69, 0x6d, 0x73, 0x12, 0x35, 0x0a, 0x06, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x43, 0x75, 0x73, 0x74, 0x6f, - 0x6d, 0x48, 0x00, 0x52, 0x06, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x42, 0x0d, 0x0a, 0x0b, 0x65, - 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x22, 0x42, 0x0a, 0x0c, 0x45, 0x6e, - 0x74, 0x69, 0x74, 0x79, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x12, 0x32, 0x0a, 0x09, 0x65, 0x78, - 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x41, 0x6e, 0x79, 0x52, 0x09, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x50, - 0x0a, 0x0b, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x0e, 0x0a, - 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x31, 0x0a, - 0x08, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x15, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, - 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x08, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, - 0x22, 0xcf, 0x01, 0x0a, 0x0f, 0x44, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x28, 0x0a, 0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, - 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x41, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x3f, - 0x0a, 0x0d, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x73, 0x18, - 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x43, 0x68, 0x61, 0x69, - 0x6e, 0x52, 0x0c, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x73, 0x12, - 0x51, 0x0a, 0x13, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x61, 0x74, 0x74, 0x72, - 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x61, - 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x52, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x52, 0x12, + 0x6d, 0x48, 0x00, 0x52, 0x06, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x12, 0x1d, 0x0a, 0x09, 0x63, + 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, + 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x42, 0x0d, 0x0a, 0x0b, 0x65, 0x6e, + 0x74, 0x69, 0x74, 0x79, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x22, 0x42, 0x0a, 0x0c, 0x45, 0x6e, 0x74, + 0x69, 0x74, 0x79, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x12, 0x32, 0x0a, 0x09, 0x65, 0x78, 0x74, + 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, + 0x6e, 0x79, 0x52, 0x09, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x50, 0x0a, + 0x0b, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x0e, 0x0a, 0x02, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x31, 0x0a, 0x08, + 0x65, 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, + 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x45, + 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x08, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x22, + 0xcf, 0x01, 0x0a, 0x0f, 0x44, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x28, 0x0a, 0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x41, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x3f, 0x0a, + 0x0d, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x73, 0x18, 0x02, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x43, 0x68, 0x61, 0x69, 0x6e, + 0x52, 0x0c, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x73, 0x12, 0x51, + 0x0a, 0x13, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x61, 0x74, 0x74, 0x72, 0x69, + 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x61, 0x75, + 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x52, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x52, 0x12, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, + 0x73, 0x22, 0xce, 0x02, 0x0a, 0x10, 0x44, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, + 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0d, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x34, + 0x0a, 0x16, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x61, 0x74, 0x74, 0x72, 0x69, + 0x62, 0x75, 0x74, 0x65, 0x73, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, - 0x65, 0x73, 0x22, 0xce, 0x02, 0x0a, 0x10, 0x44, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x65, 0x6e, 0x74, 0x69, 0x74, - 0x79, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0d, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, - 0x34, 0x0a, 0x16, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x61, 0x74, 0x74, 0x72, - 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x14, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, - 0x74, 0x65, 0x73, 0x49, 0x64, 0x12, 0x26, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x41, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x44, 0x0a, - 0x08, 0x64, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, - 0x28, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, - 0x44, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x2e, 0x44, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x64, 0x65, 0x63, 0x69, 0x73, - 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x0a, 0x0b, 0x6f, 0x62, 0x6c, 0x69, 0x67, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x6f, 0x62, 0x6c, 0x69, 0x67, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x4c, 0x0a, 0x08, 0x44, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, - 0x6e, 0x12, 0x18, 0x0a, 0x14, 0x44, 0x45, 0x43, 0x49, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, - 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x44, - 0x45, 0x43, 0x49, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x44, 0x45, 0x4e, 0x59, 0x10, 0x01, 0x12, 0x13, - 0x0a, 0x0f, 0x44, 0x45, 0x43, 0x49, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x50, 0x45, 0x52, 0x4d, 0x49, - 0x54, 0x10, 0x02, 0x22, 0x62, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x44, 0x65, 0x63, 0x69, 0x73, 0x69, - 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x4b, 0x0a, 0x11, 0x64, 0x65, - 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x18, - 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x44, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x10, 0x64, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x22, 0x66, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x44, 0x65, - 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x4e, 0x0a, 0x12, 0x64, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x61, 0x75, - 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x44, 0x65, 0x63, 0x69, - 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x11, 0x64, 0x65, - 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x73, 0x22, - 0x92, 0x01, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, - 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x31, 0x0a, 0x08, 0x65, 0x6e, - 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x61, - 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x45, 0x6e, 0x74, - 0x69, 0x74, 0x79, 0x52, 0x08, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x12, 0x3b, 0x0a, - 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x61, - 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x52, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x48, 0x00, - 0x52, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x88, 0x01, 0x01, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x73, - 0x63, 0x6f, 0x70, 0x65, 0x22, 0x63, 0x0a, 0x12, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x45, 0x6e, - 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x65, 0x6e, - 0x74, 0x69, 0x74, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, - 0x6e, 0x74, 0x69, 0x74, 0x79, 0x49, 0x64, 0x12, 0x30, 0x0a, 0x14, 0x61, 0x74, 0x74, 0x72, 0x69, - 0x62, 0x75, 0x74, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x66, 0x71, 0x6e, 0x73, 0x18, - 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x12, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, - 0x56, 0x61, 0x6c, 0x75, 0x65, 0x46, 0x71, 0x6e, 0x73, 0x22, 0x45, 0x0a, 0x11, 0x52, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x12, 0x30, - 0x0a, 0x14, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x5f, 0x66, 0x71, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x12, 0x61, 0x74, - 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x46, 0x71, 0x6e, 0x73, - 0x22, 0x60, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, - 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x45, 0x0a, 0x0c, 0x65, - 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x21, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, - 0x65, 0x6e, 0x74, 0x73, 0x52, 0x0c, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, - 0x74, 0x73, 0x32, 0x86, 0x02, 0x0a, 0x14, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x72, 0x0a, 0x0c, 0x47, - 0x65, 0x74, 0x44, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x22, 0x2e, 0x61, 0x75, - 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x44, - 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x23, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, - 0x47, 0x65, 0x74, 0x44, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x19, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x22, 0x11, 0x2f, 0x76, - 0x31, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, - 0x7a, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, - 0x74, 0x73, 0x12, 0x25, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, - 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x61, 0x75, 0x74, 0x68, - 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x6e, 0x74, - 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x18, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12, 0x22, 0x10, 0x2f, 0x76, 0x31, 0x2f, 0x65, - 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x42, 0xb2, 0x01, 0x0a, 0x11, - 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x42, 0x12, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x35, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, - 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x64, 0x66, 0x2f, 0x70, 0x6c, 0x61, 0x74, - 0x66, 0x6f, 0x72, 0x6d, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2f, 0x67, 0x6f, - 0x2f, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0xa2, 0x02, - 0x03, 0x41, 0x58, 0x58, 0xaa, 0x02, 0x0d, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0xca, 0x02, 0x0d, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0xe2, 0x02, 0x19, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, - 0xea, 0x02, 0x0d, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x65, 0x73, 0x49, 0x64, 0x12, 0x26, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x41, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x44, 0x0a, 0x08, + 0x64, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x28, + 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x44, + 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, + 0x44, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x64, 0x65, 0x63, 0x69, 0x73, 0x69, + 0x6f, 0x6e, 0x12, 0x20, 0x0a, 0x0b, 0x6f, 0x62, 0x6c, 0x69, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x6f, 0x62, 0x6c, 0x69, 0x67, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x4c, 0x0a, 0x08, 0x44, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, + 0x12, 0x18, 0x0a, 0x14, 0x44, 0x45, 0x43, 0x49, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x53, + 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x44, 0x45, + 0x43, 0x49, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x44, 0x45, 0x4e, 0x59, 0x10, 0x01, 0x12, 0x13, 0x0a, + 0x0f, 0x44, 0x45, 0x43, 0x49, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x50, 0x45, 0x52, 0x4d, 0x49, 0x54, + 0x10, 0x02, 0x22, 0x62, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x44, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, + 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x4b, 0x0a, 0x11, 0x64, 0x65, 0x63, + 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x44, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x52, 0x10, 0x64, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x22, 0x66, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x44, 0x65, 0x63, + 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, + 0x0a, 0x12, 0x64, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x61, 0x75, 0x74, + 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x44, 0x65, 0x63, 0x69, 0x73, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x11, 0x64, 0x65, 0x63, + 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x73, 0x22, 0x92, + 0x01, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, + 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x31, 0x0a, 0x08, 0x65, 0x6e, 0x74, + 0x69, 0x74, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x61, 0x75, + 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x45, 0x6e, 0x74, 0x69, + 0x74, 0x79, 0x52, 0x08, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x12, 0x3b, 0x0a, 0x05, + 0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x61, 0x75, + 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x52, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x48, 0x00, 0x52, + 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x88, 0x01, 0x01, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x73, 0x63, + 0x6f, 0x70, 0x65, 0x22, 0x63, 0x0a, 0x12, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x45, 0x6e, 0x74, + 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x65, 0x6e, 0x74, + 0x69, 0x74, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x6e, + 0x74, 0x69, 0x74, 0x79, 0x49, 0x64, 0x12, 0x30, 0x0a, 0x14, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, + 0x75, 0x74, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x66, 0x71, 0x6e, 0x73, 0x18, 0x02, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x12, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x56, + 0x61, 0x6c, 0x75, 0x65, 0x46, 0x71, 0x6e, 0x73, 0x22, 0x45, 0x0a, 0x11, 0x52, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x12, 0x30, 0x0a, + 0x14, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x5f, 0x66, 0x71, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x12, 0x61, 0x74, 0x74, + 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x46, 0x71, 0x6e, 0x73, 0x22, + 0x60, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, + 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x45, 0x0a, 0x0c, 0x65, 0x6e, + 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x21, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, + 0x6e, 0x74, 0x73, 0x52, 0x0c, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, + 0x73, 0x32, 0x86, 0x02, 0x0a, 0x14, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x72, 0x0a, 0x0c, 0x47, 0x65, + 0x74, 0x44, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x22, 0x2e, 0x61, 0x75, 0x74, + 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, + 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, + 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x47, + 0x65, 0x74, 0x44, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x19, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x22, 0x11, 0x2f, 0x76, 0x31, + 0x2f, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x7a, + 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, + 0x73, 0x12, 0x25, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, + 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x69, + 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x18, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12, 0x22, 0x10, 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x6e, + 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x42, 0xb2, 0x01, 0x0a, 0x11, 0x63, + 0x6f, 0x6d, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x42, 0x12, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, + 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x35, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, + 0x6f, 0x6d, 0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x64, 0x66, 0x2f, 0x70, 0x6c, 0x61, 0x74, 0x66, + 0x6f, 0x72, 0x6d, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2f, 0x67, 0x6f, 0x2f, + 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0xa2, 0x02, 0x03, + 0x41, 0x58, 0x58, 0xaa, 0x02, 0x0d, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0xca, 0x02, 0x0d, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0xe2, 0x02, 0x19, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, + 0x02, 0x0d, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1222,6 +1238,7 @@ func file_authorization_authorization_proto_init() { (*Entity_Jwt)(nil), (*Entity_Claims)(nil), (*Entity_Custom)(nil), + (*Entity_ClientId)(nil), } file_authorization_authorization_proto_msgTypes[7].OneofWrappers = []interface{}{} type x struct{} diff --git a/services/authorization/authorization.proto b/services/authorization/authorization.proto index 05cfd8ebde..ddf9ae42a8 100644 --- a/services/authorization/authorization.proto +++ b/services/authorization/authorization.proto @@ -18,6 +18,7 @@ message Entity { string jwt = 5; google.protobuf.Any claims = 6; EntityCustom custom = 7; + string client_id = 8; } } diff --git a/services/kas/access/accessPdp.go b/services/kas/access/accessPdp.go index 2e775123e9..b8102491ad 100644 --- a/services/kas/access/accessPdp.go +++ b/services/kas/access/accessPdp.go @@ -5,90 +5,79 @@ import ( "errors" "log/slog" - attrs "github.com/virtru/access-pdp/attributes" - accessPdp "github.com/virtru/access-pdp/pdp" + "github.com/opentdf/platform/protocol/go/authorization" + "github.com/opentdf/platform/protocol/go/policy" + otdf "github.com/opentdf/platform/sdk" ) const ( - ErrPolicyDissemInvalid = Error("policy dissem invalid") - ErrDecisionUnexpected = Error("access policy decision unexpected") + ErrPolicyDissemInvalid = Error("policy dissem invalid") + ErrDecisionUnexpected = Error("authorization decision unexpected") + ErrDecisionCountUnexpected = Error("authorization decision count unexpected") ) -func canAccess(ctx context.Context, entityID string, policy Policy, claims ClaimsObject, attrDefs []attrs.AttributeDefinition) (bool, error) { - dissemAccess, err := checkDissems(policy.Body.Dissem, entityID) - if err != nil { - return false, err - } - attrAccess, err := checkAttributes(ctx, policy.Body.DataAttributes, claims.Entitlements, attrDefs) - if err != nil { - return false, err +func canAccess(ctx context.Context, entity authorization.Entity, policy Policy, sdk *otdf.SDK) (bool, error) { + if len(policy.Body.Dissem) > 0 { + dissemAccess, err := checkDissems(policy.Body.Dissem, entity) + if err != nil { + return false, err + } + return dissemAccess, nil } - if dissemAccess && attrAccess { - return true, nil - } else { - return false, nil + if policy.Body.DataAttributes != nil { + attrAccess, err := checkAttributes(ctx, policy.Body.DataAttributes, entity, sdk) + if err != nil { + return false, err + } + return attrAccess, nil } + // if no dissem and no attributes then allow + return true, nil } -func checkDissems(dissems []string, entityID string) (bool, error) { - if entityID == "" { +func checkDissems(dissems []string, ent authorization.Entity) (bool, error) { + if ent.GetEmailAddress() == "" { return false, ErrPolicyDissemInvalid } - if len(dissems) == 0 || contains(dissems, entityID) { + if len(dissems) == 0 || contains(dissems, ent.GetEmailAddress()) { return true, nil } return false, nil } -func checkAttributes(ctx context.Context, dataAttrs []Attribute, entitlements []Entitlement, attrDefs []attrs.AttributeDefinition) (bool, error) { - // convert data and entitty attrs to attrs.AttributeInstance - dataAttrInstances, err := convertAttrsToAttrInstances(dataAttrs) - if err != nil { - return false, err +func checkAttributes(ctx context.Context, dataAttrs []Attribute, ent authorization.Entity, sdk *otdf.SDK) (bool, error) { + ec := authorization.EntityChain{Entities: make([]*authorization.Entity, 0)} + ec.Entities = append(ec.Entities, &ent) + ras := []*authorization.ResourceAttribute{{ + AttributeValueFqns: make([]string, 0), + }} + for _, attr := range dataAttrs { + ras[0].AttributeValueFqns = append(ras[0].GetAttributeValueFqns(), attr.URI) } - entityAttrMap, err := convertEntitlementsToEntityAttrMap(entitlements) - if err != nil { - return false, err + in := authorization.GetDecisionsRequest{ + DecisionRequests: []*authorization.DecisionRequest{ + { + Actions: []*policy.Action{ + {Value: &policy.Action_Standard{Standard: policy.Action_STANDARD_ACTION_DECRYPT}}, + }, + EntityChains: []*authorization.EntityChain{&ec}, + ResourceAttributes: ras, + }, + }, } - - accessPDP := accessPdp.NewAccessPDPWithSlog(slog.Default()) - - decisions, err := accessPDP.DetermineAccess(dataAttrInstances, entityAttrMap, attrDefs, &ctx) + dr, err := sdk.Authorization.GetDecisions(ctx, &in) if err != nil { - slog.WarnContext(ctx, "Error recieved from accessPDP", "err", err) + slog.ErrorContext(ctx, "Error received from GetDecisions", "err", err) return false, errors.Join(ErrDecisionUnexpected, err) } - // check the decisions - for _, decision := range decisions { - if !decision.Access { - return false, nil - } - } - return true, nil -} - -func convertAttrsToAttrInstances(attributes []Attribute) ([]attrs.AttributeInstance, error) { - instances := make([]attrs.AttributeInstance, len(attributes)) - for i, attr := range attributes { - instance, err := attrs.ParseInstanceFromURI(attr.URI) - if err != nil { - return nil, errors.Join(ErrPolicyDataAttributeParse, err) - } - instances[i] = instance + if len(dr.DecisionResponses) != 1 { + slog.ErrorContext(ctx, ErrDecisionCountUnexpected.Error(), "count", len(dr.DecisionResponses)) + return false, ErrDecisionCountUnexpected } - return instances, nil -} - -func convertEntitlementsToEntityAttrMap(entitlements []Entitlement) (map[string][]attrs.AttributeInstance, error) { - entityAttrMap := make(map[string][]attrs.AttributeInstance) - for _, entitlement := range entitlements { - instances, err := convertAttrsToAttrInstances(entitlement.EntityAttributes) - if err != nil { - return nil, err - } - entityAttrMap[entitlement.EntityID] = instances + if dr.DecisionResponses[0].Decision == authorization.DecisionResponse_DECISION_PERMIT { + return true, nil } - return entityAttrMap, nil + return false, nil } func contains(s []string, e string) bool { diff --git a/services/kas/access/accessPdp_test.go b/services/kas/access/accessPdp_test.go index 9b3795d393..15cd502f33 100644 --- a/services/kas/access/accessPdp_test.go +++ b/services/kas/access/accessPdp_test.go @@ -2,44 +2,17 @@ package access import ( "context" + "github.com/google/uuid" + "github.com/opentdf/platform/protocol/go/authorization" + "github.com/opentdf/platform/sdk" "testing" - - uuid "github.com/google/uuid" - attrs "github.com/virtru/access-pdp/attributes" ) var c = context.Background() -// ######## Dissem tests ################ - -func TestWildcardDissemSuccess(t *testing.T) { - var entityID string = "email2@example.com" - - testPolicy := Policy{ - UUID: uuid.New(), - Body: PolicyBody{ - DataAttributes: []Attribute{}, - Dissem: []string{}, - }, - } +var osdk, _ = sdk.New("", sdk.WithClientCredentials("myid", "mysecret", nil)) - testClaims := ClaimsObject{ - PublicKey: "test-public-key", - ClientPublicSigningKey: "test-client-public-signing-key", - SchemaVersion: "test-schema", - Entitlements: []Entitlement{}, - } - - testDefinitions := []attrs.AttributeDefinition{} - - output, err := canAccess(c, entityID, testPolicy, testClaims, testDefinitions) - if err != nil { - t.Error(err) - } - if !output { - t.Errorf("Output %v not equal to expected %v", output, true) - } -} +// ######## Dissem tests ################ func TestDissemSuccess(t *testing.T) { var entityID string = "email2@example.com" @@ -53,17 +26,11 @@ func TestDissemSuccess(t *testing.T) { "email3@example.com"}, }, } - - testClaims := ClaimsObject{ - PublicKey: "test-public-key", - ClientPublicSigningKey: "test-client-public-signing-key", - SchemaVersion: "test-schema", - Entitlements: []Entitlement{}, + entity := authorization.Entity{ + Id: "0", + EntityType: &authorization.Entity_EmailAddress{EmailAddress: entityID}, } - - testDefinitions := []attrs.AttributeDefinition{} - - output, err := canAccess(c, entityID, testPolicy, testClaims, testDefinitions) + output, err := canAccess(c, entity, testPolicy, osdk) if err != nil { t.Error(err) } @@ -83,309 +50,11 @@ func TestDissemFailure(t *testing.T) { "email3@example.com"}, }, } - - testClaims := ClaimsObject{ - PublicKey: "test-public-key", - ClientPublicSigningKey: "test-client-public-signing-key", - SchemaVersion: "test-schema", - Entitlements: []Entitlement{}, - } - - testDefinitions := []attrs.AttributeDefinition{} - - output, err := canAccess(c, entityID, testPolicy, testClaims, testDefinitions) - if err != nil { - t.Error(err) - } - if output { - t.Errorf("Output %v not equal to expected %v", output, false) - } -} - -// ######## All Of tests ################ - -func TestAllOfSuccess(t *testing.T) { - var entityID string = "email2@example.com" - - testPolicy := Policy{ - UUID: uuid.New(), - Body: PolicyBody{ - DataAttributes: []Attribute{ - {URI: "https://example.com/attr/Test1/value/A", Name: "Test1"}, - }, - Dissem: []string{}, - }, - } - - testClaims := ClaimsObject{ - PublicKey: "test-public-key", - ClientPublicSigningKey: "test-client-public-signing-key", - SchemaVersion: "test-schema", - Entitlements: []Entitlement{ - { - EntityID: "email2@example.com", - EntityAttributes: []Attribute{ - {URI: "https://example.com/attr/Test1/value/A", Name: "Test1"}, - {URI: "https://example2.com/attr/Test2/value/B", Name: "Test2"}, - {URI: "https://example3.com/attr/Test3/value/C", Name: "Test3"}, - }, - }, - }, - } - - testDefinitions := []attrs.AttributeDefinition{ - { - Authority: "https://example.com", - Name: "Test1", - Rule: "allOf", - Order: []string{"A", "B", "C"}, - }, - } - - output, err := canAccess(c, entityID, testPolicy, testClaims, testDefinitions) - if err != nil { - t.Error(err) - } - if !output { - t.Errorf("Output %v not equal to expected %v", output, true) - } -} - -func TestAllOfFailure(t *testing.T) { - var entityID string = "email2@example.com" - - testPolicy := Policy{ - UUID: uuid.New(), - Body: PolicyBody{ - DataAttributes: []Attribute{ - {URI: "https://example.com/attr/Test1/value/A", Name: "Test1"}, - {URI: "https://example.com/attr/Test1/value/B", Name: "Test1"}, - }, - Dissem: []string{}, - }, - } - - testClaims := ClaimsObject{ - PublicKey: "test-public-key", - ClientPublicSigningKey: "test-client-public-signing-key", - SchemaVersion: "test-schema", - Entitlements: []Entitlement{ - { - EntityID: "email2@example.com", - EntityAttributes: []Attribute{ - {URI: "https://example.com/attr/Test1/value/A", Name: "Test1"}, - {URI: "https://example2.com/attr/Test2/value/B", Name: "Test2"}, - {URI: "https://example3.com/attr/Test3/value/C", Name: "Test3"}, - }, - }, - }, - } - - testDefinitions := []attrs.AttributeDefinition{ - { - Authority: "https://example.com", - Name: "Test1", - Rule: "allOf", - Order: []string{"A", "B", "C"}, - }, - } - - output, err := canAccess(c, entityID, testPolicy, testClaims, testDefinitions) - if err != nil { - t.Error(err) - } - if output { - t.Errorf("Output %v not equal to expected %v", output, false) - } -} - -// ######## Any Of tests ################ - -func TestAnyOfSuccess(t *testing.T) { - var entityID string = "email2@example.com" - - testPolicy := Policy{ - UUID: uuid.New(), - Body: PolicyBody{ - DataAttributes: []Attribute{ - {URI: "https://example3.com/attr/Test3/value/A", Name: "Test3"}, - {URI: "https://example3.com/attr/Test3/value/C", Name: "Test3"}, - }, - Dissem: []string{}, - }, - } - - testClaims := ClaimsObject{ - PublicKey: "test-public-key", - ClientPublicSigningKey: "test-client-public-signing-key", - SchemaVersion: "test-schema", - Entitlements: []Entitlement{ - { - EntityID: "email2@example.com", - EntityAttributes: []Attribute{ - {URI: "https://example.com/attr/Test1/value/A", Name: "Test1"}, - {URI: "https://example2.com/attr/Test2/value/B", Name: "Test2"}, - {URI: "https://example3.com/attr/Test3/value/C", Name: "Test3"}, - }, - }, - }, - } - - testDefinitions := []attrs.AttributeDefinition{ - { - Authority: "https://example3.com", - Name: "Test3", - Rule: "anyOf", - Order: []string{"A", "B", "C"}, - }, - } - - output, err := canAccess(c, entityID, testPolicy, testClaims, testDefinitions) - if err != nil { - t.Error(err) - } - if !output { - t.Errorf("Output %v not equal to expected %v", output, true) - } -} - -func TestAnyOfFailure(t *testing.T) { - var entityID string = "email2@example.com" - - testPolicy := Policy{ - UUID: uuid.New(), - Body: PolicyBody{ - DataAttributes: []Attribute{ - {URI: "https://example3.com/attr/Test3/value/A", Name: "Test3"}, - {URI: "https://example3.com/attr/Test3/value/B", Name: "Test3"}, - }, - Dissem: []string{}, - }, - } - - testClaims := ClaimsObject{ - PublicKey: "test-public-key", - ClientPublicSigningKey: "test-client-public-signing-key", - SchemaVersion: "test-schema", - Entitlements: []Entitlement{ - { - EntityID: "email2@example.com", - EntityAttributes: []Attribute{ - {URI: "https://example.com/attr/Test1/value/A", Name: "Test1"}, - {URI: "https://example2.com/attr/Test2/value/B", Name: "Test2"}, - {URI: "https://example3.com/attr/Test3/value/C", Name: "Test3"}, - }, - }, - }, - } - - testDefinitions := []attrs.AttributeDefinition{ - { - Authority: "https://example3.com", - Name: "Test3", - Rule: "anyOf", - Order: []string{"A", "B", "C"}, - }, - } - - output, err := canAccess(c, entityID, testPolicy, testClaims, testDefinitions) - if err != nil { - t.Error(err) - } - if output { - t.Errorf("Output %v not equal to expected %v", output, false) - } -} - -// ######## Hierarchy tests ################ - -func TestHierarchySuccess(t *testing.T) { - var entityID string = "email2@example.com" - - testPolicy := Policy{ - UUID: uuid.New(), - Body: PolicyBody{ - DataAttributes: []Attribute{ - {URI: "https://example2.com/attr/Test2/value/C", Name: "Test2"}, - }, - Dissem: []string{}, - }, - } - - testClaims := ClaimsObject{ - PublicKey: "test-public-key", - ClientPublicSigningKey: "test-client-public-signing-key", - SchemaVersion: "test-schema", - Entitlements: []Entitlement{ - { - EntityID: "email2@example.com", - EntityAttributes: []Attribute{ - {URI: "https://example.com/attr/Test1/value/A", Name: "Test1"}, - {URI: "https://example2.com/attr/Test2/value/B", Name: "Test2"}, - {URI: "https://example3.com/attr/Test3/value/C", Name: "Test3"}, - }, - }, - }, - } - - testDefinitions := []attrs.AttributeDefinition{ - { - Authority: "https://example2.com", - Name: "Test2", - Rule: "hierarchy", - Order: []string{"A", "B", "C"}, - }, + entity := authorization.Entity{ + Id: "0", + EntityType: &authorization.Entity_EmailAddress{EmailAddress: entityID}, } - - output, err := canAccess(c, entityID, testPolicy, testClaims, testDefinitions) - if err != nil { - t.Error(err) - } - if !output { - t.Errorf("Output %v not equal to expected %v", output, true) - } -} - -func TestHierarchyFailure(t *testing.T) { - var entityID string = "email2@example.com" - - testPolicy := Policy{ - UUID: uuid.New(), - Body: PolicyBody{ - DataAttributes: []Attribute{ - {URI: "https://example2.com/attr/Test2/value/A", Name: "Test2"}, - {URI: "https://example2.com/attr/Test2/value/B", Name: "Test2"}, - }, - Dissem: []string{}, - }, - } - - testClaims := ClaimsObject{ - PublicKey: "test-public-key", - ClientPublicSigningKey: "test-client-public-signing-key", - SchemaVersion: "test-schema", - Entitlements: []Entitlement{ - { - EntityID: "email2@example.com", - EntityAttributes: []Attribute{ - {URI: "https://example.com/attr/Test1/value/A", Name: "Test1"}, - {URI: "https://example2.com/attr/Test2/value/B", Name: "Test2"}, - {URI: "https://example3.com/attr/Test3/value/C", Name: "Test3"}, - }, - }, - }, - } - - testDefinitions := []attrs.AttributeDefinition{ - { - Authority: "https://example2.com", - Name: "Test2", - Rule: "hierarchy", - Order: []string{"A", "B", "C"}, - }, - } - - output, err := canAccess(c, entityID, testPolicy, testClaims, testDefinitions) + output, err := canAccess(c, entity, testPolicy, osdk) if err != nil { t.Error(err) } @@ -393,344 +62,3 @@ func TestHierarchyFailure(t *testing.T) { t.Errorf("Output %v not equal to expected %v", output, false) } } - -// ######## Dissem Attribute combination ############ - -func TestAttrDissemSuccess(t *testing.T) { - var entityID string = "email2@example.com" - - testPolicy := Policy{ - UUID: uuid.New(), - Body: PolicyBody{ - DataAttributes: []Attribute{ - {URI: "https://example.com/attr/Test1/value/A", Name: "Test1"}, - }, - Dissem: []string{"email1@example.com", - "email2@example.com", - "email3@example.com"}, - }, - } - - testClaims := ClaimsObject{ - PublicKey: "test-public-key", - ClientPublicSigningKey: "test-client-public-signing-key", - SchemaVersion: "test-schema", - Entitlements: []Entitlement{ - { - EntityID: "email2@example.com", - EntityAttributes: []Attribute{ - {URI: "https://example.com/attr/Test1/value/A", Name: "Test1"}, - {URI: "https://example2.com/attr/Test2/value/B", Name: "Test2"}, - {URI: "https://example3.com/attr/Test3/value/C", Name: "Test3"}, - }, - }, - }, - } - - testDefinitions := []attrs.AttributeDefinition{ - { - Authority: "https://example.com", - Name: "Test1", - Rule: "allOf", - Order: []string{"A", "B", "C"}, - }, - } - - output, err := canAccess(c, entityID, testPolicy, testClaims, testDefinitions) - if err != nil { - t.Error(err) - } - if !output { - t.Errorf("Output %v not equal to expected %v", output, true) - } -} - -func TestAttrDissemFailure1(t *testing.T) { - var entityID string = "email2@example.com" - - testPolicy := Policy{ - UUID: uuid.New(), - Body: PolicyBody{ - DataAttributes: []Attribute{ - {URI: "https://example.com/attr/Test1/value/A", Name: "Test1"}, - }, - Dissem: []string{"email1@example.com", - "email3@example.com"}, - }, - } - - testClaims := ClaimsObject{ - PublicKey: "test-public-key", - ClientPublicSigningKey: "test-client-public-signing-key", - SchemaVersion: "test-schema", - Entitlements: []Entitlement{ - { - EntityID: "email2@example.com", - EntityAttributes: []Attribute{ - {URI: "https://example.com/attr/Test1/value/A", Name: "Test1"}, - {URI: "https://example2.com/attr/Test2/value/B", Name: "Test2"}, - {URI: "https://example3.com/attr/Test3/value/C", Name: "Test3"}, - }, - }, - }, - } - - testDefinitions := []attrs.AttributeDefinition{ - { - Authority: "https://example.com", - Name: "Test1", - Rule: "allOf", - Order: []string{"A", "B", "C"}, - }, - } - - output, err := canAccess(c, entityID, testPolicy, testClaims, testDefinitions) - if err != nil { - t.Error(err) - } - if output { - t.Errorf("Output %v not equal to expected %v", output, false) - } -} - -func TestAttrDissemFailure2(t *testing.T) { - var entityID string = "email2@example.com" - - testPolicy := Policy{ - UUID: uuid.New(), - Body: PolicyBody{ - DataAttributes: []Attribute{ - {URI: "https://example.com/attr/Test1/value/A", Name: "Test1"}, - {URI: "https://example.com/attr/Test1/value/B", Name: "Test1"}, - }, - Dissem: []string{"email1@example.com", - "email2@example.com", - "email3@example.com"}, - }, - } - - testClaims := ClaimsObject{ - PublicKey: "test-public-key", - ClientPublicSigningKey: "test-client-public-signing-key", - SchemaVersion: "test-schema", - Entitlements: []Entitlement{ - { - EntityID: "email2@example.com", - EntityAttributes: []Attribute{ - {URI: "https://example.com/attr/Test1/value/A", Name: "Test1"}, - {URI: "https://example2.com/attr/Test2/value/B", Name: "Test2"}, - {URI: "https://example3.com/attr/Test3/value/C", Name: "Test3"}, - }, - }, - }, - } - - testDefinitions := []attrs.AttributeDefinition{ - { - Authority: "https://example.com", - Name: "Test1", - Rule: "allOf", - Order: []string{"A", "B", "C"}, - }, - } - - output, err := canAccess(c, entityID, testPolicy, testClaims, testDefinitions) - if err != nil { - t.Error(err) - } - if output { - t.Errorf("Output %v not equal to expected %v", output, false) - } -} - -func TestAttrDissemFailure3(t *testing.T) { - var entityID string = "" - - testPolicy := Policy{ - UUID: uuid.New(), - Body: PolicyBody{ - DataAttributes: []Attribute{ - {URI: "https://example.com/attr/Test1/value/A", Name: "Test1"}, - {URI: "https://example.com/attr/Test1/value/B", Name: "Test1"}, - }, - Dissem: []string{"email1@example.com", - "email2@example.com", - "email3@example.com"}, - }, - } - - testClaims := ClaimsObject{ - PublicKey: "test-public-key", - ClientPublicSigningKey: "test-client-public-signing-key", - SchemaVersion: "test-schema", - Entitlements: []Entitlement{ - { - EntityID: "email2@example.com", - EntityAttributes: []Attribute{ - {URI: "https://example.com/attr/Test1/value/A", Name: "Test1"}, - {URI: "https://example2.com/attr/Test2/value/B", Name: "Test2"}, - {URI: "https://example3.com/attr/Test3/value/C", Name: "Test3"}, - }, - }, - }, - } - - testDefinitions := []attrs.AttributeDefinition{ - { - Authority: "https://example.com", - Name: "Test1", - Rule: "allOf", - Order: []string{"A", "B", "C"}, - }, - } - - output, err := canAccess(c, entityID, testPolicy, testClaims, testDefinitions) - if err != ErrPolicyDissemInvalid { - t.Errorf("Output %v not equal to expected %v", output, ErrPolicyDissemInvalid) - } - if output { - t.Errorf("Output %v not equal to expected %v", output, false) - } -} - -func TestAttrDissemFailure4(t *testing.T) { - var entityID string = "email2@example.com" - - testPolicy := Policy{ - UUID: uuid.New(), - Body: PolicyBody{ - DataAttributes: []Attribute{ - {URI: "", Name: "Test1"}, - }, - Dissem: []string{"email1@example.com", - "email2@example.com", - "email3@example.com"}, - }, - } - - testClaims := ClaimsObject{ - PublicKey: "test-public-key", - ClientPublicSigningKey: "test-client-public-signing-key", - SchemaVersion: "test-schema", - Entitlements: []Entitlement{ - { - EntityID: "email2@example.com", - EntityAttributes: []Attribute{ - {URI: "https://example.com/attr/Test1/value/A", Name: "Test1"}, - {URI: "https://example2.com/attr/Test2/value/B", Name: "Test2"}, - {URI: "https://example3.com/attr/Test3/value/C", Name: "Test3"}, - }, - }, - }, - } - - testDefinitions := []attrs.AttributeDefinition{ - { - Authority: "https://example.com", - Name: "Test1", - Rule: "allOf", - Order: []string{"A", "B", "C"}, - }, - } - - output, err := canAccess(c, entityID, testPolicy, testClaims, testDefinitions) - - if output != false { - t.Errorf("Expected false, but got %v", err) - } - - if err == nil { - t.Errorf("Expected error, but got %v", err) - } -} - -func TestAttrDissemFailure5(t *testing.T) { - var entityID string = "email2@example.com" - - testPolicy := Policy{ - UUID: uuid.New(), - Body: PolicyBody{ - DataAttributes: []Attribute{ - {URI: "https://example.com/attr/Test1/value/A", Name: "Test1"}, - }, - Dissem: []string{"email1@example.com", - "email2@example.com", - "email3@example.com"}, - }, - } - - testClaims := ClaimsObject{ - PublicKey: "test-public-key", - ClientPublicSigningKey: "test-client-public-signing-key", - SchemaVersion: "test-schema", - Entitlements: []Entitlement{ - { - EntityID: "email2@example.com", - EntityAttributes: []Attribute{ - {URI: "", Name: "Test1"}, - }, - }, - }, - } - - testDefinitions := []attrs.AttributeDefinition{ - { - Authority: "https://example.com", - Name: "Test1", - Rule: "allOf", - Order: []string{"A", "B", "C"}, - }, - } - - output, err := canAccess(c, entityID, testPolicy, testClaims, testDefinitions) - - if output != false { - t.Errorf("Expected false, but got %v", err) - } - - if err == nil { - t.Errorf("Expected error, but got %v", err) - } -} - -func TestAttrDissemFailure6(t *testing.T) { - var entityID string = "email2@example.com" - - testPolicy := Policy{ - UUID: uuid.New(), - Body: PolicyBody{ - DataAttributes: []Attribute{ - {URI: "https://example.com/attr/Test1/value/A", Name: "Test1"}, - }, - Dissem: []string{"email1@example.com", - "email2@example.com", - "email3@example.com"}, - }, - } - - testClaims := ClaimsObject{ - PublicKey: "test-public-key", - ClientPublicSigningKey: "test-client-public-signing-key", - SchemaVersion: "test-schema", - Entitlements: []Entitlement{ - { - EntityID: "email2@example.com", - EntityAttributes: []Attribute{ - {URI: "https://example.com/attr/Test1/value/A", Name: "Test1"}, - }, - }, - }, - } - - testDefinitions := []attrs.AttributeDefinition{} - - output, err := canAccess(c, entityID, testPolicy, testClaims, testDefinitions) - - if output != false { - t.Errorf("Expected false, but got %v", err) - } - - if err == nil { - t.Errorf("Expected error, but got %v", err) - } -} diff --git a/services/kas/access/fetchAttributes.go b/services/kas/access/fetchAttributes.go deleted file mode 100644 index 9483b76bac..0000000000 --- a/services/kas/access/fetchAttributes.go +++ /dev/null @@ -1,89 +0,0 @@ -package access - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "io" - "log/slog" - "net/http" - "net/url" - - "github.com/virtru/access-pdp/attributes" -) - -const ( - ErrAttributeDefinitionsUnmarshal = Error("attribute definitions unmarshal") - ErrAttributeDefinitionsServiceCall = Error("attribute definitions service call unexpected") -) - -func ResolveAttributeAuthority(s string) (*url.URL, error) { - u, err := url.Parse(s) - if err != nil { - slog.Error("invalid attribute authority", "err", err) - return nil, errors.Join(ErrConfig, err) - } - if u.Host == "" || (u.Scheme != "http" && u.Scheme != "https") { - slog.Error("invalid attribute authority", "url", u) - return nil, ErrConfig - } - r, err := u.Parse("v1/attrName") - if err != nil { - panic(err) - } - return r, nil -} - -func (p *Provider) fetchAttributes(ctx context.Context, namespaces []string) ([]attributes.AttributeDefinition, error) { - var definitions []attributes.AttributeDefinition - for _, ns := range namespaces { - attrDefs, err := p.fetchAttributesForNamespace(ctx, ns) - if err != nil { - slog.ErrorContext(ctx, "unable to fetch attributes for namespace", "err", err, "namespace", ns) - return nil, err - } - definitions = append(definitions, attrDefs...) - } - return definitions, nil -} - -func (p *Provider) fetchAttributesForNamespace(ctx context.Context, namespace string) ([]attributes.AttributeDefinition, error) { - slog.DebugContext(ctx, "Fetching", "namespace", namespace) - req, err := http.NewRequestWithContext(ctx, http.MethodGet, p.AttributeSvc.String(), nil) - if err != nil { - slog.ErrorContext(ctx, "unable to create http request to attributes service", "namespace", namespace, "attributeHost", p.AttributeSvc) - return nil, errors.Join(ErrAttributeDefinitionsServiceCall, err) - } - - req.Header.Set("Content-Type", "application/json") - - q := req.URL.Query() - q.Add("authority", namespace) - req.URL.RawQuery = q.Encode() - var httpClient http.Client - resp, err := httpClient.Do(req) - if err != nil { - slog.ErrorContext(ctx, "failed http request to attributes service", "err", err, "namespace", namespace, "req.URL", req.URL) - return nil, errors.Join(ErrAttributeDefinitionsServiceCall, err) - } - defer func(Body io.ReadCloser) { - err := Body.Close() - if err != nil { - slog.ErrorContext(ctx, "failed to close http request to attributes service", "err", err, "namespace", namespace, "req.URL", req.URL) - } - }(resp.Body) - if resp.StatusCode != http.StatusOK { - err := fmt.Errorf("status code %v %v", resp.StatusCode, http.StatusText(resp.StatusCode)) - return nil, errors.Join(ErrAttributeDefinitionsServiceCall, err) - } - - var definitions []attributes.AttributeDefinition - err = json.NewDecoder(resp.Body).Decode(&definitions) - if err != nil { - slog.ErrorContext(ctx, "failed to parse response from attributes service", "err", err, "namespace", namespace, "req.URL", req.URL) - return nil, errors.Join(ErrAttributeDefinitionsUnmarshal, err) - } - - return definitions, nil -} diff --git a/services/kas/access/fetchAttributes_test.go b/services/kas/access/fetchAttributes_test.go deleted file mode 100644 index d491f3511c..0000000000 --- a/services/kas/access/fetchAttributes_test.go +++ /dev/null @@ -1,195 +0,0 @@ -package access - -import ( - "context" - "net/http" - "testing" - - "github.com/jarcoal/httpmock" - "github.com/virtru/access-pdp/attributes" -) - -type WrongAttributeDefinition struct { - Wrong string `json:"wrong"` - Type string `json:"type"` - Of string `json:"of"` - Response string `json:"response"` -} - -func mockAttrProvider() *Provider { - u, err := ResolveAttributeAuthority("http://localhost:65432/api/attributes/") - if err != nil { - panic(err) - } - return &Provider{ - AttributeSvc: u, - } -} - -func TestResolveAttributeService(t *testing.T) { - p, err := ResolveAttributeAuthority("") - if p != nil || err == nil { - t.Errorf("empty ATTR_AUTHORITY_HOST should fail p=[%s], err=[%s]", p, err) - } - - p, err = ResolveAttributeAuthority("http://localhost") - if p.String() != "http://localhost/v1/attrName" || err != nil { - t.Errorf("simple ATTR_AUTHORITY_HOST should not fail p=[%s], err=[%s]", p, err) - } - - p, err = ResolveAttributeAuthority("http://localhost/api/attributes/") - if p.String() != "http://localhost/api/attributes/v1/attrName" || err != nil { - t.Errorf("ATTR_AUTHORITY_HOST with path should not fail p=[%s], err=[%s]", p, err) - } -} - -func TestFetchAttributesSuccess(t *testing.T) { - httpmock.Activate() - defer httpmock.DeactivateAndReset() - - ctx := context.Background() - namespaces := []string{"namespace1", "namespace2"} - - mockDefinitions := []attributes.AttributeDefinition{ - { - Authority: "namespace1", - Name: "attribute1", - Rule: "rule1", - State: "active", - Order: []string{"value1", "value2", "value3"}, - }, - { - Authority: "namespace2", - Name: "attribute2", - Rule: "rule2", - Order: []string{"valueA", "valueB", "valueC"}, - }, - } - - httpmock.RegisterResponder(http.MethodGet, "http://localhost:65432/api/attributes/v1/attrName", - func(req *http.Request) (*http.Response, error) { - authority := req.URL.Query().Get("authority") - - if authority == "namespace1" { - resp, err := httpmock.NewJsonResponse(200, mockDefinitions[:1]) - return resp, err - } - - // namespace2 - resp, err := httpmock.NewJsonResponse(200, mockDefinitions[1:]) - return resp, err - }, - ) - - p := mockAttrProvider() - output, err := p.fetchAttributes(ctx, namespaces) - - if err != nil { - t.Error(err) - } - - if len(output) != len(mockDefinitions) { - t.Errorf("Output %v not equal to expected %v", output, mockDefinitions) - } -} - -func TestFetchAttributesFailure(t *testing.T) { - httpmock.Activate() - defer httpmock.DeactivateAndReset() - - ctx := context.Background() - namespaces := []string{"namespace1", "namespace2"} - - mockWrongResponse := WrongAttributeDefinition{ - Wrong: "mock", - Type: "mock", - Of: "mock", - Response: "mock", - } - - httpmock.RegisterResponder(http.MethodGet, "http://localhost:65432/api/attributes/v1/attrName", - func(req *http.Request) (*http.Response, error) { - resp, err := httpmock.NewJsonResponse(200, mockWrongResponse) - return resp, err - }, - ) - - p := mockAttrProvider() - output, err := p.fetchAttributes(ctx, namespaces) - - t.Log(err) - t.Log(output) - - if len(output) != 0 { - t.Errorf("Output %v not equal to expected %v", len(output), 0) - } - - if err == nil { - t.Errorf("Error expected, but got %v", err) - } -} - -func TestFetchAttributesFailure1(t *testing.T) { - httpmock.Activate() - defer httpmock.DeactivateAndReset() - - ctx := context.Background() - namespaces := []string{"namespace1", "namespace2"} - - httpmock.RegisterResponder(http.MethodGet, "http://localhost:65432/api/attributes/v1/attrName", - func(req *http.Request) (*http.Response, error) { - return httpmock.NewStringResponse(500, ""), nil - }, - ) - - p := mockAttrProvider() - output, err := p.fetchAttributes(ctx, namespaces) - - if err == nil { - t.Error("Should throw an error") - } - - if len(output) != 0 { - t.Errorf("Output %v not equal to expected %v", len(output), 0) - } -} - -func TestFetchAttributesFailure2(t *testing.T) { - httpmock.Activate() - defer httpmock.DeactivateAndReset() - - ctx := context.Background() - namespaces := []string{"namespace1", "namespace2"} - - httpmock.RegisterResponder(http.MethodGet, "http://localhost:65432/api/attributes/v1/attrName", - func(req *http.Request) (*http.Response, error) { - return nil, Error("Mock http client error") - }, - ) - - p := mockAttrProvider() - output, err := p.fetchAttributes(ctx, namespaces) - - if err == nil { - t.Error("Should throw an error") - } - - if len(output) != 0 { - t.Errorf("Output %v not equal to expected %v", len(output), 0) - } -} - -func TestFetchAttributesForNamespaceFailure(t *testing.T) { - namespaces := []string{"namespace1", "namespace2"} - - p := mockAttrProvider() - output, err := p.fetchAttributes(context.Background(), namespaces) - - if err == nil { - t.Error("Should throw an error") - } - - if len(output) != 0 { - t.Errorf("Output %v not equal to expected %v", len(output), 0) - } -} diff --git a/services/kas/access/policy.go b/services/kas/access/policy.go index 1c08f991b1..3d50bc650f 100644 --- a/services/kas/access/policy.go +++ b/services/kas/access/policy.go @@ -1,14 +1,7 @@ package access import ( - "errors" - "github.com/google/uuid" - attrs "github.com/virtru/access-pdp/attributes" -) - -const ( - ErrPolicyDataAttributeParse = Error("policy data attribute invalid") ) type Policy struct { @@ -20,26 +13,3 @@ type PolicyBody struct { DataAttributes []Attribute `json:"dataAttributes"` Dissem []string `json:"dissem"` } - -func getNamespacesFromAttributes(body PolicyBody) ([]string, error) { - // extract the namespace from an attribute uri - var dataAttributes = body.DataAttributes - namespaces := make(map[string]bool) - for _, attr := range dataAttributes { - instance, err := attrs.ParseInstanceFromURI(attr.URI) - if err != nil { - return nil, errors.Join(ErrPolicyDataAttributeParse, err) - } - namespaces[instance.Authority] = true - } - - // get unique - keys := make([]string, len(namespaces)) - index := 0 - for key := range namespaces { - keys[index] = key - index++ - } - - return keys, nil -} diff --git a/services/kas/access/policy_test.go b/services/kas/access/policy_test.go index 953294851a..ded273abee 100644 --- a/services/kas/access/policy_test.go +++ b/services/kas/access/policy_test.go @@ -1,46 +1,5 @@ package access -import ( - "testing" -) - -func TestGetNamespacesFromAttributesSuccess(t *testing.T) { - testBody := PolicyBody{ - DataAttributes: []Attribute{ - {URI: "https://example.com/attr/Test1/value/A", Name: "TestAttr1"}, - {URI: "https://example2.com/attr/Test2/value/B", Name: "TestAttr2"}, - {URI: "https://example.com/attr/Test3/value/C", Name: "TestAttr3"}, - }, - Dissem: []string{}, - } - expectedResult := []string{"https://example2.com", "https://example.com"} - output, err := getNamespacesFromAttributes(testBody) - if err != nil { - t.Error(err) - } - if !sameStringSlice(output, expectedResult) { - t.Errorf("Output %q not equal to expected %q", output, expectedResult) - } -} - -func TestGetNamespacesFromAttributesFailure(t *testing.T) { - testBody := PolicyBody{ - DataAttributes: []Attribute{ - {URI: "", Name: "TestAttr1"}, - }, - Dissem: []string{}, - } - output, err := getNamespacesFromAttributes(testBody) - - if len(output) != 0 { - t.Errorf("Output %v not equal to expected %v", len(output), 0) - } - - if err == nil { - t.Errorf("Should throw an error, but got %v", err) - } -} - func sameStringSlice(x, y []string) bool { if len(x) != len(y) { return false diff --git a/services/kas/access/provider.go b/services/kas/access/provider.go index 09a2bcbbf9..279f523f17 100644 --- a/services/kas/access/provider.go +++ b/services/kas/access/provider.go @@ -1,6 +1,7 @@ package access import ( + otdf "github.com/opentdf/platform/sdk" "net/url" "github.com/opentdf/platform/internal/security" @@ -17,7 +18,7 @@ const ( type Provider struct { kaspb.AccessServiceServer URI url.URL `json:"uri"` - AttributeSvc *url.URL + SDK *otdf.SDK Session security.HSMSession OIDCVerifier *oidc.IDTokenVerifier } diff --git a/services/kas/access/rewrap.go b/services/kas/access/rewrap.go index 4cedfd22dc..d72095a55a 100644 --- a/services/kas/access/rewrap.go +++ b/services/kas/access/rewrap.go @@ -19,6 +19,7 @@ import ( "encoding/pem" "errors" "fmt" + "github.com/opentdf/platform/protocol/go/authorization" "io" "log/slog" "strings" @@ -52,8 +53,8 @@ type customClaimsBody struct { } type customClaimsHeader struct { - EntityID string `json:"sub"` - ClientID string `json:"clientId"` + Subject string `json:"sub"` + ClientID string `json:"client_id"` TDFClaims ClaimsObject `json:"tdf_claims"` } @@ -127,6 +128,7 @@ type verifiedRequest struct { publicKey crypto.PublicKey requestBody *RequestBody cl *customClaimsHeader + bearerToken string } func (p *Provider) verifyBearerAndParseRequestBody(ctx context.Context, in *kaspb.RewrapRequest) (*verifiedRequest, error) { @@ -195,9 +197,9 @@ func (p *Provider) verifyBearerAndParseRequestBody(ctx context.Context, in *kasp } switch clientPublicKey.(type) { case *rsa.PublicKey: - return &verifiedRequest{clientPublicKey, &requestBody, &cl}, nil + return &verifiedRequest{clientPublicKey, &requestBody, &cl, in.Bearer}, nil case *ecdsa.PublicKey: - return &verifiedRequest{clientPublicKey, &requestBody, &cl}, nil + return &verifiedRequest{clientPublicKey, &requestBody, &cl, in.Bearer}, nil } slog.WarnContext(ctx, fmt.Sprintf("clientPublicKey not a supported key, was [%T]", clientPublicKey)) return nil, err400("clientPublicKey unsupported type") @@ -281,21 +283,20 @@ func (p *Provider) tdf3Rewrap(ctx context.Context, body *verifiedRequest) (*kasp } slog.DebugContext(ctx, "extracting policy", "requestBody.policy", body.requestBody.Policy) - namespaces, err := getNamespacesFromAttributes(policy.Body) - if err != nil { - slog.WarnContext(ctx, "Could not get namespaces from policy!", "err", err) - return nil, err403("forbidden") - } - - slog.DebugContext(ctx, "Fetching attributes", "policy.namespaces", namespaces, "policy.body", policy.Body) - definitions, err := p.fetchAttributes(ctx, namespaces) - if err != nil { - slog.ErrorContext(ctx, "Could not fetch attribute definitions from attributes service!", "err", err) - return nil, err503("attribute server request failure") + // changed to ClientID from Subject + ent := authorization.Entity{ + EntityType: &authorization.Entity_Jwt{ + Jwt: body.bearerToken, + }, + } + if body.cl.ClientID != "" { + ent = authorization.Entity{ + EntityType: &authorization.Entity_ClientId{ + ClientId: body.cl.ClientID, + }, + } } - slog.DebugContext(ctx, "fetch attributes", "definitions", definitions) - - access, err := canAccess(ctx, body.cl.EntityID, *policy, body.cl.TDFClaims, definitions) + access, err := canAccess(ctx, ent, *policy, p.SDK) if err != nil { slog.WarnContext(ctx, "Could not perform access decision!", "err", err) diff --git a/services/kas/access/rewrap_test.go b/services/kas/access/rewrap_test.go index 5effedd74f..64ae675a8f 100644 --- a/services/kas/access/rewrap_test.go +++ b/services/kas/access/rewrap_test.go @@ -295,7 +295,7 @@ func signedMockJWT(signer *rsa.PrivateKey) string { panic(err) } cl := customClaimsHeader{ - EntityID: "testuser1", + Subject: "testuser1", ClientID: "testonly", TDFClaims: standardClaims(), } @@ -323,7 +323,7 @@ func jwtWrongIssuer() string { panic(err) } cl := customClaimsHeader{ - EntityID: "testuser1", + Subject: "testuser1", ClientID: "testonly", TDFClaims: standardClaims(), } diff --git a/services/kas/kas.go b/services/kas/kas.go index c23da77040..27ff89cb28 100644 --- a/services/kas/kas.go +++ b/services/kas/kas.go @@ -62,6 +62,7 @@ func NewRegistration() serviceregistry.Registration { slog.Error("hsm not enabled") panic(fmt.Errorf("hsm not enabled")) } + // FIXME msg="mismatched key access url" keyAccessURL=http://localhost:9000 kasURL=https://:9000 kasURLString := "https://" + srp.OTDF.HTTPServer.Addr kasURI, err := url.Parse(kasURLString) if err != nil { @@ -70,7 +71,7 @@ func NewRegistration() serviceregistry.Registration { p := access.Provider{ URI: *kasURI, - AttributeSvc: nil, + SDK: srp.SDK, Session: *hsm, OIDCVerifier: loadIdentityProvider(), }