From 5cd1d6c93ba9b8399f826e671b8940eb5294b872 Mon Sep 17 00:00:00 2001 From: Mai-Lapyst <67418776+Mai-Lapyst@users.noreply.github.com> Date: Tue, 28 Mar 2023 19:55:03 +0200 Subject: [PATCH] Set repository link based on the url in package.json for npm packages (#20379) automatically set repository link for package based on the repository url present inside package.json closes #20146 --- models/repo/repo.go | 43 +++++++++++++++++++++++ models/repo/repo_test.go | 62 +++++++++++++++++++++++++++++++++ routers/api/packages/npm/npm.go | 30 ++++++++++++++++ 3 files changed, 135 insertions(+) diff --git a/models/repo/repo.go b/models/repo/repo.go index dcffb63fd..3653dae01 100644 --- a/models/repo/repo.go +++ b/models/repo/repo.go @@ -658,6 +658,49 @@ func GetRepositoryByName(ownerID int64, name string) (*Repository, error) { return repo, err } +// getRepositoryURLPathSegments returns segments (owner, reponame) extracted from a url +func getRepositoryURLPathSegments(repoURL string) []string { + if strings.HasPrefix(repoURL, setting.AppURL) { + return strings.Split(strings.TrimPrefix(repoURL, setting.AppURL), "/") + } + + sshURLVariants := [4]string{ + setting.SSH.Domain + ":", + setting.SSH.User + "@" + setting.SSH.Domain + ":", + "git+ssh://" + setting.SSH.Domain + "/", + "git+ssh://" + setting.SSH.User + "@" + setting.SSH.Domain + "/", + } + + for _, sshURL := range sshURLVariants { + if strings.HasPrefix(repoURL, sshURL) { + return strings.Split(strings.TrimPrefix(repoURL, sshURL), "/") + } + } + + return nil +} + +// GetRepositoryByURL returns the repository by given url +func GetRepositoryByURL(ctx context.Context, repoURL string) (*Repository, error) { + // possible urls for git: + // https://my.domain/sub-path//.git + // https://my.domain/sub-path// + // git+ssh://user@my.domain//.git + // git+ssh://user@my.domain// + // user@my.domain:/.git + // user@my.domain:/ + + pathSegments := getRepositoryURLPathSegments(repoURL) + + if len(pathSegments) != 2 { + return nil, fmt.Errorf("unknown or malformed repository URL") + } + + ownerName := pathSegments[0] + repoName := strings.TrimSuffix(pathSegments[1], ".git") + return GetRepositoryByOwnerAndName(ctx, ownerName, repoName) +} + // GetRepositoryByID returns the repository by given id if exists. func GetRepositoryByID(ctx context.Context, id int64) (*Repository, error) { repo := new(Repository) diff --git a/models/repo/repo_test.go b/models/repo/repo_test.go index fb473151e..92a58ea3f 100644 --- a/models/repo/repo_test.go +++ b/models/repo/repo_test.go @@ -124,3 +124,65 @@ func TestMetas(t *testing.T) { assert.Equal(t, "user3", metas["org"]) assert.Equal(t, ",owners,team1,", metas["teams"]) } + +func TestGetRepositoryByURL(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + t.Run("InvalidPath", func(t *testing.T) { + repo, err := repo_model.GetRepositoryByURL(db.DefaultContext, "something") + + assert.Nil(t, repo) + assert.Error(t, err) + }) + + t.Run("ValidHttpURL", func(t *testing.T) { + test := func(t *testing.T, url string) { + repo, err := repo_model.GetRepositoryByURL(db.DefaultContext, url) + + assert.NotNil(t, repo) + assert.NoError(t, err) + + assert.Equal(t, repo.ID, int64(2)) + assert.Equal(t, repo.OwnerID, int64(2)) + } + + test(t, "https://try.gitea.io/user2/repo2") + test(t, "https://try.gitea.io/user2/repo2.git") + }) + + t.Run("ValidGitSshURL", func(t *testing.T) { + test := func(t *testing.T, url string) { + repo, err := repo_model.GetRepositoryByURL(db.DefaultContext, url) + + assert.NotNil(t, repo) + assert.NoError(t, err) + + assert.Equal(t, repo.ID, int64(2)) + assert.Equal(t, repo.OwnerID, int64(2)) + } + + test(t, "git+ssh://sshuser@try.gitea.io/user2/repo2") + test(t, "git+ssh://sshuser@try.gitea.io/user2/repo2.git") + + test(t, "git+ssh://try.gitea.io/user2/repo2") + test(t, "git+ssh://try.gitea.io/user2/repo2.git") + }) + + t.Run("ValidImplicitSshURL", func(t *testing.T) { + test := func(t *testing.T, url string) { + repo, err := repo_model.GetRepositoryByURL(db.DefaultContext, url) + + assert.NotNil(t, repo) + assert.NoError(t, err) + + assert.Equal(t, repo.ID, int64(2)) + assert.Equal(t, repo.OwnerID, int64(2)) + } + + test(t, "sshuser@try.gitea.io:user2/repo2") + test(t, "sshuser@try.gitea.io:user2/repo2.git") + + test(t, "try.gitea.io:user2/repo2") + test(t, "try.gitea.io:user2/repo2.git") + }) +} diff --git a/routers/api/packages/npm/npm.go b/routers/api/packages/npm/npm.go index 0d25f173e..51b34d3e2 100644 --- a/routers/api/packages/npm/npm.go +++ b/routers/api/packages/npm/npm.go @@ -13,6 +13,9 @@ import ( "code.gitea.io/gitea/models/db" packages_model "code.gitea.io/gitea/models/packages" + access_model "code.gitea.io/gitea/models/perm/access" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/modules/context" packages_module "code.gitea.io/gitea/modules/packages" npm_module "code.gitea.io/gitea/modules/packages/npm" @@ -166,6 +169,26 @@ func UploadPackage(ctx *context.Context) { return } + repo, err := repo_model.GetRepositoryByURL(ctx, npmPackage.Metadata.Repository.URL) + if err == nil { + canWrite := repo.OwnerID == ctx.Doer.ID + + if !canWrite { + perms, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer) + if err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + + canWrite = perms.CanWrite(unit.TypePackages) + } + + if !canWrite { + apiError(ctx, http.StatusForbidden, "no permission to upload this package") + return + } + } + buf, err := packages_module.CreateHashedBufferFromReader(bytes.NewReader(npmPackage.Data), 32*1024*1024) if err != nil { apiError(ctx, http.StatusInternalServerError, err) @@ -217,6 +240,13 @@ func UploadPackage(ctx *context.Context) { } } + if repo != nil { + if err := packages_model.SetRepositoryLink(ctx, pv.PackageID, repo.ID); err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + } + ctx.Status(http.StatusCreated) }