summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorN. L. H <github@nlh-software.de>2021-05-06 07:30:15 +0200
committerGitHub <noreply@github.com>2021-05-06 01:30:15 -0400
commit45970ae82e478dd7d5f01fcc053de5df00198abc (patch)
treef4ff21156e83af743291774d2054704821d34aa3
parent6a3ad0b24e6d0e859c2073126af429ef1892e712 (diff)
downloadgitea-45970ae82e478dd7d5f01fcc053de5df00198abc.tar.gz
gitea-45970ae82e478dd7d5f01fcc053de5df00198abc.zip
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>
-rw-r--r--routers/routes/web.go1
-rw-r--r--routers/user/oauth.go73
-rw-r--r--templates/user/auth/oidc_wellknown.tmpl1
3 files changed, 75 insertions, 0 deletions
diff --git a/routers/routes/web.go b/routers/routes/web.go
index ebd738de1e..c4d0bc32f8 100644
--- a/routers/routes/web.go
+++ b/routers/routes/web.go
@@ -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
diff --git a/routers/user/oauth.go b/routers/user/oauth.go
index ae06efd0c0..3ef5a56c01 100644
--- a/routers/user/oauth.go
+++ b/routers/user/oauth.go
@@ -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))
+ }
+}
diff --git a/templates/user/auth/oidc_wellknown.tmpl b/templates/user/auth/oidc_wellknown.tmpl
index 290ed4a71d..fcde060a8d 100644
--- a/templates/user/auth/oidc_wellknown.tmpl
+++ b/templates/user/auth/oidc_wellknown.tmpl
@@ -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"