package xrpc import ( "net/http" "os" "slices" "strings" "github.com/bluesky-social/indigo/atproto/syntax" securejoin "github.com/cyphar/filepath-securejoin" "tangled.org/core/api/tangled" "tangled.org/core/rbac" xrpcerr "tangled.org/core/xrpc/errors" ) const ( permPush int64 = 1 << iota permSettings permCreateDiscussion permEditDiscussion permTagDiscussion permOwner permInvite permDelete ) // RepoPermissions returns the permissions the authenticated user has on a repository. // This endpoint is VCS-agnostic — it works for both git and pijul repos. func (x *Xrpc) RepoPermissions(w http.ResponseWriter, r *http.Request) { l := x.Logger.With("handler", "RepoPermissions") fail := func(e xrpcerr.XrpcError, status int) { l.Error("failed", "kind", e.Tag, "error", e.Message) writeError(w, e, status) } actorDid, ok := r.Context().Value(ActorDid).(syntax.DID) if !ok { fail(xrpcerr.MissingActorDidError, http.StatusBadRequest) return } repo := r.URL.Query().Get("repo") if repo == "" { fail(xrpcerr.NewXrpcError( xrpcerr.WithTag("InvalidRequest"), xrpcerr.WithMessage("missing repo parameter"), ), http.StatusBadRequest) return } repoPath, err := x.parseRepoParam(repo) if err != nil { fail(err.(xrpcerr.XrpcError), http.StatusBadRequest) return } if _, err := os.Stat(repoPath); err != nil { if os.IsNotExist(err) { writeError(w, xrpcerr.RepoNotFoundError, http.StatusNoContent) return } fail(xrpcerr.GenericError(err), http.StatusInternalServerError) return } repoParts := strings.SplitN(repo, "/", 2) if len(repoParts) != 2 { fail(xrpcerr.NewXrpcError( xrpcerr.WithTag("InvalidRequest"), xrpcerr.WithMessage("invalid repo format, expected 'did/repoName'"), ), http.StatusBadRequest) return } didSlashRepo, err := securejoin.SecureJoin(repoParts[0], repoParts[1]) if err != nil { fail(xrpcerr.InvalidRepoError(repo), http.StatusBadRequest) return } roles := x.Enforcer.GetPermissionsInRepo(actorDid.String(), rbac.ThisServer, didSlashRepo) var mask int64 var permissions []string add := func(name string, bit int64, ok bool) { if !ok { return } mask |= bit permissions = append(permissions, name) } has := func(perm string) bool { return slices.Contains(roles, perm) } add("push", permPush, has("repo:push")) add("settings", permSettings, has("repo:settings")) add("create_discussion", permCreateDiscussion, has("repo:create_discussion")) add("edit_discussion", permEditDiscussion, has("repo:edit_discussion")) add("tag_discussion", permTagDiscussion, has("repo:tag_discussion")) add("owner", permOwner, has("repo:owner")) add("invite", permInvite, has("repo:invite")) add("delete", permDelete, has("repo:delete")) writeJson(w, tangled.RepoPermissions_Output{ Mask: mask, Permissions: permissions, }) }