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/ 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 }