Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TT-13271] custom oauth response fields #6660

Open
wants to merge 5 commits into
base: TT-13185-implement-oauth-password-flow
Choose a base branch
from

Conversation

andrei-tyk
Copy link
Contributor

@andrei-tyk andrei-tyk commented Oct 23, 2024

User description

Task: https://tyktech.atlassian.net/browse/TT-13271

Description

This PR introduces functionality to allow our Gateway (GW) users to extract specific custom values from an OAuth server's /token response, which are configured in the API specification. These values will be stored in the request context, making them available for use throughout the middleware chain. This feature addresses the need to handle custom fields in OAuth responses, such as Salesforce's instance_url, without requiring custom plugin development.

The key feature added in this implementation is the ability to define an extra_metadata array in the API specification. The fields listed in this array are extracted from the OAuth response and stored for later use in downstream middleware, enabling scenarios like URL rewriting based on custom OAuth data.

Related Issue

This feature is related to supporting OAuth integrations that deviate from the standard OAuth 2.0 specification. The JIRA ticket addresses a use case where some OAuth providers, like Salesforce, include additional fields in the /token response (e.g., instance_url).

Motivation and Context
This change is required to allow users of our Gateway to capture non-standard fields in the OAuth /token response and make use of them within their middleware chain. By implementing this feature, we avoid the need for users to create custom plugins for every unique OAuth provider that includes non-standard fields, enhancing flexibility and reducing complexity.

This also supports use cases where dynamic upstream targets (e.g., instance_url from Salesforce) need to be set based on OAuth response data, which was previously handled via custom plugins.

How This Has Been Tested
Custom Plugin Reproduction: We analyzed the behavior of the CustomGoPlugin.go and validated that the new feature could replicate the same outcome without requiring a custom plugin.
Test Environment: Testing was performed in a local development environment using OAuth integrations with and without custom fields, including Salesforce. Multiple middleware chains were tested to ensure proper propagation of custom OAuth data across the request lifecycle.


PR Type

enhancement, tests


Description

  • Added support for OAuth2 password authentication flow, allowing users to configure and use this authentication method.
  • Introduced ExtraMetadata field to extract custom fields from OAuth2 token responses and store them in the request context.
  • Enhanced the schema definitions to support new OAuth2 configurations, including password authentication and custom headers.
  • Implemented caching mechanisms for OAuth2 password tokens to improve performance and reduce redundant token requests.
  • Added comprehensive tests for the new OAuth2 password authentication flow and metadata extraction functionality.

Changes walkthrough 📝

Relevant files
Enhancement
api_definitions.go
Add support for OAuth2 password authentication and custom token fields

