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"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"unicode/utf8"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Scopes a claim can have.
|
// Scopes a claim can have.
|
||||||
@@ -2190,7 +2191,7 @@ func ParseManifest(raw []byte) (Manifest, error) {
|
|||||||
"may make it (novox/hq ADR 0228)",
|
"may make it (novox/hq ADR 0228)",
|
||||||
m.Module, name, own.IssuedBy, IssuedOutside))
|
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{}
|
localOf := map[string]string{}
|
||||||
for _, to := range m.SecretRequirements() {
|
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:
|
// 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,
|
// 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.
|
// at most PromptMax characters — counting what a person reads, not its bytes — holding only the `{member}`
|
||||||
func ownSecretPromptProblems(m Manifest, name, prompt string) []string {
|
// 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 == "" {
|
if prompt == "" {
|
||||||
return nil
|
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)",
|
"%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))
|
m.Module, name))
|
||||||
}
|
}
|
||||||
if len(prompt) > PromptMax {
|
if n := utf8.RuneCountInString(prompt); n > PromptMax {
|
||||||
problems = append(problems, fmt.Sprintf(
|
problems = append(problems, fmt.Sprintf(
|
||||||
"%s names its secret %q with a prompt %d characters long; a prompt is at most %d characters",
|
"%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) {
|
for _, ph := range promptPlaceholder.FindAllString(prompt, -1) {
|
||||||
if ph != "{member}" {
|
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, "+
|
"%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)",
|
"filled with a family's member suffix (novox/hq issue 789)",
|
||||||
m.Module, name, ph))
|
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
|
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).
|
// 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) {
|
func TestAMalformedPromptIsRefused(t *testing.T) {
|
||||||
for name, c := range map[string]struct{ prompt, says string }{
|
for name, c := range map[string]struct{ prompt, says string }{
|
||||||
|
|||||||
Reference in New Issue
Block a user