gitea源码

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