forked from tangled.org/core
this repo has no description

appview: rework repo delete

requires record deletion first

Signed-off-by: oppiliappan <me@oppi.li>

oppi.li 0dd491e1 d73d335b

verified
Changed files
+111 -82
api
tangled
appview
repo
knotserver
lexicons
rbac
xrpc
errors
+2
api/tangled/repodelete.go
···
Did string `json:"did" cborgen:"did"`
// name: Name of the repository to delete
Name string `json:"name" cborgen:"name"`
}
// RepoDelete calls the XRPC method "sh.tangled.repo.delete".
···
Did string `json:"did" cborgen:"did"`
// name: Name of the repository to delete
Name string `json:"name" cborgen:"name"`
+
// rkey: Rkey of the repository record
+
Rkey string `json:"rkey" cborgen:"rkey"`
}
// RepoDelete calls the XRPC method "sh.tangled.repo.delete".
+1
appview/repo/repo.go
···
&tangled.RepoDelete_Input{
Did: f.OwnerDid(),
Name: f.Name,
},
)
if err := xrpcclient.HandleXrpcErr(err); err != nil {
···
&tangled.RepoDelete_Input{
Did: f.OwnerDid(),
Name: f.Name,
+
Rkey: f.Rkey,
},
)
if err := xrpcclient.HandleXrpcErr(err); err != nil {
+93 -79
knotserver/xrpc/delete_repo.go
···
package xrpc
-
// import (
-
// "encoding/json"
-
// "fmt"
-
// "net/http"
-
// "os"
-
// "path/filepath"
-
//
-
// "github.com/bluesky-social/indigo/atproto/syntax"
-
// securejoin "github.com/cyphar/filepath-securejoin"
-
// "tangled.sh/tangled.sh/core/api/tangled"
-
// "tangled.sh/tangled.sh/core/rbac"
-
// xrpcerr "tangled.sh/tangled.sh/core/xrpc/errors"
-
// )
-
// func (x *Xrpc) DeleteRepo(w http.ResponseWriter, r *http.Request) {
-
// l := x.Logger.With("handler", "DeleteRepo")
-
// fail := func(e xrpcerr.XrpcError) {
-
// l.Error("failed", "kind", e.Tag, "error", e.Message)
-
// writeError(w, e, http.StatusBadRequest)
-
// }
-
//
-
// actorDid, ok := r.Context().Value(ActorDid).(syntax.DID)
-
// if !ok {
-
// fail(xrpcerr.MissingActorDidError)
-
// return
-
// }
-
//
-
// isMember, err := x.Enforcer.IsRepoDeleteAllowed(actorDid.String(), rbac.ThisServer)
-
// if err != nil {
-
// fail(xrpcerr.GenericError(err))
-
// return
-
// }
-
// if !isMember {
-
// fail(xrpcerr.AccessControlError(actorDid.String()))
-
// return
-
// }
-
//
-
// var data tangled.RepoDelete_Input
-
// if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
-
// fail(xrpcerr.GenericError(err))
-
// return
-
// }
-
//
-
// did := data.Did
-
// name := data.Name
-
//
-
// if did == "" || name == "" {
-
// fail(xrpcerr.GenericError(fmt.Errorf("did and name are required")))
-
// return
-
// }
-
//
-
// relativeRepoPath := filepath.Join(did, name)
-
// if ok, err := x.Enforcer.IsSettingsAllowed(actorDid.String(), rbac.ThisServer, relativeRepoPath); !ok || err != nil {
-
// l.Error("insufficient permissions", "did", actorDid.String(), "repo", relativeRepoPath)
-
// writeError(w, xrpcerr.AccessControlError(actorDid.String()), http.StatusUnauthorized)
-
// return
-
// }
-
//
-
// repoPath, err := securejoin.SecureJoin(x.Config.Repo.ScanPath, relativeRepoPath)
-
// if err != nil {
-
// fail(xrpcerr.GenericError(err))
-
// return
-
// }
-
//
-
// err = os.RemoveAll(repoPath)
-
// if err != nil {
-
// l.Error("deleting repo", "error", err.Error())
-
// writeError(w, xrpcerr.GenericError(err), http.StatusInternalServerError)
-
// return
-
// }
-
//
-
// err = x.Enforcer.RemoveRepo(did, rbac.ThisServer, relativeRepoPath)
-
// if err != nil {
-
// l.Error("failed to delete repo from enforcer", "error", err.Error())
-
// writeError(w, xrpcerr.GenericError(err), http.StatusInternalServerError)
-
// return
-
// }
-
//
-
// w.WriteHeader(http.StatusOK)
-
// }
···
package xrpc
+
import (
+
"encoding/json"
+
"fmt"
+
"net/http"
+
"os"
+
"path/filepath"
+
comatproto "github.com/bluesky-social/indigo/api/atproto"
+
"github.com/bluesky-social/indigo/atproto/syntax"
+
"github.com/bluesky-social/indigo/xrpc"
+
securejoin "github.com/cyphar/filepath-securejoin"
+
"tangled.sh/tangled.sh/core/api/tangled"
+
"tangled.sh/tangled.sh/core/rbac"
+
xrpcerr "tangled.sh/tangled.sh/core/xrpc/errors"
+
)
+
+
func (x *Xrpc) DeleteRepo(w http.ResponseWriter, r *http.Request) {
+
l := x.Logger.With("handler", "DeleteRepo")
+
fail := func(e xrpcerr.XrpcError) {
+
l.Error("failed", "kind", e.Tag, "error", e.Message)
+
writeError(w, e, http.StatusBadRequest)
+
}
+
+
actorDid, ok := r.Context().Value(ActorDid).(syntax.DID)
+
if !ok {
+
fail(xrpcerr.MissingActorDidError)
+
return
+
}
+
+
var data tangled.RepoDelete_Input
+
if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
+
fail(xrpcerr.GenericError(err))
+
return
+
}
+
+
did := data.Did
+
name := data.Name
+
rkey := data.Rkey
+
+
if did == "" || name == "" {
+
fail(xrpcerr.GenericError(fmt.Errorf("did and name are required")))
+
return
+
}
+
+
ident, err := x.Resolver.ResolveIdent(r.Context(), actorDid.String())
+
if err != nil || ident.Handle.IsInvalidHandle() {
+
fail(xrpcerr.GenericError(err))
+
return
+
}
+
+
xrpcc := xrpc.Client{
+
Host: ident.PDSEndpoint(),
+
}
+
+
// ensure that the record does not exists
+
_, err = comatproto.RepoGetRecord(r.Context(), &xrpcc, "", tangled.RepoNSID, actorDid.String(), rkey)
+
if err == nil {
+
fail(xrpcerr.RecordExistsError(rkey))
+
return
+
}
+
+
relativeRepoPath := filepath.Join(did, name)
+
isDeleteAllowed, err := x.Enforcer.IsRepoDeleteAllowed(actorDid.String(), rbac.ThisServer, relativeRepoPath)
+
if err != nil {
+
fail(xrpcerr.GenericError(err))
+
return
+
}
+
if !isDeleteAllowed {
+
fail(xrpcerr.AccessControlError(actorDid.String()))
+
return
+
}
+
+
repoPath, err := securejoin.SecureJoin(x.Config.Repo.ScanPath, relativeRepoPath)
+
if err != nil {
+
fail(xrpcerr.GenericError(err))
+
return
+
}
+
+
err = os.RemoveAll(repoPath)
+
if err != nil {
+
l.Error("deleting repo", "error", err.Error())
+
writeError(w, xrpcerr.GenericError(err), http.StatusInternalServerError)
+
return
+
}
+
+
err = x.Enforcer.RemoveRepo(did, rbac.ThisServer, relativeRepoPath)
+
if err != nil {
+
l.Error("failed to delete repo from enforcer", "error", err.Error())
+
writeError(w, xrpcerr.GenericError(err), http.StatusInternalServerError)
+
return
+
}
+
+
w.WriteHeader(http.StatusOK)
+
}
+1
knotserver/xrpc/xrpc.go
···
r.Post("/"+tangled.RepoSetDefaultBranchNSID, x.SetDefaultBranch)
r.Post("/"+tangled.RepoCreateNSID, x.CreateRepo)
r.Post("/"+tangled.RepoForkStatusNSID, x.ForkStatus)
r.Post("/"+tangled.RepoForkSyncNSID, x.ForkSync)
r.Post("/"+tangled.RepoHiddenRefNSID, x.HiddenRef)
···
r.Post("/"+tangled.RepoSetDefaultBranchNSID, x.SetDefaultBranch)
r.Post("/"+tangled.RepoCreateNSID, x.CreateRepo)
+
r.Post("/"+tangled.RepoDeleteNSID, x.DeleteRepo)
r.Post("/"+tangled.RepoForkStatusNSID, x.ForkStatus)
r.Post("/"+tangled.RepoForkSyncNSID, x.ForkSync)
r.Post("/"+tangled.RepoHiddenRefNSID, x.HiddenRef)
+5 -1
lexicons/repo/delete.json
···
"encoding": "application/json",
"schema": {
"type": "object",
-
"required": ["did", "name"],
"properties": {
"did": {
"type": "string",
···
"name": {
"type": "string",
"description": "Name of the repository to delete"
}
}
}
···
"encoding": "application/json",
"schema": {
"type": "object",
+
"required": ["did", "name", "rkey"],
"properties": {
"did": {
"type": "string",
···
"name": {
"type": "string",
"description": "Name of the repository to delete"
+
},
+
"rkey": {
+
"type": "string",
+
"description": "Rkey of the repository record"
}
}
}
+2 -2
rbac/rbac.go
···
return e.E.Enforce(user, domain, domain, "repo:create")
}
-
func (e *Enforcer) IsRepoDeleteAllowed(user, domain string) (bool, error) {
-
return e.E.Enforce(user, domain, domain, "repo:delete")
}
func (e *Enforcer) IsPushAllowed(user, domain, repo string) (bool, error) {
···
return e.E.Enforce(user, domain, domain, "repo:create")
}
+
func (e *Enforcer) IsRepoDeleteAllowed(user, domain, repo string) (bool, error) {
+
return e.E.Enforce(user, domain, repo, "repo:delete")
}
func (e *Enforcer) IsPushAllowed(user, domain, repo string) (bool, error) {
+7
xrpc/errors/errors.go
···
)
}
func GenericError(err error) XrpcError {
return NewXrpcError(
WithTag("Generic"),
···
)
}
+
var RecordExistsError = func(r string) XrpcError {
+
return NewXrpcError(
+
WithTag("RecordExists"),
+
WithError(fmt.Errorf("repo already exists: %s", r)),
+
)
+
}
+
func GenericError(err error) XrpcError {
return NewXrpcError(
WithTag("Generic"),