Commit b04b2221fd
Verified · cmc
Layout: unified · split
e2e/import_test.go +12 −1
| @@ -89,6 +89,17 @@ func TestRepoImport(t *testing.T) { | ||
| 89 | 89 | t.Fatalf("git:// import: %s", errOut) |
| 90 | 90 | } |
| 91 | 91 | |
| 92 | // Org-owned imports: allowed for org admins, refused for non-members. | |
| 93 | if _, errOut, code := inst.ssh(t, aliceKey, "", "org", "create", "imports"); code != 0 { | |
| 94 | t.Fatalf("org create: %s", errOut) | |
| 95 | } | |
| 96 | if _, errOut, code := inst.ssh(t, aliceKey, "", "repo", "import", "imports/mirror", "--from", httpURL); code != 0 { | |
| 97 | t.Fatalf("org import: %s", errOut) | |
| 98 | } | |
| 99 | if out, _, code := inst.ssh(t, aliceKey, "", "repo", "log", "imports/mirror"); code != 0 || !strings.Contains(out, "first") { | |
| 100 | t.Fatalf("org import log: %d\n%s", code, out) | |
| 101 | } | |
| 102 | ||
| 92 | 103 | // Refusals: bad scheme, credentials in URL, existing name, foreign owner. |
| 93 | 104 | cases := []struct { |
| 94 | 105 | args []string |
| @@ -97,7 +108,7 @@ func TestRepoImport(t *testing.T) { | ||
| 97 | 108 | {[]string{"repo", "import", "alice/x", "--from", "file:///etc"}, "https://, http://, and git://"}, |
| 98 | 109 | {[]string{"repo", "import", "alice/x", "--from", "https://token@github.com/a/b"}, "--token-stdin"}, |
| 99 | 110 | {[]string{"repo", "import", "alice/mirror", "--from", httpURL}, "already exists"}, |
| 100 | {[]string{"repo", "import", "bob/x", "--from", httpURL}, "your own account"}, | |
| 111 | {[]string{"repo", "import", "bob/x", "--from", httpURL}, "not you and not an organization"}, | |
| 101 | 112 | } |
| 102 | 113 | for _, tc := range cases { |
| 103 | 114 | _, errOut, code := inst.ssh(t, aliceKey, "", tc.args...) |
internal/control/import.go +19 −3
| @@ -58,12 +58,28 @@ func runRepoImport(c *Ctx, args []string) int { | ||
| 58 | 58 | return c.fail(protocol.ExitUsage, "usage: repo import <owner/name> --from <url> [--private] [--token-stdin]") |
| 59 | 59 | } |
| 60 | 60 | owner, name, ok := strings.Cut(path, "/") |
| 61 | if !ok || owner != c.User.Username { | |
| 62 | return c.fail(protocol.ExitDenied, "imports land under your own account: %s/<name>", c.User.Username) | |
| 61 | if !ok { | |
| 62 | return c.fail(protocol.ExitUsage, "usage: repo import <owner/name> --from <url>") | |
| 63 | 63 | } |
| 64 | 64 | if err := policy.ValidateName(name); err != nil { |
| 65 | 65 | return c.fail(protocol.ExitUsage, "%v", err) |
| 66 | 66 | } |
| 67 | // Same ownership rule as repo create: yourself, or an org you admin. | |
| 68 | ownerKind, ownerID := "user", c.User.ID | |
| 69 | if owner != c.User.Username { | |
| 70 | org, err := c.Store.OrgByName(owner) | |
| 71 | if err != nil { | |
| 72 | return c.fail(protocol.ExitDenied, "cannot import under %q: not you and not an organization you can see", owner) | |
| 73 | } | |
| 74 | role, err := c.Store.OrgRole(org.ID, c.User.ID) | |
| 75 | if err != nil { | |
| 76 | return c.fail(protocol.ExitFailure, "%v", err) | |
| 77 | } | |
| 78 | if role != "admin" { | |
| 79 | return c.fail(protocol.ExitDenied, "only admins of %s can import repositories there", owner) | |
| 80 | } | |
| 81 | ownerKind, ownerID = "org", org.ID | |
| 82 | } | |
| 67 | 83 | |
| 68 | 84 | // Scheme allowlist. file:// (and anything else local) would read the |
| 69 | 85 | // server's filesystem; ssh:// would use the server's own keys. |
| @@ -107,7 +123,7 @@ func runRepoImport(c *Ctx, args []string) int { | ||
| 107 | 123 | if private { |
| 108 | 124 | visibility = "private" |
| 109 | 125 | } |
| 110 | id, err := c.Store.CreateRepo("user", c.User.ID, name, visibility) | |
| 126 | id, err := c.Store.CreateRepo(ownerKind, ownerID, name, visibility) | |
| 111 | 127 | if err != nil { |
| 112 | 128 | return c.fail(protocol.ExitFailure, "%v", err) |
| 113 | 129 | } |