aboutsummaryrefslogtreecommitdiff
path: root/perm_test.go
blob: dc874fe34c1ac17cc9da7fd3c13c40af7bacc82b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
package main

import (
	"net/http"
	"testing"
)

func TestValidRepoPath(t *testing.T) {
	ok := []string{
		"dns", "infra/dns", "a", "x-1.2_3", "infra/x.y", "static",
		// cgit's routes are only taken one component deep, because that is the
		// only place they can be reached.
		"log", "tree", "about", "summary",
	}
	bad := []string{
		"", ".", "..", "../etc", "infra/../etc", "a/b/c", "/dns", "dns/",
		"Infra/dns", "in fra/dns", "dns.git", "dns.issues", "dns.wiki",
		"admin", "settings", "login", "-dns",
		// The forge's own names are taken in both components: /infra/settings
		// has to be the settings of the repository infra, so a repository called
		// settings inside the section infra would make that page unreachable.
		"infra/settings", "infra/issues", "infra/wiki", "issues", "wiki",
		// And cgit's, in the second component, for the same reason.
		"infra/log", "infra/tree", "infra/about", "infra/summary",
		"infra/atom", "infra/plain", "infra/snapshot",
	}
	for _, p := range ok {
		if !validRepoPath(p) {
			t.Errorf("%q was refused", p)
		}
	}
	for _, p := range bad {
		if validRepoPath(p) {
			t.Errorf("%q was allowed", p)
		}
	}
}

func TestPermissions(t *testing.T) {
	anon := Viewer{}
	stranger := Viewer{Name: "eve"}
	reader := Viewer{Name: "carol"}
	writer := Viewer{Name: "bob"}
	owner := Viewer{Name: "dave"}
	root := Viewer{Name: "alice", Admin: true}

	grants := map[string]string{"carol": grantRead, "bob": grantWrite, "dave": grantAdmin}

	private := Repo{Visibility: visPrivate, Issues: featMembers, Wiki: featDisabled, Grants: grants}
	public := Repo{Visibility: visPublic, Issues: featUsers, Wiki: featMembers, Grants: grants}

	cases := []struct {
		what                            string
		v                               Viewer
		repo                            Repo
		read, write, admin, issue, wiki bool
	}{
		{"anon on private", anon, private, false, false, false, false, false},
		{"stranger on private", stranger, private, false, false, false, false, false},
		{"reader on private", reader, private, true, false, false, true, false},
		{"writer on private", writer, private, true, true, false, true, false},
		{"owner on private", owner, private, true, true, true, true, false},
		{"global admin on private", root, private, true, true, true, true, false},

		{"anon on public", anon, public, true, false, false, false, false},
		{"stranger on public", stranger, public, true, false, false, true, false},
		{"reader on public", reader, public, true, false, false, true, true},
		{"writer on public", writer, public, true, true, false, true, true},
		{"global admin on public", root, public, true, true, true, true, true},
	}
	for _, c := range cases {
		if got := c.v.CanRead(c.repo); got != c.read {
			t.Errorf("%s: read %v", c.what, got)
		}
		if got := c.v.CanWrite(c.repo); got != c.write {
			t.Errorf("%s: write %v", c.what, got)
		}
		if got := c.v.CanAdmin(c.repo); got != c.admin {
			t.Errorf("%s: admin %v", c.what, got)
		}
		if got := c.v.CanIssues(c.repo); got != c.issue {
			t.Errorf("%s: issues %v", c.what, got)
		}
		if got := c.v.CanWiki(c.repo); got != c.wiki {
			t.Errorf("%s: wiki %v", c.what, got)
		}
	}

	off := Repo{Visibility: visPublic, Issues: featDisabled, Wiki: featDisabled, Grants: grants}
	for _, v := range []Viewer{anon, reader, writer, owner, root} {
		if v.CanIssues(off) || v.CanWiki(off) {
			t.Errorf("%s writes to a disabled feature", v.Name)
		}
	}
}

func TestUsersOnPrivateIsAnError(t *testing.T) {
	repo := Repo{Visibility: visPrivate, Issues: featUsers, Wiki: featDisabled}
	if errs := repo.validate("infra/dns"); len(errs) != 1 {
		t.Fatalf("expected one error, got %v", errs)
	}
	repo.Visibility = visPublic
	if errs := repo.validate("infra/dns"); len(errs) != 0 {
		t.Fatalf("expected none, got %v", errs)
	}
}

// Turning a feature off leaves its repository on disk, and the answer has to
// be the same at both doors.
func TestDisabledFeatureIsDisabledEverywhere(t *testing.T) {
	f := testForge(t)
	addRepo(t, f, "notes", Repo{Visibility: visPublic, Issues: featUsers, Wiki: featUsers})
	err := f.store.UpdateRepos("off", f.store.forgeAuthor(), func(m map[string]Repo) error {
		repo := m["notes"]
		repo.Issues, repo.Wiki = featDisabled, featDisabled
		m["notes"] = repo
		return nil
	})
	if err != nil {
		t.Fatal(err)
	}
	h := f.routes()
	for _, path := range []string{
		"/notes.issues/", "/notes.wiki/",
		"/notes.issues.git/info/refs?service=git-upload-pack",
		"/notes.wiki.git/info/refs?service=git-upload-pack",
	} {
		if code := get(h, path, nil).Code; code != http.StatusNotFound {
			t.Errorf("%s answered %d for a disabled feature", path, code)
		}
	}
}