From 575c792156e25e0f71cc3d4781489706e76a21eb Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Sun, 28 Jan 2018 22:37:07 +0000 Subject: [PATCH 01/27] Store most recent refresh token in offline sessions --- server/handlers.go | 33 +++++++++++++++++++++++++++++++++ storage/storage.go | 3 +++ 2 files changed, 36 insertions(+) diff --git a/server/handlers.go b/server/handlers.go index b528918f..3be6f380 100644 --- a/server/handlers.go +++ b/server/handlers.go @@ -505,6 +505,39 @@ func (s *Server) finalizeLogin(identity connector.Identity, authReq storage.Auth s.logger.Infof("login successful: connector %q, username=%q, preferred_username=%q, email=%q, groups=%q", authReq.ConnectorID, claims.Username, claims.PreferredUsername, email, claims.Groups) + if _, ok := conn.(connector.RefreshConnector); ok { + // Try to retrieve an existing OfflineSession object for the corresponding user. + if session, err := s.storage.GetOfflineSessions(identity.UserID, authReq.ConnectorID); err != nil { + if err != storage.ErrNotFound { + s.logger.Errorf("failed to get offline session: %v", err) + return "", err + } + offlineSessions := storage.OfflineSessions{ + UserID: identity.UserID, + ConnID: authReq.ConnectorID, + Refresh: make(map[string]*storage.RefreshTokenRef), + ConnectorData: identity.ConnectorData, + } + + // Create a new OfflineSession object for the user and add a reference object for + // the newly received refreshtoken. + if err := s.storage.CreateOfflineSessions(offlineSessions); err != nil { + s.logger.Errorf("failed to create offline session: %v", err) + return "", err + } + } else { + // Update existing OfflineSession obj with new RefreshTokenRef. + if err := s.storage.UpdateOfflineSessions(session.UserID, session.ConnID, func(old storage.OfflineSessions) (storage.OfflineSessions, error) { + old.ConnectorData = identity.ConnectorData + return old, nil + }); err != nil { + s.logger.Errorf("failed to update offline session: %v", err) + return "", err + } + + } + } + return path.Join(s.issuerURL.Path, "/approval") + "?req=" + authReq.ID, nil } diff --git a/storage/storage.go b/storage/storage.go index 235f74e0..cb2a7e0c 100644 --- a/storage/storage.go +++ b/storage/storage.go @@ -273,6 +273,9 @@ type OfflineSessions struct { // Refresh is a hash table of refresh token reference objects // indexed by the ClientID of the refresh token. Refresh map[string]*RefreshTokenRef + + // Authentication data provided by an upstream source. + ConnectorData []byte } // Password is an email to password mapping managed by the storage. From 0352258093d4882adc6ad480d7de5816ee8d2b82 Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Mon, 29 Jan 2018 20:46:18 +0000 Subject: [PATCH 02/27] Update handleRefreshToken logic --- server/handlers.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/server/handlers.go b/server/handlers.go index 3be6f380..1c320b29 100644 --- a/server/handlers.go +++ b/server/handlers.go @@ -995,6 +995,16 @@ func (s *Server) handleRefreshToken(w http.ResponseWriter, r *http.Request, clie scopes = requestedScopes } + var connectorData []byte + if session, err := s.storage.GetOfflineSessions(refresh.Claims.UserID, refresh.ConnectorID); err != nil { + if err != storage.ErrNotFound { + s.logger.Errorf("failed to get offline session: %v", err) + return + } + } else { + connectorData = session.ConnectorData + } + conn, err := s.getConnector(refresh.ConnectorID) if err != nil { s.logger.Errorf("connector with ID %q not found: %v", refresh.ConnectorID, err) @@ -1008,7 +1018,7 @@ func (s *Server) handleRefreshToken(w http.ResponseWriter, r *http.Request, clie Email: refresh.Claims.Email, EmailVerified: refresh.Claims.EmailVerified, Groups: refresh.Claims.Groups, - ConnectorData: refresh.ConnectorData, + ConnectorData: connectorData, } // Can the connector refresh the identity? If so, attempt to refresh the data @@ -1074,7 +1084,6 @@ func (s *Server) handleRefreshToken(w http.ResponseWriter, r *http.Request, clie old.Claims.Email = ident.Email old.Claims.EmailVerified = ident.EmailVerified old.Claims.Groups = ident.Groups - old.ConnectorData = ident.ConnectorData old.LastUsed = lastUsed return old, nil } @@ -1086,6 +1095,7 @@ func (s *Server) handleRefreshToken(w http.ResponseWriter, r *http.Request, clie return old, errors.New("refresh token invalid") } old.Refresh[refresh.ClientID].LastUsed = lastUsed + old.ConnectorData = ident.ConnectorData return old, nil }); err != nil { s.logger.Errorf("failed to update offline session: %v", err) From 5c8871317748cb76f6000c03af1095604a09235a Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Mon, 29 Jan 2018 20:48:40 +0000 Subject: [PATCH 03/27] Remove connectordata from other structs --- server/api_test.go | 1 - server/handlers.go | 37 +++++++++++++++++-------------------- storage/storage.go | 13 +++++-------- 3 files changed, 22 insertions(+), 29 deletions(-) diff --git a/server/api_test.go b/server/api_test.go index 80a22486..a7891208 100644 --- a/server/api_test.go +++ b/server/api_test.go @@ -259,7 +259,6 @@ func TestRefreshToken(t *testing.T) { EmailVerified: true, Groups: []string{"a", "b"}, }, - ConnectorData: []byte(`{"some":"data"}`), } if err := s.CreateRefresh(r); err != nil { diff --git a/server/handlers.go b/server/handlers.go index 1c320b29..08bf5d04 100644 --- a/server/handlers.go +++ b/server/handlers.go @@ -490,7 +490,6 @@ func (s *Server) finalizeLogin(identity connector.Identity, authReq storage.Auth updater := func(a storage.AuthRequest) (storage.AuthRequest, error) { a.LoggedIn = true a.Claims = claims - a.ConnectorData = identity.ConnectorData return a, nil } if err := s.storage.UpdateAuthRequest(authReq.ID, updater); err != nil { @@ -620,15 +619,14 @@ func (s *Server) sendCodeResponse(w http.ResponseWriter, r *http.Request, authRe switch responseType { case responseTypeCode: code = storage.AuthCode{ - ID: storage.NewID(), - ClientID: authReq.ClientID, - ConnectorID: authReq.ConnectorID, - Nonce: authReq.Nonce, - Scopes: authReq.Scopes, - Claims: authReq.Claims, - Expiry: s.now().Add(time.Minute * 30), - RedirectURI: authReq.RedirectURI, - ConnectorData: authReq.ConnectorData, + ID: storage.NewID(), + ClientID: authReq.ClientID, + ConnectorID: authReq.ConnectorID, + Nonce: authReq.Nonce, + Scopes: authReq.Scopes, + Claims: authReq.Claims, + Expiry: s.now().Add(time.Minute * 30), + RedirectURI: authReq.RedirectURI, } if err := s.storage.CreateAuthCode(code); err != nil { s.logger.Errorf("Failed to create auth code: %v", err) @@ -824,16 +822,15 @@ func (s *Server) handleAuthCode(w http.ResponseWriter, r *http.Request, client s var refreshToken string if reqRefresh { refresh := storage.RefreshToken{ - ID: storage.NewID(), - Token: storage.NewID(), - ClientID: authCode.ClientID, - ConnectorID: authCode.ConnectorID, - Scopes: authCode.Scopes, - Claims: authCode.Claims, - Nonce: authCode.Nonce, - ConnectorData: authCode.ConnectorData, - CreatedAt: s.now(), - LastUsed: s.now(), + ID: storage.NewID(), + Token: storage.NewID(), + ClientID: authCode.ClientID, + ConnectorID: authCode.ConnectorID, + Scopes: authCode.Scopes, + Claims: authCode.Claims, + Nonce: authCode.Nonce, + CreatedAt: s.now(), + LastUsed: s.now(), } token := &internal.RefreshToken{ RefreshId: refresh.ID, diff --git a/storage/storage.go b/storage/storage.go index cb2a7e0c..85a60965 100644 --- a/storage/storage.go +++ b/storage/storage.go @@ -181,8 +181,7 @@ type AuthRequest struct { // The connector used to login the user and any data the connector wishes to persists. // Set when the user authenticates. - ConnectorID string - ConnectorData []byte + ConnectorID string } // AuthCode represents a code which can be exchanged for an OAuth2 token response. @@ -213,9 +212,8 @@ type AuthCode struct { Scopes []string // Authentication data provided by an upstream source. - ConnectorID string - ConnectorData []byte - Claims Claims + ConnectorID string + Claims Claims Expiry time.Time } @@ -237,9 +235,8 @@ type RefreshToken struct { ClientID string // Authentication data provided by an upstream source. - ConnectorID string - ConnectorData []byte - Claims Claims + ConnectorID string + Claims Claims // Scopes present in the initial request. Refresh requests may specify a set // of scopes different from the initial request when refreshing a token, From 0857a0fe09a7dd9e45b26d966ce2ef84d7e7545a Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Mon, 29 Jan 2018 21:07:15 +0000 Subject: [PATCH 04/27] 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 } From 7fc3f230df81a30dd29e1bd72f79a908cdcc8ebb Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Mon, 29 Jan 2018 21:07:46 +0000 Subject: [PATCH 05/27] Update SQL storage backend --- storage/sql/crud.go | 70 ++++++++++++++++++++++----------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/storage/sql/crud.go b/storage/sql/crud.go index e1982928..26a42176 100644 --- a/storage/sql/crud.go +++ b/storage/sql/crud.go @@ -108,20 +108,20 @@ func (c *conn) CreateAuthRequest(a storage.AuthRequest) error { insert into auth_request ( id, client_id, response_types, scopes, redirect_uri, nonce, state, force_approval_prompt, logged_in, - claims_user_id, claims_username, claims_preferred_username, + claims_user_id, claims_username, claims_preferred_username, claims_email, claims_email_verified, claims_groups, - connector_id, connector_data, + connector_id, expiry ) values ( - $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18 + $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17 ); `, a.ID, a.ClientID, encoder(a.ResponseTypes), encoder(a.Scopes), a.RedirectURI, a.Nonce, a.State, a.ForceApprovalPrompt, a.LoggedIn, a.Claims.UserID, a.Claims.Username, a.Claims.PreferredUsername, a.Claims.Email, a.Claims.EmailVerified, encoder(a.Claims.Groups), - a.ConnectorID, a.ConnectorData, + a.ConnectorID, a.Expiry, ) if err != nil { @@ -152,16 +152,16 @@ func (c *conn) UpdateAuthRequest(id string, updater func(a storage.AuthRequest) claims_user_id = $9, claims_username = $10, claims_preferred_username = $11, claims_email = $12, claims_email_verified = $13, claims_groups = $14, - connector_id = $15, connector_data = $16, - expiry = $17 - where id = $18; + connector_id = $15, + expiry = $16 + where id = $17; `, a.ClientID, encoder(a.ResponseTypes), encoder(a.Scopes), a.RedirectURI, a.Nonce, a.State, a.ForceApprovalPrompt, a.LoggedIn, a.Claims.UserID, a.Claims.Username, a.Claims.PreferredUsername, a.Claims.Email, a.Claims.EmailVerified, encoder(a.Claims.Groups), - a.ConnectorID, a.ConnectorData, + a.ConnectorID, a.Expiry, r.ID, ) if err != nil { @@ -178,12 +178,12 @@ func (c *conn) GetAuthRequest(id string) (storage.AuthRequest, error) { func getAuthRequest(q querier, id string) (a storage.AuthRequest, err error) { err = q.QueryRow(` - select + select id, client_id, response_types, scopes, redirect_uri, nonce, state, force_approval_prompt, logged_in, claims_user_id, claims_username, claims_preferred_username, claims_email, claims_email_verified, claims_groups, - connector_id, connector_data, expiry + connector_id, expiry from auth_request where id = $1; `, id).Scan( &a.ID, &a.ClientID, decoder(&a.ResponseTypes), decoder(&a.Scopes), &a.RedirectURI, &a.Nonce, &a.State, @@ -191,7 +191,7 @@ func getAuthRequest(q querier, id string) (a storage.AuthRequest, err error) { &a.Claims.UserID, &a.Claims.Username, &a.Claims.PreferredUsername, &a.Claims.Email, &a.Claims.EmailVerified, decoder(&a.Claims.Groups), - &a.ConnectorID, &a.ConnectorData, &a.Expiry, + &a.ConnectorID, &a.Expiry, ) if err != nil { if err == sql.ErrNoRows { @@ -208,14 +208,14 @@ func (c *conn) CreateAuthCode(a storage.AuthCode) error { id, client_id, scopes, nonce, redirect_uri, claims_user_id, claims_username, claims_preferred_username, claims_email, claims_email_verified, claims_groups, - connector_id, connector_data, + connector_id, expiry ) - values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14); + values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13); `, a.ID, a.ClientID, encoder(a.Scopes), a.Nonce, a.RedirectURI, a.Claims.UserID, a.Claims.Username, a.Claims.PreferredUsername, a.Claims.Email, a.Claims.EmailVerified, - encoder(a.Claims.Groups), a.ConnectorID, a.ConnectorData, a.Expiry, + encoder(a.Claims.Groups), a.ConnectorID, a.Expiry, ) if err != nil { @@ -233,13 +233,13 @@ func (c *conn) GetAuthCode(id string) (a storage.AuthCode, err error) { id, client_id, scopes, nonce, redirect_uri, claims_user_id, claims_username, claims_preferred_username, claims_email, claims_email_verified, claims_groups, - connector_id, connector_data, + connector_id, expiry from auth_code where id = $1; `, id).Scan( &a.ID, &a.ClientID, decoder(&a.Scopes), &a.Nonce, &a.RedirectURI, &a.Claims.UserID, &a.Claims.Username, &a.Claims.PreferredUsername, &a.Claims.Email, &a.Claims.EmailVerified, - decoder(&a.Claims.Groups), &a.ConnectorID, &a.ConnectorData, &a.Expiry, + decoder(&a.Claims.Groups), &a.ConnectorID, &a.Expiry, ) if err != nil { if err == sql.ErrNoRows { @@ -256,16 +256,16 @@ func (c *conn) CreateRefresh(r storage.RefreshToken) error { id, client_id, scopes, nonce, claims_user_id, claims_username, claims_preferred_username, claims_email, claims_email_verified, claims_groups, - connector_id, connector_data, + connector_id, token, created_at, last_used ) - values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15); + values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14); `, r.ID, r.ClientID, encoder(r.Scopes), r.Nonce, r.Claims.UserID, r.Claims.Username, r.Claims.PreferredUsername, r.Claims.Email, r.Claims.EmailVerified, encoder(r.Claims.Groups), - r.ConnectorID, r.ConnectorData, + r.ConnectorID, r.Token, r.CreatedAt, r.LastUsed, ) if err != nil { @@ -299,18 +299,17 @@ func (c *conn) UpdateRefreshToken(id string, updater func(old storage.RefreshTok claims_email_verified = $8, claims_groups = $9, connector_id = $10, - connector_data = $11, - token = $12, - created_at = $13, - last_used = $14 + token = $11, + created_at = $12, + last_used = $13 where - id = $15 + id = $14 `, r.ClientID, encoder(r.Scopes), r.Nonce, r.Claims.UserID, r.Claims.Username, r.Claims.PreferredUsername, r.Claims.Email, r.Claims.EmailVerified, encoder(r.Claims.Groups), - r.ConnectorID, r.ConnectorData, + r.ConnectorID, r.Token, r.CreatedAt, r.LastUsed, id, ) if err != nil { @@ -370,7 +369,7 @@ func scanRefresh(s scanner) (r storage.RefreshToken, err error) { &r.Claims.UserID, &r.Claims.Username, &r.Claims.PreferredUsername, &r.Claims.Email, &r.Claims.EmailVerified, decoder(&r.Claims.Groups), - &r.ConnectorID, &r.ConnectorData, + &r.ConnectorID, &r.Token, &r.CreatedAt, &r.LastUsed, ) if err != nil { @@ -417,7 +416,7 @@ func (c *conn) UpdateKeys(updater func(old storage.Keys) (storage.Keys, error)) } else { _, err = tx.Exec(` update keys - set + set verification_keys = $1, signing_key = $2, signing_key_pub = $3, @@ -655,13 +654,13 @@ func scanPassword(s scanner) (p storage.Password, err error) { func (c *conn) CreateOfflineSessions(s storage.OfflineSessions) error { _, err := c.Exec(` insert into offline_session ( - user_id, conn_id, refresh + user_id, conn_id, refresh, connector_data ) values ( - $1, $2, $3 + $1, $2, $3, $4 ); `, - s.UserID, s.ConnID, encoder(s.Refresh), + s.UserID, s.ConnID, encoder(s.Refresh), s.ConnectorData, ) if err != nil { if c.alreadyExistsCheck(err) { @@ -687,9 +686,10 @@ func (c *conn) UpdateOfflineSessions(userID string, connID string, updater func( update offline_session set refresh = $1 - where user_id = $2 AND conn_id = $3; + connector_data = $2 + where user_id = $3 AND conn_id = $4; `, - encoder(newSession.Refresh), s.UserID, s.ConnID, + encoder(newSession.Refresh), s.ConnectorData, s.UserID, s.ConnID, ) if err != nil { return fmt.Errorf("update offline session: %v", err) @@ -705,7 +705,7 @@ func (c *conn) GetOfflineSessions(userID string, connID string) (storage.Offline func getOfflineSessions(q querier, userID string, connID string) (storage.OfflineSessions, error) { return scanOfflineSessions(q.QueryRow(` select - user_id, conn_id, refresh + user_id, conn_id, refresh, connector_data from offline_session where user_id = $1 AND conn_id = $2; `, userID, connID)) @@ -713,7 +713,7 @@ func getOfflineSessions(q querier, userID string, connID string) (storage.Offlin func scanOfflineSessions(s scanner) (o storage.OfflineSessions, err error) { err = s.Scan( - &o.UserID, &o.ConnID, decoder(&o.Refresh), + &o.UserID, &o.ConnID, decoder(&o.Refresh), &o.ConnectorData, ) if err != nil { if err == sql.ErrNoRows { @@ -757,7 +757,7 @@ func (c *conn) UpdateConnector(id string, updater func(s storage.Connector) (sto } _, err = tx.Exec(` update connector - set + set type = $1, name = $2, resource_version = $3, From c789c5808e4a7b3a2126b4f62fa7d5c642b827a2 Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Mon, 29 Jan 2018 21:10:31 +0000 Subject: [PATCH 06/27] Update conformance --- storage/conformance/conformance.go | 65 +++++++++++++---------------- storage/conformance/transactions.go | 1 - 2 files changed, 28 insertions(+), 38 deletions(-) diff --git a/storage/conformance/conformance.go b/storage/conformance/conformance.go index a1399807..6d2eb751 100644 --- a/storage/conformance/conformance.go +++ b/storage/conformance/conformance.go @@ -91,7 +91,6 @@ func testAuthRequestCRUD(t *testing.T, s storage.Storage) { LoggedIn: true, Expiry: neverExpire, ConnectorID: "ldap", - ConnectorData: []byte(`{"some":"data"}`), Claims: storage.Claims{ UserID: "1", Username: "jane", @@ -123,7 +122,6 @@ func testAuthRequestCRUD(t *testing.T, s storage.Storage) { LoggedIn: true, Expiry: neverExpire, ConnectorID: "ldap", - ConnectorData: []byte(`{"some":"data"}`), Claims: storage.Claims{ UserID: "2", Username: "john", @@ -165,14 +163,13 @@ func testAuthRequestCRUD(t *testing.T, s storage.Storage) { func testAuthCodeCRUD(t *testing.T, s storage.Storage) { a1 := storage.AuthCode{ - ID: storage.NewID(), - ClientID: "client1", - RedirectURI: "https://localhost:80/callback", - Nonce: "foobar", - Scopes: []string{"openid", "email"}, - Expiry: neverExpire, - ConnectorID: "ldap", - ConnectorData: []byte(`{"some":"data"}`), + ID: storage.NewID(), + ClientID: "client1", + RedirectURI: "https://localhost:80/callback", + Nonce: "foobar", + Scopes: []string{"openid", "email"}, + Expiry: neverExpire, + ConnectorID: "ldap", Claims: storage.Claims{ UserID: "1", Username: "jane", @@ -187,14 +184,13 @@ func testAuthCodeCRUD(t *testing.T, s storage.Storage) { } a2 := storage.AuthCode{ - ID: storage.NewID(), - ClientID: "client2", - RedirectURI: "https://localhost:80/callback", - Nonce: "foobar", - Scopes: []string{"openid", "email"}, - Expiry: neverExpire, - ConnectorID: "ldap", - ConnectorData: []byte(`{"some":"data"}`), + ID: storage.NewID(), + ClientID: "client2", + RedirectURI: "https://localhost:80/callback", + Nonce: "foobar", + Scopes: []string{"openid", "email"}, + Expiry: neverExpire, + ConnectorID: "ldap", Claims: storage.Claims{ UserID: "2", Username: "john", @@ -323,7 +319,6 @@ func testRefreshTokenCRUD(t *testing.T, s storage.Storage) { EmailVerified: true, Groups: []string{"a", "b"}, }, - ConnectorData: []byte(`{"some":"data"}`), } if err := s.CreateRefresh(refresh); err != nil { t.Fatalf("create refresh token: %v", err) @@ -377,7 +372,6 @@ func testRefreshTokenCRUD(t *testing.T, s storage.Storage) { EmailVerified: true, Groups: []string{"a", "b"}, }, - ConnectorData: []byte(`{"some":"data"}`), } if err := s.CreateRefresh(refresh2); err != nil { @@ -729,14 +723,13 @@ func testGC(t *testing.T, s storage.Storage) { expiry := time.Now().In(est) c := storage.AuthCode{ - ID: storage.NewID(), - ClientID: "foobar", - RedirectURI: "https://localhost:80/callback", - Nonce: "foobar", - Scopes: []string{"openid", "email"}, - Expiry: expiry, - ConnectorID: "ldap", - ConnectorData: []byte(`{"some":"data"}`), + ID: storage.NewID(), + ClientID: "foobar", + RedirectURI: "https://localhost:80/callback", + Nonce: "foobar", + Scopes: []string{"openid", "email"}, + Expiry: expiry, + ConnectorID: "ldap", Claims: storage.Claims{ UserID: "1", Username: "jane", @@ -788,7 +781,6 @@ func testGC(t *testing.T, s storage.Storage) { LoggedIn: true, Expiry: expiry, ConnectorID: "ldap", - ConnectorData: []byte(`{"some":"data"}`), Claims: storage.Claims{ UserID: "1", Username: "jane", @@ -841,14 +833,13 @@ func testTimezones(t *testing.T, s storage.Storage) { expiry := time.Now().In(est).Round(time.Millisecond) c := storage.AuthCode{ - ID: storage.NewID(), - ClientID: "foobar", - RedirectURI: "https://localhost:80/callback", - Nonce: "foobar", - Scopes: []string{"openid", "email"}, - Expiry: expiry, - ConnectorID: "ldap", - ConnectorData: []byte(`{"some":"data"}`), + ID: storage.NewID(), + ClientID: "foobar", + RedirectURI: "https://localhost:80/callback", + Nonce: "foobar", + Scopes: []string{"openid", "email"}, + Expiry: expiry, + ConnectorID: "ldap", Claims: storage.Claims{ UserID: "1", Username: "jane", diff --git a/storage/conformance/transactions.go b/storage/conformance/transactions.go index dc1be1b6..2eb509e1 100644 --- a/storage/conformance/transactions.go +++ b/storage/conformance/transactions.go @@ -67,7 +67,6 @@ func testAuthRequestConcurrentUpdate(t *testing.T, s storage.Storage) { LoggedIn: true, Expiry: neverExpire, ConnectorID: "ldap", - ConnectorData: []byte(`{"some":"data"}`), Claims: storage.Claims{ UserID: "1", Username: "jane", From c54f1656c72f153e7aefac3795b6a22c973b97d9 Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Mon, 29 Jan 2018 21:11:59 +0000 Subject: [PATCH 07/27] Fix ETCD storage backend --- storage/etcd/types.go | 83 +++++++++++++++++++++---------------------- 1 file changed, 40 insertions(+), 43 deletions(-) diff --git a/storage/etcd/types.go b/storage/etcd/types.go index 8063c69f..e9075ed6 100644 --- a/storage/etcd/types.go +++ b/storage/etcd/types.go @@ -16,24 +16,22 @@ type AuthCode struct { Nonce string `json:"nonce,omitempty"` Scopes []string `json:"scopes,omitempty"` - ConnectorID string `json:"connectorID,omitempty"` - ConnectorData []byte `json:"connectorData,omitempty"` - Claims Claims `json:"claims,omitempty"` + ConnectorID string `json:"connectorID,omitempty"` + Claims Claims `json:"claims,omitempty"` Expiry time.Time `json:"expiry"` } func fromStorageAuthCode(a storage.AuthCode) AuthCode { return AuthCode{ - ID: a.ID, - ClientID: a.ClientID, - RedirectURI: a.RedirectURI, - ConnectorID: a.ConnectorID, - ConnectorData: a.ConnectorData, - Nonce: a.Nonce, - Scopes: a.Scopes, - Claims: fromStorageClaims(a.Claims), - Expiry: a.Expiry, + ID: a.ID, + ClientID: a.ClientID, + RedirectURI: a.RedirectURI, + ConnectorID: a.ConnectorID, + Nonce: a.Nonce, + Scopes: a.Scopes, + Claims: fromStorageClaims(a.Claims), + Expiry: a.Expiry, } } @@ -74,7 +72,6 @@ func fromStorageAuthRequest(a storage.AuthRequest) AuthRequest { LoggedIn: a.LoggedIn, Claims: fromStorageClaims(a.Claims), ConnectorID: a.ConnectorID, - ConnectorData: a.ConnectorData, } } @@ -90,7 +87,6 @@ func toStorageAuthRequest(a AuthRequest) storage.AuthRequest { ForceApprovalPrompt: a.ForceApprovalPrompt, LoggedIn: a.LoggedIn, ConnectorID: a.ConnectorID, - ConnectorData: a.ConnectorData, Expiry: a.Expiry, Claims: toStorageClaims(a.Claims), } @@ -118,31 +114,29 @@ type RefreshToken struct { func toStorageRefreshToken(r RefreshToken) storage.RefreshToken { return storage.RefreshToken{ - ID: r.ID, - Token: r.Token, - CreatedAt: r.CreatedAt, - LastUsed: r.LastUsed, - ClientID: r.ClientID, - ConnectorID: r.ConnectorID, - ConnectorData: r.ConnectorData, - Scopes: r.Scopes, - Nonce: r.Nonce, - Claims: toStorageClaims(r.Claims), + ID: r.ID, + Token: r.Token, + CreatedAt: r.CreatedAt, + LastUsed: r.LastUsed, + ClientID: r.ClientID, + ConnectorID: r.ConnectorID, + Scopes: r.Scopes, + Nonce: r.Nonce, + Claims: toStorageClaims(r.Claims), } } func fromStorageRefreshToken(r storage.RefreshToken) RefreshToken { return RefreshToken{ - ID: r.ID, - Token: r.Token, - CreatedAt: r.CreatedAt, - LastUsed: r.LastUsed, - ClientID: r.ClientID, - ConnectorID: r.ConnectorID, - ConnectorData: r.ConnectorData, - Scopes: r.Scopes, - Nonce: r.Nonce, - Claims: fromStorageClaims(r.Claims), + ID: r.ID, + Token: r.Token, + CreatedAt: r.CreatedAt, + LastUsed: r.LastUsed, + ClientID: r.ClientID, + ConnectorID: r.ConnectorID, + Scopes: r.Scopes, + Nonce: r.Nonce, + Claims: fromStorageClaims(r.Claims), } } @@ -188,24 +182,27 @@ type Keys struct { // OfflineSessions is a mirrored struct from storage with JSON struct tags type OfflineSessions struct { - UserID string `json:"user_id,omitempty"` - ConnID string `json:"conn_id,omitempty"` - Refresh map[string]*storage.RefreshTokenRef `json:"refresh,omitempty"` + UserID string `json:"user_id,omitempty"` + ConnID string `json:"conn_id,omitempty"` + Refresh map[string]*storage.RefreshTokenRef `json:"refresh,omitempty"` + ConnectorData []byte `json:"connectorData,omitempty"` } func fromStorageOfflineSessions(o storage.OfflineSessions) OfflineSessions { return OfflineSessions{ - UserID: o.UserID, - ConnID: o.ConnID, - Refresh: o.Refresh, + UserID: o.UserID, + ConnID: o.ConnID, + Refresh: o.Refresh, + ConnectorData: o.ConnectorData, } } func toStorageOfflineSessions(o OfflineSessions) storage.OfflineSessions { s := storage.OfflineSessions{ - UserID: o.UserID, - ConnID: o.ConnID, - Refresh: o.Refresh, + UserID: o.UserID, + ConnID: o.ConnID, + Refresh: o.Refresh, + ConnectorData: o.ConnectorData, } if s.Refresh == nil { // Server code assumes this will be non-nil. From 7a76c767fed725d6567aba31ae08cc0dab0486ea Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Mon, 29 Jan 2018 21:15:01 +0000 Subject: [PATCH 08/27] Update Kubernetes storage backend --- storage/kubernetes/types.go | 94 ++++++++++++++++++------------------- 1 file changed, 45 insertions(+), 49 deletions(-) diff --git a/storage/kubernetes/types.go b/storage/kubernetes/types.go index a42238b3..abc11ba4 100644 --- a/storage/kubernetes/types.go +++ b/storage/kubernetes/types.go @@ -265,8 +265,7 @@ type AuthRequest struct { // with a backend. Claims Claims `json:"claims,omitempty"` // The connector used to login the user. Set when the user authenticates. - ConnectorID string `json:"connectorID,omitempty"` - ConnectorData []byte `json:"connectorData,omitempty"` + ConnectorID string `json:"connectorID,omitempty"` Expiry time.Time `json:"expiry"` } @@ -290,7 +289,6 @@ func toStorageAuthRequest(req AuthRequest) storage.AuthRequest { ForceApprovalPrompt: req.ForceApprovalPrompt, LoggedIn: req.LoggedIn, ConnectorID: req.ConnectorID, - ConnectorData: req.ConnectorData, Expiry: req.Expiry, Claims: toStorageClaims(req.Claims), } @@ -316,7 +314,6 @@ func (cli *client) fromStorageAuthRequest(a storage.AuthRequest) AuthRequest { LoggedIn: a.LoggedIn, ForceApprovalPrompt: a.ForceApprovalPrompt, ConnectorID: a.ConnectorID, - ConnectorData: a.ConnectorData, Expiry: a.Expiry, Claims: fromStorageClaims(a.Claims), } @@ -411,28 +408,26 @@ func (cli *client) fromStorageAuthCode(a storage.AuthCode) AuthCode { Name: a.ID, Namespace: cli.namespace, }, - ClientID: a.ClientID, - RedirectURI: a.RedirectURI, - ConnectorID: a.ConnectorID, - ConnectorData: a.ConnectorData, - Nonce: a.Nonce, - Scopes: a.Scopes, - Claims: fromStorageClaims(a.Claims), - Expiry: a.Expiry, + ClientID: a.ClientID, + RedirectURI: a.RedirectURI, + ConnectorID: a.ConnectorID, + Nonce: a.Nonce, + Scopes: a.Scopes, + Claims: fromStorageClaims(a.Claims), + Expiry: a.Expiry, } } func toStorageAuthCode(a AuthCode) storage.AuthCode { return storage.AuthCode{ - ID: a.ObjectMeta.Name, - ClientID: a.ClientID, - RedirectURI: a.RedirectURI, - ConnectorID: a.ConnectorID, - ConnectorData: a.ConnectorData, - Nonce: a.Nonce, - Scopes: a.Scopes, - Claims: toStorageClaims(a.Claims), - Expiry: a.Expiry, + ID: a.ObjectMeta.Name, + ClientID: a.ClientID, + RedirectURI: a.RedirectURI, + ConnectorID: a.ConnectorID, + Nonce: a.Nonce, + Scopes: a.Scopes, + Claims: toStorageClaims(a.Claims), + Expiry: a.Expiry, } } @@ -466,16 +461,15 @@ type RefreshList struct { func toStorageRefreshToken(r RefreshToken) storage.RefreshToken { return storage.RefreshToken{ - ID: r.ObjectMeta.Name, - Token: r.Token, - CreatedAt: r.CreatedAt, - LastUsed: r.LastUsed, - ClientID: r.ClientID, - ConnectorID: r.ConnectorID, - ConnectorData: r.ConnectorData, - Scopes: r.Scopes, - Nonce: r.Nonce, - Claims: toStorageClaims(r.Claims), + ID: r.ObjectMeta.Name, + Token: r.Token, + CreatedAt: r.CreatedAt, + LastUsed: r.LastUsed, + ClientID: r.ClientID, + ConnectorID: r.ConnectorID, + Scopes: r.Scopes, + Nonce: r.Nonce, + Claims: toStorageClaims(r.Claims), } } @@ -489,15 +483,14 @@ func (cli *client) fromStorageRefreshToken(r storage.RefreshToken) RefreshToken Name: r.ID, Namespace: cli.namespace, }, - Token: r.Token, - CreatedAt: r.CreatedAt, - LastUsed: r.LastUsed, - ClientID: r.ClientID, - ConnectorID: r.ConnectorID, - ConnectorData: r.ConnectorData, - Scopes: r.Scopes, - Nonce: r.Nonce, - Claims: fromStorageClaims(r.Claims), + Token: r.Token, + CreatedAt: r.CreatedAt, + LastUsed: r.LastUsed, + ClientID: r.ClientID, + ConnectorID: r.ConnectorID, + Scopes: r.Scopes, + Nonce: r.Nonce, + Claims: fromStorageClaims(r.Claims), } } @@ -552,9 +545,10 @@ type OfflineSessions struct { k8sapi.TypeMeta `json:",inline"` k8sapi.ObjectMeta `json:"metadata,omitempty"` - UserID string `json:"userID,omitempty"` - ConnID string `json:"connID,omitempty"` - Refresh map[string]*storage.RefreshTokenRef `json:"refresh,omitempty"` + UserID string `json:"userID,omitempty"` + ConnID string `json:"connID,omitempty"` + Refresh map[string]*storage.RefreshTokenRef `json:"refresh,omitempty"` + ConnectorData []byte `json:"connectorData,omitempty"` } func (cli *client) fromStorageOfflineSessions(o storage.OfflineSessions) OfflineSessions { @@ -567,17 +561,19 @@ func (cli *client) fromStorageOfflineSessions(o storage.OfflineSessions) Offline Name: cli.offlineTokenName(o.UserID, o.ConnID), Namespace: cli.namespace, }, - UserID: o.UserID, - ConnID: o.ConnID, - Refresh: o.Refresh, + UserID: o.UserID, + ConnID: o.ConnID, + Refresh: o.Refresh, + ConnectorData: o.ConnectorData, } } func toStorageOfflineSessions(o OfflineSessions) storage.OfflineSessions { s := storage.OfflineSessions{ - UserID: o.UserID, - ConnID: o.ConnID, - Refresh: o.Refresh, + UserID: o.UserID, + ConnID: o.ConnID, + Refresh: o.Refresh, + ConnectorData: o.ConnectorData, } if s.Refresh == nil { // Server code assumes this will be non-nil. From b9b315dd6418070c920d01eb87b366d8681f9a5d Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Tue, 30 Jan 2018 11:18:00 +0000 Subject: [PATCH 09/27] Fix conformance tests --- storage/conformance/conformance.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/storage/conformance/conformance.go b/storage/conformance/conformance.go index 6d2eb751..ea21edae 100644 --- a/storage/conformance/conformance.go +++ b/storage/conformance/conformance.go @@ -509,9 +509,10 @@ func testPasswordCRUD(t *testing.T, s storage.Storage) { func testOfflineSessionCRUD(t *testing.T, s storage.Storage) { userID1 := storage.NewID() session1 := storage.OfflineSessions{ - UserID: userID1, - ConnID: "Conn1", - Refresh: make(map[string]*storage.RefreshTokenRef), + UserID: userID1, + ConnID: "Conn1", + Refresh: make(map[string]*storage.RefreshTokenRef), + ConnectorData: []byte(`{"some":"data"}`), } // Creating an OfflineSession with an empty Refresh list to ensure that @@ -526,9 +527,10 @@ func testOfflineSessionCRUD(t *testing.T, s storage.Storage) { userID2 := storage.NewID() session2 := storage.OfflineSessions{ - UserID: userID2, - ConnID: "Conn2", - Refresh: make(map[string]*storage.RefreshTokenRef), + UserID: userID2, + ConnID: "Conn2", + Refresh: make(map[string]*storage.RefreshTokenRef), + ConnectorData: []byte(`{"some":"data"}`), } if err := s.CreateOfflineSessions(session2); err != nil { From 80995dff9b8a524c342292f179ca5d801af16218 Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Tue, 30 Jan 2018 11:19:08 +0000 Subject: [PATCH 10/27] Fix SQL storage --- storage/sql/crud.go | 4 ++-- storage/sql/migrate.go | 24 +++++++++++------------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/storage/sql/crud.go b/storage/sql/crud.go index 26a42176..67cab973 100644 --- a/storage/sql/crud.go +++ b/storage/sql/crud.go @@ -330,7 +330,7 @@ func getRefresh(q querier, id string) (storage.RefreshToken, error) { claims_user_id, claims_username, claims_preferred_username, claims_email, claims_email_verified, claims_groups, - connector_id, connector_data, + connector_id, token, created_at, last_used from refresh_token where id = $1; `, id)) @@ -685,7 +685,7 @@ func (c *conn) UpdateOfflineSessions(userID string, connID string, updater func( _, err = tx.Exec(` update offline_session set - refresh = $1 + refresh = $1, connector_data = $2 where user_id = $3 AND conn_id = $4; `, diff --git a/storage/sql/migrate.go b/storage/sql/migrate.go index 0ef62609..bce8e5cf 100644 --- a/storage/sql/migrate.go +++ b/storage/sql/migrate.go @@ -90,18 +90,17 @@ var migrations = []migration{ nonce text not null, state text not null, force_approval_prompt boolean not null, - + logged_in boolean not null, - + claims_user_id text not null, claims_username text not null, claims_email text not null, claims_email_verified boolean not null, claims_groups bytea not null, -- JSON array of strings - + connector_id text not null, - connector_data bytea, - + expiry timestamptz not null );`, ` @@ -111,16 +110,15 @@ var migrations = []migration{ scopes bytea not null, -- JSON array of strings nonce text not null, redirect_uri text not null, - + claims_user_id text not null, claims_username text not null, claims_email text not null, claims_email_verified boolean not null, claims_groups bytea not null, -- JSON array of strings - + connector_id text not null, - connector_data bytea, - + expiry timestamptz not null );`, ` @@ -129,15 +127,14 @@ var migrations = []migration{ client_id text not null, scopes bytea not null, -- JSON array of strings nonce text not null, - + claims_user_id text not null, claims_username text not null, claims_email text not null, claims_email_verified boolean not null, claims_groups bytea not null, -- JSON array of strings - - connector_id text not null, - connector_data bytea + + connector_id text not null );`, ` create table password ( @@ -175,6 +172,7 @@ var migrations = []migration{ user_id text not null, conn_id text not null, refresh bytea not null, + connector_data bytea not null, PRIMARY KEY (user_id, conn_id) );`, }, From 4076eed17b929830a80b43b41c80e24d31cbb1da Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Sun, 4 Feb 2018 17:20:05 +0000 Subject: [PATCH 11/27] Build opts based on scope --- connector/oidc/oidc.go | 9 +++++++-- server/handlers.go | 4 +++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/connector/oidc/oidc.go b/connector/oidc/oidc.go index dfab061a..1a9462da 100644 --- a/connector/oidc/oidc.go +++ b/connector/oidc/oidc.go @@ -168,14 +168,19 @@ func (c *oidcConnector) LoginURL(s connector.Scopes, callbackURL, state string) return "", fmt.Errorf("expected callback URL %q did not match the URL in the config %q", callbackURL, c.redirectURI) } + var opts []oauth2.AuthCodeOption if len(c.hostedDomains) > 0 { preferredDomain := c.hostedDomains[0] if len(c.hostedDomains) > 1 { preferredDomain = "*" } - return c.oauth2Config.AuthCodeURL(state, oauth2.AccessTypeOffline, oauth2.SetAuthURLParam("prompt", "consent"), oauth2.SetAuthURLParam("hd", preferredDomain)), nil + opts = append(opts, oauth2.SetAuthURLParam("hd", preferredDomain)) } - return c.oauth2Config.AuthCodeURL(state, oauth2.AccessTypeOffline, oauth2.SetAuthURLParam("prompt", "consent")), nil + + if s.OfflineAccess { + opts = append(opts, oauth2.AccessTypeOffline, oauth2.SetAuthURLParam("prompt", "consent")) + } + return c.oauth2Config.AuthCodeURL(state, opts...), nil } type oauth2Error struct { diff --git a/server/handlers.go b/server/handlers.go index 08bf5d04..a4db71cb 100644 --- a/server/handlers.go +++ b/server/handlers.go @@ -527,7 +527,9 @@ func (s *Server) finalizeLogin(identity connector.Identity, authReq storage.Auth } else { // Update existing OfflineSession obj with new RefreshTokenRef. if err := s.storage.UpdateOfflineSessions(session.UserID, session.ConnID, func(old storage.OfflineSessions) (storage.OfflineSessions, error) { - old.ConnectorData = identity.ConnectorData + if len(identity.ConnectorData) > 0 { + old.ConnectorData = identity.ConnectorData + } return old, nil }); err != nil { s.logger.Errorf("failed to update offline session: %v", err) From 433bb2afec0dd9e27baf984fb9a5b99625750aaf Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Mon, 5 Feb 2018 20:58:59 +0000 Subject: [PATCH 12/27] Remove duplicate code --- connector/oidc/oidc.go | 76 +++++++++++------------------------------- 1 file changed, 20 insertions(+), 56 deletions(-) diff --git a/connector/oidc/oidc.go b/connector/oidc/oidc.go index 1a9462da..cdb3ff55 100644 --- a/connector/oidc/oidc.go +++ b/connector/oidc/oidc.go @@ -205,11 +205,29 @@ func (c *oidcConnector) HandleCallback(s connector.Scopes, r *http.Request) (ide return identity, fmt.Errorf("oidc: failed to get token: %v", err) } + return c.createIdentity(r.Context(), identity, token) +} + +// 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) + } + + return c.createIdentity(ctx, identity, token) +} + +func (c *oidcConnector) createIdentity(ctx context.Context, identity connector.Identity, token *oauth2.Token) (connector.Identity, error) { 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(r.Context(), rawIDToken) + idToken, err := c.verifier.Verify(ctx, rawIDToken) if err != nil { return identity, fmt.Errorf("oidc: failed to verify ID Token: %v", err) } @@ -221,7 +239,7 @@ func (c *oidcConnector) HandleCallback(s connector.Scopes, r *http.Request) (ide // We immediately want to run getUserInfo if configured before we validate the claims if c.getUserInfo { - userInfo, err := c.provider.UserInfo(r.Context(), oauth2.StaticTokenSource(token)) + userInfo, err := c.provider.UserInfo(ctx, oauth2.StaticTokenSource(token)) if err != nil { return identity, fmt.Errorf("oidc: error loading userinfo: %v", err) } @@ -284,57 +302,3 @@ func (c *oidcConnector) HandleCallback(s connector.Scopes, r *http.Request) (ide return identity, nil } - -// 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 -} From d38909831c2f5db10d5552265321e6c642a90212 Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Thu, 15 Feb 2018 10:02:03 +0000 Subject: [PATCH 13/27] Fix migration in SQL connector I didn't realise quite what the migration mechanism was. Have understood it now. --- storage/sql/migrate.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/storage/sql/migrate.go b/storage/sql/migrate.go index bce8e5cf..ecf1ae77 100644 --- a/storage/sql/migrate.go +++ b/storage/sql/migrate.go @@ -100,6 +100,7 @@ var migrations = []migration{ claims_groups bytea not null, -- JSON array of strings connector_id text not null, + connector_data bytea, expiry timestamptz not null );`, @@ -118,6 +119,7 @@ var migrations = []migration{ claims_groups bytea not null, -- JSON array of strings connector_id text not null, + connector_data bytea, expiry timestamptz not null );`, @@ -134,7 +136,8 @@ var migrations = []migration{ claims_email_verified boolean not null, claims_groups bytea not null, -- JSON array of strings - connector_id text not null + connector_id text not null, + connector_data bytea );`, ` create table password ( @@ -172,7 +175,6 @@ var migrations = []migration{ user_id text not null, conn_id text not null, refresh bytea not null, - connector_data bytea not null, PRIMARY KEY (user_id, conn_id) );`, }, @@ -200,4 +202,11 @@ var migrations = []migration{ add column claims_preferred_username text not null default '';`, }, }, + { + stmts: []string{` + alter table offline_session + add column connector_data bytea not null default ''; + `, + }, + }, } From fea048b3e873096936bc0bb8ef68a21984aefe26 Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Thu, 15 Feb 2018 11:00:06 +0000 Subject: [PATCH 14/27] Fix SQL updater func --- storage/sql/crud.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/storage/sql/crud.go b/storage/sql/crud.go index 67cab973..4e0dffaf 100644 --- a/storage/sql/crud.go +++ b/storage/sql/crud.go @@ -689,7 +689,7 @@ func (c *conn) UpdateOfflineSessions(userID string, connID string, updater func( connector_data = $2 where user_id = $3 AND conn_id = $4; `, - encoder(newSession.Refresh), s.ConnectorData, s.UserID, s.ConnID, + encoder(newSession.Refresh), newSession.ConnectorData, s.UserID, s.ConnID, ) if err != nil { return fmt.Errorf("update offline session: %v", err) From 176ba709a43ebd7a525f5471e830d8cfc554693c Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Thu, 18 Apr 2019 13:52:05 +0100 Subject: [PATCH 15/27] Revert "Remove connectordata from other structs" This reverts commit 27f33516db343bd79b56a47ecef0fe514a35082d. --- server/api_test.go | 1 + server/handlers.go | 37 ++++++++++++++++++++----------------- storage/storage.go | 13 ++++++++----- 3 files changed, 29 insertions(+), 22 deletions(-) diff --git a/server/api_test.go b/server/api_test.go index a7891208..80a22486 100644 --- a/server/api_test.go +++ b/server/api_test.go @@ -259,6 +259,7 @@ func TestRefreshToken(t *testing.T) { EmailVerified: true, Groups: []string{"a", "b"}, }, + ConnectorData: []byte(`{"some":"data"}`), } if err := s.CreateRefresh(r); err != nil { diff --git a/server/handlers.go b/server/handlers.go index a4db71cb..80965daf 100644 --- a/server/handlers.go +++ b/server/handlers.go @@ -490,6 +490,7 @@ func (s *Server) finalizeLogin(identity connector.Identity, authReq storage.Auth updater := func(a storage.AuthRequest) (storage.AuthRequest, error) { a.LoggedIn = true a.Claims = claims + a.ConnectorData = identity.ConnectorData return a, nil } if err := s.storage.UpdateAuthRequest(authReq.ID, updater); err != nil { @@ -621,14 +622,15 @@ func (s *Server) sendCodeResponse(w http.ResponseWriter, r *http.Request, authRe switch responseType { case responseTypeCode: code = storage.AuthCode{ - ID: storage.NewID(), - ClientID: authReq.ClientID, - ConnectorID: authReq.ConnectorID, - Nonce: authReq.Nonce, - Scopes: authReq.Scopes, - Claims: authReq.Claims, - Expiry: s.now().Add(time.Minute * 30), - RedirectURI: authReq.RedirectURI, + ID: storage.NewID(), + ClientID: authReq.ClientID, + ConnectorID: authReq.ConnectorID, + Nonce: authReq.Nonce, + Scopes: authReq.Scopes, + Claims: authReq.Claims, + Expiry: s.now().Add(time.Minute * 30), + RedirectURI: authReq.RedirectURI, + ConnectorData: authReq.ConnectorData, } if err := s.storage.CreateAuthCode(code); err != nil { s.logger.Errorf("Failed to create auth code: %v", err) @@ -824,15 +826,16 @@ func (s *Server) handleAuthCode(w http.ResponseWriter, r *http.Request, client s var refreshToken string if reqRefresh { refresh := storage.RefreshToken{ - ID: storage.NewID(), - Token: storage.NewID(), - ClientID: authCode.ClientID, - ConnectorID: authCode.ConnectorID, - Scopes: authCode.Scopes, - Claims: authCode.Claims, - Nonce: authCode.Nonce, - CreatedAt: s.now(), - LastUsed: s.now(), + ID: storage.NewID(), + Token: storage.NewID(), + ClientID: authCode.ClientID, + ConnectorID: authCode.ConnectorID, + Scopes: authCode.Scopes, + Claims: authCode.Claims, + Nonce: authCode.Nonce, + ConnectorData: authCode.ConnectorData, + CreatedAt: s.now(), + LastUsed: s.now(), } token := &internal.RefreshToken{ RefreshId: refresh.ID, diff --git a/storage/storage.go b/storage/storage.go index 85a60965..cb2a7e0c 100644 --- a/storage/storage.go +++ b/storage/storage.go @@ -181,7 +181,8 @@ type AuthRequest struct { // The connector used to login the user and any data the connector wishes to persists. // Set when the user authenticates. - ConnectorID string + ConnectorID string + ConnectorData []byte } // AuthCode represents a code which can be exchanged for an OAuth2 token response. @@ -212,8 +213,9 @@ type AuthCode struct { Scopes []string // Authentication data provided by an upstream source. - ConnectorID string - Claims Claims + ConnectorID string + ConnectorData []byte + Claims Claims Expiry time.Time } @@ -235,8 +237,9 @@ type RefreshToken struct { ClientID string // Authentication data provided by an upstream source. - ConnectorID string - Claims Claims + ConnectorID string + ConnectorData []byte + Claims Claims // Scopes present in the initial request. Refresh requests may specify a set // of scopes different from the initial request when refreshing a token, From 9ce4393156c2f115789e7c6bf65d075511e2c17a Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Thu, 18 Apr 2019 15:03:27 +0100 Subject: [PATCH 16/27] Revert "Update SQL storage backend" --- storage/sql/crud.go | 49 +++++++++++++++++++++++---------------------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/storage/sql/crud.go b/storage/sql/crud.go index 4e0dffaf..e96a7b12 100644 --- a/storage/sql/crud.go +++ b/storage/sql/crud.go @@ -110,18 +110,18 @@ func (c *conn) CreateAuthRequest(a storage.AuthRequest) error { force_approval_prompt, logged_in, claims_user_id, claims_username, claims_preferred_username, claims_email, claims_email_verified, claims_groups, - connector_id, + connector_id, connector_data, expiry ) values ( - $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17 + $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18 ); `, a.ID, a.ClientID, encoder(a.ResponseTypes), encoder(a.Scopes), a.RedirectURI, a.Nonce, a.State, a.ForceApprovalPrompt, a.LoggedIn, a.Claims.UserID, a.Claims.Username, a.Claims.PreferredUsername, a.Claims.Email, a.Claims.EmailVerified, encoder(a.Claims.Groups), - a.ConnectorID, + a.ConnectorID, a.ConnectorData, a.Expiry, ) if err != nil { @@ -152,16 +152,16 @@ func (c *conn) UpdateAuthRequest(id string, updater func(a storage.AuthRequest) claims_user_id = $9, claims_username = $10, claims_preferred_username = $11, claims_email = $12, claims_email_verified = $13, claims_groups = $14, - connector_id = $15, - expiry = $16 - where id = $17; + connector_id = $15, connector_data = $16, + expiry = $17 + where id = $18; `, a.ClientID, encoder(a.ResponseTypes), encoder(a.Scopes), a.RedirectURI, a.Nonce, a.State, a.ForceApprovalPrompt, a.LoggedIn, a.Claims.UserID, a.Claims.Username, a.Claims.PreferredUsername, a.Claims.Email, a.Claims.EmailVerified, encoder(a.Claims.Groups), - a.ConnectorID, + a.ConnectorID, a.ConnectorData, a.Expiry, r.ID, ) if err != nil { @@ -183,7 +183,7 @@ func getAuthRequest(q querier, id string) (a storage.AuthRequest, err error) { force_approval_prompt, logged_in, claims_user_id, claims_username, claims_preferred_username, claims_email, claims_email_verified, claims_groups, - connector_id, expiry + connector_id, connector_data, expiry from auth_request where id = $1; `, id).Scan( &a.ID, &a.ClientID, decoder(&a.ResponseTypes), decoder(&a.Scopes), &a.RedirectURI, &a.Nonce, &a.State, @@ -191,7 +191,7 @@ func getAuthRequest(q querier, id string) (a storage.AuthRequest, err error) { &a.Claims.UserID, &a.Claims.Username, &a.Claims.PreferredUsername, &a.Claims.Email, &a.Claims.EmailVerified, decoder(&a.Claims.Groups), - &a.ConnectorID, &a.Expiry, + &a.ConnectorID, &a.ConnectorData, &a.Expiry, ) if err != nil { if err == sql.ErrNoRows { @@ -208,14 +208,14 @@ func (c *conn) CreateAuthCode(a storage.AuthCode) error { id, client_id, scopes, nonce, redirect_uri, claims_user_id, claims_username, claims_preferred_username, claims_email, claims_email_verified, claims_groups, - connector_id, + connector_id, connector_data, expiry ) - values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13); + values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14); `, a.ID, a.ClientID, encoder(a.Scopes), a.Nonce, a.RedirectURI, a.Claims.UserID, a.Claims.Username, a.Claims.PreferredUsername, a.Claims.Email, a.Claims.EmailVerified, - encoder(a.Claims.Groups), a.ConnectorID, a.Expiry, + encoder(a.Claims.Groups), a.ConnectorID, a.ConnectorData, a.Expiry, ) if err != nil { @@ -233,13 +233,13 @@ func (c *conn) GetAuthCode(id string) (a storage.AuthCode, err error) { id, client_id, scopes, nonce, redirect_uri, claims_user_id, claims_username, claims_preferred_username, claims_email, claims_email_verified, claims_groups, - connector_id, + connector_id, connector_data, expiry from auth_code where id = $1; `, id).Scan( &a.ID, &a.ClientID, decoder(&a.Scopes), &a.Nonce, &a.RedirectURI, &a.Claims.UserID, &a.Claims.Username, &a.Claims.PreferredUsername, &a.Claims.Email, &a.Claims.EmailVerified, - decoder(&a.Claims.Groups), &a.ConnectorID, &a.Expiry, + decoder(&a.Claims.Groups), &a.ConnectorID, &a.ConnectorData, &a.Expiry, ) if err != nil { if err == sql.ErrNoRows { @@ -256,16 +256,16 @@ func (c *conn) CreateRefresh(r storage.RefreshToken) error { id, client_id, scopes, nonce, claims_user_id, claims_username, claims_preferred_username, claims_email, claims_email_verified, claims_groups, - connector_id, + connector_id, connector_data, token, created_at, last_used ) - values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14); + values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15); `, r.ID, r.ClientID, encoder(r.Scopes), r.Nonce, r.Claims.UserID, r.Claims.Username, r.Claims.PreferredUsername, r.Claims.Email, r.Claims.EmailVerified, encoder(r.Claims.Groups), - r.ConnectorID, + r.ConnectorID, r.ConnectorData, r.Token, r.CreatedAt, r.LastUsed, ) if err != nil { @@ -299,17 +299,18 @@ func (c *conn) UpdateRefreshToken(id string, updater func(old storage.RefreshTok claims_email_verified = $8, claims_groups = $9, connector_id = $10, - token = $11, - created_at = $12, - last_used = $13 + connector_data = $11, + token = $12, + created_at = $13, + last_used = $14 where - id = $14 + id = $15 `, r.ClientID, encoder(r.Scopes), r.Nonce, r.Claims.UserID, r.Claims.Username, r.Claims.PreferredUsername, r.Claims.Email, r.Claims.EmailVerified, encoder(r.Claims.Groups), - r.ConnectorID, + r.ConnectorID, r.ConnectorData, r.Token, r.CreatedAt, r.LastUsed, id, ) if err != nil { @@ -330,7 +331,7 @@ func getRefresh(q querier, id string) (storage.RefreshToken, error) { claims_user_id, claims_username, claims_preferred_username, claims_email, claims_email_verified, claims_groups, - connector_id, + connector_id, connector_data, token, created_at, last_used from refresh_token where id = $1; `, id)) @@ -369,7 +370,7 @@ func scanRefresh(s scanner) (r storage.RefreshToken, err error) { &r.Claims.UserID, &r.Claims.Username, &r.Claims.PreferredUsername, &r.Claims.Email, &r.Claims.EmailVerified, decoder(&r.Claims.Groups), - &r.ConnectorID, + &r.ConnectorID, &r.ConnectorData, &r.Token, &r.CreatedAt, &r.LastUsed, ) if err != nil { From 41b7c855d0d95b6e941704f0ab07f218927503d3 Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Thu, 18 Apr 2019 15:06:48 +0100 Subject: [PATCH 17/27] Revert "Update conformance" This reverts commit 9c7ceabe8aebf6c740c237c5e76c21397179f901. --- storage/conformance/conformance.go | 65 ++++++++++++++++------------- storage/conformance/transactions.go | 1 + 2 files changed, 38 insertions(+), 28 deletions(-) diff --git a/storage/conformance/conformance.go b/storage/conformance/conformance.go index ea21edae..9832a7d8 100644 --- a/storage/conformance/conformance.go +++ b/storage/conformance/conformance.go @@ -91,6 +91,7 @@ func testAuthRequestCRUD(t *testing.T, s storage.Storage) { LoggedIn: true, Expiry: neverExpire, ConnectorID: "ldap", + ConnectorData: []byte(`{"some":"data"}`), Claims: storage.Claims{ UserID: "1", Username: "jane", @@ -122,6 +123,7 @@ func testAuthRequestCRUD(t *testing.T, s storage.Storage) { LoggedIn: true, Expiry: neverExpire, ConnectorID: "ldap", + ConnectorData: []byte(`{"some":"data"}`), Claims: storage.Claims{ UserID: "2", Username: "john", @@ -163,13 +165,14 @@ func testAuthRequestCRUD(t *testing.T, s storage.Storage) { func testAuthCodeCRUD(t *testing.T, s storage.Storage) { a1 := storage.AuthCode{ - ID: storage.NewID(), - ClientID: "client1", - RedirectURI: "https://localhost:80/callback", - Nonce: "foobar", - Scopes: []string{"openid", "email"}, - Expiry: neverExpire, - ConnectorID: "ldap", + ID: storage.NewID(), + ClientID: "client1", + RedirectURI: "https://localhost:80/callback", + Nonce: "foobar", + Scopes: []string{"openid", "email"}, + Expiry: neverExpire, + ConnectorID: "ldap", + ConnectorData: []byte(`{"some":"data"}`), Claims: storage.Claims{ UserID: "1", Username: "jane", @@ -184,13 +187,14 @@ func testAuthCodeCRUD(t *testing.T, s storage.Storage) { } a2 := storage.AuthCode{ - ID: storage.NewID(), - ClientID: "client2", - RedirectURI: "https://localhost:80/callback", - Nonce: "foobar", - Scopes: []string{"openid", "email"}, - Expiry: neverExpire, - ConnectorID: "ldap", + ID: storage.NewID(), + ClientID: "client2", + RedirectURI: "https://localhost:80/callback", + Nonce: "foobar", + Scopes: []string{"openid", "email"}, + Expiry: neverExpire, + ConnectorID: "ldap", + ConnectorData: []byte(`{"some":"data"}`), Claims: storage.Claims{ UserID: "2", Username: "john", @@ -319,6 +323,7 @@ func testRefreshTokenCRUD(t *testing.T, s storage.Storage) { EmailVerified: true, Groups: []string{"a", "b"}, }, + ConnectorData: []byte(`{"some":"data"}`), } if err := s.CreateRefresh(refresh); err != nil { t.Fatalf("create refresh token: %v", err) @@ -372,6 +377,7 @@ func testRefreshTokenCRUD(t *testing.T, s storage.Storage) { EmailVerified: true, Groups: []string{"a", "b"}, }, + ConnectorData: []byte(`{"some":"data"}`), } if err := s.CreateRefresh(refresh2); err != nil { @@ -725,13 +731,14 @@ func testGC(t *testing.T, s storage.Storage) { expiry := time.Now().In(est) c := storage.AuthCode{ - ID: storage.NewID(), - ClientID: "foobar", - RedirectURI: "https://localhost:80/callback", - Nonce: "foobar", - Scopes: []string{"openid", "email"}, - Expiry: expiry, - ConnectorID: "ldap", + ID: storage.NewID(), + ClientID: "foobar", + RedirectURI: "https://localhost:80/callback", + Nonce: "foobar", + Scopes: []string{"openid", "email"}, + Expiry: expiry, + ConnectorID: "ldap", + ConnectorData: []byte(`{"some":"data"}`), Claims: storage.Claims{ UserID: "1", Username: "jane", @@ -783,6 +790,7 @@ func testGC(t *testing.T, s storage.Storage) { LoggedIn: true, Expiry: expiry, ConnectorID: "ldap", + ConnectorData: []byte(`{"some":"data"}`), Claims: storage.Claims{ UserID: "1", Username: "jane", @@ -835,13 +843,14 @@ func testTimezones(t *testing.T, s storage.Storage) { expiry := time.Now().In(est).Round(time.Millisecond) c := storage.AuthCode{ - ID: storage.NewID(), - ClientID: "foobar", - RedirectURI: "https://localhost:80/callback", - Nonce: "foobar", - Scopes: []string{"openid", "email"}, - Expiry: expiry, - ConnectorID: "ldap", + ID: storage.NewID(), + ClientID: "foobar", + RedirectURI: "https://localhost:80/callback", + Nonce: "foobar", + Scopes: []string{"openid", "email"}, + Expiry: expiry, + ConnectorID: "ldap", + ConnectorData: []byte(`{"some":"data"}`), Claims: storage.Claims{ UserID: "1", Username: "jane", diff --git a/storage/conformance/transactions.go b/storage/conformance/transactions.go index 2eb509e1..dc1be1b6 100644 --- a/storage/conformance/transactions.go +++ b/storage/conformance/transactions.go @@ -67,6 +67,7 @@ func testAuthRequestConcurrentUpdate(t *testing.T, s storage.Storage) { LoggedIn: true, Expiry: neverExpire, ConnectorID: "ldap", + ConnectorData: []byte(`{"some":"data"}`), Claims: storage.Claims{ UserID: "1", Username: "jane", From 236b25b68e7448adae06f56fdfee15bf1e08aa4a Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Thu, 18 Apr 2019 15:17:31 +0100 Subject: [PATCH 18/27] Revert "Fix ETCD storage backend" --- storage/etcd/types.go | 62 ++++++++++++++++++++++++------------------- 1 file changed, 34 insertions(+), 28 deletions(-) diff --git a/storage/etcd/types.go b/storage/etcd/types.go index e9075ed6..a16eae8e 100644 --- a/storage/etcd/types.go +++ b/storage/etcd/types.go @@ -16,22 +16,24 @@ type AuthCode struct { Nonce string `json:"nonce,omitempty"` Scopes []string `json:"scopes,omitempty"` - ConnectorID string `json:"connectorID,omitempty"` - Claims Claims `json:"claims,omitempty"` + ConnectorID string `json:"connectorID,omitempty"` + ConnectorData []byte `json:"connectorData,omitempty"` + Claims Claims `json:"claims,omitempty"` Expiry time.Time `json:"expiry"` } func fromStorageAuthCode(a storage.AuthCode) AuthCode { return AuthCode{ - ID: a.ID, - ClientID: a.ClientID, - RedirectURI: a.RedirectURI, - ConnectorID: a.ConnectorID, - Nonce: a.Nonce, - Scopes: a.Scopes, - Claims: fromStorageClaims(a.Claims), - Expiry: a.Expiry, + ID: a.ID, + ClientID: a.ClientID, + RedirectURI: a.RedirectURI, + ConnectorID: a.ConnectorID, + ConnectorData: a.ConnectorData, + Nonce: a.Nonce, + Scopes: a.Scopes, + Claims: fromStorageClaims(a.Claims), + Expiry: a.Expiry, } } @@ -72,6 +74,7 @@ func fromStorageAuthRequest(a storage.AuthRequest) AuthRequest { LoggedIn: a.LoggedIn, Claims: fromStorageClaims(a.Claims), ConnectorID: a.ConnectorID, + ConnectorData: a.ConnectorData, } } @@ -87,6 +90,7 @@ func toStorageAuthRequest(a AuthRequest) storage.AuthRequest { ForceApprovalPrompt: a.ForceApprovalPrompt, LoggedIn: a.LoggedIn, ConnectorID: a.ConnectorID, + ConnectorData: a.ConnectorData, Expiry: a.Expiry, Claims: toStorageClaims(a.Claims), } @@ -114,29 +118,31 @@ type RefreshToken struct { func toStorageRefreshToken(r RefreshToken) storage.RefreshToken { return storage.RefreshToken{ - ID: r.ID, - Token: r.Token, - CreatedAt: r.CreatedAt, - LastUsed: r.LastUsed, - ClientID: r.ClientID, - ConnectorID: r.ConnectorID, - Scopes: r.Scopes, - Nonce: r.Nonce, - Claims: toStorageClaims(r.Claims), + ID: r.ID, + Token: r.Token, + CreatedAt: r.CreatedAt, + LastUsed: r.LastUsed, + ClientID: r.ClientID, + ConnectorID: r.ConnectorID, + ConnectorData: r.ConnectorData, + Scopes: r.Scopes, + Nonce: r.Nonce, + Claims: toStorageClaims(r.Claims), } } func fromStorageRefreshToken(r storage.RefreshToken) RefreshToken { return RefreshToken{ - ID: r.ID, - Token: r.Token, - CreatedAt: r.CreatedAt, - LastUsed: r.LastUsed, - ClientID: r.ClientID, - ConnectorID: r.ConnectorID, - Scopes: r.Scopes, - Nonce: r.Nonce, - Claims: fromStorageClaims(r.Claims), + ID: r.ID, + Token: r.Token, + CreatedAt: r.CreatedAt, + LastUsed: r.LastUsed, + ClientID: r.ClientID, + ConnectorID: r.ConnectorID, + ConnectorData: r.ConnectorData, + Scopes: r.Scopes, + Nonce: r.Nonce, + Claims: fromStorageClaims(r.Claims), } } From 45a40a13a314152f6083e6270b575ea676762720 Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Thu, 18 Apr 2019 15:18:50 +0100 Subject: [PATCH 19/27] Revert "Update Kubernetes storage backend" This reverts commit 228bdc324877bf67ecdd434503b9c1b25d8e7d28. --- storage/kubernetes/types.go | 73 ++++++++++++++++++++----------------- 1 file changed, 40 insertions(+), 33 deletions(-) diff --git a/storage/kubernetes/types.go b/storage/kubernetes/types.go index abc11ba4..5eda1781 100644 --- a/storage/kubernetes/types.go +++ b/storage/kubernetes/types.go @@ -265,7 +265,8 @@ type AuthRequest struct { // with a backend. Claims Claims `json:"claims,omitempty"` // The connector used to login the user. Set when the user authenticates. - ConnectorID string `json:"connectorID,omitempty"` + ConnectorID string `json:"connectorID,omitempty"` + ConnectorData []byte `json:"connectorData,omitempty"` Expiry time.Time `json:"expiry"` } @@ -289,6 +290,7 @@ func toStorageAuthRequest(req AuthRequest) storage.AuthRequest { ForceApprovalPrompt: req.ForceApprovalPrompt, LoggedIn: req.LoggedIn, ConnectorID: req.ConnectorID, + ConnectorData: req.ConnectorData, Expiry: req.Expiry, Claims: toStorageClaims(req.Claims), } @@ -314,6 +316,7 @@ func (cli *client) fromStorageAuthRequest(a storage.AuthRequest) AuthRequest { LoggedIn: a.LoggedIn, ForceApprovalPrompt: a.ForceApprovalPrompt, ConnectorID: a.ConnectorID, + ConnectorData: a.ConnectorData, Expiry: a.Expiry, Claims: fromStorageClaims(a.Claims), } @@ -408,26 +411,28 @@ func (cli *client) fromStorageAuthCode(a storage.AuthCode) AuthCode { Name: a.ID, Namespace: cli.namespace, }, - ClientID: a.ClientID, - RedirectURI: a.RedirectURI, - ConnectorID: a.ConnectorID, - Nonce: a.Nonce, - Scopes: a.Scopes, - Claims: fromStorageClaims(a.Claims), - Expiry: a.Expiry, + ClientID: a.ClientID, + RedirectURI: a.RedirectURI, + ConnectorID: a.ConnectorID, + ConnectorData: a.ConnectorData, + Nonce: a.Nonce, + Scopes: a.Scopes, + Claims: fromStorageClaims(a.Claims), + Expiry: a.Expiry, } } func toStorageAuthCode(a AuthCode) storage.AuthCode { return storage.AuthCode{ - ID: a.ObjectMeta.Name, - ClientID: a.ClientID, - RedirectURI: a.RedirectURI, - ConnectorID: a.ConnectorID, - Nonce: a.Nonce, - Scopes: a.Scopes, - Claims: toStorageClaims(a.Claims), - Expiry: a.Expiry, + ID: a.ObjectMeta.Name, + ClientID: a.ClientID, + RedirectURI: a.RedirectURI, + ConnectorID: a.ConnectorID, + ConnectorData: a.ConnectorData, + Nonce: a.Nonce, + Scopes: a.Scopes, + Claims: toStorageClaims(a.Claims), + Expiry: a.Expiry, } } @@ -461,15 +466,16 @@ type RefreshList struct { func toStorageRefreshToken(r RefreshToken) storage.RefreshToken { return storage.RefreshToken{ - ID: r.ObjectMeta.Name, - Token: r.Token, - CreatedAt: r.CreatedAt, - LastUsed: r.LastUsed, - ClientID: r.ClientID, - ConnectorID: r.ConnectorID, - Scopes: r.Scopes, - Nonce: r.Nonce, - Claims: toStorageClaims(r.Claims), + ID: r.ObjectMeta.Name, + Token: r.Token, + CreatedAt: r.CreatedAt, + LastUsed: r.LastUsed, + ClientID: r.ClientID, + ConnectorID: r.ConnectorID, + ConnectorData: r.ConnectorData, + Scopes: r.Scopes, + Nonce: r.Nonce, + Claims: toStorageClaims(r.Claims), } } @@ -483,14 +489,15 @@ func (cli *client) fromStorageRefreshToken(r storage.RefreshToken) RefreshToken Name: r.ID, Namespace: cli.namespace, }, - Token: r.Token, - CreatedAt: r.CreatedAt, - LastUsed: r.LastUsed, - ClientID: r.ClientID, - ConnectorID: r.ConnectorID, - Scopes: r.Scopes, - Nonce: r.Nonce, - Claims: fromStorageClaims(r.Claims), + Token: r.Token, + CreatedAt: r.CreatedAt, + LastUsed: r.LastUsed, + ClientID: r.ClientID, + ConnectorID: r.ConnectorID, + ConnectorData: r.ConnectorData, + Scopes: r.Scopes, + Nonce: r.Nonce, + Claims: fromStorageClaims(r.Claims), } } From 19ad7daa7f71aaf062dd4f05a15034eed916e11c Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Tue, 23 Apr 2019 10:59:36 +0100 Subject: [PATCH 20/27] Use old ConnectorData before session.ConnectorData --- server/handlers.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/server/handlers.go b/server/handlers.go index 80965daf..35164717 100644 --- a/server/handlers.go +++ b/server/handlers.go @@ -1003,6 +1003,9 @@ func (s *Server) handleRefreshToken(w http.ResponseWriter, r *http.Request, clie s.logger.Errorf("failed to get offline session: %v", err) return } + } else if len(refresh.ConnectorData) > 0 { + // Use the old connector data if it exists, should be deleted once used + connectorData = session.ConnectorData } else { connectorData = session.ConnectorData } @@ -1087,6 +1090,9 @@ func (s *Server) handleRefreshToken(w http.ResponseWriter, r *http.Request, clie old.Claims.EmailVerified = ident.EmailVerified old.Claims.Groups = ident.Groups old.LastUsed = lastUsed + + // ConnectorData has been moved to OfflineSession + old.ConnectorData = []byte{} return old, nil } From 8b344fe4d3cd329643e50f1e33d2342bc723c83d Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Fri, 10 May 2019 15:31:50 +0100 Subject: [PATCH 21/27] Fix Refresh comment --- connector/oidc/oidc.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connector/oidc/oidc.go b/connector/oidc/oidc.go index cdb3ff55..0abc0bc4 100644 --- a/connector/oidc/oidc.go +++ b/connector/oidc/oidc.go @@ -208,7 +208,7 @@ func (c *oidcConnector) HandleCallback(s connector.Scopes, r *http.Request) (ide return c.createIdentity(r.Context(), identity, token) } -// Refresh is implemented for backwards compatibility, even though it's a no-op. +// Refresh is used to refresh a session with the refresh token provided by the IdP func (c *oidcConnector) Refresh(ctx context.Context, s connector.Scopes, identity connector.Identity) (connector.Identity, error) { t := &oauth2.Token{ RefreshToken: string(identity.ConnectorData), From f6077083c91ae9b99ebef99eb2bf56e345313557 Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Wed, 25 Sep 2019 21:12:20 +0100 Subject: [PATCH 22/27] Identify error as failure to retrieve refresh token --- connector/oidc/oidc.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connector/oidc/oidc.go b/connector/oidc/oidc.go index 0abc0bc4..df849093 100644 --- a/connector/oidc/oidc.go +++ b/connector/oidc/oidc.go @@ -216,7 +216,7 @@ func (c *oidcConnector) Refresh(ctx context.Context, s connector.Scopes, identit } token, err := c.oauth2Config.TokenSource(ctx, t).Token() if err != nil { - return identity, fmt.Errorf("oidc: failed to get token: %v", err) + return identity, fmt.Errorf("oidc: failed to get refresh token: %v", err) } return c.createIdentity(ctx, identity, token) From 77fcf9ad77e02859f57b979556a9766dcc63bc61 Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Wed, 25 Sep 2019 21:20:19 +0100 Subject: [PATCH 23/27] Use a struct for connector data within OIDC connector --- connector/oidc/oidc.go | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/connector/oidc/oidc.go b/connector/oidc/oidc.go index df849093..749b56ed 100644 --- a/connector/oidc/oidc.go +++ b/connector/oidc/oidc.go @@ -3,6 +3,7 @@ package oidc import ( "context" + "encoding/json" "errors" "fmt" "net/http" @@ -61,6 +62,11 @@ var brokenAuthHeaderDomains = []string{ "oktapreview.com", } +// connectorData stores information for sessions authenticated by this connector +type connectorData struct { + refreshToken []byte +} + // Detect auth header provider issues for known providers. This lets users // avoid having to explicitly set "basicAuthUnsupported" in their config. // @@ -210,8 +216,14 @@ func (c *oidcConnector) HandleCallback(s connector.Scopes, r *http.Request) (ide // Refresh is used to refresh a session with the refresh token provided by the IdP func (c *oidcConnector) Refresh(ctx context.Context, s connector.Scopes, identity connector.Identity) (connector.Identity, error) { + cd := connectorData{} + err := json.Unmarshal(identity.ConnectorData, &cd) + if err != nil { + return identity, fmt.Errorf("oidc: failed to unmarshal connector data: %v", err) + } + t := &oauth2.Token{ - RefreshToken: string(identity.ConnectorData), + RefreshToken: string(cd.refreshToken), Expiry: time.Now().Add(-time.Hour), } token, err := c.oauth2Config.TokenSource(ctx, t).Token() @@ -284,12 +296,21 @@ func (c *oidcConnector) createIdentity(ctx context.Context, identity connector.I } } + cd := connectorData{ + refreshToken: []byte(token.RefreshToken), + } + + connData, err := json.Marshal(&cd) + if err != nil { + return identity, fmt.Errorf("oidc: failed to encode connector data: %v", err) + } + identity = connector.Identity{ UserID: idToken.Subject, Username: name, Email: email, EmailVerified: emailVerified, - ConnectorData: []byte(token.RefreshToken), + ConnectorData: connData, } if c.userIDKey != "" { From d9095073c80d0d585c93efb764bf4594c62b4656 Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Wed, 25 Sep 2019 21:27:31 +0100 Subject: [PATCH 24/27] Unindent session updates on finalizeLogin --- server/handlers.go | 65 ++++++++++++++++++++++++---------------------- 1 file changed, 34 insertions(+), 31 deletions(-) diff --git a/server/handlers.go b/server/handlers.go index 35164717..49116b88 100644 --- a/server/handlers.go +++ b/server/handlers.go @@ -505,42 +505,45 @@ func (s *Server) finalizeLogin(identity connector.Identity, authReq storage.Auth s.logger.Infof("login successful: connector %q, username=%q, preferred_username=%q, email=%q, groups=%q", authReq.ConnectorID, claims.Username, claims.PreferredUsername, email, claims.Groups) - if _, ok := conn.(connector.RefreshConnector); ok { - // Try to retrieve an existing OfflineSession object for the corresponding user. - if session, err := s.storage.GetOfflineSessions(identity.UserID, authReq.ConnectorID); err != nil { - if err != storage.ErrNotFound { - s.logger.Errorf("failed to get offline session: %v", err) - return "", err - } - offlineSessions := storage.OfflineSessions{ - UserID: identity.UserID, - ConnID: authReq.ConnectorID, - Refresh: make(map[string]*storage.RefreshTokenRef), - ConnectorData: identity.ConnectorData, - } + returnURL := path.Join(s.issuerURL.Path, "/approval") + "?req=" + authReq.ID + _, ok := conn.(connector.RefreshConnector) + if !ok { + return returnURL, nil + } - // Create a new OfflineSession object for the user and add a reference object for - // the newly received refreshtoken. - if err := s.storage.CreateOfflineSessions(offlineSessions); err != nil { - s.logger.Errorf("failed to create offline session: %v", err) - return "", err - } - } else { - // Update existing OfflineSession obj with new RefreshTokenRef. - if err := s.storage.UpdateOfflineSessions(session.UserID, session.ConnID, func(old storage.OfflineSessions) (storage.OfflineSessions, error) { - if len(identity.ConnectorData) > 0 { - old.ConnectorData = identity.ConnectorData - } - return old, nil - }); err != nil { - s.logger.Errorf("failed to update offline session: %v", err) - return "", err - } + // Try to retrieve an existing OfflineSession object for the corresponding user. + if session, err := s.storage.GetOfflineSessions(identity.UserID, authReq.ConnectorID); err != nil { + if err != storage.ErrNotFound { + s.logger.Errorf("failed to get offline session: %v", err) + return "", err + } + offlineSessions := storage.OfflineSessions{ + UserID: identity.UserID, + ConnID: authReq.ConnectorID, + Refresh: make(map[string]*storage.RefreshTokenRef), + ConnectorData: identity.ConnectorData, + } + // Create a new OfflineSession object for the user and add a reference object for + // the newly received refreshtoken. + if err := s.storage.CreateOfflineSessions(offlineSessions); err != nil { + s.logger.Errorf("failed to create offline session: %v", err) + return "", err + } + } else { + // Update existing OfflineSession obj with new RefreshTokenRef. + if err := s.storage.UpdateOfflineSessions(session.UserID, session.ConnID, func(old storage.OfflineSessions) (storage.OfflineSessions, error) { + if len(identity.ConnectorData) > 0 { + old.ConnectorData = identity.ConnectorData + } + return old, nil + }); err != nil { + s.logger.Errorf("failed to update offline session: %v", err) + return "", err } } - return path.Join(s.issuerURL.Path, "/approval") + "?req=" + authReq.ID, nil + return returnURL, nil } func (s *Server) handleApproval(w http.ResponseWriter, r *http.Request) { From c4e96dda325bf5906faf7d37acae4aacc59339be Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Wed, 25 Sep 2019 21:31:04 +0100 Subject: [PATCH 25/27] Fix migration of old connector data --- server/handlers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/handlers.go b/server/handlers.go index 49116b88..0f5b0d23 100644 --- a/server/handlers.go +++ b/server/handlers.go @@ -1008,7 +1008,7 @@ func (s *Server) handleRefreshToken(w http.ResponseWriter, r *http.Request, clie } } else if len(refresh.ConnectorData) > 0 { // Use the old connector data if it exists, should be deleted once used - connectorData = session.ConnectorData + connectorData = refresh.ConnectorData } else { connectorData = session.ConnectorData } From c782ac809c3f907046c80338f55083576c079e42 Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Thu, 26 Sep 2019 15:30:44 +0100 Subject: [PATCH 26/27] Remove defaulting from connector_data column --- storage/sql/migrate.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/storage/sql/migrate.go b/storage/sql/migrate.go index ecf1ae77..5b86bc78 100644 --- a/storage/sql/migrate.go +++ b/storage/sql/migrate.go @@ -205,7 +205,7 @@ var migrations = []migration{ { stmts: []string{` alter table offline_session - add column connector_data bytea not null default ''; + add column connector_data bytea; `, }, }, From 3156553843492c803d5dde1e3ae8d2f1d0350f3d Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Wed, 2 Oct 2019 13:39:52 +0100 Subject: [PATCH 27/27] OIDC: Rename refreshToken to RefreshToken --- connector/oidc/oidc.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/connector/oidc/oidc.go b/connector/oidc/oidc.go index 749b56ed..3e405d87 100644 --- a/connector/oidc/oidc.go +++ b/connector/oidc/oidc.go @@ -64,7 +64,7 @@ var brokenAuthHeaderDomains = []string{ // connectorData stores information for sessions authenticated by this connector type connectorData struct { - refreshToken []byte + RefreshToken []byte } // Detect auth header provider issues for known providers. This lets users @@ -223,7 +223,7 @@ func (c *oidcConnector) Refresh(ctx context.Context, s connector.Scopes, identit } t := &oauth2.Token{ - RefreshToken: string(cd.refreshToken), + RefreshToken: string(cd.RefreshToken), Expiry: time.Now().Add(-time.Hour), } token, err := c.oauth2Config.TokenSource(ctx, t).Token() @@ -297,7 +297,7 @@ func (c *oidcConnector) createIdentity(ctx context.Context, identity connector.I } cd := connectorData{ - refreshToken: []byte(token.RefreshToken), + RefreshToken: []byte(token.RefreshToken), } connData, err := json.Marshal(&cd)