github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/internal/services/integrationtesting/consistencytestutil/servicetester.go (about) 1 package consistencytestutil 2 3 import ( 4 "context" 5 "errors" 6 "io" 7 8 v1 "github.com/authzed/authzed-go/proto/authzed/api/v1" 9 "google.golang.org/grpc" 10 "google.golang.org/protobuf/types/known/structpb" 11 12 v1svc "github.com/authzed/spicedb/internal/services/v1" 13 "github.com/authzed/spicedb/pkg/datastore" 14 core "github.com/authzed/spicedb/pkg/proto/core/v1" 15 "github.com/authzed/spicedb/pkg/tuple" 16 "github.com/authzed/spicedb/pkg/zedtoken" 17 ) 18 19 func ServiceTesters(conn *grpc.ClientConn) []ServiceTester { 20 return []ServiceTester{ 21 v1ServiceTester{v1.NewPermissionsServiceClient(conn), v1.NewExperimentalServiceClient(conn)}, 22 } 23 } 24 25 type ServiceTester interface { 26 Name() string 27 Check(ctx context.Context, resource *core.ObjectAndRelation, subject *core.ObjectAndRelation, atRevision datastore.Revision, caveatContext map[string]any) (v1.CheckPermissionResponse_Permissionship, error) 28 Expand(ctx context.Context, resource *core.ObjectAndRelation, atRevision datastore.Revision) (*core.RelationTupleTreeNode, error) 29 Write(ctx context.Context, relationship *core.RelationTuple) error 30 Read(ctx context.Context, namespaceName string, atRevision datastore.Revision) ([]*core.RelationTuple, error) 31 LookupResources(ctx context.Context, resourceRelation *core.RelationReference, subject *core.ObjectAndRelation, atRevision datastore.Revision, cursor *v1.Cursor, limit uint32) ([]*v1.LookupResourcesResponse, *v1.Cursor, error) 32 LookupSubjects(ctx context.Context, resource *core.ObjectAndRelation, subjectRelation *core.RelationReference, atRevision datastore.Revision, caveatContext map[string]any, cursor *v1.Cursor, limit uint32) (map[string]*v1.LookupSubjectsResponse, *v1.Cursor, error) 33 // NOTE: ExperimentalService/BulkCheckPermission has been promoted to PermissionsService/CheckBulkPermissions 34 BulkCheck(ctx context.Context, items []*v1.BulkCheckPermissionRequestItem, atRevision datastore.Revision) ([]*v1.BulkCheckPermissionPair, error) 35 CheckBulk(ctx context.Context, items []*v1.CheckBulkPermissionsRequestItem, atRevision datastore.Revision) ([]*v1.CheckBulkPermissionsPair, error) 36 } 37 38 func optionalizeRelation(relation string) string { 39 if relation == datastore.Ellipsis { 40 return "" 41 } 42 43 return relation 44 } 45 46 // v1ServiceTester tests the V1 API. 47 type v1ServiceTester struct { 48 permClient v1.PermissionsServiceClient 49 expClient v1.ExperimentalServiceClient 50 } 51 52 func (v1st v1ServiceTester) Name() string { 53 return "v1" 54 } 55 56 func (v1st v1ServiceTester) Check(ctx context.Context, resource *core.ObjectAndRelation, subject *core.ObjectAndRelation, atRevision datastore.Revision, caveatContext map[string]any) (v1.CheckPermissionResponse_Permissionship, error) { 57 var context *structpb.Struct 58 if caveatContext != nil { 59 built, err := structpb.NewStruct(caveatContext) 60 if err != nil { 61 return v1.CheckPermissionResponse_PERMISSIONSHIP_UNSPECIFIED, err 62 } 63 context = built 64 } 65 66 checkResp, err := v1st.permClient.CheckPermission(ctx, &v1.CheckPermissionRequest{ 67 Resource: &v1.ObjectReference{ 68 ObjectType: resource.Namespace, 69 ObjectId: resource.ObjectId, 70 }, 71 Permission: resource.Relation, 72 Subject: &v1.SubjectReference{ 73 Object: &v1.ObjectReference{ 74 ObjectType: subject.Namespace, 75 ObjectId: subject.ObjectId, 76 }, 77 OptionalRelation: optionalizeRelation(subject.Relation), 78 }, 79 Consistency: &v1.Consistency{ 80 Requirement: &v1.Consistency_AtLeastAsFresh{ 81 AtLeastAsFresh: zedtoken.MustNewFromRevision(atRevision), 82 }, 83 }, 84 Context: context, 85 }) 86 if err != nil { 87 return v1.CheckPermissionResponse_PERMISSIONSHIP_UNSPECIFIED, err 88 } 89 return checkResp.Permissionship, nil 90 } 91 92 func (v1st v1ServiceTester) Expand(ctx context.Context, resource *core.ObjectAndRelation, atRevision datastore.Revision) (*core.RelationTupleTreeNode, error) { 93 expandResp, err := v1st.permClient.ExpandPermissionTree(ctx, &v1.ExpandPermissionTreeRequest{ 94 Resource: &v1.ObjectReference{ 95 ObjectType: resource.Namespace, 96 ObjectId: resource.ObjectId, 97 }, 98 Permission: resource.Relation, 99 Consistency: &v1.Consistency{ 100 Requirement: &v1.Consistency_AtLeastAsFresh{ 101 AtLeastAsFresh: zedtoken.MustNewFromRevision(atRevision), 102 }, 103 }, 104 }) 105 if err != nil { 106 return nil, err 107 } 108 return v1svc.TranslateRelationshipTree(expandResp.TreeRoot), nil 109 } 110 111 func (v1st v1ServiceTester) Write(ctx context.Context, relationship *core.RelationTuple) error { 112 _, err := v1st.permClient.WriteRelationships(ctx, &v1.WriteRelationshipsRequest{ 113 OptionalPreconditions: []*v1.Precondition{ 114 { 115 Operation: v1.Precondition_OPERATION_MUST_MATCH, 116 Filter: tuple.MustToFilter(relationship), 117 }, 118 }, 119 Updates: []*v1.RelationshipUpdate{tuple.UpdateToRelationshipUpdate(tuple.Touch(relationship))}, 120 }) 121 return err 122 } 123 124 func (v1st v1ServiceTester) Read(_ context.Context, namespaceName string, atRevision datastore.Revision) ([]*core.RelationTuple, error) { 125 readResp, err := v1st.permClient.ReadRelationships(context.Background(), &v1.ReadRelationshipsRequest{ 126 RelationshipFilter: &v1.RelationshipFilter{ 127 ResourceType: namespaceName, 128 }, 129 Consistency: &v1.Consistency{ 130 Requirement: &v1.Consistency_AtLeastAsFresh{ 131 AtLeastAsFresh: zedtoken.MustNewFromRevision(atRevision), 132 }, 133 }, 134 }) 135 if err != nil { 136 return nil, err 137 } 138 139 var tuples []*core.RelationTuple 140 for { 141 resp, err := readResp.Recv() 142 if errors.Is(err, io.EOF) { 143 break 144 } 145 146 if err != nil { 147 return nil, err 148 } 149 150 tuples = append(tuples, tuple.MustFromRelationship[*v1.ObjectReference, *v1.SubjectReference, *v1.ContextualizedCaveat](resp.Relationship)) 151 } 152 153 return tuples, nil 154 } 155 156 func (v1st v1ServiceTester) LookupResources(_ context.Context, resourceRelation *core.RelationReference, subject *core.ObjectAndRelation, atRevision datastore.Revision, cursor *v1.Cursor, limit uint32) ([]*v1.LookupResourcesResponse, *v1.Cursor, error) { 157 lookupResp, err := v1st.permClient.LookupResources(context.Background(), &v1.LookupResourcesRequest{ 158 ResourceObjectType: resourceRelation.Namespace, 159 Permission: resourceRelation.Relation, 160 Subject: &v1.SubjectReference{ 161 Object: &v1.ObjectReference{ 162 ObjectType: subject.Namespace, 163 ObjectId: subject.ObjectId, 164 }, 165 OptionalRelation: optionalizeRelation(subject.Relation), 166 }, 167 Consistency: &v1.Consistency{ 168 Requirement: &v1.Consistency_AtLeastAsFresh{ 169 AtLeastAsFresh: zedtoken.MustNewFromRevision(atRevision), 170 }, 171 }, 172 OptionalLimit: limit, 173 OptionalCursor: cursor, 174 }) 175 if err != nil { 176 return nil, nil, err 177 } 178 179 var lastCursor *v1.Cursor 180 found := []*v1.LookupResourcesResponse{} 181 for { 182 resp, err := lookupResp.Recv() 183 if errors.Is(err, io.EOF) { 184 break 185 } 186 187 if err != nil { 188 return nil, nil, err 189 } 190 191 found = append(found, resp) 192 lastCursor = resp.AfterResultCursor 193 } 194 return found, lastCursor, nil 195 } 196 197 func (v1st v1ServiceTester) LookupSubjects(_ context.Context, resource *core.ObjectAndRelation, subjectRelation *core.RelationReference, atRevision datastore.Revision, caveatContext map[string]any, cursor *v1.Cursor, limit uint32) (map[string]*v1.LookupSubjectsResponse, *v1.Cursor, error) { 198 var builtContext *structpb.Struct 199 if caveatContext != nil { 200 built, err := structpb.NewStruct(caveatContext) 201 if err != nil { 202 return nil, nil, err 203 } 204 builtContext = built 205 } 206 207 lookupResp, err := v1st.permClient.LookupSubjects(context.Background(), &v1.LookupSubjectsRequest{ 208 Resource: &v1.ObjectReference{ 209 ObjectType: resource.Namespace, 210 ObjectId: resource.ObjectId, 211 }, 212 Permission: resource.Relation, 213 SubjectObjectType: subjectRelation.Namespace, 214 OptionalSubjectRelation: optionalizeRelation(subjectRelation.Relation), 215 Consistency: &v1.Consistency{ 216 Requirement: &v1.Consistency_AtLeastAsFresh{ 217 AtLeastAsFresh: zedtoken.MustNewFromRevision(atRevision), 218 }, 219 }, 220 Context: builtContext, 221 OptionalCursor: cursor, 222 OptionalConcreteLimit: limit, 223 }) 224 if err != nil { 225 return nil, nil, err 226 } 227 228 found := map[string]*v1.LookupSubjectsResponse{} 229 var lastCursor *v1.Cursor 230 for { 231 resp, err := lookupResp.Recv() 232 if errors.Is(err, io.EOF) { 233 break 234 } 235 236 if err != nil { 237 return nil, nil, err 238 } 239 240 found[resp.Subject.SubjectObjectId] = resp 241 lastCursor = resp.AfterResultCursor 242 } 243 return found, lastCursor, nil 244 } 245 246 func (v1st v1ServiceTester) BulkCheck(ctx context.Context, items []*v1.BulkCheckPermissionRequestItem, atRevision datastore.Revision) ([]*v1.BulkCheckPermissionPair, error) { 247 result, err := v1st.expClient.BulkCheckPermission(ctx, &v1.BulkCheckPermissionRequest{ 248 Items: items, 249 Consistency: &v1.Consistency{ 250 Requirement: &v1.Consistency_AtLeastAsFresh{ 251 AtLeastAsFresh: zedtoken.MustNewFromRevision(atRevision), 252 }, 253 }, 254 }) 255 if err != nil { 256 return nil, err 257 } 258 259 return result.Pairs, nil 260 } 261 262 func (v1st v1ServiceTester) CheckBulk(ctx context.Context, items []*v1.CheckBulkPermissionsRequestItem, atRevision datastore.Revision) ([]*v1.CheckBulkPermissionsPair, error) { 263 result, err := v1st.permClient.CheckBulkPermissions(ctx, &v1.CheckBulkPermissionsRequest{ 264 Items: items, 265 Consistency: &v1.Consistency{ 266 Requirement: &v1.Consistency_AtLeastAsFresh{ 267 AtLeastAsFresh: zedtoken.MustNewFromRevision(atRevision), 268 }, 269 }, 270 }) 271 if err != nil { 272 return nil, err 273 } 274 275 return result.Pairs, nil 276 }