diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 89d04d381cd4..62edef597c35 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -2555,7 +2555,8 @@ LEVEL = Info ;LIMIT_SIZE_SWIFT = -1 ;; Maximum size of a Vagrant upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`) ;LIMIT_SIZE_VAGRANT = -1 - +;; Enable RPM re-signing by default. (It will overwrite the old signature ,using v4 format, not compatible with CentOS 6 or older) +;DEFAULT_RPM_SIGN_ENABLED = false ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; default storage for attachments, lfs and avatars diff --git a/models/issues/pull.go b/models/issues/pull.go index a4e61476198d..5fe95d56cc25 100644 --- a/models/issues/pull.go +++ b/models/issues/pull.go @@ -165,6 +165,7 @@ type PullRequest struct { Issue *Issue `xorm:"-"` Index int64 RequestedReviewers []*user_model.User `xorm:"-"` + RequestedReviewersTeams []*org_model.Team `xorm:"-"` isRequestedReviewersLoaded bool `xorm:"-"` HeadRepoID int64 `xorm:"INDEX"` @@ -305,7 +306,28 @@ func (pr *PullRequest) LoadRequestedReviewers(ctx context.Context) error { } pr.isRequestedReviewersLoaded = true for _, review := range reviews { - pr.RequestedReviewers = append(pr.RequestedReviewers, review.Reviewer) + if review.ReviewerID != 0 { + pr.RequestedReviewers = append(pr.RequestedReviewers, review.Reviewer) + } + } + + return nil +} + +// LoadRequestedReviewersTeams loads the requested reviewers teams. +func (pr *PullRequest) LoadRequestedReviewersTeams(ctx context.Context) error { + reviews, err := GetReviewsByIssueID(ctx, pr.Issue.ID) + if err != nil { + return err + } + if err = reviews.LoadReviewersTeams(ctx); err != nil { + return err + } + + for _, review := range reviews { + if review.ReviewerTeamID != 0 { + pr.RequestedReviewersTeams = append(pr.RequestedReviewersTeams, review.ReviewerTeam) + } } return nil diff --git a/models/issues/review_list.go b/models/issues/review_list.go index 7b8c3d319c35..0ee28874ec98 100644 --- a/models/issues/review_list.go +++ b/models/issues/review_list.go @@ -7,6 +7,7 @@ import ( "context" "code.gitea.io/gitea/models/db" + organization_model "code.gitea.io/gitea/models/organization" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/optional" @@ -37,6 +38,34 @@ func (reviews ReviewList) LoadReviewers(ctx context.Context) error { return nil } +// LoadReviewersTeams loads reviewers teams +func (reviews ReviewList) LoadReviewersTeams(ctx context.Context) error { + reviewersTeamsIDs := make([]int64, 0) + for _, review := range reviews { + if review.ReviewerTeamID != 0 { + reviewersTeamsIDs = append(reviewersTeamsIDs, review.ReviewerTeamID) + } + } + + teamsMap := make(map[int64]*organization_model.Team, 0) + for _, teamID := range reviewersTeamsIDs { + team, err := organization_model.GetTeamByID(ctx, teamID) + if err != nil { + return err + } + + teamsMap[teamID] = team + } + + for _, review := range reviews { + if review.ReviewerTeamID != 0 { + review.ReviewerTeam = teamsMap[review.ReviewerTeamID] + } + } + + return nil +} + func (reviews ReviewList) LoadIssues(ctx context.Context) error { issueIDs := container.FilterSlice(reviews, func(review *Review) (int64, bool) { return review.IssueID, true diff --git a/modules/git/commit_test.go b/modules/git/commit_test.go index a33e7df31adb..0ddeb182ef83 100644 --- a/modules/git/commit_test.go +++ b/modules/git/commit_test.go @@ -4,6 +4,8 @@ package git import ( + "context" + "os" "path/filepath" "strings" "testing" @@ -345,3 +347,18 @@ func TestGetCommitFileStatusMerges(t *testing.T) { assert.Equal(t, commitFileStatus.Removed, expected.Removed) assert.Equal(t, commitFileStatus.Modified, expected.Modified) } + +func Test_GetCommitBranchStart(t *testing.T) { + bareRepo1Path := filepath.Join(testReposDir, "repo1_bare") + repo, err := OpenRepository(context.Background(), bareRepo1Path) + assert.NoError(t, err) + defer repo.Close() + commit, err := repo.GetBranchCommit("branch1") + assert.NoError(t, err) + assert.EqualValues(t, "2839944139e0de9737a044f78b0e4b40d989a9e3", commit.ID.String()) + + startCommitID, err := repo.GetCommitBranchStart(os.Environ(), "branch1", commit.ID.String()) + assert.NoError(t, err) + assert.NotEmpty(t, startCommitID) + assert.EqualValues(t, "9c9aef8dd84e02bc7ec12641deb4c930a7c30185", startCommitID) +} diff --git a/modules/git/diff.go b/modules/git/diff.go index 10ef3d83fba9..833f6220f970 100644 --- a/modules/git/diff.go +++ b/modules/git/diff.go @@ -271,7 +271,17 @@ func CutDiffAroundLine(originalDiff io.Reader, line int64, old bool, numbersOfLi } // GetAffectedFiles returns the affected files between two commits -func GetAffectedFiles(repo *Repository, oldCommitID, newCommitID string, env []string) ([]string, error) { +func GetAffectedFiles(repo *Repository, branchName, oldCommitID, newCommitID string, env []string) ([]string, error) { + if oldCommitID == emptySha1ObjectID.String() || oldCommitID == emptySha256ObjectID.String() { + startCommitID, err := repo.GetCommitBranchStart(env, branchName, newCommitID) + if err != nil { + return nil, err + } + if startCommitID == "" { + return nil, fmt.Errorf("cannot find the start commit of %s", newCommitID) + } + oldCommitID = startCommitID + } stdoutReader, stdoutWriter, err := os.Pipe() if err != nil { log.Error("Unable to create os.Pipe for %s", repo.Path) diff --git a/modules/git/repo_commit.go b/modules/git/repo_commit.go index 8c3285769e4f..9405634df122 100644 --- a/modules/git/repo_commit.go +++ b/modules/git/repo_commit.go @@ -7,6 +7,7 @@ package git import ( "bytes" "io" + "os" "strconv" "strings" @@ -414,7 +415,7 @@ func (repo *Repository) commitsBefore(id ObjectID, limit int) ([]*Commit, error) commits := make([]*Commit, 0, len(formattedLog)) for _, commit := range formattedLog { - branches, err := repo.getBranches(commit, 2) + branches, err := repo.getBranches(os.Environ(), commit.ID.String(), 2) if err != nil { return nil, err } @@ -437,12 +438,15 @@ func (repo *Repository) getCommitsBeforeLimit(id ObjectID, num int) ([]*Commit, return repo.commitsBefore(id, num) } -func (repo *Repository) getBranches(commit *Commit, limit int) ([]string, error) { +func (repo *Repository) getBranches(env []string, commitID string, limit int) ([]string, error) { if DefaultFeatures().CheckVersionAtLeast("2.7.0") { stdout, _, err := NewCommand(repo.Ctx, "for-each-ref", "--format=%(refname:strip=2)"). AddOptionFormat("--count=%d", limit). - AddOptionValues("--contains", commit.ID.String(), BranchPrefix). - RunStdString(&RunOpts{Dir: repo.Path}) + AddOptionValues("--contains", commitID, BranchPrefix). + RunStdString(&RunOpts{ + Dir: repo.Path, + Env: env, + }) if err != nil { return nil, err } @@ -451,7 +455,10 @@ func (repo *Repository) getBranches(commit *Commit, limit int) ([]string, error) return branches, nil } - stdout, _, err := NewCommand(repo.Ctx, "branch").AddOptionValues("--contains", commit.ID.String()).RunStdString(&RunOpts{Dir: repo.Path}) + stdout, _, err := NewCommand(repo.Ctx, "branch").AddOptionValues("--contains", commitID).RunStdString(&RunOpts{ + Dir: repo.Path, + Env: env, + }) if err != nil { return nil, err } @@ -513,3 +520,35 @@ func (repo *Repository) AddLastCommitCache(cacheKey, fullName, sha string) error } return nil } + +func (repo *Repository) GetCommitBranchStart(env []string, branch, endCommitID string) (string, error) { + cmd := NewCommand(repo.Ctx, "log", prettyLogFormat) + cmd.AddDynamicArguments(endCommitID) + + stdout, _, runErr := cmd.RunStdBytes(&RunOpts{ + Dir: repo.Path, + Env: env, + }) + if runErr != nil { + return "", runErr + } + + parts := bytes.Split(bytes.TrimSpace(stdout), []byte{'\n'}) + + var startCommitID string + for _, commitID := range parts { + branches, err := repo.getBranches(env, string(commitID), 2) + if err != nil { + return "", err + } + for _, b := range branches { + if b != branch { + return startCommitID, nil + } + } + + startCommitID = string(commitID) + } + + return "", nil +} diff --git a/modules/git/repo_commit_test.go b/modules/git/repo_commit_test.go index fee145e924e9..19983b47b19a 100644 --- a/modules/git/repo_commit_test.go +++ b/modules/git/repo_commit_test.go @@ -4,6 +4,7 @@ package git import ( + "os" "path/filepath" "testing" @@ -31,7 +32,7 @@ func TestRepository_GetCommitBranches(t *testing.T) { for _, testCase := range testCases { commit, err := bareRepo1.GetCommit(testCase.CommitID) assert.NoError(t, err) - branches, err := bareRepo1.getBranches(commit, 2) + branches, err := bareRepo1.getBranches(os.Environ(), commit.ID.String(), 2) assert.NoError(t, err) assert.Equal(t, testCase.ExpectedBranches, branches) } diff --git a/modules/setting/packages.go b/modules/setting/packages.go index 00fba67b393a..bc093e7ea6c1 100644 --- a/modules/setting/packages.go +++ b/modules/setting/packages.go @@ -42,6 +42,8 @@ var ( LimitSizeRubyGems int64 LimitSizeSwift int64 LimitSizeVagrant int64 + + DefaultRPMSignEnabled bool }{ Enabled: true, LimitTotalOwnerCount: -1, @@ -97,6 +99,7 @@ func loadPackagesFrom(rootCfg ConfigProvider) (err error) { Packages.LimitSizeRubyGems = mustBytes(sec, "LIMIT_SIZE_RUBYGEMS") Packages.LimitSizeSwift = mustBytes(sec, "LIMIT_SIZE_SWIFT") Packages.LimitSizeVagrant = mustBytes(sec, "LIMIT_SIZE_VAGRANT") + Packages.DefaultRPMSignEnabled = sec.Key("DEFAULT_RPM_SIGN_ENABLED").MustBool(false) return nil } diff --git a/modules/structs/pull.go b/modules/structs/pull.go index 525d90c28e5f..ab627666c94a 100644 --- a/modules/structs/pull.go +++ b/modules/structs/pull.go @@ -9,21 +9,22 @@ import ( // PullRequest represents a pull request type PullRequest struct { - ID int64 `json:"id"` - URL string `json:"url"` - Index int64 `json:"number"` - Poster *User `json:"user"` - Title string `json:"title"` - Body string `json:"body"` - Labels []*Label `json:"labels"` - Milestone *Milestone `json:"milestone"` - Assignee *User `json:"assignee"` - Assignees []*User `json:"assignees"` - RequestedReviewers []*User `json:"requested_reviewers"` - State StateType `json:"state"` - Draft bool `json:"draft"` - IsLocked bool `json:"is_locked"` - Comments int `json:"comments"` + ID int64 `json:"id"` + URL string `json:"url"` + Index int64 `json:"number"` + Poster *User `json:"user"` + Title string `json:"title"` + Body string `json:"body"` + Labels []*Label `json:"labels"` + Milestone *Milestone `json:"milestone"` + Assignee *User `json:"assignee"` + Assignees []*User `json:"assignees"` + RequestedReviewers []*User `json:"requested_reviewers"` + RequestedReviewersTeams []*Team `json:"requested_reviewers_teams"` + State StateType `json:"state"` + Draft bool `json:"draft"` + IsLocked bool `json:"is_locked"` + Comments int `json:"comments"` // number of review comments made on the diff of a PR review (not including comments on commits or issues in a PR) ReviewComments int `json:"review_comments"` Additions int `json:"additions"` diff --git a/routers/api/packages/rpm/rpm.go b/routers/api/packages/rpm/rpm.go index 11d7729eec2d..4c822e0999d2 100644 --- a/routers/api/packages/rpm/rpm.go +++ b/routers/api/packages/rpm/rpm.go @@ -133,6 +133,21 @@ func UploadPackageFile(ctx *context.Context) { } defer buf.Close() + // if rpm sign enabled + if setting.Packages.DefaultRPMSignEnabled || ctx.FormBool("sign") { + pri, _, err := rpm_service.GetOrCreateKeyPair(ctx, ctx.Package.Owner.ID) + if err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + buf, err = rpm_service.SignPackage(buf, pri) + if err != nil { + // Not in rpm format, parsing failed. + apiError(ctx, http.StatusBadRequest, err) + return + } + } + pck, err := rpm_module.ParsePackage(buf) if err != nil { if errors.Is(err, util.ErrInvalidArgument) { @@ -142,7 +157,6 @@ func UploadPackageFile(ctx *context.Context) { } return } - if _, err := buf.Seek(0, io.SeekStart); err != nil { apiError(ctx, http.StatusInternalServerError, err) return diff --git a/routers/private/hook_pre_receive.go b/routers/private/hook_pre_receive.go index 88538750142f..73fe9b886cff 100644 --- a/routers/private/hook_pre_receive.go +++ b/routers/private/hook_pre_receive.go @@ -235,7 +235,7 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r globs := protectBranch.GetProtectedFilePatterns() if len(globs) > 0 { - _, err := pull_service.CheckFileProtection(gitRepo, oldCommitID, newCommitID, globs, 1, ctx.env) + _, err := pull_service.CheckFileProtection(gitRepo, branchName, oldCommitID, newCommitID, globs, 1, ctx.env) if err != nil { if !models.IsErrFilePathProtected(err) { log.Error("Unable to check file protection for commits from %s to %s in %-v: %v", oldCommitID, newCommitID, repo, err) @@ -293,7 +293,7 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r // Allow commits that only touch unprotected files globs := protectBranch.GetUnprotectedFilePatterns() if len(globs) > 0 { - unprotectedFilesOnly, err := pull_service.CheckUnprotectedFiles(gitRepo, oldCommitID, newCommitID, globs, ctx.env) + unprotectedFilesOnly, err := pull_service.CheckUnprotectedFiles(gitRepo, branchName, oldCommitID, newCommitID, globs, ctx.env) if err != nil { log.Error("Unable to check file protection for commits from %s to %s in %-v: %v", oldCommitID, newCommitID, repo, err) ctx.JSON(http.StatusInternalServerError, private.Response{ diff --git a/services/convert/pull.go b/services/convert/pull.go index c214805ed55d..4ec24a8276a1 100644 --- a/services/convert/pull.go +++ b/services/convert/pull.go @@ -106,10 +106,25 @@ func ToAPIPullRequest(ctx context.Context, pr *issues_model.PullRequest, doer *u log.Error("LoadRequestedReviewers[%d]: %v", pr.ID, err) return nil } + if err = pr.LoadRequestedReviewersTeams(ctx); err != nil { + log.Error("LoadRequestedReviewersTeams[%d]: %v", pr.ID, err) + return nil + } + for _, reviewer := range pr.RequestedReviewers { apiPullRequest.RequestedReviewers = append(apiPullRequest.RequestedReviewers, ToUser(ctx, reviewer, nil)) } + for _, reviewerTeam := range pr.RequestedReviewersTeams { + convertedTeam, err := ToTeam(ctx, reviewerTeam, true) + if err != nil { + log.Error("LoadRequestedReviewersTeams[%d]: %v", pr.ID, err) + return nil + } + + apiPullRequest.RequestedReviewersTeams = append(apiPullRequest.RequestedReviewersTeams, convertedTeam) + } + if pr.Issue.ClosedUnix != 0 { apiPullRequest.Closed = pr.Issue.ClosedUnix.AsTimePtr() } diff --git a/services/packages/rpm/repository.go b/services/packages/rpm/repository.go index c52c8a5dd98c..19968f9b30a2 100644 --- a/services/packages/rpm/repository.go +++ b/services/packages/rpm/repository.go @@ -21,14 +21,16 @@ import ( rpm_model "code.gitea.io/gitea/models/packages/rpm" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/json" + "code.gitea.io/gitea/modules/log" packages_module "code.gitea.io/gitea/modules/packages" rpm_module "code.gitea.io/gitea/modules/packages/rpm" "code.gitea.io/gitea/modules/util" packages_service "code.gitea.io/gitea/services/packages" - "github.com/keybase/go-crypto/openpgp" - "github.com/keybase/go-crypto/openpgp/armor" - "github.com/keybase/go-crypto/openpgp/packet" + "github.com/ProtonMail/go-crypto/openpgp" + "github.com/ProtonMail/go-crypto/openpgp/armor" + "github.com/ProtonMail/go-crypto/openpgp/packet" + "github.com/sassoftware/go-rpmutils" ) // GetOrCreateRepositoryVersion gets or creates the internal repository package @@ -641,3 +643,33 @@ func addDataAsFileToRepo(ctx context.Context, pv *packages_model.PackageVersion, OpenSize: wc.Written(), }, nil } + +func SignPackage(rpm *packages_module.HashedBuffer, privateKey string) (*packages_module.HashedBuffer, error) { + keyring, err := openpgp.ReadArmoredKeyRing(bytes.NewReader([]byte(privateKey))) + if err != nil { + // failed to parse key + return nil, err + } + entity := keyring[0] + h, err := rpmutils.SignRpmStream(rpm, entity.PrivateKey, nil) + if err != nil { + // error signing rpm + return nil, err + } + signBlob, err := h.DumpSignatureHeader(false) + if err != nil { + // error writing sig header + return nil, err + } + if len(signBlob)%8 != 0 { + log.Info("incorrect padding: got %d bytes, expected a multiple of 8", len(signBlob)) + return nil, err + } + + // move fp to sign end + if _, err := rpm.Seek(int64(h.OriginalSignatureHeaderSize()), io.SeekStart); err != nil { + return nil, err + } + // create signed rpm buf + return packages_module.CreateHashedBufferFromReader(io.MultiReader(bytes.NewReader(signBlob), rpm)) +} diff --git a/services/pull/patch.go b/services/pull/patch.go index e391a7f9d032..0934a86c89a6 100644 --- a/services/pull/patch.go +++ b/services/pull/patch.go @@ -503,11 +503,11 @@ func checkConflicts(ctx context.Context, pr *issues_model.PullRequest, gitRepo * } // CheckFileProtection check file Protection -func CheckFileProtection(repo *git.Repository, oldCommitID, newCommitID string, patterns []glob.Glob, limit int, env []string) ([]string, error) { +func CheckFileProtection(repo *git.Repository, branchName, oldCommitID, newCommitID string, patterns []glob.Glob, limit int, env []string) ([]string, error) { if len(patterns) == 0 { return nil, nil } - affectedFiles, err := git.GetAffectedFiles(repo, oldCommitID, newCommitID, env) + affectedFiles, err := git.GetAffectedFiles(repo, branchName, oldCommitID, newCommitID, env) if err != nil { return nil, err } @@ -533,11 +533,11 @@ func CheckFileProtection(repo *git.Repository, oldCommitID, newCommitID string, } // CheckUnprotectedFiles check if the commit only touches unprotected files -func CheckUnprotectedFiles(repo *git.Repository, oldCommitID, newCommitID string, patterns []glob.Glob, env []string) (bool, error) { +func CheckUnprotectedFiles(repo *git.Repository, branchName, oldCommitID, newCommitID string, patterns []glob.Glob, env []string) (bool, error) { if len(patterns) == 0 { return false, nil } - affectedFiles, err := git.GetAffectedFiles(repo, oldCommitID, newCommitID, env) + affectedFiles, err := git.GetAffectedFiles(repo, branchName, oldCommitID, newCommitID, env) if err != nil { return false, err } @@ -574,7 +574,7 @@ func checkPullFilesProtection(ctx context.Context, pr *issues_model.PullRequest, return nil } - pr.ChangedProtectedFiles, err = CheckFileProtection(gitRepo, pr.MergeBase, "tracking", pb.GetProtectedFilePatterns(), 10, os.Environ()) + pr.ChangedProtectedFiles, err = CheckFileProtection(gitRepo, pr.HeadBranch, pr.MergeBase, "tracking", pb.GetProtectedFilePatterns(), 10, os.Environ()) if err != nil && !models.IsErrFilePathProtected(err) { return err } diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index bfaa86428c95..18ea4a62b58f 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -23517,6 +23517,13 @@ }, "x-go-name": "RequestedReviewers" }, + "requested_reviewers_teams": { + "type": "array", + "items": { + "$ref": "#/definitions/Team" + }, + "x-go-name": "RequestedReviewersTeams" + }, "review_comments": { "description": "number of review comments made on the diff of a PR review (not including comments on commits or issues in a PR)", "type": "integer", diff --git a/tests/integration/api_packages_rpm_test.go b/tests/integration/api_packages_rpm_test.go index 1dcec6099e32..6feceaeb78d7 100644 --- a/tests/integration/api_packages_rpm_test.go +++ b/tests/integration/api_packages_rpm_test.go @@ -24,7 +24,10 @@ import ( "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/tests" + "github.com/ProtonMail/go-crypto/openpgp" + "github.com/sassoftware/go-rpmutils" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestPackageRpm(t *testing.T) { @@ -431,6 +434,30 @@ gpgkey=%sapi/packages/%s/rpm/repository.key`, AddBasicAuth(user.Name) MakeRequest(t, req, http.StatusNotFound) }) + + t.Run("UploadSign", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + url := groupURL + "/upload?sign=true" + req := NewRequestWithBody(t, "PUT", url, bytes.NewReader(content)). + AddBasicAuth(user.Name) + MakeRequest(t, req, http.StatusCreated) + + gpgReq := NewRequest(t, "GET", rootURL+"/repository.key") + gpgResp := MakeRequest(t, gpgReq, http.StatusOK) + pub, err := openpgp.ReadArmoredKeyRing(gpgResp.Body) + require.NoError(t, err) + + req = NewRequest(t, "GET", fmt.Sprintf("%s/package/%s/%s/%s", groupURL, packageName, packageVersion, packageArchitecture)) + resp := MakeRequest(t, req, http.StatusOK) + + _, sigs, err := rpmutils.Verify(resp.Body, pub) + require.NoError(t, err) + require.NotEmpty(t, sigs) + + req = NewRequest(t, "DELETE", fmt.Sprintf("%s/package/%s/%s/%s", groupURL, packageName, packageVersion, packageArchitecture)). + AddBasicAuth(user.Name) + MakeRequest(t, req, http.StatusNoContent) + }) }) } }