Skip to content

Commit

Permalink
fix up oauth validation
Browse files Browse the repository at this point in the history
Signed-off-by: Pierre Fenoll <pierrefenoll@gmail.com>
  • Loading branch information
fenollp committed Oct 20, 2022
1 parent 3b4c8d5 commit 616b713
Show file tree
Hide file tree
Showing 2 changed files with 124 additions and 96 deletions.
81 changes: 65 additions & 16 deletions openapi3/security_scheme.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"net/url"

"github.com/go-openapi/jsonpointer"

Expand Down Expand Up @@ -210,18 +211,26 @@ func (flows *OAuthFlows) Validate(ctx context.Context, opts ...ValidationOption)
ctx = WithValidationOptions(ctx, opts...)

if v := flows.Implicit; v != nil {
return v.validate(ctx, oAuthFlowTypeImplicit)
if err := v.validate(ctx, oAuthFlowTypeImplicit, opts...); err != nil {
return fmt.Errorf("the OAuth flow 'implicit' is invalid: %w", err)
}
}
if v := flows.Password; v != nil {
return v.validate(ctx, oAuthFlowTypePassword)
if err := v.validate(ctx, oAuthFlowTypePassword, opts...); err != nil {
return fmt.Errorf("the OAuth flow 'password' is invalid: %w", err)
}
}
if v := flows.ClientCredentials; v != nil {
return v.validate(ctx, oAuthFlowTypeClientCredentials)
if err := v.validate(ctx, oAuthFlowTypeClientCredentials, opts...); err != nil {
return fmt.Errorf("the OAuth flow 'clientCredentials' is invalid: %w", err)
}
}
if v := flows.AuthorizationCode; v != nil {
return v.validate(ctx, oAuthFlowAuthorizationCode)
if err := v.validate(ctx, oAuthFlowAuthorizationCode, opts...); err != nil {
return fmt.Errorf("the OAuth flow 'authorizationCode' is invalid: %w", err)
}
}
return errors.New("no OAuth flow is defined")
return nil
}

// OAuthFlow is specified by OpenAPI/Swagger standard version 3.
Expand All @@ -245,20 +254,60 @@ func (flow *OAuthFlow) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalStrictStruct(data, flow)
}

