@@ -18,6 +18,7 @@ import ( |
| 18 | 18 | |
| 19 | 19 | "github.com/tenseleyFlow/shithub/internal/auth/policy" |
| 20 | 20 | "github.com/tenseleyFlow/shithub/internal/infra/storage" |
| 21 | + "github.com/tenseleyFlow/shithub/internal/orgs" |
| 21 | 22 | reposdb "github.com/tenseleyFlow/shithub/internal/repos/sqlc" |
| 22 | 23 | usersdb "github.com/tenseleyFlow/shithub/internal/users/sqlc" |
| 23 | 24 | ) |
@@ -95,16 +96,30 @@ func PrepareDispatch(ctx context.Context, deps SSHDispatchDeps, in SSHDispatchIn |
| 95 | 96 | return nil, parsed, ErrSSHSuspended |
| 96 | 97 | } |
| 97 | 98 | |
| 98 | | - owner, err := uq.GetUserByUsername(ctx, deps.Pool, parsed.Owner) |
| 99 | + // Owner can be a user OR an org; orgs.Resolve hits the principals |
| 100 | + // table to dispatch on kind. Mirrors the same lookup the HTTP git |
| 101 | + // handler uses (web/handlers/githttp/handler.go::lookupRepo) — see |
| 102 | + // the regression that #20 fixed for the HTTPS path; this is the SSH |
| 103 | + // twin of that bug. |
| 104 | + principal, err := orgs.Resolve(ctx, deps.Pool, parsed.Owner) |
| 99 | 105 | if err != nil { |
| 100 | | - // Unknown owner — surface as not-found regardless of whether |
| 101 | | - // the row never existed or was soft-deleted. |
| 102 | 106 | return nil, parsed, ErrSSHRepoNotFound |
| 103 | 107 | } |
| 104 | | - repo, err := rq.GetRepoByOwnerUserAndName(ctx, deps.Pool, reposdb.GetRepoByOwnerUserAndNameParams{ |
| 105 | | - OwnerUserID: pgtype.Int8{Int64: owner.ID, Valid: true}, |
| 106 | | - Name: parsed.Repo, |
| 107 | | - }) |
| 108 | + var repo reposdb.Repo |
| 109 | + switch principal.Kind { |
| 110 | + case orgs.PrincipalUser: |
| 111 | + repo, err = rq.GetRepoByOwnerUserAndName(ctx, deps.Pool, reposdb.GetRepoByOwnerUserAndNameParams{ |
| 112 | + OwnerUserID: pgtype.Int8{Int64: principal.ID, Valid: true}, |
| 113 | + Name: parsed.Repo, |
| 114 | + }) |
| 115 | + case orgs.PrincipalOrg: |
| 116 | + repo, err = rq.GetRepoByOwnerOrgAndName(ctx, deps.Pool, reposdb.GetRepoByOwnerOrgAndNameParams{ |
| 117 | + OwnerOrgID: pgtype.Int8{Int64: principal.ID, Valid: true}, |
| 118 | + Name: parsed.Repo, |
| 119 | + }) |
| 120 | + default: |
| 121 | + return nil, parsed, ErrSSHRepoNotFound |
| 122 | + } |
| 108 | 123 | if err != nil { |
| 109 | 124 | if errors.Is(err, pgx.ErrNoRows) { |
| 110 | 125 | return nil, parsed, ErrSSHRepoNotFound |
@@ -144,7 +159,7 @@ func PrepareDispatch(ctx context.Context, deps SSHDispatchDeps, in SSHDispatchIn |
| 144 | 159 | if err != nil { |
| 145 | 160 | return nil, parsed, fmt.Errorf("%w: %v", ErrSSHInternal, err) |
| 146 | 161 | } |
| 147 | | - env := buildSSHEnv(user, owner, repo, in.RemoteIP, requestID) |
| 162 | + env := buildSSHEnv(user, parsed.Owner, repo, in.RemoteIP, requestID) |
| 148 | 163 | |
| 149 | 164 | return &SSHDispatchResult{ |
| 150 | 165 | Argv0: string(parsed.Service), |
@@ -180,18 +195,28 @@ func FriendlyMessageFor(err error, requestID string) string { |
| 180 | 195 | // buildSSHEnv assembles the SHITHUB_* env vars that S14's hooks read. |
| 181 | 196 | // The shape matches the HTTP path so receive-pack hooks see identical |
| 182 | 197 | // vars regardless of transport. |
| 183 | | -func buildSSHEnv(user usersdb.User, owner usersdb.User, repo reposdb.Repo, remoteIP, requestID string) []string { |
| 198 | +func buildSSHEnv(user usersdb.User, ownerName string, repo reposdb.Repo, remoteIP, requestID string) []string { |
| 184 | 199 | return []string{ |
| 185 | 200 | "SHITHUB_USER_ID=" + strconv.FormatInt(user.ID, 10), |
| 186 | 201 | "SHITHUB_USERNAME=" + user.Username, |
| 187 | 202 | "SHITHUB_REPO_ID=" + strconv.FormatInt(repo.ID, 10), |
| 188 | | - "SHITHUB_REPO_FULL_NAME=" + owner.Username + "/" + repo.Name, |
| 203 | + "SHITHUB_REPO_FULL_NAME=" + ownerName + "/" + repo.Name, |
| 189 | 204 | "SHITHUB_PROTOCOL=ssh", |
| 190 | 205 | "SHITHUB_REMOTE_IP=" + remoteIP, |
| 191 | 206 | "SHITHUB_REQUEST_ID=" + requestID, |
| 192 | 207 | // PATH must be inherited so the exec'd git binary can find its |
| 193 | 208 | // sub-helpers (git-pack-objects, git-index-pack, etc.). |
| 194 | 209 | "PATH=" + os.Getenv("PATH"), |
| 210 | + // safe.directory: when sshd runs ssh-shell as the `git` user |
| 211 | + // but the bare repo dir is owned by `shithub`, git's |
| 212 | + // dubious-ownership check rejects the invocation. We're |
| 213 | + // invoking on a path shithubd resolved (not user input), so |
| 214 | + // the trust gate already happened upstream; tell git to |
| 215 | + // trust this directory. Inline GIT_CONFIG_* avoids touching |
| 216 | + // /etc/gitconfig and confines the override to this exec. |
| 217 | + "GIT_CONFIG_COUNT=1", |
| 218 | + "GIT_CONFIG_KEY_0=safe.directory", |
| 219 | + "GIT_CONFIG_VALUE_0=*", |
| 195 | 220 | } |
| 196 | 221 | } |
| 197 | 222 | |