code.gitea.io/gitea@v1.22.3/tests/integration/gpg_git_test.go (about)

     1  // Copyright 2019 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package integration
     5  
     6  import (
     7  	"encoding/base64"
     8  	"fmt"
     9  	"net/url"
    10  	"os"
    11  	"testing"
    12  
    13  	auth_model "code.gitea.io/gitea/models/auth"
    14  	"code.gitea.io/gitea/models/unittest"
    15  	user_model "code.gitea.io/gitea/models/user"
    16  	"code.gitea.io/gitea/modules/process"
    17  	"code.gitea.io/gitea/modules/setting"
    18  	api "code.gitea.io/gitea/modules/structs"
    19  	"code.gitea.io/gitea/modules/test"
    20  	"code.gitea.io/gitea/tests"
    21  
    22  	"github.com/stretchr/testify/assert"
    23  	"golang.org/x/crypto/openpgp"
    24  	"golang.org/x/crypto/openpgp/armor"
    25  )
    26  
    27  func TestGPGGit(t *testing.T) {
    28  	tmpDir := t.TempDir() // use a temp dir to avoid messing with the user's GPG keyring
    29  	err := os.Chmod(tmpDir, 0o700)
    30  	assert.NoError(t, err)
    31  
    32  	oldGNUPGHome := os.Getenv("GNUPGHOME")
    33  	err = os.Setenv("GNUPGHOME", tmpDir)
    34  	assert.NoError(t, err)
    35  	defer os.Setenv("GNUPGHOME", oldGNUPGHome)
    36  
    37  	// Need to create a root key
    38  	rootKeyPair, err := importTestingKey(tmpDir, "gitea", "gitea@fake.local")
    39  	if !assert.NoError(t, err, "importTestingKey") {
    40  		return
    41  	}
    42  
    43  	defer test.MockVariableValue(&setting.Repository.Signing.SigningKey, rootKeyPair.PrimaryKey.KeyIdShortString())()
    44  	defer test.MockVariableValue(&setting.Repository.Signing.SigningName, "gitea")()
    45  	defer test.MockVariableValue(&setting.Repository.Signing.SigningEmail, "gitea@fake.local")()
    46  	defer test.MockVariableValue(&setting.Repository.Signing.InitialCommit, []string{"never"})()
    47  	defer test.MockVariableValue(&setting.Repository.Signing.CRUDActions, []string{"never"})()
    48  
    49  	username := "user2"
    50  	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: username})
    51  	baseAPITestContext := NewAPITestContext(t, username, "repo1")
    52  
    53  	onGiteaRun(t, func(t *testing.T, u *url.URL) {
    54  		u.Path = baseAPITestContext.GitPath()
    55  
    56  		t.Run("Unsigned-Initial", func(t *testing.T) {
    57  			defer tests.PrintCurrentTest(t)()
    58  			testCtx := NewAPITestContext(t, username, "initial-unsigned", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
    59  			t.Run("CreateRepository", doAPICreateRepository(testCtx, false))
    60  			t.Run("CheckMasterBranchUnsigned", doAPIGetBranch(testCtx, "master", func(t *testing.T, branch api.Branch) {
    61  				assert.NotNil(t, branch.Commit)
    62  				assert.NotNil(t, branch.Commit.Verification)
    63  				assert.False(t, branch.Commit.Verification.Verified)
    64  				assert.Empty(t, branch.Commit.Verification.Signature)
    65  			}))
    66  			t.Run("CreateCRUDFile-Never", crudActionCreateFile(
    67  				t, testCtx, user, "master", "never", "unsigned-never.txt", func(t *testing.T, response api.FileResponse) {
    68  					assert.False(t, response.Verification.Verified)
    69  				}))
    70  			t.Run("CreateCRUDFile-Never", crudActionCreateFile(
    71  				t, testCtx, user, "never", "never2", "unsigned-never2.txt", func(t *testing.T, response api.FileResponse) {
    72  					assert.False(t, response.Verification.Verified)
    73  				}))
    74  		})
    75  
    76  		setting.Repository.Signing.CRUDActions = []string{"parentsigned"}
    77  		t.Run("Unsigned-Initial-CRUD-ParentSigned", func(t *testing.T) {
    78  			defer tests.PrintCurrentTest(t)()
    79  			testCtx := NewAPITestContext(t, username, "initial-unsigned", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
    80  			t.Run("CreateCRUDFile-ParentSigned", crudActionCreateFile(
    81  				t, testCtx, user, "master", "parentsigned", "signed-parent.txt", func(t *testing.T, response api.FileResponse) {
    82  					assert.False(t, response.Verification.Verified)
    83  				}))
    84  			t.Run("CreateCRUDFile-ParentSigned", crudActionCreateFile(
    85  				t, testCtx, user, "parentsigned", "parentsigned2", "signed-parent2.txt", func(t *testing.T, response api.FileResponse) {
    86  					assert.False(t, response.Verification.Verified)
    87  				}))
    88  		})
    89  
    90  		setting.Repository.Signing.CRUDActions = []string{"never"}
    91  		t.Run("Unsigned-Initial-CRUD-Never", func(t *testing.T) {
    92  			defer tests.PrintCurrentTest(t)()
    93  			testCtx := NewAPITestContext(t, username, "initial-unsigned", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
    94  			t.Run("CreateCRUDFile-Never", crudActionCreateFile(
    95  				t, testCtx, user, "parentsigned", "parentsigned-never", "unsigned-never2.txt", func(t *testing.T, response api.FileResponse) {
    96  					assert.False(t, response.Verification.Verified)
    97  				}))
    98  		})
    99  
   100  		setting.Repository.Signing.CRUDActions = []string{"always"}
   101  		t.Run("Unsigned-Initial-CRUD-Always", func(t *testing.T) {
   102  			defer tests.PrintCurrentTest(t)()
   103  			testCtx := NewAPITestContext(t, username, "initial-unsigned", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
   104  			t.Run("CreateCRUDFile-Always", crudActionCreateFile(
   105  				t, testCtx, user, "master", "always", "signed-always.txt", func(t *testing.T, response api.FileResponse) {
   106  					assert.NotNil(t, response.Verification)
   107  					if response.Verification == nil {
   108  						assert.FailNow(t, "no verification provided with response! %v", response)
   109  					}
   110  					assert.True(t, response.Verification.Verified)
   111  					if !response.Verification.Verified {
   112  						t.FailNow()
   113  					}
   114  					assert.Equal(t, "gitea@fake.local", response.Verification.Signer.Email)
   115  				}))
   116  			t.Run("CreateCRUDFile-ParentSigned-always", crudActionCreateFile(
   117  				t, testCtx, user, "parentsigned", "parentsigned-always", "signed-parent2.txt", func(t *testing.T, response api.FileResponse) {
   118  					assert.NotNil(t, response.Verification)
   119  					if response.Verification == nil {
   120  						assert.FailNow(t, "no verification provided with response! %v", response)
   121  					}
   122  					assert.True(t, response.Verification.Verified)
   123  					if !response.Verification.Verified {
   124  						t.FailNow()
   125  					}
   126  					assert.Equal(t, "gitea@fake.local", response.Verification.Signer.Email)
   127  				}))
   128  		})
   129  
   130  		setting.Repository.Signing.CRUDActions = []string{"parentsigned"}
   131  		t.Run("Unsigned-Initial-CRUD-ParentSigned", func(t *testing.T) {
   132  			defer tests.PrintCurrentTest(t)()
   133  			testCtx := NewAPITestContext(t, username, "initial-unsigned", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
   134  			t.Run("CreateCRUDFile-Always-ParentSigned", crudActionCreateFile(
   135  				t, testCtx, user, "always", "always-parentsigned", "signed-always-parentsigned.txt", func(t *testing.T, response api.FileResponse) {
   136  					assert.NotNil(t, response.Verification)
   137  					if response.Verification == nil {
   138  						assert.FailNow(t, "no verification provided with response! %v", response)
   139  					}
   140  					assert.True(t, response.Verification.Verified)
   141  					if !response.Verification.Verified {
   142  						t.FailNow()
   143  					}
   144  					assert.Equal(t, "gitea@fake.local", response.Verification.Signer.Email)
   145  				}))
   146  		})
   147  
   148  		setting.Repository.Signing.InitialCommit = []string{"always"}
   149  		t.Run("AlwaysSign-Initial", func(t *testing.T) {
   150  			defer tests.PrintCurrentTest(t)()
   151  			testCtx := NewAPITestContext(t, username, "initial-always", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
   152  			t.Run("CreateRepository", doAPICreateRepository(testCtx, false))
   153  			t.Run("CheckMasterBranchSigned", doAPIGetBranch(testCtx, "master", func(t *testing.T, branch api.Branch) {
   154  				assert.NotNil(t, branch.Commit)
   155  				if branch.Commit == nil {
   156  					assert.FailNow(t, "no commit provided with branch! %v", branch)
   157  				}
   158  				assert.NotNil(t, branch.Commit.Verification)
   159  				if branch.Commit.Verification == nil {
   160  					assert.FailNow(t, "no verification provided with branch commit! %v", branch.Commit)
   161  				}
   162  				assert.True(t, branch.Commit.Verification.Verified)
   163  				if !branch.Commit.Verification.Verified {
   164  					t.FailNow()
   165  				}
   166  				assert.Equal(t, "gitea@fake.local", branch.Commit.Verification.Signer.Email)
   167  			}))
   168  		})
   169  
   170  		setting.Repository.Signing.CRUDActions = []string{"never"}
   171  		t.Run("AlwaysSign-Initial-CRUD-Never", func(t *testing.T) {
   172  			defer tests.PrintCurrentTest(t)()
   173  			testCtx := NewAPITestContext(t, username, "initial-always-never", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
   174  			t.Run("CreateRepository", doAPICreateRepository(testCtx, false))
   175  			t.Run("CreateCRUDFile-Never", crudActionCreateFile(
   176  				t, testCtx, user, "master", "never", "unsigned-never.txt", func(t *testing.T, response api.FileResponse) {
   177  					assert.False(t, response.Verification.Verified)
   178  				}))
   179  		})
   180  
   181  		setting.Repository.Signing.CRUDActions = []string{"parentsigned"}
   182  		t.Run("AlwaysSign-Initial-CRUD-ParentSigned-On-Always", func(t *testing.T) {
   183  			defer tests.PrintCurrentTest(t)()
   184  			testCtx := NewAPITestContext(t, username, "initial-always-parent", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
   185  			t.Run("CreateRepository", doAPICreateRepository(testCtx, false))
   186  			t.Run("CreateCRUDFile-ParentSigned", crudActionCreateFile(
   187  				t, testCtx, user, "master", "parentsigned", "signed-parent.txt", func(t *testing.T, response api.FileResponse) {
   188  					assert.True(t, response.Verification.Verified)
   189  					if !response.Verification.Verified {
   190  						t.FailNow()
   191  						return
   192  					}
   193  					assert.Equal(t, "gitea@fake.local", response.Verification.Signer.Email)
   194  				}))
   195  		})
   196  
   197  		setting.Repository.Signing.CRUDActions = []string{"always"}
   198  		t.Run("AlwaysSign-Initial-CRUD-Always", func(t *testing.T) {
   199  			defer tests.PrintCurrentTest(t)()
   200  			testCtx := NewAPITestContext(t, username, "initial-always-always", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
   201  			t.Run("CreateRepository", doAPICreateRepository(testCtx, false))
   202  			t.Run("CreateCRUDFile-Always", crudActionCreateFile(
   203  				t, testCtx, user, "master", "always", "signed-always.txt", func(t *testing.T, response api.FileResponse) {
   204  					assert.True(t, response.Verification.Verified)
   205  					if !response.Verification.Verified {
   206  						t.FailNow()
   207  						return
   208  					}
   209  					assert.Equal(t, "gitea@fake.local", response.Verification.Signer.Email)
   210  				}))
   211  		})
   212  
   213  		setting.Repository.Signing.Merges = []string{"commitssigned"}
   214  		t.Run("UnsignedMerging", func(t *testing.T) {
   215  			defer tests.PrintCurrentTest(t)()
   216  			testCtx := NewAPITestContext(t, username, "initial-unsigned", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
   217  			t.Run("CreatePullRequest", func(t *testing.T) {
   218  				pr, err := doAPICreatePullRequest(testCtx, testCtx.Username, testCtx.Reponame, "master", "never2")(t)
   219  				assert.NoError(t, err)
   220  				t.Run("MergePR", doAPIMergePullRequest(testCtx, testCtx.Username, testCtx.Reponame, pr.Index))
   221  			})
   222  			t.Run("CheckMasterBranchUnsigned", doAPIGetBranch(testCtx, "master", func(t *testing.T, branch api.Branch) {
   223  				assert.NotNil(t, branch.Commit)
   224  				assert.NotNil(t, branch.Commit.Verification)
   225  				assert.False(t, branch.Commit.Verification.Verified)
   226  				assert.Empty(t, branch.Commit.Verification.Signature)
   227  			}))
   228  		})
   229  
   230  		setting.Repository.Signing.Merges = []string{"basesigned"}
   231  		t.Run("BaseSignedMerging", func(t *testing.T) {
   232  			defer tests.PrintCurrentTest(t)()
   233  			testCtx := NewAPITestContext(t, username, "initial-unsigned", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
   234  			t.Run("CreatePullRequest", func(t *testing.T) {
   235  				pr, err := doAPICreatePullRequest(testCtx, testCtx.Username, testCtx.Reponame, "master", "parentsigned2")(t)
   236  				assert.NoError(t, err)
   237  				t.Run("MergePR", doAPIMergePullRequest(testCtx, testCtx.Username, testCtx.Reponame, pr.Index))
   238  			})
   239  			t.Run("CheckMasterBranchUnsigned", doAPIGetBranch(testCtx, "master", func(t *testing.T, branch api.Branch) {
   240  				assert.NotNil(t, branch.Commit)
   241  				assert.NotNil(t, branch.Commit.Verification)
   242  				assert.False(t, branch.Commit.Verification.Verified)
   243  				assert.Empty(t, branch.Commit.Verification.Signature)
   244  			}))
   245  		})
   246  
   247  		setting.Repository.Signing.Merges = []string{"commitssigned"}
   248  		t.Run("CommitsSignedMerging", func(t *testing.T) {
   249  			defer tests.PrintCurrentTest(t)()
   250  			testCtx := NewAPITestContext(t, username, "initial-unsigned", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
   251  			t.Run("CreatePullRequest", func(t *testing.T) {
   252  				pr, err := doAPICreatePullRequest(testCtx, testCtx.Username, testCtx.Reponame, "master", "always-parentsigned")(t)
   253  				assert.NoError(t, err)
   254  				t.Run("MergePR", doAPIMergePullRequest(testCtx, testCtx.Username, testCtx.Reponame, pr.Index))
   255  			})
   256  			t.Run("CheckMasterBranchUnsigned", doAPIGetBranch(testCtx, "master", func(t *testing.T, branch api.Branch) {
   257  				assert.NotNil(t, branch.Commit)
   258  				assert.NotNil(t, branch.Commit.Verification)
   259  				assert.True(t, branch.Commit.Verification.Verified)
   260  			}))
   261  		})
   262  	})
   263  }
   264  
   265  func crudActionCreateFile(t *testing.T, ctx APITestContext, user *user_model.User, from, to, path string, callback ...func(*testing.T, api.FileResponse)) func(*testing.T) {
   266  	return doAPICreateFile(ctx, path, &api.CreateFileOptions{
   267  		FileOptions: api.FileOptions{
   268  			BranchName:    from,
   269  			NewBranchName: to,
   270  			Message:       fmt.Sprintf("from:%s to:%s path:%s", from, to, path),
   271  			Author: api.Identity{
   272  				Name:  user.FullName,
   273  				Email: user.Email,
   274  			},
   275  			Committer: api.Identity{
   276  				Name:  user.FullName,
   277  				Email: user.Email,
   278  			},
   279  		},
   280  		ContentBase64: base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("This is new text for %s", path))),
   281  	}, callback...)
   282  }
   283  
   284  func importTestingKey(tmpDir, name, email string) (*openpgp.Entity, error) {
   285  	if _, _, err := process.GetManager().Exec("gpg --import tests/integration/private-testing.key", "gpg", "--import", "tests/integration/private-testing.key"); err != nil {
   286  		return nil, err
   287  	}
   288  	keyringFile, err := os.Open("tests/integration/private-testing.key")
   289  	if err != nil {
   290  		return nil, err
   291  	}
   292  	defer keyringFile.Close()
   293  
   294  	block, err := armor.Decode(keyringFile)
   295  	if err != nil {
   296  		return nil, err
   297  	}
   298  
   299  	keyring, err := openpgp.ReadKeyRing(block.Body)
   300  	if err != nil {
   301  		return nil, fmt.Errorf("Keyring access failed: '%w'", err)
   302  	}
   303  
   304  	// There should only be one entity in this file.
   305  	return keyring[0], nil
   306  }