// Validate returns an error if OAuthFlow does not comply with the OpenAPI spec.
func (flow *OAuthFlow) validate(ctx context.Context, typ oAuthFlowType) error {
if typ == oAuthFlowAuthorizationCode || typ == oAuthFlowTypeImplicit {
if v := flow.AuthorizationURL; v == "" {
return errors.New("an OAuth flow is missing 'authorizationUrl in authorizationCode or implicit '")
// Validate returns an error if OAuthFlows does not comply with the OpenAPI spec.
func (flow *OAuthFlow) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)

if v := flow.RefreshURL; v != "" {
if _, err := url.Parse(v); err != nil {
return fmt.Errorf("field 'refreshUrl' is invalid: %w", err)
}
}
if typ != oAuthFlowTypeImplicit {
if v := flow.TokenURL; v == "" {
return errors.New("an OAuth flow is missing 'tokenUrl in not implicit'")

if v := flow.Scopes; len(v) == 0 {
return errors.New("field 'scopes' is empty or missing")
}

return nil
}

func (flow *OAuthFlow) validate(ctx context.Context, typ oAuthFlowType, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)

typeIn := func(types ...oAuthFlowType) bool {
for _, ty := range types {
if ty == typ {
return true
}
}
return false
}
if v := flow.Scopes; v == nil {
return errors.New("an OAuth flow is missing 'scopes'")

if in := typeIn(oAuthFlowTypeImplicit, oAuthFlowAuthorizationCode); true {
switch {
case flow.AuthorizationURL == "" && in:
return errors.New("field 'authorizationUrl' is empty or missing")
case flow.AuthorizationURL != "" && !in:
return errors.New("field 'authorizationUrl' should not be set")
case flow.AuthorizationURL != "":
if _, err := url.Parse(flow.AuthorizationURL); err != nil {
return fmt.Errorf("field 'authorizationUrl' is invalid: %w", err)
}
}
}
return nil

if in := typeIn(oAuthFlowTypePassword, oAuthFlowTypeClientCredentials, oAuthFlowAuthorizationCode); true {
switch {
case flow.TokenURL == "" && in:
return errors.New("field 'tokenUrl' is empty or missing")
case flow.TokenURL != "" && !in:
return errors.New("field 'tokenUrl' should not be set")
case flow.TokenURL != "":
if _, err := url.Parse(flow.TokenURL); err != nil {
return fmt.Errorf("field 'tokenUrl' is invalid: %w", err)
}
}
}

return flow.Validate(ctx, opts...)
}
139 changes: 59 additions & 80 deletions openapi3/security_scheme_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,95 +15,84 @@ type securitySchemeExample struct {

func TestSecuritySchemaExample(t *testing.T) {
for _, example := range securitySchemeExamples {
t.Run(example.title, testSecuritySchemaExample(t, example))
}
}

func testSecuritySchemaExample(t *testing.T, e securitySchemeExample) func(*testing.T) {
return func(t *testing.T) {
var err error
ss := &SecurityScheme{}
err = ss.UnmarshalJSON(e.raw)
require.NoError(t, err)
err = ss.Validate(context.Background())
if e.valid {
t.Run(example.title, func(t *testing.T) {
ss := &SecurityScheme{}
err := ss.UnmarshalJSON(example.raw)
require.NoError(t, err)
} else {
require.Error(t, err)
}

err = ss.Validate(context.Background())
if example.valid {
require.NoError(t, err)
} else {
require.Error(t, err)
}
})
}
}

// from https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#fixed-fields-23
var securitySchemeExamples = []securitySchemeExample{
{
title: "Basic Authentication Sample",
raw: []byte(`
{
raw: []byte(`{
"type": "http",
"scheme": "basic"
}
`),
}`),
valid: true,
},

{
title: "Negotiate Authentication Sample",
raw: []byte(`
{
raw: []byte(`{
"type": "http",
"scheme": "negotiate"
}
`),
}`),
valid: true,
},

{
title: "Unknown http Authentication Sample",
raw: []byte(`
{
raw: []byte(`{
"type": "http",
"scheme": "notvalid"
}
`),
}`),
valid: false,
},

{
title: "API Key Sample",
raw: []byte(`
{
raw: []byte(`{
"type": "apiKey",
"name": "api_key",
"in": "header"
}
`),
}`),
valid: true,
},

{
title: "apiKey with bearerFormat",
raw: []byte(`
{
raw: []byte(`{
"type": "apiKey",
"in": "header",
"name": "X-API-KEY",
"in": "header",
"name": "X-API-KEY",
"bearerFormat": "Arbitrary text"
}
`),
}`),
valid: false,
},

{
title: "Bearer Sample with arbitrary format",
raw: []byte(`
{
raw: []byte(`{
"type": "http",
"scheme": "bearer",
"bearerFormat": "Arbitrary text"
}
`),
}`),
valid: true,
},

{
title: "Implicit OAuth2 Sample",
raw: []byte(`
{
raw: []byte(`{
"type": "oauth2",
"flows": {
"implicit": {
Expand All @@ -114,14 +103,13 @@ var securitySchemeExamples = []securitySchemeExample{
}
}
}
}
`),
}`),
valid: true,
},

{
title: "OAuth Flow Object Sample",
raw: []byte(`
{
raw: []byte(`{
"type": "oauth2",
"flows": {
"implicit": {
Expand All @@ -140,14 +128,13 @@ var securitySchemeExamples = []securitySchemeExample{
}
}
}
}
`),
}`),
valid: true,
},

{
title: "OAuth Flow Object clientCredentials/password",
raw: []byte(`
{
raw: []byte(`{
"type": "oauth2",
"flows": {
"clientCredentials": {
Expand All @@ -163,79 +150,71 @@ var securitySchemeExamples = []securitySchemeExample{
}
}
}
}
`),
}`),
valid: true,
},

{
title: "Invalid Basic",
raw: []byte(`
{
raw: []byte(`{
"type": "https",
"scheme": "basic"
}
`),
}`),
valid: false,
},

{
title: "Apikey Cookie",
raw: []byte(`
{
raw: []byte(`{
"type": "apiKey",
"in": "cookie",
"name": "somecookie"
}
`),
}`),
valid: true,
},

{
title: "OAuth Flow Object with no scopes",
raw: []byte(`
{
raw: []byte(`{
"type": "oauth2",
"flows": {
"password": {
"tokenUrl": "https://example.com/api/oauth/token"
}
}
}
`),
}`),
valid: false,
},

{
title: "OAuth Flow Object with empty scopes",
raw: []byte(`
{
raw: []byte(`{
"type": "oauth2",
"flows": {
"password": {
"tokenUrl": "https://example.com/api/oauth/token",
"scopes": {}
"tokenUrl": "https://example.com/api/oauth/token",
"scopes": {}
}
}
}
`),
valid: true,
}`),
valid: false,
},

{
title: "OIDC Type With URL",
raw: []byte(`
{
raw: []byte(`{
"type": "openIdConnect",
"openIdConnectUrl": "https://example.com/.well-known/openid-configuration"
}
`),
}`),
valid: true,
},

{
title: "OIDC Type Without URL",
raw: []byte(`
{
raw: []byte(`{
"type": "openIdConnect",
"openIdConnectUrl": ""
}
`),
}`),
valid: false,
},
}

0 comments on commit 616b713

Please sign in to comment.