Skip to content

Commit

Permalink
[TT-13391] Move upstream OAuth to EE (#6684)
Browse files Browse the repository at this point in the history
### **User description**
<details open>
<summary><a href="https://tyktech.atlassian.net/browse/TT-13359"
title="TT-13359" target="_blank">TT-13359</a></summary>
  <br />
  <table>
    <tr>
      <th>Summary</th>
      <td>[Upstream Auth] Move upstream auth features to ee folder</td>
    </tr>
    <tr>
      <th>Type</th>
      <td>
<img alt="Story"
src="https://tyktech.atlassian.net/rest/api/2/universal_avatar/view/type/issuetype/avatar/10315?size=medium"
/>
        Story
      </td>
    </tr>
    <tr>
      <th>Status</th>
      <td>In Code Review</td>
    </tr>
    <tr>
      <th>Points</th>
      <td>N/A</td>
    </tr>
    <tr>
      <th>Labels</th>
      <td>-</td>
    </tr>
  </table>
</details>
<!--
  do not remove this marker as it will break jira-lint's functionality.
  added_by_jira_lint
-->

---

<!-- Provide a general summary of your changes in the Title above -->

## Description

TASK: https://tyktech.atlassian.net/browse/TT-13359

<!-- Describe your changes in detail -->

## Related Issue

<!-- This project only accepts pull requests related to open issues. -->
<!-- If suggesting a new feature or change, please discuss it in an
issue first. -->
<!-- If fixing a bug, there should be an issue describing it with steps
to reproduce. -->
<!-- OSS: Please link to the issue here. Tyk: please create/link the
JIRA ticket. -->

## Motivation and Context

<!-- Why is this change required? What problem does it solve? -->

## How This Has Been Tested

<!-- Please describe in detail how you tested your changes -->
<!-- Include details of your testing environment, and the tests -->
<!-- you ran to see how your change affects other areas of the code,
etc. -->
<!-- This information is helpful for reviewers and QA. -->

## Screenshots (if appropriate)

## Types of changes

<!-- What types of changes does your code introduce? Put an `x` in all
the boxes that apply: -->

- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Refactoring or add test (improvements in base code or adds test
coverage to functionality)

## Checklist

<!-- Go over all the following points, and put an `x` in all the boxes
that apply -->
<!-- If there are no documentation updates required, mark the item as
checked. -->
<!-- Raise up any additional concerns not covered by the checklist. -->

- [ ] I ensured that the documentation is up to date
- [ ] I explained why this PR updates go.mod in detail with reasoning
why it's required
- [ ] I would like a code coverage CI quality gate exception and have
explained why


___

### **PR Type**
enhancement, other


___

### **Description**
- Refactored context handling across multiple files to use `httputil`
instead of `ctx`.
- Implemented upstream OAuth provider and middleware for handling OAuth
tokens in the `ee` package.
- Moved encryption and decryption logic to the `crypto` package.
- Added initialization and caching logic for upstream OAuth in the
server.
- Updated event firing to use `model.EventMetaDefault` for consistency.
- Added utilities for context data handling and request encoding to
events.



___



PRDescriptionHeader.CHANGES_WALKTHROUGH
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Enhancement</strong></td><td><details><summary>14
files</summary><table>
<tr>
  <td>
    <details>
<summary><strong>api.go</strong><dd><code>Refactor context handling and
event firing in API gateway</code></dd></summary>
<hr>

gateway/api.go

<li>Removed in-place modification of <code>http.Request</code>
context.<br> <li> Replaced <code>ctx</code> package usage with
<code>httputil</code>.<br> <li> Updated event firing to use
<code>model.EventMetaDefault</code>.<br>


</details>


  </td>
<td><a
href="https://github.com/TykTechnologies/tyk/pull/6684/files#diff-644cda3aeb4ac7f325359e85fcddb810f100dd5e6fa480b0d9f9363a743c4e05">+51/-77</a>&nbsp;
</td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>mw_oauth2_auth.go</strong><dd><code>Replace upstream
OAuth with no-op middleware</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

gateway/mw_oauth2_auth.go

<li>Removed upstream OAuth implementation.<br> <li> Added a no-op
upstream OAuth middleware.<br>


</details>


  </td>
<td><a
href="https://github.com/TykTechnologies/tyk/pull/6684/files#diff-a90347c3ad28f06a7bd1c5554ce63448774cb486cf4e9961af2323423ce8209d">+15/-378</a></td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>provider.go</strong><dd><code>Implement upstream OAuth
provider with token caching</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

ee/middleware/upstreamoauth/provider.go

<li>Implemented upstream OAuth provider for client credentials and
<br>password grants.<br> <li> Added token caching and retrieval
logic.<br>


</details>


  </td>
<td><a
href="https://github.com/TykTechnologies/tyk/pull/6684/files#diff-81f303e143b11cb1ecd6390a8e7a585f077da9f88e578c0ffed921af30081a8a">+317/-0</a>&nbsp;
</td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>mw_url_rewrite.go</strong><dd><code>Refactor URL
rewrite middleware to use httputil</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

gateway/mw_url_rewrite.go

<li>Replaced <code>ctx</code> package usage with <code>httputil</code>
for context data handling.<br>


</details>


  </td>
<td><a
href="https://github.com/TykTechnologies/tyk/pull/6684/files#diff-84a6a5c810334aaa8702669f2aebf0284f116d83e8a55ec9d1d5b8bae87f1be6">+21/-20</a>&nbsp;
</td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>rpc_backup_handlers.go</strong><dd><code>Use crypto
package for encryption in RPC backup handlers</code>&nbsp;
</dd></summary>
<hr>

gateway/rpc_backup_handlers.go

- Moved encryption and decryption logic to `crypto` package.



</details>


  </td>
<td><a
href="https://github.com/TykTechnologies/tyk/pull/6684/files#diff-69d9cb8df2bd4296a8e5e5d769009a09bd61ca65b7dbcbf29751af92698bd9ce">+9/-75</a>&nbsp;
&nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>middleware.go</strong><dd><code>Refactor middleware
context handling and event firing</code>&nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

gateway/middleware.go

<li>Replaced <code>ctx</code> package usage with <code>httputil</code>
for context handling.<br> <li> Updated event firing to use
<code>model.EventMetaDefault</code>.<br>


</details>


  </td>
<td><a
href="https://github.com/TykTechnologies/tyk/pull/6684/files#diff-703054910891a4db633eca0f42ed779d6b4fa75cd9b3aa4c503e681364201c1b">+10/-22</a>&nbsp;
</td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>event_system.go</strong><dd><code>Refactor event system
to use model package</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

gateway/event_system.go

<li>Moved <code>EventMetaDefault</code> to <code>model</code>
package.<br> <li> Updated event firing to use
<code>model.EventMetaDefault</code>.<br>


</details>


  </td>
<td><a
href="https://github.com/TykTechnologies/tyk/pull/6684/files#diff-d56e22d4f1b8d2e91bb643d30e678a3819691a18bfae8506b10e0af8dc279a0e">+11/-34</a>&nbsp;
</td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>mw_organisation_activity.go</strong><dd><code>Refactor
organisation activity middleware for context and
events</code></dd></summary>
<hr>

gateway/mw_organisation_activity.go

<li>Replaced <code>ctx</code> package usage with <code>httputil</code>
for context data handling.<br> <li> Updated event firing to use
<code>model.EventMetaDefault</code>.<br>


</details>


  </td>
<td><a
href="https://github.com/TykTechnologies/tyk/pull/6684/files#diff-26dd955903317b085be06642ae3e76fe41c8c53844d8758a1a1c8bd05b0110a2">+12/-9</a>&nbsp;
&nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>middleware.go</strong><dd><code>Implement upstream
OAuth middleware for token handling</code>&nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

ee/middleware/upstreamoauth/middleware.go

- Implemented upstream OAuth middleware for handling OAuth tokens.



</details>


  </td>
<td><a
href="https://github.com/TykTechnologies/tyk/pull/6684/files#diff-1347b256f3728407a2697f1e824391eaa329162e644741d8321f25c7a0630363">+101/-0</a>&nbsp;
</td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>server.go</strong><dd><code>Initialize upstream OAuth
caches in server</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

gateway/server.go

<li>Added initialization for upstream OAuth caches.<br> <li> Defined
functions for creating OAuth caches.<br>


</details>


  </td>
<td><a
href="https://github.com/TykTechnologies/tyk/pull/6684/files#diff-4652d1bf175a0be8f5e61ef7177c9666f23e077d8626b73ac9d13358fa8b525b">+29/-2</a>&nbsp;
&nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>helpers.go</strong><dd><code>Add AES encryption and
decryption helpers</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

internal/crypto/helpers.go

- Added functions for encryption and decryption using AES.



</details>


  </td>
<td><a
href="https://github.com/TykTechnologies/tyk/pull/6684/files#diff-3d1fc755c46eaa99e9f1edf358b9e00842342ae6333902959d7a68b46d156829">+69/-0</a>&nbsp;
&nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>context.go</strong><dd><code>Add context data handling
utilities</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

internal/httputil/context.go

<li>Added context data handling functions.<br> <li> Defined context keys
for request handling.<br>


</details>


  </td>
<td><a
href="https://github.com/TykTechnologies/tyk/pull/6684/files#diff-aa35e72b1da34689ec8451189adab625374ed898ead1c72f6953c9e946470ffd">+56/-0</a>&nbsp;
&nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>event.go</strong><dd><code>Add request encoding to
event utility</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

internal/event/event.go

- Added function to encode HTTP requests to events.



</details>


  </td>
<td><a
href="https://github.com/TykTechnologies/tyk/pull/6684/files#diff-3d64b81c3937b899f363a9ce6bd4dbca0325ce8c20a67b8fb763d0c798cef93e">+11/-0</a>&nbsp;
&nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>events.go</strong><dd><code>Add EventMetaDefault struct
for event metadata</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

internal/model/events.go

- Added `EventMetaDefault` struct for event metadata.



</details>


  </td>
<td><a
href="https://github.com/TykTechnologies/tyk/pull/6684/files#diff-3e14096ae0e251fe2fabcf8dfb7e7fa8ed8258ef2a5017a1a5051f95f4dafdc0">+8/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>                    

</table></details></td></tr><tr><td><strong>Other</strong></td><td><details><summary>1
files</summary><table>
<tr>
  <td>
    <details>
<summary><strong>ctx.go</strong><dd><code>Remove context key definitions
from ctx package</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

ctx/ctx.go

- Removed context key definitions and related functions.



</details>


  </td>
<td><a
href="https://github.com/TykTechnologies/tyk/pull/6684/files#diff-600f5f552779994b15324fda108549eec7e7be30b1d8a1a16ee8344243e0cbc7">+7/-45</a>&nbsp;
&nbsp; </td>

</tr>                    
</table></details></td></tr></tr></tbody></table>

___

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

---------

Co-authored-by: Tit Petric <[email protected]>
Co-authored-by: Tit Petric <[email protected]>
Co-authored-by: Jeffy Mathew <[email protected]>
  • Loading branch information
4 people authored Nov 5, 2024
1 parent 9335a51 commit fa63dbe
Show file tree
Hide file tree
Showing 26 changed files with 826 additions and 606 deletions.
110 changes: 110 additions & 0 deletions ee/middleware/upstreamoauth/middleware.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package upstreamoauth

import (
"fmt"
"net/http"

"github.com/sirupsen/logrus"

"github.com/TykTechnologies/tyk/header"
"github.com/TykTechnologies/tyk/internal/event"
"github.com/TykTechnologies/tyk/internal/httputil"
"github.com/TykTechnologies/tyk/internal/model"
)

// Middleware implements upstream OAuth middleware.
type Middleware struct {
Spec model.MergedAPI
Gw Gateway

Base BaseMiddleware

clientCredentialsStorageHandler Storage
passwordStorageHandler Storage
}

// Middleware implements model.Middleware.
var _ model.Middleware = &Middleware{}

// NewMiddleware returns a new instance of Middleware.
func NewMiddleware(gw Gateway, mw BaseMiddleware, spec model.MergedAPI, ccStorageHandler Storage, pwStorageHandler Storage) *Middleware {
return &Middleware{
Base: mw,
Gw: gw,
Spec: spec,
clientCredentialsStorageHandler: ccStorageHandler,
passwordStorageHandler: pwStorageHandler,
}
}

// Logger returns a logger with middleware filled out.
func (m *Middleware) Logger() *logrus.Entry {
return m.Base.Logger().WithField("mw", m.Name())
}

// Name returns the name for the middleware.
func (m *Middleware) Name() string {
return MiddlewareName
}

// EnabledForSpec checks if streaming is enabled on the config.
func (m *Middleware) EnabledForSpec() bool {
if !m.Spec.UpstreamAuth.IsEnabled() {
return false
}

if !m.Spec.UpstreamAuth.OAuth.Enabled {
return false
}

return true
}

// Init initializes the middleware.
func (m *Middleware) Init() {
m.Logger().Debug("Initializing Upstream basic auth Middleware")
}

// ProcessRequest will handle upstream OAuth.
func (m *Middleware) ProcessRequest(_ http.ResponseWriter, r *http.Request, _ interface{}) (error, int) {
provider, err := NewOAuthHeaderProvider(m.Spec.UpstreamAuth.OAuth)
if err != nil {
return fmt.Errorf("failed to get OAuth header provider: %w", err), http.StatusInternalServerError
}

payload, err := provider.getOAuthToken(r, m)
if err != nil {
return fmt.Errorf("failed to get OAuth token: %w", err), http.StatusInternalServerError
}

upstreamOAuthProvider := Provider{
HeaderName: header.Authorization,
AuthValue: payload,
}

headerName := provider.getHeaderName(m)
if headerName != "" {
upstreamOAuthProvider.HeaderName = headerName
}

if provider.headerEnabled(m) {
headerName := provider.getHeaderName(m)
if headerName != "" {
upstreamOAuthProvider.HeaderName = headerName
}
}

httputil.SetUpstreamAuth(r, upstreamOAuthProvider)
return nil, http.StatusOK
}

// FireEvent emits an upstream OAuth event with an optional custom message.
func (mw *Middleware) FireEvent(r *http.Request, e event.Event, message string, apiId string) {
if message == "" {
message = event.String(e)
}
mw.Base.FireEvent(e, EventUpstreamOAuthMeta{
EventMetaDefault: model.NewEventMetaDefault(r, message),
APIID: apiId,
})
}
54 changes: 54 additions & 0 deletions ee/middleware/upstreamoauth/model.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package upstreamoauth

import (
"time"

"github.com/TykTechnologies/tyk/apidef"
"github.com/TykTechnologies/tyk/ctx"
"github.com/TykTechnologies/tyk/internal/httpctx"
"github.com/TykTechnologies/tyk/internal/model"
)

const (
ErrorEventName = "UpstreamOAuthError"
MiddlewareName = "UpstreamOAuth"

ClientCredentialsAuthorizeType = "clientCredentials"
PasswordAuthorizeType = "password"
)

// BaseMiddleware is the subset of BaseMiddleware APIs that the middleware uses.
type BaseMiddleware interface {
model.LoggerProvider
FireEvent(name apidef.TykEvent, meta interface{})
}

// Gateway is the subset of Gateway APIs that the middleware uses.
type Gateway interface {
model.ConfigProvider
}

// Type Storage is a subset of storage.RedisCluster
type Storage interface {
GetKey(key string) (string, error)
SetKey(string, string, int64) error
Lock(key string, timeout time.Duration) (bool, error)
}

type ClientCredentialsOAuthProvider struct{}

type PerAPIClientCredentialsOAuthProvider struct{}

type PasswordOAuthProvider struct{}

type TokenData struct {
Token string `json:"token"`
ExtraMetadata map[string]interface{} `json:"extra_metadata"`
}

var (
ctxData = httpctx.NewValue[map[string]any](ctx.ContextData)

CtxGetData = ctxData.Get
CtxSetData = ctxData.Set
)
204 changes: 204 additions & 0 deletions ee/middleware/upstreamoauth/provider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
package upstreamoauth

import (
"crypto/sha256"
"encoding/hex"
"fmt"
"net/http"
"strings"
"time"

"github.com/sirupsen/logrus"
"golang.org/x/oauth2"
oauth2clientcredentials "golang.org/x/oauth2/clientcredentials"

"github.com/TykTechnologies/tyk/apidef"
"github.com/TykTechnologies/tyk/internal/model"
)

// Provider implements upstream auth provider.
type Provider struct {
// Logger is the logger to be used.
Logger *logrus.Entry
// HeaderName is the header name to be used to fill upstream auth with.
HeaderName string
// AuthValue is the value of auth header.
AuthValue string
}

// Fill sets the request's HeaderName with AuthValue
func (u Provider) Fill(r *http.Request) {
if r.Header.Get(u.HeaderName) != "" {
u.Logger.WithFields(logrus.Fields{
"header": u.HeaderName,
}).Info("Authorization header conflict detected: Client header overwritten by Gateway upstream authentication header.")
}
r.Header.Set(u.HeaderName, u.AuthValue)
}

type OAuthHeaderProvider interface {
// getOAuthToken returns the OAuth token for the request.
getOAuthToken(r *http.Request, mw *Middleware) (string, error)
// getHeaderName returns the header name for the OAuth token.
getHeaderName(mw *Middleware) string
//
headerEnabled(mw *Middleware) bool
}

func NewOAuthHeaderProvider(oauthConfig apidef.UpstreamOAuth) (OAuthHeaderProvider, error) {
if !oauthConfig.IsEnabled() {
return nil, fmt.Errorf("upstream OAuth is not enabled")
}

switch {
case len(oauthConfig.AllowedAuthorizeTypes) == 0:
return nil, fmt.Errorf("no OAuth configuration selected")
case len(oauthConfig.AllowedAuthorizeTypes) > 1:
return nil, fmt.Errorf("both client credentials and password authentication are provided")
case oauthConfig.AllowedAuthorizeTypes[0] == ClientCredentialsAuthorizeType:
return &ClientCredentialsOAuthProvider{}, nil
case oauthConfig.AllowedAuthorizeTypes[0] == PasswordAuthorizeType:
return &PasswordOAuthProvider{}, nil
default:
return nil, fmt.Errorf("no valid OAuth configuration provided")
}
}

func (p *ClientCredentialsOAuthProvider) getOAuthToken(r *http.Request, mw *Middleware) (string, error) {
client := ClientCredentialsClient{mw}
token, err := client.GetToken(r)
if err != nil {
return handleOAuthError(r, mw, err)
}

return fmt.Sprintf("Bearer %s", token), nil
}

func handleOAuthError(r *http.Request, mw *Middleware, err error) (string, error) {
mw.FireEvent(r, ErrorEventName, err.Error(), mw.Spec.APIID)
return "", err
}

func (p *ClientCredentialsOAuthProvider) getHeaderName(OAuthSpec *Middleware) string {
return OAuthSpec.Spec.UpstreamAuth.OAuth.ClientCredentials.Header.Name
}

func (p *ClientCredentialsOAuthProvider) headerEnabled(OAuthSpec *Middleware) bool {
return OAuthSpec.Spec.UpstreamAuth.OAuth.ClientCredentials.Header.Enabled
}

func newOAuth2ClientCredentialsConfig(OAuthSpec *Middleware) oauth2clientcredentials.Config {
return oauth2clientcredentials.Config{
ClientID: OAuthSpec.Spec.UpstreamAuth.OAuth.ClientCredentials.ClientID,
ClientSecret: OAuthSpec.Spec.UpstreamAuth.OAuth.ClientCredentials.ClientSecret,
TokenURL: OAuthSpec.Spec.UpstreamAuth.OAuth.ClientCredentials.TokenURL,
Scopes: OAuthSpec.Spec.UpstreamAuth.OAuth.ClientCredentials.Scopes,
}
}

func newOAuth2PasswordConfig(OAuthSpec *Middleware) 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,
}
}

