diff --git a/embedx/config.schema.json b/embedx/config.schema.json index 79bc83c88b1d..c3e991b63046 100644 --- a/embedx/config.schema.json +++ b/embedx/config.schema.json @@ -566,6 +566,11 @@ "enum": ["id_token", "userinfo"], "default": "id_token", "examples": ["id_token", "userinfo"] + }, + "upstream_parameters": { + "title": "Parameters to be passed to OIDC provider as part of auth code URL", + "type": "object", + "additionalProperties": true } }, "additionalProperties": false, diff --git a/selfservice/strategy/oidc/provider.go b/selfservice/strategy/oidc/provider.go index 30ea305a22ed..48a81f8100ac 100644 --- a/selfservice/strategy/oidc/provider.go +++ b/selfservice/strategy/oidc/provider.go @@ -26,7 +26,7 @@ type Provider interface { type OAuth2Provider interface { Provider - AuthCodeURLOptions(r ider) []oauth2.AuthCodeOption + AuthCodeURLOptions(r ider) ([]oauth2.AuthCodeOption, error) OAuth2(ctx context.Context) (*oauth2.Config, error) Claims(ctx context.Context, exchange *oauth2.Token, query url.Values) (*Claims, error) } diff --git a/selfservice/strategy/oidc/provider_apple.go b/selfservice/strategy/oidc/provider_apple.go index 706a7150c5e4..2755858a08f8 100644 --- a/selfservice/strategy/oidc/provider_apple.go +++ b/selfservice/strategy/oidc/provider_apple.go @@ -90,7 +90,7 @@ func (a *ProviderApple) OAuth2(ctx context.Context) (*oauth2.Config, error) { return a.oauth2(ctx) } -func (a *ProviderApple) AuthCodeURLOptions(r ider) []oauth2.AuthCodeOption { +func (a *ProviderApple) AuthCodeURLOptions(r ider) ([]oauth2.AuthCodeOption, error) { var options []oauth2.AuthCodeOption if isForced(r) { @@ -109,7 +109,7 @@ func (a *ProviderApple) AuthCodeURLOptions(r ider) []oauth2.AuthCodeOption { } } - return options + return options, nil } func (a *ProviderApple) Claims(ctx context.Context, exchange *oauth2.Token, query url.Values) (*Claims, error) { diff --git a/selfservice/strategy/oidc/provider_config.go b/selfservice/strategy/oidc/provider_config.go index 4eac8be4f7db..c51d26b88750 100644 --- a/selfservice/strategy/oidc/provider_config.go +++ b/selfservice/strategy/oidc/provider_config.go @@ -118,6 +118,9 @@ type Configuration struct { // endpoint to get the claims) or `id_token` (takes the claims from the id // token). It defaults to `id_token`. ClaimsSource string `json:"claims_source"` + + // Parameters to be passed to OIDC provider as part of auth code URL + UpstreamParameters json.RawMessage `json:"upstream_parameters"` } func (p Configuration) Redir(public *url.URL) string { diff --git a/selfservice/strategy/oidc/provider_dingtalk.go b/selfservice/strategy/oidc/provider_dingtalk.go index 12abffe85942..668478e58d52 100644 --- a/selfservice/strategy/oidc/provider_dingtalk.go +++ b/selfservice/strategy/oidc/provider_dingtalk.go @@ -55,10 +55,10 @@ func (g *ProviderDingTalk) oauth2(ctx context.Context) *oauth2.Config { } } -func (g *ProviderDingTalk) AuthCodeURLOptions(r ider) []oauth2.AuthCodeOption { +func (g *ProviderDingTalk) AuthCodeURLOptions(r ider) ([]oauth2.AuthCodeOption, error) { return []oauth2.AuthCodeOption{ oauth2.SetAuthURLParam("prompt", "consent"), - } + }, nil } func (g *ProviderDingTalk) OAuth2(ctx context.Context) (*oauth2.Config, error) { diff --git a/selfservice/strategy/oidc/provider_discord.go b/selfservice/strategy/oidc/provider_discord.go index 99bea24d5770..4b9ad81c80e5 100644 --- a/selfservice/strategy/oidc/provider_discord.go +++ b/selfservice/strategy/oidc/provider_discord.go @@ -55,15 +55,15 @@ func (d *ProviderDiscord) OAuth2(ctx context.Context) (*oauth2.Config, error) { return d.oauth2(ctx), nil } -func (d *ProviderDiscord) AuthCodeURLOptions(r ider) []oauth2.AuthCodeOption { +func (d *ProviderDiscord) AuthCodeURLOptions(r ider) ([]oauth2.AuthCodeOption, error) { if isForced(r) { return []oauth2.AuthCodeOption{ oauth2.SetAuthURLParam("prompt", "consent"), - } + }, nil } return []oauth2.AuthCodeOption{ oauth2.SetAuthURLParam("prompt", "none"), - } + }, nil } func (d *ProviderDiscord) Claims(ctx context.Context, exchange *oauth2.Token, query url.Values) (*Claims, error) { diff --git a/selfservice/strategy/oidc/provider_generic_oidc.go b/selfservice/strategy/oidc/provider_generic_oidc.go index 146505165807..d226d277a266 100644 --- a/selfservice/strategy/oidc/provider_generic_oidc.go +++ b/selfservice/strategy/oidc/provider_generic_oidc.go @@ -5,6 +5,7 @@ package oidc import ( "context" + "encoding/json" "net/url" "github.com/pkg/errors" @@ -83,7 +84,7 @@ func (g *ProviderGenericOIDC) OAuth2(ctx context.Context) (*oauth2.Config, error return g.oauth2ConfigFromEndpoint(ctx, endpoint), nil } -func (g *ProviderGenericOIDC) AuthCodeURLOptions(r ider) []oauth2.AuthCodeOption { +func (g *ProviderGenericOIDC) AuthCodeURLOptions(r ider) ([]oauth2.AuthCodeOption, error) { var options []oauth2.AuthCodeOption if isForced(r) { @@ -93,7 +94,17 @@ func (g *ProviderGenericOIDC) AuthCodeURLOptions(r ider) []oauth2.AuthCodeOption options = append(options, oauth2.SetAuthURLParam("claims", string(g.config.RequestedClaims))) } - return options + if len(g.config.UpstreamParameters) != 0 { + var params map[string]string + if err := json.Unmarshal(g.config.UpstreamParameters, ¶ms); err != nil { + return nil, err + } + for key, value := range params { + options = append(options, oauth2.SetAuthURLParam(key, value)) + } + } + + return options, nil } func (g *ProviderGenericOIDC) verifyAndDecodeClaimsWithProvider(ctx context.Context, provider *gooidc.Provider, raw string) (*Claims, error) { diff --git a/selfservice/strategy/oidc/provider_generic_test.go b/selfservice/strategy/oidc/provider_generic_test.go index 7c90da7e3ec5..5df7bdd8f0e1 100644 --- a/selfservice/strategy/oidc/provider_generic_test.go +++ b/selfservice/strategy/oidc/provider_generic_test.go @@ -37,17 +37,20 @@ func makeOIDCClaims() json.RawMessage { func makeAuthCodeURL(t *testing.T, r *login.Flow, reg *driver.RegistryDefault) string { p := oidc.NewProviderGenericOIDC(&oidc.Configuration{ - Provider: "generic", - ID: "valid", - ClientID: "client", - ClientSecret: "secret", - IssuerURL: "https://accounts.google.com", - Mapper: "file://./stub/hydra.schema.json", - RequestedClaims: makeOIDCClaims(), + Provider: "generic", + ID: "valid", + ClientID: "client", + ClientSecret: "secret", + IssuerURL: "https://accounts.google.com", + Mapper: "file://./stub/hydra.schema.json", + RequestedClaims: makeOIDCClaims(), + UpstreamParameters: json.RawMessage(`{"connection": "c1"}`), }, reg) c, err := p.(oidc.OAuth2Provider).OAuth2(context.Background()) require.NoError(t, err) - return c.AuthCodeURL("state", p.(oidc.OAuth2Provider).AuthCodeURLOptions(r)...) + options, err := p.(oidc.OAuth2Provider).AuthCodeURLOptions(r) + require.NoError(t, err) + return c.AuthCodeURL("state", options...) } func TestProviderGenericOIDC_AddAuthCodeURLOptions(t *testing.T) { @@ -93,4 +96,11 @@ func TestProviderGenericOIDC_AddAuthCodeURLOptions(t *testing.T) { } assert.Contains(t, makeAuthCodeURL(t, r, reg), "claims="+url.QueryEscape(string(makeOIDCClaims()))) }) + + t.Run("case=expect connection to be set", func(t *testing.T) { + r := &login.Flow{ + ID: x.NewUUID(), + } + assert.Contains(t, makeAuthCodeURL(t, r, reg), "connection=c1") + }) } diff --git a/selfservice/strategy/oidc/provider_github.go b/selfservice/strategy/oidc/provider_github.go index fe1d2bc371d1..27c75203a47f 100644 --- a/selfservice/strategy/oidc/provider_github.go +++ b/selfservice/strategy/oidc/provider_github.go @@ -56,8 +56,8 @@ func (g *ProviderGitHub) OAuth2(ctx context.Context) (*oauth2.Config, error) { return g.oauth2(ctx), nil } -func (g *ProviderGitHub) AuthCodeURLOptions(r ider) []oauth2.AuthCodeOption { - return []oauth2.AuthCodeOption{} +func (g *ProviderGitHub) AuthCodeURLOptions(r ider) ([]oauth2.AuthCodeOption, error) { + return []oauth2.AuthCodeOption{}, nil } func (g *ProviderGitHub) Claims(ctx context.Context, exchange *oauth2.Token, query url.Values) (*Claims, error) { diff --git a/selfservice/strategy/oidc/provider_github_app.go b/selfservice/strategy/oidc/provider_github_app.go index 95801ce59e47..c4e4588754f5 100644 --- a/selfservice/strategy/oidc/provider_github_app.go +++ b/selfservice/strategy/oidc/provider_github_app.go @@ -53,8 +53,8 @@ func (g *ProviderGitHubApp) OAuth2(ctx context.Context) (*oauth2.Config, error) return g.oauth2(ctx), nil } -func (g *ProviderGitHubApp) AuthCodeURLOptions(r ider) []oauth2.AuthCodeOption { - return []oauth2.AuthCodeOption{} +func (g *ProviderGitHubApp) AuthCodeURLOptions(r ider) ([]oauth2.AuthCodeOption, error) { + return []oauth2.AuthCodeOption{}, nil } func (g *ProviderGitHubApp) Claims(ctx context.Context, exchange *oauth2.Token, query url.Values) (*Claims, error) { diff --git a/selfservice/strategy/oidc/provider_google.go b/selfservice/strategy/oidc/provider_google.go index e27832692faa..d70e3a9b3aa7 100644 --- a/selfservice/strategy/oidc/provider_google.go +++ b/selfservice/strategy/oidc/provider_google.go @@ -58,15 +58,18 @@ func (g *ProviderGoogle) OAuth2(ctx context.Context) (*oauth2.Config, error) { return g.oauth2ConfigFromEndpoint(ctx, endpoint), nil } -func (g *ProviderGoogle) AuthCodeURLOptions(r ider) []oauth2.AuthCodeOption { +func (g *ProviderGoogle) AuthCodeURLOptions(r ider) ([]oauth2.AuthCodeOption, error) { scope := g.config.Scope - options := g.ProviderGenericOIDC.AuthCodeURLOptions(r) + options, err := g.ProviderGenericOIDC.AuthCodeURLOptions(r) + if err != nil { + return nil, err + } if stringslice.Has(scope, gooidc.ScopeOfflineAccess) { options = append(options, oauth2.AccessTypeOffline) } - return options + return options, nil } var _ IDTokenVerifier = new(ProviderGoogle) diff --git a/selfservice/strategy/oidc/provider_google_test.go b/selfservice/strategy/oidc/provider_google_test.go index c1a1f65b9348..f6a5705c4aea 100644 --- a/selfservice/strategy/oidc/provider_google_test.go +++ b/selfservice/strategy/oidc/provider_google_test.go @@ -55,7 +55,8 @@ func TestProviderGoogle_AccessType(t *testing.T) { ID: x.NewUUID(), } - options := p.(oidc.OAuth2Provider).AuthCodeURLOptions(r) + options, err := p.(oidc.OAuth2Provider).AuthCodeURLOptions(r) + require.NoError(t, err) assert.Contains(t, options, oauth2.AccessTypeOffline) } diff --git a/selfservice/strategy/oidc/provider_lark.go b/selfservice/strategy/oidc/provider_lark.go index 52902dc20e8c..bcdb838e102a 100644 --- a/selfservice/strategy/oidc/provider_lark.go +++ b/selfservice/strategy/oidc/provider_lark.go @@ -112,6 +112,6 @@ func (g *ProviderLark) Claims(ctx context.Context, exchange *oauth2.Token, query }, nil } -func (pl *ProviderLark) AuthCodeURLOptions(r ider) []oauth2.AuthCodeOption { - return []oauth2.AuthCodeOption{} +func (pl *ProviderLark) AuthCodeURLOptions(r ider) ([]oauth2.AuthCodeOption, error) { + return []oauth2.AuthCodeOption{}, nil } diff --git a/selfservice/strategy/oidc/provider_linkedin.go b/selfservice/strategy/oidc/provider_linkedin.go index 03a3db3e490d..859fd2ea3f31 100644 --- a/selfservice/strategy/oidc/provider_linkedin.go +++ b/selfservice/strategy/oidc/provider_linkedin.go @@ -96,8 +96,8 @@ func (l *ProviderLinkedIn) OAuth2(ctx context.Context) (*oauth2.Config, error) { return l.oauth2(ctx), nil } -func (l *ProviderLinkedIn) AuthCodeURLOptions(r ider) []oauth2.AuthCodeOption { - return []oauth2.AuthCodeOption{} +func (l *ProviderLinkedIn) AuthCodeURLOptions(r ider) ([]oauth2.AuthCodeOption, error) { + return []oauth2.AuthCodeOption{}, nil } func (l *ProviderLinkedIn) fetch(ctx context.Context, client *retryablehttp.Client, url string, result interface{}) (err error) { diff --git a/selfservice/strategy/oidc/provider_patreon.go b/selfservice/strategy/oidc/provider_patreon.go index 745dc8fcc199..9b6969123a8e 100644 --- a/selfservice/strategy/oidc/provider_patreon.go +++ b/selfservice/strategy/oidc/provider_patreon.go @@ -68,15 +68,15 @@ func (d *ProviderPatreon) OAuth2(ctx context.Context) (*oauth2.Config, error) { return d.oauth2(ctx), nil } -func (d *ProviderPatreon) AuthCodeURLOptions(r ider) []oauth2.AuthCodeOption { +func (d *ProviderPatreon) AuthCodeURLOptions(r ider) ([]oauth2.AuthCodeOption, error) { if isForced(r) { return []oauth2.AuthCodeOption{ oauth2.SetAuthURLParam("prompt", "consent"), - } + }, nil } return []oauth2.AuthCodeOption{ oauth2.SetAuthURLParam("prompt", "none"), - } + }, nil } func (d *ProviderPatreon) Claims(ctx context.Context, exchange *oauth2.Token, query url.Values) (*Claims, error) { diff --git a/selfservice/strategy/oidc/provider_slack.go b/selfservice/strategy/oidc/provider_slack.go index 7c7e26c99da4..26630a90a218 100644 --- a/selfservice/strategy/oidc/provider_slack.go +++ b/selfservice/strategy/oidc/provider_slack.go @@ -57,8 +57,8 @@ func (d *ProviderSlack) OAuth2(ctx context.Context) (*oauth2.Config, error) { return d.oauth2(ctx), nil } -func (d *ProviderSlack) AuthCodeURLOptions(r ider) []oauth2.AuthCodeOption { - return []oauth2.AuthCodeOption{} +func (d *ProviderSlack) AuthCodeURLOptions(r ider) ([]oauth2.AuthCodeOption, error) { + return []oauth2.AuthCodeOption{}, nil } func (d *ProviderSlack) Claims(ctx context.Context, exchange *oauth2.Token, query url.Values) (*Claims, error) { diff --git a/selfservice/strategy/oidc/provider_spotify.go b/selfservice/strategy/oidc/provider_spotify.go index 366105c94d0e..9eeb1d1f535d 100644 --- a/selfservice/strategy/oidc/provider_spotify.go +++ b/selfservice/strategy/oidc/provider_spotify.go @@ -56,8 +56,8 @@ func (g *ProviderSpotify) OAuth2(ctx context.Context) (*oauth2.Config, error) { return g.oauth2(ctx), nil } -func (g *ProviderSpotify) AuthCodeURLOptions(r ider) []oauth2.AuthCodeOption { - return []oauth2.AuthCodeOption{} +func (g *ProviderSpotify) AuthCodeURLOptions(r ider) ([]oauth2.AuthCodeOption, error) { + return []oauth2.AuthCodeOption{}, nil } func (g *ProviderSpotify) Claims(ctx context.Context, exchange *oauth2.Token, query url.Values) (*Claims, error) { diff --git a/selfservice/strategy/oidc/provider_vk.go b/selfservice/strategy/oidc/provider_vk.go index 2a3513b6e050..b751b296f7e8 100644 --- a/selfservice/strategy/oidc/provider_vk.go +++ b/selfservice/strategy/oidc/provider_vk.go @@ -51,8 +51,8 @@ func (g *ProviderVK) oauth2(ctx context.Context) *oauth2.Config { } } -func (g *ProviderVK) AuthCodeURLOptions(r ider) []oauth2.AuthCodeOption { - return []oauth2.AuthCodeOption{} +func (g *ProviderVK) AuthCodeURLOptions(r ider) ([]oauth2.AuthCodeOption, error) { + return []oauth2.AuthCodeOption{}, nil } func (g *ProviderVK) OAuth2(ctx context.Context) (*oauth2.Config, error) { diff --git a/selfservice/strategy/oidc/provider_yandex.go b/selfservice/strategy/oidc/provider_yandex.go index 07b30caee52b..0b8da0694bdd 100644 --- a/selfservice/strategy/oidc/provider_yandex.go +++ b/selfservice/strategy/oidc/provider_yandex.go @@ -49,8 +49,8 @@ func (g *ProviderYandex) oauth2(ctx context.Context) *oauth2.Config { } } -func (g *ProviderYandex) AuthCodeURLOptions(r ider) []oauth2.AuthCodeOption { - return []oauth2.AuthCodeOption{} +func (g *ProviderYandex) AuthCodeURLOptions(r ider) ([]oauth2.AuthCodeOption, error) { + return []oauth2.AuthCodeOption{}, nil } func (g *ProviderYandex) OAuth2(ctx context.Context) (*oauth2.Config, error) { diff --git a/selfservice/strategy/oidc/strategy.go b/selfservice/strategy/oidc/strategy.go index 449bfece3878..7c362a9c3c70 100644 --- a/selfservice/strategy/oidc/strategy.go +++ b/selfservice/strategy/oidc/strategy.go @@ -802,7 +802,11 @@ func getAuthRedirectURL(ctx context.Context, provider Provider, req ider, state return "", err } - return c.AuthCodeURL(state.String(), append(UpstreamParameters(upstreamParameters), p.AuthCodeURLOptions(req)...)...), nil + options, err := p.AuthCodeURLOptions(req) + if err != nil { + return "", nil + } + return c.AuthCodeURL(state.String(), append(UpstreamParameters(upstreamParameters), options...)...), nil case OAuth1Provider: return p.AuthURL(ctx, state.String()) default: