2022-01-23 06:54:18 +01:00
package cmd
import (
"crypto/subtle"
"errors"
"fmt"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v2/altsrc"
"heckel.io/ntfy/auth"
"heckel.io/ntfy/util"
"strings"
)
/ *
-- -
dabbling for CLI
ntfy user allow phil mytopic
ntfy user allow phil mytopic -- read - only
ntfy user deny phil mytopic
ntfy user list
phil ( admin )
- read - write access to everything
ben ( user )
- read - write access to a topic alerts
- read access to
everyone ( no user )
- read - only access to topic announcements
* /
2022-01-23 21:30:30 +01:00
var flagsUser = userCommandFlags ( )
2022-01-23 06:54:18 +01:00
var cmdUser = & cli . Command {
Name : "user" ,
Usage : "Manage users and access to topics" ,
UsageText : "ntfy user [add|del|...] ..." ,
Flags : flagsUser ,
Before : initConfigFileInputSource ( "config" , flagsUser ) ,
2022-01-23 07:00:38 +01:00
Category : categoryServer ,
2022-01-23 06:54:18 +01:00
Subcommands : [ ] * cli . Command {
{
Name : "add" ,
Aliases : [ ] string { "a" } ,
Usage : "add user to auth database" ,
Action : execUserAdd ,
Flags : [ ] cli . Flag {
& cli . StringFlag { Name : "role" , Aliases : [ ] string { "r" } , Value : string ( auth . RoleUser ) , Usage : "user role" } ,
} ,
} ,
{
Name : "remove" ,
Aliases : [ ] string { "del" , "rm" } ,
Usage : "remove user from auth database" ,
Action : execUserDel ,
} ,
{
Name : "change-pass" ,
2022-01-23 21:30:30 +01:00
Aliases : [ ] string { "chp" } ,
2022-01-23 06:54:18 +01:00
Usage : "change user password" ,
Action : execUserChangePass ,
} ,
2022-01-23 21:30:30 +01:00
{
Name : "change-role" ,
Aliases : [ ] string { "chr" } ,
Usage : "change user role" ,
Action : execUserChangeRole ,
} ,
{
Name : "list" ,
Aliases : [ ] string { "chr" } ,
Usage : "change user role" ,
2022-01-24 05:02:39 +01:00
Action : execUserList ,
2022-01-23 21:30:30 +01:00
} ,
2022-01-23 06:54:18 +01:00
} ,
}
func execUserAdd ( c * cli . Context ) error {
2022-01-23 21:30:30 +01:00
username := c . Args ( ) . Get ( 0 )
role := auth . Role ( c . String ( "role" ) )
if username == "" {
2022-01-23 06:54:18 +01:00
return errors . New ( "username expected, type 'ntfy user add --help' for help" )
2022-01-23 21:30:30 +01:00
} else if ! auth . AllowedRole ( role ) {
2022-01-23 06:54:18 +01:00
return errors . New ( "role must be either 'user' or 'admin'" )
}
password , err := readPassword ( c )
if err != nil {
return err
}
manager , err := createAuthManager ( c )
if err != nil {
return err
}
if err := manager . AddUser ( username , password , auth . Role ( role ) ) ; err != nil {
return err
}
fmt . Fprintf ( c . App . ErrWriter , "User %s added with role %s\n" , username , role )
return nil
}
func execUserDel ( c * cli . Context ) error {
2022-01-23 21:30:30 +01:00
username := c . Args ( ) . Get ( 0 )
if username == "" {
2022-01-23 06:54:18 +01:00
return errors . New ( "username expected, type 'ntfy user del --help' for help" )
}
manager , err := createAuthManager ( c )
if err != nil {
return err
}
if err := manager . RemoveUser ( username ) ; err != nil {
return err
}
fmt . Fprintf ( c . App . ErrWriter , "User %s removed\n" , username )
return nil
}
func execUserChangePass ( c * cli . Context ) error {
2022-01-23 21:30:30 +01:00
username := c . Args ( ) . Get ( 0 )
if username == "" {
2022-01-23 06:54:18 +01:00
return errors . New ( "username expected, type 'ntfy user change-pass --help' for help" )
}
password , err := readPassword ( c )
if err != nil {
return err
}
manager , err := createAuthManager ( 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 )
return nil
}
2022-01-23 21:30:30 +01:00
func execUserChangeRole ( c * cli . Context ) error {
username := c . Args ( ) . Get ( 0 )
role := auth . Role ( c . Args ( ) . Get ( 1 ) )
if username == "" || ! auth . AllowedRole ( role ) {
return errors . New ( "username and new role expected, type 'ntfy user change-role --help' for help" )
}
manager , err := createAuthManager ( c )
if err != nil {
return err
}
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 )
return nil
}
2022-01-24 05:02:39 +01:00
func execUserList ( c * cli . Context ) error {
manager , err := createAuthManager ( c )
if err != nil {
return err
}
users , err := manager . Users ( )
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
}
2022-01-23 06:54:18 +01:00
func createAuthManager ( c * cli . Context ) ( auth . Manager , error ) {
authFile := c . String ( "auth-file" )
2022-01-23 07:00:38 +01:00
authDefaultAccess := c . String ( "auth-default-access" )
2022-01-23 06:54:18 +01:00
if authFile == "" {
return nil , errors . New ( "option auth-file not set; auth is unconfigured for this server" )
} else if ! util . FileExists ( authFile ) {
return nil , errors . New ( "auth-file does not exist; please start the server at least once to create it" )
2022-01-23 07:00:38 +01:00
} else if ! util . InStringList ( [ ] string { "read-write" , "read-only" , "deny-all" } , authDefaultAccess ) {
return nil , errors . New ( "if set, auth-default-access must start set to 'read-write', 'read-only' or 'deny-all'" )
2022-01-23 06:54:18 +01:00
}
2022-01-23 07:00:38 +01:00
authDefaultRead := authDefaultAccess == "read-write" || authDefaultAccess == "read-only"
authDefaultWrite := authDefaultAccess == "read-write"
2022-01-23 06:54:18 +01:00
return auth . NewSQLiteAuth ( authFile , authDefaultRead , authDefaultWrite )
}
func readPassword ( c * cli . Context ) ( string , error ) {
fmt . Fprint ( c . App . ErrWriter , "Enter Password: " )
password , err := util . ReadPassword ( c . App . Reader )
if err != nil {
return "" , err
}
fmt . Fprintf ( c . App . ErrWriter , "\r%s\rConfirm: " , strings . Repeat ( " " , 25 ) )
confirm , err := util . ReadPassword ( c . App . Reader )
if err != nil {
return "" , err
}
fmt . Fprintf ( c . App . ErrWriter , "\r%s\r" , strings . Repeat ( " " , 25 ) )
if subtle . ConstantTimeCompare ( confirm , password ) != 1 {
return "" , errors . New ( "passwords do not match: try it again, but this time type slooowwwlly" )
}
return string ( password ) , nil
}
2022-01-23 21:30:30 +01:00
func userCommandFlags ( ) [ ] cli . Flag {
return [ ] cli . Flag {
& cli . StringFlag { Name : "config" , Aliases : [ ] string { "c" } , EnvVars : [ ] string { "NTFY_CONFIG_FILE" } , Value : "/etc/ntfy/server.yml" , DefaultText : "/etc/ntfy/server.yml" , Usage : "config file" } ,
altsrc . NewStringFlag ( & cli . StringFlag { Name : "auth-file" , Aliases : [ ] string { "H" } , EnvVars : [ ] string { "NTFY_AUTH_FILE" } , Usage : "auth database file used for access control" } ) ,
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" } ) ,
}
}