Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Google hosted domain support #974

Merged
merged 3 commits into from
Jul 7, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions Documentation/oidc-connector.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@ connectors:
# following field.
#
# 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
Expand Down
44 changes: 36 additions & 8 deletions connector/oidc/oidc.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ type Config struct {

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"`
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be json:"hostedDomains" (domains)

}

// Domains that don't support basic auth. golang.org/x/oauth2 has an internal
Expand Down Expand Up @@ -110,8 +113,9 @@ func (c *Config) Open(logger logrus.FieldLogger) (conn connector.Connector, err
verifier: provider.Verifier(
&oidc.Config{ClientID: clientID},
),
logger: logger,
cancel: cancel,
logger: logger,
cancel: cancel,
hostedDomains: c.HostedDomains,
}, nil
}

Expand All @@ -121,12 +125,13 @@ var (
)

type oidcConnector struct {
redirectURI string
oauth2Config *oauth2.Config
verifier *oidc.IDTokenVerifier
ctx context.Context
cancel context.CancelFunc
logger logrus.FieldLogger
redirectURI string
oauth2Config *oauth2.Config
verifier *oidc.IDTokenVerifier
ctx context.Context
cancel context.CancelFunc
logger logrus.FieldLogger
hostedDomains []string
}

func (c *oidcConnector) Close() error {
Expand All @@ -138,6 +143,14 @@ func (c *oidcConnector) LoginURL(s connector.Scopes, callbackURL, state string)
if c.redirectURI != callbackURL {
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
}

Expand Down Expand Up @@ -176,11 +189,26 @@ func (c *oidcConnector) HandleCallback(s connector.Scopes, r *http.Request) (ide
Username string `json:"name"`
Email string `json:"email"`
EmailVerified bool `json:"email_verified"`
HostedDomain string `json:"hd"`
}
if err := idToken.Claims(&claims); err != nil {
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 {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't it be if claims.HostedDomain == domain ?
@RoguePanda @rithujohn191

found = true
break
}
}

if !found {
return identity, fmt.Errorf("oidc: unexpected hd claim %v", claims.HostedDomain)
}
}

identity = connector.Identity{
UserID: idToken.Subject,
Username: claims.Username,
Expand Down
101 changes: 100 additions & 1 deletion connector/oidc/oidc_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
package oidc

import "testing"
import (
"github.com/Sirupsen/logrus"
"github.com/coreos/dex/connector"
"net/url"
"os"
"reflect"
"testing"
)

func TestKnownBrokenAuthHeaderProvider(t *testing.T) {
tests := []struct {
Expand All @@ -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 {
//
// }
//}
1 change: 1 addition & 0 deletions examples/config-dev.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ connectors:
# clientID: $GOOGLE_CLIENT_ID
# clientSecret: $GOOGLE_CLIENT_SECRET
# redirectURI: http://127.0.0.1:5556/dex/callback
# hostedDomain: $GOOGLE_HOSTED_DOMAIN
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm afraid this doesn't match the code. If I understand it correctly, it should be:

hostedDomains:
  - $GOOGLE_HOSTED_DOMAIN


# Let dex keep a list of passwords which can be used to login to dex.
enablePasswordDB: true
Expand Down