forked from tangled.org/core
this repo has no description
at master 4.4 kB view raw
1package xrpc 2 3import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "net/http" 8 "path/filepath" 9 "strings" 10 11 comatproto "github.com/bluesky-social/indigo/api/atproto" 12 "github.com/bluesky-social/indigo/atproto/syntax" 13 "github.com/bluesky-social/indigo/xrpc" 14 securejoin "github.com/cyphar/filepath-securejoin" 15 gogit "github.com/go-git/go-git/v5" 16 "tangled.sh/tangled.sh/core/api/tangled" 17 "tangled.sh/tangled.sh/core/hook" 18 "tangled.sh/tangled.sh/core/knotserver/git" 19 "tangled.sh/tangled.sh/core/rbac" 20 xrpcerr "tangled.sh/tangled.sh/core/xrpc/errors" 21) 22 23func (h *Xrpc) CreateRepo(w http.ResponseWriter, r *http.Request) { 24 l := h.Logger.With("handler", "NewRepo") 25 fail := func(e xrpcerr.XrpcError) { 26 l.Error("failed", "kind", e.Tag, "error", e.Message) 27 writeError(w, e, http.StatusBadRequest) 28 } 29 30 actorDid, ok := r.Context().Value(ActorDid).(syntax.DID) 31 if !ok { 32 fail(xrpcerr.MissingActorDidError) 33 return 34 } 35 36 isMember, err := h.Enforcer.IsRepoCreateAllowed(actorDid.String(), rbac.ThisServer) 37 if err != nil { 38 fail(xrpcerr.GenericError(err)) 39 return 40 } 41 if !isMember { 42 fail(xrpcerr.AccessControlError(actorDid.String())) 43 return 44 } 45 46 var data tangled.RepoCreate_Input 47 if err := json.NewDecoder(r.Body).Decode(&data); err != nil { 48 fail(xrpcerr.GenericError(err)) 49 return 50 } 51 52 rkey := data.Rkey 53 54 ident, err := h.Resolver.ResolveIdent(r.Context(), actorDid.String()) 55 if err != nil || ident.Handle.IsInvalidHandle() { 56 fail(xrpcerr.GenericError(err)) 57 return 58 } 59 60 xrpcc := xrpc.Client{ 61 Host: ident.PDSEndpoint(), 62 } 63 64 resp, err := comatproto.RepoGetRecord(r.Context(), &xrpcc, "", tangled.RepoNSID, actorDid.String(), rkey) 65 if err != nil { 66 fail(xrpcerr.GenericError(err)) 67 return 68 } 69 70 repo := resp.Value.Val.(*tangled.Repo) 71 72 defaultBranch := h.Config.Repo.MainBranch 73 if data.DefaultBranch != nil && *data.DefaultBranch != "" { 74 defaultBranch = *data.DefaultBranch 75 } 76 77 if err := validateRepoName(repo.Name); err != nil { 78 l.Error("creating repo", "error", err.Error()) 79 fail(xrpcerr.GenericError(err)) 80 return 81 } 82 83 relativeRepoPath := filepath.Join(actorDid.String(), repo.Name) 84 repoPath, _ := securejoin.SecureJoin(h.Config.Repo.ScanPath, relativeRepoPath) 85 86 if data.Source != nil && *data.Source != "" { 87 err = git.Fork(repoPath, *data.Source) 88 if err != nil { 89 l.Error("forking repo", "error", err.Error()) 90 writeError(w, xrpcerr.GenericError(err), http.StatusInternalServerError) 91 return 92 } 93 } else { 94 err = git.InitBare(repoPath, defaultBranch) 95 if err != nil { 96 l.Error("initializing bare repo", "error", err.Error()) 97 if errors.Is(err, gogit.ErrRepositoryAlreadyExists) { 98 fail(xrpcerr.RepoExistsError("repository already exists")) 99 return 100 } else { 101 writeError(w, xrpcerr.GenericError(err), http.StatusInternalServerError) 102 return 103 } 104 } 105 } 106 107 // add perms for this user to access the repo 108 err = h.Enforcer.AddRepo(actorDid.String(), rbac.ThisServer, relativeRepoPath) 109 if err != nil { 110 l.Error("adding repo permissions", "error", err.Error()) 111 writeError(w, xrpcerr.GenericError(err), http.StatusInternalServerError) 112 return 113 } 114 115 hook.SetupRepo( 116 hook.Config( 117 hook.WithScanPath(h.Config.Repo.ScanPath), 118 hook.WithInternalApi(h.Config.Server.InternalListenAddr), 119 ), 120 repoPath, 121 ) 122 123 w.WriteHeader(http.StatusOK) 124} 125 126func validateRepoName(name string) error { 127 // check for path traversal attempts 128 if name == "." || name == ".." || 129 strings.Contains(name, "/") || strings.Contains(name, "\\") { 130 return fmt.Errorf("Repository name contains invalid path characters") 131 } 132 133 // check for sequences that could be used for traversal when normalized 134 if strings.Contains(name, "./") || strings.Contains(name, "../") || 135 strings.HasPrefix(name, ".") || strings.HasSuffix(name, ".") { 136 return fmt.Errorf("Repository name contains invalid path sequence") 137 } 138 139 // then continue with character validation 140 for _, char := range name { 141 if !((char >= 'a' && char <= 'z') || 142 (char >= 'A' && char <= 'Z') || 143 (char >= '0' && char <= '9') || 144 char == '-' || char == '_' || char == '.') { 145 return fmt.Errorf("Repository name can only contain alphanumeric characters, periods, hyphens, and underscores") 146 } 147 } 148 149 // additional check to prevent multiple sequential dots 150 if strings.Contains(name, "..") { 151 return fmt.Errorf("Repository name cannot contain sequential dots") 152 } 153 154 // if all checks pass 155 return nil 156}