forked from NYANDEV/forgejo
Merge branch 'rebase-v1.21/forgejo-dependency' into wip-v1.21-forgejo
This commit is contained in:
commit
30a15784d4
114 changed files with 1496 additions and 323 deletions
10
assets/go-licenses.json
generated
10
assets/go-licenses.json
generated
File diff suppressed because one or more lines are too long
|
@ -409,6 +409,10 @@ USER = root
|
||||||
;;
|
;;
|
||||||
;; Whether execute database models migrations automatically
|
;; Whether execute database models migrations automatically
|
||||||
;AUTO_MIGRATION = true
|
;AUTO_MIGRATION = true
|
||||||
|
;;
|
||||||
|
;; Threshold value (in seconds) beyond which query execution time is logged as a warning in the xorm logger
|
||||||
|
;;
|
||||||
|
;SLOW_QUERY_TRESHOLD = 5s
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
@ -808,6 +812,11 @@ LEVEL = Info
|
||||||
;; Every new user will have restricted permissions depending on this setting
|
;; Every new user will have restricted permissions depending on this setting
|
||||||
;DEFAULT_USER_IS_RESTRICTED = false
|
;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"
|
;; Either "public", "limited" or "private", default is "public"
|
||||||
;; Limited is for users visible only to signed users
|
;; Limited is for users visible only to signed users
|
||||||
;; Private is for users visible only to members of their organizations
|
;; Private is for users visible only to members of their organizations
|
||||||
|
@ -1453,6 +1462,8 @@ LEVEL = Info
|
||||||
;;
|
;;
|
||||||
;; Default configuration for email notifications for users (user configurable). Options: enabled, onmention, disabled
|
;; Default configuration for email notifications for users (user configurable). Options: enabled, onmention, disabled
|
||||||
;DEFAULT_EMAIL_NOTIFICATIONS = enabled
|
;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
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
@ -1767,9 +1778,6 @@ LEVEL = Info
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;
|
;;
|
||||||
;AVATAR_UPLOAD_PATH = data/avatars
|
|
||||||
;REPOSITORY_AVATAR_UPLOAD_PATH = data/repo-avatars
|
|
||||||
;;
|
|
||||||
;; How Gitea deals with missing repository 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
|
;; none = no avatar will be displayed; random = random avatar will be displayed; image = default image will be used
|
||||||
;REPOSITORY_AVATAR_FALLBACK = none
|
;REPOSITORY_AVATAR_FALLBACK = none
|
||||||
|
|
|
@ -454,6 +454,7 @@ The following configuration set `Content-Type: application/vnd.android.package-a
|
||||||
- `MAX_IDLE_CONNS` **2**: Max idle database connections on connection pool, default is 2 - this will be capped to `MAX_OPEN_CONNS`.
|
- `MAX_IDLE_CONNS` **2**: Max idle database connections on connection pool, default is 2 - this will be capped to `MAX_OPEN_CONNS`.
|
||||||
- `CONN_MAX_LIFETIME` **0 or 3s**: Sets the maximum amount of time a DB connection may be reused - default is 0, meaning there is no limit (except on MySQL where it is 3s - see #6804 & #7071).
|
- `CONN_MAX_LIFETIME` **0 or 3s**: Sets the maximum amount of time a DB connection may be reused - default is 0, meaning there is no limit (except on MySQL where it is 3s - see #6804 & #7071).
|
||||||
- `AUTO_MIGRATION` **true**: Whether execute database models migrations automatically.
|
- `AUTO_MIGRATION` **true**: Whether execute database models migrations automatically.
|
||||||
|
- `SLOW_QUERY_TRESHOLD` **5s**: Threshold value in seconds beyond which query execution time is logged as a warning in the xorm logger.
|
||||||
|
|
||||||
[^1]: It may be necessary to specify a hostport even when listening on a unix socket, as the port is part of the socket name. see [#24552](https://github.com/go-gitea/gitea/issues/24552#issuecomment-1681649367) for additional details.
|
[^1]: It may be necessary to specify a hostport even when listening on a unix socket, as the port is part of the socket name. see [#24552](https://github.com/go-gitea/gitea/issues/24552#issuecomment-1681649367) for additional details.
|
||||||
|
|
||||||
|
@ -513,6 +514,7 @@ And the following unique queues:
|
||||||
|
|
||||||
- `DEFAULT_EMAIL_NOTIFICATIONS`: **enabled**: Default configuration for email notifications for users (user configurable). Options: enabled, onmention, disabled
|
- `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.
|
- `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`)
|
## Security (`security`)
|
||||||
|
|
||||||
|
|
8
go.mod
8
go.mod
|
@ -15,7 +15,6 @@ require (
|
||||||
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4
|
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4
|
||||||
github.com/42wim/sshsig v0.0.0-20211121163825-841cf5bbc121
|
github.com/42wim/sshsig v0.0.0-20211121163825-841cf5bbc121
|
||||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358
|
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/PuerkitoBio/goquery v1.8.1
|
||||||
github.com/alecthomas/chroma/v2 v2.10.0
|
github.com/alecthomas/chroma/v2 v2.10.0
|
||||||
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb
|
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb
|
||||||
|
@ -36,7 +35,7 @@ require (
|
||||||
github.com/ethantkoenig/rupture v1.0.1
|
github.com/ethantkoenig/rupture v1.0.1
|
||||||
github.com/felixge/fgprof v0.9.3
|
github.com/felixge/fgprof v0.9.3
|
||||||
github.com/fsnotify/fsnotify v1.6.0
|
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/activitypub v0.0.0-20231003111253-1fba3772399b
|
||||||
github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73
|
github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73
|
||||||
github.com/go-chi/chi/v5 v5.0.10
|
github.com/go-chi/chi/v5 v5.0.10
|
||||||
|
@ -78,7 +77,6 @@ require (
|
||||||
github.com/mholt/archiver/v3 v3.5.1
|
github.com/mholt/archiver/v3 v3.5.1
|
||||||
github.com/microcosm-cc/bluemonday v1.0.26
|
github.com/microcosm-cc/bluemonday v1.0.26
|
||||||
github.com/minio/minio-go/v7 v7.0.63
|
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/msteinert/pam v1.2.0
|
||||||
github.com/nektos/act v0.2.52
|
github.com/nektos/act v0.2.52
|
||||||
github.com/niklasfasching/go-org v1.7.0
|
github.com/niklasfasching/go-org v1.7.0
|
||||||
|
@ -101,7 +99,6 @@ require (
|
||||||
github.com/ulikunitz/xz v0.5.11
|
github.com/ulikunitz/xz v0.5.11
|
||||||
github.com/urfave/cli/v2 v2.25.7
|
github.com/urfave/cli/v2 v2.25.7
|
||||||
github.com/xanzy/go-gitlab v0.93.1
|
github.com/xanzy/go-gitlab v0.93.1
|
||||||
github.com/xeipuuv/gojsonschema v1.2.0
|
|
||||||
github.com/yohcop/openid-go v1.0.1
|
github.com/yohcop/openid-go v1.0.1
|
||||||
github.com/yuin/goldmark v1.5.6
|
github.com/yuin/goldmark v1.5.6
|
||||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
|
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
|
||||||
|
@ -232,6 +229,7 @@ require (
|
||||||
github.com/mholt/acmez v1.2.0 // indirect
|
github.com/mholt/acmez v1.2.0 // indirect
|
||||||
github.com/miekg/dns v1.1.56 // indirect
|
github.com/miekg/dns v1.1.56 // indirect
|
||||||
github.com/minio/md5-simd v1.1.2 // 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/copystructure v1.2.0 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||||
|
@ -277,8 +275,6 @@ require (
|
||||||
github.com/valyala/fastjson v1.6.4 // indirect
|
github.com/valyala/fastjson v1.6.4 // indirect
|
||||||
github.com/x448/float16 v0.8.4 // indirect
|
github.com/x448/float16 v0.8.4 // indirect
|
||||||
github.com/xanzy/ssh-agent v0.3.3 // 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/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
|
||||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||||
github.com/zeebo/blake3 v0.2.3 // 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.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 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
||||||
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
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 h1:kMFnB0vCcX7IL/m9Y5LO+KQYv+t1CQOiFe6+SV2J7bE=
|
||||||
github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
|
github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
|
||||||
github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM=
|
github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM=
|
||||||
|
@ -329,8 +327,8 @@ github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4
|
||||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
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 h1:oHsG0V/Q6E/wqTS2O1Cozzsy69nqCiguo5Q1a1ADivE=
|
||||||
github.com/fxamacker/cbor/v2 v2.5.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
|
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.6-0.20230927171611-ece6c7995e46 h1:fYiA820jw7wmAvdXrHwMItxjJkra7dT9y8yiXhtzb94=
|
||||||
github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4=
|
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/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/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24=
|
||||||
github.com/go-ap/activitypub v0.0.0-20231003111253-1fba3772399b h1:VLD6IPBDkqEsOZ+EfLO6MayuHycZ0cv4BStTlRoZduo=
|
github.com/go-ap/activitypub v0.0.0-20231003111253-1fba3772399b h1:VLD6IPBDkqEsOZ+EfLO6MayuHycZ0cv4BStTlRoZduo=
|
||||||
|
@ -1047,13 +1045,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.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.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
|
||||||
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
|
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 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
|
||||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
|
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=
|
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||||
|
@ -1237,7 +1228,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-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-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-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.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.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
|
@ -1337,9 +1327,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-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-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-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-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.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.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
@ -1353,7 +1341,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-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-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-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.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||||
|
|
|
@ -169,7 +169,12 @@ func RewriteAllPublicKeys(ctx context.Context) error {
|
||||||
return err
|
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)
|
return util.Rename(tmpPath, fPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -92,7 +92,12 @@ func RewriteAllPrincipalKeys(ctx context.Context) error {
|
||||||
return err
|
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)
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
"encoding/base32"
|
"encoding/base32"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -19,7 +20,6 @@ import (
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
uuid "github.com/google/uuid"
|
uuid "github.com/google/uuid"
|
||||||
"github.com/minio/sha256-simd"
|
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
"xorm.io/builder"
|
"xorm.io/builder"
|
||||||
"xorm.io/xorm"
|
"xorm.io/xorm"
|
||||||
|
|
|
@ -250,7 +250,7 @@ func (s AccessTokenScope) parse() (accessTokenScopeBitmap, error) {
|
||||||
remainingScopes = remainingScopes[i+1:]
|
remainingScopes = remainingScopes[i+1:]
|
||||||
}
|
}
|
||||||
singleScope := AccessTokenScope(v)
|
singleScope := AccessTokenScope(v)
|
||||||
if singleScope == "" {
|
if singleScope == "" || singleScope == "sudo" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if singleScope == AccessTokenScopeAll {
|
if singleScope == AccessTokenScopeAll {
|
||||||
|
|
|
@ -20,7 +20,7 @@ func TestAccessTokenScope_Normalize(t *testing.T) {
|
||||||
tests := []scopeTestNormalize{
|
tests := []scopeTestNormalize{
|
||||||
{"", "", nil},
|
{"", "", nil},
|
||||||
{"write:misc,write:notification,read:package,write:notification,public-only", "public-only,write:misc,write:notification,read:package", 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", "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},
|
{"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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
|
"crypto/sha256"
|
||||||
"crypto/subtle"
|
"crypto/subtle"
|
||||||
"encoding/base32"
|
"encoding/base32"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
@ -18,7 +19,6 @@ import (
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
"github.com/minio/sha256-simd"
|
|
||||||
"github.com/pquerna/otp/totp"
|
"github.com/pquerna/otp/totp"
|
||||||
"golang.org/x/crypto/pbkdf2"
|
"golang.org/x/crypto/pbkdf2"
|
||||||
)
|
)
|
||||||
|
|
|
@ -11,10 +11,13 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
|
||||||
"xorm.io/xorm"
|
"xorm.io/xorm"
|
||||||
|
"xorm.io/xorm/contexts"
|
||||||
"xorm.io/xorm/names"
|
"xorm.io/xorm/names"
|
||||||
"xorm.io/xorm/schemas"
|
"xorm.io/xorm/schemas"
|
||||||
|
|
||||||
|
@ -147,6 +150,13 @@ func InitEngine(ctx context.Context) error {
|
||||||
xormEngine.SetConnMaxLifetime(setting.Database.ConnMaxLifetime)
|
xormEngine.SetConnMaxLifetime(setting.Database.ConnMaxLifetime)
|
||||||
xormEngine.SetDefaultContext(ctx)
|
xormEngine.SetDefaultContext(ctx)
|
||||||
|
|
||||||
|
if setting.Database.SlowQueryTreshold > 0 {
|
||||||
|
xormEngine.AddHook(&SlowQueryHook{
|
||||||
|
Treshold: setting.Database.SlowQueryTreshold,
|
||||||
|
Logger: log.GetLogger("xorm"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
SetDefaultEngine(ctx, xormEngine)
|
SetDefaultEngine(ctx, xormEngine)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -300,3 +310,21 @@ func SetLogSQL(ctx context.Context, on bool) {
|
||||||
sess.Engine().ShowSQL(on)
|
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 (
|
import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
issues_model "code.gitea.io/gitea/models/issues"
|
issues_model "code.gitea.io/gitea/models/issues"
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/test"
|
||||||
|
|
||||||
_ "code.gitea.io/gitea/cmd" // for TestPrimaryKeys
|
_ "code.gitea.io/gitea/cmd" // for TestPrimaryKeys
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"xorm.io/xorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDumpDatabase(t *testing.T) {
|
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_prerelease: false
|
||||||
is_tag: false
|
is_tag: false
|
||||||
created_unix: 946684803
|
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
|
type: 1
|
||||||
created_unix: 946684810
|
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
|
id: 91
|
||||||
repo_id: 58
|
repo_id: 58
|
||||||
|
|
|
@ -1467,6 +1467,7 @@
|
||||||
owner_name: user27
|
owner_name: user27
|
||||||
lower_name: repo49
|
lower_name: repo49
|
||||||
name: repo49
|
name: repo49
|
||||||
|
description: A wonderful repository with more than just a README.md
|
||||||
default_branch: master
|
default_branch: master
|
||||||
num_watches: 0
|
num_watches: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
|
@ -1693,3 +1694,16 @@
|
||||||
size: 0
|
size: 0
|
||||||
is_fsck_enabled: true
|
is_fsck_enabled: true
|
||||||
close_issues_via_commit_in_any_branch: false
|
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_followers: 2
|
||||||
num_following: 1
|
num_following: 1
|
||||||
num_stars: 2
|
num_stars: 2
|
||||||
num_repos: 14
|
num_repos: 15
|
||||||
num_teams: 0
|
num_teams: 0
|
||||||
num_members: 0
|
num_members: 0
|
||||||
visibility: 0
|
visibility: 0
|
||||||
|
|
|
@ -41,6 +41,8 @@ var migrations = []*Migration{
|
||||||
NewMigration("Add Forgejo Blocked Users table", forgejo_v1_20.AddForgejoBlockedUser),
|
NewMigration("Add Forgejo Blocked Users table", forgejo_v1_20.AddForgejoBlockedUser),
|
||||||
// v1 -> v2
|
// v1 -> v2
|
||||||
NewMigration("create the forgejo_sem_ver table", forgejo_v1_20.CreateSemVerTable),
|
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.
|
// 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
|
// LoadPoster loads comment poster
|
||||||
func (c *Comment) LoadPoster(ctx context.Context) (err error) {
|
func (c *Comment) LoadPoster(ctx context.Context) (err error) {
|
||||||
if c.PosterID <= 0 || c.Poster != nil {
|
if c.Poster != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
package base
|
package base
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
|
||||||
"github.com/minio/sha256-simd"
|
|
||||||
"golang.org/x/crypto/pbkdf2"
|
"golang.org/x/crypto/pbkdf2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
package v1_14 //nolint
|
package v1_14 //nolint
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
|
||||||
"github.com/minio/sha256-simd"
|
|
||||||
"golang.org/x/crypto/argon2"
|
"golang.org/x/crypto/argon2"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
"golang.org/x/crypto/pbkdf2"
|
"golang.org/x/crypto/pbkdf2"
|
||||||
|
|
|
@ -576,9 +576,9 @@ func (repo *Repository) DescriptionHTML(ctx context.Context) template.HTML {
|
||||||
}, repo.Description)
|
}, repo.Description)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Failed to render description for %s (ID: %d): %v", repo.Name, repo.ID, err)
|
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.
|
// CloneLink represents different types of clone URLs of repository.
|
||||||
|
|
|
@ -138,12 +138,12 @@ func getTestCases() []struct {
|
||||||
{
|
{
|
||||||
name: "AllPublic/PublicRepositoriesOfUserIncludingCollaborative",
|
name: "AllPublic/PublicRepositoriesOfUserIncludingCollaborative",
|
||||||
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, AllPublic: true, Template: util.OptionalBoolFalse},
|
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",
|
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},
|
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",
|
name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborativeByName",
|
||||||
|
@ -158,7 +158,7 @@ func getTestCases() []struct {
|
||||||
{
|
{
|
||||||
name: "AllPublic/PublicRepositoriesOfOrganization",
|
name: "AllPublic/PublicRepositoriesOfOrganization",
|
||||||
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 17, AllPublic: true, Collaborate: util.OptionalBoolFalse, Template: util.OptionalBoolFalse},
|
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",
|
name: "AllTemplates",
|
||||||
|
|
|
@ -223,6 +223,12 @@ func GetAllUsers(ctx context.Context) ([]*User, error) {
|
||||||
return users, db.GetEngine(ctx).OrderBy("id").Where("type = ?", UserTypeIndividual).Find(&users)
|
return users, db.GetEngine(ctx).OrderBy("id").Where("type = ?", UserTypeIndividual).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.
|
// IsLocal returns true if user login type is LoginPlain.
|
||||||
func (u *User) IsLocal() bool {
|
func (u *User) IsLocal() bool {
|
||||||
return u.LoginType <= auth.Plain
|
return u.LoginType <= auth.Plain
|
||||||
|
@ -380,6 +386,11 @@ func (u *User) SetPassword(passwd string) (err error) {
|
||||||
return nil
|
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 {
|
if u.Salt, err = GetUserSalt(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,10 +9,12 @@ import (
|
||||||
"code.gitea.io/gitea/modules/structs"
|
"code.gitea.io/gitea/modules/structs"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const GhostUserID = -1
|
||||||
|
|
||||||
// NewGhostUser creates and returns a fake user for someone has deleted their account.
|
// NewGhostUser creates and returns a fake user for someone has deleted their account.
|
||||||
func NewGhostUser() *User {
|
func NewGhostUser() *User {
|
||||||
return &User{
|
return &User{
|
||||||
ID: -1,
|
ID: GhostUserID,
|
||||||
Name: "Ghost",
|
Name: "Ghost",
|
||||||
LowerName: "ghost",
|
LowerName: "ghost",
|
||||||
}
|
}
|
||||||
|
|
|
@ -550,3 +550,13 @@ func Test_ValidateUser(t *testing.T) {
|
||||||
assert.EqualValues(t, expected, err == nil, fmt.Sprintf("case: %+v", kase))
|
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
|
package hash
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
|
||||||
"github.com/minio/sha256-simd"
|
|
||||||
"golang.org/x/crypto/pbkdf2"
|
"golang.org/x/crypto/pbkdf2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -4,10 +4,9 @@
|
||||||
package avatar
|
package avatar
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/minio/sha256-simd"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// HashAvatar will generate a unique string, which ensures that when there's a
|
// HashAvatar will generate a unique string, which ensures that when there's a
|
||||||
|
|
|
@ -7,11 +7,10 @@
|
||||||
package identicon
|
package identicon
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
|
|
||||||
"github.com/minio/sha256-simd"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const minImageSize = 16
|
const minImageSize = 16
|
||||||
|
|
|
@ -6,6 +6,7 @@ package base
|
||||||
import (
|
import (
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
|
"crypto/sha256"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
|
@ -24,7 +25,6 @@ import (
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
|
||||||
"github.com/dustin/go-humanize"
|
"github.com/dustin/go-humanize"
|
||||||
"github.com/minio/sha256-simd"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// EncodeMD5 encodes string to md5 hex value.
|
// EncodeMD5 encodes string to md5 hex value.
|
||||||
|
|
|
@ -4,16 +4,14 @@
|
||||||
package context
|
package context
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"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/setting"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
"code.gitea.io/gitea/modules/web/middleware"
|
"code.gitea.io/gitea/modules/web/middleware"
|
||||||
|
|
||||||
"github.com/minio/sha256-simd"
|
|
||||||
"golang.org/x/crypto/pbkdf2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const CookieNameFlash = "gitea_flash"
|
const CookieNameFlash = "gitea_flash"
|
||||||
|
@ -46,41 +44,13 @@ func (ctx *Context) GetSiteCookie(name string) string {
|
||||||
return middleware.GetSiteCookie(ctx.Req, name)
|
return middleware.GetSiteCookie(ctx.Req, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSuperSecureCookie returns given cookie value from request header with secret string.
|
// SetLTACookie will generate a LTA token and add it as an cookie.
|
||||||
func (ctx *Context) GetSuperSecureCookie(secret, name string) (string, bool) {
|
func (ctx *Context) SetLTACookie(u *user_model.User) error {
|
||||||
val := ctx.GetSiteCookie(name)
|
days := 86400 * setting.LogInRememberDays
|
||||||
return ctx.CookieDecrypt(secret, val)
|
lookup, validator, err := auth_model.GenerateAuthToken(ctx, u.ID, timeutil.TimeStampNow().Add(int64(days)))
|
||||||
}
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", false
|
return err
|
||||||
}
|
}
|
||||||
|
ctx.SetSiteCookie(setting.CookieRememberName, lookup+":"+validator, days)
|
||||||
key := pbkdf2.Key([]byte(secret), []byte(secret), 1000, 16, sha256.New)
|
return nil
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -509,6 +509,62 @@ func GetCommitFileStatus(ctx context.Context, repoPath, commitID string) (*Commi
|
||||||
return fileStatus, nil
|
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.
|
// 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) {
|
func GetFullCommitID(ctx context.Context, repoPath, shortID string) (string, error) {
|
||||||
commitID, _, err := NewCommand(ctx, "rev-parse").AddDynamicArguments(shortID).RunStdString(&RunOpts{Dir: repoPath})
|
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.Removed, expected.Removed)
|
||||||
assert.Equal(t, commitFileStatus.Modified, expected.Modified)
|
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
|
package git
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
|
||||||
"github.com/minio/sha256-simd"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Cache represents a caching interface
|
// Cache represents a caching interface
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
package lfs
|
package lfs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"hash"
|
"hash"
|
||||||
|
@ -12,8 +13,6 @@ import (
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/storage"
|
"code.gitea.io/gitea/modules/storage"
|
||||||
|
|
||||||
"github.com/minio/sha256-simd"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
package lfs
|
package lfs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -12,8 +13,6 @@ import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/minio/sha256-simd"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -19,6 +19,7 @@ import (
|
||||||
// any modification to the underlying policies once it's been created.
|
// any modification to the underlying policies once it's been created.
|
||||||
type Sanitizer struct {
|
type Sanitizer struct {
|
||||||
defaultPolicy *bluemonday.Policy
|
defaultPolicy *bluemonday.Policy
|
||||||
|
descriptionPolicy *bluemonday.Policy
|
||||||
rendererPolicies map[string]*bluemonday.Policy
|
rendererPolicies map[string]*bluemonday.Policy
|
||||||
init sync.Once
|
init sync.Once
|
||||||
}
|
}
|
||||||
|
@ -41,6 +42,7 @@ func NewSanitizer() {
|
||||||
func InitializeSanitizer() {
|
func InitializeSanitizer() {
|
||||||
sanitizer.rendererPolicies = map[string]*bluemonday.Policy{}
|
sanitizer.rendererPolicies = map[string]*bluemonday.Policy{}
|
||||||
sanitizer.defaultPolicy = createDefaultPolicy()
|
sanitizer.defaultPolicy = createDefaultPolicy()
|
||||||
|
sanitizer.descriptionPolicy = createRepoDescriptionPolicy()
|
||||||
|
|
||||||
for name, renderer := range renderers {
|
for name, renderer := range renderers {
|
||||||
sanitizerRules := renderer.SanitizerRules()
|
sanitizerRules := renderer.SanitizerRules()
|
||||||
|
@ -161,6 +163,27 @@ func createDefaultPolicy() *bluemonday.Policy {
|
||||||
return 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) {
|
func addSanitizerRules(policy *bluemonday.Policy, rules []setting.MarkupSanitizerRule) {
|
||||||
for _, rule := range rules {
|
for _, rule := range rules {
|
||||||
if rule.AllowDataURIImages {
|
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.
|
// Sanitize takes a string that contains a HTML fragment or document and applies policy whitelist.
|
||||||
func Sanitize(s string) string {
|
func Sanitize(s string) string {
|
||||||
NewSanitizer()
|
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) {
|
func TestSanitizeNonEscape(t *testing.T) {
|
||||||
descStr := "<scrİpt><script>alert(document.domain)</script></scrİpt>"
|
descStr := "<scrİpt><script>alert(document.domain)</script></scrİpt>"
|
||||||
|
|
||||||
|
|
|
@ -167,7 +167,11 @@ func getDirectorySize(path string) (int64, error) {
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if info.IsDir() {
|
|
||||||
|
fileName := info.Name()
|
||||||
|
// Ignore temporary Git files as they will like be missing once info.Info is
|
||||||
|
// called and cause a disrupt to the whole operation.
|
||||||
|
if info.IsDir() || strings.HasSuffix(fileName, ".lock") || strings.HasPrefix(filepath.Base(fileName), "tmp_graph") {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
f, err := info.Info()
|
f, err := info.Info()
|
||||||
|
|
|
@ -7,13 +7,12 @@ import (
|
||||||
"crypto/aes"
|
"crypto/aes"
|
||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/minio/sha256-simd"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// AesEncrypt encrypts text and given key with AES.
|
// AesEncrypt encrypts text and given key with AES.
|
||||||
|
|
|
@ -7,6 +7,7 @@ package setting
|
||||||
var Admin struct {
|
var Admin struct {
|
||||||
DisableRegularOrgCreation bool
|
DisableRegularOrgCreation bool
|
||||||
DefaultEmailNotification string
|
DefaultEmailNotification string
|
||||||
|
SendNotificationEmailOnNewUser bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadAdminFrom(rootCfg ConfigProvider) {
|
func loadAdminFrom(rootCfg ConfigProvider) {
|
||||||
|
|
|
@ -45,6 +45,7 @@ var (
|
||||||
ConnMaxLifetime time.Duration
|
ConnMaxLifetime time.Duration
|
||||||
IterateBufferSize int
|
IterateBufferSize int
|
||||||
AutoMigration bool
|
AutoMigration bool
|
||||||
|
SlowQueryTreshold time.Duration
|
||||||
}{
|
}{
|
||||||
Timeout: 500,
|
Timeout: 500,
|
||||||
IterateBufferSize: 50,
|
IterateBufferSize: 50,
|
||||||
|
@ -87,6 +88,7 @@ func loadDBSetting(rootCfg ConfigProvider) {
|
||||||
Database.DBConnectRetries = sec.Key("DB_RETRIES").MustInt(10)
|
Database.DBConnectRetries = sec.Key("DB_RETRIES").MustInt(10)
|
||||||
Database.DBConnectBackoff = sec.Key("DB_RETRY_BACKOFF").MustDuration(3 * time.Second)
|
Database.DBConnectBackoff = sec.Key("DB_RETRY_BACKOFF").MustDuration(3 * time.Second)
|
||||||
Database.AutoMigration = sec.Key("AUTO_MIGRATION").MustBool(true)
|
Database.AutoMigration = sec.Key("AUTO_MIGRATION").MustBool(true)
|
||||||
|
Database.SlowQueryTreshold = sec.Key("SLOW_QUERY_TRESHOLD").MustDuration(5 * time.Second)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DBConnStr returns database connection string
|
// DBConnStr returns database connection string
|
||||||
|
|
|
@ -19,7 +19,6 @@ var (
|
||||||
SecretKey string
|
SecretKey string
|
||||||
InternalToken string // internal access token
|
InternalToken string // internal access token
|
||||||
LogInRememberDays int
|
LogInRememberDays int
|
||||||
CookieUserName string
|
|
||||||
CookieRememberName string
|
CookieRememberName string
|
||||||
ReverseProxyAuthUser string
|
ReverseProxyAuthUser string
|
||||||
ReverseProxyAuthEmail string
|
ReverseProxyAuthEmail string
|
||||||
|
@ -104,7 +103,6 @@ func loadSecurityFrom(rootCfg ConfigProvider) {
|
||||||
sec := rootCfg.Section("security")
|
sec := rootCfg.Section("security")
|
||||||
InstallLock = HasInstallLock(rootCfg)
|
InstallLock = HasInstallLock(rootCfg)
|
||||||
LogInRememberDays = sec.Key("LOGIN_REMEMBER_DAYS").MustInt(7)
|
LogInRememberDays = sec.Key("LOGIN_REMEMBER_DAYS").MustInt(7)
|
||||||
CookieUserName = sec.Key("COOKIE_USERNAME").MustString("gitea_awesome")
|
|
||||||
SecretKey = loadSecret(sec, "SECRET_KEY_URI", "SECRET_KEY")
|
SecretKey = loadSecret(sec, "SECRET_KEY_URI", "SECRET_KEY")
|
||||||
if SecretKey == "" {
|
if SecretKey == "" {
|
||||||
// FIXME: https://github.com/go-gitea/gitea/issues/16832
|
// FIXME: https://github.com/go-gitea/gitea/issues/16832
|
||||||
|
|
|
@ -68,6 +68,7 @@ var Service = struct {
|
||||||
DefaultKeepEmailPrivate bool
|
DefaultKeepEmailPrivate bool
|
||||||
DefaultAllowCreateOrganization bool
|
DefaultAllowCreateOrganization bool
|
||||||
DefaultUserIsRestricted bool
|
DefaultUserIsRestricted bool
|
||||||
|
AllowDotsInUsernames bool
|
||||||
EnableTimetracking bool
|
EnableTimetracking bool
|
||||||
DefaultEnableTimetracking bool
|
DefaultEnableTimetracking bool
|
||||||
DefaultEnableDependencies bool
|
DefaultEnableDependencies bool
|
||||||
|
@ -180,6 +181,7 @@ func loadServiceFrom(rootCfg ConfigProvider) {
|
||||||
Service.DefaultKeepEmailPrivate = sec.Key("DEFAULT_KEEP_EMAIL_PRIVATE").MustBool()
|
Service.DefaultKeepEmailPrivate = sec.Key("DEFAULT_KEEP_EMAIL_PRIVATE").MustBool()
|
||||||
Service.DefaultAllowCreateOrganization = sec.Key("DEFAULT_ALLOW_CREATE_ORGANIZATION").MustBool(true)
|
Service.DefaultAllowCreateOrganization = sec.Key("DEFAULT_ALLOW_CREATE_ORGANIZATION").MustBool(true)
|
||||||
Service.DefaultUserIsRestricted = sec.Key("DEFAULT_USER_IS_RESTRICTED").MustBool(false)
|
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)
|
Service.EnableTimetracking = sec.Key("ENABLE_TIMETRACKING").MustBool(true)
|
||||||
if Service.EnableTimetracking {
|
if Service.EnableTimetracking {
|
||||||
Service.DefaultEnableTimetracking = sec.Key("DEFAULT_ENABLE_TIMETRACKING").MustBool(true)
|
Service.DefaultEnableTimetracking = sec.Key("DEFAULT_ENABLE_TIMETRACKING").MustBool(true)
|
||||||
|
|
|
@ -17,7 +17,6 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -165,10 +164,6 @@ func sessionHandler(session ssh.Session) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func publicKeyHandler(ctx ssh.Context, key ssh.PublicKey) bool {
|
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
|
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())
|
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
|
// look for the exact principal
|
||||||
principalLoop:
|
principalLoop:
|
||||||
for _, principal := range cert.ValidPrincipals {
|
for _, principal := range cert.ValidPrincipals {
|
||||||
pkey, err := asymkey_model.SearchPublicKeyByContentExact(parentCtx, principal)
|
pkey, err := asymkey_model.SearchPublicKeyByContentExact(ctx, principal)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if asymkey_model.IsErrKeyNotExist(err) {
|
if asymkey_model.IsErrKeyNotExist(err) {
|
||||||
log.Debug("Principal Rejected: %s Unknown Principal: %s", ctx.RemoteAddr(), principal)
|
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))
|
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 err != nil {
|
||||||
if asymkey_model.IsErrKeyNotExist(err) {
|
if asymkey_model.IsErrKeyNotExist(err) {
|
||||||
log.Warn("Unknown public key: %s from %s", gossh.FingerprintSHA256(key), ctx.RemoteAddr())
|
log.Warn("Unknown public key: %s from %s", gossh.FingerprintSHA256(key), ctx.RemoteAddr())
|
||||||
|
|
|
@ -7,10 +7,9 @@ import (
|
||||||
"crypto"
|
"crypto"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
|
"crypto/sha256"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
|
|
||||||
"github.com/minio/sha256-simd"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// GenerateKeyPair generates a public and private keypair
|
// GenerateKeyPair generates a public and private keypair
|
||||||
|
|
|
@ -7,12 +7,12 @@ import (
|
||||||
"crypto"
|
"crypto"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
|
"crypto/sha256"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"regexp"
|
"regexp"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/minio/sha256-simd"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -4,10 +4,6 @@
|
||||||
package util
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/aes"
|
|
||||||
"crypto/cipher"
|
|
||||||
"crypto/rand"
|
|
||||||
"errors"
|
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
@ -40,52 +36,3 @@ func CopyFile(src, dest string) error {
|
||||||
}
|
}
|
||||||
return os.Chmod(dest, si.Mode())
|
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
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/aes"
|
|
||||||
"crypto/rand"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -37,21 +35,3 @@ func TestCopyFile(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, testContent, dstContent)
|
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 (
|
var (
|
||||||
validUsernamePattern = regexp.MustCompile(`^[\da-zA-Z][-.\w]*$`)
|
validUsernamePatternWithDots = regexp.MustCompile(`^[\da-zA-Z][-.\w]*$`)
|
||||||
invalidUsernamePattern = regexp.MustCompile(`[-._]{2,}|[-._]$`) // No consecutive or trailing non-alphanumeric chars
|
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
|
// IsValidUsername checks if username is valid
|
||||||
func IsValidUsername(name string) bool {
|
func IsValidUsername(name string) bool {
|
||||||
// It is difficult to find a single pattern that is both readable and effective,
|
// It is difficult to find a single pattern that is both readable and effective,
|
||||||
// but it's easier to use positive and negative checks.
|
// 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 {
|
tests := []struct {
|
||||||
arg string
|
arg string
|
||||||
want bool
|
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 {
|
provider := func(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(respOrig http.ResponseWriter, req *http.Request) {
|
return http.HandlerFunc(func(respOrig http.ResponseWriter, req *http.Request) {
|
||||||
// wrap the response writer to check whether the response has been written
|
// wrap the response writer to check whether the response has been written
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/translation"
|
"code.gitea.io/gitea/modules/translation"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
"code.gitea.io/gitea/modules/validation"
|
"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:
|
case validation.ErrRegexPattern:
|
||||||
data["ErrorMsg"] = trName + l.Tr("form.regex_pattern_error", errs[0].Message)
|
data["ErrorMsg"] = trName + l.Tr("form.regex_pattern_error", errs[0].Message)
|
||||||
case validation.ErrUsername:
|
case validation.ErrUsername:
|
||||||
|
if setting.Service.AllowDotsInUsernames {
|
||||||
data["ErrorMsg"] = trName + l.Tr("form.username_error")
|
data["ErrorMsg"] = trName + l.Tr("form.username_error")
|
||||||
|
} else {
|
||||||
|
data["ErrorMsg"] = trName + l.Tr("form.username_error_no_dots")
|
||||||
|
}
|
||||||
case validation.ErrInvalidGroupTeamMap:
|
case validation.ErrInvalidGroupTeamMap:
|
||||||
data["ErrorMsg"] = trName + l.Tr("form.invalid_group_team_map_error", errs[0].Message)
|
data["ErrorMsg"] = trName + l.Tr("form.invalid_group_team_map_error", errs[0].Message)
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -295,6 +295,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_allow_create_organization_popup = Allow new user accounts to create organizations by default.
|
||||||
default_enable_timetracking = Enable Time Tracking by Default
|
default_enable_timetracking = Enable Time Tracking by Default
|
||||||
default_enable_timetracking_popup = Enable time tracking for new repositories 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 = 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'.
|
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
|
password_algorithm = Password Hash Algorithm
|
||||||
|
@ -439,6 +440,10 @@ activate_email = Verify your email address
|
||||||
activate_email.title = %s, please 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>:
|
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 = Welcome to Gitea
|
||||||
register_notify.title = %[1]s, welcome to %[2]s
|
register_notify.title = %[1]s, welcome to %[2]s
|
||||||
register_notify.text_1 = this is your registration confirmation email for %s!
|
register_notify.text_1 = this is your registration confirmation email for %s!
|
||||||
|
@ -533,6 +538,7 @@ include_error = ` must contain substring "%s".`
|
||||||
glob_pattern_error = ` glob pattern is invalid: %s.`
|
glob_pattern_error = ` glob pattern is invalid: %s.`
|
||||||
regex_pattern_error = ` regex 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 = ` 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`
|
invalid_group_team_map_error = ` mapping is invalid: %s`
|
||||||
unknown_error = Unknown error:
|
unknown_error = Unknown error:
|
||||||
captcha_incorrect = The CAPTCHA code is incorrect.
|
captcha_incorrect = The CAPTCHA code is incorrect.
|
||||||
|
@ -1286,6 +1292,8 @@ commits.find = Search
|
||||||
commits.search_all = All Branches
|
commits.search_all = All Branches
|
||||||
commits.author = Author
|
commits.author = Author
|
||||||
commits.message = Message
|
commits.message = Message
|
||||||
|
commits.browse_further = Browse further
|
||||||
|
commits.renamed_from = Renamed from %s
|
||||||
commits.date = Date
|
commits.date = Date
|
||||||
commits.older = Older
|
commits.older = Older
|
||||||
commits.newer = Newer
|
commits.newer = Newer
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"crypto"
|
"crypto"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
|
"crypto/sha256"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
|
@ -26,8 +27,6 @@ import (
|
||||||
chef_module "code.gitea.io/gitea/modules/packages/chef"
|
chef_module "code.gitea.io/gitea/modules/packages/chef"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
"code.gitea.io/gitea/services/auth"
|
"code.gitea.io/gitea/services/auth"
|
||||||
|
|
||||||
"github.com/minio/sha256-simd"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -6,6 +6,7 @@ package maven
|
||||||
import (
|
import (
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
|
"crypto/sha256"
|
||||||
"crypto/sha512"
|
"crypto/sha512"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
|
@ -26,8 +27,6 @@ import (
|
||||||
maven_module "code.gitea.io/gitea/modules/packages/maven"
|
maven_module "code.gitea.io/gitea/modules/packages/maven"
|
||||||
"code.gitea.io/gitea/routers/api/packages/helper"
|
"code.gitea.io/gitea/routers/api/packages/helper"
|
||||||
packages_service "code.gitea.io/gitea/services/packages"
|
packages_service "code.gitea.io/gitea/services/packages"
|
||||||
|
|
||||||
"github.com/minio/sha256-simd"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -55,10 +55,23 @@ func Search(ctx *context.APIContext) {
|
||||||
|
|
||||||
listOptions := utils.GetListOptions(ctx)
|
listOptions := utils.GetListOptions(ctx)
|
||||||
|
|
||||||
users, maxResults, err := user_model.SearchUsers(ctx, &user_model.SearchUserOptions{
|
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,
|
Actor: ctx.Doer,
|
||||||
Keyword: ctx.FormTrim("q"),
|
Keyword: ctx.FormTrim("q"),
|
||||||
UID: ctx.FormInt64("uid"),
|
UID: uid,
|
||||||
Type: user_model.UserTypeIndividual,
|
Type: user_model.UserTypeIndividual,
|
||||||
ListOptions: listOptions,
|
ListOptions: listOptions,
|
||||||
})
|
})
|
||||||
|
@ -69,6 +82,7 @@ func Search(ctx *context.APIContext) {
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ctx.SetLinkHeader(int(maxResults), listOptions.PageSize)
|
ctx.SetLinkHeader(int(maxResults), listOptions.PageSize)
|
||||||
ctx.SetTotalCountHeader(maxResults)
|
ctx.SetTotalCountHeader(maxResults)
|
||||||
|
|
|
@ -358,6 +358,12 @@ func SubmitInstall(ctx *context.Context) {
|
||||||
ctx.RenderWithErr(ctx.Tr("form.password_not_match"), tplInstall, form)
|
ctx.RenderWithErr(ctx.Tr("form.password_not_match"), tplInstall, form)
|
||||||
return
|
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
|
// Init the engine with migration
|
||||||
|
@ -547,18 +553,13 @@ func SubmitInstall(ctx *context.Context) {
|
||||||
u, _ = user_model.GetUserByName(ctx, u.Name)
|
u, _ = user_model.GetUserByName(ctx, u.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
days := 86400 * setting.LogInRememberDays
|
if err := ctx.SetLTACookie(u); err != nil {
|
||||||
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 {
|
|
||||||
ctx.RenderWithErr(ctx.Tr("install.save_config_failed", err), tplInstall, &form)
|
ctx.RenderWithErr(ctx.Tr("install.save_config_failed", err), tplInstall, &form)
|
||||||
return
|
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)
|
ctx.RenderWithErr(ctx.Tr("install.save_config_failed", err), tplInstall, &form)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
package auth
|
package auth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/subtle"
|
||||||
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -30,6 +32,7 @@ import (
|
||||||
"code.gitea.io/gitea/services/externalaccount"
|
"code.gitea.io/gitea/services/externalaccount"
|
||||||
"code.gitea.io/gitea/services/forms"
|
"code.gitea.io/gitea/services/forms"
|
||||||
"code.gitea.io/gitea/services/mailer"
|
"code.gitea.io/gitea/services/mailer"
|
||||||
|
notify_service "code.gitea.io/gitea/services/notify"
|
||||||
|
|
||||||
"github.com/markbates/goth"
|
"github.com/markbates/goth"
|
||||||
)
|
)
|
||||||
|
@ -49,21 +52,47 @@ func AutoSignIn(ctx *context.Context) (bool, error) {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
uname := ctx.GetSiteCookie(setting.CookieUserName)
|
authCookie := ctx.GetSiteCookie(setting.CookieRememberName)
|
||||||
if len(uname) == 0 {
|
if len(authCookie) == 0 {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
isSucceed := false
|
isSucceed := false
|
||||||
defer func() {
|
defer func() {
|
||||||
if !isSucceed {
|
if !isSucceed {
|
||||||
log.Trace("auto-login cookie cleared: %s", uname)
|
log.Trace("Auto login cookie is cleared: %s", authCookie)
|
||||||
ctx.DeleteSiteCookie(setting.CookieUserName)
|
|
||||||
ctx.DeleteSiteCookie(setting.CookieRememberName)
|
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 err != nil {
|
||||||
if !user_model.IsErrUserNotExist(err) {
|
if !user_model.IsErrUserNotExist(err) {
|
||||||
return false, fmt.Errorf("GetUserByName: %w", err)
|
return false, fmt.Errorf("GetUserByName: %w", err)
|
||||||
|
@ -71,17 +100,11 @@ func AutoSignIn(ctx *context.Context) (bool, error) {
|
||||||
return false, nil
|
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
|
isSucceed = true
|
||||||
|
|
||||||
if err := updateSession(ctx, nil, map[string]any{
|
if err := updateSession(ctx, nil, map[string]any{
|
||||||
// Set session IDs
|
// Set session IDs
|
||||||
"uid": u.ID,
|
"uid": authToken.UID,
|
||||||
"uname": u.Name,
|
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return false, fmt.Errorf("unable to updateSession: %w", err)
|
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 {
|
func handleSignInFull(ctx *context.Context, u *user_model.User, remember, obeyRedirect bool) string {
|
||||||
if remember {
|
if remember {
|
||||||
days := 86400 * setting.LogInRememberDays
|
if err := ctx.SetLTACookie(u); err != nil {
|
||||||
ctx.SetSiteCookie(setting.CookieUserName, u.Name, days)
|
ctx.ServerError("GenerateAuthToken", err)
|
||||||
ctx.SetSuperSecureCookie(base.EncodeMD5(u.Rands+u.Passwd),
|
return setting.AppSubURL + "/"
|
||||||
setting.CookieRememberName, u.Name, days)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := updateSession(ctx, []string{
|
if err := updateSession(ctx, []string{
|
||||||
|
@ -307,7 +330,6 @@ func handleSignInFull(ctx *context.Context, u *user_model.User, remember, obeyRe
|
||||||
"linkAccount",
|
"linkAccount",
|
||||||
}, map[string]any{
|
}, map[string]any{
|
||||||
"uid": u.ID,
|
"uid": u.ID,
|
||||||
"uname": u.Name,
|
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
ctx.ServerError("RegenerateSession", err)
|
ctx.ServerError("RegenerateSession", err)
|
||||||
return setting.AppSubURL + "/"
|
return setting.AppSubURL + "/"
|
||||||
|
@ -368,7 +390,6 @@ func getUserName(gothUser *goth.User) string {
|
||||||
func HandleSignOut(ctx *context.Context) {
|
func HandleSignOut(ctx *context.Context) {
|
||||||
_ = ctx.Session.Flush()
|
_ = ctx.Session.Flush()
|
||||||
_ = ctx.Session.Destroy(ctx.Resp, ctx.Req)
|
_ = ctx.Session.Destroy(ctx.Resp, ctx.Req)
|
||||||
ctx.DeleteSiteCookie(setting.CookieUserName)
|
|
||||||
ctx.DeleteSiteCookie(setting.CookieRememberName)
|
ctx.DeleteSiteCookie(setting.CookieRememberName)
|
||||||
ctx.Csrf.DeleteCookie(ctx)
|
ctx.Csrf.DeleteCookie(ctx)
|
||||||
middleware.DeleteRedirectToCookie(ctx.Resp)
|
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
|
// update external user information
|
||||||
if gothUser != nil {
|
if gothUser != nil {
|
||||||
if err := externalaccount.UpdateExternalUser(u, *gothUser); err != 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["Email"] = u.Email
|
||||||
ctx.Data["ActiveCodeLives"] = timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, ctx.Locale)
|
ctx.Data["ActiveCodeLives"] = timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, ctx.Locale)
|
||||||
ctx.HTML(http.StatusOK, TplActivate)
|
ctx.HTML(http.StatusOK, TplActivate)
|
||||||
|
|
||||||
if setting.CacheService.Enabled {
|
if setting.CacheService.Enabled {
|
||||||
if err := ctx.Cache.Put("MailResendLimit_"+u.LowerName, u.LowerName, 180); err != nil {
|
if err := ctx.Cache.Put("MailResendLimit_"+u.LowerName, u.LowerName, 180); err != nil {
|
||||||
log.Error("Set cache(MailResendLimit) fail: %v", err)
|
log.Error("Set cache(MailResendLimit) fail: %v", err)
|
||||||
|
@ -732,7 +753,6 @@ func handleAccountActivation(ctx *context.Context, user *user_model.User) {
|
||||||
|
|
||||||
if err := updateSession(ctx, nil, map[string]any{
|
if err := updateSession(ctx, nil, map[string]any{
|
||||||
"uid": user.ID,
|
"uid": user.ID,
|
||||||
"uname": user.Name,
|
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
log.Error("Unable to regenerate session for user: %-v with email: %s: %v", user, user.Email, err)
|
log.Error("Unable to regenerate session for user: %-v with email: %s: %v", user, user.Email, err)
|
||||||
ctx.ServerError("ActivateUserEmail", err)
|
ctx.ServerError("ActivateUserEmail", err)
|
||||||
|
|
|
@ -1119,7 +1119,6 @@ func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model
|
||||||
if !needs2FA {
|
if !needs2FA {
|
||||||
if err := updateSession(ctx, nil, map[string]any{
|
if err := updateSession(ctx, nil, map[string]any{
|
||||||
"uid": u.ID,
|
"uid": u.ID,
|
||||||
"uname": u.Name,
|
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
ctx.ServerError("updateSession", err)
|
ctx.ServerError("updateSession", err)
|
||||||
return
|
return
|
||||||
|
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
"github.com/gorilla/feeds"
|
"github.com/gorilla/feeds"
|
||||||
|
"github.com/jaytaylor/html2text"
|
||||||
)
|
)
|
||||||
|
|
||||||
func toBranchLink(ctx *context.Context, act *activities_model.Action) string {
|
func toBranchLink(ctx *context.Context, act *activities_model.Action) string {
|
||||||
|
@ -239,8 +240,15 @@ func feedActionsToFeedItems(ctx *context.Context, actions activities_model.Actio
|
||||||
content = desc
|
content = desc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// It's a common practice for feed generators to use plain text titles.
|
||||||
|
// See https://codeberg.org/forgejo/forgejo/pulls/1595
|
||||||
|
plainTitle, err := html2text.FromString(title, html2text.Options{OmitLinks: true})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
items = append(items, &feeds.Item{
|
items = append(items, &feeds.Item{
|
||||||
Title: title,
|
Title: plainTitle,
|
||||||
Link: link,
|
Link: link,
|
||||||
Description: desc,
|
Description: desc,
|
||||||
Author: &feeds.Author{
|
Author: &feeds.Author{
|
||||||
|
|
|
@ -8,11 +8,12 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// RenderBranchFeed render format for branch or file
|
// RenderBranchFeed render format for branch or file
|
||||||
func RenderBranchFeed(ctx *context.Context) {
|
func RenderBranchFeed(feedType string) func(ctx *context.Context) {
|
||||||
_, _, showFeedType := GetFeedType(ctx.Params(":reponame"), ctx.Req)
|
return func(ctx *context.Context) {
|
||||||
if ctx.Repo.TreePath == "" {
|
if ctx.Repo.TreePath == "" {
|
||||||
ShowBranchFeed(ctx, ctx.Repo.Repository, showFeedType)
|
ShowBranchFeed(ctx, ctx.Repo.Repository, feedType)
|
||||||
} else {
|
} else {
|
||||||
ShowFileFeed(ctx, ctx.Repo.Repository, showFeedType)
|
ShowFileFeed(ctx, ctx.Repo.Repository, feedType)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,8 +54,7 @@ func Home(ctx *context.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check auto-login.
|
// Check auto-login.
|
||||||
uname := ctx.GetSiteCookie(setting.CookieUserName)
|
if len(ctx.GetSiteCookie(setting.CookieRememberName)) != 0 {
|
||||||
if len(uname) != 0 {
|
|
||||||
ctx.Redirect(setting.AppSubURL + "/user/login")
|
ctx.Redirect(setting.AppSubURL + "/user/login")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -239,6 +239,22 @@ func FileHistory(ctx *context.Context) {
|
||||||
ctx.ServerError("CommitsByFileAndRange", err)
|
ctx.ServerError("CommitsByFileAndRange", err)
|
||||||
return
|
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["Commits"] = git_model.ConvertFromGitCommit(ctx, commits, ctx.Repo.Repository)
|
||||||
|
|
||||||
ctx.Data["Username"] = ctx.Repo.Owner.Name
|
ctx.Data["Username"] = ctx.Repo.Owner.Name
|
||||||
|
|
|
@ -387,7 +387,9 @@ func NewReleasePost(ctx *context.Context) {
|
||||||
return
|
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)
|
ctx.RenderWithErr(ctx.Tr("form.target_branch_not_exist"), tplReleaseNew, &form)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -165,7 +165,7 @@ func renderDirectory(ctx *context.Context, treeLink string) {
|
||||||
|
|
||||||
if ctx.Repo.TreePath != "" {
|
if ctx.Repo.TreePath != "" {
|
||||||
ctx.Data["HideRepoInfo"] = true
|
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)
|
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()
|
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["FileIsSymlink"] = entry.IsLink()
|
||||||
ctx.Data["FileName"] = blob.Name()
|
ctx.Data["FileName"] = blob.Name()
|
||||||
ctx.Data["RawFileLink"] = rawLink + "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
|
ctx.Data["RawFileLink"] = rawLink + "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
|
||||||
|
@ -726,12 +726,19 @@ func Home(ctx *context.Context) {
|
||||||
if setting.Other.EnableFeed {
|
if setting.Other.EnableFeed {
|
||||||
isFeed, _, showFeedType := feed.GetFeedType(ctx.Params(":reponame"), ctx.Req)
|
isFeed, _, showFeedType := feed.GetFeedType(ctx.Params(":reponame"), ctx.Req)
|
||||||
if isFeed {
|
if isFeed {
|
||||||
switch {
|
if ctx.Link == fmt.Sprintf("%s.%s", ctx.Repo.RepoLink, showFeedType) {
|
||||||
case ctx.Link == fmt.Sprintf("%s.%s", ctx.Repo.RepoLink, showFeedType):
|
|
||||||
feed.ShowRepoFeed(ctx, ctx.Repo.Repository, showFeedType)
|
feed.ShowRepoFeed(ctx, ctx.Repo.Repository, showFeedType)
|
||||||
case ctx.Repo.TreePath == "":
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.Repo.Repository.IsEmpty {
|
||||||
|
ctx.NotFound("MustBeNotEmpty", nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.Repo.TreePath == "" {
|
||||||
feed.ShowBranchFeed(ctx, ctx.Repo.Repository, showFeedType)
|
feed.ShowBranchFeed(ctx, ctx.Repo.Repository, showFeedType)
|
||||||
case ctx.Repo.TreePath != "":
|
} else {
|
||||||
feed.ShowFileFeed(ctx, ctx.Repo.Repository, showFeedType)
|
feed.ShowFileFeed(ctx, ctx.Repo.Repository, showFeedType)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|
|
@ -78,6 +78,15 @@ func AccountPost(ctx *context.Context) {
|
||||||
ctx.ServerError("UpdateUser", err)
|
ctx.ServerError("UpdateUser", err)
|
||||||
return
|
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)
|
log.Trace("User password updated: %s", ctx.Doer.Name)
|
||||||
ctx.Flash.Success(ctx.Tr("settings.change_password_success"))
|
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
|
_ "code.gitea.io/gitea/modules/session" // to registers all internal adapters
|
||||||
|
|
||||||
"gitea.com/go-chi/captcha"
|
"gitea.com/go-chi/captcha"
|
||||||
"github.com/NYTimes/gziphandler"
|
|
||||||
chi_middleware "github.com/go-chi/chi/v5/middleware"
|
chi_middleware "github.com/go-chi/chi/v5/middleware"
|
||||||
"github.com/go-chi/cors"
|
"github.com/go-chi/cors"
|
||||||
|
"github.com/klauspost/compress/gzhttp"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"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
|
// CorsHandler return a http handler who set CORS options if enabled by config
|
||||||
func CorsHandler() func(next http.Handler) http.Handler {
|
func CorsHandler() func(next http.Handler) http.Handler {
|
||||||
if setting.CORSConfig.Enabled {
|
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.
|
// Redirect to log in page if auto-signin info is provided and has not signed in.
|
||||||
if !options.SignOutRequired && !ctx.IsSigned &&
|
if !options.SignOutRequired && !ctx.IsSigned &&
|
||||||
len(ctx.GetSiteCookie(setting.CookieUserName)) > 0 {
|
len(ctx.GetSiteCookie(setting.CookieRememberName)) > 0 {
|
||||||
if ctx.Req.URL.Path != "/user/events" {
|
if ctx.Req.URL.Path != "/user/events" {
|
||||||
middleware.SetRedirectToCookie(ctx.Resp, setting.AppSubURL+ctx.Req.URL.RequestURI())
|
middleware.SetRedirectToCookie(ctx.Resp, setting.AppSubURL+ctx.Req.URL.RequestURI())
|
||||||
}
|
}
|
||||||
|
@ -229,11 +224,11 @@ func Routes() *web.Route {
|
||||||
var mid []any
|
var mid []any
|
||||||
|
|
||||||
if setting.EnableGzip {
|
if setting.EnableGzip {
|
||||||
h, err := gziphandler.GzipHandlerWithOpts(gziphandler.MinSize(GzipMinSize))
|
wrapper, err := gzhttp.NewWrapper(gzhttp.RandomJitter(32, 0, false))
|
||||||
if err != nil {
|
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 {
|
if setting.Service.EnableCaptcha {
|
||||||
|
@ -1477,8 +1472,8 @@ func registerRoutes(m *web.Route) {
|
||||||
m.Get("/cherry-pick/{sha:([a-f0-9]{7,40})$}", repo.SetEditorconfigIfExists, repo.CherryPick)
|
m.Get("/cherry-pick/{sha:([a-f0-9]{7,40})$}", repo.SetEditorconfigIfExists, repo.CherryPick)
|
||||||
}, repo.MustBeNotEmpty, context.RepoRef(), reqRepoCodeReader)
|
}, repo.MustBeNotEmpty, context.RepoRef(), reqRepoCodeReader)
|
||||||
|
|
||||||
m.Get("/rss/branch/*", context.RepoRefByType(context.RepoRefBranch), feedEnabled, feed.RenderBranchFeed)
|
m.Get("/rss/branch/*", repo.MustBeNotEmpty, context.RepoRefByType(context.RepoRefBranch), feedEnabled, feed.RenderBranchFeed("rss"))
|
||||||
m.Get("/atom/branch/*", context.RepoRefByType(context.RepoRefBranch), feedEnabled, feed.RenderBranchFeed)
|
m.Get("/atom/branch/*", repo.MustBeNotEmpty, context.RepoRefByType(context.RepoRefBranch), feedEnabled, feed.RenderBranchFeed("atom"))
|
||||||
|
|
||||||
m.Group("/src", func() {
|
m.Group("/src", func() {
|
||||||
m.Get("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.Home)
|
m.Get("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.Home)
|
||||||
|
|
|
@ -76,10 +76,6 @@ func handleSignIn(resp http.ResponseWriter, req *http.Request, sess SessionStore
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(fmt.Sprintf("Error setting session: %v", err))
|
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
|
// Language setting of the user overwrites the one previously set
|
||||||
// If the user does not have a locale set, we save the current one.
|
// If the user does not have a locale set, we save the current one.
|
||||||
|
|
|
@ -365,7 +365,7 @@ func (f *EditVariableForm) Validate(req *http.Request, errs binding.Errors) bind
|
||||||
|
|
||||||
// NewAccessTokenForm form for creating access token
|
// NewAccessTokenForm form for creating access token
|
||||||
type NewAccessTokenForm struct {
|
type NewAccessTokenForm struct {
|
||||||
Name string `binding:"Required;MaxSize(255)"`
|
Name string `binding:"Required;MaxSize(255)" locale:"settings.token_name"`
|
||||||
Scope []string
|
Scope []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ package lfs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
stdCtx "context"
|
stdCtx "context"
|
||||||
|
"crypto/sha256"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
|
@ -33,7 +34,6 @@ import (
|
||||||
"code.gitea.io/gitea/modules/storage"
|
"code.gitea.io/gitea/modules/storage"
|
||||||
|
|
||||||
"github.com/golang-jwt/jwt/v5"
|
"github.com/golang-jwt/jwt/v5"
|
||||||
"github.com/minio/sha256-simd"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// requestContext contain variables from the HTTP request.
|
// 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 = SendAsync
|
||||||
|
|
||||||
|
// 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()
|
||||||
|
oldSendAsync := sa
|
||||||
|
defer func() {
|
||||||
|
sa = oldSendAsync
|
||||||
|
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)
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
crypto_hmac "crypto/hmac"
|
crypto_hmac "crypto/hmac"
|
||||||
|
"crypto/sha256"
|
||||||
"encoding/base32"
|
"encoding/base32"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
"github.com/minio/sha256-simd"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// A token is a verifiable container describing an action.
|
// 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 {
|
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
|
line := comment.Line
|
||||||
if line != 0 {
|
if line != 0 {
|
||||||
comment.Position = 1
|
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)
|
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)
|
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)
|
NewRelease(ctx context.Context, rel *repo_model.Release)
|
||||||
UpdateRelease(ctx context.Context, doer *user_model.User, 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)
|
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
|
// PackageCreate notifies creation of a package to notifiers
|
||||||
func PackageCreate(ctx context.Context, doer *user_model.User, pd *packages_model.PackageDescriptor) {
|
func PackageCreate(ctx context.Context, doer *user_model.User, pd *packages_model.PackageDescriptor) {
|
||||||
for _, notifier := range notifiers {
|
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) 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
|
// PackageCreate places a place holder function
|
||||||
func (*NullNotifier) PackageCreate(ctx context.Context, doer *user_model.User, pd *packages_model.PackageDescriptor) {
|
func (*NullNotifier) PackageCreate(ctx context.Context, doer *user_model.User, pd *packages_model.PackageDescriptor) {
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/hmac"
|
"crypto/hmac"
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
|
"crypto/sha256"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -29,7 +30,6 @@ import (
|
||||||
webhook_module "code.gitea.io/gitea/modules/webhook"
|
webhook_module "code.gitea.io/gitea/modules/webhook"
|
||||||
|
|
||||||
"github.com/gobwas/glob"
|
"github.com/gobwas/glob"
|
||||||
"github.com/minio/sha256-simd"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Deliver deliver hook task
|
// Deliver deliver hook task
|
||||||
|
|
|
@ -159,6 +159,8 @@
|
||||||
<dd>{{if .Service.DefaultKeepEmailPrivate}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
|
<dd>{{if .Service.DefaultKeepEmailPrivate}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
|
||||||
<dt>{{ctx.Locale.Tr "admin.config.default_allow_create_organization"}}</dt>
|
<dt>{{ctx.Locale.Tr "admin.config.default_allow_create_organization"}}</dt>
|
||||||
<dd>{{if .Service.DefaultAllowCreateOrganization}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
|
<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>
|
<dt>{{ctx.Locale.Tr "admin.config.enable_timetracking"}}</dt>
|
||||||
<dd>{{if .Service.EnableTimetracking}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
|
<dd>{{if .Service.EnableTimetracking}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
|
||||||
{{if .Service.EnableTimetracking}}
|
{{if .Service.EnableTimetracking}}
|
||||||
|
|
|
@ -2,7 +2,8 @@
|
||||||
<html lang="{{ctx.Locale.Lang}}" class="theme-{{if .SignedUser.Theme}}{{.SignedUser.Theme}}{{else}}{{DefaultTheme}}{{end}}">
|
<html lang="{{ctx.Locale.Lang}}" class="theme-{{if .SignedUser.Theme}}{{.SignedUser.Theme}}{{else}}{{DefaultTheme}}{{end}}">
|
||||||
<head>
|
<head>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<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}}
|
{{if .ManifestData}}<link rel="manifest" href="data:{{.ManifestData}}">{{end}}
|
||||||
<meta name="author" content="{{if .Repository}}{{.Owner.Name}}{{else}}{{MetaAuthor}}{{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}}">
|
<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>{{ctx.Locale.Tr "mail.admin.new_user.user_info"}}</h3>
|
||||||
|
<li>{{ctx.Locale.Tr "admin.users.created"}}: {{DateTime "full" .NewUser.LastLoginUnix}}</li>
|
||||||
|
<li>{{ctx.Locale.Tr "admin.users.last_login"}}: {{DateTime "full" .NewUser.CreatedUnix}}</li>
|
||||||
|
</ul>
|
||||||
|
<p> {{.Body | Str2html}} </p>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -13,6 +13,11 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{template "repo/commits_table" .}}
|
{{template "repo/commits_table" .}}
|
||||||
|
{{if .OldFilename}}
|
||||||
|
<div class="ui bottom attached header">
|
||||||
|
<span>{{ctx.Locale.Tr "repo.commits.renamed_from" .OldFilename}} (<a href="{{.OldFilenameHistory}}">{{ctx.Locale.Tr "repo.commits.browse_further"}}</a>)</span>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{template "base/footer" .}}
|
{{template "base/footer" .}}
|
||||||
|
|
|
@ -33,7 +33,7 @@
|
||||||
</div>
|
</div>
|
||||||
{{if .MilestoneID}}
|
{{if .MilestoneID}}
|
||||||
<div class="meta gt-my-2">
|
<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"}}
|
{{svg "octicon-milestone" 16 "gt-mr-2 gt-vm"}}
|
||||||
<span class="gt-vm">{{.Milestone.Name}}</span>
|
<span class="gt-vm">{{.Milestone.Name}}</span>
|
||||||
</a>
|
</a>
|
||||||
|
@ -42,7 +42,7 @@
|
||||||
{{if $.Page.LinkedPRs}}
|
{{if $.Page.LinkedPRs}}
|
||||||
{{range index $.Page.LinkedPRs .ID}}
|
{{range index $.Page.LinkedPRs .ID}}
|
||||||
<div class="meta gt-my-2">
|
<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-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>
|
<span class="gt-vm">{{.Title}} <span class="text light grey">#{{.Index}}</span></span>
|
||||||
</a>
|
</a>
|
||||||
|
@ -54,7 +54,7 @@
|
||||||
{{if or .Labels .Assignees}}
|
{{if or .Labels .Assignees}}
|
||||||
<div class="extra content labels-list gt-p-0 gt-pt-2">
|
<div class="extra content labels-list gt-p-0 gt-pt-2">
|
||||||
{{range .Labels}}
|
{{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}}
|
{{end}}
|
||||||
<div class="right floated">
|
<div class="right floated">
|
||||||
{{range .Assignees}}
|
{{range .Assignees}}
|
||||||
|
|
|
@ -363,7 +363,7 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
{{else if eq .Type 22}}
|
{{else if eq .Type 22}}
|
||||||
<div class="timeline-item-group">
|
<div class="timeline-item-group" id="{{.HashTag}}">
|
||||||
<div class="timeline-item event">
|
<div class="timeline-item event">
|
||||||
{{if .OriginalAuthor}}
|
{{if .OriginalAuthor}}
|
||||||
{{else}}
|
{{else}}
|
||||||
|
@ -402,7 +402,7 @@
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{{if or .Content .Attachments}}
|
{{if or .Content .Attachments}}
|
||||||
<div class="timeline-item comment" id="{{.HashTag}}">
|
<div class="timeline-item comment">
|
||||||
<div class="content comment-container">
|
<div class="content comment-container">
|
||||||
<div class="ui top attached header comment-header gt-df gt-ac gt-sb">
|
<div class="ui top attached header comment-header gt-df gt-ac gt-sb">
|
||||||
<div class="comment-header-left gt-df gt-ac">
|
<div class="comment-header-left gt-df gt-ac">
|
||||||
|
|
|
@ -976,21 +976,24 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<p>
|
<div class="ui warning message">
|
||||||
{{if .Repository.IsArchived}}
|
{{if .Repository.IsArchived}}
|
||||||
{{ctx.Locale.Tr "repo.settings.unarchive.text"}}
|
{{ctx.Locale.Tr "repo.settings.unarchive.text"}}
|
||||||
{{else}}
|
{{else}}
|
||||||
{{ctx.Locale.Tr "repo.settings.archive.text"}}
|
{{ctx.Locale.Tr "repo.settings.archive.text"}}
|
||||||
{{end}}
|
{{end}}
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<form action="{{.Link}}" method="post">
|
<form action="{{.Link}}" method="post">
|
||||||
{{.CsrfTokenHtml}}
|
{{.CsrfTokenHtml}}
|
||||||
<input type="hidden" name="action" value="{{if .Repository.IsArchived}}unarchive{{else}}archive{{end}}">
|
<input type="hidden" name="action" value="{{if .Repository.IsArchived}}unarchive{{else}}archive{{end}}">
|
||||||
<input type="hidden" name="repo_id" value="{{.Repository.ID}}">
|
<input type="hidden" name="repo_id" value="{{.Repository.ID}}">
|
||||||
{{template "base/modal_actions_confirm" .}}
|
<div class="text right actions">
|
||||||
|
<button class="ui cancel button">{{ctx.Locale.Tr "settings.cancel"}}</button>
|
||||||
|
<button class="ui red button">{{ctx.Locale.Tr "repo.settings.archive.button"}}</button>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{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
|
|
@ -189,6 +189,43 @@ func TestAPIGetComment(t *testing.T) {
|
||||||
assert.Equal(t, expect.Created.Unix(), apiComment.Created.Unix())
|
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) {
|
func TestAPIEditComment(t *testing.T) {
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrepareTestEnv(t)()
|
||||||
const newCommentBody = "This is the new comment body"
|
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