Skip to content

Commit 639224b

Browse files
zeripathsilverwindguillep2k
committed
Show Signer in commit lists and add basic trust (go-gitea#10425)
Backport go-gitea#10425 Backport go-gitea#10511 * Show Signer in commit lists and add basic trust (go-gitea#10425) Show the avatar of the signer in the commit list pages as we do not enforce that the signer is an author or committer. This makes it clearer who has signed the commit. Also display commits signed by non-members differently from members and in particular make it clear when a non-member signer is different from the committer to help reduce the risk of spoofing. Signed-off-by: Andrew Thornton <[email protected]> Fix the signing icon in the view_list.tmpl page (go-gitea#10511) Co-Authored-By: silverwind <[email protected]> Co-authored-by: guillep2k <[email protected]>
1 parent 11300ee commit 639224b

File tree

15 files changed

+418
-70
lines changed

15 files changed

+418
-70
lines changed

docs/content/doc/features/comparison.en-us.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ _Symbols used in table:_
6060
| Git LFS 2.0 ||||||||
6161
| Group Milestones ||||||||
6262
| Granular user roles (Code, Issues, Wiki etc) ||||||||
63-
| Verified Committer | || ? |||||
63+
| Verified Committer | || ? |||||
6464
| GPG Signed Commits ||||||||
6565
| Reject unsigned commits | [](https://github.com/go-gitea/gitea/issues/2770) |||||||
6666
| Repository Activity page ||||||||

models/gpg_key.go

+40-3
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,7 @@ type CommitVerification struct {
369369
CommittingUser *User
370370
SigningEmail string
371371
SigningKey *GPGKey
372+
TrustStatus string
372373
}
373374

374375
// SignCommit represents a commit with validation of signature.
@@ -754,18 +755,54 @@ func verifyWithGPGSettings(gpgSettings *git.GPGSettings, sig *packet.Signature,
754755
}
755756

756757
// ParseCommitsWithSignature checks if signaute of commits are corresponding to users gpg keys.
757-
func ParseCommitsWithSignature(oldCommits *list.List) *list.List {
758+
func ParseCommitsWithSignature(oldCommits *list.List, repository *Repository) *list.List {
758759
var (
759760
newCommits = list.New()
760761
e = oldCommits.Front()
761762
)
763+
memberMap := map[int64]bool{}
764+
762765
for e != nil {
763766
c := e.Value.(UserCommit)
764-
newCommits.PushBack(SignCommit{
767+
signCommit := SignCommit{
765768
UserCommit: &c,
766769
Verification: ParseCommitWithSignature(c.Commit),
767-
})
770+
}
771+
772+
_ = CalculateTrustStatus(signCommit.Verification, repository, &memberMap)
773+
774+
newCommits.PushBack(signCommit)
768775
e = e.Next()
769776
}
770777
return newCommits
771778
}
779+
780+
// CalculateTrustStatus will calculate the TrustStatus for a commit verification within a repository
781+
func CalculateTrustStatus(verification *CommitVerification, repository *Repository, memberMap *map[int64]bool) (err error) {
782+
if verification.Verified {
783+
verification.TrustStatus = "trusted"
784+
if verification.SigningUser.ID != 0 {
785+
var isMember bool
786+
if memberMap != nil {
787+
var has bool
788+
isMember, has = (*memberMap)[verification.SigningUser.ID]
789+
if !has {
790+
isMember, err = repository.IsOwnerMemberCollaborator(verification.SigningUser.ID)
791+
(*memberMap)[verification.SigningUser.ID] = isMember
792+
}
793+
} else {
794+
isMember, err = repository.IsOwnerMemberCollaborator(verification.SigningUser.ID)
795+
}
796+
797+
if !isMember {
798+
verification.TrustStatus = "untrusted"
799+
if verification.CommittingUser.ID != verification.SigningUser.ID {
800+
// The committing user and the signing user are not the same and are not the default key
801+
// This should be marked as questionable unless the signing user is a collaborator/team member etc.
802+
verification.TrustStatus = "unmatched"
803+
}
804+
}
805+
}
806+
}
807+
return
808+
}

models/repo_collaboration.go

+20
Original file line numberDiff line numberDiff line change
@@ -202,3 +202,23 @@ func (repo *Repository) getRepoTeams(e Engine) (teams []*Team, err error) {
202202
func (repo *Repository) GetRepoTeams() ([]*Team, error) {
203203
return repo.getRepoTeams(x)
204204
}
205+
206+
// IsOwnerMemberCollaborator checks if a provided user is the owner, a collaborator or a member of a team in a repository
207+
func (repo *Repository) IsOwnerMemberCollaborator(userID int64) (bool, error) {
208+
if repo.OwnerID == userID {
209+
return true, nil
210+
}
211+
teamMember, err := x.Join("INNER", "team_repo", "team_repo.team_id = team_user.team_id").
212+
Join("INNER", "team_unit", "team_unit.team_id = team_user.team_id").
213+
Where("team_repo.repo_id = ?", repo.ID).
214+
And("team_unit.`type` = ?", UnitTypeCode).
215+
And("team_user.uid = ?", userID).Table("team_user").Exist(&TeamUser{})
216+
if err != nil {
217+
return false, err
218+
}
219+
if teamMember {
220+
return true, nil
221+
}
222+
223+
return x.Get(&Collaboration{RepoID: repo.ID, UserID: userID})
224+
}

options/locale/locale_en-US.ini

+2
Original file line numberDiff line numberDiff line change
@@ -797,6 +797,8 @@ commits.date = Date
797797
commits.older = Older
798798
commits.newer = Newer
799799
commits.signed_by = Signed by
800+
commits.signed_by_untrusted_user = Signed by untrusted user
801+
commits.signed_by_untrusted_user_unmatched = Signed by untrusted user who does not match committer
800802
commits.gpg_key_id = GPG Key ID
801803
802804
ext_issues = Ext. Issues

routers/repo/commit.go

+10-4
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ func Commits(ctx *context.Context) {
6565
return
6666
}
6767
commits = models.ValidateCommitsWithEmails(commits)
68-
commits = models.ParseCommitsWithSignature(commits)
68+
commits = models.ParseCommitsWithSignature(commits, ctx.Repo.Repository)
6969
commits = models.ParseCommitsWithStatus(commits, ctx.Repo.Repository)
7070
ctx.Data["Commits"] = commits
7171

@@ -134,7 +134,7 @@ func SearchCommits(ctx *context.Context) {
134134
return
135135
}
136136
commits = models.ValidateCommitsWithEmails(commits)
137-
commits = models.ParseCommitsWithSignature(commits)
137+
commits = models.ParseCommitsWithSignature(commits, ctx.Repo.Repository)
138138
commits = models.ParseCommitsWithStatus(commits, ctx.Repo.Repository)
139139
ctx.Data["Commits"] = commits
140140

@@ -180,7 +180,7 @@ func FileHistory(ctx *context.Context) {
180180
return
181181
}
182182
commits = models.ValidateCommitsWithEmails(commits)
183-
commits = models.ParseCommitsWithSignature(commits)
183+
commits = models.ParseCommitsWithSignature(commits, ctx.Repo.Repository)
184184
commits = models.ParseCommitsWithStatus(commits, ctx.Repo.Repository)
185185
ctx.Data["Commits"] = commits
186186

@@ -262,12 +262,18 @@ func Diff(ctx *context.Context) {
262262
setPathsCompareContext(ctx, parentCommit, commit, headTarget)
263263
ctx.Data["Title"] = commit.Summary() + " · " + base.ShortSha(commitID)
264264
ctx.Data["Commit"] = commit
265-
ctx.Data["Verification"] = models.ParseCommitWithSignature(commit)
265+
verification := models.ParseCommitWithSignature(commit)
266+
ctx.Data["Verification"] = verification
266267
ctx.Data["Author"] = models.ValidateCommitWithEmail(commit)
267268
ctx.Data["Diff"] = diff
268269
ctx.Data["Parents"] = parents
269270
ctx.Data["DiffNotAvailable"] = diff.NumFiles() == 0
270271

272+
if err := models.CalculateTrustStatus(verification, ctx.Repo.Repository, nil); err != nil {
273+
ctx.ServerError("CalculateTrustStatus", err)
274+
return
275+
}
276+
271277
note := &git.Note{}
272278
err = git.GetNote(ctx.Repo.GitRepo, commitID, note)
273279
if err == nil {

routers/repo/compare.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ func PrepareCompareDiff(
316316
}
317317

318318
compareInfo.Commits = models.ValidateCommitsWithEmails(compareInfo.Commits)
319-
compareInfo.Commits = models.ParseCommitsWithSignature(compareInfo.Commits)
319+
compareInfo.Commits = models.ParseCommitsWithSignature(compareInfo.Commits, headRepo)
320320
compareInfo.Commits = models.ParseCommitsWithStatus(compareInfo.Commits, headRepo)
321321
ctx.Data["Commits"] = compareInfo.Commits
322322
ctx.Data["CommitCount"] = compareInfo.Commits.Len()

routers/repo/pull.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -477,7 +477,7 @@ func ViewPullCommits(ctx *context.Context) {
477477
ctx.Data["Reponame"] = ctx.Repo.Repository.Name
478478
commits = prInfo.Commits
479479
commits = models.ValidateCommitsWithEmails(commits)
480-
commits = models.ParseCommitsWithSignature(commits)
480+
commits = models.ParseCommitsWithSignature(commits, ctx.Repo.Repository)
481481
commits = models.ParseCommitsWithStatus(commits, ctx.Repo.Repository)
482482
ctx.Data["Commits"] = commits
483483
ctx.Data["CommitCount"] = commits.Len()

routers/repo/view.go

+8-1
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,14 @@ func renderDirectory(ctx *context.Context, treeLink string) {
181181
// Show latest commit info of repository in table header,
182182
// or of directory if not in root directory.
183183
ctx.Data["LatestCommit"] = latestCommit
184-
ctx.Data["LatestCommitVerification"] = models.ParseCommitWithSignature(latestCommit)
184+
verification := models.ParseCommitWithSignature(latestCommit)
185+
186+
if err := models.CalculateTrustStatus(verification, ctx.Repo.Repository, nil); err != nil {
187+
ctx.ServerError("CalculateTrustStatus", err)
188+
return
189+
}
190+
ctx.Data["LatestCommitVerification"] = verification
191+
185192
ctx.Data["LatestCommitUser"] = models.ValidateCommitWithEmail(latestCommit)
186193

187194
statuses, err := models.GetLatestCommitStatus(ctx.Repo.Repository, ctx.Repo.Commit.ID.String(), 0)

routers/repo/wiki.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ func renderRevisionPage(ctx *context.Context) (*git.Repository, *git.TreeEntry)
284284
return nil, nil
285285
}
286286
commitsHistory = models.ValidateCommitsWithEmails(commitsHistory)
287-
commitsHistory = models.ParseCommitsWithSignature(commitsHistory)
287+
commitsHistory = models.ParseCommitsWithSignature(commitsHistory, ctx.Repo.Repository)
288288

289289
ctx.Data["Commits"] = commitsHistory
290290

templates/repo/commit_page.tmpl

+44-27
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,22 @@
22
<div class="repository diff">
33
{{template "repo/header" .}}
44
<div class="ui container {{if .IsSplitStyle}}fluid padded{{end}}">
5-
<div class="ui top attached info clearing segment {{if .Commit.Signature}} isSigned {{if .Verification.Verified }} isVerified {{end}}{{end}}">
5+
{{$class := ""}}
6+
{{if .Commit.Signature}}
7+
{{$class = (printf "%s%s" $class " isSigned")}}
8+
{{if .Verification.Verified}}
9+
{{if eq .Verification.TrustStatus "trusted"}}
10+
{{$class = (printf "%s%s" $class " isVerified")}}
11+
{{else if eq .Verification.TrustStatus "untrusted"}}
12+
{{$class = (printf "%s%s" $class " isVerifiedUntrusted")}}
13+
{{else}}
14+
{{$class = (printf "%s%s" $class " isVerifiedUnmatched")}}
15+
{{end}}
16+
{{else if .Verification.Warning}}
17+
{{$class = (printf "%s%s" $class " isWarning")}}
18+
{{end}}
19+
{{end}}
20+
<div class="ui top attached info clearing segment {{$class}}">
621
<a class="ui floated right blue tiny button" href="{{EscapePound .SourcePath}}">
722
{{.i18n.Tr "repo.diff.browse_source"}}
823
</a>
@@ -12,15 +27,15 @@
1227
{{end}}
1328
<span class="text grey"><i class="octicon octicon-git-branch"></i>{{.BranchName}}</span>
1429
</div>
15-
<div class="ui attached info segment {{if .Commit.Signature}} isSigned {{if .Verification.Verified }} isVerified {{end}}{{end}}">
30+
<div class="ui attached info segment {{$class}}">
1631
<div class="ui stackable grid">
1732
<div class="nine wide column">
1833
{{if .Author}}
1934
<img class="ui avatar image" src="{{.Author.RelAvatarLink}}" />
2035
{{if .Author.FullName}}
21-
<a href="{{.Author.HomeLink}}"><strong>{{.Author.FullName}}</strong></a> {{if .IsSigned}}<{{.Commit.Author.Email}}>{{end}}
36+
<a href="{{.Author.HomeLink}}"><strong>{{.Author.FullName}}</strong> {{if .IsSigned}}&lt;{{.Commit.Author.Email}}&gt;{{end}}</a>
2237
{{else}}
23-
<a href="{{.Author.HomeLink}}"><strong>{{.Commit.Author.Name}}</strong></a> {{if .IsSigned}}<{{.Commit.Author.Email}}>{{end}}
38+
<a href="{{.Author.HomeLink}}"><strong>{{.Commit.Author.Name}}</strong> {{if .IsSigned}}&lt;{{.Commit.Author.Email}}&gt;{{end}}</a>
2439
{{end}}
2540
{{else}}
2641
<img class="ui avatar image" src="{{AvatarLink .Commit.Author.Email}}" />
@@ -30,7 +45,7 @@
3045
<span> </span>
3146
{{if ne .Verification.CommittingUser.ID 0}}
3247
<img class="ui avatar image" src="{{.Verification.CommittingUser.RelAvatarLink}}" />
33-
<a href="{{.Verification.CommittingUser.HomeLink}}"><strong>{{.Commit.Committer.Name}}</strong></a> <{{.Commit.Committer.Email}}>
48+
<a href="{{.Verification.CommittingUser.HomeLink}}"><strong>{{.Commit.Committer.Name}}</strong> &lt;{{.Commit.Committer.Email}}&gt;</a>
3449
{{else}}
3550
<img class="ui avatar image" src="{{AvatarLink .Commit.Committer.Email}}" />
3651
<strong>{{.Commit.Committer.Name}}</strong>
@@ -58,40 +73,42 @@
5873
</div><!-- end grid -->
5974
</div>
6075
{{if .Commit.Signature}}
61-
{{if .Verification.Verified }}
62-
<div class="ui bottom attached positive message">
76+
<div class="ui bottom attached message {{$class}}">
77+
{{if .Verification.Verified }}
6378
{{if ne .Verification.SigningUser.ID 0}}
64-
<i class="green lock icon"></i>
65-
<span>{{.i18n.Tr "repo.commits.signed_by"}}:</span>
79+
<i class="lock icon"></i>
80+
{{if eq .Verification.TrustStatus "trusted"}}
81+
<span class="ui text">{{.i18n.Tr "repo.commits.signed_by"}}:</span>
82+
{{else if eq .Verification.TrustStatus "untrusted"}}
83+
<span class="ui text">{{.i18n.Tr "repo.commits.signed_by_untrusted_user"}}:</span>
84+
{{else}}
85+
<span class="ui text">{{.i18n.Tr "repo.commits.signed_by_untrusted_user_unmatched"}}:</span>
86+
{{end}}
6687
<img class="ui avatar image" src="{{.Verification.SigningUser.RelAvatarLink}}" />
67-
<a href="{{.Verification.SigningUser.HomeLink}}"><strong>{{.Verification.SigningUser.Name}}</strong></a> <{{.Verification.SigningEmail}}>
68-
<span class="pull-right"><span>{{.i18n.Tr "repo.commits.gpg_key_id"}}:</span> {{.Verification.SigningKey.KeyID}}</span>
88+
<a href="{{.Verification.SigningUser.HomeLink}}"><strong>{{.Verification.SigningUser.Name}}</strong> <{{.Verification.SigningEmail}}></a>
89+
<span class="pull-right"><span class="ui text">{{.i18n.Tr "repo.commits.gpg_key_id"}}:</span> {{.Verification.SigningKey.KeyID}}</span>
6990
{{else}}
7091
<i class="icons" title="{{.i18n.Tr "gpg.default_key"}}">
71-
<i class="green lock icon"></i>
92+
<i class="lock icon"></i>
7293
<i class="tiny inverted cog icon centerlock"></i>
7394
</i>
74-
<span>{{.i18n.Tr "repo.commits.signed_by"}}:</span>
95+
<span class="ui text">{{.i18n.Tr "repo.commits.signed_by"}}:</span>
7596
<img class="ui avatar image" src="{{AvatarLink .Verification.SigningEmail}}" />
7697
<strong>{{.Verification.SigningUser.Name}}</strong> <{{.Verification.SigningEmail}}>
77-
<span class="pull-right"><span>{{.i18n.Tr "repo.commits.gpg_key_id"}}:</span> <i class="cogs icon" title="{{.i18n.Tr "gpg.default_key"}}"></i>{{.Verification.SigningKey.KeyID}}</span>
98+
<span class="pull-right"><span class="ui text">{{.i18n.Tr "repo.commits.gpg_key_id"}}:</span> <i class="cogs icon" title="{{.i18n.Tr "gpg.default_key"}}"></i>{{.Verification.SigningKey.KeyID}}</span>
7899
{{end}}
79-
</div>
80-
{{else if .Verification.Warning}}
81-
<div class="ui bottom attached message">
82-
<i class="red unlock icon"></i>
83-
<span class="red text">{{.i18n.Tr .Verification.Reason}}</span>
84-
<span class="pull-right"><span class="red text">{{.i18n.Tr "repo.commits.gpg_key_id"}}:</span> <i class="red warning icon"></i>{{.Verification.SigningKey.KeyID}}</span>
85-
</div>
86-
{{else}}
87-
<div class="ui bottom attached message">
88-
<i class="grey unlock icon"></i>
100+
{{else if .Verification.Warning}}
101+
<i class="unlock icon"></i>
102+
<span class="ui text">{{.i18n.Tr .Verification.Reason}}</span>
103+
<span class="pull-right"><span class="ui text">{{.i18n.Tr "repo.commits.gpg_key_id"}}:</span> <i class="warning icon"></i>{{.Verification.SigningKey.KeyID}}</span>
104+
{{else}}
105+
<i class="unlock icon"></i>
89106
{{.i18n.Tr .Verification.Reason}}
90107
{{if and .Verification.SigningKey (ne .Verification.SigningKey.KeyID "")}}
91-
<span class="pull-right"><span class="red text">{{.i18n.Tr "repo.commits.gpg_key_id"}}:</span> <i class="red warning icon"></i>{{.Verification.SigningKey.KeyID}}</span>
108+
<span class="pull-right"><span class="ui text">{{.i18n.Tr "repo.commits.gpg_key_id"}}:</span> <i class="warning icon"></i>{{.Verification.SigningKey.KeyID}}</span>
92109
{{end}}
93-
</div>
94-
{{end}}
110+
{{end}}
111+
</div>
95112
{{end}}
96113
{{if .Note}}
97114
<div class="ui top attached info segment message git-notes">

templates/repo/commits_list.tmpl

+20-12
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,13 @@
2828
{{if .Signature}}
2929
{{$class = (printf "%s%s" $class " isSigned")}}
3030
{{if .Verification.Verified}}
31-
{{$class = (printf "%s%s" $class " isVerified")}}
31+
{{if eq .Verification.TrustStatus "trusted"}}
32+
{{$class = (printf "%s%s" $class " isVerified")}}
33+
{{else if eq .Verification.TrustStatus "untrusted"}}
34+
{{$class = (printf "%s%s" $class " isVerifiedUntrusted")}}
35+
{{else}}
36+
{{$class = (printf "%s%s" $class " isVerifiedUnmatched")}}
37+
{{end}}
3238
{{else if .Verification.Warning}}
3339
{{$class = (printf "%s%s" $class " isWarning")}}
3440
{{end}}
@@ -38,20 +44,22 @@
3844
{{else}}
3945
<span class="{{$class}}">
4046
{{end}}
41-
{{ShortSha .ID.String}}
47+
<span class="shortsha">{{ShortSha .ID.String}}</span>
4248
{{if .Signature}}
4349
<div class="ui detail icon button">
4450
{{if .Verification.Verified}}
45-
{{if ne .Verification.SigningUser.ID 0}}
46-
<i title="{{.Verification.Reason}}" class="lock green icon"></i>
47-
{{else}}
48-
<i title="{{.Verification.Reason}}" class="icons">
49-
<i class="green lock icon"></i>
50-
<i class="tiny inverted cog icon centerlock"></i>
51-
</i>
52-
{{end}}
53-
{{else if .Verification.Warning}}
54-
<i title="{{$.i18n.Tr .Verification.Reason}}" class="red unlock icon"></i>
51+
<div title="{{if eq .Verification.TrustStatus "trusted"}}{{else if eq .Verification.TrustStatus "untrusted"}}{{$.i18n.Tr "repo.commits.signed_by_untrusted_user"}}: {{else}}{{$.i18n.Tr "repo.commits.signed_by_untrusted_user_unmatched"}}: {{end}}{{.Verification.Reason}}">
52+
{{if ne .Verification.SigningUser.ID 0}}
53+
<i class="lock icon"></i>
54+
<img class="ui signature avatar image" src="{{.Verification.SigningUser.RelAvatarLink}}" />
55+
{{else}}
56+
<i title="{{.Verification.Reason}}" class="icons">
57+
<i class="lock icon"></i>
58+
<i class="tiny inverted cog icon centerlock"></i>
59+
</i>
60+
<img class="ui signature avatar image" src="{{AvatarLink .Verification.SigningEmail}}" />
61+
{{end}}
62+
</div>
5563
{{else}}
5664
<i title="{{$.i18n.Tr .Verification.Reason}}" class="unlock icon"></i>
5765
{{end}}

0 commit comments

Comments
 (0)