diff options
author | zeripath <art27@cantab.net> | 2019-02-03 23:56:53 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-02-03 23:56:53 +0000 |
commit | 01c10a951b9db0b9f020ef657ca71eb5e5882a84 (patch) | |
tree | 6cb29db94edda9d90e9a509028bada2a61ade928 /integrations | |
parent | 634cbaad2b8d091a2a3065de81e0b2b76daa5ef7 (diff) | |
download | gitea-01c10a951b9db0b9f020ef657ca71eb5e5882a84.tar.gz gitea-01c10a951b9db0b9f020ef657ca71eb5e5882a84.zip |
Fix ssh deploy and user key constraints (#1357) (#5939)
1. A key can either be an ssh user key or a deploy key. It cannot be both.
2. If a key is a user key - it can only be associated with one user.
3. If a key is a deploy key - it can be used in multiple repositories and the permissions it has on those repositories can be different.
4. If a repository is deleted, its deploy keys must be deleted too.
We currently don't enforce any of this and multiple repositories access with different permissions doesn't work at all. This PR enforces the following constraints:
- [x] You should not be able to add the same user key as another user
- [x] You should not be able to add a ssh user key which is being used as a deploy key
- [x] You should not be able to add a ssh deploy key which is being used as a user key
- [x] If you add an ssh deploy key to another repository you should be able to use it in different modes without losing the ability to use it in the other mode.
- [x] If you delete a repository you must delete all its deploy keys.
Fix #1357
Diffstat (limited to 'integrations')
-rw-r--r-- | integrations/api_helper_for_declarative_test.go | 152 | ||||
-rw-r--r-- | integrations/deploy_key_push_test.go | 160 | ||||
-rw-r--r-- | integrations/git_helper_for_declarative_test.go | 127 | ||||
-rw-r--r-- | integrations/git_test.go | 206 | ||||
-rw-r--r-- | integrations/ssh_key_test.go | 217 |
5 files changed, 563 insertions, 299 deletions
diff --git a/integrations/api_helper_for_declarative_test.go b/integrations/api_helper_for_declarative_test.go new file mode 100644 index 0000000000..32a4ce8047 --- /dev/null +++ b/integrations/api_helper_for_declarative_test.go @@ -0,0 +1,152 @@ +// Copyright 2019 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 integrations + +import ( + "fmt" + "io/ioutil" + "net/http" + "testing" + + api "code.gitea.io/sdk/gitea" + "github.com/stretchr/testify/assert" +) + +type APITestContext struct { + Reponame string + Session *TestSession + Token string + Username string + ExpectedCode int +} + +func NewAPITestContext(t *testing.T, username, reponame string) APITestContext { + session := loginUser(t, username) + token := getTokenForLoggedInUser(t, session) + return APITestContext{ + Session: session, + Token: token, + Username: username, + Reponame: reponame, + } +} + +func (ctx APITestContext) GitPath() string { + return fmt.Sprintf("%s/%s.git", ctx.Username, ctx.Reponame) +} + +func doAPICreateRepository(ctx APITestContext, empty bool, callback ...func(*testing.T, api.Repository)) func(*testing.T) { + return func(t *testing.T) { + createRepoOption := &api.CreateRepoOption{ + AutoInit: !empty, + Description: "Temporary repo", + Name: ctx.Reponame, + Private: true, + Gitignores: "", + License: "WTFPL", + Readme: "Default", + } + req := NewRequestWithJSON(t, "POST", "/api/v1/user/repos?token="+ctx.Token, createRepoOption) + if ctx.ExpectedCode != 0 { + ctx.Session.MakeRequest(t, req, ctx.ExpectedCode) + return + } + resp := ctx.Session.MakeRequest(t, req, http.StatusCreated) + + var repository api.Repository + DecodeJSON(t, resp, &repository) + if len(callback) > 0 { + callback[0](t, repository) + } + } +} + +func doAPIGetRepository(ctx APITestContext, callback ...func(*testing.T, api.Repository)) func(*testing.T) { + return func(t *testing.T) { + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", ctx.Username, ctx.Reponame, ctx.Token) + + req := NewRequest(t, "GET", urlStr) + if ctx.ExpectedCode != 0 { + ctx.Session.MakeRequest(t, req, ctx.ExpectedCode) + return + } + resp := ctx.Session.MakeRequest(t, req, http.StatusOK) + + var repository api.Repository + DecodeJSON(t, resp, &repository) + if len(callback) > 0 { + callback[0](t, repository) + } + } +} + +func doAPIDeleteRepository(ctx APITestContext) func(*testing.T) { + return func(t *testing.T) { + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", ctx.Username, ctx.Reponame, ctx.Token) + + req := NewRequest(t, "DELETE", urlStr) + if ctx.ExpectedCode != 0 { + ctx.Session.MakeRequest(t, req, ctx.ExpectedCode) + return + } + ctx.Session.MakeRequest(t, req, http.StatusNoContent) + } +} + +func doAPICreateUserKey(ctx APITestContext, keyname, keyFile string, callback ...func(*testing.T, api.PublicKey)) func(*testing.T) { + return func(t *testing.T) { + urlStr := fmt.Sprintf("/api/v1/user/keys?token=%s", ctx.Token) + + dataPubKey, err := ioutil.ReadFile(keyFile + ".pub") + assert.NoError(t, err) + req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateKeyOption{ + Title: keyname, + Key: string(dataPubKey), + }) + if ctx.ExpectedCode != 0 { + ctx.Session.MakeRequest(t, req, ctx.ExpectedCode) + return + } + resp := ctx.Session.MakeRequest(t, req, http.StatusCreated) + var publicKey api.PublicKey + DecodeJSON(t, resp, &publicKey) + if len(callback) > 0 { + callback[0](t, publicKey) + } + } +} + +func doAPIDeleteUserKey(ctx APITestContext, keyID int64) func(*testing.T) { + return func(t *testing.T) { + urlStr := fmt.Sprintf("/api/v1/user/keys/%d?token=%s", keyID, ctx.Token) + + req := NewRequest(t, "DELETE", urlStr) + if ctx.ExpectedCode != 0 { + ctx.Session.MakeRequest(t, req, ctx.ExpectedCode) + return + } + ctx.Session.MakeRequest(t, req, http.StatusNoContent) + } +} + +func doAPICreateDeployKey(ctx APITestContext, keyname, keyFile string, readOnly bool) func(*testing.T) { + return func(t *testing.T) { + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/keys?token=%s", ctx.Username, ctx.Reponame, ctx.Token) + + dataPubKey, err := ioutil.ReadFile(keyFile + ".pub") + assert.NoError(t, err) + req := NewRequestWithJSON(t, "POST", urlStr, api.CreateKeyOption{ + Title: keyname, + Key: string(dataPubKey), + ReadOnly: readOnly, + }) + + if ctx.ExpectedCode != 0 { + ctx.Session.MakeRequest(t, req, ctx.ExpectedCode) + return + } + ctx.Session.MakeRequest(t, req, http.StatusCreated) + } +} diff --git a/integrations/deploy_key_push_test.go b/integrations/deploy_key_push_test.go deleted file mode 100644 index 8b3d665629..0000000000 --- a/integrations/deploy_key_push_test.go +++ /dev/null @@ -1,160 +0,0 @@ -// Copyright 2019 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 integrations - -import ( - "fmt" - "io/ioutil" - "log" - "net/http" - "net/url" - "os" - "os/exec" - "path/filepath" - "testing" - "time" - - "code.gitea.io/git" - - "code.gitea.io/gitea/modules/setting" - api "code.gitea.io/sdk/gitea" - "github.com/stretchr/testify/assert" -) - -func createEmptyRepository(username, reponame string) func(*testing.T) { - return func(t *testing.T) { - session := loginUser(t, username) - token := getTokenForLoggedInUser(t, session) - req := NewRequestWithJSON(t, "POST", "/api/v1/user/repos?token="+token, &api.CreateRepoOption{ - AutoInit: false, - Description: "Temporary empty repo", - Name: reponame, - Private: false, - }) - session.MakeRequest(t, req, http.StatusCreated) - } -} - -func createDeployKey(username, reponame, keyname, keyFile string, readOnly bool) func(*testing.T) { - return func(t *testing.T) { - session := loginUser(t, username) - token := getTokenForLoggedInUser(t, session) - urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/keys?token=%s", username, reponame, token) - - dataPubKey, err := ioutil.ReadFile(keyFile + ".pub") - assert.NoError(t, err) - req := NewRequestWithJSON(t, "POST", urlStr, api.CreateKeyOption{ - Title: keyname, - Key: string(dataPubKey), - ReadOnly: readOnly, - }) - session.MakeRequest(t, req, http.StatusCreated) - } -} - -func initTestRepository(dstPath string) func(*testing.T) { - return func(t *testing.T) { - // Init repository in dstPath - assert.NoError(t, git.InitRepository(dstPath, false)) - assert.NoError(t, ioutil.WriteFile(filepath.Join(dstPath, "README.md"), []byte(fmt.Sprintf("# Testing Repository\n\nOriginally created in: %s", dstPath)), 0644)) - assert.NoError(t, git.AddChanges(dstPath, true)) - signature := git.Signature{ - Email: "test@example.com", - Name: "test", - When: time.Now(), - } - assert.NoError(t, git.CommitChanges(dstPath, git.CommitChangesOptions{ - Committer: &signature, - Author: &signature, - Message: "Initial Commit", - })) - } -} - -func pushTestRepository(dstPath, username, reponame string, u url.URL, keyFile string) func(*testing.T) { - return func(t *testing.T) { - //Setup remote link - u.Scheme = "ssh" - u.User = url.User("git") - u.Host = fmt.Sprintf("%s:%d", setting.SSH.ListenHost, setting.SSH.ListenPort) - - //Setup ssh wrapper - os.Setenv("GIT_SSH_COMMAND", - "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i "+ - filepath.Join(setting.AppWorkPath, keyFile)) - os.Setenv("GIT_SSH_VARIANT", "ssh") - - log.Printf("Adding remote: %s\n", u.String()) - _, err := git.NewCommand("remote", "add", "origin", u.String()).RunInDir(dstPath) - assert.NoError(t, err) - - log.Printf("Pushing to: %s\n", u.String()) - _, err = git.NewCommand("push", "-u", "origin", "master").RunInDir(dstPath) - assert.NoError(t, err) - } -} - -func checkRepositoryEmptyStatus(username, reponame string, isEmpty bool) func(*testing.T) { - return func(t *testing.T) { - session := loginUser(t, username) - token := getTokenForLoggedInUser(t, session) - urlStr := fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", username, reponame, token) - - req := NewRequest(t, "GET", urlStr) - resp := session.MakeRequest(t, req, http.StatusOK) - - var repository api.Repository - DecodeJSON(t, resp, &repository) - - assert.Equal(t, isEmpty, repository.Empty) - } -} - -func deleteRepository(username, reponame string) func(*testing.T) { - return func(t *testing.T) { - session := loginUser(t, username) - token := getTokenForLoggedInUser(t, session) - urlStr := fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", username, reponame, token) - - req := NewRequest(t, "DELETE", urlStr) - session.MakeRequest(t, req, http.StatusNoContent) - } -} - -func TestPushDeployKeyOnEmptyRepo(t *testing.T) { - onGiteaRun(t, testPushDeployKeyOnEmptyRepo) -} - -func testPushDeployKeyOnEmptyRepo(t *testing.T, u *url.URL) { - reponame := "deploy-key-empty-repo-1" - username := "user2" - u.Path = fmt.Sprintf("%s/%s.git", username, reponame) - keyname := fmt.Sprintf("%s-push", reponame) - - t.Run("CreateEmptyRepository", createEmptyRepository(username, reponame)) - t.Run("CheckIsEmpty", checkRepositoryEmptyStatus(username, reponame, true)) - - //Setup the push deploy key file - keyFile := filepath.Join(setting.AppDataPath, keyname) - err := exec.Command("ssh-keygen", "-f", keyFile, "-t", "rsa", "-N", "").Run() - assert.NoError(t, err) - defer os.RemoveAll(keyFile) - defer os.RemoveAll(keyFile + ".pub") - - t.Run("CreatePushDeployKey", createDeployKey(username, reponame, keyname, keyFile, false)) - - // Setup the testing repository - dstPath, err := ioutil.TempDir("", "repo-tmp-deploy-key-empty-repo-1") - assert.NoError(t, err) - defer os.RemoveAll(dstPath) - - t.Run("InitTestRepository", initTestRepository(dstPath)) - t.Run("SSHPushTestRepository", pushTestRepository(dstPath, username, reponame, *u, keyFile)) - - log.Println("Done Push") - t.Run("CheckIsNotEmpty", checkRepositoryEmptyStatus(username, reponame, false)) - - t.Run("DeleteRepository", deleteRepository(username, reponame)) -} diff --git a/integrations/git_helper_for_declarative_test.go b/integrations/git_helper_for_declarative_test.go new file mode 100644 index 0000000000..572abe95a2 --- /dev/null +++ b/integrations/git_helper_for_declarative_test.go @@ -0,0 +1,127 @@ +// Copyright 2019 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 integrations + +import ( + "context" + "fmt" + "io/ioutil" + "net" + "net/http" + "net/url" + "os" + "os/exec" + "path/filepath" + "testing" + "time" + + "code.gitea.io/git" + "code.gitea.io/gitea/modules/setting" + "github.com/Unknwon/com" + "github.com/stretchr/testify/assert" +) + +func withKeyFile(t *testing.T, keyname string, callback func(string)) { + keyFile := filepath.Join(setting.AppDataPath, keyname) + err := exec.Command("ssh-keygen", "-f", keyFile, "-t", "rsa", "-N", "").Run() + assert.NoError(t, err) + + //Setup ssh wrapper + os.Setenv("GIT_SSH_COMMAND", + "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i "+ + filepath.Join(setting.AppWorkPath, keyFile)) + os.Setenv("GIT_SSH_VARIANT", "ssh") + + callback(keyFile) + + defer os.RemoveAll(keyFile) + defer os.RemoveAll(keyFile + ".pub") +} + +func createSSHUrl(gitPath string, u *url.URL) *url.URL { + u2 := *u + u2.Scheme = "ssh" + u2.User = url.User("git") + u2.Host = fmt.Sprintf("%s:%d", setting.SSH.ListenHost, setting.SSH.ListenPort) + u2.Path = gitPath + return &u2 +} + +func onGiteaRun(t *testing.T, callback func(*testing.T, *url.URL)) { + prepareTestEnv(t) + s := http.Server{ + Handler: mac, + } + + u, err := url.Parse(setting.AppURL) + assert.NoError(t, err) + listener, err := net.Listen("tcp", u.Host) + assert.NoError(t, err) + + defer func() { + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) + s.Shutdown(ctx) + cancel() + }() + + go s.Serve(listener) + //Started by config go ssh.Listen(setting.SSH.ListenHost, setting.SSH.ListenPort, setting.SSH.ServerCiphers, setting.SSH.ServerKeyExchanges, setting.SSH.ServerMACs) + + callback(t, u) +} + +func doGitClone(dstLocalPath string, u *url.URL) func(*testing.T) { + return func(t *testing.T) { + assert.NoError(t, git.Clone(u.String(), dstLocalPath, git.CloneRepoOptions{})) + assert.True(t, com.IsExist(filepath.Join(dstLocalPath, "README.md"))) + } +} + +func doGitCloneFail(dstLocalPath string, u *url.URL) func(*testing.T) { + return func(t *testing.T) { + assert.Error(t, git.Clone(u.String(), dstLocalPath, git.CloneRepoOptions{})) + assert.False(t, com.IsExist(filepath.Join(dstLocalPath, "README.md"))) + } +} + +func doGitInitTestRepository(dstPath string) func(*testing.T) { + return func(t *testing.T) { + // Init repository in dstPath + assert.NoError(t, git.InitRepository(dstPath, false)) + assert.NoError(t, ioutil.WriteFile(filepath.Join(dstPath, "README.md"), []byte(fmt.Sprintf("# Testing Repository\n\nOriginally created in: %s", dstPath)), 0644)) + assert.NoError(t, git.AddChanges(dstPath, true)) + signature := git.Signature{ + Email: "test@example.com", + Name: "test", + When: time.Now(), + } + assert.NoError(t, git.CommitChanges(dstPath, git.CommitChangesOptions{ + Committer: &signature, + Author: &signature, + Message: "Initial Commit", + })) + } +} + +func doGitAddRemote(dstPath, remoteName string, u *url.URL) func(*testing.T) { + return func(t *testing.T) { + _, err := git.NewCommand("remote", "add", remoteName, u.String()).RunInDir(dstPath) + assert.NoError(t, err) + } +} + +func doGitPushTestRepository(dstPath, remoteName, branch string) func(*testing.T) { + return func(t *testing.T) { + _, err := git.NewCommand("push", "-u", remoteName, branch).RunInDir(dstPath) + assert.NoError(t, err) + } +} + +func doGitPushTestRepositoryFail(dstPath, remoteName, branch string) func(*testing.T) { + return func(t *testing.T) { + _, err := git.NewCommand("push", "-u", remoteName, branch).RunInDir(dstPath) + assert.Error(t, err) + } +} diff --git a/integrations/git_test.go b/integrations/git_test.go index 96d39e0519..a372525864 100644 --- a/integrations/git_test.go +++ b/integrations/git_test.go @@ -5,25 +5,17 @@ package integrations import ( - "context" "crypto/rand" "fmt" "io/ioutil" - "net" - "net/http" "net/url" "os" - "os/exec" "path/filepath" "testing" "time" "code.gitea.io/git" - "code.gitea.io/gitea/models" - "code.gitea.io/gitea/modules/setting" - api "code.gitea.io/sdk/gitea" - "github.com/Unknwon/com" "github.com/stretchr/testify/assert" ) @@ -32,160 +24,86 @@ const ( bigSize = 128 * 1024 * 1024 //128Mo ) -func onGiteaRun(t *testing.T, callback func(*testing.T, *url.URL)) { - prepareTestEnv(t) - s := http.Server{ - Handler: mac, - } +func TestGit(t *testing.T) { + onGiteaRun(t, testGit) +} - u, err := url.Parse(setting.AppURL) - assert.NoError(t, err) - listener, err := net.Listen("tcp", u.Host) - assert.NoError(t, err) +func testGit(t *testing.T, u *url.URL) { + username := "user2" + baseAPITestContext := NewAPITestContext(t, username, "repo1") - defer func() { - ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) - s.Shutdown(ctx) - cancel() - }() + u.Path = baseAPITestContext.GitPath() - go s.Serve(listener) - //Started by config go ssh.Listen(setting.SSH.ListenHost, setting.SSH.ListenPort, setting.SSH.ServerCiphers, setting.SSH.ServerKeyExchanges, setting.SSH.ServerMACs) + t.Run("HTTP", func(t *testing.T) { + httpContext := baseAPITestContext + httpContext.Reponame = "repo-tmp-17" - callback(t, u) -} + dstPath, err := ioutil.TempDir("", httpContext.Reponame) + assert.NoError(t, err) + defer os.RemoveAll(dstPath) + t.Run("Standard", func(t *testing.T) { + ensureAnonymousClone(t, u) -func TestGit(t *testing.T) { - onGiteaRun(t, func(t *testing.T, u *url.URL) { - u.Path = "user2/repo1.git" + t.Run("CreateRepo", doAPICreateRepository(httpContext, false)) - t.Run("HTTP", func(t *testing.T) { - dstPath, err := ioutil.TempDir("", "repo-tmp-17") - assert.NoError(t, err) - defer os.RemoveAll(dstPath) - t.Run("Standard", func(t *testing.T) { - t.Run("CloneNoLogin", func(t *testing.T) { - dstLocalPath, err := ioutil.TempDir("", "repo1") - assert.NoError(t, err) - defer os.RemoveAll(dstLocalPath) - err = git.Clone(u.String(), dstLocalPath, git.CloneRepoOptions{}) - assert.NoError(t, err) - assert.True(t, com.IsExist(filepath.Join(dstLocalPath, "README.md"))) - }) + u.Path = httpContext.GitPath() + u.User = url.UserPassword(username, userPassword) - t.Run("CreateRepo", func(t *testing.T) { - session := loginUser(t, "user2") - token := getTokenForLoggedInUser(t, session) - req := NewRequestWithJSON(t, "POST", "/api/v1/user/repos?token="+token, &api.CreateRepoOption{ - AutoInit: true, - Description: "Temporary repo", - Name: "repo-tmp-17", - Private: false, - Gitignores: "", - License: "WTFPL", - Readme: "Default", - }) - session.MakeRequest(t, req, http.StatusCreated) - }) + t.Run("Clone", doGitClone(dstPath, u)) - u.Path = "user2/repo-tmp-17.git" - u.User = url.UserPassword("user2", userPassword) - t.Run("Clone", func(t *testing.T) { - err = git.Clone(u.String(), dstPath, git.CloneRepoOptions{}) - assert.NoError(t, err) - assert.True(t, com.IsExist(filepath.Join(dstPath, "README.md"))) + t.Run("PushCommit", func(t *testing.T) { + t.Run("Little", func(t *testing.T) { + commitAndPush(t, littleSize, dstPath) }) - - t.Run("PushCommit", func(t *testing.T) { - t.Run("Little", func(t *testing.T) { - commitAndPush(t, littleSize, dstPath) - }) - t.Run("Big", func(t *testing.T) { - commitAndPush(t, bigSize, dstPath) - }) + t.Run("Big", func(t *testing.T) { + commitAndPush(t, bigSize, dstPath) }) }) - t.Run("LFS", func(t *testing.T) { - t.Run("PushCommit", func(t *testing.T) { - //Setup git LFS - _, err = git.NewCommand("lfs").AddArguments("install").RunInDir(dstPath) - assert.NoError(t, err) - _, err = git.NewCommand("lfs").AddArguments("track", "data-file-*").RunInDir(dstPath) - assert.NoError(t, err) - err = git.AddChanges(dstPath, false, ".gitattributes") - assert.NoError(t, err) - - t.Run("Little", func(t *testing.T) { - commitAndPush(t, littleSize, dstPath) - }) - t.Run("Big", func(t *testing.T) { - commitAndPush(t, bigSize, dstPath) - }) + }) + t.Run("LFS", func(t *testing.T) { + t.Run("PushCommit", func(t *testing.T) { + //Setup git LFS + _, err = git.NewCommand("lfs").AddArguments("install").RunInDir(dstPath) + assert.NoError(t, err) + _, err = git.NewCommand("lfs").AddArguments("track", "data-file-*").RunInDir(dstPath) + assert.NoError(t, err) + err = git.AddChanges(dstPath, false, ".gitattributes") + assert.NoError(t, err) + + t.Run("Little", func(t *testing.T) { + commitAndPush(t, littleSize, dstPath) }) - t.Run("Locks", func(t *testing.T) { - lockTest(t, u.String(), dstPath) + t.Run("Big", func(t *testing.T) { + commitAndPush(t, bigSize, dstPath) }) }) - }) - t.Run("SSH", func(t *testing.T) { - //Setup remote link - u.Scheme = "ssh" - u.User = url.User("git") - u.Host = fmt.Sprintf("%s:%d", setting.SSH.ListenHost, setting.SSH.ListenPort) - u.Path = "user2/repo-tmp-18.git" - - //Setup key - keyFile := filepath.Join(setting.AppDataPath, "my-testing-key") - err := exec.Command("ssh-keygen", "-f", keyFile, "-t", "rsa", "-N", "").Run() - assert.NoError(t, err) - defer os.RemoveAll(keyFile) - defer os.RemoveAll(keyFile + ".pub") - - session := loginUser(t, "user1") - keyOwner := models.AssertExistsAndLoadBean(t, &models.User{Name: "user2"}).(*models.User) - token := getTokenForLoggedInUser(t, session) - urlStr := fmt.Sprintf("/api/v1/admin/users/%s/keys?token=%s", keyOwner.Name, token) - - dataPubKey, err := ioutil.ReadFile(keyFile + ".pub") - assert.NoError(t, err) - req := NewRequestWithValues(t, "POST", urlStr, map[string]string{ - "key": string(dataPubKey), - "title": "test-key", + t.Run("Locks", func(t *testing.T) { + lockTest(t, u.String(), dstPath) }) - session.MakeRequest(t, req, http.StatusCreated) + }) + }) + t.Run("SSH", func(t *testing.T) { + sshContext := baseAPITestContext + sshContext.Reponame = "repo-tmp-18" + keyname := "my-testing-key" + //Setup key the user ssh key + withKeyFile(t, keyname, func(keyFile string) { + t.Run("CreateUserKey", doAPICreateUserKey(sshContext, "test-key", keyFile)) - //Setup ssh wrapper - os.Setenv("GIT_SSH_COMMAND", - "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i "+ - filepath.Join(setting.AppWorkPath, keyFile)) - os.Setenv("GIT_SSH_VARIANT", "ssh") + //Setup remote link + sshURL := createSSHUrl(sshContext.GitPath(), u) //Setup clone folder - dstPath, err := ioutil.TempDir("", "repo-tmp-18") + dstPath, err := ioutil.TempDir("", sshContext.Reponame) assert.NoError(t, err) defer os.RemoveAll(dstPath) t.Run("Standard", func(t *testing.T) { - t.Run("CreateRepo", func(t *testing.T) { - session := loginUser(t, "user2") - token := getTokenForLoggedInUser(t, session) - req := NewRequestWithJSON(t, "POST", "/api/v1/user/repos?token="+token, &api.CreateRepoOption{ - AutoInit: true, - Description: "Temporary repo", - Name: "repo-tmp-18", - Private: false, - Gitignores: "", - License: "WTFPL", - Readme: "Default", - }) - session.MakeRequest(t, req, http.StatusCreated) - }) + t.Run("CreateRepo", doAPICreateRepository(sshContext, false)) + //TODO get url from api - t.Run("Clone", func(t *testing.T) { - _, err = git.NewCommand("clone").AddArguments(u.String(), dstPath).Run() - assert.NoError(t, err) - assert.True(t, com.IsExist(filepath.Join(dstPath, "README.md"))) - }) + t.Run("Clone", doGitClone(dstPath, sshURL)) + //time.Sleep(5 * time.Minute) t.Run("PushCommit", func(t *testing.T) { t.Run("Little", func(t *testing.T) { @@ -217,10 +135,20 @@ func TestGit(t *testing.T) { lockTest(t, u.String(), dstPath) }) }) + }) + }) } +func ensureAnonymousClone(t *testing.T, u *url.URL) { + dstLocalPath, err := ioutil.TempDir("", "repo1") + assert.NoError(t, err) + defer os.RemoveAll(dstLocalPath) + t.Run("CloneAnonymous", doGitClone(dstLocalPath, u)) + +} + func lockTest(t *testing.T, remote, repoPath string) { _, err := git.NewCommand("remote").AddArguments("set-url", "origin", remote).RunInDir(repoPath) //TODO add test ssh git-lfs-creds assert.NoError(t, err) diff --git a/integrations/ssh_key_test.go b/integrations/ssh_key_test.go new file mode 100644 index 0000000000..9ad43ae226 --- /dev/null +++ b/integrations/ssh_key_test.go @@ -0,0 +1,217 @@ +// Copyright 2019 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 integrations + +import ( + "fmt" + "io/ioutil" + "net/http" + "net/url" + "os" + "path/filepath" + "testing" + "time" + + "code.gitea.io/git" + api "code.gitea.io/sdk/gitea" + "github.com/stretchr/testify/assert" +) + +func doCheckRepositoryEmptyStatus(ctx APITestContext, isEmpty bool) func(*testing.T) { + return doAPIGetRepository(ctx, func(t *testing.T, repository api.Repository) { + assert.Equal(t, isEmpty, repository.Empty) + }) +} + +func doAddChangesToCheckout(dstPath, filename string) func(*testing.T) { + return func(t *testing.T) { + assert.NoError(t, ioutil.WriteFile(filepath.Join(dstPath, filename), []byte(fmt.Sprintf("# Testing Repository\n\nOriginally created in: %s at time: %v", dstPath, time.Now())), 0644)) + assert.NoError(t, git.AddChanges(dstPath, true)) + signature := git.Signature{ + Email: "test@example.com", + Name: "test", + When: time.Now(), + } + assert.NoError(t, git.CommitChanges(dstPath, git.CommitChangesOptions{ + Committer: &signature, + Author: &signature, + Message: "Initial Commit", + })) + } +} + +func TestPushDeployKeyOnEmptyRepo(t *testing.T) { + onGiteaRun(t, testPushDeployKeyOnEmptyRepo) +} + +func testPushDeployKeyOnEmptyRepo(t *testing.T, u *url.URL) { + // OK login + ctx := NewAPITestContext(t, "user2", "deploy-key-empty-repo-1") + keyname := fmt.Sprintf("%s-push", ctx.Reponame) + u.Path = ctx.GitPath() + + t.Run("CreateEmptyRepository", doAPICreateRepository(ctx, true)) + + t.Run("CheckIsEmpty", doCheckRepositoryEmptyStatus(ctx, true)) + + withKeyFile(t, keyname, func(keyFile string) { + t.Run("CreatePushDeployKey", doAPICreateDeployKey(ctx, keyname, keyFile, false)) + + // Setup the testing repository + dstPath, err := ioutil.TempDir("", "repo-tmp-deploy-key-empty-repo-1") + assert.NoError(t, err) + defer os.RemoveAll(dstPath) + + t.Run("InitTestRepository", doGitInitTestRepository(dstPath)) + + //Setup remote link + sshURL := createSSHUrl(ctx.GitPath(), u) + + t.Run("AddRemote", doGitAddRemote(dstPath, "origin", sshURL)) + + t.Run("SSHPushTestRepository", doGitPushTestRepository(dstPath, "origin", "master")) + + t.Run("CheckIsNotEmpty", doCheckRepositoryEmptyStatus(ctx, false)) + + t.Run("DeleteRepository", doAPIDeleteRepository(ctx)) + }) +} + +func TestKeyOnlyOneType(t *testing.T) { + onGiteaRun(t, testKeyOnlyOneType) +} + +func testKeyOnlyOneType(t *testing.T, u *url.URL) { + // Once a key is a user key we cannot use it as a deploy key + // If we delete it from the user we should be able to use it as a deploy key + reponame := "ssh-key-test-repo" + username := "user2" + u.Path = fmt.Sprintf("%s/%s.git", username, reponame) + keyname := fmt.Sprintf("%s-push", reponame) + + // OK login + ctx := NewAPITestContext(t, username, reponame) + + otherCtx := ctx + otherCtx.Reponame = "ssh-key-test-repo-2" + + failCtx := ctx + failCtx.ExpectedCode = http.StatusUnprocessableEntity + + t.Run("CreateRepository", doAPICreateRepository(ctx, false)) + t.Run("CreateOtherRepository", doAPICreateRepository(otherCtx, false)) + + withKeyFile(t, keyname, func(keyFile string) { + var userKeyPublicKeyID int64 + t.Run("KeyCanOnlyBeUser", func(t *testing.T) { + dstPath, err := ioutil.TempDir("", ctx.Reponame) + assert.NoError(t, err) + defer os.RemoveAll(dstPath) + + sshURL := createSSHUrl(ctx.GitPath(), u) + + t.Run("FailToClone", doGitCloneFail(dstPath, sshURL)) + + t.Run("CreateUserKey", doAPICreateUserKey(ctx, keyname, keyFile, func(t *testing.T, publicKey api.PublicKey) { + userKeyPublicKeyID = publicKey.ID + })) + + t.Run("FailToAddReadOnlyDeployKey", doAPICreateDeployKey(failCtx, keyname, keyFile, true)) + + t.Run("FailToAddDeployKey", doAPICreateDeployKey(failCtx, keyname, keyFile, false)) + + t.Run("Clone", doGitClone(dstPath, sshURL)) + + t.Run("AddChanges", doAddChangesToCheckout(dstPath, "CHANGES1.md")) + + t.Run("Push", doGitPushTestRepository(dstPath, "origin", "master")) + + t.Run("DeleteUserKey", doAPIDeleteUserKey(ctx, userKeyPublicKeyID)) + }) + + t.Run("KeyCanBeAnyDeployButNotUserAswell", func(t *testing.T) { + dstPath, err := ioutil.TempDir("", ctx.Reponame) + assert.NoError(t, err) + defer os.RemoveAll(dstPath) + + sshURL := createSSHUrl(ctx.GitPath(), u) + + t.Run("FailToClone", doGitCloneFail(dstPath, sshURL)) + + // Should now be able to add... + t.Run("AddReadOnlyDeployKey", doAPICreateDeployKey(ctx, keyname, keyFile, true)) + + t.Run("Clone", doGitClone(dstPath, sshURL)) + + t.Run("AddChanges", doAddChangesToCheckout(dstPath, "CHANGES2.md")) + + t.Run("FailToPush", doGitPushTestRepositoryFail(dstPath, "origin", "master")) + + otherSSHURL := createSSHUrl(otherCtx.GitPath(), u) + dstOtherPath, err := ioutil.TempDir("", otherCtx.Reponame) + assert.NoError(t, err) + defer os.RemoveAll(dstOtherPath) + + t.Run("AddWriterDeployKeyToOther", doAPICreateDeployKey(otherCtx, keyname, keyFile, false)) + + t.Run("CloneOther", doGitClone(dstOtherPath, otherSSHURL)) + + t.Run("AddChangesToOther", doAddChangesToCheckout(dstOtherPath, "CHANGES3.md")) + + t.Run("PushToOther", doGitPushTestRepository(dstOtherPath, "origin", "master")) + + t.Run("FailToCreateUserKey", doAPICreateUserKey(failCtx, keyname, keyFile)) + }) + + t.Run("DeleteRepositoryShouldReleaseKey", func(t *testing.T) { + otherSSHURL := createSSHUrl(otherCtx.GitPath(), u) + dstOtherPath, err := ioutil.TempDir("", otherCtx.Reponame) + assert.NoError(t, err) + defer os.RemoveAll(dstOtherPath) + + t.Run("DeleteRepository", doAPIDeleteRepository(ctx)) + + t.Run("FailToCreateUserKeyAsStillDeploy", doAPICreateUserKey(failCtx, keyname, keyFile)) + + t.Run("MakeSureCloneOtherStillWorks", doGitClone(dstOtherPath, otherSSHURL)) + + t.Run("AddChangesToOther", doAddChangesToCheckout(dstOtherPath, "CHANGES3.md")) + + t.Run("PushToOther", doGitPushTestRepository(dstOtherPath, "origin", "master")) + + t.Run("DeleteOtherRepository", doAPIDeleteRepository(otherCtx)) + + t.Run("RecreateRepository", doAPICreateRepository(ctx, false)) + + t.Run("CreateUserKey", doAPICreateUserKey(ctx, keyname, keyFile, func(t *testing.T, publicKey api.PublicKey) { + userKeyPublicKeyID = publicKey.ID + })) + + dstPath, err := ioutil.TempDir("", ctx.Reponame) + assert.NoError(t, err) + defer os.RemoveAll(dstPath) + + sshURL := createSSHUrl(ctx.GitPath(), u) + + t.Run("Clone", doGitClone(dstPath, sshURL)) + + t.Run("AddChanges", doAddChangesToCheckout(dstPath, "CHANGES1.md")) + + t.Run("Push", doGitPushTestRepository(dstPath, "origin", "master")) + }) + + t.Run("DeleteUserKeyShouldRemoveAbilityToClone", func(t *testing.T) { + dstPath, err := ioutil.TempDir("", ctx.Reponame) + assert.NoError(t, err) + defer os.RemoveAll(dstPath) + + sshURL := createSSHUrl(ctx.GitPath(), u) + + t.Run("DeleteUserKey", doAPIDeleteUserKey(ctx, userKeyPublicKeyID)) + + t.Run("FailToClone", doGitCloneFail(dstPath, sshURL)) + }) + }) +} |