Skip to content

Commit b623b85

Browse files
committed
Merge remote-tracking branch 'giteaofficial/main'
* giteaofficial/main: Add API routes to lock and unlock issues (go-gitea#34165) Make ROOT_URL support using request Host header (go-gitea#32564) Valid email address should only start with alphanumeric (go-gitea#28174) Fix notify watch failure when the content is too long (go-gitea#34233) Add "--fullname" arg to gitea admin user create (go-gitea#34241) Fix various UI problems (go-gitea#34243) markup: improve code block readability and isolate copy button (go-gitea#34009) Don't assume the default wiki branch is master in the wiki API (go-gitea#34244) [skip ci] Updated translations via Crowdin Optimize the calling code of queryElems (go-gitea#34235) Actions Runner rest api (go-gitea#33873) Fix some trivial problems (go-gitea#34237) Swift files can be passed either as file or as form value (go-gitea#34068) # Conflicts: # templates/repo/wiki/revision.tmpl
2 parents e116825 + e947f30 commit b623b85

Some content is hidden

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

65 files changed

+2399
-303
lines changed

cmd/admin_user_create.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@ var microcmdUserCreate = &cli.Command{
8181
Name: "restricted",
8282
Usage: "Make a restricted user account",
8383
},
84+
&cli.StringFlag{
85+
Name: "fullname",
86+
Usage: `The full, human-readable name of the user`,
87+
},
8488
},
8589
}
8690

@@ -191,6 +195,7 @@ func runCreateUser(c *cli.Context) error {
191195
Passwd: password,
192196
MustChangePassword: mustChangePassword,
193197
Visibility: visibility,
198+
FullName: c.String("fullname"),
194199
}
195200

196201
overwriteDefault := &user_model.CreateUserOverwriteOptions{

cmd/admin_user_create_test.go

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -50,17 +50,17 @@ func TestAdminUserCreate(t *testing.T) {
5050
assert.Equal(t, check{IsAdmin: false, MustChangePassword: false}, createCheck("u5", "--must-change-password=false"))
5151
})
5252

