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
191 lines
5.3 KiB
Go
191 lines
5.3 KiB
Go
// Copyright © 2023 Ory Corp
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package check_test
|
|
|
|
import (
|
|
"context"
|
|
_ "embed"
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"go.opentelemetry.io/otel/sdk/trace"
|
|
"go.opentelemetry.io/otel/sdk/trace/tracetest"
|
|
|
|
"github.com/ory/keto/internal/check"
|
|
"github.com/ory/keto/internal/driver"
|
|
"github.com/ory/keto/internal/driver/config"
|
|
"github.com/ory/keto/internal/namespace"
|
|
"github.com/ory/keto/internal/namespace/ast"
|
|
"github.com/ory/keto/internal/testhelpers"
|
|
)
|
|
|
|
func wideNamespace(width int) *namespace.Namespace {
|
|
wideNS := &namespace.Namespace{
|
|
Name: fmt.Sprintf("%d_wide", width),
|
|
Relations: []ast.Relation{{Name: "editor"}},
|
|
}
|
|
viewerRelation := &ast.Relation{
|
|
Name: "viewer",
|
|
SubjectSetRewrite: &ast.SubjectSetRewrite{
|
|
Operation: ast.OperatorOr,
|
|
Children: ast.Children{},
|
|
},
|
|
}
|
|
for i := 0; i < width; i++ {
|
|
relation := fmt.Sprintf("relation-%d", i)
|
|
viewerRelation.SubjectSetRewrite.Children = append(
|
|
viewerRelation.SubjectSetRewrite.Children,
|
|
&ast.ComputedSubjectSet{Relation: relation},
|
|
)
|
|
wideNS.Relations = append(wideNS.Relations, ast.Relation{Name: relation})
|
|
}
|
|
viewerRelation.SubjectSetRewrite.Children = append(
|
|
viewerRelation.SubjectSetRewrite.Children,
|
|
&ast.ComputedSubjectSet{Relation: "editor"},
|
|
)
|
|
wideNS.Relations = append(wideNS.Relations, *viewerRelation)
|
|
|
|
return wideNS
|
|
}
|
|
|
|
func BenchmarkCheckEngine(b *testing.B) {
|
|
ctx := context.Background()
|
|
var (
|
|
depths = []int{2, 4, 8, 16, 32}
|
|
widths = []int{10, 20, 40, 80, 100}
|
|
maxDepth = depths[len(depths)-1]
|
|
)
|
|
|
|
namespaces := []*namespace.Namespace{
|
|
{
|
|
Name: "deep",
|
|
Relations: []ast.Relation{
|
|
{Name: "owner"},
|
|
{
|
|
Name: "editor",
|
|
SubjectSetRewrite: &ast.SubjectSetRewrite{
|
|
Children: ast.Children{&ast.ComputedSubjectSet{
|
|
Relation: "owner",
|
|
}},
|
|
},
|
|
},
|
|
{
|
|
Name: "viewer",
|
|
SubjectSetRewrite: &ast.SubjectSetRewrite{
|
|
Children: ast.Children{
|
|
&ast.ComputedSubjectSet{
|
|
Relation: "editor",
|
|
},
|
|
&ast.TupleToSubjectSet{
|
|
Relation: "parent",
|
|
ComputedSubjectSetRelation: "viewer",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
reg := driver.NewSqliteTestRegistry(b, driver.WithNamespaces(namespaces))
|
|
reg.Logger().Logger.SetLevel(logrus.InfoLevel)
|
|
|
|
tuples := []string{
|
|
"deep:deep_file#parent@deep:folder_1#...",
|
|
}
|
|
for i := 1; i < maxDepth; i++ {
|
|
tuples = append(tuples, fmt.Sprintf("deep:folder_%d#parent@deep:folder_%d#...", i, i+1))
|
|
}
|
|
for _, d := range depths {
|
|
tuples = append(tuples, fmt.Sprintf("deep:folder_%d#owner@user_%d", d, d))
|
|
}
|
|
for _, w := range widths {
|
|
namespaces = append(namespaces, wideNamespace(w))
|
|
tuples = append(tuples, fmt.Sprintf("%d-wide:wide_file#editor@user", w))
|
|
}
|
|
testhelpers.MapAndInsertTuplesFromString(b, reg, tuples)
|
|
|
|
require.NoError(b, reg.Config(ctx).Set(config.KeyLimitMaxReadDepth, 100*maxDepth))
|
|
e := check.NewEngine(reg)
|
|
|
|
b.Run("case=deep tree", func(b *testing.B) {
|
|
for _, depth := range depths {
|
|
b.Run(fmt.Sprintf("depth=%03d", depth), func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
rt := testhelpers.TupleFromString(b, fmt.Sprintf("deep:deep_file#viewer@user_%d", depth))
|
|
res := e.CheckRelationTuple(ctx, rt, 2*depth)
|
|
assert.NoError(b, res.Err)
|
|
if res.Membership != check.IsMember {
|
|
b.Error("user should be able to view 'deep_file'")
|
|
}
|
|
}
|
|
})
|
|
}
|
|
})
|
|
|
|
b.Run("case=wide tree", func(b *testing.B) {
|
|
for _, width := range widths {
|
|
b.Run(fmt.Sprintf("width=%03d", width), func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
rt := testhelpers.TupleFromString(b, fmt.Sprintf("%d-wide:wide_file#editor@user", width))
|
|
res := e.CheckRelationTuple(ctx, rt, 2*width)
|
|
assert.NoError(b, res.Err)
|
|
if res.Membership != check.IsMember {
|
|
b.Error("user should be able to view 'wide_file'")
|
|
}
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
//go:embed testfixtures/project_opl.ts
|
|
var ProjectOPLConfig string
|
|
|
|
func BenchmarkComputedUsersets(b *testing.B) {
|
|
ctx := context.Background()
|
|
|
|
spans := tracetest.NewSpanRecorder()
|
|
tracer := trace.NewTracerProvider(trace.WithSpanProcessor(spans)).Tracer("")
|
|
reg := driver.NewSqliteTestRegistry(b, driver.WithLogLevel("debug"), driver.WithOPL(ProjectOPLConfig), driver.WithTracer(tracer), driver.WithConfig(config.KeyFeatureFlagStrictMode, true))
|
|
reg.Logger().Logger.SetLevel(logrus.DebugLevel)
|
|
|
|
testhelpers.MapAndInsertTuplesFromString(b, reg, []string{
|
|
"Project:Ory#owner@User:Admin",
|
|
"Project:Ory#developer@User:Dev",
|
|
})
|
|
|
|
e := check.NewEngine(reg)
|
|
|
|
query := testhelpers.TupleFromString(b, "Project:Ory#readProject@User:Dev")
|
|
|
|
b.Run("Computed userset", func(b *testing.B) {
|
|
initialDBSpans := dbSpans(spans)
|
|
for i := 0; i < b.N; i++ {
|
|
res := e.CheckRelationTuple(ctx, query, 0)
|
|
assert.NoError(b, res.Err)
|
|
if res.Err != nil {
|
|
b.Errorf("got unexpected error: %v", res.Err)
|
|
}
|
|
if res.Membership != check.IsMember {
|
|
b.Error("check failed")
|
|
}
|
|
}
|
|
b.ReportMetric((float64(dbSpans(spans)-initialDBSpans))/float64(b.N), "queries/op")
|
|
})
|
|
}
|
|
|
|
func dbSpans(spans *tracetest.SpanRecorder) (count int) {
|
|
for _, s := range spans.Started() {
|
|
if strings.HasPrefix(s.Name(), "sql-conn-query") {
|
|
count++
|
|
}
|
|
}
|
|
return
|
|
}
|