More work on tentacles.
This commit is contained in:
1 parent
573d15d7b4
commit
4cf3335017
29 files changed
+1194
-66
No files matched your search
@@ -0,0 +1,201 @@
|
||||
// Copyright (c) 2026 Storm Dragon
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.stormux.org/storm/cthulhu/internal/interaction"
|
||||
"git.stormux.org/storm/cthulhu/internal/tentacle"
|
||||
"git.stormux.org/storm/cthulhu/internal/tentaclewire"
|
||||
)
|
||||
|
||||
type consentFormRequest struct {
|
||||
ctx context.Context
|
||||
form interaction.Form
|
||||
reply chan internalUIResult
|
||||
}
|
||||
type consentTestForms struct{ requests chan consentFormRequest }
|
||||
|
||||
func (forms *consentTestForms) Open(context.Context, interaction.Form) (<-chan internalUIResult, error) {
|
||||
return nil, errors.New("ordinary form used for consent")
|
||||
}
|
||||
func (forms *consentTestForms) Notice(context.Context, tentaclewire.Presentation) {}
|
||||
func (forms *consentTestForms) OpenLocal(ctx context.Context, form interaction.Form) (<-chan internalUIResult, error) {
|
||||
reply := make(chan internalUIResult, 1)
|
||||
forms.requests <- consentFormRequest{ctx, form, reply}
|
||||
return reply, nil
|
||||
}
|
||||
|
||||
func consent_runtime_fixture(t *testing.T) (*tentacleRuntime, *tentacle.Session, *consentTestForms, *focusGate) {
|
||||
t.Helper()
|
||||
executable, err := os.Executable()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
manager, err := tentacle.NewManager(tentacle.DefaultLimits(), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
if err := manager.Shutdown(ctx); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
})
|
||||
policy := tentacle.NewRemotePolicy()
|
||||
if err := manager.UseRemotePolicy(policy); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
session, err := manager.Start(t.Context(), tentacle.Spec{ID: "consent-probe", Program: []string{executable, "-test.run=^TestRemoteManagementPeer$", "--", "remote-management-peer"}, SessionEvents: true, RemoteGrants: []string{"output-observe", "clipboard-exchange"}})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
select {
|
||||
case <-session.Ready():
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatal("peer not ready")
|
||||
}
|
||||
if err := session.Activate(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
forms := &consentTestForms{requests: make(chan consentFormRequest, 1)}
|
||||
runtime := &tentacleRuntime{remotePolicy: policy, forms: forms, sessions: []*tentacle.Session{session}, interactionSlots: make(chan struct{}, 1)}
|
||||
return runtime, session, forms, &focusGate{remotePolicy: policy}
|
||||
}
|
||||
|
||||
func TestRemoteConsentFormDefaultsAndRevocation(t *testing.T) {
|
||||
for _, action := range []string{"approve", "cancel", "empty", "lock", "handoff", "revoke", "disable"} {
|
||||
t.Run(action, func(t *testing.T) {
|
||||
runtime, session, forms, gate := consent_runtime_fixture(t)
|
||||
ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second)
|
||||
defer cancel()
|
||||
result := make(chan error, 1)
|
||||
go func() {
|
||||
_, err := runtime.authorize_remote(ctx, "consent-probe", "controlled", []string{"output-observe", "clipboard-exchange"})
|
||||
result <- err
|
||||
}()
|
||||
var request consentFormRequest
|
||||
select {
|
||||
case request = <-forms.requests:
|
||||
case <-ctx.Done():
|
||||
t.Fatal("no consent form")
|
||||
}
|
||||
if _, err := interaction.NewSession(request.form); err != nil {
|
||||
t.Fatal("invalid consent form", err)
|
||||
}
|
||||
if request.form.Elements[0].ID != "cancel" {
|
||||
t.Fatal("approval was default action")
|
||||
}
|
||||
for _, element := range request.form.Elements {
|
||||
if element.Checked {
|
||||
t.Fatal("permission preselected")
|
||||
}
|
||||
}
|
||||
reply := internalUIResult{Activated: "approve", Values: map[string]interaction.Value{"output-observe": {Checked: true}}}
|
||||
switch action {
|
||||
case "cancel":
|
||||
reply.Activated = "cancel"
|
||||
case "empty":
|
||||
reply.Values = nil
|
||||
case "lock":
|
||||
gate.SetLocked(true, nil, nil, nil, nil, &brailleWorker{})
|
||||
gate.SetLocked(false, nil, nil, nil, nil, &brailleWorker{})
|
||||
case "handoff":
|
||||
gate.SetSuspended(true)
|
||||
gate.SetSuspended(false)
|
||||
case "revoke":
|
||||
runtime.revokeManagedRemote("consent-probe")
|
||||
case "disable":
|
||||
session.Disable()
|
||||
}
|
||||
request.reply <- reply
|
||||
select {
|
||||
case err := <-result:
|
||||
if action == "approve" && err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if action != "approve" && err == nil {
|
||||
t.Fatal("stale/denied consent issued action")
|
||||
}
|
||||
case <-ctx.Done():
|
||||
t.Fatal("consent worker did not finish")
|
||||
}
|
||||
if action != "approve" {
|
||||
return
|
||||
}
|
||||
pending, err := session.Invoke(ctx, "open", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var lease tentaclewire.RemoteLease
|
||||
select {
|
||||
case opened := <-pending:
|
||||
if opened.Err != nil || json.Unmarshal(opened.Value, &lease) != nil {
|
||||
t.Fatal("approved action failed", opened.Err)
|
||||
}
|
||||
case <-ctx.Done():
|
||||
t.Fatal("open timed out")
|
||||
}
|
||||
delivery, err := session.RemoteContext(lease, "output-observe")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := session.RemoteContext(lease, "clipboard-exchange"); err == nil {
|
||||
t.Fatal("unchecked permission granted")
|
||||
}
|
||||
gate.SetSuspended(true)
|
||||
if delivery.Err() == nil {
|
||||
t.Fatal("handoff did not synchronously cancel delivery")
|
||||
}
|
||||
gate.SetSuspended(false)
|
||||
if _, err := session.RemoteContext(lease, "output-observe"); err == nil {
|
||||
t.Fatal("handoff resume revived access")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoteGrantedTentacleUsesLocalForms(t *testing.T) {
|
||||
runtime, session, forms, _ := consent_runtime_fixture(t)
|
||||
form := interaction.Form{Title: "Certificate review", Elements: []interaction.Element{{ID: "cancel", Label: "Cancel", Kind: interaction.Button}}}
|
||||
if _, err := runtime.open_tentacle_form(t.Context(), tentacleCommand{session: session}, form); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
select {
|
||||
case <-forms.requests:
|
||||
default:
|
||||
t.Fatal("remote form used ordinary input")
|
||||
}
|
||||
}
|
||||
|
||||
type ordinaryOnlyForms struct{ tentacleForms }
|
||||
|
||||
func TestRemoteConsentCannotFallBackToOrdinaryForms(t *testing.T) {
|
||||
runtime, session, _, _ := consent_runtime_fixture(t)
|
||||
runtime.forms = ordinaryOnlyForms{runtime.forms}
|
||||
if _, err := runtime.authorize_remote(t.Context(), "consent-probe", "controlled", []string{"output-observe"}); err == nil {
|
||||
t.Fatal("consent fell back to ordinary input")
|
||||
}
|
||||
if _, err := runtime.open_tentacle_form(t.Context(), tentacleCommand{session: session}, remote_consent_form("consent-probe", "controlled", []string{"output-observe"})); err == nil {
|
||||
t.Fatal("sensitive form fell back to ordinary input")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemotePolicyAttachedToRuntimeManagers(t *testing.T) {
|
||||
runtime := &tentacleRuntime{remotePolicy: tentacle.NewRemotePolicy(), newManager: func() (*tentacle.Manager, error) { return tentacle.NewManager(tentacle.DefaultLimits(), nil) }}
|
||||
manager, err := runtime.makeManager()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := manager.UseRemotePolicy(tentacle.NewRemotePolicy()); err == nil {
|
||||
t.Fatal("reader manager omitted policy")
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user