Skip to content

Commit e1a4e7e

Browse files
authored
Merge branch 'main' into sync-doc-to-zh-cn
2 parents 7769114 + a370efc commit e1a4e7e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

83 files changed

+519
-289
lines changed

custom/conf/app.example.ini

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,11 @@ ENABLE = true
544544
;;
545545
;; Maximum length of oauth2 token/cookie stored on server
546546
;MAX_TOKEN_LENGTH = 32767
547+
;;
548+
;; Pre-register OAuth2 applications for some universally useful services
549+
;; * https://github.com/hickford/git-credential-oauth
550+
;; * https://github.com/git-ecosystem/git-credential-manager
551+
;DEFAULT_APPLICATIONS = git-credential-oauth, git-credential-manager
547552

548553
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
549554
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

docs/content/administration/config-cheat-sheet.en-us.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1100,6 +1100,7 @@ This section only does "set" config, a removed config key from this section won'
11001100
- `JWT_SECRET_URI`: **_empty_**: Instead of defining JWT_SECRET in the configuration, this configuration option can be used to give Gitea a path to a file that contains the secret (example value: `file:/etc/gitea/oauth2_jwt_secret`)
11011101
- `JWT_SIGNING_PRIVATE_KEY_FILE`: **jwt/private.pem**: Private key file path used to sign OAuth2 tokens. The path is relative to `APP_DATA_PATH`. This setting is only needed if `JWT_SIGNING_ALGORITHM` is set to `RS256`, `RS384`, `RS512`, `ES256`, `ES384` or `ES512`. The file must contain a RSA or ECDSA private key in the PKCS8 format. If no key exists a 4096 bit key will be created for you.
11021102
- `MAX_TOKEN_LENGTH`: **32767**: Maximum length of token/cookie to accept from OAuth2 provider
1103+
- `DEFAULT_APPLICATIONS`: **git-credential-oauth, git-credential-manager**: Pre-register OAuth applications for some services on startup. See the [OAuth2 documentation](/development/oauth2-provider.md) for the list of available options.
11031104

11041105
## i18n (`i18n`)
11051106

docs/content/development/oauth2-provider.en-us.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,17 @@ Gitea token scopes are as follows:
7878
|     **read:user** | Grants read access to user operations, such as getting user repo subscriptions and user settings. |
7979
|     **write:user** | Grants read/write/delete access to user operations, such as updating user repo subscriptions, followed users, and user settings. |
8080

