Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validate user ref when creating user repository #250

Merged
merged 1 commit into from
Oct 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions gitea/client_repositories_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,19 @@ func (c *UserRepositoriesClient) listUserRepos(username string) ([]*gitea.Reposi
return validateRepositoryObjects(apiObjs)
}

// GetUserLogin returns the authenticated user
func (c *UserRepositoriesClient) GetUserLogin(ctx context.Context) (gitprovider.IdentityRef, error) {
// GET /user
user, _, err := c.c.GetMyUserInfo()
if err != nil {
return nil, err
}
return gitprovider.UserRef{
Domain: c.domain,
UserLogin: user.UserName,
}, nil
}

// Create creates a repository for the given organization, with the data and options
//
// ErrAlreadyExists will be returned if the resource already exists.
Expand All @@ -108,6 +121,17 @@ func (c *UserRepositoriesClient) Create(ctx context.Context,
return nil, err
}

// extra validation to ensure we don't create a project when the wrong owner
// is passed in
idRef, err := c.GetUserLogin(ctx)
if err != nil {
return nil, fmt.Errorf("unable to get owner from API")
}

if ref.GetIdentity() != idRef.GetIdentity() {
return nil, gitprovider.NewErrIncorrectUser(ref.GetIdentity())
}

apiObj, err := createRepository(ctx, c.c, ref, "", req, opts...)
if err != nil {
return nil, err
Expand Down
17 changes: 11 additions & 6 deletions gitea/integration_repositories_user_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,13 @@ var _ = Describe("Gitea Provider", func() {
Expect(*info.DefaultBranch).To(Equal(defaultBranch))
}

It("should get the current user", func() {
user, err := c.UserRepositories().GetUserLogin(ctx)
Expect(err).ToNot(HaveOccurred())

Expect(user.GetIdentity()).To(Equal(giteaUser))
})

It("should be possible to create a user repository", func() {
// First, check what repositories are available
repos, err := c.UserRepositories().List(ctx, newUserRef(giteaUser))
Expand Down Expand Up @@ -100,17 +107,15 @@ var _ = Describe("Gitea Provider", func() {
Expect(getSpec.Equals(postSpec)).To(BeTrue())
})

It("should return correct repo info when creating a repository with wrong UserLogin", func() {
It("should fail when creating a repository with wrong UserLogin", func() {
repoName := fmt.Sprintf("test-user-repo-creation-%03d", rand.Intn(1000))
repoRef := newUserRepoRef(repoName)
repoRef.UserLogin = "yadda-yadda-yada"

repo, err := c.UserRepositories().Create(ctx, repoRef, gitprovider.RepositoryInfo{})
_, err := c.UserRepositories().Create(ctx, repoRef, gitprovider.RepositoryInfo{})

Expect(err).To(BeNil())
Expect(
repo.Repository().GetCloneURL(gitprovider.TransportTypeHTTPS)).
To(Equal(fmt.Sprintf("%s/%s/%s.git", giteaBaseUrl, giteaUser, repoName)))
expectedErr := gitprovider.NewErrIncorrectUser(repoRef.UserLogin)
Expect(err).To(MatchError(expectedErr))
})

It("should error at creation time if the repo already does exist", func() {
Expand Down
24 changes: 24 additions & 0 deletions github/client_repositories_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,19 @@ type UserRepositoriesClient struct {
*clientContext
}

// GetUserLogin returns the current authenticated user.
func (c *UserRepositoriesClient) GetUserLogin(ctx context.Context) (gitprovider.IdentityRef, error) {
// GET /user
user, err := c.c.GetUser(ctx)
if err != nil {
return nil, err
}
return gitprovider.UserRef{
Domain: c.domain,
UserLogin: user.GetLogin(),
}, nil
}

// Get returns the repository at the given path.
//
// ErrNotFound is returned if the resource does not exist.
Expand Down Expand Up @@ -88,6 +101,17 @@ func (c *UserRepositoriesClient) Create(ctx context.Context,
return nil, err
}

// extra validation to ensure we don't create a project when the wrong owner
// is passed in
idRef, err := c.GetUserLogin(ctx)
if err != nil {
return nil, fmt.Errorf("unable to get owner from API")
}

if ref.GetIdentity() != idRef.GetIdentity() {
return nil, gitprovider.NewErrIncorrectUser(ref.GetIdentity())
}

apiObj, err := createRepository(ctx, c.c, ref, "", req, opts...)
if err != nil {
return nil, err
Expand Down
9 changes: 9 additions & 0 deletions github/githubclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ type githubClient interface {
// DANGEROUS COMMAND: In order to use this, you must set destructiveActions to true.
DeleteRepo(ctx context.Context, owner, repo string) error

// GetUser is a wrapper for "GET /user"
GetUser(ctx context.Context) (*github.User, error)

// ListKeys is a wrapper for "GET /repos/{owner}/{repo}/keys".
// This function handles pagination, HTTP error wrapping, and validates the server result.
ListKeys(ctx context.Context, owner, repo string) ([]*github.Key, error)
Expand Down Expand Up @@ -297,6 +300,12 @@ func (c *githubClientImpl) ListKeys(ctx context.Context, owner, repo string) ([]
return apiObjs, nil
}

func (c *githubClientImpl) GetUser(ctx context.Context) (*github.User, error) {
// GET /user
user, _, err := c.c.Users.Get(ctx, "")
return user, err
}

func (c *githubClientImpl) ListCommitsPage(ctx context.Context, owner, repo, branch string, perPage int, page int) ([]*github.Commit, error) {
apiObjs := make([]*github.Commit, 0)
lcOpts := &github.CommitsListOptions{
Expand Down
17 changes: 11 additions & 6 deletions github/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,13 @@ var _ = Describe("GitHub Provider", func() {
}
}

It("should get the current user", func() {
user, err := c.UserRepositories().GetUserLogin(ctx)
Expect(err).ToNot(HaveOccurred())

Expect(user.GetIdentity()).To(Equal(testUser))
})

It("should list the available organizations the user has access to", func() {
// Get a list of all organizations the user is part of
orgs, err := c.Organizations().List(ctx)
Expand Down Expand Up @@ -345,16 +352,14 @@ var _ = Describe("GitHub Provider", func() {
Expect(getSpec.Equals(postSpec)).To(BeTrue())
})

It("should return correct repo info when creating a repository with wrong UserLogin", func() {
It("should fail when creating a repository with wrong UserLogin", func() {
repoName := fmt.Sprintf("test-user-repo-creation-%03d", rand.Intn(1000))
repoRef := newUserRepoRef("yadda-yadda-yada", repoName)

repo, err := c.UserRepositories().Create(ctx, repoRef, gitprovider.RepositoryInfo{})
_, err := c.UserRepositories().Create(ctx, repoRef, gitprovider.RepositoryInfo{})

Expect(err).To(BeNil())
Expect(
repo.Repository().GetCloneURL(gitprovider.TransportTypeHTTPS)).
To(Equal(fmt.Sprintf("https://%s/%s/%s.git", githubDomain, testUser, repoName)))
expectedErr := gitprovider.NewErrIncorrectUser(repoRef.GetIdentity())
Expect(err).To(MatchError(expectedErr))
})

It("should error at creation time if the repo already does exist", func() {
Expand Down
24 changes: 24 additions & 0 deletions gitlab/client_repositories_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,19 @@ type UserRepositoriesClient struct {
*clientContext
}

// GetUserLogin returns the current authenticated user.
func (c *UserRepositoriesClient) GetUserLogin(ctx context.Context) (gitprovider.IdentityRef, error) {
// GET /user
user, err := c.c.GetUser(ctx)
if err != nil {
return nil, err
}
return &gitprovider.UserRef{
Domain: c.domain,
UserLogin: user.Username,
}, nil
}

// Get returns the repository at the given path.
//
// ErrNotFound is returned if the resource does not exist.
Expand Down Expand Up @@ -89,6 +102,17 @@ func (c *UserRepositoriesClient) Create(ctx context.Context,
return nil, err
}

// extra validation to ensure we don't create a project when the wrong owner
// is passed in
idRef, err := c.GetUserLogin(ctx)
if err != nil {
return nil, fmt.Errorf("unable to get owner from API")
}

if ref.GetIdentity() != idRef.GetIdentity() {
return nil, gitprovider.NewErrIncorrectUser(ref.GetIdentity())
}

apiObj, err := createProject(ctx, c.c, ref, "", req, opts...)
if err != nil {
return nil, err
Expand Down
9 changes: 9 additions & 0 deletions gitlab/gitlabclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ type gitlabClient interface {
// DANGEROUS COMMAND: In order to use this, you must set destructiveActions to true.
DeleteProject(ctx context.Context, projectName string) error

// GetUser is a wrapper for "GET /user"
GetUser(ctx context.Context) (*gitlab.User, error)

// Deploy key methods

// ListKeys is a wrapper for "GET /projects/{project}/deploy_keys".
Expand Down Expand Up @@ -341,6 +344,12 @@ func (c *gitlabClientImpl) DeleteProject(ctx context.Context, projectName string
return err
}

func (c *gitlabClientImpl) GetUser(ctx context.Context) (*gitlab.User, error) {
// GET /user
proj, _, err := c.c.Users.CurrentUser(gitlab.WithContext(ctx))
return proj, err
}

func (c *gitlabClientImpl) ListKeys(projectName string) ([]*gitlab.ProjectDeployKey, error) {
apiObjs := []*gitlab.ProjectDeployKey{}
opts := &gitlab.ListProjectDeployKeysOptions{}
Expand Down
17 changes: 11 additions & 6 deletions gitlab/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,13 @@ var _ = Describe("GitLab Provider", func() {
}
}

It("should get the current user", func() {
user, err := c.UserRepositories().GetUserLogin(ctx)
Expect(err).ToNot(HaveOccurred())

Expect(user.GetIdentity()).To(Equal(testUserName))
})

It("should list the available organizations the user has access to", func() {
// Get a list of all organizations the user is part of
orgs, err := c.Organizations().List(ctx)
Expand Down Expand Up @@ -796,16 +803,14 @@ var _ = Describe("GitLab Provider", func() {
Expect(errors.Is(err, gitprovider.ErrAlreadyExists)).To(BeTrue())
})

It("should return correct repo info when creating a repository with wrong UserLogin", func() {
It("should fail info when creating a repository with wrong UserLogin", func() {
repoName := fmt.Sprintf("test-user-repo-creation-%03d", rand.Intn(1000))
repoRef := newUserRepoRef(testBaseUrl, "yadda-yadda-yada", repoName)

repo, err := c.UserRepositories().Create(ctx, repoRef, gitprovider.RepositoryInfo{})
souleb marked this conversation as resolved.
Show resolved Hide resolved
_, err := c.UserRepositories().Create(ctx, repoRef, gitprovider.RepositoryInfo{})

Expect(err).To(BeNil())
Expect(
repo.Repository().GetCloneURL(gitprovider.TransportTypeHTTPS)).
To(Equal(fmt.Sprintf("%s/%s/%s.git", testBaseUrl, testUserName, repoName)))
expectedErr := gitprovider.NewErrIncorrectUser(repoRef.GetIdentity())
Expect(err).To(MatchError(expectedErr))
})

It("should update if the user repo already exists when reconciling", func() {
Expand Down
3 changes: 3 additions & 0 deletions gitprovider/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@ type UserRepositoriesClient interface {
// ErrAlreadyExists will be returned if the resource already exists.
Create(ctx context.Context, r UserRepositoryRef, req RepositoryInfo, opts ...RepositoryCreateOption) (UserRepository, error)

// GetUserLogin returns the current authenticated user.
GetUserLogin(ctx context.Context) (IdentityRef, error)

// Reconcile makes sure the given desired state (req) becomes the actual state in the backing Git provider.
//
// If req doesn't exist under the hood, it is created (actionTaken == true).
Expand Down
18 changes: 18 additions & 0 deletions gitprovider/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package gitprovider

import (
"errors"
"fmt"
"net/http"
"time"
)
Expand Down Expand Up @@ -131,3 +132,20 @@ type InvalidCredentialsError struct {
// InvalidCredentialsError extends HTTPError.
HTTPError `json:",inline"`
}

// ErrIncorrectUser describes that the user provided was incorrect
//
// It is returned by `UserRepositories().Create` when an incorrect UserLogin is passed in
type ErrIncorrectUser struct {
user string
}

// Error implements the error interface.
func NewErrIncorrectUser(user string) *ErrIncorrectUser {
return &ErrIncorrectUser{user}
}

// Error implements the error interface.
func (e *ErrIncorrectUser) Error() string {
return fmt.Sprintf("incorrect user '%s' provided", e.user)
}
9 changes: 9 additions & 0 deletions stash/client_repositories_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,15 @@ type UserRepositoriesClient struct {
*clientContext
}

// GetUserLogin returns the authenticated user.
//
// Stash currently doesn't have an endpoint for this, so this
// is mostly to implement the interface.
func (c *UserRepositoriesClient) GetUserLogin(ctx context.Context) (gitprovider.IdentityRef, error) {
// TODO: call API for stash
return gitprovider.UserRef{}, nil
}

// Get returns the repository at the given path.
// ErrNotFound is returned if the resource does not exist.
func (c *UserRepositoriesClient) Get(ctx context.Context, ref gitprovider.UserRepositoryRef) (gitprovider.UserRepository, error) {
Expand Down
Loading