diff --git a/.gitignore b/.gitignore index db3eaf3e3a..20d0d22d3f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ bin dist _output +.idea +.DS_Store \ No newline at end of file diff --git a/connector/connector.go b/connector/connector.go index edd7fa5706..19eed05497 100644 --- a/connector/connector.go +++ b/connector/connector.go @@ -27,8 +27,8 @@ type Identity struct { Username string Email string EmailVerified bool - - Groups []string + Groups []string + Locale string // ConnectorData holds data used by the connector for subsequent requests after initial // authentication, such as access tokens for upstream provides. diff --git a/connector/oidc/oidc.go b/connector/oidc/oidc.go index 4a64df8b60..563aae112f 100644 --- a/connector/oidc/oidc.go +++ b/connector/oidc/oidc.go @@ -259,11 +259,14 @@ func (c *oidcConnector) HandleCallback(s connector.Scopes, r *http.Request) (ide } } + locale, _ := claims["locale"].(string) + identity = connector.Identity{ UserID: idToken.Subject, Username: name, Email: email, EmailVerified: emailVerified, + Locale: locale, } if c.userIDKey != "" { diff --git a/server/handlers.go b/server/handlers.go index 9bff36ee84..fb74a398de 100644 --- a/server/handlers.go +++ b/server/handlers.go @@ -484,6 +484,7 @@ func (s *Server) finalizeLogin(identity connector.Identity, authReq storage.Auth Email: identity.Email, EmailVerified: identity.EmailVerified, Groups: identity.Groups, + Locale: identity.Locale, } updater := func(a storage.AuthRequest) (storage.AuthRequest, error) { @@ -974,6 +975,7 @@ func (s *Server) handleRefreshToken(w http.ResponseWriter, r *http.Request, clie EmailVerified: refresh.Claims.EmailVerified, Groups: refresh.Claims.Groups, ConnectorData: refresh.ConnectorData, + Locale: refresh.Claims.Locale, } // Can the connector refresh the identity? If so, attempt to refresh the data @@ -997,6 +999,7 @@ func (s *Server) handleRefreshToken(w http.ResponseWriter, r *http.Request, clie Email: ident.Email, EmailVerified: ident.EmailVerified, Groups: ident.Groups, + Locale: ident.Locale, } accessToken, err := s.newAccessToken(client.ID, claims, scopes, refresh.Nonce, refresh.ConnectorID) diff --git a/server/oauth2.go b/server/oauth2.go index 6104b54988..1bfa6c7952 100644 --- a/server/oauth2.go +++ b/server/oauth2.go @@ -253,10 +253,10 @@ type idTokenClaims struct { AccessTokenHash string `json:"at_hash,omitempty"` - Email string `json:"email,omitempty"` - EmailVerified *bool `json:"email_verified,omitempty"` - - Groups []string `json:"groups,omitempty"` + Email string `json:"email,omitempty"` + EmailVerified *bool `json:"email_verified,omitempty"` + Groups []string `json:"groups,omitempty"` + Locale string `json:"locale,omitempty"` Name string `json:"name,omitempty"` @@ -329,6 +329,7 @@ func (s *Server) newIDToken(clientID string, claims storage.Claims, scopes []str tok.Groups = claims.Groups case scope == scopeProfile: tok.Name = claims.Username + tok.Locale = claims.Locale case scope == scopeFederatedID: tok.FederatedIDClaims = &federatedIDClaims{ ConnectorID: connID, diff --git a/storage/conformance/conformance.go b/storage/conformance/conformance.go index a13998077c..357b1638ad 100644 --- a/storage/conformance/conformance.go +++ b/storage/conformance/conformance.go @@ -98,6 +98,7 @@ func testAuthRequestCRUD(t *testing.T, s storage.Storage) { Email: "jane.doe@example.com", EmailVerified: true, Groups: []string{"a", "b"}, + Locale: "en_US", }, } @@ -179,6 +180,7 @@ func testAuthCodeCRUD(t *testing.T, s storage.Storage) { Email: "jane.doe@example.com", EmailVerified: true, Groups: []string{"a", "b"}, + Locale: "en_US", }, } @@ -322,6 +324,7 @@ func testRefreshTokenCRUD(t *testing.T, s storage.Storage) { Email: "jane.doe@example.com", EmailVerified: true, Groups: []string{"a", "b"}, + Locale: "en_US", }, ConnectorData: []byte(`{"some":"data"}`), } @@ -795,6 +798,7 @@ func testGC(t *testing.T, s storage.Storage) { Email: "jane.doe@example.com", EmailVerified: true, Groups: []string{"a", "b"}, + Locale: "en_US", }, } @@ -855,6 +859,7 @@ func testTimezones(t *testing.T, s storage.Storage) { Email: "jane.doe@example.com", EmailVerified: true, Groups: []string{"a", "b"}, + Locale: "en_US", }, } if err := s.CreateAuthCode(c); err != nil { diff --git a/storage/etcd/types.go b/storage/etcd/types.go index 0d8f521ad4..b6637a5c53 100644 --- a/storage/etcd/types.go +++ b/storage/etcd/types.go @@ -153,6 +153,7 @@ type Claims struct { Email string `json:"email"` EmailVerified bool `json:"emailVerified"` Groups []string `json:"groups,omitempty"` + Locale string `json:"locale,omitempty"` } func fromStorageClaims(i storage.Claims) Claims { @@ -162,6 +163,7 @@ func fromStorageClaims(i storage.Claims) Claims { Email: i.Email, EmailVerified: i.EmailVerified, Groups: i.Groups, + Locale: i.Locale, } } @@ -172,6 +174,7 @@ func toStorageClaims(i Claims) storage.Claims { Email: i.Email, EmailVerified: i.EmailVerified, Groups: i.Groups, + Locale: i.Locale, } } diff --git a/storage/kubernetes/types.go b/storage/kubernetes/types.go index 1ed405b50a..2ca56fc103 100644 --- a/storage/kubernetes/types.go +++ b/storage/kubernetes/types.go @@ -215,6 +215,7 @@ type Claims struct { Email string `json:"email"` EmailVerified bool `json:"emailVerified"` Groups []string `json:"groups,omitempty"` + Locale string `json:"locale,omitempty"` } func fromStorageClaims(i storage.Claims) Claims { @@ -224,6 +225,7 @@ func fromStorageClaims(i storage.Claims) Claims { Email: i.Email, EmailVerified: i.EmailVerified, Groups: i.Groups, + Locale: i.Locale, } } @@ -234,6 +236,7 @@ func toStorageClaims(i Claims) storage.Claims { Email: i.Email, EmailVerified: i.EmailVerified, Groups: i.Groups, + Locale: i.Locale, } } diff --git a/storage/sql/crud.go b/storage/sql/crud.go index d7c055ab18..bc9be186b6 100644 --- a/storage/sql/crud.go +++ b/storage/sql/crud.go @@ -109,18 +109,18 @@ func (c *conn) CreateAuthRequest(a storage.AuthRequest) error { id, client_id, response_types, scopes, redirect_uri, nonce, state, force_approval_prompt, logged_in, claims_user_id, claims_username, claims_email, claims_email_verified, - claims_groups, + claims_groups, claims_locale, 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.Email, a.Claims.EmailVerified, - encoder(a.Claims.Groups), + encoder(a.Claims.Groups), a.Claims.Locale, a.ConnectorID, a.ConnectorData, a.Expiry, ) @@ -151,15 +151,15 @@ func (c *conn) UpdateAuthRequest(id string, updater func(a storage.AuthRequest) nonce = $5, state = $6, force_approval_prompt = $7, logged_in = $8, claims_user_id = $9, claims_username = $10, claims_email = $11, claims_email_verified = $12, - claims_groups = $13, - connector_id = $14, connector_data = $15, - expiry = $16 - where id = $17; + claims_groups = $13, claims_locale = $14, + 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.Email, a.Claims.EmailVerified, - encoder(a.Claims.Groups), + encoder(a.Claims.Groups), a.Claims.Locale, a.ConnectorID, a.ConnectorData, a.Expiry, r.ID, ) @@ -181,14 +181,14 @@ func getAuthRequest(q querier, id string) (a storage.AuthRequest, err error) { id, client_id, response_types, scopes, redirect_uri, nonce, state, force_approval_prompt, logged_in, claims_user_id, claims_username, claims_email, claims_email_verified, - claims_groups, + claims_groups, claims_locale, 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, &a.ForceApprovalPrompt, &a.LoggedIn, &a.Claims.UserID, &a.Claims.Username, &a.Claims.Email, &a.Claims.EmailVerified, - decoder(&a.Claims.Groups), + decoder(&a.Claims.Groups), &a.Claims.Locale, &a.ConnectorID, &a.ConnectorData, &a.Expiry, ) if err != nil { @@ -205,15 +205,15 @@ func (c *conn) CreateAuthCode(a storage.AuthCode) error { insert into auth_code ( id, client_id, scopes, nonce, redirect_uri, claims_user_id, claims_username, - claims_email, claims_email_verified, claims_groups, + claims_email, claims_email_verified, claims_groups, claims_locale, 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.Email, a.Claims.EmailVerified, encoder(a.Claims.Groups), - a.ConnectorID, a.ConnectorData, a.Expiry, + a.Claims.Locale, a.ConnectorID, a.ConnectorData, a.Expiry, ) if err != nil { @@ -231,13 +231,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_email, claims_email_verified, claims_groups, - connector_id, connector_data, + claims_locale, 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.Email, &a.Claims.EmailVerified, decoder(&a.Claims.Groups), - &a.ConnectorID, &a.ConnectorData, &a.Expiry, + &a.Claims.Locale, &a.ConnectorID, &a.ConnectorData, &a.Expiry, ) if err != nil { if err == sql.ErrNoRows { @@ -253,15 +253,15 @@ func (c *conn) CreateRefresh(r storage.RefreshToken) error { insert into refresh_token ( id, client_id, scopes, nonce, claims_user_id, claims_username, claims_email, claims_email_verified, - claims_groups, + claims_groups, claims_locale, 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.Email, r.Claims.EmailVerified, - encoder(r.Claims.Groups), + encoder(r.Claims.Groups), r.Claims.Locale, r.ConnectorID, r.ConnectorData, r.Token, r.CreatedAt, r.LastUsed, ) @@ -294,17 +294,18 @@ func (c *conn) UpdateRefreshToken(id string, updater func(old storage.RefreshTok claims_email = $6, claims_email_verified = $7, claims_groups = $8, - connector_id = $9, - connector_data = $10, - token = $11, - created_at = $12, - last_used = $13 + claims_locale = $9, + connector_id = $10, + 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.Email, r.Claims.EmailVerified, - encoder(r.Claims.Groups), + encoder(r.Claims.Groups), r.Claims.Locale, r.ConnectorID, r.ConnectorData, r.Token, r.CreatedAt, r.LastUsed, id, ) @@ -324,7 +325,7 @@ func getRefresh(q querier, id string) (storage.RefreshToken, error) { select id, client_id, scopes, nonce, claims_user_id, claims_username, claims_email, claims_email_verified, - claims_groups, + claims_groups, claims_locale, connector_id, connector_data, token, created_at, last_used from refresh_token where id = $1; @@ -336,7 +337,7 @@ func (c *conn) ListRefreshTokens() ([]storage.RefreshToken, error) { select id, client_id, scopes, nonce, claims_user_id, claims_username, claims_email, claims_email_verified, - claims_groups, + claims_groups, claims_locale, connector_id, connector_data, token, created_at, last_used from refresh_token; @@ -362,7 +363,7 @@ func scanRefresh(s scanner) (r storage.RefreshToken, err error) { err = s.Scan( &r.ID, &r.ClientID, decoder(&r.Scopes), &r.Nonce, &r.Claims.UserID, &r.Claims.Username, &r.Claims.Email, &r.Claims.EmailVerified, - decoder(&r.Claims.Groups), + decoder(&r.Claims.Groups), &r.Claims.Locale, &r.ConnectorID, &r.ConnectorData, &r.Token, &r.CreatedAt, &r.LastUsed, ) diff --git a/storage/sql/migrate.go b/storage/sql/migrate.go index e30629e742..7468d00042 100644 --- a/storage/sql/migrate.go +++ b/storage/sql/migrate.go @@ -190,4 +190,16 @@ var migrations = []migration{ );`, }, }, + { + stmts: []string{` + alter table auth_request + add column claims_locale text;`, + ` + alter table auth_code + add column claims_locale text;`, + ` + alter table refresh_token + add column claims_locale text;`, + }, + }, } diff --git a/storage/storage.go b/storage/storage.go index 893fb10035..9003dba136 100644 --- a/storage/storage.go +++ b/storage/storage.go @@ -141,8 +141,8 @@ type Claims struct { Username string Email string EmailVerified bool - - Groups []string + Groups []string + Locale string } // AuthRequest represents a OAuth2 client authorization request. It holds the state