Skip to content

Commit d07ae65

Browse files
committed
Merge remote-tracking branch 'giteaofficial/main'
* giteaofficial/main: Don't include link of deleted branch when listing branches (go-gitea#31028) [skip ci] Updated translations via Crowdin Refactor sha1 and time-limited code (go-gitea#31023) Return `access_denied` error when an OAuth2 request is denied (go-gitea#30974) Avoid 500 panic error when uploading invalid maven package file (go-gitea#31014) Fix incorrect "blob excerpt" link when comparing files (go-gitea#31013) Fix project column title overflow (go-gitea#31011) Fix data-race during testing (go-gitea#30999)
2 parents ab16ce1 + 1007ce7 commit d07ae65

File tree

26 files changed

+260
-151
lines changed

26 files changed

+260
-151
lines changed

models/unit/unit.go

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"errors"
88
"fmt"
99
"strings"
10+
"sync/atomic"
1011

1112
"code.gitea.io/gitea/models/perm"
1213
"code.gitea.io/gitea/modules/container"
@@ -106,10 +107,23 @@ var (
106107
TypeExternalTracker,
107108
}
108109

109-
// DisabledRepoUnits contains the units that have been globally disabled
110-
DisabledRepoUnits = []Type{}
110+
disabledRepoUnitsAtomic atomic.Pointer[[]Type] // the units that have been globally disabled
111111
)
112112

