Skip to content

Commit 6985315

Browse files
committed
OAuth 2.0 token exchange. Allow multiple resource parameters
1 parent e2adc87 commit 6985315

File tree

6 files changed

+32
-24
lines changed

6 files changed

+32
-24
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. Allow 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: 2 additions & 2 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) Oauth2TokenExchangeCredentialsOption {
40+
return credentials.WithResource(resource...)
4141
}
4242

4343
// RequestedTokenType

internal/credentials/oauth2.go

Lines changed: 13 additions & 11 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 = resource
150150

151151
return nil
152152
}
153153

154-
func WithResource(resource string) resourceOption {
155-
return resourceOption(resource)
154+
func WithResource(resource ...string) resourceOption {
155+
return resource
156156
}
157157

158158
// RequestedTokenType
@@ -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

@@ -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,8 +622,8 @@ 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...))
627627
}
628628

629629
if cfg.Audience != nil && len(cfg.Audience.Values) > 0 {
@@ -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,

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)