81+
## Pre-configured Applications
82+
83+
Gitea creates OAuth applications for the following services by default on startup, as we assume that these are universally useful.
84+
85+
|Application|Description|Client ID|
86+
|-----------|-----------|---------|
87+
|[git-credential-oauth](https://github.com/hickford/git-credential-oauth)|Git credential helper|`a4792ccc-144e-407e-86c9-5e7d8d9c3269`|
88+
|[Git Credential Manager](https://github.com/git-ecosystem/git-credential-manager)|Git credential helper|`e90ee53c-94e2-48ac-9358-a874fb9e0662`|
89+
90+
To prevent unexpected behavior, they are being displayed as locked in the UI and their creation can instead be controlled by the `DEFAULT_APPLICATIONS` parameter in `app.ini`.
91+
8192
## Client types
8293

8394
Gitea supports both confidential and public client types, [as defined by RFC 6749](https://datatracker.ietf.org/doc/html/rfc6749#section-2.1).

models/auth/oauth2.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import (
1313
"strings"
1414

1515
"code.gitea.io/gitea/models/db"
16+
"code.gitea.io/gitea/modules/container"
17+
"code.gitea.io/gitea/modules/setting"
1618
"code.gitea.io/gitea/modules/timeutil"
1719
"code.gitea.io/gitea/modules/util"
1820

@@ -46,6 +48,83 @@ func init() {
4648
db.RegisterModel(new(OAuth2Grant))
4749
}
4850

51+
type BuiltinOAuth2Application struct {
52+
ConfigName string
53+
DisplayName string
54+
RedirectURIs []string
55+
}
56+
57+
func BuiltinApplications() map[string]*BuiltinOAuth2Application {
58+
m := make(map[string]*BuiltinOAuth2Application)
59+
m["a4792ccc-144e-407e-86c9-5e7d8d9c3269"] = &BuiltinOAuth2Application{
60+
ConfigName: "git-credential-oauth",
61+
DisplayName: "git-credential-oauth",
62+
RedirectURIs: []string{"http://127.0.0.1", "https://127.0.0.1"},
63+
}
64+
m["e90ee53c-94e2-48ac-9358-a874fb9e0662"] = &BuiltinOAuth2Application{
65+
ConfigName: "git-credential-manager",
66+
DisplayName: "Git Credential Manager",
67+
RedirectURIs: []string{"http://127.0.0.1", "https://127.0.0.1"},
68+
}
69+
return m
70+
}
71+
72+
func Init(ctx context.Context) error {
73+
builtinApps := BuiltinApplications()
74+
var builtinAllClientIDs []string
75+
for clientID := range builtinApps {
76+
builtinAllClientIDs = append(builtinAllClientIDs, clientID)
77+
}
78+
79+
var registeredApps []*OAuth2Application
80+
if err := db.GetEngine(ctx).In("client_id", builtinAllClientIDs).Find(&registeredApps); err != nil {
81+
return err
82+
}
83+
84+
clientIDsToAdd := container.Set[string]{}
85+
for _, configName := range setting.OAuth2.DefaultApplications {
86+
found := false
87+
for clientID, builtinApp := range builtinApps {
88+
if builtinApp.ConfigName == configName {
89+
clientIDsToAdd.Add(clientID) // add all user-configured apps to the "add" list
90+
found = true
91+
}
92+
}
93+
if !found {
94+
return fmt.Errorf("unknown oauth2 application: %q", configName)
95+
}
96+
}
97+
clientIDsToDelete := container.Set[string]{}
98+
for _, app := range registeredApps {
99+
if !clientIDsToAdd.Contains(app.ClientID) {
100+
clientIDsToDelete.Add(app.ClientID) // if a registered app is not in the "add" list, it should be deleted
101+
}
102+
}
103+
for _, app := range registeredApps {
104+
clientIDsToAdd.Remove(app.ClientID) // no need to re-add existing (registered) apps, so remove them from the set
105+
}
106+
107+
for _, app := range registeredApps {
108+
if clientIDsToDelete.Contains(app.ClientID) {
109+
if err := deleteOAuth2Application(ctx, app.ID, 0); err != nil {
110+
return err
111+
}
112+
}
113+
}
114+
for clientID := range clientIDsToAdd {
115+
builtinApp := builtinApps[clientID]
116+
if err := db.Insert(ctx, &OAuth2Application{
117+
Name: builtinApp.DisplayName,
118+
ClientID: clientID,
119+
RedirectURIs: builtinApp.RedirectURIs,
120+
}); err != nil {
121+
return err
122+
}
123+
}
124+
125+
return nil
126+
}
127+
49128
// TableName sets the table name to `oauth2_application`
50129
func (app *OAuth2Application) TableName() string {
51130
return "oauth2_application"
@@ -205,6 +284,10 @@ func UpdateOAuth2Application(opts UpdateOAuth2ApplicationOptions) (*OAuth2Applic
205284
if app.UID != opts.UserID {
206285
return nil, fmt.Errorf("UID mismatch")
207286
}
287+
builtinApps := BuiltinApplications()
288+
if _, builtin := builtinApps[app.ClientID]; builtin {
289+
return nil, fmt.Errorf("failed to edit OAuth2 application: application is locked: %s", app.ClientID)
290+
}
208291

209292
app.Name = opts.Name
210293
app.RedirectURIs = opts.RedirectURIs
@@ -261,6 +344,14 @@ func DeleteOAuth2Application(id, userid int64) error {
261344
return err
262345
}
263346
defer committer.Close()
347+
app, err := GetOAuth2ApplicationByID(ctx, id)
348+
if err != nil {
349+
return err
350+
}
351+
builtinApps := BuiltinApplications()
352+
if _, builtin := builtinApps[app.ClientID]; builtin {
353+
return fmt.Errorf("failed to delete OAuth2 application: application is locked: %s", app.ClientID)
354+
}
264355
if err := deleteOAuth2Application(ctx, id, userid); err != nil {
265356
return err
266357
}

models/issues/pull.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -533,13 +533,12 @@ func (pr *PullRequest) SetMerged(ctx context.Context) (bool, error) {
533533
}
534534

535535
// NewPullRequest creates new pull request with labels for repository.
536-
func NewPullRequest(outerCtx context.Context, repo *repo_model.Repository, issue *Issue, labelIDs []int64, uuids []string, pr *PullRequest) (err error) {
537-
ctx, committer, err := db.TxContext(outerCtx)
536+
func NewPullRequest(ctx context.Context, repo *repo_model.Repository, issue *Issue, labelIDs []int64, uuids []string, pr *PullRequest) (err error) {
537+
ctx, committer, err := db.TxContext(ctx)
538538
if err != nil {
539539
return err
540540
}
541541
defer committer.Close()
542-
ctx.WithContext(outerCtx)
543542

544543
idx, err := db.GetNextResourceIndex(ctx, "issue_index", repo.ID)
545544
if err != nil {
@@ -948,14 +947,14 @@ func PullRequestCodeOwnersReview(ctx context.Context, pull *Issue, pr *PullReque
948947

949948
for _, u := range uniqUsers {
950949
if u.ID != pull.Poster.ID {
951-
if _, err := AddReviewRequest(pull, u, pull.Poster); err != nil {
950+
if _, err := AddReviewRequest(ctx, pull, u, pull.Poster); err != nil {
952951
log.Warn("Failed add assignee user: %s to PR review: %s#%d, error: %s", u.Name, pr.BaseRepo.Name, pr.ID, err)
953952
return err
954953
}
955954
}
956955
}
957956
for _, t := range uniqTeams {
958-
if _, err := AddTeamReviewRequest(pull, t, pull.Poster); err != nil {
957+
if _, err := AddTeamReviewRequest(ctx, pull, t, pull.Poster); err != nil {
959958
log.Warn("Failed add assignee team: %s to PR review: %s#%d, error: %s", t.Name, pr.BaseRepo.Name, pr.ID, err)
960959
return err
961960
}

models/issues/pull_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,14 +88,14 @@ func TestLoadRequestedReviewers(t *testing.T) {
8888
user1, err := user_model.GetUserByID(db.DefaultContext, 1)
8989
assert.NoError(t, err)
9090

91-
comment, err := issues_model.AddReviewRequest(issue, user1, &user_model.User{})
91+
comment, err := issues_model.AddReviewRequest(db.DefaultContext, issue, user1, &user_model.User{})
9292
assert.NoError(t, err)
9393
assert.NotNil(t, comment)
9494

9595
assert.NoError(t, pull.LoadRequestedReviewers(db.DefaultContext))
9696
assert.Len(t, pull.RequestedReviewers, 1)
9797

98-
comment, err = issues_model.RemoveReviewRequest(issue, user1, &user_model.User{})
98+
comment, err = issues_model.RemoveReviewRequest(db.DefaultContext, issue, user1, &user_model.User{})
9999
assert.NoError(t, err)
100100
assert.NotNil(t, comment)
101101

models/issues/review.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -560,8 +560,8 @@ func InsertReviews(reviews []*Review) error {
560560
}
561561

562562
// AddReviewRequest add a review request from one reviewer
563-
func AddReviewRequest(issue *Issue, reviewer, doer *user_model.User) (*Comment, error) {
564-
ctx, committer, err := db.TxContext(db.DefaultContext)
563+
func AddReviewRequest(ctx context.Context, issue *Issue, reviewer, doer *user_model.User) (*Comment, error) {
564+
ctx, committer, err := db.TxContext(ctx)
565565
if err != nil {
566566
return nil, err
567567
}
@@ -615,8 +615,8 @@ func AddReviewRequest(issue *Issue, reviewer, doer *user_model.User) (*Comment,
615615
}
616616

617617
// RemoveReviewRequest remove a review request from one reviewer
618-
func RemoveReviewRequest(issue *Issue, reviewer, doer *user_model.User) (*Comment, error) {
619-
ctx, committer, err := db.TxContext(db.DefaultContext)
618+
func RemoveReviewRequest(ctx context.Context, issue *Issue, reviewer, doer *user_model.User) (*Comment, error) {
619+
ctx, committer, err := db.TxContext(ctx)
620620
if err != nil {
621621
return nil, err
622622
}
@@ -676,8 +676,8 @@ func restoreLatestOfficialReview(ctx context.Context, issueID, reviewerID int64)
676676
}
677677

678678
// AddTeamReviewRequest add a review request from one team
679-
func AddTeamReviewRequest(issue *Issue, reviewer *organization.Team, doer *user_model.User) (*Comment, error) {
680-
ctx, committer, err := db.TxContext(db.DefaultContext)
679+
func AddTeamReviewRequest(ctx context.Context, issue *Issue, reviewer *organization.Team, doer *user_model.User) (*Comment, error) {
680+
ctx, committer, err := db.TxContext(ctx)
681681
if err != nil {
682682
return nil, err
683683
}
@@ -735,8 +735,8 @@ func AddTeamReviewRequest(issue *Issue, reviewer *organization.Team, doer *user_
735735
}
736736

737737
// RemoveTeamReviewRequest remove a review request from one team
738-
func RemoveTeamReviewRequest(issue *Issue, reviewer *organization.Team, doer *user_model.User) (*Comment, error) {
739-
ctx, committer, err := db.TxContext(db.DefaultContext)
738+
func RemoveTeamReviewRequest(ctx context.Context, issue *Issue, reviewer *organization.Team, doer *user_model.User) (*Comment, error) {
739+
ctx, committer, err := db.TxContext(ctx)
740740
if err != nil {
741741
return nil, err
742742
}

modules/context/base.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,10 @@ func (b *Base) Params(p string) string {
147147
return s
148148
}
149149

150+
func (b *Base) PathParamRaw(p string) string {
151+
return chi.URLParam(b.Req, strings.TrimPrefix(p, ":"))
152+
}
153+
150154
// ParamsInt64 returns the param on route as int64
151155
func (b *Base) ParamsInt64(p string) int64 {
152156
v, _ := strconv.ParseInt(b.Params(p), 10, 64)

modules/context/context.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ func Contexter() func(next http.Handler) http.Handler {
141141
// TODO: "install.go" also shares the same logic, which should be refactored to a general function
142142
ctx.TemplateContext = NewTemplateContext(ctx)
143143
ctx.TemplateContext["Locale"] = ctx.Locale
144+
ctx.TemplateContext["AvatarUtils"] = templates.NewAvatarUtils(ctx)
144145

145146
ctx.Data.MergeFrom(middleware.CommonTemplateContextData())
146147
ctx.Data["Context"] = ctx // TODO: use "ctx" in template and remove this

modules/markup/common/footnote.go

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,12 @@ func CleanValue(value []byte) []byte {
2929
value = bytes.TrimSpace(value)
3030
rs := bytes.Runes(value)
3131
result := make([]rune, 0, len(rs))
32-
needsDash := false
3332
for _, r := range rs {
34-
switch {
35-
case unicode.IsLetter(r) || unicode.IsNumber(r) || r == '_':
36-
if needsDash && len(result) > 0 {
37-
result = append(result, '-')
38-
}
39-
needsDash = false
33+
if unicode.IsLetter(r) || unicode.IsNumber(r) || r == '_' || r == '-' {
4034
result = append(result, unicode.ToLower(r))
41-
default:
42-
needsDash = true
35+
}
36+
if unicode.IsSpace(r) {
37+
result = append(result, '-')
4338
}
4439
}
4540
return []byte(string(result))
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Copyright 2023 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
package common
4+
5+
import (
6+
"testing"
7+
8+
"github.com/stretchr/testify/assert"
9+
)
10+
11+
func TestCleanValue(t *testing.T) {
12+
tests := []struct {
13+
param string
14+
expect string
15+
}{
16+
// Github behavior test cases
17+
{"", ""},
18+
{"test(0)", "test0"},
19+
{"test!1", "test1"},
20+
{"test:2", "test2"},
21+
{"test*3", "test3"},
22+
{"test!4", "test4"},
23+
{"test:5", "test5"},
24+
{"test*6", "test6"},
25+
{"test:6 a", "test6-a"},
26+
{"test:6 !b", "test6-b"},
27+
{"test:ad # df", "testad--df"},
28+
{"test:ad #23 df 2*/*", "testad-23-df-2"},
29+
{"test:ad 23 df 2*/*", "testad-23-df-2"},
30+
{"test:ad # 23 df 2*/*", "testad--23-df-2"},
31+
{"Anchors in Markdown", "anchors-in-markdown"},
32+
{"a_b_c", "a_b_c"},
33+
{"a-b-c", "a-b-c"},
34+
{"a-b-c----", "a-b-c----"},
35+
{"test:6a", "test6a"},
36+
{"test:a6", "testa6"},
37+
{"tes a a a a", "tes-a-a---a--a"},
38+
{" tes a a a a ", "tes-a-a---a--a"},
39+
{"Header with \"double quotes\"", "header-with-double-quotes"},
40+
{"Placeholder to force scrolling on link's click", "placeholder-to-force-scrolling-on-links-click"},
41+
{"tes()", "tes"},
42+
{"tes(0)", "tes0"},
43+
{"tes{0}", "tes0"},
44+
{"tes[0]", "tes0"},
45+
{"test【0】", "test0"},
46+
{"tes…@a", "tesa"},
47+
{"tes¥& a", "tes-a"},
48+
{"tes= a", "tes-a"},
49+
{"tes|a", "tesa"},
50+
{"tes\\a", "tesa"},
51+
{"tes/a", "tesa"},
52+
{"a啊啊b", "a啊啊b"},
53+
{"c🤔️🤔️d", "cd"},
54+
{"a⚡a", "aa"},
55+
{"e.~f", "ef"},
56+
}
57+
for _, test := range tests {
58+
assert.Equal(t, []byte(test.expect), CleanValue([]byte(test.param)), test.param)
59+
}
60+
}

modules/setting/oauth2.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ var OAuth2 = struct {
100100
JWTSecretBase64 string `ini:"JWT_SECRET"`
101101
JWTSigningPrivateKeyFile string `ini:"JWT_SIGNING_PRIVATE_KEY_FILE"`
102102
MaxTokenLength int
103+
DefaultApplications []string
103104
}{
104105
Enable: true,
105106
AccessTokenExpirationTime: 3600,
@@ -108,6 +109,7 @@ var OAuth2 = struct {
108109
JWTSigningAlgorithm: "RS256",
109110
JWTSigningPrivateKeyFile: "jwt/private.pem",
110111
MaxTokenLength: math.MaxInt16,
112+
DefaultApplications: []string{"git-credential-oauth", "git-credential-manager"},
111113
}
112114

113115
func loadOAuth2From(rootCfg ConfigProvider) {

0 commit comments

Comments
 (0)