internal/policy/names.go

e6cd75b5f28bacf51620bb531320c30fd4e66bfd
gitbay/internal/policy/names.go history · blame · raw

85 lines · 3049 bytes

5 symbols in this file
 1// Package policy holds access-control and naming rules.
 2package policy
 3
 4import (
 5	"fmt"
 6	"regexp"
 7	"strings"
 8)
 9
10// reservedNames are forbidden as usernames and org names because they are, or
11// will be, top-level web routes (the UI serves /<owner>/<name>). Any change to
12// the httpd mux's top-level routes must be reflected here; the httpd package
13// asserts this in its tests.
14var reservedNames = map[string]bool{
15	"admin":         true,
16	"api":           true,
17	"archive":       true,
18	"bookmarks":     true,
19	"explore":       true,
20	"favicon.svg":   true,
21	"gitbay":        true, // vanity go-import path on gitbay.org
22	"gitbay-bot":    true, // authors dependency-update issues
23	"ghost":         true, // authors what deleted accounts wrote (#322)
24	"healthz":       true,
25	"login":         true,
26	"logout":        true,
27	"new":           true,
28	"notifications": true,
29	"privacy":       true,
30	"raw":           true,
31	"register":      true,
32	"search":        true,
33	"settings":      true,
34	"static":        true,
35}
36
37// namePat matches valid user, org, and repo names: lowercase alphanumerics,
38// dot, dash, underscore; must start with an alphanumeric, or with a single
39// dot before one. A leading dot marks a repository as infrastructure rather
40// than a project — .gitbay holds an owner's profile content — and is refused
41// for owners by ValidateOwnerName. Dots are further restricted by
42// ValidateName to avoid "." / ".." and ".git" suffixes.
43var namePat = regexp.MustCompile(`^\.?[a-z0-9][a-z0-9._-]{0,61}$`)
44
45// ValidateOwnerName checks a username or org name.
46func ValidateOwnerName(name string) error {
47	if err := ValidateName(name); err != nil {
48		return err
49	}
50	// The leading dot is a repository affordance. An owner is a top-level
51	// route, and /.gitbay is not one.
52	if strings.HasPrefix(name, ".") {
53		return fmt.Errorf("invalid name %q: must start with a letter or digit", name)
54	}
55	if reservedNames[name] {
56		return fmt.Errorf("name %q is reserved", name)
57	}
58	return nil
59}
60
61// ValidateName checks a repo name (reserved words are allowed for repos;
62// routes are namespaced under the owner).
63func ValidateName(name string) error {
64	if !namePat.MatchString(name) {
65		return fmt.Errorf("invalid name %q: lowercase letters, digits, '.', '-', '_' only; must start with a letter or digit; max 63 chars", name)
66	}
67	if name == "." || name == ".." {
68		return fmt.Errorf("invalid name %q", name)
69	}
70	// HasSuffix covers "repo.git" and the bare ".git" the leading-dot rule
71	// would otherwise let through.
72	if strings.HasSuffix(name, ".git") {
73		return fmt.Errorf("invalid name %q: must not end in .git", name)
74	}
75	// /{owner}/activity.atom is the owner's feed; a repository by that
76	// name would be unreachable.
77	if strings.HasSuffix(name, ".atom") {
78		return fmt.Errorf("invalid name %q: must not end in .atom", name)
79	}
80	return nil
81}
82
83// Reserved reports whether name is a reserved route word. Exported so the
84// httpd tests can assert route/reserved-list agreement.
85func Reserved(name string) bool { return reservedNames[name] }