Skip to content

Commit

Permalink
feat: Add all json schemas endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
Kate Döen committed Aug 31, 2021
1 parent e7a237a commit d6466fc
Show file tree
Hide file tree
Showing 4 changed files with 232 additions and 13 deletions.
133 changes: 121 additions & 12 deletions schema/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"os"

"github.com/ory/kratos/driver/config"
"github.com/ory/x/urlx"

"github.com/julienschmidt/httprouter"
"github.com/pkg/errors"
Expand Down Expand Up @@ -41,11 +42,13 @@ const SchemasPath string = "schemas"

func (h *Handler) RegisterPublicRoutes(public *x.RouterPublic) {
h.r.CSRFHandler().IgnoreGlobs(fmt.Sprintf("/%s/*", SchemasPath))
public.GET(fmt.Sprintf("/%s/:id", SchemasPath), h.get)
public.GET(fmt.Sprintf("/%s/:id", SchemasPath), h.getByID)
public.GET(fmt.Sprintf("/%s", SchemasPath), h.getAll)
}

func (h *Handler) RegisterAdminRoutes(admin *x.RouterAdmin) {
admin.GET(fmt.Sprintf("/%s/:id", SchemasPath), x.RedirectToPublicRoute(h.r))
admin.GET(fmt.Sprintf("/%s", SchemasPath), x.RedirectToPublicRoute(h.r))
}

// Raw JSON Schema
Expand Down Expand Up @@ -77,34 +80,140 @@ type getJsonSchema struct {
// 200: jsonSchema
// 404: jsonError
// 500: jsonError
func (h *Handler) get(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
func (h *Handler) getByID(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
s, err := h.r.IdentityTraitsSchemas(r.Context()).GetByID(ps.ByName("id"))
if err != nil {
h.r.Writer().WriteError(w, r, errors.WithStack(herodot.ErrNotFound.WithDebugf("%+v", err)))
return
}
var src io.ReadCloser

if s.URL.Scheme == "file" {
src, err = os.Open(s.URL.Host + s.URL.Path)
ls, err := readSchema(s)
if err != nil {
h.r.Writer().WriteError(w, r, errors.WithStack(herodot.ErrInternalServerError.WithReasonf("The file for this JSON Schema ID could not be found or opened. This is a configuration issue.").WithDebugf("%+v", err)))
return
}

w.Header().Add("Content-Type", "application/json")
enc := json.NewEncoder(w)
enc.SetIndent("", " ")
if err := enc.Encode(ls); err != nil {
h.r.Writer().WriteError(w, r, errors.WithStack(herodot.ErrInternalServerError.WithReasonf("The file for this JSON Schema ID could not be found or opened. This is a configuration issue.").WithDebugf("%+v", err)))
return
}
}

// Raw JSON Schema map
//
// swagger:model jsonSchemas
// nolint:deadcode,unused
type jsonSchemas map[string]json.RawMessage

// nolint:deadcode,unused
// swagger:parameters getJsonSchemas
type getJsonSchemas struct {
// Items per Page
//
// This is the number of items per page.
//
// required: false
// in: query
// default: 100
// min: 1
// max: 500
PerPage int `json:"per_page"`

// Pagination Page
//
// required: false
// in: query
// default: 0
// min: 0
Page int `json:"page"`
}

// swagger:route GET /schemas v0alpha1 getJsonSchemas
//
// Get all JSON Schemas
//
// Produces:
// - application/json
//
// Schemes: http, https
//
// Responses:
// 200: jsonSchemas
// 500: jsonError
func (h *Handler) getAll(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
page, itemsPerPage := x.ParsePagination(r)

ids, err := h.r.IdentityTraitsSchemas(r.Context()).GetIDs()
if err != nil {
h.r.Writer().WriteError(w, r, errors.WithStack(herodot.ErrNotFound.WithDebugf("%+v", err)))
return
}

total := int64(len(ids))
skip := page * itemsPerPage
if skip > len(ids) {
skip = len(ids)
}
items := skip + itemsPerPage
if items > len(ids) {
items = len(ids)
}
ids = ids[skip:items]

ss := map[string]json.RawMessage{}

for _, id := range ids {
s, err := h.r.IdentityTraitsSchemas(r.Context()).GetByID(id)
if err != nil {
h.r.Writer().WriteError(w, r, errors.WithStack(herodot.ErrInternalServerError.WithReasonf("The file for this JSON Schema ID could not be found or opened. This is a configuration issue.").WithDebugf("%+v", err)))
h.r.Writer().WriteError(w, r, errors.WithStack(herodot.ErrNotFound.WithDebugf("%+v", err)))
return
}
defer src.Close()
} else {
resp, err := http.Get(s.URL.String())
ls, err := readSchema(s)
if err != nil {
h.r.Writer().WriteError(w, r, errors.WithStack(herodot.ErrInternalServerError.WithReasonf("The file for this JSON Schema ID could not be found or opened. This is a configuration issue.").WithDebugf("%+v", err)))
return
}
defer resp.Body.Close()
src = resp.Body
ss[id] = ls
}

x.PaginationHeader(w, urlx.AppendPaths(h.r.Config(r.Context()).SelfAdminURL(), fmt.Sprintf("/%s", SchemasPath)), total, page, itemsPerPage)
w.Header().Add("Content-Type", "application/json")
if _, err := io.Copy(w, src); err != nil {

enc := json.NewEncoder(w)
enc.SetIndent("", " ")
if err := enc.Encode(ss); err != nil {
h.r.Writer().WriteError(w, r, errors.WithStack(herodot.ErrInternalServerError.WithReasonf("The file for this JSON Schema ID could not be found or opened. This is a configuration issue.").WithDebugf("%+v", err)))
return
}
}

func readSchema(schema *Schema) (json.RawMessage, error) {
var err error
var src io.ReadCloser

if schema.URL.Scheme == "file" {
src, err = os.Open(schema.URL.Host + schema.URL.Path)
if err != nil {
return nil, err
}
defer src.Close()
} else {
resp, err := http.Get(schema.URL.String())
if err != nil {
return nil, err
}
defer resp.Body.Close()
src = resp.Body
}

var result json.RawMessage
err = json.NewDecoder(src).Decode(&result)
if err != nil {
return nil, err
}

return result, nil
}
77 changes: 76 additions & 1 deletion schema/handler_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package schema_test

import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
Expand All @@ -20,7 +21,7 @@ import (
"github.com/ory/x/urlx"
)

func TestHandler(t *testing.T) {
func TestHandlerByID(t *testing.T) {
conf, reg := internal.NewFastRegistryWithMocks(t)
router := x.NewRouterPublic()
reg.SchemaHandler().RegisterPublicRoutes(router)
Expand Down Expand Up @@ -126,3 +127,77 @@ func TestHandler(t *testing.T) {
_ = getFromTS("not-existing", http.StatusNotFound)
})
}

func TestHandlerAll(t *testing.T) {
conf, reg := internal.NewFastRegistryWithMocks(t)
router := x.NewRouterPublic()
reg.SchemaHandler().RegisterPublicRoutes(router)
ts := httptest.NewServer(router)
defer ts.Close()

schemas := schema.Schemas{
{
ID: "default",
URL: urlx.ParseOrPanic("file://./stub/identity.schema.json"),
RawURL: "file://./stub/identity.schema.json",
},
{
ID: "identity2",
URL: urlx.ParseOrPanic("file://./stub/identity-2.schema.json"),
RawURL: "file://./stub/identity-2.schema.json",
},
}

getSchemaById := func(id string) *schema.Schema {
s, err := schemas.GetByID(id)
require.NoError(t, err)
return s
}

getFromTS := func(expectCode int) []byte {
res, err := ts.Client().Get(fmt.Sprintf("%s/schemas", ts.URL))
require.NoError(t, err)
body, err := ioutil.ReadAll(res.Body)
require.NoError(t, err)
require.NoError(t, res.Body.Close())

require.EqualValues(t, expectCode, res.StatusCode, "%s", body)
return body
}

getFromFS := func(id string) string {
f, err := os.Open(strings.TrimPrefix(getSchemaById(id).RawURL, "file://"))
require.NoError(t, err)
raw, err := ioutil.ReadAll(f)
require.NoError(t, err)
require.NoError(t, f.Close())
return string(raw)
}

var schemasConfig []config.Schema
for _, s := range schemas {
if s.ID != config.DefaultIdentityTraitsSchemaID {
schemasConfig = append(schemasConfig, config.Schema{
ID: s.ID,
URL: s.RawURL,
})
}
}

conf.MustSet(config.ViperKeyPublicBaseURL, ts.URL)
conf.MustSet(config.ViperKeyDefaultIdentitySchemaURL, getSchemaById(config.DefaultIdentityTraitsSchemaID).RawURL)
conf.MustSet(config.ViperKeyIdentitySchemas, schemasConfig)

t.Run("case=get all schemas", func(t *testing.T) {
body := getFromTS(http.StatusOK)

var result map[string]json.RawMessage
require.NoError(t, json.Unmarshal(body, &result))
for _, s := range schemas {
require.Contains(t, result, s.ID)
p, err := json.MarshalIndent(result[s.ID], "", " ")
require.NoError(t, err)
require.EqualValues(t, getFromFS(s.ID), string(p)+"\n")
}
})
}
10 changes: 10 additions & 0 deletions schema/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@ func (s Schemas) GetByID(id string) (*Schema, error) {
return nil, errors.WithStack(herodot.ErrBadRequest.WithReasonf("Unable to find JSON Schema ID: %s", id))
}

func (s Schemas) GetIDs() ([]string, error) {
ids := []string{}

for _, ss := range s {
ids = append(ids, ss.ID)
}

return ids, nil
}

var orderedKeyCacheMutex sync.RWMutex
var orderedKeyCache map[string][]string

Expand Down
25 changes: 25 additions & 0 deletions schema/schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,31 @@ func TestSchemas_GetByID(t *testing.T) {
})
}

func TestSchemas_GetIDs(t *testing.T) {
ss := Schemas{
Schema{
ID: "foo",
},
Schema{
ID: "bar",
},
Schema{
ID: "foobar",
},
Schema{
ID: config.DefaultIdentityTraitsSchemaID,
},
}

t.Run("case=get all schema IDs", func(t *testing.T) {
s, err := ss.GetIDs()
require.NoError(t, err)
for _, schema := range ss {
assert.Contains(t, s, schema.ID)
}
})
}

func TestGetKeysInOrder(t *testing.T) {
for i, tc := range []struct {
schemaRef string
Expand Down

0 comments on commit d6466fc

Please sign in to comment.