{server,storage}: add LoggedIn flag to AuthRequest and improve storage docs
Currently, whether or not a user has authenticated themselves through a connector is indicated by a pointer being nil or non-nil. Instead add an explicit flag that marks this.
This commit is contained in:
		| @@ -264,7 +264,8 @@ func (s *Server) finalizeLogin(identity connector.Identity, authReqID, connector | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	updater := func(a storage.AuthRequest) (storage.AuthRequest, error) { | 	updater := func(a storage.AuthRequest) (storage.AuthRequest, error) { | ||||||
| 		a.Claims = &claims | 		a.LoggedIn = true | ||||||
|  | 		a.Claims = claims | ||||||
| 		a.ConnectorID = connectorID | 		a.ConnectorID = connectorID | ||||||
| 		a.ConnectorData = identity.ConnectorData | 		a.ConnectorData = identity.ConnectorData | ||||||
| 		return a, nil | 		return a, nil | ||||||
| @@ -282,7 +283,7 @@ func (s *Server) handleApproval(w http.ResponseWriter, r *http.Request) { | |||||||
| 		s.renderError(w, http.StatusInternalServerError, errServerError, "") | 		s.renderError(w, http.StatusInternalServerError, errServerError, "") | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	if authReq.Claims == nil { | 	if !authReq.LoggedIn { | ||||||
| 		log.Printf("Auth request does not have an identity for approval") | 		log.Printf("Auth request does not have an identity for approval") | ||||||
| 		s.renderError(w, http.StatusInternalServerError, errServerError, "") | 		s.renderError(w, http.StatusInternalServerError, errServerError, "") | ||||||
| 		return | 		return | ||||||
| @@ -341,7 +342,7 @@ func (s *Server) sendCodeResponse(w http.ResponseWriter, r *http.Request, authRe | |||||||
| 				ConnectorID: authReq.ConnectorID, | 				ConnectorID: authReq.ConnectorID, | ||||||
| 				Nonce:       authReq.Nonce, | 				Nonce:       authReq.Nonce, | ||||||
| 				Scopes:      authReq.Scopes, | 				Scopes:      authReq.Scopes, | ||||||
| 				Claims:      *authReq.Claims, | 				Claims:      authReq.Claims, | ||||||
| 				Expiry:      s.now().Add(time.Minute * 5), | 				Expiry:      s.now().Add(time.Minute * 5), | ||||||
| 				RedirectURI: authReq.RedirectURI, | 				RedirectURI: authReq.RedirectURI, | ||||||
| 			} | 			} | ||||||
| @@ -358,7 +359,7 @@ func (s *Server) sendCodeResponse(w http.ResponseWriter, r *http.Request, authRe | |||||||
| 			} | 			} | ||||||
| 			q.Set("code", code.ID) | 			q.Set("code", code.ID) | ||||||
| 		case responseTypeToken: | 		case responseTypeToken: | ||||||
| 			idToken, expiry, err := s.newIDToken(authReq.ClientID, *authReq.Claims, authReq.Scopes, authReq.Nonce) | 			idToken, expiry, err := s.newIDToken(authReq.ClientID, authReq.Claims, authReq.Scopes, authReq.Nonce) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				log.Printf("failed to create ID token: %v", err) | 				log.Printf("failed to create ID token: %v", err) | ||||||
| 				tokenErr(w, errServerError, "", http.StatusInternalServerError) | 				tokenErr(w, errServerError, "", http.StatusInternalServerError) | ||||||
|   | |||||||
| @@ -118,9 +118,11 @@ type AuthRequest struct { | |||||||
| 	// attempts. | 	// attempts. | ||||||
| 	ForceApprovalPrompt bool `json:"forceApprovalPrompt,omitempty"` | 	ForceApprovalPrompt bool `json:"forceApprovalPrompt,omitempty"` | ||||||
|  |  | ||||||
|  | 	LoggedIn bool `json:"loggedIn"` | ||||||
|  |  | ||||||
| 	// The identity of the end user. Generally nil until the user authenticates | 	// The identity of the end user. Generally nil until the user authenticates | ||||||
| 	// with a backend. | 	// with a backend. | ||||||
| 	Claims *Claims `json:"claims,omitempty"` | 	Claims Claims `json:"claims,omitempty"` | ||||||
| 	// The connector used to login the user. Set when the user authenticates. | 	// 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"` | 	ConnectorData []byte `json:"connectorData,omitempty"` | ||||||
| @@ -145,13 +147,11 @@ func toStorageAuthRequest(req AuthRequest) storage.AuthRequest { | |||||||
| 		Nonce:               req.Nonce, | 		Nonce:               req.Nonce, | ||||||
| 		State:               req.State, | 		State:               req.State, | ||||||
| 		ForceApprovalPrompt: req.ForceApprovalPrompt, | 		ForceApprovalPrompt: req.ForceApprovalPrompt, | ||||||
|  | 		LoggedIn:            req.LoggedIn, | ||||||
| 		ConnectorID:         req.ConnectorID, | 		ConnectorID:         req.ConnectorID, | ||||||
| 		ConnectorData:       req.ConnectorData, | 		ConnectorData:       req.ConnectorData, | ||||||
| 		Expiry:              req.Expiry, | 		Expiry:              req.Expiry, | ||||||
| 	} | 		Claims:              toStorageClaims(req.Claims), | ||||||
| 	if req.Claims != nil { |  | ||||||
| 		i := toStorageClaims(*req.Claims) |  | ||||||
| 		a.Claims = &i |  | ||||||
| 	} | 	} | ||||||
| 	return a | 	return a | ||||||
| } | } | ||||||
| @@ -172,14 +172,12 @@ func (cli *client) fromStorageAuthRequest(a storage.AuthRequest) AuthRequest { | |||||||
| 		RedirectURI:         a.RedirectURI, | 		RedirectURI:         a.RedirectURI, | ||||||
| 		Nonce:               a.Nonce, | 		Nonce:               a.Nonce, | ||||||
| 		State:               a.State, | 		State:               a.State, | ||||||
|  | 		LoggedIn:            a.LoggedIn, | ||||||
| 		ForceApprovalPrompt: a.ForceApprovalPrompt, | 		ForceApprovalPrompt: a.ForceApprovalPrompt, | ||||||
| 		ConnectorID:         a.ConnectorID, | 		ConnectorID:         a.ConnectorID, | ||||||
| 		ConnectorData:       a.ConnectorData, | 		ConnectorData:       a.ConnectorData, | ||||||
| 		Expiry:              a.Expiry, | 		Expiry:              a.Expiry, | ||||||
| 	} | 		Claims:              fromStorageClaims(a.Claims), | ||||||
| 	if a.Claims != nil { |  | ||||||
| 		i := fromStorageClaims(*a.Claims) |  | ||||||
| 		req.Claims = &i |  | ||||||
| 	} | 	} | ||||||
| 	return req | 	return req | ||||||
| } | } | ||||||
|   | |||||||
| @@ -70,28 +70,41 @@ type Storage interface { | |||||||
| 	DeleteRefresh(id string) error | 	DeleteRefresh(id string) error | ||||||
|  |  | ||||||
| 	// Update functions are assumed to be a performed within a single object transaction. | 	// Update functions are assumed to be a performed within a single object transaction. | ||||||
|  | 	// | ||||||
|  | 	// updaters may be called multiple times. | ||||||
| 	UpdateClient(id string, updater func(old Client) (Client, error)) error | 	UpdateClient(id string, updater func(old Client) (Client, error)) error | ||||||
| 	UpdateKeys(updater func(old Keys) (Keys, error)) error | 	UpdateKeys(updater func(old Keys) (Keys, error)) error | ||||||
| 	UpdateAuthRequest(id string, updater func(a AuthRequest) (AuthRequest, error)) error | 	UpdateAuthRequest(id string, updater func(a AuthRequest) (AuthRequest, error)) error | ||||||
|  |  | ||||||
|  | 	// TODO(ericchiang): Add a GarbageCollect(now time.Time) method so conformance tests | ||||||
|  | 	// can test implementations. | ||||||
| } | } | ||||||
|  |  | ||||||
| // Client is an OAuth2 client. | // Client represents an OAuth2 client. | ||||||
| // | // | ||||||
| // For further reading see: | // For further reading see: | ||||||
| //   * Trusted peers: https://developers.google.com/identity/protocols/CrossClientAuth | //   * Trusted peers: https://developers.google.com/identity/protocols/CrossClientAuth | ||||||
| //   * Public clients: https://developers.google.com/api-client-library/python/auth/installed-app | //   * Public clients: https://developers.google.com/api-client-library/python/auth/installed-app | ||||||
| type Client struct { | type Client struct { | ||||||
|  | 	// Client ID and secret used to identify the client. | ||||||
| 	ID     string `json:"id" yaml:"id"` | 	ID     string `json:"id" yaml:"id"` | ||||||
| 	Secret string `json:"secret" yaml:"secret"` | 	Secret string `json:"secret" yaml:"secret"` | ||||||
|  |  | ||||||
|  | 	// A registered set of redirect URIs. When redirecting from dex to the client, the URI | ||||||
|  | 	// requested to redirect to MUST match one of these values, unless the client is "public". | ||||||
| 	RedirectURIs []string `json:"redirectURIs" yaml:"redirectURIs"` | 	RedirectURIs []string `json:"redirectURIs" yaml:"redirectURIs"` | ||||||
|  |  | ||||||
| 	// TrustedPeers are a list of peers which can issue tokens on this client's behalf. | 	// TrustedPeers are a list of peers which can issue tokens on this client's behalf using | ||||||
|  | 	// the dynamic "oauth2:server:client_id:(client_id)" scope. If a peer makes such a request, | ||||||
|  | 	// this client's ID will appear as the ID Token's audience. | ||||||
|  | 	// | ||||||
| 	// Clients inherently trust themselves. | 	// Clients inherently trust themselves. | ||||||
| 	TrustedPeers []string `json:"trustedPeers" yaml:"trustedPeers"` | 	TrustedPeers []string `json:"trustedPeers" yaml:"trustedPeers"` | ||||||
|  |  | ||||||
| 	// Public clients must use either use a redirectURL 127.0.0.1:X or "urn:ietf:wg:oauth:2.0:oob" | 	// Public clients must use either use a redirectURL 127.0.0.1:X or "urn:ietf:wg:oauth:2.0:oob" | ||||||
| 	Public bool `json:"public" yaml:"public"` | 	Public bool `json:"public" yaml:"public"` | ||||||
|  |  | ||||||
|  | 	// Name and LogoURL used when displaying this client to the end user. | ||||||
| 	Name    string `json:"name" yaml:"name"` | 	Name    string `json:"name" yaml:"name"` | ||||||
| 	LogoURL string `json:"logoURL" yaml:"logoURL"` | 	LogoURL string `json:"logoURL" yaml:"logoURL"` | ||||||
| } | } | ||||||
| @@ -109,13 +122,17 @@ type Claims struct { | |||||||
| // AuthRequest represents a OAuth2 client authorization request. It holds the state | // AuthRequest represents a OAuth2 client authorization request. It holds the state | ||||||
| // of a single auth flow up to the point that the user authorizes the client. | // of a single auth flow up to the point that the user authorizes the client. | ||||||
| type AuthRequest struct { | type AuthRequest struct { | ||||||
|  | 	// ID used to identify the authorization request. | ||||||
| 	ID string | 	ID string | ||||||
|  |  | ||||||
|  | 	// ID of the client requesting authorization from a user. | ||||||
| 	ClientID string | 	ClientID string | ||||||
|  |  | ||||||
|  | 	// Values parsed from the initial request. These describe the resources the client is | ||||||
|  | 	// requesting as well as values describing the form of the response. | ||||||
| 	ResponseTypes []string | 	ResponseTypes []string | ||||||
| 	Scopes        []string | 	Scopes        []string | ||||||
| 	RedirectURI   string | 	RedirectURI   string | ||||||
|  |  | ||||||
| 	Nonce         string | 	Nonce         string | ||||||
| 	State         string | 	State         string | ||||||
|  |  | ||||||
| @@ -124,38 +141,60 @@ type AuthRequest struct { | |||||||
| 	// attempts. | 	// attempts. | ||||||
| 	ForceApprovalPrompt bool | 	ForceApprovalPrompt bool | ||||||
|  |  | ||||||
|  | 	Expiry time.Time | ||||||
|  |  | ||||||
|  | 	// Has the user proved their identity through a backing identity provider? | ||||||
|  | 	// | ||||||
|  | 	// If false, the following fields are invalid. | ||||||
|  | 	LoggedIn bool | ||||||
|  |  | ||||||
| 	// The identity of the end user. Generally nil until the user authenticates | 	// The identity of the end user. Generally nil until the user authenticates | ||||||
| 	// with a backend. | 	// with a backend. | ||||||
| 	Claims *Claims | 	Claims Claims | ||||||
|  |  | ||||||
| 	// The connector used to login the user and any data the connector wishes to persists. | 	// The connector used to login the user and any data the connector wishes to persists. | ||||||
| 	// Set when the user authenticates. | 	// Set when the user authenticates. | ||||||
| 	ConnectorID   string | 	ConnectorID   string | ||||||
| 	ConnectorData []byte | 	ConnectorData []byte | ||||||
|  |  | ||||||
| 	Expiry time.Time |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // AuthCode represents a code which can be exchanged for an OAuth2 token response. | // AuthCode represents a code which can be exchanged for an OAuth2 token response. | ||||||
|  | // | ||||||
|  | // This value is created once an end user has authorized a client, the server has | ||||||
|  | // redirect the end user back to the client, but the client hasn't exchanged the | ||||||
|  | // code for an access_token and id_token. | ||||||
| type AuthCode struct { | type AuthCode struct { | ||||||
|  | 	// Actual string returned as the "code" value. | ||||||
| 	ID string | 	ID string | ||||||
|  |  | ||||||
|  | 	// The client this code value is valid for. When exchanging the code for a | ||||||
|  | 	// token response, the client must use its client_secret to authenticate. | ||||||
| 	ClientID string | 	ClientID string | ||||||
|  |  | ||||||
|  | 	// As part of the OAuth2 spec when a client makes a token request it MUST | ||||||
|  | 	// present the same redirect_uri as the initial redirect. This values is saved | ||||||
|  | 	// to make this check. | ||||||
|  | 	// | ||||||
|  | 	// https://tools.ietf.org/html/rfc6749#section-4.1.3 | ||||||
| 	RedirectURI string | 	RedirectURI string | ||||||
|  |  | ||||||
| 	ConnectorID   string | 	// If provided by the client in the initial request, the provider MUST create | ||||||
| 	ConnectorData []byte | 	// a ID Token with this nonce in the JWT payload. | ||||||
|  |  | ||||||
| 	Nonce string | 	Nonce string | ||||||
|  |  | ||||||
|  | 	// Scopes authorized by the end user for the client. | ||||||
| 	Scopes []string | 	Scopes []string | ||||||
|  |  | ||||||
|  | 	// Authentication data provided by an upstream source. | ||||||
|  | 	ConnectorID   string | ||||||
|  | 	ConnectorData []byte | ||||||
| 	Claims        Claims | 	Claims        Claims | ||||||
|  |  | ||||||
| 	Expiry time.Time | 	Expiry time.Time | ||||||
| } | } | ||||||
|  |  | ||||||
| // RefreshToken is an OAuth2 refresh token. | // RefreshToken is an OAuth2 refresh token which allows a client to request new | ||||||
|  | // tokens on the end user's behalf. | ||||||
| type RefreshToken struct { | type RefreshToken struct { | ||||||
| 	// The actual refresh token. | 	// The actual refresh token. | ||||||
| 	RefreshToken string | 	RefreshToken string | ||||||
| @@ -163,17 +202,19 @@ type RefreshToken struct { | |||||||
| 	// Client this refresh token is valid for. | 	// Client this refresh token is valid for. | ||||||
| 	ClientID string | 	ClientID string | ||||||
|  |  | ||||||
|  | 	// Authentication data provided by an upstream source. | ||||||
| 	ConnectorID   string | 	ConnectorID   string | ||||||
| 	ConnectorData []byte | 	ConnectorData []byte | ||||||
|  | 	Claims        Claims | ||||||
|  |  | ||||||
| 	// Scopes present in the initial request. Refresh requests may specify a set | 	// Scopes present in the initial request. Refresh requests may specify a set | ||||||
| 	// of scopes different from the initial request when refreshing a token, | 	// of scopes different from the initial request when refreshing a token, | ||||||
| 	// however those scopes must be encompassed by this set. | 	// however those scopes must be encompassed by this set. | ||||||
| 	Scopes []string | 	Scopes []string | ||||||
|  |  | ||||||
|  | 	// Nonce value supplied during the initial redirect. This is required to be part | ||||||
|  | 	// of the claims of any future id_token generated by the client. | ||||||
| 	Nonce string | 	Nonce string | ||||||
|  |  | ||||||
| 	Claims Claims |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // VerificationKey is a rotated signing key which can still be used to verify | // VerificationKey is a rotated signing key which can still be used to verify | ||||||
| @@ -188,6 +229,7 @@ type Keys struct { | |||||||
| 	// Key for creating and verifying signatures. These may be nil. | 	// Key for creating and verifying signatures. These may be nil. | ||||||
| 	SigningKey    *jose.JSONWebKey | 	SigningKey    *jose.JSONWebKey | ||||||
| 	SigningKeyPub *jose.JSONWebKey | 	SigningKeyPub *jose.JSONWebKey | ||||||
|  |  | ||||||
| 	// Old signing keys which have been rotated but can still be used to validate | 	// Old signing keys which have been rotated but can still be used to validate | ||||||
| 	// existing signatures. | 	// existing signatures. | ||||||
| 	VerificationKeys []VerificationKey | 	VerificationKeys []VerificationKey | ||||||
|   | |||||||
| @@ -35,7 +35,7 @@ func testUpdateAuthRequest(t *testing.T, s storage.Storage) { | |||||||
| 		t.Fatalf("failed creating auth request: %v", err) | 		t.Fatalf("failed creating auth request: %v", err) | ||||||
| 	} | 	} | ||||||
| 	if err := s.UpdateAuthRequest(a.ID, func(old storage.AuthRequest) (storage.AuthRequest, error) { | 	if err := s.UpdateAuthRequest(a.ID, func(old storage.AuthRequest) (storage.AuthRequest, error) { | ||||||
| 		old.Claims = &identity | 		old.Claims = identity | ||||||
| 		old.ConnectorID = "connID" | 		old.ConnectorID = "connID" | ||||||
| 		return old, nil | 		return old, nil | ||||||
| 	}); err != nil { | 	}); err != nil { | ||||||
| @@ -46,11 +46,8 @@ func testUpdateAuthRequest(t *testing.T, s storage.Storage) { | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatalf("failed to get auth req: %v", err) | 		t.Fatalf("failed to get auth req: %v", err) | ||||||
| 	} | 	} | ||||||
| 	if got.Claims == nil { | 	if !reflect.DeepEqual(got.Claims, identity) { | ||||||
| 		t.Fatalf("no identity in auth request") | 		t.Fatalf("update failed, wanted identity=%#v got %#v", identity, got.Claims) | ||||||
| 	} |  | ||||||
| 	if !reflect.DeepEqual(*got.Claims, identity) { |  | ||||||
| 		t.Fatalf("update failed, wanted identity=%#v got %#v", identity, *got.Claims) |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user