Skip to content

Commit

Permalink
fix: declare map[string]interface{} as extensible objects
Browse files Browse the repository at this point in the history
Closes #12
  • Loading branch information
aeneasr committed May 18, 2020
1 parent 7057de2 commit 1d4ec85
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 66 deletions.
133 changes: 93 additions & 40 deletions swagutil/cmd/sanitize.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,54 +6,100 @@ import (
"strconv"
"strings"

"github.com/ory/x/cmdx"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/tidwall/gjson"
"github.com/tidwall/sjson"

"github.com/ory/x/cmdx"
)

func sanitizeIter(raw string) string {
result := raw
gjson.Parse(raw).ForEach(func(key, value gjson.Result) bool {
var err error
if !key.Exists() {
return true
}
func escapeKey(key gjson.Result) string {
return strings.ReplaceAll(key.Str, ".", "\\.")
}

func jp(elems []string) string {
return strings.Join(elems, ".")
}

switch value.Type {
case gjson.JSON:
if value.IsArray() {
r2 := value.Raw
i := 0
value.ForEach(func(k2, v2 gjson.Result) bool {
if v2.Type != gjson.JSON {
return true
}

r2, err = sjson.SetRaw(r2, strconv.Itoa(i), sanitizeIter(v2.Raw))
cmdx.Must(err, "could not update path (%s - %s): %s", strconv.Itoa(i), v2.Raw, err)
i++
return true
})
value.Raw = r2
func check(err error) {
if err != nil {
panic(err)
}
}

func traverse(
document string,
key, value gjson.Result,
paths []string,
cbs ...func(document string, k, _ gjson.Result, paths []string) string,
) string {
switch value.Type {
case gjson.JSON:
if value.IsArray() {
for _, cb := range cbs {
document = cb(document, key, value, paths)
}

result, err = sjson.SetRaw(result, strings.ReplaceAll(key.String(), ".", "\\."), sanitizeIter(value.Raw))
cmdx.Must(err, "could not update path (%s - %s): %s", key.Raw, value.Raw, err)
case gjson.String:
switch key.String() {
case "x-go-package":
fallthrough
case "x-go-name":
result, err = sjson.Delete(result, key.String())
cmdx.Must(err, "could not delete path (%s - %s): %s", key.Raw, value.Raw, err)
var i int
value.ForEach(func(_, item gjson.Result) bool {
path := append(paths, strconv.Itoa(i))
document = traverse(document, gjson.Result{}, item, path, cbs...)
i++
return true
})
} else if value.IsObject() {
for _, cb := range cbs {
document = cb(document, key, value, paths)
}

value.ForEach(func(key, value gjson.Result) bool {
path := append(paths, escapeKey(key))
document = traverse(document, key, value, path, cbs...)
return true
})
}
return true
})
return result
default:
for _, cb := range cbs {
document = cb(document, key, value, paths)
}
}
return document
}

func makeMapStringInterfacePolymorph(document string, key, value gjson.Result, paths []string) string {
// First we check if type conforms to
//
// `{type:"object",additionalProperties: {type:"object"}`
if !value.IsObject() ||
value.Get("type").String() != "object" ||
!value.Get("additionalProperties").IsObject() ||
value.Get("additionalProperties.type").String() != "object" {
return document
}

// Type conforms, let's fix:
//
// * https://github.com/go-swagger/go-swagger/issues/1402
// * https://github.com/ory/sdk/issues/12
document, err := sjson.Set(document, jp(append(paths, "additionalProperties")), true)
check(err)

return document
}

func removeTypeAnnotations(document string, k, value gjson.Result, paths []string) string {
if value.Type != gjson.String {
return document
}
switch k.String() {
case "x-go-package":
fallthrough
case "x-go-name":
result, err := sjson.Delete(document, jp(paths))
check(err)
return result
}
return document
}

func sanitize(in string, out string) error {
Expand All @@ -62,15 +108,22 @@ func sanitize(in string, out string) error {
return errors.Wrapf(err, "unable to read file")
}

result := []byte(sanitizeIter(string(file)))
result, err = sjson.SetRawBytes(result, "definitions.UUID", []byte(`{"type": "string", "format": "uuid4"}`))
document := string(file)
gjson.Parse(document).ForEach(func(k, v gjson.Result) bool {
document = traverse(document, k, v, []string{escapeKey(k)},
removeTypeAnnotations,
makeMapStringInterfacePolymorph)
return true
})

document, err = sjson.SetRaw(document, "definitions.UUID", `{"type": "string", "format": "uuid4"}`)
if err != nil {
return errors.Wrap(err, "could not set definitions.UUID")
}

_ = os.Remove(out)

return errors.Wrapf(ioutil.WriteFile(out, result, 0766), "unable to write file")
return errors.Wrapf(ioutil.WriteFile(out, []byte(document), 0766), "unable to write file")
}

// sanitizeCmd represents the sanitize command
Expand Down
6 changes: 2 additions & 4 deletions swagutil/cmd/sanitize_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (

func TestSanitize(t *testing.T) {
fp := filepath.Join(os.TempDir(), uuid.New().String()+".json")
defer os.Remove(fp)
// defer os.Remove(fp)

require.NoError(t, sanitize("stub/in.json", fp))

Expand All @@ -26,7 +26,5 @@ func TestSanitize(t *testing.T) {
require.NotEmpty(t, actual)
require.NotEmpty(t, expected)

assert.JSONEq(t, string(expected), string(actual))

// t.Logf("wrote to: %s",fp)
assert.JSONEq(t, string(expected), string(actual), fp)
}
28 changes: 7 additions & 21 deletions swagutil/cmd/stub/expected.json
Original file line number Diff line number Diff line change
Expand Up @@ -1983,9 +1983,7 @@
"context": {
"description": "Context is an optional object which can hold arbitrary data. The data will be made available when fetching the\nconsent request under the \"context\" field. This is useful in scenarios where login and consent endpoints share\ndata.",
"type": "object",
"additionalProperties": {
"type": "object"
}
"additionalProperties":true
},
"force_subject_identifier": {
"description": "ForceSubjectIdentifier forces the \"pairwise\" user ID of the end-user that authenticated. The \"pairwise\" user ID refers to the\n(Pairwise Identifier Algorithm)[http://openid.net/specs/openid-connect-core-1_0.html#PairwiseAlg] of the OpenID\nConnect specification. It allows you to set an obfuscated subject (\"user\") identifier that is unique to the client.\n\nPlease note that this changes the user ID on endpoint /userinfo and sub claim of the ID Token. It does not change the\nsub claim in the OAuth 2.0 Introspection.\n\nPer default, ORY Hydra handles this value with its own algorithm. In case you want to set this yourself\nyou can use this field. Please note that setting this field has no effect if `pairwise` is not configured in\nORY Hydra or the OAuth 2.0 Client does not expect a pairwise identifier (set via `subject_type` key in the client's\nconfiguration).\n\nPlease also be aware that ORY Hydra is unable to properly compute this value during authentication. This implies\nthat you have to compute this value on every authentication process (probably depending on the client ID or some\nother unique value).\n\nIf you fail to compute the proper value, then authentication processes which have id_token_hint set might fail.",
Expand Down Expand Up @@ -2034,9 +2032,7 @@
"context": {
"description": "Context contains arbitrary information set by the login endpoint or is empty if not set.",
"type": "object",
"additionalProperties": {
"type": "object"
}
"additionalProperties":true
},
"login_challenge": {
"description": "LoginChallenge is the login challenge this consent challenge belongs to. It can be used to associate\na login and consent request in the login & consent app.",
Expand Down Expand Up @@ -2084,16 +2080,12 @@
"access_token": {
"description": "AccessToken sets session data for the access and refresh token, as well as any future tokens issued by the\nrefresh grant. Keep in mind that this data will be available to anyone performing OAuth 2.0 Challenge Introspection.\nIf only your services can perform OAuth 2.0 Challenge Introspection, this is usually fine. But if third parties\ncan access that endpoint as well, sensitive data from the session might be exposed to them. Use with care!",
"type": "object",
"additionalProperties": {
"type": "object"
}
"additionalProperties": true
},
"id_token": {
"description": "IDToken sets session data for the OpenID Connect ID token. Keep in mind that the session'id payloads are readable\nby anyone that has access to the ID Challenge. Use with care!",
"type": "object",
"additionalProperties": {
"type": "object"
}
"additionalProperties": true
}
}
},
Expand Down Expand Up @@ -2338,9 +2330,7 @@
"metadata": {
"description": "Metadata is arbitrary data.",
"type": "object",
"additionalProperties": {
"type": "object"
}
"additionalProperties": true
},
"owner": {
"description": "Owner is a string identifying the owner of the OAuth 2.0 Client.",
Expand Down Expand Up @@ -2446,9 +2436,7 @@
"ext": {
"description": "Extra is arbitrary data set by the session.",
"type": "object",
"additionalProperties": {
"type": "object"
}
"additionalProperties": true
},
"iat": {
"description": "Issued at is an integer timestamp, measured in the number of seconds\nsince January 1 1970 UTC, indicating when this token was\noriginally issued.",
Expand Down Expand Up @@ -2562,9 +2550,7 @@
"id_token_hint_claims": {
"description": "IDTokenHintClaims are the claims of the ID Token previously issued by the Authorization Server being passed as a hint about the\nEnd-User's current or past authenticated session with the Client.",
"type": "object",
"additionalProperties": {
"type": "object"
}
"additionalProperties":true
},
"login_hint": {
"description": "LoginHint hints about the login identifier the End-User might use to log in (if necessary).\nThis hint can be used by an RP if it first asks the End-User for their e-mail address (or other identifier)\nand then wants to pass that value as a hint to the discovered authorization service. This value MAY also be a\nphone number in the format specified for the phone_number Claim. The use of this parameter is optional.",
Expand Down
2 changes: 1 addition & 1 deletion swagutil/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ require (
github.com/pkg/errors v0.8.1
github.com/spf13/cobra v0.0.5
github.com/spf13/viper v1.6.1
github.com/stretchr/testify v1.4.0
github.com/stretchr/testify v1.5.1
github.com/tidwall/gjson v1.3.5
github.com/tidwall/sjson v1.0.4
)
2 changes: 2 additions & 0 deletions swagutil/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,8 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/subosito/gotenv v1.1.1/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
Expand Down

0 comments on commit 1d4ec85

Please sign in to comment.