Compare commits

...

8 Commits

Author SHA1 Message Date
Kemal Zebari
1007ce764e
Don't include link of deleted branch when listing branches (#31028)
From
https://github.com/go-gitea/gitea/issues/31018#issuecomment-2119622680.

This commit removes the link to a deleted branch name because it returns
a 404 while it is in this deleted state. GitHub also throws a 404 when
navigating to a branch link that was just deleted, but this deleted
branch is removed from the branch list after a page refresh. Since with
Gitea this deleted branch would be kept around for quite some time
(well, until the "cleanup deleted branches" cron job begins), it makes
sense to not have this as a link that users can navigate to.
2024-05-21 02:23:07 +00:00
GiteaBot
ba83d27ab0 [skip ci] Updated translations via Crowdin 2024-05-21 00:26:00 +00:00
wxiaoguang
fb1ad920b7
Refactor sha1 and time-limited code (#31023)
Remove "EncodeSha1", it shouldn't be used as a general purpose hasher
(just like we have removed "EncodeMD5" in #28622)

Rewrite the "time-limited code" related code and write better tests, the
old code doesn't seem quite right.
2024-05-20 15:12:50 +00:00
Zettat123
f1d9f18d96
Return access_denied error when an OAuth2 request is denied (#30974)
According to [RFC
6749](https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1),
when the resource owner or authorization server denied an request, an
`access_denied` error should be returned. But currently in this case
Gitea does not return any error.

For example, if the user clicks "Cancel" here, an `access_denied` error
should be returned.

<img width="360px"
src="https://github.com/go-gitea/gitea/assets/15528715/be31c09b-4c0a-4701-b7a4-f54b8fe3a6c5"
/>
2024-05-20 07:17:00 +00:00
wxiaoguang
de9bcd1d23
Avoid 500 panic error when uploading invalid maven package file (#31014)
PackageDescriptor.Metadata might be nil (and maybe not only for maven).
This is only a quick fix.

The new `if` block is written intentionally to avoid unnecessary
indenting to the existing code.
2024-05-20 06:44:16 +00:00
wxiaoguang
f48cc501c4
Fix incorrect "blob excerpt" link when comparing files (#31013)
When comparing files between the base repo and forked repo, the "blob
excerpt" link should point to the forked repo, because the commit
doesn't exist in base repo.

Co-authored-by: Giteabot <teabot@gitea.io>
2024-05-20 05:57:57 +00:00
wxiaoguang
b6574099ed
Fix project column title overflow (#31011)
By the way:
* Re-format the "color.go" to Golang code style
* Remove unused `overflow-y: scroll;` from `.project-column` because
there is `overflow: visible`
2024-05-20 05:21:01 +00:00
wxiaoguang
47accfebbd
Fix data-race during testing (#30999)
Fix #30992
2024-05-20 04:35:38 +00:00
26 changed files with 258 additions and 149 deletions

View File

@ -7,6 +7,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"strings" "strings"
"sync/atomic"
"code.gitea.io/gitea/models/perm" "code.gitea.io/gitea/models/perm"
"code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/container"
@ -106,10 +107,23 @@ var (
TypeExternalTracker, TypeExternalTracker,
} }
// DisabledRepoUnits contains the units that have been globally disabled disabledRepoUnitsAtomic atomic.Pointer[[]Type] // the units that have been globally disabled
DisabledRepoUnits = []Type{}
) )
// DisabledRepoUnitsGet returns the globally disabled units, it is a quick patch to fix data-race during testing.
// Because the queue worker might read when a test is mocking the value. FIXME: refactor to a clear solution later.
func DisabledRepoUnitsGet() []Type {
v := disabledRepoUnitsAtomic.Load()
if v == nil {
return nil
}
return *v
}
func DisabledRepoUnitsSet(v []Type) {
disabledRepoUnitsAtomic.Store(&v)
}
// Get valid set of default repository units from settings // Get valid set of default repository units from settings
func validateDefaultRepoUnits(defaultUnits, settingDefaultUnits []Type) []Type { func validateDefaultRepoUnits(defaultUnits, settingDefaultUnits []Type) []Type {
units := defaultUnits units := defaultUnits
@ -127,7 +141,7 @@ func validateDefaultRepoUnits(defaultUnits, settingDefaultUnits []Type) []Type {
} }
// Remove disabled units // Remove disabled units
for _, disabledUnit := range DisabledRepoUnits { for _, disabledUnit := range DisabledRepoUnitsGet() {
for i, unit := range units { for i, unit := range units {
if unit == disabledUnit { if unit == disabledUnit {
units = append(units[:i], units[i+1:]...) units = append(units[:i], units[i+1:]...)
@ -140,11 +154,11 @@ func validateDefaultRepoUnits(defaultUnits, settingDefaultUnits []Type) []Type {
// LoadUnitConfig load units from settings // LoadUnitConfig load units from settings
func LoadUnitConfig() error { func LoadUnitConfig() error {
var invalidKeys []string disabledRepoUnits, invalidKeys := FindUnitTypes(setting.Repository.DisabledRepoUnits...)
DisabledRepoUnits, invalidKeys = FindUnitTypes(setting.Repository.DisabledRepoUnits...)
if len(invalidKeys) > 0 { if len(invalidKeys) > 0 {
log.Warn("Invalid keys in disabled repo units: %s", strings.Join(invalidKeys, ", ")) log.Warn("Invalid keys in disabled repo units: %s", strings.Join(invalidKeys, ", "))
} }
DisabledRepoUnitsSet(disabledRepoUnits)
setDefaultRepoUnits, invalidKeys := FindUnitTypes(setting.Repository.DefaultRepoUnits...) setDefaultRepoUnits, invalidKeys := FindUnitTypes(setting.Repository.DefaultRepoUnits...)
if len(invalidKeys) > 0 { if len(invalidKeys) > 0 {
@ -167,7 +181,7 @@ func LoadUnitConfig() error {
// UnitGlobalDisabled checks if unit type is global disabled // UnitGlobalDisabled checks if unit type is global disabled
func (u Type) UnitGlobalDisabled() bool { func (u Type) UnitGlobalDisabled() bool {
for _, ud := range DisabledRepoUnits { for _, ud := range DisabledRepoUnitsGet() {
if u == ud { if u == ud {
return true return true
} }

View File

@ -14,10 +14,10 @@ import (
func TestLoadUnitConfig(t *testing.T) { func TestLoadUnitConfig(t *testing.T) {
t.Run("regular", func(t *testing.T) { t.Run("regular", func(t *testing.T) {
defer func(disabledRepoUnits, defaultRepoUnits, defaultForkRepoUnits []Type) { defer func(disabledRepoUnits, defaultRepoUnits, defaultForkRepoUnits []Type) {
DisabledRepoUnits = disabledRepoUnits DisabledRepoUnitsSet(disabledRepoUnits)
DefaultRepoUnits = defaultRepoUnits DefaultRepoUnits = defaultRepoUnits
DefaultForkRepoUnits = defaultForkRepoUnits DefaultForkRepoUnits = defaultForkRepoUnits
}(DisabledRepoUnits, DefaultRepoUnits, DefaultForkRepoUnits) }(DisabledRepoUnitsGet(), DefaultRepoUnits, DefaultForkRepoUnits)
defer func(disabledRepoUnits, defaultRepoUnits, defaultForkRepoUnits []string) { defer func(disabledRepoUnits, defaultRepoUnits, defaultForkRepoUnits []string) {
setting.Repository.DisabledRepoUnits = disabledRepoUnits setting.Repository.DisabledRepoUnits = disabledRepoUnits
setting.Repository.DefaultRepoUnits = defaultRepoUnits setting.Repository.DefaultRepoUnits = defaultRepoUnits
@ -28,16 +28,16 @@ func TestLoadUnitConfig(t *testing.T) {
setting.Repository.DefaultRepoUnits = []string{"repo.code", "repo.releases", "repo.issues", "repo.pulls"} setting.Repository.DefaultRepoUnits = []string{"repo.code", "repo.releases", "repo.issues", "repo.pulls"}
setting.Repository.DefaultForkRepoUnits = []string{"repo.releases"} setting.Repository.DefaultForkRepoUnits = []string{"repo.releases"}
assert.NoError(t, LoadUnitConfig()) assert.NoError(t, LoadUnitConfig())
assert.Equal(t, []Type{TypeIssues}, DisabledRepoUnits) assert.Equal(t, []Type{TypeIssues}, DisabledRepoUnitsGet())
assert.Equal(t, []Type{TypeCode, TypeReleases, TypePullRequests}, DefaultRepoUnits) assert.Equal(t, []Type{TypeCode, TypeReleases, TypePullRequests}, DefaultRepoUnits)
assert.Equal(t, []Type{TypeReleases}, DefaultForkRepoUnits) assert.Equal(t, []Type{TypeReleases}, DefaultForkRepoUnits)
}) })
t.Run("invalid", func(t *testing.T) { t.Run("invalid", func(t *testing.T) {
defer func(disabledRepoUnits, defaultRepoUnits, defaultForkRepoUnits []Type) { defer func(disabledRepoUnits, defaultRepoUnits, defaultForkRepoUnits []Type) {
DisabledRepoUnits = disabledRepoUnits DisabledRepoUnitsSet(disabledRepoUnits)
DefaultRepoUnits = defaultRepoUnits DefaultRepoUnits = defaultRepoUnits
DefaultForkRepoUnits = defaultForkRepoUnits DefaultForkRepoUnits = defaultForkRepoUnits
}(DisabledRepoUnits, DefaultRepoUnits, DefaultForkRepoUnits) }(DisabledRepoUnitsGet(), DefaultRepoUnits, DefaultForkRepoUnits)
defer func(disabledRepoUnits, defaultRepoUnits, defaultForkRepoUnits []string) { defer func(disabledRepoUnits, defaultRepoUnits, defaultForkRepoUnits []string) {
setting.Repository.DisabledRepoUnits = disabledRepoUnits setting.Repository.DisabledRepoUnits = disabledRepoUnits
setting.Repository.DefaultRepoUnits = defaultRepoUnits setting.Repository.DefaultRepoUnits = defaultRepoUnits
@ -48,16 +48,16 @@ func TestLoadUnitConfig(t *testing.T) {
setting.Repository.DefaultRepoUnits = []string{"repo.code", "invalid.2", "repo.releases", "repo.issues", "repo.pulls"} setting.Repository.DefaultRepoUnits = []string{"repo.code", "invalid.2", "repo.releases", "repo.issues", "repo.pulls"}
setting.Repository.DefaultForkRepoUnits = []string{"invalid.3", "repo.releases"} setting.Repository.DefaultForkRepoUnits = []string{"invalid.3", "repo.releases"}
assert.NoError(t, LoadUnitConfig()) assert.NoError(t, LoadUnitConfig())
assert.Equal(t, []Type{TypeIssues}, DisabledRepoUnits) assert.Equal(t, []Type{TypeIssues}, DisabledRepoUnitsGet())
assert.Equal(t, []Type{TypeCode, TypeReleases, TypePullRequests}, DefaultRepoUnits) assert.Equal(t, []Type{TypeCode, TypeReleases, TypePullRequests}, DefaultRepoUnits)
assert.Equal(t, []Type{TypeReleases}, DefaultForkRepoUnits) assert.Equal(t, []Type{TypeReleases}, DefaultForkRepoUnits)
}) })
t.Run("duplicate", func(t *testing.T) { t.Run("duplicate", func(t *testing.T) {
defer func(disabledRepoUnits, defaultRepoUnits, defaultForkRepoUnits []Type) { defer func(disabledRepoUnits, defaultRepoUnits, defaultForkRepoUnits []Type) {
DisabledRepoUnits = disabledRepoUnits DisabledRepoUnitsSet(disabledRepoUnits)
DefaultRepoUnits = defaultRepoUnits DefaultRepoUnits = defaultRepoUnits
DefaultForkRepoUnits = defaultForkRepoUnits DefaultForkRepoUnits = defaultForkRepoUnits
}(DisabledRepoUnits, DefaultRepoUnits, DefaultForkRepoUnits) }(DisabledRepoUnitsGet(), DefaultRepoUnits, DefaultForkRepoUnits)
defer func(disabledRepoUnits, defaultRepoUnits, defaultForkRepoUnits []string) { defer func(disabledRepoUnits, defaultRepoUnits, defaultForkRepoUnits []string) {
setting.Repository.DisabledRepoUnits = disabledRepoUnits setting.Repository.DisabledRepoUnits = disabledRepoUnits
setting.Repository.DefaultRepoUnits = defaultRepoUnits setting.Repository.DefaultRepoUnits = defaultRepoUnits
@ -68,16 +68,16 @@ func TestLoadUnitConfig(t *testing.T) {
setting.Repository.DefaultRepoUnits = []string{"repo.code", "repo.releases", "repo.issues", "repo.pulls", "repo.code"} setting.Repository.DefaultRepoUnits = []string{"repo.code", "repo.releases", "repo.issues", "repo.pulls", "repo.code"}
setting.Repository.DefaultForkRepoUnits = []string{"repo.releases", "repo.releases"} setting.Repository.DefaultForkRepoUnits = []string{"repo.releases", "repo.releases"}
assert.NoError(t, LoadUnitConfig()) assert.NoError(t, LoadUnitConfig())
assert.Equal(t, []Type{TypeIssues}, DisabledRepoUnits) assert.Equal(t, []Type{TypeIssues}, DisabledRepoUnitsGet())
assert.Equal(t, []Type{TypeCode, TypeReleases, TypePullRequests}, DefaultRepoUnits) assert.Equal(t, []Type{TypeCode, TypeReleases, TypePullRequests}, DefaultRepoUnits)
assert.Equal(t, []Type{TypeReleases}, DefaultForkRepoUnits) assert.Equal(t, []Type{TypeReleases}, DefaultForkRepoUnits)
}) })
t.Run("empty_default", func(t *testing.T) { t.Run("empty_default", func(t *testing.T) {
defer func(disabledRepoUnits, defaultRepoUnits, defaultForkRepoUnits []Type) { defer func(disabledRepoUnits, defaultRepoUnits, defaultForkRepoUnits []Type) {
DisabledRepoUnits = disabledRepoUnits DisabledRepoUnitsSet(disabledRepoUnits)
DefaultRepoUnits = defaultRepoUnits DefaultRepoUnits = defaultRepoUnits
DefaultForkRepoUnits = defaultForkRepoUnits DefaultForkRepoUnits = defaultForkRepoUnits
}(DisabledRepoUnits, DefaultRepoUnits, DefaultForkRepoUnits) }(DisabledRepoUnitsGet(), DefaultRepoUnits, DefaultForkRepoUnits)
defer func(disabledRepoUnits, defaultRepoUnits, defaultForkRepoUnits []string) { defer func(disabledRepoUnits, defaultRepoUnits, defaultForkRepoUnits []string) {
setting.Repository.DisabledRepoUnits = disabledRepoUnits setting.Repository.DisabledRepoUnits = disabledRepoUnits
setting.Repository.DefaultRepoUnits = defaultRepoUnits setting.Repository.DefaultRepoUnits = defaultRepoUnits
@ -88,7 +88,7 @@ func TestLoadUnitConfig(t *testing.T) {
setting.Repository.DefaultRepoUnits = []string{} setting.Repository.DefaultRepoUnits = []string{}
setting.Repository.DefaultForkRepoUnits = []string{"repo.releases", "repo.releases"} setting.Repository.DefaultForkRepoUnits = []string{"repo.releases", "repo.releases"}
assert.NoError(t, LoadUnitConfig()) assert.NoError(t, LoadUnitConfig())
assert.Equal(t, []Type{TypeIssues}, DisabledRepoUnits) assert.Equal(t, []Type{TypeIssues}, DisabledRepoUnitsGet())
assert.ElementsMatch(t, []Type{TypeCode, TypePullRequests, TypeReleases, TypeWiki, TypePackages, TypeProjects, TypeActions}, DefaultRepoUnits) assert.ElementsMatch(t, []Type{TypeCode, TypePullRequests, TypeReleases, TypeWiki, TypePackages, TypeProjects, TypeActions}, DefaultRepoUnits)
assert.Equal(t, []Type{TypeReleases}, DefaultForkRepoUnits) assert.Equal(t, []Type{TypeReleases}, DefaultForkRepoUnits)
}) })

View File

@ -10,6 +10,7 @@ import (
"net/mail" "net/mail"
"regexp" "regexp"
"strings" "strings"
"time"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/base"
@ -353,14 +354,12 @@ func ChangeInactivePrimaryEmail(ctx context.Context, uid int64, oldEmailAddr, ne
// VerifyActiveEmailCode verifies active email code when active account // VerifyActiveEmailCode verifies active email code when active account
func VerifyActiveEmailCode(ctx context.Context, code, email string) *EmailAddress { func VerifyActiveEmailCode(ctx context.Context, code, email string) *EmailAddress {
minutes := setting.Service.ActiveCodeLives
if user := GetVerifyUser(ctx, code); user != nil { if user := GetVerifyUser(ctx, code); user != nil {
// time limit code // time limit code
prefix := code[:base.TimeLimitCodeLength] prefix := code[:base.TimeLimitCodeLength]
data := fmt.Sprintf("%d%s%s%s%s", user.ID, email, user.LowerName, user.Passwd, user.Rands) data := fmt.Sprintf("%d%s%s%s%s", user.ID, email, user.LowerName, user.Passwd, user.Rands)
if base.VerifyTimeLimitCode(data, minutes, prefix) { if base.VerifyTimeLimitCode(time.Now(), data, setting.Service.ActiveCodeLives, prefix) {
emailAddress := &EmailAddress{UID: user.ID, Email: email} emailAddress := &EmailAddress{UID: user.ID, Email: email}
if has, _ := db.GetEngine(ctx).Get(emailAddress); has { if has, _ := db.GetEngine(ctx).Get(emailAddress); has {
return emailAddress return emailAddress

View File

@ -304,7 +304,7 @@ func (u *User) OrganisationLink() string {
func (u *User) GenerateEmailActivateCode(email string) string { func (u *User) GenerateEmailActivateCode(email string) string {
code := base.CreateTimeLimitCode( code := base.CreateTimeLimitCode(
fmt.Sprintf("%d%s%s%s%s", u.ID, email, u.LowerName, u.Passwd, u.Rands), fmt.Sprintf("%d%s%s%s%s", u.ID, email, u.LowerName, u.Passwd, u.Rands),
setting.Service.ActiveCodeLives, nil) setting.Service.ActiveCodeLives, time.Now(), nil)
// Add tail hex username // Add tail hex username
code += hex.EncodeToString([]byte(u.LowerName)) code += hex.EncodeToString([]byte(u.LowerName))
@ -791,14 +791,11 @@ func GetVerifyUser(ctx context.Context, code string) (user *User) {
// VerifyUserActiveCode verifies active code when active account // VerifyUserActiveCode verifies active code when active account
func VerifyUserActiveCode(ctx context.Context, code string) (user *User) { func VerifyUserActiveCode(ctx context.Context, code string) (user *User) {
minutes := setting.Service.ActiveCodeLives
if user = GetVerifyUser(ctx, code); user != nil { if user = GetVerifyUser(ctx, code); user != nil {
// time limit code // time limit code
prefix := code[:base.TimeLimitCodeLength] prefix := code[:base.TimeLimitCodeLength]
data := fmt.Sprintf("%d%s%s%s%s", user.ID, user.Email, user.LowerName, user.Passwd, user.Rands) data := fmt.Sprintf("%d%s%s%s%s", user.ID, user.Email, user.LowerName, user.Passwd, user.Rands)
if base.VerifyTimeLimitCode(time.Now(), data, setting.Service.ActiveCodeLives, prefix) {
if base.VerifyTimeLimitCode(data, minutes, prefix) {
return user return user
} }
} }

View File

@ -4,12 +4,15 @@
package base package base
import ( import (
"crypto/hmac"
"crypto/sha1" "crypto/sha1"
"crypto/sha256" "crypto/sha256"
"crypto/subtle"
"encoding/base64" "encoding/base64"
"encoding/hex" "encoding/hex"
"errors" "errors"
"fmt" "fmt"
"hash"
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
@ -25,13 +28,6 @@ import (
"github.com/dustin/go-humanize" "github.com/dustin/go-humanize"
) )
// EncodeSha1 string to sha1 hex value.
func EncodeSha1(str string) string {
h := sha1.New()
_, _ = h.Write([]byte(str))
return hex.EncodeToString(h.Sum(nil))
}
// EncodeSha256 string to sha256 hex value. // EncodeSha256 string to sha256 hex value.
func EncodeSha256(str string) string { func EncodeSha256(str string) string {
h := sha256.New() h := sha256.New()
@ -62,63 +58,62 @@ func BasicAuthDecode(encoded string) (string, string, error) {
} }
// VerifyTimeLimitCode verify time limit code // VerifyTimeLimitCode verify time limit code
func VerifyTimeLimitCode(data string, minutes int, code string) bool { func VerifyTimeLimitCode(now time.Time, data string, minutes int, code string) bool {
if len(code) <= 18 { if len(code) <= 18 {
return false return false
} }
// split code startTimeStr := code[:12]
start := code[:12] aliveTimeStr := code[12:18]
lives := code[12:18] aliveTime, _ := strconv.Atoi(aliveTimeStr) // no need to check err, if anything wrong, the following code check will fail soon
if d, err := strconv.ParseInt(lives, 10, 0); err == nil {
minutes = int(d)
}
// right active code // check code
retCode := CreateTimeLimitCode(data, minutes, start) retCode := CreateTimeLimitCode(data, aliveTime, startTimeStr, nil)
if retCode == code && minutes > 0 { if subtle.ConstantTimeCompare([]byte(retCode), []byte(code)) != 1 {
// check time is expired or not retCode = CreateTimeLimitCode(data, aliveTime, startTimeStr, sha1.New()) // TODO: this is only for the support of legacy codes, remove this in/after 1.23
before, _ := time.ParseInLocation("200601021504", start, time.Local) if subtle.ConstantTimeCompare([]byte(retCode), []byte(code)) != 1 {
now := time.Now() return false
if before.Add(time.Minute*time.Duration(minutes)).Unix() > now.Unix() {
return true
} }
} }
return false // check time is expired or not: startTime <= now && now < startTime + minutes
startTime, _ := time.ParseInLocation("200601021504", startTimeStr, time.Local)
return (startTime.Before(now) || startTime.Equal(now)) && now.Before(startTime.Add(time.Minute*time.Duration(minutes)))
} }
// TimeLimitCodeLength default value for time limit code // TimeLimitCodeLength default value for time limit code
const TimeLimitCodeLength = 12 + 6 + 40 const TimeLimitCodeLength = 12 + 6 + 40
// CreateTimeLimitCode create a time limit code // CreateTimeLimitCode create a time-limited code.
// code format: 12 length date time string + 6 minutes string + 40 sha1 encoded string // Format: 12 length date time string + 6 minutes string (not used) + 40 hash string, some other code depends on this fixed length
func CreateTimeLimitCode(data string, minutes int, startInf any) string { // If h is nil, then use the default hmac hash.
format := "200601021504" func CreateTimeLimitCode[T time.Time | string](data string, minutes int, startTimeGeneric T, h hash.Hash) string {
const format = "200601021504"
var start, end time.Time var start time.Time
var startStr, endStr string var startTimeAny any = startTimeGeneric
if t, ok := startTimeAny.(time.Time); ok {
if startInf == nil { start = t
// Use now time create code
start = time.Now()
startStr = start.Format(format)
} else { } else {
// use start string create code var err error
startStr = startInf.(string) start, err = time.ParseInLocation(format, startTimeAny.(string), time.Local)
start, _ = time.ParseInLocation(format, startStr, time.Local) if err != nil {
startStr = start.Format(format) return "" // return an invalid code because the "parse" failed
}
} }
startStr := start.Format(format)
end := start.Add(time.Minute * time.Duration(minutes))
end = start.Add(time.Minute * time.Duration(minutes)) if h == nil {
endStr = end.Format(format) h = hmac.New(sha1.New, setting.GetGeneralTokenSigningSecret())
}
// create sha1 encode string _, _ = fmt.Fprintf(h, "%s%s%s%s%d", data, hex.EncodeToString(setting.GetGeneralTokenSigningSecret()), startStr, end.Format(format), minutes)
sh := sha1.New() encoded := hex.EncodeToString(h.Sum(nil))
_, _ = sh.Write([]byte(fmt.Sprintf("%s%s%s%s%d", data, hex.EncodeToString(setting.GetGeneralTokenSigningSecret()), startStr, endStr, minutes)))
encoded := hex.EncodeToString(sh.Sum(nil))
code := fmt.Sprintf("%s%06d%s", startStr, minutes, encoded) code := fmt.Sprintf("%s%06d%s", startStr, minutes, encoded)
if len(code) != TimeLimitCodeLength {
panic("there is a hard requirement for the length of time-limited code") // it shouldn't happen
}
return code return code
} }

View File

@ -4,20 +4,18 @@
package base package base
import ( import (
"crypto/sha1"
"fmt"
"os" "os"
"testing" "testing"
"time" "time"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/test"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestEncodeSha1(t *testing.T) {
assert.Equal(t,
"8843d7f92416211de9ebb963ff4ce28125932878",
EncodeSha1("foobar"),
)
}
func TestEncodeSha256(t *testing.T) { func TestEncodeSha256(t *testing.T) {
assert.Equal(t, assert.Equal(t,
"c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2", "c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2",
@ -46,43 +44,54 @@ func TestBasicAuthDecode(t *testing.T) {
} }
func TestVerifyTimeLimitCode(t *testing.T) { func TestVerifyTimeLimitCode(t *testing.T) {
tc := []struct { defer test.MockVariableValue(&setting.InstallLock, true)()
data string initGeneralSecret := func(secret string) {
minutes int setting.InstallLock = true
code string setting.CfgProvider, _ = setting.NewConfigProviderFromData(fmt.Sprintf(`
valid bool [oauth2]
}{{ JWT_SECRET = %s
data: "data", `, secret))
minutes: 2, setting.LoadCommonSettings()
code: testCreateTimeLimitCode(t, "data", 2),
valid: true,
}, {
data: "abc123-ß",
minutes: 1,
code: testCreateTimeLimitCode(t, "abc123-ß", 1),
valid: true,
}, {
data: "data",
minutes: 2,
code: "2021012723240000005928251dac409d2c33a6eb82c63410aaad569bed",
valid: false,
}}
for _, test := range tc {
actualValid := VerifyTimeLimitCode(test.data, test.minutes, test.code)
assert.Equal(t, test.valid, actualValid, "data: '%s' code: '%s' should be valid: %t", test.data, test.code, test.valid)
} }
}
func testCreateTimeLimitCode(t *testing.T, data string, m int) string { initGeneralSecret("KZb_QLUd4fYVyxetjxC4eZkrBgWM2SndOOWDNtgUUko")
result0 := CreateTimeLimitCode(data, m, nil) now := time.Now()
result1 := CreateTimeLimitCode(data, m, time.Now().Format("200601021504"))
result2 := CreateTimeLimitCode(data, m, time.Unix(time.Now().Unix()+int64(time.Minute)*int64(m), 0).Format("200601021504"))
assert.Equal(t, result0, result1) t.Run("TestGenericParameter", func(t *testing.T) {
assert.NotEqual(t, result0, result2) time2000 := time.Date(2000, 1, 2, 3, 4, 5, 0, time.Local)
assert.Equal(t, "2000010203040000026fa5221b2731b7cf80b1b506f5e39e38c115fee5", CreateTimeLimitCode("test-sha1", 2, time2000, sha1.New()))
assert.Equal(t, "2000010203040000026fa5221b2731b7cf80b1b506f5e39e38c115fee5", CreateTimeLimitCode("test-sha1", 2, "200001020304", sha1.New()))
assert.Equal(t, "2000010203040000024842227a2f87041ff82025199c0187410a9297bf", CreateTimeLimitCode("test-hmac", 2, time2000, nil))
assert.Equal(t, "2000010203040000024842227a2f87041ff82025199c0187410a9297bf", CreateTimeLimitCode("test-hmac", 2, "200001020304", nil))
})
assert.True(t, len(result0) != 0) t.Run("TestInvalidCode", func(t *testing.T) {
return result0 assert.False(t, VerifyTimeLimitCode(now, "data", 2, ""))
assert.False(t, VerifyTimeLimitCode(now, "data", 2, "invalid code"))
})
t.Run("TestCreateAndVerify", func(t *testing.T) {
code := CreateTimeLimitCode("data", 2, now, nil)
assert.False(t, VerifyTimeLimitCode(now.Add(-time.Minute), "data", 2, code)) // not started yet
assert.True(t, VerifyTimeLimitCode(now, "data", 2, code))
assert.True(t, VerifyTimeLimitCode(now.Add(time.Minute), "data", 2, code))
assert.False(t, VerifyTimeLimitCode(now.Add(time.Minute), "DATA", 2, code)) // invalid data
assert.False(t, VerifyTimeLimitCode(now.Add(2*time.Minute), "data", 2, code)) // expired
})
t.Run("TestDifferentSecret", func(t *testing.T) {
// use another secret to ensure the code is invalid for different secret
verifyDataCode := func(c string) bool {
return VerifyTimeLimitCode(now, "data", 2, c)
}
code1 := CreateTimeLimitCode("data", 2, now, sha1.New())
code2 := CreateTimeLimitCode("data", 2, now, nil)
assert.True(t, verifyDataCode(code1))
assert.True(t, verifyDataCode(code2))
initGeneralSecret("000_QLUd4fYVyxetjxC4eZkrBgWM2SndOOWDNtgUUko")
assert.False(t, verifyDataCode(code1))
assert.False(t, verifyDataCode(code2))
})
} }
func TestFileSize(t *testing.T) { func TestFileSize(t *testing.T) {

View File

@ -4,6 +4,8 @@
package git package git
import ( import (
"crypto/sha1"
"encoding/hex"
"fmt" "fmt"
"io" "io"
"os" "os"
@ -128,3 +130,9 @@ func (l *LimitedReaderCloser) Read(p []byte) (n int, err error) {
func (l *LimitedReaderCloser) Close() error { func (l *LimitedReaderCloser) Close() error {
return l.C.Close() return l.C.Close()
} }
func HashFilePathForWebUI(s string) string {
h := sha1.New()
_, _ = h.Write([]byte(s))
return hex.EncodeToString(h.Sum(nil))
}

17
modules/git/utils_test.go Normal file
View File

@ -0,0 +1,17 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package git
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestHashFilePathForWebUI(t *testing.T) {
assert.Equal(t,
"8843d7f92416211de9ebb963ff4ce28125932878",
HashFilePathForWebUI("foobar"),
)
}

View File

@ -1,5 +1,6 @@
// Copyright 2023 The Gitea Authors. All rights reserved. // Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
package util package util
import ( import (
@ -8,7 +9,7 @@ import (
"strings" "strings"
) )
// Get color as RGB values in 0..255 range from the hex color string (with or without #) // HexToRBGColor parses color as RGB values in 0..255 range from the hex color string (with or without #)
func HexToRBGColor(colorString string) (float64, float64, float64) { func HexToRBGColor(colorString string) (float64, float64, float64) {
hexString := colorString hexString := colorString
if strings.HasPrefix(colorString, "#") { if strings.HasPrefix(colorString, "#") {
@ -35,7 +36,7 @@ func HexToRBGColor(colorString string) (float64, float64, float64) {
return r, g, b return r, g, b
} }
// Returns relative luminance for a SRGB color - https://en.wikipedia.org/wiki/Relative_luminance // GetRelativeLuminance returns relative luminance for a SRGB color - https://en.wikipedia.org/wiki/Relative_luminance
// Keep this in sync with web_src/js/utils/color.js // Keep this in sync with web_src/js/utils/color.js
func GetRelativeLuminance(color string) float64 { func GetRelativeLuminance(color string) float64 {
r, g, b := HexToRBGColor(color) r, g, b := HexToRBGColor(color)
@ -46,8 +47,8 @@ func UseLightText(backgroundColor string) bool {
return GetRelativeLuminance(backgroundColor) < 0.453 return GetRelativeLuminance(backgroundColor) < 0.453
} }
// Given a background color, returns a black or white foreground color that the highest // ContrastColor returns a black or white foreground color that the highest contrast ratio.
// contrast ratio. In the future, the APCA contrast function, or CSS `contrast-color` will be better. // In the future, the APCA contrast function, or CSS `contrast-color` will be better.
// https://github.com/color-js/color.js/blob/eb7b53f7a13bb716ec8b28c7a56f052cd599acd9/src/contrast/APCA.js#L42 // https://github.com/color-js/color.js/blob/eb7b53f7a13bb716ec8b28c7a56f052cd599acd9/src/contrast/APCA.js#L42
func ContrastColor(backgroundColor string) string { func ContrastColor(backgroundColor string) string {
if UseLightText(backgroundColor) { if UseLightText(backgroundColor) {

View File

@ -3415,6 +3415,7 @@ error.unit_not_allowed = You are not allowed to access this repository section.
title = Packages title = Packages
desc = Manage repository packages. desc = Manage repository packages.
empty = There are no packages yet. empty = There are no packages yet.
no_metadata = No metadata.
empty.documentation = For more information on the package registry, see <a target="_blank" rel="noopener noreferrer" href="%s">the documentation</a>. empty.documentation = For more information on the package registry, see <a target="_blank" rel="noopener noreferrer" href="%s">the documentation</a>.
empty.repo = Did you upload a package, but it's not shown here? Go to <a href="%[1]s">package settings</a> and link it to this repo. empty.repo = Did you upload a package, but it's not shown here? Go to <a href="%[1]s">package settings</a> and link it to this repo.
registry.documentation = For more information on the %s registry, see <a target="_blank" rel="noopener noreferrer" href="%s">the documentation</a>. registry.documentation = For more information on the %s registry, see <a target="_blank" rel="noopener noreferrer" href="%s">the documentation</a>.

View File

@ -436,6 +436,7 @@ oauth_signin_submit=アカウントにリンク
oauth.signin.error=認可リクエストの処理中にエラーが発生しました。このエラーが解決しない場合は、サイト管理者に問い合わせてください。 oauth.signin.error=認可リクエストの処理中にエラーが発生しました。このエラーが解決しない場合は、サイト管理者に問い合わせてください。
oauth.signin.error.access_denied=認可リクエストが拒否されました。 oauth.signin.error.access_denied=認可リクエストが拒否されました。
oauth.signin.error.temporarily_unavailable=認証サーバーが一時的に利用できないため、認可に失敗しました。後でもう一度やり直してください。 oauth.signin.error.temporarily_unavailable=認証サーバーが一時的に利用できないため、認可に失敗しました。後でもう一度やり直してください。
oauth_callback_unable_auto_reg=自動登録が有効になっていますが、OAuth2プロバイダー %[1]s の応答はフィールド %[2]s が不足しており、自動でアカウントを作成することができません。 アカウントを作成またはリンクするか、サイト管理者に問い合わせてください。
openid_connect_submit=接続 openid_connect_submit=接続
openid_connect_title=既存のアカウントに接続 openid_connect_title=既存のアカウントに接続
openid_connect_desc=選択したOpenID URIは未登録です。 ここで新しいアカウントと関連付けます。 openid_connect_desc=選択したOpenID URIは未登録です。 ここで新しいアカウントと関連付けます。
@ -763,6 +764,8 @@ manage_themes=デフォルトのテーマを選択
manage_openid=OpenIDアドレスの管理 manage_openid=OpenIDアドレスの管理
email_desc=プライマリメールアドレスは、通知、パスワードの回復、さらにメールアドレスを隠さない場合は、WebベースのGit操作にも使用されます。 email_desc=プライマリメールアドレスは、通知、パスワードの回復、さらにメールアドレスを隠さない場合は、WebベースのGit操作にも使用されます。
theme_desc=この設定がサイト全体のデフォルトのテーマとなります。 theme_desc=この設定がサイト全体のデフォルトのテーマとなります。
theme_colorblindness_help=色覚障害テーマのサポート
theme_colorblindness_prompt=Giteaには基本的な色覚障害サポートを含むテーマがいくつか入っていますが、それらは色定義が少ししかありません。 作業はまだ進行中です。 テーマCSSファイルにもっと多くの色を定義していくことで、さらに改善できる余地があります。
primary=プライマリー primary=プライマリー
activated=アクティベート済み activated=アクティベート済み
requires_activation=アクティベーションが必要 requires_activation=アクティベーションが必要
@ -3317,6 +3320,7 @@ self_check.database_collation_case_insensitive=データベースは照合順序
self_check.database_inconsistent_collation_columns=データベースは照合順序 %s を使用していますが、以下のカラムはそれと一致しない照合順序を使用しており、予期せぬ問題を引き起こす可能性があります。 self_check.database_inconsistent_collation_columns=データベースは照合順序 %s を使用していますが、以下のカラムはそれと一致しない照合順序を使用しており、予期せぬ問題を引き起こす可能性があります。
self_check.database_fix_mysql=MySQL/MariaDBユーザーの方は、"gitea doctor convert" コマンドを使用することで、照合順序の問題を修正できます。 また、"ALTER ... COLLATE ..." のSQLを手で実行しても修正することができます。 self_check.database_fix_mysql=MySQL/MariaDBユーザーの方は、"gitea doctor convert" コマンドを使用することで、照合順序の問題を修正できます。 また、"ALTER ... COLLATE ..." のSQLを手で実行しても修正することができます。
self_check.database_fix_mssql=MSSQLユーザーの方は、問題を修正するには今のところ "ALTER ... COLLATE ..." のSQLを手で実行するしかありません。 self_check.database_fix_mssql=MSSQLユーザーの方は、問題を修正するには今のところ "ALTER ... COLLATE ..." のSQLを手で実行するしかありません。
self_check.location_origin_mismatch=現在のURL (%[1]s) は、Giteaが見ているURL (%[2]s) に一致していません。 リバースプロキシを使用している場合は、"Host" ヘッダーと "X-Forwarded-Proto" ヘッダーが正しく設定されていることを確認してください。
[action] [action]
create_repo=がリポジトリ <a href="%s">%s</a> を作成しました create_repo=がリポジトリ <a href="%s">%s</a> を作成しました
@ -3344,6 +3348,7 @@ mirror_sync_create=が <a href="%[1]s">%[4]s</a> の新しい参照 <a href="%[2
mirror_sync_delete=が <a href="%[1]s">%[3]s</a> の参照 <code>%[2]s</code> をミラーから反映し、削除しました mirror_sync_delete=が <a href="%[1]s">%[3]s</a> の参照 <code>%[2]s</code> をミラーから反映し、削除しました
approve_pull_request=`が <a href="%[1]s">%[3]s#%[2]s</a> を承認しました` approve_pull_request=`が <a href="%[1]s">%[3]s#%[2]s</a> を承認しました`
reject_pull_request=`が <a href="%[1]s">%[3]s#%[2]s</a>について変更を提案しました` reject_pull_request=`が <a href="%[1]s">%[3]s#%[2]s</a>について変更を提案しました`
publish_release=`が <a href="%[1]s">%[3]s</a> の <a href="%[2]s">%[4]s</a> をリリースしました`
review_dismissed=`が <b>%[4]s</b> の <a href="%[1]s">%[3]s#%[2]s</a> へのレビューを棄却しました` review_dismissed=`が <b>%[4]s</b> の <a href="%[1]s">%[3]s#%[2]s</a> へのレビューを棄却しました`
review_dismissed_reason=理由: review_dismissed_reason=理由:
create_branch=がブランチ <a href="%[2]s">%[3]s</a> を <a href="%[1]s">%[4]s</a> に作成しました create_branch=がブランチ <a href="%[2]s">%[3]s</a> を <a href="%[1]s">%[4]s</a> に作成しました

View File

@ -541,6 +541,16 @@ func GrantApplicationOAuth(ctx *context.Context) {
ctx.Error(http.StatusBadRequest) ctx.Error(http.StatusBadRequest)
return return
} }
if !form.Granted {
handleAuthorizeError(ctx, AuthorizeError{
State: form.State,
ErrorDescription: "the request is denied",
ErrorCode: ErrorCodeAccessDenied,
}, form.RedirectURI)
return
}
app, err := auth.GetOAuth2ApplicationByClientID(ctx, form.ClientID) app, err := auth.GetOAuth2ApplicationByClientID(ctx, form.ClientID)
if err != nil { if err != nil {
ctx.ServerError("GetOAuth2ApplicationByClientID", err) ctx.ServerError("GetOAuth2ApplicationByClientID", err)

View File

@ -931,7 +931,7 @@ func ExcerptBlob(ctx *context.Context) {
} }
} }
ctx.Data["section"] = section ctx.Data["section"] = section
ctx.Data["FileNameHash"] = base.EncodeSha1(filePath) ctx.Data["FileNameHash"] = git.HashFilePathForWebUI(filePath)
ctx.Data["AfterCommitID"] = commitID ctx.Data["AfterCommitID"] = commitID
ctx.Data["Anchor"] = anchor ctx.Data["Anchor"] = anchor
ctx.HTML(http.StatusOK, tplBlobExcerpt) ctx.HTML(http.StatusOK, tplBlobExcerpt)

View File

@ -161,6 +161,7 @@ func (f *AuthorizationForm) Validate(req *http.Request, errs binding.Errors) bin
// GrantApplicationForm form for authorizing oauth2 clients // GrantApplicationForm form for authorizing oauth2 clients
type GrantApplicationForm struct { type GrantApplicationForm struct {
ClientID string `binding:"Required"` ClientID string `binding:"Required"`
Granted bool
RedirectURI string RedirectURI string
State string State string
Scope string Scope string

View File

@ -23,7 +23,6 @@ import (
pull_model "code.gitea.io/gitea/models/pull" pull_model "code.gitea.io/gitea/models/pull"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/analyze" "code.gitea.io/gitea/modules/analyze"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/charset" "code.gitea.io/gitea/modules/charset"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/highlight" "code.gitea.io/gitea/modules/highlight"
@ -746,7 +745,7 @@ parsingLoop:
diffLineTypeBuffers[DiffLineAdd] = new(bytes.Buffer) diffLineTypeBuffers[DiffLineAdd] = new(bytes.Buffer)
diffLineTypeBuffers[DiffLineDel] = new(bytes.Buffer) diffLineTypeBuffers[DiffLineDel] = new(bytes.Buffer)
for _, f := range diff.Files { for _, f := range diff.Files {
f.NameHash = base.EncodeSha1(f.Name) f.NameHash = git.HashFilePathForWebUI(f.Name)
for _, buffer := range diffLineTypeBuffers { for _, buffer := range diffLineTypeBuffers {
buffer.Reset() buffer.Reset()

View File

@ -1,4 +1,8 @@
{{if eq .PackageDescriptor.Package.Type "maven"}} {{if and (eq .PackageDescriptor.Package.Type "maven") (not .PackageDescriptor.Metadata)}}
<h4 class="ui top attached header">{{ctx.Locale.Tr "packages.installation"}}</h4>
<div class="ui attached segment">{{ctx.Locale.Tr "packages.no_metadata"}}</div>
{{end}}
{{if and (eq .PackageDescriptor.Package.Type "maven") .PackageDescriptor.Metadata}}
<h4 class="ui top attached header">{{ctx.Locale.Tr "packages.installation"}}</h4> <h4 class="ui top attached header">{{ctx.Locale.Tr "packages.installation"}}</h4>
<div class="ui attached segment"> <div class="ui attached segment">
<div class="ui form"> <div class="ui form">

View File

@ -1,4 +1,7 @@
{{if eq .PackageDescriptor.Package.Type "maven"}} {{if and (eq .PackageDescriptor.Package.Type "maven") (not .PackageDescriptor.Metadata)}}
<div class="item">{{svg "octicon-note" 16 "tw-mr-2"}} {{ctx.Locale.Tr "packages.no_metadata"}}</div>
{{end}}
{{if and (eq .PackageDescriptor.Package.Type "maven") .PackageDescriptor.Metadata}}
{{if .PackageDescriptor.Metadata.Name}}<div class="item">{{svg "octicon-note" 16 "tw-mr-2"}} {{.PackageDescriptor.Metadata.Name}}</div>{{end}} {{if .PackageDescriptor.Metadata.Name}}<div class="item">{{svg "octicon-note" 16 "tw-mr-2"}} {{.PackageDescriptor.Metadata.Name}}</div>{{end}}
{{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}} {{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
{{range .PackageDescriptor.Metadata.Licenses}}<div class="item" title="{{ctx.Locale.Tr "packages.details.license"}}">{{svg "octicon-law" 16 "tw-mr-2"}} {{.}}</div>{{end}} {{range .PackageDescriptor.Metadata.Licenses}}<div class="item" title="{{ctx.Locale.Tr "packages.details.license"}}">{{svg "octicon-law" 16 "tw-mr-2"}} {{.}}</div>{{end}}

View File

@ -68,18 +68,14 @@
{{range .Columns}} {{range .Columns}}
<div class="ui segment project-column"{{if .Color}} style="background: {{.Color}} !important; color: {{ContrastColor .Color}} !important"{{end}} data-id="{{.ID}}" data-sorting="{{.Sorting}}" data-url="{{$.Link}}/{{.ID}}"> <div class="ui segment project-column"{{if .Color}} style="background: {{.Color}} !important; color: {{ContrastColor .Color}} !important"{{end}} data-id="{{.ID}}" data-sorting="{{.Sorting}}" data-url="{{$.Link}}/{{.ID}}">
<div class="project-column-header{{if $canWriteProject}} tw-cursor-grab{{end}}"> <div class="project-column-header{{if $canWriteProject}} tw-cursor-grab{{end}}">
<div class="ui large label project-column-title tw-py-1"> <div class="ui circular label project-column-issue-count">
<div class="ui small circular grey label project-column-issue-count"> {{.NumIssues ctx}}
{{.NumIssues ctx}}
</div>
<span class="project-column-title-label">{{.Title}}</span>
</div> </div>
<div class="project-column-title-label gt-ellipsis">{{.Title}}</div>
{{if $canWriteProject}} {{if $canWriteProject}}
<div class="ui dropdown jump item"> <div class="ui dropdown tw-p-1">
<div class="tw-px-2"> {{svg "octicon-kebab-horizontal"}}
{{svg "octicon-kebab-horizontal"}} <div class="menu">
</div>
<div class="menu user-menu">
<a class="item show-modal button" data-modal="#edit-project-column-modal-{{.ID}}"> <a class="item show-modal button" data-modal="#edit-project-column-modal-{{.ID}}">
{{svg "octicon-pencil"}} {{svg "octicon-pencil"}}
{{ctx.Locale.Tr "repo.projects.column.edit"}} {{ctx.Locale.Tr "repo.projects.column.edit"}}

View File

@ -87,7 +87,7 @@
<td class="eight wide"> <td class="eight wide">
{{if .DBBranch.IsDeleted}} {{if .DBBranch.IsDeleted}}
<div class="flex-text-block"> <div class="flex-text-block">
<a class="gt-ellipsis" href="{{$.RepoLink}}/src/branch/{{PathEscapeSegments .DBBranch.Name}}">{{.DBBranch.Name}}</a> <span class="gt-ellipsis">{{.DBBranch.Name}}</span>
<button class="btn interact-fg tw-px-1" data-clipboard-text="{{.DBBranch.Name}}">{{svg "octicon-copy" 14}}</button> <button class="btn interact-fg tw-px-1" data-clipboard-text="{{.DBBranch.Name}}">{{svg "octicon-copy" 14}}</button>
</div> </div>
<p class="info">{{ctx.Locale.Tr "repo.branch.deleted_by" .DBBranch.DeletedBy.Name}} {{TimeSinceUnix .DBBranch.DeletedUnix ctx.Locale}}</p> <p class="info">{{ctx.Locale.Tr "repo.branch.deleted_by" .DBBranch.DeletedBy.Name}} {{TimeSinceUnix .DBBranch.DeletedUnix ctx.Locale}}</p>

View File

@ -1,4 +1,5 @@
{{$file := .file}} {{$file := .file}}
{{$blobExcerptRepoLink := or ctx.RootData.CommitRepoLink ctx.RootData.RepoLink}}
<colgroup> <colgroup>
<col width="50"> <col width="50">
<col width="10"> <col width="10">
@ -18,17 +19,17 @@
<td class="lines-num lines-num-old"> <td class="lines-num lines-num-old">
<div class="tw-flex"> <div class="tw-flex">
{{if or (eq $line.GetExpandDirection 3) (eq $line.GetExpandDirection 5)}} {{if or (eq $line.GetExpandDirection 3) (eq $line.GetExpandDirection 5)}}
<button class="code-expander-button" hx-target="closest tr" hx-get="{{$.root.RepoLink}}/blob_excerpt/{{PathEscape $.root.AfterCommitID}}?{{$line.GetBlobExcerptQuery}}&style=split&direction=down&wiki={{$.root.PageIsWiki}}&anchor=diff-{{$file.NameHash}}K{{$line.SectionInfo.RightIdx}}"> <button class="code-expander-button" hx-target="closest tr" hx-get="{{$blobExcerptRepoLink}}/blob_excerpt/{{PathEscape $.root.AfterCommitID}}?{{$line.GetBlobExcerptQuery}}&style=split&direction=down&wiki={{$.root.PageIsWiki}}&anchor=diff-{{$file.NameHash}}K{{$line.SectionInfo.RightIdx}}">
{{svg "octicon-fold-down"}} {{svg "octicon-fold-down"}}
</button> </button>
{{end}} {{end}}
{{if or (eq $line.GetExpandDirection 3) (eq $line.GetExpandDirection 4)}} {{if or (eq $line.GetExpandDirection 3) (eq $line.GetExpandDirection 4)}}
<button class="code-expander-button" hx-target="closest tr" hx-get="{{$.root.RepoLink}}/blob_excerpt/{{PathEscape $.root.AfterCommitID}}?{{$line.GetBlobExcerptQuery}}&style=split&direction=up&wiki={{$.root.PageIsWiki}}&anchor=diff-{{$file.NameHash}}K{{$line.SectionInfo.RightIdx}}"> <button class="code-expander-button" hx-target="closest tr" hx-get="{{$blobExcerptRepoLink}}/blob_excerpt/{{PathEscape $.root.AfterCommitID}}?{{$line.GetBlobExcerptQuery}}&style=split&direction=up&wiki={{$.root.PageIsWiki}}&anchor=diff-{{$file.NameHash}}K{{$line.SectionInfo.RightIdx}}">
{{svg "octicon-fold-up"}} {{svg "octicon-fold-up"}}
</button> </button>
{{end}} {{end}}
{{if eq $line.GetExpandDirection 2}} {{if eq $line.GetExpandDirection 2}}
<button class="code-expander-button" hx-target="closest tr" hx-get="{{$.root.RepoLink}}/blob_excerpt/{{PathEscape $.root.AfterCommitID}}?{{$line.GetBlobExcerptQuery}}&style=split&direction=&wiki={{$.root.PageIsWiki}}&anchor=diff-{{$file.NameHash}}K{{$line.SectionInfo.RightIdx}}"> <button class="code-expander-button" hx-target="closest tr" hx-get="{{$blobExcerptRepoLink}}/blob_excerpt/{{PathEscape $.root.AfterCommitID}}?{{$line.GetBlobExcerptQuery}}&style=split&direction=&wiki={{$.root.PageIsWiki}}&anchor=diff-{{$file.NameHash}}K{{$line.SectionInfo.RightIdx}}">
{{svg "octicon-fold"}} {{svg "octicon-fold"}}
</button> </button>
{{end}} {{end}}

View File

@ -1,4 +1,5 @@
{{$file := .file}} {{$file := .file}}
{{$blobExcerptRepoLink := or ctx.RootData.CommitRepoLink ctx.RootData.RepoLink}}
<colgroup> <colgroup>
<col width="50"> <col width="50">
<col width="50"> <col width="50">
@ -14,17 +15,17 @@
<td colspan="2" class="lines-num"> <td colspan="2" class="lines-num">
<div class="tw-flex"> <div class="tw-flex">
{{if or (eq $line.GetExpandDirection 3) (eq $line.GetExpandDirection 5)}} {{if or (eq $line.GetExpandDirection 3) (eq $line.GetExpandDirection 5)}}
<button class="code-expander-button" hx-target="closest tr" hx-get="{{$.root.RepoLink}}/blob_excerpt/{{PathEscape $.root.AfterCommitID}}?{{$line.GetBlobExcerptQuery}}&style=unified&direction=down&wiki={{$.root.PageIsWiki}}&anchor=diff-{{$file.NameHash}}K{{$line.SectionInfo.RightIdx}}"> <button class="code-expander-button" hx-target="closest tr" hx-get="{{$blobExcerptRepoLink}}/blob_excerpt/{{PathEscape $.root.AfterCommitID}}?{{$line.GetBlobExcerptQuery}}&style=unified&direction=down&wiki={{$.root.PageIsWiki}}&anchor=diff-{{$file.NameHash}}K{{$line.SectionInfo.RightIdx}}">
{{svg "octicon-fold-down"}} {{svg "octicon-fold-down"}}
</button> </button>
{{end}} {{end}}
{{if or (eq $line.GetExpandDirection 3) (eq $line.GetExpandDirection 4)}} {{if or (eq $line.GetExpandDirection 3) (eq $line.GetExpandDirection 4)}}
<button class="code-expander-button" hx-target="closest tr" hx-get="{{$.root.RepoLink}}/blob_excerpt/{{PathEscape $.root.AfterCommitID}}?{{$line.GetBlobExcerptQuery}}&style=unified&direction=up&wiki={{$.root.PageIsWiki}}&anchor=diff-{{$file.NameHash}}K{{$line.SectionInfo.RightIdx}}"> <button class="code-expander-button" hx-target="closest tr" hx-get="{{$blobExcerptRepoLink}}/blob_excerpt/{{PathEscape $.root.AfterCommitID}}?{{$line.GetBlobExcerptQuery}}&style=unified&direction=up&wiki={{$.root.PageIsWiki}}&anchor=diff-{{$file.NameHash}}K{{$line.SectionInfo.RightIdx}}">
{{svg "octicon-fold-up"}} {{svg "octicon-fold-up"}}
</button> </button>
{{end}} {{end}}
{{if eq $line.GetExpandDirection 2}} {{if eq $line.GetExpandDirection 2}}
<button class="code-expander-button" hx-target="closest tr" hx-get="{{$.root.RepoLink}}/blob_excerpt/{{PathEscape $.root.AfterCommitID}}?{{$line.GetBlobExcerptQuery}}&style=unified&direction=&wiki={{$.root.PageIsWiki}}&anchor=diff-{{$file.NameHash}}K{{$line.SectionInfo.RightIdx}}"> <button class="code-expander-button" hx-target="closest tr" hx-get="{{$blobExcerptRepoLink}}/blob_excerpt/{{PathEscape $.root.AfterCommitID}}?{{$line.GetBlobExcerptQuery}}&style=unified&direction=&wiki={{$.root.PageIsWiki}}&anchor=diff-{{$file.NameHash}}K{{$line.SectionInfo.RightIdx}}">
{{svg "octicon-fold"}} {{svg "octicon-fold"}}
</button> </button>
{{end}} {{end}}

View File

@ -23,8 +23,8 @@
<input type="hidden" name="scope" value="{{.Scope}}"> <input type="hidden" name="scope" value="{{.Scope}}">
<input type="hidden" name="nonce" value="{{.Nonce}}"> <input type="hidden" name="nonce" value="{{.Nonce}}">
<input type="hidden" name="redirect_uri" value="{{.RedirectURI}}"> <input type="hidden" name="redirect_uri" value="{{.RedirectURI}}">
<button type="submit" id="authorize-app" value="{{ctx.Locale.Tr "auth.authorize_application"}}" class="ui red inline button">{{ctx.Locale.Tr "auth.authorize_application"}}</button> <button type="submit" id="authorize-app" name="granted" value="true" class="ui red inline button">{{ctx.Locale.Tr "auth.authorize_application"}}</button>
<a href="{{.RedirectURI}}" class="ui basic primary inline button">Cancel</a> <button type="submit" name="granted" value="false" class="ui basic primary inline button">{{ctx.Locale.Tr "cancel"}}</button>
</form> </form>
</div> </div>
</div> </div>

View File

@ -15,6 +15,7 @@ import (
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/packages/maven" "code.gitea.io/gitea/modules/packages/maven"
"code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/tests" "code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -241,4 +242,13 @@ func TestPackageMaven(t *testing.T) {
putFile(t, fmt.Sprintf("/%s/maven-metadata.xml", snapshotVersion), "test", http.StatusCreated) putFile(t, fmt.Sprintf("/%s/maven-metadata.xml", snapshotVersion), "test", http.StatusCreated)
putFile(t, fmt.Sprintf("/%s/maven-metadata.xml", snapshotVersion), "test-overwrite", http.StatusCreated) putFile(t, fmt.Sprintf("/%s/maven-metadata.xml", snapshotVersion), "test-overwrite", http.StatusCreated)
}) })
t.Run("InvalidFile", func(t *testing.T) {
ver := packageVersion + "-invalid"
putFile(t, fmt.Sprintf("/%s/%s", ver, filename), "any invalid content", http.StatusCreated)
req := NewRequestf(t, "GET", "/%s/-/packages/maven/%s-%s/%s", user.Name, groupID, artifactID, ver)
resp := MakeRequest(t, req, http.StatusOK)
assert.Contains(t, resp.Body.String(), "No metadata.")
assert.True(t, test.IsNormalPageCompleted(resp.Body.String()))
})
} }

View File

@ -6,9 +6,14 @@ package integration
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"net/url"
"strings" "strings"
"testing" "testing"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
repo_service "code.gitea.io/gitea/services/repository"
"code.gitea.io/gitea/tests" "code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -118,3 +123,37 @@ func TestCompareBranches(t *testing.T) {
inspectCompare(t, htmlDoc, diffCount, diffChanges) inspectCompare(t, htmlDoc, diffCount, diffChanges)
} }
func TestCompareCodeExpand(t *testing.T) {
onGiteaRun(t, func(t *testing.T, u *url.URL) {
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
repo, err := repo_service.CreateRepositoryDirectly(db.DefaultContext, user1, user1, repo_service.CreateRepoOptions{
Name: "test_blob_excerpt",
Readme: "Default",
AutoInit: true,
DefaultBranch: "main",
})
assert.NoError(t, err)
session := loginUser(t, user1.Name)
testEditFile(t, session, user1.Name, repo.Name, "main", "README.md", strings.Repeat("a\n", 30))
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
session = loginUser(t, user2.Name)
testRepoFork(t, session, user1.Name, repo.Name, user2.Name, "test_blob_excerpt-fork")
testCreateBranch(t, session, user2.Name, "test_blob_excerpt-fork", "branch/main", "forked-branch", http.StatusSeeOther)
testEditFile(t, session, user2.Name, "test_blob_excerpt-fork", "forked-branch", "README.md", strings.Repeat("a\n", 15)+"CHANGED\n"+strings.Repeat("a\n", 15))
req := NewRequest(t, "GET", "/user1/test_blob_excerpt/compare/main...user2/test_blob_excerpt-fork:forked-branch")
resp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
els := htmlDoc.Find(`button.code-expander-button[hx-get]`)
// all the links in the comparison should be to the forked repo&branch
assert.NotZero(t, els.Length())
for i := 0; i < els.Length(); i++ {
link := els.Eq(i).AttrOr("hx-get", "")
assert.True(t, strings.HasPrefix(link, "/user2/test_blob_excerpt-fork/blob_excerpt/"))
}
})
}

View File

@ -9,13 +9,15 @@ import (
"testing" "testing"
unit_model "code.gitea.io/gitea/models/unit" unit_model "code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/tests" "code.gitea.io/gitea/tests"
) )
func TestOrgProjectAccess(t *testing.T) { func TestOrgProjectAccess(t *testing.T) {
defer tests.PrepareTestEnv(t)() defer tests.PrepareTestEnv(t)()
defer test.MockVariableValue(&unit_model.DisabledRepoUnits, append(slices.Clone(unit_model.DisabledRepoUnits), unit_model.TypeProjects))()
disabledRepoUnits := unit_model.DisabledRepoUnitsGet()
unit_model.DisabledRepoUnitsSet(append(slices.Clone(disabledRepoUnits), unit_model.TypeProjects))
defer unit_model.DisabledRepoUnitsSet(disabledRepoUnits)
// repo project, 404 // repo project, 404
req := NewRequest(t, "GET", "/user2/repo1/projects") req := NewRequest(t, "GET", "/user2/repo1/projects")

View File

@ -14,7 +14,6 @@
width: 320px; width: 320px;
height: calc(100vh - 450px); height: calc(100vh - 450px);
min-height: 60vh; min-height: 60vh;
overflow-y: scroll;
flex: 0 0 auto; flex: 0 0 auto;
overflow: visible; overflow: visible;
display: flex; display: flex;
@ -30,17 +29,15 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
gap: 0.5em;
} }
.project-column-title { .ui.label.project-column-issue-count {
background: none !important; color: inherit;
line-height: 1.25 !important;
cursor: inherit;
} }
.project-column-title, .project-column-title-label {
.project-column-issue-count { flex: 1;
color: inherit !important;
} }
.project-column > .cards { .project-column > .cards {