This repository has been archived on 2026-08-16. You can view files and clone it, but cannot push or open issues or pull requests.
ollie-9p/server/elevate_tree.go

198 lines
5.6 KiB
Go

package server
import (
"context"
"fmt"
"os"
"strings"
"ollie/elevate"
fs "olliesrv/fs"
)
// elevateTree implements fs.FileTree for the /elevate/ namespace.
type elevateTree struct {
broker *elevate.Broker
}
func newElevateTree(broker *elevate.Broker) *elevateTree {
return &elevateTree{broker: broker}
}
func (t *elevateTree) Stat(name string) (os.FileInfo, error) {
switch {
case name == "policy":
return &fs.SyntheticFileInfo{Name_: "policy", Mode_: 0666}, nil
case name == "pending":
return &fs.SyntheticFileInfo{Name_: "pending", Mode_: 0755, IsDir_: true}, nil
case strings.HasPrefix(name, "pending/"):
id := strings.TrimPrefix(name, "pending/")
if t.broker.PendingByID(id) != nil {
return &fs.SyntheticFileInfo{Name_: id, Mode_: 0666}, nil
}
return nil, fmt.Errorf("%s: not found", name)
}
return nil, fmt.Errorf("%s: not found", name)
}
func (t *elevateTree) List() ([]os.DirEntry, error) {
return []os.DirEntry{
fs.FileEntry("policy", 0666),
fs.DirEntry("pending", 0755),
}, nil
}
func (t *elevateTree) Open(name string) (fs.File, error) {
switch {
case name == "policy":
return &elevatePolicyFile{broker: t.broker}, nil
case name == "pending":
return &elevatePendingDir{broker: t.broker}, nil
case strings.HasPrefix(name, "pending/"):
id := strings.TrimPrefix(name, "pending/")
req := t.broker.PendingByID(id)
if req == nil {
return nil, fmt.Errorf("%s: not found", name)
}
return &elevatePendingFile{broker: t.broker, req: req}, nil
}
return nil, fmt.Errorf("%s: not found", name)
}
func (t *elevateTree) Create(name string) error { return fmt.Errorf("cannot create in elevate") }
func (t *elevateTree) Delete(name string) error { return fmt.Errorf("cannot delete in elevate") }
func (t *elevateTree) Rename(_, _ string) error { return fmt.Errorf("cannot rename in elevate") }
// --- policy file ---
type elevatePolicyFile struct {
broker *elevate.Broker
}
func (f *elevatePolicyFile) Stat() (os.FileInfo, error) {
return &fs.SyntheticFileInfo{Name_: "policy", Mode_: 0666}, nil
}
func (f *elevatePolicyFile) Read() ([]byte, error) {
p := f.broker.GlobalPolicy().Global()
return p.Marshal()
}
func (f *elevatePolicyFile) Write(data []byte) error {
var p elevate.Policy
if err := elevate.ParsePolicy(data, &p); err != nil {
return err
}
return f.broker.GlobalPolicy().SetGlobal(p)
}
func (f *elevatePolicyFile) BlockingRead(_ context.Context, _ string) ([]byte, string, error) {
return nil, "", fmt.Errorf("not blocking")
}
func (f *elevatePolicyFile) OneShot() bool { return false }
func (f *elevatePolicyFile) IsBlocking() bool { return false }
func (f *elevatePolicyFile) Async() bool { return false }
// --- pending dir ---
type elevatePendingDir struct {
broker *elevate.Broker
}
func (f *elevatePendingDir) Stat() (os.FileInfo, error) {
return &fs.SyntheticFileInfo{Name_: "pending", Mode_: 0755, IsDir_: true}, nil
}
func (f *elevatePendingDir) Read() ([]byte, error) {
// List pending request IDs
var sb strings.Builder
for _, r := range f.broker.Pending() {
sb.WriteString(r.ID)
sb.WriteByte('\t')
sb.WriteString(r.Cmd)
sb.WriteByte('\n')
}
return []byte(sb.String()), nil
}
func (f *elevatePendingDir) Write([]byte) error { return fmt.Errorf("read-only") }
func (f *elevatePendingDir) BlockingRead(_ context.Context, _ string) ([]byte, string, error) {
return nil, "", fmt.Errorf("not blocking")
}
func (f *elevatePendingDir) OneShot() bool { return false }
func (f *elevatePendingDir) IsBlocking() bool { return false }
func (f *elevatePendingDir) Async() bool { return false }
// --- pending/<id> file ---
type elevatePendingFile struct {
broker *elevate.Broker
req *elevate.Request
}
func (f *elevatePendingFile) Stat() (os.FileInfo, error) {
return &fs.SyntheticFileInfo{Name_: f.req.ID, Mode_: 0666}, nil
}
func (f *elevatePendingFile) Read() ([]byte, error) {
return []byte(f.req.Summary() + "\n"), nil
}
func (f *elevatePendingFile) Write(data []byte) error {
cmd := strings.TrimSpace(string(data))
var res elevate.Resolution
switch cmd {
case "approve":
res = elevate.ResolveApprove
case "deny":
res = elevate.ResolveDeny
case "persist":
res = elevate.ResolvePersist
default:
return fmt.Errorf("unknown resolution: %s (use approve/deny/persist)", cmd)
}
if !f.broker.Resolve(f.req.ID, res) {
return fmt.Errorf("request already resolved or timed out")
}
return nil
}
func (f *elevatePendingFile) BlockingRead(_ context.Context, _ string) ([]byte, string, error) {
return nil, "", fmt.Errorf("not blocking")
}
func (f *elevatePendingFile) OneShot() bool { return false }
func (f *elevatePendingFile) IsBlocking() bool { return false }
func (f *elevatePendingFile) Async() bool { return false }
// --- s/{id}/elevate file (session policy) ---
type sessionElevateFile struct {
broker *elevate.Broker
sessionID string
}
func (f *sessionElevateFile) Stat() (os.FileInfo, error) {
return &fs.SyntheticFileInfo{Name_: "elevate", Mode_: 0666}, nil
}
func (f *sessionElevateFile) Read() ([]byte, error) {
p := f.broker.SessionPolicy(f.sessionID)
return p.Marshal()
}
func (f *sessionElevateFile) Write(data []byte) error {
var p elevate.Policy
if err := elevate.ParsePolicy(data, &p); err != nil {
return err
}
f.broker.SetSessionPolicy(f.sessionID, p)
return nil
}
func (f *sessionElevateFile) BlockingRead(_ context.Context, _ string) ([]byte, string, error) {
return nil, "", fmt.Errorf("not blocking")
}
func (f *sessionElevateFile) OneShot() bool { return false }
func (f *sessionElevateFile) IsBlocking() bool { return false }
func (f *sessionElevateFile) Async() bool { return false }