Monorepo for Tangled tangled.org

knotserver/xrpc: use repo DID resolution in all handlers #1136

open opened by oyster.cafe targeting master from oyster.cafe/tangled-core: master
Labels

None yet.

assignee

None yet.

Participants 1
AT URI
at://did:plc:3fwecdnvtcscjnrx2p4n7alz/sh.tangled.repo.pull/3mgprvt2ef522
+227 -115
Diff #13
+127 -26
knotserver/xrpc/create_repo.go
··· 6 6 "errors" 7 7 "fmt" 8 8 "net/http" 9 - "path/filepath" 9 + "os" 10 10 "strings" 11 11 "time" 12 12 13 - comatproto "github.com/bluesky-social/indigo/api/atproto" 14 13 "github.com/bluesky-social/indigo/atproto/syntax" 15 - "github.com/bluesky-social/indigo/xrpc" 14 + indigoxrpc "github.com/bluesky-social/indigo/xrpc" 16 15 securejoin "github.com/cyphar/filepath-securejoin" 17 16 gogit "github.com/go-git/go-git/v5" 18 17 "tangled.org/core/api/tangled" 19 18 "tangled.org/core/hook" 20 19 "tangled.org/core/knotserver/git" 20 + "tangled.org/core/knotserver/repodid" 21 21 "tangled.org/core/rbac" 22 22 xrpcerr "tangled.org/core/xrpc/errors" 23 23 ) ··· 51 51 return 52 52 } 53 53 54 - rkey := data.Rkey 54 + repoName := data.Name 55 55 56 - ident, err := h.Resolver.ResolveIdent(r.Context(), actorDid.String()) 57 - if err != nil || ident.Handle.IsInvalidHandle() { 58 - fail(xrpcerr.GenericError(err)) 56 + if repoName == "" { 57 + fail(xrpcerr.GenericError(fmt.Errorf("repository name is required"))) 59 58 return 60 59 } 61 60 62 - xrpcc := xrpc.Client{ 63 - Host: ident.PDSEndpoint(), 61 + defaultBranch := h.Config.Repo.MainBranch 62 + if data.DefaultBranch != nil && *data.DefaultBranch != "" { 63 + defaultBranch = *data.DefaultBranch 64 64 } 65 65 66 - resp, err := comatproto.RepoGetRecord(r.Context(), &xrpcc, "", tangled.RepoNSID, actorDid.String(), rkey) 67 - if err != nil { 66 + if err := validateRepoName(repoName); err != nil { 67 + l.Error("creating repo", "error", err.Error()) 68 68 fail(xrpcerr.GenericError(err)) 69 69 return 70 70 } 71 71 72 - repo := resp.Value.Val.(*tangled.Repo) 72 + var repoDid string 73 + var prepared *repodid.PreparedDID 73 74 74 - defaultBranch := h.Config.Repo.MainBranch 75 - if data.DefaultBranch != nil && *data.DefaultBranch != "" { 76 - defaultBranch = *data.DefaultBranch 75 + knotServiceUrl := "https://" + h.Config.Server.Hostname 76 + if h.Config.Server.Dev { 77 + knotServiceUrl = "http://" + h.Config.Server.Hostname 77 78 } 78 79 79 - if err := validateRepoName(repo.Name); err != nil { 80 - l.Error("creating repo", "error", err.Error()) 81 - fail(xrpcerr.GenericError(err)) 80 + switch { 81 + case data.RepoDid != nil && strings.HasPrefix(*data.RepoDid, "did:web:"): 82 + if err := repodid.VerifyRepoDIDWeb(r.Context(), h.Resolver, *data.RepoDid, knotServiceUrl); err != nil { 83 + l.Error("verifying did:web", "error", err.Error()) 84 + writeError(w, xrpcerr.GenericError(err), http.StatusBadRequest) 85 + return 86 + } 87 + 88 + exists, err := h.Db.RepoDidExists(*data.RepoDid) 89 + if err != nil { 90 + l.Error("checking did:web uniqueness", "error", err.Error()) 91 + writeError(w, xrpcerr.GenericError(err), http.StatusInternalServerError) 92 + return 93 + } 94 + if exists { 95 + writeError(w, xrpcerr.GenericError(fmt.Errorf("did:web %s is already in use on this knot", *data.RepoDid)), http.StatusConflict) 96 + return 97 + } 98 + 99 + repoDid = *data.RepoDid 100 + 101 + case data.RepoDid != nil && *data.RepoDid != "": 102 + writeError(w, xrpcerr.GenericError(fmt.Errorf("only did:web is accepted as a user-provided repo DID; did:plc is auto-generated")), http.StatusBadRequest) 82 103 return 104 + 105 + default: 106 + existingDid, dbErr := h.Db.GetRepoDid(actorDid.String(), repoName) 107 + if dbErr == nil && existingDid != "" { 108 + didRepoPath, _ := securejoin.SecureJoin(h.Config.Repo.ScanPath, existingDid) 109 + if _, statErr := os.Stat(didRepoPath); statErr == nil { 110 + l.Info("repo already exists from previous attempt", "repoDid", existingDid) 111 + output := tangled.RepoCreate_Output{RepoDid: &existingDid} 112 + writeJson(w, &output) 113 + return 114 + } 115 + l.Warn("stale repo key found without directory, cleaning up", "repoDid", existingDid) 116 + if delErr := h.Db.DeleteRepoKey(existingDid); delErr != nil { 117 + l.Error("failed to clean up stale repo key", "repoDid", existingDid, "error", delErr.Error()) 118 + writeError(w, xrpcerr.GenericError(fmt.Errorf("failed to clean up stale state, retry later")), http.StatusInternalServerError) 119 + return 120 + } 121 + } 122 + 123 + var prepErr error 124 + prepared, prepErr = repodid.PrepareRepoDID(h.Config.Server.PlcUrl, knotServiceUrl) 125 + if prepErr != nil { 126 + l.Error("preparing repo DID", "error", prepErr.Error()) 127 + writeError(w, xrpcerr.GenericError(prepErr), http.StatusInternalServerError) 128 + return 129 + } 130 + repoDid = prepared.RepoDid 131 + 132 + atUri := fmt.Sprintf("at://%s/%s/%s", actorDid, tangled.RepoNSID, data.Rkey) 133 + if err := h.Db.StoreRepoKey(repoDid, prepared.SigningKeyRaw, actorDid.String(), repoName, atUri); err != nil { 134 + if strings.Contains(err.Error(), "UNIQUE constraint failed") { 135 + writeError(w, xrpcerr.GenericError(fmt.Errorf("repository %s already being created", repoName)), http.StatusConflict) 136 + return 137 + } 138 + l.Error("claiming repo key slot", "error", err.Error()) 139 + writeError(w, xrpcerr.GenericError(err), http.StatusInternalServerError) 140 + return 141 + } 142 + } 143 + 144 + l = l.With("repoDid", repoDid) 145 + 146 + repoPath, _ := securejoin.SecureJoin(h.Config.Repo.ScanPath, repoDid) 147 + rbacPath := repoDid 148 + 149 + cleanup := func() { 150 + if rmErr := os.RemoveAll(repoPath); rmErr != nil { 151 + l.Error("failed to clean up repo directory", "path", repoPath, "error", rmErr.Error()) 152 + } 83 153 } 84 154 85 - relativeRepoPath := filepath.Join(actorDid.String(), repo.Name) 86 - repoPath, _ := securejoin.SecureJoin(h.Config.Repo.ScanPath, relativeRepoPath) 155 + cleanupAll := func() { 156 + cleanup() 157 + if delErr := h.Db.DeleteRepoKey(repoDid); delErr != nil { 158 + l.Error("failed to clean up repo key", "error", delErr.Error()) 159 + } 160 + } 87 161 88 162 if data.Source != nil && *data.Source != "" { 89 163 err = git.Fork(repoPath, *data.Source, h.Config) 90 164 if err != nil { 91 165 l.Error("forking repo", "error", err.Error()) 166 + cleanupAll() 92 167 writeError(w, xrpcerr.GenericError(err), http.StatusInternalServerError) 93 168 return 94 169 } ··· 96 171 err = git.InitBare(repoPath, defaultBranch) 97 172 if err != nil { 98 173 l.Error("initializing bare repo", "error", err.Error()) 174 + cleanupAll() 99 175 if errors.Is(err, gogit.ErrRepositoryAlreadyExists) { 100 176 fail(xrpcerr.RepoExistsError("repository already exists")) 101 177 return 102 - } else { 103 - writeError(w, xrpcerr.GenericError(err), http.StatusInternalServerError) 178 + } 179 + writeError(w, xrpcerr.GenericError(err), http.StatusInternalServerError) 180 + return 181 + } 182 + } 183 + 184 + if data.RepoDid != nil && strings.HasPrefix(*data.RepoDid, "did:web:") { 185 + webAtUri := fmt.Sprintf("at://%s/%s/%s", actorDid, tangled.RepoNSID, data.Rkey) 186 + if err := h.Db.StoreRepoDidWeb(repoDid, actorDid.String(), repoName, webAtUri); err != nil { 187 + cleanupAll() 188 + if strings.Contains(err.Error(), "UNIQUE constraint failed") { 189 + writeError(w, xrpcerr.GenericError(fmt.Errorf("did:web %s is already in use", repoDid)), http.StatusConflict) 104 190 return 105 191 } 192 + l.Error("storing did:web repo entry", "error", err.Error()) 193 + writeError(w, xrpcerr.GenericError(err), http.StatusInternalServerError) 194 + return 195 + } 196 + } 197 + 198 + if prepared != nil { 199 + plcCtx, plcCancel := context.WithTimeout(context.Background(), 30*time.Second) 200 + defer plcCancel() 201 + if err := prepared.Submit(plcCtx); err != nil { 202 + l.Error("submitting to PLC directory", "error", err.Error()) 203 + cleanupAll() 204 + writeError(w, xrpcerr.GenericError(fmt.Errorf("PLC directory submission failed: %w", err)), http.StatusInternalServerError) 205 + return 106 206 } 107 207 } 108 208 109 209 // add perms for this user to access the repo 110 - err = h.Enforcer.AddRepo(actorDid.String(), rbac.ThisServer, relativeRepoPath) 210 + err = h.Enforcer.AddRepo(actorDid.String(), rbac.ThisServer, rbacPath) 111 211 if err != nil { 112 212 l.Error("adding repo permissions", "error", err.Error()) 213 + cleanupAll() 113 214 writeError(w, xrpcerr.GenericError(err), http.StatusInternalServerError) 114 215 return 115 216 } ··· 127 228 // Therefore, to bypass the local tap, requestCrawl directly to the knotmirror. 128 229 go func() { 129 230 if h.Config.Server.Dev { 130 - repoAt := fmt.Sprintf("at://%s/%s/%s", actorDid, tangled.RepoNSID, rkey) 231 + repoAt := fmt.Sprintf("at://%s/%s/%s", actorDid, tangled.RepoNSID, data.Rkey) 131 232 rCtx, rCancel := context.WithTimeout(context.Background(), 10*time.Second) 132 233 defer rCancel() 133 234 h.requestCrawl(rCtx, &tangled.SyncRequestCrawl_Input{ ··· 137 238 } 138 239 }() 139 240 140 - w.WriteHeader(http.StatusOK) 241 + writeJson(w, &tangled.RepoCreate_Output{RepoDid: &repoDid}) 141 242 } 142 243 143 244 func (h *Xrpc) requestCrawl(ctx context.Context, input *tangled.SyncRequestCrawl_Input) error { 144 245 h.Logger.Info("requesting crawl", "mirrors", h.Config.KnotMirrors) 145 246 for _, knotmirror := range h.Config.KnotMirrors { 146 - xrpcc := xrpc.Client{Host: knotmirror} 247 + xrpcc := indigoxrpc.Client{Host: knotmirror} 147 248 if err := tangled.SyncRequestCrawl(ctx, &xrpcc, input); err != nil { 148 249 h.Logger.Error("error requesting crawl", "err", err) 149 250 } else {
+10 -7
knotserver/xrpc/delete_branch.go
··· 8 8 comatproto "github.com/bluesky-social/indigo/api/atproto" 9 9 "github.com/bluesky-social/indigo/atproto/syntax" 10 10 "github.com/bluesky-social/indigo/xrpc" 11 - securejoin "github.com/cyphar/filepath-securejoin" 12 11 "tangled.org/core/api/tangled" 13 12 "tangled.org/core/knotserver/git" 14 13 "tangled.org/core/rbac" ··· 57 56 } 58 57 59 58 repo := resp.Value.Val.(*tangled.Repo) 60 - didPath, err := securejoin.SecureJoin(ident.DID.String(), repo.Name) 59 + repoDid, err := x.Db.GetRepoDid(ident.DID.String(), repo.Name) 61 60 if err != nil { 62 - fail(xrpcerr.GenericError(err)) 61 + fail(xrpcerr.RepoNotFoundError) 62 + return 63 + } 64 + repoPath, _, _, err := x.Db.ResolveRepoDIDOnDisk(x.Config.Repo.ScanPath, repoDid) 65 + if err != nil { 66 + fail(xrpcerr.RepoNotFoundError) 63 67 return 64 68 } 65 69 66 - if ok, err := x.Enforcer.IsPushAllowed(actorDid.String(), rbac.ThisServer, didPath); !ok || err != nil { 67 - l.Error("insufficent permissions", "did", actorDid.String(), "repo", didPath) 70 + if ok, err := x.Enforcer.IsPushAllowed(actorDid.String(), rbac.ThisServer, repoDid); !ok || err != nil { 71 + l.Error("insufficent permissions", "did", actorDid.String(), "repo", repoDid) 68 72 writeError(w, xrpcerr.AccessControlError(actorDid.String()), http.StatusUnauthorized) 69 73 return 70 74 } 71 75 72 - path, _ := securejoin.SecureJoin(x.Config.Repo.ScanPath, didPath) 73 - gr, err := git.PlainOpen(path) 76 + gr, err := git.PlainOpen(repoPath) 74 77 if err != nil { 75 78 fail(xrpcerr.GenericError(err)) 76 79 return
+15 -9
knotserver/xrpc/delete_repo.go
··· 5 5 "fmt" 6 6 "net/http" 7 7 "os" 8 - "path/filepath" 9 8 10 9 comatproto "github.com/bluesky-social/indigo/api/atproto" 11 10 "github.com/bluesky-social/indigo/atproto/syntax" 12 11 "github.com/bluesky-social/indigo/xrpc" 13 - securejoin "github.com/cyphar/filepath-securejoin" 14 12 "tangled.org/core/api/tangled" 15 13 "tangled.org/core/rbac" 16 14 xrpcerr "tangled.org/core/xrpc/errors" ··· 61 59 return 62 60 } 63 61 64 - relativeRepoPath := filepath.Join(did, name) 65 - isDeleteAllowed, err := x.Enforcer.IsRepoDeleteAllowed(actorDid.String(), rbac.ThisServer, relativeRepoPath) 62 + repoDid, err := x.Db.GetRepoDid(did, name) 66 63 if err != nil { 67 - fail(xrpcerr.GenericError(err)) 64 + fail(xrpcerr.RepoNotFoundError) 68 65 return 69 66 } 70 - if !isDeleteAllowed { 71 - fail(xrpcerr.AccessControlError(actorDid.String())) 67 + repoPath, _, _, err := x.Db.ResolveRepoDIDOnDisk(x.Config.Repo.ScanPath, repoDid) 68 + if err != nil { 69 + fail(xrpcerr.RepoNotFoundError) 72 70 return 73 71 } 74 72 75 - repoPath, err := securejoin.SecureJoin(x.Config.Repo.ScanPath, relativeRepoPath) 73 + isDeleteAllowed, err := x.Enforcer.IsRepoDeleteAllowed(actorDid.String(), rbac.ThisServer, repoDid) 76 74 if err != nil { 77 75 fail(xrpcerr.GenericError(err)) 78 76 return 79 77 } 78 + if !isDeleteAllowed { 79 + fail(xrpcerr.AccessControlError(actorDid.String())) 80 + return 81 + } 80 82 81 83 err = os.RemoveAll(repoPath) 82 84 if err != nil { ··· 85 87 return 86 88 } 87 89 88 - err = x.Enforcer.RemoveRepo(did, rbac.ThisServer, relativeRepoPath) 90 + err = x.Enforcer.RemoveRepo(did, rbac.ThisServer, repoDid) 89 91 if err != nil { 90 92 l.Error("failed to delete repo from enforcer", "error", err.Error()) 91 93 writeError(w, xrpcerr.GenericError(err), http.StatusInternalServerError) 92 94 return 93 95 } 94 96 97 + if err := x.Db.DeleteRepoKey(repoDid); err != nil { 98 + l.Error("failed to delete repo key", "error", err.Error()) 99 + } 100 + 95 101 w.WriteHeader(http.StatusOK) 96 102 }
+11 -9
knotserver/xrpc/fork_status.go
··· 7 7 "path/filepath" 8 8 9 9 "github.com/bluesky-social/indigo/atproto/syntax" 10 - securejoin "github.com/cyphar/filepath-securejoin" 11 10 "tangled.org/core/api/tangled" 12 11 "tangled.org/core/knotserver/git" 13 12 "tangled.org/core/rbac" ··· 51 50 name = filepath.Base(source) 52 51 } 53 52 54 - relativeRepoPath := filepath.Join(did, name) 55 - 56 - if ok, err := x.Enforcer.IsPushAllowed(actorDid.String(), rbac.ThisServer, relativeRepoPath); !ok || err != nil { 57 - l.Error("insufficient permissions", "did", actorDid.String(), "repo", relativeRepoPath) 58 - writeError(w, xrpcerr.AccessControlError(actorDid.String()), http.StatusUnauthorized) 53 + repoDid, err := x.Db.GetRepoDid(did, name) 54 + if err != nil { 55 + fail(xrpcerr.RepoNotFoundError) 59 56 return 60 57 } 61 - 62 - repoPath, err := securejoin.SecureJoin(x.Config.Repo.ScanPath, relativeRepoPath) 58 + repoPath, _, _, err := x.Db.ResolveRepoDIDOnDisk(x.Config.Repo.ScanPath, repoDid) 63 59 if err != nil { 64 - fail(xrpcerr.GenericError(err)) 60 + fail(xrpcerr.RepoNotFoundError) 61 + return 62 + } 63 + 64 + if ok, err := x.Enforcer.IsPushAllowed(actorDid.String(), rbac.ThisServer, repoDid); !ok || err != nil { 65 + l.Error("insufficient permissions", "did", actorDid.String(), "repo", repoDid) 66 + writeError(w, xrpcerr.AccessControlError(actorDid.String()), http.StatusUnauthorized) 65 67 return 66 68 } 67 69
+11 -10
knotserver/xrpc/fork_sync.go
··· 4 4 "encoding/json" 5 5 "fmt" 6 6 "net/http" 7 - "path/filepath" 8 7 9 8 "github.com/bluesky-social/indigo/atproto/syntax" 10 - securejoin "github.com/cyphar/filepath-securejoin" 11 9 "tangled.org/core/api/tangled" 12 10 "tangled.org/core/knotserver/git" 13 11 "tangled.org/core/rbac" ··· 42 40 return 43 41 } 44 42 45 - relativeRepoPath := filepath.Join(did, name) 46 - 47 - if ok, err := x.Enforcer.IsPushAllowed(actorDid.String(), rbac.ThisServer, relativeRepoPath); !ok || err != nil { 48 - l.Error("insufficient permissions", "did", actorDid.String(), "repo", relativeRepoPath) 49 - writeError(w, xrpcerr.AccessControlError(actorDid.String()), http.StatusUnauthorized) 43 + repoDid, err := x.Db.GetRepoDid(did, name) 44 + if err != nil { 45 + fail(xrpcerr.RepoNotFoundError) 50 46 return 51 47 } 52 - 53 - repoPath, err := securejoin.SecureJoin(x.Config.Repo.ScanPath, relativeRepoPath) 48 + repoPath, _, _, err := x.Db.ResolveRepoDIDOnDisk(x.Config.Repo.ScanPath, repoDid) 54 49 if err != nil { 55 - fail(xrpcerr.GenericError(err)) 50 + fail(xrpcerr.RepoNotFoundError) 51 + return 52 + } 53 + 54 + if ok, err := x.Enforcer.IsPushAllowed(actorDid.String(), rbac.ThisServer, repoDid); !ok || err != nil { 55 + l.Error("insufficient permissions", "did", actorDid.String(), "repo", repoDid) 56 + writeError(w, xrpcerr.AccessControlError(actorDid.String()), http.StatusUnauthorized) 56 57 return 57 58 } 58 59
+8 -10
knotserver/xrpc/hidden_ref.go
··· 8 8 comatproto "github.com/bluesky-social/indigo/api/atproto" 9 9 "github.com/bluesky-social/indigo/atproto/syntax" 10 10 "github.com/bluesky-social/indigo/xrpc" 11 - securejoin "github.com/cyphar/filepath-securejoin" 12 11 "tangled.org/core/api/tangled" 13 12 "tangled.org/core/knotserver/git" 14 13 "tangled.org/core/rbac" ··· 63 62 } 64 63 65 64 repo := resp.Value.Val.(*tangled.Repo) 66 - didPath, err := securejoin.SecureJoin(actorDid.String(), repo.Name) 65 + repoDid, err := x.Db.GetRepoDid(actorDid.String(), repo.Name) 67 66 if err != nil { 68 - fail(xrpcerr.GenericError(err)) 67 + fail(xrpcerr.RepoNotFoundError) 69 68 return 70 69 } 71 - 72 - if ok, err := x.Enforcer.IsPushAllowed(actorDid.String(), rbac.ThisServer, didPath); !ok || err != nil { 73 - l.Error("insufficient permissions", "did", actorDid.String(), "repo", didPath) 74 - writeError(w, xrpcerr.AccessControlError(actorDid.String()), http.StatusUnauthorized) 70 + repoPath, _, _, err := x.Db.ResolveRepoDIDOnDisk(x.Config.Repo.ScanPath, repoDid) 71 + if err != nil { 72 + fail(xrpcerr.RepoNotFoundError) 75 73 return 76 74 } 77 75 78 - repoPath, err := securejoin.SecureJoin(x.Config.Repo.ScanPath, didPath) 79 - if err != nil { 80 - fail(xrpcerr.GenericError(err)) 76 + if ok, err := x.Enforcer.IsPushAllowed(actorDid.String(), rbac.ThisServer, repoDid); !ok || err != nil { 77 + l.Error("insufficient permissions", "did", actorDid.String(), "repo", repoDid) 78 + writeError(w, xrpcerr.AccessControlError(actorDid.String()), http.StatusUnauthorized) 81 79 return 82 80 } 83 81
+8 -10
knotserver/xrpc/merge.go
··· 7 7 "net/http" 8 8 9 9 "github.com/bluesky-social/indigo/atproto/syntax" 10 - securejoin "github.com/cyphar/filepath-securejoin" 11 10 "tangled.org/core/api/tangled" 12 11 "tangled.org/core/knotserver/git" 13 12 "tangled.org/core/patchutil" ··· 43 42 return 44 43 } 45 44 46 - relativeRepoPath, err := securejoin.SecureJoin(did, name) 45 + repoDid, err := x.Db.GetRepoDid(did, name) 47 46 if err != nil { 48 - fail(xrpcerr.GenericError(err)) 47 + fail(xrpcerr.RepoNotFoundError) 49 48 return 50 49 } 51 - 52 - if ok, err := x.Enforcer.IsPushAllowed(actorDid.String(), rbac.ThisServer, relativeRepoPath); !ok || err != nil { 53 - l.Error("insufficient permissions", "did", actorDid.String(), "repo", relativeRepoPath) 54 - writeError(w, xrpcerr.AccessControlError(actorDid.String()), http.StatusUnauthorized) 50 + repoPath, _, _, err := x.Db.ResolveRepoDIDOnDisk(x.Config.Repo.ScanPath, repoDid) 51 + if err != nil { 52 + fail(xrpcerr.RepoNotFoundError) 55 53 return 56 54 } 57 55 58 - repoPath, err := securejoin.SecureJoin(x.Config.Repo.ScanPath, relativeRepoPath) 59 - if err != nil { 60 - fail(xrpcerr.GenericError(err)) 56 + if ok, err := x.Enforcer.IsPushAllowed(actorDid.String(), rbac.ThisServer, repoDid); !ok || err != nil { 57 + l.Error("insufficient permissions", "did", actorDid.String(), "repo", repoDid) 58 + writeError(w, xrpcerr.AccessControlError(actorDid.String()), http.StatusUnauthorized) 61 59 return 62 60 } 63 61
+4 -6
knotserver/xrpc/merge_check.go
··· 6 6 "fmt" 7 7 "net/http" 8 8 9 - securejoin "github.com/cyphar/filepath-securejoin" 10 9 "tangled.org/core/api/tangled" 11 10 "tangled.org/core/knotserver/git" 12 11 "tangled.org/core/patchutil" ··· 34 33 return 35 34 } 36 35 37 - relativeRepoPath, err := securejoin.SecureJoin(did, name) 36 + repoDid, err := x.Db.GetRepoDid(did, name) 38 37 if err != nil { 39 - fail(xrpcerr.GenericError(err)) 38 + fail(xrpcerr.RepoNotFoundError) 40 39 return 41 40 } 42 - 43 - repoPath, err := securejoin.SecureJoin(x.Config.Repo.ScanPath, relativeRepoPath) 41 + repoPath, _, _, err := x.Db.ResolveRepoDIDOnDisk(x.Config.Repo.ScanPath, repoDid) 44 42 if err != nil { 45 - fail(xrpcerr.GenericError(err)) 43 + fail(xrpcerr.RepoNotFoundError) 46 44 return 47 45 } 48 46
+9 -6
knotserver/xrpc/set_default_branch.go
··· 8 8 comatproto "github.com/bluesky-social/indigo/api/atproto" 9 9 "github.com/bluesky-social/indigo/atproto/syntax" 10 10 "github.com/bluesky-social/indigo/xrpc" 11 - securejoin "github.com/cyphar/filepath-securejoin" 12 11 "tangled.org/core/api/tangled" 13 12 "tangled.org/core/knotserver/git" 14 13 "tangled.org/core/rbac" ··· 59 58 } 60 59 61 60 repo := resp.Value.Val.(*tangled.Repo) 62 - didPath, err := securejoin.SecureJoin(actorDid.String(), repo.Name) 61 + repoDid, err := x.Db.GetRepoDid(actorDid.String(), repo.Name) 63 62 if err != nil { 64 - fail(xrpcerr.GenericError(err)) 63 + fail(xrpcerr.RepoNotFoundError) 64 + return 65 + } 66 + repoPath, _, _, err := x.Db.ResolveRepoDIDOnDisk(x.Config.Repo.ScanPath, repoDid) 67 + if err != nil { 68 + fail(xrpcerr.RepoNotFoundError) 65 69 return 66 70 } 67 71 68 - if ok, err := x.Enforcer.IsPushAllowed(actorDid.String(), rbac.ThisServer, didPath); !ok || err != nil { 72 + if ok, err := x.Enforcer.IsPushAllowed(actorDid.String(), rbac.ThisServer, repoDid); !ok || err != nil { 69 73 l.Error("insufficent permissions", "did", actorDid.String()) 70 74 writeError(w, xrpcerr.AccessControlError(actorDid.String()), http.StatusUnauthorized) 71 75 return 72 76 } 73 77 74 - path, _ := securejoin.SecureJoin(x.Config.Repo.ScanPath, didPath) 75 - gr, err := git.PlainOpen(path) 78 + gr, err := git.PlainOpen(repoPath) 76 79 if err != nil { 77 80 fail(xrpcerr.GenericError(err)) 78 81 return
+24 -22
knotserver/xrpc/xrpc.go
··· 4 4 "encoding/json" 5 5 "log/slog" 6 6 "net/http" 7 + "os" 8 + "path/filepath" 7 9 "strings" 8 10 9 11 securejoin "github.com/cyphar/filepath-securejoin" 12 + "github.com/go-chi/chi/v5" 10 13 "tangled.org/core/api/tangled" 11 14 "tangled.org/core/idresolver" 12 15 "tangled.org/core/jetstream" ··· 16 19 "tangled.org/core/rbac" 17 20 xrpcerr "tangled.org/core/xrpc/errors" 18 21 "tangled.org/core/xrpc/serviceauth" 19 - 20 - "github.com/go-chi/chi/v5" 21 22 ) 22 23 23 24 type Xrpc struct { ··· 78 79 return r 79 80 } 80 81 81 - // parseRepoParam parses a repo parameter in 'did/repoName' format and returns 82 - // the full repository path on disk 83 82 func (x *Xrpc) parseRepoParam(repo string) (string, error) { 84 - if repo == "" { 83 + if repo == "" || !strings.HasPrefix(repo, "did:") { 85 84 return "", xrpcerr.NewXrpcError( 86 85 xrpcerr.WithTag("InvalidRequest"), 87 - xrpcerr.WithMessage("missing repo parameter"), 86 + xrpcerr.WithMessage("missing or invalid repo parameter, expected a repo DID"), 88 87 ) 89 88 } 90 89 91 - // Parse repo string (did/repoName format) 92 - parts := strings.SplitN(repo, "/", 2) 93 - if len(parts) != 2 { 94 - return "", xrpcerr.NewXrpcError( 95 - xrpcerr.WithTag("InvalidRequest"), 96 - xrpcerr.WithMessage("invalid repo format, expected 'did/repoName'"), 97 - ) 90 + if !strings.Contains(repo, "/") { 91 + repoPath, _, _, err := x.Db.ResolveRepoDIDOnDisk(x.Config.Repo.ScanPath, repo) 92 + if err != nil { 93 + return "", xrpcerr.RepoNotFoundError 94 + } 95 + return repoPath, nil 98 96 } 99 97 100 - did := parts[0] 101 - repoName := parts[1] 98 + parts := strings.SplitN(repo, "/", 2) 99 + ownerDid, repoName := parts[0], parts[1] 100 + 101 + repoDid, err := x.Db.GetRepoDid(ownerDid, repoName) 102 + if err == nil { 103 + repoPath, _, _, resolveErr := x.Db.ResolveRepoDIDOnDisk(x.Config.Repo.ScanPath, repoDid) 104 + if resolveErr == nil { 105 + return repoPath, nil 106 + } 107 + } 102 108 103 - // Construct repository path using the same logic as didPath 104 - didRepoPath, err := securejoin.SecureJoin(did, repoName) 105 - if err != nil { 109 + repoPath, joinErr := securejoin.SecureJoin(x.Config.Repo.ScanPath, filepath.Join(ownerDid, repoName)) 110 + if joinErr != nil { 106 111 return "", xrpcerr.RepoNotFoundError 107 112 } 108 - 109 - repoPath, err := securejoin.SecureJoin(x.Config.Repo.ScanPath, didRepoPath) 110 - if err != nil { 113 + if _, statErr := os.Stat(repoPath); statErr != nil { 111 114 return "", xrpcerr.RepoNotFoundError 112 115 } 113 - 114 116 return repoPath, nil 115 117 } 116 118

History

14 rounds 0 comments
sign up or login to add to the discussion
1 commit
expand
knotserver/xrpc: use repo DID resolution in all handlers
no conflicts, ready to merge
expand 0 comments
1 commit
expand
knotserver/xrpc: use repo DID resolution in all handlers
expand 0 comments
1 commit
expand
knotserver/xrpc: use repo DID resolution in all handlers
expand 0 comments
1 commit
expand
knotserver/xrpc: use repo DID resolution in all handlers
expand 0 comments
1 commit
expand
knotserver/xrpc: use repo DID resolution in all handlers
expand 0 comments
1 commit
expand
knotserver/xrpc: use repo DID resolution in all handlers
expand 0 comments
1 commit
expand
knotserver/xrpc: use repo DID resolution in all handlers
expand 0 comments
1 commit
expand
knotserver/xrpc: use repo DID resolution in all handlers
expand 0 comments
1 commit
expand
knotserver/xrpc: use repo DID resolution in all handlers
expand 0 comments
1 commit
expand
knotserver/xrpc: use repo DID resolution in all handlers
expand 0 comments
1 commit
expand
knotserver/xrpc: use repo DID resolution in all handlers
expand 0 comments
1 commit
expand
knotserver/xrpc: use repo DID resolution in all handlers
expand 0 comments
1 commit
expand
knotserver/xrpc: use repo DID resolution in all handlers
expand 0 comments
1 commit
expand
knotserver/xrpc: use repo DID resolution in all handlers
expand 0 comments