Skip to content

Commit e0f6968

Browse files
authored
Merge pull request #1366 from UgnineSirdis/oauth2-fix-resource
OAuth 2.0 token exchange. Allow multiple resource parameters
2 parents e2adc87 + a03cf91 commit e0f6968

File tree

6 files changed

+46
-38
lines changed

6 files changed

+46
-38
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
* OAuth 2.0 token exchange: allowed multiple resource parameters in according to https://www.rfc-editor.org/rfc/rfc8693
2+
13
## v3.76.0
24
* Added experimental topic listener implementation
35
* Fixed `internal/xstrings.Buffer()` leak without call `buffer.Free()`

credentials/credentials.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ Config file must be a valid json file
5151
Fields of json file
5252
5353
grant-type: [string] Grant type option (default: "urn:ietf:params:oauth:grant-type:token-exchange")
54-
res: [string] Resource option (optional)
54+
res: [string | list of strings] Resource option (optional)
5555
aud: [string | list of strings] Audience option for token exchange request (optional)
5656
scope: [string | list of strings] Scope option (optional)
5757
requested-token-type: [string] Requested token type option (default: "urn:ietf:params:oauth:token-type:access_token")

credentials/options.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ func WithGrantType(grantType string) Oauth2TokenExchangeCredentialsOption {
3636
}
3737

3838
// Resource
39-
func WithResource(resource string) Oauth2TokenExchangeCredentialsOption {
40-
return credentials.WithResource(resource)
39+
func WithResource(resource string, resources ...string) Oauth2TokenExchangeCredentialsOption {
40+
return credentials.WithResource(resource, resources...)
4141
}
4242

4343
// RequestedTokenType
@@ -46,8 +46,8 @@ func WithRequestedTokenType(requestedTokenType string) Oauth2TokenExchangeCreden
4646
}
4747

