The grants part composes the bus's user list with the composer the controller uses (ADR 0547 §5), copied rather than moved: the controller is not changed and keeps its own until it loses the lease. Left out: the bus client, the broker's address from the environment and the client's two methods; the lease's two subjects are copied in. One table test holds the copy to four meshes' lists and the server golden, written by the controller's own internal/broker from the same fixtures (testdata/controller-user-lists.json).
118 lines
4.8 KiB
Go
118 lines
4.8 KiB
Go
package buscompose
|
|
|
|
import (
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
// A tool call names its caller (novox/hq issue 365, by ADR 0259 §3's precedent for asks).
|
|
//
|
|
// mesh.mod.<module>.call.<tool>[.<node>].<caller>
|
|
// mesh.seat.<seat>.call.<verb>[.<node>].<caller>
|
|
//
|
|
// The last token is the bus user that published the call, and each user is granted these subjects with its own
|
|
// name there and no other — the caller is a fact the server enforces, and the tool runtime hands it to the
|
|
// module from the subject a call arrived on (mesh-tools node-tools internal/bus caller.go holds the same shape).
|
|
//
|
|
// **A kind of its own, `call`, not the `tool` subject with a token appended.** Every grant to call a tool is a
|
|
// wildcard over the `tool` kind — `.tool.<t>.*` for the instance on any machine, `mesh.mod.*.tool.>` for every
|
|
// tool — and either would match a `tool` subject with any caller appended, so a caller could name another.
|
|
// Under `call` nothing is granted but the caller-named subjects. No stream's filter covers it: a tool call is
|
|
// never persisted (design 25 §3).
|
|
//
|
|
// **Derived from the `tool` grants, in one place** (callerNamed): wherever a principal may call or answer a
|
|
// tool, it may call it in its own name, or answer it naming any caller — so no kind of principal is left
|
|
// unable to call in its own name, and a new grant to call is one in both shapes by construction.
|
|
//
|
|
// The `tool` grants stay beside these for one release, so every caller and every runtime moves without a gap:
|
|
// their retirement is hq issue 464.
|
|
const CallKind = "call"
|
|
|
|
var userName = regexp.MustCompile(`^[A-Za-z0-9_-]+(\.[A-Za-z0-9_-]+)*$`)
|
|
|
|
// CallerToken is a bus user's name as the last token of a call it publishes: each dot written `~`, which no part
|
|
// of a user's name may hold (safeSubject), so the token names that user and no other. "" for a name that is not
|
|
// a user's.
|
|
func CallerToken(user string) string {
|
|
if !userName.MatchString(user) {
|
|
return ""
|
|
}
|
|
return strings.ReplaceAll(user, ".", "~")
|
|
}
|
|
|
|
// toolGrant splits a grant on the `tool` kind — `mesh.mod.<m>.tool.<rest>` or `mesh.seat.<s>.tool.<rest>`, any
|
|
// part possibly a wildcard — at the kind; ok is false for any other subject.
|
|
func toolGrant(subject string) (head, rest string, ok bool) {
|
|
parts := strings.SplitN(subject, ".", 5)
|
|
if len(parts) != 5 || parts[0] != "mesh" || (parts[1] != "mod" && parts[1] != "seat") || parts[3] != "tool" ||
|
|
parts[2] == "" || parts[4] == "" {
|
|
return "", "", false
|
|
}
|
|
return parts[0] + "." + parts[1] + "." + parts[2], parts[4], true
|
|
}
|
|
|
|
// CalledSubject is where a call to a tool subject goes naming its caller; "" when the subject is not a tool's
|
|
// or the user is not a bus user.
|
|
func CalledSubject(subject, user string) string {
|
|
head, rest, ok := toolGrant(subject)
|
|
token := CallerToken(user)
|
|
if !ok || token == "" || strings.ContainsAny(rest, "*>") {
|
|
return ""
|
|
}
|
|
return head + "." + CallKind + "." + rest + "." + token
|
|
}
|
|
|
|
// CalledPattern is what a holder answering a tool subject also subscribes, to hear the calls that name their
|
|
// caller: the same address under the `call` kind, any caller last. "" for a subject that is not a tool's.
|
|
func CalledPattern(subject string) string {
|
|
head, rest, ok := toolGrant(subject)
|
|
if !ok || strings.ContainsAny(rest, "*>") {
|
|
return ""
|
|
}
|
|
return head + "." + CallKind + "." + rest + ".*"
|
|
}
|
|
|
|
// calledPublish is a grant to call on the `tool` kind, as the same grant in the caller's own name: its last
|
|
// token the caller's, and nothing that reaches past it. A `>` is every tail a call carries — the tool alone or
|
|
// the tool and the machine — so it becomes both, each ending in the caller.
|
|
func calledPublish(grant, token string) []string {
|
|
head, rest, ok := toolGrant(grant)
|
|
if !ok || token == "" {
|
|
return nil
|
|
}
|
|
base := head + "." + CallKind + "."
|
|
switch {
|
|
case rest == ">":
|
|
return []string{base + "*." + token, base + "*.*." + token}
|
|
case strings.HasSuffix(rest, ".>"):
|
|
return []string{base + strings.TrimSuffix(rest, ">") + "*." + token}
|
|
}
|
|
return []string{base + rest + "." + token}
|
|
}
|
|
|
|
// calledSubscribe is a grant to answer on the `tool` kind, as the same grant for the calls naming any caller.
|
|
func calledSubscribe(grant string) []string {
|
|
head, rest, ok := toolGrant(grant)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
base := head + "." + CallKind + "."
|
|
if strings.HasSuffix(rest, ">") {
|
|
return []string{base + rest}
|
|
}
|
|
return []string{base + rest + ".*"}
|
|
}
|
|
|
|
// callerNamed adds, beside a principal's grants on the `tool` kind, the same grants under `call`: to publish in
|
|
// its own name, to subscribe naming anybody.
|
|
func callerNamed(user string, pub, sub []string) ([]string, []string) {
|
|
token := CallerToken(user)
|
|
for _, g := range pub {
|
|
pub = append(pub, calledPublish(g, token)...)
|
|
}
|
|
for _, g := range sub {
|
|
sub = append(sub, calledSubscribe(g)...)
|
|
}
|
|
return unique(pub), unique(sub)
|
|
}
|