summaryrefslogtreecommitdiffstats
path: root/models
diff options
context:
space:
mode:
Diffstat (limited to 'models')
-rw-r--r--models/gpg_key.go89
-rw-r--r--models/migrations/migrations.go2
-rw-r--r--models/migrations/v152.go14
-rw-r--r--models/pull_sign.go50
-rw-r--r--models/repo.go56
-rw-r--r--models/repo_sign.go93
6 files changed, 212 insertions, 92 deletions
diff --git a/models/gpg_key.go b/models/gpg_key.go
index 662eac939b..b944fdcbff 100644
--- a/models/gpg_key.go
+++ b/models/gpg_key.go
@@ -831,7 +831,7 @@ func ParseCommitsWithSignature(oldCommits *list.List, repository *Repository) *l
newCommits = list.New()
e = oldCommits.Front()
)
- memberMap := map[int64]bool{}
+ keyMap := map[string]bool{}
for e != nil {
c := e.Value.(UserCommit)
@@ -840,7 +840,7 @@ func ParseCommitsWithSignature(oldCommits *list.List, repository *Repository) *l
Verification: ParseCommitWithSignature(c.Commit),
}
- _ = CalculateTrustStatus(signCommit.Verification, repository, &memberMap)
+ _ = CalculateTrustStatus(signCommit.Verification, repository, &keyMap)
newCommits.PushBack(signCommit)
e = e.Next()
@@ -849,31 +849,70 @@ func ParseCommitsWithSignature(oldCommits *list.List, repository *Repository) *l
}
// 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)
- }
+func CalculateTrustStatus(verification *CommitVerification, repository *Repository, keyMap *map[string]bool) (err error) {
+ if !verification.Verified {
+ return
+ }
- 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"
- }
- }
+ // There are several trust models in Gitea
+ trustModel := repository.GetTrustModel()
+
+ // In the Committer trust model a signature is trusted if it matches the committer
+ // - it doesn't matter if they're a collaborator, the owner, Gitea or Github
+ // NB: This model is commit verification only
+ if trustModel == CommitterTrustModel {
+ // default to "unmatched"
+ verification.TrustStatus = "unmatched"
+
+ // We can only verify against users in our database but the default key will match
+ // against by email if it is not in the db.
+ if (verification.SigningUser.ID != 0 &&
+ verification.CommittingUser.ID == verification.SigningUser.ID) ||
+ (verification.SigningUser.ID == 0 && verification.CommittingUser.ID == 0 &&
+ verification.SigningUser.Email == verification.CommittingUser.Email) {
+ verification.TrustStatus = "trusted"
}
+ return
}
+
+ // Now we drop to the more nuanced trust models...
+ verification.TrustStatus = "trusted"
+
+ if verification.SigningUser.ID == 0 {
+ // This commit is signed by the default key - but this key is not assigned to a user in the DB.
+
+ // However in the CollaboratorCommitterTrustModel we cannot mark this as trusted
+ // unless the default key matches the email of a non-user.
+ if trustModel == CollaboratorCommitterTrustModel && (verification.CommittingUser.ID != 0 ||
+ verification.SigningUser.Email != verification.CommittingUser.Email) {
+ verification.TrustStatus = "untrusted"
+ }
+ return
+ }
+
+ var isMember bool
+ if keyMap != nil {
+ var has bool
+ isMember, has = (*keyMap)[verification.SigningKey.KeyID]
+ if !has {
+ isMember, err = repository.IsOwnerMemberCollaborator(verification.SigningUser.ID)
+ (*keyMap)[verification.SigningKey.KeyID] = 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
+ // This should be marked as questionable unless the signing user is a collaborator/team member etc.
+ verification.TrustStatus = "unmatched"
+ }
+ } else if trustModel == CollaboratorCommitterTrustModel && verification.CommittingUser.ID != verification.SigningUser.ID {
+ // The committing user and the signing user are not the same and our trustmodel states that they must match
+ verification.TrustStatus = "unmatched"
+ }
+
return
}
diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go
index 5317cc5743..ea1bf59649 100644
--- a/models/migrations/migrations.go
+++ b/models/migrations/migrations.go
@@ -237,6 +237,8 @@ var migrations = []Migration{
NewMigration("add primary key to repo_topic", addPrimaryKeyToRepoTopic),
// v151 -> v152
NewMigration("set default password algorithm to Argon2", setDefaultPasswordToArgon2),
+ // v152 -> v153
+ NewMigration("add TrustModel field to Repository", addTrustModelToRepository),
}
// GetCurrentDBVersion returns the current db version
diff --git a/models/migrations/v152.go b/models/migrations/v152.go
new file mode 100644
index 0000000000..f71f71e22f
--- /dev/null
+++ b/models/migrations/v152.go
@@ -0,0 +1,14 @@
+// Copyright 2020 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 "xorm.io/xorm"
+
+func addTrustModelToRepository(x *xorm.Engine) error {
+ type Repository struct {
+ TrustModel int
+ }
+ return x.Sync2(new(Repository))
+}
diff --git a/models/pull_sign.go b/models/pull_sign.go
index ab61232d65..10a6522ebe 100644
--- a/models/pull_sign.go
+++ b/models/pull_sign.go
@@ -11,16 +11,16 @@ import (
)
// SignMerge determines if we should sign a PR merge commit to the base repository
-func (pr *PullRequest) SignMerge(u *User, tmpBasePath, baseCommit, headCommit string) (bool, string, error) {
+func (pr *PullRequest) SignMerge(u *User, tmpBasePath, baseCommit, headCommit string) (bool, string, *git.Signature, error) {
if err := pr.LoadBaseRepo(); err != nil {
log.Error("Unable to get Base Repo for pull request")
- return false, "", err
+ return false, "", nil, err
}
repo := pr.BaseRepo
- signingKey := signingKey(repo.RepoPath())
+ signingKey, signer := SigningKey(repo.RepoPath())
if signingKey == "" {
- return false, "", &ErrWontSign{noKey}
+ return false, "", nil, &ErrWontSign{noKey}
}
rules := signingModeFromStrings(setting.Repository.Signing.Merges)
@@ -31,101 +31,101 @@ Loop:
for _, rule := range rules {
switch rule {
case never:
- return false, "", &ErrWontSign{never}
+ return false, "", nil, &ErrWontSign{never}
case always:
break Loop
case pubkey:
keys, err := ListGPGKeys(u.ID, ListOptions{})
if err != nil {
- return false, "", err
+ return false, "", nil, err
}
if len(keys) == 0 {
- return false, "", &ErrWontSign{pubkey}
+ return false, "", nil, &ErrWontSign{pubkey}
}
case twofa:
twofaModel, err := GetTwoFactorByUID(u.ID)
if err != nil && !IsErrTwoFactorNotEnrolled(err) {
- return false, "", err
+ return false, "", nil, err
}
if twofaModel == nil {
- return false, "", &ErrWontSign{twofa}
+ return false, "", nil, &ErrWontSign{twofa}
}
case approved:
protectedBranch, err := GetProtectedBranchBy(repo.ID, pr.BaseBranch)
if err != nil {
- return false, "", err
+ return false, "", nil, err
}
if protectedBranch == nil {
- return false, "", &ErrWontSign{approved}
+ return false, "", nil, &ErrWontSign{approved}
}
if protectedBranch.GetGrantedApprovalsCount(pr) < 1 {
- return false, "", &ErrWontSign{approved}
+ return false, "", nil, &ErrWontSign{approved}
}
case baseSigned:
if gitRepo == nil {
gitRepo, err = git.OpenRepository(tmpBasePath)
if err != nil {
- return false, "", err
+ return false, "", nil, err
}
defer gitRepo.Close()
}
commit, err := gitRepo.GetCommit(baseCommit)
if err != nil {
- return false, "", err
+ return false, "", nil, err
}
verification := ParseCommitWithSignature(commit)
if !verification.Verified {
- return false, "", &ErrWontSign{baseSigned}
+ return false, "", nil, &ErrWontSign{baseSigned}
}
case headSigned:
if gitRepo == nil {
gitRepo, err = git.OpenRepository(tmpBasePath)
if err != nil {
- return false, "", err
+ return false, "", nil, err
}
defer gitRepo.Close()
}
commit, err := gitRepo.GetCommit(headCommit)
if err != nil {
- return false, "", err
+ return false, "", nil, err
}
verification := ParseCommitWithSignature(commit)
if !verification.Verified {
- return false, "", &ErrWontSign{headSigned}
+ return false, "", nil, &ErrWontSign{headSigned}
}
case commitsSigned:
if gitRepo == nil {
gitRepo, err = git.OpenRepository(tmpBasePath)
if err != nil {
- return false, "", err
+ return false, "", nil, err
}
defer gitRepo.Close()
}
commit, err := gitRepo.GetCommit(headCommit)
if err != nil {
- return false, "", err
+ return false, "", nil, err
}
verification := ParseCommitWithSignature(commit)
if !verification.Verified {
- return false, "", &ErrWontSign{commitsSigned}
+ return false, "", nil, &ErrWontSign{commitsSigned}
}
// need to work out merge-base
mergeBaseCommit, _, err := gitRepo.GetMergeBase("", baseCommit, headCommit)
if err != nil {
- return false, "", err
+ return false, "", nil, err
}
commitList, err := commit.CommitsBeforeUntil(mergeBaseCommit)
if err != nil {
- return false, "", err
+ return false, "", nil, err
}
for e := commitList.Front(); e != nil; e = e.Next() {
commit = e.Value.(*git.Commit)
verification := ParseCommitWithSignature(commit)
if !verification.Verified {
- return false, "", &ErrWontSign{commitsSigned}
+ return false, "", nil, &ErrWontSign{commitsSigned}
}
}
}
}
- return true, signingKey, nil
+ return true, signingKey, signer, nil
}
diff --git a/models/repo.go b/models/repo.go
index 2bbc74f439..25fe3f63d3 100644
--- a/models/repo.go
+++ b/models/repo.go
@@ -143,6 +143,47 @@ const (
RepositoryBeingMigrated // repository is migrating
)
+// TrustModelType defines the types of trust model for this repository
+type TrustModelType int
+
+// kinds of TrustModel
+const (
+ DefaultTrustModel TrustModelType = iota // default trust model
+ CommitterTrustModel
+ CollaboratorTrustModel
+ CollaboratorCommitterTrustModel
+)
+
+// String converts a TrustModelType to a string
+func (t TrustModelType) String() string {
+ switch t {
+ case DefaultTrustModel:
+ return "default"
+ case CommitterTrustModel:
+ return "committer"
+ case CollaboratorTrustModel:
+ return "collaborator"
+ case CollaboratorCommitterTrustModel:
+ return "collaboratorcommitter"
+ }
+ return "default"
+}
+
+// ToTrustModel converts a string to a TrustModelType
+func ToTrustModel(model string) TrustModelType {
+ switch strings.ToLower(strings.TrimSpace(model)) {
+ case "default":
+ return DefaultTrustModel
+ case "collaborator":
+ return CollaboratorTrustModel
+ case "committer":
+ return CommitterTrustModel
+ case "collaboratorcommitter":
+ return CollaboratorCommitterTrustModel
+ }
+ return DefaultTrustModel
+}
+
// Repository represents a git repository.
type Repository struct {
ID int64 `xorm:"pk autoincr"`
@@ -198,6 +239,8 @@ type Repository struct {
CloseIssuesViaCommitInAnyBranch bool `xorm:"NOT NULL DEFAULT false"`
Topics []string `xorm:"TEXT JSON"`
+ TrustModel TrustModelType
+
// Avatar: ID(10-20)-md5(32) - must fit into 64 symbols
Avatar string `xorm:"VARCHAR(64)"`
@@ -1038,6 +1081,7 @@ type CreateRepoOptions struct {
IsMirror bool
AutoInit bool
Status RepositoryStatus
+ TrustModel TrustModelType
}
// GetRepoInitFile returns repository init files
@@ -2383,6 +2427,18 @@ func UpdateRepositoryCols(repo *Repository, cols ...string) error {
return updateRepositoryCols(x, repo, cols...)
}
+// GetTrustModel will get the TrustModel for the repo or the default trust model
+func (repo *Repository) GetTrustModel() TrustModelType {
+ trustModel := repo.TrustModel
+ if trustModel == DefaultTrustModel {
+ trustModel = ToTrustModel(setting.Repository.Signing.DefaultTrustModel)
+ if trustModel == DefaultTrustModel {
+ return CollaboratorTrustModel
+ }
+ }
+ return trustModel
+}
+
// DoctorUserStarNum recalculate Stars number for all user
func DoctorUserStarNum() (err error) {
const batchSize = 100
diff --git a/models/repo_sign.go b/models/repo_sign.go
index c9dd5ea4dc..be9309ed4e 100644
--- a/models/repo_sign.go
+++ b/models/repo_sign.go
@@ -31,7 +31,7 @@ const (
func signingModeFromStrings(modeStrings []string) []signingMode {
returnable := make([]signingMode, 0, len(modeStrings))
for _, mode := range modeStrings {
- signMode := signingMode(strings.ToLower(mode))
+ signMode := signingMode(strings.ToLower(strings.TrimSpace(mode)))
switch signMode {
case never:
return []signingMode{never}
@@ -59,9 +59,10 @@ func signingModeFromStrings(modeStrings []string) []signingMode {
return returnable
}
-func signingKey(repoPath string) string {
+// SigningKey returns the KeyID and git Signature for the repo
+func SigningKey(repoPath string) (string, *git.Signature) {
if setting.Repository.Signing.SigningKey == "none" {
- return ""
+ return "", nil
}
if setting.Repository.Signing.SigningKey == "default" || setting.Repository.Signing.SigningKey == "" {
@@ -69,19 +70,27 @@ func signingKey(repoPath string) string {
value, _ := git.NewCommand("config", "--get", "commit.gpgsign").RunInDir(repoPath)
sign, valid := git.ParseBool(strings.TrimSpace(value))
if !sign || !valid {
- return ""
+ return "", nil
}
signingKey, _ := git.NewCommand("config", "--get", "user.signingkey").RunInDir(repoPath)
- return strings.TrimSpace(signingKey)
+ signingName, _ := git.NewCommand("config", "--get", "user.name").RunInDir(repoPath)
+ signingEmail, _ := git.NewCommand("config", "--get", "user.email").RunInDir(repoPath)
+ return strings.TrimSpace(signingKey), &git.Signature{
+ Name: strings.TrimSpace(signingName),
+ Email: strings.TrimSpace(signingEmail),
+ }
}
- return setting.Repository.Signing.SigningKey
+ return setting.Repository.Signing.SigningKey, &git.Signature{
+ Name: setting.Repository.Signing.SigningName,
+ Email: setting.Repository.Signing.SigningEmail,
+ }
}
// PublicSigningKey gets the public signing key within a provided repository directory
func PublicSigningKey(repoPath string) (string, error) {
- signingKey := signingKey(repoPath)
+ signingKey, _ := SigningKey(repoPath)
if signingKey == "" {
return "", nil
}
@@ -96,143 +105,143 @@ func PublicSigningKey(repoPath string) (string, error) {
}
// SignInitialCommit determines if we should sign the initial commit to this repository
-func SignInitialCommit(repoPath string, u *User) (bool, string, error) {
+func SignInitialCommit(repoPath string, u *User) (bool, string, *git.Signature, error) {
rules := signingModeFromStrings(setting.Repository.Signing.InitialCommit)
- signingKey := signingKey(repoPath)
+ signingKey, sig := SigningKey(repoPath)
if signingKey == "" {
- return false, "", &ErrWontSign{noKey}
+ return false, "", nil, &ErrWontSign{noKey}
}
Loop:
for _, rule := range rules {
switch rule {
case never:
- return false, "", &ErrWontSign{never}
+ return false, "", nil, &ErrWontSign{never}
case always:
break Loop
case pubkey:
keys, err := ListGPGKeys(u.ID, ListOptions{})
if err != nil {
- return false, "", err
+ return false, "", nil, err
}
if len(keys) == 0 {
- return false, "", &ErrWontSign{pubkey}
+ return false, "", nil, &ErrWontSign{pubkey}
}
case twofa:
twofaModel, err := GetTwoFactorByUID(u.ID)
if err != nil && !IsErrTwoFactorNotEnrolled(err) {
- return false, "", err
+ return false, "", nil, err
}
if twofaModel == nil {
- return false, "", &ErrWontSign{twofa}
+ return false, "", nil, &ErrWontSign{twofa}
}
}
}
- return true, signingKey, nil
+ return true, signingKey, sig, nil
}
// SignWikiCommit determines if we should sign the commits to this repository wiki
-func (repo *Repository) SignWikiCommit(u *User) (bool, string, error) {
+func (repo *Repository) SignWikiCommit(u *User) (bool, string, *git.Signature, error) {
rules := signingModeFromStrings(setting.Repository.Signing.Wiki)
- signingKey := signingKey(repo.WikiPath())
+ signingKey, sig := SigningKey(repo.WikiPath())
if signingKey == "" {
- return false, "", &ErrWontSign{noKey}
+ return false, "", nil, &ErrWontSign{noKey}
}
Loop:
for _, rule := range rules {
switch rule {
case never:
- return false, "", &ErrWontSign{never}
+ return false, "", nil, &ErrWontSign{never}
case always:
break Loop
case pubkey:
keys, err := ListGPGKeys(u.ID, ListOptions{})
if err != nil {
- return false, "", err
+ return false, "", nil, err
}
if len(keys) == 0 {
- return false, "", &ErrWontSign{pubkey}
+ return false, "", nil, &ErrWontSign{pubkey}
}
case twofa:
twofaModel, err := GetTwoFactorByUID(u.ID)
if err != nil && !IsErrTwoFactorNotEnrolled(err) {
- return false, "", err
+ return false, "", nil, err
}
if twofaModel == nil {
- return false, "", &ErrWontSign{twofa}
+ return false, "", nil, &ErrWontSign{twofa}
}
case parentSigned:
gitRepo, err := git.OpenRepository(repo.WikiPath())
if err != nil {
- return false, "", err
+ return false, "", nil, err
}
defer gitRepo.Close()
commit, err := gitRepo.GetCommit("HEAD")
if err != nil {
- return false, "", err
+ return false, "", nil, err
}
if commit.Signature == nil {
- return false, "", &ErrWontSign{parentSigned}
+ return false, "", nil, &ErrWontSign{parentSigned}
}
verification := ParseCommitWithSignature(commit)
if !verification.Verified {
- return false, "", &ErrWontSign{parentSigned}
+ return false, "", nil, &ErrWontSign{parentSigned}
}
}
}
- return true, signingKey, nil
+ return true, signingKey, sig, nil
}
// SignCRUDAction determines if we should sign a CRUD commit to this repository
-func (repo *Repository) SignCRUDAction(u *User, tmpBasePath, parentCommit string) (bool, string, error) {
+func (repo *Repository) SignCRUDAction(u *User, tmpBasePath, parentCommit string) (bool, string, *git.Signature, error) {
rules := signingModeFromStrings(setting.Repository.Signing.CRUDActions)
- signingKey := signingKey(repo.RepoPath())
+ signingKey, sig := SigningKey(repo.RepoPath())
if signingKey == "" {
- return false, "", &ErrWontSign{noKey}
+ return false, "", nil, &ErrWontSign{noKey}
}
Loop:
for _, rule := range rules {
switch rule {
case never:
- return false, "", &ErrWontSign{never}
+ return false, "", nil, &ErrWontSign{never}
case always:
break Loop
case pubkey:
keys, err := ListGPGKeys(u.ID, ListOptions{})
if err != nil {
- return false, "", err
+ return false, "", nil, err
}
if len(keys) == 0 {
- return false, "", &ErrWontSign{pubkey}
+ return false, "", nil, &ErrWontSign{pubkey}
}
case twofa:
twofaModel, err := GetTwoFactorByUID(u.ID)
if err != nil && !IsErrTwoFactorNotEnrolled(err) {
- return false, "", err
+ return false, "", nil, err
}
if twofaModel == nil {
- return false, "", &ErrWontSign{twofa}
+ return false, "", nil, &ErrWontSign{twofa}
}
case parentSigned:
gitRepo, err := git.OpenRepository(tmpBasePath)
if err != nil {
- return false, "", err
+ return false, "", nil, err
}
defer gitRepo.Close()
commit, err := gitRepo.GetCommit(parentCommit)
if err != nil {
- return false, "", err
+ return false, "", nil, err
}
if commit.Signature == nil {
- return false, "", &ErrWontSign{parentSigned}
+ return false, "", nil, &ErrWontSign{parentSigned}
}
verification := ParseCommitWithSignature(commit)
if !verification.Verified {
- return false, "", &ErrWontSign{parentSigned}
+ return false, "", nil, &ErrWontSign{parentSigned}
}
}
}
- return true, signingKey, nil
+ return true, signingKey, sig, nil
}