53-
createUser := func(name, args string) error {
54-
return app.Run(strings.Fields(fmt.Sprintf("./gitea admin user create --username %s --email %s@gitea.local %s", name, name, args)))
53+
createUser := func(name string, args ...string) error {
54+
return app.Run(append([]string{"./gitea", "admin", "user", "create", "--username", name, "--email", name + "@gitea.local"}, args...))
5555
}
5656

5757
t.Run("UserType", func(t *testing.T) {
5858
reset()
59-
assert.ErrorContains(t, createUser("u", "--user-type invalid"), "invalid user type")
60-
assert.ErrorContains(t, createUser("u", "--user-type bot --password 123"), "can only be set for individual users")
61-
assert.ErrorContains(t, createUser("u", "--user-type bot --must-change-password"), "can only be set for individual users")
59+
assert.ErrorContains(t, createUser("u", "--user-type", "invalid"), "invalid user type")
60+
assert.ErrorContains(t, createUser("u", "--user-type", "bot", "--password", "123"), "can only be set for individual users")
61+
assert.ErrorContains(t, createUser("u", "--user-type", "bot", "--must-change-password"), "can only be set for individual users")
6262

63-
assert.NoError(t, createUser("u", "--user-type bot"))
63+
assert.NoError(t, createUser("u", "--user-type", "bot"))
6464
u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "u"})
6565
assert.Equal(t, user_model.UserTypeBot, u.Type)
6666
assert.Empty(t, u.Passwd)
@@ -75,7 +75,7 @@ func TestAdminUserCreate(t *testing.T) {
7575

7676
// using "--access-token" only means "all" access
7777
reset()
78-
assert.NoError(t, createUser("u", "--random-password --access-token"))
78+
assert.NoError(t, createUser("u", "--random-password", "--access-token"))
7979
assert.Equal(t, 1, unittest.GetCount(t, &user_model.User{}))
8080
assert.Equal(t, 1, unittest.GetCount(t, &auth_model.AccessToken{}))
8181
accessToken := unittest.AssertExistsAndLoadBean(t, &auth_model.AccessToken{Name: "gitea-admin"})
@@ -85,7 +85,7 @@ func TestAdminUserCreate(t *testing.T) {
8585

8686
// using "--access-token" with name & scopes
8787
reset()
88-
assert.NoError(t, createUser("u", "--random-password --access-token --access-token-name new-token-name --access-token-scopes read:issue,read:user"))
88+
assert.NoError(t, createUser("u", "--random-password", "--access-token", "--access-token-name", "new-token-name", "--access-token-scopes", "read:issue,read:user"))
8989
assert.Equal(t, 1, unittest.GetCount(t, &user_model.User{}))
9090
assert.Equal(t, 1, unittest.GetCount(t, &auth_model.AccessToken{}))
9191
accessToken = unittest.AssertExistsAndLoadBean(t, &auth_model.AccessToken{Name: "new-token-name"})
@@ -98,23 +98,38 @@ func TestAdminUserCreate(t *testing.T) {
9898

9999
// using "--access-token-name" without "--access-token"
100100
reset()
101-
err = createUser("u", "--random-password --access-token-name new-token-name")
101+
err = createUser("u", "--random-password", "--access-token-name", "new-token-name")
102102
assert.Equal(t, 0, unittest.GetCount(t, &user_model.User{}))
103103
assert.Equal(t, 0, unittest.GetCount(t, &auth_model.AccessToken{}))
104104
assert.ErrorContains(t, err, "access-token-name and access-token-scopes flags are only valid when access-token flag is set")
105105

106106
// using "--access-token-scopes" without "--access-token"
107107
reset()
108-
err = createUser("u", "--random-password --access-token-scopes read:issue")
108+
err = createUser("u", "--random-password", "--access-token-scopes", "read:issue")
109109
assert.Equal(t, 0, unittest.GetCount(t, &user_model.User{}))
110110
assert.Equal(t, 0, unittest.GetCount(t, &auth_model.AccessToken{}))
111111
assert.ErrorContains(t, err, "access-token-name and access-token-scopes flags are only valid when access-token flag is set")
112112

113113
// empty permission
114114
reset()
115-
err = createUser("u", "--random-password --access-token --access-token-scopes public-only")
115+
err = createUser("u", "--random-password", "--access-token", "--access-token-scopes", "public-only")
116116
assert.Equal(t, 0, unittest.GetCount(t, &user_model.User{}))
117117
assert.Equal(t, 0, unittest.GetCount(t, &auth_model.AccessToken{}))
118118
assert.ErrorContains(t, err, "access token does not have any permission")
119119
})
120+
121+
t.Run("UserFields", func(t *testing.T) {
122+
reset()
123+
assert.NoError(t, createUser("u-FullNameWithSpace", "--random-password", "--fullname", "First O'Middle Last"))
124+
unittest.AssertExistsAndLoadBean(t, &user_model.User{
125+
Name: "u-FullNameWithSpace",
126+
LowerName: "u-fullnamewithspace",
127+
FullName: "First O'Middle Last",
128+
129+
})
130+
131+
assert.NoError(t, createUser("u-FullNameEmpty", "--random-password", "--fullname", ""))
132+
u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "u-fullnameempty"})
133+
assert.Empty(t, u.FullName)
134+
})
120135
}

