Răsfoiți Sursa

WIP: Predefined users

binwiederhier 7 luni în urmă
părinte
comite
efef587671
5 a modificat fișierele cu 48 adăugiri și 21 ștergeri
  1. 3 0
      cmd/serve.go
  2. 0 1
      cmd/user.go
  3. 1 0
      server/config.go
  4. 8 1
      server/server.go
  5. 36 19
      user/manager.go

+ 3 - 0
cmd/serve.go

@@ -52,6 +52,7 @@ var flagsServe = append(
 	altsrc.NewStringFlag(&cli.StringFlag{Name: "auth-file", Aliases: []string{"auth_file", "H"}, EnvVars: []string{"NTFY_AUTH_FILE"}, Usage: "auth database file used for access control"}),
 	altsrc.NewStringFlag(&cli.StringFlag{Name: "auth-file", Aliases: []string{"auth_file", "H"}, EnvVars: []string{"NTFY_AUTH_FILE"}, Usage: "auth database file used for access control"}),
 	altsrc.NewStringFlag(&cli.StringFlag{Name: "auth-startup-queries", Aliases: []string{"auth_startup_queries"}, EnvVars: []string{"NTFY_AUTH_STARTUP_QUERIES"}, Usage: "queries run when the auth database is initialized"}),
 	altsrc.NewStringFlag(&cli.StringFlag{Name: "auth-startup-queries", Aliases: []string{"auth_startup_queries"}, EnvVars: []string{"NTFY_AUTH_STARTUP_QUERIES"}, Usage: "queries run when the auth database is initialized"}),
 	altsrc.NewStringFlag(&cli.StringFlag{Name: "auth-default-access", Aliases: []string{"auth_default_access", "p"}, EnvVars: []string{"NTFY_AUTH_DEFAULT_ACCESS"}, Value: "read-write", Usage: "default permissions if no matching entries in the auth database are found"}),
 	altsrc.NewStringFlag(&cli.StringFlag{Name: "auth-default-access", Aliases: []string{"auth_default_access", "p"}, EnvVars: []string{"NTFY_AUTH_DEFAULT_ACCESS"}, Value: "read-write", Usage: "default permissions if no matching entries in the auth database are found"}),
+	altsrc.NewStringSliceFlag(&cli.StringSliceFlag{Name: "auth-users", Aliases: []string{"auth_users"}, EnvVars: []string{"NTFY_AUTH_USERS"}, Usage: "pre-provisioned declarative users"}),
 	altsrc.NewStringFlag(&cli.StringFlag{Name: "attachment-cache-dir", Aliases: []string{"attachment_cache_dir"}, EnvVars: []string{"NTFY_ATTACHMENT_CACHE_DIR"}, Usage: "cache directory for attached files"}),
 	altsrc.NewStringFlag(&cli.StringFlag{Name: "attachment-cache-dir", Aliases: []string{"attachment_cache_dir"}, EnvVars: []string{"NTFY_ATTACHMENT_CACHE_DIR"}, Usage: "cache directory for attached files"}),
 	altsrc.NewStringFlag(&cli.StringFlag{Name: "attachment-total-size-limit", Aliases: []string{"attachment_total_size_limit", "A"}, EnvVars: []string{"NTFY_ATTACHMENT_TOTAL_SIZE_LIMIT"}, Value: util.FormatSize(server.DefaultAttachmentTotalSizeLimit), Usage: "limit of the on-disk attachment cache"}),
 	altsrc.NewStringFlag(&cli.StringFlag{Name: "attachment-total-size-limit", Aliases: []string{"attachment_total_size_limit", "A"}, EnvVars: []string{"NTFY_ATTACHMENT_TOTAL_SIZE_LIMIT"}, Value: util.FormatSize(server.DefaultAttachmentTotalSizeLimit), Usage: "limit of the on-disk attachment cache"}),
 	altsrc.NewStringFlag(&cli.StringFlag{Name: "attachment-file-size-limit", Aliases: []string{"attachment_file_size_limit", "Y"}, EnvVars: []string{"NTFY_ATTACHMENT_FILE_SIZE_LIMIT"}, Value: util.FormatSize(server.DefaultAttachmentFileSizeLimit), Usage: "per-file attachment size limit (e.g. 300k, 2M, 100M)"}),
 	altsrc.NewStringFlag(&cli.StringFlag{Name: "attachment-file-size-limit", Aliases: []string{"attachment_file_size_limit", "Y"}, EnvVars: []string{"NTFY_ATTACHMENT_FILE_SIZE_LIMIT"}, Value: util.FormatSize(server.DefaultAttachmentFileSizeLimit), Usage: "per-file attachment size limit (e.g. 300k, 2M, 100M)"}),
@@ -157,6 +158,7 @@ func execServe(c *cli.Context) error {
 	authFile := c.String("auth-file")
 	authFile := c.String("auth-file")
 	authStartupQueries := c.String("auth-startup-queries")
 	authStartupQueries := c.String("auth-startup-queries")
 	authDefaultAccess := c.String("auth-default-access")
 	authDefaultAccess := c.String("auth-default-access")
+	authUsers := c.StringSlice("auth-users")
 	attachmentCacheDir := c.String("attachment-cache-dir")
 	attachmentCacheDir := c.String("attachment-cache-dir")
 	attachmentTotalSizeLimitStr := c.String("attachment-total-size-limit")
 	attachmentTotalSizeLimitStr := c.String("attachment-total-size-limit")
 	attachmentFileSizeLimitStr := c.String("attachment-file-size-limit")
 	attachmentFileSizeLimitStr := c.String("attachment-file-size-limit")
@@ -406,6 +408,7 @@ func execServe(c *cli.Context) error {
 	conf.AuthFile = authFile
 	conf.AuthFile = authFile
 	conf.AuthStartupQueries = authStartupQueries
 	conf.AuthStartupQueries = authStartupQueries
 	conf.AuthDefault = authDefault
 	conf.AuthDefault = authDefault
+	conf.AuthUsers = nil // FIXME
 	conf.AttachmentCacheDir = attachmentCacheDir
 	conf.AttachmentCacheDir = attachmentCacheDir
 	conf.AttachmentTotalSizeLimit = attachmentTotalSizeLimit
 	conf.AttachmentTotalSizeLimit = attachmentTotalSizeLimit
 	conf.AttachmentFileSizeLimit = attachmentFileSizeLimit
 	conf.AttachmentFileSizeLimit = attachmentFileSizeLimit

+ 0 - 1
cmd/user.go

@@ -94,7 +94,6 @@ Example:
 
 
 You may set the NTFY_PASSWORD environment variable to pass the new password or NTFY_PASSWORD_HASH to pass
 You may set the NTFY_PASSWORD environment variable to pass the new password or NTFY_PASSWORD_HASH to pass
 directly the bcrypt hash. This is useful if you are updating users via scripts.
 directly the bcrypt hash. This is useful if you are updating users via scripts.
-
 `,
 `,
 		},
 		},
 		{
 		{

+ 1 - 0
server/config.go

@@ -93,6 +93,7 @@ type Config struct {
 	AuthFile                             string
 	AuthFile                             string
 	AuthStartupQueries                   string
 	AuthStartupQueries                   string
 	AuthDefault                          user.Permission
 	AuthDefault                          user.Permission
+	AuthUsers                            []user.User
 	AuthBcryptCost                       int
 	AuthBcryptCost                       int
 	AuthStatsQueueWriterInterval         time.Duration
 	AuthStatsQueueWriterInterval         time.Duration
 	AttachmentCacheDir                   string
 	AttachmentCacheDir                   string

+ 8 - 1
server/server.go

@@ -189,7 +189,14 @@ func New(conf *Config) (*Server, error) {
 	}
 	}
 	var userManager *user.Manager
 	var userManager *user.Manager
 	if conf.AuthFile != "" {
 	if conf.AuthFile != "" {
-		userManager, err = user.NewManager(conf.AuthFile, conf.AuthStartupQueries, conf.AuthDefault, conf.AuthBcryptCost, conf.AuthStatsQueueWriterInterval)
+		authConfig := &user.Config{
+			Filename:            conf.AuthFile,
+			StartupQueries:      conf.AuthStartupQueries,
+			DefaultAccess:       conf.AuthDefault,
+			BcryptCost:          conf.AuthBcryptCost,
+			QueueWriterInterval: conf.AuthStatsQueueWriterInterval,
+		}
+		userManager, err = user.NewManager(authConfig)
 		if err != nil {
 		if err != nil {
 			return nil, err
 			return nil, err
 		}
 		}

+ 36 - 19
user/manager.go

@@ -441,36 +441,53 @@ var (
 // Manager is an implementation of Manager. It stores users and access control list
 // Manager is an implementation of Manager. It stores users and access control list
 // in a SQLite database.
 // in a SQLite database.
 type Manager struct {
 type Manager struct {
-	db            *sql.DB
-	defaultAccess Permission              // Default permission if no ACL matches
-	statsQueue    map[string]*Stats       // "Queue" to asynchronously write user stats to the database (UserID -> Stats)
-	tokenQueue    map[string]*TokenUpdate // "Queue" to asynchronously write token access stats to the database (Token ID -> TokenUpdate)
-	bcryptCost    int                     // Makes testing easier
-	mu            sync.Mutex
+	config     *Config
+	db         *sql.DB
+	statsQueue map[string]*Stats       // "Queue" to asynchronously write user stats to the database (UserID -> Stats)
+	tokenQueue map[string]*TokenUpdate // "Queue" to asynchronously write token access stats to the database (Token ID -> TokenUpdate)
+	mu         sync.Mutex
+}
+
+type Config struct {
+	Filename            string
+	StartupQueries      string
+	DefaultAccess       Permission          // Default permission if no ACL matches
+	ProvisionedUsers    []*User             // Predefined users to create on startup
+	ProvisionedAccess   map[string][]*Grant // Predefined access grants to create on startup
+	BcryptCost          int                 // Makes testing easier
+	QueueWriterInterval time.Duration
 }
 }
 
 
 var _ Auther = (*Manager)(nil)
 var _ Auther = (*Manager)(nil)
 
 
 // NewManager creates a new Manager instance
 // NewManager creates a new Manager instance
-func NewManager(filename, startupQueries string, defaultAccess Permission, bcryptCost int, queueWriterInterval time.Duration) (*Manager, error) {
-	db, err := sql.Open("sqlite3", filename)
+func NewManager(config *Config) (*Manager, error) {
+	// Set defaults
+	if config.BcryptCost <= 0 {
+		config.BcryptCost = DefaultUserPasswordBcryptCost
+	}
+	if config.QueueWriterInterval.Seconds() <= 0 {
+		config.QueueWriterInterval = DefaultUserStatsQueueWriterInterval
+	}
+
+	// Open DB and run setup queries
+	db, err := sql.Open("sqlite3", config.Filename)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
 	if err := setupDB(db); err != nil {
 	if err := setupDB(db); err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
-	if err := runStartupQueries(db, startupQueries); err != nil {
+	if err := runStartupQueries(db, config.StartupQueries); err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
 	manager := &Manager{
 	manager := &Manager{
-		db:            db,
-		defaultAccess: defaultAccess,
-		statsQueue:    make(map[string]*Stats),
-		tokenQueue:    make(map[string]*TokenUpdate),
-		bcryptCost:    bcryptCost,
+		db:         db,
+		config:     config,
+		statsQueue: make(map[string]*Stats),
+		tokenQueue: make(map[string]*TokenUpdate),
 	}
 	}
-	go manager.asyncQueueWriter(queueWriterInterval)
+	go manager.asyncQueueWriter(config.QueueWriterInterval)
 	return manager, nil
 	return manager, nil
 }
 }
 
 
@@ -843,7 +860,7 @@ func (a *Manager) Authorize(user *User, topic string, perm Permission) error {
 	}
 	}
 	defer rows.Close()
 	defer rows.Close()
 	if !rows.Next() {
 	if !rows.Next() {
-		return a.resolvePerms(a.defaultAccess, perm)
+		return a.resolvePerms(a.config.DefaultAccess, perm)
 	}
 	}
 	var read, write bool
 	var read, write bool
 	if err := rows.Scan(&read, &write); err != nil {
 	if err := rows.Scan(&read, &write); err != nil {
@@ -873,7 +890,7 @@ func (a *Manager) AddUser(username, password string, role Role, hashed bool) err
 	if hashed {
 	if hashed {
 		hash = []byte(password)
 		hash = []byte(password)
 	} else {
 	} else {
-		hash, err = bcrypt.GenerateFromPassword([]byte(password), a.bcryptCost)
+		hash, err = bcrypt.GenerateFromPassword([]byte(password), a.config.BcryptCost)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
@@ -1205,7 +1222,7 @@ func (a *Manager) ChangePassword(username, password string, hashed bool) error {
 	if hashed {
 	if hashed {
 		hash = []byte(password)
 		hash = []byte(password)
 	} else {
 	} else {
-		hash, err = bcrypt.GenerateFromPassword([]byte(password), a.bcryptCost)
+		hash, err = bcrypt.GenerateFromPassword([]byte(password), a.config.BcryptCost)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
@@ -1387,7 +1404,7 @@ func (a *Manager) RemoveReservations(username string, topics ...string) error {
 
 
 // DefaultAccess returns the default read/write access if no access control entry matches
 // DefaultAccess returns the default read/write access if no access control entry matches
 func (a *Manager) DefaultAccess() Permission {
 func (a *Manager) DefaultAccess() Permission {
-	return a.defaultAccess
+	return a.config.DefaultAccess
 }
 }
 
 
 // AddTier creates a new tier in the database
 // AddTier creates a new tier in the database