Count an own secret's prompt by character and refuse {member} on a single secret (issue 789)
This commit is contained in:
@@ -14,6 +14,7 @@ import (
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// Scopes a claim can have.
|
||||
@@ -2190,7 +2191,7 @@ func ParseManifest(raw []byte) (Manifest, error) {
|
||||
"may make it (novox/hq ADR 0228)",
|
||||
m.Module, name, own.IssuedBy, IssuedOutside))
|
||||
}
|
||||
problems = append(problems, ownSecretPromptProblems(m, name, own.Prompt)...)
|
||||
problems = append(problems, ownSecretPromptProblems(m, name, own.Prompt, IsFamily(name))...)
|
||||
}
|
||||
localOf := map[string]string{}
|
||||
for _, to := range m.SecretRequirements() {
|
||||
@@ -2785,8 +2786,10 @@ var promptPlaceholder = regexp.MustCompile(`\{[^{}]*\}`)
|
||||
|
||||
// ownSecretPromptProblems is what is wrong with an own secret's declared prompt, in the module check's words:
|
||||
// empty is fine (no prompt is declared, and the desk asks by name alone as it does today); otherwise one line,
|
||||
// at most PromptMax characters, holding only the `{member}` placeholder.
|
||||
func ownSecretPromptProblems(m Manifest, name, prompt string) []string {
|
||||
// at most PromptMax characters — counting what a person reads, not its bytes — holding only the `{member}`
|
||||
// placeholder, and only where the secret is a family: a single secret's prompt has no member to fill, so
|
||||
// `{member}` there would stay in the prompt literally (novox/hq issue 789).
|
||||
func ownSecretPromptProblems(m Manifest, name, prompt string, family bool) []string {
|
||||
if prompt == "" {
|
||||
return nil
|
||||
}
|
||||
@@ -2796,10 +2799,10 @@ func ownSecretPromptProblems(m Manifest, name, prompt string) []string {
|
||||
"%s names its secret %q with a prompt that holds more than one line; a prompt is one line (novox/hq issue 789)",
|
||||
m.Module, name))
|
||||
}
|
||||
if len(prompt) > PromptMax {
|
||||
if n := utf8.RuneCountInString(prompt); n > PromptMax {
|
||||
problems = append(problems, fmt.Sprintf(
|
||||
"%s names its secret %q with a prompt %d characters long; a prompt is at most %d characters",
|
||||
m.Module, name, len(prompt), PromptMax))
|
||||
m.Module, name, n, PromptMax))
|
||||
}
|
||||
for _, ph := range promptPlaceholder.FindAllString(prompt, -1) {
|
||||
if ph != "{member}" {
|
||||
@@ -2807,6 +2810,11 @@ func ownSecretPromptProblems(m Manifest, name, prompt string) []string {
|
||||
"%s names its secret %q with a prompt that holds %s; a prompt holds only the {member} placeholder, "+
|
||||
"filled with a family's member suffix (novox/hq issue 789)",
|
||||
m.Module, name, ph))
|
||||
} else if !family {
|
||||
problems = append(problems, fmt.Sprintf(
|
||||
"%s names its secret %q with a prompt that holds {member}; {member} is replaced only for a "+
|
||||
"family's member (novox/hq issue 789)",
|
||||
m.Module, name))
|
||||
}
|
||||
}
|
||||
return problems
|
||||
|
||||
@@ -58,6 +58,31 @@ func TestAnOwnSecretWithNoPromptIsFine(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// PromptMax counts characters, not bytes: a prompt of 100 multi-byte characters is well within it even
|
||||
// though it holds well over 120 bytes (novox/hq issue 789).
|
||||
func TestAPromptOfMultiByteCharactersIsCountedByCharacter(t *testing.T) {
|
||||
prompt := strings.Repeat("é", 100)
|
||||
if n := len(prompt); n <= 120 {
|
||||
t.Fatalf("the test prompt is %d bytes, not over 120 as intended", n)
|
||||
}
|
||||
_, err := ParseManifest([]byte(`{"module":"mesh-mail","version":"1","own-secrets":{
|
||||
"smtp-relay-key":{"path":"/var/lib/mesh-mail/smtp-relay-key","taken":"at-start",
|
||||
"prompt":` + jsonString(prompt) + `}}}`))
|
||||
if err != nil {
|
||||
t.Fatalf("a 100-character prompt was refused: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// {member} is replaced only for a family's member: a single secret's prompt may not hold it (novox/hq issue 789).
|
||||
func TestAPromptHoldingMemberOnASingleSecretIsRefused(t *testing.T) {
|
||||
_, err := ParseManifest([]byte(`{"module":"mesh-mail","version":"1","own-secrets":{
|
||||
"smtp-relay-key":{"path":"/var/lib/mesh-mail/smtp-relay-key","taken":"at-start",
|
||||
"prompt":"For {member}"}}}`))
|
||||
if err == nil || !strings.Contains(err.Error(), "{member} is replaced only for a family's member") {
|
||||
t.Fatalf("a single secret's prompt holding {member}: %v; want a refusal saying so", err)
|
||||
}
|
||||
}
|
||||
|
||||
// A prompt that is not one short line naming only {member} is refused by the module check (novox/hq issue 789).
|
||||
func TestAMalformedPromptIsRefused(t *testing.T) {
|
||||
for name, c := range map[string]struct{ prompt, says string }{
|
||||
|
||||
Reference in New Issue
Block a user