Browse Source

Add ForceQueryResponseMode to OIDC connector

Adds a ForceQueryResponseMode configuration option which can be used to
enforce the response_mode parameter set to `query` in the LoginURL and
is set to false by default.

Although not `query` is the default response_mode according to the OIDC
specification some implementations use other response_modes by default
and therefore require this option to be set to work with Dex.

Fixes #2580

Signed-off-by: Johannes Tandler <johannes.tandler@zeiss.com>
pull/2714/head
Johannes Tandler 3 years ago
parent
commit
64eb0992fb
  1. 11
      connector/oidc/oidc.go
  2. 86
      connector/oidc/oidc_test.go

11
connector/oidc/oidc.go

@ -62,6 +62,11 @@ type Config struct {
// This setting allows you to override the default behavior of Dex and enforce the mappings defined in `claimMapping`.
OverrideClaimMapping bool `json:"overrideClaimMapping"` // defaults to false
// ForceQueryResponseModeSet ensures the `response_mode` query parameter to be explicitly set in the LoginURL.
// Although the OIDC specification defines query to be the default,
// some implementations require this parameter to be set in order to deliver the code as a query.
ForceQueryResponseMode bool `json:"forceQueryResponseMode"`
ClaimMapping struct {
// Configurable key which contains the preferred username claims
PreferredUsernameKey string `json:"preferred_username"` // defaults to "preferred_username"
@ -160,6 +165,7 @@ func (c *Config) Open(id string, logger log.Logger) (conn connector.Connector, e
userIDKey: c.UserIDKey,
userNameKey: c.UserNameKey,
overrideClaimMapping: c.OverrideClaimMapping,
forceQueryResponseMode: c.ForceQueryResponseMode,
preferredUsernameKey: c.ClaimMapping.PreferredUsernameKey,
emailKey: c.ClaimMapping.EmailKey,
groupsKey: c.ClaimMapping.GroupsKey,
@ -186,6 +192,7 @@ type oidcConnector struct {
userIDKey string
userNameKey string
overrideClaimMapping bool
forceQueryResponseMode bool
preferredUsernameKey string
emailKey string
groupsKey string
@ -208,6 +215,10 @@ func (c *oidcConnector) LoginURL(s connector.Scopes, callbackURL, state string)
opts = append(opts, oauth2.SetAuthURLParam("acr_values", acrValues))
}
if c.forceQueryResponseMode {
opts = append(opts, oauth2.SetAuthURLParam("response_mode", "query"))
}
if s.OfflineAccess {
opts = append(opts, oauth2.AccessTypeOffline, oauth2.SetAuthURLParam("prompt", c.promptType))
}

86
connector/oidc/oidc_test.go

@ -11,6 +11,7 @@ import (
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"reflect"
"strings"
"testing"
@ -42,6 +43,91 @@ func TestKnownBrokenAuthHeaderProvider(t *testing.T) {
}
}
func TestLoginURL(t *testing.T) {
t.Helper()
tests := []struct {
name string
forceQueryResponseModeSet bool
expectedURLValues map[string]string
expectedNonExistingURLValues []string
}{
{
name: "default",
expectedURLValues: map[string]string{
"scope": "openid email groups",
},
expectedNonExistingURLValues: []string{"response_mode"},
},
{
name: "forceResponseMode",
forceQueryResponseModeSet: true,
expectedURLValues: map[string]string{
"scope": "openid email groups",
"response_mode": "query",
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
idTokenDesired := true
testServer, err := setupServer(nil, idTokenDesired)
if err != nil {
t.Fatal("failed to setup test server", err)
}
defer testServer.Close()
scopes := []string{"email", "groups"}
serverURL := testServer.URL
config := Config{
Issuer: serverURL,
ClientID: "clientID",
ClientSecret: "clientSecret",
Scopes: scopes,
RedirectURI: fmt.Sprintf("%s/callback", serverURL),
InsecureEnableGroups: true,
}
if tc.forceQueryResponseModeSet {
config.ForceQueryResponseMode = true
}
conn, err := newConnector(config)
if err != nil {
t.Fatal("failed to create new connector", err)
}
state := "" // state is handled by the oAuth library, so no need to test it
loginURL, err := conn.LoginURL(connector.Scopes{}, config.RedirectURI, state)
if err != nil {
t.Error("unable to get login url", err)
}
u, err := url.Parse(loginURL)
if err != nil {
t.Fatal("failed to parse login url", err)
}
actualQueryParams := u.Query()
if tc.expectedURLValues != nil {
for param, values := range tc.expectedURLValues {
expectEquals(t, actualQueryParams.Get(param), values)
}
}
if tc.expectedNonExistingURLValues != nil {
for _, param := range tc.expectedNonExistingURLValues {
if actualQueryParams.Has(param) {
t.Error("Unexpected query param found", param)
}
}
}
})
}
}
func TestHandleCallback(t *testing.T) {
t.Helper()

Loading…
Cancel
Save