diff options
author | Lunny Xiao <xiaolunwen@gmail.com> | 2021-12-10 16:14:24 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-12-10 16:14:24 +0800 |
commit | 3ca5dc7e32b372d14ff80d96f14b8f6a805862f1 (patch) | |
tree | 50d193ed0dacf2888d57b193a9b0d36065aff205 /services/asymkey | |
parent | 0a9fcf63a49799ad3b0f146c54879161bac61e10 (diff) | |
download | gitea-3ca5dc7e32b372d14ff80d96f14b8f6a805862f1.tar.gz gitea-3ca5dc7e32b372d14ff80d96f14b8f6a805862f1.zip |
Move keys to models/asymkey (#17917)
* Move keys to models/keys
* Rename models/keys -> models/asymkey
* change the missed package name
* Fix package alias
* Fix test
* Fix docs
* Fix test
* Fix test
* merge
Diffstat (limited to 'services/asymkey')
-rw-r--r-- | services/asymkey/deploy_key.go | 30 | ||||
-rw-r--r-- | services/asymkey/main_test.go | 16 | ||||
-rw-r--r-- | services/asymkey/sign.go | 388 | ||||
-rw-r--r-- | services/asymkey/ssh_key.go | 49 | ||||
-rw-r--r-- | services/asymkey/ssh_key_test.go | 83 |
5 files changed, 566 insertions, 0 deletions
diff --git a/services/asymkey/deploy_key.go b/services/asymkey/deploy_key.go new file mode 100644 index 0000000000..aa0925ab13 --- /dev/null +++ b/services/asymkey/deploy_key.go @@ -0,0 +1,30 @@ +// Copyright 2021 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 asymkey + +import ( + "code.gitea.io/gitea/models" + asymkey_model "code.gitea.io/gitea/models/asymkey" + "code.gitea.io/gitea/models/db" + user_model "code.gitea.io/gitea/models/user" +) + +// DeleteDeployKey deletes deploy key from its repository authorized_keys file if needed. +func DeleteDeployKey(doer *user_model.User, id int64) error { + ctx, committer, err := db.TxContext() + if err != nil { + return err + } + defer committer.Close() + + if err := models.DeleteDeployKey(ctx, doer, id); err != nil { + return err + } + if err := committer.Commit(); err != nil { + return err + } + + return asymkey_model.RewriteAllPublicKeys() +} diff --git a/services/asymkey/main_test.go b/services/asymkey/main_test.go new file mode 100644 index 0000000000..a891a10cf6 --- /dev/null +++ b/services/asymkey/main_test.go @@ -0,0 +1,16 @@ +// 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 asymkey + +import ( + "path/filepath" + "testing" + + "code.gitea.io/gitea/models/unittest" +) + +func TestMain(m *testing.M) { + unittest.MainTest(m, filepath.Join("..", "..")) +} diff --git a/services/asymkey/sign.go b/services/asymkey/sign.go new file mode 100644 index 0000000000..4ccc581803 --- /dev/null +++ b/services/asymkey/sign.go @@ -0,0 +1,388 @@ +// Copyright 2021 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 asymkey + +import ( + "fmt" + "strings" + + "code.gitea.io/gitea/models" + asymkey_model "code.gitea.io/gitea/models/asymkey" + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/login" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/process" + "code.gitea.io/gitea/modules/setting" +) + +type signingMode string + +const ( + never signingMode = "never" + always signingMode = "always" + pubkey signingMode = "pubkey" + twofa signingMode = "twofa" + parentSigned signingMode = "parentsigned" + baseSigned signingMode = "basesigned" + headSigned signingMode = "headsigned" + commitsSigned signingMode = "commitssigned" + approved signingMode = "approved" + noKey signingMode = "nokey" +) + +func signingModeFromStrings(modeStrings []string) []signingMode { + returnable := make([]signingMode, 0, len(modeStrings)) + for _, mode := range modeStrings { + signMode := signingMode(strings.ToLower(strings.TrimSpace(mode))) + switch signMode { + case never: + return []signingMode{never} + case always: + return []signingMode{always} + case pubkey: + fallthrough + case twofa: + fallthrough + case parentSigned: + fallthrough + case baseSigned: + fallthrough + case headSigned: + fallthrough + case approved: + fallthrough + case commitsSigned: + returnable = append(returnable, signMode) + } + } + if len(returnable) == 0 { + return []signingMode{never} + } + return returnable +} + +// ErrWontSign explains the first reason why a commit would not be signed +// There may be other reasons - this is just the first reason found +type ErrWontSign struct { + Reason signingMode +} + +func (e *ErrWontSign) Error() string { + return fmt.Sprintf("wont sign: %s", e.Reason) +} + +// IsErrWontSign checks if an error is a ErrWontSign +func IsErrWontSign(err error) bool { + _, ok := err.(*ErrWontSign) + return ok +} + +// SigningKey returns the KeyID and git Signature for the repo +func SigningKey(repoPath string) (string, *git.Signature) { + if setting.Repository.Signing.SigningKey == "none" { + return "", nil + } + + if setting.Repository.Signing.SigningKey == "default" || setting.Repository.Signing.SigningKey == "" { + // Can ignore the error here as it means that commit.gpgsign is not set + value, _ := git.NewCommand("config", "--get", "commit.gpgsign").RunInDir(repoPath) + sign, valid := git.ParseBool(strings.TrimSpace(value)) + if !sign || !valid { + return "", nil + } + + signingKey, _ := git.NewCommand("config", "--get", "user.signingkey").RunInDir(repoPath) + 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, &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) + if signingKey == "" { + return "", nil + } + + content, stderr, err := process.GetManager().ExecDir(-1, repoPath, + "gpg --export -a", "gpg", "--export", "-a", signingKey) + if err != nil { + log.Error("Unable to get default signing key in %s: %s, %s, %v", repoPath, signingKey, stderr, err) + return "", err + } + return content, nil +} + +// SignInitialCommit determines if we should sign the initial commit to this repository +func SignInitialCommit(repoPath string, u *user_model.User) (bool, string, *git.Signature, error) { + rules := signingModeFromStrings(setting.Repository.Signing.InitialCommit) + signingKey, sig := SigningKey(repoPath) + if signingKey == "" { + return false, "", nil, &ErrWontSign{noKey} + } + +Loop: + for _, rule := range rules { + switch rule { + case never: + return false, "", nil, &ErrWontSign{never} + case always: + break Loop + case pubkey: + keys, err := asymkey_model.ListGPGKeys(db.DefaultContext, u.ID, db.ListOptions{}) + if err != nil { + return false, "", nil, err + } + if len(keys) == 0 { + return false, "", nil, &ErrWontSign{pubkey} + } + case twofa: + twofaModel, err := login.GetTwoFactorByUID(u.ID) + if err != nil && !login.IsErrTwoFactorNotEnrolled(err) { + return false, "", nil, err + } + if twofaModel == nil { + return false, "", nil, &ErrWontSign{twofa} + } + } + } + return true, signingKey, sig, nil +} + +// SignWikiCommit determines if we should sign the commits to this repository wiki +func SignWikiCommit(repoWikiPath string, u *user_model.User) (bool, string, *git.Signature, error) { + rules := signingModeFromStrings(setting.Repository.Signing.Wiki) + signingKey, sig := SigningKey(repoWikiPath) + if signingKey == "" { + return false, "", nil, &ErrWontSign{noKey} + } + +Loop: + for _, rule := range rules { + switch rule { + case never: + return false, "", nil, &ErrWontSign{never} + case always: + break Loop + case pubkey: + keys, err := asymkey_model.ListGPGKeys(db.DefaultContext, u.ID, db.ListOptions{}) + if err != nil { + return false, "", nil, err + } + if len(keys) == 0 { + return false, "", nil, &ErrWontSign{pubkey} + } + case twofa: + twofaModel, err := login.GetTwoFactorByUID(u.ID) + if err != nil && !login.IsErrTwoFactorNotEnrolled(err) { + return false, "", nil, err + } + if twofaModel == nil { + return false, "", nil, &ErrWontSign{twofa} + } + case parentSigned: + gitRepo, err := git.OpenRepository(repoWikiPath) + if err != nil { + return false, "", nil, err + } + defer gitRepo.Close() + commit, err := gitRepo.GetCommit("HEAD") + if err != nil { + return false, "", nil, err + } + if commit.Signature == nil { + return false, "", nil, &ErrWontSign{parentSigned} + } + verification := asymkey_model.ParseCommitWithSignature(commit) + if !verification.Verified { + return false, "", nil, &ErrWontSign{parentSigned} + } + } + } + return true, signingKey, sig, nil +} + +// SignCRUDAction determines if we should sign a CRUD commit to this repository +func SignCRUDAction(repoPath string, u *user_model.User, tmpBasePath, parentCommit string) (bool, string, *git.Signature, error) { + rules := signingModeFromStrings(setting.Repository.Signing.CRUDActions) + signingKey, sig := SigningKey(repoPath) + if signingKey == "" { + return false, "", nil, &ErrWontSign{noKey} + } + +Loop: + for _, rule := range rules { + switch rule { + case never: + return false, "", nil, &ErrWontSign{never} + case always: + break Loop + case pubkey: + keys, err := asymkey_model.ListGPGKeys(db.DefaultContext, u.ID, db.ListOptions{}) + if err != nil { + return false, "", nil, err + } + if len(keys) == 0 { + return false, "", nil, &ErrWontSign{pubkey} + } + case twofa: + twofaModel, err := login.GetTwoFactorByUID(u.ID) + if err != nil && !login.IsErrTwoFactorNotEnrolled(err) { + return false, "", nil, err + } + if twofaModel == nil { + return false, "", nil, &ErrWontSign{twofa} + } + case parentSigned: + gitRepo, err := git.OpenRepository(tmpBasePath) + if err != nil { + return false, "", nil, err + } + defer gitRepo.Close() + commit, err := gitRepo.GetCommit(parentCommit) + if err != nil { + return false, "", nil, err + } + if commit.Signature == nil { + return false, "", nil, &ErrWontSign{parentSigned} + } + verification := asymkey_model.ParseCommitWithSignature(commit) + if !verification.Verified { + return false, "", nil, &ErrWontSign{parentSigned} + } + } + } + return true, signingKey, sig, nil +} + +// SignMerge determines if we should sign a PR merge commit to the base repository +func SignMerge(pr *models.PullRequest, u *user_model.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, "", nil, err + } + repo := pr.BaseRepo + + signingKey, signer := SigningKey(repo.RepoPath()) + if signingKey == "" { + return false, "", nil, &ErrWontSign{noKey} + } + rules := signingModeFromStrings(setting.Repository.Signing.Merges) + + var gitRepo *git.Repository + var err error + +Loop: + for _, rule := range rules { + switch rule { + case never: + return false, "", nil, &ErrWontSign{never} + case always: + break Loop + case pubkey: + keys, err := asymkey_model.ListGPGKeys(db.DefaultContext, u.ID, db.ListOptions{}) + if err != nil { + return false, "", nil, err + } + if len(keys) == 0 { + return false, "", nil, &ErrWontSign{pubkey} + } + case twofa: + twofaModel, err := login.GetTwoFactorByUID(u.ID) + if err != nil && !login.IsErrTwoFactorNotEnrolled(err) { + return false, "", nil, err + } + if twofaModel == nil { + return false, "", nil, &ErrWontSign{twofa} + } + case approved: + protectedBranch, err := models.GetProtectedBranchBy(repo.ID, pr.BaseBranch) + if err != nil { + return false, "", nil, err + } + if protectedBranch == nil { + return false, "", nil, &ErrWontSign{approved} + } + if protectedBranch.GetGrantedApprovalsCount(pr) < 1 { + return false, "", nil, &ErrWontSign{approved} + } + case baseSigned: + if gitRepo == nil { + gitRepo, err = git.OpenRepository(tmpBasePath) + if err != nil { + return false, "", nil, err + } + defer gitRepo.Close() + } + commit, err := gitRepo.GetCommit(baseCommit) + if err != nil { + return false, "", nil, err + } + verification := asymkey_model.ParseCommitWithSignature(commit) + if !verification.Verified { + return false, "", nil, &ErrWontSign{baseSigned} + } + case headSigned: + if gitRepo == nil { + gitRepo, err = git.OpenRepository(tmpBasePath) + if err != nil { + return false, "", nil, err + } + defer gitRepo.Close() + } + commit, err := gitRepo.GetCommit(headCommit) + if err != nil { + return false, "", nil, err + } + verification := asymkey_model.ParseCommitWithSignature(commit) + if !verification.Verified { + return false, "", nil, &ErrWontSign{headSigned} + } + case commitsSigned: + if gitRepo == nil { + gitRepo, err = git.OpenRepository(tmpBasePath) + if err != nil { + return false, "", nil, err + } + defer gitRepo.Close() + } + commit, err := gitRepo.GetCommit(headCommit) + if err != nil { + return false, "", nil, err + } + verification := asymkey_model.ParseCommitWithSignature(commit) + if !verification.Verified { + return false, "", nil, &ErrWontSign{commitsSigned} + } + // need to work out merge-base + mergeBaseCommit, _, err := gitRepo.GetMergeBase("", baseCommit, headCommit) + if err != nil { + return false, "", nil, err + } + commitList, err := commit.CommitsBeforeUntil(mergeBaseCommit) + if err != nil { + return false, "", nil, err + } + for _, commit := range commitList { + verification := asymkey_model.ParseCommitWithSignature(commit) + if !verification.Verified { + return false, "", nil, &ErrWontSign{commitsSigned} + } + } + } + } + return true, signingKey, signer, nil +} diff --git a/services/asymkey/ssh_key.go b/services/asymkey/ssh_key.go new file mode 100644 index 0000000000..1f6b93eb24 --- /dev/null +++ b/services/asymkey/ssh_key.go @@ -0,0 +1,49 @@ +// Copyright 2021 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 asymkey + +import ( + asymkey_model "code.gitea.io/gitea/models/asymkey" + "code.gitea.io/gitea/models/db" + user_model "code.gitea.io/gitea/models/user" +) + +// DeletePublicKey deletes SSH key information both in database and authorized_keys file. +func DeletePublicKey(doer *user_model.User, id int64) (err error) { + key, err := asymkey_model.GetPublicKeyByID(id) + if err != nil { + return err + } + + // Check if user has access to delete this key. + if !doer.IsAdmin && doer.ID != key.OwnerID { + return asymkey_model.ErrKeyAccessDenied{ + UserID: doer.ID, + KeyID: key.ID, + Note: "public", + } + } + + ctx, committer, err := db.TxContext() + if err != nil { + return err + } + defer committer.Close() + + if err = asymkey_model.DeletePublicKeys(ctx, id); err != nil { + return err + } + + if err = committer.Commit(); err != nil { + return err + } + committer.Close() + + if key.Type == asymkey_model.KeyTypePrincipal { + return asymkey_model.RewriteAllPrincipalKeys() + } + + return asymkey_model.RewriteAllPublicKeys() +} diff --git a/services/asymkey/ssh_key_test.go b/services/asymkey/ssh_key_test.go new file mode 100644 index 0000000000..0ce235f7f6 --- /dev/null +++ b/services/asymkey/ssh_key_test.go @@ -0,0 +1,83 @@ +// Copyright 2021 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 asymkey + +import ( + "testing" + + asymkey_model "code.gitea.io/gitea/models/asymkey" + "code.gitea.io/gitea/models/login" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "github.com/stretchr/testify/assert" +) + +func TestAddLdapSSHPublicKeys(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User) + s := &login.Source{ID: 1} + + testCases := []struct { + keyString string + number int + keyContents []string + }{ + { + keyString: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM= nocomment\n", + number: 1, + keyContents: []string{ + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM=", + }, + }, + { + keyString: `ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM= nocomment +ssh-dss AAAAB3NzaC1kc3MAAACBAOChCC7lf6Uo9n7BmZ6M8St19PZf4Tn59NriyboW2x/DZuYAz3ibZ2OkQ3S0SqDIa0HXSEJ1zaExQdmbO+Ux/wsytWZmCczWOVsaszBZSl90q8UnWlSH6P+/YA+RWJm5SFtuV9PtGIhyZgoNuz5kBQ7K139wuQsecdKktISwTakzAAAAFQCzKsO2JhNKlL+wwwLGOcLffoAmkwAAAIBpK7/3xvduajLBD/9vASqBQIHrgK2J+wiQnIb/Wzy0UsVmvfn8A+udRbBo+csM8xrSnlnlJnjkJS3qiM5g+eTwsLIV1IdKPEwmwB+VcP53Cw6lSyWyJcvhFb0N6s08NZysLzvj0N+ZC/FnhKTLzIyMtkHf/IrPCwlM+pV/M/96YgAAAIEAqQcGn9CKgzgPaguIZooTAOQdvBLMI5y0bQjOW6734XOpqQGf/Kra90wpoasLKZjSYKNPjE+FRUOrStLrxcNs4BeVKhy2PYTRnybfYVk1/dmKgH6P1YSRONsGKvTsH6c5IyCRG0ncCgYeF8tXppyd642982daopE7zQ/NPAnJfag= nocomment`, + number: 2, + keyContents: []string{ + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM=", + "ssh-dss AAAAB3NzaC1kc3MAAACBAOChCC7lf6Uo9n7BmZ6M8St19PZf4Tn59NriyboW2x/DZuYAz3ibZ2OkQ3S0SqDIa0HXSEJ1zaExQdmbO+Ux/wsytWZmCczWOVsaszBZSl90q8UnWlSH6P+/YA+RWJm5SFtuV9PtGIhyZgoNuz5kBQ7K139wuQsecdKktISwTakzAAAAFQCzKsO2JhNKlL+wwwLGOcLffoAmkwAAAIBpK7/3xvduajLBD/9vASqBQIHrgK2J+wiQnIb/Wzy0UsVmvfn8A+udRbBo+csM8xrSnlnlJnjkJS3qiM5g+eTwsLIV1IdKPEwmwB+VcP53Cw6lSyWyJcvhFb0N6s08NZysLzvj0N+ZC/FnhKTLzIyMtkHf/IrPCwlM+pV/M/96YgAAAIEAqQcGn9CKgzgPaguIZooTAOQdvBLMI5y0bQjOW6734XOpqQGf/Kra90wpoasLKZjSYKNPjE+FRUOrStLrxcNs4BeVKhy2PYTRnybfYVk1/dmKgH6P1YSRONsGKvTsH6c5IyCRG0ncCgYeF8tXppyd642982daopE7zQ/NPAnJfag=", + }, + }, + { + keyString: `ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM= nocomment +# comment asmdna,ndp +ssh-dss AAAAB3NzaC1kc3MAAACBAOChCC7lf6Uo9n7BmZ6M8St19PZf4Tn59NriyboW2x/DZuYAz3ibZ2OkQ3S0SqDIa0HXSEJ1zaExQdmbO+Ux/wsytWZmCczWOVsaszBZSl90q8UnWlSH6P+/YA+RWJm5SFtuV9PtGIhyZgoNuz5kBQ7K139wuQsecdKktISwTakzAAAAFQCzKsO2JhNKlL+wwwLGOcLffoAmkwAAAIBpK7/3xvduajLBD/9vASqBQIHrgK2J+wiQnIb/Wzy0UsVmvfn8A+udRbBo+csM8xrSnlnlJnjkJS3qiM5g+eTwsLIV1IdKPEwmwB+VcP53Cw6lSyWyJcvhFb0N6s08NZysLzvj0N+ZC/FnhKTLzIyMtkHf/IrPCwlM+pV/M/96YgAAAIEAqQcGn9CKgzgPaguIZooTAOQdvBLMI5y0bQjOW6734XOpqQGf/Kra90wpoasLKZjSYKNPjE+FRUOrStLrxcNs4BeVKhy2PYTRnybfYVk1/dmKgH6P1YSRONsGKvTsH6c5IyCRG0ncCgYeF8tXppyd642982daopE7zQ/NPAnJfag= nocomment`, + number: 2, + keyContents: []string{ + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM=", + "ssh-dss AAAAB3NzaC1kc3MAAACBAOChCC7lf6Uo9n7BmZ6M8St19PZf4Tn59NriyboW2x/DZuYAz3ibZ2OkQ3S0SqDIa0HXSEJ1zaExQdmbO+Ux/wsytWZmCczWOVsaszBZSl90q8UnWlSH6P+/YA+RWJm5SFtuV9PtGIhyZgoNuz5kBQ7K139wuQsecdKktISwTakzAAAAFQCzKsO2JhNKlL+wwwLGOcLffoAmkwAAAIBpK7/3xvduajLBD/9vASqBQIHrgK2J+wiQnIb/Wzy0UsVmvfn8A+udRbBo+csM8xrSnlnlJnjkJS3qiM5g+eTwsLIV1IdKPEwmwB+VcP53Cw6lSyWyJcvhFb0N6s08NZysLzvj0N+ZC/FnhKTLzIyMtkHf/IrPCwlM+pV/M/96YgAAAIEAqQcGn9CKgzgPaguIZooTAOQdvBLMI5y0bQjOW6734XOpqQGf/Kra90wpoasLKZjSYKNPjE+FRUOrStLrxcNs4BeVKhy2PYTRnybfYVk1/dmKgH6P1YSRONsGKvTsH6c5IyCRG0ncCgYeF8tXppyd642982daopE7zQ/NPAnJfag=", + }, + }, + { + keyString: `ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM= nocomment +382488320jasdj1lasmva/vasodifipi4193-fksma.cm +ssh-dss AAAAB3NzaC1kc3MAAACBAOChCC7lf6Uo9n7BmZ6M8St19PZf4Tn59NriyboW2x/DZuYAz3ibZ2OkQ3S0SqDIa0HXSEJ1zaExQdmbO+Ux/wsytWZmCczWOVsaszBZSl90q8UnWlSH6P+/YA+RWJm5SFtuV9PtGIhyZgoNuz5kBQ7K139wuQsecdKktISwTakzAAAAFQCzKsO2JhNKlL+wwwLGOcLffoAmkwAAAIBpK7/3xvduajLBD/9vASqBQIHrgK2J+wiQnIb/Wzy0UsVmvfn8A+udRbBo+csM8xrSnlnlJnjkJS3qiM5g+eTwsLIV1IdKPEwmwB+VcP53Cw6lSyWyJcvhFb0N6s08NZysLzvj0N+ZC/FnhKTLzIyMtkHf/IrPCwlM+pV/M/96YgAAAIEAqQcGn9CKgzgPaguIZooTAOQdvBLMI5y0bQjOW6734XOpqQGf/Kra90wpoasLKZjSYKNPjE+FRUOrStLrxcNs4BeVKhy2PYTRnybfYVk1/dmKgH6P1YSRONsGKvTsH6c5IyCRG0ncCgYeF8tXppyd642982daopE7zQ/NPAnJfag= nocomment`, + number: 2, + keyContents: []string{ + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM=", + "ssh-dss AAAAB3NzaC1kc3MAAACBAOChCC7lf6Uo9n7BmZ6M8St19PZf4Tn59NriyboW2x/DZuYAz3ibZ2OkQ3S0SqDIa0HXSEJ1zaExQdmbO+Ux/wsytWZmCczWOVsaszBZSl90q8UnWlSH6P+/YA+RWJm5SFtuV9PtGIhyZgoNuz5kBQ7K139wuQsecdKktISwTakzAAAAFQCzKsO2JhNKlL+wwwLGOcLffoAmkwAAAIBpK7/3xvduajLBD/9vASqBQIHrgK2J+wiQnIb/Wzy0UsVmvfn8A+udRbBo+csM8xrSnlnlJnjkJS3qiM5g+eTwsLIV1IdKPEwmwB+VcP53Cw6lSyWyJcvhFb0N6s08NZysLzvj0N+ZC/FnhKTLzIyMtkHf/IrPCwlM+pV/M/96YgAAAIEAqQcGn9CKgzgPaguIZooTAOQdvBLMI5y0bQjOW6734XOpqQGf/Kra90wpoasLKZjSYKNPjE+FRUOrStLrxcNs4BeVKhy2PYTRnybfYVk1/dmKgH6P1YSRONsGKvTsH6c5IyCRG0ncCgYeF8tXppyd642982daopE7zQ/NPAnJfag=", + }, + }, + } + + for i, kase := range testCases { + s.ID = int64(i) + 20 + asymkey_model.AddPublicKeysBySource(user, s, []string{kase.keyString}) + keys, err := asymkey_model.ListPublicKeysBySource(user.ID, s.ID) + assert.NoError(t, err) + if err != nil { + continue + } + assert.Len(t, keys, kase.number) + + for _, key := range keys { + assert.Contains(t, kase.keyContents, key.Content) + } + for _, key := range keys { + DeletePublicKey(user, key.ID) + } + } +} |