internal/policy/names.go
84 lines · 2979 bytes
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 "healthz": true,
24 "login": true,
25 "logout": true,
26 "new": true,
27 "notifications": true,
28 "privacy": true,
29 "raw": true,
30 "register": true,
31 "search": true,
32 "settings": true,
33 "static": true,
34}
35
36// namePat matches valid user, org, and repo names: lowercase alphanumerics,
37// dot, dash, underscore; must start with an alphanumeric, or with a single
38// dot before one. A leading dot marks a repository as infrastructure rather
39// than a project — .gitbay holds an owner's profile content — and is refused
40// for owners by ValidateOwnerName. Dots are further restricted by
41// ValidateName to avoid "." / ".." and ".git" suffixes.
42var namePat = regexp.MustCompile(`^\.?[a-z0-9][a-z0-9._-]{0,61}$`)
43
44// ValidateOwnerName checks a username or org name.
45func ValidateOwnerName(name string) error {
46 if err := ValidateName(name); err != nil {
47 return err
48 }
49 // The leading dot is a repository affordance. An owner is a top-level
50 // route, and /.gitbay is not one.
51 if strings.HasPrefix(name, ".") {
52 return fmt.Errorf("invalid name %q: must start with a letter or digit", name)
53 }
54 if reservedNames[name] {
55 return fmt.Errorf("name %q is reserved", name)
56 }
57 return nil
58}
59
60// ValidateName checks a repo name (reserved words are allowed for repos;
61// routes are namespaced under the owner).
62func ValidateName(name string) error {
63 if !namePat.MatchString(name) {
64 return fmt.Errorf("invalid name %q: lowercase letters, digits, '.', '-', '_' only; must start with a letter or digit; max 63 chars", name)
65 }
66 if name == "." || name == ".." {
67 return fmt.Errorf("invalid name %q", name)
68 }
69 // HasSuffix covers "repo.git" and the bare ".git" the leading-dot rule
70 // would otherwise let through.
71 if strings.HasSuffix(name, ".git") {
72 return fmt.Errorf("invalid name %q: must not end in .git", name)
73 }
74 // /{owner}/activity.atom is the owner's feed; a repository by that
75 // name would be unreachable.
76 if strings.HasSuffix(name, ".atom") {
77 return fmt.Errorf("invalid name %q: must not end in .atom", name)
78 }
79 return nil
80}
81
82// Reserved reports whether name is a reserved route word. Exported so the
83// httpd tests can assert route/reserved-list agreement.
84func Reserved(name string) bool { return reservedNames[name] }