snapshot of https://github.com/ory/keto.git (master, 2026-09-16; blobs > 131072B omitted)
Some checks failed
CI / Run lints and checks (push) Failing after 47s
CI / Run tests (push) Failing after 1s
CI / Run race tests (push) Failing after 5s
CI / Build CLI docs (push) Has been skipped
CI / Run buf actions (push) Failing after 5s
CodeQL / Analyze (go) (push) Failing after 3s
CodeQL / Analyze (javascript) (push) Failing after 2s
Docker Image Scanners / scanners (push) Failing after 3s
Format / format (push) Failing after 3s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Draft newsletter (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped
Some checks failed
CI / Run lints and checks (push) Failing after 47s
CI / Run tests (push) Failing after 1s
CI / Run race tests (push) Failing after 5s
CI / Build CLI docs (push) Has been skipped
CI / Run buf actions (push) Failing after 5s
CodeQL / Analyze (go) (push) Failing after 3s
CodeQL / Analyze (javascript) (push) Failing after 2s
Docker Image Scanners / scanners (push) Failing after 3s
Format / format (push) Failing after 3s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Draft newsletter (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped
This commit is contained in:
100
internal/check/step/expand.go
Normal file
100
internal/check/step/expand.go
Normal file
@@ -0,0 +1,100 @@
|
||||
// Copyright © 2026 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package step
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/ory/herodot"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/ory/keto/internal/check"
|
||||
"github.com/ory/keto/internal/relationtuple"
|
||||
"github.com/ory/keto/internal/x/graph"
|
||||
)
|
||||
|
||||
// ExpandSubjectStep expands subject-set pointers under the tuple's relation and recursively checks each one.
|
||||
type ExpandSubjectStep struct {
|
||||
// SubjectSetTypes are the OPL-declared subject-set types for this relation.
|
||||
// When empty, all subject-set pointers are expanded.
|
||||
// When non-empty, these types can be used to narrow down the expansion.
|
||||
SubjectSetTypes []relationtuple.SubjectSetType
|
||||
}
|
||||
|
||||
func (ExpandSubjectStep) Kind() check.StepKind { return check.StepExpand }
|
||||
|
||||
func (s ExpandSubjectStep) Execute(ctx context.Context, req check.CheckRequest, ex check.Executor) check.Result {
|
||||
if req.RestDepth <= 0 {
|
||||
return maxDepthReached(ex, req)
|
||||
}
|
||||
|
||||
ex.Deps().Logger().
|
||||
WithField("request", req.Tuple.String()).
|
||||
Trace("check expand subject")
|
||||
|
||||
results, err := ex.Deps().RelationTupleManager().TraverseSubjectSetExpansion(ctx, req.Tuple, s.SubjectSetTypes)
|
||||
if errors.Is(err, herodot.ErrNotFound()) {
|
||||
return check.ResultNotMember
|
||||
} else if err != nil {
|
||||
return check.Result{Err: errors.WithStack(err)}
|
||||
}
|
||||
|
||||
check.ReportTuplesLoaded(ctx, len(results))
|
||||
|
||||
// Check whether the current hop already resolved the query.
|
||||
for _, result := range results {
|
||||
if result.Found {
|
||||
check.ReportSubjectFound(ctx, result.To)
|
||||
return check.ResultIsMember
|
||||
}
|
||||
}
|
||||
|
||||
if len(results) == 0 {
|
||||
return check.ResultNotMember
|
||||
}
|
||||
|
||||
// If we have results, but no direct hits, we need to check the next level of expansion.
|
||||
// We check the depth here to prevent planning a large number of child checks when they will already be at max depth.
|
||||
if req.RestDepth <= 1 {
|
||||
return maxDepthReached(ex, req)
|
||||
}
|
||||
|
||||
// Build the visited-tracking context from the current ctx so that child
|
||||
// checks share the same visited set and prevent infinite loops.
|
||||
var visited bool
|
||||
visitCtx := graph.InitVisited(ctx)
|
||||
|
||||
runner := ex.NewUnionRunner(visitCtx)
|
||||
if maxWidth := ex.Deps().Config(ctx).MaxReadWidth(); len(results) > maxWidth {
|
||||
ex.Deps().Logger().
|
||||
WithField("method", "ExpandSubjectStep").
|
||||
WithField("request", req.Tuple.String()).
|
||||
WithField("max_width", maxWidth).
|
||||
WithField("results", len(results)).
|
||||
Debug("too many results, truncating to width limit")
|
||||
results = results[:maxWidth]
|
||||
|
||||
runner.AddLimitation(check.LimitationMaxWidthExceeded)
|
||||
}
|
||||
|
||||
for _, result := range results {
|
||||
if runner.Done() {
|
||||
break
|
||||
}
|
||||
sub := &relationtuple.SubjectSet{
|
||||
Namespace: result.To.Namespace,
|
||||
Object: result.To.Object,
|
||||
Relation: result.To.Relation,
|
||||
}
|
||||
visitCtx, visited = graph.CheckAndAddVisited(visitCtx, sub)
|
||||
if visited {
|
||||
continue
|
||||
}
|
||||
runner.Add(check.PlannedStep{
|
||||
Step: IsAllowedStep{skipDirect: true},
|
||||
Req: check.CheckRequest{Tuple: result.To, RestDepth: req.RestDepth - 1},
|
||||
})
|
||||
}
|
||||
return runner.Result()
|
||||
}
|
||||
Reference in New Issue
Block a user