Skip to content

Commit

Permalink
Feature/oauth userinfo (go-gitea#15721)
Browse files Browse the repository at this point in the history
* Implemented userinfo go-gitea#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>
  • Loading branch information
3 people authored and AbdulrhmnGhanem committed Aug 10, 2021
1 parent 4f0e3a8 commit 39feeac
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 0 deletions.
1 change: 1 addition & 0 deletions routers/routes/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
73 changes: 73 additions & 0 deletions routers/user/oauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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))
}
}
1 change: 1 addition & 0 deletions templates/user/auth/oidc_wellknown.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down

0 comments on commit 39feeac

Please sign in to comment.