summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--routers/web/user/oauth.go98
-rw-r--r--routers/web/web.go1
-rw-r--r--services/auth/oauth2.go12
-rw-r--r--services/forms/user_form.go11
-rw-r--r--templates/user/auth/oidc_wellknown.tmpl1
5 files changed, 67 insertions, 56 deletions
diff --git a/routers/web/user/oauth.go b/routers/web/user/oauth.go
index 67e4ea0622..771bd90b15 100644
--- a/routers/web/user/oauth.go
+++ b/routers/web/user/oauth.go
@@ -96,24 +96,6 @@ 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
@@ -253,35 +235,56 @@ type userInfoResponse struct {
// 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 := auth.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)
+ if ctx.User == nil || ctx.Data["AuthedMethod"] != (&auth.OAuth2{}).Name() {
+ ctx.Resp.Header().Set("WWW-Authenticate", `Bearer realm=""`)
+ ctx.HandleText(http.StatusUnauthorized, "no valid authorization")
return
}
response := &userInfoResponse{
- Sub: fmt.Sprint(authUser.ID),
- Name: authUser.FullName,
- Username: authUser.Name,
- Email: authUser.Email,
- Picture: authUser.AvatarLink(),
+ Sub: fmt.Sprint(ctx.User.ID),
+ Name: ctx.User.FullName,
+ Username: ctx.User.Name,
+ Email: ctx.User.Email,
+ Picture: ctx.User.AvatarLink(),
}
ctx.JSON(http.StatusOK, response)
}
+// IntrospectOAuth introspects an oauth token
+func IntrospectOAuth(ctx *context.Context) {
+ if ctx.User == nil {
+ ctx.Resp.Header().Set("WWW-Authenticate", `Bearer realm=""`)
+ ctx.HandleText(http.StatusUnauthorized, "no valid authorization")
+ return
+ }
+
+ var response struct {
+ Active bool `json:"active"`
+ Scope string `json:"scope,omitempty"`
+ jwt.StandardClaims
+ }
+
+ form := web.GetForm(ctx).(*forms.IntrospectTokenForm)
+ token, err := oauth2.ParseToken(form.Token)
+ if err == nil {
+ if token.Valid() == nil {
+ grant, err := models.GetOAuth2GrantByID(token.GrantID)
+ if err == nil && grant != nil {
+ app, err := models.GetOAuth2ApplicationByID(grant.ApplicationID)
+ if err == nil && app != nil {
+ response.Active = true
+ response.Scope = grant.Scope
+ response.Issuer = setting.AppURL
+ response.Audience = app.ClientID
+ response.Subject = fmt.Sprint(grant.UserID)
+ }
+ }
+ }
+ }
+
+ ctx.JSON(http.StatusOK, response)
+}
+
// AuthorizeOAuth manages authorize requests
func AuthorizeOAuth(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.AuthorizationForm)
@@ -697,18 +700,3 @@ 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/routers/web/web.go b/routers/web/web.go
index a47fd518ac..98395578f6 100644
--- a/routers/web/web.go
+++ b/routers/web/web.go
@@ -311,6 +311,7 @@ func RegisterRoutes(m *web.Route) {
m.Get("/login/oauth/userinfo", ignSignInAndCsrf, user.InfoOAuth)
m.Post("/login/oauth/access_token", CorsHandler(), bindIgnErr(forms.AccessTokenForm{}), ignSignInAndCsrf, user.AccessTokenOAuth)
m.Get("/login/oauth/keys", ignSignInAndCsrf, user.OIDCKeys)
+ m.Post("/login/oauth/introspect", CorsHandler(), bindIgnErr(forms.IntrospectTokenForm{}), ignSignInAndCsrf, user.IntrospectOAuth)
m.Group("/user/settings", func() {
m.Get("", userSetting.Profile)
diff --git a/services/auth/oauth2.go b/services/auth/oauth2.go
index 93806c7072..f7f870dade 100644
--- a/services/auth/oauth2.go
+++ b/services/auth/oauth2.go
@@ -113,7 +113,7 @@ func (o *OAuth2) Verify(req *http.Request, w http.ResponseWriter, store DataStor
return nil
}
- if !middleware.IsAPIPath(req) && !isAttachmentDownload(req) {
+ if !middleware.IsAPIPath(req) && !isAttachmentDownload(req) && !isAuthenticatedTokenRequest(req) {
return nil
}
@@ -134,3 +134,13 @@ func (o *OAuth2) Verify(req *http.Request, w http.ResponseWriter, store DataStor
log.Trace("OAuth2 Authorization: Logged in user %-v", user)
return user
}
+
+func isAuthenticatedTokenRequest(req *http.Request) bool {
+ switch req.URL.Path {
+ case "/login/oauth/userinfo":
+ fallthrough
+ case "/login/oauth/introspect":
+ return true
+ }
+ return false
+}
diff --git a/services/forms/user_form.go b/services/forms/user_form.go
index 1e12795c70..7d6b976936 100644
--- a/services/forms/user_form.go
+++ b/services/forms/user_form.go
@@ -215,6 +215,17 @@ func (f *AccessTokenForm) Validate(req *http.Request, errs binding.Errors) bindi
return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
}
+// IntrospectTokenForm for introspecting tokens
+type IntrospectTokenForm struct {
+ Token string `json:"token"`
+}
+
+// Validate validates the fields
+func (f *IntrospectTokenForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
// __________________________________________.___ _______ ________ _________
// / _____/\_ _____/\__ ___/\__ ___/| |\ \ / _____/ / _____/
// \_____ \ | __)_ | | | | | |/ | \/ \ ___ \_____ \
diff --git a/templates/user/auth/oidc_wellknown.tmpl b/templates/user/auth/oidc_wellknown.tmpl
index 93a048b513..d4cbf7dfec 100644
--- a/templates/user/auth/oidc_wellknown.tmpl
+++ b/templates/user/auth/oidc_wellknown.tmpl
@@ -4,6 +4,7 @@
"token_endpoint": "{{AppUrl | JSEscape | Safe}}login/oauth/access_token",
"jwks_uri": "{{AppUrl | JSEscape | Safe}}login/oauth/keys",
"userinfo_endpoint": "{{AppUrl | JSEscape | Safe}}login/oauth/userinfo",
+ "introspection_endpoint": "{{AppUrl | JSEscape | Safe}}login/oauth/introspect",
"response_types_supported": [
"code",
"id_token"