Feature/oauth userinfo (#15721)
* Implemented userinfo #8534 * Make lint happy * Add userinfo endpoint to openid-configuration * Give an error when uid equals 0 * Implemented BearerTokenErrorCode handling * instead of ctx.error use ctx.json so that clients parse error and error_description correctly * Removed unneeded if statement * Use switch instead of subsequent if statements Have a default for unknown errorcodes. Co-authored-by: Nils Hillmann <hillmann@nlh-software.de> Co-authored-by: nlhsoftware <nlhsoftware@noreply.localhost>
This commit is contained in:
		
							parent
							
								
									6a3ad0b24e
								
							
						
					
					
						commit
						45970ae82e
					
				
					 3 changed files with 75 additions and 0 deletions
				
			
		|  | @ -410,6 +410,7 @@ func RegisterRoutes(m *web.Route) { | |||
| 		// TODO manage redirection
 | ||||
| 		m.Post("/authorize", bindIgnErr(forms.AuthorizationForm{}), user.AuthorizeOAuth) | ||||
| 	}, ignSignInAndCsrf, reqSignIn) | ||||
| 	m.Get("/login/oauth/userinfo", ignSignInAndCsrf, user.InfoOAuth) | ||||
| 	if setting.CORSConfig.Enabled { | ||||
| 		m.Post("/login/oauth/access_token", cors.Handler(cors.Options{ | ||||
| 			//Scheme:           setting.CORSConfig.Scheme, // FIXME: the cors middleware needs scheme option
 | ||||
|  |  | |||
|  | @ -13,6 +13,7 @@ import ( | |||
| 	"strings" | ||||
| 
 | ||||
| 	"code.gitea.io/gitea/models" | ||||
| 	"code.gitea.io/gitea/modules/auth/sso" | ||||
| 	"code.gitea.io/gitea/modules/base" | ||||
| 	"code.gitea.io/gitea/modules/context" | ||||
| 	"code.gitea.io/gitea/modules/log" | ||||
|  | @ -93,6 +94,24 @@ func (err AccessTokenError) Error() string { | |||
| 	return fmt.Sprintf("%s: %s", err.ErrorCode, err.ErrorDescription) | ||||
| } | ||||
| 
 | ||||
| // BearerTokenErrorCode represents an error code specified in RFC 6750
 | ||||
| type BearerTokenErrorCode string | ||||
| 
 | ||||
| const ( | ||||
| 	// BearerTokenErrorCodeInvalidRequest represents an error code specified in RFC 6750
 | ||||
| 	BearerTokenErrorCodeInvalidRequest BearerTokenErrorCode = "invalid_request" | ||||
| 	// BearerTokenErrorCodeInvalidToken represents an error code specified in RFC 6750
 | ||||
| 	BearerTokenErrorCodeInvalidToken BearerTokenErrorCode = "invalid_token" | ||||
| 	// BearerTokenErrorCodeInsufficientScope represents an error code specified in RFC 6750
 | ||||
| 	BearerTokenErrorCodeInsufficientScope BearerTokenErrorCode = "insufficient_scope" | ||||
| ) | ||||
| 
 | ||||
| // BearerTokenError represents an error response specified in RFC 6750
 | ||||
| type BearerTokenError struct { | ||||
| 	ErrorCode        BearerTokenErrorCode `json:"error" form:"error"` | ||||
| 	ErrorDescription string               `json:"error_description"` | ||||
| } | ||||
| 
 | ||||
| // TokenType specifies the kind of token
 | ||||
| type TokenType string | ||||
| 
 | ||||
|  | @ -193,6 +212,45 @@ func newAccessTokenResponse(grant *models.OAuth2Grant, clientSecret string) (*Ac | |||
| 	}, nil | ||||
| } | ||||
| 
 | ||||
| type userInfoResponse struct { | ||||
| 	Sub      string `json:"sub"` | ||||
| 	Name     string `json:"name"` | ||||
| 	Username string `json:"preferred_username"` | ||||
| 	Email    string `json:"email"` | ||||
| 	Picture  string `json:"picture"` | ||||
| } | ||||
| 
 | ||||
| // InfoOAuth manages request for userinfo endpoint
 | ||||
| func InfoOAuth(ctx *context.Context) { | ||||
| 	header := ctx.Req.Header.Get("Authorization") | ||||
| 	auths := strings.Fields(header) | ||||
| 	if len(auths) != 2 || auths[0] != "Bearer" { | ||||
| 		ctx.HandleText(http.StatusUnauthorized, "no valid auth token authorization") | ||||
| 		return | ||||
| 	} | ||||
| 	uid := sso.CheckOAuthAccessToken(auths[1]) | ||||
| 	if uid == 0 { | ||||
| 		handleBearerTokenError(ctx, BearerTokenError{ | ||||
| 			ErrorCode:        BearerTokenErrorCodeInvalidToken, | ||||
| 			ErrorDescription: "Access token not assigned to any user", | ||||
| 		}) | ||||
| 		return | ||||
| 	} | ||||
| 	authUser, err := models.GetUserByID(uid) | ||||
| 	if err != nil { | ||||
| 		ctx.ServerError("GetUserByID", err) | ||||
| 		return | ||||
| 	} | ||||
| 	response := &userInfoResponse{ | ||||
| 		Sub:      fmt.Sprint(authUser.ID), | ||||
| 		Name:     authUser.FullName, | ||||
| 		Username: authUser.Name, | ||||
| 		Email:    authUser.Email, | ||||
| 		Picture:  authUser.AvatarLink(), | ||||
| 	} | ||||
| 	ctx.JSON(http.StatusOK, response) | ||||
| } | ||||
| 
 | ||||
| // AuthorizeOAuth manages authorize requests
 | ||||
| func AuthorizeOAuth(ctx *context.Context) { | ||||
| 	form := web.GetForm(ctx).(*forms.AuthorizationForm) | ||||
|  | @ -571,3 +629,18 @@ func handleAuthorizeError(ctx *context.Context, authErr AuthorizeError, redirect | |||
| 	redirect.RawQuery = q.Encode() | ||||
| 	ctx.Redirect(redirect.String(), 302) | ||||
| } | ||||
| 
 | ||||
| func handleBearerTokenError(ctx *context.Context, beErr BearerTokenError) { | ||||
| 	ctx.Resp.Header().Set("WWW-Authenticate", fmt.Sprintf("Bearer realm=\"\", error=\"%s\", error_description=\"%s\"", beErr.ErrorCode, beErr.ErrorDescription)) | ||||
| 	switch beErr.ErrorCode { | ||||
| 	case BearerTokenErrorCodeInvalidRequest: | ||||
| 		ctx.JSON(http.StatusBadRequest, beErr) | ||||
| 	case BearerTokenErrorCodeInvalidToken: | ||||
| 		ctx.JSON(http.StatusUnauthorized, beErr) | ||||
| 	case BearerTokenErrorCodeInsufficientScope: | ||||
| 		ctx.JSON(http.StatusForbidden, beErr) | ||||
| 	default: | ||||
| 		log.Error("Invalid BearerTokenErrorCode: %v", beErr.ErrorCode) | ||||
| 		ctx.ServerError("Unhandled BearerTokenError", fmt.Errorf("BearerTokenError: error=\"%v\", error_description=\"%v\"", beErr.ErrorCode, beErr.ErrorDescription)) | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -2,6 +2,7 @@ | |||
|     "issuer": "{{AppUrl | JSEscape | Safe}}", | ||||
|     "authorization_endpoint": "{{AppUrl | JSEscape | Safe}}login/oauth/authorize", | ||||
|     "token_endpoint": "{{AppUrl | JSEscape | Safe}}login/oauth/access_token", | ||||
|     "userinfo_endpoint": "{{AppUrl | JSEscape | Safe}}login/oauth/userinfo", | ||||
|     "response_types_supported": [ | ||||
|         "code", | ||||
|         "id_token" | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue