From 36ccb8829b2eb1034032c80195a556f8044100e6 Mon Sep 17 00:00:00 2001 From: Wim Date: Sat, 18 Jun 2022 20:29:29 +0200 Subject: [PATCH 001/246] Return 404 when tag is broken (#20024) Fixes #19979 --- modules/context/repo.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/context/repo.go b/modules/context/repo.go index 8e75ad07d5..217cbd3dfc 100644 --- a/modules/context/repo.go +++ b/modules/context/repo.go @@ -942,6 +942,10 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetTagCommit(refName) if err != nil { + if git.IsErrNotExist(err) { + ctx.NotFound("GetTagCommit", err) + return + } ctx.ServerError("GetTagCommit", err) return } From a180d945ebe79626482fc71a0930a8ac145c01c5 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sun, 19 Jun 2022 04:05:31 +0200 Subject: [PATCH 002/246] Dump should only copy regular files and symlink regular files (#20015) (#20021) Co-authored-by: wxiaoguang --- cmd/dump.go | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/cmd/dump.go b/cmd/dump.go index ea41c0c029..d807cb0587 100644 --- a/cmd/dump.go +++ b/cmd/dump.go @@ -22,7 +22,7 @@ import ( "code.gitea.io/gitea/modules/util" "gitea.com/go-chi/session" - archiver "github.com/mholt/archiver/v3" + "github.com/mholt/archiver/v3" "github.com/urfave/cli" ) @@ -439,8 +439,23 @@ func addRecursiveExclude(w archiver.Writer, insidePath, absPath string, excludeA } } } else { - if err = addFile(w, currentInsidePath, currentAbsPath, verbose); err != nil { - return err + // only copy regular files and symlink regular files, skip non-regular files like socket/pipe/... + shouldAdd := file.Mode().IsRegular() + if !shouldAdd && file.Mode()&os.ModeSymlink == os.ModeSymlink { + target, err := filepath.EvalSymlinks(currentAbsPath) + if err != nil { + return err + } + targetStat, err := os.Stat(target) + if err != nil { + return err + } + shouldAdd = targetStat.Mode().IsRegular() + } + if shouldAdd { + if err = addFile(w, currentInsidePath, currentAbsPath, verbose); err != nil { + return err + } } } } From 8733f4b25a7bb7c2d9ca1f77ea4ca6386555465a Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Sun, 19 Jun 2022 19:55:59 +0800 Subject: [PATCH 003/246] use quoted regexp instead of git fixed-value (#20030) Backport #20029 --- modules/git/git.go | 5 +++-- modules/git/git_test.go | 6 ++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/modules/git/git.go b/modules/git/git.go index 3a3663995b..0652a75f9a 100644 --- a/modules/git/git.go +++ b/modules/git/git.go @@ -11,6 +11,7 @@ import ( "fmt" "os" "os/exec" + "regexp" "runtime" "strings" "sync" @@ -337,7 +338,7 @@ func configSetNonExist(key, value string) error { } func configAddNonExist(key, value string) error { - _, _, err := NewCommand(DefaultContext, "config", "--fixed-value", "--get", key, value).RunStdString(nil) + _, _, err := NewCommand(DefaultContext, "config", "--get", key, regexp.QuoteMeta(value)).RunStdString(nil) if err == nil { // already exist return nil @@ -357,7 +358,7 @@ func configUnsetAll(key, value string) error { _, _, err := NewCommand(DefaultContext, "config", "--get", key).RunStdString(nil) if err == nil { // exist, need to remove - _, _, err = NewCommand(DefaultContext, "config", "--global", "--fixed-value", "--unset-all", key, value).RunStdString(nil) + _, _, err = NewCommand(DefaultContext, "config", "--global", "--unset-all", key, regexp.QuoteMeta(value)).RunStdString(nil) if err != nil { return fmt.Errorf("failed to unset git global config %s, err: %w", key, err) } diff --git a/modules/git/git_test.go b/modules/git/git_test.go index 061c876cde..c1a9ec351a 100644 --- a/modules/git/git_test.go +++ b/modules/git/git_test.go @@ -78,4 +78,10 @@ func TestGitConfig(t *testing.T) { assert.NoError(t, configUnsetAll("test.key-b", "val-2b")) assert.False(t, gitConfigContains("key-b = val-2b")) + + assert.NoError(t, configSet("test.key-x", "*")) + assert.True(t, gitConfigContains("key-x = *")) + assert.NoError(t, configSetNonExist("test.key-x", "*")) + assert.NoError(t, configUnsetAll("test.key-x", "*")) + assert.False(t, gitConfigContains("key-x = *")) } From a4b1967ea31a5d957775fda8a18a020e77a55c15 Mon Sep 17 00:00:00 2001 From: a1012112796 <1012112796@qq.com> Date: Sun, 19 Jun 2022 21:37:14 +0800 Subject: [PATCH 004/246] Fix delete pull head ref for DeleteIssue (#20032) (#20034) Backport #20032 In DeleteIssue the PR git head reference should be `/refs/pull/xxx/head` not `/refs/pull/xxx` Fix #19655 Signed-off-by: a1012112796 <1012112796@qq.com> --- routers/web/repo/issue.go | 5 +++++ services/issue/issue.go | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index 11d2daeeff..5b72ff79af 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -878,6 +878,11 @@ func DeleteIssue(ctx *context.Context) { return } + if issue.IsPull { + ctx.Redirect(fmt.Sprintf("%s/pulls", ctx.Repo.Repository.HTMLURL()), http.StatusSeeOther) + return + } + ctx.Redirect(fmt.Sprintf("%s/issues", ctx.Repo.Repository.HTMLURL()), http.StatusSeeOther) } diff --git a/services/issue/issue.go b/services/issue/issue.go index ded281e209..467bc14b84 100644 --- a/services/issue/issue.go +++ b/services/issue/issue.go @@ -149,7 +149,7 @@ func DeleteIssue(doer *user_model.User, gitRepo *git.Repository, issue *issues_m // delete pull request related git data if issue.IsPull { - if err := gitRepo.RemoveReference(fmt.Sprintf("%s%d", git.PullPrefix, issue.PullRequest.Index)); err != nil { + if err := gitRepo.RemoveReference(fmt.Sprintf("%s%d/head", git.PullPrefix, issue.PullRequest.Index)); err != nil { return err } } From 2a48833f9382a531f849195503b2e27fd35d7524 Mon Sep 17 00:00:00 2001 From: Wim Date: Sun, 19 Jun 2022 19:02:18 +0200 Subject: [PATCH 005/246] Respond with a 401 on git push when password isn't changed yet (#20027) Fixes #19090 If the user-agent starts with git and user must change password but hasn't return a 401 with the message. It must be a 401, git doesn't seem to show the contents of the error message when we return a 403 Co-authored-by: 6543 <6543@obermui.de> --- modules/context/auth.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/context/auth.go b/modules/context/auth.go index 09c2295455..e6d882eb5b 100644 --- a/modules/context/auth.go +++ b/modules/context/auth.go @@ -7,6 +7,7 @@ package context import ( "net/http" + "strings" "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/modules/log" @@ -41,6 +42,10 @@ func Toggle(options *ToggleOptions) func(ctx *Context) { if ctx.Doer.MustChangePassword { if ctx.Req.URL.Path != "/user/settings/change_password" { + if strings.HasPrefix(ctx.Req.UserAgent(), "git") { + ctx.Error(http.StatusUnauthorized, ctx.Tr("auth.must_change_password")) + return + } ctx.Data["Title"] = ctx.Tr("auth.must_change_password") ctx.Data["ChangePasscodeLink"] = setting.AppSubURL + "/user/change_password" if ctx.Req.URL.Path != "/user/events" { From ab9fcb0cf48618f5cad4748b0f0c5e50f8194b71 Mon Sep 17 00:00:00 2001 From: zeripath Date: Sun, 19 Jun 2022 19:41:12 +0100 Subject: [PATCH 006/246] Backtick table name in generic orphan check (#20019) (#20037) Backport #20019 - Resolves #20018 --- modules/doctor/dbconsistency.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/doctor/dbconsistency.go b/modules/doctor/dbconsistency.go index 349a2121cf..f8b62e898a 100644 --- a/modules/doctor/dbconsistency.go +++ b/modules/doctor/dbconsistency.go @@ -201,10 +201,10 @@ func checkDBConsistency(ctx context.Context, logger log.Logger, autofix bool) er "oauth2_authorization_code", "oauth2_grant", "oauth2_authorization_code.grant_id=oauth2_grant.id"), // find stopwatches without existing user genericOrphanCheck("Orphaned Stopwatches without existing User", - "stopwatch", "user", "stopwatch.user_id=user.id"), + "stopwatch", "user", "stopwatch.user_id=`user`.id"), // find stopwatches without existing issue genericOrphanCheck("Orphaned Stopwatches without existing Issue", - "stopwatch", "issue", "stopwatch.issue_id=issue.id"), + "stopwatch", "issue", "stopwatch.issue_id=`issue`.id"), ) for _, c := range consistencyChecks { From 1823bfde7caa216926b2c28d8e7a00d495906e7c Mon Sep 17 00:00:00 2001 From: Wim Date: Sun, 19 Jun 2022 22:12:48 +0200 Subject: [PATCH 007/246] Alter hook_task TEXT fields to LONGTEXT (#20038) (#20041) Mysql TEXT has a limit of 64KB, change this to LONGTEXT in mysql only so we can have bigger hook payloads. Postgresql has unlimited TEXT - https://www.postgresql.org/docs/current/datatype-character.html Sqlite has unlimited TEXT - https://www.sqlitetutorial.net/sqlite-data-types/#:~:text=The%20maximum%20length%20of%20TEXT,SQLite%20supports%20various%20character%20encodings. Backport of #20038 Co-authored-by: zeripath --- models/migrations/migrations.go | 2 ++ models/migrations/v217.go | 26 ++++++++++++++++++++++++++ models/webhook/hooktask.go | 6 +++--- 3 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 models/migrations/v217.go diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index c843e33eb7..edd4beb451 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -389,6 +389,8 @@ var migrations = []Migration{ NewMigration("allow to view files in PRs", addReviewViewedFiles), // v216 -> v217 NewMigration("Improve Action table indices", improveActionTableIndices), + // v217 -> v218 + NewMigration("Alter hook_task table TEXT fields to LONGTEXT", alterHookTaskTextFieldsToLongText), } // GetCurrentDBVersion returns the current db version diff --git a/models/migrations/v217.go b/models/migrations/v217.go new file mode 100644 index 0000000000..280dba17a9 --- /dev/null +++ b/models/migrations/v217.go @@ -0,0 +1,26 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package migrations + +import ( + "code.gitea.io/gitea/modules/setting" + + "xorm.io/xorm" +) + +func alterHookTaskTextFieldsToLongText(x *xorm.Engine) error { + sess := x.NewSession() + defer sess.Close() + if err := sess.Begin(); err != nil { + return err + } + + if setting.Database.UseMySQL { + if _, err := sess.Exec("ALTER TABLE `hook_task` CHANGE `payload_content` `payload_content` LONGTEXT, CHANGE `request_content` `request_content` LONGTEXT, change `response_content` `response_content` LONGTEXT"); err != nil { + return err + } + } + return sess.Commit() +} diff --git a/models/webhook/hooktask.go b/models/webhook/hooktask.go index c71b18f662..aff94fb38c 100644 --- a/models/webhook/hooktask.go +++ b/models/webhook/hooktask.go @@ -105,7 +105,7 @@ type HookTask struct { HookID int64 UUID string api.Payloader `xorm:"-"` - PayloadContent string `xorm:"TEXT"` + PayloadContent string `xorm:"LONGTEXT"` EventType HookEventType IsDelivered bool Delivered int64 @@ -113,9 +113,9 @@ type HookTask struct { // History info. IsSucceed bool - RequestContent string `xorm:"TEXT"` + RequestContent string `xorm:"LONGTEXT"` RequestInfo *HookRequest `xorm:"-"` - ResponseContent string `xorm:"TEXT"` + ResponseContent string `xorm:"LONGTEXT"` ResponseInfo *HookResponse `xorm:"-"` } From 2dc657108525194d18524f86d71f3e0d98d5654e Mon Sep 17 00:00:00 2001 From: zeripath Date: Mon, 20 Jun 2022 02:34:54 +0100 Subject: [PATCH 008/246] Simplify and fix migration 216 (#20036) There appears to be a strange bug whereby the comment_id index can sometimes be missed or missing from the action table despite the sync2 that should create it in the earlier part of this migration. However, looking through the code for Sync2 there is no need for this pre-code to exist and Sync2 should drop/create the indices as necessary. I think therefore we should simplify the migration to simply be Sync2. Signed-off-by: Andrew Thornton Co-authored-by: 6543 <6543@obermui.de> --- models/migrations/v216.go | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/models/migrations/v216.go b/models/migrations/v216.go index b011c11d95..67c360016d 100644 --- a/models/migrations/v216.go +++ b/models/migrations/v216.go @@ -42,26 +42,5 @@ func (a *improveActionTableIndicesAction) TableIndices() []*schemas.Index { } func improveActionTableIndices(x *xorm.Engine) error { - { - type Action struct { - ID int64 `xorm:"pk autoincr"` - UserID int64 `xorm:"INDEX"` // Receiver user id. - OpType int - ActUserID int64 `xorm:"INDEX"` // Action user id. - RepoID int64 `xorm:"INDEX"` - CommentID int64 `xorm:"INDEX"` - IsDeleted bool `xorm:"INDEX NOT NULL DEFAULT false"` - RefName string - IsPrivate bool `xorm:"INDEX NOT NULL DEFAULT false"` - Content string `xorm:"TEXT"` - CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` - } - if err := x.Sync2(&Action{}); err != nil { - return err - } - if err := x.DropIndexes(&Action{}); err != nil { - return err - } - } return x.Sync2(&improveActionTableIndicesAction{}) } From 761db4d53ed47234c6016411f09597e419a8473e Mon Sep 17 00:00:00 2001 From: Gusted Date: Mon, 20 Jun 2022 08:44:55 +0200 Subject: [PATCH 009/246] Disable federation by default (#20045) (#20046) * Disable federation by default (#20045) - Backport #20045 - A Gitea instance should choose whetever they want to federate(as once it has more features also brings extra costs/moderation/unexpected behavior) with other AP/ForgeFed software. * Fix tests --- custom/conf/app.example.ini | 4 ++-- .../doc/advanced/config-cheat-sheet.en-us.md | 2 +- integrations/api_nodeinfo_test.go | 13 ++++++++----- modules/setting/federation.go | 2 +- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 065c57ef51..dfaea70021 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -2247,10 +2247,10 @@ PATH = ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; Enable/Disable federation capabilities -; ENABLED = true +;ENABLED = false ;; ;; Enable/Disable user statistics for nodeinfo if federation is enabled -; SHARE_USER_STATISTICS = true +;SHARE_USER_STATISTICS = true ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/docs/content/doc/advanced/config-cheat-sheet.en-us.md b/docs/content/doc/advanced/config-cheat-sheet.en-us.md index 4f041d417e..42499ba807 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md +++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md @@ -1088,7 +1088,7 @@ Task queue configuration has been moved to `queue.task`. However, the below conf ## Federation (`federation`) -- `ENABLED`: **true**: Enable/Disable federation capabilities +- `ENABLED`: **false**: Enable/Disable federation capabilities - `SHARE_USER_STATISTICS`: **true**: Enable/Disable user statistics for nodeinfo if federation is enabled ## Packages (`packages`) diff --git a/integrations/api_nodeinfo_test.go b/integrations/api_nodeinfo_test.go index c2fcd2fea5..cf9ff4da1b 100644 --- a/integrations/api_nodeinfo_test.go +++ b/integrations/api_nodeinfo_test.go @@ -11,17 +11,20 @@ import ( "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/routers" "github.com/stretchr/testify/assert" ) func TestNodeinfo(t *testing.T) { - onGiteaRun(t, func(*testing.T, *url.URL) { - setting.Federation.Enabled = true - defer func() { - setting.Federation.Enabled = false - }() + setting.Federation.Enabled = true + c = routers.NormalRoutes() + defer func() { + setting.Federation.Enabled = false + c = routers.NormalRoutes() + }() + onGiteaRun(t, func(*testing.T, *url.URL) { req := NewRequestf(t, "GET", "/api/v1/nodeinfo") resp := MakeRequest(t, req, http.StatusOK) var nodeinfo api.NodeInfo diff --git a/modules/setting/federation.go b/modules/setting/federation.go index fd39e5c7c2..258a666aee 100644 --- a/modules/setting/federation.go +++ b/modules/setting/federation.go @@ -12,7 +12,7 @@ var ( Enabled bool ShareUserStatistics bool }{ - Enabled: true, + Enabled: false, ShareUserStatistics: true, } ) From 710a1419fa58ac8b8d8d981eaf969db4be34dc69 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Mon, 20 Jun 2022 15:09:50 +0200 Subject: [PATCH 010/246] Changelog v1.17.0-rc1 (#20023) Co-authored-by: Gusted Co-authored-by: delvh Co-authored-by: zeripath Co-authored-by: Lauris BH Co-authored-by: Lunny Xiao --- .changelog.yml | 7 ++ CHANGELOG.md | 275 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 282 insertions(+) diff --git a/.changelog.yml b/.changelog.yml index 657dfa1c0e..5c0b2a0cb0 100644 --- a/.changelog.yml +++ b/.changelog.yml @@ -1,3 +1,6 @@ +# config for changelog tool +# source: https://gitea.com/gitea/changelog + # The full repository name repo: go-gitea/gitea @@ -18,6 +21,10 @@ groups: name: SECURITY labels: - kind/security + - + name: FEDERATION + labels: + - theme/federation - name: FEATURES labels: diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ca8bef008..7e3bed7f0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,281 @@ This changelog goes through all the changes that have been made in each release without substantial changes to our git log; to see the highlights of what has been added to each release, please refer to the [blog](https://blog.gitea.io). +## [1.17.0-rc1](https://github.com/go-gitea/gitea/releases/tag/v1.17.0-rc1) - 2022-06-18 + +* BREAKING + * Require go1.18 for Gitea 1.17 (#19918) + * Make AppDataPath absolute against the AppWorkPath if it is not (#19815) + * Nuke the incorrect permission report on /api/v1/notifications (#19761) + * Refactor git module, make Gitea use internal git config (#19732) + * Remove `RequireHighlightJS` field, update plantuml example. (#19615) + * Increase minimal required git version to 2.0 (#19577) + * Add a directory prefix `gitea-src-VERSION` to release-tar-file (#19396) + * Use "main" as default branch name (#19354) + * Make cron task no notice on success (#19221) + * Add pam account authorization check (#19040) + * Show messages for users if the ROOT_URL is wrong, show JavaScript errors (#18971) + * Refactor mirror code & fix StartToMirror (#18904) + * Remove deprecated SSH ciphers from default (#18697) + * Add the possibility to allow the user to have a favicon which differs from the main logo (#18542) + * Update reserved usernames list (#18438) + * Support custom ACME provider (#18340) + * Change initial TrustModel to committer (#18335) + * Update HTTP status codes (#18063) + * Upgrade Alpine from 3.13 to 3.15 (#18050) + * Restrict email address validation (#17688) + * Refactor Router Logger (#17308) +* SECURITY + * Remove deprecated SSH ciphers from default (#18697) +* FEDERATION + * Return statistic information for nodeinfo (#19561) + * Add Webfinger endpoint (#19462) + * Store the foreign ID of issues during migration (#18446) +* FEATURES + * Automatically render wiki TOC (#19873) + * Adding button to link accounts from user settings (#19792) + * Allow set default merge style while creating repo (#19751) + * Auto merge pull requests when all checks succeeded (#9307 & #19648) + * Improve reviewing PR UX (#19612) + * Add support for rendering console output with colors (#19497) + * Add Helm Chart registry (#19406) + * Add Goroutine stack inspector to admin/monitor (#19207) + * RSS/Atom support for Orgs & Repos (#17714 & #19055) + * Add button for issue deletion (#19032) + * Allow to mark files in a PR as viewed (#19007) + * Add Index to comment for migrations and mirroring (#18806) + * Add health check endpoint (#18465) + * Add packagist webhook (#18224) + * Add "Allow edits from maintainer" feature (#18002) + * Add apply-patch, basic revert and cherry-pick functionality (#17902) + * Add Package Registry (#16510) + * Add LDAP group sync to Teams (#16299) + * Pause queues (#15928) + * Added auto-save whitespace behavior if it changed manually (#15566) + * Find files in repo (#15028) + * Provide configuration to allow camo-media proxying (#12802) +* API + * Add endpoint to serve blob or LFS file content (#19689) + * Add endpoint to check if team has repo access (#19540) + * More commit info (#19252) + * Allow to create file on empty repo (#19224) + * Allow removing issues (#18879) + * Add endpoint to query collaborators permission for a repository (#18761) + * Return primary language and repository language stats API URL (#18396) + * Implement http signatures support for the API (#17565) +* ENHANCEMENTS + * Add dbconsistency checks for Stopwatches (#20010) + * Add fetch.writeCommitGraph to gitconfig (#20006) + * Add fgprof pprof profiler (#20005) + * Move agit dependency (#19998) + * Empty log queue on flush and close (#19994) + * Remove tab/TabName usage where it's not needed (#19973) + * Improve file header on mobile (#19945) + * Move issues related files into models/issues (#19931) + * Add breaking email restrictions checker in doctor (#19903) + * Improve UX on modal for deleting an access token (#19894) + * Add alt text to logo (#19892) + * Move some code into models/git (#19879) + * Remove customized (unmaintained) dropdown, improve aria a11y for dropdown (#19861) + * Make user profile image show full image on mobile (#19840) + * Replace blue button and label classes with primary (#19763) + * Remove fomantic progress module (#19760) + * Allows repo search to match against "owner/repo" pattern strings (#19754) + * Move org functions (#19753) + * Move almost all functions' parameter db.Engine to context.Context (#19748) + * Show source/target branches on PR's list (#19747) + * Use http.StatusTemporaryRedirect(307) when serve avatar directly (#19739) + * Add doctor orphan check for orphaned pull requests without an existing base repo (#19731) + * Make Ctrl+Enter (quick submit) work for issue comment and wiki editor (#19729) + * Update go-chi/cache to utilize Ping() (#19719) + * Improve commit list/view on mobile (#19712) + * Move some repository related code into sub package (#19711) + * Use a better OlderThan for DeleteInactiveUsers (#19693) + * Introduce eslint-plugin-jquery (#19690) + * Tidy up `` template (#19678) + * Calculate filename hash only once (#19654) + * Simplify `IsVendor` (#19626) + * Add "Reference" section to Issue view sidebar (#19609) + * Only set CanColorStdout / CanColorStderr to true if the stdout/stderr is a terminal (#19581) + * Use for a repo action one database transaction (#19576) + * Simplify loops to copy (#19569) + * Added X-Mailer header to outgoing emails (#19562) + * use middleware to open gitRepo (#19559) + * Mute link in diff header (#19556) + * Improve UI on mobile (#19546) + * Fix Pull Request comment filename word breaks (#19535) + * Permalink files In PR diff (#19534) + * PullService lock via pullID (#19520) + * Make repository file list useable on mobile (#19515) + * more context for models (#19511) + * Allow package dump skipping (#19506) + * Refactor readme file renderer (#19502) + * By default force vertical tabs on mobile (#19486) + * Github style following followers (#19482) + * Improve action table indices (#19472) + * Use horizontal tabs for repo header on mobile (#19468) + * pass gitRepo down since its used for main repo and wiki (#19461) + * Admin should not delete himself (#19423) + * Use queue instead of memory queue in webhook send service (#19390) + * Simplify the code to get issue count (#19380) + * Add commit status popup to issuelist (#19375) + * Add RSS Feed buttons to Repo, User and Org pages (#19370) + * Add logic to switch between source/rendered on Markdown (#19356) + * Move some helper files out of models (#19355) + * Move access and repo permission to models/perm/access (#19350) + * Disallow selecting the text of buttons (#19330) + * Allow custom redirect for landing page (#19324) + * Repository level enable package or disable (#19323) + * Remove dependent on session auth for api/v1 routers (#19321) + * Never use /api/v1 from Gitea UI Pages (#19318) + * Remove legacy unmaintained packages, refactor to support change default locale (#19308) + * Move milestone to models/issues/ (#19278) + * Configure OpenSSH log level via Environment in Docker (#19274) + * Move reaction to models/issues/ (#19264) + * Make git.OpenRepository accept Context (#19260) + * Move some issue methods as functions (#19255) + * Show last cron messages on monitor page (#19223) + * New cron task: delete old system notices (#19219) + * Add Redis Sentinel Authentication Support (#19213) + * Add auto logging of goroutine pid label (#19212) + * Set OpenGraph title to DisplayName in profile pages (#19206) + * Add pprof labels in processes and for lifecycles (#19202) + * Let web and API routes have different auth methods group (#19168) + * Move init repository related functions to modules (#19159) + * Feeds: render markdown to html (#19058) + * Allow users to self-request a PR review (#19030) + * Allow render HTML with css/js external links (#19017) + * Fix script compatiable with OpenWrt (#19000) + * Support ignore all santize for external renderer (#18984) + * Add note to GPG key response if user has no keys (#18961) + * Improve Stopwatch behavior (#18930) + * Improve mirror iterator (#18928) + * Uncapitalize errors (#18915) + * Prevent Stats Indexer reporting error if repo dir missing (#18870) + * Refactor SecToTime() function (#18863) + * Replace deprecated String.prototype.substr() with String.prototype.slice() (#18796) + * Move deletebeans into models/db (#18781) + * Fix display time of milestones (#18753) + * Add config option to disable "Update branch by rebase" (#18745) + * Display template path of current page in dev mode (#18717) + * Add number in queue status to monitor page (#18712) + * Change git.cmd to RunWithContext (#18693) + * Refactor i18n, use Locale to provide i18n/translation related functions (#18648) + * Delete old git.NewCommand() and use it as git.NewCommandContext() (#18552) + * Move organization related structs into sub package (#18518) + * Warn at startup if the provided `SCRIPT_TYPE` is not on the PATH (#18467) + * Use `CryptoRandomBytes` instead of `CryptoRandomString` (#18439) + * Use explicit jQuery import, remove unused eslint globals (#18435) + * Allow to filter repositories by language in explore, user and organization repositories lists (#18430) + * Use base32 for 2FA scratch token (#18384) + * Unexport var git.GlobalCommandArgs (#18376) + * Don't underline commit status icon on hover (#18372) + * Always use git command but not os.Command (#18363) + * Switch to non-deprecation setting (#18358) + * Set the LastModified header for raw files (#18356) + * Refactor jwt.StandardClaims to RegisteredClaims (#18344) + * Enable deprecation error for v1.17.0 (#18341) + * Refactor httplib (#18338) + * Limit max-height of CodeMirror editors for issue comment and wiki (#18271) + * Validate migration files (#18203) + * Format with gofumpt (#18184) + * Allow custom default merge message with .gitea/default_merge_message/_TEMPLATE.md (#18177) + * Prettify number of issues (#17760) + * Add a "admin user generate-access-token" subcommand (#17722) + * Move project files into models/project sub package (#17704) + * Custom regexp external issues (#17624) + * Add smtp password to install page (#17564) + * Add config options to hide issue events (#17414) + * Prevent double click new issue/pull/comment button (#16157) + * Show issue assignee on project board (#15232) +* BUGFIXES + * Alter hook_task TEXT fields to LONGTEXT (#20038) (#20041) + * Respond with a 401 on git push when password isn't changed yet (#20026) (#20027) + * Return 404 when tag is broken (#20017) (#20024) + * Write Commit-Graphs in RepositoryDumper (#20004) + * Use DisplayName() instead of FullName in Oauth Provider (#19991) + * Don't buffer doctor logger (#19982) + * Always try to fetch repo for mirrors (#19975) + * Uppercase first languages letters (#19965) + * Fix cli command restore-repo: "units" should be parsed as StringSlice (#19953) + * Ensure minimum mirror interval is reported on settings page (#19895) + * Exclude Archived repos from Dashboard Milestones (#19882) + * gitconfig: set safe.directory = * (#19870) + * Prevent NPE on update mirror settings (#19864) + * Only return valid stopwatches to the EventSource (#19863) + * Prevent NPE whilst migrating if there is a team request review (#19855) + * Fix inconsistency in doctor output (#19836) + * Fix release tag for webhook (#19830) + * Add title attribute to dependencies in sidebar (#19807) + * Estimate Action Count in Statistics (#19775) + * Do not update user stars numbers unless fix is specified (#19750) + * Improved ref comment link when origin is body/title (#19741) + * Fix nodeinfo caching and prevent NPE if cache non-existent (#19721) + * Fix duplicate entry error when add team member (#19702) + * Fix sending empty notifications (#19589) + * Update image URL for Discord webhook (#19536) + * Don't let repo clone URL overflow (#19517) + * Allow commit status popup on /pulls page (#19507) + * Fix two UI bugs: JS error in imagediff.js, 500 error in diff/compare.tmpl (#19494) + * Fix logging of Transfer API (#19456) + * Fix panic in teams API when requesting members (#19360) + * Refactor CSRF protection modules, make sure CSRF tokens can be up-to-date. (#19337) + * An attempt to sync a non-mirror repo must give 400 (Bad Request) (#19300) + * Move checks for pulls before merge into own function (#19271) + * Fix `contrib/upgrade.sh` (#19222) + * Set the default branch for repositories generated from templates (#19136) + * Fix EasyMDE error when input Enter (#19004) + * Don't clean up hardcoded `tmp` (#18983) + * Delete related notifications on issue deletion too (#18953) + * Fix trace log to show value instead of pointers (#18926) + * Fix behavior or checkbox submission. (#18851) + * Add `ContextUser` (#18798) + * Fix some mirror bugs (#18649) + * Quote MAKE to prevent path expansion with space error (#18622) + * Preserve users if restoring a repository on the same Gitea instance (#18604) + * Fix non-ASCII search on database (#18437) + * Automatically pause queue if index service is unavailable (#15066) +* TESTING + * Allow postgres integration tests to run over unix pipe (#19875) + * Prevent intermittent NPE in queue tests (#19301) + * Add test for importing pull requests in gitea uploader for migrations (#18752) + * Remove redundant comparison in repo dump/restore (#18660) + * More repo dump/restore tests, including pull requests (#18621) + * Add test coverage for original author conversion during migrations (#18506) +* TRANSLATION + * Update issue_no_dependencies description (#19112) + * Refactor webhooks i18n (#18380) +* BUILD + * Use alpine 3.16 (#19797) + * Require node 14.0 (#19451) +* DOCS + * Update documents (git/fomantic/db, etc) (#19868) + * Update the ROOT documentation and error messages (#19832) + * Update document to use FHS `/usr/local/bin/gitea` instead of `/app/...` for Docker (#19794) + * Update documentation to disable duration settings with -1 instead of 0 (#19647) + * Add warning to set SENDMAIL_ARGS to -- (#19102) + * Update nginx reverse proxy docs (#18922) + * Add example to render html files (#18736) + * Make SSH passtrough documentation better (#18687) + * Changelog 1.16.0 & 1.15.11 (#18468 & #18455) (#18470) + * Update the SSH passthrough documentation (#18366) + * Add `contrib/upgrade.sh` (#18286) +* MISC + * Fix aria for logo (#19955) + * In code search, get code unit accessible repos in one (main) query (#19764) + * Enable packages by default again (#19746) + * Add tooltip to pending PR comments (#19662) + * Improve sync performance for pull-mirrors (#19125) + * Improve dashboard's repo list performance (#18963) + * Avoid database lookups for `DescriptionHTML` (#18924) + * Remove CodeMirror dependencies (#18911) + * Disable unnecessary mirroring elements (#18527) + * Disable unnecessary OpenID/OAuth2 elements (#18491) + * Disable unnecessary GitHooks elements (#18485) + * Change some logging levels (#18421) + * Prevent showing webauthn error for every time visiting `/user/settings/security` (#18385) + * Use correct translation key for errors (#18342) + ## [1.16.8](https://github.com/go-gitea/gitea/releases/tag/v1.16.8) - 2022-05-16 * ENHANCEMENTS From 29ac31628ce6c84c0423b38ff3966997bdc6bb00 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Tue, 21 Jun 2022 20:08:25 +0200 Subject: [PATCH 011/246] Release page show all tags in compare dropdown (#20070) (#20072) Backport #20070 Just get all tags when creating the compare dropdown. Fix #19936 --- routers/web/repo/release.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/routers/web/repo/release.go b/routers/web/repo/release.go index 666294631c..df953fd0b9 100644 --- a/routers/web/repo/release.go +++ b/routers/web/repo/release.go @@ -98,7 +98,14 @@ func releasesOrTags(ctx *context.Context, isTagList bool) { listOptions.PageSize = setting.API.MaxResponseItems } - tags, err := ctx.Repo.GitRepo.GetTags(listOptions.GetStartEnd()) + // TODO(20073) tags are used for compare feature witch needs all tags + // filtering is doen at the client side atm + tagListStart, tagListEnd := 0, 0 + if isTagList { + tagListStart, tagListEnd = listOptions.GetStartEnd() + } + + tags, err := ctx.Repo.GitRepo.GetTags(tagListStart, tagListEnd) if err != nil { ctx.ServerError("GetTags", err) return From dbafb4f4d4f8080ae58a3ab83d65451df11e4544 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 23 Jun 2022 00:26:41 +0800 Subject: [PATCH 012/246] Use correct variable for issue count (#20086) --- templates/user/dashboard/issues.tmpl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/user/dashboard/issues.tmpl b/templates/user/dashboard/issues.tmpl index bd7d54b670..9024d9ede6 100644 --- a/templates/user/dashboard/issues.tmpl +++ b/templates/user/dashboard/issues.tmpl @@ -61,11 +61,11 @@ From 05464ac2a50e27657a1ecd84912ef33a686849e3 Mon Sep 17 00:00:00 2001 From: Jimmy Praet Date: Thu, 23 Jun 2022 11:51:44 +0200 Subject: [PATCH 013/246] Dashboard feed respect setting.UI.FeedPagingNum again (#20094) (#20099) Fixes #20080 --- routers/web/user/home.go | 1 + routers/web/user/profile.go | 1 + 2 files changed, 2 insertions(+) diff --git a/routers/web/user/home.go b/routers/web/user/home.go index 7fe80a2a4b..698117e957 100644 --- a/routers/web/user/home.go +++ b/routers/web/user/home.go @@ -141,6 +141,7 @@ func Dashboard(ctx *context.Context) { OnlyPerformedBy: false, IncludeDeleted: false, Date: ctx.FormString("date"), + ListOptions: db.ListOptions{PageSize: setting.UI.FeedPagingNum}, }) if err != nil { ctx.ServerError("GetFeeds", err) diff --git a/routers/web/user/profile.go b/routers/web/user/profile.go index dd5804cd42..609f3242c6 100644 --- a/routers/web/user/profile.go +++ b/routers/web/user/profile.go @@ -188,6 +188,7 @@ func Profile(ctx *context.Context) { OnlyPerformedBy: true, IncludeDeleted: false, Date: ctx.FormString("date"), + ListOptions: db.ListOptions{PageSize: setting.UI.FeedPagingNum}, }) if err != nil { ctx.ServerError("GetFeeds", err) From 764e75d9b9265371a7bd3eb4b9798f20d9f279ed Mon Sep 17 00:00:00 2001 From: SteveTheEngineer Date: Fri, 24 Jun 2022 05:05:23 +0300 Subject: [PATCH 014/246] Catch the error before the response is processed by goth. (#20000) (#20102) The code introduced by #18185 gets the error from response after it was processed by goth. That is incorrect, as goth (and golang.org/x/oauth) doesn't really care about the error, and it sends a token request with an empty authorization code to the server anyway, which always results in a `oauth2: cannot fetch token: 400 Bad Request` error from goth. It means that unless the "state" parameter is omitted from the error response (which is required to be present, according to [RFC 6749, Section 4.1.2.1](https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1)) or the page is reloaded (makes the session invalid), a 500 Internal Server Error page will be displayed. This fixes it by handling the error before the request is passed to goth. --- routers/web/auth/oauth.go | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/routers/web/auth/oauth.go b/routers/web/auth/oauth.go index d868b05a44..56f8294b1a 100644 --- a/routers/web/auth/oauth.go +++ b/routers/web/auth/oauth.go @@ -37,6 +37,7 @@ import ( "gitea.com/go-chi/binding" "github.com/golang-jwt/jwt/v4" "github.com/markbates/goth" + "github.com/markbates/goth/gothic" ) const ( @@ -1098,24 +1099,31 @@ func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model func oAuth2UserLoginCallback(authSource *auth.Source, request *http.Request, response http.ResponseWriter) (*user_model.User, goth.User, error) { oauth2Source := authSource.Cfg.(*oauth2.Source) + // Make sure that the response is not an error response. + errorName := request.FormValue("error") + + if len(errorName) > 0 { + errorDescription := request.FormValue("error_description") + + // Delete the goth session + err := gothic.Logout(response, request) + if err != nil { + return nil, goth.User{}, err + } + + return nil, goth.User{}, errCallback{ + Code: errorName, + Description: errorDescription, + } + } + + // Proceed to authenticate through goth. gothUser, err := oauth2Source.Callback(request, response) if err != nil { if err.Error() == "securecookie: the value is too long" || strings.Contains(err.Error(), "Data too long") { log.Error("OAuth2 Provider %s returned too long a token. Current max: %d. Either increase the [OAuth2] MAX_TOKEN_LENGTH or reduce the information returned from the OAuth2 provider", authSource.Name, setting.OAuth2.MaxTokenLength) err = fmt.Errorf("OAuth2 Provider %s returned too long a token. Current max: %d. Either increase the [OAuth2] MAX_TOKEN_LENGTH or reduce the information returned from the OAuth2 provider", authSource.Name, setting.OAuth2.MaxTokenLength) } - // goth does not provide the original error message - // https://github.com/markbates/goth/issues/348 - if strings.Contains(err.Error(), "server response missing access_token") || strings.Contains(err.Error(), "could not find a matching session for this request") { - errorCode := request.FormValue("error") - errorDescription := request.FormValue("error_description") - if errorCode != "" || errorDescription != "" { - return nil, goth.User{}, errCallback{ - Code: errorCode, - Description: errorDescription, - } - } - } return nil, goth.User{}, err } From fb5ca1bf644c4cf3960ce8161ebc2250a82b08cc Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 24 Jun 2022 15:02:22 +0800 Subject: [PATCH 015/246] Fix wrong login requirement routers (#20101) (#20104) --- routers/web/web.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routers/web/web.go b/routers/web/web.go index 374bafbc8d..f8ea3877de 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -721,7 +721,7 @@ func RegisterRoutes(m *web.Route) { }, reqPackageAccess(perm.AccessModeWrite)) }) }) - }, context.PackageAssignment(), reqPackageAccess(perm.AccessModeRead)) + }, ignSignIn, context.PackageAssignment(), reqPackageAccess(perm.AccessModeRead)) } }, context_service.UserAssignmentWeb()) From 0b7b342ab0b3cd26d8eb4ffb7f2df2ce505bb2ac Mon Sep 17 00:00:00 2001 From: Gusted Date: Sat, 25 Jun 2022 21:50:23 +0200 Subject: [PATCH 016/246] Fix remove file on initial comment (#20127) (#20128) Backport #20127 Store the file uuid(which is returned by Gitea in the upload file response) onto the file object, so it can be used for the remove feature to specify this file. Fix #20115 --- web_src/js/features/common-global.js | 3 ++- web_src/js/features/repo-legacy.js | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/web_src/js/features/common-global.js b/web_src/js/features/common-global.js index eb59bcbe38..a508db39c5 100644 --- a/web_src/js/features/common-global.js +++ b/web_src/js/features/common-global.js @@ -192,7 +192,8 @@ export function initGlobalDropzone() { thumbnailWidth: 480, thumbnailHeight: 480, init() { - this.on('success', (_file, data) => { + this.on('success', (file, data) => { + file.uuid = data.uuid; const input = $(``).val(data.uuid); $dropzone.find('.files').append(input); }); diff --git a/web_src/js/features/repo-legacy.js b/web_src/js/features/repo-legacy.js index 6cdde6a1e4..2bf80d5511 100644 --- a/web_src/js/features/repo-legacy.js +++ b/web_src/js/features/repo-legacy.js @@ -300,6 +300,7 @@ async function onEditContent(event) { thumbnailHeight: 480, init() { this.on('success', (file, data) => { + file.uuid = data.uuid; fileUuidDict[file.uuid] = {submitted: false}; const input = $(``).val(data.uuid); $dropzone.find('.files').append(input); From 0dab13884ad610930ad256c2a466eefc480e9cee Mon Sep 17 00:00:00 2001 From: Gusted Date: Mon, 27 Jun 2022 07:20:47 +0200 Subject: [PATCH 017/246] Show scrollbar when necessary (#20142) (#20143) - Backport #20142 - Firefox on Windows will unconditionally show scrollbars when you specify `overflow: scroll`. This is bad behavior, as you don't always need the scrollbar. Changing the scroll value to auto fixes this issue and only shows the scrollbar when necessary. - Resolves #20139 --- web_src/less/_repository.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_src/less/_repository.less b/web_src/less/_repository.less index 2686c0d280..bc1affb770 100644 --- a/web_src/less/_repository.less +++ b/web_src/less/_repository.less @@ -3051,7 +3051,7 @@ td.blob-excerpt { align-items: center; display: flex; justify-content: space-between; - overflow-x: scroll; + overflow-x: auto; padding: 8px 12px !important; } From 1ffc700777527c84b66d086c6dc95641c0ae1318 Mon Sep 17 00:00:00 2001 From: silverwind Date: Fri, 1 Jul 2022 13:37:52 +0200 Subject: [PATCH 018/246] Update default allowed attachment types (#20193) Synced the list to what is allowed on GitHub currently. --- custom/conf/app.example.ini | 2 +- docs/content/doc/advanced/config-cheat-sheet.en-us.md | 2 +- modules/setting/attachment.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index dfaea70021..b5b8095373 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -1687,7 +1687,7 @@ PATH = ;ENABLED = true ;; ;; Comma-separated list of allowed file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types. -;ALLOWED_TYPES = .docx,.gif,.gz,.jpeg,.jpg,.mp4,.log,.pdf,.png,.pptx,.txt,.xlsx,.zip +;ALLOWED_TYPES = .csv,.docx,.fodg,.fodp,.fods,.fodt,.gif,.gz,.jpeg,.jpg,.log,.md,.mov,.mp4,.odf,.odg,.odp,.ods,.odt,.pdf,.png,.pptx,.svg,.tgz,.txt,.webm,.xls,.xlsx,.zip ;; ;; Max size of each file. Defaults to 4MB ;MAX_SIZE = 4 diff --git a/docs/content/doc/advanced/config-cheat-sheet.en-us.md b/docs/content/doc/advanced/config-cheat-sheet.en-us.md index 42499ba807..3834a3b6b6 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md +++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md @@ -741,7 +741,7 @@ Default templates for project boards: ## Issue and pull request attachments (`attachment`) - `ENABLED`: **true**: Whether issue and pull request attachments are enabled. -- `ALLOWED_TYPES`: **.docx,.gif,.gz,.jpeg,.jpg,mp4,.log,.pdf,.png,.pptx,.txt,.xlsx,.zip**: Comma-separated list of allowed file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types. +- `ALLOWED_TYPES`: **.csv,.docx,.fodg,.fodp,.fods,.fodt,.gif,.gz,.jpeg,.jpg,.log,.md,.mov,.mp4,.odf,.odg,.odp,.ods,.odt,.pdf,.png,.pptx,.svg,.tgz,.txt,.webm,.xls,.xlsx,.zip**: Comma-separated list of allowed file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types. - `MAX_SIZE`: **4**: Maximum size (MB). - `MAX_FILES`: **5**: Maximum number of attachments that can be uploaded at once. - `STORAGE_TYPE`: **local**: Storage type for attachments, `local` for local disk or `minio` for s3 compatible object storage service, default is `local` or other name defined with `[storage.xxx]` diff --git a/modules/setting/attachment.go b/modules/setting/attachment.go index c3c2c3fafd..474b73293c 100644 --- a/modules/setting/attachment.go +++ b/modules/setting/attachment.go @@ -27,7 +27,7 @@ func newAttachmentService() { Attachment.Storage = getStorage("attachments", storageType, sec) - Attachment.AllowedTypes = sec.Key("ALLOWED_TYPES").MustString(".docx,.gif,.gz,.jpeg,.jpg,.mp4,.log,.pdf,.png,.pptx,.txt,.xlsx,.zip") + Attachment.AllowedTypes = sec.Key("ALLOWED_TYPES").MustString(".csv,.docx,.fodg,.fodp,.fods,.fodt,.gif,.gz,.jpeg,.jpg,.log,.md,.mov,.mp4,.odf,.odg,.odp,.ods,.odt,.pdf,.png,.pptx,.svg,.tgz,.txt,.webm,.xls,.xlsx,.zip") Attachment.MaxSize = sec.Key("MAX_SIZE").MustInt64(4) Attachment.MaxFiles = sec.Key("MAX_FILES").MustInt(5) Attachment.Enabled = sec.Key("ENABLED").MustBool(true) From 3e4fe009e729d2facec48f93ffcffc72b4bb359c Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 1 Jul 2022 21:00:05 +0800 Subject: [PATCH 019/246] Check if project has the same repository id with issue when assign project to issue (#20133) (#20188) * Check if project has the same repository id with issue when assign project to issue * Check if issue's repository id match project's repository id * Add more permission checking * Remove invalid argument * Fix errors * Add generic check * Remove duplicated check * Return error + add check for new issues * Apply suggestions from code review Co-authored-by: Gusted Co-authored-by: KN4CK3R Co-authored-by: 6543 <6543@obermui.de> --- models/issues/issue_project.go | 11 +++++++++++ models/issues/milestone.go | 5 +++++ routers/api/v1/repo/pull_review.go | 2 +- routers/web/repo/issue.go | 14 ++++++++++++-- routers/web/repo/projects.go | 10 +++++++++- routers/web/repo/pull_review.go | 8 +++++++- routers/web/web.go | 2 +- services/issue/milestone.go | 11 +++++++++++ services/pull/review.go | 16 +++++++++++----- 9 files changed, 68 insertions(+), 11 deletions(-) diff --git a/models/issues/issue_project.go b/models/issues/issue_project.go index 5e0a337f7d..c054fa3a43 100644 --- a/models/issues/issue_project.go +++ b/models/issues/issue_project.go @@ -124,6 +124,17 @@ func ChangeProjectAssign(issue *Issue, doer *user_model.User, newProjectID int64 func addUpdateIssueProject(ctx context.Context, issue *Issue, doer *user_model.User, newProjectID int64) error { oldProjectID := issue.projectID(ctx) + // Only check if we add a new project and not remove it. + if newProjectID > 0 { + newProject, err := project_model.GetProjectByID(ctx, newProjectID) + if err != nil { + return err + } + if newProject.RepoID != issue.RepoID { + return fmt.Errorf("issue's repository is not the same as project's repository") + } + } + if _, err := db.GetEngine(ctx).Where("project_issue.issue_id=?", issue.ID).Delete(&project_model.ProjectIssue{}); err != nil { return err } diff --git a/models/issues/milestone.go b/models/issues/milestone.go index 6c10959108..fba599e6ec 100644 --- a/models/issues/milestone.go +++ b/models/issues/milestone.go @@ -124,6 +124,11 @@ func NewMilestone(m *Milestone) (err error) { return committer.Commit() } +// HasMilestoneByRepoID returns if the milestone exists in the repository. +func HasMilestoneByRepoID(ctx context.Context, repoID, id int64) (bool, error) { + return db.GetEngine(ctx).ID(id).Where("repo_id=?", repoID).Exist(new(Milestone)) +} + // GetMilestoneByRepoID returns the milestone in a repository. func GetMilestoneByRepoID(ctx context.Context, repoID, id int64) (*Milestone, error) { m := new(Milestone) diff --git a/routers/api/v1/repo/pull_review.go b/routers/api/v1/repo/pull_review.go index 57548f2102..1b61a40222 100644 --- a/routers/api/v1/repo/pull_review.go +++ b/routers/api/v1/repo/pull_review.go @@ -886,7 +886,7 @@ func dismissReview(ctx *context.APIContext, msg string, isDismiss bool) { return } - _, err := pull_service.DismissReview(ctx, review.ID, msg, ctx.Doer, isDismiss) + _, err := pull_service.DismissReview(ctx, review.ID, ctx.Repo.Repository.ID, msg, ctx.Doer, isDismiss) if err != nil { ctx.Error(http.StatusInternalServerError, "pull_service.DismissReview", err) return diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index 5b72ff79af..e6f9529e31 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -803,7 +803,8 @@ func NewIssue(ctx *context.Context) { body := ctx.FormString("body") ctx.Data["BodyQuery"] = body - ctx.Data["IsProjectsEnabled"] = ctx.Repo.CanRead(unit.TypeProjects) + isProjectsEnabled := ctx.Repo.CanRead(unit.TypeProjects) + ctx.Data["IsProjectsEnabled"] = isProjectsEnabled ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled upload.AddUploadContext(ctx, "comment") @@ -819,7 +820,7 @@ func NewIssue(ctx *context.Context) { } projectID := ctx.FormInt64("project") - if projectID > 0 { + if projectID > 0 && isProjectsEnabled { project, err := project_model.GetProjectByID(ctx, projectID) if err != nil { log.Error("GetProjectByID: %d: %v", projectID, err) @@ -1043,6 +1044,11 @@ func NewIssuePost(ctx *context.Context) { } if projectID > 0 { + if !ctx.Repo.CanRead(unit.TypeProjects) { + // User must also be able to see the project. + ctx.Error(http.StatusBadRequest, "user hasn't permissions to read projects") + return + } if err := issues_model.ChangeProjectAssign(issue, ctx.Doer, projectID); err != nil { ctx.ServerError("ChangeProjectAssign", err) return @@ -1783,6 +1789,10 @@ func getActionIssues(ctx *context.Context) []*issues_model.Issue { issueUnitEnabled := ctx.Repo.CanRead(unit.TypeIssues) prUnitEnabled := ctx.Repo.CanRead(unit.TypePullRequests) for _, issue := range issues { + if issue.RepoID != ctx.Repo.Repository.ID { + ctx.NotFound("some issue's RepoID is incorrect", errors.New("some issue's RepoID is incorrect")) + return nil + } if issue.IsPull && !prUnitEnabled || !issue.IsPull && !issueUnitEnabled { ctx.NotFound("IssueOrPullRequestUnitNotAllowed", nil) return nil diff --git a/routers/web/repo/projects.go b/routers/web/repo/projects.go index 51c891dbf0..71f264830d 100644 --- a/routers/web/repo/projects.go +++ b/routers/web/repo/projects.go @@ -5,6 +5,7 @@ package repo import ( + "errors" "fmt" "net/http" "net/url" @@ -633,10 +634,17 @@ func MoveIssues(ctx *context.Context) { } if len(movedIssues) != len(form.Issues) { - ctx.ServerError("IssuesNotFound", err) + ctx.ServerError("some issues do not exist", errors.New("some issues do not exist")) return } + for _, issue := range movedIssues { + if issue.RepoID != project.RepoID { + ctx.ServerError("Some issue's repoID is not equal to project's repoID", errors.New("Some issue's repoID is not equal to project's repoID")) + return + } + } + if err = project_model.MoveIssuesOnProjectBoard(board, sortedIssueIDs); err != nil { ctx.ServerError("MoveIssuesOnProjectBoard", err) return diff --git a/routers/web/repo/pull_review.go b/routers/web/repo/pull_review.go index cc7ae9bbfa..5a9f7a8138 100644 --- a/routers/web/repo/pull_review.go +++ b/routers/web/repo/pull_review.go @@ -5,6 +5,7 @@ package repo import ( + "errors" "fmt" "net/http" @@ -118,6 +119,11 @@ func UpdateResolveConversation(ctx *context.Context) { return } + if comment.Issue.RepoID != ctx.Repo.Repository.ID { + ctx.NotFound("comment's repoID is incorrect", errors.New("comment's repoID is incorrect")) + return + } + var permResult bool if permResult, err = issues_model.CanMarkConversation(comment.Issue, ctx.Doer); err != nil { ctx.ServerError("CanMarkConversation", err) @@ -236,7 +242,7 @@ func SubmitReview(ctx *context.Context) { // DismissReview dismissing stale review by repo admin func DismissReview(ctx *context.Context) { form := web.GetForm(ctx).(*forms.DismissReviewForm) - comm, err := pull_service.DismissReview(ctx, form.ReviewID, form.Message, ctx.Doer, true) + comm, err := pull_service.DismissReview(ctx, form.ReviewID, ctx.Repo.Repository.ID, form.Message, ctx.Doer, true) if err != nil { ctx.ServerError("pull_service.DismissReview", err) return diff --git a/routers/web/web.go b/routers/web/web.go index f8ea3877de..d594caf643 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -898,7 +898,7 @@ func RegisterRoutes(m *web.Route) { m.Post("/labels", reqRepoIssuesOrPullsWriter, repo.UpdateIssueLabel) m.Post("/milestone", reqRepoIssuesOrPullsWriter, repo.UpdateIssueMilestone) - m.Post("/projects", reqRepoIssuesOrPullsWriter, repo.UpdateIssueProject) + m.Post("/projects", reqRepoIssuesOrPullsWriter, reqRepoProjectsReader, repo.UpdateIssueProject) m.Post("/assignee", reqRepoIssuesOrPullsWriter, repo.UpdateIssueAssignee) m.Post("/request_review", reqRepoIssuesOrPullsReader, repo.UpdatePullReviewRequest) m.Post("/dismiss_review", reqRepoAdmin, bindIgnErr(forms.DismissReviewForm{}), repo.DismissReview) diff --git a/services/issue/milestone.go b/services/issue/milestone.go index af337c3f14..d7c5fa4551 100644 --- a/services/issue/milestone.go +++ b/services/issue/milestone.go @@ -15,6 +15,17 @@ import ( ) func changeMilestoneAssign(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, oldMilestoneID int64) error { + // Only check if milestone exists if we don't remove it. + if issue.MilestoneID > 0 { + has, err := issues_model.HasMilestoneByRepoID(ctx, issue.RepoID, issue.MilestoneID) + if err != nil { + return fmt.Errorf("HasMilestoneByRepoID: %v", err) + } + if !has { + return fmt.Errorf("HasMilestoneByRepoID: issue doesn't exist") + } + } + if err := issues_model.UpdateIssueCols(ctx, issue, "milestone_id"); err != nil { return err } diff --git a/services/pull/review.go b/services/pull/review.go index 9cb58fa3a1..b40e20a26f 100644 --- a/services/pull/review.go +++ b/services/pull/review.go @@ -271,7 +271,7 @@ func SubmitReview(ctx context.Context, doer *user_model.User, gitRepo *git.Repos } // DismissReview dismissing stale review by repo admin -func DismissReview(ctx context.Context, reviewID int64, message string, doer *user_model.User, isDismiss bool) (comment *issues_model.Comment, err error) { +func DismissReview(ctx context.Context, reviewID, repoID int64, message string, doer *user_model.User, isDismiss bool) (comment *issues_model.Comment, err error) { review, err := issues_model.GetReviewByID(ctx, reviewID) if err != nil { return @@ -281,6 +281,16 @@ func DismissReview(ctx context.Context, reviewID int64, message string, doer *us return nil, fmt.Errorf("not need to dismiss this review because it's type is not Approve or change request") } + // load data for notify + if err = review.LoadAttributes(ctx); err != nil { + return nil, err + } + + // Check if the review's repoID is the one we're currently expecting. + if review.Issue.RepoID != repoID { + return nil, fmt.Errorf("reviews's repository is not the same as the one we expect") + } + if err = issues_model.DismissReview(review, isDismiss); err != nil { return } @@ -289,10 +299,6 @@ func DismissReview(ctx context.Context, reviewID int64, message string, doer *us return nil, nil } - // load data for notify - if err = review.LoadAttributes(ctx); err != nil { - return - } if err = review.Issue.LoadPullRequest(); err != nil { return } From bf43db10a9057f3cf71efa965b60ed2533690093 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Fri, 1 Jul 2022 23:16:59 +0800 Subject: [PATCH 020/246] Fix cli command restore-repo: "units" should be parsed as cli.String (#20183) (#20187) --- cmd/dump_repo.go | 6 +++++- cmd/restore_repo.go | 14 +++++++++----- services/migrations/dump.go | 2 +- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/cmd/dump_repo.go b/cmd/dump_repo.go index e980af3011..7d2477e203 100644 --- a/cmd/dump_repo.go +++ b/cmd/dump_repo.go @@ -128,7 +128,9 @@ func runDumpRepository(ctx *cli.Context) error { } else { units := strings.Split(ctx.String("units"), ",") for _, unit := range units { - switch strings.ToLower(unit) { + switch strings.ToLower(strings.TrimSpace(unit)) { + case "": + continue case "wiki": opts.Wiki = true case "issues": @@ -145,6 +147,8 @@ func runDumpRepository(ctx *cli.Context) error { opts.Comments = true case "pull_requests": opts.PullRequests = true + default: + return errors.New("invalid unit: " + unit) } } } diff --git a/cmd/restore_repo.go b/cmd/restore_repo.go index c3081279df..2256cc61ab 100644 --- a/cmd/restore_repo.go +++ b/cmd/restore_repo.go @@ -7,6 +7,7 @@ package cmd import ( "errors" "net/http" + "strings" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/private" @@ -37,10 +38,10 @@ var CmdRestoreRepository = cli.Command{ Value: "", Usage: "Restore destination repository name", }, - cli.StringSliceFlag{ + cli.StringFlag{ Name: "units", - Value: nil, - Usage: `Which items will be restored, one or more units should be repeated with this flag. + Value: "", + Usage: `Which items will be restored, one or more units should be separated as comma. wiki, issues, labels, releases, release_assets, milestones, pull_requests, comments are allowed. Empty means all units.`, }, cli.BoolFlag{ @@ -55,13 +56,16 @@ func runRestoreRepository(c *cli.Context) error { defer cancel() setting.LoadFromExisting() - + var units []string + if s := c.String("units"); s != "" { + units = strings.Split(s, ",") + } statusCode, errStr := private.RestoreRepo( ctx, c.String("repo_dir"), c.String("owner_name"), c.String("repo_name"), - c.StringSlice("units"), + units, c.Bool("validation"), ) if statusCode == http.StatusOK { diff --git a/services/migrations/dump.go b/services/migrations/dump.go index 21d03b333f..a9ec459519 100644 --- a/services/migrations/dump.go +++ b/services/migrations/dump.go @@ -590,7 +590,7 @@ func updateOptionsUnits(opts *base.MigrateOptions, units []string) error { opts.ReleaseAssets = true } else { for _, unit := range units { - switch strings.ToLower(unit) { + switch strings.ToLower(strings.TrimSpace(unit)) { case "": continue case "wiki": From d22826a28ef49ca182b5bc4f9995e68d931d0d54 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Sat, 2 Jul 2022 00:01:05 +0800 Subject: [PATCH 021/246] Fix `dump-repo` git init, fix wrong error type for NullDownloader (#20182) (#20186) * Fix `dump-repo` git init * Fix wrong error type for NullDownloader --- cmd/dump_repo.go | 6 ++++++ modules/indexer/code/elastic_search.go | 2 +- modules/migration/null_downloader.go | 20 ++++++++++---------- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/cmd/dump_repo.go b/cmd/dump_repo.go index 7d2477e203..c9d24c6c83 100644 --- a/cmd/dump_repo.go +++ b/cmd/dump_repo.go @@ -10,6 +10,7 @@ import ( "strings" "code.gitea.io/gitea/modules/convert" + "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" base "code.gitea.io/gitea/modules/migration" "code.gitea.io/gitea/modules/setting" @@ -83,6 +84,11 @@ func runDumpRepository(ctx *cli.Context) error { return err } + // migrations.GiteaLocalUploader depends on git module + if err := git.InitSimple(context.Background()); err != nil { + return err + } + log.Info("AppPath: %s", setting.AppPath) log.Info("AppWorkPath: %s", setting.AppWorkPath) log.Info("Custom path: %s", setting.CustomPath) diff --git a/modules/indexer/code/elastic_search.go b/modules/indexer/code/elastic_search.go index 7263f27657..1f6c7d1840 100644 --- a/modules/indexer/code/elastic_search.go +++ b/modules/indexer/code/elastic_search.go @@ -284,7 +284,7 @@ func (b *ElasticSearchIndexer) Index(ctx context.Context, repo *repo_model.Repos reqs := make([]elastic.BulkableRequest, 0) if len(changes.Updates) > 0 { // Now because of some insanity with git cat-file not immediately failing if not run in a valid git directory we need to run git rev-parse first! - if err := git.EnsureValidGitRepository(git.DefaultContext, repo.RepoPath()); err != nil { + if err := git.EnsureValidGitRepository(ctx, repo.RepoPath()); err != nil { log.Error("Unable to open git repo: %s for %-v: %v", repo.RepoPath(), repo, err) return err } diff --git a/modules/migration/null_downloader.go b/modules/migration/null_downloader.go index 32da720f16..ad925c32ce 100644 --- a/modules/migration/null_downloader.go +++ b/modules/migration/null_downloader.go @@ -19,52 +19,52 @@ func (n NullDownloader) SetContext(_ context.Context) {} // GetRepoInfo returns a repository information func (n NullDownloader) GetRepoInfo() (*Repository, error) { - return nil, &ErrNotSupported{Entity: "RepoInfo"} + return nil, ErrNotSupported{Entity: "RepoInfo"} } // GetTopics return repository topics func (n NullDownloader) GetTopics() ([]string, error) { - return nil, &ErrNotSupported{Entity: "Topics"} + return nil, ErrNotSupported{Entity: "Topics"} } // GetMilestones returns milestones func (n NullDownloader) GetMilestones() ([]*Milestone, error) { - return nil, &ErrNotSupported{Entity: "Milestones"} + return nil, ErrNotSupported{Entity: "Milestones"} } // GetReleases returns releases func (n NullDownloader) GetReleases() ([]*Release, error) { - return nil, &ErrNotSupported{Entity: "Releases"} + return nil, ErrNotSupported{Entity: "Releases"} } // GetLabels returns labels func (n NullDownloader) GetLabels() ([]*Label, error) { - return nil, &ErrNotSupported{Entity: "Labels"} + return nil, ErrNotSupported{Entity: "Labels"} } // GetIssues returns issues according start and limit func (n NullDownloader) GetIssues(page, perPage int) ([]*Issue, bool, error) { - return nil, false, &ErrNotSupported{Entity: "Issues"} + return nil, false, ErrNotSupported{Entity: "Issues"} } // GetComments returns comments of an issue or PR func (n NullDownloader) GetComments(commentable Commentable) ([]*Comment, bool, error) { - return nil, false, &ErrNotSupported{Entity: "Comments"} + return nil, false, ErrNotSupported{Entity: "Comments"} } // GetAllComments returns paginated comments func (n NullDownloader) GetAllComments(page, perPage int) ([]*Comment, bool, error) { - return nil, false, &ErrNotSupported{Entity: "AllComments"} + return nil, false, ErrNotSupported{Entity: "AllComments"} } // GetPullRequests returns pull requests according page and perPage func (n NullDownloader) GetPullRequests(page, perPage int) ([]*PullRequest, bool, error) { - return nil, false, &ErrNotSupported{Entity: "PullRequests"} + return nil, false, ErrNotSupported{Entity: "PullRequests"} } // GetReviews returns pull requests review func (n NullDownloader) GetReviews(reviewable Reviewable) ([]*Review, error) { - return nil, &ErrNotSupported{Entity: "Reviews"} + return nil, ErrNotSupported{Entity: "Reviews"} } // FormatCloneURL add authentication into remote URLs From e321b40bb0c5557bc25f7e001e5230f04809e4e3 Mon Sep 17 00:00:00 2001 From: zeripath Date: Sat, 2 Jul 2022 14:31:51 +0100 Subject: [PATCH 022/246] Refix indices on actions table (#20158) (#20198) Backport #20158 Unforunately the previous PR #20035 created indices that were not helpful for SQLite. This PR adjusts these after testing using the try.gitea.io db. Fix #20129 Signed-off-by: Andrew Thornton --- models/action.go | 6 ++--- models/migrations/migrations.go | 9 +++++-- models/migrations/v199.go | 9 +------ models/migrations/v216.go | 42 ++---------------------------- models/migrations/v218.go | 46 +++++++++++++++++++++++++++++++++ 5 files changed, 59 insertions(+), 53 deletions(-) create mode 100644 models/migrations/v218.go diff --git a/models/action.go b/models/action.go index 78cc93e1a2..7dbdaa8530 100644 --- a/models/action.go +++ b/models/action.go @@ -92,12 +92,12 @@ func init() { // TableIndices implements xorm's TableIndices interface func (a *Action) TableIndices() []*schemas.Index { + repoIndex := schemas.NewIndex("r_u_d", schemas.IndexType) + repoIndex.AddColumn("repo_id", "user_id", "is_deleted") + actUserIndex := schemas.NewIndex("au_r_c_u_d", schemas.IndexType) actUserIndex.AddColumn("act_user_id", "repo_id", "created_unix", "user_id", "is_deleted") - repoIndex := schemas.NewIndex("r_c_u_d", schemas.IndexType) - repoIndex.AddColumn("repo_id", "created_unix", "user_id", "is_deleted") - return []*schemas.Index{actUserIndex, repoIndex} } diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index edd4beb451..0d35ac78d3 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -56,6 +56,9 @@ type Version struct { Version int64 } +// Use noopMigration when there is a migration that has been no-oped +var noopMigration = func(_ *xorm.Engine) error { return nil } + // This is a sequence of migrations. Add new migrations to the bottom of the list. // If you want to "retire" a migration, remove it from the top of the list and // update minDBVersion accordingly @@ -351,7 +354,7 @@ var migrations = []Migration{ // v198 -> v199 NewMigration("Add issue content history table", addTableIssueContentHistory), // v199 -> v200 - NewMigration("No-op (remote version is using AppState now)", addRemoteVersionTableNoop), + NewMigration("No-op (remote version is using AppState now)", noopMigration), // v200 -> v201 NewMigration("Add table app_state", addTableAppState), // v201 -> v202 @@ -388,9 +391,11 @@ var migrations = []Migration{ // v215 -> v216 NewMigration("allow to view files in PRs", addReviewViewedFiles), // v216 -> v217 - NewMigration("Improve Action table indices", improveActionTableIndices), + NewMigration("No-op (Improve Action table indices v1)", noopMigration), // v217 -> v218 NewMigration("Alter hook_task table TEXT fields to LONGTEXT", alterHookTaskTextFieldsToLongText), + // v218 -> v219 + NewMigration("Improve Action table indices v2", improveActionTableIndices), } // GetCurrentDBVersion returns the current db version diff --git a/models/migrations/v199.go b/models/migrations/v199.go index 4351ba4fa8..29f9d49dbe 100644 --- a/models/migrations/v199.go +++ b/models/migrations/v199.go @@ -4,11 +4,4 @@ package migrations -import ( - "xorm.io/xorm" -) - -func addRemoteVersionTableNoop(x *xorm.Engine) error { - // we used to use a table `remote_version` to store information for updater, now we use `AppState`, so this migration task is a no-op now. - return nil -} +// We used to use a table `remote_version` to store information for updater, now we use `AppState`, so this migration task is a no-op now. diff --git a/models/migrations/v216.go b/models/migrations/v216.go index 67c360016d..ab44808402 100644 --- a/models/migrations/v216.go +++ b/models/migrations/v216.go @@ -4,43 +4,5 @@ package migrations -import ( - "code.gitea.io/gitea/modules/timeutil" - - "xorm.io/xorm" - "xorm.io/xorm/schemas" -) - -type improveActionTableIndicesAction struct { - ID int64 `xorm:"pk autoincr"` - UserID int64 // Receiver user id. - OpType int - ActUserID int64 // Action user id. - RepoID int64 - CommentID int64 `xorm:"INDEX"` - IsDeleted bool `xorm:"NOT NULL DEFAULT false"` - RefName string - IsPrivate bool `xorm:"NOT NULL DEFAULT false"` - Content string `xorm:"TEXT"` - CreatedUnix timeutil.TimeStamp `xorm:"created"` -} - -// TableName sets the name of this table -func (a *improveActionTableIndicesAction) TableName() string { - return "action" -} - -// TableIndices implements xorm's TableIndices interface -func (a *improveActionTableIndicesAction) TableIndices() []*schemas.Index { - actUserIndex := schemas.NewIndex("au_r_c_u_d", schemas.IndexType) - actUserIndex.AddColumn("act_user_id", "repo_id", "created_unix", "user_id", "is_deleted") - - repoIndex := schemas.NewIndex("r_c_u_d", schemas.IndexType) - repoIndex.AddColumn("repo_id", "created_unix", "user_id", "is_deleted") - - return []*schemas.Index{actUserIndex, repoIndex} -} - -func improveActionTableIndices(x *xorm.Engine) error { - return x.Sync2(&improveActionTableIndicesAction{}) -} +// This migration added non-ideal indices to the action table which on larger datasets slowed things down +// it has been superceded by v218.go diff --git a/models/migrations/v218.go b/models/migrations/v218.go new file mode 100644 index 0000000000..dee8e5517e --- /dev/null +++ b/models/migrations/v218.go @@ -0,0 +1,46 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package migrations + +import ( + "code.gitea.io/gitea/modules/timeutil" + + "xorm.io/xorm" + "xorm.io/xorm/schemas" +) + +type improveActionTableIndicesAction struct { + ID int64 `xorm:"pk autoincr"` + UserID int64 // Receiver user id. + OpType int + ActUserID int64 // Action user id. + RepoID int64 + CommentID int64 `xorm:"INDEX"` + IsDeleted bool `xorm:"NOT NULL DEFAULT false"` + RefName string + IsPrivate bool `xorm:"NOT NULL DEFAULT false"` + Content string `xorm:"TEXT"` + CreatedUnix timeutil.TimeStamp `xorm:"created"` +} + +// TableName sets the name of this table +func (*improveActionTableIndicesAction) TableName() string { + return "action" +} + +// TableIndices implements xorm's TableIndices interface +func (*improveActionTableIndicesAction) TableIndices() []*schemas.Index { + repoIndex := schemas.NewIndex("r_u_d", schemas.IndexType) + repoIndex.AddColumn("repo_id", "user_id", "is_deleted") + + actUserIndex := schemas.NewIndex("au_r_c_u_d", schemas.IndexType) + actUserIndex.AddColumn("act_user_id", "repo_id", "created_unix", "user_id", "is_deleted") + + return []*schemas.Index{actUserIndex, repoIndex} +} + +func improveActionTableIndices(x *xorm.Engine) error { + return x.Sync2(&improveActionTableIndicesAction{}) +} From 35fd55c7df6d707b6fe8f0b5c4793c94df1af127 Mon Sep 17 00:00:00 2001 From: zeripath Date: Mon, 4 Jul 2022 03:15:35 +0100 Subject: [PATCH 023/246] Update Bluemonday to v1.0.19 (#20199) (#20209) --- go.mod | 6 +++--- go.sum | 13 ++++++------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/go.mod b/go.mod index e06ccfe13d..da59b3f42e 100644 --- a/go.mod +++ b/go.mod @@ -64,7 +64,7 @@ require ( github.com/mattn/go-isatty v0.0.14 github.com/mattn/go-sqlite3 v1.14.12 github.com/mholt/archiver/v3 v3.5.1 - github.com/microcosm-cc/bluemonday v1.0.18 + github.com/microcosm-cc/bluemonday v1.0.19 github.com/minio/minio-go/v7 v7.0.26 github.com/msteinert/pam v1.0.0 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 @@ -91,9 +91,9 @@ require ( go.jolheiser.com/hcaptcha v0.0.4 go.jolheiser.com/pwn v0.0.3 golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122 - golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 + golang.org/x/net v0.0.0-20220630215102-69896b714898 golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 - golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 + golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a golang.org/x/text v0.3.7 golang.org/x/tools v0.1.10 gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df diff --git a/go.sum b/go.sum index 9c99adfbe1..843f45ad4d 100644 --- a/go.sum +++ b/go.sum @@ -1146,8 +1146,8 @@ github.com/mholt/acmez v1.0.2 h1:C8wsEBIUVi6e0DYoxqCcFuXtwc4AWXL/jgcDjF7mjVo= github.com/mholt/acmez v1.0.2/go.mod h1:8qnn8QA/Ewx8E3ZSsmscqsIjhhpxuy9vqdgbX2ceceM= github.com/mholt/archiver/v3 v3.5.1 h1:rDjOBX9JSF5BvoJGvjqK479aL70qh9DIpZCl+k7Clwo= github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4= -github.com/microcosm-cc/bluemonday v1.0.18 h1:6HcxvXDAi3ARt3slx6nTesbvorIc3QeTzBNRvWktHBo= -github.com/microcosm-cc/bluemonday v1.0.18/go.mod h1:Z0r70sCuXHig8YpBzCc5eGHAap2K7e/u082ZUpDRRqM= +github.com/microcosm-cc/bluemonday v1.0.19 h1:OI7hoF5FY4pFz2VA//RN8TfM0YJ2dJcl4P4APrCWy6c= +github.com/microcosm-cc/bluemonday v1.0.19/go.mod h1:QNzV2UbLK2/53oIIwTOyLUSABMkjZ4tqiyC1g/DyqxE= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= @@ -1783,14 +1783,13 @@ golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA= -golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220630215102-69896b714898 h1:K7wO6V1IrczY9QOQ2WkVpw4JQSwCd52UsxVEirZUfiw= +golang.org/x/net v0.0.0-20220630215102-69896b714898/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1937,8 +1936,8 @@ golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 h1:nonptSpoQ4vQjyraW20DXPAglgQfVnM9ZC6MmNLMR60= -golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= From f42fc3b287cb592ca0ca3245aa6f86d14052c0a2 Mon Sep 17 00:00:00 2001 From: Gusted Date: Tue, 5 Jul 2022 14:29:04 +0200 Subject: [PATCH 024/246] Init popup for new code comment (#20234) (#20235) - Backport #20234 - Initialize the popup for the tooltip inside the new code comment. - This works and is good enough to have this issue fixed for 1.17 Fix #20068 --- web_src/js/features/common-global.js | 23 ++++++++++++++--------- web_src/js/features/repo-diff.js | 2 ++ web_src/js/index.js | 2 ++ 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/web_src/js/features/common-global.js b/web_src/js/features/common-global.js index a508db39c5..419c5996dc 100644 --- a/web_src/js/features/common-global.js +++ b/web_src/js/features/common-global.js @@ -75,6 +75,20 @@ export function initGlobalButtonClickOnEnter() { }); } +export function initPopup(target) { + const $el = $(target); + const attr = $el.attr('data-variation'); + const attrs = attr ? attr.split(' ') : []; + const variations = new Set([...attrs, 'inverted', 'tiny']); + $el.attr('data-variation', [...variations].join(' ')).popup(); +} + +export function initGlobalPopups() { + $('.tooltip').each((_, el) => { + initPopup(el); + }); +} + export function initGlobalCommon() { // Show exact time $('.time-since').each(function () { @@ -121,15 +135,6 @@ export function initGlobalCommon() { $('.ui.checkbox').checkbox(); - // init popups - $('.tooltip').each((_, el) => { - const $el = $(el); - const attr = $el.attr('data-variation'); - const attrs = attr ? attr.split(' ') : []; - const variations = new Set([...attrs, 'inverted', 'tiny']); - $el.attr('data-variation', [...variations].join(' ')).popup(); - }); - $('.top.menu .tooltip').popup({ onShow() { if ($('.top.menu .menu.transition').hasClass('visible')) { diff --git a/web_src/js/features/repo-diff.js b/web_src/js/features/repo-diff.js index 8403a2fd42..92d8ecfc86 100644 --- a/web_src/js/features/repo-diff.js +++ b/web_src/js/features/repo-diff.js @@ -3,6 +3,7 @@ import {initCompReactionSelector} from './comp/ReactionSelector.js'; import {initRepoIssueContentHistory} from './repo-issue-content.js'; import {validateTextareaNonEmpty} from './comp/EasyMDE.js'; import {initViewedCheckboxListenerFor, countAndUpdateViewedFiles} from './pull-view-file.js'; +import {initPopup} from './common-global.js'; const {csrfToken} = window.config; @@ -52,6 +53,7 @@ export function initRepoDiffConversationForm() { const newConversationHolder = $(await $.post(form.attr('action'), form.serialize())); const {path, side, idx} = newConversationHolder.data(); + initPopup(newConversationHolder.find('.tooltip')); form.closest('.conversation-holder').replaceWith(newConversationHolder); if (form.closest('tr').data('line-type') === 'same') { $(`[data-path="${path}"] a.add-code-comment[data-idx="${idx}"]`).addClass('invisible'); diff --git a/web_src/js/index.js b/web_src/js/index.js index 0568da64ae..69bc3342a5 100644 --- a/web_src/js/index.js +++ b/web_src/js/index.js @@ -55,6 +55,7 @@ import { initGlobalEnterQuickSubmit, initGlobalFormDirtyLeaveConfirm, initGlobalLinkActions, + initGlobalPopups, initHeadNavbarContentToggle, } from './features/common-global.js'; import {initRepoTopicBar} from './features/repo-home.js'; @@ -99,6 +100,7 @@ initVueEnv(); $(document).ready(() => { initGlobalCommon(); + initGlobalPopups(); initGlobalButtonClickOnEnter(); initGlobalButtons(); initGlobalCopyToClipboardListener(); From 01a4fb0ae6328ed2f6c6fa99b65180afedfa374c Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Tue, 5 Jul 2022 23:01:01 +0800 Subject: [PATCH 025/246] Bypass Firefox (iOS) bug (#20244) (#20250) Backport #20244 * https://github.com/go-gitea/gitea/issues/20240 At the moment, Firefox (iOS) (10x) has an engine bug. See https://github.com/go-gitea/gitea/issues/20240 If a script inserts a newly created (and content changed) element into DOM, there will be a nonsense error event reporting: Script error: line 0, col 0. This PR ignores such nonsense error event. Fix #20240 --- web_src/js/bootstrap.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/web_src/js/bootstrap.js b/web_src/js/bootstrap.js index cf13b2a559..213c9e41df 100644 --- a/web_src/js/bootstrap.js +++ b/web_src/js/bootstrap.js @@ -20,6 +20,11 @@ export function showGlobalErrorMessage(msg) { * @param {ErrorEvent} e */ function processWindowErrorEvent(e) { + if (!e.error && e.lineno === 0 && e.colno === 0 && e.filename === '' && window.navigator.userAgent.includes('FxiOS/')) { + // At the moment, Firefox (iOS) (10x) has an engine bug. See https://github.com/go-gitea/gitea/issues/20240 + // If a script inserts a newly created (and content changed) element into DOM, there will be a nonsense error event reporting: Script error: line 0, col 0. + return; // ignore such nonsense error event + } showGlobalErrorMessage(`JavaScript error: ${e.message} (${e.filename} @ ${e.lineno}:${e.colno}). Open browser console to see more details.`); } From c88a59bb23b324cc32b33e4275d0dc3eeb6776de Mon Sep 17 00:00:00 2001 From: zeripath Date: Tue, 5 Jul 2022 16:15:56 +0100 Subject: [PATCH 026/246] Adjust max-widths for the repository file table (#20243) (#20247) Backport #20243 Adjust the max-widths for the repository file table to allow for nicer resizing of the names and commit messages. Fix #20040 Signed-off-by: Andrew Thornton ## Screenshots ## MediaXL ![Screenshot from 2022-07-05 10-22-12](https://user-images.githubusercontent.com/1824502/177295867-7ba8cf60-8f61-4227-892f-e5a0477e4146.png) ## MediaLg ![Screenshot from 2022-07-05 10-24-37](https://user-images.githubusercontent.com/1824502/177296301-e066e206-10f7-4a15-a68b-0f772a95f369.png) ## MediaMd ![Screenshot from 2022-07-05 10-23-03](https://user-images.githubusercontent.com/1824502/177295965-69397649-16ca-456a-bc0c-ed507fcb7f44.png) ## MediaSm ![Screenshot from 2022-07-05 10-26-44](https://user-images.githubusercontent.com/1824502/177296700-ca2a853b-c47b-4592-baf4-4bc08a7e1c9c.png) Co-authored-by: Lunny Xiao --- web_src/less/_repository.less | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/web_src/less/_repository.less b/web_src/less/_repository.less index bc1affb770..7bbd412b61 100644 --- a/web_src/less/_repository.less +++ b/web_src/less/_repository.less @@ -352,11 +352,31 @@ overflow: initial; &.name { - max-width: 150px; + @media @mediaXl { + max-width: 150px; + } + @media @mediaLg { + max-width: 200px; + } + @media @mediaMd { + max-width: 300px; + } + width: 33%; + + max-width: calc(100vw - 140px); } &.message { - max-width: 400px; + @media @mediaXl { + max-width: 400px; + } + @media @mediaLg { + max-width: 350px; + } + @media @mediaMd { + max-width: 250px; + } + width: 66%; } &.age { From 76ba23a14fa39036eb4d8c96b3326ea4842ef9e7 Mon Sep 17 00:00:00 2001 From: zeripath Date: Tue, 5 Jul 2022 16:58:10 +0100 Subject: [PATCH 027/246] Display full name (#20171) (#20246) Backport #20171 The setting `DEFAULT_SHOW_FULL_NAME` promises to use the user's full name everywhere it can be used. Unfortunately the function `*user_model.User.ShortName()` currently uses the `.Name` instead - but this should also use the `.FullName()`. Therefore we should make `*user_model.User.ShortName()` base its pre-shortened name on the `.FullName()` function. Co-authored-by: Baekjun Kim <36013575+kimbj95@users.noreply.github.com> --- models/user/user.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/models/user/user.go b/models/user/user.go index f7d457b91b..a2c759cf80 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -485,6 +485,9 @@ func (u *User) GitName() string { // ShortName ellipses username to length func (u *User) ShortName(length int) string { + if setting.UI.DefaultShowFullName && len(u.FullName) > 0 { + return base.EllipsisString(u.FullName, length) + } return base.EllipsisString(u.Name, length) } From 42be548ecc1b8e81a62d2e14927d9c9851d31b5c Mon Sep 17 00:00:00 2001 From: zeripath Date: Wed, 6 Jul 2022 20:51:40 +0100 Subject: [PATCH 028/246] EscapeFilter the group dn membership (#20200) (#20254) Backport #20200 The uid provided to the group filter must be properly escaped using the provided ldap.EscapeFilter function. Fix #20181 Signed-off-by: Andrew Thornton --- services/auth/source/ldap/source_search.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/auth/source/ldap/source_search.go b/services/auth/source/ldap/source_search.go index d01fd14c8b..1c0eb783d9 100644 --- a/services/auth/source/ldap/source_search.go +++ b/services/auth/source/ldap/source_search.go @@ -199,7 +199,7 @@ func checkRestricted(l *ldap.Conn, ls *Source, userDN string) bool { // List all group memberships of a user func (ls *Source) listLdapGroupMemberships(l *ldap.Conn, uid string) []string { var ldapGroups []string - groupFilter := fmt.Sprintf("(%s=%s)", ls.GroupMemberUID, uid) + groupFilter := fmt.Sprintf("(%s=%s)", ls.GroupMemberUID, ldap.EscapeFilter(uid)) result, err := l.Search(ldap.NewSearchRequest( ls.GroupDN, ldap.ScopeWholeSubtree, From a92d247fdd09446ec8d6f5d20328fcf5b4149a7b Mon Sep 17 00:00:00 2001 From: zeripath Date: Wed, 6 Jul 2022 22:03:23 +0100 Subject: [PATCH 029/246] Only show Followers that current user can access (#20220) (#20252) Backport #20220 Users who are following or being followed by a user should only be displayed if the viewing user can see them. Signed-off-by: Andrew Thornton --- models/user/user.go | 59 ++++++++++++++++++++++++++++----- routers/api/v1/user/follower.go | 8 ++--- routers/web/user/profile.go | 8 ++--- 3 files changed, 58 insertions(+), 17 deletions(-) diff --git a/models/user/user.go b/models/user/user.go index a2c759cf80..490e7223ce 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -316,37 +316,45 @@ func (u *User) GenerateEmailActivateCode(email string) string { } // GetUserFollowers returns range of user's followers. -func GetUserFollowers(u *User, listOptions db.ListOptions) ([]*User, error) { - sess := db.GetEngine(db.DefaultContext). +func GetUserFollowers(ctx context.Context, u, viewer *User, listOptions db.ListOptions) ([]*User, int64, error) { + sess := db.GetEngine(ctx). + Select("`user`.*"). + Join("LEFT", "follow", "`user`.id=follow.user_id"). Where("follow.follow_id=?", u.ID). - Join("LEFT", "follow", "`user`.id=follow.user_id") + And(isUserVisibleToViewerCond(viewer)) if listOptions.Page != 0 { sess = db.SetSessionPagination(sess, &listOptions) users := make([]*User, 0, listOptions.PageSize) - return users, sess.Find(&users) + count, err := sess.FindAndCount(&users) + return users, count, err } users := make([]*User, 0, 8) - return users, sess.Find(&users) + count, err := sess.FindAndCount(&users) + return users, count, err } // GetUserFollowing returns range of user's following. -func GetUserFollowing(u *User, listOptions db.ListOptions) ([]*User, error) { +func GetUserFollowing(ctx context.Context, u, viewer *User, listOptions db.ListOptions) ([]*User, int64, error) { sess := db.GetEngine(db.DefaultContext). + Select("`user`.*"). + Join("LEFT", "follow", "`user`.id=follow.follow_id"). Where("follow.user_id=?", u.ID). - Join("LEFT", "follow", "`user`.id=follow.follow_id") + And(isUserVisibleToViewerCond(viewer)) if listOptions.Page != 0 { sess = db.SetSessionPagination(sess, &listOptions) users := make([]*User, 0, listOptions.PageSize) - return users, sess.Find(&users) + count, err := sess.FindAndCount(&users) + return users, count, err } users := make([]*User, 0, 8) - return users, sess.Find(&users) + count, err := sess.FindAndCount(&users) + return users, count, err } // NewGitSig generates and returns the signature of given user. @@ -1222,6 +1230,39 @@ func GetAdminUser() (*User, error) { return &admin, nil } +func isUserVisibleToViewerCond(viewer *User) builder.Cond { + if viewer != nil && viewer.IsAdmin { + return builder.NewCond() + } + + if viewer == nil || viewer.IsRestricted { + return builder.Eq{ + "`user`.visibility": structs.VisibleTypePublic, + } + } + + return builder.Neq{ + "`user`.visibility": structs.VisibleTypePrivate, + }.Or( + builder.In("`user`.id", + builder. + Select("`follow`.user_id"). + From("follow"). + Where(builder.Eq{"`follow`.follow_id": viewer.ID})), + builder.In("`user`.id", + builder. + Select("`team_user`.uid"). + From("team_user"). + Join("INNER", "`team_user` AS t2", "`team_user`.id = `t2`.id"). + Where(builder.Eq{"`t2`.uid": viewer.ID})), + builder.In("`user`.id", + builder. + Select("`team_user`.uid"). + From("team_user"). + Join("INNER", "`team_user` AS t2", "`team_user`.org_id = `t2`.org_id"). + Where(builder.Eq{"`t2`.uid": viewer.ID}))) +} + // IsUserVisibleToViewer check if viewer is able to see user profile func IsUserVisibleToViewer(ctx context.Context, u, viewer *User) bool { if viewer != nil && viewer.IsAdmin { diff --git a/routers/api/v1/user/follower.go b/routers/api/v1/user/follower.go index 3c81b27f8d..22f8f40e1c 100644 --- a/routers/api/v1/user/follower.go +++ b/routers/api/v1/user/follower.go @@ -24,13 +24,13 @@ func responseAPIUsers(ctx *context.APIContext, users []*user_model.User) { } func listUserFollowers(ctx *context.APIContext, u *user_model.User) { - users, err := user_model.GetUserFollowers(u, utils.GetListOptions(ctx)) + users, count, err := user_model.GetUserFollowers(ctx, u, ctx.Doer, utils.GetListOptions(ctx)) if err != nil { ctx.Error(http.StatusInternalServerError, "GetUserFollowers", err) return } - ctx.SetTotalCountHeader(int64(u.NumFollowers)) + ctx.SetTotalCountHeader(count) responseAPIUsers(ctx, users) } @@ -86,13 +86,13 @@ func ListFollowers(ctx *context.APIContext) { } func listUserFollowing(ctx *context.APIContext, u *user_model.User) { - users, err := user_model.GetUserFollowing(u, utils.GetListOptions(ctx)) + users, count, err := user_model.GetUserFollowing(ctx, u, ctx.Doer, utils.GetListOptions(ctx)) if err != nil { ctx.Error(http.StatusInternalServerError, "GetUserFollowing", err) return } - ctx.SetTotalCountHeader(int64(u.NumFollowing)) + ctx.SetTotalCountHeader(count) responseAPIUsers(ctx, users) } diff --git a/routers/web/user/profile.go b/routers/web/user/profile.go index 609f3242c6..6f23d239e2 100644 --- a/routers/web/user/profile.go +++ b/routers/web/user/profile.go @@ -157,7 +157,7 @@ func Profile(ctx *context.Context) { switch tab { case "followers": - items, err := user_model.GetUserFollowers(ctx.ContextUser, db.ListOptions{ + items, count, err := user_model.GetUserFollowers(ctx, ctx.ContextUser, ctx.Doer, db.ListOptions{ PageSize: setting.UI.User.RepoPagingNum, Page: page, }) @@ -167,9 +167,9 @@ func Profile(ctx *context.Context) { } ctx.Data["Cards"] = items - total = ctx.ContextUser.NumFollowers + total = int(count) case "following": - items, err := user_model.GetUserFollowing(ctx.ContextUser, db.ListOptions{ + items, count, err := user_model.GetUserFollowing(ctx, ctx.ContextUser, ctx.Doer, db.ListOptions{ PageSize: setting.UI.User.RepoPagingNum, Page: page, }) @@ -179,7 +179,7 @@ func Profile(ctx *context.Context) { } ctx.Data["Cards"] = items - total = ctx.ContextUser.NumFollowing + total = int(count) case "activity": ctx.Data["Feeds"], err = models.GetFeeds(ctx, models.GetFeedsOptions{ RequestedUser: ctx.ContextUser, From 654c173b9d01755033c8b295f2ef2083a95c3549 Mon Sep 17 00:00:00 2001 From: Gusted Date: Thu, 7 Jul 2022 14:56:50 +0200 Subject: [PATCH 030/246] Fix NPE when using non-numeric (#20277) (#20278) - Backport (#20277 - This code is only valid when `refNumeric` exist(otherwise we didn't find such numeric PR and can skip that check) and give a free-pas to the "BEFORE" check when `ref` is nil. - Resolves #20109 --- modules/markup/html.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/markup/html.go b/modules/markup/html.go index 69d9ba3ef2..6071180501 100644 --- a/modules/markup/html.go +++ b/modules/markup/html.go @@ -841,9 +841,10 @@ func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) { // Repos with external issue trackers might still need to reference local PRs // We need to concern with the first one that shows up in the text, whichever it is - if hasExtTrackFormat && !isNumericStyle { + if hasExtTrackFormat && !isNumericStyle && refNumeric != nil { // If numeric (PR) was found, and it was BEFORE the non-numeric pattern, use that - if foundNumeric && refNumeric.RefLocation.Start < ref.RefLocation.Start { + // Allow a free-pass when non-numeric pattern wasn't found. + if found && (ref == nil || refNumeric.RefLocation.Start < ref.RefLocation.Start) { found = foundNumeric ref = refNumeric } From 039a60225a4b71e0e880db3dfd7b640fa02f5e66 Mon Sep 17 00:00:00 2001 From: zeripath Date: Thu, 7 Jul 2022 16:48:03 +0100 Subject: [PATCH 031/246] Make notification bell more prominent on mobile (#20108, #20236, #20251) (#20269) Backport #20108 Backport #20236 Backport #20251 Make notification bell more prominent on mobile Co-authored-by: Andrew Thornton Co-authored-by: Tyrone Yeh Signed-off-by: Andrew Thornton --- templates/base/head_navbar.tmpl | 19 +++++++++++++++---- web_src/less/_base.less | 4 ++-- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/templates/base/head_navbar.tmpl b/templates/base/head_navbar.tmpl index fab1d2d0b1..e367575bca 100644 --- a/templates/base/head_navbar.tmpl +++ b/templates/base/head_navbar.tmpl @@ -1,8 +1,22 @@ - + {{svg "octicon-bell"}} - {{.i18n.Tr "notifications"}} - {{$notificationUnreadCount := 0}} - {{if .NotificationUnreadCount}}{{$notificationUnreadCount = call .NotificationUnreadCount}}{{end}} {{$notificationUnreadCount}} diff --git a/web_src/less/_base.less b/web_src/less/_base.less index 78f32956ef..638801e392 100644 --- a/web_src/less/_base.less +++ b/web_src/less/_base.less @@ -1329,7 +1329,7 @@ footer { @media @mediaMdAndUp { .mobile-only, .ui.button.mobile-only { - display: none; + display: none !important; } // has the same behaviour of sr-only, hiding the content for @@ -1341,7 +1341,7 @@ footer { @media @mediaSm { .not-mobile { - display: none; + display: none !important; } } From 5e5ff77ed7731ccc608ba4851452a77eab285b9c Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Fri, 8 Jul 2022 21:44:36 +0800 Subject: [PATCH 032/246] Use git.HOME_PATH for Git HOME directory (#20114) (#20293) Before, in #19732, the old home directory is not correct. This PR introduces a new config option for git home: git.HOME_PATH, which is default to %(APP_DATA_PATH)/home And pass env GNUPGHOME to git command, force Gitea to use a stable GNUPGHOME directory --- custom/conf/app.example.ini | 5 ++- .../doc/advanced/config-cheat-sheet.en-us.md | 2 ++ docs/content/doc/advanced/signing.en-us.md | 7 ++-- models/unittest/testdb.go | 2 ++ modules/git/command.go | 35 +++++++++++++------ modules/git/git.go | 22 +++++++----- modules/git/git_test.go | 6 ++-- modules/setting/git.go | 13 ++++++- 8 files changed, 64 insertions(+), 28 deletions(-) diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index b5b8095373..4585966d6e 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -617,7 +617,10 @@ ROUTER = console ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; The path of git executable. If empty, Gitea searches through the PATH environment. -PATH = +;PATH = +;; +;; The HOME directory for Git +;HOME_PATH = %(APP_DATA_PATH)/home ;; ;; Disables highlight of added and removed changes ;DISABLE_DIFF_HIGHLIGHT = false diff --git a/docs/content/doc/advanced/config-cheat-sheet.en-us.md b/docs/content/doc/advanced/config-cheat-sheet.en-us.md index 3834a3b6b6..2f7bb3aa98 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md +++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md @@ -947,6 +947,8 @@ Default templates for project boards: ## Git (`git`) - `PATH`: **""**: The path of Git executable. If empty, Gitea searches through the PATH environment. +- `HOME_PATH`: **%(APP_DATA_PATH)/home**: The HOME directory for Git. + This directory will be used to contain the `.gitconfig` and possible `.gnupg` directories that Gitea's git calls will use. If you can confirm Gitea is the only application running in this environment, you can set it to the normal home directory for Gitea user. - `DISABLE_DIFF_HIGHLIGHT`: **false**: Disables highlight of added and removed changes. - `MAX_GIT_DIFF_LINES`: **1000**: Max number of lines allowed of a single file in diff view. - `MAX_GIT_DIFF_LINE_CHARACTERS`: **5000**: Max character count per line highlighted in diff view. diff --git a/docs/content/doc/advanced/signing.en-us.md b/docs/content/doc/advanced/signing.en-us.md index 8ae2f94e9e..9ef94deb75 100644 --- a/docs/content/doc/advanced/signing.en-us.md +++ b/docs/content/doc/advanced/signing.en-us.md @@ -97,10 +97,11 @@ repositories, `SIGNING_KEY=default` could be used to provide different signing keys on a per-repository basis. However, this is clearly not an ideal UI and therefore subject to change. -**Since 1.17**, Gitea runs git in its own home directory `[repository].ROOT` and uses its own config `{[repository].ROOT}/.gitconfig`. +**Since 1.17**, Gitea runs git in its own home directory `[git].HOME_PATH` (default to `%(APP_DATA_PATH)/home`) +and uses its own config `{[git].HOME_PATH}/.gitconfig`. If you have your own customized git config for Gitea, you should set these configs in system git config (aka `/etc/gitconfig`) -or the Gitea internal git config `{[repository].ROOT}/.gitconfig`. -Related home files for git command (like `.gnupg`) should also be put in Gitea's git home directory `[repository].ROOT`. +or the Gitea internal git config `{[git].HOME_PATH}/.gitconfig`. +Related home files for git command (like `.gnupg`) should also be put in Gitea's git home directory `[git].HOME_PATH`. ### `INITIAL_COMMIT` diff --git a/models/unittest/testdb.go b/models/unittest/testdb.go index baea46dbce..7f9af55374 100644 --- a/models/unittest/testdb.go +++ b/models/unittest/testdb.go @@ -107,6 +107,8 @@ func MainTest(m *testing.M, testOpts *TestOptions) { setting.Packages.Storage.Path = filepath.Join(setting.AppDataPath, "packages") + setting.Git.HomePath = filepath.Join(setting.AppDataPath, "home") + if err = storage.Init(); err != nil { fatalTestError("storage.Init: %v\n", err) } diff --git a/modules/git/command.go b/modules/git/command.go index d71497f1d7..a1bacbb707 100644 --- a/modules/git/command.go +++ b/modules/git/command.go @@ -105,23 +105,36 @@ type RunOpts struct { PipelineFunc func(context.Context, context.CancelFunc) error } +func commonBaseEnvs() []string { + // at the moment, do not set "GIT_CONFIG_NOSYSTEM", users may have put some configs like "receive.certNonceSeed" in it + envs := []string{ + "HOME=" + HomeDir(), // make Gitea use internal git config only, to prevent conflicts with user's git config + "GIT_NO_REPLACE_OBJECTS=1", // ignore replace references (https://git-scm.com/docs/git-replace) + } + + // some environment variables should be passed to git command + passThroughEnvKeys := []string{ + "GNUPGHOME", // git may call gnupg to do commit signing + } + for _, key := range passThroughEnvKeys { + if val, ok := os.LookupEnv(key); ok { + envs = append(envs, key+"="+val) + } + } + return envs +} + // CommonGitCmdEnvs returns the common environment variables for a "git" command. func CommonGitCmdEnvs() []string { - // at the moment, do not set "GIT_CONFIG_NOSYSTEM", users may have put some configs like "receive.certNonceSeed" in it - return []string{ - fmt.Sprintf("LC_ALL=%s", DefaultLocale), - "GIT_TERMINAL_PROMPT=0", // avoid prompting for credentials interactively, supported since git v2.3 - "GIT_NO_REPLACE_OBJECTS=1", // ignore replace references (https://git-scm.com/docs/git-replace) - "HOME=" + HomeDir(), // make Gitea use internal git config only, to prevent conflicts with user's git config - } + return append(commonBaseEnvs(), []string{ + "LC_ALL=" + DefaultLocale, + "GIT_TERMINAL_PROMPT=0", // avoid prompting for credentials interactively, supported since git v2.3 + }...) } // CommonCmdServEnvs is like CommonGitCmdEnvs but it only returns minimal required environment variables for the "gitea serv" command func CommonCmdServEnvs() []string { - return []string{ - "GIT_NO_REPLACE_OBJECTS=1", // ignore replace references (https://git-scm.com/docs/git-replace) - "HOME=" + HomeDir(), // make Gitea use internal git config only, to prevent conflicts with user's git config - } + return commonBaseEnvs() } // Run runs the command with the RunOpts diff --git a/modules/git/git.go b/modules/git/git.go index 0652a75f9a..3bc08ff93b 100644 --- a/modules/git/git.go +++ b/modules/git/git.go @@ -11,6 +11,7 @@ import ( "fmt" "os" "os/exec" + "path/filepath" "regexp" "runtime" "strings" @@ -19,7 +20,6 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" - "github.com/hashicorp/go-version" ) @@ -126,8 +126,8 @@ func VersionInfo() string { } func checkInit() error { - if setting.RepoRootPath == "" { - return errors.New("can not init Git's HomeDir (RepoRootPath is empty), the setting and git modules are not initialized correctly") + if setting.Git.HomePath == "" { + return errors.New("unable to init Git's HomeDir, incorrect initialization of the setting and git modules") } if DefaultContext != nil { log.Warn("git module has been initialized already, duplicate init should be fixed") @@ -137,14 +137,14 @@ func checkInit() error { // HomeDir is the home dir for git to store the global config file used by Gitea internally func HomeDir() string { - if setting.RepoRootPath == "" { + if setting.Git.HomePath == "" { // strict check, make sure the git module is initialized correctly. // attention: when the git module is called in gitea sub-command (serv/hook), the log module is not able to show messages to users. // for example: if there is gitea git hook code calling git.NewCommand before git.InitXxx, the integration test won't show the real failure reasons. - log.Fatal("can not get Git's HomeDir (RepoRootPath is empty), the setting and git modules are not initialized correctly") + log.Fatal("Unable to init Git's HomeDir, incorrect initialization of the setting and git modules") return "" } - return setting.RepoRootPath + return setting.Git.HomePath } // InitSimple initializes git module with a very simple step, no config changes, no global command arguments. @@ -175,11 +175,15 @@ func InitOnceWithSync(ctx context.Context) (err error) { } initOnce.Do(func() { - err = InitSimple(ctx) - if err != nil { + if err = InitSimple(ctx); err != nil { return } + // when git works with gnupg (commit signing), there should be a stable home for gnupg commands + if _, ok := os.LookupEnv("GNUPGHOME"); !ok { + _ = os.Setenv("GNUPGHOME", filepath.Join(HomeDir(), ".gnupg")) + } + // Since git wire protocol has been released from git v2.18 if setting.Git.EnableAutoGitWireProtocol && CheckGitVersionAtLeast("2.18") == nil { globalCommandArgs = append(globalCommandArgs, "-c", "protocol.version=2") @@ -206,7 +210,7 @@ func InitOnceWithSync(ctx context.Context) (err error) { // syncGitConfig only modifies gitconfig, won't change global variables (otherwise there will be data-race problem) func syncGitConfig() (err error) { if err = os.MkdirAll(HomeDir(), os.ModePerm); err != nil { - return fmt.Errorf("unable to create directory %s, err: %w", setting.RepoRootPath, err) + return fmt.Errorf("unable to prepare git home directory %s, err: %w", HomeDir(), err) } // Git requires setting user.name and user.email in order to commit changes - old comment: "if they're not set just add some defaults" diff --git a/modules/git/git_test.go b/modules/git/git_test.go index c1a9ec351a..c5a63de064 100644 --- a/modules/git/git_test.go +++ b/modules/git/git_test.go @@ -21,12 +21,12 @@ import ( func testRun(m *testing.M) error { _ = log.NewLogger(1000, "console", "console", `{"level":"trace","stacktracelevel":"NONE","stderr":true}`) - repoRootPath, err := os.MkdirTemp(os.TempDir(), "repos") + gitHomePath, err := os.MkdirTemp(os.TempDir(), "git-home") if err != nil { return fmt.Errorf("unable to create temp dir: %w", err) } - defer util.RemoveAll(repoRootPath) - setting.RepoRootPath = repoRootPath + defer util.RemoveAll(gitHomePath) + setting.Git.HomePath = gitHomePath if err = InitOnceWithSync(context.Background()); err != nil { return fmt.Errorf("failed to call Init: %w", err) diff --git a/modules/setting/git.go b/modules/setting/git.go index 9b2698f01e..266bbc3c5a 100644 --- a/modules/setting/git.go +++ b/modules/setting/git.go @@ -5,6 +5,7 @@ package setting import ( + "path/filepath" "time" "code.gitea.io/gitea/modules/log" @@ -13,6 +14,7 @@ import ( // Git settings var Git = struct { Path string + HomePath string DisableDiffHighlight bool MaxGitDiffLines int MaxGitDiffLineCharacters int @@ -67,7 +69,16 @@ var Git = struct { } func newGit() { - if err := Cfg.Section("git").MapTo(&Git); err != nil { + sec := Cfg.Section("git") + + if err := sec.MapTo(&Git); err != nil { log.Fatal("Failed to map Git settings: %v", err) } + + Git.HomePath = sec.Key("HOME_PATH").MustString("home") + if !filepath.IsAbs(Git.HomePath) { + Git.HomePath = filepath.Join(AppDataPath, Git.HomePath) + } else { + Git.HomePath = filepath.Clean(Git.HomePath) + } } From d371ced49db7728b69a60b0ac2eb11467045be8c Mon Sep 17 00:00:00 2001 From: Gusted Date: Sat, 9 Jul 2022 20:01:44 +0200 Subject: [PATCH 033/246] Store read access in access for team repo's (#20275) (#20276) Backport #20275 Currently when a Team has read access to a organization's non-private repository, their access(in the `access` table) won't be stored in the database. This cause issues for code that rely on read access being stored, like retrieving all users who have read permission to that repository(even though this is confusing as this doesn't include all registered users). So from now-on if we see that the repository is owned by a organization don't increase the `minMode` to write permission. Resolves #20083 --- models/perm/access/access.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/models/perm/access/access.go b/models/perm/access/access.go index 7647519025..0e5e4ff2bb 100644 --- a/models/perm/access/access.go +++ b/models/perm/access/access.go @@ -86,7 +86,13 @@ func updateUserAccess(accessMap map[int64]*userAccess, user *user_model.User, mo // FIXME: do cross-comparison so reduce deletions and additions to the minimum? func refreshAccesses(ctx context.Context, repo *repo_model.Repository, accessMap map[int64]*userAccess) (err error) { minMode := perm.AccessModeRead - if !repo.IsPrivate { + if err := repo.GetOwner(ctx); err != nil { + return fmt.Errorf("GetOwner: %v", err) + } + + // If the repo isn't private and isn't owned by a organization, + // increase the minMode to Write. + if !repo.IsPrivate && !repo.Owner.IsOrganization() { minMode = perm.AccessModeWrite } From 1d02a9c9fbb84bca9e6fb1dd595b07e2524c3766 Mon Sep 17 00:00:00 2001 From: Gusted Date: Sat, 9 Jul 2022 22:39:38 +0200 Subject: [PATCH 034/246] Bump goldmark to v1.4.13 (#20300) (#20301) Backport #20300 - Update goldmark to v1.4.13 to fix a issue with quotes after a empty list item(See https://github.com/yuin/goldmark/issues/313) and downstream issue https://codeberg.org/Codeberg/Community/issues/645 --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index da59b3f42e..0e5f68c78b 100644 --- a/go.mod +++ b/go.mod @@ -85,7 +85,7 @@ require ( github.com/urfave/cli v1.22.9 github.com/xanzy/go-gitlab v0.64.0 github.com/yohcop/openid-go v1.0.0 - github.com/yuin/goldmark v1.4.12 + github.com/yuin/goldmark v1.4.13 github.com/yuin/goldmark-highlighting v0.0.0-20220208100518-594be1970594 github.com/yuin/goldmark-meta v1.1.0 go.jolheiser.com/hcaptcha v0.0.4 diff --git a/go.sum b/go.sum index 843f45ad4d..aadf65f996 100644 --- a/go.sum +++ b/go.sum @@ -1540,8 +1540,8 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.5/go.mod h1:rmuwmfZ0+bvzB24eSC//bk1R1Zp3hM0OXYv/G2LIilg= -github.com/yuin/goldmark v1.4.12 h1:6hffw6vALvEDqJ19dOJvJKOoAOKe4NDaTqvd2sktGN0= -github.com/yuin/goldmark v1.4.12/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark-highlighting v0.0.0-20220208100518-594be1970594 h1:yHfZyN55+5dp1wG7wDKv8HQ044moxkyGq12KFFMFDxg= github.com/yuin/goldmark-highlighting v0.0.0-20220208100518-594be1970594/go.mod h1:U9ihbh+1ZN7fR5Se3daSPoz1CGF9IYtSvWwVQtnzGHU= github.com/yuin/goldmark-meta v1.1.0 h1:pWw+JLHGZe8Rk0EGsMVssiNb/AaPMHfSRszZeUeiOUc= From 317c565e775a45ff90e4d947b0fb18cb480e04fe Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Sun, 10 Jul 2022 18:09:42 +0800 Subject: [PATCH 035/246] Do not create empty ".ssh" directory when loading config (#20289) (#20298) Backport #20289 The code is as old as back to 2016, creating the directory automatically is not correct IMO. In other places for ssh key writing (RewriteAllPrincipalKeys / appendAuthorizedKeysToFile, etc), the directory will still be created when updating the keys. This PR will resolve the confusing and annoying problem: the dummy and empty ".ssh" directory in new git home --- modules/setting/setting.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/modules/setting/setting.go b/modules/setting/setting.go index 88f306b3fa..b9881fa5e6 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -862,9 +862,7 @@ func loadFromConf(allowEmpty bool, extraConfig string) { SSH.AuthorizedPrincipalsAllow, SSH.AuthorizedPrincipalsEnabled = parseAuthorizedPrincipalsAllow(sec.Key("SSH_AUTHORIZED_PRINCIPALS_ALLOW").Strings(",")) if !SSH.Disabled && !SSH.StartBuiltinServer { - if err := os.MkdirAll(SSH.RootPath, 0o700); err != nil { - log.Fatal("Failed to create '%s': %v", SSH.RootPath, err) - } else if err = os.MkdirAll(SSH.KeyTestPath, 0o644); err != nil { + if err = os.MkdirAll(SSH.KeyTestPath, 0o644); err != nil { log.Fatal("Failed to create '%s': %v", SSH.KeyTestPath, err) } From c556a83c352999cc4092fa62c31d708b52a3b8be Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Sun, 10 Jul 2022 20:48:35 +0200 Subject: [PATCH 036/246] Prevent "empty" scrollbars on Firefox (#20294) (#20308) Addition to: Show scrollbar when necessary #20142 Fixes the "empty" scrollbars with Firefox. --- web_src/less/_admin.less | 2 +- web_src/less/_repository.less | 4 ++-- web_src/less/_user.less | 2 +- web_src/less/features/gitgraph.less | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/web_src/less/_admin.less b/web_src/less/_admin.less index 3af0c57d11..b18bc7c5e0 100644 --- a/web_src/less/_admin.less +++ b/web_src/less/_admin.less @@ -12,7 +12,7 @@ .table.segment { padding: 0; font-size: 13px; - overflow-x: scroll; + overflow-x: auto; &:not(.striped) { thead { diff --git a/web_src/less/_repository.less b/web_src/less/_repository.less index 7bbd412b61..0ad937f028 100644 --- a/web_src/less/_repository.less +++ b/web_src/less/_repository.less @@ -3352,7 +3352,7 @@ td.blob-excerpt { .commit-header-row { .ui.horizontal.list { width: 100%; - overflow-x: scroll; + overflow-x: auto; margin-top: 2px; .item { @@ -3401,7 +3401,7 @@ td.blob-excerpt { } .commit-table { - overflow-x: scroll; + overflow-x: auto; td.sha, th.sha { diff --git a/web_src/less/_user.less b/web_src/less/_user.less index 4576d1c9e7..a9b6c02fb7 100644 --- a/web_src/less/_user.less +++ b/web_src/less/_user.less @@ -170,5 +170,5 @@ } #notification_div .tab.segment { - overflow-x: scroll; + overflow-x: auto; } diff --git a/web_src/less/features/gitgraph.less b/web_src/less/features/gitgraph.less index 25dcc02159..efb6071e49 100644 --- a/web_src/less/features/gitgraph.less +++ b/web_src/less/features/gitgraph.less @@ -1,5 +1,5 @@ #git-graph-container { - overflow-x: scroll; + overflow-x: auto; width: 100%; min-height: 350px; From 54ef65886133227b77c41eb4e54f9a1b97f2a850 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Mon, 11 Jul 2022 23:27:51 +0800 Subject: [PATCH 037/246] Refactor SSH init code, fix directory creation for TrustedUserCAKeys file (#20299) (#20306) Backport #20299. Follow #20298. Only the `GlobalInitInstalled` function should prepare the SSH files for external server or starts the builtin server. * `trustedUserCaKeys` is removed, use `SSH.TrustedUserCAKeys` directly * introduce `ssh.Init`, move the SSH init code from `routers/init.go` to it * `ssh.Init` will start builtin SSH server or prepare external SSH server files --- modules/setting/setting.go | 21 +++----------- modules/ssh/init.go | 55 +++++++++++++++++++++++++++++++++++++ modules/ssh/ssh_graceful.go | 4 +-- routers/init.go | 12 ++------ 4 files changed, 63 insertions(+), 29 deletions(-) create mode 100644 modules/ssh/init.go diff --git a/modules/setting/setting.go b/modules/setting/setting.go index b9881fa5e6..d9f8a8edd1 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -843,8 +843,9 @@ func loadFromConf(allowEmpty bool, extraConfig string) { SSH.StartBuiltinServer = false } - trustedUserCaKeys := sec.Key("SSH_TRUSTED_USER_CA_KEYS").Strings(",") - for _, caKey := range trustedUserCaKeys { + SSH.TrustedUserCAKeysFile = sec.Key("SSH_TRUSTED_USER_CA_KEYS_FILENAME").MustString(filepath.Join(SSH.RootPath, "gitea-trusted-user-ca-keys.pem")) + + for _, caKey := range SSH.TrustedUserCAKeys { pubKey, _, _, _, err := gossh.ParseAuthorizedKey([]byte(caKey)) if err != nil { log.Fatal("Failed to parse TrustedUserCaKeys: %s %v", caKey, err) @@ -852,7 +853,7 @@ func loadFromConf(allowEmpty bool, extraConfig string) { SSH.TrustedUserCAKeysParsed = append(SSH.TrustedUserCAKeysParsed, pubKey) } - if len(trustedUserCaKeys) > 0 { + if len(SSH.TrustedUserCAKeys) > 0 { // Set the default as email,username otherwise we can leave it empty sec.Key("SSH_AUTHORIZED_PRINCIPALS_ALLOW").MustString("username,email") } else { @@ -861,20 +862,6 @@ func loadFromConf(allowEmpty bool, extraConfig string) { SSH.AuthorizedPrincipalsAllow, SSH.AuthorizedPrincipalsEnabled = parseAuthorizedPrincipalsAllow(sec.Key("SSH_AUTHORIZED_PRINCIPALS_ALLOW").Strings(",")) - if !SSH.Disabled && !SSH.StartBuiltinServer { - if err = os.MkdirAll(SSH.KeyTestPath, 0o644); err != nil { - log.Fatal("Failed to create '%s': %v", SSH.KeyTestPath, err) - } - - if len(trustedUserCaKeys) > 0 && SSH.AuthorizedPrincipalsEnabled { - fname := sec.Key("SSH_TRUSTED_USER_CA_KEYS_FILENAME").MustString(filepath.Join(SSH.RootPath, "gitea-trusted-user-ca-keys.pem")) - if err := os.WriteFile(fname, - []byte(strings.Join(trustedUserCaKeys, "\n")), 0o600); err != nil { - log.Fatal("Failed to create '%s': %v", fname, err) - } - } - } - SSH.MinimumKeySizeCheck = sec.Key("MINIMUM_KEY_SIZE_CHECK").MustBool(SSH.MinimumKeySizeCheck) minimumKeySizes := Cfg.Section("ssh.minimum_key_sizes").Keys() for _, key := range minimumKeySizes { diff --git a/modules/ssh/init.go b/modules/ssh/init.go new file mode 100644 index 0000000000..f6332bb18b --- /dev/null +++ b/modules/ssh/init.go @@ -0,0 +1,55 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "fmt" + "net" + "os" + "path/filepath" + "strconv" + "strings" + + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" +) + +func Init() error { + if setting.SSH.Disabled { + return nil + } + + if setting.SSH.StartBuiltinServer { + Listen(setting.SSH.ListenHost, setting.SSH.ListenPort, setting.SSH.ServerCiphers, setting.SSH.ServerKeyExchanges, setting.SSH.ServerMACs) + log.Info("SSH server started on %s. Cipher list (%v), key exchange algorithms (%v), MACs (%v)", + net.JoinHostPort(setting.SSH.ListenHost, strconv.Itoa(setting.SSH.ListenPort)), + setting.SSH.ServerCiphers, setting.SSH.ServerKeyExchanges, setting.SSH.ServerMACs, + ) + return nil + } + + builtinUnused() + + // FIXME: why 0o644 for a directory ..... + if err := os.MkdirAll(setting.SSH.KeyTestPath, 0o644); err != nil { + return fmt.Errorf("failed to create directory %q for ssh key test: %w", setting.SSH.KeyTestPath, err) + } + + if len(setting.SSH.TrustedUserCAKeys) > 0 && setting.SSH.AuthorizedPrincipalsEnabled { + caKeysFileName := setting.SSH.TrustedUserCAKeysFile + caKeysFileDir := filepath.Dir(caKeysFileName) + + err := os.MkdirAll(caKeysFileDir, 0o700) // SSH.RootPath by default (That is `~/.ssh` in most cases) + if err != nil { + return fmt.Errorf("failed to create directory %q for ssh trusted ca keys: %w", caKeysFileDir, err) + } + + if err := os.WriteFile(caKeysFileName, []byte(strings.Join(setting.SSH.TrustedUserCAKeys, "\n")), 0o600); err != nil { + return fmt.Errorf("failed to write ssh trusted ca keys to %q: %w", caKeysFileName, err) + } + } + + return nil +} diff --git a/modules/ssh/ssh_graceful.go b/modules/ssh/ssh_graceful.go index 98fe17b3bc..9b91baf09e 100644 --- a/modules/ssh/ssh_graceful.go +++ b/modules/ssh/ssh_graceful.go @@ -29,7 +29,7 @@ func listen(server *ssh.Server) { log.Info("SSH Listener: %s Closed", server.Addr) } -// Unused informs our cleanup routine that we will not be using a ssh port -func Unused() { +// builtinUnused informs our cleanup routine that we will not be using a ssh port +func builtinUnused() { graceful.GetManager().InformCleanup() } diff --git a/routers/init.go b/routers/init.go index 2898c44607..72ccf3526c 100644 --- a/routers/init.go +++ b/routers/init.go @@ -6,10 +6,8 @@ package routers import ( "context" - "net" "reflect" "runtime" - "strconv" "code.gitea.io/gitea/models" asymkey_model "code.gitea.io/gitea/models/asymkey" @@ -158,14 +156,8 @@ func GlobalInitInstalled(ctx context.Context) { mustInitCtx(ctx, syncAppPathForGit) - if setting.SSH.StartBuiltinServer { - ssh.Listen(setting.SSH.ListenHost, setting.SSH.ListenPort, setting.SSH.ServerCiphers, setting.SSH.ServerKeyExchanges, setting.SSH.ServerMACs) - log.Info("SSH server started on %s. Cipher list (%v), key exchange algorithms (%v), MACs (%v)", - net.JoinHostPort(setting.SSH.ListenHost, strconv.Itoa(setting.SSH.ListenPort)), - setting.SSH.ServerCiphers, setting.SSH.ServerKeyExchanges, setting.SSH.ServerMACs) - } else { - ssh.Unused() - } + mustInit(ssh.Init) + auth.Init() svg.Init() } From b8ab9298e10fcabe51d5673563359857b3f512fb Mon Sep 17 00:00:00 2001 From: Gusted Date: Tue, 12 Jul 2022 12:51:35 +0000 Subject: [PATCH 038/246] Add write check for creating Commit status (#20332) (#20333) - Backport #20332 - Add write code checks for creating new commit status - Regression from #5314 - Resolves #20331 --- routers/api/v1/api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 03f7a57d5c..ff29d7fb50 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -1010,7 +1010,7 @@ func Routes() *web.Route { }, mustAllowPulls, reqRepoReader(unit.TypeCode), context.ReferencesGitRepo()) m.Group("/statuses", func() { m.Combo("/{sha}").Get(repo.GetCommitStatuses). - Post(reqToken(), bind(api.CreateStatusOption{}), repo.NewCommitStatus) + Post(reqToken(), reqRepoWriter(unit.TypeCode), bind(api.CreateStatusOption{}), repo.NewCommitStatus) }, reqRepoReader(unit.TypeCode)) m.Group("/commits", func() { m.Get("", context.ReferencesGitRepo(), repo.GetAllCommits) From 26f4fe2b442e920902c7aa0f7b960a97af12e1ba Mon Sep 17 00:00:00 2001 From: zeripath Date: Tue, 12 Jul 2022 19:55:25 +0100 Subject: [PATCH 039/246] Correctly handle draft releases without a tag (#20314) (#20335) Backport #20314 `errors.Is(err, git.ErrNotExist{})` is not working Fixes #20313 Co-authored-by: Chongyi Zheng --- services/migrations/gitea_uploader.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/services/migrations/gitea_uploader.go b/services/migrations/gitea_uploader.go index e71b2ca17a..c7a6f9b02f 100644 --- a/services/migrations/gitea_uploader.go +++ b/services/migrations/gitea_uploader.go @@ -7,7 +7,6 @@ package migrations import ( "context" - "errors" "fmt" "io" "os" @@ -268,7 +267,7 @@ func (g *GiteaLocalUploader) CreateReleases(releases ...*base.Release) error { // calc NumCommits if possible if rel.TagName != "" { commit, err := g.gitRepo.GetTagCommit(rel.TagName) - if !errors.Is(err, git.ErrNotExist{}) { + if !git.IsErrNotExist(err) { if err != nil { return fmt.Errorf("GetTagCommit[%v]: %v", rel.TagName, err) } From 66686f6d0eeb24fdfe709dbc0a7d5aa67caa54f9 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 13 Jul 2022 09:47:29 +0800 Subject: [PATCH 040/246] Hide notify mail setting ui if not enabled (#20138) (#20336) Co-authored-by: 6543 <6543@obermui.de> --- routers/web/user/setting/account.go | 1 + templates/user/settings/account.tmpl | 2 ++ 2 files changed, 3 insertions(+) diff --git a/routers/web/user/setting/account.go b/routers/web/user/setting/account.go index 3e96cc7c85..0228b5521b 100644 --- a/routers/web/user/setting/account.go +++ b/routers/web/user/setting/account.go @@ -34,6 +34,7 @@ func Account(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("settings") ctx.Data["PageIsSettingsAccount"] = true ctx.Data["Email"] = ctx.Doer.Email + ctx.Data["EnableNotifyMail"] = setting.Service.EnableNotifyMail loadAccountData(ctx) diff --git a/templates/user/settings/account.tmpl b/templates/user/settings/account.tmpl index 3662ec0fa7..1b3fd136de 100644 --- a/templates/user/settings/account.tmpl +++ b/templates/user/settings/account.tmpl @@ -43,6 +43,7 @@
+ {{end}} {{range .Emails}}
{{if not .IsPrimary}} From 92a43d577d1fbe8e901c303f98a93e0c035de066 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ing=2E=20Jaroslav=20=C5=A0afka?= Date: Wed, 13 Jul 2022 19:36:23 +0200 Subject: [PATCH 041/246] Fix checks in PR for empty commits (#20290) (#20352) Backport #20290 * Fix #19603 * fill HeadCommitID in PullRequest * compare real commits ID as check for merging Signed-off-by: Andrew Thornton Co-authored-by: Andrew Thornton --- integrations/pull_status_test.go | 30 +++++++++++++++++-- models/issues/pull.go | 6 ++++ options/locale/locale_en-US.ini | 3 +- services/pull/check.go | 2 +- services/pull/patch.go | 8 +++++ templates/repo/issue/view_content/pull.tmpl | 16 +++++++--- .../js/components/PullRequestMergeForm.vue | 2 +- 7 files changed, 58 insertions(+), 9 deletions(-) diff --git a/integrations/pull_status_test.go b/integrations/pull_status_test.go index a5247f56ec..33a27cd812 100644 --- a/integrations/pull_status_test.go +++ b/integrations/pull_status_test.go @@ -105,7 +105,11 @@ func doAPICreateCommitStatus(ctx APITestContext, commitID string, status api.Com } } -func TestPullCreate_EmptyChangesWithCommits(t *testing.T) { +func TestPullCreate_EmptyChangesWithDifferentCommits(t *testing.T) { + // Merge must continue if commits SHA are different, even if content is same + // Reason: gitflow and merging master back into develop, where is high possiblity, there are no changes + // but just commit saying "Merge branch". And this meta commit can be also tagged, + // so we need to have this meta commit also in develop branch. onGiteaRun(t, func(t *testing.T, u *url.URL) { session := loginUser(t, "user1") testRepoFork(t, session, "user2", "repo1", "user1", "repo1") @@ -126,6 +130,28 @@ func TestPullCreate_EmptyChangesWithCommits(t *testing.T) { doc := NewHTMLParser(t, resp.Body) text := strings.TrimSpace(doc.doc.Find(".merge-section").Text()) - assert.Contains(t, text, "This branch is equal with the target branch.") + assert.Contains(t, text, "This pull request can be merged automatically.") + }) +} + +func TestPullCreate_EmptyChangesWithSameCommits(t *testing.T) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + session := loginUser(t, "user1") + testRepoFork(t, session, "user2", "repo1", "user1", "repo1") + testCreateBranch(t, session, "user1", "repo1", "branch/master", "status1", http.StatusSeeOther) + url := path.Join("user1", "repo1", "compare", "master...status1") + req := NewRequestWithValues(t, "POST", url, + map[string]string{ + "_csrf": GetCSRF(t, session, url), + "title": "pull request from status1", + }, + ) + session.MakeRequest(t, req, http.StatusSeeOther) + req = NewRequest(t, "GET", "/user1/repo1/pulls/1") + resp := session.MakeRequest(t, req, http.StatusOK) + doc := NewHTMLParser(t, resp.Body) + + text := strings.TrimSpace(doc.doc.Find(".merge-section").Text()) + assert.Contains(t, text, "This branch is already included in the target branch. There is nothing to merge.") }) } diff --git a/models/issues/pull.go b/models/issues/pull.go index f2ca19b03e..0524acb211 100644 --- a/models/issues/pull.go +++ b/models/issues/pull.go @@ -122,6 +122,7 @@ const ( PullRequestStatusManuallyMerged PullRequestStatusError PullRequestStatusEmpty + PullRequestStatusAncestor ) // PullRequestFlow the flow of pull request @@ -423,6 +424,11 @@ func (pr *PullRequest) IsEmpty() bool { return pr.Status == PullRequestStatusEmpty } +// IsAncestor returns true if the Head Commit of this PR is an ancestor of the Base Commit +func (pr *PullRequest) IsAncestor() bool { + return pr.Status == PullRequestStatusAncestor +} + // SetMerged sets a pull request to merged and closes the corresponding issue func (pr *PullRequest) SetMerged(ctx context.Context) (bool, error) { if pr.HasMerged { diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 347022fbdb..3c03dc9c44 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1531,7 +1531,8 @@ pulls.remove_prefix = Remove %s prefix pulls.data_broken = This pull request is broken due to missing fork information. pulls.files_conflicted = This pull request has changes conflicting with the target branch. pulls.is_checking = "Merge conflict checking is in progress. Try again in few moments." -pulls.is_empty = "This branch is equal with the target branch." +pulls.is_ancestor = "This branch is already included in the target branch. There is nothing to merge." +pulls.is_empty = "The changes on this branch are already on the target branch. This will be an empty commit." pulls.required_status_check_failed = Some required checks were not successful. pulls.required_status_check_missing = Some required checks are missing. pulls.required_status_check_administrator = As an administrator, you may still merge this pull request. diff --git a/services/pull/check.go b/services/pull/check.go index 6621a281fa..288f4dc0b7 100644 --- a/services/pull/check.go +++ b/services/pull/check.go @@ -89,7 +89,7 @@ func CheckPullMergable(stdCtx context.Context, doer *user_model.User, perm *acce return ErrIsWorkInProgress } - if !pr.CanAutoMerge() { + if !pr.CanAutoMerge() && !pr.IsEmpty() { return ErrNotMergableState } diff --git a/services/pull/patch.go b/services/pull/patch.go index c7a69501c3..bb09acc89f 100644 --- a/services/pull/patch.go +++ b/services/pull/patch.go @@ -87,6 +87,14 @@ func TestPatch(pr *issues_model.PullRequest) error { } } pr.MergeBase = strings.TrimSpace(pr.MergeBase) + if pr.HeadCommitID, err = gitRepo.GetRefCommitID(git.BranchPrefix + "tracking"); err != nil { + return fmt.Errorf("GetBranchCommitID: can't find commit ID for head: %w", err) + } + + if pr.HeadCommitID == pr.MergeBase { + pr.Status = issues_model.PullRequestStatusAncestor + return nil + } // 2. Check for conflicts if conflicts, err := checkConflicts(ctx, pr, gitRepo, tmpBasePath); err != nil || conflicts || pr.Status == issues_model.PullRequestStatusEmpty { diff --git a/templates/repo/issue/view_content/pull.tmpl b/templates/repo/issue/view_content/pull.tmpl index 5a23bfa33b..23aa97217d 100644 --- a/templates/repo/issue/view_content/pull.tmpl +++ b/templates/repo/issue/view_content/pull.tmpl @@ -195,12 +195,12 @@ {{svg "octicon-sync"}} {{$.i18n.Tr "repo.pulls.is_checking"}}
- {{else if .Issue.PullRequest.IsEmpty}} + {{else if .Issue.PullRequest.IsAncestor}}
{{svg "octicon-alert" 16}} - {{$.i18n.Tr "repo.pulls.is_empty"}} + {{$.i18n.Tr "repo.pulls.is_ancestor"}}
- {{else if .Issue.PullRequest.CanAutoMerge}} + {{else if or .Issue.PullRequest.CanAutoMerge .Issue.PullRequest.IsEmpty}} {{if .IsBlockedByApprovals}}
{{svg "octicon-x"}} @@ -282,7 +282,6 @@
{{end}} {{end}} - {{if and (gt .Issue.PullRequest.CommitsBehind 0) (not .Issue.IsClosed) (not .Issue.PullRequest.IsChecking) (not .IsPullFilesConflicted) (not .IsPullRequestBroken) (not $canAutoMerge)}}
@@ -321,6 +320,14 @@
{{end}} + {{if .Issue.PullRequest.IsEmpty}} +
+ +
+ {{svg "octicon-alert" 16}} + {{$.i18n.Tr "repo.pulls.is_empty"}} +
+ {{end}} {{if .AllowMerge}} {{/* user is allowed to merge */}} {{$prUnit := .Repository.MustGetUnit $.UnitTypePullRequests}} @@ -348,6 +355,7 @@ 'canMergeNow': {{$canMergeNow}}, 'allOverridableChecksOk': {{not $notAllOverridableChecksOk}}, + 'emptyCommit': {{.Issue.PullRequest.IsEmpty}}, 'pullHeadCommitID': {{.PullHeadCommitID}}, 'isPullBranchDeletable': {{.IsPullBranchDeletable}}, 'defaultDeleteBranchAfterMerge': {{$prUnit.PullRequestsConfig.DefaultDeleteBranchAfterMerge}}, diff --git a/web_src/js/components/PullRequestMergeForm.vue b/web_src/js/components/PullRequestMergeForm.vue index 75fbceb800..08b1f9cb86 100644 --- a/web_src/js/components/PullRequestMergeForm.vue +++ b/web_src/js/components/PullRequestMergeForm.vue @@ -48,7 +48,7 @@
-
+
- + {{svg "octicon-bell"}} diff --git a/templates/repo/clone_buttons.tmpl b/templates/repo/clone_buttons.tmpl index ea2525addf..5affc5f322 100644 --- a/templates/repo/clone_buttons.tmpl +++ b/templates/repo/clone_buttons.tmpl @@ -19,6 +19,6 @@ document.getElementById('repo-clone-url').value = btn ? btn.getAttribute('data-link') : ''; })(); - From ae86a0bc9fb5c65005bd37649d3bac9f7413c58c Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Fri, 29 Jul 2022 01:17:56 +0200 Subject: [PATCH 065/246] packages/generic: Do not restrict package versions to SemVer (#20414) (#20531) There are existing packages out there whose version do not conform to SemVer, yet, one would like to have them available in a generic package repository. To this end, remove the SemVer restriction on package versions when using the Generic package registry, and replace it with a check that simply makes sure the version isn't empty. Signed-off-by: Gergely Nagy Co-authored-by: KN4CK3R Co-authored-by: 6543 <6543@obermui.de> Co-authored-by: Gergely Nagy --- docs/content/doc/packages/generic.en-us.md | 2 +- integrations/api_packages_generic_test.go | 1 - routers/api/packages/generic/generic.go | 14 ++++++-------- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/docs/content/doc/packages/generic.en-us.md b/docs/content/doc/packages/generic.en-us.md index afef323938..9d4a2dd82d 100644 --- a/docs/content/doc/packages/generic.en-us.md +++ b/docs/content/doc/packages/generic.en-us.md @@ -37,7 +37,7 @@ PUT https://gitea.example.com/api/packages/{owner}/generic/{package_name}/{packa | ----------------- | ----------- | | `owner` | The owner of the package. | | `package_name` | The package name. It can contain only lowercase letters (`a-z`), uppercase letter (`A-Z`), numbers (`0-9`), dots (`.`), hyphens (`-`), or underscores (`_`). | -| `package_version` | The package version as described in the [SemVer](https://semver.org/) spec. | +| `package_version` | The package version, a non-empty string. | | `file_name` | The filename. It can contain only lowercase letters (`a-z`), uppercase letter (`A-Z`), numbers (`0-9`), dots (`.`), hyphens (`-`), or underscores (`_`). | Example request using HTTP Basic authentication: diff --git a/integrations/api_packages_generic_test.go b/integrations/api_packages_generic_test.go index c507702eaa..adaf99e981 100644 --- a/integrations/api_packages_generic_test.go +++ b/integrations/api_packages_generic_test.go @@ -42,7 +42,6 @@ func TestPackageGeneric(t *testing.T) { pd, err := packages.GetPackageDescriptor(db.DefaultContext, pvs[0]) assert.NoError(t, err) - assert.NotNil(t, pd.SemVer) assert.Nil(t, pd.Metadata) assert.Equal(t, packageName, pd.Package.Name) assert.Equal(t, packageVersion, pd.Version.Version) diff --git a/routers/api/packages/generic/generic.go b/routers/api/packages/generic/generic.go index d862f77259..9a3a185d9d 100644 --- a/routers/api/packages/generic/generic.go +++ b/routers/api/packages/generic/generic.go @@ -8,6 +8,7 @@ import ( "errors" "net/http" "regexp" + "strings" packages_model "code.gitea.io/gitea/models/packages" "code.gitea.io/gitea/modules/context" @@ -15,8 +16,6 @@ import ( packages_module "code.gitea.io/gitea/modules/packages" "code.gitea.io/gitea/routers/api/packages/helper" packages_service "code.gitea.io/gitea/services/packages" - - "github.com/hashicorp/go-version" ) var ( @@ -97,8 +96,7 @@ func UploadPackage(ctx *context.Context) { Name: packageName, Version: packageVersion, }, - SemverCompatible: true, - Creator: ctx.Doer, + Creator: ctx.Doer, }, &packages_service.PackageFileCreationInfo{ PackageFileInfo: packages_service.PackageFileInfo{ @@ -157,10 +155,10 @@ func sanitizeParameters(ctx *context.Context) (string, string, string, error) { return "", "", "", errors.New("Invalid package name or filename") } - v, err := version.NewSemver(ctx.Params("packageversion")) - if err != nil { - return "", "", "", err + packageVersion := strings.TrimSpace(ctx.Params("packageversion")) + if packageVersion == "" { + return "", "", "", errors.New("Invalid package version") } - return packageName, v.String(), filename, nil + return packageName, packageVersion, filename, nil } From 6986e56791af1c66f913b34b60f66c3429cd9185 Mon Sep 17 00:00:00 2001 From: zeripath Date: Fri, 29 Jul 2022 01:10:42 +0100 Subject: [PATCH 066/246] Stop logging EOFs and exit(1)s in ssh handler (#20476) (#20529) Backport #20476 The code in modules/ssh/ssh.go:sessionHandler() currently cause an error to be logged if `gitea serv` exits with a exit(1). This logging is useless because the accompanying stderr is not provided and in any case the exit(1) is most likely due to permissions errors. Further it then causes the EOF to be logged - even though this is not helpful. This PR simply checks the errors returned and stops logging them. In the case of misconfigurations causing `gitea serv` to fail with exit(1) the current logging is not helpful at determining this and users should simply review the message passed over the ssh connection. Fix #20473 Signed-off-by: Andrew Thornton --- modules/ssh/ssh.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/modules/ssh/ssh.go b/modules/ssh/ssh.go index 2affeb781a..bffe29f4c3 100644 --- a/modules/ssh/ssh.go +++ b/modules/ssh/ssh.go @@ -11,6 +11,7 @@ import ( "crypto/rsa" "crypto/x509" "encoding/pem" + "errors" "fmt" "io" "net" @@ -142,10 +143,14 @@ func sessionHandler(session ssh.Session) { // Wait for the command to exit and log any errors we get err = cmd.Wait() if err != nil { - log.Error("SSH: Wait: %v", err) + // Cannot use errors.Is here because ExitError doesn't implement Is + // Thus errors.Is will do equality test NOT type comparison + if _, ok := err.(*exec.ExitError); !ok { + log.Error("SSH: Wait: %v", err) + } } - if err := session.Exit(getExitStatusFromError(err)); err != nil { + if err := session.Exit(getExitStatusFromError(err)); err != nil && !errors.Is(err, io.EOF) { log.Error("Session failed to exit. %s", err) } } From d6bc1558c6e2d13e35925239efde4c6a687fbd79 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Fri, 29 Jul 2022 14:58:56 +0200 Subject: [PATCH 067/246] Update lunny/levelqueue to prevent NPE when reads are performed after close (#20534) (#20537) Co-authored-by: zeripath --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index fe331b69ee..e366e10240 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( gitea.com/go-chi/cache v0.2.0 gitea.com/go-chi/captcha v0.0.0-20211013065431-70641c1a35d5 gitea.com/go-chi/session v0.0.0-20211218221615-e3605d8b28b8 - gitea.com/lunny/levelqueue v0.4.1 + gitea.com/lunny/levelqueue v0.4.2-0.20220729054728-f020868cc2f7 github.com/42wim/sshsig v0.0.0-20211121163825-841cf5bbc121 github.com/NYTimes/gziphandler v1.1.1 github.com/PuerkitoBio/goquery v1.8.0 diff --git a/go.sum b/go.sum index b767a205c7..95d93d14e3 100644 --- a/go.sum +++ b/go.sum @@ -78,8 +78,8 @@ gitea.com/go-chi/captcha v0.0.0-20211013065431-70641c1a35d5 h1:J/1i8u40TbcLP/w2w gitea.com/go-chi/captcha v0.0.0-20211013065431-70641c1a35d5/go.mod h1:hQ9SYHKdOX968wJglb/NMQ+UqpOKwW4L+EYdvkWjHSo= gitea.com/go-chi/session v0.0.0-20211218221615-e3605d8b28b8 h1:tJQRXgZigkLeeW9LPlps9G9aMoE6LAmqigLA+wxmd1Q= gitea.com/go-chi/session v0.0.0-20211218221615-e3605d8b28b8/go.mod h1:fc/pjt5EqNKgqQXYzcas1Z5L5whkZHyOvTA7OzWVJck= -gitea.com/lunny/levelqueue v0.4.1 h1:RZ+AFx5gBsZuyqCvofhAkPQ9uaVDPJnsULoJZIYaJNw= -gitea.com/lunny/levelqueue v0.4.1/go.mod h1:HBqmLbz56JWpfEGG0prskAV97ATNRoj5LDmPicD22hU= +gitea.com/lunny/levelqueue v0.4.2-0.20220729054728-f020868cc2f7 h1:Zc3RQWC2xOVglLciQH/ZIC5IqSk3Jn96LflGQLv18Rg= +gitea.com/lunny/levelqueue v0.4.2-0.20220729054728-f020868cc2f7/go.mod h1:HBqmLbz56JWpfEGG0prskAV97ATNRoj5LDmPicD22hU= gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s= gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU= gitee.com/travelliu/dm v1.8.11192/go.mod h1:DHTzyhCrM843x9VdKVbZ+GKXGRbKM2sJ4LxihRxShkE= From 210b096da72acacbddc28d65f6ce2502772307f1 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Fri, 29 Jul 2022 15:37:18 +0200 Subject: [PATCH 068/246] Ensure that all unmerged files are merged when conflict checking (#20528) (#20536) There is a subtle bug in the code relating to collating the results of `git ls-files -u -z` in `unmergedFiles()`. The code here makes the mistake of assuming that every unmerged file will always have a stage 1 conflict, and this results in conflicts that occur in stage 3 only being dropped. This PR simply adjusts this code to ensure that any empty unmergedFile will always be passed down the channel. The PR also adds a lot of Trace commands to attempt to help find future bugs in this code. Fix #19527 Signed-off-by: Andrew Thornton Co-authored-by: zeripath --- services/pull/patch.go | 4 +++- services/pull/patch_unmerged.go | 25 ++++++++++++++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/services/pull/patch.go b/services/pull/patch.go index bb09acc89f..32895b2e78 100644 --- a/services/pull/patch.go +++ b/services/pull/patch.go @@ -124,6 +124,7 @@ func (e *errMergeConflict) Error() string { } func attemptMerge(ctx context.Context, file *unmergedFile, tmpBasePath string, gitRepo *git.Repository) error { + log.Trace("Attempt to merge:\n%v", file) switch { case file.stage1 != nil && (file.stage2 == nil || file.stage3 == nil): // 1. Deleted in one or both: @@ -295,7 +296,8 @@ func checkConflicts(ctx context.Context, pr *issues_model.PullRequest, gitRepo * var treeHash string treeHash, _, err = git.NewCommand(ctx, "write-tree").RunStdString(&git.RunOpts{Dir: tmpBasePath}) if err != nil { - return false, err + lsfiles, _, _ := git.NewCommand(ctx, "ls-files", "-u").RunStdString(&git.RunOpts{Dir: tmpBasePath}) + return false, fmt.Errorf("unable to write unconflicted tree: %w\n`git ls-files -u`:\n%s", err, lsfiles) } treeHash = strings.TrimSpace(treeHash) baseTree, err := gitRepo.GetTree("base") diff --git a/services/pull/patch_unmerged.go b/services/pull/patch_unmerged.go index 3839419142..465465d0da 100644 --- a/services/pull/patch_unmerged.go +++ b/services/pull/patch_unmerged.go @@ -42,6 +42,17 @@ func (line *lsFileLine) SameAs(other *lsFileLine) bool { line.path == other.path } +// String provides a string representation for logging +func (line *lsFileLine) String() string { + if line == nil { + return "" + } + if line.err != nil { + return fmt.Sprintf("%d %s %s %s %v", line.stage, line.mode, line.path, line.sha, line.err) + } + return fmt.Sprintf("%d %s %s %s", line.stage, line.mode, line.path, line.sha) +} + // readUnmergedLsFileLines calls git ls-files -u -z and parses the lines into mode-sha-stage-path quadruplets // it will push these to the provided channel closing it at the end func readUnmergedLsFileLines(ctx context.Context, tmpBasePath string, outputChan chan *lsFileLine) { @@ -118,6 +129,17 @@ type unmergedFile struct { err error } +// String provides a string representation of the an unmerged file for logging +func (u *unmergedFile) String() string { + if u == nil { + return "" + } + if u.err != nil { + return fmt.Sprintf("error: %v\n%v\n%v\n%v", u.err, u.stage1, u.stage2, u.stage3) + } + return fmt.Sprintf("%v\n%v\n%v", u.stage1, u.stage2, u.stage3) +} + // unmergedFiles will collate the output from readUnstagedLsFileLines in to file triplets and send them // to the provided channel, closing at the end. func unmergedFiles(ctx context.Context, tmpBasePath string, unmerged chan *unmergedFile) { @@ -138,6 +160,7 @@ func unmergedFiles(ctx context.Context, tmpBasePath string, unmerged chan *unmer next := &unmergedFile{} for line := range lsFileLineChan { + log.Trace("Got line: %v Current State:\n%v", line, next) if line.err != nil { log.Error("Unable to run ls-files -u -z! Error: %v", line.err) unmerged <- &unmergedFile{err: fmt.Errorf("unable to run ls-files -u -z! Error: %v", line.err)} @@ -149,7 +172,7 @@ func unmergedFiles(ctx context.Context, tmpBasePath string, unmerged chan *unmer case 0: // Should not happen as this represents successfully merged file - we will tolerate and ignore though case 1: - if next.stage1 != nil { + if next.stage1 != nil || next.stage2 != nil || next.stage3 != nil { // We need to handle the unstaged file stage1,stage2,stage3 unmerged <- next } From fc7b5afd9b23a2b194b498dc6f4802ee31339639 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Fri, 29 Jul 2022 19:14:50 +0200 Subject: [PATCH 069/246] Add missing Tabs on organisation/package view (#20539) hotfix #20106 --- routers/web/user/package.go | 16 ++++++++++ templates/user/overview/header.tmpl | 48 ++++++++++++++++++++++++----- 2 files changed, 57 insertions(+), 7 deletions(-) diff --git a/routers/web/user/package.go b/routers/web/user/package.go index b2b550cb73..4519d2751e 100644 --- a/routers/web/user/package.go +++ b/routers/web/user/package.go @@ -8,6 +8,7 @@ import ( "net/http" "code.gitea.io/gitea/models/db" + org_model "code.gitea.io/gitea/models/organization" packages_model "code.gitea.io/gitea/models/packages" container_model "code.gitea.io/gitea/models/packages/container" "code.gitea.io/gitea/models/perm" @@ -91,6 +92,21 @@ func ListPackages(ctx *context.Context) { ctx.Data["Total"] = total ctx.Data["RepositoryAccessMap"] = repositoryAccessMap + // TODO: context/org -> HandleOrgAssignment() can not be used + if ctx.ContextUser.IsOrganization() { + org := org_model.OrgFromUser(ctx.ContextUser) + ctx.Data["Org"] = org + ctx.Data["OrgLink"] = ctx.ContextUser.OrganisationLink() + + if ctx.Doer != nil { + ctx.Data["IsOrganizationMember"], _ = org_model.IsOrganizationMember(ctx, org.ID, ctx.Doer.ID) + ctx.Data["IsOrganizationOwner"], _ = org_model.IsOrganizationOwner(ctx, org.ID, ctx.Doer.ID) + } else { + ctx.Data["IsOrganizationMember"] = false + ctx.Data["IsOrganizationOwner"] = false + } + } + pager := context.NewPagination(int(total), setting.UI.PackagesPagingNum, page, 5) pager.AddParam(ctx, "q", "Query") pager.AddParam(ctx, "type", "PackageType") diff --git a/templates/user/overview/header.tmpl b/templates/user/overview/header.tmpl index 666805c8f6..e65abac46a 100644 --- a/templates/user/overview/header.tmpl +++ b/templates/user/overview/header.tmpl @@ -1,14 +1,22 @@
-
-
-
-
- {{avatar .ContextUser 32}} - {{.ContextUser.Name}} + + {{with .ContextUser}} +
+
+
+
+ {{avatar . 100}} + {{.DisplayName}} + + {{if .Visibility.IsLimited}}
{{$.i18n.Tr "org.settings.visibility.limited_shortname"}}
{{end}} + {{if .Visibility.IsPrivate}}
{{$.i18n.Tr "org.settings.visibility.private_shortname"}}
{{end}} +
+
-
+ {{end}} +
From d1e53bfd7f6bf62baa53c6e7b3973396db074075 Mon Sep 17 00:00:00 2001 From: Gusted Date: Sat, 30 Jul 2022 04:28:48 +0200 Subject: [PATCH 070/246] Update notification count for non-mobile version (#20544) - Since #20108 we have two version of the notification bell, one for mobile the other for non-mobile. However the code only accounts for one notification count and thus was only updating the non-mobile one. - This code fixes that by applying the code for all `.notification_count`s. - Frontport will be in #20543 --- web_src/js/features/notification.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/web_src/js/features/notification.js b/web_src/js/features/notification.js index 36df196cac..d184db0ddd 100644 --- a/web_src/js/features/notification.js +++ b/web_src/js/features/notification.js @@ -28,14 +28,12 @@ async function receiveUpdateCount(event) { try { const data = JSON.parse(event.data); - const notificationCount = document.querySelector('.notification_count'); - if (data.Count > 0) { - notificationCount.classList.remove('hidden'); - } else { - notificationCount.classList.add('hidden'); + const notificationCounts = document.querySelectorAll('.notification_count'); + for (const count of notificationCounts) { + count.classList.toggle('hidden', data.Count === 0); + count.textContent = `${data.Count}`; } - notificationCount.textContent = `${data.Count}`; await updateNotificationTable(); } catch (error) { console.error(error, event); From 97a8c96c5b45123f580244edbc9b2ad3e102b6ee Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sat, 30 Jul 2022 17:52:04 +0200 Subject: [PATCH 071/246] Add Docker /v2/_catalog endpoint (#20469) (#20556) * Added properties for packages. * Fixed authenticate header format. * Added _catalog endpoint. * Check owner visibility. * Extracted condition. * Added test for _catalog. Co-authored-by: 6543 <6543@obermui.de> Co-authored-by: KN4CK3R Co-authored-by: Lunny Xiao Co-authored-by: Lauris BH Co-authored-by: wxiaoguang --- integrations/api_packages_container_test.go | 84 +++++++++++++++++---- integrations/api_packages_npm_test.go | 6 +- models/migrations/migrations.go | 4 + models/migrations/v219.go | 31 ++++++++ models/migrations/v220.go | 29 +++++++ models/packages/container/search.go | 36 +++++++++ models/packages/descriptor.go | 42 ++++++----- models/packages/package.go | 17 +++-- models/packages/package_property.go | 10 ++- models/user/search.go | 49 ++++++------ modules/packages/container/metadata.go | 1 + routers/api/packages/api.go | 1 + routers/api/packages/composer/api.go | 2 +- routers/api/packages/composer/composer.go | 2 +- routers/api/packages/container/blob.go | 12 ++- routers/api/packages/container/container.go | 35 ++++++++- routers/api/packages/container/manifest.go | 12 ++- routers/api/packages/npm/api.go | 2 +- routers/web/org/setting.go | 7 ++ routers/web/user/setting/profile.go | 6 ++ services/packages/container/cleanup.go | 25 ++++++ services/packages/packages.go | 46 ++++++++--- 22 files changed, 374 insertions(+), 85 deletions(-) create mode 100644 models/migrations/v219.go create mode 100644 models/migrations/v220.go diff --git a/integrations/api_packages_container_test.go b/integrations/api_packages_container_test.go index 2b5be9dd4c..850ac626f6 100644 --- a/integrations/api_packages_container_test.go +++ b/integrations/api_packages_container_test.go @@ -26,6 +26,7 @@ import ( func TestPackageContainer(t *testing.T) { defer prepareTestEnv(t)() + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User) has := func(l packages_model.PackagePropertyList, name string) bool { @@ -36,6 +37,15 @@ func TestPackageContainer(t *testing.T) { } return false } + getAllByName := func(l packages_model.PackagePropertyList, name string) []string { + values := make([]string, 0, len(l)) + for _, pp := range l { + if pp.Name == name { + values = append(values, pp.Value) + } + } + return values + } images := []string{"test", "te/st"} tags := []string{"latest", "main"} @@ -66,7 +76,7 @@ func TestPackageContainer(t *testing.T) { Token string `json:"token"` } - authenticate := []string{`Bearer realm="` + setting.AppURL + `v2/token"`} + authenticate := []string{`Bearer realm="` + setting.AppURL + `v2/token",service="container_registry",scope="*"`} t.Run("Anonymous", func(t *testing.T) { defer PrintCurrentTest(t)() @@ -236,7 +246,8 @@ func TestPackageContainer(t *testing.T) { assert.Nil(t, pd.SemVer) assert.Equal(t, image, pd.Package.Name) assert.Equal(t, tag, pd.Version.Version) - assert.True(t, has(pd.Properties, container_module.PropertyManifestTagged)) + assert.ElementsMatch(t, []string{strings.ToLower(user.LowerName + "/" + image)}, getAllByName(pd.PackageProperties, container_module.PropertyRepository)) + assert.True(t, has(pd.VersionProperties, container_module.PropertyManifestTagged)) assert.IsType(t, &container_module.Metadata{}, pd.Metadata) metadata := pd.Metadata.(*container_module.Metadata) @@ -330,7 +341,8 @@ func TestPackageContainer(t *testing.T) { assert.Nil(t, pd.SemVer) assert.Equal(t, image, pd.Package.Name) assert.Equal(t, untaggedManifestDigest, pd.Version.Version) - assert.False(t, has(pd.Properties, container_module.PropertyManifestTagged)) + assert.ElementsMatch(t, []string{strings.ToLower(user.LowerName + "/" + image)}, getAllByName(pd.PackageProperties, container_module.PropertyRepository)) + assert.False(t, has(pd.VersionProperties, container_module.PropertyManifestTagged)) assert.IsType(t, &container_module.Metadata{}, pd.Metadata) @@ -362,18 +374,10 @@ func TestPackageContainer(t *testing.T) { assert.Nil(t, pd.SemVer) assert.Equal(t, image, pd.Package.Name) assert.Equal(t, multiTag, pd.Version.Version) - assert.True(t, has(pd.Properties, container_module.PropertyManifestTagged)) + assert.ElementsMatch(t, []string{strings.ToLower(user.LowerName + "/" + image)}, getAllByName(pd.PackageProperties, container_module.PropertyRepository)) + assert.True(t, has(pd.VersionProperties, container_module.PropertyManifestTagged)) - getAllByName := func(l packages_model.PackagePropertyList, name string) []string { - values := make([]string, 0, len(l)) - for _, pp := range l { - if pp.Name == name { - values = append(values, pp.Value) - } - } - return values - } - assert.ElementsMatch(t, []string{manifestDigest, untaggedManifestDigest}, getAllByName(pd.Properties, container_module.PropertyManifestReference)) + assert.ElementsMatch(t, []string{manifestDigest, untaggedManifestDigest}, getAllByName(pd.VersionProperties, container_module.PropertyManifestReference)) assert.IsType(t, &container_module.Metadata{}, pd.Metadata) metadata := pd.Metadata.(*container_module.Metadata) @@ -528,4 +532,56 @@ func TestPackageContainer(t *testing.T) { }) }) } + + t.Run("OwnerNameChange", func(t *testing.T) { + defer PrintCurrentTest(t)() + + checkCatalog := func(owner string) func(t *testing.T) { + return func(t *testing.T) { + defer PrintCurrentTest(t)() + + req := NewRequest(t, "GET", fmt.Sprintf("%sv2/_catalog", setting.AppURL)) + addTokenAuthHeader(req, userToken) + resp := MakeRequest(t, req, http.StatusOK) + + type RepositoryList struct { + Repositories []string `json:"repositories"` + } + + repoList := &RepositoryList{} + DecodeJSON(t, resp, &repoList) + + assert.Len(t, repoList.Repositories, len(images)) + names := make([]string, 0, len(images)) + for _, image := range images { + names = append(names, strings.ToLower(owner+"/"+image)) + } + assert.ElementsMatch(t, names, repoList.Repositories) + } + } + + t.Run(fmt.Sprintf("Catalog[%s]", user.LowerName), checkCatalog(user.LowerName)) + + session := loginUser(t, user.Name) + + newOwnerName := "newUsername" + + req := NewRequestWithValues(t, "POST", "/user/settings", map[string]string{ + "_csrf": GetCSRF(t, session, "/user/settings"), + "name": newOwnerName, + "email": "user2@example.com", + "language": "en-US", + }) + session.MakeRequest(t, req, http.StatusSeeOther) + + t.Run(fmt.Sprintf("Catalog[%s]", newOwnerName), checkCatalog(newOwnerName)) + + req = NewRequestWithValues(t, "POST", "/user/settings", map[string]string{ + "_csrf": GetCSRF(t, session, "/user/settings"), + "name": user.Name, + "email": "user2@example.com", + "language": "en-US", + }) + session.MakeRequest(t, req, http.StatusSeeOther) + }) } diff --git a/integrations/api_packages_npm_test.go b/integrations/api_packages_npm_test.go index 28a3711939..ad88ac5da6 100644 --- a/integrations/api_packages_npm_test.go +++ b/integrations/api_packages_npm_test.go @@ -85,9 +85,9 @@ func TestPackageNpm(t *testing.T) { assert.IsType(t, &npm.Metadata{}, pd.Metadata) assert.Equal(t, packageName, pd.Package.Name) assert.Equal(t, packageVersion, pd.Version.Version) - assert.Len(t, pd.Properties, 1) - assert.Equal(t, npm.TagProperty, pd.Properties[0].Name) - assert.Equal(t, packageTag, pd.Properties[0].Value) + assert.Len(t, pd.VersionProperties, 1) + assert.Equal(t, npm.TagProperty, pd.VersionProperties[0].Name) + assert.Equal(t, packageTag, pd.VersionProperties[0].Value) pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID) assert.NoError(t, err) diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 0d35ac78d3..beeba866dc 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -396,6 +396,10 @@ var migrations = []Migration{ NewMigration("Alter hook_task table TEXT fields to LONGTEXT", alterHookTaskTextFieldsToLongText), // v218 -> v219 NewMigration("Improve Action table indices v2", improveActionTableIndices), + // v219 -> v220 + NewMigration("Add sync_on_commit column to push_mirror table", addSyncOnCommitColForPushMirror), + // v220 -> v221 + NewMigration("Add container repository property", addContainerRepositoryProperty), } // GetCurrentDBVersion returns the current db version diff --git a/models/migrations/v219.go b/models/migrations/v219.go new file mode 100644 index 0000000000..7b4f34b704 --- /dev/null +++ b/models/migrations/v219.go @@ -0,0 +1,31 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package migrations + +import ( + "time" + + "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/modules/timeutil" + + "xorm.io/xorm" +) + +func addSyncOnCommitColForPushMirror(x *xorm.Engine) error { + type PushMirror struct { + ID int64 `xorm:"pk autoincr"` + RepoID int64 `xorm:"INDEX"` + Repo *repo.Repository `xorm:"-"` + RemoteName string + + SyncOnCommit bool `xorm:"NOT NULL DEFAULT true"` + Interval time.Duration + CreatedUnix timeutil.TimeStamp `xorm:"created"` + LastUpdateUnix timeutil.TimeStamp `xorm:"INDEX last_update"` + LastError string `xorm:"text"` + } + + return x.Sync2(new(PushMirror)) +} diff --git a/models/migrations/v220.go b/models/migrations/v220.go new file mode 100644 index 0000000000..f5983582a3 --- /dev/null +++ b/models/migrations/v220.go @@ -0,0 +1,29 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package migrations + +import ( + packages_model "code.gitea.io/gitea/models/packages" + container_module "code.gitea.io/gitea/modules/packages/container" + + "xorm.io/xorm" + "xorm.io/xorm/schemas" +) + +func addContainerRepositoryProperty(x *xorm.Engine) error { + switch x.Dialect().URI().DBType { + case schemas.SQLITE: + _, err := x.Exec("INSERT INTO package_property (ref_type, ref_id, name, value) SELECT ?, p.id, ?, u.lower_name || '/' || p.lower_name FROM package p JOIN `user` u ON p.owner_id = u.id WHERE p.type = ?", packages_model.PropertyTypePackage, container_module.PropertyRepository, packages_model.TypeContainer) + if err != nil { + return err + } + default: + _, err := x.Exec("INSERT INTO package_property (ref_type, ref_id, name, value) SELECT ?, p.id, ?, CONCAT(u.lower_name, '/', p.lower_name) FROM package p JOIN `user` u ON p.owner_id = u.id WHERE p.type = ?", packages_model.PropertyTypePackage, container_module.PropertyRepository, packages_model.TypeContainer) + if err != nil { + return err + } + } + return nil +} diff --git a/models/packages/container/search.go b/models/packages/container/search.go index 972cac9528..a3409fe743 100644 --- a/models/packages/container/search.go +++ b/models/packages/container/search.go @@ -12,6 +12,7 @@ import ( "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/packages" + user_model "code.gitea.io/gitea/models/user" container_module "code.gitea.io/gitea/modules/packages/container" "xorm.io/builder" @@ -210,6 +211,7 @@ func SearchImageTags(ctx context.Context, opts *ImageTagsSearchOptions) ([]*pack return pvs, count, err } +// SearchExpiredUploadedBlobs gets all uploaded blobs which are older than specified func SearchExpiredUploadedBlobs(ctx context.Context, olderThan time.Duration) ([]*packages.PackageFile, error) { var cond builder.Cond = builder.Eq{ "package_version.is_internal": true, @@ -225,3 +227,37 @@ func SearchExpiredUploadedBlobs(ctx context.Context, olderThan time.Duration) ([ Where(cond). Find(&pfs) } + +// GetRepositories gets a sorted list of all repositories +func GetRepositories(ctx context.Context, actor *user_model.User, n int, last string) ([]string, error) { + var cond builder.Cond = builder.Eq{ + "package.type": packages.TypeContainer, + "package_property.ref_type": packages.PropertyTypePackage, + "package_property.name": container_module.PropertyRepository, + } + + cond = cond.And(builder.Exists( + builder. + Select("package_version.id"). + Where(builder.Eq{"package_version.is_internal": false}.And(builder.Expr("package.id = package_version.package_id"))). + From("package_version"), + )) + + if last != "" { + cond = cond.And(builder.Gt{"package_property.value": strings.ToLower(last)}) + } + + cond = cond.And(user_model.BuildCanSeeUserCondition(actor)) + + sess := db.GetEngine(ctx). + Table("package"). + Select("package_property.value"). + Join("INNER", "user", "`user`.id = package.owner_id"). + Join("INNER", "package_property", "package_property.ref_id = package.id"). + Where(cond). + Asc("package_property.value"). + Limit(n) + + repositories := make([]string, 0, n) + return repositories, sess.Find(&repositories) +} diff --git a/models/packages/descriptor.go b/models/packages/descriptor.go index fbdc40f37f..31819ccca1 100644 --- a/models/packages/descriptor.go +++ b/models/packages/descriptor.go @@ -40,15 +40,16 @@ func (l PackagePropertyList) GetByName(name string) string { // PackageDescriptor describes a package type PackageDescriptor struct { - Package *Package - Owner *user_model.User - Repository *repo_model.Repository - Version *PackageVersion - SemVer *version.Version - Creator *user_model.User - Properties PackagePropertyList - Metadata interface{} - Files []*PackageFileDescriptor + Package *Package + Owner *user_model.User + Repository *repo_model.Repository + Version *PackageVersion + SemVer *version.Version + Creator *user_model.User + PackageProperties PackagePropertyList + VersionProperties PackagePropertyList + Metadata interface{} + Files []*PackageFileDescriptor } // PackageFileDescriptor describes a package file @@ -102,6 +103,10 @@ func GetPackageDescriptor(ctx context.Context, pv *PackageVersion) (*PackageDesc return nil, err } } + pps, err := GetProperties(ctx, PropertyTypePackage, p.ID) + if err != nil { + return nil, err + } pvps, err := GetProperties(ctx, PropertyTypeVersion, pv.ID) if err != nil { return nil, err @@ -152,15 +157,16 @@ func GetPackageDescriptor(ctx context.Context, pv *PackageVersion) (*PackageDesc } return &PackageDescriptor{ - Package: p, - Owner: o, - Repository: repository, - Version: pv, - SemVer: semVer, - Creator: creator, - Properties: PackagePropertyList(pvps), - Metadata: metadata, - Files: pfds, + Package: p, + Owner: o, + Repository: repository, + Version: pv, + SemVer: semVer, + Creator: creator, + PackageProperties: PackagePropertyList(pps), + VersionProperties: PackagePropertyList(pvps), + Metadata: metadata, + Files: pfds, }, nil } diff --git a/models/packages/package.go b/models/packages/package.go index bdb535492b..97cfbc6cad 100644 --- a/models/packages/package.go +++ b/models/packages/package.go @@ -131,6 +131,12 @@ func TryInsertPackage(ctx context.Context, p *Package) (*Package, error) { return p, nil } +// DeletePackageByID deletes a package by id +func DeletePackageByID(ctx context.Context, packageID int64) error { + _, err := db.GetEngine(ctx).ID(packageID).Delete(&Package{}) + return err +} + // SetRepositoryLink sets the linked repository func SetRepositoryLink(ctx context.Context, packageID, repoID int64) error { _, err := db.GetEngine(ctx).ID(packageID).Cols("repo_id").Update(&Package{RepoID: repoID}) @@ -192,21 +198,20 @@ func GetPackagesByType(ctx context.Context, ownerID int64, packageType Type) ([] Find(&ps) } -// DeletePackagesIfUnreferenced deletes a package if there are no associated versions -func DeletePackagesIfUnreferenced(ctx context.Context) error { +// FindUnreferencedPackages gets all packages without associated versions +func FindUnreferencedPackages(ctx context.Context) ([]*Package, error) { in := builder. Select("package.id"). From("package"). LeftJoin("package_version", "package_version.package_id = package.id"). Where(builder.Expr("package_version.id IS NULL")) - _, err := db.GetEngine(ctx). + ps := make([]*Package, 0, 10) + return ps, db.GetEngine(ctx). // double select workaround for MySQL // https://stackoverflow.com/questions/4471277/mysql-delete-from-with-subquery-as-condition Where(builder.In("package.id", builder.Select("id").From(in, "temp"))). - Delete(&Package{}) - - return err + Find(&ps) } // HasOwnerPackages tests if a user/org has packages diff --git a/models/packages/package_property.go b/models/packages/package_property.go index bf7dc346c6..fc10713801 100644 --- a/models/packages/package_property.go +++ b/models/packages/package_property.go @@ -21,9 +21,11 @@ const ( PropertyTypeVersion PropertyType = iota // 0 // PropertyTypeFile means the reference is a package file PropertyTypeFile // 1 + // PropertyTypePackage means the reference is a package + PropertyTypePackage // 2 ) -// PackageProperty represents a property of a package version or file +// PackageProperty represents a property of a package, version or file type PackageProperty struct { ID int64 `xorm:"pk autoincr"` RefType PropertyType `xorm:"INDEX NOT NULL"` @@ -68,3 +70,9 @@ func DeletePropertyByID(ctx context.Context, propertyID int64) error { _, err := db.GetEngine(ctx).ID(propertyID).Delete(&PackageProperty{}) return err } + +// DeletePropertyByName deletes properties by name +func DeletePropertyByName(ctx context.Context, refType PropertyType, refID int64, name string) error { + _, err := db.GetEngine(ctx).Where("ref_type = ? AND ref_id = ? AND name = ?", refType, refID, name).Delete(&PackageProperty{}) + return err +} diff --git a/models/user/search.go b/models/user/search.go index a81cee1c22..f8e6c89f06 100644 --- a/models/user/search.go +++ b/models/user/search.go @@ -58,31 +58,7 @@ func (opts *SearchUserOptions) toSearchQueryBase() *xorm.Session { cond = cond.And(builder.In("visibility", opts.Visible)) } - if opts.Actor != nil { - var exprCond builder.Cond = builder.Expr("org_user.org_id = `user`.id") - - // If Admin - they see all users! - if !opts.Actor.IsAdmin { - // Force visibility for privacy - var accessCond builder.Cond - if !opts.Actor.IsRestricted { - accessCond = builder.Or( - builder.In("id", builder.Select("org_id").From("org_user").LeftJoin("`user`", exprCond).Where(builder.And(builder.Eq{"uid": opts.Actor.ID}, builder.Eq{"visibility": structs.VisibleTypePrivate}))), - builder.In("visibility", structs.VisibleTypePublic, structs.VisibleTypeLimited)) - } else { - // restricted users only see orgs they are a member of - accessCond = builder.In("id", builder.Select("org_id").From("org_user").LeftJoin("`user`", exprCond).Where(builder.And(builder.Eq{"uid": opts.Actor.ID}))) - } - // Don't forget about self - accessCond = accessCond.Or(builder.Eq{"id": opts.Actor.ID}) - cond = cond.And(accessCond) - } - - } else { - // Force visibility for privacy - // Not logged in - only public users - cond = cond.And(builder.In("visibility", structs.VisibleTypePublic)) - } + cond = cond.And(BuildCanSeeUserCondition(opts.Actor)) if opts.UID > 0 { cond = cond.And(builder.Eq{"id": opts.UID}) @@ -170,3 +146,26 @@ func IterateUser(f func(user *User) error) error { } } } + +// BuildCanSeeUserCondition creates a condition which can be used to restrict results to users/orgs the actor can see +func BuildCanSeeUserCondition(actor *User) builder.Cond { + if actor != nil { + // If Admin - they see all users! + if !actor.IsAdmin { + // Users can see an organization they are a member of + cond := builder.In("`user`.id", builder.Select("org_id").From("org_user").Where(builder.Eq{"uid": actor.ID})) + if !actor.IsRestricted { + // Not-Restricted users can see public and limited users/organizations + cond = cond.Or(builder.In("`user`.visibility", structs.VisibleTypePublic, structs.VisibleTypeLimited)) + } + // Don't forget about self + return cond.Or(builder.Eq{"`user`.id": actor.ID}) + } + + return nil + } + + // Force visibility for privacy + // Not logged in - only public users + return builder.In("`user`.visibility", structs.VisibleTypePublic) +} diff --git a/modules/packages/container/metadata.go b/modules/packages/container/metadata.go index 087d38e5bd..4222cdb30a 100644 --- a/modules/packages/container/metadata.go +++ b/modules/packages/container/metadata.go @@ -16,6 +16,7 @@ import ( ) const ( + PropertyRepository = "container.repository" PropertyDigest = "container.digest" PropertyMediaType = "container.mediatype" PropertyManifestTagged = "container.manifest.tagged" diff --git a/routers/api/packages/api.go b/routers/api/packages/api.go index b5fdc739d7..bb9a42e33d 100644 --- a/routers/api/packages/api.go +++ b/routers/api/packages/api.go @@ -257,6 +257,7 @@ func ContainerRoutes() *web.Route { r.Get("", container.ReqContainerAccess, container.DetermineSupport) r.Get("/token", container.Authenticate) + r.Get("/_catalog", container.ReqContainerAccess, container.GetRepositoryList) r.Group("/{username}", func() { r.Group("/{image}", func() { r.Group("/blobs/uploads", func() { diff --git a/routers/api/packages/composer/api.go b/routers/api/packages/composer/api.go index d8f67d130c..468c6aaaaa 100644 --- a/routers/api/packages/composer/api.go +++ b/routers/api/packages/composer/api.go @@ -88,7 +88,7 @@ func createPackageMetadataResponse(registryURL string, pds []*packages_model.Pac for _, pd := range pds { packageType := "" - for _, pvp := range pd.Properties { + for _, pvp := range pd.VersionProperties { if pvp.Name == composer_module.TypeProperty { packageType = pvp.Value break diff --git a/routers/api/packages/composer/composer.go b/routers/api/packages/composer/composer.go index 23de28c7f9..d0b2a50aad 100644 --- a/routers/api/packages/composer/composer.go +++ b/routers/api/packages/composer/composer.go @@ -225,7 +225,7 @@ func UploadPackage(ctx *context.Context) { SemverCompatible: true, Creator: ctx.Doer, Metadata: cp.Metadata, - Properties: map[string]string{ + VersionProperties: map[string]string{ composer_module.TypeProperty: cp.Type, }, }, diff --git a/routers/api/packages/container/blob.go b/routers/api/packages/container/blob.go index 8f6254f583..8a9cbd4a15 100644 --- a/routers/api/packages/container/blob.go +++ b/routers/api/packages/container/blob.go @@ -29,6 +29,7 @@ func saveAsPackageBlob(hsr packages_module.HashedSizeReader, pi *packages_servic contentStore := packages_module.NewContentStore() err := db.WithTx(func(ctx context.Context) error { + created := true p := &packages_model.Package{ OwnerID: pi.Owner.ID, Type: packages_model.TypeContainer, @@ -37,12 +38,21 @@ func saveAsPackageBlob(hsr packages_module.HashedSizeReader, pi *packages_servic } var err error if p, err = packages_model.TryInsertPackage(ctx, p); err != nil { - if err != packages_model.ErrDuplicatePackage { + if err == packages_model.ErrDuplicatePackage { + created = false + } else { log.Error("Error inserting package: %v", err) return err } } + if created { + if _, err := packages_model.InsertProperty(ctx, packages_model.PropertyTypePackage, p.ID, container_module.PropertyRepository, strings.ToLower(pi.Owner.LowerName+"/"+pi.Name)); err != nil { + log.Error("Error setting package property: %v", err) + return err + } + } + pv := &packages_model.PackageVersion{ PackageID: p.ID, CreatorID: pi.Owner.ID, diff --git a/routers/api/packages/container/container.go b/routers/api/packages/container/container.go index 2a564b3446..b961cd4afb 100644 --- a/routers/api/packages/container/container.go +++ b/routers/api/packages/container/container.go @@ -112,7 +112,7 @@ func apiErrorDefined(ctx *context.Context, err *namedError) { // ReqContainerAccess is a middleware which checks the current user valid (real user or ghost for anonymous access) func ReqContainerAccess(ctx *context.Context) { if ctx.Doer == nil { - ctx.Resp.Header().Add("WWW-Authenticate", `Bearer realm="`+setting.AppURL+`v2/token"`) + ctx.Resp.Header().Add("WWW-Authenticate", `Bearer realm="`+setting.AppURL+`v2/token",service="container_registry",scope="*"`) apiErrorDefined(ctx, errUnauthorized) } } @@ -151,6 +151,39 @@ func Authenticate(ctx *context.Context) { }) } +// https://docs.docker.com/registry/spec/api/#listing-repositories +func GetRepositoryList(ctx *context.Context) { + n := ctx.FormInt("n") + if n <= 0 || n > 100 { + n = 100 + } + last := ctx.FormTrim("last") + + repositories, err := container_model.GetRepositories(ctx, ctx.Doer, n, last) + if err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + + type RepositoryList struct { + Repositories []string `json:"repositories"` + } + + if len(repositories) == n { + v := url.Values{} + if n > 0 { + v.Add("n", strconv.Itoa(n)) + } + v.Add("last", repositories[len(repositories)-1]) + + ctx.Resp.Header().Set("Link", fmt.Sprintf(`; rel="next"`, v.Encode())) + } + + jsonResponse(ctx, http.StatusOK, RepositoryList{ + Repositories: repositories, + }) +} + // https://github.com/opencontainers/distribution-spec/blob/main/spec.md#mounting-a-blob-from-another-repository // https://github.com/opencontainers/distribution-spec/blob/main/spec.md#single-post // https://github.com/opencontainers/distribution-spec/blob/main/spec.md#pushing-a-blob-in-chunks diff --git a/routers/api/packages/container/manifest.go b/routers/api/packages/container/manifest.go index d899ac8ee2..319c9bcabc 100644 --- a/routers/api/packages/container/manifest.go +++ b/routers/api/packages/container/manifest.go @@ -267,6 +267,7 @@ func processImageManifestIndex(mci *manifestCreationInfo, buf *packages_module.H } func createPackageAndVersion(ctx context.Context, mci *manifestCreationInfo, metadata *container_module.Metadata) (*packages_model.PackageVersion, error) { + created := true p := &packages_model.Package{ OwnerID: mci.Owner.ID, Type: packages_model.TypeContainer, @@ -275,12 +276,21 @@ func createPackageAndVersion(ctx context.Context, mci *manifestCreationInfo, met } var err error if p, err = packages_model.TryInsertPackage(ctx, p); err != nil { - if err != packages_model.ErrDuplicatePackage { + if err == packages_model.ErrDuplicatePackage { + created = false + } else { log.Error("Error inserting package: %v", err) return nil, err } } + if created { + if _, err := packages_model.InsertProperty(ctx, packages_model.PropertyTypePackage, p.ID, container_module.PropertyRepository, strings.ToLower(mci.Owner.LowerName+"/"+mci.Image)); err != nil { + log.Error("Error setting package property: %v", err) + return nil, err + } + } + metadata.IsTagged = mci.IsTagged metadataJSON, err := json.Marshal(metadata) diff --git a/routers/api/packages/npm/api.go b/routers/api/packages/npm/api.go index 56c8977043..4b6b803971 100644 --- a/routers/api/packages/npm/api.go +++ b/routers/api/packages/npm/api.go @@ -25,7 +25,7 @@ func createPackageMetadataResponse(registryURL string, pds []*packages_model.Pac for _, pd := range pds { versions[pd.SemVer.String()] = createPackageMetadataVersion(registryURL, pd) - for _, pvp := range pd.Properties { + for _, pvp := range pd.VersionProperties { if pvp.Name == npm_module.TagProperty { distTags[pvp.Value] = pd.Version.Version } diff --git a/routers/web/org/setting.go b/routers/web/org/setting.go index c22a124e74..3f7bc59856 100644 --- a/routers/web/org/setting.go +++ b/routers/web/org/setting.go @@ -24,6 +24,7 @@ import ( user_setting "code.gitea.io/gitea/routers/web/user/setting" "code.gitea.io/gitea/services/forms" "code.gitea.io/gitea/services/org" + container_service "code.gitea.io/gitea/services/packages/container" repo_service "code.gitea.io/gitea/services/repository" user_service "code.gitea.io/gitea/services/user" ) @@ -88,6 +89,12 @@ func SettingsPost(ctx *context.Context) { } return } + + if err := container_service.UpdateRepositoryNames(ctx, org.AsUser(), form.Name); err != nil { + ctx.ServerError("UpdateRepositoryNames", err) + return + } + // reset ctx.org.OrgLink with new name ctx.Org.OrgLink = setting.AppSubURL + "/org/" + url.PathEscape(form.Name) log.Trace("Organization name changed: %s -> %s", org.Name, form.Name) diff --git a/routers/web/user/setting/profile.go b/routers/web/user/setting/profile.go index 972271269f..a56bd63ee9 100644 --- a/routers/web/user/setting/profile.go +++ b/routers/web/user/setting/profile.go @@ -30,6 +30,7 @@ import ( "code.gitea.io/gitea/modules/web/middleware" "code.gitea.io/gitea/services/agit" "code.gitea.io/gitea/services/forms" + container_service "code.gitea.io/gitea/services/packages/container" user_service "code.gitea.io/gitea/services/user" ) @@ -90,6 +91,11 @@ func HandleUsernameChange(ctx *context.Context, user *user_model.User, newName s return err } + if err := container_service.UpdateRepositoryNames(ctx, user, newName); err != nil { + ctx.ServerError("UpdateRepositoryNames", err) + return err + } + log.Trace("User name changed: %s -> %s", user.Name, newName) return nil } diff --git a/services/packages/container/cleanup.go b/services/packages/container/cleanup.go index 390a0b7b05..920218b548 100644 --- a/services/packages/container/cleanup.go +++ b/services/packages/container/cleanup.go @@ -6,10 +6,13 @@ package container import ( "context" + "strings" "time" packages_model "code.gitea.io/gitea/models/packages" container_model "code.gitea.io/gitea/models/packages/container" + user_model "code.gitea.io/gitea/models/user" + container_module "code.gitea.io/gitea/modules/packages/container" "code.gitea.io/gitea/modules/util" ) @@ -78,3 +81,25 @@ func cleanupExpiredUploadedBlobs(ctx context.Context, olderThan time.Duration) e return nil } + +// UpdateRepositoryNames updates the repository name property for all packages of the specific owner +func UpdateRepositoryNames(ctx context.Context, owner *user_model.User, newOwnerName string) error { + ps, err := packages_model.GetPackagesByType(ctx, owner.ID, packages_model.TypeContainer) + if err != nil { + return err + } + + newOwnerName = strings.ToLower(newOwnerName) + + for _, p := range ps { + if err := packages_model.DeletePropertyByName(ctx, packages_model.PropertyTypePackage, p.ID, container_module.PropertyRepository); err != nil { + return err + } + + if _, err := packages_model.InsertProperty(ctx, packages_model.PropertyTypePackage, p.ID, container_module.PropertyRepository, newOwnerName+"/"+p.LowerName); err != nil { + return err + } + } + + return nil +} diff --git a/services/packages/packages.go b/services/packages/packages.go index 7f25fce5b8..312f7812b3 100644 --- a/services/packages/packages.go +++ b/services/packages/packages.go @@ -32,10 +32,11 @@ type PackageInfo struct { // PackageCreationInfo describes a package to create type PackageCreationInfo struct { PackageInfo - SemverCompatible bool - Creator *user_model.User - Metadata interface{} - Properties map[string]string + SemverCompatible bool + Creator *user_model.User + Metadata interface{} + PackageProperties map[string]string + VersionProperties map[string]string } // PackageFileInfo describes a package file @@ -108,8 +109,9 @@ func createPackageAndAddFile(pvci *PackageCreationInfo, pfci *PackageFileCreatio } func createPackageAndVersion(ctx context.Context, pvci *PackageCreationInfo, allowDuplicate bool) (*packages_model.PackageVersion, bool, error) { - log.Trace("Creating package: %v, %v, %v, %s, %s, %+v, %v", pvci.Creator.ID, pvci.Owner.ID, pvci.PackageType, pvci.Name, pvci.Version, pvci.Properties, allowDuplicate) + log.Trace("Creating package: %v, %v, %v, %s, %s, %+v, %+v, %v", pvci.Creator.ID, pvci.Owner.ID, pvci.PackageType, pvci.Name, pvci.Version, pvci.PackageProperties, pvci.VersionProperties, allowDuplicate) + packageCreated := true p := &packages_model.Package{ OwnerID: pvci.Owner.ID, Type: pvci.PackageType, @@ -119,18 +121,29 @@ func createPackageAndVersion(ctx context.Context, pvci *PackageCreationInfo, all } var err error if p, err = packages_model.TryInsertPackage(ctx, p); err != nil { - if err != packages_model.ErrDuplicatePackage { + if err == packages_model.ErrDuplicatePackage { + packageCreated = false + } else { log.Error("Error inserting package: %v", err) return nil, false, err } } + if packageCreated { + for name, value := range pvci.PackageProperties { + if _, err := packages_model.InsertProperty(ctx, packages_model.PropertyTypePackage, p.ID, name, value); err != nil { + log.Error("Error setting package property: %v", err) + return nil, false, err + } + } + } + metadataJSON, err := json.Marshal(pvci.Metadata) if err != nil { return nil, false, err } - created := true + versionCreated := true pv := &packages_model.PackageVersion{ PackageID: p.ID, CreatorID: pvci.Creator.ID, @@ -140,7 +153,7 @@ func createPackageAndVersion(ctx context.Context, pvci *PackageCreationInfo, all } if pv, err = packages_model.GetOrInsertVersion(ctx, pv); err != nil { if err == packages_model.ErrDuplicatePackageVersion { - created = false + versionCreated = false } if err != packages_model.ErrDuplicatePackageVersion || !allowDuplicate { log.Error("Error inserting package: %v", err) @@ -148,8 +161,8 @@ func createPackageAndVersion(ctx context.Context, pvci *PackageCreationInfo, all } } - if created { - for name, value := range pvci.Properties { + if versionCreated { + for name, value := range pvci.VersionProperties { if _, err := packages_model.InsertProperty(ctx, packages_model.PropertyTypeVersion, pv.ID, name, value); err != nil { log.Error("Error setting package version property: %v", err) return nil, false, err @@ -157,7 +170,7 @@ func createPackageAndVersion(ctx context.Context, pvci *PackageCreationInfo, all } } - return pv, created, nil + return pv, versionCreated, nil } // AddFileToExistingPackage adds a file to an existing package. If the package does not exist, ErrPackageNotExist is returned @@ -348,9 +361,18 @@ func Cleanup(unused context.Context, olderThan time.Duration) error { return err } - if err := packages_model.DeletePackagesIfUnreferenced(ctx); err != nil { + ps, err := packages_model.FindUnreferencedPackages(ctx) + if err != nil { return err } + for _, p := range ps { + if err := packages_model.DeleteAllProperties(ctx, packages_model.PropertyTypePackage, p.ID); err != nil { + return err + } + if err := packages_model.DeletePackageByID(ctx, p.ID); err != nil { + return err + } + } pbs, err := packages_model.FindExpiredUnreferencedBlobs(ctx, olderThan) if err != nil { From eeb490c7ab11cb6895ddf6e3069a2397c81296c9 Mon Sep 17 00:00:00 2001 From: silverwind Date: Sat, 30 Jul 2022 18:37:02 +0200 Subject: [PATCH 072/246] Rework raw file http header logic (#20484) (#20542) - Always respect the user's configured mime type map - Allow more types like image/pdf/video/audio to serve with correct content-type - Shorten cache duration of raw files to 5 minutes, matching GitHub - Don't set `content-disposition: attachment`, let the browser decide whether it wants to download or display a file directly - Implement rfc5987 for filenames, remove previous hack. Confirmed it working in Safari. - Make PDF attachment work in Safari by removing `sandbox` attribute. This change will make a lot more file types open directly in browser now. Logic should generally be more readable than before with less `if` nesting and such. Replaces: https://github.com/go-gitea/gitea/pull/20460 Replaces: https://github.com/go-gitea/gitea/pull/20455 Fixes: https://github.com/go-gitea/gitea/issues/20404 --- modules/typesniffer/typesniffer.go | 10 ++++ routers/common/repo.go | 96 ++++++++++++++++++------------ 2 files changed, 67 insertions(+), 39 deletions(-) diff --git a/modules/typesniffer/typesniffer.go b/modules/typesniffer/typesniffer.go index b6a6646d50..e50928e8c2 100644 --- a/modules/typesniffer/typesniffer.go +++ b/modules/typesniffer/typesniffer.go @@ -70,6 +70,16 @@ func (ct SniffedType) IsRepresentableAsText() bool { return ct.IsText() || ct.IsSvgImage() } +// IsBrowsableType returns whether a non-text type can be displayed in a browser +func (ct SniffedType) IsBrowsableBinaryType() bool { + return ct.IsImage() || ct.IsSvgImage() || ct.IsPDF() || ct.IsVideo() || ct.IsAudio() +} + +// GetMimeType returns the mime type +func (ct SniffedType) GetMimeType() string { + return strings.SplitN(ct.contentType, ";", 2)[0] +} + // DetectContentType extends http.DetectContentType with more content types. Defaults to text/unknown if input is empty. func DetectContentType(data []byte) SniffedType { if len(data) == 0 { diff --git a/routers/common/repo.go b/routers/common/repo.go index b3cd749115..a9e80fad48 100644 --- a/routers/common/repo.go +++ b/routers/common/repo.go @@ -7,12 +7,13 @@ package common import ( "fmt" "io" + "net/url" "path" "path/filepath" "strings" "time" - "code.gitea.io/gitea/modules/charset" + charsetModule "code.gitea.io/gitea/modules/charset" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/httpcache" @@ -42,7 +43,7 @@ func ServeBlob(ctx *context.Context, blob *git.Blob, lastModified time.Time) err } // ServeData download file from io.Reader -func ServeData(ctx *context.Context, name string, size int64, reader io.Reader) error { +func ServeData(ctx *context.Context, filePath string, size int64, reader io.Reader) error { buf := make([]byte, 1024) n, err := util.ReadAtMost(reader, buf) if err != nil { @@ -52,56 +53,73 @@ func ServeData(ctx *context.Context, name string, size int64, reader io.Reader) buf = buf[:n] } - ctx.Resp.Header().Set("Cache-Control", "public,max-age=86400") + httpcache.AddCacheControlToHeader(ctx.Resp.Header(), 5*time.Minute) if size >= 0 { ctx.Resp.Header().Set("Content-Length", fmt.Sprintf("%d", size)) } else { - log.Error("ServeData called to serve data: %s with size < 0: %d", name, size) + log.Error("ServeData called to serve data: %s with size < 0: %d", filePath, size) } - name = path.Base(name) - // Google Chrome dislike commas in filenames, so let's change it to a space - name = strings.ReplaceAll(name, ",", " ") + fileName := path.Base(filePath) + sniffedType := typesniffer.DetectContentType(buf) + isPlain := sniffedType.IsText() || ctx.FormBool("render") + mimeType := "" + charset := "" - st := typesniffer.DetectContentType(buf) - - mappedMimeType := "" if setting.MimeTypeMap.Enabled { - fileExtension := strings.ToLower(filepath.Ext(name)) - mappedMimeType = setting.MimeTypeMap.Map[fileExtension] + fileExtension := strings.ToLower(filepath.Ext(fileName)) + mimeType = setting.MimeTypeMap.Map[fileExtension] } - if st.IsText() || ctx.FormBool("render") { - cs, err := charset.DetectEncoding(buf) - if err != nil { - log.Error("Detect raw file %s charset failed: %v, using by default utf-8", name, err) - cs = "utf-8" - } - if mappedMimeType == "" { - mappedMimeType = "text/plain" - } - ctx.Resp.Header().Set("Content-Type", mappedMimeType+"; charset="+strings.ToLower(cs)) - } else { - ctx.Resp.Header().Set("Access-Control-Expose-Headers", "Content-Disposition") - if mappedMimeType != "" { - ctx.Resp.Header().Set("Content-Type", mappedMimeType) - } - if (st.IsImage() || st.IsPDF()) && (setting.UI.SVG.Enabled || !st.IsSvgImage()) { - ctx.Resp.Header().Set("Content-Disposition", fmt.Sprintf(`inline; filename="%s"`, name)) - if st.IsSvgImage() || st.IsPDF() { - ctx.Resp.Header().Set("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'; sandbox") - ctx.Resp.Header().Set("X-Content-Type-Options", "nosniff") - if st.IsSvgImage() { - ctx.Resp.Header().Set("Content-Type", typesniffer.SvgMimeType) - } else { - ctx.Resp.Header().Set("Content-Type", typesniffer.ApplicationOctetStream) - } - } + + if mimeType == "" { + if sniffedType.IsBrowsableBinaryType() { + mimeType = sniffedType.GetMimeType() + } else if isPlain { + mimeType = "text/plain" } else { - ctx.Resp.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, name)) + mimeType = typesniffer.ApplicationOctetStream } } + if isPlain { + charset, err = charsetModule.DetectEncoding(buf) + if err != nil { + log.Error("Detect raw file %s charset failed: %v, using by default utf-8", filePath, err) + charset = "utf-8" + } + } + + if charset != "" { + ctx.Resp.Header().Set("Content-Type", mimeType+"; charset="+strings.ToLower(charset)) + } else { + ctx.Resp.Header().Set("Content-Type", mimeType) + } + ctx.Resp.Header().Set("X-Content-Type-Options", "nosniff") + + isSVG := sniffedType.IsSvgImage() + + // serve types that can present a security risk with CSP + if isSVG { + ctx.Resp.Header().Set("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'; sandbox") + } else if sniffedType.IsPDF() { + // no sandbox attribute for pdf as it breaks rendering in at least safari. this + // should generally be safe as scripts inside PDF can not escape the PDF document + // see https://bugs.chromium.org/p/chromium/issues/detail?id=413851 for more discussion + ctx.Resp.Header().Set("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'") + } + + disposition := "inline" + if isSVG && !setting.UI.SVG.Enabled { + disposition = "attachment" + } + + // encode filename per https://datatracker.ietf.org/doc/html/rfc5987 + encodedFileName := `filename*=UTF-8''` + url.PathEscape(fileName) + + ctx.Resp.Header().Set("Content-Disposition", disposition+"; "+encodedFileName) + ctx.Resp.Header().Set("Access-Control-Expose-Headers", "Content-Disposition") + _, err = ctx.Resp.Write(buf) if err != nil { return err From 09f2e1e1a26039bbff09348403e99269d907aa28 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sat, 30 Jul 2022 20:16:25 +0200 Subject: [PATCH 073/246] WebAuthn CredentialID field needs to be increased in size (#20530) (#20555) WebAuthn have updated their specification to set the maximum size of the CredentialID to 1023 bytes. This is somewhat larger than our current size and therefore we need to migrate. The PR changes the struct to add CredentialIDBytes and migrates the CredentialID string to the bytes field before another migration drops the old CredentialID field. Another migration renames this field back. Fix #20457 Signed-off-by: Andrew Thornton Co-authored-by: zeripath --- models/auth/webauthn.go | 18 ++- models/auth/webauthn_test.go | 5 +- .../expected_webauthn_credential.yml | 9 ++ .../webauthn_credential.yml | 31 ++++++ models/migrations/migrations.go | 6 + models/migrations/v221.go | 75 +++++++++++++ models/migrations/v221_test.go | 65 +++++++++++ models/migrations/v222.go | 64 +++++++++++ models/migrations/v223.go | 103 ++++++++++++++++++ routers/web/auth/webauthn.go | 3 +- 10 files changed, 363 insertions(+), 16 deletions(-) create mode 100644 models/migrations/fixtures/Test_storeWebauthnCredentialIDAsBytes/expected_webauthn_credential.yml create mode 100644 models/migrations/fixtures/Test_storeWebauthnCredentialIDAsBytes/webauthn_credential.yml create mode 100644 models/migrations/v221.go create mode 100644 models/migrations/v221_test.go create mode 100644 models/migrations/v222.go create mode 100644 models/migrations/v223.go diff --git a/models/auth/webauthn.go b/models/auth/webauthn.go index 2dc3043780..d3062342f5 100644 --- a/models/auth/webauthn.go +++ b/models/auth/webauthn.go @@ -6,7 +6,6 @@ package auth import ( "context" - "encoding/base32" "fmt" "strings" @@ -20,14 +19,14 @@ import ( // ErrWebAuthnCredentialNotExist represents a "ErrWebAuthnCRedentialNotExist" kind of error. type ErrWebAuthnCredentialNotExist struct { ID int64 - CredentialID string + CredentialID []byte } func (err ErrWebAuthnCredentialNotExist) Error() string { - if err.CredentialID == "" { + if len(err.CredentialID) == 0 { return fmt.Sprintf("WebAuthn credential does not exist [id: %d]", err.ID) } - return fmt.Sprintf("WebAuthn credential does not exist [credential_id: %s]", err.CredentialID) + return fmt.Sprintf("WebAuthn credential does not exist [credential_id: %x]", err.CredentialID) } // IsErrWebAuthnCredentialNotExist checks if an error is a ErrWebAuthnCredentialNotExist. @@ -43,7 +42,7 @@ type WebAuthnCredential struct { Name string LowerName string `xorm:"unique(s)"` UserID int64 `xorm:"INDEX unique(s)"` - CredentialID string `xorm:"INDEX VARCHAR(410)"` + CredentialID []byte `xorm:"INDEX VARBINARY(1024)"` PublicKey []byte AttestationType string AAGUID []byte @@ -94,9 +93,8 @@ type WebAuthnCredentialList []*WebAuthnCredential func (list WebAuthnCredentialList) ToCredentials() []webauthn.Credential { creds := make([]webauthn.Credential, 0, len(list)) for _, cred := range list { - credID, _ := base32.HexEncoding.DecodeString(cred.CredentialID) creds = append(creds, webauthn.Credential{ - ID: credID, + ID: cred.CredentialID, PublicKey: cred.PublicKey, AttestationType: cred.AttestationType, Authenticator: webauthn.Authenticator{ @@ -164,11 +162,11 @@ func HasWebAuthnRegistrationsByUID(uid int64) (bool, error) { } // GetWebAuthnCredentialByCredID returns WebAuthn credential by credential ID -func GetWebAuthnCredentialByCredID(userID int64, credID string) (*WebAuthnCredential, error) { +func GetWebAuthnCredentialByCredID(userID int64, credID []byte) (*WebAuthnCredential, error) { return getWebAuthnCredentialByCredID(db.DefaultContext, userID, credID) } -func getWebAuthnCredentialByCredID(ctx context.Context, userID int64, credID string) (*WebAuthnCredential, error) { +func getWebAuthnCredentialByCredID(ctx context.Context, userID int64, credID []byte) (*WebAuthnCredential, error) { cred := new(WebAuthnCredential) if found, err := db.GetEngine(ctx).Where("user_id = ? AND credential_id = ?", userID, credID).Get(cred); err != nil { return nil, err @@ -187,7 +185,7 @@ func createCredential(ctx context.Context, userID int64, name string, cred *weba c := &WebAuthnCredential{ UserID: userID, Name: name, - CredentialID: base32.HexEncoding.EncodeToString(cred.ID), + CredentialID: cred.ID, PublicKey: cred.PublicKey, AttestationType: cred.AttestationType, AAGUID: cred.Authenticator.AAGUID, diff --git a/models/auth/webauthn_test.go b/models/auth/webauthn_test.go index 216bf11080..cc39691ce2 100644 --- a/models/auth/webauthn_test.go +++ b/models/auth/webauthn_test.go @@ -5,7 +5,6 @@ package auth import ( - "encoding/base32" "testing" "code.gitea.io/gitea/models/unittest" @@ -61,9 +60,7 @@ func TestCreateCredential(t *testing.T) { res, err := CreateCredential(1, "WebAuthn Created Credential", &webauthn.Credential{ID: []byte("Test")}) assert.NoError(t, err) assert.Equal(t, "WebAuthn Created Credential", res.Name) - bs, err := base32.HexEncoding.DecodeString(res.CredentialID) - assert.NoError(t, err) - assert.Equal(t, []byte("Test"), bs) + assert.Equal(t, []byte("Test"), res.CredentialID) unittest.AssertExistsIf(t, true, &WebAuthnCredential{Name: "WebAuthn Created Credential", UserID: 1}) } diff --git a/models/migrations/fixtures/Test_storeWebauthnCredentialIDAsBytes/expected_webauthn_credential.yml b/models/migrations/fixtures/Test_storeWebauthnCredentialIDAsBytes/expected_webauthn_credential.yml new file mode 100644 index 0000000000..55a237a0d6 --- /dev/null +++ b/models/migrations/fixtures/Test_storeWebauthnCredentialIDAsBytes/expected_webauthn_credential.yml @@ -0,0 +1,9 @@ +- + id: 1 + credential_id: "TVHE44TOH7DF7V48SEAIT3EMMJ7TGBOQ289E5AQB34S98LFCUFJ7U2NAVI8RJG6K2F4TC8AQ8KBNO7AGEOQOL9NE43GR63HTEHJSLOG=" +- + id: 2 + credential_id: "051CLMMKB62S6M9M2A4H54K7MMCQALFJ36G4TGB2S9A47APLTILU6C6744CEBG4EKCGV357N21BSLH8JD33GQMFAR6DQ70S76P34J6FR=" +- + id: 4 + credential_id: "APU4B1NDTEVTEM60V4T0FRL7SRJMO9KIE2AKFQ8JDGTQ7VHFI41FDEFTDLBVQEAE4ER49QV2GTGVFDNBO31BPOA3OQN6879OT6MTU3G=" diff --git a/models/migrations/fixtures/Test_storeWebauthnCredentialIDAsBytes/webauthn_credential.yml b/models/migrations/fixtures/Test_storeWebauthnCredentialIDAsBytes/webauthn_credential.yml new file mode 100644 index 0000000000..c02a76e374 --- /dev/null +++ b/models/migrations/fixtures/Test_storeWebauthnCredentialIDAsBytes/webauthn_credential.yml @@ -0,0 +1,31 @@ +- + id: 1 + lower_name: "u2fkey-correctly-migrated" + name: "u2fkey-correctly-migrated" + user_id: 1 + credential_id: "TVHE44TOH7DF7V48SEAIT3EMMJ7TGBOQ289E5AQB34S98LFCUFJ7U2NAVI8RJG6K2F4TC8AQ8KBNO7AGEOQOL9NE43GR63HTEHJSLOG=" + public_key: 0x040d0967a2cad045011631187576492a0beb5b377954b4f694c5afc8bdf25270f87f09a9ab6ce9c282f447ba71b2f2bae2105b32b847e0704f310f48644e3eddf2 + attestation_type: 'fido-u2f' + sign_count: 1 + clone_warning: false +- + id: 2 + lower_name: "non-u2f-key" + name: "non-u2f-key" + user_id: 1 + credential_id: "051CLMMKB62S6M9M2A4H54K7MMCQALFJ36G4TGB2S9A47APLTILU6C6744CEBG4EKCGV357N21BSLH8JD33GQMFAR6DQ70S76P34J6FR" + public_key: 0x040d0967a2cad045011631187576492a0beb5b377954b4f694c5afc8bdf25270f87f09a9ab6ce9c282f447ba71b2f2bae2105b32b847e0704f310f48644e3eddf2 + attestation_type: 'none' + sign_count: 1 + clone_warning: false +- + id: 4 + lower_name: "packed-key" + name: "packed-key" + user_id: 1 + credential_id: "APU4B1NDTEVTEM60V4T0FRL7SRJMO9KIE2AKFQ8JDGTQ7VHFI41FDEFTDLBVQEAE4ER49QV2GTGVFDNBO31BPOA3OQN6879OT6MTU3G=" + public_key: 0x040d0967a2cad045011631187576492a0beb5b377954b4f694c5afc8bdf25270f87f09a9ab6ce9c282f447ba71b2f2bae2105b32b847e0704f310f48644e3eddf2 + attestation_type: 'fido-u2f' + sign_count: 1 + clone_warning: false + diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index beeba866dc..2719f45efb 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -400,6 +400,12 @@ var migrations = []Migration{ NewMigration("Add sync_on_commit column to push_mirror table", addSyncOnCommitColForPushMirror), // v220 -> v221 NewMigration("Add container repository property", addContainerRepositoryProperty), + // v221 -> v222 + NewMigration("Store WebAuthentication CredentialID as bytes and increase size to at least 1024", storeWebauthnCredentialIDAsBytes), + // v222 -> v223 + NewMigration("Drop old CredentialID column", dropOldCredentialIDColumn), + // v223 -> v224 + NewMigration("Rename CredentialIDBytes column to CredentialID", renameCredentialIDBytes), } // GetCurrentDBVersion returns the current db version diff --git a/models/migrations/v221.go b/models/migrations/v221.go new file mode 100644 index 0000000000..f3bcfcdf1d --- /dev/null +++ b/models/migrations/v221.go @@ -0,0 +1,75 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package migrations + +import ( + "encoding/base32" + "fmt" + + "code.gitea.io/gitea/modules/timeutil" + + "xorm.io/xorm" +) + +func storeWebauthnCredentialIDAsBytes(x *xorm.Engine) error { + // Create webauthnCredential table + type webauthnCredential struct { + ID int64 `xorm:"pk autoincr"` + Name string + LowerName string `xorm:"unique(s)"` + UserID int64 `xorm:"INDEX unique(s)"` + CredentialID string `xorm:"INDEX VARCHAR(410)"` + // Note the lack of INDEX here - these will be created once the column is renamed in v223.go + CredentialIDBytes []byte `xorm:"VARBINARY(1024)"` // CredentialID is at most 1023 bytes as per spec released 20 July 2022 + PublicKey []byte + AttestationType string + AAGUID []byte + SignCount uint32 `xorm:"BIGINT"` + CloneWarning bool + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` + } + if err := x.Sync2(&webauthnCredential{}); err != nil { + return err + } + + var start int + creds := make([]*webauthnCredential, 0, 50) + for { + err := x.Select("id, credential_id").OrderBy("id").Limit(50, start).Find(&creds) + if err != nil { + return err + } + + err = func() error { + sess := x.NewSession() + defer sess.Close() + if err := sess.Begin(); err != nil { + return fmt.Errorf("unable to allow start session. Error: %w", err) + } + for _, cred := range creds { + cred.CredentialIDBytes, err = base32.HexEncoding.DecodeString(cred.CredentialID) + if err != nil { + return fmt.Errorf("unable to parse credential id %s for credential[%d]: %w", cred.CredentialID, cred.ID, err) + } + count, err := sess.ID(cred.ID).Cols("credential_id_bytes").Update(cred) + if count != 1 || err != nil { + return fmt.Errorf("unable to update credential id bytes for credential[%d]: %d,%w", cred.ID, count, err) + } + } + return sess.Commit() + }() + if err != nil { + return err + } + + if len(creds) < 50 { + break + } + start += 50 + creds = creds[:0] + } + return nil +} diff --git a/models/migrations/v221_test.go b/models/migrations/v221_test.go new file mode 100644 index 0000000000..c50ca5c873 --- /dev/null +++ b/models/migrations/v221_test.go @@ -0,0 +1,65 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package migrations + +import ( + "encoding/base32" + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_storeWebauthnCredentialIDAsBytes(t *testing.T) { + // Create webauthnCredential table + type WebauthnCredential struct { + ID int64 `xorm:"pk autoincr"` + Name string + LowerName string `xorm:"unique(s)"` + UserID int64 `xorm:"INDEX unique(s)"` + CredentialID string `xorm:"INDEX VARCHAR(410)"` + PublicKey []byte + AttestationType string + AAGUID []byte + SignCount uint32 `xorm:"BIGINT"` + CloneWarning bool + } + + type ExpectedWebauthnCredential struct { + ID int64 `xorm:"pk autoincr"` + CredentialID string // CredentialID is at most 1023 bytes as per spec released 20 July 2022 + } + + type ConvertedWebauthnCredential struct { + ID int64 `xorm:"pk autoincr"` + CredentialIDBytes []byte `xorm:"VARBINARY(1024)"` // CredentialID is at most 1023 bytes as per spec released 20 July 2022 + } + + // Prepare and load the testing database + x, deferable := prepareTestEnv(t, 0, new(WebauthnCredential), new(ExpectedWebauthnCredential)) + defer deferable() + if x == nil || t.Failed() { + return + } + + if err := storeWebauthnCredentialIDAsBytes(x); err != nil { + assert.NoError(t, err) + return + } + + expected := []ExpectedWebauthnCredential{} + if err := x.Table("expected_webauthn_credential").Asc("id").Find(&expected); !assert.NoError(t, err) { + return + } + + got := []ConvertedWebauthnCredential{} + if err := x.Table("webauthn_credential").Select("id, credential_id_bytes").Asc("id").Find(&got); !assert.NoError(t, err) { + return + } + + for i, e := range expected { + credIDBytes, _ := base32.HexEncoding.DecodeString(e.CredentialID) + assert.Equal(t, credIDBytes, got[i].CredentialIDBytes) + } +} diff --git a/models/migrations/v222.go b/models/migrations/v222.go new file mode 100644 index 0000000000..99acdfd206 --- /dev/null +++ b/models/migrations/v222.go @@ -0,0 +1,64 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package migrations + +import ( + "context" + "fmt" + + "code.gitea.io/gitea/modules/timeutil" + + "xorm.io/xorm" +) + +func dropOldCredentialIDColumn(x *xorm.Engine) error { + // This migration maybe rerun so that we should check if it has been run + credentialIDExist, err := x.Dialect().IsColumnExist(x.DB(), context.Background(), "webauthn_credential", "credential_id") + if err != nil { + return err + } + if !credentialIDExist { + // Column is already non-extant + return nil + } + credentialIDBytesExists, err := x.Dialect().IsColumnExist(x.DB(), context.Background(), "webauthn_credential", "credential_id_bytes") + if err != nil { + return err + } + if !credentialIDBytesExists { + // looks like 221 hasn't properly run + return fmt.Errorf("webauthn_credential does not have a credential_id_bytes column... it is not safe to run this migration") + } + + // Create webauthnCredential table + type webauthnCredential struct { + ID int64 `xorm:"pk autoincr"` + Name string + LowerName string `xorm:"unique(s)"` + UserID int64 `xorm:"INDEX unique(s)"` + CredentialID string `xorm:"INDEX VARCHAR(410)"` + // Note the lack of the INDEX on CredentialIDBytes - we will add this in v223.go + CredentialIDBytes []byte `xorm:"VARBINARY(1024)"` // CredentialID is at most 1023 bytes as per spec released 20 July 2022 + PublicKey []byte + AttestationType string + AAGUID []byte + SignCount uint32 `xorm:"BIGINT"` + CloneWarning bool + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` + } + if err := x.Sync2(&webauthnCredential{}); err != nil { + return err + } + + // Drop the old credential ID + sess := x.NewSession() + defer sess.Close() + + if err := dropTableColumns(sess, "webauthn_credential", "credential_id"); err != nil { + return fmt.Errorf("unable to drop old credentialID column: %w", err) + } + return sess.Commit() +} diff --git a/models/migrations/v223.go b/models/migrations/v223.go new file mode 100644 index 0000000000..d7ee4812b8 --- /dev/null +++ b/models/migrations/v223.go @@ -0,0 +1,103 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package migrations + +import ( + "context" + "fmt" + + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/timeutil" + + "xorm.io/xorm" +) + +func renameCredentialIDBytes(x *xorm.Engine) error { + // This migration maybe rerun so that we should check if it has been run + credentialIDExist, err := x.Dialect().IsColumnExist(x.DB(), context.Background(), "webauthn_credential", "credential_id") + if err != nil { + return err + } + if credentialIDExist { + credentialIDBytesExists, err := x.Dialect().IsColumnExist(x.DB(), context.Background(), "webauthn_credential", "credential_id_bytes") + if err != nil { + return err + } + if !credentialIDBytesExists { + return nil + } + } + + err = func() error { + // webauthnCredential table + type webauthnCredential struct { + ID int64 `xorm:"pk autoincr"` + Name string + LowerName string `xorm:"unique(s)"` + UserID int64 `xorm:"INDEX unique(s)"` + // Note the lack of INDEX here + CredentialIDBytes []byte `xorm:"VARBINARY(1024)"` // CredentialID is at most 1023 bytes as per spec released 20 July 2022 + PublicKey []byte + AttestationType string + AAGUID []byte + SignCount uint32 `xorm:"BIGINT"` + CloneWarning bool + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` + } + sess := x.NewSession() + defer sess.Close() + if err := sess.Begin(); err != nil { + return err + } + + if err := sess.Sync2(new(webauthnCredential)); err != nil { + return fmt.Errorf("error on Sync2: %v", err) + } + + if credentialIDExist { + // if both errors and message exist, drop message at first + if err := dropTableColumns(sess, "webauthn_credential", "credential_id"); err != nil { + return err + } + } + + switch { + case setting.Database.UseMySQL: + if _, err := sess.Exec("ALTER TABLE `webauthn_credential` CHANGE credential_id_bytes credential_id VARBINARY(1024)"); err != nil { + return err + } + case setting.Database.UseMSSQL: + if _, err := sess.Exec("sp_rename 'webauthn_credential.credential_id_bytes', 'credential_id', 'COLUMN'"); err != nil { + return err + } + default: + if _, err := sess.Exec("ALTER TABLE `webauthn_credential` RENAME COLUMN credential_id_bytes TO credential_id"); err != nil { + return err + } + } + return sess.Commit() + }() + if err != nil { + return err + } + + // Create webauthnCredential table + type webauthnCredential struct { + ID int64 `xorm:"pk autoincr"` + Name string + LowerName string `xorm:"unique(s)"` + UserID int64 `xorm:"INDEX unique(s)"` + CredentialID []byte `xorm:"INDEX VARBINARY(1024)"` // CredentialID is at most 1023 bytes as per spec released 20 July 2022 + PublicKey []byte + AttestationType string + AAGUID []byte + SignCount uint32 `xorm:"BIGINT"` + CloneWarning bool + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` + } + return x.Sync2(&webauthnCredential{}) +} diff --git a/routers/web/auth/webauthn.go b/routers/web/auth/webauthn.go index c0cf58f3d3..c5e308c66b 100644 --- a/routers/web/auth/webauthn.go +++ b/routers/web/auth/webauthn.go @@ -5,7 +5,6 @@ package auth import ( - "encoding/base32" "errors" "net/http" @@ -132,7 +131,7 @@ func WebAuthnLoginAssertionPost(ctx *context.Context) { } // Success! Get the credential and update the sign count with the new value we received. - dbCred, err := auth.GetWebAuthnCredentialByCredID(user.ID, base32.HexEncoding.EncodeToString(cred.ID)) + dbCred, err := auth.GetWebAuthnCredentialByCredID(user.ID, cred.ID) if err != nil { ctx.ServerError("GetWebAuthnCredentialByCredID", err) return From 8769df117d6cc2f4ab00d6e1d54ef4241d063f11 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sat, 30 Jul 2022 21:08:25 +0200 Subject: [PATCH 074/246] Changelog v1.17.0 (#20541) --- CHANGELOG.md | 80 +++++++++++++++++++++++++--------------------------- 1 file changed, 38 insertions(+), 42 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ae2159d1ce..dcbafe8f0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,44 +4,7 @@ This changelog goes through all the changes that have been made in each release without substantial changes to our git log; to see the highlights of what has been added to each release, please refer to the [blog](https://blog.gitea.io). -## [1.17.0-rc2](https://github.com/go-gitea/gitea/releases/tag/v1.17.0-rc2) - 2022-07-13 - -* SECURITY - * Use git.HOME_PATH for Git HOME directory (#20114) (#20293) - * Add write check for creating Commit Statuses (#20332) (#20333) -* ENHANCEMENTS - * Make notification bell more prominent on mobile (#20108, #20236, #20251) (#20269) - * Adjust max-widths for the repository file table (#20243) (#20247) - * Display full name (#20171) (#20246) -* BUGFIXES - * Allow RSA 2047 bit keys (#20272) (#20396) - * Add missing return for when topic isn't found (#20351) (#20395) - * Fix commit status icon when in subdirectory (#20285) (#20385) - * Initialize cron last (#20373) (#20384) - * Set target on create release with existing tag (#20381) (#20382) - * Update xorm.io/xorm to fix a interpreting db column sizes issue on 32bit systems (#20371) (#20372) - * Make sure `repo_dir` is an empty directory or doesn't exist before 'dump-repo' (#20205) (#20370) - * Prevent context deadline error propagation in GetCommitsInfo (#20346) (#20361) - * Correctly handle draft releases without a tag (#20314) (#20335) - * Prevent "empty" scrollbars on Firefox (#20294) (#20308) - * Refactor SSH init code, fix directory creation for TrustedUserCAKeys file (#20299) (#20306) - * Bump goldmark to v1.4.13 (#20300) (#20301) - * Do not create empty ".ssh" directory when loading config (#20289) (#20298) - * Fix NPE when using non-numeric (#20277) (#20278) - * Store read access in access for team repositories (#20275) (#20276) - * EscapeFilter the group dn membership (#20200) (#20254) - * Only show Followers that current user can access (#20220) (#20252) - * Update Bluemonday to v1.0.19 (#20199) (#20209) - * Refix indices on actions table (#20158) (#20198) - * Check if project has the same repository id with issue when assign project to issue (#20133) (#20188) - * Fix remove file on initial comment (#20127) (#20128) - * Catch the error before the response is processed by goth (#20000) (#20102) - * Dashboard feed respect setting.UI.FeedPagingNum again (#20094) (#20099) - * Alter hook_task TEXT fields to LONGTEXT (#20038) (#20041) - * Respond with a 401 on git push when password isn't changed yet (#20026) (#20027) - * Return 404 when tag is broken (#20017) (#20024) - -## [1.17.0-rc1](https://github.com/go-gitea/gitea/releases/tag/v1.17.0-rc1) - 2022-06-18 +## [1.17.0](https://github.com/go-gitea/gitea/releases/tag/v1.17.0) - 2022-07-30 * BREAKING * Require go1.18 for Gitea 1.17 (#19918) @@ -66,6 +29,8 @@ been added to each release, please refer to the [blog](https://blog.gitea.io). * Restrict email address validation (#17688) * Refactor Router Logger (#17308) * SECURITY + * Use git.HOME_PATH for Git HOME directory (#20114) (#20293) + * Add write check for creating Commit Statuses (#20332) (#20333) * Remove deprecated SSH ciphers from default (#18697) * FEDERATION * Return statistic information for nodeinfo (#19561) @@ -104,6 +69,9 @@ been added to each release, please refer to the [blog](https://blog.gitea.io). * Return primary language and repository language stats API URL (#18396) * Implement http signatures support for the API (#17565) * ENHANCEMENTS + * Make notification bell more prominent on mobile (#20108, #20236, #20251) (#20269) + * Adjust max-widths for the repository file table (#20243) (#20247) + * Display full name (#20171) (#20246) * Add dbconsistency checks for Stopwatches (#20010) * Add fetch.writeCommitGraph to gitconfig (#20006) * Add fgprof pprof profiler (#20005) @@ -148,7 +116,6 @@ been added to each release, please refer to the [blog](https://blog.gitea.io). * PullService lock via pullID (#19520) * Make repository file list useable on mobile (#19515) * more context for models (#19511) - * Allow package dump skipping (#19506) * Refactor readme file renderer (#19502) * By default force vertical tabs on mobile (#19486) * Github style following followers (#19482) @@ -165,7 +132,6 @@ been added to each release, please refer to the [blog](https://blog.gitea.io). * Move access and repo permission to models/perm/access (#19350) * Disallow selecting the text of buttons (#19330) * Allow custom redirect for landing page (#19324) - * Repository level enable package or disable (#19323) * Remove dependent on session auth for api/v1 routers (#19321) * Never use /api/v1 from Gitea UI Pages (#19318) * Remove legacy unmaintained packages, refactor to support change default locale (#19308) @@ -222,13 +188,44 @@ been added to each release, please refer to the [blog](https://blog.gitea.io). * Allow custom default merge message with .gitea/default_merge_message/_TEMPLATE.md (#18177) * Prettify number of issues (#17760) * Add a "admin user generate-access-token" subcommand (#17722) - * Move project files into models/project sub package (#17704) * Custom regexp external issues (#17624) * Add smtp password to install page (#17564) * Add config options to hide issue events (#17414) * Prevent double click new issue/pull/comment button (#16157) * Show issue assignee on project board (#15232) * BUGFIXES + * WebAuthn CredentialID field needs to be increased in size (#20530) (#20555) + * Ensure that all unmerged files are merged when conflict checking (#20528) (#20536) + * Stop logging EOFs and exit(1)s in ssh handler (#20476) (#20529) + * Add labels to two buttons that were missing them (#20419) (#20524) + * Fix ROOT_URL detection for URLs without trailing slash (#20502) (#20503) + * Dismiss prior pull reviews if done via web in review dismiss (#20197) (#20407) + * Allow RSA 2047 bit keys (#20272) (#20396) + * Add missing return for when topic isn't found (#20351) (#20395) + * Fix commit status icon when in subdirectory (#20285) (#20385) + * Initialize cron last (#20373) (#20384) + * Set target on create release with existing tag (#20381) (#20382) + * Update xorm.io/xorm to fix a interpreting db column sizes issue on 32bit systems (#20371) (#20372) + * Make sure `repo_dir` is an empty directory or doesn't exist before 'dump-repo' (#20205) (#20370) + * Prevent context deadline error propagation in GetCommitsInfo (#20346) (#20361) + * Correctly handle draft releases without a tag (#20314) (#20335) + * Prevent "empty" scrollbars on Firefox (#20294) (#20308) + * Refactor SSH init code, fix directory creation for TrustedUserCAKeys file (#20299) (#20306) + * Bump goldmark to v1.4.13 (#20300) (#20301) + * Do not create empty ".ssh" directory when loading config (#20289) (#20298) + * Fix NPE when using non-numeric (#20277) (#20278) + * Store read access in access for team repositories (#20275) (#20276) + * EscapeFilter the group dn membership (#20200) (#20254) + * Only show Followers that current user can access (#20220) (#20252) + * Update Bluemonday to v1.0.19 (#20199) (#20209) + * Refix indices on actions table (#20158) (#20198) + * Check if project has the same repository id with issue when assign project to issue (#20133) (#20188) + * Fix remove file on initial comment (#20127) (#20128) + * Catch the error before the response is processed by goth (#20000) (#20102) + * Dashboard feed respect setting.UI.FeedPagingNum again (#20094) (#20099) + * Alter hook_task TEXT fields to LONGTEXT (#20038) (#20041) + * Respond with a 401 on git push when password isn't changed yet (#20026) (#20027) + * Return 404 when tag is broken (#20017) (#20024) * Alter hook_task TEXT fields to LONGTEXT (#20038) (#20041) * Respond with a 401 on git push when password isn't changed yet (#20026) (#20027) * Return 404 when tag is broken (#20017) (#20024) @@ -303,7 +300,6 @@ been added to each release, please refer to the [blog](https://blog.gitea.io). * MISC * Fix aria for logo (#19955) * In code search, get code unit accessible repos in one (main) query (#19764) - * Enable packages by default again (#19746) * Add tooltip to pending PR comments (#19662) * Improve sync performance for pull-mirrors (#19125) * Improve dashboard's repo list performance (#18963) From 51c8c0f3feb6b71ff5bf45e9e1422f2428eb7f5f Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Sun, 31 Jul 2022 22:41:06 +0800 Subject: [PATCH 075/246] Fix default merge style for pull requests (#20564) (#20565) --- templates/repo/issue/view_content/pull.tmpl | 1 + web_src/js/components/PullRequestMergeForm.vue | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/templates/repo/issue/view_content/pull.tmpl b/templates/repo/issue/view_content/pull.tmpl index 23aa97217d..0d92393cb2 100644 --- a/templates/repo/issue/view_content/pull.tmpl +++ b/templates/repo/issue/view_content/pull.tmpl @@ -358,6 +358,7 @@ 'emptyCommit': {{.Issue.PullRequest.IsEmpty}}, 'pullHeadCommitID': {{.PullHeadCommitID}}, 'isPullBranchDeletable': {{.IsPullBranchDeletable}}, + 'defaultMergeStyle': {{.MergeStyle}}, 'defaultDeleteBranchAfterMerge': {{$prUnit.PullRequestsConfig.DefaultDeleteBranchAfterMerge}}, 'mergeMessageFieldPlaceHolder': {{$.i18n.Tr "repo.editor.commit_message_desc"}}, diff --git a/web_src/js/components/PullRequestMergeForm.vue b/web_src/js/components/PullRequestMergeForm.vue index 08b1f9cb86..64ba98728e 100644 --- a/web_src/js/components/PullRequestMergeForm.vue +++ b/web_src/js/components/PullRequestMergeForm.vue @@ -145,7 +145,10 @@ export default { created() { this.mergeStyleAllowedCount = this.mergeForm.mergeStyles.reduce((v, msd) => v + (msd.allowed ? 1 : 0), 0); - this.switchMergeStyle(this.mergeForm.mergeStyles.find((e) => e.allowed)?.name, !this.mergeForm.canMergeNow); + + let mergeStyle = this.mergeForm.mergeStyles.find((e) => e.allowed && e.name === this.mergeForm.defaultMergeStyle)?.name; + if (!mergeStyle) mergeStyle = this.mergeForm.mergeStyles.find((e) => e.allowed)?.name; + this.switchMergeStyle(mergeStyle, !this.mergeForm.canMergeNow); }, mounted() { From 56b99551ae95c18c41ca45cd58e739895af44ffc Mon Sep 17 00:00:00 2001 From: silverwind Date: Tue, 2 Aug 2022 06:31:38 +0200 Subject: [PATCH 076/246] Clean up and fix clone button script (#20415 & #20600) (#20599) * Clean up and fix clone button script (#20415) The button 'primary' class needs to be set in a synchronous script to prevent flicker of the button which was regressed recently, fixed that. Additionally, reduced the two script tags to just one, the previous scripts were actually initializing the buttons thrice on the empty repo page, now it only initializes once. Finally, removed duplicate code and re-used the inline function in the update code as well. I had to split out the script into a separate template as on the empty repo page, the script needs access to the clone URL span in the example text, which is rendered below the clone buttons, so buttons and script could not be combined. * Add default value for clone URLs Default clone URLs to HTTP(S) in DOM rendering. JS will immediately replace this if the user preference is SSH. Fixes: https://github.com/go-gitea/gitea/issues/20558 --- templates/repo/clone_buttons.tmpl | 11 +---------- templates/repo/clone_script.tmpl | 23 +++++++++++++++++++++++ templates/repo/empty.tmpl | 19 +++---------------- templates/repo/home.tmpl | 1 + templates/repo/wiki/revision.tmpl | 1 + templates/repo/wiki/view.tmpl | 1 + web_src/js/features/repo-common.js | 30 +++--------------------------- 7 files changed, 33 insertions(+), 53 deletions(-) create mode 100644 templates/repo/clone_script.tmpl diff --git a/templates/repo/clone_buttons.tmpl b/templates/repo/clone_buttons.tmpl index 5affc5f322..b3aac28db8 100644 --- a/templates/repo/clone_buttons.tmpl +++ b/templates/repo/clone_buttons.tmpl @@ -9,16 +9,7 @@ SSH {{end}} - - - + diff --git a/templates/repo/clone_script.tmpl b/templates/repo/clone_script.tmpl new file mode 100644 index 0000000000..48b7ad3299 --- /dev/null +++ b/templates/repo/clone_script.tmpl @@ -0,0 +1,23 @@ + diff --git a/templates/repo/empty.tmpl b/templates/repo/empty.tmpl index eee607ecaf..7703cb661a 100644 --- a/templates/repo/empty.tmpl +++ b/templates/repo/empty.tmpl @@ -37,7 +37,7 @@ git init {{if ne .Repository.DefaultBranch "master"}}git checkout -b {{.Repository.DefaultBranch}}{{end}} git add README.md git commit -m "first commit" -git remote add origin +git remote add origin {{$.CloneButtonOriginLink.HTTPS}} git push -u origin {{.Repository.DefaultBranch}}
@@ -46,24 +46,11 @@ git push -u origin {{.Repository.DefaultBranch}}

{{.i18n.Tr "repo.push_exist_repo"}}

-
git remote add origin 
+									
git remote add origin {{$.CloneButtonOriginLink.HTTPS}}
 git push -u origin {{.Repository.DefaultBranch}}
- - + {{template "repo/clone_script" .}} {{end}} {{else}}
diff --git a/templates/repo/home.tmpl b/templates/repo/home.tmpl index 1a0323f5d1..28cb9df636 100644 --- a/templates/repo/home.tmpl +++ b/templates/repo/home.tmpl @@ -124,6 +124,7 @@ {{if eq $n 0}}
{{template "repo/clone_buttons" .}} + {{template "repo/clone_script" .}}
{{end}} - {{if (not .IsDeleted)}} + {{if and (not .IsDeleted) (not $.DisableDownloadSourceArchives)}}