The controller cannot know the operator's account's uid, names an access by its declared id (which holds a slash), and should not parse words. A tool runner's account may now come by name alone and is read here (refused when absent or root's, its uid said in the access's listing); the take act matches the declared id exactly; an access wait says its reason (keep or disk) and, for a filesystem that refuses ACLs, whether the take act can enable them.
88 lines
4.0 KiB
Go
88 lines
4.0 KiB
Go
package link
|
|
|
|
import (
|
|
"crypto/ed25519"
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// The take act's ask (novox/hq ADR 0325 §8): a hand-over's fields and the act's own in one signed object, under a
|
|
// signing context of its own. The controller's side holds the same names and words.
|
|
func TestTheTakeActsAskKeepsItsFieldNamesAndContext(t *testing.T) {
|
|
body, _ := json.Marshal(AccessActAsk{Act: ActTake, Access: "radarr.movies", Warrant: "w"})
|
|
if got := keysOf(t, body); got != "access act warrant" {
|
|
t.Fatalf("the act's fields are %q", got)
|
|
}
|
|
if AskAccessActSubject("ace") != "mesh.node.ace.ask.access-act" {
|
|
t.Fatalf("the subject is %q", AskAccessActSubject("ace"))
|
|
}
|
|
if AccessActContext != "novox-mesh access-act v2\n" {
|
|
t.Fatalf("the signing context is %q", AccessActContext)
|
|
}
|
|
}
|
|
|
|
// signedAct is an act's ask as the controller makes it: the hand-over's fields beside the act's, signed over the
|
|
// act's context and its bytes.
|
|
func signedAct(t *testing.T, key ed25519.PrivateKey, ask HandOverAsk, act AccessActAsk) []byte {
|
|
t.Helper()
|
|
raw, _ := json.Marshal(struct {
|
|
HandOverAsk
|
|
AccessActAsk
|
|
}{ask, act})
|
|
body, _ := json.Marshal(SignedHandOver{Ask: raw, Signature: ed25519.Sign(key, append([]byte(AccessActContext), raw...))})
|
|
return body
|
|
}
|
|
|
|
// **Nothing is done without a warrant**, nor for any act but take, nor signed as a hand-over (ADR 0325 §8).
|
|
func TestTheTakeActRunsOnlyOnAWarrant(t *testing.T) {
|
|
m, key := aMember(t)
|
|
now := time.Now()
|
|
var given []AccessAct
|
|
handle := func(a AccessAct) (string, error) { given = append(given, a); return "started", nil }
|
|
check := HandOverCheck{Signer: m.Signer, Node: m.Node, Nonces: &Nonces{}}
|
|
ask := func(nonce string) HandOverAsk {
|
|
return HandOverAsk{Node: m.Node, Path: "/srv/media/movies", By: "the operator", Expires: now.Add(HandOverGood),
|
|
Nonce: nonce}
|
|
}
|
|
for why, body := range map[string][]byte{
|
|
"no warrant": signedAct(t, key, ask(aNonce+"01"), AccessActAsk{Act: ActTake, Access: "radarr.movies"}),
|
|
"another act": signedAct(t, key, ask(aNonce+"02"), AccessActAsk{Act: "share", Access: "radarr.movies", Warrant: "w"}),
|
|
"no access": signedAct(t, key, ask(aNonce+"03"), AccessActAsk{Act: ActTake, Warrant: "w"}),
|
|
"a hand-over's": signedAsk(t, key, ask(aNonce+"04")),
|
|
"a slash in the warrant": signedAct(t, key, ask(aNonce+"05"), AccessActAsk{Act: ActTake, Access: "radarr.movies",
|
|
Warrant: "w/1"}),
|
|
"two lines as access": signedAct(t, key, ask(aNonce+"07"), AccessActAsk{Act: ActTake, Access: "radarr.\nmovies",
|
|
Warrant: "w"}),
|
|
} {
|
|
if a := AnswerAccessAct(check, handle, body); a.Refused == "" || !strings.HasSuffix(a.Refused, "nothing was changed") {
|
|
t.Errorf("%s: not refused, or refused without saying nothing was changed: %+v", why, a)
|
|
}
|
|
}
|
|
if len(given) != 0 {
|
|
t.Fatalf("a refused ask reached the act: %+v", given)
|
|
}
|
|
a := AnswerAccessAct(check, handle, signedAct(t, key, ask(aNonce+"06"), AccessActAsk{Act: ActTake,
|
|
Access: "radarr.movies", Warrant: "w-1"}))
|
|
if a.Said != "started" || len(given) != 1 || given[0].Warrant != "w-1" || given[0].Path != "/srv/media/movies" {
|
|
t.Fatalf("a warranted ask did not reach the act whole: %+v %+v", a, given)
|
|
}
|
|
}
|
|
|
|
// The controller names an access by its declared resource id exactly, which may hold a `/`: it reaches the act,
|
|
// which matches it against what was declared.
|
|
func TestATakeActNamesItsAccessByItsDeclaredId(t *testing.T) {
|
|
m, key := aMember(t)
|
|
var given []AccessAct
|
|
handle := func(a AccessAct) (string, error) { given = append(given, a); return "started", nil }
|
|
check := HandOverCheck{Signer: m.Signer, Node: m.Node, Nonces: &Nonces{}}
|
|
ask := HandOverAsk{Node: m.Node, Path: "/services/media/movies", By: "the operator",
|
|
Expires: time.Now().Add(HandOverGood), Nonce: aNonce + "08"}
|
|
id := "radarr.access-services/media/movies"
|
|
a := AnswerAccessAct(check, handle, signedAct(t, key, ask, AccessActAsk{Act: ActTake, Access: id, Warrant: "w-1"}))
|
|
if a.Refused != "" || len(given) != 1 || given[0].Access != id {
|
|
t.Fatalf("an access id with a slash did not reach the act as declared: %+v %+v", a, given)
|
|
}
|
|
}
|