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

This commit is contained in:
upstream-mirror
2026-09-16 01:08:34 +05:00
commit 9456a4e922
2560 changed files with 120911 additions and 0 deletions

View File

@@ -0,0 +1,320 @@
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package relationtuple
import (
"context"
"encoding/json"
"net/http"
"connectrpc.com/connect"
"github.com/pkg/errors"
"go.opentelemetry.io/otel/trace"
"github.com/ory/herodot"
rts "github.com/ory/keto/gen/go/ory/keto/relation_tuples/v1alpha2"
"github.com/ory/keto/gen/go/ory/keto/relation_tuples/v1alpha2/relationtuplesconnect"
"github.com/ory/keto/internal/x/validate"
"github.com/ory/keto/ketoapi"
"github.com/ory/keto/x/events"
)
var _ relationtuplesconnect.WriteServiceHandler = (*WriteHandler)(nil)
func protoTuplesWithAction(deltas []*rts.RelationTupleDelta, action rts.RelationTupleDelta_Action) (filtered []*ketoapi.RelationTuple, err error) {
for _, d := range deltas {
if d.Action == action {
it, err := (&ketoapi.RelationTuple{}).FromDataProvider(d.RelationTuple)
if err != nil {
return nil, err
}
filtered = append(filtered, it)
}
}
return
}
func (h *WriteHandler) TransactRelationTuples(ctx context.Context, req *connect.Request[rts.TransactRelationTuplesRequest]) (*connect.Response[rts.TransactRelationTuplesResponse], error) {
insertTuples, err := protoTuplesWithAction(req.Msg.RelationTupleDeltas, rts.RelationTupleDelta_ACTION_INSERT)
if err != nil {
return nil, err
}
deleteTuples, err := protoTuplesWithAction(req.Msg.RelationTupleDeltas, rts.RelationTupleDelta_ACTION_DELETE)
if err != nil {
return nil, err
}
err = h.d.RelationTupleManager().Transaction(ctx, func(ctx context.Context) error {
its, err := h.d.Mapper().FromTuple(ctx, append(insertTuples, deleteTuples...)...)
if err != nil {
return err
}
return h.d.RelationTupleManager().TransactRelationTuples(ctx, its[:len(insertTuples)], its[len(insertTuples):])
})
if err != nil {
return nil, err
}
trace.SpanFromContext(ctx).AddEvent(events.NewRelationtuplesChanged(ctx))
snaptokens := make([]string, len(insertTuples))
for i := range insertTuples {
snaptokens[i] = "not yet implemented"
}
return connect.NewResponse(&rts.TransactRelationTuplesResponse{
Snaptokens: snaptokens,
}), nil
}
func (h *WriteHandler) DeleteRelationTuples(ctx context.Context, req *connect.Request[rts.DeleteRelationTuplesRequest]) (*connect.Response[rts.DeleteRelationTuplesResponse], error) {
var q ketoapi.RelationQuery
switch {
case req.Msg.RelationQuery != nil:
q.FromDataProvider(&queryWrapper{req.Msg.RelationQuery})
//lint:ignore SA1019 required for compatibility
case req.Msg.Query != nil: //nolint:staticcheck
//lint:ignore SA1019 backwards compatibility
q.FromDataProvider(&deprecatedQueryWrapper{(*rts.ListRelationTuplesRequest_Query)(req.Msg.Query)}) //nolint:staticcheck
default:
return nil, errors.WithStack(herodot.ErrBadRequest().WithReason("invalid request"))
}
iq, err := h.d.ReadOnlyMapper().FromQuery(ctx, &q)
if err != nil {
return nil, err
}
if err := h.d.RelationTupleManager().DeleteAllRelationTuples(ctx, iq); err != nil {
return nil, errors.WithStack(herodot.ErrInternalServerError().WithError(err.Error()))
}
trace.SpanFromContext(ctx).AddEvent(events.NewRelationtuplesDeleted(ctx))
return connect.NewResponse(&rts.DeleteRelationTuplesResponse{}), nil
}
// Create Relationship Request Parameters
//
// swagger:parameters createRelationship
//
//lint:ignore U1000 required for OpenAPI
type createRelationship struct {
// in: body
Body createRelationshipBody
}
// Create Relationship Request Body
//
// swagger:model createRelationshipBody
type createRelationshipBody struct {
ketoapi.RelationQuery
}
// swagger:route PUT /admin/relation-tuples relationship createRelationship
//
// # Create a Relationship
//
// Use this endpoint to create a relationship.
//
// Consumes:
// - application/json
//
// Produces:
// - application/json
//
// Schemes: http, https
//
// Responses:
// 201: relationship
// 400: errorGeneric
// default: errorGeneric
//
// Extensions:
// x-ory-ratelimit-bucket: keto-admin-low
func (h *WriteHandler) createRelation(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var rt ketoapi.RelationTuple
if err := json.NewDecoder(r.Body).Decode(&rt); err != nil {
h.d.Writer().WriteError(w, r, errors.WithStack(herodot.ErrBadRequest().WithError(err.Error())))
return
}
if err := rt.Validate(); err != nil {
h.d.Writer().WriteError(w, r, err)
return
}
h.d.Logger().WithFields(rt.ToLoggerFields()).Debug("creating relation tuple")
err := h.d.RelationTupleManager().Transaction(ctx, func(ctx context.Context) error {
it, err := h.d.Mapper().FromTuple(ctx, &rt)
if err != nil {
h.d.Logger().WithError(err).WithFields(rt.ToLoggerFields()).Errorf("could not map relation tuple to UUIDs")
return err
}
if err := h.d.RelationTupleManager().WriteRelationTuples(ctx, it...); err != nil {
h.d.Logger().WithError(err).WithFields(rt.ToLoggerFields()).Errorf("got an error while creating the relation tuple")
return err
}
return nil
})
if err != nil {
h.d.Writer().WriteError(w, r, err)
return
}
trace.SpanFromContext(ctx).AddEvent(events.NewRelationtuplesCreated(ctx))
h.d.Writer().WriteCreated(w, r,
ReadRouteBase+"?"+rt.ToURLQuery().Encode(),
&rt,
)
}
// swagger:route DELETE /admin/relation-tuples relationship deleteRelationships
//
// # Delete Relationships
//
// Use this endpoint to delete relationships
//
// Consumes:
// - application/x-www-form-urlencoded
//
// Produces:
// - application/json
//
// Schemes: http, https
//
// Responses:
// 204: emptyResponse
// 400: errorGeneric
// default: errorGeneric
//
// Extensions:
// x-ory-ratelimit-bucket: keto-admin-low
func (h *WriteHandler) deleteRelations(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
if err := validate.All(r,
validate.NoExtraQueryParams(ketoapi.RelationQueryKeys...),
validate.QueryParamsContainsOneOf(ketoapi.NamespaceKey),
validate.HasEmptyBody(),
); err != nil {
h.d.Writer().WriteError(w, r, err)
return
}
q := r.URL.Query()
query, err := (&ketoapi.RelationQuery{}).FromURLQuery(q)
if err != nil {
h.d.Writer().WriteError(w, r, herodot.ErrBadRequest().WithError(err.Error()))
return
}
l := h.d.Logger()
for k := range q {
l = l.WithField(k, q.Get(k))
}
l.Debug("deleting relationships")
iq, err := h.d.ReadOnlyMapper().FromQuery(ctx, query)
if err != nil {
h.d.Logger().WithError(err).Errorf("could not map fields to UUIDs")
h.d.Writer().WriteError(w, r, err)
return
}
if err := h.d.RelationTupleManager().DeleteAllRelationTuples(ctx, iq); err != nil {
l.WithError(err).Errorf("got an error while deleting relationships")
h.d.Writer().WriteError(w, r, herodot.ErrInternalServerError().WithError(err.Error()))
return
}
trace.SpanFromContext(ctx).AddEvent(events.NewRelationtuplesDeleted(ctx))
w.WriteHeader(http.StatusNoContent)
}
func internalTuplesWithAction(deltas []*ketoapi.PatchDelta, action ketoapi.PatchAction) (filtered []*ketoapi.RelationTuple) {
for _, d := range deltas {
if d.Action == action {
filtered = append(filtered, d.RelationTuple)
}
}
return
}
// swagger:route PATCH /admin/relation-tuples relationship patchRelationships
//
// # Patch Multiple Relationships
//
// Use this endpoint to patch one or more relationships.
//
// Consumes:
// - application/json
//
// Produces:
// - application/json
//
// Schemes: http, https
//
// Responses:
// 204: emptyResponse
// 400: errorGeneric
// 404: errorGeneric
// default: errorGeneric
//
// Extensions:
// x-ory-ratelimit-bucket: keto-admin-low
func (h *WriteHandler) patchRelationTuples(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var deltas []*ketoapi.PatchDelta
if err := json.NewDecoder(r.Body).Decode(&deltas); err != nil {
h.d.Writer().WriteError(w, r, herodot.ErrBadRequest().WithError(err.Error()))
return
}
for _, d := range deltas {
if d.RelationTuple == nil {
h.d.Writer().WriteError(w, r, herodot.ErrBadRequest().WithError("relation_tuple is missing"))
return
}
if err := d.RelationTuple.Validate(); err != nil {
h.d.Writer().WriteError(w, r, err)
return
}
switch d.Action {
case ketoapi.ActionInsert, ketoapi.ActionDelete:
default:
h.d.Writer().WriteError(w, r, herodot.ErrBadRequest().WithError("unknown action "+string(d.Action)))
return
}
}
insertTuples := internalTuplesWithAction(deltas, ketoapi.ActionInsert)
deleteTuples := internalTuplesWithAction(deltas, ketoapi.ActionDelete)
err := h.d.RelationTupleManager().Transaction(ctx, func(ctx context.Context) error {
its, err := h.d.Mapper().FromTuple(ctx, append(insertTuples, deleteTuples...)...)
if err != nil {
h.d.Logger().WithError(err).Errorf("got an error while mapping fields to UUID")
return err
}
return h.d.RelationTupleManager().TransactRelationTuples(ctx, its[:len(insertTuples)], its[len(insertTuples):])
})
if err != nil {
h.d.Writer().WriteError(w, r, err)
return
}
trace.SpanFromContext(ctx).AddEvent(events.NewRelationtuplesChanged(ctx))
w.WriteHeader(http.StatusNoContent)
}