Quellcode durchsuchen

CLI tests for 'ntfy user'

Philipp Heckel vor 4 Jahren
Ursprung
Commit
29c2fc5472
4 geänderte Dateien mit 158 neuen und 18 gelöschten Zeilen
  1. 42 14
      cmd/user.go
  2. 103 0
      cmd/user_test.go
  3. 4 4
      go.mod
  4. 9 0
      go.sum

+ 42 - 14
cmd/user.go

@@ -23,8 +23,9 @@ var cmdUser = &cli.Command{
 		{
 			Name:      "add",
 			Aliases:   []string{"a"},
-			Usage:     "add user",
+			Usage:     "Adds a new user",
 			UsageText: "ntfy user add [--role=admin|user] USERNAME",
+			Before:    inheritRootReaderFunc,
 			Action:    execUserAdd,
 			Flags: []cli.Flag{
 				&cli.StringFlag{Name: "role", Aliases: []string{"r"}, Value: string(auth.RoleUser), Usage: "user role"},
@@ -43,8 +44,9 @@ Examples:
 		{
 			Name:      "remove",
 			Aliases:   []string{"del", "rm"},
-			Usage:     "remove user",
+			Usage:     "Removes a user",
 			UsageText: "ntfy user remove USERNAME",
+			Before:    inheritRootReaderFunc,
 			Action:    execUserDel,
 			Description: `Remove a user from the ntfy user database.
 
@@ -55,8 +57,9 @@ Example:
 		{
 			Name:      "change-pass",
 			Aliases:   []string{"chp"},
-			Usage:     "change user password",
+			Usage:     "Changes a user's password",
 			UsageText: "ntfy user change-pass USERNAME",
+			Before:    inheritRootReaderFunc,
 			Action:    execUserChangePass,
 			Description: `Change the password for the given user.
 
@@ -70,8 +73,9 @@ Example:
 		{
 			Name:      "change-role",
 			Aliases:   []string{"chr"},
-			Usage:     "change user role",
+			Usage:     "Changes the role of a user",
 			UsageText: "ntfy user change-role USERNAME ROLE",
+			Before:    inheritRootReaderFunc,
 			Action:    execUserChangeRole,
 			Description: `Change the role for the given user to admin or user.
 
@@ -92,7 +96,8 @@ Example:
 		{
 			Name:    "list",
 			Aliases: []string{"l"},
-			Usage:   "list users",
+			Usage:   "Shows a list of users",
+			Before:  inheritRootReaderFunc,
 			Action:  execUserList,
 		},
 	},
@@ -125,18 +130,21 @@ func execUserAdd(c *cli.Context) error {
 	} else if !auth.AllowedRole(role) {
 		return errors.New("role must be either 'user' or 'admin'")
 	}
-	password, err := readPassword(c)
+	manager, err := createAuthManager(c)
 	if err != nil {
 		return err
 	}
-	manager, err := createAuthManager(c)
+	if user, _ := manager.User(username); user != nil {
+		return fmt.Errorf("user %s already exists", username)
+	}
+	password, err := readPassword(c)
 	if err != nil {
 		return err
 	}
-	if err := manager.AddUser(username, password, auth.Role(role)); err != nil {
+	if err := manager.AddUser(username, password, role); err != nil {
 		return err
 	}
-	fmt.Fprintf(c.App.ErrWriter, "User %s added with role %s\n", username, role)
+	fmt.Fprintf(c.App.ErrWriter, "user %s added with role %s\n", username, role)
 	return nil
 }
 
@@ -151,10 +159,13 @@ func execUserDel(c *cli.Context) error {
 	if err != nil {
 		return err
 	}
+	if _, err := manager.User(username); err == auth.ErrNotFound {
+		return fmt.Errorf("user %s does not exist", username)
+	}
 	if err := manager.RemoveUser(username); err != nil {
 		return err
 	}
-	fmt.Fprintf(c.App.ErrWriter, "User %s removed\n", username)
+	fmt.Fprintf(c.App.ErrWriter, "user %s removed\n", username)
 	return nil
 }
 
@@ -165,18 +176,21 @@ func execUserChangePass(c *cli.Context) error {
 	} else if username == userEveryone {
 		return errors.New("username not allowed")
 	}
-	password, err := readPassword(c)
+	manager, err := createAuthManager(c)
 	if err != nil {
 		return err
 	}
-	manager, err := createAuthManager(c)
+	if _, err := manager.User(username); err == auth.ErrNotFound {
+		return fmt.Errorf("user %s does not exist", username)
+	}
+	password, err := readPassword(c)
 	if err != nil {
 		return err
 	}
 	if err := manager.ChangePassword(username, password); err != nil {
 		return err
 	}
-	fmt.Fprintf(c.App.ErrWriter, "Changed password for user %s\n", username)
+	fmt.Fprintf(c.App.ErrWriter, "changed password for user %s\n", username)
 	return nil
 }
 
@@ -192,10 +206,13 @@ func execUserChangeRole(c *cli.Context) error {
 	if err != nil {
 		return err
 	}
+	if _, err := manager.User(username); err == auth.ErrNotFound {
+		return fmt.Errorf("user %s does not exist", username)
+	}
 	if err := manager.ChangeRole(username, role); err != nil {
 		return err
 	}
-	fmt.Fprintf(c.App.ErrWriter, "Changed role for user %s to %s\n", username, role)
+	fmt.Fprintf(c.App.ErrWriter, "changed role for user %s to %s\n", username, role)
 	return nil
 }
 
@@ -251,3 +268,14 @@ func userCommandFlags() []cli.Flag {
 		altsrc.NewStringFlag(&cli.StringFlag{Name: "auth-default-access", Aliases: []string{"p"}, EnvVars: []string{"NTFY_AUTH_DEFAULT_ACCESS"}, Value: "read-write", Usage: "default permissions if no matching entries in the auth database are found"}),
 	}
 }
+
+// inheritRootReaderFunc is a workaround for a urfave/cli bug that makes subcommands not inherit the App.Reader.
+// This bug was fixed in master, but not in v2.3.0.
+func inheritRootReaderFunc(ctx *cli.Context) error {
+	for _, c := range ctx.Lineage() {
+		if c.App != nil && c.App.Reader != nil {
+			ctx.App.Reader = c.App.Reader
+		}
+	}
+	return nil
+}

+ 103 - 0
cmd/user_test.go

@@ -0,0 +1,103 @@
+package cmd
+
+import (
+	"github.com/stretchr/testify/require"
+	"github.com/urfave/cli/v2"
+	"heckel.io/ntfy/server"
+	"heckel.io/ntfy/test"
+	"path/filepath"
+	"testing"
+)
+
+func TestCLI_User_Add(t *testing.T) {
+	s, conf, port := newTestServerWithAuth(t)
+	defer test.StopServer(t, s, port)
+
+	app, stdin, _, stderr := newTestApp()
+	stdin.WriteString("mypass\nmypass")
+	require.Nil(t, runUserCommand(app, conf, "add", "phil"))
+	require.Contains(t, stderr.String(), "user phil added with role user")
+}
+
+func TestCLI_User_Add_Exists(t *testing.T) {
+	s, conf, port := newTestServerWithAuth(t)
+	defer test.StopServer(t, s, port)
+
+	app, stdin, _, stderr := newTestApp()
+	stdin.WriteString("mypass\nmypass")
+	require.Nil(t, runUserCommand(app, conf, "add", "phil"))
+	require.Contains(t, stderr.String(), "user phil added with role user")
+
+	app, stdin, _, stderr = newTestApp()
+	stdin.WriteString("mypass\nmypass")
+	err := runUserCommand(app, conf, "add", "phil")
+	require.Error(t, err)
+	require.Contains(t, err.Error(), "user phil already exists")
+}
+
+func TestCLI_User_Add_Admin(t *testing.T) {
+	s, conf, port := newTestServerWithAuth(t)
+	defer test.StopServer(t, s, port)
+
+	app, stdin, _, stderr := newTestApp()
+	stdin.WriteString("mypass\nmypass")
+	require.Nil(t, runUserCommand(app, conf, "add", "--role=admin", "phil"))
+	require.Contains(t, stderr.String(), "user phil added with role admin")
+}
+
+func TestCLI_User_Add_Password_Mismatch(t *testing.T) {
+	s, conf, port := newTestServerWithAuth(t)
+	defer test.StopServer(t, s, port)
+
+	app, stdin, _, _ := newTestApp()
+	stdin.WriteString("mypass\nNOTMATCH")
+	err := runUserCommand(app, conf, "add", "phil")
+	require.Error(t, err)
+	require.Contains(t, err.Error(), "passwords do not match: try it again, but this time type slooowwwlly")
+}
+
+func TestCLI_User_ChangePass(t *testing.T) {
+	s, conf, port := newTestServerWithAuth(t)
+	defer test.StopServer(t, s, port)
+
+	// Add user
+	app, stdin, _, stderr := newTestApp()
+	stdin.WriteString("mypass\nmypass")
+	require.Nil(t, runUserCommand(app, conf, "add", "phil"))
+	require.Contains(t, stderr.String(), "user phil added with role user")
+
+	// Change pass
+	app, stdin, _, stderr = newTestApp()
+	stdin.WriteString("newpass\nnewpass")
+	require.Nil(t, runUserCommand(app, conf, "change-pass", "phil"))
+	require.Contains(t, stderr.String(), "changed password for user phil")
+}
+
+func newTestServerWithAuth(t *testing.T) (s *server.Server, conf *server.Config, port int) {
+	conf = server.NewConfig()
+	conf.AuthFile = filepath.Join(t.TempDir(), "user.db")
+	conf.AuthDefaultRead = false
+	conf.AuthDefaultWrite = false
+	s, port = test.StartServerWithConfig(t, conf)
+	return
+}
+
+func runUserCommand(app *cli.App, conf *server.Config, args ...string) error {
+	var defaultAccess string
+	if conf.AuthDefaultRead && conf.AuthDefaultWrite {
+		defaultAccess = "read-write"
+	} else if conf.AuthDefaultRead && !conf.AuthDefaultWrite {
+		defaultAccess = "read-only"
+	} else if !conf.AuthDefaultRead && conf.AuthDefaultWrite {
+		defaultAccess = "write-only"
+	} else if !conf.AuthDefaultRead && !conf.AuthDefaultWrite {
+		defaultAccess = "deny-all"
+	}
+	userArgs := []string{
+		"ntfy",
+		"user",
+		"--auth-file=" + conf.AuthFile,
+		"--auth-default-access=" + defaultAccess,
+	}
+	return app.Run(append(userArgs, args...))
+}

+ 4 - 4
go.mod

@@ -20,13 +20,13 @@ require (
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
 	golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
 	golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11
-	google.golang.org/api v0.66.0
+	google.golang.org/api v0.67.0
 	gopkg.in/yaml.v2 v2.4.0
 )
 
 require (
 	cloud.google.com/go v0.100.2 // indirect
-	cloud.google.com/go/compute v1.1.0 // indirect
+	cloud.google.com/go/compute v1.2.0 // indirect
 	cloud.google.com/go/iam v0.1.1 // indirect
 	github.com/AlekSi/pointer v1.0.0 // indirect
 	github.com/census-instrumentation/opencensus-proto v0.3.0 // indirect
@@ -46,11 +46,11 @@ require (
 	github.com/russross/blackfriday/v2 v2.1.0 // indirect
 	go.opencensus.io v0.23.0 // indirect
 	golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d // indirect
-	golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect
+	golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27 // indirect
 	golang.org/x/text v0.3.7 // indirect
 	golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
 	google.golang.org/appengine v1.6.7 // indirect
-	google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350 // indirect
+	google.golang.org/genproto v0.0.0-20220203182621-f4ae394cde3f // indirect
 	google.golang.org/grpc v1.44.0 // indirect
 	google.golang.org/protobuf v1.27.1 // indirect
 	gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect

+ 9 - 0
go.sum

@@ -39,6 +39,8 @@ cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM7
 cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow=
 cloud.google.com/go/compute v1.1.0 h1:pyPhehLfZ6pVzRgJmXGYvCY4K7WSWRhVw0AwhgVvS84=
 cloud.google.com/go/compute v1.1.0/go.mod h1:2NIffxgWfORSI7EOYMFatGTfjMLnqrOKBEyYb6NoRgA=
+cloud.google.com/go/compute v1.2.0 h1:EKki8sSdvDU0OO9mAXGwPXOTOgPz2l08R0/IutDH11I=
+cloud.google.com/go/compute v1.2.0/go.mod h1:xlogom/6gr8RJGBe7nT2eGsQYAFUbbv8dbC29qE3Xmw=
 cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
 cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
 cloud.google.com/go/firestore v1.6.1 h1:8rBq3zRjnHx8UtBvaOWqBB1xq9jH6/wltfQLlTMh2Fw=
@@ -440,6 +442,8 @@ golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0=
 golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27 h1:XDXtA5hveEEV8JB2l7nhMTp3t3cHp9ZpwcdjqyEWLlo=
+golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
@@ -553,6 +557,8 @@ google.golang.org/api v0.64.0/go.mod h1:931CdxA8Rm4t6zqTFGSsgwbAEZ2+GMYurbndwSim
 google.golang.org/api v0.65.0/go.mod h1:ArYhxgGadlWmqO1IqVujw6Cs8IdD33bTmzKo2Sh+cbg=
 google.golang.org/api v0.66.0 h1:CbGy4LEiXCVCiNEDFgGpWOVwsDT7E2Qej1ZvN1P7KPg=
 google.golang.org/api v0.66.0/go.mod h1:I1dmXYpX7HGwz/ejRxwQp2qj5bFAz93HiCU1C1oYd9M=
+google.golang.org/api v0.67.0 h1:lYaaLa+x3VVUhtosaK9xihwQ9H9KRa557REHwwZ2orM=
+google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -633,6 +639,9 @@ google.golang.org/genproto v0.0.0-20220114231437-d2e6a121cae0/go.mod h1:5CzLGKJ6
 google.golang.org/genproto v0.0.0-20220118154757-00ab72f36ad5/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
 google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350 h1:YxHp5zqIcAShDEvRr5/0rVESVS+njYF68PSdazrNLJo=
 google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20220201184016-50beb8ab5c44/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20220203182621-f4ae394cde3f h1:w9Sx4FBkwsN0jMZz8E42tMdmhZ5b2Z/vFx2LKAxxI9o=
+google.golang.org/genproto v0.0.0-20220203182621-f4ae394cde3f/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
 google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=