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