custom/conf/app.example.ini

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -59,27 +59,16 @@ RUN_USER = ; git
5959
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
6060
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
6161
;;
62-
;; The protocol the server listens on. One of 'http', 'https', 'http+unix', 'fcgi' or 'fcgi+unix'. Defaults to 'http'
63-
;; Note: Value must be lowercase.
62+
;; The protocol the server listens on. One of "http", "https", "http+unix", "fcgi" or "fcgi+unix".
6463
;PROTOCOL = http
6564
;;
66-
;; Expect PROXY protocol headers on connections
67-
;USE_PROXY_PROTOCOL = false
68-
;;
69-
;; Use PROXY protocol in TLS Bridging mode
70-
;PROXY_PROTOCOL_TLS_BRIDGING = false
71-
;;
72-
; Timeout to wait for PROXY protocol header (set to 0 to have no timeout)
73-
;PROXY_PROTOCOL_HEADER_TIMEOUT=5s
74-
;;
75-
; Accept PROXY protocol headers with UNKNOWN type
76-
;PROXY_PROTOCOL_ACCEPT_UNKNOWN=false
77-
;;
78-
;; Set the domain for the server
65+
;; Set the domain for the server.
66+
;; Most users should set it to the real website domain of their Gitea instance.
7967
;DOMAIN = localhost
8068
;;
8169
;; The AppURL used by Gitea to generate absolute links, defaults to "{PROTOCOL}://{DOMAIN}:{HTTP_PORT}/".
82-
;; Most users should set it to the real website URL of their Gitea instance.
70+
;; Most users should set it to the real website URL of their Gitea instance when there is a reverse proxy.
71+
;; When it is empty, Gitea will use HTTP "Host" header to generate ROOT_URL, and fall back to the default one if no "Host" header.
8372
;ROOT_URL =
8473
;;
8574
;; For development purpose only. It makes Gitea handle sub-path ("/sub-path/owner/repo/...") directly when debugging without a reverse proxy.
@@ -90,13 +79,25 @@ RUN_USER = ; git
9079
;STATIC_URL_PREFIX =
9180
;;
9281
;; The address to listen on. Either a IPv4/IPv6 address or the path to a unix socket.
93-
;; If PROTOCOL is set to `http+unix` or `fcgi+unix`, this should be the name of the Unix socket file to use.
82+
;; If PROTOCOL is set to "http+unix" or "fcgi+unix", this should be the name of the Unix socket file to use.
9483
;; Relative paths will be made absolute against the _`AppWorkPath`_.
9584
;HTTP_ADDR = 0.0.0.0
9685
;;
97-
;; The port to listen on. Leave empty when using a unix socket.
86+
;; The port to listen on for "http" or "https" protocol. Leave empty when using a unix socket.
9887
;HTTP_PORT = 3000
9988
;;
89+
;; Expect PROXY protocol headers on connections
90+
;USE_PROXY_PROTOCOL = false
91+
;;
92+
;; Use PROXY protocol in TLS Bridging mode
93+
;PROXY_PROTOCOL_TLS_BRIDGING = false
94+
;;
95+
;; Timeout to wait for PROXY protocol header (set to 0 to have no timeout)
96+
;PROXY_PROTOCOL_HEADER_TIMEOUT = 5s
97+
;;
98+
;; Accept PROXY protocol headers with UNKNOWN type
99+
;PROXY_PROTOCOL_ACCEPT_UNKNOWN = false
100+
;;
100101
;; If REDIRECT_OTHER_PORT is true, and PROTOCOL is set to https an http server
101102
;; will be started on PORT_TO_REDIRECT and it will redirect plain, non-secure http requests to the main
102103
;; ROOT_URL. Defaults are false for REDIRECT_OTHER_PORT and 80 for

models/actions/runner.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"code.gitea.io/gitea/models/shared/types"
1515
user_model "code.gitea.io/gitea/models/user"
1616
"code.gitea.io/gitea/modules/optional"
17+
"code.gitea.io/gitea/modules/setting"
1718
"code.gitea.io/gitea/modules/timeutil"
1819
"code.gitea.io/gitea/modules/translation"
1920
"code.gitea.io/gitea/modules/util"
@@ -123,8 +124,15 @@ func (r *ActionRunner) IsOnline() bool {
123124
return false
124125
}
125126

