forked from binwiederhier/ntfy
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathaccess.go
228 lines (207 loc) · 7.88 KB
/
access.go
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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
//go:build !noserver
package cmd
import (
"errors"
"fmt"
"github.com/urfave/cli/v2"
"heckel.io/ntfy/v2/user"
"heckel.io/ntfy/v2/util"
)
func init() {
commands = append(commands, cmdAccess)
}
const (
userEveryone = "everyone"
)
var flagsAccess = append(
append([]cli.Flag{}, flagsUser...),
&cli.BoolFlag{Name: "reset", Aliases: []string{"r"}, Usage: "reset access for user (and topic)"},
)
var cmdAccess = &cli.Command{
Name: "access",
Usage: "Grant/revoke access to a topic, or show access",
UsageText: "ntfy access [USERNAME [TOPIC [PERMISSION]]]",
Flags: flagsAccess,
Before: initConfigFileInputSourceFunc("config", flagsAccess, initLogFunc),
Action: execUserAccess,
Category: categoryServer,
Description: `Manage the access control list for the ntfy server.
This is a server-only command. It directly manages the user.db as defined in the server config
file server.yml. The command only works if 'auth-file' is properly defined. Please also refer
to the related command 'ntfy user'.
The command allows you to show the access control list, as well as change it, depending on how
it is called.
Usage:
ntfy access # Shows access control list (alias: 'ntfy user list')
ntfy access USERNAME # Shows access control entries for USERNAME
ntfy access USERNAME TOPIC PERMISSION # Allow/deny access for USERNAME to TOPIC
Arguments:
USERNAME an existing user, as created with 'ntfy user add', or "everyone"/"*"
to define access rules for anonymous/unauthenticated clients
TOPIC name of a topic with optional wildcards, e.g. "mytopic*"
PERMISSION one of the following:
- read-write (alias: rw)
- read-only (aliases: read, ro)
- write-only (aliases: write, wo)
- deny (alias: none)
Examples:
ntfy access # Shows access control list (alias: 'ntfy user list')
ntfy access phil # Shows access for user phil
ntfy access phil mytopic rw # Allow read-write access to mytopic for user phil
ntfy access everyone mytopic rw # Allow anonymous read-write access to mytopic
ntfy access everyone "up*" write # Allow anonymous write-only access to topics "up..."
ntfy access --reset # Reset entire access control list
ntfy access --reset phil # Reset all access for user phil
ntfy access --reset phil mytopic # Reset access for user phil and topic mytopic
`,
}
func execUserAccess(c *cli.Context) error {
if c.NArg() > 3 {
return errors.New("too many arguments, please check 'ntfy access --help' for usage details")
}
manager, err := createUserManager(c)
if err != nil {
return err
}
username := c.Args().Get(0)
if username == userEveryone {
username = user.Everyone
}
topic := c.Args().Get(1)
perms := c.Args().Get(2)
reset := c.Bool("reset")
if reset {
if perms != "" {
return errors.New("too many arguments, please check 'ntfy access --help' for usage details")
}
return resetAccess(c, manager, username, topic)
} else if perms == "" {
if topic != "" {
return errors.New("invalid syntax, please check 'ntfy access --help' for usage details")
}
return showAccess(c, manager, username)
}
return changeAccess(c, manager, username, topic, perms)
}
func changeAccess(c *cli.Context, manager *user.Manager, username string, topic string, perms string) error {
if !util.Contains([]string{"", "read-write", "rw", "read-only", "read", "ro", "write-only", "write", "wo", "none", "deny"}, perms) {
return errors.New("permission must be one of: read-write, read-only, write-only, or deny (or the aliases: read, ro, write, wo, none)")
}
permission, err := user.ParsePermission(perms)
if err != nil {
return err
}
u, err := manager.User(username)
if err == user.ErrUserNotFound {
return fmt.Errorf("user %s does not exist", username)
} else if u.Role == user.RoleAdmin {
return fmt.Errorf("user %s is an admin user, access control entries have no effect", username)
}
if err := manager.AllowAccess(username, topic, permission); err != nil {
return err
}
if permission.IsReadWrite() {
fmt.Fprintf(c.App.ErrWriter, "granted read-write access to topic %s\n\n", topic)
} else if permission.IsRead() {
fmt.Fprintf(c.App.ErrWriter, "granted read-only access to topic %s\n\n", topic)
} else if permission.IsWrite() {
fmt.Fprintf(c.App.ErrWriter, "granted write-only access to topic %s\n\n", topic)
} else {
fmt.Fprintf(c.App.ErrWriter, "revoked all access to topic %s\n\n", topic)
}
return showUserAccess(c, manager, username)
}
func resetAccess(c *cli.Context, manager *user.Manager, username, topic string) error {
if username == "" {
return resetAllAccess(c, manager)
} else if topic == "" {
return resetUserAccess(c, manager, username)
}
return resetUserTopicAccess(c, manager, username, topic)
}
func resetAllAccess(c *cli.Context, manager *user.Manager) error {
if err := manager.ResetAccess("", ""); err != nil {
return err
}
fmt.Fprintln(c.App.ErrWriter, "reset access for all users")
return nil
}
func resetUserAccess(c *cli.Context, manager *user.Manager, username string) error {
if err := manager.ResetAccess(username, ""); err != nil {
return err
}
fmt.Fprintf(c.App.ErrWriter, "reset access for user %s\n\n", username)
return showUserAccess(c, manager, username)
}
func resetUserTopicAccess(c *cli.Context, manager *user.Manager, username string, topic string) error {
if err := manager.ResetAccess(username, topic); err != nil {
return err
}
fmt.Fprintf(c.App.ErrWriter, "reset access for user %s and topic %s\n\n", username, topic)
return showUserAccess(c, manager, username)
}
func showAccess(c *cli.Context, manager *user.Manager, username string) error {
if username == "" {
return showAllAccess(c, manager)
}
return showUserAccess(c, manager, username)
}
func showAllAccess(c *cli.Context, manager *user.Manager) error {
users, err := manager.Users()
if err != nil {
return err
}
return showUsers(c, manager, users)
}
func showUserAccess(c *cli.Context, manager *user.Manager, username string) error {
users, err := manager.User(username)
if err == user.ErrUserNotFound {
return fmt.Errorf("user %s does not exist", username)
} else if err != nil {
return err
}
return showUsers(c, manager, []*user.User{users})
}
func showUsers(c *cli.Context, manager *user.Manager, users []*user.User) error {
for _, u := range users {
grants, err := manager.Grants(u.Name)
if err != nil {
return err
}
tier := "none"
if u.Tier != nil {
tier = u.Tier.Name
}
fmt.Fprintf(c.App.ErrWriter, "user %s (role: %s, tier: %s)\n", u.Name, u.Role, tier)
if u.Role == user.RoleAdmin {
fmt.Fprintf(c.App.ErrWriter, "- read-write access to all topics (admin role)\n")
} else if len(grants) > 0 {
for _, grant := range grants {
if grant.Allow.IsReadWrite() {
fmt.Fprintf(c.App.ErrWriter, "- read-write access to topic %s\n", grant.TopicPattern)
} else if grant.Allow.IsRead() {
fmt.Fprintf(c.App.ErrWriter, "- read-only access to topic %s\n", grant.TopicPattern)
} else if grant.Allow.IsWrite() {
fmt.Fprintf(c.App.ErrWriter, "- write-only access to topic %s\n", grant.TopicPattern)
} else {
fmt.Fprintf(c.App.ErrWriter, "- no access to topic %s\n", grant.TopicPattern)
}
}
} else {
fmt.Fprintf(c.App.ErrWriter, "- no topic-specific permissions\n")
}
if u.Name == user.Everyone {
access := manager.DefaultAccess()
if access.IsReadWrite() {
fmt.Fprintln(c.App.ErrWriter, "- read-write access to all (other) topics (server config)")
} else if access.IsRead() {
fmt.Fprintln(c.App.ErrWriter, "- read-only access to all (other) topics (server config)")
} else if access.IsWrite() {
fmt.Fprintln(c.App.ErrWriter, "- write-only access to all (other) topics (server config)")
} else {
fmt.Fprintln(c.App.ErrWriter, "- no access to any (other) topics (server config)")
}
}
}
return nil
}