Add preferred_username support (OIDC provider) (#420)
* Add support for preferred username. * Add missing TOC entries. * Add note about preferred_username support. * Adjust tests. * Check on not implemented error for GetPreferredUsername() call. Co-authored-by: Felix Fontein <felix@fontein.de> Co-authored-by: Joel Speed <Joel.speed@hotmail.co.uk>
This commit is contained in:
		
							parent
							
								
									0bca3564b5
								
							
						
					
					
						commit
						d934309b44
					
				|  | @ -18,12 +18,16 @@ Valid providers are : | ||||||
| - [Keycloak](#keycloak-auth-provider) | - [Keycloak](#keycloak-auth-provider) | ||||||
| - [GitLab](#gitlab-auth-provider) | - [GitLab](#gitlab-auth-provider) | ||||||
| - [LinkedIn](#linkedin-auth-provider) | - [LinkedIn](#linkedin-auth-provider) | ||||||
|  | - [Microsoft Azure AD](#microsoft-azure-ad-provider) | ||||||
|  | - [OpenID Connect](#openid-connect-provider) | ||||||
| - [login.gov](#logingov-provider) | - [login.gov](#logingov-provider) | ||||||
| - [Nextcloud](#nextcloud-provider) | - [Nextcloud](#nextcloud-provider) | ||||||
| - [DigitalOcean](#digitalocean-auth-provider) | - [DigitalOcean](#digitalocean-auth-provider) | ||||||
| 
 | 
 | ||||||
| The provider can be selected using the `provider` configuration value. | The provider can be selected using the `provider` configuration value. | ||||||
| 
 | 
 | ||||||
|  | Please note that not all provides support all claims. The `preferred_username` claim is currently only supported by the OpenID Connect provider. | ||||||
|  | 
 | ||||||
| ### Google Auth Provider | ### Google Auth Provider | ||||||
| 
 | 
 | ||||||
| For Google, the registration steps are: | For Google, the registration steps are: | ||||||
|  |  | ||||||
|  | @ -73,10 +73,10 @@ An example [oauth2_proxy.cfg]({{ site.gitweb }}/contrib/oauth2_proxy.cfg.example | ||||||
| | `-oidc-jwks-url` | string | OIDC JWKS URI for token verification; required if OIDC discovery is disabled | | | | `-oidc-jwks-url` | string | OIDC JWKS URI for token verification; required if OIDC discovery is disabled | | | ||||||
| | `-pass-access-token` | bool | pass OAuth access_token to upstream via X-Forwarded-Access-Token header | false | | | `-pass-access-token` | bool | pass OAuth access_token to upstream via X-Forwarded-Access-Token header | false | | ||||||
| | `-pass-authorization-header` | bool | pass OIDC IDToken to upstream via Authorization Bearer header | false | | | `-pass-authorization-header` | bool | pass OIDC IDToken to upstream via Authorization Bearer header | false | | ||||||
| | `-pass-basic-auth` | bool | pass HTTP Basic Auth, X-Forwarded-User and X-Forwarded-Email information to upstream | true | | | `-pass-basic-auth` | bool | pass HTTP Basic Auth, X-Forwarded-User, X-Forwarded-Email and X-Forwarded-Preferred-Username information to upstream | true | | ||||||
| | `-prefer-email-to-user` | bool | Prefer to use the Email address as the Username when passing information to upstream. Will only use Username if Email is unavailable, eg. htaccess authentication. | false | | | `-prefer-email-to-user` | bool | Prefer to use the Email address as the Username when passing information to upstream. Will only use Username if Email is unavailable, eg. htaccess authentication. | false | | ||||||
| | `-pass-host-header` | bool | pass the request Host Header to upstream | true | | | `-pass-host-header` | bool | pass the request Host Header to upstream | true | | ||||||
| | `-pass-user-headers` | bool | pass X-Forwarded-User and X-Forwarded-Email information to upstream | true | | | `-pass-user-headers` | bool | pass X-Forwarded-User, X-Forwarded-Email and X-Forwarded-Preferred-Username information to upstream | true | | ||||||
| | `-profile-url` | string | Profile access endpoint | | | | `-profile-url` | string | Profile access endpoint | | | ||||||
| | `-provider` | string | OAuth provider | google | | | `-provider` | string | OAuth provider | google | | ||||||
| | `-provider-display-name` | string | Override the provider's name with the given string; used for the sign-in page | (depends on provider) | | | `-provider-display-name` | string | Override the provider's name with the given string; used for the sign-in page | (depends on provider) | | ||||||
|  | @ -98,7 +98,7 @@ An example [oauth2_proxy.cfg]({{ site.gitweb }}/contrib/oauth2_proxy.cfg.example | ||||||
| | `-reverse-proxy` | bool | are we running behind a reverse proxy, controls whether headers like X-Real-Ip are accepted | false | | | `-reverse-proxy` | bool | are we running behind a reverse proxy, controls whether headers like X-Real-Ip are accepted | false | | ||||||
| | `-scope` | string | OAuth scope specification | | | | `-scope` | string | OAuth scope specification | | | ||||||
| | `-session-store-type` | string | [Session data storage backend](configuration/sessions); redis or cookie | cookie | | | `-session-store-type` | string | [Session data storage backend](configuration/sessions); redis or cookie | cookie | | ||||||
| | `-set-xauthrequest` | bool | set X-Auth-Request-User and X-Auth-Request-Email response headers (useful in Nginx auth_request mode) | false | | | `-set-xauthrequest` | bool | set X-Auth-Request-User, X-Auth-Request-Email and X-Auth-Request-Preferred-Username response headers (useful in Nginx auth_request mode) | false | | ||||||
| | `-set-authorization-header` | bool | set Authorization Bearer response header (useful in Nginx auth_request mode) | false | | | `-set-authorization-header` | bool | set Authorization Bearer response header (useful in Nginx auth_request mode) | false | | ||||||
| | `-signature-key` | string | GAP-Signature request signature key (algorithm:secretkey) | | | | `-signature-key` | string | GAP-Signature request signature key (algorithm:secretkey) | | | ||||||
| | `-silence-ping-logging` | bool | disable logging of requests to ping endpoint | false | | | `-silence-ping-logging` | bool | disable logging of requests to ping endpoint | false | | ||||||
|  |  | ||||||
|  | @ -48,6 +48,7 @@ var SignatureHeaders = []string{ | ||||||
| 	"Authorization", | 	"Authorization", | ||||||
| 	"X-Forwarded-User", | 	"X-Forwarded-User", | ||||||
| 	"X-Forwarded-Email", | 	"X-Forwarded-Email", | ||||||
|  | 	"X-Forwarded-Preferred-User", | ||||||
| 	"X-Forwarded-Access-Token", | 	"X-Forwarded-Access-Token", | ||||||
| 	"Cookie", | 	"Cookie", | ||||||
| 	"Gap-Auth", | 	"Gap-Auth", | ||||||
|  | @ -352,6 +353,13 @@ func (p *OAuthProxy) redeemCode(host, code string) (s *sessionsapi.SessionState, | ||||||
| 		s.Email, err = p.provider.GetEmailAddress(s) | 		s.Email, err = p.provider.GetEmailAddress(s) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	if s.PreferredUsername == "" { | ||||||
|  | 		s.PreferredUsername, err = p.provider.GetPreferredUsername(s) | ||||||
|  | 		if err != nil && err.Error() == "not implemented" { | ||||||
|  | 			err = nil | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	if s.User == "" { | 	if s.User == "" { | ||||||
| 		s.User, err = p.provider.GetUserName(s) | 		s.User, err = p.provider.GetUserName(s) | ||||||
| 		if err != nil && err.Error() == "not implemented" { | 		if err != nil && err.Error() == "not implemented" { | ||||||
|  | @ -670,7 +678,7 @@ func (p *OAuthProxy) SignIn(rw http.ResponseWriter, req *http.Request) { | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| //UserInfo endpoint outputs session email in JSON format
 | //UserInfo endpoint outputs session email and preferred username in JSON format
 | ||||||
| func (p *OAuthProxy) UserInfo(rw http.ResponseWriter, req *http.Request) { | func (p *OAuthProxy) UserInfo(rw http.ResponseWriter, req *http.Request) { | ||||||
| 
 | 
 | ||||||
| 	session, err := p.getAuthenticatedSession(rw, req) | 	session, err := p.getAuthenticatedSession(rw, req) | ||||||
|  | @ -680,7 +688,11 @@ func (p *OAuthProxy) UserInfo(rw http.ResponseWriter, req *http.Request) { | ||||||
| 	} | 	} | ||||||
| 	userInfo := struct { | 	userInfo := struct { | ||||||
| 		Email             string `json:"email"` | 		Email             string `json:"email"` | ||||||
| 	}{session.Email} | 		PreferredUsername string `json:"preferredUsername,omitempty"` | ||||||
|  | 	}{ | ||||||
|  | 		Email:             session.Email, | ||||||
|  | 		PreferredUsername: session.PreferredUsername, | ||||||
|  | 	} | ||||||
| 	rw.Header().Set("Content-Type", "application/json") | 	rw.Header().Set("Content-Type", "application/json") | ||||||
| 	rw.WriteHeader(http.StatusOK) | 	rw.WriteHeader(http.StatusOK) | ||||||
| 	json.NewEncoder(rw).Encode(userInfo) | 	json.NewEncoder(rw).Encode(userInfo) | ||||||
|  | @ -939,6 +951,11 @@ func (p *OAuthProxy) addHeadersForProxying(rw http.ResponseWriter, req *http.Req | ||||||
| 				req.Header.Del("X-Forwarded-Email") | 				req.Header.Del("X-Forwarded-Email") | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  | 		if session.PreferredUsername != "" { | ||||||
|  | 			req.Header["X-Forwarded-Preferred-Username"] = []string{session.PreferredUsername} | ||||||
|  | 		} else { | ||||||
|  | 			req.Header.Del("X-Forwarded-Preferred-Username") | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if p.PassUserHeaders { | 	if p.PassUserHeaders { | ||||||
|  | @ -948,6 +965,11 @@ func (p *OAuthProxy) addHeadersForProxying(rw http.ResponseWriter, req *http.Req | ||||||
| 		} else { | 		} else { | ||||||
| 			req.Header.Del("X-Forwarded-Email") | 			req.Header.Del("X-Forwarded-Email") | ||||||
| 		} | 		} | ||||||
|  | 		if session.PreferredUsername != "" { | ||||||
|  | 			req.Header["X-Forwarded-Preferred-Username"] = []string{session.PreferredUsername} | ||||||
|  | 		} else { | ||||||
|  | 			req.Header.Del("X-Forwarded-Preferred-Username") | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if p.SetXAuthRequest { | 	if p.SetXAuthRequest { | ||||||
|  | @ -957,6 +979,11 @@ func (p *OAuthProxy) addHeadersForProxying(rw http.ResponseWriter, req *http.Req | ||||||
| 		} else { | 		} else { | ||||||
| 			rw.Header().Del("X-Auth-Request-Email") | 			rw.Header().Del("X-Auth-Request-Email") | ||||||
| 		} | 		} | ||||||
|  | 		if session.PreferredUsername != "" { | ||||||
|  | 			rw.Header().Set("X-Auth-Request-Preferred-Username", session.PreferredUsername) | ||||||
|  | 		} else { | ||||||
|  | 			rw.Header().Del("X-Auth-Request-Preferred-Username") | ||||||
|  | 		} | ||||||
| 
 | 
 | ||||||
| 		if p.PassAccessToken { | 		if p.PassAccessToken { | ||||||
| 			if session.AccessToken != "" { | 			if session.AccessToken != "" { | ||||||
|  | @ -1069,6 +1096,7 @@ func (p *OAuthProxy) GetJwtSession(req *http.Request) (*sessionsapi.SessionState | ||||||
| 			Subject           string `json:"sub"` | 			Subject           string `json:"sub"` | ||||||
| 			Email             string `json:"email"` | 			Email             string `json:"email"` | ||||||
| 			Verified          *bool  `json:"email_verified"` | 			Verified          *bool  `json:"email_verified"` | ||||||
|  | 			PreferredUsername string `json:"preferred_username"` | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		if err := bearerToken.Claims(&claims); err != nil { | 		if err := bearerToken.Claims(&claims); err != nil { | ||||||
|  | @ -1090,6 +1118,7 @@ func (p *OAuthProxy) GetJwtSession(req *http.Request) (*sessionsapi.SessionState | ||||||
| 			ExpiresOn:         bearerToken.Expiry, | 			ExpiresOn:         bearerToken.Expiry, | ||||||
| 			Email:             claims.Email, | 			Email:             claims.Email, | ||||||
| 			User:              claims.Email, | 			User:              claims.Email, | ||||||
|  | 			PreferredUsername: claims.PreferredUsername, | ||||||
| 		} | 		} | ||||||
| 		return session, nil | 		return session, nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -19,6 +19,7 @@ type SessionState struct { | ||||||
| 	RefreshToken      string    `json:",omitempty"` | 	RefreshToken      string    `json:",omitempty"` | ||||||
| 	Email             string    `json:",omitempty"` | 	Email             string    `json:",omitempty"` | ||||||
| 	User              string    `json:",omitempty"` | 	User              string    `json:",omitempty"` | ||||||
|  | 	PreferredUsername string    `json:",omitempty"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // SessionStateJSON is used to encode SessionState into JSON without exposing time.Time zero value
 | // SessionStateJSON is used to encode SessionState into JSON without exposing time.Time zero value
 | ||||||
|  | @ -46,7 +47,7 @@ func (s *SessionState) Age() time.Duration { | ||||||
| 
 | 
 | ||||||
| // String constructs a summary of the session state
 | // String constructs a summary of the session state
 | ||||||
| func (s *SessionState) String() string { | func (s *SessionState) String() string { | ||||||
| 	o := fmt.Sprintf("Session{email:%s user:%s", s.Email, s.User) | 	o := fmt.Sprintf("Session{email:%s user:%s PreferredUsername:%s", s.Email, s.User, s.PreferredUsername) | ||||||
| 	if s.AccessToken != "" { | 	if s.AccessToken != "" { | ||||||
| 		o += " token:true" | 		o += " token:true" | ||||||
| 	} | 	} | ||||||
|  | @ -72,6 +73,7 @@ func (s *SessionState) EncodeSessionState(c *encryption.Cipher) (string, error) | ||||||
| 		// Store only Email and User when cipher is unavailable
 | 		// Store only Email and User when cipher is unavailable
 | ||||||
| 		ss.Email = s.Email | 		ss.Email = s.Email | ||||||
| 		ss.User = s.User | 		ss.User = s.User | ||||||
|  | 		ss.PreferredUsername = s.PreferredUsername | ||||||
| 	} else { | 	} else { | ||||||
| 		ss = *s | 		ss = *s | ||||||
| 		var err error | 		var err error | ||||||
|  | @ -87,6 +89,12 @@ func (s *SessionState) EncodeSessionState(c *encryption.Cipher) (string, error) | ||||||
| 				return "", err | 				return "", err | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  | 		if ss.PreferredUsername != "" { | ||||||
|  | 			ss.PreferredUsername, err = c.Encrypt(ss.PreferredUsername) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return "", err | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
| 		if ss.AccessToken != "" { | 		if ss.AccessToken != "" { | ||||||
| 			ss.AccessToken, err = c.Encrypt(ss.AccessToken) | 			ss.AccessToken, err = c.Encrypt(ss.AccessToken) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
|  | @ -201,6 +209,7 @@ func DecodeSessionState(v string, c *encryption.Cipher) (*SessionState, error) { | ||||||
| 		ss = &SessionState{ | 		ss = &SessionState{ | ||||||
| 			Email:             ss.Email, | 			Email:             ss.Email, | ||||||
| 			User:              ss.User, | 			User:              ss.User, | ||||||
|  | 			PreferredUsername: ss.PreferredUsername, | ||||||
| 		} | 		} | ||||||
| 	} else { | 	} else { | ||||||
| 		// Backward compatibility with using unencrypted Email
 | 		// Backward compatibility with using unencrypted Email
 | ||||||
|  | @ -217,6 +226,12 @@ func DecodeSessionState(v string, c *encryption.Cipher) (*SessionState, error) { | ||||||
| 				ss.User = decryptedUser | 				ss.User = decryptedUser | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  | 		if ss.PreferredUsername != "" { | ||||||
|  | 			ss.PreferredUsername, err = c.Decrypt(ss.PreferredUsername) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return nil, err | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
| 		if ss.AccessToken != "" { | 		if ss.AccessToken != "" { | ||||||
| 			ss.AccessToken, err = c.Decrypt(ss.AccessToken) | 			ss.AccessToken, err = c.Decrypt(ss.AccessToken) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
|  |  | ||||||
|  | @ -20,6 +20,7 @@ func TestSessionStateSerialization(t *testing.T) { | ||||||
| 	assert.Equal(t, nil, err) | 	assert.Equal(t, nil, err) | ||||||
| 	s := &sessions.SessionState{ | 	s := &sessions.SessionState{ | ||||||
| 		Email:             "user@domain.com", | 		Email:             "user@domain.com", | ||||||
|  | 		PreferredUsername: "user", | ||||||
| 		AccessToken:       "token1234", | 		AccessToken:       "token1234", | ||||||
| 		IDToken:           "rawtoken1234", | 		IDToken:           "rawtoken1234", | ||||||
| 		CreatedAt:         time.Now(), | 		CreatedAt:         time.Now(), | ||||||
|  | @ -34,6 +35,7 @@ func TestSessionStateSerialization(t *testing.T) { | ||||||
| 	assert.Equal(t, nil, err) | 	assert.Equal(t, nil, err) | ||||||
| 	assert.Equal(t, "user@domain.com", ss.User) | 	assert.Equal(t, "user@domain.com", ss.User) | ||||||
| 	assert.Equal(t, s.Email, ss.Email) | 	assert.Equal(t, s.Email, ss.Email) | ||||||
|  | 	assert.Equal(t, s.PreferredUsername, ss.PreferredUsername) | ||||||
| 	assert.Equal(t, s.AccessToken, ss.AccessToken) | 	assert.Equal(t, s.AccessToken, ss.AccessToken) | ||||||
| 	assert.Equal(t, s.IDToken, ss.IDToken) | 	assert.Equal(t, s.IDToken, ss.IDToken) | ||||||
| 	assert.Equal(t, s.CreatedAt.Unix(), ss.CreatedAt.Unix()) | 	assert.Equal(t, s.CreatedAt.Unix(), ss.CreatedAt.Unix()) | ||||||
|  | @ -46,6 +48,7 @@ func TestSessionStateSerialization(t *testing.T) { | ||||||
| 	assert.Equal(t, nil, err) | 	assert.Equal(t, nil, err) | ||||||
| 	assert.NotEqual(t, "user@domain.com", ss.User) | 	assert.NotEqual(t, "user@domain.com", ss.User) | ||||||
| 	assert.NotEqual(t, s.Email, ss.Email) | 	assert.NotEqual(t, s.Email, ss.Email) | ||||||
|  | 	assert.NotEqual(t, s.PreferredUsername, ss.PreferredUsername) | ||||||
| 	assert.Equal(t, s.CreatedAt.Unix(), ss.CreatedAt.Unix()) | 	assert.Equal(t, s.CreatedAt.Unix(), ss.CreatedAt.Unix()) | ||||||
| 	assert.Equal(t, s.ExpiresOn.Unix(), ss.ExpiresOn.Unix()) | 	assert.Equal(t, s.ExpiresOn.Unix(), ss.ExpiresOn.Unix()) | ||||||
| 	assert.NotEqual(t, s.AccessToken, ss.AccessToken) | 	assert.NotEqual(t, s.AccessToken, ss.AccessToken) | ||||||
|  | @ -60,6 +63,7 @@ func TestSessionStateSerializationWithUser(t *testing.T) { | ||||||
| 	assert.Equal(t, nil, err) | 	assert.Equal(t, nil, err) | ||||||
| 	s := &sessions.SessionState{ | 	s := &sessions.SessionState{ | ||||||
| 		User:              "just-user", | 		User:              "just-user", | ||||||
|  | 		PreferredUsername: "ju", | ||||||
| 		Email:             "user@domain.com", | 		Email:             "user@domain.com", | ||||||
| 		AccessToken:       "token1234", | 		AccessToken:       "token1234", | ||||||
| 		CreatedAt:         time.Now(), | 		CreatedAt:         time.Now(), | ||||||
|  | @ -74,6 +78,7 @@ func TestSessionStateSerializationWithUser(t *testing.T) { | ||||||
| 	assert.Equal(t, nil, err) | 	assert.Equal(t, nil, err) | ||||||
| 	assert.Equal(t, s.User, ss.User) | 	assert.Equal(t, s.User, ss.User) | ||||||
| 	assert.Equal(t, s.Email, ss.Email) | 	assert.Equal(t, s.Email, ss.Email) | ||||||
|  | 	assert.Equal(t, s.PreferredUsername, ss.PreferredUsername) | ||||||
| 	assert.Equal(t, s.AccessToken, ss.AccessToken) | 	assert.Equal(t, s.AccessToken, ss.AccessToken) | ||||||
| 	assert.Equal(t, s.CreatedAt.Unix(), ss.CreatedAt.Unix()) | 	assert.Equal(t, s.CreatedAt.Unix(), ss.CreatedAt.Unix()) | ||||||
| 	assert.Equal(t, s.ExpiresOn.Unix(), ss.ExpiresOn.Unix()) | 	assert.Equal(t, s.ExpiresOn.Unix(), ss.ExpiresOn.Unix()) | ||||||
|  | @ -85,6 +90,7 @@ func TestSessionStateSerializationWithUser(t *testing.T) { | ||||||
| 	assert.Equal(t, nil, err) | 	assert.Equal(t, nil, err) | ||||||
| 	assert.NotEqual(t, s.User, ss.User) | 	assert.NotEqual(t, s.User, ss.User) | ||||||
| 	assert.NotEqual(t, s.Email, ss.Email) | 	assert.NotEqual(t, s.Email, ss.Email) | ||||||
|  | 	assert.NotEqual(t, s.PreferredUsername, ss.PreferredUsername) | ||||||
| 	assert.Equal(t, s.CreatedAt.Unix(), ss.CreatedAt.Unix()) | 	assert.Equal(t, s.CreatedAt.Unix(), ss.CreatedAt.Unix()) | ||||||
| 	assert.Equal(t, s.ExpiresOn.Unix(), ss.ExpiresOn.Unix()) | 	assert.Equal(t, s.ExpiresOn.Unix(), ss.ExpiresOn.Unix()) | ||||||
| 	assert.NotEqual(t, s.AccessToken, ss.AccessToken) | 	assert.NotEqual(t, s.AccessToken, ss.AccessToken) | ||||||
|  | @ -94,6 +100,7 @@ func TestSessionStateSerializationWithUser(t *testing.T) { | ||||||
| func TestSessionStateSerializationNoCipher(t *testing.T) { | func TestSessionStateSerializationNoCipher(t *testing.T) { | ||||||
| 	s := &sessions.SessionState{ | 	s := &sessions.SessionState{ | ||||||
| 		Email:             "user@domain.com", | 		Email:             "user@domain.com", | ||||||
|  | 		PreferredUsername: "user", | ||||||
| 		AccessToken:       "token1234", | 		AccessToken:       "token1234", | ||||||
| 		CreatedAt:         time.Now(), | 		CreatedAt:         time.Now(), | ||||||
| 		ExpiresOn:         time.Now().Add(time.Duration(1) * time.Hour), | 		ExpiresOn:         time.Now().Add(time.Duration(1) * time.Hour), | ||||||
|  | @ -107,6 +114,7 @@ func TestSessionStateSerializationNoCipher(t *testing.T) { | ||||||
| 	assert.Equal(t, nil, err) | 	assert.Equal(t, nil, err) | ||||||
| 	assert.Equal(t, "user@domain.com", ss.User) | 	assert.Equal(t, "user@domain.com", ss.User) | ||||||
| 	assert.Equal(t, s.Email, ss.Email) | 	assert.Equal(t, s.Email, ss.Email) | ||||||
|  | 	assert.Equal(t, s.PreferredUsername, ss.PreferredUsername) | ||||||
| 	assert.Equal(t, "", ss.AccessToken) | 	assert.Equal(t, "", ss.AccessToken) | ||||||
| 	assert.Equal(t, "", ss.RefreshToken) | 	assert.Equal(t, "", ss.RefreshToken) | ||||||
| } | } | ||||||
|  | @ -115,6 +123,7 @@ func TestSessionStateSerializationNoCipherWithUser(t *testing.T) { | ||||||
| 	s := &sessions.SessionState{ | 	s := &sessions.SessionState{ | ||||||
| 		User:              "just-user", | 		User:              "just-user", | ||||||
| 		Email:             "user@domain.com", | 		Email:             "user@domain.com", | ||||||
|  | 		PreferredUsername: "user", | ||||||
| 		AccessToken:       "token1234", | 		AccessToken:       "token1234", | ||||||
| 		CreatedAt:         time.Now(), | 		CreatedAt:         time.Now(), | ||||||
| 		ExpiresOn:         time.Now().Add(time.Duration(1) * time.Hour), | 		ExpiresOn:         time.Now().Add(time.Duration(1) * time.Hour), | ||||||
|  | @ -128,6 +137,7 @@ func TestSessionStateSerializationNoCipherWithUser(t *testing.T) { | ||||||
| 	assert.Equal(t, nil, err) | 	assert.Equal(t, nil, err) | ||||||
| 	assert.Equal(t, s.User, ss.User) | 	assert.Equal(t, s.User, ss.User) | ||||||
| 	assert.Equal(t, s.Email, ss.Email) | 	assert.Equal(t, s.Email, ss.Email) | ||||||
|  | 	assert.Equal(t, s.PreferredUsername, ss.PreferredUsername) | ||||||
| 	assert.Equal(t, "", ss.AccessToken) | 	assert.Equal(t, "", ss.AccessToken) | ||||||
| 	assert.Equal(t, "", ss.RefreshToken) | 	assert.Equal(t, "", ss.RefreshToken) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -121,6 +121,7 @@ func (p *OIDCProvider) redeemRefreshToken(s *sessions.SessionState) (err error) | ||||||
| 		s.IDToken = newSession.IDToken | 		s.IDToken = newSession.IDToken | ||||||
| 		s.Email = newSession.Email | 		s.Email = newSession.Email | ||||||
| 		s.User = newSession.User | 		s.User = newSession.User | ||||||
|  | 		s.PreferredUsername = newSession.PreferredUsername | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	s.AccessToken = newSession.AccessToken | 	s.AccessToken = newSession.AccessToken | ||||||
|  | @ -165,6 +166,7 @@ func (p *OIDCProvider) createSessionState(token *oauth2.Token, idToken *oidc.IDT | ||||||
| 			newSession.IDToken = token.Extra("id_token").(string) | 			newSession.IDToken = token.Extra("id_token").(string) | ||||||
| 			newSession.Email = claims.Email | 			newSession.Email = claims.Email | ||||||
| 			newSession.User = claims.Subject | 			newSession.User = claims.Subject | ||||||
|  | 			newSession.PreferredUsername = claims.PreferredUsername | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -236,4 +238,5 @@ type OIDCClaims struct { | ||||||
| 	Subject           string `json:"sub"` | 	Subject           string `json:"sub"` | ||||||
| 	Email             string `json:"email"` | 	Email             string `json:"email"` | ||||||
| 	Verified          *bool  `json:"email_verified"` | 	Verified          *bool  `json:"email_verified"` | ||||||
|  | 	PreferredUsername string `json:"preferred_username"` | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -119,6 +119,11 @@ func (p *ProviderData) GetUserName(s *sessions.SessionState) (string, error) { | ||||||
| 	return "", errors.New("not implemented") | 	return "", errors.New("not implemented") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // GetPreferredUsername returns the Account preferred username
 | ||||||
|  | func (p *ProviderData) GetPreferredUsername(s *sessions.SessionState) (string, error) { | ||||||
|  | 	return "", errors.New("not implemented") | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // ValidateGroup validates that the provided email exists in the configured provider
 | // ValidateGroup validates that the provided email exists in the configured provider
 | ||||||
| // email group(s).
 | // email group(s).
 | ||||||
| func (p *ProviderData) ValidateGroup(email string) bool { | func (p *ProviderData) ValidateGroup(email string) bool { | ||||||
|  |  | ||||||
|  | @ -10,6 +10,7 @@ type Provider interface { | ||||||
| 	Data() *ProviderData | 	Data() *ProviderData | ||||||
| 	GetEmailAddress(*sessions.SessionState) (string, error) | 	GetEmailAddress(*sessions.SessionState) (string, error) | ||||||
| 	GetUserName(*sessions.SessionState) (string, error) | 	GetUserName(*sessions.SessionState) (string, error) | ||||||
|  | 	GetPreferredUsername(*sessions.SessionState) (string, error) | ||||||
| 	Redeem(string, string) (*sessions.SessionState, error) | 	Redeem(string, string) (*sessions.SessionState, error) | ||||||
| 	ValidateGroup(string) bool | 	ValidateGroup(string) bool | ||||||
| 	ValidateSessionState(*sessions.SessionState) bool | 	ValidateSessionState(*sessions.SessionState) bool | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue