mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-12-19 07:19:51 +00:00
Merge branch 'rebase-forgejo-dependency' into wip-forgejo
This commit is contained in:
commit
2337fe54dc
10
assets/go-licenses.json
generated
10
assets/go-licenses.json
generated
File diff suppressed because one or more lines are too long
|
@ -809,6 +809,11 @@ LEVEL = Info
|
|||
;; Every new user will have restricted permissions depending on this setting
|
||||
;DEFAULT_USER_IS_RESTRICTED = false
|
||||
;;
|
||||
;; Users will be able to use dots when choosing their username. Disabling this is
|
||||
;; helpful if your usersare having issues with e.g. RSS feeds or advanced third-party
|
||||
;; extensions that use strange regex patterns.
|
||||
; ALLOW_DOTS_IN_USERNAMES = true
|
||||
;;
|
||||
;; Either "public", "limited" or "private", default is "public"
|
||||
;; Limited is for users visible only to signed users
|
||||
;; Private is for users visible only to members of their organizations
|
||||
|
@ -1451,6 +1456,8 @@ LEVEL = Info
|
|||
;;
|
||||
;; Default configuration for email notifications for users (user configurable). Options: enabled, onmention, disabled
|
||||
;DEFAULT_EMAIL_NOTIFICATIONS = enabled
|
||||
;; Send an email to all admins when a new user signs up to inform the admins about this act. Options: true, false
|
||||
;SEND_NOTIFICATION_EMAIL_ON_NEW_USER = false
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
@ -1765,9 +1772,6 @@ LEVEL = Info
|
|||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;
|
||||
;AVATAR_UPLOAD_PATH = data/avatars
|
||||
;REPOSITORY_AVATAR_UPLOAD_PATH = data/repo-avatars
|
||||
;;
|
||||
;; How Gitea deals with missing repository avatars
|
||||
;; none = no avatar will be displayed; random = random avatar will be displayed; image = default image will be used
|
||||
;REPOSITORY_AVATAR_FALLBACK = none
|
||||
|
|
|
@ -510,6 +510,7 @@ And the following unique queues:
|
|||
|
||||
- `DEFAULT_EMAIL_NOTIFICATIONS`: **enabled**: Default configuration for email notifications for users (user configurable). Options: enabled, onmention, disabled
|
||||
- `DISABLE_REGULAR_ORG_CREATION`: **false**: Disallow regular (non-admin) users from creating organizations.
|
||||
- `SEND_NOTIFICATION_EMAIL_ON_NEW_USER`: **false**: Send an email to all admins when a new user signs up to inform the admins about this act.
|
||||
|
||||
## Security (`security`)
|
||||
|
||||
|
|
8
go.mod
8
go.mod
|
@ -15,7 +15,6 @@ require (
|
|||
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4
|
||||
github.com/42wim/sshsig v0.0.0-20211121163825-841cf5bbc121
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358
|
||||
github.com/NYTimes/gziphandler v1.1.1
|
||||
github.com/PuerkitoBio/goquery v1.8.1
|
||||
github.com/alecthomas/chroma/v2 v2.9.1
|
||||
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb
|
||||
|
@ -36,7 +35,7 @@ require (
|
|||
github.com/ethantkoenig/rupture v1.0.1
|
||||
github.com/felixge/fgprof v0.9.3
|
||||
github.com/fsnotify/fsnotify v1.6.0
|
||||
github.com/gliderlabs/ssh v0.3.5
|
||||
github.com/gliderlabs/ssh v0.3.6-0.20230927171611-ece6c7995e46
|
||||
github.com/go-ap/activitypub v0.0.0-20231003111253-1fba3772399b
|
||||
github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73
|
||||
github.com/go-chi/chi/v5 v5.0.10
|
||||
|
@ -78,7 +77,6 @@ require (
|
|||
github.com/mholt/archiver/v3 v3.5.1
|
||||
github.com/microcosm-cc/bluemonday v1.0.25
|
||||
github.com/minio/minio-go/v7 v7.0.63
|
||||
github.com/minio/sha256-simd v1.0.1
|
||||
github.com/msteinert/pam v1.2.0
|
||||
github.com/nektos/act v0.2.52
|
||||
github.com/niklasfasching/go-org v1.7.0
|
||||
|
@ -101,7 +99,6 @@ require (
|
|||
github.com/ulikunitz/xz v0.5.11
|
||||
github.com/urfave/cli/v2 v2.25.7
|
||||
github.com/xanzy/go-gitlab v0.93.0
|
||||
github.com/xeipuuv/gojsonschema v1.2.0
|
||||
github.com/yohcop/openid-go v1.0.1
|
||||
github.com/yuin/goldmark v1.5.6
|
||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
|
||||
|
@ -234,6 +231,7 @@ require (
|
|||
github.com/mholt/acmez v1.2.0 // indirect
|
||||
github.com/miekg/dns v1.1.56 // indirect
|
||||
github.com/minio/md5-simd v1.1.2 // indirect
|
||||
github.com/minio/sha256-simd v1.0.1 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
|
@ -277,8 +275,6 @@ require (
|
|||
github.com/valyala/fastjson v1.6.4 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||
github.com/zeebo/blake3 v0.2.3 // indirect
|
||||
|
|
17
go.sum
17
go.sum
|
@ -101,8 +101,6 @@ github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBa
|
|||
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
||||
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
||||
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
||||
github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
|
||||
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c h1:kMFnB0vCcX7IL/m9Y5LO+KQYv+t1CQOiFe6+SV2J7bE=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
|
||||
github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM=
|
||||
|
@ -328,8 +326,8 @@ github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4
|
|||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
github.com/fxamacker/cbor/v2 v2.5.0 h1:oHsG0V/Q6E/wqTS2O1Cozzsy69nqCiguo5Q1a1ADivE=
|
||||
github.com/fxamacker/cbor/v2 v2.5.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
|
||||
github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY=
|
||||
github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4=
|
||||
github.com/gliderlabs/ssh v0.3.6-0.20230927171611-ece6c7995e46 h1:fYiA820jw7wmAvdXrHwMItxjJkra7dT9y8yiXhtzb94=
|
||||
github.com/gliderlabs/ssh v0.3.6-0.20230927171611-ece6c7995e46/go.mod h1:i/TCLcdiX9Up/vs+Rp8c3yMbqp2Y4Y7Nh9uzGFCa5pM=
|
||||
github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE=
|
||||
github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24=
|
||||
github.com/go-ap/activitypub v0.0.0-20231003111253-1fba3772399b h1:VLD6IPBDkqEsOZ+EfLO6MayuHycZ0cv4BStTlRoZduo=
|
||||
|
@ -1041,13 +1039,6 @@ github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3k
|
|||
github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
|
||||
github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
|
||||
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
|
@ -1231,7 +1222,6 @@ golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qx
|
|||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
|
@ -1331,9 +1321,7 @@ golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
@ -1347,7 +1335,6 @@ golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||
|
|
|
@ -169,7 +169,12 @@ func RewriteAllPublicKeys(ctx context.Context) error {
|
|||
return err
|
||||
}
|
||||
|
||||
t.Close()
|
||||
if err := t.Sync(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := t.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
return util.Rename(tmpPath, fPath)
|
||||
}
|
||||
|
||||
|
|
|
@ -92,7 +92,12 @@ func RewriteAllPrincipalKeys(ctx context.Context) error {
|
|||
return err
|
||||
}
|
||||
|
||||
t.Close()
|
||||
if err := t.Sync(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := t.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
return util.Rename(tmpPath, fPath)
|
||||
}
|
||||
|
||||
|
|
96
models/auth/auth_token.go
Normal file
96
models/auth/auth_token.go
Normal file
|
@ -0,0 +1,96 @@
|
|||
// Copyright 2023 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
// AuthorizationToken represents a authorization token to a user.
|
||||
type AuthorizationToken struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
UID int64 `xorm:"INDEX"`
|
||||
LookupKey string `xorm:"INDEX UNIQUE"`
|
||||
HashedValidator string
|
||||
Expiry timeutil.TimeStamp
|
||||
}
|
||||
|
||||
// TableName provides the real table name.
|
||||
func (AuthorizationToken) TableName() string {
|
||||
return "forgejo_auth_token"
|
||||
}
|
||||
|
||||
func init() {
|
||||
db.RegisterModel(new(AuthorizationToken))
|
||||
}
|
||||
|
||||
// IsExpired returns if the authorization token is expired.
|
||||
func (authToken *AuthorizationToken) IsExpired() bool {
|
||||
return authToken.Expiry.AsLocalTime().Before(time.Now())
|
||||
}
|
||||
|
||||
// GenerateAuthToken generates a new authentication token for the given user.
|
||||
// It returns the lookup key and validator values that should be passed to the
|
||||
// user via a long-term cookie.
|
||||
func GenerateAuthToken(ctx context.Context, userID int64, expiry timeutil.TimeStamp) (lookupKey, validator string, err error) {
|
||||
// Request 64 random bytes. The first 32 bytes will be used for the lookupKey
|
||||
// and the other 32 bytes will be used for the validator.
|
||||
rBytes, err := util.CryptoRandomBytes(64)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
hexEncoded := hex.EncodeToString(rBytes)
|
||||
validator, lookupKey = hexEncoded[64:], hexEncoded[:64]
|
||||
|
||||
_, err = db.GetEngine(ctx).Insert(&AuthorizationToken{
|
||||
UID: userID,
|
||||
Expiry: expiry,
|
||||
LookupKey: lookupKey,
|
||||
HashedValidator: HashValidator(rBytes[32:]),
|
||||
})
|
||||
return lookupKey, validator, err
|
||||
}
|
||||
|
||||
// FindAuthToken will find a authorization token via the lookup key.
|
||||
func FindAuthToken(ctx context.Context, lookupKey string) (*AuthorizationToken, error) {
|
||||
var authToken AuthorizationToken
|
||||
has, err := db.GetEngine(ctx).Where("lookup_key = ?", lookupKey).Get(&authToken)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, fmt.Errorf("lookup key %q: %w", lookupKey, util.ErrNotExist)
|
||||
}
|
||||
return &authToken, nil
|
||||
}
|
||||
|
||||
// DeleteAuthToken will delete the authorization token.
|
||||
func DeleteAuthToken(ctx context.Context, authToken *AuthorizationToken) error {
|
||||
_, err := db.DeleteByBean(ctx, authToken)
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteAuthTokenByUser will delete all authorization tokens for the user.
|
||||
func DeleteAuthTokenByUser(ctx context.Context, userID int64) error {
|
||||
if userID == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err := db.DeleteByBean(ctx, &AuthorizationToken{UID: userID})
|
||||
return err
|
||||
}
|
||||
|
||||
// HashValidator will return a hexified hashed version of the validator.
|
||||
func HashValidator(validator []byte) string {
|
||||
h := sha256.New()
|
||||
h.Write(validator)
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
|
@ -5,6 +5,7 @@ package auth
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/base32"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
|
@ -19,7 +20,6 @@ import (
|
|||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
uuid "github.com/google/uuid"
|
||||
"github.com/minio/sha256-simd"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"xorm.io/builder"
|
||||
"xorm.io/xorm"
|
||||
|
|
|
@ -250,7 +250,7 @@ func (s AccessTokenScope) parse() (accessTokenScopeBitmap, error) {
|
|||
remainingScopes = remainingScopes[i+1:]
|
||||
}
|
||||
singleScope := AccessTokenScope(v)
|
||||
if singleScope == "" {
|
||||
if singleScope == "" || singleScope == "sudo" {
|
||||
continue
|
||||
}
|
||||
if singleScope == AccessTokenScopeAll {
|
||||
|
|
|
@ -20,7 +20,7 @@ func TestAccessTokenScope_Normalize(t *testing.T) {
|
|||
tests := []scopeTestNormalize{
|
||||
{"", "", nil},
|
||||
{"write:misc,write:notification,read:package,write:notification,public-only", "public-only,write:misc,write:notification,read:package", nil},
|
||||
{"all", "all", nil},
|
||||
{"all,sudo", "all", nil},
|
||||
{"write:activitypub,write:admin,write:misc,write:notification,write:organization,write:package,write:issue,write:repository,write:user", "all", nil},
|
||||
{"write:activitypub,write:admin,write:misc,write:notification,write:organization,write:package,write:issue,write:repository,write:user,public-only", "public-only,all", nil},
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ package auth
|
|||
import (
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"crypto/sha256"
|
||||
"crypto/subtle"
|
||||
"encoding/base32"
|
||||
"encoding/base64"
|
||||
|
@ -18,7 +19,6 @@ import (
|
|||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"github.com/minio/sha256-simd"
|
||||
"github.com/pquerna/otp/totp"
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
)
|
||||
|
|
|
@ -11,10 +11,13 @@ import (
|
|||
"io"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"xorm.io/xorm"
|
||||
"xorm.io/xorm/contexts"
|
||||
"xorm.io/xorm/names"
|
||||
"xorm.io/xorm/schemas"
|
||||
|
||||
|
@ -147,6 +150,13 @@ func InitEngine(ctx context.Context) error {
|
|||
xormEngine.SetConnMaxLifetime(setting.Database.ConnMaxLifetime)
|
||||
xormEngine.SetDefaultContext(ctx)
|
||||
|
||||
if setting.Database.SlowQueryTreshold > 0 {
|
||||
xormEngine.AddHook(&SlowQueryHook{
|
||||
Treshold: setting.Database.SlowQueryTreshold,
|
||||
Logger: log.GetLogger("xorm"),
|
||||
})
|
||||
}
|
||||
|
||||
SetDefaultEngine(ctx, xormEngine)
|
||||
return nil
|
||||
}
|
||||
|
@ -300,3 +310,21 @@ func SetLogSQL(ctx context.Context, on bool) {
|
|||
sess.Engine().ShowSQL(on)
|
||||
}
|
||||
}
|
||||
|
||||
type SlowQueryHook struct {
|
||||
Treshold time.Duration
|
||||
Logger log.Logger
|
||||
}
|
||||
|
||||
var _ contexts.Hook = &SlowQueryHook{}
|
||||
|
||||
func (SlowQueryHook) BeforeProcess(c *contexts.ContextHook) (context.Context, error) {
|
||||
return c.Ctx, nil
|
||||
}
|
||||
|
||||
func (h *SlowQueryHook) AfterProcess(c *contexts.ContextHook) error {
|
||||
if c.ExecuteTime >= h.Treshold {
|
||||
h.Logger.Log(8, log.WARN, "[Slow SQL Query] %s %v - %v", c.SQL, c.Args, c.ExecuteTime)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -6,15 +6,19 @@ package db_test
|
|||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
|
||||
_ "code.gitea.io/gitea/cmd" // for TestPrimaryKeys
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func TestDumpDatabase(t *testing.T) {
|
||||
|
@ -85,3 +89,37 @@ func TestPrimaryKeys(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSlowQuery(t *testing.T) {
|
||||
lc, cleanup := test.NewLogChecker("slow-query")
|
||||
lc.StopMark("[Slow SQL Query]")
|
||||
defer cleanup()
|
||||
|
||||
e := db.GetEngine(db.DefaultContext)
|
||||
engine, ok := e.(*xorm.Engine)
|
||||
assert.True(t, ok)
|
||||
|
||||
// It's not possible to clean this up with XORM, but it's luckily not harmful
|
||||
// to leave around.
|
||||
engine.AddHook(&db.SlowQueryHook{
|
||||
Treshold: time.Second * 10,
|
||||
Logger: log.GetLogger("slow-query"),
|
||||
})
|
||||
|
||||
// NOOP query.
|
||||
e.Exec("SELECT 1 WHERE false;")
|
||||
|
||||
_, stopped := lc.Check(100 * time.Millisecond)
|
||||
assert.False(t, stopped)
|
||||
|
||||
engine.AddHook(&db.SlowQueryHook{
|
||||
Treshold: 0, // Every query should be logged.
|
||||
Logger: log.GetLogger("slow-query"),
|
||||
})
|
||||
|
||||
// NOOP query.
|
||||
e.Exec("SELECT 1 WHERE false;")
|
||||
|
||||
_, stopped = lc.Check(100 * time.Millisecond)
|
||||
assert.True(t, stopped)
|
||||
}
|
||||
|
|
|
@ -150,3 +150,17 @@
|
|||
is_prerelease: false
|
||||
is_tag: false
|
||||
created_unix: 946684803
|
||||
|
||||
- id: 12
|
||||
repo_id: 59
|
||||
publisher_id: 2
|
||||
tag_name: "v1.0"
|
||||
lower_tag_name: "v1.0"
|
||||
target: "main"
|
||||
title: "v1.0"
|
||||
sha1: "d8f53dfb33f6ccf4169c34970b5e747511c18beb"
|
||||
num_commits: 1
|
||||
is_draft: false
|
||||
is_prerelease: false
|
||||
is_tag: false
|
||||
created_unix: 946684803
|
||||
|
|
|
@ -608,6 +608,38 @@
|
|||
type: 1
|
||||
created_unix: 946684810
|
||||
|
||||
# BEGIN Forgejo [GITEA] Improve HTML title on repositories
|
||||
-
|
||||
id: 1093
|
||||
repo_id: 59
|
||||
type: 1
|
||||
created_unix: 946684810
|
||||
|
||||
-
|
||||
id: 1094
|
||||
repo_id: 59
|
||||
type: 2
|
||||
created_unix: 946684810
|
||||
|
||||
-
|
||||
id: 1095
|
||||
repo_id: 59
|
||||
type: 3
|
||||
created_unix: 946684810
|
||||
|
||||
-
|
||||
id: 1096
|
||||
repo_id: 59
|
||||
type: 4
|
||||
created_unix: 946684810
|
||||
|
||||
-
|
||||
id: 1097
|
||||
repo_id: 59
|
||||
type: 5
|
||||
created_unix: 946684810
|
||||
# END Forgejo [GITEA] Improve HTML title on repositories
|
||||
|
||||
-
|
||||
id: 91
|
||||
repo_id: 58
|
||||
|
|
|
@ -1467,6 +1467,7 @@
|
|||
owner_name: user27
|
||||
lower_name: repo49
|
||||
name: repo49
|
||||
description: A wonderful repository with more than just a README.md
|
||||
default_branch: master
|
||||
num_watches: 0
|
||||
num_stars: 0
|
||||
|
@ -1693,3 +1694,16 @@
|
|||
size: 0
|
||||
is_fsck_enabled: true
|
||||
close_issues_via_commit_in_any_branch: false
|
||||
|
||||
-
|
||||
id: 59
|
||||
owner_id: 2
|
||||
owner_name: user2
|
||||
lower_name: repo59
|
||||
name: repo59
|
||||
default_branch: master
|
||||
is_empty: false
|
||||
is_archived: false
|
||||
is_private: false
|
||||
status: 0
|
||||
num_issues: 0
|
||||
|
|
|
@ -66,7 +66,7 @@
|
|||
num_followers: 2
|
||||
num_following: 1
|
||||
num_stars: 2
|
||||
num_repos: 14
|
||||
num_repos: 15
|
||||
num_teams: 0
|
||||
num_members: 0
|
||||
visibility: 0
|
||||
|
|
|
@ -41,6 +41,8 @@ var migrations = []*Migration{
|
|||
NewMigration("Add Forgejo Blocked Users table", forgejo_v1_20.AddForgejoBlockedUser),
|
||||
// v1 -> v2
|
||||
NewMigration("create the forgejo_sem_ver table", forgejo_v1_20.CreateSemVerTable),
|
||||
// v2 -> v3
|
||||
NewMigration("create the forgejo_auth_token table", forgejo_v1_20.CreateAuthorizationTokenTable),
|
||||
}
|
||||
|
||||
// GetCurrentDBVersion returns the current Forgejo database version.
|
||||
|
|
25
models/forgejo_migrations/v1_20/v3.go
Normal file
25
models/forgejo_migrations/v1_20/v3.go
Normal file
|
@ -0,0 +1,25 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package forgejo_v1_20 //nolint:revive
|
||||
|
||||
import (
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
type AuthorizationToken struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
UID int64 `xorm:"INDEX"`
|
||||
LookupKey string `xorm:"INDEX UNIQUE"`
|
||||
HashedValidator string
|
||||
Expiry timeutil.TimeStamp
|
||||
}
|
||||
|
||||
func (AuthorizationToken) TableName() string {
|
||||
return "forgejo_auth_token"
|
||||
}
|
||||
|
||||
func CreateAuthorizationTokenTable(x *xorm.Engine) error {
|
||||
return x.Sync(new(AuthorizationToken))
|
||||
}
|
|
@ -342,7 +342,7 @@ func (c *Comment) AfterLoad(session *xorm.Session) {
|
|||
|
||||
// LoadPoster loads comment poster
|
||||
func (c *Comment) LoadPoster(ctx context.Context) (err error) {
|
||||
if c.PosterID <= 0 || c.Poster != nil {
|
||||
if c.Poster != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
package base
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
|
||||
"github.com/minio/sha256-simd"
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
)
|
||||
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
package v1_14 //nolint
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
|
||||
"github.com/minio/sha256-simd"
|
||||
"golang.org/x/crypto/argon2"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
|
|
|
@ -584,9 +584,9 @@ func (repo *Repository) DescriptionHTML(ctx context.Context) template.HTML {
|
|||
}, repo.Description)
|
||||
if err != nil {
|
||||
log.Error("Failed to render description for %s (ID: %d): %v", repo.Name, repo.ID, err)
|
||||
return template.HTML(markup.Sanitize(repo.Description))
|
||||
return template.HTML(markup.SanitizeDescription(repo.Description))
|
||||
}
|
||||
return template.HTML(markup.Sanitize(desc))
|
||||
return template.HTML(markup.SanitizeDescription(desc))
|
||||
}
|
||||
|
||||
// CloneLink represents different types of clone URLs of repository.
|
||||
|
|
|
@ -138,12 +138,12 @@ func getTestCases() []struct {
|
|||
{
|
||||
name: "AllPublic/PublicRepositoriesOfUserIncludingCollaborative",
|
||||
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, AllPublic: true, Template: util.OptionalBoolFalse},
|
||||
count: 31,
|
||||
count: 32,
|
||||
},
|
||||
{
|
||||
name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborative",
|
||||
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Private: true, AllPublic: true, AllLimited: true, Template: util.OptionalBoolFalse},
|
||||
count: 36,
|
||||
count: 37,
|
||||
},
|
||||
{
|
||||
name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborativeByName",
|
||||
|
@ -158,7 +158,7 @@ func getTestCases() []struct {
|
|||
{
|
||||
name: "AllPublic/PublicRepositoriesOfOrganization",
|
||||
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 17, AllPublic: true, Collaborate: util.OptionalBoolFalse, Template: util.OptionalBoolFalse},
|
||||
count: 31,
|
||||
count: 32,
|
||||
},
|
||||
{
|
||||
name: "AllTemplates",
|
||||
|
|
|
@ -226,6 +226,12 @@ func GetAllUsers(ctx context.Context) ([]*User, error) {
|
|||
return users, db.GetEngine(ctx).OrderBy("id").In("type", UserTypeIndividual, UserTypeF3).Find(&users)
|
||||
}
|
||||
|
||||
// GetAllAdmins returns a slice of all adminusers found in DB.
|
||||
func GetAllAdmins(ctx context.Context) ([]*User, error) {
|
||||
users := make([]*User, 0)
|
||||
return users, db.GetEngine(ctx).OrderBy("id").Where("type = ?", UserTypeIndividual).And("is_admin = ?", true).Find(&users)
|
||||
}
|
||||
|
||||
// IsLocal returns true if user login type is LoginPlain.
|
||||
func (u *User) IsLocal() bool {
|
||||
return u.LoginType <= auth.Plain
|
||||
|
@ -383,6 +389,11 @@ func (u *User) SetPassword(passwd string) (err error) {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Invalidate all authentication tokens for this user.
|
||||
if err := auth.DeleteAuthTokenByUser(db.DefaultContext, u.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if u.Salt, err = GetUserSalt(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -9,10 +9,12 @@ import (
|
|||
"code.gitea.io/gitea/modules/structs"
|
||||
)
|
||||
|
||||
const GhostUserID = -1
|
||||
|
||||
// NewGhostUser creates and returns a fake user for someone has deleted their account.
|
||||
func NewGhostUser() *User {
|
||||
return &User{
|
||||
ID: -1,
|
||||
ID: GhostUserID,
|
||||
Name: "Ghost",
|
||||
LowerName: "ghost",
|
||||
}
|
||||
|
|
|
@ -550,3 +550,13 @@ func Test_ValidateUser(t *testing.T) {
|
|||
assert.EqualValues(t, expected, err == nil, fmt.Sprintf("case: %+v", kase))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAllAdmins(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
admins, err := user_model.GetAllAdmins(db.DefaultContext)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Len(t, admins, 1)
|
||||
assert.Equal(t, int64(1), admins[0].ID)
|
||||
}
|
||||
|
|
|
@ -4,12 +4,12 @@
|
|||
package hash
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
|
||||
"github.com/minio/sha256-simd"
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
)
|
||||
|
||||
|
|
|
@ -4,10 +4,9 @@
|
|||
package avatar
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"strconv"
|
||||
|
||||
"github.com/minio/sha256-simd"
|
||||
)
|
||||
|
||||
// HashAvatar will generate a unique string, which ensures that when there's a
|
||||
|
|
|
@ -7,11 +7,10 @@
|
|||
package identicon
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
|
||||
"github.com/minio/sha256-simd"
|
||||
)
|
||||
|
||||
const minImageSize = 16
|
||||
|
|
|
@ -6,6 +6,7 @@ package base
|
|||
import (
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
|
@ -24,7 +25,6 @@ import (
|
|||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/minio/sha256-simd"
|
||||
)
|
||||
|
||||
// EncodeMD5 encodes string to md5 hex value.
|
||||
|
|
|
@ -4,16 +4,14 @@
|
|||
package context
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/web/middleware"
|
||||
|
||||
"github.com/minio/sha256-simd"
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
)
|
||||
|
||||
const CookieNameFlash = "gitea_flash"
|
||||
|
@ -46,41 +44,13 @@ func (ctx *Context) GetSiteCookie(name string) string {
|
|||
return middleware.GetSiteCookie(ctx.Req, name)
|
||||
}
|
||||
|
||||
// GetSuperSecureCookie returns given cookie value from request header with secret string.
|
||||
func (ctx *Context) GetSuperSecureCookie(secret, name string) (string, bool) {
|
||||
val := ctx.GetSiteCookie(name)
|
||||
return ctx.CookieDecrypt(secret, val)
|
||||
}
|
||||
|
||||
// CookieDecrypt returns given value from with secret string.
|
||||
func (ctx *Context) CookieDecrypt(secret, val string) (string, bool) {
|
||||
if val == "" {
|
||||
return "", false
|
||||
}
|
||||
|
||||
text, err := hex.DecodeString(val)
|
||||
// SetLTACookie will generate a LTA token and add it as an cookie.
|
||||
func (ctx *Context) SetLTACookie(u *user_model.User) error {
|
||||
days := 86400 * setting.LogInRememberDays
|
||||
lookup, validator, err := auth_model.GenerateAuthToken(ctx, u.ID, timeutil.TimeStampNow().Add(int64(days)))
|
||||
if err != nil {
|
||||
return "", false
|
||||
return err
|
||||
}
|
||||
|
||||
key := pbkdf2.Key([]byte(secret), []byte(secret), 1000, 16, sha256.New)
|
||||
text, err = util.AESGCMDecrypt(key, text)
|
||||
return string(text), err == nil
|
||||
}
|
||||
|
||||
// SetSuperSecureCookie sets given cookie value to response header with secret string.
|
||||
func (ctx *Context) SetSuperSecureCookie(secret, name, value string, maxAge int) {
|
||||
text := ctx.CookieEncrypt(secret, value)
|
||||
ctx.SetSiteCookie(name, text, maxAge)
|
||||
}
|
||||
|
||||
// CookieEncrypt encrypts a given value using the provided secret
|
||||
func (ctx *Context) CookieEncrypt(secret, value string) string {
|
||||
key := pbkdf2.Key([]byte(secret), []byte(secret), 1000, 16, sha256.New)
|
||||
text, err := util.AESGCMEncrypt(key, []byte(value))
|
||||
if err != nil {
|
||||
panic("error encrypting cookie: " + err.Error())
|
||||
}
|
||||
|
||||
return hex.EncodeToString(text)
|
||||
ctx.SetSiteCookie(setting.CookieRememberName, lookup+":"+validator, days)
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -509,6 +509,62 @@ func GetCommitFileStatus(ctx context.Context, repoPath, commitID string) (*Commi
|
|||
return fileStatus, nil
|
||||
}
|
||||
|
||||
func parseCommitRenames(renames *[][2]string, stdout io.Reader) {
|
||||
rd := bufio.NewReader(stdout)
|
||||
for {
|
||||
// Skip (R || three digits || NULL byte)
|
||||
_, err := rd.Discard(5)
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
log.Error("Unexpected error whilst reading from git log --name-status. Error: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
oldFileName, err := rd.ReadString('\x00')
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
log.Error("Unexpected error whilst reading from git log --name-status. Error: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
newFileName, err := rd.ReadString('\x00')
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
log.Error("Unexpected error whilst reading from git log --name-status. Error: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
oldFileName = strings.TrimSuffix(oldFileName, "\x00")
|
||||
newFileName = strings.TrimSuffix(newFileName, "\x00")
|
||||
*renames = append(*renames, [2]string{oldFileName, newFileName})
|
||||
}
|
||||
}
|
||||
|
||||
// GetCommitFileRenames returns the renames that the commit contains.
|
||||
func GetCommitFileRenames(ctx context.Context, repoPath, commitID string) ([][2]string, error) {
|
||||
renames := [][2]string{}
|
||||
stdout, w := io.Pipe()
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
parseCommitRenames(&renames, stdout)
|
||||
close(done)
|
||||
}()
|
||||
|
||||
stderr := new(bytes.Buffer)
|
||||
err := NewCommand(ctx, "show", "--name-status", "--pretty=format:", "-z", "--diff-filter=R").AddDynamicArguments(commitID).Run(&RunOpts{
|
||||
Dir: repoPath,
|
||||
Stdout: w,
|
||||
Stderr: stderr,
|
||||
})
|
||||
w.Close() // Close writer to exit parsing goroutine
|
||||
if err != nil {
|
||||
return nil, ConcatenateError(err, stderr.String())
|
||||
}
|
||||
|
||||
<-done
|
||||
return renames, nil
|
||||
}
|
||||
|
||||
// GetFullCommitID returns full length (40) of commit ID by given short SHA in a repository.
|
||||
func GetFullCommitID(ctx context.Context, repoPath, shortID string) (string, error) {
|
||||
commitID, _, err := NewCommand(ctx, "rev-parse").AddDynamicArguments(shortID).RunStdString(&RunOpts{Dir: repoPath})
|
||||
|
|
|
@ -278,3 +278,30 @@ func TestGetCommitFileStatusMerges(t *testing.T) {
|
|||
assert.Equal(t, commitFileStatus.Removed, expected.Removed)
|
||||
assert.Equal(t, commitFileStatus.Modified, expected.Modified)
|
||||
}
|
||||
|
||||
func TestParseCommitRenames(t *testing.T) {
|
||||
testcases := []struct {
|
||||
output string
|
||||
renames [][2]string
|
||||
}{
|
||||
{
|
||||
output: "R090\x00renamed.txt\x00history.txt\x00",
|
||||
renames: [][2]string{{"renamed.txt", "history.txt"}},
|
||||
},
|
||||
{
|
||||
output: "R090\x00renamed.txt\x00history.txt\x00R000\x00corruptedstdouthere",
|
||||
renames: [][2]string{{"renamed.txt", "history.txt"}},
|
||||
},
|
||||
{
|
||||
output: "R100\x00renamed.txt\x00history.txt\x00R001\x00readme.md\x00README.md\x00",
|
||||
renames: [][2]string{{"renamed.txt", "history.txt"}, {"readme.md", "README.md"}},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testcase := range testcases {
|
||||
renames := [][2]string{}
|
||||
parseCommitRenames(&renames, strings.NewReader(testcase.output))
|
||||
|
||||
assert.Equal(t, testcase.renames, renames)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,12 +4,11 @@
|
|||
package git
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"github.com/minio/sha256-simd"
|
||||
)
|
||||
|
||||
// Cache represents a caching interface
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
package lfs
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"hash"
|
||||
|
@ -12,8 +13,6 @@ import (
|
|||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/storage"
|
||||
|
||||
"github.com/minio/sha256-simd"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
package lfs
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
@ -12,8 +13,6 @@ import (
|
|||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/minio/sha256-simd"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
@ -18,9 +18,10 @@ import (
|
|||
// Sanitizer is a protection wrapper of *bluemonday.Policy which does not allow
|
||||
// any modification to the underlying policies once it's been created.
|
||||
type Sanitizer struct {
|
||||
defaultPolicy *bluemonday.Policy
|
||||
rendererPolicies map[string]*bluemonday.Policy
|
||||
init sync.Once
|
||||
defaultPolicy *bluemonday.Policy
|
||||
descriptionPolicy *bluemonday.Policy
|
||||
rendererPolicies map[string]*bluemonday.Policy
|
||||
init sync.Once
|
||||
}
|
||||
|
||||
var (
|
||||
|
@ -41,6 +42,7 @@ func NewSanitizer() {
|
|||
func InitializeSanitizer() {
|
||||
sanitizer.rendererPolicies = map[string]*bluemonday.Policy{}
|
||||
sanitizer.defaultPolicy = createDefaultPolicy()
|
||||
sanitizer.descriptionPolicy = createRepoDescriptionPolicy()
|
||||
|
||||
for name, renderer := range renderers {
|
||||
sanitizerRules := renderer.SanitizerRules()
|
||||
|
@ -161,6 +163,27 @@ func createDefaultPolicy() *bluemonday.Policy {
|
|||
return policy
|
||||
}
|
||||
|
||||
// createRepoDescriptionPolicy returns a minimal more strict policy that is used for
|
||||
// repository descriptions.
|
||||
func createRepoDescriptionPolicy() *bluemonday.Policy {
|
||||
policy := bluemonday.NewPolicy()
|
||||
|
||||
// Allow italics and bold.
|
||||
policy.AllowElements("i", "b", "em", "strong")
|
||||
|
||||
// Allow code.
|
||||
policy.AllowElements("code")
|
||||
|
||||
// Allow links
|
||||
policy.AllowAttrs("href", "target", "rel").OnElements("a")
|
||||
|
||||
// Allow classes for emojis
|
||||
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^emoji$`)).OnElements("img", "span")
|
||||
policy.AllowAttrs("aria-label").OnElements("span")
|
||||
|
||||
return policy
|
||||
}
|
||||
|
||||
func addSanitizerRules(policy *bluemonday.Policy, rules []setting.MarkupSanitizerRule) {
|
||||
for _, rule := range rules {
|
||||
if rule.AllowDataURIImages {
|
||||
|
@ -176,6 +199,12 @@ func addSanitizerRules(policy *bluemonday.Policy, rules []setting.MarkupSanitize
|
|||
}
|
||||
}
|
||||
|
||||
// SanitizeDescription sanitizes the HTML generated for a repository description.
|
||||
func SanitizeDescription(s string) string {
|
||||
NewSanitizer()
|
||||
return sanitizer.descriptionPolicy.Sanitize(s)
|
||||
}
|
||||
|
||||
// Sanitize takes a string that contains a HTML fragment or document and applies policy whitelist.
|
||||
func Sanitize(s string) string {
|
||||
NewSanitizer()
|
||||
|
|
|
@ -73,6 +73,28 @@ func Test_Sanitizer(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestDescriptionSanitizer(t *testing.T) {
|
||||
NewSanitizer()
|
||||
|
||||
testCases := []string{
|
||||
`<h1>Title</h1>`, `Title`,
|
||||
`<img src='img.png' alt='image'>`, ``,
|
||||
`<span class="emoji" aria-label="thumbs up">THUMBS UP</span>`, `<span class="emoji" aria-label="thumbs up">THUMBS UP</span>`,
|
||||
`<span style="color: red">Hello World</span>`, `<span>Hello World</span>`,
|
||||
`<br>`, ``,
|
||||
`<a href="https://example.com" target="_blank" rel="noopener noreferrer">https://example.com</a>`, `<a href="https://example.com" target="_blank" rel="noopener noreferrer">https://example.com</a>`,
|
||||
`<mark>Important!</mark>`, `Important!`,
|
||||
`<details>Click me! <summary>Nothing to see here.</summary></details>`, `Click me! Nothing to see here.`,
|
||||
`<input type="hidden">`, ``,
|
||||
`<b>I</b> have a <i>strong</i> <strong>opinion</strong> about <em>this</em>.`, `<b>I</b> have a <i>strong</i> <strong>opinion</strong> about <em>this</em>.`,
|
||||
`Provides alternative <code>wg(8)</code> tool`, `Provides alternative <code>wg(8)</code> tool`,
|
||||
}
|
||||
|
||||
for i := 0; i < len(testCases); i += 2 {
|
||||
assert.Equal(t, testCases[i+1], SanitizeDescription(testCases[i]))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSanitizeNonEscape(t *testing.T) {
|
||||
descStr := "<scrİpt><script>alert(document.domain)</script></scrİpt>"
|
||||
|
||||
|
|
|
@ -7,13 +7,12 @@ import (
|
|||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/minio/sha256-simd"
|
||||
)
|
||||
|
||||
// AesEncrypt encrypts text and given key with AES.
|
||||
|
|
|
@ -5,8 +5,9 @@ package setting
|
|||
|
||||
// Admin settings
|
||||
var Admin struct {
|
||||
DisableRegularOrgCreation bool
|
||||
DefaultEmailNotification string
|
||||
DisableRegularOrgCreation bool
|
||||
DefaultEmailNotification string
|
||||
SendNotificationEmailOnNewUser bool
|
||||
}
|
||||
|
||||
func loadAdminFrom(rootCfg ConfigProvider) {
|
||||
|
|
|
@ -44,6 +44,7 @@ var (
|
|||
ConnMaxLifetime time.Duration
|
||||
IterateBufferSize int
|
||||
AutoMigration bool
|
||||
SlowQueryTreshold time.Duration
|
||||
}{
|
||||
Timeout: 500,
|
||||
IterateBufferSize: 50,
|
||||
|
@ -86,6 +87,7 @@ func loadDBSetting(rootCfg ConfigProvider) {
|
|||
Database.DBConnectRetries = sec.Key("DB_RETRIES").MustInt(10)
|
||||
Database.DBConnectBackoff = sec.Key("DB_RETRY_BACKOFF").MustDuration(3 * time.Second)
|
||||
Database.AutoMigration = sec.Key("AUTO_MIGRATION").MustBool(true)
|
||||
Database.SlowQueryTreshold = sec.Key("SLOW_QUERY_TRESHOLD").MustDuration(5 * time.Second)
|
||||
}
|
||||
|
||||
// DBConnStr returns database connection string
|
||||
|
|
|
@ -19,7 +19,6 @@ var (
|
|||
SecretKey string
|
||||
InternalToken string // internal access token
|
||||
LogInRememberDays int
|
||||
CookieUserName string
|
||||
CookieRememberName string
|
||||
ReverseProxyAuthUser string
|
||||
ReverseProxyAuthEmail string
|
||||
|
@ -104,7 +103,6 @@ func loadSecurityFrom(rootCfg ConfigProvider) {
|
|||
sec := rootCfg.Section("security")
|
||||
InstallLock = HasInstallLock(rootCfg)
|
||||
LogInRememberDays = sec.Key("LOGIN_REMEMBER_DAYS").MustInt(7)
|
||||
CookieUserName = sec.Key("COOKIE_USERNAME").MustString("gitea_awesome")
|
||||
SecretKey = loadSecret(sec, "SECRET_KEY_URI", "SECRET_KEY")
|
||||
if SecretKey == "" {
|
||||
// FIXME: https://github.com/go-gitea/gitea/issues/16832
|
||||
|
|
|
@ -68,6 +68,7 @@ var Service = struct {
|
|||
DefaultKeepEmailPrivate bool
|
||||
DefaultAllowCreateOrganization bool
|
||||
DefaultUserIsRestricted bool
|
||||
AllowDotsInUsernames bool
|
||||
EnableTimetracking bool
|
||||
DefaultEnableTimetracking bool
|
||||
DefaultEnableDependencies bool
|
||||
|
@ -180,6 +181,7 @@ func loadServiceFrom(rootCfg ConfigProvider) {
|
|||
Service.DefaultKeepEmailPrivate = sec.Key("DEFAULT_KEEP_EMAIL_PRIVATE").MustBool()
|
||||
Service.DefaultAllowCreateOrganization = sec.Key("DEFAULT_ALLOW_CREATE_ORGANIZATION").MustBool(true)
|
||||
Service.DefaultUserIsRestricted = sec.Key("DEFAULT_USER_IS_RESTRICTED").MustBool(false)
|
||||
Service.AllowDotsInUsernames = sec.Key("ALLOW_DOTS_IN_USERNAMES").MustBool(true)
|
||||
Service.EnableTimetracking = sec.Key("ENABLE_TIMETRACKING").MustBool(true)
|
||||
if Service.EnableTimetracking {
|
||||
Service.DefaultEnableTimetracking = sec.Key("DEFAULT_ENABLE_TIMETRACKING").MustBool(true)
|
||||
|
|
|
@ -17,7 +17,6 @@ import (
|
|||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
@ -165,10 +164,6 @@ func sessionHandler(session ssh.Session) {
|
|||
}
|
||||
|
||||
func publicKeyHandler(ctx ssh.Context, key ssh.PublicKey) bool {
|
||||
// FIXME: the "ssh.Context" is not thread-safe, so db operations should use the immutable parent "Context"
|
||||
// TODO: Remove after https://github.com/gliderlabs/ssh/pull/211
|
||||
parentCtx := reflect.ValueOf(ctx).Elem().FieldByName("Context").Interface().(context.Context)
|
||||
|
||||
if log.IsDebug() { // <- FingerprintSHA256 is kinda expensive so only calculate it if necessary
|
||||
log.Debug("Handle Public Key: Fingerprint: %s from %s", gossh.FingerprintSHA256(key), ctx.RemoteAddr())
|
||||
}
|
||||
|
@ -200,7 +195,7 @@ func publicKeyHandler(ctx ssh.Context, key ssh.PublicKey) bool {
|
|||
// look for the exact principal
|
||||
principalLoop:
|
||||
for _, principal := range cert.ValidPrincipals {
|
||||
pkey, err := asymkey_model.SearchPublicKeyByContentExact(parentCtx, principal)
|
||||
pkey, err := asymkey_model.SearchPublicKeyByContentExact(ctx, principal)
|
||||
if err != nil {
|
||||
if asymkey_model.IsErrKeyNotExist(err) {
|
||||
log.Debug("Principal Rejected: %s Unknown Principal: %s", ctx.RemoteAddr(), principal)
|
||||
|
@ -257,7 +252,7 @@ func publicKeyHandler(ctx ssh.Context, key ssh.PublicKey) bool {
|
|||
log.Debug("Handle Public Key: %s Fingerprint: %s is not a certificate", ctx.RemoteAddr(), gossh.FingerprintSHA256(key))
|
||||
}
|
||||
|
||||
pkey, err := asymkey_model.SearchPublicKeyByContent(parentCtx, strings.TrimSpace(string(gossh.MarshalAuthorizedKey(key))))
|
||||
pkey, err := asymkey_model.SearchPublicKeyByContent(ctx, strings.TrimSpace(string(gossh.MarshalAuthorizedKey(key))))
|
||||
if err != nil {
|
||||
if asymkey_model.IsErrKeyNotExist(err) {
|
||||
log.Warn("Unknown public key: %s from %s", gossh.FingerprintSHA256(key), ctx.RemoteAddr())
|
||||
|
|
|
@ -7,10 +7,9 @@ import (
|
|||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
|
||||
"github.com/minio/sha256-simd"
|
||||
)
|
||||
|
||||
// GenerateKeyPair generates a public and private keypair
|
||||
|
|
|
@ -7,12 +7,12 @@ import (
|
|||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/minio/sha256-simd"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
|
|
|
@ -4,10 +4,6 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
@ -40,52 +36,3 @@ func CopyFile(src, dest string) error {
|
|||
}
|
||||
return os.Chmod(dest, si.Mode())
|
||||
}
|
||||
|
||||
// AESGCMEncrypt (from legacy package): encrypts plaintext with the given key using AES in GCM mode. should be replaced.
|
||||
func AESGCMEncrypt(key, plaintext []byte) ([]byte, error) {
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nonce := make([]byte, gcm.NonceSize())
|
||||
if _, err := rand.Read(nonce); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ciphertext := gcm.Seal(nil, nonce, plaintext, nil)
|
||||
return append(nonce, ciphertext...), nil
|
||||
}
|
||||
|
||||
// AESGCMDecrypt (from legacy package): decrypts ciphertext with the given key using AES in GCM mode. should be replaced.
|
||||
func AESGCMDecrypt(key, ciphertext []byte) ([]byte, error) {
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
size := gcm.NonceSize()
|
||||
if len(ciphertext)-size <= 0 {
|
||||
return nil, errors.New("ciphertext is empty")
|
||||
}
|
||||
|
||||
nonce := ciphertext[:size]
|
||||
ciphertext = ciphertext[size:]
|
||||
|
||||
plainText, err := gcm.Open(nil, nonce, ciphertext, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return plainText, nil
|
||||
}
|
||||
|
|
|
@ -4,8 +4,6 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
@ -37,21 +35,3 @@ func TestCopyFile(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
assert.Equal(t, testContent, dstContent)
|
||||
}
|
||||
|
||||
func TestAESGCM(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
key := make([]byte, aes.BlockSize)
|
||||
_, err := rand.Read(key)
|
||||
assert.NoError(t, err)
|
||||
|
||||
plaintext := []byte("this will be encrypted")
|
||||
|
||||
ciphertext, err := AESGCMEncrypt(key, plaintext)
|
||||
assert.NoError(t, err)
|
||||
|
||||
decrypted, err := AESGCMDecrypt(key, ciphertext)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, plaintext, decrypted)
|
||||
}
|
||||
|
|
|
@ -117,13 +117,20 @@ func IsValidExternalTrackerURLFormat(uri string) bool {
|
|||
}
|
||||
|
||||
var (
|
||||
validUsernamePattern = regexp.MustCompile(`^[\da-zA-Z][-.\w]*$`)
|
||||
invalidUsernamePattern = regexp.MustCompile(`[-._]{2,}|[-._]$`) // No consecutive or trailing non-alphanumeric chars
|
||||
validUsernamePatternWithDots = regexp.MustCompile(`^[\da-zA-Z][-.\w]*$`)
|
||||
validUsernamePatternWithoutDots = regexp.MustCompile(`^[\da-zA-Z][-\w]*$`)
|
||||
|
||||
// No consecutive or trailing non-alphanumeric chars, catches both cases
|
||||
invalidUsernamePattern = regexp.MustCompile(`[-._]{2,}|[-._]$`)
|
||||
)
|
||||
|
||||
// IsValidUsername checks if username is valid
|
||||
func IsValidUsername(name string) bool {
|
||||
// It is difficult to find a single pattern that is both readable and effective,
|
||||
// but it's easier to use positive and negative checks.
|
||||
return validUsernamePattern.MatchString(name) && !invalidUsernamePattern.MatchString(name)
|
||||
if setting.Service.AllowDotsInUsernames {
|
||||
return validUsernamePatternWithDots.MatchString(name) && !invalidUsernamePattern.MatchString(name)
|
||||
}
|
||||
|
||||
return validUsernamePatternWithoutDots.MatchString(name) && !invalidUsernamePattern.MatchString(name)
|
||||
}
|
||||
|
|
|
@ -155,7 +155,8 @@ func Test_IsValidExternalTrackerURLFormat(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestIsValidUsername(t *testing.T) {
|
||||
func TestIsValidUsernameAllowDots(t *testing.T) {
|
||||
setting.Service.AllowDotsInUsernames = true
|
||||
tests := []struct {
|
||||
arg string
|
||||
want bool
|
||||
|
@ -185,3 +186,31 @@ func TestIsValidUsername(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsValidUsernameBanDots(t *testing.T) {
|
||||
setting.Service.AllowDotsInUsernames = false
|
||||
defer func() {
|
||||
setting.Service.AllowDotsInUsernames = true
|
||||
}()
|
||||
|
||||
tests := []struct {
|
||||
arg string
|
||||
want bool
|
||||
}{
|
||||
{arg: "a", want: true},
|
||||
{arg: "abc", want: true},
|
||||
{arg: "0.b-c", want: false},
|
||||
{arg: "a.b-c_d", want: false},
|
||||
{arg: ".abc", want: false},
|
||||
{arg: "abc.", want: false},
|
||||
{arg: "a..bc", want: false},
|
||||
{arg: "a...bc", want: false},
|
||||
{arg: "a.-bc", want: false},
|
||||
{arg: "a._bc", want: false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.arg, func(t *testing.T) {
|
||||
assert.Equalf(t, tt.want, IsValidUsername(tt.arg), "IsValidUsername[AllowDotsInUsernames=false](%v)", tt.arg)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -147,6 +147,16 @@ func toHandlerProvider(handler any) func(next http.Handler) http.Handler {
|
|||
}
|
||||
}
|
||||
|
||||
if hp, ok := handler.(func(next http.Handler) http.HandlerFunc); ok {
|
||||
return func(next http.Handler) http.Handler {
|
||||
h := hp(next) // this handle could be dynamically generated, so we can't use it for debug info
|
||||
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
||||
routing.UpdateFuncInfo(req.Context(), funcInfo)
|
||||
h.ServeHTTP(resp, req)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
provider := func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(respOrig http.ResponseWriter, req *http.Request) {
|
||||
// wrap the response writer to check whether the response has been written
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"reflect"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/translation"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/validation"
|
||||
|
@ -135,7 +136,11 @@ func Validate(errs binding.Errors, data map[string]any, f Form, l translation.Lo
|
|||
case validation.ErrRegexPattern:
|
||||
data["ErrorMsg"] = trName + l.Tr("form.regex_pattern_error", errs[0].Message)
|
||||
case validation.ErrUsername:
|
||||
data["ErrorMsg"] = trName + l.Tr("form.username_error")
|
||||
if setting.Service.AllowDotsInUsernames {
|
||||
data["ErrorMsg"] = trName + l.Tr("form.username_error")
|
||||
} else {
|
||||
data["ErrorMsg"] = trName + l.Tr("form.username_error_no_dots")
|
||||
}
|
||||
case validation.ErrInvalidGroupTeamMap:
|
||||
data["ErrorMsg"] = trName + l.Tr("form.invalid_group_team_map_error", errs[0].Message)
|
||||
default:
|
||||
|
|
|
@ -296,6 +296,7 @@ default_allow_create_organization = Allow Creation of Organizations by Default
|
|||
default_allow_create_organization_popup = Allow new user accounts to create organizations by default.
|
||||
default_enable_timetracking = Enable Time Tracking by Default
|
||||
default_enable_timetracking_popup = Enable time tracking for new repositories by default.
|
||||
allow_dots_in_usernames = Allow users to use dots in their usernames. Doesn't affect existing accounts.
|
||||
no_reply_address = Hidden Email Domain
|
||||
no_reply_address_helper = Domain name for users with a hidden email address. For example, the username 'joe' will be logged in Git as 'joe@noreply.example.org' if the hidden email domain is set to 'noreply.example.org'.
|
||||
password_algorithm = Password Hash Algorithm
|
||||
|
@ -441,6 +442,10 @@ activate_email = Verify your email address
|
|||
activate_email.title = %s, please verify your email address
|
||||
activate_email.text = Please click the following link to verify your email address within <b>%s</b>:
|
||||
|
||||
admin.new_user.subject = New user %s just signed up
|
||||
admin.new_user.user_info = User Information
|
||||
admin.new_user.text = Please <a href="%s">click here</a> to manage the user from the admin panel.
|
||||
|
||||
register_notify = Welcome to Gitea
|
||||
register_notify.title = %[1]s, welcome to %[2]s
|
||||
register_notify.text_1 = this is your registration confirmation email for %s!
|
||||
|
@ -535,6 +540,7 @@ include_error = ` must contain substring "%s".`
|
|||
glob_pattern_error = ` glob pattern is invalid: %s.`
|
||||
regex_pattern_error = ` regex pattern is invalid: %s.`
|
||||
username_error = ` can only contain alphanumeric chars ('0-9','a-z','A-Z'), dash ('-'), underscore ('_') and dot ('.'). It cannot begin or end with non-alphanumeric chars, and consecutive non-alphanumeric chars are also forbidden.`
|
||||
username_error_no_dots = ` can only contain alphanumeric chars ('0-9','a-z','A-Z'), dash ('-') and underscore ('_'). It cannot begin or end with non-alphanumeric chars, and consecutive non-alphanumeric chars are also forbidden.`
|
||||
invalid_group_team_map_error = ` mapping is invalid: %s`
|
||||
unknown_error = Unknown error:
|
||||
captcha_incorrect = The CAPTCHA code is incorrect.
|
||||
|
@ -1292,6 +1298,8 @@ commits.find = Search
|
|||
commits.search_all = All Branches
|
||||
commits.author = Author
|
||||
commits.message = Message
|
||||
commits.browse_further = Browse further
|
||||
commits.renamed_from = Renamed from %s
|
||||
commits.date = Date
|
||||
commits.older = Older
|
||||
commits.newer = Newer
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"crypto"
|
||||
"crypto/rsa"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/pem"
|
||||
|
@ -26,8 +27,6 @@ import (
|
|||
chef_module "code.gitea.io/gitea/modules/packages/chef"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/services/auth"
|
||||
|
||||
"github.com/minio/sha256-simd"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
@ -6,6 +6,7 @@ package maven
|
|||
import (
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"encoding/hex"
|
||||
"encoding/xml"
|
||||
|
@ -26,8 +27,6 @@ import (
|
|||
maven_module "code.gitea.io/gitea/modules/packages/maven"
|
||||
"code.gitea.io/gitea/routers/api/packages/helper"
|
||||
packages_service "code.gitea.io/gitea/services/packages"
|
||||
|
||||
"github.com/minio/sha256-simd"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
@ -55,19 +55,33 @@ func Search(ctx *context.APIContext) {
|
|||
|
||||
listOptions := utils.GetListOptions(ctx)
|
||||
|
||||
users, maxResults, err := user_model.SearchUsers(ctx, &user_model.SearchUserOptions{
|
||||
Actor: ctx.Doer,
|
||||
Keyword: ctx.FormTrim("q"),
|
||||
UID: ctx.FormInt64("uid"),
|
||||
Type: user_model.UserTypeIndividual,
|
||||
ListOptions: listOptions,
|
||||
})
|
||||
if err != nil {
|
||||
ctx.JSON(http.StatusInternalServerError, map[string]any{
|
||||
"ok": false,
|
||||
"error": err.Error(),
|
||||
uid := ctx.FormInt64("uid")
|
||||
var users []*user_model.User
|
||||
var maxResults int64
|
||||
var err error
|
||||
|
||||
switch uid {
|
||||
case user_model.GhostUserID:
|
||||
maxResults = 1
|
||||
users = []*user_model.User{user_model.NewGhostUser()}
|
||||
case user_model.ActionsUserID:
|
||||
maxResults = 1
|
||||
users = []*user_model.User{user_model.NewActionsUser()}
|
||||
default:
|
||||
users, maxResults, err = user_model.SearchUsers(ctx, &user_model.SearchUserOptions{
|
||||
Actor: ctx.Doer,
|
||||
Keyword: ctx.FormTrim("q"),
|
||||
UID: uid,
|
||||
Type: user_model.UserTypeIndividual,
|
||||
ListOptions: listOptions,
|
||||
})
|
||||
return
|
||||
if err != nil {
|
||||
ctx.JSON(http.StatusInternalServerError, map[string]any{
|
||||
"ok": false,
|
||||
"error": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(int(maxResults), listOptions.PageSize)
|
||||
|
|
|
@ -358,6 +358,12 @@ func SubmitInstall(ctx *context.Context) {
|
|||
ctx.RenderWithErr(ctx.Tr("form.password_not_match"), tplInstall, form)
|
||||
return
|
||||
}
|
||||
if len(form.AdminPasswd) < setting.MinPasswordLength {
|
||||
ctx.Data["Err_Admin"] = true
|
||||
ctx.Data["Err_AdminPasswd"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("auth.password_too_short", setting.MinPasswordLength), tplInstall, form)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Init the engine with migration
|
||||
|
@ -547,18 +553,13 @@ func SubmitInstall(ctx *context.Context) {
|
|||
u, _ = user_model.GetUserByName(ctx, u.Name)
|
||||
}
|
||||
|
||||
days := 86400 * setting.LogInRememberDays
|
||||
ctx.SetSiteCookie(setting.CookieUserName, u.Name, days)
|
||||
|
||||
ctx.SetSuperSecureCookie(base.EncodeMD5(u.Rands+u.Passwd),
|
||||
setting.CookieRememberName, u.Name, days)
|
||||
|
||||
// Auto-login for admin
|
||||
if err = ctx.Session.Set("uid", u.ID); err != nil {
|
||||
if err := ctx.SetLTACookie(u); err != nil {
|
||||
ctx.RenderWithErr(ctx.Tr("install.save_config_failed", err), tplInstall, &form)
|
||||
return
|
||||
}
|
||||
if err = ctx.Session.Set("uname", u.Name); err != nil {
|
||||
|
||||
// Auto-login for admin
|
||||
if err = ctx.Session.Set("uid", u.ID); err != nil {
|
||||
ctx.RenderWithErr(ctx.Tr("install.save_config_failed", err), tplInstall, &form)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
"crypto/subtle"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
@ -30,6 +32,7 @@ import (
|
|||
"code.gitea.io/gitea/services/externalaccount"
|
||||
"code.gitea.io/gitea/services/forms"
|
||||
"code.gitea.io/gitea/services/mailer"
|
||||
notify_service "code.gitea.io/gitea/services/notify"
|
||||
|
||||
"github.com/markbates/goth"
|
||||
)
|
||||
|
@ -49,21 +52,47 @@ func AutoSignIn(ctx *context.Context) (bool, error) {
|
|||
return false, nil
|
||||
}
|
||||
|
||||
uname := ctx.GetSiteCookie(setting.CookieUserName)
|
||||
if len(uname) == 0 {
|
||||
authCookie := ctx.GetSiteCookie(setting.CookieRememberName)
|
||||
if len(authCookie) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
isSucceed := false
|
||||
defer func() {
|
||||
if !isSucceed {
|
||||
log.Trace("auto-login cookie cleared: %s", uname)
|
||||
ctx.DeleteSiteCookie(setting.CookieUserName)
|
||||
log.Trace("Auto login cookie is cleared: %s", authCookie)
|
||||
ctx.DeleteSiteCookie(setting.CookieRememberName)
|
||||
}
|
||||
}()
|
||||
|
||||
u, err := user_model.GetUserByName(ctx, uname)
|
||||
lookupKey, validator, found := strings.Cut(authCookie, ":")
|
||||
if !found {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
authToken, err := auth.FindAuthToken(ctx, lookupKey)
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
||||
if authToken.IsExpired() {
|
||||
err = auth.DeleteAuthToken(ctx, authToken)
|
||||
return false, err
|
||||
}
|
||||
|
||||
rawValidator, err := hex.DecodeString(validator)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if subtle.ConstantTimeCompare([]byte(authToken.HashedValidator), []byte(auth.HashValidator(rawValidator))) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
u, err := user_model.GetUserByID(ctx, authToken.UID)
|
||||
if err != nil {
|
||||
if !user_model.IsErrUserNotExist(err) {
|
||||
return false, fmt.Errorf("GetUserByName: %w", err)
|
||||
|
@ -71,17 +100,11 @@ func AutoSignIn(ctx *context.Context) (bool, error) {
|
|||
return false, nil
|
||||
}
|
||||
|
||||
if val, ok := ctx.GetSuperSecureCookie(
|
||||
base.EncodeMD5(u.Rands+u.Passwd), setting.CookieRememberName); !ok || val != u.Name {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
isSucceed = true
|
||||
|
||||
if err := updateSession(ctx, nil, map[string]any{
|
||||
// Set session IDs
|
||||
"uid": u.ID,
|
||||
"uname": u.Name,
|
||||
"uid": authToken.UID,
|
||||
}); err != nil {
|
||||
return false, fmt.Errorf("unable to updateSession: %w", err)
|
||||
}
|
||||
|
@ -290,10 +313,10 @@ func handleSignIn(ctx *context.Context, u *user_model.User, remember bool) {
|
|||
|
||||
func handleSignInFull(ctx *context.Context, u *user_model.User, remember, obeyRedirect bool) string {
|
||||
if remember {
|
||||
days := 86400 * setting.LogInRememberDays
|
||||
ctx.SetSiteCookie(setting.CookieUserName, u.Name, days)
|
||||
ctx.SetSuperSecureCookie(base.EncodeMD5(u.Rands+u.Passwd),
|
||||
setting.CookieRememberName, u.Name, days)
|
||||
if err := ctx.SetLTACookie(u); err != nil {
|
||||
ctx.ServerError("GenerateAuthToken", err)
|
||||
return setting.AppSubURL + "/"
|
||||
}
|
||||
}
|
||||
|
||||
if err := updateSession(ctx, []string{
|
||||
|
@ -306,8 +329,7 @@ func handleSignInFull(ctx *context.Context, u *user_model.User, remember, obeyRe
|
|||
"twofaRemember",
|
||||
"linkAccount",
|
||||
}, map[string]any{
|
||||
"uid": u.ID,
|
||||
"uname": u.Name,
|
||||
"uid": u.ID,
|
||||
}); err != nil {
|
||||
ctx.ServerError("RegenerateSession", err)
|
||||
return setting.AppSubURL + "/"
|
||||
|
@ -368,7 +390,6 @@ func getUserName(gothUser *goth.User) string {
|
|||
func HandleSignOut(ctx *context.Context) {
|
||||
_ = ctx.Session.Flush()
|
||||
_ = ctx.Session.Destroy(ctx.Resp, ctx.Req)
|
||||
ctx.DeleteSiteCookie(setting.CookieUserName)
|
||||
ctx.DeleteSiteCookie(setting.CookieRememberName)
|
||||
ctx.Csrf.DeleteCookie(ctx)
|
||||
middleware.DeleteRedirectToCookie(ctx.Resp)
|
||||
|
@ -586,6 +607,7 @@ func handleUserCreated(ctx *context.Context, u *user_model.User, gothUser *goth.
|
|||
}
|
||||
}
|
||||
|
||||
notify_service.NewUserSignUp(ctx, u)
|
||||
// update external user information
|
||||
if gothUser != nil {
|
||||
if err := externalaccount.UpdateExternalUser(u, *gothUser); err != nil {
|
||||
|
@ -609,7 +631,6 @@ func handleUserCreated(ctx *context.Context, u *user_model.User, gothUser *goth.
|
|||
ctx.Data["Email"] = u.Email
|
||||
ctx.Data["ActiveCodeLives"] = timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, ctx.Locale)
|
||||
ctx.HTML(http.StatusOK, TplActivate)
|
||||
|
||||
if setting.CacheService.Enabled {
|
||||
if err := ctx.Cache.Put("MailResendLimit_"+u.LowerName, u.LowerName, 180); err != nil {
|
||||
log.Error("Set cache(MailResendLimit) fail: %v", err)
|
||||
|
@ -731,8 +752,7 @@ func handleAccountActivation(ctx *context.Context, user *user_model.User) {
|
|||
log.Trace("User activated: %s", user.Name)
|
||||
|
||||
if err := updateSession(ctx, nil, map[string]any{
|
||||
"uid": user.ID,
|
||||
"uname": user.Name,
|
||||
"uid": user.ID,
|
||||
}); err != nil {
|
||||
log.Error("Unable to regenerate session for user: %-v with email: %s: %v", user, user.Email, err)
|
||||
ctx.ServerError("ActivateUserEmail", err)
|
||||
|
|
|
@ -1119,8 +1119,7 @@ func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model
|
|||
// we can't sign the user in just yet. Instead, redirect them to the 2FA authentication page.
|
||||
if !needs2FA {
|
||||
if err := updateSession(ctx, nil, map[string]any{
|
||||
"uid": u.ID,
|
||||
"uname": u.Name,
|
||||
"uid": u.ID,
|
||||
}); err != nil {
|
||||
ctx.ServerError("updateSession", err)
|
||||
return
|
||||
|
|
|
@ -54,8 +54,7 @@ func Home(ctx *context.Context) {
|
|||
}
|
||||
|
||||
// Check auto-login.
|
||||
uname := ctx.GetSiteCookie(setting.CookieUserName)
|
||||
if len(uname) != 0 {
|
||||
if len(ctx.GetSiteCookie(setting.CookieRememberName)) != 0 {
|
||||
ctx.Redirect(setting.AppSubURL + "/user/login")
|
||||
return
|
||||
}
|
||||
|
|
|
@ -239,6 +239,22 @@ func FileHistory(ctx *context.Context) {
|
|||
ctx.ServerError("CommitsByFileAndRange", err)
|
||||
return
|
||||
}
|
||||
oldestCommit := commits[len(commits)-1]
|
||||
|
||||
renamedFiles, err := git.GetCommitFileRenames(ctx, ctx.Repo.GitRepo.Path, oldestCommit.ID.String())
|
||||
if err != nil {
|
||||
ctx.ServerError("GetCommitFileRenames", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, renames := range renamedFiles {
|
||||
if renames[1] == fileName {
|
||||
ctx.Data["OldFilename"] = renames[0]
|
||||
ctx.Data["OldFilenameHistory"] = fmt.Sprintf("%s/commits/commit/%s/%s", ctx.Repo.RepoLink, oldestCommit.ID.String(), renames[0])
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
ctx.Data["Commits"] = git_model.ConvertFromGitCommit(ctx, commits, ctx.Repo.Repository)
|
||||
|
||||
ctx.Data["Username"] = ctx.Repo.Owner.Name
|
||||
|
|
|
@ -390,7 +390,9 @@ func NewReleasePost(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
if !ctx.Repo.GitRepo.IsBranchExist(form.Target) {
|
||||
// form.Target can be a branch name or a full commitID.
|
||||
if !ctx.Repo.GitRepo.IsBranchExist(form.Target) &&
|
||||
len(form.Target) == git.SHAFullLength && !ctx.Repo.GitRepo.IsCommitExist(form.Target) {
|
||||
ctx.RenderWithErr(ctx.Tr("form.target_branch_not_exist"), tplReleaseNew, &form)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -165,7 +165,7 @@ func renderDirectory(ctx *context.Context, treeLink string) {
|
|||
|
||||
if ctx.Repo.TreePath != "" {
|
||||
ctx.Data["HideRepoInfo"] = true
|
||||
ctx.Data["Title"] = ctx.Tr("repo.file.title", ctx.Repo.Repository.Name+"/"+path.Base(ctx.Repo.TreePath), ctx.Repo.RefName)
|
||||
ctx.Data["Title"] = ctx.Tr("repo.file.title", ctx.Repo.Repository.Name+"/"+ctx.Repo.TreePath, ctx.Repo.RefName)
|
||||
}
|
||||
|
||||
subfolder, readmeFile, err := findReadmeFileInEntries(ctx, entries, true)
|
||||
|
@ -344,7 +344,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st
|
|||
}
|
||||
defer dataRc.Close()
|
||||
|
||||
ctx.Data["Title"] = ctx.Tr("repo.file.title", ctx.Repo.Repository.Name+"/"+path.Base(ctx.Repo.TreePath), ctx.Repo.RefName)
|
||||
ctx.Data["Title"] = ctx.Tr("repo.file.title", ctx.Repo.Repository.Name+"/"+ctx.Repo.TreePath, ctx.Repo.RefName)
|
||||
ctx.Data["FileIsSymlink"] = entry.IsLink()
|
||||
ctx.Data["FileName"] = blob.Name()
|
||||
ctx.Data["RawFileLink"] = rawLink + "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
|
||||
|
|
|
@ -78,6 +78,15 @@ func AccountPost(ctx *context.Context) {
|
|||
ctx.ServerError("UpdateUser", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Re-generate LTA cookie.
|
||||
if len(ctx.GetSiteCookie(setting.CookieRememberName)) != 0 {
|
||||
if err := ctx.SetLTACookie(ctx.Doer); err != nil {
|
||||
ctx.ServerError("SetLTACookie", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
log.Trace("User password updated: %s", ctx.Doer.Name)
|
||||
ctx.Flash.Success(ctx.Tr("settings.change_password_success"))
|
||||
}
|
||||
|
|
|
@ -48,17 +48,12 @@ import (
|
|||
_ "code.gitea.io/gitea/modules/session" // to registers all internal adapters
|
||||
|
||||
"gitea.com/go-chi/captcha"
|
||||
"github.com/NYTimes/gziphandler"
|
||||
chi_middleware "github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/go-chi/cors"
|
||||
"github.com/klauspost/compress/gzhttp"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
const (
|
||||
// GzipMinSize represents min size to compress for the body size of response
|
||||
GzipMinSize = 1400
|
||||
)
|
||||
|
||||
// CorsHandler return a http handler who set CORS options if enabled by config
|
||||
func CorsHandler() func(next http.Handler) http.Handler {
|
||||
if setting.CORSConfig.Enabled {
|
||||
|
@ -186,7 +181,7 @@ func verifyAuthWithOptions(options *common.VerifyOptions) func(ctx *context.Cont
|
|||
|
||||
// Redirect to log in page if auto-signin info is provided and has not signed in.
|
||||
if !options.SignOutRequired && !ctx.IsSigned &&
|
||||
len(ctx.GetSiteCookie(setting.CookieUserName)) > 0 {
|
||||
len(ctx.GetSiteCookie(setting.CookieRememberName)) > 0 {
|
||||
if ctx.Req.URL.Path != "/user/events" {
|
||||
middleware.SetRedirectToCookie(ctx.Resp, setting.AppSubURL+ctx.Req.URL.RequestURI())
|
||||
}
|
||||
|
@ -229,11 +224,11 @@ func Routes() *web.Route {
|
|||
var mid []any
|
||||
|
||||
if setting.EnableGzip {
|
||||
h, err := gziphandler.GzipHandlerWithOpts(gziphandler.MinSize(GzipMinSize))
|
||||
wrapper, err := gzhttp.NewWrapper(gzhttp.RandomJitter(32, 0, false))
|
||||
if err != nil {
|
||||
log.Fatal("GzipHandlerWithOpts failed: %v", err)
|
||||
log.Fatal("gzhttp.NewWrapper failed: %v", err)
|
||||
}
|
||||
mid = append(mid, h)
|
||||
mid = append(mid, wrapper)
|
||||
}
|
||||
|
||||
if setting.Service.EnableCaptcha {
|
||||
|
|
|
@ -72,10 +72,6 @@ func handleSignIn(resp http.ResponseWriter, req *http.Request, sess SessionStore
|
|||
if err != nil {
|
||||
log.Error(fmt.Sprintf("Error setting session: %v", err))
|
||||
}
|
||||
err = sess.Set("uname", user.Name)
|
||||
if err != nil {
|
||||
log.Error(fmt.Sprintf("Error setting session: %v", err))
|
||||
}
|
||||
|
||||
// Language setting of the user overwrites the one previously set
|
||||
// If the user does not have a locale set, we save the current one.
|
||||
|
|
|
@ -106,6 +106,12 @@ func ListTasks() TaskTable {
|
|||
next = e.NextRun()
|
||||
prev = e.PreviousRun()
|
||||
}
|
||||
|
||||
// If the manual run is after the cron run, use that instead.
|
||||
if prev.Before(task.LastRun) {
|
||||
prev = task.LastRun
|
||||
}
|
||||
|
||||
task.lock.Lock()
|
||||
tTable = append(tTable, &TaskTableRow{
|
||||
Name: task.Name,
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
system_model "code.gitea.io/gitea/models/system"
|
||||
|
@ -37,6 +38,8 @@ type Task struct {
|
|||
LastMessage string
|
||||
LastDoer string
|
||||
ExecTimes int64
|
||||
// This stores the time of the last manual run of this task.
|
||||
LastRun time.Time
|
||||
}
|
||||
|
||||
// DoRunAtStart returns if this task should run at the start
|
||||
|
@ -88,6 +91,12 @@ func (t *Task) RunWithUser(doer *user_model.User, config Config) {
|
|||
}
|
||||
}()
|
||||
graceful.GetManager().RunWithShutdownContext(func(baseCtx context.Context) {
|
||||
// Store the time of this run, before the function is executed, so it
|
||||
// matches the behavior of what the cron library does.
|
||||
t.lock.Lock()
|
||||
t.LastRun = time.Now()
|
||||
t.lock.Unlock()
|
||||
|
||||
pm := process.GetManager()
|
||||
doerName := ""
|
||||
if doer != nil && doer.ID != -1 {
|
||||
|
|
|
@ -5,6 +5,7 @@ package lfs
|
|||
|
||||
import (
|
||||
stdCtx "context"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
|
@ -33,7 +34,6 @@ import (
|
|||
"code.gitea.io/gitea/modules/storage"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/minio/sha256-simd"
|
||||
)
|
||||
|
||||
// requestContext contain variables from the HTTP request.
|
||||
|
|
80
services/mailer/mail_admin_new_user.go
Normal file
80
services/mailer/mail_admin_new_user.go
Normal file
|
@ -0,0 +1,80 @@
|
|||
// Copyright 2023 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
package mailer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/templates"
|
||||
"code.gitea.io/gitea/modules/translation"
|
||||
)
|
||||
|
||||
const (
|
||||
tplNewUserMail base.TplName = "notify/admin_new_user"
|
||||
)
|
||||
|
||||
var sa = SendAsyncs
|
||||
|
||||
// MailNewUser sends notification emails on new user registrations to all admins
|
||||
func MailNewUser(ctx context.Context, u *user_model.User) {
|
||||
if !setting.Admin.SendNotificationEmailOnNewUser {
|
||||
return
|
||||
}
|
||||
|
||||
if setting.MailService == nil {
|
||||
// No mail service configured
|
||||
return
|
||||
}
|
||||
|
||||
recipients, err := user_model.GetAllAdmins(ctx)
|
||||
if err != nil {
|
||||
log.Error("user_model.GetAllAdmins: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
langMap := make(map[string][]string)
|
||||
for _, r := range recipients {
|
||||
langMap[r.Language] = append(langMap[r.Language], r.Email)
|
||||
}
|
||||
|
||||
for lang, tos := range langMap {
|
||||
mailNewUser(ctx, u, lang, tos)
|
||||
}
|
||||
}
|
||||
|
||||
func mailNewUser(ctx context.Context, u *user_model.User, lang string, tos []string) {
|
||||
locale := translation.NewLocale(lang)
|
||||
|
||||
subject := locale.Tr("mail.admin.new_user.subject", u.Name)
|
||||
manageUserURL := setting.AppSubURL + "/admin/users/" + strconv.FormatInt(u.ID, 10)
|
||||
body := locale.Tr("mail.admin.new_user.text", manageUserURL)
|
||||
mailMeta := map[string]any{
|
||||
"NewUser": u,
|
||||
"Subject": subject,
|
||||
"Body": body,
|
||||
"Language": locale.Language(),
|
||||
"locale": locale,
|
||||
"Str2html": templates.Str2html,
|
||||
}
|
||||
|
||||
var mailBody bytes.Buffer
|
||||
|
||||
if err := bodyTemplates.ExecuteTemplate(&mailBody, string(tplNewUserMail), mailMeta); err != nil {
|
||||
log.Error("ExecuteTemplate [%s]: %v", string(tplNewUserMail)+"/body", err)
|
||||
return
|
||||
}
|
||||
|
||||
msgs := make([]*Message, 0, len(tos))
|
||||
for _, to := range tos {
|
||||
msg := NewMessage(to, subject, mailBody.String())
|
||||
msg.Info = subject
|
||||
msgs = append(msgs, msg)
|
||||
}
|
||||
sa(msgs)
|
||||
}
|
88
services/mailer/mail_admin_new_user_test.go
Normal file
88
services/mailer/mail_admin_new_user_test.go
Normal file
|
@ -0,0 +1,88 @@
|
|||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package mailer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func getTestUsers() []*user_model.User {
|
||||
admin := new(user_model.User)
|
||||
admin.Name = "admin"
|
||||
admin.IsAdmin = true
|
||||
admin.Language = "en_US"
|
||||
admin.Email = "admin@example.com"
|
||||
|
||||
newUser := new(user_model.User)
|
||||
newUser.Name = "new_user"
|
||||
newUser.Language = "en_US"
|
||||
newUser.IsAdmin = false
|
||||
newUser.Email = "new_user@example.com"
|
||||
newUser.LastLoginUnix = 1693648327
|
||||
newUser.CreatedUnix = 1693648027
|
||||
|
||||
user_model.CreateUser(db.DefaultContext, admin)
|
||||
user_model.CreateUser(db.DefaultContext, newUser)
|
||||
|
||||
users := make([]*user_model.User, 0)
|
||||
users = append(users, admin)
|
||||
users = append(users, newUser)
|
||||
|
||||
return users
|
||||
}
|
||||
|
||||
func cleanUpUsers(ctx context.Context, users []*user_model.User) {
|
||||
for _, u := range users {
|
||||
db.DeleteByID(ctx, u.ID, new(user_model.User))
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminNotificationMail_test(t *testing.T) {
|
||||
mailService := setting.Mailer{
|
||||
From: "test@example.com",
|
||||
Protocol: "dummy",
|
||||
}
|
||||
|
||||
setting.MailService = &mailService
|
||||
setting.Domain = "localhost"
|
||||
setting.AppSubURL = "http://localhost"
|
||||
|
||||
// test with SEND_NOTIFICATION_EMAIL_ON_NEW_USER enabled
|
||||
setting.Admin.SendNotificationEmailOnNewUser = true
|
||||
|
||||
ctx := context.Background()
|
||||
NewContext(ctx)
|
||||
|
||||
users := getTestUsers()
|
||||
oldSendAsyncs := sa
|
||||
defer func() {
|
||||
sa = oldSendAsyncs
|
||||
cleanUpUsers(ctx, users)
|
||||
}()
|
||||
|
||||
sa = func(msgs []*Message) {
|
||||
assert.Equal(t, len(msgs), 1, "Test provides only one admin user, so only one email must be sent")
|
||||
assert.Equal(t, msgs[0].To, users[0].Email, "checks if the recipient is the admin of the instance")
|
||||
manageUserURL := "/admin/users/" + strconv.FormatInt(users[1].ID, 10)
|
||||
assert.True(t, strings.ContainsAny(msgs[0].Body, manageUserURL), "checks if the message contains the link to manage the newly created user from the admin panel")
|
||||
}
|
||||
MailNewUser(ctx, users[1])
|
||||
|
||||
// test with SEND_NOTIFICATION_EMAIL_ON_NEW_USER disabled; emails shouldn't be sent
|
||||
setting.Admin.SendNotificationEmailOnNewUser = false
|
||||
sa = func(msgs []*Message) {
|
||||
assert.Equal(t, 1, 0, "this shouldn't execute. MailNewUser must exit early since SEND_NOTIFICATION_EMAIL_ON_NEW_USER is disabled")
|
||||
}
|
||||
|
||||
MailNewUser(ctx, users[1])
|
||||
}
|
|
@ -202,3 +202,7 @@ func (m *mailNotifier) RepoPendingTransfer(ctx context.Context, doer, newOwner *
|
|||
log.Error("SendRepoTransferNotifyMail: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *mailNotifier) NewUserSignUp(ctx context.Context, newUser *user_model.User) {
|
||||
MailNewUser(ctx, newUser)
|
||||
}
|
||||
|
|
|
@ -6,14 +6,13 @@ package token
|
|||
import (
|
||||
"context"
|
||||
crypto_hmac "crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/base32"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"github.com/minio/sha256-simd"
|
||||
)
|
||||
|
||||
// A token is a verifiable container describing an action.
|
||||
|
|
|
@ -859,6 +859,11 @@ func (g *GiteaLocalUploader) CreateReviews(reviews ...*base.Review) error {
|
|||
}
|
||||
|
||||
for _, comment := range review.Comments {
|
||||
// Skip code comment if it doesn't have a diff it is commeting on.
|
||||
if comment.DiffHunk == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
line := comment.Line
|
||||
if line != 0 {
|
||||
comment.Position = 1
|
||||
|
|
|
@ -59,6 +59,8 @@ type Notifier interface {
|
|||
EditWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, page, comment string)
|
||||
DeleteWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, page string)
|
||||
|
||||
NewUserSignUp(ctx context.Context, newUser *user_model.User)
|
||||
|
||||
NewRelease(ctx context.Context, rel *repo_model.Release)
|
||||
UpdateRelease(ctx context.Context, doer *user_model.User, rel *repo_model.Release)
|
||||
DeleteRelease(ctx context.Context, doer *user_model.User, rel *repo_model.Release)
|
||||
|
|
|
@ -347,6 +347,13 @@ func RepoPendingTransfer(ctx context.Context, doer, newOwner *user_model.User, r
|
|||
}
|
||||
}
|
||||
|
||||
// NewUserSignUp notifies about a newly signed up user to notifiers
|
||||
func NewUserSignUp(ctx context.Context, newUser *user_model.User) {
|
||||
for _, notifier := range notifiers {
|
||||
notifier.NewUserSignUp(ctx, newUser)
|
||||
}
|
||||
}
|
||||
|
||||
// PackageCreate notifies creation of a package to notifiers
|
||||
func PackageCreate(ctx context.Context, doer *user_model.User, pd *packages_model.PackageDescriptor) {
|
||||
for _, notifier := range notifiers {
|
||||
|
|
|
@ -197,6 +197,9 @@ func (*NullNotifier) SyncDeleteRef(ctx context.Context, doer *user_model.User, r
|
|||
func (*NullNotifier) RepoPendingTransfer(ctx context.Context, doer, newOwner *user_model.User, repo *repo_model.Repository) {
|
||||
}
|
||||
|
||||
func (*NullNotifier) NewUserSignUp(ctx context.Context, newUser *user_model.User) {
|
||||
}
|
||||
|
||||
// PackageCreate places a place holder function
|
||||
func (*NullNotifier) PackageCreate(ctx context.Context, doer *user_model.User, pd *packages_model.PackageDescriptor) {
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"context"
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
@ -29,7 +30,6 @@ import (
|
|||
webhook_module "code.gitea.io/gitea/modules/webhook"
|
||||
|
||||
"github.com/gobwas/glob"
|
||||
"github.com/minio/sha256-simd"
|
||||
)
|
||||
|
||||
// Deliver deliver hook task
|
||||
|
|
|
@ -159,6 +159,8 @@
|
|||
<dd>{{if .Service.DefaultKeepEmailPrivate}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
|
||||
<dt>{{ctx.Locale.Tr "admin.config.default_allow_create_organization"}}</dt>
|
||||
<dd>{{if .Service.DefaultAllowCreateOrganization}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
|
||||
<dt>{{ctx.Locale.Tr "admin.config.allow_dots_in_usernames"}}</dt>
|
||||
<dd>{{if .Service.AllowDotsInUsernames}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
|
||||
<dt>{{ctx.Locale.Tr "admin.config.enable_timetracking"}}</dt>
|
||||
<dd>{{if .Service.EnableTimetracking}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
|
||||
{{if .Service.EnableTimetracking}}
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
<html lang="{{ctx.Locale.Lang}}" data-theme="{{ThemeName .SignedUser}}">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{{if .Title}}{{.Title | RenderEmojiPlain}} - {{end}}{{if .Repository.Name}}{{.Repository.Name}} - {{end}}{{AppName}}</title>
|
||||
{{/* Display `- .Repsository.FullName` only if `.Title` does not already start with that. */}}
|
||||
<title>{{if .Title}}{{.Title | RenderEmojiPlain}} - {{end}}{{if and (.Repository.Name) (not (StringUtils.HasPrefix .Title .Repository.FullName))}}{{.Repository.FullName}} - {{end}}{{AppName}}</title>
|
||||
{{if .ManifestData}}<link rel="manifest" href="data:{{.ManifestData}}">{{end}}
|
||||
<meta name="author" content="{{if .Repository}}{{.Owner.Name}}{{else}}{{MetaAuthor}}{{end}}">
|
||||
<meta name="description" content="{{if .Repository}}{{.Repository.Name}}{{if .Repository.Description}} - {{.Repository.Description}}{{end}}{{else}}{{MetaDescription}}{{end}}">
|
||||
|
|
22
templates/mail/notify/admin_new_user.tmpl
Normal file
22
templates/mail/notify/admin_new_user.tmpl
Normal file
|
@ -0,0 +1,22 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<title>{{.Subject}}</title>
|
||||
|
||||
<style>
|
||||
blockquote { padding-left: 1em; margin: 1em 0; border-left: 1px solid grey; color: #777}
|
||||
.footer { font-size:small; color:#666;}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<ul>
|
||||
<h3>{{.locale.Tr "mail.admin.new_user.user_info"}}</h3>
|
||||
<li>{{.locale.Tr "admin.users.created"}}: {{DateTime "full" .NewUser.LastLoginUnix}}</li>
|
||||
<li>{{.locale.Tr "admin.users.last_login"}}: {{DateTime "full" .NewUser.CreatedUnix}}</li>
|
||||
</ul>
|
||||
<p> {{.Body | Str2html}} </p>
|
||||
</body>
|
||||
</html>
|
|
@ -7,7 +7,7 @@
|
|||
<div class="markup"><pre class="code-block"><code><repositories>
|
||||
<repository>
|
||||
<id>gitea</id>
|
||||
<url><gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/maven"></gitea-origin-url></url>
|
||||
<url><gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/maven"></gitea-origin-url></url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
|
|
|
@ -13,6 +13,11 @@
|
|||
</div>
|
||||
</div>
|
||||
{{template "repo/commits_table" .}}
|
||||
{{if .OldFilename}}
|
||||
<div class="ui bottom attached header">
|
||||
<span>{{.locale.Tr "repo.commits.renamed_from" .OldFilename}} (<a href="{{.OldFilenameHistory}}">{{.locale.Tr "repo.commits.browse_further"}}</a>)</span>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{template "base/footer" .}}
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
</div>
|
||||
{{if .MilestoneID}}
|
||||
<div class="meta gt-my-2">
|
||||
<a class="milestone" href="{{$.Page.RepoLink}}/milestone/{{.MilestoneID}}">
|
||||
<a class="milestone" href="{{.Repo.Link}}/milestone/{{.MilestoneID}}">
|
||||
{{svg "octicon-milestone" 16 "gt-mr-2 gt-vm"}}
|
||||
<span class="gt-vm">{{.Milestone.Name}}</span>
|
||||
</a>
|
||||
|
@ -42,7 +42,7 @@
|
|||
{{if $.Page.LinkedPRs}}
|
||||
{{range index $.Page.LinkedPRs .ID}}
|
||||
<div class="meta gt-my-2">
|
||||
<a href="{{$.Page.RepoLink}}/pulls/{{.Index}}">
|
||||
<a href="{{$.Issue.Repo.Link}}/pulls/{{.Index}}">
|
||||
<span class="gt-m-0 text {{if .PullRequest.HasMerged}}purple{{else if .IsClosed}}red{{else}}green{{end}}">{{svg "octicon-git-merge" 16 "gt-mr-2 gt-vm"}}</span>
|
||||
<span class="gt-vm">{{.Title}} <span class="text light grey">#{{.Index}}</span></span>
|
||||
</a>
|
||||
|
@ -54,7 +54,7 @@
|
|||
{{if or .Labels .Assignees}}
|
||||
<div class="extra content labels-list gt-p-0 gt-pt-2">
|
||||
{{range .Labels}}
|
||||
<a target="_blank" href="{{$.Page.RepoLink}}/issues?labels={{.ID}}">{{RenderLabel ctx .}}</a>
|
||||
<a target="_blank" href="{{$.Issue.Repo.Link}}/issues?labels={{.ID}}">{{RenderLabel ctx .}}</a>
|
||||
{{end}}
|
||||
<div class="right floated">
|
||||
{{range .Assignees}}
|
||||
|
|
|
@ -364,7 +364,7 @@
|
|||
{{end}}
|
||||
</div>
|
||||
{{else if eq .Type 22}}
|
||||
<div class="timeline-item-group">
|
||||
<div class="timeline-item-group" id="{{.HashTag}}">
|
||||
<div class="timeline-item event">
|
||||
{{if .OriginalAuthor}}
|
||||
{{else}}
|
||||
|
@ -403,7 +403,7 @@
|
|||
</span>
|
||||
</div>
|
||||
{{if or .Content .Attachments}}
|
||||
<div class="timeline-item comment" id="{{.HashTag}}">
|
||||
<div class="timeline-item comment">
|
||||
<div class="content comment-container">
|
||||
<div class="ui top attached header comment-header gt-df gt-ac gt-sb">
|
||||
<div class="comment-header-left gt-df gt-ac">
|
||||
|
|
|
@ -976,20 +976,23 @@
|
|||
{{end}}
|
||||
</div>
|
||||
<div class="content">
|
||||
<p>
|
||||
<div class="ui warning message">
|
||||
{{if .Repository.IsArchived}}
|
||||
{{ctx.Locale.Tr "repo.settings.unarchive.text"}}
|
||||
{{else}}
|
||||
{{ctx.Locale.Tr "repo.settings.archive.text"}}
|
||||
{{end}}
|
||||
</p>
|
||||
</div>
|
||||
<form action="{{.Link}}" method="post">
|
||||
{{.CsrfTokenHtml}}
|
||||
<input type="hidden" name="action" value="{{if .Repository.IsArchived}}unarchive{{else}}archive{{end}}">
|
||||
<input type="hidden" name="repo_id" value="{{.Repository.ID}}">
|
||||
{{template "base/modal_actions_confirm" .}}
|
||||
</div>
|
||||
<form action="{{.Link}}" method="post">
|
||||
{{.CsrfTokenHtml}}
|
||||
<input type="hidden" name="action" value="{{if .Repository.IsArchived}}unarchive{{else}}archive{{end}}">
|
||||
<input type="hidden" name="repo_id" value="{{.Repository.ID}}">
|
||||
<div class="text right actions">
|
||||
<button class="ui cancel button">{{.locale.Tr "settings.cancel"}}</button>
|
||||
<button class="ui red button">{{.locale.Tr "repo.settings.archive.button"}}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
|
1
tests/gitea-repositories-meta/user2/repo59.git/HEAD
Normal file
1
tests/gitea-repositories-meta/user2/repo59.git/HEAD
Normal file
|
@ -0,0 +1 @@
|
|||
ref: refs/heads/master
|
4
tests/gitea-repositories-meta/user2/repo59.git/config
Normal file
4
tests/gitea-repositories-meta/user2/repo59.git/config
Normal file
|
@ -0,0 +1,4 @@
|
|||
[core]
|
||||
repositoryformatversion = 0
|
||||
filemode = true
|
||||
bare = true
|
|
@ -0,0 +1 @@
|
|||
Unnamed repository; edit this file 'description' to name the repository.
|
|
@ -0,0 +1,6 @@
|
|||
# git ls-files --others --exclude-from=.git/info/exclude
|
||||
# Lines that start with '#' are comments.
|
||||
# For a project mostly in C, the following would be a good set of
|
||||
# exclude patterns (uncomment them if you want to use them):
|
||||
# *.[oa]
|
||||
# *~
|
Binary file not shown.
|
@ -0,0 +1,2 @@
|
|||
P pack-6dd3a6fe138f1d77e14c2e6b8e6c41e5ae242adf.pack
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,4 @@
|
|||
# pack-refs with: peeled fully-peeled sorted
|
||||
d8f53dfb33f6ccf4169c34970b5e747511c18beb refs/heads/cake-recipe
|
||||
80b83c5c8220c3aa3906e081f202a2a7563ec879 refs/heads/master
|
||||
d8f53dfb33f6ccf4169c34970b5e747511c18beb refs/tags/v1.0
|
|
@ -7,6 +7,7 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
asymkey_model "code.gitea.io/gitea/models/asymkey"
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
|
@ -282,3 +283,54 @@ func TestAPIRenameUser(t *testing.T) {
|
|||
})
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
}
|
||||
|
||||
func TestAPICron(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
// user1 is an admin user
|
||||
session := loginUser(t, "user1")
|
||||
|
||||
t.Run("List", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadAdmin)
|
||||
urlStr := fmt.Sprintf("/api/v1/admin/cron?token=%s", token)
|
||||
req := NewRequest(t, "GET", urlStr)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
assert.NotEmpty(t, resp.Header().Get("X-Total-Count"))
|
||||
|
||||
var crons []api.Cron
|
||||
DecodeJSON(t, resp, &crons)
|
||||
|
||||
assert.NotEmpty(t, crons)
|
||||
})
|
||||
|
||||
t.Run("Execute", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
now := time.Now()
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteAdmin)
|
||||
/// Archive cleanup is harmless, because in the text environment there are none
|
||||
/// and is thus an NOOP operation and therefore doesn't interfere with any other
|
||||
/// tests.
|
||||
urlStr := fmt.Sprintf("/api/v1/admin/cron/archive_cleanup?token=%s", token)
|
||||
req := NewRequest(t, "POST", urlStr)
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
|
||||
// Check for the latest run time for this cron, to ensure it
|
||||
// has been run.
|
||||
urlStr = fmt.Sprintf("/api/v1/admin/cron?token=%s", token)
|
||||
req = NewRequest(t, "GET", urlStr)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
var crons []api.Cron
|
||||
DecodeJSON(t, resp, &crons)
|
||||
|
||||
for _, cron := range crons {
|
||||
if cron.Name == "archive_cleanup" {
|
||||
assert.True(t, now.Before(cron.Prev))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -189,6 +189,43 @@ func TestAPIGetComment(t *testing.T) {
|
|||
assert.Equal(t, expect.Created.Unix(), apiComment.Created.Unix())
|
||||
}
|
||||
|
||||
func TestAPIGetSystemUserComment(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{})
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID})
|
||||
repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
||||
|
||||
for _, systemUser := range []*user_model.User{
|
||||
user_model.NewGhostUser(),
|
||||
user_model.NewActionsUser(),
|
||||
} {
|
||||
body := fmt.Sprintf("Hello %s", systemUser.Name)
|
||||
comment, err := issues_model.CreateComment(db.DefaultContext, &issues_model.CreateCommentOptions{
|
||||
Type: issues_model.CommentTypeComment,
|
||||
Doer: systemUser,
|
||||
Repo: repo,
|
||||
Issue: issue,
|
||||
Content: body,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/comments/%d", repoOwner.Name, repo.Name, comment.ID)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
var apiComment api.Comment
|
||||
DecodeJSON(t, resp, &apiComment)
|
||||
|
||||
if assert.NotNil(t, apiComment.Poster) {
|
||||
if assert.Equal(t, systemUser.ID, apiComment.Poster.ID) {
|
||||
assert.NoError(t, comment.LoadPoster(db.DefaultContext))
|
||||
assert.Equal(t, systemUser.Name, apiComment.Poster.UserName)
|
||||
}
|
||||
}
|
||||
assert.Equal(t, body, apiComment.Body)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPIEditComment(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
const newCommentBody = "This is the new comment body"
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue