diff options
| author | mo khan <mo@mokhan.ca> | 2025-07-22 17:35:49 -0600 |
|---|---|---|
| committer | mo khan <mo@mokhan.ca> | 2025-07-22 17:35:49 -0600 |
| commit | 20ef0d92694465ac86b550df139e8366a0a2b4fa (patch) | |
| tree | 3f14589e1ce6eb9306a3af31c3a1f9e1af5ed637 /vendor/github.com/authzed/zed/internal/printers | |
| parent | 44e0d272c040cdc53a98b9f1dc58ae7da67752e6 (diff) | |
feat: connect to spicedb
Diffstat (limited to 'vendor/github.com/authzed/zed/internal/printers')
4 files changed, 332 insertions, 0 deletions
diff --git a/vendor/github.com/authzed/zed/internal/printers/debug.go b/vendor/github.com/authzed/zed/internal/printers/debug.go new file mode 100644 index 0000000..2dbb9a6 --- /dev/null +++ b/vendor/github.com/authzed/zed/internal/printers/debug.go @@ -0,0 +1,197 @@ +package printers + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/gookit/color" + + v1 "github.com/authzed/authzed-go/proto/authzed/api/v1" + "github.com/authzed/spicedb/pkg/tuple" +) + +// DisplayCheckTrace prints out the check trace found in the given debug message. +func DisplayCheckTrace(checkTrace *v1.CheckDebugTrace, tp *TreePrinter, hasError bool) { + displayCheckTrace(checkTrace, tp, hasError, map[string]struct{}{}) +} + +func displayCheckTrace(checkTrace *v1.CheckDebugTrace, tp *TreePrinter, hasError bool, encountered map[string]struct{}) { + red := color.FgRed.Render + green := color.FgGreen.Render + cyan := color.FgCyan.Render + white := color.FgWhite.Render + faint := color.FgGray.Render + magenta := color.FgMagenta.Render + yellow := color.FgYellow.Render + + orange := color.C256(166).Sprint + purple := color.C256(99).Sprint + lightgreen := color.C256(35).Sprint + caveatColor := color.C256(198).Sprint + + hasPermission := green("✓") + resourceColor := white + permissionColor := color.FgWhite.Render + + switch checkTrace.PermissionType { + case v1.CheckDebugTrace_PERMISSION_TYPE_PERMISSION: + permissionColor = lightgreen + case v1.CheckDebugTrace_PERMISSION_TYPE_RELATION: + permissionColor = orange + } + + switch checkTrace.Result { + case v1.CheckDebugTrace_PERMISSIONSHIP_CONDITIONAL_PERMISSION: + switch checkTrace.CaveatEvaluationInfo.Result { + case v1.CaveatEvalInfo_RESULT_FALSE: + hasPermission = red("⨉") + resourceColor = faint + permissionColor = faint + + case v1.CaveatEvalInfo_RESULT_MISSING_SOME_CONTEXT: + hasPermission = magenta("?") + resourceColor = faint + permissionColor = faint + } + case v1.CheckDebugTrace_PERMISSIONSHIP_NO_PERMISSION: + hasPermission = red("⨉") + resourceColor = faint + permissionColor = faint + case v1.CheckDebugTrace_PERMISSIONSHIP_UNSPECIFIED: + hasPermission = yellow("∵") + } + + additional := "" + if checkTrace.GetWasCachedResult() { + sourceKind := "" + source := checkTrace.Source + if source != "" { + parts := strings.Split(source, ":") + if len(parts) > 0 { + sourceKind = parts[0] + } + } + switch sourceKind { + case "": + additional = cyan(" (cached)") + + case "spicedb": + additional = cyan(" (cached by spicedb)") + + case "materialize": + additional = purple(" (cached by materialize)") + + default: + additional = cyan(fmt.Sprintf(" (cached by %s)", sourceKind)) + } + } else if hasError && isPartOfCycle(checkTrace, map[string]struct{}{}) { + hasPermission = orange("!") + resourceColor = white + } + + isEndOfCycle := false + if hasError { + key := cycleKey(checkTrace) + _, isEndOfCycle = encountered[key] + if isEndOfCycle { + additional = color.C256(166).Sprint(" (cycle)") + } + encountered[key] = struct{}{} + } + + timing := "" + if checkTrace.Duration != nil { + timing = fmt.Sprintf(" (%s)", checkTrace.Duration.AsDuration().String()) + } + + tp = tp.Child( + fmt.Sprintf( + "%s %s:%s %s%s%s", + hasPermission, + resourceColor(checkTrace.Resource.ObjectType), + resourceColor(checkTrace.Resource.ObjectId), + permissionColor(checkTrace.Permission), + additional, + timing, + ), + ) + + if isEndOfCycle { + return + } + + if checkTrace.GetCaveatEvaluationInfo() != nil { + indicator := "" + exprColor := color.FgWhite.Render + switch checkTrace.CaveatEvaluationInfo.Result { + case v1.CaveatEvalInfo_RESULT_FALSE: + indicator = red("⨉") + exprColor = faint + + case v1.CaveatEvalInfo_RESULT_TRUE: + indicator = green("✓") + + case v1.CaveatEvalInfo_RESULT_MISSING_SOME_CONTEXT: + indicator = magenta("?") + } + + white := color.HEXStyle("fff") + white.SetOpts(color.Opts{color.OpItalic}) + + contextMap := checkTrace.CaveatEvaluationInfo.Context.AsMap() + caveatName := checkTrace.CaveatEvaluationInfo.CaveatName + + c := tp.Child(fmt.Sprintf("%s %s %s", indicator, exprColor(checkTrace.CaveatEvaluationInfo.Expression), caveatColor(caveatName))) + if len(contextMap) > 0 { + contextJSON, _ := json.MarshalIndent(contextMap, "", " ") + c.Child(string(contextJSON)) + } else { + if checkTrace.CaveatEvaluationInfo.Result != v1.CaveatEvalInfo_RESULT_MISSING_SOME_CONTEXT { + c.Child(faint("(no matching context found)")) + } + } + + if checkTrace.CaveatEvaluationInfo.Result == v1.CaveatEvalInfo_RESULT_MISSING_SOME_CONTEXT { + c.Child(fmt.Sprintf("missing context: %s", strings.Join(checkTrace.CaveatEvaluationInfo.PartialCaveatInfo.MissingRequiredContext, ", "))) + } + } + + if checkTrace.GetSubProblems() != nil { + for _, subProblem := range checkTrace.GetSubProblems().Traces { + displayCheckTrace(subProblem, tp, hasError, encountered) + } + } else if checkTrace.Result == v1.CheckDebugTrace_PERMISSIONSHIP_HAS_PERMISSION { + tp.Child(purple(fmt.Sprintf("%s:%s %s", checkTrace.Subject.Object.ObjectType, checkTrace.Subject.Object.ObjectId, checkTrace.Subject.OptionalRelation))) + } +} + +func cycleKey(checkTrace *v1.CheckDebugTrace) string { + return fmt.Sprintf("%s#%s", tuple.V1StringObjectRef(checkTrace.Resource), checkTrace.Permission) +} + +func isPartOfCycle(checkTrace *v1.CheckDebugTrace, encountered map[string]struct{}) bool { + if checkTrace.GetSubProblems() == nil { + return false + } + + encounteredCopy := make(map[string]struct{}, len(encountered)) + for k, v := range encountered { + encounteredCopy[k] = v + } + + key := cycleKey(checkTrace) + if _, ok := encounteredCopy[key]; ok { + return true + } + + encounteredCopy[key] = struct{}{} + + for _, subProblem := range checkTrace.GetSubProblems().Traces { + if isPartOfCycle(subProblem, encounteredCopy) { + return true + } + } + + return false +} diff --git a/vendor/github.com/authzed/zed/internal/printers/table.go b/vendor/github.com/authzed/zed/internal/printers/table.go new file mode 100644 index 0000000..fd6f024 --- /dev/null +++ b/vendor/github.com/authzed/zed/internal/printers/table.go @@ -0,0 +1,26 @@ +package printers + +import ( + "io" + + "github.com/olekukonko/tablewriter" +) + +// PrintTable writes an terminal-friendly table of the values to the target. +func PrintTable(target io.Writer, headers []string, rows [][]string) { + table := tablewriter.NewWriter(target) + table.SetHeader(headers) + table.SetAutoWrapText(false) + table.SetAutoFormatHeaders(true) + table.SetHeaderAlignment(tablewriter.ALIGN_LEFT) + table.SetAlignment(tablewriter.ALIGN_LEFT) + table.SetCenterSeparator("") + table.SetColumnSeparator("") + table.SetRowSeparator("") + table.SetHeaderLine(false) + table.SetBorder(false) + table.SetTablePadding("\t") + table.SetNoWhiteSpace(true) + table.AppendBulk(rows) + table.Render() +} diff --git a/vendor/github.com/authzed/zed/internal/printers/tree.go b/vendor/github.com/authzed/zed/internal/printers/tree.go new file mode 100644 index 0000000..b787210 --- /dev/null +++ b/vendor/github.com/authzed/zed/internal/printers/tree.go @@ -0,0 +1,66 @@ +package printers + +import ( + "fmt" + + "github.com/jzelinskie/stringz" + + v1 "github.com/authzed/authzed-go/proto/authzed/api/v1" +) + +func prettySubject(subj *v1.SubjectReference) string { + if subj.OptionalRelation == "" { + return fmt.Sprintf( + "%s:%s", + stringz.TrimPrefixIndex(subj.Object.ObjectType, "/"), + subj.Object.ObjectId, + ) + } + return fmt.Sprintf( + "%s:%s->%s", + stringz.TrimPrefixIndex(subj.Object.ObjectType, "/"), + subj.Object.ObjectId, + subj.OptionalRelation, + ) +} + +// TreeNodeTree walks an Authzed Tree Node and creates corresponding nodes +// for a treeprinter. +func TreeNodeTree(tp *TreePrinter, treeNode *v1.PermissionRelationshipTree) { + if treeNode.ExpandedObject != nil { + tp = tp.Child(fmt.Sprintf( + "%s:%s->%s", + stringz.TrimPrefixIndex(treeNode.ExpandedObject.ObjectType, "/"), + treeNode.ExpandedObject.ObjectId, + treeNode.ExpandedRelation, + )) + } + switch typed := treeNode.TreeType.(type) { + case *v1.PermissionRelationshipTree_Intermediate: + switch typed.Intermediate.Operation { + case v1.AlgebraicSubjectSet_OPERATION_UNION: + union := tp.Child("union") + for _, child := range typed.Intermediate.Children { + TreeNodeTree(union, child) + } + case v1.AlgebraicSubjectSet_OPERATION_INTERSECTION: + intersection := tp.Child("intersection") + for _, child := range typed.Intermediate.Children { + TreeNodeTree(intersection, child) + } + case v1.AlgebraicSubjectSet_OPERATION_EXCLUSION: + exclusion := tp.Child("exclusion") + for _, child := range typed.Intermediate.Children { + TreeNodeTree(exclusion, child) + } + default: + panic("unknown expand operation") + } + case *v1.PermissionRelationshipTree_Leaf: + for _, subject := range typed.Leaf.Subjects { + tp.Child(prettySubject(subject)) + } + default: + panic("unknown TreeNode type") + } +} diff --git a/vendor/github.com/authzed/zed/internal/printers/treeprinter.go b/vendor/github.com/authzed/zed/internal/printers/treeprinter.go new file mode 100644 index 0000000..55c3830 --- /dev/null +++ b/vendor/github.com/authzed/zed/internal/printers/treeprinter.go @@ -0,0 +1,43 @@ +package printers + +import ( + "strings" + + "github.com/xlab/treeprint" + + "github.com/authzed/zed/internal/console" +) + +type TreePrinter struct { + tree treeprint.Tree +} + +func NewTreePrinter() *TreePrinter { + return &TreePrinter{} +} + +func (tp *TreePrinter) Child(val string) *TreePrinter { + if tp.tree == nil { + tp.tree = treeprint.NewWithRoot(val) + return tp + } + return &TreePrinter{tree: tp.tree.AddBranch(val)} +} + +func (tp *TreePrinter) Print() { + console.Println(tp.String()) +} + +func (tp *TreePrinter) Indented() string { + var sb strings.Builder + lines := strings.Split(tp.String(), "\n") + for _, line := range lines { + sb.WriteString(" " + line + "\n") + } + + return sb.String() +} + +func (tp *TreePrinter) String() string { + return tp.tree.String() +} |