4848
// Scope
49-
func WithScope(scope ...string) Oauth2TokenExchangeCredentialsOption {
50-
return credentials.WithScope(scope...)
49+
func WithScope(scope string, scopes ...string) Oauth2TokenExchangeCredentialsOption {
50+
return credentials.WithScope(scope, scopes...)
5151
}
5252

5353
// RequestTimeout
@@ -96,8 +96,8 @@ type oauthCredentialsAndJWTCredentialsOption interface {
9696
credentials.JWTTokenSourceOption
9797
}
9898

99-
func WithAudience(audience ...string) oauthCredentialsAndJWTCredentialsOption {
100-
return credentials.WithAudience(audience...)
99+
func WithAudience(audience string, audiences ...string) oauthCredentialsAndJWTCredentialsOption {
100+
return credentials.WithAudience(audience, audiences...)
101101
}
102102

103103
// Issuer

internal/credentials/oauth2.go

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -143,16 +143,16 @@ func WithGrantType(grantType string) grantTypeOption {
143143
}
144144

145145
// Resource
146-
type resourceOption string
146+
type resourceOption []string
147147

148148
func (resource resourceOption) ApplyOauth2CredentialsOption(c *oauth2TokenExchange) error {
149-
c.resource = string(resource)
149+
c.resource = append(c.resource, resource...)
150150

151151
return nil
152152
}
153153

154-
func WithResource(resource string) resourceOption {
155-
return resourceOption(resource)
154+
func WithResource(resource string, resources ...string) resourceOption {
155+
return append([]string{resource}, resources...)
156156
}
157157

158158
// RequestedTokenType
@@ -172,26 +172,26 @@ func WithRequestedTokenType(requestedTokenType string) requestedTokenTypeOption
172172
type audienceOption []string
173173

174174
func (audience audienceOption) ApplyOauth2CredentialsOption(c *oauth2TokenExchange) error {
175-
c.audience = audience
175+
c.audience = append(c.audience, audience...)
176176

177177
return nil
178178
}
179179

180-
func WithAudience(audience ...string) audienceOption {
181-
return audience
180+
func WithAudience(audience string, audiences ...string) audienceOption {
181+
return append([]string{audience}, audiences...)
182182
}
183183

184184
// Scope
185185
type scopeOption []string
186186

187187
func (scope scopeOption) ApplyOauth2CredentialsOption(c *oauth2TokenExchange) error {
188-
c.scope = scope
188+
c.scope = append(c.scope, scope...)
189189

190190
return nil
191191
}
192192

193-
func WithScope(scope ...string) scopeOption {
194-
return scope
193+
func WithScope(scope string, scopes ...string) scopeOption {
194+
return append([]string{scope}, scopes...)
195195
}
196196

197197
// RequestTimeout
@@ -309,7 +309,7 @@ type oauth2TokenExchange struct {
309309
// urn:ietf:params:oauth:grant-type:token-exchange by default
310310
grantType string
311311

312-
resource string
312+
resource []string
313313
audience []string
314314
scope []string
315315

@@ -573,7 +573,7 @@ func (cfg *oauth2TokenSourceConfig) applyConfigFixedJWT(tokenSrcType int) (*toke
573573
}
574574

575575
if cfg.Audience != nil && len(cfg.Audience.Values) > 0 {
576-
opts = append(opts, WithAudience(cfg.Audience.Values...))
576+
opts = append(opts, WithAudience(cfg.Audience.Values[0], cfg.Audience.Values[1:]...))
577577
}
578578

579579
if cfg.ID != "" {
@@ -607,7 +607,7 @@ func (cfg *oauth2TokenSourceConfig) applyConfig(tokenSrcType int) (*tokenSourceO
607607
//nolint:tagliatelle
608608
type oauth2Config struct {
609609
GrantType string `json:"grant-type"`
610-
Resource string `json:"res"`
610+
Resource *stringOrArrayConfig `json:"res"`
611611
Audience *stringOrArrayConfig `json:"aud"`
612612
Scope *stringOrArrayConfig `json:"scope"`
613613
RequestedTokenType string `json:"requested-token-type"`
@@ -622,16 +622,16 @@ func (cfg *oauth2Config) applyConfig(opts *[]Oauth2TokenExchangeCredentialsOptio
622622
*opts = append(*opts, WithGrantType(cfg.GrantType))
623623
}
624624

625-
if cfg.Resource != "" {
626-
*opts = append(*opts, WithResource(cfg.Resource))
625+
if cfg.Resource != nil && len(cfg.Resource.Values) > 0 {
626+
*opts = append(*opts, WithResource(cfg.Resource.Values[0], cfg.Resource.Values[1:]...))
627627
}
628628

629629
if cfg.Audience != nil && len(cfg.Audience.Values) > 0 {
630-
*opts = append(*opts, WithAudience(cfg.Audience.Values...))
630+
*opts = append(*opts, WithAudience(cfg.Audience.Values[0], cfg.Audience.Values[1:]...))
631631
}
632632

633633
if cfg.Scope != nil && len(cfg.Scope.Values) > 0 {
634-
*opts = append(*opts, WithScope(cfg.Scope.Values...))
634+
*opts = append(*opts, WithScope(cfg.Scope.Values[0], cfg.Scope.Values[1:]...))
635635
}
636636

637637
if cfg.RequestedTokenType != "" {
@@ -723,8 +723,10 @@ func (provider *oauth2TokenExchange) addTokenSrc(params *url.Values, src TokenSo
723723
func (provider *oauth2TokenExchange) getRequestParams() (string, error) {
724724
params := url.Values{}
725725
params.Set("grant_type", provider.grantType)
726-
if provider.resource != "" {
727-
params.Set("resource", provider.resource)
726+
for _, res := range provider.resource {
727+
if res != "" {
728+
params.Add("resource", res)
729+
}
728730
}
729731
for _, aud := range provider.audience {
730732
if aud != "" {
@@ -1054,7 +1056,7 @@ func (provider *oauth2TokenExchange) String() string {
10541056
defer buffer.Free()
10551057
fmt.Fprintf(
10561058
buffer,
1057-
"OAuth2TokenExchange{Endpoint:%q,GrantType:%s,Resource:%s,Audience:%v,Scope:%v,RequestedTokenType:%s",
1059+
"OAuth2TokenExchange{Endpoint:%q,GrantType:%s,Resource:%v,Audience:%v,Scope:%v,RequestedTokenType:%s",
10581060
provider.tokenEndpoint,
10591061
provider.grantType,
10601062
provider.resource,
@@ -1151,7 +1153,7 @@ func WithSubject(subject string) subjectOption {
11511153

11521154
// Audience
11531155
func (audience audienceOption) ApplyJWTTokenSourceOption(s *jwtTokenSource) error {
1154-
s.audience = audience
1156+
s.audience = append(s.audience, audience...)
11551157

11561158
return nil
11571159
}

internal/credentials/oauth2_test.go

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -423,7 +423,7 @@ func TestErrorInSourceToken(t *testing.T) {
423423
WithTokenEndpoint("http:trololo"),
424424
WithGrantType("grant_type"),
425425
WithRequestTimeout(time.Second),
426-
WithResource("res"),
426+
WithResource("res", "res2"),
427427
WithFixedSubjectToken("t", "tt"),
428428
WithActorToken(&errorTokenSource{}),
429429
WithSourceInfo("TestErrorInSourceToken"),
@@ -432,7 +432,7 @@ func TestErrorInSourceToken(t *testing.T) {
432432

433433
// Check that token prints well
434434
formatted := fmt.Sprint(client)
435-
require.Equal(t, `OAuth2TokenExchange{Endpoint:"http:trololo",GrantType:grant_type,Resource:res,Audience:[],Scope:[],RequestedTokenType:urn:ietf:params:oauth:token-type:access_token,SubjectToken:FixedTokenSource{Token:"****(CRC-32c: 856A5AA8)",Type:tt},ActorToken:&{},From:"TestErrorInSourceToken"}`, formatted) //nolint:lll
435+
require.Equal(t, `OAuth2TokenExchange{Endpoint:"http:trololo",GrantType:grant_type,Resource:[res res2],Audience:[],Scope:[],RequestedTokenType:urn:ietf:params:oauth:token-type:access_token,SubjectToken:FixedTokenSource{Token:"****(CRC-32c: 856A5AA8)",Type:tt},ActorToken:&{},From:"TestErrorInSourceToken"}`, formatted) //nolint:lll
436436

437437
token, err := client.Token(context.Background())
438438
require.ErrorIs(t, err, errTokenSource)
@@ -442,7 +442,7 @@ func TestErrorInSourceToken(t *testing.T) {
442442
WithTokenEndpoint("http:trololo"),
443443
WithGrantType("grant_type"),
444444
WithRequestTimeout(time.Second),
445-
WithResource("res"),
445+
WithResource("res", "res2"),
446446
WithSubjectToken(&errorTokenSource{}),
447447
)
448448
require.NoError(t, err)
@@ -483,7 +483,7 @@ func TestErrorInHTTPRequest(t *testing.T) {
483483

484484
// check format:
485485
formatted := fmt.Sprint(client)
486-
require.Equal(t, `OAuth2TokenExchange{Endpoint:"http://invalid_host:42/exchange",GrantType:urn:ietf:params:oauth:grant-type:token-exchange,Resource:,Audience:[],Scope:[1 2 3],RequestedTokenType:urn:ietf:params:oauth:token-type:access_token,SubjectToken:JWTTokenSource{Method:RS256,KeyID:key_id,Issuer:"test_issuer",Subject:"",Audience:[test_audience],ID:,TokenTTL:1h0m0s},ActorToken:JWTTokenSource{Method:RS256,KeyID:key_id,Issuer:"test_issuer",Subject:"",Audience:[],ID:,TokenTTL:1h0m0s},From:"TestErrorInHTTPRequest"}`, formatted) //nolint:lll
486+
require.Equal(t, `OAuth2TokenExchange{Endpoint:"http://invalid_host:42/exchange",GrantType:urn:ietf:params:oauth:grant-type:token-exchange,Resource:[],Audience:[],Scope:[1 2 3],RequestedTokenType:urn:ietf:params:oauth:token-type:access_token,SubjectToken:JWTTokenSource{Method:RS256,KeyID:key_id,Issuer:"test_issuer",Subject:"",Audience:[test_audience],ID:,TokenTTL:1h0m0s},ActorToken:JWTTokenSource{Method:RS256,KeyID:key_id,Issuer:"test_issuer",Subject:"",Audience:[],ID:,TokenTTL:1h0m0s},From:"TestErrorInHTTPRequest"}`, formatted) //nolint:lll
487487
}, xtest.StopAfter(15*time.Second))
488488
}
489489

@@ -780,12 +780,16 @@ func TestParseSettingsFromFile(t *testing.T) {
780780
"token-type": "test-token-type"
781781
}
782782
}`,
783-
ExpectedFormattedCredentials: `OAuth2TokenExchange{Endpoint:"http://localhost:123",GrantType:grant,Resource:tEst,Audience:[],Scope:[],RequestedTokenType:urn:ietf:params:oauth:token-type:access_token,SubjectToken:FixedTokenSource{Token:"****(CRC-32c: 1203ABFA)",Type:test-token-type},From:"TestParseSettingsFromFile"}`, //nolint:lll
783+
ExpectedFormattedCredentials: `OAuth2TokenExchange{Endpoint:"http://localhost:123",GrantType:grant,Resource:[tEst],Audience:[],Scope:[],RequestedTokenType:urn:ietf:params:oauth:token-type:access_token,SubjectToken:FixedTokenSource{Token:"****(CRC-32c: 1203ABFA)",Type:test-token-type},From:"TestParseSettingsFromFile"}`, //nolint:lll
784784
},
785785
{
786786
Cfg: `{
787787
"token-endpoint": "http://localhost:123",
788788
"aud": "test-aud",
789+
"res": [
790+
"r1",
791+
"r2"
792+
],
789793
"scope": [
790794
"s1",
791795
"s2"
@@ -797,7 +801,7 @@ func TestParseSettingsFromFile(t *testing.T) {
797801
"token-type": "test-token-type"
798802
}
799803
}`,
800-
ExpectedFormattedCredentials: `OAuth2TokenExchange{Endpoint:"http://localhost:123",GrantType:urn:ietf:params:oauth:grant-type:token-exchange,Resource:,Audience:[test-aud],Scope:[s1 s2],RequestedTokenType:urn:ietf:params:oauth:token-type:access_token,ActorToken:FixedTokenSource{Token:"****(CRC-32c: 1203ABFA)",Type:test-token-type},From:"TestParseSettingsFromFile"}`, //nolint:lll
804+
ExpectedFormattedCredentials: `OAuth2TokenExchange{Endpoint:"http://localhost:123",GrantType:urn:ietf:params:oauth:grant-type:token-exchange,Resource:[r1 r2],Audience:[test-aud],Scope:[s1 s2],RequestedTokenType:urn:ietf:params:oauth:token-type:access_token,ActorToken:FixedTokenSource{Token:"****(CRC-32c: 1203ABFA)",Type:test-token-type},From:"TestParseSettingsFromFile"}`, //nolint:lll
801805
},
802806
{
803807
Cfg: `{
@@ -816,7 +820,7 @@ func TestParseSettingsFromFile(t *testing.T) {
816820
"unknown_field": [123]
817821
}
818822
}`,
819-
ExpectedFormattedCredentials: `OAuth2TokenExchange{Endpoint:"http://localhost:123",GrantType:urn:ietf:params:oauth:grant-type:token-exchange,Resource:,Audience:[],Scope:[],RequestedTokenType:access_token,SubjectToken:JWTTokenSource{Method:PS256,KeyID:test_key_id,Issuer:"test_issuer",Subject:"test_subject",Audience:[a1 a2],ID:123,TokenTTL:24h0m0s},From:"TestParseSettingsFromFile"}`, //nolint:lll
823+
ExpectedFormattedCredentials: `OAuth2TokenExchange{Endpoint:"http://localhost:123",GrantType:urn:ietf:params:oauth:grant-type:token-exchange,Resource:[],Audience:[],Scope:[],RequestedTokenType:access_token,SubjectToken:JWTTokenSource{Method:PS256,KeyID:test_key_id,Issuer:"test_issuer",Subject:"test_subject",Audience:[a1 a2],ID:123,TokenTTL:24h0m0s},From:"TestParseSettingsFromFile"}`, //nolint:lll
820824
},
821825
{
822826
Cfg: `{
@@ -828,7 +832,7 @@ func TestParseSettingsFromFile(t *testing.T) {
828832
"ttl": "3m"
829833
}
830834
}`,
831-
ExpectedFormattedCredentials: `OAuth2TokenExchange{Endpoint:"http://localhost:123",GrantType:urn:ietf:params:oauth:grant-type:token-exchange,Resource:,Audience:[],Scope:[],RequestedTokenType:urn:ietf:params:oauth:token-type:access_token,SubjectToken:JWTTokenSource{Method:ES256,KeyID:,Issuer:"",Subject:"",Audience:[],ID:,TokenTTL:3m0s},From:"TestParseSettingsFromFile"}`, //nolint:lll
835+
ExpectedFormattedCredentials: `OAuth2TokenExchange{Endpoint:"http://localhost:123",GrantType:urn:ietf:params:oauth:grant-type:token-exchange,Resource:[],Audience:[],Scope:[],RequestedTokenType:urn:ietf:params:oauth:token-type:access_token,SubjectToken:JWTTokenSource{Method:ES256,KeyID:,Issuer:"",Subject:"",Audience:[],ID:,TokenTTL:3m0s},From:"TestParseSettingsFromFile"}`, //nolint:lll
832836
},
833837
{
834838
Cfg: `{
@@ -839,7 +843,7 @@ func TestParseSettingsFromFile(t *testing.T) {
839843
"private-key": "` + testHMACSecretKeyBase64Content + `"
840844
}
841845
}`,
842-
ExpectedFormattedCredentials: `OAuth2TokenExchange{Endpoint:"http://localhost:123",GrantType:urn:ietf:params:oauth:grant-type:token-exchange,Resource:,Audience:[],Scope:[],RequestedTokenType:urn:ietf:params:oauth:token-type:access_token,SubjectToken:JWTTokenSource{Method:HS512,KeyID:,Issuer:"",Subject:"",Audience:[],ID:,TokenTTL:1h0m0s},From:"TestParseSettingsFromFile"}`, //nolint:lll
846+
ExpectedFormattedCredentials: `OAuth2TokenExchange{Endpoint:"http://localhost:123",GrantType:urn:ietf:params:oauth:grant-type:token-exchange,Resource:[],Audience:[],Scope:[],RequestedTokenType:urn:ietf:params:oauth:token-type:access_token,SubjectToken:JWTTokenSource{Method:HS512,KeyID:,Issuer:"",Subject:"",Audience:[],ID:,TokenTTL:1h0m0s},From:"TestParseSettingsFromFile"}`, //nolint:lll
843847
},
844848
{
845849
Cfg: `{

options.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ Config file must be a valid json file
8888
Fields of json file
8989
9090
grant-type: [string] Grant type option (default: "urn:ietf:params:oauth:grant-type:token-exchange")
91-
res: [string] Resource option (optional)
91+
res: [string | list of strings] Resource option (optional)
9292
aud: [string | list of strings] Audience option for token exchange request (optional)
9393
scope: [string | list of strings] Scope option (optional)
9494
requested-token-type: [string] Requested token type option (default: "urn:ietf:params:oauth:token-type:access_token")

0 commit comments

Comments
 (0)