Skip to content

Commit

Permalink
feat: add check endpoints that do not mirror status code (#853)
Browse files Browse the repository at this point in the history
Co-authored-by: hperl <34397+hperl@users.noreply.github.com>
  • Loading branch information
zepatrik and hperl committed Jun 15, 2022
1 parent 01a7564 commit 07d0fbd
Show file tree
Hide file tree
Showing 21 changed files with 1,624 additions and 410 deletions.
28 changes: 12 additions & 16 deletions go.mod
@@ -1,22 +1,18 @@
module github.com/ory/keto

replace google.golang.org/protobuf v1.25.1-0.20201020201750-d3470999428b => google.golang.org/protobuf v1.25.0

replace github.com/soheilhy/cmux => github.com/soheilhy/cmux v0.1.5-0.20210114230657-cdd3331e3e7c

replace github.com/gogo/protobuf => github.com/gogo/protobuf v1.3.2

replace github.com/oleiade/reflections => github.com/oleiade/reflections v1.0.1

replace github.com/gobuffalo/packr => github.com/gobuffalo/packr v1.30.1

replace github.com/ory/keto/proto => ./proto

replace github.com/dgrijalva/jwt-go => github.com/golang-jwt/jwt/v4 v4.0.0

replace github.com/containerd/containerd => github.com/containerd/containerd v1.6.1
replace (
github.com/gogo/protobuf => github.com/gogo/protobuf v1.3.2
github.com/oleiade/reflections => github.com/oleiade/reflections v1.0.1
github.com/ory/keto/proto => ./proto
github.com/soheilhy/cmux => github.com/soheilhy/cmux v0.1.5-0.20210114230657-cdd3331e3e7c
)

replace github.com/opencontainers/runc => github.com/opencontainers/runc v1.0.3
// vulnerable dependencies
replace (
github.com/containerd/containerd => github.com/containerd/containerd v1.5.10
github.com/dgrijalva/jwt-go => github.com/golang-jwt/jwt/v4 v4.0.0
github.com/opencontainers/runc => github.com/opencontainers/runc v1.0.3
)

require (
github.com/cenkalti/backoff/v3 v3.0.0
Expand Down
224 changes: 74 additions & 150 deletions go.sum

Large diffs are not rendered by default.

133 changes: 97 additions & 36 deletions internal/check/handler.go
Expand Up @@ -3,9 +3,10 @@ package check
import (
"context"
"encoding/json"
"io"
"net/http"
"net/url"

"github.com/ory/herodot"
"github.com/pkg/errors"

rts "github.com/ory/keto/proto/ory/keto/relation_tuples/v1alpha2"
Expand Down Expand Up @@ -36,11 +37,16 @@ func NewHandler(d handlerDependencies) *Handler {
return &Handler{d: d}
}

const RouteBase = "/relation-tuples/check"
const (
RouteBase = "/relation-tuples/check"
OpenAPIRouteBase = RouteBase + "/openapi"
)

func (h *Handler) RegisterReadRoutes(r *x.ReadRouter) {
r.GET(RouteBase, h.getCheck)
r.POST(RouteBase, h.postCheck)
r.GET(RouteBase, h.getCheckMirrorStatus)
r.GET(OpenAPIRouteBase, h.getCheckNoStatus)
r.POST(RouteBase, h.postCheckMirrorStatus)
r.POST(OpenAPIRouteBase, h.postCheckNoStatus)
}

func (h *Handler) RegisterWriteRoutes(_ *x.WriteRouter) {}
Expand All @@ -51,7 +57,7 @@ func (h *Handler) RegisterReadGRPC(s *grpc.Server) {

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

// RESTResponse is the response for a check request.
// RESTResponse represents the response for a check request.
//
// The content of the allowed field is mirrored in the HTTP status code.
//
Expand All @@ -70,7 +76,7 @@ type getCheckRequest struct {
MaxDepth int `json:"max-depth"`
}

// swagger:route GET /relation-tuples/check read getCheck
// swagger:route GET /relation-tuples/check/openapi read getCheck
//
// Check a relation tuple
//
Expand All @@ -87,39 +93,65 @@ type getCheckRequest struct {
// Responses:
// 200: getCheckResponse
// 400: genericError
// 403: getCheckResponse
// 500: genericError
func (h *Handler) getCheck(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
maxDepth, err := x.GetMaxDepthFromQuery(r.URL.Query())
func (h *Handler) getCheckNoStatus(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
allowed, err := h.getCheck(r.Context(), r.URL.Query())
if err != nil {
h.d.Writer().WriteError(w, r, herodot.ErrBadRequest.WithError(err.Error()))
return
}

tuple, err := (&relationtuple.InternalRelationTuple{}).FromURLQuery(r.URL.Query())
if errors.Is(err, relationtuple.ErrNilSubject) {
h.d.Writer().WriteError(w, r, herodot.ErrBadRequest.WithReason("Subject has to be specified."))
return
} else if err != nil {
h.d.Writer().WriteError(w, r, herodot.ErrBadRequest.WithError(err.Error()))
h.d.Writer().WriteError(w, r, err)
return
}
h.d.Writer().Write(w, r, &RESTResponse{Allowed: allowed})
}

allowed, err := h.d.PermissionEngine().SubjectIsAllowed(r.Context(), tuple, maxDepth)
// swagger:route GET /relation-tuples/check read getCheckMirrorStatus
//
// Check a relation tuple
//
// To learn how relation tuples and the check works, head over to [the documentation](../concepts/relation-tuples.mdx).
//
// Consumes:
// - application/x-www-form-urlencoded
//
// Produces:
// - application/json
//
// Schemes: http, https
//
// Responses:
// 200: getCheckResponse
// 400: genericError
// 403: getCheckResponse
// 500: genericError
func (h *Handler) getCheckMirrorStatus(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
allowed, err := h.getCheck(r.Context(), r.URL.Query())
if err != nil {
h.d.Writer().WriteError(w, r, err)
return
}

if allowed {
h.d.Writer().WriteCode(w, r, http.StatusOK, &RESTResponse{Allowed: true})
h.d.Writer().Write(w, r, &RESTResponse{Allowed: allowed})
return
}

h.d.Writer().WriteCode(w, r, http.StatusForbidden, &RESTResponse{Allowed: false})
h.d.Writer().WriteCode(w, r, http.StatusForbidden, &RESTResponse{Allowed: allowed})
}

func (h *Handler) getCheck(ctx context.Context, q url.Values) (bool, error) {
maxDepth, err := x.GetMaxDepthFromQuery(q)
if err != nil {
return false, err
}

tuple, err := (&relationtuple.InternalRelationTuple{}).FromURLQuery(q)
if err != nil {
return false, err
}

return h.d.PermissionEngine().SubjectIsAllowed(ctx, tuple, maxDepth)
}

// swagger:route POST /relation-tuples/check read postCheck
// swagger:route POST /relation-tuples/check/openapi read postCheck
//
// Check a relation tuple
//
Expand All @@ -136,33 +168,62 @@ func (h *Handler) getCheck(w http.ResponseWriter, r *http.Request, _ httprouter.
// Responses:
// 200: getCheckResponse
// 400: genericError
// 403: getCheckResponse
// 500: genericError
func (h *Handler) postCheck(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
maxDepth, err := x.GetMaxDepthFromQuery(r.URL.Query())
func (h *Handler) postCheckNoStatus(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
allowed, err := h.postCheck(r.Context(), r.Body, r.URL.Query())
if err != nil {
h.d.Writer().WriteError(w, r, herodot.ErrBadRequest.WithError(err.Error()))
return
}

var tuple relationtuple.InternalRelationTuple
if err := json.NewDecoder(r.Body).Decode(&tuple); err != nil {
h.d.Writer().WriteError(w, r, errors.WithStack(herodot.ErrBadRequest.WithReasonf("Unable to decode JSON payload: %s", err)))
h.d.Writer().WriteError(w, r, err)
return
}
h.d.Writer().Write(w, r, &RESTResponse{Allowed: allowed})
}

allowed, err := h.d.PermissionEngine().SubjectIsAllowed(r.Context(), &tuple, maxDepth)
// swagger:route POST /relation-tuples/check read postCheckMirrorStatus
//
// Check a relation tuple
//
// To learn how relation tuples and the check works, head over to [the documentation](../concepts/relation-tuples.mdx).
//
// Consumes:
// - application/json
//
// Produces:
// - application/json
//
// Schemes: http, https
//
// Responses:
// 200: getCheckResponse
// 400: genericError
// 403: getCheckResponse
// 500: genericError
func (h *Handler) postCheckMirrorStatus(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
allowed, err := h.postCheck(r.Context(), r.Body, r.URL.Query())
if err != nil {
h.d.Writer().WriteError(w, r, err)
return
}

if allowed {
h.d.Writer().WriteCode(w, r, http.StatusOK, &RESTResponse{Allowed: true})
h.d.Writer().Write(w, r, &RESTResponse{Allowed: allowed})
return
}

h.d.Writer().WriteCode(w, r, http.StatusForbidden, &RESTResponse{Allowed: false})
h.d.Writer().WriteCode(w, r, http.StatusForbidden, &RESTResponse{Allowed: allowed})
}

func (h *Handler) postCheck(ctx context.Context, body io.Reader, query url.Values) (bool, error) {
maxDepth, err := x.GetMaxDepthFromQuery(query)
if err != nil {
return false, err
}

var tuple relationtuple.InternalRelationTuple
if err := json.NewDecoder(body).Decode(&tuple); err != nil {
return false, errors.WithStack(err)
}

return h.d.PermissionEngine().SubjectIsAllowed(ctx, &tuple, maxDepth)
}

func (h *Handler) Check(ctx context.Context, req *rts.CheckRequest) (*rts.CheckResponse, error) {
Expand Down

0 comments on commit 07d0fbd

Please sign in to comment.