]> source.dussan.org Git - gitea.git/commitdiff
Add configurable Trust Models (#11712)
authorzeripath <art27@cantab.net>
Sat, 19 Sep 2020 16:44:55 +0000 (17:44 +0100)
committerGitHub <noreply@github.com>
Sat, 19 Sep 2020 16:44:55 +0000 (00:44 +0800)
* Add configurable Trust Models

Gitea's default signature verification model differs from GitHub. GitHub
uses signatures to verify that the committer is who they say they are -
meaning that when GitHub makes a signed commit it must be the committer.
The GitHub model prevents re-publishing of commits after revocation of a
key and prevents re-signing of other people's commits to create a
completely trusted repository signed by one key or a set of trusted
keys.

The default behaviour of Gitea in contrast is to always display the
avatar and information related to a signature. This allows signatures to
be decoupled from the committer. That being said, allowing arbitary
users to present other peoples commits as theirs is not necessarily
desired therefore we have a trust model whereby signatures from
collaborators are marked trusted, signatures matching the commit line
are marked untrusted and signatures that match a user in the db but not
the committer line are marked unmatched.

The problem with this model is that this conflicts with Github therefore
we need to provide an option to allow users to choose the Github model
should they wish to.

Signed-off-by: Andrew Thornton <art27@cantab.net>
* Adjust locale strings

Signed-off-by: Andrew Thornton <art27@cantab.net>
* as per @6543

Co-authored-by: 6543 <6543@obermui.de>
* Update models/gpg_key.go

* Add migration for repository

Signed-off-by: Andrew Thornton <art27@cantab.net>
Co-authored-by: 6543 <6543@obermui.de>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
29 files changed:
custom/conf/app.example.ini
docs/content/doc/advanced/config-cheat-sheet.en-us.md
models/gpg_key.go
models/migrations/migrations.go
models/migrations/v152.go [new file with mode: 0644]
models/pull_sign.go
models/repo.go
models/repo_sign.go
modules/auth/repo_form.go
modules/context/repo.go
modules/git/repo_tree.go
modules/repofiles/delete.go
modules/repofiles/temp_repo.go
modules/repofiles/update.go
modules/repository/create.go
modules/repository/generate.go
modules/repository/init.go
modules/setting/repository.go
modules/structs/repo.go
options/locale/locale_en-US.ini
routers/api/v1/repo/repo.go
routers/repo/issue.go
routers/repo/repo.go
routers/repo/setting.go
services/pull/merge.go
services/wiki/wiki.go
templates/repo/create.tmpl
templates/repo/settings/options.tmpl
templates/swagger/v1_json.tmpl

index af3418f70c9b009a25b56700fd482f7e4abbb8e0..ffc4e406780e7ebb96e8b8f438d915f3b9d5c469 100644 (file)
@@ -124,6 +124,8 @@ SIGNING_KEY = default
 ; by setting the SIGNING_KEY ID to the correct ID.)
 SIGNING_NAME =
 SIGNING_EMAIL =
+; Sets the default trust model for repositories. Options are: collaborator, committer, collaboratorcommitter
+DEFAULT_TRUST_MODEL=collaborator
 ; Determines when gitea should sign the initial commit when creating a repository
 ; Either:
 ; - never
index 7f969add2c90269d015e96bf5c3e1e8bfcf425df..1e48ee2597f76d7edae66db1e770602a54e1b8aa 100644 (file)
@@ -101,6 +101,10 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
   - `twofa`: Only sign if the user is logged in with twofa
   - `always`: Always sign
   - Options other than `never` and `always` can be combined as a comma separated list.
+- `DEFAULT_TRUST_MODEL`: **collaborator**: \[collaborator, committer, collaboratorcommitter\]: The default trust model used for verifying commits.
+   - `collaborator`: Trust signatures signed by keys of collaborators.
+   - `committer`: Trust signatures that match committers (This matches GitHub and will force Gitea signed commits to have Gitea as the commmitter).
+   - `collaboratorcommitter`: Trust signatures signed by keys of collaborators which match the commiter.
 - `WIKI`: **never**: \[never, pubkey, twofa, always, parentsigned\]: Sign commits to wiki.
 - `CRUD_ACTIONS`: **pubkey, twofa, parentsigned**: \[never, pubkey, twofa, parentsigned, always\]: Sign CRUD actions.
   - Options as above, with the addition of:
index 662eac939b68dc3e5a3124c6fb3d765989915631..b944fdcbffe456ce6670134ac8f9a569b8ef3ccd 100644 (file)
@@ -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
 }
