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