apidef/api_definitions.go

  • Added PasswordAuthentication struct for OAuth2 password authentication
    flow.
  • Introduced ExtraMetadata field for extracting custom token fields.
  • Updated ClientCredentials struct with Enabled and HeaderName fields.
  • +30/-2   
    upstream.go
    Enhance OAuth2 support with password authentication and metadata
    extraction

    apidef/oas/upstream.go

  • Added PasswordAuthentication struct for OAuth2 password authentication
    flow.
  • Introduced ExtraMetadata field for extracting custom token fields.
  • Updated ClientCredentials struct with Enabled and HeaderName fields.
  • +72/-6   
    schema.go
    Update schema for OAuth2 password authentication and metadata

    apidef/schema.go

  • Updated schema to include enabled and header_name for OAuth2
    configurations.
  • Added password_authentication object to schema.
  • +36/-3   
    api_loader.go
    Integrate OAuth2 middleware into API processing chain       

    gateway/api_loader.go

  • Added UpstreamBasicAuth and UpstreamOAuth middleware to the chain.
  • +3/-4     
    mw_oauth2_auth.go
    Implement OAuth2 password flow and token metadata extraction

    gateway/mw_oauth2_auth.go

  • Implemented PasswordOAuthProvider for OAuth2 password flow.
  • Added caching mechanism for password tokens.
  • Introduced setExtraMetadata function for token metadata extraction.
  • +132/-11
    rpc_backup_handlers.go
    Refactor secret padding function for flexibility                 

    gateway/rpc_backup_handlers.go

    • Refactored getPaddedSecret to accept a string parameter.
    +3/-3     
    x-tyk-api-gateway.json
    Update JSON schema for OAuth2 password authentication       

    apidef/oas/schema/x-tyk-api-gateway.json

  • Updated JSON schema to include OAuth2 password authentication
    properties.
  • Added headerName to OAuth2 configurations.
  • +42/-4   
    Tests
    mw_oauth2_auth_test.go
    Add tests for OAuth2 password authentication and metadata extraction

    gateway/mw_oauth2_auth_test.go

  • Added tests for OAuth2 password credentials token request.
  • Enhanced existing tests with additional assertions.
  • +95/-10 

    💡 PR-Agent usage: Comment /help "your question" on any pull request to receive relevant information

    …ustom-oauth-response-fields
    
    # Conflicts:
    #	apidef/api_definitions.go
    #	apidef/oas/upstream.go
    #	gateway/mw_oauth2_auth_test.go
    @buger
    Copy link
    Member

    buger commented Oct 23, 2024

    I'm a bot and I 👍 this PR title. 🤖

    @andrei-tyk andrei-tyk changed the base branch from master to TT-13185-implement-oauth-password-flow October 23, 2024 09:24
    Copy link
    Contributor

    API Changes

    --- prev.txt	2024-10-23 09:24:27.588073018 +0000
    +++ current.txt	2024-10-23 09:24:21.316055363 +0000
    @@ -886,6 +886,9 @@
     						"client_credentials": {
     							"type": "object",
     							"properties": {
    +								"enabled": {
    +									"type": "boolean"
    +								},
     								"client_id": {
     									"type": "string"
     								},
    @@ -897,11 +900,41 @@
     								},
     								"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"
    +								}
    +							}
    +						  }
     						}
     					}
     				}
    @@ -1218,11 +1251,16 @@
     
     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:"-"`
    @@ -1716,6 +1754,29 @@
     	SegregateByClient bool                `bson:"segregate_by_client" json:"segregate_by_client"`
     }
     
    +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"`
    +
    +	// TokenProvider is the OAuth2 password authentication flow token for internal use.
    +	Token *oauth2.Token `bson:"-" json:"-"`
    +}
    +    PasswordAuthentication holds the configuration for upstream OAuth2 password
    +    authentication flow.
    +
     type PersistGraphQLMeta struct {
     	Path      string                 `bson:"path" json:"path"`
     	Method    string                 `bson:"method" json:"method"`
    @@ -2078,9 +2139,10 @@
     	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.
    -	// Defaults to `Authorization`.
    -	HeaderName string `bson:"header_name" json:"header_name,omitempty"`
    +	// PasswordAuthentication holds the configuration for upstream OAauth password authentication flow.
    +	PasswordAuthentication PasswordAuthentication `bson:"password_authentication,omitempty" json:"passwordAuthentication,omitempty"`
    +	// ExtraMetadata holds the keys that we want to extract from the token and pass to the upstream.
    +	ExtraMetadata []string `bson:"extra_metadata" json:"extra_metadata,omitempty"`
     }
         UpstreamOAuth holds upstream OAuth2 authentication configuration.
     
    @@ -3104,6 +3166,14 @@
     func (cb *CircuitBreaker) Fill(circuitBreaker apidef.CircuitBreakerMeta)
         Fill fills *CircuitBreaker from apidef.CircuitBreakerMeta.
     
    +type ClientAuthData struct {
    +	// ClientID is the application's ID.
    +	ClientID string `bson:"clientId" json:"clientId"`
    +	// ClientSecret is the application's secret.
    +	ClientSecret string `bson:"clientSecret" json:"clientSecret"`
    +}
    +    ClientAuthData holds the client ID and secret for OAuth2 authentication.
    +
     type ClientCertificates struct {
     	// Enabled activates static mTLS for the API.
     	Enabled bool `bson:"enabled" json:"enabled"`
    @@ -3120,15 +3190,17 @@
         Fill fills *ClientCertificates from apidef.APIDefinition.
     
     type ClientCredentials struct {
    -	// ClientID is the application's ID.
    -	ClientID string `bson:"clientID" json:"clientID"`
    -	// ClientSecret is the application's secret.
    -	ClientSecret string `bson:"clientSecret" json:"clientSecret"`
    +	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"`
     }
         ClientCredentials holds the configuration for OAuth2 Client Credentials
         flow.
    @@ -4029,6 +4101,30 @@
     type Operations map[string]*Operation
         Operations holds Operation definitions.
     
    +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"`
    +}
    +    PasswordAuthentication holds the configuration for upstream OAuth2 password
    +    authentication flow.
    +
    +func (p *PasswordAuthentication) ExtractTo(api *apidef.PasswordAuthentication)
    +
    +func (p *PasswordAuthentication) Fill(api apidef.PasswordAuthentication)
    +
     type Path struct {
     	// Delete holds plugin configuration for DELETE requests.
     	Delete *Plugins `bson:"DELETE,omitempty" json:"DELETE,omitempty"`
    @@ -4884,9 +4980,10 @@
     	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.
    -	// Defaults to `Authorization`.
    -	HeaderName string `bson:"headerName" json:"headerName"`
    +	// PasswordAuthentication holds the configuration for upstream OAauth password authentication flow.
    +	PasswordAuthentication *PasswordAuthentication `bson:"passwordAuthentication,omitempty" json:"passwordAuthentication,omitempty"`
    +	// ExtraMetadata holds the keys that we want to extract from the token and pass to the upstream.
    +	ExtraMetadata []string `bson:"extraMetadata" json:"extraMetadata,omitempty"`
     }
         UpstreamOAuth holds the configuration for OAuth2 Client Credentials flow.
     
    @@ -9904,6 +10001,8 @@
     
     func (k *OrganizationMonitor) SetOrgSentinel(orgChan chan bool, orgId string)
     
    +type PasswordOAuthProvider struct{}
    +
     type PerAPIClientCredentialsOAuthProvider struct{}
     
     type PersistGraphQLOperationMiddleware struct {

    Copy link
    Contributor

    PR Reviewer Guide 🔍

    Here are some key observations to aid the review process:

    ⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
    🧪 PR contains tests
    🔒 No security concerns identified
    ⚡ Recommended focus areas for review

    Error Handling
    Ensure that the function 'setExtraMetadata' properly handles cases where the token does not contain the requested metadata keys.

    Data Integrity
    Verify that the encryption and decryption of the token are secure and correctly implemented to prevent data leaks or corruption.

    Performance
    Review the potential performance impact of the new metadata extraction logic, especially in high-throughput environments.

    Copy link
    Contributor

    PR Code Suggestions ✨

    Explore these optional code suggestions:

    CategorySuggestion                                                                                                                                    Score
    Security
    Add validation for ExtraMetadata keys to enhance security

    Ensure that the ExtraMetadata field is properly validated to avoid potential
    security risks such as injection attacks or unauthorized data access.

    gateway/mw_oauth2_auth.go [288-292]

     for _, key := range OAuthSpec.Spec.UpstreamAuth.OAuth.ExtraMetadata {
    -    val := token.Extra(key)
    -    if val != "" {
    -        contextDataObject[key] = val
    +    if isValidKey(key) {
    +        val := token.Extra(key)
    +        if val != "" {
    +            contextDataObject[key] = val
    +        }
         }
     }
    Suggestion importance[1-10]: 9

    Why: The suggestion to validate the ExtraMetadata keys is crucial for preventing potential security risks such as injection attacks or unauthorized data access. Implementing a validation function like isValidKey ensures that only safe and expected keys are processed, significantly enhancing the security of the code.

    9

    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
    Projects
    None yet
    Development

    Successfully merging this pull request may close these issues.

    3 participants