diff --git a/docs/content/doc/features/comparison.en-us.md b/docs/content/doc/features/comparison.en-us.md index 21731b20fdd..dab1ee7eafc 100644 --- a/docs/content/doc/features/comparison.en-us.md +++ b/docs/content/doc/features/comparison.en-us.md @@ -60,7 +60,7 @@ _Symbols used in table:_ | Git LFS 2.0 | ✓ | ✘ | ✓ | ✓ | ✓ | ⁄ | ✓ | | Group Milestones | ✘ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ | | Granular user roles (Code, Issues, Wiki etc) | ✓ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ | -| Verified Committer | ✘ | ✘ | ? | ✓ | ✓ | ✓ | ✘ | +| Verified Committer | ⁄ | ✘ | ? | ✓ | ✓ | ✓ | ✘ | | GPG Signed Commits | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | | Reject unsigned commits | [✓](https://github.com/go-gitea/gitea/pull/9708) | ✘ | ✓ | ✓ | ✓ | ✘ | ✓ | | Repository Activity page | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | diff --git a/models/gpg_key.go b/models/gpg_key.go index 643aa6822c0..a32312a12dd 100644 --- a/models/gpg_key.go +++ b/models/gpg_key.go @@ -374,6 +374,7 @@ type CommitVerification struct { CommittingUser *User SigningEmail string SigningKey *GPGKey + TrustStatus string } // SignCommit represents a commit with validation of signature. @@ -759,18 +760,54 @@ func verifyWithGPGSettings(gpgSettings *git.GPGSettings, sig *packet.Signature, } // ParseCommitsWithSignature checks if signaute of commits are corresponding to users gpg keys. -func ParseCommitsWithSignature(oldCommits *list.List) *list.List { +func ParseCommitsWithSignature(oldCommits *list.List, repository *Repository) *list.List { var ( newCommits = list.New() e = oldCommits.Front() ) + memberMap := map[int64]bool{} + for e != nil { c := e.Value.(UserCommit) - newCommits.PushBack(SignCommit{ + signCommit := SignCommit{ UserCommit: &c, Verification: ParseCommitWithSignature(c.Commit), - }) + } + + _ = CalculateTrustStatus(signCommit.Verification, repository, &memberMap) + + newCommits.PushBack(signCommit) e = e.Next() } return newCommits } + +// CalculateTrustStatus will calculate the TrustStatus for a commit verification within a repository +func CalculateTrustStatus(verification *CommitVerification, repository *Repository, memberMap *map[int64]bool) (err error) { + if verification.Verified { + verification.TrustStatus = "trusted" + if verification.SigningUser.ID != 0 { + var isMember bool + if memberMap != nil { + var has bool + isMember, has = (*memberMap)[verification.SigningUser.ID] + if !has { + isMember, err = repository.IsOwnerMemberCollaborator(verification.SigningUser.ID) + (*memberMap)[verification.SigningUser.ID] = isMember + } + } else { + isMember, err = repository.IsOwnerMemberCollaborator(verification.SigningUser.ID) + } + + if !isMember { + verification.TrustStatus = "untrusted" + if verification.CommittingUser.ID != verification.SigningUser.ID { + // The committing user and the signing user are not the same and are not the default key + // This should be marked as questionable unless the signing user is a collaborator/team member etc. + verification.TrustStatus = "unmatched" + } + } + } + } + return +} diff --git a/models/repo_collaboration.go b/models/repo_collaboration.go index 8c6ef362303..85bc99f3203 100644 --- a/models/repo_collaboration.go +++ b/models/repo_collaboration.go @@ -210,3 +210,23 @@ func (repo *Repository) getRepoTeams(e Engine) (teams []*Team, err error) { func (repo *Repository) GetRepoTeams() ([]*Team, error) { return repo.getRepoTeams(x) } + +// IsOwnerMemberCollaborator checks if a provided user is the owner, a collaborator or a member of a team in a repository +func (repo *Repository) IsOwnerMemberCollaborator(userID int64) (bool, error) { + if repo.OwnerID == userID { + return true, nil + } + teamMember, err := x.Join("INNER", "team_repo", "team_repo.team_id = team_user.team_id"). + Join("INNER", "team_unit", "team_unit.team_id = team_user.team_id"). + Where("team_repo.repo_id = ?", repo.ID). + And("team_unit.`type` = ?", UnitTypeCode). + And("team_user.uid = ?", userID).Table("team_user").Exist(&TeamUser{}) + if err != nil { + return false, err + } + if teamMember { + return true, nil + } + + return x.Get(&Collaboration{RepoID: repo.ID, UserID: userID}) +} diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index cbe8aaad7a4..4a38dc62c1f 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -809,6 +809,8 @@ commits.date = Date commits.older = Older commits.newer = Newer commits.signed_by = Signed by +commits.signed_by_untrusted_user = Signed by untrusted user +commits.signed_by_untrusted_user_unmatched = Signed by untrusted user who does not match committer commits.gpg_key_id = GPG Key ID ext_issues = Ext. Issues diff --git a/routers/private/hook.go b/routers/private/hook.go index 44e82ebe6c7..1d8cb4b48ec 100644 --- a/routers/private/hook.go +++ b/routers/private/hook.go @@ -90,10 +90,8 @@ func readAndVerifyCommit(sha string, repo *git.Repository, env []string) error { if err != nil { return err } - log.Info("have commit %s", commit.ID.String()) verification := models.ParseCommitWithSignature(commit) if !verification.Verified { - log.Info("unverified commit %s", commit.ID.String()) cancel() return &errUnverifiedCommit{ commit.ID.String(), diff --git a/routers/repo/commit.go b/routers/repo/commit.go index b2fa2790bcd..2767986fda6 100644 --- a/routers/repo/commit.go +++ b/routers/repo/commit.go @@ -70,7 +70,7 @@ func Commits(ctx *context.Context) { return } commits = models.ValidateCommitsWithEmails(commits) - commits = models.ParseCommitsWithSignature(commits) + commits = models.ParseCommitsWithSignature(commits, ctx.Repo.Repository) commits = models.ParseCommitsWithStatus(commits, ctx.Repo.Repository) ctx.Data["Commits"] = commits @@ -139,7 +139,7 @@ func SearchCommits(ctx *context.Context) { return } commits = models.ValidateCommitsWithEmails(commits) - commits = models.ParseCommitsWithSignature(commits) + commits = models.ParseCommitsWithSignature(commits, ctx.Repo.Repository) commits = models.ParseCommitsWithStatus(commits, ctx.Repo.Repository) ctx.Data["Commits"] = commits @@ -185,7 +185,7 @@ func FileHistory(ctx *context.Context) { return } commits = models.ValidateCommitsWithEmails(commits) - commits = models.ParseCommitsWithSignature(commits) + commits = models.ParseCommitsWithSignature(commits, ctx.Repo.Repository) commits = models.ParseCommitsWithStatus(commits, ctx.Repo.Repository) ctx.Data["Commits"] = commits @@ -269,12 +269,18 @@ func Diff(ctx *context.Context) { setPathsCompareContext(ctx, parentCommit, commit, headTarget) ctx.Data["Title"] = commit.Summary() + " · " + base.ShortSha(commitID) ctx.Data["Commit"] = commit - ctx.Data["Verification"] = models.ParseCommitWithSignature(commit) + verification := models.ParseCommitWithSignature(commit) + ctx.Data["Verification"] = verification ctx.Data["Author"] = models.ValidateCommitWithEmail(commit) ctx.Data["Diff"] = diff ctx.Data["Parents"] = parents ctx.Data["DiffNotAvailable"] = diff.NumFiles() == 0 + if err := models.CalculateTrustStatus(verification, ctx.Repo.Repository, nil); err != nil { + ctx.ServerError("CalculateTrustStatus", err) + return + } + note := &git.Note{} err = git.GetNote(ctx.Repo.GitRepo, commitID, note) if err == nil { diff --git a/routers/repo/compare.go b/routers/repo/compare.go index 833b7d91829..d7fddc45587 100644 --- a/routers/repo/compare.go +++ b/routers/repo/compare.go @@ -316,7 +316,7 @@ func PrepareCompareDiff( } compareInfo.Commits = models.ValidateCommitsWithEmails(compareInfo.Commits) - compareInfo.Commits = models.ParseCommitsWithSignature(compareInfo.Commits) + compareInfo.Commits = models.ParseCommitsWithSignature(compareInfo.Commits, headRepo) compareInfo.Commits = models.ParseCommitsWithStatus(compareInfo.Commits, headRepo) ctx.Data["Commits"] = compareInfo.Commits ctx.Data["CommitCount"] = compareInfo.Commits.Len() diff --git a/routers/repo/pull.go b/routers/repo/pull.go index 11c376be7e6..92538945b02 100644 --- a/routers/repo/pull.go +++ b/routers/repo/pull.go @@ -495,7 +495,7 @@ func ViewPullCommits(ctx *context.Context) { ctx.Data["Reponame"] = ctx.Repo.Repository.Name commits = prInfo.Commits commits = models.ValidateCommitsWithEmails(commits) - commits = models.ParseCommitsWithSignature(commits) + commits = models.ParseCommitsWithSignature(commits, ctx.Repo.Repository) commits = models.ParseCommitsWithStatus(commits, ctx.Repo.Repository) ctx.Data["Commits"] = commits ctx.Data["CommitCount"] = commits.Len() diff --git a/routers/repo/view.go b/routers/repo/view.go index 8364b1636b2..5bcf4dae3bb 100644 --- a/routers/repo/view.go +++ b/routers/repo/view.go @@ -333,7 +333,14 @@ func renderDirectory(ctx *context.Context, treeLink string) { // Show latest commit info of repository in table header, // or of directory if not in root directory. ctx.Data["LatestCommit"] = latestCommit - ctx.Data["LatestCommitVerification"] = models.ParseCommitWithSignature(latestCommit) + verification := models.ParseCommitWithSignature(latestCommit) + + if err := models.CalculateTrustStatus(verification, ctx.Repo.Repository, nil); err != nil { + ctx.ServerError("CalculateTrustStatus", err) + return + } + ctx.Data["LatestCommitVerification"] = verification + ctx.Data["LatestCommitUser"] = models.ValidateCommitWithEmail(latestCommit) statuses, err := models.GetLatestCommitStatus(ctx.Repo.Repository, ctx.Repo.Commit.ID.String(), 0) diff --git a/routers/repo/wiki.go b/routers/repo/wiki.go index f18625a5997..a01498fb0a1 100644 --- a/routers/repo/wiki.go +++ b/routers/repo/wiki.go @@ -284,7 +284,7 @@ func renderRevisionPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) return nil, nil } commitsHistory = models.ValidateCommitsWithEmails(commitsHistory) - commitsHistory = models.ParseCommitsWithSignature(commitsHistory) + commitsHistory = models.ParseCommitsWithSignature(commitsHistory, ctx.Repo.Repository) ctx.Data["Commits"] = commitsHistory diff --git a/templates/repo/commit_page.tmpl b/templates/repo/commit_page.tmpl index 0c3430c6acd..1cfd0944d51 100644 --- a/templates/repo/commit_page.tmpl +++ b/templates/repo/commit_page.tmpl @@ -2,7 +2,22 @@