From a5c05d72506502eafbe625f251beef6fdf179ae5 Mon Sep 17 00:00:00 2001 From: Toan Nguyen Date: Sat, 16 Nov 2024 00:10:02 +0700 Subject: [PATCH] add basic auth tests --- .golangci.yml | 2 - connector/connector_test.go | 5 +- connector/internal/request.go | 8 ++- connector/testdata/auth/schema.yaml | 10 ++++ .../jsonschema/ndc-http-schema.schema.json | 14 ++++-- ndc-http-schema/schema/auth.go | 41 ++++++++++------ ndc-http-schema/schema/setting_test.go | 49 ++++++++++++++++++- 7 files changed, 104 insertions(+), 25 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 363032b..fd5bf4d 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -5,9 +5,7 @@ linters: - err113 - lll - gocognit - - execinquery - exportloopref - - gomnd - funlen - godot - gofumpt diff --git a/connector/connector_test.go b/connector/connector_test.go index 3e5046d..66fa22a 100644 --- a/connector/connector_test.go +++ b/connector/connector_test.go @@ -654,8 +654,9 @@ func createMockServer(t *testing.T, apiKey string, bearerToken string) *httptest mux.HandleFunc("/model", func(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodPost: - if r.Header.Get("api_key") != apiKey { - t.Errorf("invalid api key, expected %s, got %s", apiKey, r.Header.Get("api_key")) + user, password, ok := r.BasicAuth() + if !ok || user != "user" || password != "password" { + t.Errorf("invalid basic auth, expected user:password, got %s:%s", user, password) t.FailNow() return } diff --git a/connector/internal/request.go b/connector/internal/request.go index ce2cc51..38440c2 100644 --- a/connector/internal/request.go +++ b/connector/internal/request.go @@ -3,6 +3,7 @@ package internal import ( "bytes" "context" + "encoding/base64" "fmt" "io" "math/rand/v2" @@ -203,7 +204,12 @@ func (req *RetryableRequest) applySecurityScheme(securityScheme rest.SecuritySch case *rest.BasicAuthConfig: username := config.GetUsername() password := config.GetPassword() - req.URL.User = url.UserPassword(username, password) + if config.Header != "" { + b64Value := base64.StdEncoding.EncodeToString([]byte(username + ":" + password)) + req.Headers.Set(rest.AuthorizationHeader, "Basic "+b64Value) + } else { + req.URL.User = url.UserPassword(username, password) + } return true, nil case *rest.HTTPAuthConfig: diff --git a/connector/testdata/auth/schema.yaml b/connector/testdata/auth/schema.yaml index 330691d..099301a 100644 --- a/connector/testdata/auth/schema.yaml +++ b/connector/testdata/auth/schema.yaml @@ -15,6 +15,14 @@ settings: value: env: PET_STORE_BEARER_TOKEN scheme: bearer + basic: + type: basic + header: Authorization + username: + value: user + password: + value: password + scheme: bearer petstore_auth: type: oauth2 flows: @@ -113,6 +121,8 @@ procedures: request: url: /model method: post + security: + - basic: [] requestBody: contentType: application/json response: diff --git a/ndc-http-schema/jsonschema/ndc-http-schema.schema.json b/ndc-http-schema/jsonschema/ndc-http-schema.schema.json index 1b3bf9d..598b32f 100644 --- a/ndc-http-schema/jsonschema/ndc-http-schema.schema.json +++ b/ndc-http-schema/jsonschema/ndc-http-schema.schema.json @@ -567,7 +567,7 @@ "basic" ] }, - "user": { + "username": { "$ref": "#/$defs/EnvString" }, "password": { @@ -577,7 +577,7 @@ "type": "object", "required": [ "type", - "user", + "username", "password" ] }, @@ -593,7 +593,15 @@ "$ref": "#/$defs/EnvString" }, "header": { - "type": "string" + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Request contains a header field in the form of Authorization: Basic \u003ccredentials\u003e" }, "scheme": { "type": "string" diff --git a/ndc-http-schema/schema/auth.go b/ndc-http-schema/schema/auth.go index 5060bfe..3ed157a 100644 --- a/ndc-http-schema/schema/auth.go +++ b/ndc-http-schema/schema/auth.go @@ -168,8 +168,15 @@ func (j SecurityScheme) JSONSchema() *jsonschema.Schema { Type: "string", Enum: []any{BasicAuthScheme}, }) - basicAuthSchema.Set("user", envStringRef) + basicAuthSchema.Set("username", envStringRef) basicAuthSchema.Set("password", envStringRef) + httpAuthSchema.Set("header", &jsonschema.Schema{ + Description: "Request contains a header field in the form of Authorization: Basic ", + OneOf: []*jsonschema.Schema{ + {Type: "string"}, + {Type: "null"}, + }, + }) oauth2Schema := orderedmap.New[string, *jsonschema.Schema]() oauth2Schema.Set("type", &jsonschema.Schema{ @@ -212,7 +219,7 @@ func (j SecurityScheme) JSONSchema() *jsonschema.Schema { { Type: "object", Properties: basicAuthSchema, - Required: []string{"type", "user", "password"}, + Required: []string{"type", "username", "password"}, }, { Type: "object", @@ -399,7 +406,7 @@ func (ss APIKeyAuthConfig) GetValue() string { // // [bearer]: https://swagger.io/docs/specification/authentication/bearer-authentication type HTTPAuthConfig struct { - Type SecuritySchemeType `json:"type" mapstructure:"type" yaml:"type"` + Type SecuritySchemeType `json:"type" mapstructure:"type" yaml:"type"` Header string `json:"header" mapstructure:"header" yaml:"header"` Scheme string `json:"scheme" mapstructure:"scheme" yaml:"scheme"` Value utils.EnvString `json:"value" mapstructure:"value" yaml:"value"` @@ -457,33 +464,34 @@ func (ss HTTPAuthConfig) GetValue() string { // // [basic]: https://swagger.io/docs/specification/authentication/basic-authentication type BasicAuthConfig struct { - Type SecuritySchemeType `json:"type" mapstructure:"type" yaml:"type"` - User utils.EnvString `json:"user" mapstructure:"user" yaml:"user"` + Type SecuritySchemeType `json:"type" mapstructure:"type" yaml:"type"` + Header string `json:"header" mapstructure:"header" yaml:"header"` + Username utils.EnvString `json:"username" mapstructure:"username" yaml:"username"` Password utils.EnvString `json:"password" mapstructure:"password" yaml:"password"` // cached values - user *string + username *string password *string } // NewBasicAuthConfig creates a new BasicAuthConfig instance. -func NewBasicAuthConfig(user, password utils.EnvString) *BasicAuthConfig { +func NewBasicAuthConfig(username, password utils.EnvString) *BasicAuthConfig { return &BasicAuthConfig{ Type: BasicAuthScheme, - User: user, + Username: username, Password: password, } } // Validate if the current instance is valid func (ss *BasicAuthConfig) Validate() error { - user, err := ss.User.Get() + user, err := ss.Username.Get() if err != nil { return fmt.Errorf("BasicAuthConfig.User: %w", err) } // user and password can be empty. - ss.user = &user + ss.username = &user password, err := ss.Password.Get() if err != nil { @@ -501,11 +509,11 @@ func (ss BasicAuthConfig) GetType() SecuritySchemeType { // GetUsername get the username value func (ss BasicAuthConfig) GetUsername() string { - if ss.user != nil { - return *ss.user + if ss.username != nil { + return *ss.username } - value, _ := ss.User.Get() + value, _ := ss.Username.Get() return value } @@ -632,6 +640,7 @@ func (ss OAuth2Config) Validate() error { return fmt.Errorf("%s: %w", key, err) } } + return nil } @@ -639,7 +648,7 @@ func (ss OAuth2Config) Validate() error { // // [OpenID Connect]: https://swagger.io/docs/specification/authentication/openid-connect-discovery type OpenIDConnectConfig struct { - Type SecuritySchemeType `json:"type" mapstructure:"type" yaml:"type"` + Type SecuritySchemeType `json:"type" mapstructure:"type" yaml:"type"` OpenIDConnectURL string `json:"openIdConnectUrl" mapstructure:"openIdConnectUrl" yaml:"openIdConnectUrl"` } @@ -672,7 +681,7 @@ func (ss OpenIDConnectConfig) Validate() error { // CookieAuthConfig represents a cookie authentication configuration. type CookieAuthConfig struct { - Type SecuritySchemeType `json:"type" mapstructure:"type" yaml:"type"` + Type SecuritySchemeType `json:"type" mapstructure:"type" yaml:"type"` } var _ SecuritySchemer = &CookieAuthConfig{} @@ -696,7 +705,7 @@ func (ss CookieAuthConfig) Validate() error { // MutualTLSAuthConfig represents a mutualTLS authentication configuration. type MutualTLSAuthConfig struct { - Type SecuritySchemeType `json:"type" mapstructure:"type" yaml:"type"` + Type SecuritySchemeType `json:"type" mapstructure:"type" yaml:"type"` } var _ SecuritySchemer = &MutualTLSAuthConfig{} diff --git a/ndc-http-schema/schema/setting_test.go b/ndc-http-schema/schema/setting_test.go index c0f2267..985ffe6 100644 --- a/ndc-http-schema/schema/setting_test.go +++ b/ndc-http-schema/schema/setting_test.go @@ -42,12 +42,33 @@ func TestNDCHttpSettings(t *testing.T) { "in": "header", "name": "api_key" }, + "http": { + "type": "http", + "value": { + "env": "PET_STORE_API_KEY" + }, + "scheme": "bearer", + "header": "Authorization" + }, + "basic": { + "type": "basic", + "username": { + "value": "user" + }, + "password": { + "value": "password" + } + }, "cookie": { "type": "cookie" }, "mutualTLS": { "type": "mutualTLS" }, + "oidc": { + "type": "openIdConnect", + "openIdConnectUrl": "http://localhost:8080/oauth/token" + }, "petstore_auth": { "type": "oauth2", "flows": { @@ -88,12 +109,33 @@ func TestNDCHttpSettings(t *testing.T) { value: utils.ToPtr("api_key"), }, }, + "basic": { + SecuritySchemer: &BasicAuthConfig{ + Type: BasicAuthScheme, + Username: utils.NewEnvStringValue("user"), + Password: utils.NewEnvStringValue("password"), + username: utils.ToPtr("user"), + password: utils.ToPtr("password"), + }, + }, + "http": { + SecuritySchemer: &HTTPAuthConfig{ + Type: HTTPAuthScheme, + Header: "Authorization", + Scheme: "bearer", + Value: utils.NewEnvStringVariable("PET_STORE_API_KEY"), + value: utils.ToPtr("api_key"), + }, + }, "cookie": { SecuritySchemer: NewCookieAuthConfig(), }, "mutualTLS": { SecuritySchemer: NewMutualTLSAuthConfig(), }, + "oidc": { + SecuritySchemer: NewOpenIDConnectConfig("http://localhost:8080/oauth/token"), + }, "petstore_auth": { SecuritySchemer: &OAuth2Config{ Type: OAuth2Scheme, @@ -131,7 +173,12 @@ func TestNDCHttpSettings(t *testing.T) { } assert.DeepEqual(t, tc.expected.Headers, result.Headers) assert.DeepEqual(t, tc.expected.Security, result.Security) - assert.DeepEqual(t, tc.expected.SecuritySchemes, result.SecuritySchemes, cmp.Exporter(func(t reflect.Type) bool { return true })) + for key, expectedSS := range tc.expected.SecuritySchemes { + ss := result.SecuritySchemes[key] + ss.JSONSchema() + assert.Equal(t, expectedSS.GetType(), ss.GetType()) + assert.DeepEqual(t, expectedSS.SecuritySchemer, ss.SecuritySchemer, cmp.Exporter(func(t reflect.Type) bool { return true })) + } assert.DeepEqual(t, tc.expected.Version, result.Version) _, err := json.Marshal(tc.expected)