mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-08-29 22:06:06 +02:00
Add configurable Trust Models (#11712)
* 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>
This commit is contained in:
parent
89c94e2f8e
commit
4979f15c3f
29 changed files with 441 additions and 139 deletions
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
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"
|
||||
}
|
||||
}
|
||||
}
|
||||
func CalculateTrustStatus(verification *CommitVerification, repository *Repository, keyMap *map[string]bool) (err error) {
|
||||
if !verification.Verified {
|
||||
return
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
14
models/migrations/v152.go
Normal file
14
models/migrations/v152.go
Normal file
|
@ -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))
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -167,6 +167,19 @@
|
|||
<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/>
|
||||
|
|
|
@ -340,6 +340,52 @@
|
|||
</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"}}
|
||||
|
|
|
@ -11937,6 +11937,17 @@
|
|||
"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"
|
||||
|
|
Loading…
Reference in a new issue