Skip to content

Commit

Permalink
feat: expose OPL syntax check API
Browse files Browse the repository at this point in the history
  • Loading branch information
hperl committed Oct 12, 2022
1 parent 0059efc commit 57ff639
Show file tree
Hide file tree
Showing 44 changed files with 3,271 additions and 110 deletions.
1 change: 1 addition & 0 deletions cmd/client/grpc_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type contextKeys string
const (
FlagReadRemote = "read-remote"
FlagWriteRemote = "write-remote"
FlagOplRemote = "syntax-remote"

FlagInsecureNoTransportSecurity = "insecure-disable-transport-security"
FlagInsecureSkipHostVerification = "insecure-skip-hostname-verification"
Expand Down
4 changes: 4 additions & 0 deletions cmd/client/test_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ type (
const (
WriteServer ServerType = "write"
ReadServer ServerType = "read"
OplServer ServerType = "opl"
)

func NewTestServer(t *testing.T,
Expand All @@ -54,6 +55,9 @@ func NewTestServer(t *testing.T,
case WriteServer:
ts.NewServer = ts.Reg.WriteGRPCServer
ts.FlagRemote = FlagWriteRemote
case OplServer:
ts.NewServer = ts.Reg.OplGRPCServer
ts.FlagRemote = FlagOplRemote
default:
t.Logf("Got unknown server type %s", rw)
t.FailNow()
Expand Down
28 changes: 28 additions & 0 deletions embedx/config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,34 @@
"$ref": "#/definitions/tlsx"
}
}
},
"opl": {
"type": "object",
"title": "Ory Permission Language Syntax API (http and gRPC)",
"additionalProperties": false,
"properties": {
"port": {
"type": "integer",
"default": 4469,
"title": "Port",
"description": "The port to listen on.",
"minimum": 0,
"maximum": 65535
},
"host": {
"type": "string",
"default": "",
"examples": ["localhost", "127.0.0.1"],
"title": "Host",
"description": "The network interface to listen on."
},
"cors": {
"$ref": "#/definitions/cors"
},
"tls": {
"$ref": "#/definitions/tlsx"
}
}
}
}
},
Expand Down
4 changes: 0 additions & 4 deletions internal/check/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,10 @@ func (h *Handler) RegisterReadRoutes(r *x.ReadRouter) {
r.POST(OpenAPIRouteBase, h.postCheckNoStatus)
}

func (h *Handler) RegisterWriteRoutes(_ *x.WriteRouter) {}

func (h *Handler) RegisterReadGRPC(s *grpc.Server) {
rts.RegisterCheckServiceServer(s, h)
}

func (h *Handler) RegisterWriteGRPC(_ *grpc.Server) {}