index 5317cc5743d5dcc5732870c3263aa4d81df9b951..ea1bf596492f6c937ad5ab8d4d9b2929f3af35f7 100644 (file)
@@ -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 (file)
index 0000000..f71f71e
--- /dev/null
@@ -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))
+}
index ab61232d652c4fa1b409c9ba8a316b3d98c6ab7d..10a6522ebef733cafd2fbd2d59dc279bfbf166b1 100644 (file)
@@ -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
 }
index 2bbc74f439b37679be7eae8ce8f35e2a780ae1d0..25fe3f63d39c9af045779a633e96e0f84218ab98 100644 (file)
@@ -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
index c9dd5ea4dc808021ed9ba0e1b950901e4d37e5de..be9309ed4eac15f89126442e35fad9370f010b0f 100644 (file)
@@ -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
 }
index 3ad57085b0533988f6049b87aa762c0d2bd1b089..f1130f372b7945b969718ba4c3b1cd3c295397a9 100644 (file)
@@ -45,6 +45,7 @@ type CreateRepoForm struct {
        Webhooks     bool
        Avatar       bool
        Labels       bool
+       TrustModel   string
 }
 
 // Validate validates the fields
@@ -142,6 +143,9 @@ type RepoSettingForm struct {
        EnableIssueDependencies          bool
        IsArchived                       bool
 
+       // Signing Settings
+       TrustModel string
+
        // Admin settings
        EnableHealthCheck                     bool
        EnableCloseIssuesViaCommitInAnyBranch bool
index 2c773614606fc4d5913692052ea0568e0aaa8146..cb2e60d2634b57f59fd76b3cbd46b8abfe34a81e 100644 (file)
@@ -114,7 +114,7 @@ func (r *Repository) CanCommitToBranch(doer *models.User) (CanCommitToBranchResu
                requireSigned = protectedBranch.RequireSignedCommits
        }
 
-       sign, keyID, err := r.Repository.SignCRUDAction(doer, r.Repository.RepoPath(), git.BranchPrefix+r.BranchName)
+       sign, keyID, _, err := r.Repository.SignCRUDAction(doer, r.Repository.RepoPath(), git.BranchPrefix+r.BranchName)
 
        canCommit := r.CanEnableEditor() && userCanPush
        if requireSigned {
index a662aaab4f8233def4a27af05032812b86cb9ddd..57896318a7247e72ee3e42557109c4490372b3a2 100644 (file)
@@ -62,7 +62,7 @@ type CommitTreeOpts struct {
 }
 
 // CommitTree creates a commit from a given tree id for the user with provided message
-func (repo *Repository) CommitTree(sig *Signature, tree *Tree, opts CommitTreeOpts) (SHA1, error) {
+func (repo *Repository) CommitTree(author *Signature, committer *Signature, tree *Tree, opts CommitTreeOpts) (SHA1, error) {
        err := LoadGitVersion()
        if err != nil {
                return SHA1{}, err
@@ -72,11 +72,11 @@ func (repo *Repository) CommitTree(sig *Signature, tree *Tree, opts CommitTreeOp
 
        // Because this may call hooks we should pass in the environment
        env := append(os.Environ(),
-               "GIT_AUTHOR_NAME="+sig.Name,
-               "GIT_AUTHOR_EMAIL="+sig.Email,
+               "GIT_AUTHOR_NAME="+author.Name,
+               "GIT_AUTHOR_EMAIL="+author.Email,
                "GIT_AUTHOR_DATE="+commitTimeStr,
-               "GIT_COMMITTER_NAME="+sig.Name,
-               "GIT_COMMITTER_EMAIL="+sig.Email,
+               "GIT_COMMITTER_NAME="+committer.Name,
+               "GIT_COMMITTER_EMAIL="+committer.Email,
                "GIT_COMMITTER_DATE="+commitTimeStr,
        )
        cmd := NewCommand("commit-tree", tree.ID.String())
index 2ffc75e7c8e13ca456ea35cc6360a8d30d46d4a9..8343776c4716cbb3403f16ae4558ded7cfa69a10 100644 (file)
@@ -67,7 +67,7 @@ func DeleteRepoFile(repo *models.Repository, doer *models.User, opts *DeleteRepo
                                }
                        }
                        if protectedBranch.RequireSignedCommits {
-                               _, _, err := repo.SignCRUDAction(doer, repo.RepoPath(), opts.OldBranch)
+                               _, _, _, err := repo.SignCRUDAction(doer, repo.RepoPath(), opts.OldBranch)
                                if err != nil {
                                        if !models.IsErrWontSign(err) {
                                                return nil, err
index ec671a9322cabb12b02a0f05353e1737bd09a6fe..e0d6c9fcb6cf896d43a19ca6ebcf2da1f647a132 100644 (file)
@@ -204,8 +204,6 @@ func (t *TemporaryUploadRepository) CommitTreeWithDate(author, committer *models
                "GIT_AUTHOR_NAME="+authorSig.Name,
                "GIT_AUTHOR_EMAIL="+authorSig.Email,
                "GIT_AUTHOR_DATE="+authorDate.Format(time.RFC3339),
-               "GIT_COMMITTER_NAME="+committerSig.Name,
-               "GIT_COMMITTER_EMAIL="+committerSig.Email,
                "GIT_COMMITTER_DATE="+committerDate.Format(time.RFC3339),
        )
 
@@ -217,14 +215,32 @@ func (t *TemporaryUploadRepository) CommitTreeWithDate(author, committer *models
 
        // Determine if we should sign
        if git.CheckGitVersionConstraint(">= 1.7.9") == nil {
-               sign, keyID, _ := t.repo.SignCRUDAction(author, t.basePath, "HEAD")
+               sign, keyID, signer, _ := t.repo.SignCRUDAction(author, t.basePath, "HEAD")
                if sign {
                        args = append(args, "-S"+keyID)
+                       if t.repo.GetTrustModel() == models.CommitterTrustModel || t.repo.GetTrustModel() == models.CollaboratorCommitterTrustModel {
+                               if committerSig.Name != authorSig.Name || committerSig.Email != authorSig.Email {
+                                       // Add trailers
+                                       _, _ = messageBytes.WriteString("\n")
+                                       _, _ = messageBytes.WriteString("Co-Authored-By: ")
+                                       _, _ = messageBytes.WriteString(committerSig.String())
+                                       _, _ = messageBytes.WriteString("\n")
+                                       _, _ = messageBytes.WriteString("Co-Committed-By: ")
+                                       _, _ = messageBytes.WriteString(committerSig.String())
+                                       _, _ = messageBytes.WriteString("\n")
+                               }
+                               committerSig = signer
+                       }
                } else if git.CheckGitVersionConstraint(">= 2.0.0") == nil {
                        args = append(args, "--no-gpg-sign")
                }
        }
 
+       env = append(env,
+               "GIT_COMMITTER_NAME="+committerSig.Name,
+               "GIT_COMMITTER_EMAIL="+committerSig.Email,
+       )
+
        stdout := new(bytes.Buffer)
        stderr := new(bytes.Buffer)
        if err := git.NewCommand(args...).RunInDirTimeoutEnvFullPipeline(env, -1, t.basePath, stdout, stderr, messageBytes); err != nil {
index dcb87ec92b1e14fc30a4353c5ffc09c1fe7c5297..f7fa5f0289fd03b34907600a35b4a3d962861736 100644 (file)
@@ -161,7 +161,7 @@ func CreateOrUpdateRepoFile(repo *models.Repository, doer *models.User, opts *Up
                                }
                        }
                        if protectedBranch.RequireSignedCommits {
-                               _, _, err := repo.SignCRUDAction(doer, repo.RepoPath(), opts.OldBranch)
+                               _, _, _, err := repo.SignCRUDAction(doer, repo.RepoPath(), opts.OldBranch)
                                if err != nil {
                                        if !models.IsErrWontSign(err) {
                                                return nil, err
index abbec05a37b0fb4898ff02f2c48fca63b8bd2e79..c180b9b9481d7e51c70fe27ce08a66d73252d448 100644 (file)
@@ -41,6 +41,7 @@ func CreateRepository(doer, u *models.User, opts models.CreateRepoOptions) (_ *m
                CloseIssuesViaCommitInAnyBranch: setting.Repository.DefaultCloseIssuesViaCommitsInAnyBranch,
                Status:                          opts.Status,
                IsEmpty:                         !opts.AutoInit,
+               TrustModel:                      opts.TrustModel,
        }
 
        err = models.WithTx(func(ctx models.DBContext) error {
index c5fb0af383774757c8e7f143ba5769df056b140e..1314464a6e1ff3bf2e4eaf80564d15fdb9c03d2f 100644 (file)
@@ -243,6 +243,7 @@ func GenerateRepository(ctx models.DBContext, doer, owner *models.User, template
                IsEmpty:       !opts.GitContent || templateRepo.IsEmpty,
                IsFsckEnabled: templateRepo.IsFsckEnabled,
                TemplateID:    templateRepo.ID,
+               TrustModel:    templateRepo.TrustModel,
        }
 
        if err = models.CreateRepository(ctx, doer, owner, generateRepo); err != nil {
index c2038e18d4ca2d3779007d5e76509c82d7464672..d066544a85f5ae678cf8849e323cc4437b8b60bc 100644 (file)
@@ -109,10 +109,10 @@ func initRepoCommit(tmpPath string, repo *models.Repository, u *models.User, def
                "GIT_AUTHOR_NAME="+sig.Name,
                "GIT_AUTHOR_EMAIL="+sig.Email,
                "GIT_AUTHOR_DATE="+commitTimeStr,
-               "GIT_COMMITTER_NAME="+sig.Name,
-               "GIT_COMMITTER_EMAIL="+sig.Email,
                "GIT_COMMITTER_DATE="+commitTimeStr,
        )
+       committerName := sig.Name
+       committerEmail := sig.Email
 
        if stdout, err := git.NewCommand("add", "--all").
                SetDescription(fmt.Sprintf("initRepoCommit (git add): %s", tmpPath)).
@@ -132,14 +132,25 @@ func initRepoCommit(tmpPath string, repo *models.Repository, u *models.User, def
        }
 
        if git.CheckGitVersionConstraint(">= 1.7.9") == nil {
-               sign, keyID, _ := models.SignInitialCommit(tmpPath, u)
+               sign, keyID, signer, _ := models.SignInitialCommit(tmpPath, u)
                if sign {
                        args = append(args, "-S"+keyID)
+
+                       if repo.GetTrustModel() == models.CommitterTrustModel || repo.GetTrustModel() == models.CollaboratorCommitterTrustModel {
+                               // need to set the committer to the KeyID owner
+                               committerName = signer.Name
+                               committerEmail = signer.Email
+                       }
                } else if git.CheckGitVersionConstraint(">= 2.0.0") == nil {
                        args = append(args, "--no-gpg-sign")
                }
        }
 
+       env = append(env,
+               "GIT_COMMITTER_NAME="+committerName,
+               "GIT_COMMITTER_EMAIL="+committerEmail,
+       )
+
        if stdout, err := git.NewCommand(args...).
                SetDescription(fmt.Sprintf("initRepoCommit (git commit): %s", tmpPath)).
                RunInDirWithEnv(tmpPath, env); err != nil {
index eb1501d7b86ebe63ed35e86c5f0b1753cff80059..67dd80535338d970e4508b4e9c63015cddca7b72 100644 (file)
@@ -83,13 +83,14 @@ var (
                } `ini:"repository.issue"`
 
                Signing struct {
-                       SigningKey    string
-                       SigningName   string
-                       SigningEmail  string
-                       InitialCommit []string
-                       CRUDActions   []string `ini:"CRUD_ACTIONS"`
-                       Merges        []string
-                       Wiki          []string
+                       SigningKey        string
+                       SigningName       string
+                       SigningEmail      string
+                       InitialCommit     []string
+                       CRUDActions       []string `ini:"CRUD_ACTIONS"`
+                       Merges            []string
+                       Wiki              []string
+                       DefaultTrustModel string
                } `ini:"repository.signing"`
        }{
                DetectedCharsetsOrder: []string{
@@ -209,21 +210,23 @@ var (
 
                // Signing settings
                Signing: struct {
-                       SigningKey    string
-                       SigningName   string
-                       SigningEmail  string
-                       InitialCommit []string
-                       CRUDActions   []string `ini:"CRUD_ACTIONS"`
-                       Merges        []string
-                       Wiki          []string
+                       SigningKey        string
+                       SigningName       string
+                       SigningEmail      string
+                       InitialCommit     []string
+                       CRUDActions       []string `ini:"CRUD_ACTIONS"`
+                       Merges            []string
+                       Wiki              []string
+                       DefaultTrustModel string
                }{
-                       SigningKey:    "default",
-                       SigningName:   "",
-                       SigningEmail:  "",
-                       InitialCommit: []string{"always"},
-                       CRUDActions:   []string{"pubkey", "twofa", "parentsigned"},
-                       Merges:        []string{"pubkey", "twofa", "basesigned", "commitssigned"},
-                       Wiki:          []string{"never"},
+                       SigningKey:        "default",
+                       SigningName:       "",
+                       SigningEmail:      "",
+                       InitialCommit:     []string{"always"},
+                       CRUDActions:       []string{"pubkey", "twofa", "parentsigned"},
+                       Merges:            []string{"pubkey", "twofa", "basesigned", "commitssigned"},
+                       Wiki:              []string{"never"},
+                       DefaultTrustModel: "collaborator",
                },
        }
        RepoRootPath string
@@ -268,6 +271,13 @@ func newRepository() {
                log.Fatal("Failed to map Repository.PullRequest settings: %v", err)
        }
 
+       // Handle default trustmodel settings
+       Repository.Signing.DefaultTrustModel = strings.ToLower(strings.TrimSpace(Repository.Signing.DefaultTrustModel))
+       if Repository.Signing.DefaultTrustModel == "default" {
+               Repository.Signing.DefaultTrustModel = "collaborator"
+       }
+
+       // Handle preferred charset orders
        preferred := make([]string, 0, len(Repository.DetectedCharsetsOrder))
        for _, charset := range Repository.DetectedCharsetsOrder {
                canonicalCharset := strings.ToLower(strings.TrimSpace(charset))
index c57702b2821b6fcc2e80efcfd5e35de42ce4858a..c86b19dfd0865efda30ca105f33c4b7be861c7dc 100644 (file)
@@ -117,6 +117,9 @@ type CreateRepoOption struct {
        Readme string `json:"readme"`
        // DefaultBranch of the repository (used when initializes and in template)
        DefaultBranch string `json:"default_branch" binding:"GitRefName;MaxSize(100)"`
+       // TrustModel of the repository
+       // enum: default,collaborator,committer,collaboratorcommitter
+       TrustModel string `json:"trust_model"`
 }
 
 // EditRepoOption options when editing a repository's properties
index 376a63125299157ef61b0e0ebd3614575299f177..ea76d4cb6a7c50d49bb7ccd8b1729542eea8f1e1 100644 (file)
@@ -1464,6 +1464,19 @@ settings.transfer_desc = Transfer this repository to a user or to an organizatio
 settings.transfer_notices_1 = - You will lose access to the repository if you transfer it to an individual user.
 settings.transfer_notices_2 = - You will keep access to the repository if you transfer it to an organization that you (co-)own.
 settings.transfer_form_title = Enter the repository name as confirmation:
+settings.signing_settings = Signing Verification Settings
+settings.trust_model = Signature Trust Model
+settings.trust_model.default = Default Trust Model
+settings.trust_model.default.desc= Use the default repository trust model for this installation.
+settings.trust_model.collaborator = Collaborator
+settings.trust_model.collaborator.long = Collaborator: Trust signatures by collaborators
+settings.trust_model.collaborator.desc = Valid signatures by collaborators of this repository will be marked "trusted" - (whether they match the committer or not). Otherwise, valid signatures will be marked "untrusted" if the signature matches the committer and "unmatched" if not.
+settings.trust_model.committer = Committer
+settings.trust_model.committer.long = Committer: Trust signatures that match committers (This matches GitHub and will force Gitea signed commits to have Gitea as the committer)
+settings.trust_model.committer.desc = Valid signatures will only be marked "trusted" if they match the committer, otherwise they will be marked "unmatched". This will force Gitea to be the committer on signed commits with the actual committer marked as Co-Authored-By: and Co-Committed-By: trailer in the commit. The default Gitea key must match a User in the database.
+settings.trust_model.collaboratorcommitter = Collaborator+Committer
+settings.trust_model.collaboratorcommitter.long = Collaborator+Committer: Trust signatures by collaborators which match the committer
+settings.trust_model.collaboratorcommitter.desc = Valid signatures by collaborators of this repository will be marked "trusted" if they match the committer. Otherwise, valid signatures will be marked "untrusted" if the signature matches the committer and "unmatched" otherwise. This will force Gitea to be marked as the committer on signed commits with the actual committer marked as Co-Authored-By: and Co-Committed-By: trailer in the commit. The default Gitea key must match a User in the database,
 settings.wiki_delete = Delete Wiki Data
 settings.wiki_delete_desc = Deleting repository wiki data is permanent and cannot be undone.
 settings.wiki_delete_notices_1 = - This will permanently delete and disable the repository wiki for %s.
index 603187c16dbb40aceb6930de920260e66c0b5373..baf8110a19fc5f15dadbfbd708f4108936c6b950 100644 (file)
@@ -244,6 +244,7 @@ func CreateUserRepo(ctx *context.APIContext, owner *models.User, opt api.CreateR
                IsPrivate:     opt.Private,
                AutoInit:      opt.AutoInit,
                DefaultBranch: opt.DefaultBranch,
+               TrustModel:    models.ToTrustModel(opt.TrustModel),
        })
        if err != nil {
                if models.IsErrRepoAlreadyExist(err) {
index 7c4f2cea9b8eb53d9c64fc771917a3d4724a12bf..be46ddbeb93d76a4ba5c0ac98c18d592bac84d6a 100644 (file)
@@ -1259,7 +1259,7 @@ func ViewIssue(ctx *context.Context) {
                }
                ctx.Data["WillSign"] = false
                if ctx.User != nil {
-                       sign, key, err := pull.SignMerge(ctx.User, pull.BaseRepo.RepoPath(), pull.BaseBranch, pull.GetGitRefName())
+                       sign, key, _, err := pull.SignMerge(ctx.User, pull.BaseRepo.RepoPath(), pull.BaseBranch, pull.GetGitRefName())
                        ctx.Data["WillSign"] = sign
                        ctx.Data["SigningKey"] = key
                        if err != nil {
index d12640dd6dc74a72d50e21e2d9f10d342073e1de..4a088ff9cd1111bdd913ce25b19ec5cfdd4b3893 100644 (file)
@@ -238,6 +238,7 @@ func CreatePost(ctx *context.Context, form auth.CreateRepoForm) {
                        IsPrivate:     form.Private || setting.Repository.ForcePrivate,
                        DefaultBranch: form.DefaultBranch,
                        AutoInit:      form.AutoInit,
+                       TrustModel:    models.ToTrustModel(form.TrustModel),
                })
                if err == nil {
                        log.Trace("Repository created [%d]: %s/%s", repo.ID, ctxUser.Name, repo.Name)
index 8d07bf09a4f3ab69c6fe63ed04b1271e4288fc24..d2c20fb03a6bf36bcfad6edd5ce7811431e85d10 100644 (file)
@@ -51,6 +51,11 @@ func Settings(ctx *context.Context) {
        ctx.Data["Title"] = ctx.Tr("repo.settings")
        ctx.Data["PageIsSettingsOptions"] = true
        ctx.Data["ForcePrivate"] = setting.Repository.ForcePrivate
+
+       signing, _ := models.SigningKey(ctx.Repo.Repository.RepoPath())
+       ctx.Data["SigningKeyAvailable"] = len(signing) > 0
+       ctx.Data["SigningSettings"] = setting.Repository.Signing
+
        ctx.HTML(200, tplSettingsOptions)
 }
 
@@ -318,6 +323,26 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) {
                ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
                ctx.Redirect(ctx.Repo.RepoLink + "/settings")
 
+       case "signing":
+               changed := false
+
+               trustModel := models.ToTrustModel(form.TrustModel)
+               if trustModel != repo.TrustModel {
+                       repo.TrustModel = trustModel
+                       changed = true
+               }
+
+               if changed {
+                       if err := models.UpdateRepository(repo, false); err != nil {
+                               ctx.ServerError("UpdateRepository", err)
+                               return
+                       }
+               }
+               log.Trace("Repository signing settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name)
+
+               ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
+               ctx.Redirect(ctx.Repo.RepoLink + "/settings")
+
        case "admin":
                if !ctx.User.IsAdmin {
                        ctx.Error(403)
index b430a9080e37defbf5f78e51cdfe73df63b9fd64..7a19649327511622bbff283ee93ccce3501854e4 100644 (file)
@@ -209,18 +209,23 @@ func rawMerge(pr *models.PullRequest, doer *models.User, mergeStyle models.Merge
        outbuf.Reset()
        errbuf.Reset()
 
+       sig := doer.NewGitSig()
+       committer := sig
+
        // Determine if we should sign
        signArg := ""
        if git.CheckGitVersionConstraint(">= 1.7.9") == nil {
-               sign, keyID, _ := pr.SignMerge(doer, tmpBasePath, "HEAD", trackingBranch)
+               sign, keyID, signer, _ := pr.SignMerge(doer, tmpBasePath, "HEAD", trackingBranch)
                if sign {
                        signArg = "-S" + keyID
+                       if pr.BaseRepo.GetTrustModel() == models.CommitterTrustModel || pr.BaseRepo.GetTrustModel() == models.CollaboratorCommitterTrustModel {
+                               committer = signer
+                       }
                } else if git.CheckGitVersionConstraint(">= 2.0.0") == nil {
                        signArg = "--no-gpg-sign"
                }
        }
 
-       sig := doer.NewGitSig()
        commitTimeStr := time.Now().Format(time.RFC3339)
 
        // Because this may call hooks we should pass in the environment
@@ -228,8 +233,8 @@ func rawMerge(pr *models.PullRequest, doer *models.User, mergeStyle models.Merge
                "GIT_AUTHOR_NAME="+sig.Name,
                "GIT_AUTHOR_EMAIL="+sig.Email,
                "GIT_AUTHOR_DATE="+commitTimeStr,
-               "GIT_COMMITTER_NAME="+sig.Name,
-               "GIT_COMMITTER_EMAIL="+sig.Email,
+               "GIT_COMMITTER_NAME="+committer.Name,
+               "GIT_COMMITTER_EMAIL="+committer.Email,
                "GIT_COMMITTER_DATE="+commitTimeStr,
        )
 
@@ -346,6 +351,10 @@ func rawMerge(pr *models.PullRequest, doer *models.User, mergeStyle models.Merge
                                return "", fmt.Errorf("git commit [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String())
                        }
                } else {
+                       if committer != sig {
+                               // add trailer
+                               message += fmt.Sprintf("\nCo-Authored-By: %s\nCo-Committed-By: %s\n", sig.String(), sig.String())
+                       }
                        if err := git.NewCommand("commit", signArg, fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email), "-m", message).RunInDirTimeoutEnvPipeline(env, -1, tmpBasePath, &outbuf, &errbuf); err != nil {
                                log.Error("git commit [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String())
                                return "", fmt.Errorf("git commit [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String())
@@ -526,7 +535,7 @@ func IsSignedIfRequired(pr *models.PullRequest, doer *models.User) (bool, error)
                return true, nil
        }
 
-       sign, _, err := pr.SignMerge(doer, pr.BaseRepo.RepoPath(), pr.BaseBranch, pr.GetGitRefName())
+       sign, _, _, err := pr.SignMerge(doer, pr.BaseRepo.RepoPath(), pr.BaseBranch, pr.GetGitRefName())
 
        return sign, err
 }
index 3616823c5d6dc7f73a59e31460e164c6ad8ba6ab..fab02bae0c4dc4144fe313767da7c9a3576ef345 100644 (file)
@@ -185,16 +185,22 @@ func updateWikiPage(doer *models.User, repo *models.Repository, oldWikiName, new
                Message: message,
        }
 
-       sign, signingKey, _ := repo.SignWikiCommit(doer)
+       committer := doer.NewGitSig()
+
+       sign, signingKey, signer, _ := repo.SignWikiCommit(doer)
        if sign {
                commitTreeOpts.KeyID = signingKey
+               if repo.GetTrustModel() == models.CommitterTrustModel || repo.GetTrustModel() == models.CollaboratorCommitterTrustModel {
+                       committer = signer
+               }
        } else {
                commitTreeOpts.NoGPGSign = true
        }
        if hasMasterBranch {
                commitTreeOpts.Parents = []string{"HEAD"}
        }
-       commitHash, err := gitRepo.CommitTree(doer.NewGitSig(), tree, commitTreeOpts)
+
+       commitHash, err := gitRepo.CommitTree(doer.NewGitSig(), committer, tree, commitTreeOpts)
        if err != nil {
                log.Error("%v", err)
                return err
@@ -302,14 +308,19 @@ func DeleteWikiPage(doer *models.User, repo *models.Repository, wikiName string)
                Parents: []string{"HEAD"},
        }
 
-       sign, signingKey, _ := repo.SignWikiCommit(doer)
+       committer := doer.NewGitSig()
+
+       sign, signingKey, signer, _ := repo.SignWikiCommit(doer)
        if sign {
                commitTreeOpts.KeyID = signingKey
+               if repo.GetTrustModel() == models.CommitterTrustModel || repo.GetTrustModel() == models.CollaboratorCommitterTrustModel {
+                       committer = signer
+               }
        } else {
                commitTreeOpts.NoGPGSign = true
        }
 
-       commitHash, err := gitRepo.CommitTree(doer.NewGitSig(), tree, commitTreeOpts)
+       commitHash, err := gitRepo.CommitTree(doer.NewGitSig(), committer, tree, commitTreeOpts)
        if err != nil {
                return err
        }
index c4b25c73d83153077dfd73a339b3dfac503a5988..d5c540724b5153f7effa83996a3b488c69d283d1 100644 (file)
                                                        <label for="default_branch">{{.i18n.Tr "repo.default_branch"}}</label>
                                                        <input id="default_branch" name="default_branch" value="{{.default_branch}}" placeholder="{{.default_branch}}">
                                                </div>
+                                               <div class="inline field">
+                                                       <label>{{.i18n.Tr "repo.settings.trust_model"}}</label>
+                                                       <div class="ui selection owner dropdown">
+                                                       <input type="hidden" id="trust_model" name="trust_model" value="default" required>
+                                                       <div class="default text">{{.i18n.Tr "repo.settings.trust_model"}}</div>
+                                                       <i class="dropdown icon"></i>
+                                                       <div class="menu">
+                                                               <div class="item" data-value="default">{{.i18n.Tr "repo.settings.trust_model.default"}}</div>
+                                                               <div class="item" data-value="collaborator">{{.i18n.Tr "repo.settings.trust_model.collaborator"}}</div>
+                                                               <div class="item" data-value="committer">{{.i18n.Tr "repo.settings.trust_model.committer"}}</div>
+                                                               <div class="item" data-value="collaboratorcommitter">{{.i18n.Tr "repo.settings.trust_model.collaboratorcommitter"}}</div>
+                                                       </div>
+                                               </div>
                                        </div>
 
                                        <br/>
index 8a490ac6453284c4121004cecb5a8ef1a5bb966e..a8e050c58347c6e6d66f00a7756b0eace1281863 100644 (file)
                        </form>
                </div>
 
+               <h4 class="ui top attached header">
+                       {{.i18n.Tr "repo.settings.signing_settings"}}
+               </h4>
+               <div class="ui attached segment">
+                       <form class="ui form" method="post">
+                               {{.CsrfTokenHtml}}
+                               <input type="hidden" name="action" value="signing">
+                               <div class="field">
+                                       <label>{{.i18n.Tr "repo.settings.trust_model"}}</label>
+                                       <div class="field">
+                                               <div class="ui radio checkbox">
+                                                       <input type="radio" id="trust_model_default" name="trust_model" {{if eq .Repository.TrustModel.String "default"}}checked="checked"{{end}} value="default">
+                                                       <label for="trust_model_default">{{.i18n.Tr "repo.settings.trust_model.default"}}</label>
+                                                       <p class="help">{{.i18n.Tr "repo.settings.trust_model.default.desc"}}</p>
+                                               </div>
+                                       </div>
+                                       <div class="field">
+                                               <div class="ui radio checkbox">
+                                                       <input type="radio" id="trust_model_collaborator" name="trust_model" {{if eq .Repository.TrustModel.String "collaborator"}}checked="checked"{{end}} value="collaborator">
+                                                       <label for="trust_model_collaborator">{{.i18n.Tr "repo.settings.trust_model.collaborator.long"}}</label>
+                                                       <p class="help">{{.i18n.Tr "repo.settings.trust_model.collaborator.desc"}}</p>
+                                               </div>
+                                       </div>
+                                       <div class="field">
+                                               <div class="ui radio checkbox">
+                                                       <input type="radio" name="trust_model" id="trust_model_committer" {{if eq .Repository.TrustModel.String "committer"}}checked="checked"{{end}} value="committer">
+                                                       <label for="trust_model_committer">{{.i18n.Tr "repo.settings.trust_model.committer.long"}}</label>
+                                                       <p class="help">{{.i18n.Tr "repo.settings.trust_model.committer.desc"}}</p>
+                                               </div>
+                                       </div>
+                                       <div class="field">
+                                               <div class="ui radio checkbox">
+                                                       <input type="radio" name="trust_model" id="trust_model_collaboratorcommitter" {{if eq .Repository.TrustModel.String "collaboratorcommitter"}}checked="checked"{{end}} value="collaboratorcommitter">
+                                                       <label for="trust_model_collaboratorcommitter">{{.i18n.Tr "repo.settings.trust_model.collaboratorcommitter.long"}}</label>
+                                                       <p class="help">{{.i18n.Tr "repo.settings.trust_model.collaboratorcommitter.desc"}}</p>
+                                               </div>
+                                       </div>
+                               </div>
+
+                               <div class="ui divider"></div>
+                               <div class="field">
+                                       <button class="ui green button">{{$.i18n.Tr "repo.settings.update_settings"}}</button>
+                               </div>
+                       </form>
+               </div>
+
                {{if .IsAdmin}}
                <h4 class="ui top attached header">
                        {{.i18n.Tr "repo.settings.admin_settings"}}
index 4b78b40dd29b659ec3524e644f7e155e109b9139..b687b4c57f376baaed5ddbab9c96ee3475fb7be8 100644 (file)
           "description": "Readme of the repository to create",
           "type": "string",
           "x-go-name": "Readme"
+        },
+        "trust_model": {
+          "description": "TrustModel of the repository",
+          "type": "string",
+          "enum": [
+            "default",
+            "collaborator",
+            "committer",
+            "collaboratorcommitter"
+          ],
+          "x-go-name": "TrustModel"
         }
       },
       "x-go-package": "code.gitea.io/gitea/modules/structs"