Skip to content

Commit 7530aa8

Browse files
authored
Merge branch 'main' into optimize-access-refresh-clean
2 parents 06fd3ec + cab35ff commit 7530aa8

File tree

122 files changed

+3023
-2005
lines changed

Some content is hidden

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

122 files changed

+3023
-2005
lines changed

Makefile

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,11 @@ XGO_VERSION := go-1.25.x
3131

3232
AIR_PACKAGE ?= github.com/air-verse/air@v1
3333
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3
34-
GOFUMPT_PACKAGE ?= mvdan.cc/[email protected].1
35-
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.4.0
34+
GOFUMPT_PACKAGE ?= mvdan.cc/[email protected].2
35+
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.5.0
3636
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/[email protected]
3737
MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/[email protected]
38-
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@717e3cb29becaaf00e56953556c6d80f8a01b286
38+
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.33.1
3939
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
4040
GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1
4141
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1

cmd/serv.go

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import (
1313
"path/filepath"
1414
"strconv"
1515
"strings"
16-
"time"
1716
"unicode"
1817

1918
asymkey_model "code.gitea.io/gitea/models/asymkey"
@@ -32,7 +31,6 @@ import (
3231
"code.gitea.io/gitea/modules/setting"
3332
"code.gitea.io/gitea/services/lfs"
3433

35-
"github.com/golang-jwt/jwt/v5"
3634
"github.com/kballard/go-shellquote"
3735
"github.com/urfave/cli/v3"
3836
)
@@ -133,27 +131,6 @@ func getAccessMode(verb, lfsVerb string) perm.AccessMode {
133131
return perm.AccessModeNone
134132
}
135133

136-
func getLFSAuthToken(ctx context.Context, lfsVerb string, results *private.ServCommandResults) (string, error) {
137-
now := time.Now()
138-
claims := lfs.Claims{
139-
RegisteredClaims: jwt.RegisteredClaims{
140-
ExpiresAt: jwt.NewNumericDate(now.Add(setting.LFS.HTTPAuthExpiry)),
141-
NotBefore: jwt.NewNumericDate(now),
142-
},
143-
RepoID: results.RepoID,
144-
Op: lfsVerb,
145-
UserID: results.UserID,
146-
}
147-
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
148-
149-
// Sign and get the complete encoded token as a string using the secret
150-
tokenString, err := token.SignedString(setting.LFS.JWTSecretBytes)
151-
if err != nil {
152-
return "", fail(ctx, "Failed to sign JWT Token", "Failed to sign JWT token: %v", err)
153-
}
154-
return "Bearer " + tokenString, nil
155-
}
156-
157134
func runServ(ctx context.Context, c *cli.Command) error {
158135
// FIXME: This needs to internationalised
159136
setup(ctx, c.Bool("debug"))
@@ -283,7 +260,7 @@ func runServ(ctx context.Context, c *cli.Command) error {
283260

284261
// LFS SSH protocol
285262
if verb == git.CmdVerbLfsTransfer {
286-
token, err := getLFSAuthToken(ctx, lfsVerb, results)
263+
token, err := lfs.GetLFSAuthTokenWithBearer(lfs.AuthTokenOptions{Op: lfsVerb, UserID: results.UserID, RepoID: results.RepoID})
287264
if err != nil {
288265
return err
289266
}
@@ -294,7 +271,7 @@ func runServ(ctx context.Context, c *cli.Command) error {
294271
if verb == git.CmdVerbLfsAuthenticate {
295272
url := fmt.Sprintf("%s%s/%s.git/info/lfs", setting.AppURL, url.PathEscape(results.OwnerName), url.PathEscape(results.RepoName))
296273

297-
token, err := getLFSAuthToken(ctx, lfsVerb, results)
274+
token, err := lfs.GetLFSAuthTokenWithBearer(lfs.AuthTokenOptions{Op: lfsVerb, UserID: results.UserID, RepoID: results.RepoID})
298275
if err != nil {
299276
return err
300277
}

custom/conf/app.example.ini

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1343,6 +1343,10 @@ LEVEL = Info
13431343
;; Dont mistake it for Reactions.
13441344
;CUSTOM_EMOJIS = gitea, codeberg, gitlab, git, github, gogs
13451345
;;
1346+
;; Comma separated list of enabled emojis, for example: smile, thumbsup, thumbsdown
1347+
;; Leave it empty to enable all emojis.
1348+
;ENABLED_EMOJIS =
1349+
;;
13461350
;; Whether the full name of the users should be shown where possible. If the full name isn't set, the username will be used.
13471351
;DEFAULT_SHOW_FULL_NAME = false
13481352
;;
@@ -2536,7 +2540,19 @@ LEVEL = Info
25362540
;; * sanitized: Sanitize the content and render it inside current page, default to only allow a few HTML tags and attributes. Customized sanitizer rules can be defined in [markup.sanitizer.*] .
25372541
;; * no-sanitizer: Disable the sanitizer and render the content inside current page. It's **insecure** and may lead to XSS attack if the content contains malicious code.
25382542
;; * iframe: Render the content in a separate standalone page and embed it into current page by iframe. The iframe is in sandbox mode with same-origin disabled, and the JS code are safely isolated from parent page.
2539-
;RENDER_CONTENT_MODE=sanitized
2543+
;RENDER_CONTENT_MODE = sanitized
2544+
;; The sandbox applied to the iframe and Content-Security-Policy header when RENDER_CONTENT_MODE is `iframe`.
2545+
;; It defaults to a safe set of "allow-*" restrictions (space separated).
2546+
;; You can also set it by your requirements or use "disabled" to disable the sandbox completely.
2547+
;; When set it, make sure there is no security risk:
2548+
;; * PDF-only content: generally safe to use "disabled", and it needs to be "disabled" because PDF only renders with no sandbox.
2549+
;; * HTML content with JS: if the "RENDER_COMMAND" can guarantee there is no XSS, then it is safe, otherwise, you need to fine tune the "allow-*" restrictions.
2550+
;RENDER_CONTENT_SANDBOX =
2551+
;; Whether post-process the rendered HTML content, including:
2552+
;; resolve relative links and image sources, recognizing issue/commit references, escaping invisible characters,
2553+
;; mentioning users, rendering permlink code blocks, replacing emoji shorthands, etc.
2554+
;; By default, this is true when RENDER_CONTENT_MODE is `sanitized`, otherwise false.
2555+
;NEED_POST_PROCESS = false
25402556

25412557
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
25422558
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

models/admin/task.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
repo_model "code.gitea.io/gitea/models/repo"
1212
user_model "code.gitea.io/gitea/models/user"
1313
"code.gitea.io/gitea/modules/json"
14+
"code.gitea.io/gitea/modules/log"
1415
"code.gitea.io/gitea/modules/migration"
1516
"code.gitea.io/gitea/modules/secret"
1617
"code.gitea.io/gitea/modules/setting"
@@ -123,17 +124,17 @@ func (task *Task) MigrateConfig() (*migration.MigrateOptions, error) {
123124
// decrypt credentials
124125
if opts.CloneAddrEncrypted != "" {
125126
if opts.CloneAddr, err = secret.DecryptSecret(setting.SecretKey, opts.CloneAddrEncrypted); err != nil {
126-
return nil, err
127+
log.Error("Unable to decrypt CloneAddr, maybe SECRET_KEY is wrong: %v", err)
127128
}
128129
}
129130
if opts.AuthPasswordEncrypted != "" {
130131
if opts.AuthPassword, err = secret.DecryptSecret(setting.SecretKey, opts.AuthPasswordEncrypted); err != nil {
131-
return nil, err
132+
log.Error("Unable to decrypt AuthPassword, maybe SECRET_KEY is wrong: %v", err)
132133
}
133134
}
134135
if opts.AuthTokenEncrypted != "" {
135136
if opts.AuthToken, err = secret.DecryptSecret(setting.SecretKey, opts.AuthTokenEncrypted); err != nil {
136-
return nil, err
137+
log.Error("Unable to decrypt AuthToken, maybe SECRET_KEY is wrong: %v", err)
137138
}
138139
}
139140

models/asymkey/ssh_key.go

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -67,13 +67,6 @@ func (key *PublicKey) OmitEmail() string {
6767
return strings.Join(strings.Split(key.Content, " ")[:2], " ")
6868
}
6969

70-
// AuthorizedString returns formatted public key string for authorized_keys file.
71-
//
72-
// TODO: Consider dropping this function
73-
func (key *PublicKey) AuthorizedString() string {
74-
return AuthorizedStringForKey(key)
75-
}
76-
7770
func addKey(ctx context.Context, key *PublicKey) (err error) {
7871
if len(key.Fingerprint) == 0 {
7972
key.Fingerprint, err = CalcFingerprint(key.Content)

models/asymkey/ssh_key_authorized_keys.go

Lines changed: 42 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -17,30 +17,14 @@ import (
1717
"code.gitea.io/gitea/modules/log"
1818
"code.gitea.io/gitea/modules/setting"
1919
"code.gitea.io/gitea/modules/util"
20-
)
2120

22-
// _____ __ .__ .__ .___
23-
// / _ \ __ ___/ |_| |__ ___________|__|_______ ____ __| _/
24-
// / /_\ \| | \ __\ | \ / _ \_ __ \ \___ // __ \ / __ |
25-
// / | \ | /| | | Y ( <_> ) | \/ |/ /\ ___// /_/ |
26-
// \____|__ /____/ |__| |___| /\____/|__| |__/_____ \\___ >____ |
27-
// \/ \/ \/ \/ \/
28-
// ____ __.
29-
// | |/ _|____ ___.__. ______
30-
// | <_/ __ < | |/ ___/
31-
// | | \ ___/\___ |\___ \
32-
// |____|__ \___ > ____/____ >
33-
// \/ \/\/ \/
34-
//
35-
// This file contains functions for creating authorized_keys files
36-
//
37-
// There is a dependence on the database within RegeneratePublicKeys however most of these functions probably belong in a module
38-
39-
const (
40-
tplCommentPrefix = `# gitea public key`
41-
tplPublicKey = tplCommentPrefix + "\n" + `command=%s,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,no-user-rc,restrict %s` + "\n"
21+
"golang.org/x/crypto/ssh"
4222
)
4323

24+
// AuthorizedStringCommentPrefix is a magic tag
25+
// some functions like RegeneratePublicKeys needs this tag to skip the keys generated by Gitea, while keep other keys
26+
const AuthorizedStringCommentPrefix = `# gitea public key`
27+
4428
var sshOpLocker sync.Mutex
4529

4630
func WithSSHOpLocker(f func() error) error {
@@ -50,17 +34,45 @@ func WithSSHOpLocker(f func() error) error {
5034
}
5135

5236
// AuthorizedStringForKey creates the authorized keys string appropriate for the provided key
53-
func AuthorizedStringForKey(key *PublicKey) string {
37+
func AuthorizedStringForKey(key *PublicKey) (string, error) {
5438
sb := &strings.Builder{}
55-
_ = setting.SSH.AuthorizedKeysCommandTemplateTemplate.Execute(sb, map[string]any{
39+
_, err := writeAuthorizedStringForKey(key, sb)
40+
return sb.String(), err
41+
}
42+
43+
// WriteAuthorizedStringForValidKey writes the authorized key for the provided key. If the key is invalid, it does nothing.
44+
func WriteAuthorizedStringForValidKey(key *PublicKey, w io.Writer) error {
45+
validKey, err := writeAuthorizedStringForKey(key, w)
46+
if !validKey {
47+
log.Debug("WriteAuthorizedStringForValidKey: key %s is not valid: %v", key, err)
48+
return nil
49+
}
50+
return err
51+
}
52+
53+
func writeAuthorizedStringForKey(key *PublicKey, w io.Writer) (keyValid bool, err error) {
54+
const tpl = AuthorizedStringCommentPrefix + "\n" + `command=%s,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,no-user-rc,restrict %s %s` + "\n"
55+
pubKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(key.Content))
56+
if err != nil {
57+
return false, err
58+
}
59+
// now the key is valid, the code below could only return template/IO related errors
60+
sbCmd := &strings.Builder{}
61+
err = setting.SSH.AuthorizedKeysCommandTemplateTemplate.Execute(sbCmd, map[string]any{
5662
"AppPath": util.ShellEscape(setting.AppPath),
5763
"AppWorkPath": util.ShellEscape(setting.AppWorkPath),
5864
"CustomConf": util.ShellEscape(setting.CustomConf),
5965
"CustomPath": util.ShellEscape(setting.CustomPath),
6066
"Key": key,
6167
})
62-
63-
return fmt.Sprintf(tplPublicKey, util.ShellEscape(sb.String()), key.Content)
68+
if err != nil {
69+
return true, err
70+
}
71+
sshCommandEscaped := util.ShellEscape(sbCmd.String())
72+
sshKeyMarshalled := strings.TrimSpace(string(ssh.MarshalAuthorizedKey(pubKey)))
73+
sshKeyComment := fmt.Sprintf("user-%d", key.OwnerID)
74+
_, err = fmt.Fprintf(w, tpl, sshCommandEscaped, sshKeyMarshalled, sshKeyComment)
75+
return true, err
6476
}
6577

6678
// appendAuthorizedKeysToFile appends new SSH keys' content to authorized_keys file.
@@ -112,18 +124,17 @@ func appendAuthorizedKeysToFile(keys ...*PublicKey) error {
112124
if key.Type == KeyTypePrincipal {
113125
continue
114126
}
115-
if _, err = f.WriteString(key.AuthorizedString()); err != nil {
127+
if err = WriteAuthorizedStringForValidKey(key, f); err != nil {
116128
return err
117129
}
118130
}
119131
return nil
120132
}
121133

122134
// RegeneratePublicKeys regenerates the authorized_keys file
123-
func RegeneratePublicKeys(ctx context.Context, t io.StringWriter) error {
135+
func RegeneratePublicKeys(ctx context.Context, t io.Writer) error {
124136
if err := db.GetEngine(ctx).Where("type != ?", KeyTypePrincipal).Iterate(new(PublicKey), func(idx int, bean any) (err error) {
125-
_, err = t.WriteString((bean.(*PublicKey)).AuthorizedString())
126-
return err
137+
return WriteAuthorizedStringForValidKey(bean.(*PublicKey), t)
127138
}); err != nil {
128139
return err
129140
}
@@ -144,11 +155,11 @@ func RegeneratePublicKeys(ctx context.Context, t io.StringWriter) error {
144155
scanner := bufio.NewScanner(f)
145156
for scanner.Scan() {
146157
line := scanner.Text()
147-
if strings.HasPrefix(line, tplCommentPrefix) {
158+
if strings.HasPrefix(line, AuthorizedStringCommentPrefix) {
148159
scanner.Scan()
149160
continue
150161
}
151-
_, err = t.WriteString(line + "\n")
162+
_, err = io.WriteString(t, line+"\n")
152163
if err != nil {
153164
return err
154165
}

models/auth/twofactor.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,11 +111,11 @@ func (t *TwoFactor) SetSecret(secretString string) error {
111111
func (t *TwoFactor) ValidateTOTP(passcode string) (bool, error) {
112112
decodedStoredSecret, err := base64.StdEncoding.DecodeString(t.Secret)
113113
if err != nil {
114-
return false, err
114+
return false, fmt.Errorf("ValidateTOTP invalid base64: %w", err)
115115
}
116116
secretBytes, err := secret.AesDecrypt(t.getEncryptionKey(), decodedStoredSecret)
117117
if err != nil {
118-
return false, err
118+
return false, fmt.Errorf("ValidateTOTP unable to decrypt (maybe SECRET_KEY is wrong): %w", err)
119119
}
120120
secretStr := string(secretBytes)
121121
return totp.Validate(passcode, secretStr), nil

models/git/commit_status.go

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,21 @@ import (
3030

3131
// CommitStatus holds a single Status of a single Commit
3232
type CommitStatus struct {
33-
ID int64 `xorm:"pk autoincr"`
34-
Index int64 `xorm:"INDEX UNIQUE(repo_sha_index)"`
35-
RepoID int64 `xorm:"INDEX UNIQUE(repo_sha_index)"`
36-
Repo *repo_model.Repository `xorm:"-"`
37-
State commitstatus.CommitStatusState `xorm:"VARCHAR(7) NOT NULL"`
38-
SHA string `xorm:"VARCHAR(64) NOT NULL INDEX UNIQUE(repo_sha_index)"`
39-
TargetURL string `xorm:"TEXT"`
40-
Description string `xorm:"TEXT"`
41-
ContextHash string `xorm:"VARCHAR(64) index"`
42-
Context string `xorm:"TEXT"`
43-
Creator *user_model.User `xorm:"-"`
33+
ID int64 `xorm:"pk autoincr"`
34+
Index int64 `xorm:"INDEX UNIQUE(repo_sha_index)"`
35+
RepoID int64 `xorm:"INDEX UNIQUE(repo_sha_index)"`
36+
Repo *repo_model.Repository `xorm:"-"`
37+
State commitstatus.CommitStatusState `xorm:"VARCHAR(7) NOT NULL"`
38+
SHA string `xorm:"VARCHAR(64) NOT NULL INDEX UNIQUE(repo_sha_index)"`
39+
40+
// TargetURL points to the commit status page reported by a CI system
41+
// If Gitea Actions is used, it is a relative link like "{RepoLink}/actions/runs/{RunID}/jobs{JobID}"
42+
TargetURL string `xorm:"TEXT"`
43+
44+
Description string `xorm:"TEXT"`
45+
ContextHash string `xorm:"VARCHAR(64) index"`
46+
Context string `xorm:"TEXT"`
47+
Creator *user_model.User `xorm:"-"`
4448
CreatorID int64
4549

4650
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
@@ -211,21 +215,45 @@ func (status *CommitStatus) LocaleString(lang translation.Locale) string {
211215

212216
// HideActionsURL set `TargetURL` to an empty string if the status comes from Gitea Actions
213217
func (status *CommitStatus) HideActionsURL(ctx context.Context) {
218+
if _, ok := status.cutTargetURLGiteaActionsPrefix(ctx); ok {
219+
status.TargetURL = ""
220+
}
221+
}
222+
223+
func (status *CommitStatus) cutTargetURLGiteaActionsPrefix(ctx context.Context) (string, bool) {
214224
if status.RepoID == 0 {
215-
return
225+
return "", false
216226
}
217227

218228
if status.Repo == nil {
219229
if err := status.loadRepository(ctx); err != nil {
220230
log.Error("loadRepository: %v", err)
221-
return
231+
return "", false
222232
}
223233
}
224234

225235
prefix := status.Repo.Link() + "/actions"
226-
if strings.HasPrefix(status.TargetURL, prefix) {
227-
status.TargetURL = ""
236+
return strings.CutPrefix(status.TargetURL, prefix)
237+
}
238+
239+
// ParseGiteaActionsTargetURL parses the commit status target URL as Gitea Actions link
240+
func (status *CommitStatus) ParseGiteaActionsTargetURL(ctx context.Context) (runID, jobID int64, ok bool) {
241+
s, ok := status.cutTargetURLGiteaActionsPrefix(ctx)
242+
if !ok {
243+
return 0, 0, false
244+
}
245+
246+
parts := strings.Split(s, "/") // expect: /runs/{runID}/jobs/{jobID}
247+
if len(parts) < 5 || parts[1] != "runs" || parts[3] != "jobs" {
248+
return 0, 0, false
249+
}
250+
251+
runID, err1 := strconv.ParseInt(parts[2], 10, 64)
252+
jobID, err2 := strconv.ParseInt(parts[4], 10, 64)
253+
if err1 != nil || err2 != nil {
254+
return 0, 0, false
228255
}
256+
return runID, jobID, true
229257
}
230258

231259
// CalcCommitStatus returns commit status state via some status, the commit statues should order by id desc

0 commit comments

Comments
 (0)