mirror of
https://github.com/go-gitea/gitea.git
synced 2025-08-14 00:02:41 -04:00
Compare commits
8 Commits
edbf74c418
...
1007ce764e
Author | SHA1 | Date | |
---|---|---|---|
|
1007ce764e | ||
|
ba83d27ab0 | ||
|
fb1ad920b7 | ||
|
f1d9f18d96 | ||
|
de9bcd1d23 | ||
|
f48cc501c4 | ||
|
b6574099ed | ||
|
47accfebbd |
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
})
|
})
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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) {
|
||||||
|
@ -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
17
modules/git/utils_test.go
Normal 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"),
|
||||||
|
)
|
||||||
|
}
|
@ -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) {
|
||||||
|
@ -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>.
|
||||||
|
@ -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> に作成しました
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
||||||
|
@ -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">
|
||||||
|
@ -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}}
|
||||||
|
@ -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"}}
|
||||||
|
@ -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>
|
||||||
|
@ -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}}
|
||||||
|
@ -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}}
|
||||||
|
@ -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>
|
||||||
|
@ -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()))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
@ -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/"))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -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")
|
||||||
|
@ -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 {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user