diff --git a/internal/services/steelthreadtesting/definitions.go b/internal/services/steelthreadtesting/definitions.go index 819ab34d4f..6e1819b9e0 100644 --- a/internal/services/steelthreadtesting/definitions.go +++ b/internal/services/steelthreadtesting/definitions.go @@ -386,4 +386,55 @@ var steelThreadTestCases = []steelThreadTestCase{ }, }, }, + { + name: "basic bulk checks", + datafile: "document-with-a-few-relationships.yaml", + operations: []steelThreadOperationCase{ + { + name: "basic bulk checks", + operationName: "bulkCheckPermissions", + arguments: map[string]any{ + "check_requests": []string{ + "document:doc-1#view@user:user-0", + "document:doc-1#view@user:user-1", + "document:doc-1#view@user:user-2", + "document:doc-2#view@user:user-0", + "document:doc-2#view@user:user-1", + "document:doc-2#view@user:user-2", + "document:doc-3#view@user:user-0", + "document:doc-3#view@user:user-1", + "document:doc-3#view@user:user-2", + }, + }, + }, + }, + }, + { + name: "bulk checks with traits", + datafile: "document-with-traits.yaml", + operations: []steelThreadOperationCase{ + { + name: "bulk checks", + operationName: "bulkCheckPermissions", + arguments: map[string]any{ + "check_requests": []string{ + "document:firstdoc#view@user:tom", + "document:firstdoc#view@user:fred", + "document:seconddoc#view@user:tom", + "document:seconddoc#view@user:fred", + `document:seconddoc#view@user:tom[unused:{"somecondition": 41}]`, + `document:seconddoc#view@user:fred[unused:{"somecondition": 41}]`, + `document:seconddoc#view@user:tom[unused:{"somecondition": 42}]`, + `document:seconddoc#view@user:fred[unused:{"somecondition": 42}]`, + "document:thirddoc#view@user:tom", + "document:thirddoc#view@user:fred", + `document:thirddoc#view@user:tom[unused:{"somecondition": 41}]`, + `document:thirddoc#view@user:fred[unused:{"somecondition": 41}]`, + `document:thirddoc#view@user:tom[unused:{"somecondition": 42}]`, + `document:thirddoc#view@user:fred[unused:{"somecondition": 42}]`, + }, + }, + }, + }, + }, } diff --git a/internal/services/steelthreadtesting/operations.go b/internal/services/steelthreadtesting/operations.go index 5c1240a75c..575636fbba 100644 --- a/internal/services/steelthreadtesting/operations.go +++ b/internal/services/steelthreadtesting/operations.go @@ -328,11 +328,71 @@ func bulkImportExportRelationships(parameters map[string]any, client v1.Permissi return exportedRels, nil } +func bulkCheckPermissions(parameters map[string]any, client v1.PermissionsServiceClient) (any, error) { + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + defer cancel() + + itemStrings := parameters["check_requests"].([]string) + checkRequests := make([]*v1.CheckBulkPermissionsRequestItem, 0, len(itemStrings)) + for _, itemString := range itemStrings { + parsed, err := tuple.ParseV1Rel(itemString) + if err != nil { + return nil, err + } + + var context *structpb.Struct + if parsed.GetOptionalCaveat() != nil { + context = parsed.GetOptionalCaveat().Context + } + + checkRequests = append(checkRequests, &v1.CheckBulkPermissionsRequestItem{ + Resource: parsed.Resource, + Permission: parsed.Relation, + Subject: parsed.Subject, + Context: context, + }) + } + + resp, err := client.CheckBulkPermissions(ctx, &v1.CheckBulkPermissionsRequest{ + Items: checkRequests, + Consistency: &v1.Consistency{ + Requirement: &v1.Consistency_FullyConsistent{ + FullyConsistent: true, + }, + }, + }) + if err != nil { + return nil, err + } + + respItems := make([]yaml.Node, 0) + for index, pair := range resp.Pairs { + prefix := itemStrings[index] + " -> " + if pair.GetItem() != nil { + resultStr := pair.GetItem().Permissionship.String() + respItems = append(respItems, yaml.Node{ + Kind: yaml.ScalarNode, + Value: prefix + resultStr, + Style: yaml.SingleQuotedStyle, + }) + } else { + respItems = append(respItems, yaml.Node{ + Kind: yaml.ScalarNode, + Value: prefix + pair.GetError().Message, + Style: yaml.SingleQuotedStyle, + }) + } + } + + return respItems, nil +} + var operations = map[string]stOperation{ "lookupSubjects": lookupSubjects, "lookupResources": lookupResources, "cursoredLookupResources": cursoredLookupResources, "bulkImportExportRelationships": bulkImportExportRelationships, + "bulkCheckPermissions": bulkCheckPermissions, } func formatResolvedResource(resource *v1.LookupResourcesResponse) string { diff --git a/internal/services/steelthreadtesting/steelresults/basic-bulk-checks-basic-bulk-checks-results.yaml b/internal/services/steelthreadtesting/steelresults/basic-bulk-checks-basic-bulk-checks-results.yaml new file mode 100644 index 0000000000..63cf569b61 --- /dev/null +++ b/internal/services/steelthreadtesting/steelresults/basic-bulk-checks-basic-bulk-checks-results.yaml @@ -0,0 +1,10 @@ +--- +- 'document:doc-1#view@user:user-0 -> PERMISSIONSHIP_HAS_PERMISSION' +- 'document:doc-1#view@user:user-1 -> PERMISSIONSHIP_HAS_PERMISSION' +- 'document:doc-1#view@user:user-2 -> PERMISSIONSHIP_NO_PERMISSION' +- 'document:doc-2#view@user:user-0 -> PERMISSIONSHIP_HAS_PERMISSION' +- 'document:doc-2#view@user:user-1 -> PERMISSIONSHIP_NO_PERMISSION' +- 'document:doc-2#view@user:user-2 -> PERMISSIONSHIP_NO_PERMISSION' +- 'document:doc-3#view@user:user-0 -> PERMISSIONSHIP_NO_PERMISSION' +- 'document:doc-3#view@user:user-1 -> PERMISSIONSHIP_NO_PERMISSION' +- 'document:doc-3#view@user:user-2 -> PERMISSIONSHIP_NO_PERMISSION' diff --git a/internal/services/steelthreadtesting/steelresults/bulk-checks-with-traits-bulk-checks-results.yaml b/internal/services/steelthreadtesting/steelresults/bulk-checks-with-traits-bulk-checks-results.yaml new file mode 100644 index 0000000000..2d824753ab --- /dev/null +++ b/internal/services/steelthreadtesting/steelresults/bulk-checks-with-traits-bulk-checks-results.yaml @@ -0,0 +1,15 @@ +--- +- 'document:firstdoc#view@user:tom -> PERMISSIONSHIP_NO_PERMISSION' +- 'document:firstdoc#view@user:fred -> PERMISSIONSHIP_HAS_PERMISSION' +- 'document:seconddoc#view@user:tom -> PERMISSIONSHIP_CONDITIONAL_PERMISSION' +- 'document:seconddoc#view@user:fred -> PERMISSIONSHIP_CONDITIONAL_PERMISSION' +- 'document:seconddoc#view@user:tom[unused:{"somecondition": 41}] -> PERMISSIONSHIP_HAS_PERMISSION' +- 'document:seconddoc#view@user:fred[unused:{"somecondition": 41}] -> PERMISSIONSHIP_NO_PERMISSION' +- 'document:seconddoc#view@user:tom[unused:{"somecondition": 42}] -> PERMISSIONSHIP_NO_PERMISSION' +- 'document:seconddoc#view@user:fred[unused:{"somecondition": 42}] -> PERMISSIONSHIP_HAS_PERMISSION' +- 'document:thirddoc#view@user:tom -> PERMISSIONSHIP_NO_PERMISSION' +- 'document:thirddoc#view@user:fred -> PERMISSIONSHIP_CONDITIONAL_PERMISSION' +- 'document:thirddoc#view@user:tom[unused:{"somecondition": 41}] -> PERMISSIONSHIP_NO_PERMISSION' +- 'document:thirddoc#view@user:fred[unused:{"somecondition": 41}] -> PERMISSIONSHIP_NO_PERMISSION' +- 'document:thirddoc#view@user:tom[unused:{"somecondition": 42}] -> PERMISSIONSHIP_NO_PERMISSION' +- 'document:thirddoc#view@user:fred[unused:{"somecondition": 42}] -> PERMISSIONSHIP_HAS_PERMISSION' diff --git a/internal/services/steelthreadtesting/testdata/document-with-traits.yaml b/internal/services/steelthreadtesting/testdata/document-with-traits.yaml new file mode 100644 index 0000000000..7d67f3a6a4 --- /dev/null +++ b/internal/services/steelthreadtesting/testdata/document-with-traits.yaml @@ -0,0 +1,22 @@ +--- +schema: |+ + use expiration + + definition user {} + + caveat somecaveat(somesecret int, somecondition int) { + somecondition == somesecret + } + + definition document { + relation viewer: user with expiration | user with somecaveat | user with somecaveat and expiration + permission view = viewer + } + +relationships: | + document:firstdoc#viewer@user:tom[expiration:2022-01-02T12:23:34Z] + document:firstdoc#viewer@user:fred[expiration:2322-01-02T12:23:34Z] + document:seconddoc#viewer@user:tom[somecaveat:{"somesecret":41}] + document:seconddoc#viewer@user:fred[somecaveat:{"somesecret":42}] + document:thirddoc#viewer@user:tom[somecaveat:{"somesecret":41}][expiration:2022-01-02T12:23:34Z] + document:thirddoc#viewer@user:fred[somecaveat:{"somesecret":42}][expiration:2322-01-02T12:23:34Z]