type ClientCredentialsClient struct {
mw *Middleware
}

type PasswordClient struct {
mw *Middleware
}

func generateClientCredentialsCacheKey(config apidef.UpstreamOAuth, apiId string) string {
key := fmt.Sprintf(
"cc-%s|%s|%s|%s",
apiId,
config.ClientCredentials.ClientID,
config.ClientCredentials.TokenURL,
strings.Join(config.ClientCredentials.Scopes, ","))

hash := sha256.New()
hash.Write([]byte(key))
return hex.EncodeToString(hash.Sum(nil))
}

func retryGetKeyAndLock(cacheKey string, cache Storage) (string, error) {
const maxRetries = 10
const retryDelay = 100 * time.Millisecond

var tokenData string
var err error

for i := 0; i < maxRetries; i++ {
tokenData, err = cache.GetKey(cacheKey)
if err == nil {
return tokenData, nil
}

lockKey := cacheKey + ":lock"
ok, err := cache.Lock(lockKey, time.Second*5)
if err == nil && ok {
return "", nil
}

time.Sleep(retryDelay)
}

return "", fmt.Errorf("failed to acquire lock after retries: %w", err)
}

func SetExtraMetadata(r *http.Request, keyList []string, metadata map[string]interface{}) {
contextDataObject := CtxGetData(r)
if contextDataObject == nil {
contextDataObject = make(map[string]interface{})
}
for _, key := range keyList {
if val, ok := metadata[key]; ok && val != "" {
contextDataObject[key] = val
}
}
CtxSetData(r, contextDataObject)
}

