github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/internal/services/v1/debug.go (about) 1 package v1 2 3 import ( 4 "cmp" 5 "context" 6 "fmt" 7 "slices" 8 "strings" 9 10 v1 "github.com/authzed/authzed-go/proto/authzed/api/v1" 11 12 cexpr "github.com/authzed/spicedb/internal/caveats" 13 "github.com/authzed/spicedb/pkg/datastore" 14 dispatch "github.com/authzed/spicedb/pkg/proto/dispatch/v1" 15 "github.com/authzed/spicedb/pkg/schemadsl/compiler" 16 "github.com/authzed/spicedb/pkg/schemadsl/generator" 17 "github.com/authzed/spicedb/pkg/tuple" 18 ) 19 20 // ConvertCheckDispatchDebugInformation converts dispatch debug information found in the response metadata 21 // into DebugInformation returnable to the API. 22 func ConvertCheckDispatchDebugInformation( 23 ctx context.Context, 24 caveatContext map[string]any, 25 metadata *dispatch.ResponseMeta, 26 reader datastore.Reader, 27 ) (*v1.DebugInformation, error) { 28 debugInfo := metadata.DebugInfo 29 if debugInfo == nil { 30 return nil, nil 31 } 32 33 caveats, err := reader.ListAllCaveats(ctx) 34 if err != nil { 35 return nil, err 36 } 37 38 namespaces, err := reader.ListAllNamespaces(ctx) 39 if err != nil { 40 return nil, err 41 } 42 43 defs := make([]compiler.SchemaDefinition, 0, len(namespaces)+len(caveats)) 44 for _, caveat := range caveats { 45 defs = append(defs, caveat.Definition) 46 } 47 for _, ns := range namespaces { 48 defs = append(defs, ns.Definition) 49 } 50 51 schema, _, err := generator.GenerateSchema(defs) 52 if err != nil { 53 return nil, err 54 } 55 56 converted, err := convertCheckTrace(ctx, caveatContext, debugInfo.Check, reader) 57 if err != nil { 58 return nil, err 59 } 60 61 return &v1.DebugInformation{ 62 Check: converted, 63 SchemaUsed: strings.TrimSpace(schema), 64 }, nil 65 } 66 67 func convertCheckTrace(ctx context.Context, caveatContext map[string]any, ct *dispatch.CheckDebugTrace, reader datastore.Reader) (*v1.CheckDebugTrace, error) { 68 permissionType := v1.CheckDebugTrace_PERMISSION_TYPE_UNSPECIFIED 69 if ct.ResourceRelationType == dispatch.CheckDebugTrace_PERMISSION { 70 permissionType = v1.CheckDebugTrace_PERMISSION_TYPE_PERMISSION 71 } else if ct.ResourceRelationType == dispatch.CheckDebugTrace_RELATION { 72 permissionType = v1.CheckDebugTrace_PERMISSION_TYPE_RELATION 73 } 74 75 subRelation := ct.Request.Subject.Relation 76 if subRelation == tuple.Ellipsis { 77 subRelation = "" 78 } 79 80 permissionship := v1.CheckDebugTrace_PERMISSIONSHIP_NO_PERMISSION 81 var partialResults []*dispatch.ResourceCheckResult 82 for _, checkResult := range ct.Results { 83 if checkResult.Membership == dispatch.ResourceCheckResult_MEMBER { 84 permissionship = v1.CheckDebugTrace_PERMISSIONSHIP_HAS_PERMISSION 85 break 86 } 87 88 if checkResult.Membership == dispatch.ResourceCheckResult_CAVEATED_MEMBER && permissionship != v1.CheckDebugTrace_PERMISSIONSHIP_HAS_PERMISSION { 89 permissionship = v1.CheckDebugTrace_PERMISSIONSHIP_CONDITIONAL_PERMISSION 90 partialResults = append(partialResults, checkResult) 91 } 92 } 93 94 var caveatEvalInfo *v1.CaveatEvalInfo 95 if permissionship == v1.CheckDebugTrace_PERMISSIONSHIP_CONDITIONAL_PERMISSION && len(partialResults) == 1 { 96 partialCheckResult := partialResults[0] 97 computedResult, err := cexpr.RunCaveatExpression(ctx, partialCheckResult.Expression, caveatContext, reader, cexpr.RunCaveatExpressionWithDebugInformation) 98 if err != nil { 99 return nil, err 100 } 101 102 var partialCaveatInfo *v1.PartialCaveatInfo 103 caveatResult := v1.CaveatEvalInfo_RESULT_FALSE 104 if computedResult.Value() { 105 caveatResult = v1.CaveatEvalInfo_RESULT_TRUE 106 } else if computedResult.IsPartial() { 107 caveatResult = v1.CaveatEvalInfo_RESULT_MISSING_SOME_CONTEXT 108 missingNames, _ := computedResult.MissingVarNames() 109 partialCaveatInfo = &v1.PartialCaveatInfo{ 110 MissingRequiredContext: missingNames, 111 } 112 } 113 114 contextStruct, err := computedResult.ContextStruct() 115 if err != nil { 116 return nil, fmt.Errorf("could not serialize context: %w. please report this error", err) 117 } 118 119 exprString, err := computedResult.ExpressionString() 120 if err != nil { 121 return nil, err 122 } 123 124 caveatName := "" 125 if partialCheckResult.Expression.GetCaveat() != nil { 126 caveatName = partialCheckResult.Expression.GetCaveat().CaveatName 127 } 128 129 caveatEvalInfo = &v1.CaveatEvalInfo{ 130 Expression: exprString, 131 Result: caveatResult, 132 Context: contextStruct, 133 PartialCaveatInfo: partialCaveatInfo, 134 CaveatName: caveatName, 135 } 136 } 137 138 if len(ct.SubProblems) > 0 { 139 subProblems := make([]*v1.CheckDebugTrace, 0, len(ct.SubProblems)) 140 for _, subProblem := range ct.SubProblems { 141 converted, err := convertCheckTrace(ctx, caveatContext, subProblem, reader) 142 if err != nil { 143 return nil, err 144 } 145 146 subProblems = append(subProblems, converted) 147 } 148 149 slices.SortFunc(subProblems, func(a, b *v1.CheckDebugTrace) int { 150 return cmp.Compare(tuple.StringObjectRef(a.Resource), tuple.StringObjectRef(a.Resource)) 151 }) 152 153 return &v1.CheckDebugTrace{ 154 Resource: &v1.ObjectReference{ 155 ObjectType: ct.Request.ResourceRelation.Namespace, 156 ObjectId: strings.Join(ct.Request.ResourceIds, ","), 157 }, 158 Permission: ct.Request.ResourceRelation.Relation, 159 PermissionType: permissionType, 160 Subject: &v1.SubjectReference{ 161 Object: &v1.ObjectReference{ 162 ObjectType: ct.Request.Subject.Namespace, 163 ObjectId: ct.Request.Subject.ObjectId, 164 }, 165 OptionalRelation: subRelation, 166 }, 167 CaveatEvaluationInfo: caveatEvalInfo, 168 Result: permissionship, 169 Resolution: &v1.CheckDebugTrace_SubProblems_{ 170 SubProblems: &v1.CheckDebugTrace_SubProblems{ 171 Traces: subProblems, 172 }, 173 }, 174 Duration: ct.Duration, 175 }, nil 176 } 177 178 return &v1.CheckDebugTrace{ 179 Resource: &v1.ObjectReference{ 180 ObjectType: ct.Request.ResourceRelation.Namespace, 181 ObjectId: strings.Join(ct.Request.ResourceIds, ","), 182 }, 183 Permission: ct.Request.ResourceRelation.Relation, 184 PermissionType: permissionType, 185 Subject: &v1.SubjectReference{ 186 Object: &v1.ObjectReference{ 187 ObjectType: ct.Request.Subject.Namespace, 188 ObjectId: ct.Request.Subject.ObjectId, 189 }, 190 OptionalRelation: subRelation, 191 }, 192 CaveatEvaluationInfo: caveatEvalInfo, 193 Result: permissionship, 194 Resolution: &v1.CheckDebugTrace_WasCachedResult{ 195 WasCachedResult: ct.IsCachedResult, 196 }, 197 Duration: ct.Duration, 198 }, nil 199 }