113+
// DisabledRepoUnitsGet returns the globally disabled units, it is a quick patch to fix data-race during testing.
114+
// Because the queue worker might read when a test is mocking the value. FIXME: refactor to a clear solution later.
115+
func DisabledRepoUnitsGet() []Type {
116+
v := disabledRepoUnitsAtomic.Load()
117+
if v == nil {
118+
return nil
119+
}
120+
return *v
121+
}
122+
123+
func DisabledRepoUnitsSet(v []Type) {
124+
disabledRepoUnitsAtomic.Store(&v)
125+
}
126+
113127
// Get valid set of default repository units from settings
114128
func validateDefaultRepoUnits(defaultUnits, settingDefaultUnits []Type) []Type {
115129
units := defaultUnits
@@ -127,7 +141,7 @@ func validateDefaultRepoUnits(defaultUnits, settingDefaultUnits []Type) []Type {
127141
}
128142

129143
// Remove disabled units
130-
for _, disabledUnit := range DisabledRepoUnits {
144+
for _, disabledUnit := range DisabledRepoUnitsGet() {
131145
for i, unit := range units {
132146
if unit == disabledUnit {
133147
units = append(units[:i], units[i+1:]...)
@@ -140,11 +154,11 @@ func validateDefaultRepoUnits(defaultUnits, settingDefaultUnits []Type) []Type {
140154

141155
// LoadUnitConfig load units from settings
142156
func LoadUnitConfig() error {
143-
var invalidKeys []string
144-
DisabledRepoUnits, invalidKeys = FindUnitTypes(setting.Repository.DisabledRepoUnits...)
157+
disabledRepoUnits, invalidKeys := FindUnitTypes(setting.Repository.DisabledRepoUnits...)
145158
if len(invalidKeys) > 0 {
146159
log.Warn("Invalid keys in disabled repo units: %s", strings.Join(invalidKeys, ", "))
147160
}
161+
DisabledRepoUnitsSet(disabledRepoUnits)
148162

149163
setDefaultRepoUnits, invalidKeys := FindUnitTypes(setting.Repository.DefaultRepoUnits...)
150164
if len(invalidKeys) > 0 {
@@ -167,7 +181,7 @@ func LoadUnitConfig() error {
167181

168182
// UnitGlobalDisabled checks if unit type is global disabled
169183
func (u Type) UnitGlobalDisabled() bool {
170-
for _, ud := range DisabledRepoUnits {
184+
for _, ud := range DisabledRepoUnitsGet() {
171185
if u == ud {
172186
return true
173187
}

models/unit/unit_test.go

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ import (
1414
func TestLoadUnitConfig(t *testing.T) {
1515
t.Run("regular", func(t *testing.T) {
1616
defer func(disabledRepoUnits, defaultRepoUnits, defaultForkRepoUnits []Type) {
17-
DisabledRepoUnits = disabledRepoUnits
17+
DisabledRepoUnitsSet(disabledRepoUnits)
1818
DefaultRepoUnits = defaultRepoUnits
1919
DefaultForkRepoUnits = defaultForkRepoUnits
20-
}(DisabledRepoUnits, DefaultRepoUnits, DefaultForkRepoUnits)
20+
}(DisabledRepoUnitsGet(), DefaultRepoUnits, DefaultForkRepoUnits)
2121
defer func(disabledRepoUnits, defaultRepoUnits, defaultForkRepoUnits []string) {
2222
setting.Repository.DisabledRepoUnits = disabledRepoUnits
2323
setting.Repository.DefaultRepoUnits = defaultRepoUnits
@@ -28,16 +28,16 @@ func TestLoadUnitConfig(t *testing.T) {
2828
setting.Repository.DefaultRepoUnits = []string{"repo.code", "repo.releases", "repo.issues", "repo.pulls"}
2929
setting.Repository.DefaultForkRepoUnits = []string{"repo.releases"}
3030
assert.NoError(t, LoadUnitConfig())
31-
assert.Equal(t, []Type{TypeIssues}, DisabledRepoUnits)
31+
assert.Equal(t, []Type{TypeIssues}, DisabledRepoUnitsGet())
3232
assert.Equal(t, []Type{TypeCode, TypeReleases, TypePullRequests}, DefaultRepoUnits)
3333
assert.Equal(t, []Type{TypeReleases}, DefaultForkRepoUnits)
3434
})
3535
t.Run("invalid", func(t *testing.T) {
3636
defer func(disabledRepoUnits, defaultRepoUnits, defaultForkRepoUnits []Type) {
37-
DisabledRepoUnits = disabledRepoUnits
37+
DisabledRepoUnitsSet(disabledRepoUnits)
3838
DefaultRepoUnits = defaultRepoUnits
3939
DefaultForkRepoUnits = defaultForkRepoUnits
40-
}(DisabledRepoUnits, DefaultRepoUnits, DefaultForkRepoUnits)
40+
}(DisabledRepoUnitsGet(), DefaultRepoUnits, DefaultForkRepoUnits)
4141
defer func(disabledRepoUnits, defaultRepoUnits, defaultForkRepoUnits []string) {
4242
setting.Repository.DisabledRepoUnits = disabledRepoUnits
4343
setting.Repository.DefaultRepoUnits = defaultRepoUnits
@@ -48,16 +48,16 @@ func TestLoadUnitConfig(t *testing.T) {
4848
setting.Repository.DefaultRepoUnits = []string{"repo.code", "invalid.2", "repo.releases", "repo.issues", "repo.pulls"}
4949
setting.Repository.DefaultForkRepoUnits = []string{"invalid.3", "repo.releases"}
5050
assert.NoError(t, LoadUnitConfig())
51-
assert.Equal(t, []Type{TypeIssues}, DisabledRepoUnits)
51+
assert.Equal(t, []Type{TypeIssues}, DisabledRepoUnitsGet())
5252
assert.Equal(t, []Type{TypeCode, TypeReleases, TypePullRequests}, DefaultRepoUnits)
5353
assert.Equal(t, []Type{TypeReleases}, DefaultForkRepoUnits)
5454
})
5555
t.Run("duplicate", func(t *testing.T) {
5656
defer func(disabledRepoUnits, defaultRepoUnits, defaultForkRepoUnits []Type) {
57-
DisabledRepoUnits = disabledRepoUnits
57+
DisabledRepoUnitsSet(disabledRepoUnits)
5858
DefaultRepoUnits = defaultRepoUnits
5959
DefaultForkRepoUnits = defaultForkRepoUnits
60-
}(DisabledRepoUnits, DefaultRepoUnits, DefaultForkRepoUnits)
60+
}(DisabledRepoUnitsGet(), DefaultRepoUnits, DefaultForkRepoUnits)
6161
defer func(disabledRepoUnits, defaultRepoUnits, defaultForkRepoUnits []string) {
6262
setting.Repository.DisabledRepoUnits = disabledRepoUnits
6363
setting.Repository.DefaultRepoUnits = defaultRepoUnits
@@ -68,16 +68,16 @@ func TestLoadUnitConfig(t *testing.T) {
6868
setting.Repository.DefaultRepoUnits = []string{"repo.code", "repo.releases", "repo.issues", "repo.pulls", "repo.code"}
6969
setting.Repository.DefaultForkRepoUnits = []string{"repo.releases", "repo.releases"}
7070
assert.NoError(t, LoadUnitConfig())
71-
assert.Equal(t, []Type{TypeIssues}, DisabledRepoUnits)
71+
assert.Equal(t, []Type{TypeIssues}, DisabledRepoUnitsGet())
7272
assert.Equal(t, []Type{TypeCode, TypeReleases, TypePullRequests}, DefaultRepoUnits)
7373
assert.Equal(t, []Type{TypeReleases}, DefaultForkRepoUnits)
7474
})
7575
t.Run("empty_default", func(t *testing.T) {
7676
defer func(disabledRepoUnits, defaultRepoUnits, defaultForkRepoUnits []Type) {
77-
DisabledRepoUnits = disabledRepoUnits
77+
DisabledRepoUnitsSet(disabledRepoUnits)
7878
DefaultRepoUnits = defaultRepoUnits
7979
DefaultForkRepoUnits = defaultForkRepoUnits
80-
}(DisabledRepoUnits, DefaultRepoUnits, DefaultForkRepoUnits)
80+
}(DisabledRepoUnitsGet(), DefaultRepoUnits, DefaultForkRepoUnits)
8181
defer func(disabledRepoUnits, defaultRepoUnits, defaultForkRepoUnits []string) {
8282
setting.Repository.DisabledRepoUnits = disabledRepoUnits
8383
setting.Repository.DefaultRepoUnits = defaultRepoUnits
@@ -88,7 +88,7 @@ func TestLoadUnitConfig(t *testing.T) {
8888
setting.Repository.DefaultRepoUnits = []string{}
8989
setting.Repository.DefaultForkRepoUnits = []string{"repo.releases", "repo.releases"}
9090
assert.NoError(t, LoadUnitConfig())
91-
assert.Equal(t, []Type{TypeIssues}, DisabledRepoUnits)
91+
assert.Equal(t, []Type{TypeIssues}, DisabledRepoUnitsGet())
9292
assert.ElementsMatch(t, []Type{TypeCode, TypePullRequests, TypeReleases, TypeWiki, TypePackages, TypeProjects, TypeActions}, DefaultRepoUnits)
9393
assert.Equal(t, []Type{TypeReleases}, DefaultForkRepoUnits)
9494
})

models/user/email_address.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"net/mail"
1111
"regexp"
1212
"strings"
13+
"time"
1314

1415
"code.gitea.io/gitea/models/db"
1516
"code.gitea.io/gitea/modules/base"
@@ -353,14 +354,12 @@ func ChangeInactivePrimaryEmail(ctx context.Context, uid int64, oldEmailAddr, ne
353354

354355
// VerifyActiveEmailCode verifies active email code when active account
355356
func VerifyActiveEmailCode(ctx context.Context, code, email string) *EmailAddress {
356-
minutes := setting.Service.ActiveCodeLives
357-
358357
if user := GetVerifyUser(ctx, code); user != nil {
359358
// time limit code
360359
prefix := code[:base.TimeLimitCodeLength]
361360
data := fmt.Sprintf("%d%s%s%s%s", user.ID, email, user.LowerName, user.Passwd, user.Rands)
362361

363-
if base.VerifyTimeLimitCode(data, minutes, prefix) {
362+
if base.VerifyTimeLimitCode(time.Now(), data, setting.Service.ActiveCodeLives, prefix) {
364363
emailAddress := &EmailAddress{UID: user.ID, Email: email}
365364
if has, _ := db.GetEngine(ctx).Get(emailAddress); has {
366365
return emailAddress

models/user/user.go

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,7 @@ func (u *User) OrganisationLink() string {
304304
func (u *User) GenerateEmailActivateCode(email string) string {
305305
code := base.CreateTimeLimitCode(
306306
fmt.Sprintf("%d%s%s%s%s", u.ID, email, u.LowerName, u.Passwd, u.Rands),
307-
setting.Service.ActiveCodeLives, nil)
307+
setting.Service.ActiveCodeLives, time.Now(), nil)
308308

309309
// Add tail hex username
310310
code += hex.EncodeToString([]byte(u.LowerName))
@@ -791,14 +791,11 @@ func GetVerifyUser(ctx context.Context, code string) (user *User) {
791791

792792
// VerifyUserActiveCode verifies active code when active account
793793
func VerifyUserActiveCode(ctx context.Context, code string) (user *User) {
794-
minutes := setting.Service.ActiveCodeLives
795-
796794
if user = GetVerifyUser(ctx, code); user != nil {
797795
// time limit code
798796
prefix := code[:base.TimeLimitCodeLength]
799797
data := fmt.Sprintf("%d%s%s%s%s", user.ID, user.Email, user.LowerName, user.Passwd, user.Rands)
800-
801-
if base.VerifyTimeLimitCode(data, minutes, prefix) {
798+
if base.VerifyTimeLimitCode(time.Now(), data, setting.Service.ActiveCodeLives, prefix) {
802799
return user
803800
}
804801
}

modules/base/tool.go

Lines changed: 40 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@
44
package base
55

66
import (
7+
"crypto/hmac"
78
"crypto/sha1"
89
"crypto/sha256"
10+
"crypto/subtle"
911
"encoding/base64"
1012
"encoding/hex"
1113
"errors"
1214
"fmt"
15+
"hash"
1316
"os"
1417
"path/filepath"
1518
"runtime"
@@ -25,13 +28,6 @@ import (
2528
"github.com/dustin/go-humanize"
2629
)
2730

28-
// EncodeSha1 string to sha1 hex value.
29-
func EncodeSha1(str string) string {
30-
h := sha1.New()
31-
_, _ = h.Write([]byte(str))
32-
return hex.EncodeToString(h.Sum(nil))
33-
}
34-
3531
// EncodeSha256 string to sha256 hex value.
3632
func EncodeSha256(str string) string {
3733
h := sha256.New()
@@ -62,63 +58,62 @@ func BasicAuthDecode(encoded string) (string, string, error) {
6258
}
6359

6460
// VerifyTimeLimitCode verify time limit code
65-
func VerifyTimeLimitCode(data string, minutes int, code string) bool {
61+
func VerifyTimeLimitCode(now time.Time, data string, minutes int, code string) bool {
6662
if len(code) <= 18 {
6763
return false
6864
}
6965

70-
// split code
71-
start := code[:12]
72-
lives := code[12:18]
73-
if d, err := strconv.ParseInt(lives, 10, 0); err == nil {
74-
minutes = int(d)
75-
}
66+
startTimeStr := code[:12]
67+
aliveTimeStr := code[12:18]
68+
aliveTime, _ := strconv.Atoi(aliveTimeStr) // no need to check err, if anything wrong, the following code check will fail soon
7669

77-
// right active code
78-
retCode := CreateTimeLimitCode(data, minutes, start)
79-
if retCode == code && minutes > 0 {
80-
// check time is expired or not
81-
before, _ := time.ParseInLocation("200601021504", start, time.Local)
82-
now := time.Now()
83-
if before.Add(time.Minute*time.Duration(minutes)).Unix() > now.Unix() {
84-
return true
70+
// check code
71+
retCode := CreateTimeLimitCode(data, aliveTime, startTimeStr, nil)
72+
if subtle.ConstantTimeCompare([]byte(retCode), []byte(code)) != 1 {
73+
retCode = CreateTimeLimitCode(data, aliveTime, startTimeStr, sha1.New()) // TODO: this is only for the support of legacy codes, remove this in/after 1.23
74+
if subtle.ConstantTimeCompare([]byte(retCode), []byte(code)) != 1 {
75+
return false
8576
}
8677
}
8778

88-
return false
79+
// check time is expired or not: startTime <= now && now < startTime + minutes
80+
startTime, _ := time.ParseInLocation("200601021504", startTimeStr, time.Local)
81+
return (startTime.Before(now) || startTime.Equal(now)) && now.Before(startTime.Add(time.Minute*time.Duration(minutes)))
8982
}
9083

9184
// TimeLimitCodeLength default value for time limit code
9285
const TimeLimitCodeLength = 12 + 6 + 40
9386

94-
// CreateTimeLimitCode create a time limit code
95-
// code format: 12 length date time string + 6 minutes string + 40 sha1 encoded string
96-
func CreateTimeLimitCode(data string, minutes int, startInf any) string {
97-
format := "200601021504"
98-
99-
var start, end time.Time
100-
var startStr, endStr string
87+
// CreateTimeLimitCode create a time-limited code.
88+
// Format: 12 length date time string + 6 minutes string (not used) + 40 hash string, some other code depends on this fixed length
89+
// If h is nil, then use the default hmac hash.
90+
func CreateTimeLimitCode[T time.Time | string](data string, minutes int, startTimeGeneric T, h hash.Hash) string {
91+
const format = "200601021504"
10192

102-
if startInf == nil {
103-
// Use now time create code
104-
start = time.Now()
105-
startStr = start.Format(format)
93+
var start time.Time
94+
var startTimeAny any = startTimeGeneric
95+
if t, ok := startTimeAny.(time.Time); ok {
96+
start = t
10697
} else {
107-
// use start string create code
108-
startStr = startInf.(string)
109-
start, _ = time.ParseInLocation(format, startStr, time.Local)
110-
startStr = start.Format(format)
98+
var err error
99+
start, err = time.ParseInLocation(format, startTimeAny.(string), time.Local)
100+
if err != nil {
101+
return "" // return an invalid code because the "parse" failed
102+
}
111103
}
104+
startStr := start.Format(format)
105+
end := start.Add(time.Minute * time.Duration(minutes))
112106

113-
end = start.Add(time.Minute * time.Duration(minutes))
114-
endStr = end.Format(format)
115-
116-
// create sha1 encode string
117-
sh := sha1.New()
118-
_, _ = sh.Write([]byte(fmt.Sprintf("%s%s%s%s%d", data, hex.EncodeToString(setting.GetGeneralTokenSigningSecret()), startStr, endStr, minutes)))
119-
encoded := hex.EncodeToString(sh.Sum(nil))
107+
if h == nil {
108+
h = hmac.New(sha1.New, setting.GetGeneralTokenSigningSecret())
109+
}
110+
_, _ = fmt.Fprintf(h, "%s%s%s%s%d", data, hex.EncodeToString(setting.GetGeneralTokenSigningSecret()), startStr, end.Format(format), minutes)
111+
encoded := hex.EncodeToString(h.Sum(nil))
120112

121113
code := fmt.Sprintf("%s%06d%s", startStr, minutes, encoded)
114+
if len(code) != TimeLimitCodeLength {
115+
panic("there is a hard requirement for the length of time-limited code") // it shouldn't happen
116+
}
122117
return code
123118
}
124119

0 commit comments

Comments
 (0)