mirror of
				https://github.com/binwiederhier/ntfy.git
				synced 2025-10-31 13:02:24 +01:00 
			
		
		
		
	Docblocking
This commit is contained in:
		
							parent
							
								
									26dde0f286
								
							
						
					
					
						commit
						89957e7058
					
				
					 3 changed files with 74 additions and 12 deletions
				
			
		
							
								
								
									
										2
									
								
								Makefile
									
										
									
									
									
								
							
							
						
						
									
										2
									
								
								Makefile
									
										
									
									
									
								
							|  | @ -80,7 +80,7 @@ vet: | ||||||
| 	go vet ./... | 	go vet ./... | ||||||
| 
 | 
 | ||||||
| lint: | lint: | ||||||
| 	which golint || go get -u golang.org/x/lint/golint | 	which golint || go install golang.org/x/lint/golint@latest | ||||||
| 	go list ./... | grep -v /vendor/ | xargs -L1 golint -set_exit_status | 	go list ./... | grep -v /vendor/ | xargs -L1 golint -set_exit_status | ||||||
| 
 | 
 | ||||||
| staticcheck: .PHONY | staticcheck: .PHONY | ||||||
|  |  | ||||||
							
								
								
									
										43
									
								
								auth/auth.go
									
										
									
									
									
								
							
							
						
						
									
										43
									
								
								auth/auth.go
									
										
									
									
									
								
							|  | @ -4,22 +4,53 @@ import "errors" | ||||||
| 
 | 
 | ||||||