// RESTResponse represents the response for a check request.
//
// The content of the allowed field is mirrored in the HTTP status code.
Expand Down
4 changes: 3 additions & 1 deletion internal/driver/config/opl_config_namespace_watcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,9 @@ func (nw *oplConfigWatcher) parseFiles() {
continue
}
nn, ee := schema.Parse(string(content))
errs = append(errs, ee...)
for _, e := range ee {
errs = append(errs, e)
}
for _, n := range nn {
n := n // alias because we want a reference
namespaces = append(namespaces, &n)
Expand Down
53 changes: 31 additions & 22 deletions internal/driver/config/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,15 @@ const (
KeyDSN = "dsn"

KeyLimitMaxReadDepth = "limit.max_read_depth"
KeyReadAPIHost = "serve.read.host"
KeyReadAPIPort = "serve.read.port"

KeyWriteAPIHost = "serve.write.host"
KeyWriteAPIPort = "serve.write.port"

KeyMetricsHost = "serve.metrics.host"
KeyMetricsPort = "serve.metrics.port"
KeyReadAPIHost = "serve.read.host"
KeyReadAPIPort = "serve.read.port"
KeyWriteAPIHost = "serve.write.host"
KeyWriteAPIPort = "serve.write.port"
KeyOPLSyntaxAPIHost = "serve.opl.host"
KeyOPLSyntaxAPIPort = "serve.opl.port"
KeyMetricsHost = "serve.metrics.host"
KeyMetricsPort = "serve.metrics.port"

KeyNamespaces = "namespaces"

Expand Down Expand Up @@ -155,23 +156,39 @@ func (k *Config) Set(key string, v any) error {
func (k *Config) ReadAPIListenOn() string {
return fmt.Sprintf(
"%s:%d",
k.p.StringF(KeyReadAPIHost, ""),
k.p.IntF(KeyReadAPIPort, 4466),
k.p.String(KeyReadAPIHost),
k.p.Int(KeyReadAPIPort),
)
}

func (k *Config) MaxReadDepth() int {
return k.p.Int(KeyLimitMaxReadDepth)
func (k *Config) WriteAPIListenOn() string {
return fmt.Sprintf(
"%s:%d",
k.p.String(KeyWriteAPIHost),
k.p.Int(KeyWriteAPIPort),
)
}

func (k *Config) WriteAPIListenOn() string {
func (k *Config) MetricsListenOn() string {
return fmt.Sprintf(
"%s:%d",
k.p.String(KeyMetricsHost),
k.p.Int(KeyMetricsPort),
)
}

func (k *Config) OPLSyntaxAPIListenOn() string {
return fmt.Sprintf(
"%s:%d",
k.p.StringF(KeyWriteAPIHost, ""),
k.p.IntF(KeyWriteAPIPort, 4467),
k.p.String(KeyOPLSyntaxAPIHost),
k.p.Int(KeyOPLSyntaxAPIPort),
)
}

func (k *Config) MaxReadDepth() int {
return k.p.Int(KeyLimitMaxReadDepth)
}

func (k *Config) CORS(iface string) (cors.Options, bool) {
switch iface {
case "read", "write", "metrics":
Expand Down Expand Up @@ -321,11 +338,3 @@ func (k *Config) namespaceConfig() (namespaceConfig, error) {
return nil, errors.WithStack(herodot.ErrInternalServerError.WithReasonf("could not infer namespaces for type %T", nTyped))
}
}

func (k *Config) MetricsListenOn() string {
return fmt.Sprintf(
"%s:%d",
k.p.StringF(KeyMetricsHost, ""),
k.p.IntF(KeyMetricsPort, 4468),
)
}
89 changes: 82 additions & 7 deletions internal/driver/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"strings"
"syscall"

"github.com/ory/keto/internal/schema"
rts "github.com/ory/keto/proto/ory/keto/relation_tuples/v1alpha2"

prometheus "github.com/ory/x/prometheusx"
Expand All @@ -18,7 +19,7 @@ import (
"github.com/ory/x/logrusx"

grpcMiddleware "github.com/grpc-ecosystem/go-grpc-middleware"
grpc_logrus "github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus"
grpcLogrus "github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus"
"github.com/julienschmidt/httprouter"
"github.com/ory/herodot"
"github.com/ory/x/reqlog"
Expand Down Expand Up @@ -120,6 +121,7 @@ func (r *RegistryDefault) ServeAll(ctx context.Context) error {

eg.Go(r.serveRead(innerCtx, doneShutdown))
eg.Go(r.serveWrite(innerCtx, doneShutdown))
eg.Go(r.serveOPLSyntax(innerCtx, doneShutdown))
eg.Go(r.serveMetrics(innerCtx, doneShutdown))

return eg.Wait()
Expand Down Expand Up @@ -149,6 +151,18 @@ func (r *RegistryDefault) serveWrite(ctx context.Context, done chan<- struct{})
}
}

func (r *RegistryDefault) serveOPLSyntax(ctx context.Context, done chan<- struct{}) func() error {
rt, s := r.OPLSyntaxRouter(ctx), r.OplGRPCServer(ctx)

if tracer := r.Tracer(ctx); tracer.IsLoaded() {
rt = otelx.TraceHandler(rt)
}

return func() error {
return multiplexPort(ctx, r.Logger().WithField("endpoint", "opl"), r.Config(ctx).OPLSyntaxAPIListenOn(), rt, s, done)
}
}

func (r *RegistryDefault) serveMetrics(ctx context.Context, done chan<- struct{}) func() error {
ctx, cancel := context.WithCancel(ctx)

Expand Down Expand Up @@ -283,6 +297,7 @@ func (r *RegistryDefault) allHandlers() []Handler {
relationtuple.NewHandler(r),
check.NewHandler(r),
expand.NewHandler(r),
schema.NewHandler(r),
}
}
return r.handlers
Expand All @@ -303,7 +318,9 @@ func (r *RegistryDefault) ReadRouter(ctx context.Context) http.Handler {
r.HealthHandler().SetVersionRoutes(br.Router)

for _, h := range r.allHandlers() {
h.RegisterReadRoutes(br)
if h, ok := h.(ReadHandler); ok {
h.RegisterReadRoutes(br)
}
}

n.UseHandler(br)
Expand Down Expand Up @@ -337,7 +354,45 @@ func (r *RegistryDefault) WriteRouter(ctx context.Context) http.Handler {
r.HealthHandler().SetVersionRoutes(pr.Router)

for _, h := range r.allHandlers() {
h.RegisterWriteRoutes(pr)
if h, ok := h.(WriteHandler); ok {
h.RegisterWriteRoutes(pr)
}
}

n.UseHandler(pr)
n.Use(r.PrometheusManager())

if r.sqaService != nil {
n.Use(r.sqaService)
}

var handler http.Handler = n
options, enabled := r.Config(ctx).CORS("write")
if enabled {
handler = cors.New(options).Handler(handler)
}

return handler
}

func (r *RegistryDefault) OPLSyntaxRouter(ctx context.Context) http.Handler {
n := negroni.New()
for _, f := range r.defaultHttpMiddlewares {
n.UseFunc(f)
}
n.Use(reqlog.NewMiddlewareFromLogger(r.l, "syntax#Ory Keto").ExcludePaths(healthx.AliveCheckPath, healthx.ReadyCheckPath))

pr := &x.OPLSyntaxRouter{Router: httprouter.New()}
r.PrometheusManager().RegisterRouter(pr.Router)
r.MetricsHandler().SetRoutes(pr.Router)

r.HealthHandler().SetHealthRoutes(pr.Router, false)
r.HealthHandler().SetVersionRoutes(pr.Router)

for _, h := range r.allHandlers() {
if h, ok := h.(OPLSyntaxHandler); ok {
h.RegisterSyntaxRoutes(pr)
}
}

n.UseHandler(pr)
Expand All @@ -362,7 +417,7 @@ func (r *RegistryDefault) unaryInterceptors(ctx context.Context) []grpc.UnarySer
is = append(is,
herodot.UnaryErrorUnwrapInterceptor,
grpcMiddleware.ChainUnaryServer(
grpc_logrus.UnaryServerInterceptor(r.l.Entry),
grpcLogrus.UnaryServerInterceptor(r.l.Entry),
),
)
if r.Tracer(ctx).IsLoaded() {
Expand All @@ -380,7 +435,7 @@ func (r *RegistryDefault) streamInterceptors(ctx context.Context) []grpc.StreamS
is = append(is,
herodot.StreamErrorUnwrapInterceptor,
grpcMiddleware.ChainStreamServer(
grpc_logrus.StreamServerInterceptor(r.l.Entry),
grpcLogrus.StreamServerInterceptor(r.l.Entry),
),
)
if r.Tracer(ctx).IsLoaded() {
Expand Down Expand Up @@ -411,7 +466,9 @@ func (r *RegistryDefault) ReadGRPCServer(ctx context.Context) *grpc.Server {
reflection.Register(s)

for _, h := range r.allHandlers() {
h.RegisterReadGRPC(s)
if h, ok := h.(ReadHandler); ok {
h.RegisterReadGRPC(s)
}
}

return s
Expand All @@ -425,7 +482,25 @@ func (r *RegistryDefault) WriteGRPCServer(ctx context.Context) *grpc.Server {
reflection.Register(s)

for _, h := range r.allHandlers() {
h.RegisterWriteGRPC(s)
if h, ok := h.(WriteHandler); ok {
h.RegisterWriteGRPC(s)
}
}

return s
}

func (r *RegistryDefault) OplGRPCServer(ctx context.Context) *grpc.Server {
s := r.newGrpcServer(ctx)

grpcHealthV1.RegisterHealthServer(s, r.HealthServer())
rts.RegisterVersionServiceServer(s, r)
reflection.Register(s)

for _, h := range r.allHandlers() {
if h, ok := h.(OPLSyntaxHandler); ok {
h.RegisterSyntaxGRPC(s)
}
}

return s
Expand Down
1 change: 1 addition & 0 deletions internal/driver/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ type (

ReadGRPCServer(ctx context.Context) *grpc.Server
WriteGRPCServer(ctx context.Context) *grpc.Server
OplGRPCServer(ctx context.Context) *grpc.Server

ServeAll(ctx context.Context) error
ServeAllSQA(cmd *cobra.Command) error
Expand Down
13 changes: 10 additions & 3 deletions internal/driver/registry_default.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,19 @@ type (
grpcTransportCredentials credentials.TransportCredentials
defaultMigrationOptions []popx.MigrationBoxOption
}
Handler interface {
ReadHandler interface {
RegisterReadRoutes(r *x.ReadRouter)
RegisterWriteRoutes(r *x.WriteRouter)
RegisterReadGRPC(s *grpc.Server)
}
WriteHandler interface {
RegisterWriteRoutes(r *x.WriteRouter)
RegisterWriteGRPC(s *grpc.Server)
}
OPLSyntaxHandler interface {
RegisterSyntaxRoutes(r *x.OPLSyntaxRouter)
RegisterSyntaxGRPC(s *grpc.Server)
}
Handler interface{}
)

func (r *RegistryDefault) Mapper() *relationtuple.Mapper {
Expand Down Expand Up @@ -120,7 +127,7 @@ func (r *RegistryDefault) GetVersion(_ context.Context, _ *rts.GetVersionRequest

func (r *RegistryDefault) Tracer(ctx context.Context) *otelx.Tracer {
if r.tracer == nil {
// Tracing is initialized only once so it can not be hot reloaded or context-aware.
// Tracing is initialized only once, so it can not be hot reloaded or context-aware.
t, err := otelx.New("Ory Keto", r.Logger(), r.Config(ctx).TracingConfig())
if err != nil {
r.Logger().WithError(err).Fatalf("Unable to initialize Tracer.")
Expand Down
8 changes: 8 additions & 0 deletions internal/e2e/cli_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"encoding/json"
"fmt"
"strconv"
"testing"
"time"

"github.com/ory/keto/ketoapi"
Expand Down Expand Up @@ -35,6 +36,13 @@ type cliClient struct {

var _ client = (*cliClient)(nil)

func (g *cliClient) oplCheckSyntax(t require.TestingT, _ []byte) []*ketoapi.ParseError {
if t, ok := t.(*testing.T); ok {
t.Skip("not implemented as a command yet")
}
return []*ketoapi.ParseError{}
}

func (g *cliClient) createTuple(t require.TestingT, r *ketoapi.RelationTuple) {
tupleEnc, err := json.Marshal(r)
require.NoError(t, err)
Expand Down

0 comments on commit 57ff639

Please sign in to comment.