Merge pull request #974 from roguePanda/google-hosted-domain

Google hosted domain support
This commit is contained in:
rithu leena john 2017-07-07 10:26:28 -07:00 committed by GitHub
commit a5d218fd08
4 changed files with 144 additions and 9 deletions

View file

@ -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

View file

@ -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,

View file

@ -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 {
//
// }
//}

View file

@ -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