| // Auther is a generic interface to implement password-based authentication and authorization | // Auther is a generic interface to implement password-based authentication and authorization | ||||||
| type Auther interface { | type Auther interface { | ||||||
| 	Authenticate(user, pass string) (*User, error) | 	// Authenticate checks username and password and returns a user if correct. The method | ||||||
|  | 	// returns in constant-ish time, regardless of whether the user exists or the password is | ||||||
|  | 	// correct or incorrect. | ||||||
|  | 	Authenticate(username, password string) (*User, error) | ||||||
|  | 
 | ||||||
|  | 	// Authorize returns nil if the given user has access to the given topic using the desired | ||||||
|  | 	// permission. The user param may be nil to signal an anonymous user. | ||||||
| 	Authorize(user *User, topic string, perm Permission) error | 	Authorize(user *User, topic string, perm Permission) error | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // Manager is an interface representing user and access management | ||||||
| type Manager interface { | type Manager interface { | ||||||
|  | 	// AddUser adds a user with the given username, password and role. The password should be hashed | ||||||
|  | 	// before it is stored in a persistence layer. | ||||||
| 	AddUser(username, password string, role Role) error | 	AddUser(username, password string, role Role) error | ||||||
|  | 
 | ||||||
|  | 	// RemoveUser deletes the user with the given username. The function returns nil on success, even | ||||||
|  | 	// if the user did not exist in the first place. | ||||||
| 	RemoveUser(username string) error | 	RemoveUser(username string) error | ||||||
|  | 
 | ||||||
|  | 	// Users returns a list of users. It always also returns the Everyone user ("*"). | ||||||
| 	Users() ([]*User, error) | 	Users() ([]*User, error) | ||||||
|  | 
 | ||||||
|  | 	// User returns the user with the given username if it exists, or ErrNotFound otherwise. | ||||||
|  | 	// You may also pass Everyone to retrieve the anonymous user and its Grant list. | ||||||
| 	User(username string) (*User, error) | 	User(username string) (*User, error) | ||||||
|  | 
 | ||||||
|  | 	// ChangePassword changes a user's password | ||||||
| 	ChangePassword(username, password string) error | 	ChangePassword(username, password string) error | ||||||
|  | 
 | ||||||
|  | 	// ChangeRole changes a user's role. When a role is changed from RoleUser to RoleAdmin, | ||||||
|  | 	// all existing access control entries (Grant) are removed, since they are no longer needed. | ||||||
| 	ChangeRole(username string, role Role) error | 	ChangeRole(username string, role Role) error | ||||||
| 	DefaultAccess() (read bool, write bool) | 
 | ||||||
|  | 	// AllowAccess adds or updates an entry in th access control list for a specific user. It controls | ||||||
|  | 	// read/write access to a topic. | ||||||
| 	AllowAccess(username string, topic string, read bool, write bool) error | 	AllowAccess(username string, topic string, read bool, write bool) error | ||||||
|  | 
 | ||||||
|  | 	// ResetAccess removes an access control list entry for a specific username/topic, or (if topic is | ||||||
|  | 	// empty) for an entire user. | ||||||
| 	ResetAccess(username string, topic string) error | 	ResetAccess(username string, topic string) error | ||||||
|  | 
 | ||||||
|  | 	// DefaultAccess returns the default read/write access if no access control entry matches | ||||||
|  | 	DefaultAccess() (read bool, write bool) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // User is a struct that represents a user | ||||||
| type User struct { | type User struct { | ||||||
| 	Name   string | 	Name   string | ||||||
| 	Hash   string // password hash (bcrypt) | 	Hash   string // password hash (bcrypt) | ||||||
|  | @ -27,35 +58,43 @@ type User struct { | ||||||
| 	Grants []Grant | 	Grants []Grant | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // Grant is a struct that represents an access control entry to a topic | ||||||
| type Grant struct { | type Grant struct { | ||||||
| 	Topic string | 	Topic string | ||||||
| 	Read  bool | 	Read  bool | ||||||
| 	Write bool | 	Write bool | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // Permission represents a read or write permission to a topic | ||||||
| type Permission int | type Permission int | ||||||
| 
 | 
 | ||||||
|  | // Permissions to a topic | ||||||
| const ( | const ( | ||||||
| 	PermissionRead  = Permission(1) | 	PermissionRead  = Permission(1) | ||||||
| 	PermissionWrite = Permission(2) | 	PermissionWrite = Permission(2) | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | // Role represents a user's role, either admin or regular user | ||||||
| type Role string | type Role string | ||||||
| 
 | 
 | ||||||
|  | // User roles | ||||||
| const ( | const ( | ||||||
| 	RoleAdmin     = Role("admin") | 	RoleAdmin     = Role("admin") | ||||||
| 	RoleUser      = Role("user") | 	RoleUser      = Role("user") | ||||||
| 	RoleAnonymous = Role("anonymous") | 	RoleAnonymous = Role("anonymous") | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | // Everyone is a special username representing anonymous users | ||||||
| const ( | const ( | ||||||
| 	Everyone = "*" | 	Everyone = "*" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | // AllowedRole returns true if the given role can be used for new users | ||||||
| func AllowedRole(role Role) bool { | func AllowedRole(role Role) bool { | ||||||
| 	return role == RoleUser || role == RoleAdmin | 	return role == RoleUser || role == RoleAdmin | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // Error constants used by the package | ||||||
| var ( | var ( | ||||||
| 	ErrUnauthenticated = errors.New("unauthenticated") | 	ErrUnauthenticated = errors.New("unauthenticated") | ||||||
| 	ErrUnauthorized    = errors.New("unauthorized") | 	ErrUnauthorized    = errors.New("unauthorized") | ||||||
|  |  | ||||||
|  | @ -61,6 +61,8 @@ const ( | ||||||
| 	deleteTopicAccessQuery = `DELETE FROM access WHERE user = ? AND topic = ?` | 	deleteTopicAccessQuery = `DELETE FROM access WHERE user = ? AND topic = ?` | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | // SQLiteAuth is an implementation of Auther and Manager. It stores users and access control list | ||||||
|  | // in a SQLite database. | ||||||
| type SQLiteAuth struct { | type SQLiteAuth struct { | ||||||
| 	db           *sql.DB | 	db           *sql.DB | ||||||
| 	defaultRead  bool | 	defaultRead  bool | ||||||
|  | @ -74,6 +76,7 @@ var ( | ||||||
| var _ Auther = (*SQLiteAuth)(nil) | var _ Auther = (*SQLiteAuth)(nil) | ||||||
| var _ Manager = (*SQLiteAuth)(nil) | var _ Manager = (*SQLiteAuth)(nil) | ||||||
| 
 | 
 | ||||||
|  | // NewSQLiteAuth creates a new SQLiteAuth instance | ||||||
| func NewSQLiteAuth(filename string, defaultRead, defaultWrite bool) (*SQLiteAuth, error) { | func NewSQLiteAuth(filename string, defaultRead, defaultWrite bool) (*SQLiteAuth, error) { | ||||||
| 	db, err := sql.Open("sqlite3", filename) | 	db, err := sql.Open("sqlite3", filename) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | @ -97,6 +100,9 @@ func setupNewAuthDB(db *sql.DB) error { | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // Authenticate checks username and password and returns a user if correct. The method | ||||||
|  | // returns in constant-ish time, regardless of whether the user exists or the password is | ||||||
|  | // correct or incorrect. | ||||||
| func (a *SQLiteAuth) Authenticate(username, password string) (*User, error) { | func (a *SQLiteAuth) Authenticate(username, password string) (*User, error) { | ||||||
| 	if username == Everyone { | 	if username == Everyone { | ||||||
| 		return nil, ErrUnauthenticated | 		return nil, ErrUnauthenticated | ||||||
|  | @ -113,17 +119,19 @@ func (a *SQLiteAuth) Authenticate(username, password string) (*User, error) { | ||||||
| 	return user, nil | 	return user, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // Authorize returns nil if the given user has access to the given topic using the desired | ||||||
|  | // permission. The user param may be nil to signal an anonymous user. | ||||||
| func (a *SQLiteAuth) Authorize(user *User, topic string, perm Permission) error { | func (a *SQLiteAuth) Authorize(user *User, topic string, perm Permission) error { | ||||||
| 	if user != nil && user.Role == RoleAdmin { | 	if user != nil && user.Role == RoleAdmin { | ||||||
| 		return nil // Admin can do everything | 		return nil // Admin can do everything | ||||||
| 	} | 	} | ||||||
| 	// 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). |  | ||||||
| 	username := Everyone | 	username := Everyone | ||||||
| 	if user != nil { | 	if user != nil { | ||||||
| 		username = user.Name | 		username = user.Name | ||||||
| 	} | 	} | ||||||
|  | 	// 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). | ||||||
| 	rows, err := a.db.Query(selectTopicPermsQuery, username, topic) | 	rows, err := a.db.Query(selectTopicPermsQuery, username, topic) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
|  | @ -150,8 +158,10 @@ func (a *SQLiteAuth) resolvePerms(read, write bool, perm Permission) error { | ||||||
| 	return ErrUnauthorized | 	return ErrUnauthorized | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // AddUser adds a user with the given username, password and role. The password should be hashed | ||||||
|  | // before it is stored in a persistence layer. | ||||||
| func (a *SQLiteAuth) AddUser(username, password string, role Role) error { | func (a *SQLiteAuth) AddUser(username, password string, role Role) error { | ||||||
| 	if !allowedUsernameRegex.MatchString(username) || (role != RoleAdmin && role != RoleUser) { | 	if !allowedUsernameRegex.MatchString(username) || !AllowedRole(role) { | ||||||
| 		return ErrInvalidArgument | 		return ErrInvalidArgument | ||||||
| 	} | 	} | ||||||
| 	hash, err := bcrypt.GenerateFromPassword([]byte(password), bcryptCost) | 	hash, err := bcrypt.GenerateFromPassword([]byte(password), bcryptCost) | ||||||
|  | @ -164,6 +174,8 @@ func (a *SQLiteAuth) AddUser(username, password string, role Role) error { | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // RemoveUser deletes the user with the given username. The function returns nil on success, even | ||||||
|  | // if the user did not exist in the first place. | ||||||
| func (a *SQLiteAuth) RemoveUser(username string) error { | func (a *SQLiteAuth) RemoveUser(username string) error { | ||||||
| 	if !allowedUsernameRegex.MatchString(username) || username == Everyone { | 	if !allowedUsernameRegex.MatchString(username) || username == Everyone { | ||||||
| 		return ErrInvalidArgument | 		return ErrInvalidArgument | ||||||
|  | @ -177,6 +189,7 @@ func (a *SQLiteAuth) RemoveUser(username string) error { | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // Users returns a list of users. It always also returns the Everyone user ("*"). | ||||||
| func (a *SQLiteAuth) Users() ([]*User, error) { | func (a *SQLiteAuth) Users() ([]*User, error) { | ||||||
| 	rows, err := a.db.Query(selectUsernamesQuery) | 	rows, err := a.db.Query(selectUsernamesQuery) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | @ -210,6 +223,8 @@ func (a *SQLiteAuth) Users() ([]*User, error) { | ||||||
| 	return users, nil | 	return users, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // User returns the user with the given username if it exists, or ErrNotFound otherwise. | ||||||
|  | // You may also pass Everyone to retrieve the anonymous user and its Grant list. | ||||||
| func (a *SQLiteAuth) User(username string) (*User, error) { | func (a *SQLiteAuth) User(username string) (*User, error) { | ||||||
| 	if username == Everyone { | 	if username == Everyone { | ||||||
| 		return a.everyoneUser() | 		return a.everyoneUser() | ||||||
|  | @ -277,6 +292,7 @@ func (a *SQLiteAuth) readGrants(username string) ([]Grant, error) { | ||||||
| 	return grants, nil | 	return grants, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // ChangePassword changes a user's password | ||||||
| func (a *SQLiteAuth) ChangePassword(username, password string) error { | func (a *SQLiteAuth) ChangePassword(username, password string) error { | ||||||
| 	hash, err := bcrypt.GenerateFromPassword([]byte(password), bcryptCost) | 	hash, err := bcrypt.GenerateFromPassword([]byte(password), bcryptCost) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | @ -288,8 +304,10 @@ func (a *SQLiteAuth) ChangePassword(username, password string) error { | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // ChangeRole changes a user's role. When a role is changed from RoleUser to RoleAdmin, | ||||||
|  | // all existing access control entries (Grant) are removed, since they are no longer needed. | ||||||
| func (a *SQLiteAuth) ChangeRole(username string, role Role) error { | func (a *SQLiteAuth) ChangeRole(username string, role Role) error { | ||||||
| 	if !allowedUsernameRegex.MatchString(username) || (role != RoleAdmin && role != RoleUser) { | 	if !allowedUsernameRegex.MatchString(username) || !AllowedRole(role) { | ||||||
| 		return ErrInvalidArgument | 		return ErrInvalidArgument | ||||||
| 	} | 	} | ||||||
| 	if _, err := a.db.Exec(updateUserRoleQuery, string(role), username); err != nil { | 	if _, err := a.db.Exec(updateUserRoleQuery, string(role), username); err != nil { | ||||||
|  | @ -303,10 +321,8 @@ func (a *SQLiteAuth) ChangeRole(username string, role Role) error { | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (a *SQLiteAuth) DefaultAccess() (read bool, write bool) { | // AllowAccess adds or updates an entry in th access control list for a specific user. It controls | ||||||
| 	return a.defaultRead, a.defaultWrite | // read/write access to a topic. | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (a *SQLiteAuth) AllowAccess(username string, topic string, read bool, write bool) error { | func (a *SQLiteAuth) AllowAccess(username string, topic string, read bool, write bool) error { | ||||||
| 	if _, err := a.db.Exec(upsertUserAccessQuery, username, topic, read, write); err != nil { | 	if _, err := a.db.Exec(upsertUserAccessQuery, username, topic, read, write); err != nil { | ||||||
| 		return err | 		return err | ||||||
|  | @ -314,6 +330,8 @@ func (a *SQLiteAuth) AllowAccess(username string, topic string, read bool, write | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // ResetAccess removes an access control list entry for a specific username/topic, or (if topic is | ||||||
|  | // empty) for an entire user. | ||||||
| func (a *SQLiteAuth) ResetAccess(username string, topic string) error { | func (a *SQLiteAuth) ResetAccess(username string, topic string) error { | ||||||
| 	if username == "" && topic == "" { | 	if username == "" && topic == "" { | ||||||
| 		_, err := a.db.Exec(deleteAllAccessQuery, username) | 		_, err := a.db.Exec(deleteAllAccessQuery, username) | ||||||
|  | @ -325,3 +343,8 @@ func (a *SQLiteAuth) ResetAccess(username string, topic string) error { | ||||||
| 	_, err := a.db.Exec(deleteTopicAccessQuery, username, topic) | 	_, err := a.db.Exec(deleteTopicAccessQuery, username, topic) | ||||||
| 	return err | 	return err | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | // DefaultAccess returns the default read/write access if no access control entry matches | ||||||
|  | func (a *SQLiteAuth) DefaultAccess() (read bool, write bool) { | ||||||
|  | 	return a.defaultRead, a.defaultWrite | ||||||
|  | } | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Philipp Heckel
						Philipp Heckel