Skip to content

Commit

Permalink
feat: add support for google_oauth2 token. Related to issue: https://… (
Browse files Browse the repository at this point in the history
#169)

* feat: add support for google_oauth2 token. Related to issue: #168

* add test cases

* add test cases

* add error formatting changes

---------

Co-authored-by: anjali.agarwal <anjali.aggarwal@gojek.com>
  • Loading branch information
anjali9791 and anjaliagg9791 authored Aug 13, 2024
1 parent f192300 commit 6fbebbe
Show file tree
Hide file tree
Showing 5 changed files with 449 additions and 33 deletions.
3 changes: 2 additions & 1 deletion core/appeal/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -1571,7 +1571,8 @@ func (s *Service) populateAppealMetadata(ctx context.Context, a *domain.Appeal,
cfg.URL = urlStr
}

metadataCl, err := http.NewHTTPClient(&cfg.HTTPClientConfig)
clientCreator := &http.HttpClientCreatorStruct{}
metadataCl, err := http.NewHTTPClient(&cfg.HTTPClientConfig, clientCreator)
if err != nil {
return fmt.Errorf("key: %s, %w", key, err)
}
Expand Down
5 changes: 2 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ require (
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.11.2
go.opentelemetry.io/otel/sdk v1.11.2
golang.org/x/net v0.8.0
golang.org/x/oauth2 v0.6.0
golang.org/x/oauth2 v0.22.0
golang.org/x/sync v0.1.0
google.golang.org/api v0.106.0
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f
Expand All @@ -54,8 +54,7 @@ require (

require (
cloud.google.com/go v0.107.0 // indirect
cloud.google.com/go/compute v1.14.0 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
cloud.google.com/go/compute/metadata v0.3.0 // indirect
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/Microsoft/go-winio v0.5.2 // indirect
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
Expand Down
10 changes: 4 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,8 @@ cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6m
cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s=
cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU=
cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U=
cloud.google.com/go/compute v1.14.0 h1:hfm2+FfxVmnRlh6LpB7cg1ZNU+5edAHmW679JePztk0=
cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo=
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
cloud.google.com/go/datacatalog v1.8.0 h1:6kZ4RIOW/uT7QWC5SfPfq/G8sYzr/v+UOmOAxy4Z1TE=
cloud.google.com/go/datacatalog v1.8.0/go.mod h1:KYuoVOv9BM8EYz/4eMFxrr4DUKhGIOXxZoKYF5wdISM=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
Expand Down Expand Up @@ -1610,8 +1608,8 @@ golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j
golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE=
golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw=
golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw=
golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA=
golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
Expand Down
100 changes: 77 additions & 23 deletions pkg/http/http.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
package http

import (
"bytes"
"context"
"encoding/base64"
"fmt"
"net/http"

"github.com/go-playground/validator/v10"
"github.com/mcuadros/go-defaults"
validator "github.com/go-playground/validator/v10"
defaults "github.com/mcuadros/go-defaults"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
"google.golang.org/api/idtoken"
"net/http"
)

type HTTPAuthConfig struct {
Type string `mapstructure:"type" json:"type" yaml:"type" validate:"required,oneof=basic api_key bearer google_idtoken"`
Type string `mapstructure:"type" json:"type" yaml:"type" validate:"required,oneof=basic api_key bearer google_idtoken google_oauth2"`

// basic auth
Username string `mapstructure:"username,omitempty" json:"username,omitempty" yaml:"username,omitempty" validate:"required_if=Type basic"`
Expand All @@ -35,24 +36,31 @@ type HTTPAuthConfig struct {

// HTTPClientConfig is the configuration required by iam.Client
type HTTPClientConfig struct {
URL string `mapstructure:"url" json:"url" yaml:"url" validate:"required,url"`
Headers map[string]string `mapstructure:"headers,omitempty" json:"headers,omitempty" yaml:"headers,omitempty"`
Auth *HTTPAuthConfig `mapstructure:"auth,omitempty" json:"auth,omitempty" yaml:"auth,omitempty" validate:"omitempty,dive"`

HTTPClient *http.Client `mapstructure:"-" json:"-" yaml:"-"`
URL string `mapstructure:"url" json:"url" yaml:"url" validate:"required,url"`
Headers map[string]string `mapstructure:"headers,omitempty" json:"headers,omitempty" yaml:"headers,omitempty"`
Auth *HTTPAuthConfig `mapstructure:"auth,omitempty" json:"auth,omitempty" yaml:"auth,omitempty" validate:"omitempty,dive"`
Method string `mapstructure:"method,omitempty" json:"method,omitempty" yaml:"method,omitempty"`
Body string `mapstructure:"body,omitempty" json:"body,omitempty" yaml:"body,omitempty"`
HTTPClient *http.Client `mapstructure:"-" json:"-" yaml:"-"`
Validator *validator.Validate
}

// HTTPClient wraps the http client for external approver resolver service
type HTTPClient struct {
httpClient *http.Client
config *HTTPClientConfig
url string
}
type HttpClientCreatorStruct struct {
}

url string
type HttpClientCreator interface {
GetHttpClientForGoogleOAuth2(ctx context.Context, creds []byte) (*http.Client, error)
GetHttpClientForGoogleIdToken(ctx context.Context, creds []byte, audience string) (*http.Client, error)
}

// NewHTTPClient returns *iam.Client
func NewHTTPClient(config *HTTPClientConfig) (*HTTPClient, error) {
func NewHTTPClient(config *HTTPClientConfig, clientCreator HttpClientCreator) (*HTTPClient, error) {
defaults.SetDefaults(config)
if err := validator.New().Struct(config); err != nil {
return nil, err
Expand All @@ -62,25 +70,32 @@ func NewHTTPClient(config *HTTPClientConfig) (*HTTPClient, error) {
httpClient = http.DefaultClient
}

if config.Auth != nil && config.Auth.Type == "google_idtoken" {
if config.Auth != nil && (config.Auth.Type == "google_idtoken" || config.Auth.Type == "google_oauth2") {
var creds []byte
switch {
case config.Auth.CredentialsJSONBase64 != "":
v, err := base64.StdEncoding.DecodeString(config.Auth.CredentialsJSONBase64)
var err error
creds, err = decodeCredentials(config.Auth.CredentialsJSONBase64)
if err != nil {
return nil, fmt.Errorf("decoding credentials_json_base64: %w", err)
return nil, err
}
creds = v
default:
return nil, fmt.Errorf("missing credentials for google_idtoken auth")
return nil, fmt.Errorf("missing credentials for %q auth type", config.Auth.Type)
}

ctx := context.Background()
ts, err := idtoken.NewTokenSource(ctx, config.Auth.Audience, idtoken.WithCredentialsJSON(creds))
if err != nil {
return nil, err
var err error
if config.Auth.Type == "google_idtoken" {
httpClient, err = clientCreator.GetHttpClientForGoogleIdToken(ctx, creds, config.Auth.Audience)
if err != nil {
return nil, err
}
} else if config.Auth.Type == "google_oauth2" {
httpClient, err = clientCreator.GetHttpClientForGoogleOAuth2(ctx, creds)
if err != nil {
return nil, err
}
}
httpClient = oauth2.NewClient(ctx, ts)
}

return &HTTPClient{
Expand All @@ -90,6 +105,34 @@ func NewHTTPClient(config *HTTPClientConfig) (*HTTPClient, error) {
}, nil
}

func (c *HTTPClient) GetClient() string {
return c.url
}

func (c *HttpClientCreatorStruct) GetHttpClientForGoogleOAuth2(ctx context.Context, creds []byte) (*http.Client, error) {
credsConfig, err := google.CredentialsFromJSON(ctx, creds, "https://www.googleapis.com/auth/cloud-platform")
if err != nil {
return nil, err
}
return oauth2.NewClient(ctx, credsConfig.TokenSource), nil
}

func (c *HttpClientCreatorStruct) GetHttpClientForGoogleIdToken(ctx context.Context, creds []byte, audience string) (*http.Client, error) {
ts, err := idtoken.NewTokenSource(ctx, audience, idtoken.WithCredentialsJSON(creds))
if err != nil {
return nil, err
}
return oauth2.NewClient(ctx, ts), nil
}

func decodeCredentials(encodedCreds string) ([]byte, error) {
v, err := base64.StdEncoding.DecodeString(encodedCreds)
if err != nil {
return nil, fmt.Errorf("decoding credentials_json_base64: %w", err)
}
return v, nil
}

func (c *HTTPClient) setAuth(req *http.Request) {
if c.config.Auth != nil {
switch c.config.Auth.Type {
Expand All @@ -113,18 +156,29 @@ func (c *HTTPClient) setAuth(req *http.Request) {
}

func (c *HTTPClient) MakeRequest(ctx context.Context) (*http.Response, error) {
req, err := http.NewRequest(http.MethodGet, c.config.URL, nil)
method := "GET"
if c.config.Method != "" {
method = c.config.Method
}
var body []byte
if c.config.Method == "POST" {
if c.config.Body != "" {
body = []byte(c.config.Body)
}
}

req, err := http.NewRequest(method, c.config.URL, bytes.NewBuffer(body))
if err != nil {
return nil, err
}

req = req.WithContext(ctx)
req.Header.Set("Accept", "application/json")

for k, v := range c.config.Headers {
req.Header.Set(k, v)
}
c.setAuth(req)

res, err := c.httpClient.Do(req)
if err != nil {
return nil, err
Expand Down
Loading

0 comments on commit 6fbebbe

Please sign in to comment.