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  }