Skip to content

Commit 81d7204

Browse files
authored
Merge branch 'main' into gitea_lfs_serve_direct
2 parents 8a8f5df + 06f8264 commit 81d7204

File tree

13 files changed

+195
-85
lines changed

13 files changed

+195
-85
lines changed

models/fixtures/oauth2_grant.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,19 @@
55
scope: "openid profile"
66
created_unix: 1546869730
77
updated_unix: 1546869730
8+
9+
- id: 2
10+
user_id: 3
11+
application_id: 1
12+
counter: 1
13+
scope: "openid"
14+
created_unix: 1546869730
15+
updated_unix: 1546869730
16+
17+
- id: 3
18+
user_id: 5
19+
application_id: 1
20+
counter: 1
21+
scope: "openid profile email"
22+
created_unix: 1546869730
23+
updated_unix: 1546869730

modules/proxy/proxy.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,9 @@ func Match(u string) bool {
5656
// Proxy returns the system proxy
5757
func Proxy() func(req *http.Request) (*url.URL, error) {
5858
if !setting.Proxy.Enabled {
59-
return nil
59+
return func(req *http.Request) (*url.URL, error) {
60+
return nil, nil
61+
}
6062
}
6163
if setting.Proxy.ProxyURL == "" {
6264
return http.ProxyFromEnvironment

options/locale/locale_en-US.ini

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1330,8 +1330,8 @@ issues.dependency.remove = Remove
13301330
issues.dependency.remove_info = Remove this dependency
13311331
issues.dependency.added_dependency = `added a new dependency %s`
13321332
issues.dependency.removed_dependency = `removed a dependency %s`
1333-
issues.dependency.issue_closing_blockedby = Closing this pull request is blocked by the following issues
1334-
issues.dependency.pr_closing_blockedby = Closing this issue is blocked by the following issues
1333+
issues.dependency.pr_closing_blockedby = Closing this pull request is blocked by the following issues
1334+
issues.dependency.issue_closing_blockedby = Closing this issue is blocked by the following issues
13351335
issues.dependency.issue_close_blocks = This issue blocks closing of the following issues
13361336
issues.dependency.pr_close_blocks = This pull request blocks closing of the following issues
13371337
issues.dependency.issue_close_blocked = You need to close all issues blocking this issue before you can close it.

routers/web/repo/issue.go

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1727,9 +1727,12 @@ func UpdateIssueContent(ctx *context.Context) {
17271727
return
17281728
}
17291729

1730-
if err := updateAttachments(issue, ctx.FormStrings("files[]")); err != nil {
1731-
ctx.ServerError("UpdateAttachments", err)
1732-
return
1730+
// when update the request doesn't intend to update attachments (eg: change checkbox state), ignore attachment updates
1731+
if !ctx.FormBool("ignore_attachments") {
1732+
if err := updateAttachments(issue, ctx.FormStrings("files[]")); err != nil {
1733+
ctx.ServerError("UpdateAttachments", err)
1734+
return
1735+
}
17331736
}
17341737

17351738
content, err := markdown.RenderString(&markup.RenderContext{
@@ -2127,13 +2130,6 @@ func UpdateCommentContent(ctx *context.Context) {
21272130
return
21282131
}
21292132

2130-
if comment.Type == models.CommentTypeComment {
2131-
if err := comment.LoadAttachments(); err != nil {
2132-
ctx.ServerError("LoadAttachments", err)
2133-
return
2134-
}
2135-
}
2136-
21372133
if !ctx.IsSigned || (ctx.User.ID != comment.PosterID && !ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull)) {
21382134
ctx.Error(http.StatusForbidden)
21392135
return
@@ -2155,9 +2151,19 @@ func UpdateCommentContent(ctx *context.Context) {
21552151
return
21562152
}
21572153

2158-
if err := updateAttachments(comment, ctx.FormStrings("files[]")); err != nil {
2159-
ctx.ServerError("UpdateAttachments", err)
2160-
return
2154+
if comment.Type == models.CommentTypeComment {
2155+
if err := comment.LoadAttachments(); err != nil {
2156+
ctx.ServerError("LoadAttachments", err)
2157+
return
2158+
}
2159+
}
2160+
2161+
// when the update request doesn't intend to update attachments (eg: change checkbox state), ignore attachment updates
2162+
if !ctx.FormBool("ignore_attachments") {
2163+
if err := updateAttachments(comment, ctx.FormStrings("files[]")); err != nil {
2164+
ctx.ServerError("UpdateAttachments", err)
2165+
return
2166+
}
21612167
}
21622168

21632169
content, err := markdown.RenderString(&markup.RenderContext{

routers/web/user/oauth.go

Lines changed: 53 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -96,24 +96,6 @@ func (err AccessTokenError) Error() string {
9696
return fmt.Sprintf("%s: %s", err.ErrorCode, err.ErrorDescription)
9797
}
9898

99-
// BearerTokenErrorCode represents an error code specified in RFC 6750
100-
type BearerTokenErrorCode string
101-
102-
const (
103-
// BearerTokenErrorCodeInvalidRequest represents an error code specified in RFC 6750
104-
BearerTokenErrorCodeInvalidRequest BearerTokenErrorCode = "invalid_request"
105-
// BearerTokenErrorCodeInvalidToken represents an error code specified in RFC 6750
106-
BearerTokenErrorCodeInvalidToken BearerTokenErrorCode = "invalid_token"
107-
// BearerTokenErrorCodeInsufficientScope represents an error code specified in RFC 6750
108-
BearerTokenErrorCodeInsufficientScope BearerTokenErrorCode = "insufficient_scope"
109-
)
110-
111-
// BearerTokenError represents an error response specified in RFC 6750
112-
type BearerTokenError struct {
113-
ErrorCode BearerTokenErrorCode `json:"error" form:"error"`
114-
ErrorDescription string `json:"error_description"`
115-
}
116-
11799
// TokenType specifies the kind of token
118100
type TokenType string
119101

@@ -187,7 +169,7 @@ func newAccessTokenResponse(grant *models.OAuth2Grant, signingKey oauth2.JWTSign
187169
ErrorDescription: "cannot find application",
188170
}
189171
}
190-
err = app.LoadUser()
172+
user, err := models.GetUserByID(grant.UserID)
191173
if err != nil {
192174
if models.IsErrUserNotExist(err) {
193175
return nil, &AccessTokenError{
@@ -212,17 +194,17 @@ func newAccessTokenResponse(grant *models.OAuth2Grant, signingKey oauth2.JWTSign
212194
Nonce: grant.Nonce,
213195
}
214196
if grant.ScopeContains("profile") {
215-
idToken.Name = app.User.FullName
216-
idToken.PreferredUsername = app.User.Name
217-
idToken.Profile = app.User.HTMLURL()
218-
idToken.Picture = app.User.AvatarLink()
219-
idToken.Website = app.User.Website
220-
idToken.Locale = app.User.Language
221-
idToken.UpdatedAt = app.User.UpdatedUnix
197+
idToken.Name = user.FullName
198+
idToken.PreferredUsername = user.Name
199+
idToken.Profile = user.HTMLURL()
200+
idToken.Picture = user.AvatarLink()
201+
idToken.Website = user.Website
202+
idToken.Locale = user.Language
203+
idToken.UpdatedAt = user.UpdatedUnix
222204
}
223205
if grant.ScopeContains("email") {
224-
idToken.Email = app.User.Email
225-
idToken.EmailVerified = app.User.IsActive
206+
idToken.Email = user.Email
207+
idToken.EmailVerified = user.IsActive
226208
}
227209

228210
signedIDToken, err = idToken.SignToken(signingKey)
@@ -253,35 +235,56 @@ type userInfoResponse struct {
253235

254236
// InfoOAuth manages request for userinfo endpoint
255237
func InfoOAuth(ctx *context.Context) {
256-
header := ctx.Req.Header.Get("Authorization")
257-
auths := strings.Fields(header)
258-
if len(auths) != 2 || auths[0] != "Bearer" {
259-
ctx.HandleText(http.StatusUnauthorized, "no valid auth token authorization")
260-
return
261-
}
262-
uid := auth.CheckOAuthAccessToken(auths[1])
263-
if uid == 0 {
264-
handleBearerTokenError(ctx, BearerTokenError{
265-
ErrorCode: BearerTokenErrorCodeInvalidToken,
266-
ErrorDescription: "Access token not assigned to any user",
267-
})
268-
return
269-
}
270-
authUser, err := models.GetUserByID(uid)
271-
if err != nil {
272-
ctx.ServerError("GetUserByID", err)
238+
if ctx.User == nil || ctx.Data["AuthedMethod"] != (&auth.OAuth2{}).Name() {
239+
ctx.Resp.Header().Set("WWW-Authenticate", `Bearer realm=""`)
240+
ctx.HandleText(http.StatusUnauthorized, "no valid authorization")
273241
return
274242
}
275243
response := &userInfoResponse{
276-
Sub: fmt.Sprint(authUser.ID),
277-
Name: authUser.FullName,
278-
Username: authUser.Name,
279-
Email: authUser.Email,
280-
Picture: authUser.AvatarLink(),
244+
Sub: fmt.Sprint(ctx.User.ID),
245+
Name: ctx.User.FullName,
246+
Username: ctx.User.Name,
247+
Email: ctx.User.Email,
248+
Picture: ctx.User.AvatarLink(),
281249
}
282250
ctx.JSON(http.StatusOK, response)
283251
}
284252

253+
// IntrospectOAuth introspects an oauth token
254+
func IntrospectOAuth(ctx *context.Context) {
255+
if ctx.User == nil {
256+
ctx.Resp.Header().Set("WWW-Authenticate", `Bearer realm=""`)
257+
ctx.HandleText(http.StatusUnauthorized, "no valid authorization")
258+
return
259+
}
260+
261+
var response struct {
262+
Active bool `json:"active"`
263+
Scope string `json:"scope,omitempty"`
264+
jwt.StandardClaims
265+
}
266+
267+
form := web.GetForm(ctx).(*forms.IntrospectTokenForm)
268+
token, err := oauth2.ParseToken(form.Token)
269+
if err == nil {
270+
if token.Valid() == nil {
271+
grant, err := models.GetOAuth2GrantByID(token.GrantID)
272+
if err == nil && grant != nil {
273+
app, err := models.GetOAuth2ApplicationByID(grant.ApplicationID)
274+
if err == nil && app != nil {
275+
response.Active = true
276+
response.Scope = grant.Scope
277+
response.Issuer = setting.AppURL
278+
response.Audience = app.ClientID
279+
response.Subject = fmt.Sprint(grant.UserID)
280+
}
281+
}
282+
}
283+
}
284+
285+
ctx.JSON(http.StatusOK, response)
286+
}
287+
285288
// AuthorizeOAuth manages authorize requests
286289
func AuthorizeOAuth(ctx *context.Context) {
287290
form := web.GetForm(ctx).(*forms.AuthorizationForm)
@@ -697,18 +700,3 @@ func handleAuthorizeError(ctx *context.Context, authErr AuthorizeError, redirect
697700
redirect.RawQuery = q.Encode()
698701
ctx.Redirect(redirect.String(), 302)
699702
}
700-
701-
func handleBearerTokenError(ctx *context.Context, beErr BearerTokenError) {
702-
ctx.Resp.Header().Set("WWW-Authenticate", fmt.Sprintf("Bearer realm=\"\", error=\"%s\", error_description=\"%s\"", beErr.ErrorCode, beErr.ErrorDescription))
703-
switch beErr.ErrorCode {
704-
case BearerTokenErrorCodeInvalidRequest:
705-
ctx.JSON(http.StatusBadRequest, beErr)
706-
case BearerTokenErrorCodeInvalidToken:
707-
ctx.JSON(http.StatusUnauthorized, beErr)
708-
case BearerTokenErrorCodeInsufficientScope:
709-
ctx.JSON(http.StatusForbidden, beErr)
710-
default:
711-
log.Error("Invalid BearerTokenErrorCode: %v", beErr.ErrorCode)
712-
ctx.ServerError("Unhandled BearerTokenError", fmt.Errorf("BearerTokenError: error=\"%v\", error_description=\"%v\"", beErr.ErrorCode, beErr.ErrorDescription))
713-
}
714-
}

routers/web/user/oauth_test.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// Copyright 2021 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package user
6+
7+
import (
8+
"testing"
9+
10+
"code.gitea.io/gitea/models"
11+
"code.gitea.io/gitea/services/auth/source/oauth2"
12+
13+
"github.com/golang-jwt/jwt"
14+
"github.com/stretchr/testify/assert"
15+
)
16+
17+
func createAndParseToken(t *testing.T, grant *models.OAuth2Grant) *oauth2.OIDCToken {
18+
signingKey, err := oauth2.CreateJWTSingingKey("HS256", make([]byte, 32))
19+
assert.NoError(t, err)
20+
assert.NotNil(t, signingKey)
21+
oauth2.DefaultSigningKey = signingKey
22+
23+
response, terr := newAccessTokenResponse(grant, signingKey)
24+
assert.Nil(t, terr)
25+
assert.NotNil(t, response)
26+
27+
parsedToken, err := jwt.ParseWithClaims(response.IDToken, &oauth2.OIDCToken{}, func(token *jwt.Token) (interface{}, error) {
28+
assert.NotNil(t, token.Method)
29+
assert.Equal(t, signingKey.SigningMethod().Alg(), token.Method.Alg())
30+
return signingKey.VerifyKey(), nil
31+
})
32+
assert.NoError(t, err)
33+
assert.True(t, parsedToken.Valid)
34+
35+
oidcToken, ok := parsedToken.Claims.(*oauth2.OIDCToken)
36+
assert.True(t, ok)
37+
assert.NotNil(t, oidcToken)
38+
39+
return oidcToken
40+
}
41+
42+
func TestNewAccessTokenResponse_OIDCToken(t *testing.T) {
43+
assert.NoError(t, models.PrepareTestDatabase())
44+
45+
grants, err := models.GetOAuth2GrantsByUserID(3)
46+
assert.NoError(t, err)
47+
assert.Len(t, grants, 1)
48+
49+
// Scopes: openid
50+
oidcToken := createAndParseToken(t, grants[0])
51+
assert.Empty(t, oidcToken.Name)
52+
assert.Empty(t, oidcToken.PreferredUsername)
53+
assert.Empty(t, oidcToken.Profile)
54+
assert.Empty(t, oidcToken.Picture)
55+
assert.Empty(t, oidcToken.Website)
56+
assert.Empty(t, oidcToken.UpdatedAt)
57+
assert.Empty(t, oidcToken.Email)
58+
assert.False(t, oidcToken.EmailVerified)
59+
60+
user := models.AssertExistsAndLoadBean(t, &models.User{ID: 5}).(*models.User)
61+
grants, err = models.GetOAuth2GrantsByUserID(user.ID)
62+
assert.NoError(t, err)
63+
assert.Len(t, grants, 1)
64+
65+
// Scopes: openid profile email
66+
oidcToken = createAndParseToken(t, grants[0])
67+
assert.Equal(t, user.FullName, oidcToken.Name)
68+
assert.Equal(t, user.Name, oidcToken.PreferredUsername)
69+
assert.Equal(t, user.HTMLURL(), oidcToken.Profile)
70+
assert.Equal(t, user.AvatarLink(), oidcToken.Picture)
71+
assert.Equal(t, user.Website, oidcToken.Website)
72+
assert.Equal(t, user.UpdatedUnix, oidcToken.UpdatedAt)
73+
assert.Equal(t, user.Email, oidcToken.Email)
74+
assert.Equal(t, user.IsActive, oidcToken.EmailVerified)
75+
}

routers/web/web.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,7 @@ func RegisterRoutes(m *web.Route) {
311311
m.Get("/login/oauth/userinfo", ignSignInAndCsrf, user.InfoOAuth)
312312
m.Post("/login/oauth/access_token", CorsHandler(), bindIgnErr(forms.AccessTokenForm{}), ignSignInAndCsrf, user.AccessTokenOAuth)
313313
m.Get("/login/oauth/keys", ignSignInAndCsrf, user.OIDCKeys)
314+
m.Post("/login/oauth/introspect", CorsHandler(), bindIgnErr(forms.IntrospectTokenForm{}), ignSignInAndCsrf, user.IntrospectOAuth)
314315

315316
m.Group("/user/settings", func() {
316317
m.Get("", userSetting.Profile)

services/auth/oauth2.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ func (o *OAuth2) Verify(req *http.Request, w http.ResponseWriter, store DataStor
113113
return nil
114114
}
115115

116-
if !middleware.IsAPIPath(req) && !isAttachmentDownload(req) {
116+
if !middleware.IsAPIPath(req) && !isAttachmentDownload(req) && !isAuthenticatedTokenRequest(req) {
117117
return nil
118118
}
119119

@@ -134,3 +134,13 @@ func (o *OAuth2) Verify(req *http.Request, w http.ResponseWriter, store DataStor
134134
log.Trace("OAuth2 Authorization: Logged in user %-v", user)
135135
return user
136136
}
137+
138+
func isAuthenticatedTokenRequest(req *http.Request) bool {
139+
switch req.URL.Path {
140+
case "/login/oauth/userinfo":
141+
fallthrough
142+
case "/login/oauth/introspect":
143+
return true
144+
}
145+
return false
146+
}

services/forms/user_form.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,17 @@ func (f *AccessTokenForm) Validate(req *http.Request, errs binding.Errors) bindi
215215
return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
216216
}
217217

218+
// IntrospectTokenForm for introspecting tokens
219+
type IntrospectTokenForm struct {
220+
Token string `json:"token"`
221+
}
222+
223+
// Validate validates the fields
224+
func (f *IntrospectTokenForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
225+
ctx := context.GetContext(req)
226+
return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
227+
}
228+
218229
// __________________________________________.___ _______ ________ _________
219230
// / _____/\_ _____/\__ ___/\__ ___/| |\ \ / _____/ / _____/
220231
// \_____ \ | __)_ | | | | | |/ | \/ \ ___ \_____ \

templates/repo/issue/view_content/sidebar.tmpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -490,7 +490,7 @@
490490
{{range .BlockedByDependencies}}
491491
<div class="item dependency{{if .Issue.IsClosed}} is-closed{{end}} df ac sb">
492492
<div class="item-left df jc fc f1">
493-
<a class="title" href="{{.Repository.Link}}/issues/{{.Issue.Index}}">
493+
<a class="title" href="{{.Repository.Link}}/{{if .Issue.IsPull}}pulls{{else}}issues{{end}}/{{.Issue.Index}}">
494494
#{{.Issue.Index}} {{.Issue.Title | RenderEmoji}}
495495
</a>
496496
<div class="text small">

templates/user/auth/oidc_wellknown.tmpl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"token_endpoint": "{{AppUrl | JSEscape | Safe}}login/oauth/access_token",
55
"jwks_uri": "{{AppUrl | JSEscape | Safe}}login/oauth/keys",
66
"userinfo_endpoint": "{{AppUrl | JSEscape | Safe}}login/oauth/userinfo",
7+
"introspection_endpoint": "{{AppUrl | JSEscape | Safe}}login/oauth/introspect",
78
"response_types_supported": [
89
"code",
910
"id_token"

0 commit comments

Comments
 (0)