1package safedir
2
3import (
4 "os"
5 "path/filepath"
6 "strings"
7 "testing"
8)
9
10func TestEnsurePrivate(t *testing.T) {
11 tests := []struct {
12 name string
13 setup func(t *testing.T, path string)
14 wantErr string
15 }{
16 {
17 name: "missing dir is created private",
18 },
19 {
20 name: "existing private dir is accepted",
21 setup: func(t *testing.T, path string) {
22 t.Helper()
23 if err := os.MkdirAll(path, 0o700); err != nil {
24 t.Fatalf("mkdir private dir: %v", err)
25 }
26 },
27 },
28 {
29 name: "group writable dir is rejected",
30 setup: func(t *testing.T, path string) {
31 t.Helper()
32 if err := os.MkdirAll(path, 0o700); err != nil {
33 t.Fatalf("mkdir group writable dir: %v", err)
34 }
35 if err := os.Chmod(path, 0o770); err != nil {
36 t.Fatalf("chmod group writable dir: %v", err)
37 }
38 },
39 wantErr: "permissions 0770, want 0700",
40 },
41 {
42 name: "world writable dir is rejected",
43 setup: func(t *testing.T, path string) {
44 t.Helper()
45 if err := os.MkdirAll(path, 0o700); err != nil {
46 t.Fatalf("mkdir world writable dir: %v", err)
47 }
48 if err := os.Chmod(path, 0o707); err != nil {
49 t.Fatalf("chmod world writable dir: %v", err)
50 }
51 },
52 wantErr: "permissions 0707, want 0700",
53 },
54 {
55 name: "world readable dir is rejected",
56 setup: func(t *testing.T, path string) {
57 t.Helper()
58 if err := os.MkdirAll(path, 0o700); err != nil {
59 t.Fatalf("mkdir world readable dir: %v", err)
60 }
61 if err := os.Chmod(path, 0o755); err != nil {
62 t.Fatalf("chmod world readable dir: %v", err)
63 }
64 },
65 wantErr: "permissions 0755, want 0700",
66 },
67 {
68 name: "execute-only others dir is rejected",
69 setup: func(t *testing.T, path string) {
70 t.Helper()
71 if err := os.MkdirAll(path, 0o700); err != nil {
72 t.Fatalf("mkdir execute-only others dir: %v", err)
73 }
74 if err := os.Chmod(path, 0o711); err != nil {
75 t.Fatalf("chmod execute-only others dir: %v", err)
76 }
77 },
78 wantErr: "permissions 0711, want 0700",
79 },
80 {
81 name: "owner read execute dir is rejected",
82 setup: func(t *testing.T, path string) {
83 t.Helper()
84 if err := os.MkdirAll(path, 0o700); err != nil {
85 t.Fatalf("mkdir owner read execute dir: %v", err)
86 }
87 if err := os.Chmod(path, 0o500); err != nil {
88 t.Fatalf("chmod owner read execute dir: %v", err)
89 }
90 },
91 wantErr: "permissions 0500, want 0700",
92 },
93 {
94 name: "symlink is rejected",
95 setup: func(t *testing.T, path string) {
96 t.Helper()
97 if err := os.Symlink(t.TempDir(), path); err != nil {
98 t.Fatalf("symlink dir: %v", err)
99 }
100 },
101 wantErr: "symlink",
102 },
103 {
104 name: "regular file is rejected",
105 setup: func(t *testing.T, path string) {
106 t.Helper()
107 if err := os.WriteFile(path, []byte("not a dir"), 0o600); err != nil {
108 t.Fatalf("write file: %v", err)
109 }
110 },
111 wantErr: "not a directory",
112 },
113 }
114
115 for _, tt := range tests {
116 t.Run(tt.name, func(t *testing.T) {
117 path := filepath.Join(t.TempDir(), "private")
118 if tt.setup != nil {
119 tt.setup(t, path)
120 }
121
122 err := EnsurePrivate(path)
123 if tt.wantErr != "" {
124 if err == nil {
125 t.Fatalf("EnsurePrivate() error = nil, want containing %q", tt.wantErr)
126 }
127 if !strings.Contains(err.Error(), tt.wantErr) {
128 t.Fatalf("EnsurePrivate() error = %q, want containing %q", err, tt.wantErr)
129 }
130 return
131 }
132 if err != nil {
133 t.Fatalf("EnsurePrivate() error = %v", err)
134 }
135 fi, err := os.Stat(path)
136 if err != nil {
137 t.Fatalf("stat private dir: %v", err)
138 }
139 if !fi.IsDir() {
140 t.Fatalf("%s is not a directory", path)
141 }
142 if perm := fi.Mode().Perm(); perm != 0o700 {
143 t.Fatalf("dir perm = %o, want 0700", perm)
144 }
145 })
146 }
147}
148
149// A wrong-uid directory is not covered here because creating one requires
150// elevated privileges; EnsurePrivate checks the uid returned by Lstat.