64d6a9f85e
Co-authored-by: ayattara <alassane.yattara@savoirfairelinux.com> Co-authored-by: Carlos Florencio <carlosmflorencio@gmail.com>
196 lines
7.9 KiB
Go
196 lines
7.9 KiB
Go
package backendapp
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/kandev/kandev/internal/gitcredentials"
|
|
taskmodels "github.com/kandev/kandev/internal/task/models"
|
|
)
|
|
|
|
func gitCredentialScopeTestRepository(remoteURL string) *fakeGitHubBrokerTaskRepository {
|
|
repository := gitCredentialScopeRepository(remoteURL)
|
|
repository.ProviderOwner = "acme"
|
|
repository.ProviderName = "widgets"
|
|
return &fakeGitHubBrokerTaskRepository{
|
|
task: &taskmodels.Task{ID: "task-1", WorkspaceID: "workspace-1"},
|
|
session: &taskmodels.TaskSession{ID: "session-1", TaskID: "task-1", State: taskmodels.TaskSessionStateRunning},
|
|
repository: repository,
|
|
links: []*taskmodels.TaskRepository{{TaskID: "task-1", RepositoryID: "repository-1"}},
|
|
}
|
|
}
|
|
|
|
func gitCredentialScopeRepository(remoteURL string) *taskmodels.Repository {
|
|
return &taskmodels.Repository{
|
|
ID: "repository-1", WorkspaceID: "workspace-1", Provider: "github", RemoteURL: remoteURL,
|
|
}
|
|
}
|
|
|
|
func gitCredentialScopeTaskRepository(repository *taskmodels.Repository) *fakeGitHubBrokerTaskRepository {
|
|
return &fakeGitHubBrokerTaskRepository{
|
|
task: &taskmodels.Task{ID: "task-1", WorkspaceID: "workspace-1"},
|
|
session: &taskmodels.TaskSession{ID: "session-1", TaskID: "task-1", State: taskmodels.TaskSessionStateRunning},
|
|
repository: repository,
|
|
links: []*taskmodels.TaskRepository{{TaskID: "task-1", RepositoryID: "repository-1"}},
|
|
}
|
|
}
|
|
|
|
func gitCredentialScopeForPath(path string) gitcredentials.Scope {
|
|
return gitCredentialScopeForHostPath("github.com", path)
|
|
}
|
|
|
|
func gitCredentialScopeForHostPath(host, path string) gitcredentials.Scope {
|
|
return gitcredentials.Scope{
|
|
ProviderID: "github", WorkspaceID: "workspace-1", TaskID: "task-1", SessionID: "session-1",
|
|
RepositoryID: "repository-1", Host: host, Path: path,
|
|
}
|
|
}
|
|
|
|
// The executor derives the lease scope from the repository's clone URL, so the
|
|
// authorizer must recognize the same repository however that column spells it:
|
|
// SSH or HTTPS, with or without the ".git" suffix the broker canonicalizes away.
|
|
func TestGitHubBrokerScopeAuthorizerAcceptsEquivalentRepositorySpellings(t *testing.T) {
|
|
for _, testCase := range []struct {
|
|
name string
|
|
remoteURL string
|
|
scopePath string
|
|
}{
|
|
{name: "scp style SSH remote", remoteURL: "git@github.com:acme/widgets.git", scopePath: "/acme/widgets"},
|
|
{name: "ssh scheme remote", remoteURL: "ssh://git@github.com/acme/widgets.git", scopePath: "/acme/widgets"},
|
|
{name: "https remote canonical scope", remoteURL: "https://github.com/acme/widgets.git", scopePath: "/acme/widgets"},
|
|
{name: "https remote suffixed scope", remoteURL: "https://github.com/acme/widgets.git", scopePath: "/acme/widgets.git"},
|
|
{name: "derived from provider columns", remoteURL: "", scopePath: "/acme/widgets"},
|
|
} {
|
|
t.Run(testCase.name, func(t *testing.T) {
|
|
authorizer := &githubBrokerScopeAuthorizer{repo: gitCredentialScopeTestRepository(testCase.remoteURL)}
|
|
|
|
if err := authorizer.AuthorizeGitCredential(
|
|
context.Background(), gitCredentialScopeForPath(testCase.scopePath),
|
|
); err != nil {
|
|
t.Fatalf("AuthorizeGitCredential() error = %v", err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGitHubBrokerScopeAuthorizerRejectsForeignRepositoryScope(t *testing.T) {
|
|
for name, testCase := range map[string]struct {
|
|
repository *taskmodels.Repository
|
|
scopePath string
|
|
}{
|
|
"different repository": {
|
|
repository: gitCredentialScopeTestRepository("git@github.com:acme/widgets.git").repository,
|
|
scopePath: "/acme/widgets-staging",
|
|
},
|
|
"ssh remote on another host, no provider identity": {
|
|
repository: gitCredentialScopeRepository("git@github.example:acme/widgets.git"),
|
|
scopePath: "/acme/widgets",
|
|
},
|
|
"unsupported remote, no provider identity": {
|
|
repository: gitCredentialScopeRepository("file:///tmp/widgets.git"),
|
|
scopePath: "/acme/widgets",
|
|
},
|
|
"https remote on another host wins over provider identity": {
|
|
repository: gitCredentialScopeTestRepository("https://github.example/acme/widgets.git").repository,
|
|
scopePath: "/acme/widgets",
|
|
},
|
|
} {
|
|
t.Run(name, func(t *testing.T) {
|
|
authorizer := &githubBrokerScopeAuthorizer{repo: gitCredentialScopeTaskRepository(testCase.repository)}
|
|
|
|
if err := authorizer.AuthorizeGitCredential(
|
|
context.Background(), gitCredentialScopeForPath(testCase.scopePath),
|
|
); err == nil {
|
|
t.Fatal("AuthorizeGitCredential() error = nil, want scope denial")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGitHubBrokerScopeAuthorizerRechecksTaskScopeBeforeLegacyFallback(t *testing.T) {
|
|
for name, mutate := range map[string]func(*fakeGitHubBrokerTaskRepository){
|
|
"terminal session": func(repo *fakeGitHubBrokerTaskRepository) {
|
|
repo.session.State = taskmodels.TaskSessionStateCompleted
|
|
},
|
|
"session for another task": func(repo *fakeGitHubBrokerTaskRepository) {
|
|
repo.session.TaskID = "other-task"
|
|
},
|
|
"unlinked repository": func(repo *fakeGitHubBrokerTaskRepository) {
|
|
repo.links = nil
|
|
},
|
|
} {
|
|
t.Run(name, func(t *testing.T) {
|
|
repo := gitCredentialScopeTestRepository("https://github.com/acme/widgets.git")
|
|
mutate(repo)
|
|
authorizer := &githubBrokerScopeAuthorizer{repo: repo}
|
|
|
|
if err := authorizer.AuthorizeGitCredential(
|
|
context.Background(), gitCredentialScopeForPath("/acme/widgets.git"),
|
|
); err == nil {
|
|
t.Fatal("AuthorizeGitCredential() error = nil, want live task scope denial")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGitHubBrokerScopeAuthorizerDoesNotBypassProviderIdentityOnLegacyFallback(t *testing.T) {
|
|
for name, identity := range map[string]gitcredentials.Scope{
|
|
"mismatched provider identity": {
|
|
IdentityProviderID: "999",
|
|
},
|
|
"unexpected parent identity": {
|
|
IdentityProviderID: "100", ParentProviderID: "200",
|
|
},
|
|
} {
|
|
t.Run(name, func(t *testing.T) {
|
|
repo := gitCredentialScopeTestRepository("https://github.com/acme/widgets.git")
|
|
repo.repository.ProviderHost = ""
|
|
repo.repository.ProviderRepoID = "100"
|
|
authorizer := &githubBrokerScopeAuthorizer{repo: repo}
|
|
scope := gitCredentialScopeForPath("/acme/widgets.git")
|
|
scope.IdentityProviderID = identity.IdentityProviderID
|
|
scope.ParentProviderID = identity.ParentProviderID
|
|
|
|
if err := authorizer.AuthorizeGitCredential(context.Background(), scope); err == nil {
|
|
t.Fatal("AuthorizeGitCredential() error = nil, want provider identity denial")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGitHubBrokerScopeAuthorizerAcceptsPluginRepositoryScope(t *testing.T) {
|
|
repository := &taskmodels.Repository{
|
|
ID: "repository-1", WorkspaceID: "workspace-1", Provider: "bitbucket",
|
|
ProviderHost: "https://bitbucket.example", RemoteURL: "https://bitbucket.example/scm/ENG/widgets.git",
|
|
}
|
|
authorizer := &githubBrokerScopeAuthorizer{repo: gitCredentialScopeTaskRepository(repository)}
|
|
|
|
if err := authorizer.AuthorizeGitCredential(context.Background(), gitcredentials.Scope{
|
|
ProviderID: "bitbucket", WorkspaceID: "workspace-1", TaskID: "task-1", SessionID: "session-1",
|
|
RepositoryID: "repository-1", Host: "bitbucket.example", Path: "/scm/ENG/widgets.git",
|
|
}); err != nil {
|
|
t.Fatalf("AuthorizeGitCredential() error = %v", err)
|
|
}
|
|
}
|
|
|
|
// An SSH URL cannot express the provider's HTTPS port, so the persisted
|
|
// provider identity, not a rewrite of the remote, decides the origin.
|
|
func TestGitHubBrokerScopeAuthorizerUsesProviderHostPortForSSHRemote(t *testing.T) {
|
|
repository := gitCredentialScopeRepository("ssh://git@ghe.example:2222/acme/widgets.git")
|
|
repository.ProviderHost = "https://ghe.example:8443"
|
|
repository.ProviderOwner = "acme"
|
|
repository.ProviderName = "widgets"
|
|
authorizer := &githubBrokerScopeAuthorizer{repo: gitCredentialScopeTaskRepository(repository)}
|
|
|
|
if err := authorizer.AuthorizeGitCredential(
|
|
context.Background(), gitCredentialScopeForHostPath("ghe.example:8443", "/acme/widgets"),
|
|
); err != nil {
|
|
t.Fatalf("AuthorizeGitCredential() error = %v", err)
|
|
}
|
|
if err := authorizer.AuthorizeGitCredential(
|
|
context.Background(), gitCredentialScopeForHostPath("ghe.example", "/acme/widgets"),
|
|
); err == nil {
|
|
t.Fatal("AuthorizeGitCredential() error = nil, want portless origin denial")
|
|
}
|
|
}
|