From 0857a0fe09a7dd9e45b26d966ce2ef84d7e7545a Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Mon, 29 Jan 2018 21:07:15 +0000 Subject: [PATCH] Implement refresh in OIDC connector This has added the access=offline parameter and prompt=consent parameter to the initial request, this works with google, assuming other providers will ignore the prompt parameter --- connector/oidc/oidc.go | 55 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/connector/oidc/oidc.go b/connector/oidc/oidc.go index b5e075ad..dfab061a 100644 --- a/connector/oidc/oidc.go +++ b/connector/oidc/oidc.go @@ -9,6 +9,7 @@ import ( "net/url" "strings" "sync" + "time" "github.com/coreos/go-oidc" "golang.org/x/oauth2" @@ -172,9 +173,9 @@ func (c *oidcConnector) LoginURL(s connector.Scopes, callbackURL, state string) if len(c.hostedDomains) > 1 { preferredDomain = "*" } - return c.oauth2Config.AuthCodeURL(state, oauth2.SetAuthURLParam("hd", preferredDomain)), nil + return c.oauth2Config.AuthCodeURL(state, oauth2.AccessTypeOffline, oauth2.SetAuthURLParam("prompt", "consent"), oauth2.SetAuthURLParam("hd", preferredDomain)), nil } - return c.oauth2Config.AuthCodeURL(state), nil + return c.oauth2Config.AuthCodeURL(state, oauth2.AccessTypeOffline, oauth2.SetAuthURLParam("prompt", "consent")), nil } type oauth2Error struct { @@ -265,6 +266,7 @@ func (c *oidcConnector) HandleCallback(s connector.Scopes, r *http.Request) (ide Username: name, Email: email, EmailVerified: emailVerified, + ConnectorData: []byte(token.RefreshToken), } if c.userIDKey != "" { @@ -280,5 +282,54 @@ func (c *oidcConnector) HandleCallback(s connector.Scopes, r *http.Request) (ide // Refresh is implemented for backwards compatibility, even though it's a no-op. func (c *oidcConnector) Refresh(ctx context.Context, s connector.Scopes, identity connector.Identity) (connector.Identity, error) { + t := &oauth2.Token{ + RefreshToken: string(identity.ConnectorData), + Expiry: time.Now().Add(-time.Hour), + } + token, err := c.oauth2Config.TokenSource(ctx, t).Token() + if err != nil { + return identity, fmt.Errorf("oidc: failed to get token: %v", err) + } + + rawIDToken, ok := token.Extra("id_token").(string) + if !ok { + return identity, errors.New("oidc: no id_token in token response") + } + idToken, err := c.verifier.Verify(ctx, rawIDToken) + if err != nil { + return identity, fmt.Errorf("oidc: failed to verify ID Token: %v", err) + } + + var claims struct { + 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 { + 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, + Email: claims.Email, + EmailVerified: claims.EmailVerified, + ConnectorData: []byte(token.RefreshToken), + } return identity, nil }