// EventUpstreamOAuthMeta is the metadata structure for an upstream OAuth event
type EventUpstreamOAuthMeta struct {
model.EventMetaDefault
APIID string
}

func (p *PasswordOAuthProvider) getOAuthToken(r *http.Request, mw *Middleware) (string, error) {
client := PasswordClient{mw}
token, err := client.GetToken(r)
if err != nil {
return handleOAuthError(r, mw, err)
}

return fmt.Sprintf("Bearer %s", token), nil
}

func (p *PasswordOAuthProvider) getHeaderName(OAuthSpec *Middleware) string {
return OAuthSpec.Spec.UpstreamAuth.OAuth.PasswordAuthentication.Header.Name
}

func (p *PasswordOAuthProvider) headerEnabled(OAuthSpec *Middleware) bool {
return OAuthSpec.Spec.UpstreamAuth.OAuth.PasswordAuthentication.Header.Enabled
}

func generatePasswordOAuthCacheKey(config apidef.UpstreamOAuth, apiId string) string {
key := fmt.Sprintf(
"pw-%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))
}
26 changes: 26 additions & 0 deletions ee/middleware/upstreamoauth/provider_client_credentials.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package upstreamoauth

import (
"context"
"net/http"

"golang.org/x/oauth2"
)

func (cache *ClientCredentialsClient) ObtainToken(ctx context.Context) (*oauth2.Token, error) {
cfg := newOAuth2ClientCredentialsConfig(cache.mw)
tokenSource := cfg.TokenSource(ctx)
return tokenSource.Token()
}

func (cache *ClientCredentialsClient) GetToken(r *http.Request) (string, error) {
cacheKey := generateClientCredentialsCacheKey(cache.mw.Spec.UpstreamAuth.OAuth, cache.mw.Spec.APIID)
secret := cache.mw.Gw.GetConfig().Secret
extraMetadata := cache.mw.Spec.UpstreamAuth.OAuth.ClientCredentials.ExtraMetadata

obtainTokenFunc := func(ctx context.Context) (*oauth2.Token, error) {
return cache.ObtainToken(ctx)
}

return getToken(r, cacheKey, obtainTokenFunc, secret, extraMetadata, cache.mw.clientCredentialsStorageHandler)
}
Loading

0 comments on commit fa63dbe

Please sign in to comment.