forked from mystiq/dex
Merge pull request #974 from roguePanda/google-hosted-domain
Google hosted domain support
This commit is contained in:
commit
a5d218fd08
4 changed files with 144 additions and 9 deletions
|
@ -42,6 +42,13 @@ connectors:
|
||||||
# following field.
|
# following field.
|
||||||
#
|
#
|
||||||
# basicAuthUnsupported: true
|
# basicAuthUnsupported: true
|
||||||
|
|
||||||
|
# Google supports whitelisting allowed domains when using G Suite
|
||||||
|
# (Google Apps). The following field can be set to a list of domains
|
||||||
|
# that can log in:
|
||||||
|
#
|
||||||
|
# hostedDomains:
|
||||||
|
# - example.com
|
||||||
```
|
```
|
||||||
|
|
||||||
[oidc-doc]: openid-connect.md
|
[oidc-doc]: openid-connect.md
|
||||||
|
|
|
@ -33,6 +33,9 @@ type Config struct {
|
||||||
|
|
||||||
Scopes []string `json:"scopes"` // defaults to "profile" and "email"
|
Scopes []string `json:"scopes"` // defaults to "profile" and "email"
|
||||||
|
|
||||||
|
// Optional list of whitelisted domains when using Google
|
||||||
|
// If this field is nonempty, only users from a listed domain will be allowed to log in
|
||||||
|
HostedDomains []string `json:"hostedDomain"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Domains that don't support basic auth. golang.org/x/oauth2 has an internal
|
// Domains that don't support basic auth. golang.org/x/oauth2 has an internal
|
||||||
|
@ -110,8 +113,9 @@ func (c *Config) Open(logger logrus.FieldLogger) (conn connector.Connector, err
|
||||||
verifier: provider.Verifier(
|
verifier: provider.Verifier(
|
||||||
&oidc.Config{ClientID: clientID},
|
&oidc.Config{ClientID: clientID},
|
||||||
),
|
),
|
||||||
logger: logger,
|
logger: logger,
|
||||||
cancel: cancel,
|
cancel: cancel,
|
||||||
|
hostedDomains: c.HostedDomains,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,12 +125,13 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
type oidcConnector struct {
|
type oidcConnector struct {
|
||||||
redirectURI string
|
redirectURI string
|
||||||
oauth2Config *oauth2.Config
|
oauth2Config *oauth2.Config
|
||||||
verifier *oidc.IDTokenVerifier
|
verifier *oidc.IDTokenVerifier
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
logger logrus.FieldLogger
|
logger logrus.FieldLogger
|
||||||
|
hostedDomains []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *oidcConnector) Close() error {
|
func (c *oidcConnector) Close() error {
|
||||||
|
@ -138,6 +143,14 @@ func (c *oidcConnector) LoginURL(s connector.Scopes, callbackURL, state string)
|
||||||
if c.redirectURI != callbackURL {
|
if c.redirectURI != callbackURL {
|
||||||
return "", fmt.Errorf("expected callback URL %q did not match the URL in the config %q", callbackURL, c.redirectURI)
|
return "", fmt.Errorf("expected callback URL %q did not match the URL in the config %q", callbackURL, c.redirectURI)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(c.hostedDomains) > 0 {
|
||||||
|
preferredDomain := c.hostedDomains[0]
|
||||||
|
if len(c.hostedDomains) > 1 {
|
||||||
|
preferredDomain = "*"
|
||||||
|
}
|
||||||
|
return c.oauth2Config.AuthCodeURL(state, oauth2.SetAuthURLParam("hd", preferredDomain)), nil
|
||||||
|
}
|
||||||
return c.oauth2Config.AuthCodeURL(state), nil
|
return c.oauth2Config.AuthCodeURL(state), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,11 +189,26 @@ func (c *oidcConnector) HandleCallback(s connector.Scopes, r *http.Request) (ide
|
||||||
Username string `json:"name"`
|
Username string `json:"name"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
EmailVerified bool `json:"email_verified"`
|
EmailVerified bool `json:"email_verified"`
|
||||||
|
HostedDomain string `json:"hd"`
|
||||||
}
|
}
|
||||||
if err := idToken.Claims(&claims); err != nil {
|
if err := idToken.Claims(&claims); err != nil {
|
||||||
return identity, fmt.Errorf("oidc: failed to decode claims: %v", err)
|
return identity, fmt.Errorf("oidc: failed to decode claims: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(c.hostedDomains) > 0 {
|
||||||
|
found := false
|
||||||
|
for _, domain := range c.hostedDomains {
|
||||||
|
if claims.HostedDomain != domain {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
return identity, fmt.Errorf("oidc: unexpected hd claim %v", claims.HostedDomain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
identity = connector.Identity{
|
identity = connector.Identity{
|
||||||
UserID: idToken.Subject,
|
UserID: idToken.Subject,
|
||||||
Username: claims.Username,
|
Username: claims.Username,
|
||||||
|
|
|
@ -1,6 +1,13 @@
|
||||||
package oidc
|
package oidc
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/coreos/dex/connector"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
func TestKnownBrokenAuthHeaderProvider(t *testing.T) {
|
func TestKnownBrokenAuthHeaderProvider(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
|
@ -21,3 +28,95 @@ func TestKnownBrokenAuthHeaderProvider(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestOidcConnector_LoginURL(t *testing.T) {
|
||||||
|
logger := &logrus.Logger{
|
||||||
|
Out: os.Stderr,
|
||||||
|
Formatter: &logrus.TextFormatter{DisableColors: true},
|
||||||
|
Level: logrus.DebugLevel,
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
scopes connector.Scopes
|
||||||
|
hostedDomains []string
|
||||||
|
|
||||||
|
wantScopes string
|
||||||
|
wantHdParam string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
connector.Scopes{}, []string{"example.com"},
|
||||||
|
"openid profile email", "example.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
connector.Scopes{}, []string{"mydomain.org", "example.com"},
|
||||||
|
"openid profile email", "*",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
connector.Scopes{}, []string{},
|
||||||
|
"openid profile email", "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
connector.Scopes{OfflineAccess: true}, []string{},
|
||||||
|
"openid profile email", "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
callback := "https://dex.example.com/callback"
|
||||||
|
state := "secret"
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
config := &Config{
|
||||||
|
Issuer: "https://accounts.google.com",
|
||||||
|
ClientID: "client-id",
|
||||||
|
ClientSecret: "client-secret",
|
||||||
|
RedirectURI: "https://dex.example.com/callback",
|
||||||
|
HostedDomains: test.hostedDomains,
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := config.Open(logger)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to open connector: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
loginURL, err := conn.(connector.CallbackConnector).LoginURL(test.scopes, callback, state)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to get login URL: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
actual, err := url.Parse(loginURL)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to parse login URL: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
wanted, _ := url.Parse("https://accounts.google.com/o/oauth2/v2/auth")
|
||||||
|
wantedQuery := &url.Values{}
|
||||||
|
wantedQuery.Set("client_id", config.ClientID)
|
||||||
|
wantedQuery.Set("redirect_uri", config.RedirectURI)
|
||||||
|
wantedQuery.Set("response_type", "code")
|
||||||
|
wantedQuery.Set("state", "secret")
|
||||||
|
wantedQuery.Set("scope", test.wantScopes)
|
||||||
|
if test.wantHdParam != "" {
|
||||||
|
wantedQuery.Set("hd", test.wantHdParam)
|
||||||
|
}
|
||||||
|
wanted.RawQuery = wantedQuery.Encode()
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(actual, wanted) {
|
||||||
|
t.Errorf("Wanted %v, got %v", wanted, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//func TestOidcConnector_HandleCallback(t *testing.T) {
|
||||||
|
// logger := &logrus.Logger{
|
||||||
|
// Out: os.Stderr,
|
||||||
|
// Formatter: &logrus.TextFormatter{DisableColors: true},
|
||||||
|
// Level: logrus.DebugLevel,
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// tests := []struct {
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
|
@ -67,6 +67,7 @@ connectors:
|
||||||
# clientID: $GOOGLE_CLIENT_ID
|
# clientID: $GOOGLE_CLIENT_ID
|
||||||
# clientSecret: $GOOGLE_CLIENT_SECRET
|
# clientSecret: $GOOGLE_CLIENT_SECRET
|
||||||
# redirectURI: http://127.0.0.1:5556/dex/callback
|
# redirectURI: http://127.0.0.1:5556/dex/callback
|
||||||
|
# hostedDomain: $GOOGLE_HOSTED_DOMAIN
|
||||||
|
|
||||||
# Let dex keep a list of passwords which can be used to login to dex.
|
# Let dex keep a list of passwords which can be used to login to dex.
|
||||||
enablePasswordDB: true
|
enablePasswordDB: true
|
||||||
|
|
Loading…
Reference in a new issue