126-
// Editable checks if the runner is editable by the user
127-
func (r *ActionRunner) Editable(ownerID, repoID int64) bool {
127+
// EditableInContext checks if the runner is editable by the "context" owner/repo
128+
// ownerID == 0 and repoID == 0 means "admin" context, any runner including global runners could be edited
129+
// ownerID == 0 and repoID != 0 means "repo" context, any runner belonging to the given repo could be edited
130+
// ownerID != 0 and repoID == 0 means "owner(org/user)" context, any runner belonging to the given user/org could be edited
131+
// ownerID != 0 and repoID != 0 means "owner" OR "repo" context, legacy behavior, but we should forbid using it
132+
func (r *ActionRunner) EditableInContext(ownerID, repoID int64) bool {
133+
if ownerID != 0 && repoID != 0 {
134+
setting.PanicInDevOrTesting("ownerID and repoID should not be both set")
135+
}
128136
if ownerID == 0 && repoID == 0 {
129137
return true
130138
}
@@ -168,6 +176,12 @@ func init() {
168176
db.RegisterModel(&ActionRunner{})
169177
}
170178

179+
// FindRunnerOptions
180+
// ownerID == 0 and repoID == 0 means any runner including global runners
181+
// repoID != 0 and WithAvailable == false means any runner for the given repo
182+
// repoID != 0 and WithAvailable == true means any runner for the given repo, parent user/org, and global runners
183+
// ownerID != 0 and repoID == 0 and WithAvailable == false means any runner for the given user/org
184+
// ownerID != 0 and repoID == 0 and WithAvailable == true means any runner for the given user/org and global runners
171185
type FindRunnerOptions struct {
172186
db.ListOptions
173187
IDs []int64

models/fixtures/action_runner.yml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
-
2+
id: 34346
3+
name: runner_to_be_deleted-user
4+
uuid: 3EF231BD-FBB7-4E4B-9602-E6F28363EF18
5+
token_hash: 3EF231BD-FBB7-4E4B-9602-E6F28363EF18
6+
version: "1.0.0"
7+
owner_id: 1
8+
repo_id: 0
9+
description: "This runner is going to be deleted"
10+
agent_labels: '["runner_to_be_deleted","linux"]'
11+
-
12+
id: 34347
13+
name: runner_to_be_deleted-org
14+
uuid: 3EF231BD-FBB7-4E4B-9602-E6F28363EF19
15+
token_hash: 3EF231BD-FBB7-4E4B-9602-E6F28363EF19
16+
version: "1.0.0"
17+
owner_id: 3
18+
repo_id: 0
19+
description: "This runner is going to be deleted"
20+
agent_labels: '["runner_to_be_deleted","linux"]'
21+
-
22+
id: 34348
23+
name: runner_to_be_deleted-repo1
24+
uuid: 3EF231BD-FBB7-4E4B-9602-E6F28363EF20
25+
token_hash: 3EF231BD-FBB7-4E4B-9602-E6F28363EF20
26+
version: "1.0.0"
27+
owner_id: 0
28+
repo_id: 1
29+
description: "This runner is going to be deleted"
30+
agent_labels: '["runner_to_be_deleted","linux"]'
31+
-
32+
id: 34349
33+
name: runner_to_be_deleted
34+
uuid: 3EF231BD-FBB7-4E4B-9602-E6F28363EF17
35+
token_hash: 3EF231BD-FBB7-4E4B-9602-E6F28363EF17
36+
version: "1.0.0"
37+
owner_id: 0
38+
repo_id: 0
39+
description: "This runner is going to be deleted"
40+
agent_labels: '["runner_to_be_deleted","linux"]'

models/issues/issue_lock.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,14 @@ import (
1212

1313
// IssueLockOptions defines options for locking and/or unlocking an issue/PR
1414
type IssueLockOptions struct {
15-
Doer *user_model.User
16-
Issue *Issue
15+
Doer *user_model.User
16+
Issue *Issue
17+
18+
// Reason is the doer-provided comment message for the locked issue
19+
// GitHub doesn't support changing the "reasons" by config file, so GitHub has pre-defined "reason" enum values.
20+
// Gitea is not like GitHub, it allows site admin to define customized "reasons" in the config file.
21+
// So the API caller might not know what kind of "reasons" are valid, and the customized reasons are not translatable.
22+
// To make things clear and simple: doer have the chance to use any reason they like, we do not do validation.
1723
Reason string
1824
}
1925

models/packages/package_version.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -279,9 +279,7 @@ func (opts *PackageSearchOptions) configureOrderBy(e db.Engine) {
279279
default:
280280
e.Desc("package_version.created_unix")
281281
}
282-
283-
// Sort by id for stable order with duplicates in the other field
284-
e.Asc("package_version.id")
282+
e.Desc("package_version.id") // Sort by id for stable order with duplicates in the other field
285283
}
286284

287285
// SearchVersions gets all versions of packages matching the search options

modules/httplib/url.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,16 @@ func GuessCurrentHostURL(ctx context.Context) string {
7070
// 1. The reverse proxy is configured correctly, it passes "X-Forwarded-Proto/Host" headers. Perfect, Gitea can handle it correctly.
7171
// 2. The reverse proxy is not configured correctly, doesn't pass "X-Forwarded-Proto/Host" headers, eg: only one "proxy_pass http://gitea:3000" in Nginx.
7272
// 3. There is no reverse proxy.
73-
// Without an extra config option, Gitea is impossible to distinguish between case 2 and case 3,
74-
// then case 2 would result in wrong guess like guessed AppURL becomes "http://gitea:3000/", which is not accessible by end users.
75-
// So in the future maybe it should introduce a new config option, to let site admin decide how to guess the AppURL.
73+
// Without more information, Gitea is impossible to distinguish between case 2 and case 3, then case 2 would result in
74+
// wrong guess like guessed AppURL becomes "http://gitea:3000/" behind a "https" reverse proxy, which is not accessible by end users.
75+
// So we introduced "UseHostHeader" option, it could be enabled by setting "ROOT_URL" to empty
7676
reqScheme := getRequestScheme(req)
7777
if reqScheme == "" {
78+
// if no reverse proxy header, try to use "Host" header for absolute URL
79+
if setting.UseHostHeader && req.Host != "" {
80+
return util.Iif(req.TLS == nil, "http://", "https://") + req.Host
81+
}
82+
// fall back to default AppURL
7883
return strings.TrimSuffix(setting.AppURL, setting.AppSubURL+"/")
7984
}
8085
// X-Forwarded-Host has many problems: non-standard, not well-defined (X-Forwarded-Port or not), conflicts with Host header.

modules/httplib/url_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package httplib
55

66
import (
77
"context"
8+
"crypto/tls"
89
"net/http"
910
"testing"
1011

@@ -39,6 +40,25 @@ func TestIsRelativeURL(t *testing.T) {
3940
}
4041
}
4142

43+
func TestGuessCurrentHostURL(t *testing.T) {
44+
defer test.MockVariableValue(&setting.AppURL, "http://cfg-host/sub/")()
45+
defer test.MockVariableValue(&setting.AppSubURL, "/sub")()
46+
defer test.MockVariableValue(&setting.UseHostHeader, false)()
47+
48+
ctx := t.Context()
49+
assert.Equal(t, "http://cfg-host", GuessCurrentHostURL(ctx))
50+
51+
ctx = context.WithValue(ctx, RequestContextKey, &http.Request{Host: "localhost:3000"})
52+
assert.Equal(t, "http://cfg-host", GuessCurrentHostURL(ctx))
53+
54+
defer test.MockVariableValue(&setting.UseHostHeader, true)()
55+
ctx = context.WithValue(ctx, RequestContextKey, &http.Request{Host: "http-host:3000"})
56+
assert.Equal(t, "http://http-host:3000", GuessCurrentHostURL(ctx))
57+
58+
ctx = context.WithValue(ctx, RequestContextKey, &http.Request{Host: "http-host", TLS: &tls.ConnectionState{}})
59+
assert.Equal(t, "https://http-host", GuessCurrentHostURL(ctx))
60+
}
61+
4262
func TestMakeAbsoluteURL(t *testing.T) {
4363
defer test.MockVariableValue(&setting.Protocol, "http")()
4464
defer test.MockVariableValue(&setting.AppURL, "http://cfg-host/sub/")()

modules/markup/html.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@ var globalVars = sync.OnceValue(func() *globalVarsType {
7171
// it is still accepted by the CommonMark specification, as well as the HTML5 spec:
7272
// http://spec.commonmark.org/0.28/#email-address
7373
// https://html.spec.whatwg.org/multipage/input.html#e-mail-state-(type%3Demail)
74-
v.emailRegex = regexp.MustCompile("(?:\\s|^|\\(|\\[)([a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9]{2,}(?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+)(?:\\s|$|\\)|\\]|;|,|\\?|!|\\.(\\s|$))")
74+
// At the moment, we use stricter rule for rendering purpose: only allow the "name" part starting after the word boundary
75+
v.emailRegex = regexp.MustCompile(`\b([-\w.!#$%&'*+/=?^{|}~]*@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9]{2,}(?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+)\b`)
7576

7677
// emojiShortCodeRegex find emoji by alias like :smile:
7778
v.emojiShortCodeRegex = regexp.MustCompile(`:[-+\w]+:`)

modules/markup/html_email.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@
33

44
package markup
55

6-
import "golang.org/x/net/html"
6+
import (
7+
"strings"
8+
9+
"golang.org/x/net/html"
10+
)
711

812
// emailAddressProcessor replaces raw email addresses with a mailto: link.
913
func emailAddressProcessor(ctx *RenderContext, node *html.Node) {
@@ -14,6 +18,14 @@ func emailAddressProcessor(ctx *RenderContext, node *html.Node) {
1418
return
1519
}
1620

21+
var nextByte byte
22+
if len(node.Data) > m[3] {
23+
nextByte = node.Data[m[3]]
24+
}
25+
if strings.IndexByte(":/", nextByte) != -1 {
26+
// for cases: "[email protected]:owner/repo.git", "https://[email protected]/owner/repo.git"
27+
return
28+
}
1729
mail := node.Data[m[2]:m[3]]
1830
replaceContent(node, m[2], m[3], createLink(ctx, "mailto:"+mail, mail, "" /*mailto*/))
1931
node = node.NextSibling.NextSibling

0 commit comments

Comments
 (0)