From d6b96627c1cf04d254326ce5e751fddf2093a9b1 Mon Sep 17 00:00:00 2001 From: zeripath Date: Mon, 19 Dec 2022 11:37:15 +0000 Subject: [PATCH 01/18] Add setting to disable the git apply step in test patch (#22130) For a long time Gitea has tested PR patches using a git apply --check method, and in fact prior to the introduction of a read-tree assisted three-way merge in #18004, this was the only way of checking patches. Since #18004, the git apply --check method has been a fallback method, only used when the read-tree three-way merge method has detected a conflict. The read-tree assisted three-way merge method is much faster and less resource intensive method of detecting conflicts. #18004 kept the git apply method around because it was thought possible that this fallback might be able to rectify conflicts that the read-tree three-way merge detected. I am not certain if this could ever be the case. Given the uncertainty here and the now relative stability of the read-tree method - this PR makes using this fallback optional and disables it by default. The hope is that users will not notice any significant difference in conflict detection and we will be able to remove the git apply fallback in future, and/or improve the read-tree three-way merge method to catch any conflicts that git apply method might have been able to fix. An additional benefit is that patch checking should be significantly less resource intensive and much quicker. (See https://github.com/go-gitea/gitea/issues/22083\#issuecomment-1347961737) Ref #22083 Signed-off-by: Andrew Thornton Co-authored-by: Lunny Xiao Co-authored-by: KN4CK3R --- custom/conf/app.example.ini | 3 +++ .../doc/advanced/config-cheat-sheet.en-us.md | 1 + modules/setting/repository.go | 2 ++ services/pull/patch.go | 21 +++++++++++++++---- 4 files changed, 23 insertions(+), 4 deletions(-) diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 8824d31a4a..754eab452f 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -1036,6 +1036,9 @@ ROUTER = console ;; ;; Add co-authored-by and co-committed-by trailers if committer does not match author ;ADD_CO_COMMITTER_TRAILERS = true +;; +;; In addition to testing patches using the three-way merge method, re-test conflicting patches with git apply +;TEST_CONFLICTING_PATCHES_WITH_GIT_APPLY = 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 dcf91c3396..0268938187 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md +++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md @@ -134,6 +134,7 @@ In addition there is _`StaticRootPath`_ which can be set as a built-in at build - `DEFAULT_MERGE_MESSAGE_OFFICIAL_APPROVERS_ONLY`: **true**: In default merge messages only include approvers who are officially allowed to review. - `POPULATE_SQUASH_COMMENT_WITH_COMMIT_MESSAGES`: **false**: In default squash-merge messages include the commit message of all commits comprising the pull request. - `ADD_CO_COMMITTER_TRAILERS`: **true**: Add co-authored-by and co-committed-by trailers to merge commit messages if committer does not match author. +- `TEST_CONFLICTING_PATCHES_WITH_GIT_APPLY`: **false**: PR patches are tested using a three-way merge method to discover if there are conflicts. If this setting is set to **true**, conflicting patches will be retested using `git apply` - This was the previous behaviour in 1.18 (and earlier) but is somewhat inefficient. Please report if you find that this setting is required. ### Repository - Issue (`repository.issue`) diff --git a/modules/setting/repository.go b/modules/setting/repository.go index 19594369be..ea288d2ed2 100644 --- a/modules/setting/repository.go +++ b/modules/setting/repository.go @@ -82,6 +82,7 @@ var ( DefaultMergeMessageOfficialApproversOnly bool PopulateSquashCommentWithCommitMessages bool AddCoCommitterTrailers bool + TestConflictingPatchesWithGitApply bool } `ini:"repository.pull-request"` // Issue Setting @@ -204,6 +205,7 @@ var ( DefaultMergeMessageOfficialApproversOnly bool PopulateSquashCommentWithCommitMessages bool AddCoCommitterTrailers bool + TestConflictingPatchesWithGitApply bool }{ WorkInProgressPrefixes: []string{"WIP:", "[WIP]"}, // Same as GitHub. See diff --git a/services/pull/patch.go b/services/pull/patch.go index e0da410c4d..9ef8b86043 100644 --- a/services/pull/patch.go +++ b/services/pull/patch.go @@ -23,6 +23,7 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/process" repo_module "code.gitea.io/gitea/modules/repository" + "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" "github.com/gobwas/glob" @@ -289,13 +290,15 @@ func checkConflicts(ctx context.Context, pr *issues_model.PullRequest, gitRepo * // 2. AttemptThreeWayMerge first - this is much quicker than plain patch to base description := fmt.Sprintf("PR[%d] %s/%s#%d", pr.ID, pr.BaseRepo.OwnerName, pr.BaseRepo.Name, pr.Index) - conflict, _, err := AttemptThreeWayMerge(ctx, + conflict, conflictFiles, err := AttemptThreeWayMerge(ctx, tmpBasePath, gitRepo, pr.MergeBase, "base", "tracking", description) if err != nil { return false, err } if !conflict { + // No conflicts detected so we need to check if the patch is empty... + // a. Write the newly merged tree and check the new tree-hash var treeHash string treeHash, _, err = git.NewCommand(ctx, "write-tree").RunStdString(&git.RunOpts{Dir: tmpBasePath}) if err != nil { @@ -307,6 +310,8 @@ func checkConflicts(ctx context.Context, pr *issues_model.PullRequest, gitRepo * if err != nil { return false, err } + + // b. compare the new tree-hash with the base tree hash if treeHash == baseTree.ID.String() { log.Debug("PullRequest[%d]: Patch is empty - ignoring", pr.ID) pr.Status = issues_model.PullRequestStatusEmpty @@ -315,9 +320,17 @@ func checkConflicts(ctx context.Context, pr *issues_model.PullRequest, gitRepo * return false, nil } - // 3. OK read-tree has failed so we need to try a different thing - this might actually succeed where the above fails due to whitespace handling. + // 3. OK the three-way merge method has detected conflicts + // 3a. Are still testing with GitApply? If not set the conflict status and move on + if !setting.Repository.PullRequest.TestConflictingPatchesWithGitApply { + pr.Status = issues_model.PullRequestStatusConflict + pr.ConflictedFiles = conflictFiles - // 3a. Create a plain patch from head to base + log.Trace("Found %d files conflicted: %v", len(pr.ConflictedFiles), pr.ConflictedFiles) + return true, nil + } + + // 3b. Create a plain patch from head to base tmpPatchFile, err := os.CreateTemp("", "patch") if err != nil { log.Error("Unable to create temporary patch file! Error: %v", err) @@ -340,7 +353,7 @@ func checkConflicts(ctx context.Context, pr *issues_model.PullRequest, gitRepo * patchPath := tmpPatchFile.Name() tmpPatchFile.Close() - // 3b. if the size of that patch is 0 - there can be no conflicts! + // 3c. if the size of that patch is 0 - there can be no conflicts! if stat.Size() == 0 { log.Debug("PullRequest[%d]: Patch is empty - ignoring", pr.ID) pr.Status = issues_model.PullRequestStatusEmpty From 27746715842da4739d3dba2f3c423df520113a18 Mon Sep 17 00:00:00 2001 From: silverwind Date: Mon, 19 Dec 2022 22:14:49 +0100 Subject: [PATCH 02/18] Fix heatmap first color being unused (#22157) vue3-calendar-heatmap has the behaviour that the first and second colors are mapped to values null and 0, meaning the second color was not used as intended for values > 0. I think this is a behaviour change from previous vue2 version that was missed during the upgrade. This change makes first and second values the same, so the heatmap can now use one additional color for meaningful values. Before: Screenshot 2022-12-18 at 09 17 58 After: Screenshot 2022-12-18 at 09 18 15 Co-authored-by: Lunny Xiao --- web_src/js/components/ActivityHeatmap.vue | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/web_src/js/components/ActivityHeatmap.vue b/web_src/js/components/ActivityHeatmap.vue index df7e0beb00..6cd72a8bf7 100644 --- a/web_src/js/components/ActivityHeatmap.vue +++ b/web_src/js/components/ActivityHeatmap.vue @@ -32,6 +32,7 @@ export default { }, data: () => ({ colorRange: [ + 'var(--color-secondary-alpha-70)', 'var(--color-secondary-alpha-70)', 'var(--color-primary-light-4)', 'var(--color-primary-light-2)', @@ -50,6 +51,12 @@ export default { return s; } }, + mounted() { + // work around issue with first legend color being rendered twice and legend cut off + const legend = document.querySelector('.vch__external-legend-wrapper'); + legend.setAttribute('viewBox', '12 0 80 10'); + legend.style.marginRight = '-12px'; + }, methods: { handleDayClick(e) { // Reset filter if same date is clicked From 40ba750c4bf1f3f5f8dff5af57b2db4b600f237f Mon Sep 17 00:00:00 2001 From: Gusted Date: Tue, 20 Dec 2022 03:04:55 +0100 Subject: [PATCH 03/18] Check for zero time instant in `TimeStamp.IsZero()` (#22171) - Currently, the 'IsZero' function for 'TimeStamp' just checks if the unix time is zero, which is not the behavior of 'Time.IsZero()', but Gitea is using this method in accordance with the behavior of 'Time.IsZero()'. - Adds a new condition to check for the zero time instant. - Fixes a bug where non-expiring GPG keys where shown as they expired on Jan 01, 0001. - Related https://codeberg.org/Codeberg/Community/issues/791 Before: ![image](https://user-images.githubusercontent.com/25481501/208509035-ecc5fa4a-3bd1-4fa3-beba-90875719163c.png) After: ![image](https://user-images.githubusercontent.com/25481501/208508950-3e7f6eeb-be83-432a-89a6-d738553dafe4.png) --- modules/timeutil/timestamp.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/modules/timeutil/timestamp.go b/modules/timeutil/timestamp.go index 4618db9a76..c8e0d4bdc1 100644 --- a/modules/timeutil/timestamp.go +++ b/modules/timeutil/timestamp.go @@ -12,8 +12,13 @@ import ( // TimeStamp defines a timestamp type TimeStamp int64 -// mock is NOT concurrency-safe!! -var mock time.Time +var ( + // mock is NOT concurrency-safe!! + mock time.Time + + // Used for IsZero, to check if timestamp is the zero time instant. + timeZeroUnix = time.Time{}.Unix() +) // Set sets the time to a mocked time.Time func Set(now time.Time) { @@ -102,5 +107,5 @@ func (ts TimeStamp) FormatDate() string { // IsZero is zero time func (ts TimeStamp) IsZero() bool { - return int64(ts) == 0 + return int64(ts) == 0 || int64(ts) == timeZeroUnix } From 659055138b6d32492b20c9f4d1d5a3cdaa47188d Mon Sep 17 00:00:00 2001 From: Jason Song Date: Tue, 20 Dec 2022 17:07:13 +0800 Subject: [PATCH 04/18] Secrets storage with SecretKey encrypted (#22142) Fork of #14483, but [gave up MasterKey](https://github.com/go-gitea/gitea/pull/14483#issuecomment-1350728557), and fixed some problems. Close #12065. Needed by #13539. Featrues: - Secrets for repo and org, not user yet. - Use SecretKey to encrypte/encrypt secrets. - Trim spaces of secret value. - Add a new locale ini block, to make it easy to support secrets for user. Snapshots: Repo level secrets: ![image](https://user-images.githubusercontent.com/9418365/207823319-b8a4903f-38ca-4af7-9d05-336a5af906f3.png) Rrg level secrets ![image](https://user-images.githubusercontent.com/9418365/207823371-8bd02e93-1928-40d1-8c76-f48b255ace36.png) Co-authored-by: Lauris BH Co-authored-by: Lunny Xiao Co-authored-by: wxiaoguang Co-authored-by: delvh Co-authored-by: KN4CK3R --- docs/content/doc/secrets/overview.en-us.md | 36 ++++++ models/migrations/migrations.go | 2 + models/migrations/v1_19/v236.go | 23 ++++ models/organization/org.go | 2 + models/repo.go | 2 + models/secret/secret.go | 124 +++++++++++++++++++++ options/locale/locale_en-US.ini | 16 +++ routers/web/org/setting.go | 51 +++++++++ routers/web/repo/setting.go | 40 +++++++ routers/web/web.go | 10 ++ services/forms/user_form.go | 12 ++ templates/org/settings/navbar.tmpl | 3 + templates/org/settings/secrets.tmpl | 83 ++++++++++++++ templates/repo/settings/deploy_keys.tmpl | 2 + templates/repo/settings/nav.tmpl | 2 +- templates/repo/settings/navbar.tmpl | 2 +- templates/repo/settings/secrets.tmpl | 60 ++++++++++ 17 files changed, 468 insertions(+), 2 deletions(-) create mode 100644 docs/content/doc/secrets/overview.en-us.md create mode 100644 models/migrations/v1_19/v236.go create mode 100644 models/secret/secret.go create mode 100644 templates/org/settings/secrets.tmpl create mode 100644 templates/repo/settings/secrets.tmpl diff --git a/docs/content/doc/secrets/overview.en-us.md b/docs/content/doc/secrets/overview.en-us.md new file mode 100644 index 0000000000..1a88d6cfbc --- /dev/null +++ b/docs/content/doc/secrets/overview.en-us.md @@ -0,0 +1,36 @@ +--- +date: "2022-12-19T21:26:00+08:00" +title: "Encrypted secrets" +slug: "secrets/overview" +draft: false +toc: false +menu: + sidebar: + parent: "secrets" + name: "Overview" + weight: 1 + identifier: "overview" +--- + +# Encrypted secrets + +Encrypted secrets allow you to store sensitive information in your organization or repository. +Secrets are available on Gitea 1.19+. + +# Naming your secrets + +The following rules apply to secret names: + +Secret names can only contain alphanumeric characters (`[a-z]`, `[A-Z]`, `[0-9]`) or underscores (`_`). Spaces are not allowed. + +Secret names must not start with the `GITHUB_` and `GITEA_` prefix. + +Secret names must not start with a number. + +Secret names are not case-sensitive. + +Secret names must be unique at the level they are created at. + +For example, a secret created at the repository level must have a unique name in that repository, and a secret created at the organization level must have a unique name at that level. + +If a secret with the same name exists at multiple levels, the secret at the lowest level takes precedence. For example, if an organization-level secret has the same name as a repository-level secret, then the repository-level secret takes precedence. diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index e718355f83..591bfa3e86 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -442,6 +442,8 @@ var migrations = []Migration{ NewMigration("Add package cleanup rule table", v1_19.CreatePackageCleanupRuleTable), // v235 -> v236 NewMigration("Add index for access_token", v1_19.AddIndexForAccessToken), + // v236 -> v237 + NewMigration("Create secrets table", v1_19.CreateSecretsTable), } // GetCurrentDBVersion returns the current db version diff --git a/models/migrations/v1_19/v236.go b/models/migrations/v1_19/v236.go new file mode 100644 index 0000000000..f172a85b1f --- /dev/null +++ b/models/migrations/v1_19/v236.go @@ -0,0 +1,23 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_19 //nolint + +import ( + "code.gitea.io/gitea/modules/timeutil" + + "xorm.io/xorm" +) + +func CreateSecretsTable(x *xorm.Engine) error { + type Secret struct { + ID int64 + OwnerID int64 `xorm:"INDEX UNIQUE(owner_repo_name) NOT NULL"` + RepoID int64 `xorm:"INDEX UNIQUE(owner_repo_name) NOT NULL DEFAULT 0"` + Name string `xorm:"UNIQUE(owner_repo_name) NOT NULL"` + Data string `xorm:"LONGTEXT"` + CreatedUnix timeutil.TimeStamp `xorm:"created NOT NULL"` + } + + return x.Sync(new(Secret)) +} diff --git a/models/organization/org.go b/models/organization/org.go index b3d77b4ec6..9d9e9cda46 100644 --- a/models/organization/org.go +++ b/models/organization/org.go @@ -12,6 +12,7 @@ import ( "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/perm" repo_model "code.gitea.io/gitea/models/repo" + secret_model "code.gitea.io/gitea/models/secret" "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/log" @@ -370,6 +371,7 @@ func DeleteOrganization(ctx context.Context, org *Organization) error { &TeamUser{OrgID: org.ID}, &TeamUnit{OrgID: org.ID}, &TeamInvite{OrgID: org.ID}, + &secret_model.Secret{OwnerID: org.ID}, ); err != nil { return fmt.Errorf("DeleteBeans: %w", err) } diff --git a/models/repo.go b/models/repo.go index 9af600c9ba..e95887077c 100644 --- a/models/repo.go +++ b/models/repo.go @@ -21,6 +21,7 @@ import ( access_model "code.gitea.io/gitea/models/perm/access" project_model "code.gitea.io/gitea/models/project" repo_model "code.gitea.io/gitea/models/repo" + secret_model "code.gitea.io/gitea/models/secret" system_model "code.gitea.io/gitea/models/system" "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" @@ -150,6 +151,7 @@ func DeleteRepository(doer *user_model.User, uid, repoID int64) error { &admin_model.Task{RepoID: repoID}, &repo_model.Watch{RepoID: repoID}, &webhook.Webhook{RepoID: repoID}, + &secret_model.Secret{RepoID: repoID}, ); err != nil { return fmt.Errorf("deleteBeans: %w", err) } diff --git a/models/secret/secret.go b/models/secret/secret.go new file mode 100644 index 0000000000..f970d5319e --- /dev/null +++ b/models/secret/secret.go @@ -0,0 +1,124 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package secret + +import ( + "context" + "fmt" + "regexp" + "strings" + + "code.gitea.io/gitea/models/db" + secret_module "code.gitea.io/gitea/modules/secret" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/timeutil" + "code.gitea.io/gitea/modules/util" + + "xorm.io/builder" +) + +type ErrSecretInvalidValue struct { + Name *string + Data *string +} + +func (err ErrSecretInvalidValue) Error() string { + if err.Name != nil { + return fmt.Sprintf("secret name %q is invalid", *err.Name) + } + if err.Data != nil { + return fmt.Sprintf("secret data %q is invalid", *err.Data) + } + return util.ErrInvalidArgument.Error() +} + +func (err ErrSecretInvalidValue) Unwrap() error { + return util.ErrInvalidArgument +} + +// Secret represents a secret +type Secret struct { + ID int64 + OwnerID int64 `xorm:"INDEX UNIQUE(owner_repo_name) NOT NULL"` + RepoID int64 `xorm:"INDEX UNIQUE(owner_repo_name) NOT NULL DEFAULT 0"` + Name string `xorm:"UNIQUE(owner_repo_name) NOT NULL"` + Data string `xorm:"LONGTEXT"` // encrypted data + CreatedUnix timeutil.TimeStamp `xorm:"created NOT NULL"` +} + +// newSecret Creates a new already encrypted secret +func newSecret(ownerID, repoID int64, name, data string) *Secret { + return &Secret{ + OwnerID: ownerID, + RepoID: repoID, + Name: strings.ToUpper(name), + Data: data, + } +} + +// InsertEncryptedSecret Creates, encrypts, and validates a new secret with yet unencrypted data and insert into database +func InsertEncryptedSecret(ctx context.Context, ownerID, repoID int64, name, data string) (*Secret, error) { + encrypted, err := secret_module.EncryptSecret(setting.SecretKey, strings.TrimSpace(data)) + if err != nil { + return nil, err + } + secret := newSecret(ownerID, repoID, name, encrypted) + if err := secret.Validate(); err != nil { + return secret, err + } + return secret, db.Insert(ctx, secret) +} + +func init() { + db.RegisterModel(new(Secret)) +} + +var ( + secretNameReg = regexp.MustCompile("^[A-Z_][A-Z0-9_]*$") + forbiddenSecretPrefixReg = regexp.MustCompile("^GIT(EA|HUB)_") +) + +// Validate validates the required fields and formats. +func (s *Secret) Validate() error { + switch { + case len(s.Name) == 0 || len(s.Name) > 50: + return ErrSecretInvalidValue{Name: &s.Name} + case len(s.Data) == 0: + return ErrSecretInvalidValue{Data: &s.Data} + case !secretNameReg.MatchString(s.Name) || + forbiddenSecretPrefixReg.MatchString(s.Name): + return ErrSecretInvalidValue{Name: &s.Name} + default: + return nil + } +} + +type FindSecretsOptions struct { + db.ListOptions + OwnerID int64 + RepoID int64 +} + +func (opts *FindSecretsOptions) toConds() builder.Cond { + cond := builder.NewCond() + if opts.OwnerID > 0 { + cond = cond.And(builder.Eq{"owner_id": opts.OwnerID}) + } + if opts.RepoID > 0 { + cond = cond.And(builder.Eq{"repo_id": opts.RepoID}) + } + + return cond +} + +func FindSecrets(ctx context.Context, opts FindSecretsOptions) ([]*Secret, error) { + var secrets []*Secret + sess := db.GetEngine(ctx) + if opts.PageSize != 0 { + sess = db.SetSessionPagination(sess, &opts.ListOptions) + } + return secrets, sess. + Where(opts.toConds()). + Find(&secrets) +} diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 70f982b8dc..dd31562c3a 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -3212,3 +3212,19 @@ owner.settings.cleanuprules.remove.days = Remove versions older than owner.settings.cleanuprules.remove.pattern = Remove versions matching owner.settings.cleanuprules.success.update = Cleanup rule has been updated. owner.settings.cleanuprules.success.delete = Cleanup rule has been deleted. + +[secrets] +secrets = Secrets +description = Secrets will be passed to certain actions and cannot be read otherwise. +none = There are no secrets yet. +value = Value +name = Name +creation = Add Secret +creation.name_placeholder = case-insensitive, alphanumeric characters or underscores only, cannot start with GITEA_ or GITHUB_ +creation.value_placeholder = Input any content. Whitespace at the start and end will be omitted. +creation.success = The secret '%s' has been added. +creation.failed = Failed to add secret. +deletion = Remove secret +deletion.description = Removing a secret will revoke its access to repositories. Continue? +deletion.success = The secret has been removed. +deletion.failed = Failed to remove secret. diff --git a/routers/web/org/setting.go b/routers/web/org/setting.go index 899e554ba0..e625962f75 100644 --- a/routers/web/org/setting.go +++ b/routers/web/org/setting.go @@ -12,6 +12,7 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" + secret_model "code.gitea.io/gitea/models/secret" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/models/webhook" "code.gitea.io/gitea/modules/base" @@ -37,6 +38,8 @@ const ( tplSettingsHooks base.TplName = "org/settings/hooks" // tplSettingsLabels template path for render labels settings tplSettingsLabels base.TplName = "org/settings/labels" + // tplSettingsSecrets template path for render secrets settings + tplSettingsSecrets base.TplName = "org/settings/secrets" ) // Settings render the main settings page @@ -246,3 +249,51 @@ func Labels(ctx *context.Context) { ctx.Data["LabelTemplates"] = repo_module.LabelTemplates ctx.HTML(http.StatusOK, tplSettingsLabels) } + +// Secrets render organization secrets page +func Secrets(ctx *context.Context) { + ctx.Data["Title"] = ctx.Tr("repo.secrets") + ctx.Data["PageIsOrgSettings"] = true + ctx.Data["PageIsOrgSettingsSecrets"] = true + + secrets, err := secret_model.FindSecrets(ctx, secret_model.FindSecretsOptions{OwnerID: ctx.Org.Organization.ID}) + if err != nil { + ctx.ServerError("FindSecrets", err) + return + } + ctx.Data["Secrets"] = secrets + + ctx.HTML(http.StatusOK, tplSettingsSecrets) +} + +// SecretsPost add secrets +func SecretsPost(ctx *context.Context) { + form := web.GetForm(ctx).(*forms.AddSecretForm) + + _, err := secret_model.InsertEncryptedSecret(ctx, ctx.Org.Organization.ID, 0, form.Title, form.Content) + if err != nil { + ctx.Flash.Error(ctx.Tr("secrets.creation.failed")) + log.Error("validate secret: %v", err) + ctx.Redirect(ctx.Org.OrgLink + "/settings/secrets") + return + } + + log.Trace("Org %d: secret added", ctx.Org.Organization.ID) + ctx.Flash.Success(ctx.Tr("secrets.creation.success", form.Title)) + ctx.Redirect(ctx.Org.OrgLink + "/settings/secrets") +} + +// SecretsDelete delete secrets +func SecretsDelete(ctx *context.Context) { + id := ctx.FormInt64("id") + if _, err := db.DeleteByBean(ctx, &secret_model.Secret{ID: id}); err != nil { + ctx.Flash.Error(ctx.Tr("secrets.deletion.failed")) + log.Error("delete secret %d: %v", id, err) + } else { + ctx.Flash.Success(ctx.Tr("secrets.deletion.success")) + } + + ctx.JSON(http.StatusOK, map[string]interface{}{ + "redirect": ctx.Org.OrgLink + "/settings/secrets", + }) +} diff --git a/routers/web/repo/setting.go b/routers/web/repo/setting.go index f35adcaa10..913ed6c7cb 100644 --- a/routers/web/repo/setting.go +++ b/routers/web/repo/setting.go @@ -19,6 +19,7 @@ import ( "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/perm" repo_model "code.gitea.io/gitea/models/repo" + secret_model "code.gitea.io/gitea/models/secret" unit_model "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/base" @@ -1113,12 +1114,37 @@ func DeployKeys(ctx *context.Context) { } ctx.Data["Deploykeys"] = keys + secrets, err := secret_model.FindSecrets(ctx, secret_model.FindSecretsOptions{RepoID: ctx.Repo.Repository.ID}) + if err != nil { + ctx.ServerError("FindSecrets", err) + return + } + ctx.Data["Secrets"] = secrets + ctx.HTML(http.StatusOK, tplDeployKeys) } +// SecretsPost response for creating a new secret +func SecretsPost(ctx *context.Context) { + form := web.GetForm(ctx).(*forms.AddSecretForm) + + _, err := secret_model.InsertEncryptedSecret(ctx, 0, ctx.Repo.Repository.ID, form.Title, form.Content) + if err != nil { + ctx.Flash.Error(ctx.Tr("secrets.creation.failed")) + log.Error("validate secret: %v", err) + ctx.Redirect(ctx.Repo.RepoLink + "/settings/keys") + return + } + + log.Trace("Secret added: %d", ctx.Repo.Repository.ID) + ctx.Flash.Success(ctx.Tr("secrets.creation.success", form.Title)) + ctx.Redirect(ctx.Repo.RepoLink + "/settings/keys") +} + // DeployKeysPost response for adding a deploy key of a repository func DeployKeysPost(ctx *context.Context) { form := web.GetForm(ctx).(*forms.AddKeyForm) + ctx.Data["Title"] = ctx.Tr("repo.settings.deploy_keys") ctx.Data["PageIsSettingsKeys"] = true ctx.Data["DisableSSH"] = setting.SSH.Disabled @@ -1177,6 +1203,20 @@ func DeployKeysPost(ctx *context.Context) { ctx.Redirect(ctx.Repo.RepoLink + "/settings/keys") } +func DeleteSecret(ctx *context.Context) { + id := ctx.FormInt64("id") + if _, err := db.DeleteByBean(ctx, &secret_model.Secret{ID: id}); err != nil { + ctx.Flash.Error(ctx.Tr("secrets.deletion.failed")) + log.Error("delete secret %d: %v", id, err) + } else { + ctx.Flash.Success(ctx.Tr("secrets.deletion.success")) + } + + ctx.JSON(http.StatusOK, map[string]interface{}{ + "redirect": ctx.Repo.RepoLink + "/settings/keys", + }) +} + // DeleteDeployKey response for deleting a deploy key func DeleteDeployKey(ctx *context.Context) { if err := asymkey_service.DeleteDeployKey(ctx.Doer, ctx.FormInt64("id")); err != nil { diff --git a/routers/web/web.go b/routers/web/web.go index f9d97758a1..20d067a163 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -774,6 +774,12 @@ func RegisterRoutes(m *web.Route) { m.Post("/initialize", web.Bind(forms.InitializeLabelsForm{}), org.InitializeLabels) }) + m.Group("/secrets", func() { + m.Get("", org.Secrets) + m.Post("", web.Bind(forms.AddSecretForm{}), org.SecretsPost) + m.Post("/delete", org.SecretsDelete) + }) + m.Route("/delete", "GET,POST", org.SettingsDelete) m.Group("/packages", func() { @@ -912,6 +918,10 @@ func RegisterRoutes(m *web.Route) { m.Combo("").Get(repo.DeployKeys). Post(web.Bind(forms.AddKeyForm{}), repo.DeployKeysPost) m.Post("/delete", repo.DeleteDeployKey) + m.Group("/secrets", func() { + m.Post("", web.Bind(forms.AddSecretForm{}), repo.SecretsPost) + m.Post("/delete", repo.DeleteSecret) + }) }) m.Group("/lfs", func() { diff --git a/services/forms/user_form.go b/services/forms/user_form.go index cd2c45261b..bbea58310a 100644 --- a/services/forms/user_form.go +++ b/services/forms/user_form.go @@ -363,6 +363,18 @@ func (f *AddKeyForm) Validate(req *http.Request, errs binding.Errors) binding.Er return middleware.Validate(errs, ctx.Data, f, ctx.Locale) } +// AddSecretForm for adding secrets +type AddSecretForm struct { + Title string `binding:"Required;MaxSize(50)"` + Content string `binding:"Required"` +} + +// Validate validates the fields +func (f *AddSecretForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { + ctx := context.GetContext(req) + return middleware.Validate(errs, ctx.Data, f, ctx.Locale) +} + // NewAccessTokenForm form for creating access token type NewAccessTokenForm struct { Name string `binding:"Required;MaxSize(255)"` diff --git a/templates/org/settings/navbar.tmpl b/templates/org/settings/navbar.tmpl index 9ff30ae4ff..765bb6aaae 100644 --- a/templates/org/settings/navbar.tmpl +++ b/templates/org/settings/navbar.tmpl @@ -12,6 +12,9 @@ {{.locale.Tr "repo.labels"}} + + {{.locale.Tr "secrets.secrets"}} + {{if .EnableOAuth2}} {{.locale.Tr "settings.applications"}} diff --git a/templates/org/settings/secrets.tmpl b/templates/org/settings/secrets.tmpl new file mode 100644 index 0000000000..dd2a437b75 --- /dev/null +++ b/templates/org/settings/secrets.tmpl @@ -0,0 +1,83 @@ +{{template "base/head" .}} +
+ {{template "org/header" .}} +
+
+ {{template "org/settings/navbar" .}} +
+ {{template "base/alert" .}} +

+ {{.locale.Tr "secrets.secrets"}} +
+
{{.locale.Tr "secrets.creation"}}
+
+

+
+
+
+ {{.CsrfTokenHtml}} +
+ {{.locale.Tr "secrets.description"}} +
+
+ + +
+
+ + +
+ + +
+
+ {{if .Secrets}} +
+ {{range .Secrets}} +
+
+ +
+
+ {{svg "octicon-key" 32}} +
+
+ {{.Name}} +
******
+
+ + {{$.locale.Tr "settings.add_on"}} + {{.CreatedUnix.FormatShort}} + +
+
+
+ {{end}} +
+ {{else}} + {{.locale.Tr "secrets.none"}} + {{end}} +
+
+
+
+
+ + + +{{template "base/footer" .}} diff --git a/templates/repo/settings/deploy_keys.tmpl b/templates/repo/settings/deploy_keys.tmpl index 44c916eefb..31d1c1f7ab 100644 --- a/templates/repo/settings/deploy_keys.tmpl +++ b/templates/repo/settings/deploy_keys.tmpl @@ -75,6 +75,8 @@ {{end}} +
+ {{template "repo/settings/secrets" .}}
diff --git a/templates/repo/settings/navbar.tmpl b/templates/repo/settings/navbar.tmpl index e2b741b8d0..236a82f348 100644 --- a/templates/repo/settings/navbar.tmpl +++ b/templates/repo/settings/navbar.tmpl @@ -25,7 +25,7 @@ {{end}} - {{.locale.Tr "repo.settings.deploy_keys"}} + {{.locale.Tr "secrets.secrets"}} {{if .LFSStartServer}} diff --git a/templates/repo/settings/secrets.tmpl b/templates/repo/settings/secrets.tmpl new file mode 100644 index 0000000000..6fb97beb4a --- /dev/null +++ b/templates/repo/settings/secrets.tmpl @@ -0,0 +1,60 @@ +
+

+ {{.locale.Tr "secrets.secrets"}} +
+
{{.locale.Tr "secrets.creation"}}
+
+

+
+
+
+ {{.CsrfTokenHtml}} +
+ {{.locale.Tr "secrets.description"}} +
+
+ + +
+
+ + +
+ + +
+
+ {{if .Secrets}} +
+ {{range .Secrets}} +
+
+ +
+
+ {{svg "octicon-key" 32}} +
+
+ {{.Name}} +
******
+
+ + {{$.locale.Tr "settings.add_on"}} + {{.CreatedUnix.FormatShort}} + +
+
+
+ {{end}} +
+ {{else}} + {{.locale.Tr "secrets.none"}} + {{end}} +
+
From 495b8b3635bdcc42aa419be7845c9bbaf72d7473 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 21 Dec 2022 02:18:15 +0800 Subject: [PATCH 05/18] Fix delete secret modal (#22187) Fix #22181 --- templates/repo/settings/deploy_keys.tmpl | 4 ++-- templates/repo/settings/secrets.tmpl | 22 +++++++++++++++++++++- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/templates/repo/settings/deploy_keys.tmpl b/templates/repo/settings/deploy_keys.tmpl index 31d1c1f7ab..32a1258b3a 100644 --- a/templates/repo/settings/deploy_keys.tmpl +++ b/templates/repo/settings/deploy_keys.tmpl @@ -51,7 +51,7 @@ {{range .Deploykeys}}
-
@@ -79,7 +79,7 @@ {{template "repo/settings/secrets" .}}
-