Skip to content

Commit 62be65d

Browse files
authored
Merge pull request #1987 from josephschorr/cel-rewrite
Fix debug traces when caveats use the same param name
2 parents f4bbdf3 + 32ce9c5 commit 62be65d

File tree

13 files changed

+983
-228
lines changed

13 files changed

+983
-228
lines changed

internal/caveats/debug.go

+164
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
package caveats
2+
3+
import (
4+
"fmt"
5+
"maps"
6+
"strconv"
7+
"strings"
8+
9+
"google.golang.org/protobuf/types/known/structpb"
10+
11+
"github.com/authzed/spicedb/pkg/caveats"
12+
"github.com/authzed/spicedb/pkg/genutil/mapz"
13+
corev1 "github.com/authzed/spicedb/pkg/proto/core/v1"
14+
"github.com/authzed/spicedb/pkg/spiceerrors"
15+
)
16+
17+
// BuildDebugInformation returns a human-readable string representation of the given
18+
// ExpressionResult and a Struct representation of the context values used in the expression.
19+
func BuildDebugInformation(exprResult ExpressionResult) (string, *structpb.Struct, error) {
20+
// If a concrete result, return its information directly.
21+
if concrete, ok := exprResult.(*caveats.CaveatResult); ok {
22+
exprString, err := concrete.ParentCaveat().ExprString()
23+
if err != nil {
24+
return "", nil, err
25+
}
26+
27+
contextStruct, err := concrete.ContextStruct()
28+
if err != nil {
29+
return "", nil, err
30+
}
31+
32+
return exprString, contextStruct, nil
33+
}
34+
35+
// Collect parameters which are shared across expressions.
36+
syntheticResult, ok := exprResult.(syntheticResult)
37+
if !ok {
38+
return "", nil, spiceerrors.MustBugf("unknown ExpressionResult type: %T", exprResult)
39+
}
40+
41+
resultsByParam := mapz.NewMultiMap[string, *caveats.CaveatResult]()
42+
if err := collectParameterUsage(syntheticResult, resultsByParam); err != nil {
43+
return "", nil, err
44+
}
45+
46+
// Build the synthetic debug information.
47+
exprString, contextMap, err := buildDebugInformation(syntheticResult, resultsByParam)
48+
if err != nil {
49+
return "", nil, err
50+
}
51+
52+
// Convert the context map to a struct.
53+
contextStruct, err := structpb.NewStruct(contextMap)
54+
if err != nil {
55+
return "", nil, err
56+
}
57+
58+
return exprString, contextStruct, nil
59+
}
60+
61+
func buildDebugInformation(sr syntheticResult, resultsByParam *mapz.MultiMap[string, *caveats.CaveatResult]) (string, map[string]any, error) {
62+
childExprStrings := make([]string, 0, len(sr.exprResultsForDebug))
63+
combinedContext := map[string]any{}
64+
65+
for _, child := range sr.exprResultsForDebug {
66+
if _, ok := child.(*caveats.CaveatResult); ok {
67+
childExprString, contextMap, err := buildDebugInformationForConcrete(child.(*caveats.CaveatResult), resultsByParam)
68+
if err != nil {
69+
return "", nil, err
70+
}
71+
72+
childExprStrings = append(childExprStrings, "("+childExprString+")")
73+
maps.Copy(combinedContext, contextMap)
74+
continue
75+
}
76+
77+
childExprString, contextMap, err := buildDebugInformation(child.(syntheticResult), resultsByParam)
78+
if err != nil {
79+
return "", nil, err
80+
}
81+
82+
childExprStrings = append(childExprStrings, "("+childExprString+")")
83+
maps.Copy(combinedContext, contextMap)
84+
}
85+
86+
var combinedExprString string
87+
switch sr.op {
88+
case corev1.CaveatOperation_AND:
89+
combinedExprString = strings.Join(childExprStrings, " && ")
90+
91+
case corev1.CaveatOperation_OR:
92+
combinedExprString = strings.Join(childExprStrings, " || ")
93+
94+
case corev1.CaveatOperation_NOT:
95+
if len(childExprStrings) != 1 {
96+
return "", nil, spiceerrors.MustBugf("NOT operator must have exactly one child")
97+
}
98+
99+
combinedExprString = "!" + childExprStrings[0]
100+
101+
default:
102+
return "", nil, fmt.Errorf("unknown operator: %v", sr.op)
103+
}
104+
105+
return combinedExprString, combinedContext, nil
106+
}
107+
108+
func buildDebugInformationForConcrete(cr *caveats.CaveatResult, resultsByParam *mapz.MultiMap[string, *caveats.CaveatResult]) (string, map[string]any, error) {
109+
// For each paramter used in the context of the caveat, check if it is shared across multiple
110+
// caveats. If so, rewrite the parameter to a unique name.
111+
existingContextMap := cr.ContextValues()
112+
contextMap := make(map[string]any, len(existingContextMap))
113+
114+
caveat := *cr.ParentCaveat()
115+
116+
for paramName, paramValue := range existingContextMap {
117+
index := mapz.IndexOfValueInMultimap(resultsByParam, paramName, cr)
118+
if resultsByParam.CountOf(paramName) > 1 {
119+
newName := paramName + "__" + strconv.Itoa(index)
120+
if resultsByParam.Has(newName) {
121+
return "", nil, fmt.Errorf("failed to generate unique name for parameter: %s", newName)
122+
}
123+
124+
rewritten, err := caveat.RewriteVariable(paramName, newName)
125+
if err != nil {
126+
return "", nil, err
127+
}
128+
129+
caveat = rewritten
130+
contextMap[newName] = paramValue
131+
continue
132+
}
133+
134+
contextMap[paramName] = paramValue
135+
}
136+
137+
exprString, err := caveat.ExprString()
138+
if err != nil {
139+
return "", nil, err
140+
}
141+
142+
return exprString, contextMap, nil
143+
}
144+
145+
func collectParameterUsage(sr syntheticResult, resultsByParam *mapz.MultiMap[string, *caveats.CaveatResult]) error {
146+
for _, exprResult := range sr.exprResultsForDebug {
147+
if concrete, ok := exprResult.(*caveats.CaveatResult); ok {
148+
for paramName := range concrete.ContextValues() {
149+
resultsByParam.Add(paramName, concrete)
150+
}
151+
} else {
152+
cast, ok := exprResult.(syntheticResult)
153+
if !ok {
154+
return spiceerrors.MustBugf("unknown ExpressionResult type: %T", exprResult)
155+
}
156+
157+
if err := collectParameterUsage(cast, resultsByParam); err != nil {
158+
return err
159+
}
160+
}
161+
}
162+
163+
return nil
164+
}

0 commit comments

Comments
 (0)