diff --git a/apidef/api_definitions.go b/apidef/api_definitions.go index a8910814bda..a4027a5e30b 100644 --- a/apidef/api_definitions.go +++ b/apidef/api_definitions.go @@ -808,9 +808,30 @@ type UpstreamOAuth struct { Enabled bool `bson:"enabled" json:"enabled"` // ClientCredentials holds the client credentials for upstream OAuth2 authentication. ClientCredentials ClientCredentials `bson:"client_credentials" json:"client_credentials"` - // HeaderName is the custom header name to be used for upstream basic authentication. + // PasswordAuthentication holds the configuration for upstream OAauth password authentication flow. + PasswordAuthentication PasswordAuthentication `bson:"password_authentication,omitempty" json:"passwordAuthentication,omitempty"` +} + +// PasswordAuthentication holds the configuration for upstream OAuth2 password authentication flow. +type PasswordAuthentication struct { + ClientAuthData + // Enabled activates upstream OAuth2 password authentication. + Enabled bool `bson:"enabled" json:"enabled"` + // Username is the username to be used for upstream OAuth2 password authentication. + Username string `bson:"username" json:"username"` + // Password is the password to be used for upstream OAuth2 password authentication. + Password string `bson:"password" json:"password"` + // TokenURL is the resource server's token endpoint + // URL. This is a constant specific to each server. + TokenURL string `bson:"token_url" json:"token_url"` + // Scopes specifies optional requested permissions. + Scopes []string `bson:"scopes" json:"scopes,omitempty"` + // HeaderName is the custom header name to be used for OAuth password authentication flow. // Defaults to `Authorization`. - HeaderName string `bson:"header_name" json:"header_name,omitempty"` + HeaderName string `bson:"header_name" json:"header_name"` + + // TokenProvider is the OAuth2 password authentication flow token for internal use. + Token *oauth2.Token `bson:"-" json:"-"` } // ClientAuthData holds the client ID and secret for upstream OAuth2 authentication. @@ -824,11 +845,16 @@ type ClientAuthData struct { // ClientCredentials holds the client credentials for upstream OAuth2 authentication. type ClientCredentials struct { ClientAuthData + // Enabled activates upstream OAuth2 client credentials authentication. + Enabled bool `bson:"enabled" json:"enabled"` // TokenURL is the resource server's token endpoint // URL. This is a constant specific to each server. TokenURL string `bson:"token_url" json:"token_url"` // Scopes specifies optional requested permissions. Scopes []string `bson:"scopes" json:"scopes,omitempty"` + // HeaderName is the custom header name to be used for OAuth client credential flow authentication. + // Defaults to `Authorization`. + HeaderName string `bson:"header_name" json:"header_name"` // TokenProvider is the OAuth2 token provider for internal use. TokenProvider oauth2.TokenSource `bson:"-" json:"-"` diff --git a/apidef/oas/schema/x-tyk-api-gateway.json b/apidef/oas/schema/x-tyk-api-gateway.json index 176cfe8fff1..9571ba9270c 100644 --- a/apidef/oas/schema/x-tyk-api-gateway.json +++ b/apidef/oas/schema/x-tyk-api-gateway.json @@ -2043,6 +2043,9 @@ "clientCredentials": { "type": "object", "properties": { + "enabled": { + "type": "boolean" + }, "clientId": { "type": "string" }, @@ -2052,13 +2055,48 @@ "tokenUrl": { "type": "string" }, - "scopes":{ - "type": ["array", "null"] + "scopes": { + "type": [ + "array", + "null" + ] + }, + "headerName": { + "type": "string" } } }, - "headerName": { - "type": "string" + "passwordAuthentication": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "clientId": { + "type": "string" + }, + "clientSecret": { + "type": "string" + }, + "tokenUrl": { + "type": "string" + }, + "scopes": { + "type": [ + "array", + "null" + ] + }, + "username": { + "type": "string" + }, + "password": { + "type": "string" + }, + "headerName": { + "type": "string" + } + } } } } diff --git a/apidef/oas/upstream.go b/apidef/oas/upstream.go index b4367158a3f..3db4bb02d16 100644 --- a/apidef/oas/upstream.go +++ b/apidef/oas/upstream.go @@ -639,34 +639,72 @@ type UpstreamOAuth struct { Enabled bool `bson:"enabled" json:"enabled"` // ClientCredentials holds the configuration for OAuth2 Client Credentials flow. ClientCredentials *ClientCredentials `bson:"clientCredentials,omitempty" json:"clientCredentials,omitempty"` - // HeaderName is the custom header name to be used for upstream basic authentication. + // PasswordAuthentication holds the configuration for upstream OAauth password authentication flow. + PasswordAuthentication *PasswordAuthentication `bson:"passwordAuthentication,omitempty" json:"passwordAuthentication,omitempty"` +} + +// PasswordAuthentication holds the configuration for upstream OAuth2 password authentication flow. +type PasswordAuthentication struct { + ClientAuthData + // Enabled activates upstream OAuth2 password authentication. + Enabled bool `bson:"enabled" json:"enabled"` + // Username is the username to be used for upstream OAuth2 password authentication. + Username string `bson:"username" json:"username"` + // Password is the password to be used for upstream OAuth2 password authentication. + Password string `bson:"password" json:"password"` + // TokenURL is the resource server's token endpoint + // URL. This is a constant specific to each server. + TokenURL string `bson:"tokenURL" json:"tokenURL"` + // Scopes specifies optional requested permissions. + Scopes []string `bson:"scopes" json:"scopes,omitempty"` + // HeaderName is the custom header name to be used for OAuth password authentication flow. // Defaults to `Authorization`. HeaderName string `bson:"headerName" json:"headerName"` } -// ClientCredentials holds the configuration for OAuth2 Client Credentials flow. -type ClientCredentials struct { +// ClientAuthData holds the client ID and secret for OAuth2 authentication. +type ClientAuthData struct { // ClientID is the application's ID. - ClientID string `bson:"clientID" json:"clientID"` + ClientID string `bson:"clientId" json:"clientId"` // ClientSecret is the application's secret. ClientSecret string `bson:"clientSecret" json:"clientSecret"` +} + +// ClientCredentials holds the configuration for OAuth2 Client Credentials flow. +type ClientCredentials struct { + ClientAuthData + // Enabled activates upstream OAuth2 client credentials authentication. + Enabled bool `bson:"enabled" json:"enabled"` // TokenURL is the resource server's token endpoint // URL. This is a constant specific to each server. TokenURL string `bson:"tokenURL" json:"tokenURL"` // Scopes specifies optional requested permissions. Scopes []string `bson:"scopes,omitempty" json:"scopes,omitempty"` + // HeaderName is the custom header name to be used for OAuth client credential flow authentication. + // Defaults to `Authorization`. + HeaderName string `bson:"headerName" json:"headerName"` } func (c *ClientCredentials) Fill(api apidef.ClientCredentials) { + c.Enabled = api.Enabled c.ClientID = api.ClientID c.ClientSecret = api.ClientSecret c.TokenURL = api.TokenURL c.Scopes = api.Scopes + c.HeaderName = api.HeaderName +} + +func (p *PasswordAuthentication) Fill(api apidef.PasswordAuthentication) { + p.Enabled = api.Enabled + p.Username = api.Username + p.Password = api.Password + p.TokenURL = api.TokenURL + p.Scopes = api.Scopes + p.HeaderName = api.HeaderName } func (u *UpstreamOAuth) Fill(api apidef.UpstreamOAuth) { u.Enabled = api.Enabled - u.HeaderName = api.HeaderName if u.ClientCredentials == nil { u.ClientCredentials = &ClientCredentials{} @@ -675,18 +713,36 @@ func (u *UpstreamOAuth) Fill(api apidef.UpstreamOAuth) { if ShouldOmit(u.ClientCredentials) { u.ClientCredentials = nil } + + if u.PasswordAuthentication == nil { + u.PasswordAuthentication = &PasswordAuthentication{} + } + u.PasswordAuthentication.Fill(api.PasswordAuthentication) + if ShouldOmit(u.PasswordAuthentication) { + u.PasswordAuthentication = nil + } } func (c *ClientCredentials) ExtractTo(api *apidef.ClientCredentials) { + api.Enabled = c.Enabled api.ClientID = c.ClientID api.ClientSecret = c.ClientSecret api.TokenURL = c.TokenURL api.Scopes = c.Scopes + api.HeaderName = c.HeaderName +} + +func (p *PasswordAuthentication) ExtractTo(api *apidef.PasswordAuthentication) { + api.Enabled = p.Enabled + api.Username = p.Username + api.Password = p.Password + api.TokenURL = p.TokenURL + api.Scopes = p.Scopes + api.HeaderName = p.HeaderName } func (u *UpstreamOAuth) ExtractTo(api *apidef.UpstreamOAuth) { api.Enabled = u.Enabled - api.HeaderName = u.HeaderName if u.ClientCredentials == nil { u.ClientCredentials = &ClientCredentials{} @@ -695,4 +751,12 @@ func (u *UpstreamOAuth) ExtractTo(api *apidef.UpstreamOAuth) { }() } u.ClientCredentials.ExtractTo(&api.ClientCredentials) + + if u.PasswordAuthentication == nil { + u.PasswordAuthentication = &PasswordAuthentication{} + defer func() { + u.PasswordAuthentication = nil + }() + } + u.PasswordAuthentication.ExtractTo(&api.PasswordAuthentication) } diff --git a/apidef/schema.go b/apidef/schema.go index f54a037902c..c0619e3960b 100644 --- a/apidef/schema.go +++ b/apidef/schema.go @@ -794,6 +794,9 @@ const Schema = `{ "client_credentials": { "type": "object", "properties": { + "enabled": { + "type": "boolean" + }, "client_id": { "type": "string" }, @@ -805,11 +808,41 @@ const Schema = `{ }, "scopes":{ "type": ["array", "null"] - } + }, + "header_name": { + "type": "string" + } } }, - "header_name": { - "type": "string" + "password_authentication": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "client_id": { + "type": "string" + }, + "client_secret": { + "type": "string" + }, + "username": { + "type": "string" + }, + "password": { + "type": "string" + }, + "token_url": { + "type": "string" + }, + "scopes": { + "type": ["array", "null"] + }, + "header_name": { + "type": "string" + } + } + } } } } diff --git a/gateway/mw_oauth2_auth.go b/gateway/mw_oauth2_auth.go index 085b22472c5..a8491f8dd1a 100644 --- a/gateway/mw_oauth2_auth.go +++ b/gateway/mw_oauth2_auth.go @@ -28,20 +28,70 @@ const ( type OAuthHeaderProvider interface { // getOAuthToken returns the OAuth token for the request. getOAuthToken(r *http.Request, OAuthSpec *UpstreamOAuth) (string, error) + getHeaderName(OAuthSpec *UpstreamOAuth) string } type ClientCredentialsOAuthProvider struct{} type PerAPIClientCredentialsOAuthProvider struct{} -func newUpstreamOAuthClientCredentialsCache(connectionHandler *storage.ConnectionHandler) *upstreamOAuthClientCredentialsCache { +type PasswordOAuthProvider struct{} + +func newUpstreamOAuthClientCredentialsCache(connectionHandler *storage.ConnectionHandler) UpstreamOAuthCache { return &upstreamOAuthClientCredentialsCache{RedisCluster: storage.RedisCluster{KeyPrefix: "upstreamOAuthCC-", ConnectionHandler: connectionHandler}} } +func newUpstreamOAuthPasswordCache(connectionHandler *storage.ConnectionHandler) UpstreamOAuthCache { + return &upstreamOAuthPasswordCache{RedisCluster: storage.RedisCluster{KeyPrefix: "upstreamOAuthPW-", ConnectionHandler: connectionHandler}} +} + type upstreamOAuthClientCredentialsCache struct { storage.RedisCluster } +type upstreamOAuthPasswordCache struct { + storage.RedisCluster +} + +func (cache *upstreamOAuthPasswordCache) getToken(r *http.Request, OAuthSpec *UpstreamOAuth) (string, error) { + cacheKey := generatePasswordOAuthCacheKey(OAuthSpec.Spec.UpstreamAuth.OAuth, OAuthSpec.Spec.APIID) + + tokenString, err := retryGetKeyAndLock(cacheKey, &cache.RedisCluster) + if err != nil { + return "", err + } + + if tokenString != "" { + decryptedToken := decrypt(getPaddedSecret(OAuthSpec.Gw.GetConfig().Secret), tokenString) + return decryptedToken, nil + } + + token, err := cache.obtainToken(r.Context(), OAuthSpec) + if err != nil { + return "", err + } + + encryptedToken := encrypt(getPaddedSecret(OAuthSpec.Gw.GetConfig().Secret), token.AccessToken) + + ttl := time.Until(token.Expiry) + if err := setTokenInCache(cacheKey, encryptedToken, ttl, &cache.RedisCluster); err != nil { + return "", err + } + + return token.AccessToken, nil +} + +func (cache *upstreamOAuthPasswordCache) obtainToken(ctx context.Context, OAuthSpec *UpstreamOAuth) (*oauth2.Token, error) { + cfg := newOAuth2PasswordConfig(OAuthSpec) + + token, err := cfg.PasswordCredentialsToken(ctx, OAuthSpec.Spec.UpstreamAuth.OAuth.PasswordAuthentication.Username, OAuthSpec.Spec.UpstreamAuth.OAuth.PasswordAuthentication.Password) + if err != nil { + return &oauth2.Token{}, err + } + + return token, nil +} + type UpstreamOAuthCache interface { // getToken returns the token from cache or issues a request to obtain it from the OAuth provider. getToken(r *http.Request, OAuthSpec *UpstreamOAuth) (string, error) @@ -81,10 +131,6 @@ func (OAuthSpec *UpstreamOAuth) ProcessRequest(_ http.ResponseWriter, r *http.Re HeaderName: header.Authorization, } - if oauthConfig.HeaderName != "" { - upstreamOAuthProvider.HeaderName = oauthConfig.HeaderName - } - provider, err := getOAuthHeaderProvider(oauthConfig) if err != nil { return fmt.Errorf("failed to get OAuth header provider: %w", err), http.StatusInternalServerError @@ -96,14 +142,30 @@ func (OAuthSpec *UpstreamOAuth) ProcessRequest(_ http.ResponseWriter, r *http.Re } upstreamOAuthProvider.AuthValue = payload + headerName := provider.getHeaderName(OAuthSpec) + if headerName != "" { + upstreamOAuthProvider.HeaderName = headerName + } httputil.SetUpstreamAuth(r, upstreamOAuthProvider) return nil, http.StatusOK } func getOAuthHeaderProvider(oauthConfig apidef.UpstreamOAuth) (OAuthHeaderProvider, error) { - // to be extended when PasswordAuth is implemented - return &ClientCredentialsOAuthProvider{}, nil + if !oauthConfig.IsEnabled() { + return nil, fmt.Errorf("upstream OAuth is not enabled") + } + + switch { + case oauthConfig.ClientCredentials.Enabled && oauthConfig.PasswordAuthentication.Enabled: + return nil, fmt.Errorf("both client credentials and password authentication are provided") + case oauthConfig.ClientCredentials.Enabled: + return &ClientCredentialsOAuthProvider{}, nil + case oauthConfig.PasswordAuthentication.Enabled: + return &PasswordOAuthProvider{}, nil + default: + return nil, fmt.Errorf("no valid OAuth configuration provided") + } } func (p *PerAPIClientCredentialsOAuthProvider) getOAuthHeaderValue(r *http.Request, OAuthSpec *UpstreamOAuth) (string, error) { @@ -143,7 +205,41 @@ func (p *ClientCredentialsOAuthProvider) getOAuthToken(r *http.Request, OAuthSpe return fmt.Sprintf("Bearer %s", token), nil } -func generateCacheKey(config apidef.UpstreamOAuth, apiId string) string { +func (p *ClientCredentialsOAuthProvider) getHeaderName(OAuthSpec *UpstreamOAuth) string { + return OAuthSpec.Spec.UpstreamAuth.OAuth.ClientCredentials.HeaderName +} + +func (p *PasswordOAuthProvider) getOAuthToken(r *http.Request, OAuthSpec *UpstreamOAuth) (string, error) { + if OAuthSpec.Gw.UpstreamOAuthCache == nil { + OAuthSpec.Gw.UpstreamOAuthCache = newUpstreamOAuthPasswordCache(OAuthSpec.Gw.StorageConnectionHandler) + } + + token, err := OAuthSpec.Gw.UpstreamOAuthCache.getToken(r, OAuthSpec) + if err != nil { + return handleOAuthError(r, OAuthSpec, err) + } + + return fmt.Sprintf("Bearer %s", token), nil +} + +func (p *PasswordOAuthProvider) getHeaderName(OAuthSpec *UpstreamOAuth) string { + return OAuthSpec.Spec.UpstreamAuth.OAuth.PasswordAuthentication.HeaderName +} + +func generatePasswordOAuthCacheKey(config apidef.UpstreamOAuth, apiId string) string { + key := fmt.Sprintf( + "%s|%s|%s|%s", + apiId, + config.PasswordAuthentication.ClientID, + config.PasswordAuthentication.ClientSecret, + strings.Join(config.PasswordAuthentication.Scopes, ",")) + + hash := sha256.New() + hash.Write([]byte(key)) + return hex.EncodeToString(hash.Sum(nil)) +} + +func generateClientCredentialsCacheKey(config apidef.UpstreamOAuth, apiId string) string { key := fmt.Sprintf( "%s|%s|%s|%s", apiId, @@ -157,7 +253,7 @@ func generateCacheKey(config apidef.UpstreamOAuth, apiId string) string { } func (cache *upstreamOAuthClientCredentialsCache) getToken(r *http.Request, OAuthSpec *UpstreamOAuth) (string, error) { - cacheKey := generateCacheKey(OAuthSpec.Spec.UpstreamAuth.OAuth, OAuthSpec.Spec.APIID) + cacheKey := generateClientCredentialsCacheKey(OAuthSpec.Spec.UpstreamAuth.OAuth, OAuthSpec.Spec.APIID) tokenString, err := retryGetKeyAndLock(cacheKey, &cache.RedisCluster) if err != nil { @@ -165,7 +261,7 @@ func (cache *upstreamOAuthClientCredentialsCache) getToken(r *http.Request, OAut } if tokenString != "" { - decryptedToken := decrypt(getPaddedSecret(OAuthSpec.Gw), tokenString) + decryptedToken := decrypt(getPaddedSecret(OAuthSpec.Gw.GetConfig().Secret), tokenString) return decryptedToken, nil } @@ -174,7 +270,7 @@ func (cache *upstreamOAuthClientCredentialsCache) getToken(r *http.Request, OAut return "", err } - encryptedToken := encrypt(getPaddedSecret(OAuthSpec.Gw), token.AccessToken) + encryptedToken := encrypt(getPaddedSecret(OAuthSpec.Gw.GetConfig().Secret), token.AccessToken) ttl := time.Until(token.Expiry) if err := setTokenInCache(cacheKey, encryptedToken, ttl, &cache.RedisCluster); err != nil { @@ -218,6 +314,17 @@ func newOAuth2ClientCredentialsConfig(OAuthSpec *UpstreamOAuth) oauth2clientcred } } +func newOAuth2PasswordConfig(OAuthSpec *UpstreamOAuth) oauth2.Config { + return oauth2.Config{ + ClientID: OAuthSpec.Spec.UpstreamAuth.OAuth.PasswordAuthentication.ClientID, + ClientSecret: OAuthSpec.Spec.UpstreamAuth.OAuth.PasswordAuthentication.ClientSecret, + Endpoint: oauth2.Endpoint{ + TokenURL: OAuthSpec.Spec.UpstreamAuth.OAuth.PasswordAuthentication.TokenURL, + }, + Scopes: OAuthSpec.Spec.UpstreamAuth.OAuth.PasswordAuthentication.Scopes, + } +} + func (cache *upstreamOAuthClientCredentialsCache) obtainToken(ctx context.Context, OAuthSpec *UpstreamOAuth) (*oauth2.Token, error) { cfg := newOAuth2ClientCredentialsConfig(OAuthSpec) diff --git a/gateway/mw_oauth2_auth_test.go b/gateway/mw_oauth2_auth_test.go index 85d5e906d23..1a183a9d98b 100644 --- a/gateway/mw_oauth2_auth_test.go +++ b/gateway/mw_oauth2_auth_test.go @@ -21,37 +21,39 @@ func TestUpstreamOauth2(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.String() != "/token" { - t.Errorf("authenticate client request URL = %q; want %q", r.URL, "/token") + assert.Fail(t, "authenticate client request URL = %q; want %q", r.URL, "/token") } headerAuth := r.Header.Get("Authorization") if headerAuth != "Basic Q0xJRU5UX0lEOkNMSUVOVF9TRUNSRVQ=" { - t.Errorf("Unexpected authorization header, %v is found.", headerAuth) + assert.Fail(t, "Unexpected authorization header, %v is found.", headerAuth) } if got, want := r.Header.Get("Content-Type"), "application/x-www-form-urlencoded"; got != want { - t.Errorf("Content-Type header = %q; want %q", got, want) + assert.Fail(t, "Content-Type header = %q; want %q", got, want) } body, err := io.ReadAll(r.Body) if err != nil { r.Body.Close() } if err != nil { - t.Errorf("failed reading request body: %s.", err) + assert.Fail(t, "failed reading request body: %s.", err) } if string(body) != "grant_type=client_credentials&scope=scope1+scope2" { - t.Errorf("payload = %q; want %q", string(body), "grant_type=client_credentials&scope=scope1+scope2") + assert.Fail(t, "payload = %q; want %q", string(body), "grant_type=client_credentials&scope=scope1+scope2") } w.Header().Set("Content-Type", "application/x-www-form-urlencoded") w.Write([]byte("access_token=90d64460d14870c08c81352a05dedd3465940a7c&token_type=bearer")) })) - defer ts.Close() + defer t.Cleanup(func() { ts.Close() }) cfg := apidef.ClientCredentials{ + Enabled: true, ClientAuthData: apidef.ClientAuthData{ ClientID: "CLIENT_ID", ClientSecret: "CLIENT_SECRET", }, - TokenURL: ts.URL + "/token", - Scopes: []string{"scope1", "scope2"}, + TokenURL: ts.URL + "/token", + Scopes: []string{"scope1", "scope2"}, + HeaderName: "", } tst.Gw.BuildAndLoadAPI( @@ -63,7 +65,6 @@ func TestUpstreamOauth2(t *testing.T) { OAuth: apidef.UpstreamOAuth{ Enabled: true, ClientCredentials: cfg, - HeaderName: "", }, } spec.Proxy.StripListenPath = true @@ -91,3 +92,85 @@ func TestUpstreamOauth2(t *testing.T) { }...) } + +func TestPasswordCredentialsTokenRequest(t *testing.T) { + tst := StartTest(nil) + t.Cleanup(tst.Close) + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + expected := "/token" + if r.URL.String() != expected { + assert.Fail(t, "URL = %q; want %q", r.URL, expected) + } + headerAuth := r.Header.Get("Authorization") + expected = "Basic Q0xJRU5UX0lEOkNMSUVOVF9TRUNSRVQ=" + if headerAuth != expected { + assert.Fail(t, "Authorization header = %q; want %q", headerAuth, expected) + } + headerContentType := r.Header.Get("Content-Type") + expected = "application/x-www-form-urlencoded" + if headerContentType != expected { + assert.Fail(t, "Content-Type header = %q; want %q", headerContentType, expected) + } + body, err := io.ReadAll(r.Body) + if err != nil { + assert.Fail(t, "Failed reading request body: %s.", err) + } + expected = "grant_type=password&password=password1&scope=scope1+scope2&username=user1" + if string(body) != expected { + assert.Fail(t, "payload = %q; want %q", string(body), expected) + } + w.Header().Set("Content-Type", "application/x-www-form-urlencoded") + w.Write([]byte("access_token=90d64460d14870c08c81352a05dedd3465940a7c&scope=user&token_type=bearer")) + })) + defer t.Cleanup(func() { ts.Close() }) + + cfg := apidef.PasswordAuthentication{ + Enabled: true, + ClientAuthData: apidef.ClientAuthData{ + ClientID: "CLIENT_ID", + ClientSecret: "CLIENT_SECRET", + }, + Username: "user1", + Password: "password1", + TokenURL: ts.URL + "/token", + Scopes: []string{"scope1", "scope2"}, + HeaderName: "", + } + + tst.Gw.BuildAndLoadAPI( + func(spec *APISpec) { + spec.Proxy.ListenPath = "/upstream-oauth-password/" + spec.UseKeylessAccess = true + spec.UpstreamAuth = apidef.UpstreamAuth{ + Enabled: true, + OAuth: apidef.UpstreamOAuth{ + Enabled: true, + PasswordAuthentication: cfg, + }, + } + spec.Proxy.StripListenPath = true + }, + ) + + _, _ = tst.Run(t, test.TestCases{ + { + Path: "/upstream-oauth-password/", + Code: http.StatusOK, + BodyMatchFunc: func(body []byte) bool { + resp := struct { + Headers map[string]string `json:"headers"` + }{} + err := json.Unmarshal(body, &resp) + assert.NoError(t, err) + + assert.Contains(t, resp.Headers, header.Authorization) + assert.NotEmpty(t, resp.Headers[header.Authorization]) + assert.Equal(t, "Bearer 90d64460d14870c08c81352a05dedd3465940a7c", resp.Headers[header.Authorization]) + + return true + }, + }, + }...) +} diff --git a/gateway/rpc_backup_handlers.go b/gateway/rpc_backup_handlers.go index f129827dd9f..3d682c81407 100644 --- a/gateway/rpc_backup_handlers.go +++ b/gateway/rpc_backup_handlers.go @@ -113,8 +113,8 @@ func (gw *Gateway) LoadPoliciesFromRPCBackup() (map[string]user.Policy, error) { } } -func getPaddedSecret(gw *Gateway) []byte { - return []byte(rightPad2Len(gw.GetConfig().Secret, "=", 32)) +func getPaddedSecret(secret string) []byte { + return []byte(rightPad2Len(secret, "=", 32)) } func (gw *Gateway) saveRPCPoliciesBackup(list string) error { @@ -136,7 +136,7 @@ func (gw *Gateway) saveRPCPoliciesBackup(list string) error { return errors.New("--> RPC Backup save failed: redis connection failed") } - cryptoText := encrypt(getPaddedSecret(gw), list) + cryptoText := encrypt(getPaddedSecret(gw.GetConfig().Secret), list) err := store.SetKey(BackupPolicyKeyBase+tagList, cryptoText, -1) if err != nil { return errors.New("Failed to store node backup: " + err.Error())