Skip to content
Snippets Groups Projects
handlers.go 40.7 KiB
Newer Older
  • Learn to ignore specific revisions
  • 			if err != nil {
    				s.tokenErrHelper(w, errInvalidClient, fmt.Sprintf("Error validating cross client trust %v.", err), http.StatusBadRequest)
    				return
    			}
    			if !isTrusted {
    				invalidScopes = append(invalidScopes, scope)
    			}
    		}
    	}
    	if !hasOpenIDScope {
    		s.tokenErrHelper(w, errInvalidRequest, `Missing required scope(s) ["openid"].`, http.StatusBadRequest)
    		return
    	}
    	if len(unrecognized) > 0 {
    		s.tokenErrHelper(w, errInvalidRequest, fmt.Sprintf("Unrecognized scope(s) %q", unrecognized), http.StatusBadRequest)
    		return
    	}
    	if len(invalidScopes) > 0 {
    		s.tokenErrHelper(w, errInvalidRequest, fmt.Sprintf("Client can't request scope(s) %q", invalidScopes), http.StatusBadRequest)
    		return
    	}
    
    	// Which connector
    	connID := s.passwordConnector
    	conn, err := s.getConnector(connID)
    	if err != nil {
    		s.tokenErrHelper(w, errInvalidRequest, "Requested connector does not exist.", http.StatusBadRequest)
    		return
    	}
    
    	passwordConnector, ok := conn.Connector.(connector.PasswordConnector)
    	if !ok {
    		s.tokenErrHelper(w, errInvalidRequest, "Requested password connector does not correct type.", http.StatusBadRequest)
    		return
    	}
    
    	// Login
    	username := q.Get("username")
    	password := q.Get("password")
    	identity, ok, err := passwordConnector.Login(r.Context(), parseScopes(scopes), username, password)
    	if err != nil {
    
    		s.logger.Errorf("Failed to login user: %v", err)
    
    		s.tokenErrHelper(w, errInvalidRequest, "Could not login user", http.StatusBadRequest)
    		return
    	}
    	if !ok {
    		s.tokenErrHelper(w, errAccessDenied, "Invalid username or password", http.StatusUnauthorized)
    		return
    	}
    
    	// Build the claims to send the id token
    	claims := storage.Claims{
    		UserID:            identity.UserID,
    		Username:          identity.Username,
    		PreferredUsername: identity.PreferredUsername,
    		Email:             identity.Email,
    		EmailVerified:     identity.EmailVerified,
    		Groups:            identity.Groups,
    	}
    
    	accessToken := storage.NewID()
    
    	idToken, expiry, err := s.newIDToken(client.ID, claims, scopes, nonce, accessToken, "", connID)
    
    	if err != nil {
    		s.tokenErrHelper(w, errServerError, fmt.Sprintf("failed to create ID token: %v", err), http.StatusInternalServerError)
    		return
    	}
    
    	reqRefresh := func() bool {
    		// Ensure the connector supports refresh tokens.
    		//
    		// Connectors like `saml` do not implement RefreshConnector.
    		_, ok := conn.Connector.(connector.RefreshConnector)
    		if !ok {
    			return false
    		}
    
    		for _, scope := range scopes {
    			if scope == scopeOfflineAccess {
    				return true
    			}
    		}
    		return false
    	}()
    	var refreshToken string
    	if reqRefresh {
    		refresh := storage.RefreshToken{
    			ID:          storage.NewID(),
    			Token:       storage.NewID(),
    
    			ClientID:    client.ID,
    
    			ConnectorID: connID,
    			Scopes:      scopes,
    			Claims:      claims,
    			Nonce:       nonce,
    			// ConnectorData: authCode.ConnectorData,
    			CreatedAt: s.now(),
    			LastUsed:  s.now(),
    		}
    		token := &internal.RefreshToken{
    			RefreshId: refresh.ID,
    			Token:     refresh.Token,
    		}
    		if refreshToken, err = internal.Marshal(token); err != nil {
    			s.logger.Errorf("failed to marshal refresh token: %v", err)
    			s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError)
    			return
    		}
    
    		if err := s.storage.CreateRefresh(refresh); err != nil {
    			s.logger.Errorf("failed to create refresh token: %v", err)
    			s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError)
    			return
    		}
    
    		// deleteToken determines if we need to delete the newly created refresh token
    		// due to a failure in updating/creating the OfflineSession object for the
    		// corresponding user.
    		var deleteToken bool
    		defer func() {
    			if deleteToken {
    				// Delete newly created refresh token from storage.
    				if err := s.storage.DeleteRefresh(refresh.ID); err != nil {
    					s.logger.Errorf("failed to delete refresh token: %v", err)
    					s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError)
    					return
    				}
    			}
    		}()
    
    		tokenRef := storage.RefreshTokenRef{
    			ID:        refresh.ID,
    			ClientID:  refresh.ClientID,
    			CreatedAt: refresh.CreatedAt,
    			LastUsed:  refresh.LastUsed,
    		}
    
    		// Try to retrieve an existing OfflineSession object for the corresponding user.
    		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)
    				s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError)
    				deleteToken = true
    				return
    			}
    			offlineSessions := storage.OfflineSessions{
    				UserID:  refresh.Claims.UserID,
    				ConnID:  refresh.ConnectorID,
    				Refresh: make(map[string]*storage.RefreshTokenRef),
    			}
    			offlineSessions.Refresh[tokenRef.ClientID] = &tokenRef
    
    			// 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)
    				s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError)
    				deleteToken = true
    				return
    			}
    		} else {
    			if oldTokenRef, ok := session.Refresh[tokenRef.ClientID]; ok {
    				// Delete old refresh token from storage.
    				if err := s.storage.DeleteRefresh(oldTokenRef.ID); err != nil {
    
    					if err == storage.ErrNotFound {
    						s.logger.Warnf("database inconsistent, refresh token missing: %v", oldTokenRef.ID)
    					} else {
    						s.logger.Errorf("failed to delete refresh token: %v", err)
    						s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError)
    						deleteToken = true
    						return
    					}
    
    				}
    			}
    
    			// Update existing OfflineSession obj with new RefreshTokenRef.
    			if err := s.storage.UpdateOfflineSessions(session.UserID, session.ConnID, func(old storage.OfflineSessions) (storage.OfflineSessions, error) {
    				old.Refresh[tokenRef.ClientID] = &tokenRef
    				return old, nil
    			}); err != nil {
    				s.logger.Errorf("failed to update offline session: %v", err)
    				s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError)
    				deleteToken = true
    				return
    			}
    		}
    	}
    
    
    	resp := s.toAccessTokenResponse(idToken, accessToken, refreshToken, expiry)
    	s.writeAccessToken(w, resp)
    
    Josh Soref's avatar
    Josh Soref committed
    type accessTokenResponse struct {
    
    	AccessToken  string `json:"access_token"`
    	TokenType    string `json:"token_type"`
    	ExpiresIn    int    `json:"expires_in"`
    	RefreshToken string `json:"refresh_token,omitempty"`
    	IDToken      string `json:"id_token"`
    }
    
    
    Josh Soref's avatar
    Josh Soref committed
    func (s *Server) toAccessTokenResponse(idToken, accessToken, refreshToken string, expiry time.Time) *accessTokenResponse {
    	return &accessTokenResponse{
    
    		accessToken,
    
    Eric Chiang's avatar
    Eric Chiang committed
    		"bearer",
    
    		int(expiry.Sub(s.now()).Seconds()),
    
    Eric Chiang's avatar
    Eric Chiang committed
    		refreshToken,
    		idToken,
    	}
    
    Josh Soref's avatar
    Josh Soref committed
    func (s *Server) writeAccessToken(w http.ResponseWriter, resp *accessTokenResponse) {
    
    Eric Chiang's avatar
    Eric Chiang committed
    	data, err := json.Marshal(resp)
    	if err != nil {
    
    		s.logger.Errorf("failed to marshal access token response: %v", err)
    		s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError)
    
    Eric Chiang's avatar
    Eric Chiang committed
    		return
    	}
    	w.Header().Set("Content-Type", "application/json")
    	w.Header().Set("Content-Length", strconv.Itoa(len(data)))
    
    
    	// Token response must include cache headers https://tools.ietf.org/html/rfc6749#section-5.1
    	w.Header().Set("Cache-Control", "no-store")
    	w.Header().Set("Pragma", "no-cache")
    
    Eric Chiang's avatar
    Eric Chiang committed
    	w.Write(data)
    }
    
    
    func (s *Server) renderError(r *http.Request, w http.ResponseWriter, status int, description string) {
    	if err := s.templates.err(r, w, status, description); err != nil {
    
    		s.logger.Errorf("Server template error: %v", err)
    	}
    
    Eric Chiang's avatar
    Eric Chiang committed
    }
    
    
    func (s *Server) tokenErrHelper(w http.ResponseWriter, typ string, description string, statusCode int) {
    	if err := tokenErr(w, typ, description, statusCode); err != nil {
    
    		s.logger.Errorf("token error response: %v", err)
    
    
    // Check for username prompt override from connector. Defaults to "Username".
    func usernamePrompt(conn connector.PasswordConnector) string {
    	if attr := conn.Prompt(); attr != "" {
    		return attr
    	}
    	return "Username"
    }