More auth
This commit is contained in:
		
							parent
							
								
									393f95aeac
								
							
						
					
					
						commit
						460162737a
					
				
					 9 changed files with 264 additions and 230 deletions
				
			
		
							
								
								
									
										20
									
								
								auth/auth.go
									
										
									
									
									
								
							
							
						
						
									
										20
									
								
								auth/auth.go
									
										
									
									
									
								
							|  | @ -15,6 +15,7 @@ type Manager interface { | |||
| 	User(username string) (*User, error) | ||||
| 	ChangePassword(username, password string) error | ||||
| 	ChangeRole(username string, role Role) error | ||||
| 	DefaultAccess() (read bool, write bool) | ||||
| 	AllowAccess(username string, topic string, read bool, write bool) error | ||||
| 	ResetAccess(username string, topic string) error | ||||
| } | ||||
|  | @ -42,21 +43,14 @@ const ( | |||
| type Role string | ||||
| 
 | ||||
| const ( | ||||
| 	RoleAdmin = Role("admin") | ||||
| 	RoleUser  = Role("user") | ||||
| 	RoleNone  = Role("none") | ||||
| 	RoleAdmin     = Role("admin") | ||||
| 	RoleUser      = Role("user") | ||||
| 	RoleAnonymous = Role("anonymous") | ||||
| ) | ||||
| 
 | ||||
| var Everyone = &User{ | ||||
| 	Name: "", | ||||
| 	Role: RoleNone, | ||||
| } | ||||
| 
 | ||||
| var Roles = []Role{ | ||||
| 	RoleAdmin, | ||||
| 	RoleUser, | ||||
| 	RoleNone, | ||||
| } | ||||
| const ( | ||||
| 	Everyone = "*" | ||||
| ) | ||||
| 
 | ||||
| func AllowedRole(role Role) bool { | ||||
| 	return role == RoleUser || role == RoleAdmin | ||||
|  |  | |||
|  | @ -21,10 +21,6 @@ INSERT INTO access VALUES ('','write-all',1,1); | |||
| 
 | ||||
| */ | ||||
| 
 | ||||
| const ( | ||||
| 	bcryptCost = 11 | ||||
| ) | ||||
| 
 | ||||
| // Auther-related queries | ||||
| const ( | ||||
| 	createAuthTablesQueries = ` | ||||
|  | @ -51,26 +47,28 @@ const ( | |||
| 	selectTopicPermsQuery = ` | ||||
| 		SELECT read, write  | ||||
| 		FROM access  | ||||
| 		WHERE user IN ('', ?) AND topic = ? | ||||
| 		WHERE user IN ('*', ?) AND topic = ? | ||||
| 		ORDER BY user DESC | ||||
| 	` | ||||
| ) | ||||
| 
 | ||||
| // Manager-related queries | ||||
| const ( | ||||
| 	insertUserQuery           = `INSERT INTO user (user, pass, role) VALUES (?, ?, ?)` | ||||
| 	selectUsernamesQuery      = `SELECT user FROM user ORDER BY role, user` | ||||
| 	selectUserTopicPermsQuery = `SELECT topic, read, write FROM access WHERE user = ?` | ||||
| 	updateUserPassQuery       = `UPDATE user SET pass = ? WHERE user = ?` | ||||
| 	updateUserRoleQuery       = `UPDATE user SET role = ? WHERE user = ?` | ||||
| 	upsertAccessQuery         = ` | ||||
| 	insertUserQuery      = `INSERT INTO user (user, pass, role) VALUES (?, ?, ?)` | ||||
| 	selectUsernamesQuery = `SELECT user FROM user ORDER BY role, user` | ||||
| 	updateUserPassQuery  = `UPDATE user SET pass = ? WHERE user = ?` | ||||
| 	updateUserRoleQuery  = `UPDATE user SET role = ? WHERE user = ?` | ||||
| 	deleteUserQuery      = `DELETE FROM user WHERE user = ?` | ||||
| 
 | ||||
| 	upsertUserAccessQuery = ` | ||||
| 		INSERT INTO access (user, topic, read, write)  | ||||
| 		VALUES (?, ?, ?, ?) | ||||
| 		ON CONFLICT (user, topic) DO UPDATE SET read=excluded.read, write=excluded.write | ||||
| 	` | ||||
| 	deleteUserQuery      = `DELETE FROM user WHERE user = ?` | ||||
| 	deleteAllAccessQuery = `DELETE FROM access WHERE user = ?` | ||||
| 	deleteAccessQuery    = `DELETE FROM access WHERE user = ? AND topic = ?` | ||||
| 	selectUserAccessQuery  = `SELECT topic, read, write FROM access WHERE user = ?` | ||||
| 	deleteAllAccessQuery   = `DELETE FROM access` | ||||
| 	deleteUserAccessQuery  = `DELETE FROM access WHERE user = ?` | ||||
| 	deleteTopicAccessQuery = `DELETE FROM access WHERE user = ? AND topic = ?` | ||||
| ) | ||||
| 
 | ||||
| type SQLiteAuth struct { | ||||
|  | @ -106,6 +104,9 @@ func setupNewAuthDB(db *sql.DB) error { | |||
| } | ||||
| 
 | ||||
| func (a *SQLiteAuth) Authenticate(username, password string) (*User, error) { | ||||
| 	if username == Everyone { | ||||
| 		return nil, ErrUnauthorized | ||||
| 	} | ||||
| 	rows, err := a.db.Query(selectUserQuery, username) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
|  | @ -135,7 +136,7 @@ func (a *SQLiteAuth) Authorize(user *User, topic string, perm Permission) error | |||
| 	// Select the read/write permissions for this user/topic combo. The query may return two | ||||
| 	// rows (one for everyone, and one for the user), but prioritizes the user. The value for | ||||
| 	// user.Name may be empty (= everyone). | ||||
| 	var username string | ||||
| 	username := Everyone | ||||
| 	if user != nil { | ||||
| 		username = user.Name | ||||
| 	} | ||||
|  | @ -166,7 +167,7 @@ func (a *SQLiteAuth) resolvePerms(read, write bool, perm Permission) error { | |||
| } | ||||
| 
 | ||||
| func (a *SQLiteAuth) AddUser(username, password string, role Role) error { | ||||
| 	hash, err := bcrypt.GenerateFromPassword([]byte(password), bcryptCost) | ||||
| 	hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | @ -180,7 +181,7 @@ func (a *SQLiteAuth) RemoveUser(username string) error { | |||
| 	if _, err := a.db.Exec(deleteUserQuery, username); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if _, err := a.db.Exec(deleteAllAccessQuery, username); err != nil { | ||||
| 	if _, err := a.db.Exec(deleteUserAccessQuery, username); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
|  | @ -211,10 +212,18 @@ func (a *SQLiteAuth) Users() ([]*User, error) { | |||
| 		} | ||||
| 		users = append(users, user) | ||||
| 	} | ||||
| 	everyone, err := a.everyoneUser() | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	users = append(users, everyone) | ||||
| 	return users, nil | ||||
| } | ||||
| 
 | ||||
| func (a *SQLiteAuth) User(username string) (*User, error) { | ||||
| 	if username == Everyone { | ||||
| 		return a.everyoneUser() | ||||
| 	} | ||||
| 	urows, err := a.db.Query(selectUserQuery, username) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
|  | @ -229,26 +238,10 @@ func (a *SQLiteAuth) User(username string) (*User, error) { | |||
| 	} else if err := urows.Err(); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	arows, err := a.db.Query(selectUserTopicPermsQuery, username) | ||||
| 	grants, err := a.readGrants(username) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	defer arows.Close() | ||||
| 	grants := make([]Grant, 0) | ||||
| 	for arows.Next() { | ||||
| 		var topic string | ||||
| 		var read, write bool | ||||
| 		if err := arows.Scan(&topic, &read, &write); err != nil { | ||||
| 			return nil, err | ||||
| 		} else if err := arows.Err(); err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		grants = append(grants, Grant{ | ||||
| 			Topic: topic, | ||||
| 			Read:  read, | ||||
| 			Write: write, | ||||
| 		}) | ||||
| 	} | ||||
| 	return &User{ | ||||
| 		Name:   username, | ||||
| 		Pass:   hash, | ||||
|  | @ -257,8 +250,45 @@ func (a *SQLiteAuth) User(username string) (*User, error) { | |||
| 	}, nil | ||||
| } | ||||
| 
 | ||||
| func (a *SQLiteAuth) everyoneUser() (*User, error) { | ||||
| 	grants, err := a.readGrants(Everyone) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return &User{ | ||||
| 		Name:   Everyone, | ||||
| 		Pass:   "", | ||||
| 		Role:   RoleAnonymous, | ||||
| 		Grants: grants, | ||||
| 	}, nil | ||||
| } | ||||
| 
 | ||||
| func (a *SQLiteAuth) readGrants(username string) ([]Grant, error) { | ||||
| 	rows, err := a.db.Query(selectUserAccessQuery, username) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	defer rows.Close() | ||||
| 	grants := make([]Grant, 0) | ||||
| 	for rows.Next() { | ||||
| 		var topic string | ||||
| 		var read, write bool | ||||
| 		if err := rows.Scan(&topic, &read, &write); err != nil { | ||||
| 			return nil, err | ||||
| 		} else if err := rows.Err(); err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		grants = append(grants, Grant{ | ||||
| 			Topic: topic, | ||||
| 			Read:  read, | ||||
| 			Write: write, | ||||
| 		}) | ||||
| 	} | ||||
| 	return grants, nil | ||||
| } | ||||
| 
 | ||||
| func (a *SQLiteAuth) ChangePassword(username, password string) error { | ||||
| 	hash, err := bcrypt.GenerateFromPassword([]byte(password), bcryptCost) | ||||
| 	hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | @ -273,29 +303,32 @@ func (a *SQLiteAuth) ChangeRole(username string, role Role) error { | |||
| 		return err | ||||
| 	} | ||||
| 	if role == RoleAdmin { | ||||
| 		if _, err := a.db.Exec(deleteAllAccessQuery, username); err != nil { | ||||
| 		if _, err := a.db.Exec(deleteUserAccessQuery, username); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (a *SQLiteAuth) DefaultAccess() (read bool, write bool) { | ||||
| 	return a.defaultRead, a.defaultWrite | ||||
| } | ||||
| 
 | ||||
| func (a *SQLiteAuth) AllowAccess(username string, topic string, read bool, write bool) error { | ||||
| 	if _, err := a.db.Exec(upsertAccessQuery, username, topic, read, write); err != nil { | ||||
| 	if _, err := a.db.Exec(upsertUserAccessQuery, username, topic, read, write); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (a *SQLiteAuth) ResetAccess(username string, topic string) error { | ||||
| 	if topic == "" { | ||||
| 		if _, err := a.db.Exec(deleteAllAccessQuery, username); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} else { | ||||
| 		if _, err := a.db.Exec(deleteAccessQuery, username, topic); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	if username == "" && topic == "" { | ||||
| 		_, err := a.db.Exec(deleteAllAccessQuery, username) | ||||
| 		return err | ||||
| 	} else if topic == "" { | ||||
| 		_, err := a.db.Exec(deleteUserAccessQuery, username) | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| 	_, err := a.db.Exec(deleteTopicAccessQuery, username, topic) | ||||
| 	return err | ||||
| } | ||||
|  |  | |||
							
								
								
									
										174
									
								
								cmd/access.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										174
									
								
								cmd/access.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,174 @@ | |||
| package cmd | ||||
| 
 | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"github.com/urfave/cli/v2" | ||||
| 	"heckel.io/ntfy/auth" | ||||
| 	"heckel.io/ntfy/util" | ||||
| ) | ||||
| 
 | ||||
| /* | ||||
| 
 | ||||
| ntfy access                        # Shows access control list | ||||
| ntfy access phil                   # Shows access for user phil | ||||
| ntfy access phil mytopic           # Shows access for user phil and topic mytopic | ||||
| 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 --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 | ||||
| 
 | ||||
| */ | ||||
| 
 | ||||
| const ( | ||||
| 	userEveryone = "everyone" | ||||
| ) | ||||
| 
 | ||||
| var flagsAccess = append( | ||||
| 	userCommandFlags(), | ||||
| 	&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:    initConfigFileInputSource("config", flagsAccess), | ||||
| 	Action:    execUserAccess, | ||||
| 	Category:  categoryServer, | ||||
| } | ||||
| 
 | ||||
| func execUserAccess(c *cli.Context) error { | ||||
| 	manager, err := createAuthManager(c) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	username := c.Args().Get(0) | ||||
| 	if username == userEveryone { | ||||
| 		username = auth.Everyone | ||||
| 	} | ||||
| 	topic := c.Args().Get(1) | ||||
| 	perms := c.Args().Get(2) | ||||
| 	reset := c.Bool("reset") | ||||
| 	if reset { | ||||
| 		return resetAccess(c, manager, username, topic) | ||||
| 	} else if perms == "" { | ||||
| 		return showAccess(c, manager, username) | ||||
| 	} | ||||
| 	return changeAccess(c, manager, username, topic, perms) | ||||
| } | ||||
| 
 | ||||
| func changeAccess(c *cli.Context, manager auth.Manager, username string, topic string, perms string) error { | ||||
| 	if !util.InStringList([]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)") | ||||
| 	} | ||||
| 	read := util.InStringList([]string{"read-write", "rw", "read-only", "read", "ro"}, perms) | ||||
| 	write := util.InStringList([]string{"read-write", "rw", "write-only", "write", "wo"}, perms) | ||||
| 	if err := manager.AllowAccess(username, topic, read, write); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if read && write { | ||||
| 		fmt.Fprintf(c.App.Writer, "Granted read-write access to topic %s\n\n", topic) | ||||
| 	} else if read { | ||||
| 		fmt.Fprintf(c.App.Writer, "Granted read-only access to topic %s\n\n", topic) | ||||
| 	} else if write { | ||||
| 		fmt.Fprintf(c.App.Writer, "Granted write-only access to topic %s\n\n", topic) | ||||
| 	} else { | ||||
| 		fmt.Fprintf(c.App.Writer, "Revoked all access to topic %s\n\n", topic) | ||||
| 	} | ||||
| 	return showUserAccess(c, manager, username) | ||||
| } | ||||
| 
 | ||||
| func resetAccess(c *cli.Context, manager auth.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 auth.Manager) error { | ||||
| 	if err := manager.ResetAccess("", ""); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	fmt.Fprintln(c.App.Writer, "Reset access for all users") | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func resetUserAccess(c *cli.Context, manager auth.Manager, username string) error { | ||||
| 	if err := manager.ResetAccess(username, ""); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	fmt.Fprintf(c.App.Writer, "Reset access for user %s\n\n", username) | ||||
| 	return showUserAccess(c, manager, username) | ||||
| } | ||||
| 
 | ||||
| func resetUserTopicAccess(c *cli.Context, manager auth.Manager, username string, topic string) error { | ||||
| 	if err := manager.ResetAccess(username, topic); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	fmt.Fprintf(c.App.Writer, "Reset access for user %s and topic %s\n\n", username, topic) | ||||
| 	return showUserAccess(c, manager, username) | ||||
| } | ||||
| 
 | ||||
| func showAccess(c *cli.Context, manager auth.Manager, username string) error { | ||||
| 	if username == "" { | ||||
| 		return showAllAccess(c, manager) | ||||
| 	} | ||||
| 	return showUserAccess(c, manager, username) | ||||
| } | ||||
| 
 | ||||
| func showAllAccess(c *cli.Context, manager auth.Manager) error { | ||||
| 	users, err := manager.Users() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return showUsers(c, manager, users) | ||||
| } | ||||
| 
 | ||||
| func showUserAccess(c *cli.Context, manager auth.Manager, username string) error { | ||||
| 	users, err := manager.User(username) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return showUsers(c, manager, []*auth.User{users}) | ||||
| } | ||||
| 
 | ||||
| func showUsers(c *cli.Context, manager auth.Manager, users []*auth.User) error { | ||||
| 	for _, user := range users { | ||||
| 		fmt.Fprintf(c.App.Writer, "User %s (%s)\n", user.Name, user.Role) | ||||
| 		if user.Role == auth.RoleAdmin { | ||||
| 			fmt.Fprintf(c.App.ErrWriter, "- read-write access to all topics (admin role)\n") | ||||
| 		} else if len(user.Grants) > 0 { | ||||
| 			for _, grant := range user.Grants { | ||||
| 				if grant.Read && grant.Write { | ||||
| 					fmt.Fprintf(c.App.ErrWriter, "- read-write access to topic %s\n", grant.Topic) | ||||
| 				} else if grant.Read { | ||||
| 					fmt.Fprintf(c.App.ErrWriter, "- read-only access to topic %s\n", grant.Topic) | ||||
| 				} else if grant.Write { | ||||
| 					fmt.Fprintf(c.App.ErrWriter, "- write-only access to topic %s\n", grant.Topic) | ||||
| 				} else { | ||||
| 					fmt.Fprintf(c.App.ErrWriter, "- no access to topic %s\n", grant.Topic) | ||||
| 				} | ||||
| 			} | ||||
| 		} else { | ||||
| 			fmt.Fprintf(c.App.ErrWriter, "- no topic-specific permissions\n") | ||||
| 		} | ||||
| 		if user.Name == auth.Everyone { | ||||
| 			defaultRead, defaultWrite := manager.DefaultAccess() | ||||
| 			if defaultRead && defaultWrite { | ||||
| 				fmt.Fprintln(c.App.ErrWriter, "- read-write access to all (other) topics (server config)") | ||||
| 			} else if defaultRead { | ||||
| 				fmt.Fprintln(c.App.ErrWriter, "- read-only access to all (other) topics (server config)") | ||||
| 			} else if defaultWrite { | ||||
| 				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 | ||||
| } | ||||
|  | @ -37,8 +37,7 @@ func New() *cli.App { | |||
| 			// Server commands | ||||
| 			cmdServe, | ||||
| 			cmdUser, | ||||
| 			cmdAllow, | ||||
| 			cmdDeny, | ||||
| 			cmdAccess, | ||||
| 
 | ||||
| 			// Client commands | ||||
| 			cmdPublish, | ||||
|  |  | |||
							
								
								
									
										26
									
								
								cmd/user.go
									
										
									
									
									
								
							
							
						
						
									
										26
									
								
								cmd/user.go
									
										
									
									
									
								
							|  | @ -159,31 +159,7 @@ func execUserList(c *cli.Context) error { | |||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return showUsers(c, users) | ||||
| } | ||||
| 
 | ||||
| func showUsers(c *cli.Context, users []*auth.User) error { | ||||
| 	for _, user := range users { | ||||
| 		fmt.Fprintf(c.App.Writer, "User %s (%s)\n", user.Name, user.Role) | ||||
| 		if user.Role == auth.RoleAdmin { | ||||
| 			fmt.Fprintf(c.App.ErrWriter, "- read-write access to all topics (admin role)\n") | ||||
| 		} else if len(user.Grants) > 0 { | ||||
| 			for _, grant := range user.Grants { | ||||
| 				if grant.Read && grant.Write { | ||||
| 					fmt.Fprintf(c.App.ErrWriter, "- read-write access to topic %s\n", grant.Topic) | ||||
| 				} else if grant.Read { | ||||
| 					fmt.Fprintf(c.App.ErrWriter, "- read-only access to topic %s\n", grant.Topic) | ||||
| 				} else if grant.Write { | ||||
| 					fmt.Fprintf(c.App.ErrWriter, "- write-only access to topic %s\n", grant.Topic) | ||||
| 				} else { | ||||
| 					fmt.Fprintf(c.App.ErrWriter, "- no access to topic %s\n", grant.Topic) | ||||
| 				} | ||||
| 			} | ||||
| 		} else { | ||||
| 			fmt.Fprintf(c.App.ErrWriter, "- no topic-specific permissions\n") | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| 	return showUsers(c, manager, users) | ||||
| } | ||||
| 
 | ||||
| func createAuthManager(c *cli.Context) (auth.Manager, error) { | ||||
|  |  | |||
|  | @ -1,108 +0,0 @@ | |||
| package cmd | ||||
| 
 | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"github.com/urfave/cli/v2" | ||||
| 	"heckel.io/ntfy/auth" | ||||
| 	"heckel.io/ntfy/util" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	userEveryone = "everyone" | ||||
| ) | ||||
| 
 | ||||
| var flagsAllow = append( | ||||
| 	userCommandFlags(), | ||||
| 	&cli.BoolFlag{Name: "reset", Aliases: []string{"r"}, Usage: "reset access for user (and topic)"}, | ||||
| ) | ||||
| 
 | ||||
| var cmdAllow = &cli.Command{ | ||||
| 	Name:      "allow", | ||||
| 	Usage:     "Grant a user access to a topic", | ||||
| 	UsageText: "ntfy allow USERNAME TOPIC [read-write|read-only|write-only|none]", | ||||
| 	Flags:     flagsAllow, | ||||
| 	Before:    initConfigFileInputSource("config", flagsAllow), | ||||
| 	Action:    execUserAllow, | ||||
| 	Category:  categoryServer, | ||||
| } | ||||
| 
 | ||||
| func execUserAllow(c *cli.Context) error { | ||||
| 	username := c.Args().Get(0) | ||||
| 	topic := c.Args().Get(1) | ||||
| 	perms := c.Args().Get(2) | ||||
| 	reset := c.Bool("reset") | ||||
| 	if username == "" { | ||||
| 		return errors.New("username expected, type 'ntfy allow --help' for help") | ||||
| 	} else if !reset && topic == "" { | ||||
| 		return errors.New("topic expected, type 'ntfy allow --help' for help") | ||||
| 	} else if !util.InStringList([]string{"", "read-write", "rw", "read-only", "read", "ro", "write-only", "write", "wo", "none"}, perms) { | ||||
| 		return errors.New("permission must be one of: read-write, read-only, write-only, or none (or the aliases: read, ro, write, wo)") | ||||
| 	} | ||||
| 	if username == userEveryone { | ||||
| 		username = "" | ||||
| 	} | ||||
| 	read := util.InStringList([]string{"", "read-write", "rw", "read-only", "read", "ro"}, perms) | ||||
| 	write := util.InStringList([]string{"", "read-write", "rw", "write-only", "write", "wo"}, perms) | ||||
| 	manager, err := createAuthManager(c) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if reset { | ||||
| 		return doAccessReset(c, manager, username, topic) | ||||
| 	} | ||||
| 	return doAccessAllow(c, manager, username, topic, read, write) | ||||
| } | ||||
| 
 | ||||
| func doAccessAllow(c *cli.Context, manager auth.Manager, username string, topic string, read bool, write bool) error { | ||||
| 	if err := manager.AllowAccess(username, topic, read, write); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if username == "" { | ||||
| 		if read && write { | ||||
| 			fmt.Fprintf(c.App.Writer, "Anonymous users granted full access to topic %s\n", topic) | ||||
| 		} else if read { | ||||
| 			fmt.Fprintf(c.App.Writer, "Anonymous users granted read-only access to topic %s\n", topic) | ||||
| 		} else if write { | ||||
| 			fmt.Fprintf(c.App.Writer, "Anonymous users granted write-only access to topic %s\n", topic) | ||||
| 		} else { | ||||
| 			fmt.Fprintf(c.App.Writer, "Revoked all access to topic %s for all anonymous users\n", topic) | ||||
| 		} | ||||
| 	} else { | ||||
| 		if read && write { | ||||
| 			fmt.Fprintf(c.App.Writer, "User %s now has read-write access to topic %s\n", username, topic) | ||||
| 		} else if read { | ||||
| 			fmt.Fprintf(c.App.Writer, "User %s now has read-only access to topic %s\n", username, topic) | ||||
| 		} else if write { | ||||
| 			fmt.Fprintf(c.App.Writer, "User %s now has write-only access to topic %s\n", username, topic) | ||||
| 		} else { | ||||
| 			fmt.Fprintf(c.App.Writer, "Revoked all access to topic %s for user %s\n", topic, username) | ||||
| 		} | ||||
| 	} | ||||
| 	user, err := manager.User(username) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	fmt.Fprintln(c.App.Writer) | ||||
| 	return showUsers(c, []*auth.User{user}) | ||||
| } | ||||
| 
 | ||||
| func doAccessReset(c *cli.Context, manager auth.Manager, username, topic string) error { | ||||
| 	if err := manager.ResetAccess(username, topic); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if username == "" { | ||||
| 		if topic == "" { | ||||
| 			fmt.Fprintln(c.App.Writer, "Reset access for all anonymous users and all topics") | ||||
| 		} else { | ||||
| 			fmt.Fprintf(c.App.Writer, "Reset access to topic %s for all anonymous users\n", topic) | ||||
| 		} | ||||
| 	} else { | ||||
| 		if topic == "" { | ||||
| 			fmt.Fprintf(c.App.Writer, "Reset access for user %s to all topics\n", username) | ||||
| 		} else { | ||||
| 			fmt.Fprintf(c.App.Writer, "Reset access for user %s and topic %s\n", username, topic) | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | @ -1,35 +0,0 @@ | |||
| package cmd | ||||
| 
 | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"github.com/urfave/cli/v2" | ||||
| ) | ||||
| 
 | ||||
| var flagsDeny = userCommandFlags() | ||||
| var cmdDeny = &cli.Command{ | ||||
| 	Name:      "deny", | ||||
| 	Usage:     "Revoke user access from a topic", | ||||
| 	UsageText: "ntfy deny USERNAME TOPIC", | ||||
| 	Flags:     flagsDeny, | ||||
| 	Before:    initConfigFileInputSource("config", flagsDeny), | ||||
| 	Action:    execUserDeny, | ||||
| 	Category:  categoryServer, | ||||
| } | ||||
| 
 | ||||
| func execUserDeny(c *cli.Context) error { | ||||
| 	username := c.Args().Get(0) | ||||
| 	topic := c.Args().Get(1) | ||||
| 	if username == "" { | ||||
| 		return errors.New("username expected, type 'ntfy deny --help' for help") | ||||
| 	} else if topic == "" { | ||||
| 		return errors.New("topic expected, type 'ntfy deny --help' for help") | ||||
| 	} | ||||
| 	if username == userEveryone { | ||||
| 		username = "" | ||||
| 	} | ||||
| 	manager, err := createAuthManager(c) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return doAccessAllow(c, manager, username, topic, false, false) | ||||
| } | ||||
|  | @ -41,6 +41,7 @@ var ( | |||
| 	errHTTPBadRequestWebSocketsUpgradeHeaderMissing  = &errHTTP{40016, http.StatusBadRequest, "invalid request: client not using the websocket protocol", ""} | ||||
| 	errHTTPNotFound                                  = &errHTTP{40401, http.StatusNotFound, "page not found", ""} | ||||
| 	errHTTPUnauthorized                              = &errHTTP{40101, http.StatusUnauthorized, "unauthorized", ""} | ||||
| 	errHTTPForbidden                                 = &errHTTP{40301, http.StatusForbidden, "forbidden", ""} | ||||
| 	errHTTPTooManyRequestsLimitRequests              = &errHTTP{42901, http.StatusTooManyRequests, "limit reached: too many requests, please be nice", "https://ntfy.sh/docs/publish/#limitations"} | ||||
| 	errHTTPTooManyRequestsLimitEmails                = &errHTTP{42902, http.StatusTooManyRequests, "limit reached: too many emails, please be nice", "https://ntfy.sh/docs/publish/#limitations"} | ||||
| 	errHTTPTooManyRequestsLimitSubscriptions         = &errHTTP{42903, http.StatusTooManyRequests, "limit reached: too many active subscriptions, please be nice", "https://ntfy.sh/docs/publish/#limitations"} | ||||
|  |  | |||
|  | @ -1144,7 +1144,7 @@ func (s *Server) withAuth(next handleFunc, perm auth.Permission) handleFunc { | |||
| 		} | ||||
| 		if err := s.auth.Authorize(user, t.ID, perm); err != nil { | ||||
| 			log.Printf("unauthorized: %s", err.Error()) | ||||
| 			return errHTTPUnauthorized | ||||
| 			return errHTTPForbidden | ||||
| 		} | ||||
| 		return next(w, r, v) | ||||
| 	} | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue