Skip to content

Commit e818de1

Browse files
authored
Refactor legacy code (#35708) (#35713)
Backport #35708
1 parent 0a87bf9 commit e818de1

File tree

15 files changed

+407
-236
lines changed

15 files changed

+407
-236
lines changed

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"
@@ -31,7 +30,6 @@ import (
3130
"code.gitea.io/gitea/modules/setting"
3231
"code.gitea.io/gitea/services/lfs"
3332

34-
"github.com/golang-jwt/jwt/v5"
3533
"github.com/kballard/go-shellquote"
3634
"github.com/urfave/cli/v2"
3735
)
@@ -131,27 +129,6 @@ func getAccessMode(verb, lfsVerb string) perm.AccessMode {
131129
return perm.AccessModeNone
132130
}
133131

134-
func getLFSAuthToken(ctx context.Context, lfsVerb string, results *private.ServCommandResults) (string, error) {
135-
now := time.Now()
136-
claims := lfs.Claims{
137-
RegisteredClaims: jwt.RegisteredClaims{
138-
ExpiresAt: jwt.NewNumericDate(now.Add(setting.LFS.HTTPAuthExpiry)),
139-
NotBefore: jwt.NewNumericDate(now),
140-
},
141-
RepoID: results.RepoID,
142-
Op: lfsVerb,
143-
UserID: results.UserID,
144-
}
145-
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
146-
147-
// Sign and get the complete encoded token as a string using the secret
148-
tokenString, err := token.SignedString(setting.LFS.JWTSecretBytes)
149-
if err != nil {
150-
return "", fail(ctx, "Failed to sign JWT Token", "Failed to sign JWT token: %v", err)
151-
}
152-
return "Bearer " + tokenString, nil
153-
}
154-
155132
func runServ(c *cli.Context) error {
156133
ctx, cancel := installSignals()
157134
defer cancel()
@@ -284,7 +261,7 @@ func runServ(c *cli.Context) error {
284261

285262
// LFS SSH protocol
286263
if verb == git.CmdVerbLfsTransfer {
287-
token, err := getLFSAuthToken(ctx, lfsVerb, results)
264+
token, err := lfs.GetLFSAuthTokenWithBearer(lfs.AuthTokenOptions{Op: lfsVerb, UserID: results.UserID, RepoID: results.RepoID})
288265
if err != nil {
289266
return err
290267
}
@@ -295,7 +272,7 @@ func runServ(c *cli.Context) error {
295272
if verb == git.CmdVerbLfsAuthenticate {
296273
url := fmt.Sprintf("%s%s/%s.git/info/lfs", setting.AppURL, url.PathEscape(results.OwnerName), url.PathEscape(results.RepoName))
297274

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

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/repo/upload.go

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -137,16 +137,9 @@ func DeleteUploads(ctx context.Context, uploads ...*Upload) (err error) {
137137

138138
for _, upload := range uploads {
139139
localPath := upload.LocalPath()
140-
isFile, err := util.IsFile(localPath)
141-
if err != nil {
142-
log.Error("Unable to check if %s is a file. Error: %v", localPath, err)
143-
}
144-
if !isFile {
145-
continue
146-
}
147-
148140
if err := util.Remove(localPath); err != nil {
149-
return fmt.Errorf("remove upload: %w", err)
141+
// just continue, don't fail the whole operation if a file is missing (removed by others)
142+
log.Error("unable to remove upload file %s: %v", localPath, err)
150143
}
151144
}
152145

modules/git/hook.go

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -51,30 +51,16 @@ func GetHook(repoPath, name string) (*Hook, error) {
5151
name: name,
5252
path: filepath.Join(repoPath, "hooks", name+".d", name),
5353
}
54-
isFile, err := util.IsFile(h.path)
55-
if err != nil {
56-
return nil, err
57-
}
58-
if isFile {
59-
data, err := os.ReadFile(h.path)
60-
if err != nil {
61-
return nil, err
62-
}
54+
if data, err := os.ReadFile(h.path); err == nil {
6355
h.IsActive = true
6456
h.Content = string(data)
6557
return h, nil
58+
} else if !os.IsNotExist(err) {
59+
return nil, err
6660
}
6761

6862
samplePath := filepath.Join(repoPath, "hooks", name+".sample")
69-
isFile, err = util.IsFile(samplePath)
70-
if err != nil {
71-
return nil, err
72-
}
73-
if isFile {
74-
data, err := os.ReadFile(samplePath)
75-
if err != nil {
76-
return nil, err
77-
}
63+
if data, err := os.ReadFile(samplePath); err == nil {
7864
h.Sample = string(data)
7965
}
8066
return h, nil

modules/setting/config_provider.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -202,11 +202,11 @@ func NewConfigProviderFromFile(file string) (ConfigProvider, error) {
202202
loadedFromEmpty := true
203203

204204
if file != "" {
205-
isFile, err := util.IsFile(file)
205+
isExist, err := util.IsExist(file)
206206
if err != nil {
207-
return nil, fmt.Errorf("unable to check if %q is a file. Error: %v", file, err)
207+
return nil, fmt.Errorf("unable to check if %q exists: %v", file, err)
208208
}
209-
if isFile {
209+
if isExist {
210210
if err = cfg.Append(file); err != nil {
211211
return nil, fmt.Errorf("failed to load config file %q: %v", file, err)
212212
}

modules/util/path.go

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -115,15 +115,10 @@ func IsDir(dir string) (bool, error) {
115115
return false, err
116116
}
117117

118-
// IsFile returns true if given path is a file,
119-
// or returns false when it's a directory or does not exist.
120-
func IsFile(filePath string) (bool, error) {
121-
f, err := os.Stat(filePath)
118+
func IsRegularFile(filePath string) (bool, error) {
119+
f, err := os.Lstat(filePath)
122120
if err == nil {
123-
return !f.IsDir(), nil
124-
}
125-
if os.IsNotExist(err) {
126-
return false, nil
121+
return f.Mode().IsRegular(), nil
127122
}
128123
return false, err
129124
}

routers/private/key.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ func UpdatePublicKeyInRepo(ctx *context.PrivateContext) {
4545
ctx.PlainText(http.StatusOK, "success")
4646
}
4747

48-
// AuthorizedPublicKeyByContent searches content as prefix (leak e-mail part)
48+
// AuthorizedPublicKeyByContent searches content as prefix (without comment part)
4949
// and returns public key found.
5050
func AuthorizedPublicKeyByContent(ctx *context.PrivateContext) {
5151
content := ctx.FormString("content")
@@ -57,5 +57,14 @@ func AuthorizedPublicKeyByContent(ctx *context.PrivateContext) {
5757
})
5858
return
5959
}
60-
ctx.PlainText(http.StatusOK, publicKey.AuthorizedString())
60+
61+
authorizedString, err := asymkey_model.AuthorizedStringForKey(publicKey)
62+
if err != nil {
63+
ctx.JSON(http.StatusInternalServerError, private.Response{
64+
Err: err.Error(),
65+
UserMsg: "invalid public key",
66+
})
67+
return
68+
}
69+
ctx.PlainText(http.StatusOK, authorizedString)
6170
}

services/asymkey/ssh_key_authorized_principals.go

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,7 @@ import (
2525
// There is a dependence on the database within RewriteAllPrincipalKeys & RegeneratePrincipalKeys
2626
// The sshOpLocker is used from ssh_key_authorized_keys.go
2727

28-
const (
29-
authorizedPrincipalsFile = "authorized_principals"
30-
tplCommentPrefix = `# gitea public key`
31-
)
28+
const authorizedPrincipalsFile = "authorized_principals"
3229

3330
// RewriteAllPrincipalKeys removes any authorized principal and rewrite all keys from database again.
3431
// Note: db.GetEngine(ctx).Iterate does not get latest data after insert/delete, so we have to call this function
@@ -90,10 +87,9 @@ func rewriteAllPrincipalKeys(ctx context.Context) error {
9087
return util.Rename(tmpPath, fPath)
9188
}
9289

93-
func regeneratePrincipalKeys(ctx context.Context, t io.StringWriter) error {
90+
func regeneratePrincipalKeys(ctx context.Context, t io.Writer) error {
9491
if err := db.GetEngine(ctx).Where("type = ?", asymkey_model.KeyTypePrincipal).Iterate(new(asymkey_model.PublicKey), func(idx int, bean any) (err error) {
95-
_, err = t.WriteString((bean.(*asymkey_model.PublicKey)).AuthorizedString())
96-
return err
92+
return asymkey_model.WriteAuthorizedStringForValidKey(bean.(*asymkey_model.PublicKey), t)
9793
}); err != nil {
9894
return err
9995
}
@@ -114,11 +110,11 @@ func regeneratePrincipalKeys(ctx context.Context, t io.StringWriter) error {
114110
scanner := bufio.NewScanner(f)
115111
for scanner.Scan() {
116112
line := scanner.Text()
117-
if strings.HasPrefix(line, tplCommentPrefix) {
113+
if strings.HasPrefix(line, asymkey_model.AuthorizedStringCommentPrefix) {
118114
scanner.Scan()
119115
continue
120116
}
121-
_, err = t.WriteString(line + "\n")
117+
_, err = io.WriteString(t, line+"\n")
122118
if err != nil {
123119
return err
124120
}

services/doctor/authorizedkeys.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@ import (
2020
asymkey_service "code.gitea.io/gitea/services/asymkey"
2121
)
2222

23-
const tplCommentPrefix = `# gitea public key`
24-
2523
func checkAuthorizedKeys(ctx context.Context, logger log.Logger, autofix bool) error {
2624
if setting.SSH.StartBuiltinServer || !setting.SSH.CreateAuthorizedKeysFile {
2725
return nil
@@ -47,7 +45,7 @@ func checkAuthorizedKeys(ctx context.Context, logger log.Logger, autofix bool) e
4745
scanner := bufio.NewScanner(f)
4846
for scanner.Scan() {
4947
line := scanner.Text()
50-
if strings.HasPrefix(line, tplCommentPrefix) {
48+
if strings.HasPrefix(line, asymkey_model.AuthorizedStringCommentPrefix) {
5149
continue
5250
}
5351
linesInAuthorizedKeys.Add(line)
@@ -67,7 +65,7 @@ func checkAuthorizedKeys(ctx context.Context, logger log.Logger, autofix bool) e
6765
scanner = bufio.NewScanner(regenerated)
6866
for scanner.Scan() {
6967
line := scanner.Text()
70-
if strings.HasPrefix(line, tplCommentPrefix) {
68+
if strings.HasPrefix(line, asymkey_model.AuthorizedStringCommentPrefix) {
7169
continue
7270
}
7371
if linesInAuthorizedKeys.Contains(line) {

0 commit comments

Comments
 (0)