diff --git a/CHANGELOG.md b/CHANGELOG.md index 3011188..d3cc78d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ * Add 'host update' command (fix [#2](https://github.com/moul/sshportal/issues/2)) * Add 'user update' command (fix [#3](https://github.com/moul/sshportal/issues/3)) * Add 'acl update' command (fix [#4](https://github.com/moul/sshportal/issues/4)) +* Allow connecting to the shell mode with the registered username or email (fix [#5](https://github.com/moul/sshportal/issues/5)) +* Add 'listhosts' role (fix [#5](https://github.com/moul/sshportal/issues/5)) ## v1.2.0 (2017-11-22) diff --git a/Makefile b/Makefile index abf1a37..8db5e48 100644 --- a/Makefile +++ b/Makefile @@ -24,7 +24,7 @@ _docker_install: .PHONY: dev dev: -go get github.com/githubnemo/CompileDaemon - CompileDaemon -exclude-dir=.git -exclude=".#*" -color=true -command="./sshportal --demo --debug --port=$(PORT)" . + CompileDaemon -exclude-dir=.git -exclude=".#*" -color=true -command="./sshportal --demo --debug --bind-address=:$(PORT)" . .PHONY: test test: diff --git a/db.go b/db.go index 2130888..4fa7097 100644 --- a/db.go +++ b/db.go @@ -65,10 +65,16 @@ type UserKey struct { Comment string `valid:"optional"` } +type UserRole struct { + gorm.Model + Name string `valid:"required,length(1|32),unix_user"` + Users []*User `gorm:"many2many:user_user_roles"` +} + type User struct { // FIXME: use uuid for ID gorm.Model - IsAdmin bool + Roles []*UserRole `gorm:"many2many:user_user_roles"` Email string `valid:"required,email"` Name string `valid:"required,length(1|32),unix_user"` Keys []*UserKey `gorm:"ForeignKey:UserID"` @@ -188,10 +194,31 @@ func UserGroupsByIdentifiers(db *gorm.DB, identifiers []string) *gorm.DB { // User helpers func UsersPreload(db *gorm.DB) *gorm.DB { - return db.Preload("Groups").Preload("Keys") + return db.Preload("Groups").Preload("Keys").Preload("Roles") } func UsersByIdentifiers(db *gorm.DB, identifiers []string) *gorm.DB { - return db.Where("id IN (?)", identifiers).Or("email IN (?)", identifiers) + return db.Where("id IN (?)", identifiers).Or("email IN (?)", identifiers).Or("name IN (?)", identifiers) +} +func UserHasRole(user User, name string) bool { + for _, role := range user.Roles { + if role.Name == name { + return true + } + } + return false +} +func UserCheckRoles(user User, names []string) error { + ok := false + for _, name := range names { + if UserHasRole(user, name) { + ok = true + break + } + } + if ok { + return nil + } + return fmt.Errorf("you don't have permission to access this feature (requires any of these roles: '%s')", strings.Join(names, "', '")) } // ACL helpers @@ -209,3 +236,11 @@ func UserKeysPreload(db *gorm.DB) *gorm.DB { func UserKeysByIdentifiers(db *gorm.DB, identifiers []string) *gorm.DB { return db.Where("id IN (?)", identifiers) } + +// UserRole helpers +func UserRolesPreload(db *gorm.DB) *gorm.DB { + return db.Preload("Users") +} +func UserRolesByIdentifiers(db *gorm.DB, identifiers []string) *gorm.DB { + return db.Where("id IN (?)", identifiers).Or("name IN (?)", identifiers) +} diff --git a/dbinit.go b/dbinit.go index b5607f0..a076c2d 100644 --- a/dbinit.go +++ b/dbinit.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "log" "os" @@ -201,6 +202,96 @@ func dbInit(db *gorm.DB) error { Rollback: func(tx *gorm.DB) error { return db.Model(&HostGroup{}).RemoveIndex("uix_hostgroups_name").Error }, + }, { + ID: "15", + Migrate: func(tx *gorm.DB) error { + type UserRole struct { + gorm.Model + Name string `valid:"required,length(1|32),unix_user"` + Users []*User `gorm:"many2many:user_user_roles"` + } + return tx.AutoMigrate(&UserRole{}).Error + }, + Rollback: func(tx *gorm.DB) error { + return tx.DropTable("user_roles").Error + }, + }, { + ID: "16", + Migrate: func(tx *gorm.DB) error { + type User struct { + gorm.Model + IsAdmin bool + Roles []*UserRole `gorm:"many2many:user_user_roles"` + Email string `valid:"required,email"` + Name string `valid:"required,length(1|32),unix_user"` + Keys []*UserKey `gorm:"ForeignKey:UserID"` + Groups []*UserGroup `gorm:"many2many:user_user_groups;"` + Comment string `valid:"optional"` + InviteToken string `valid:"optional,length(10|60)"` + } + return tx.AutoMigrate(&User{}).Error + }, + Rollback: func(tx *gorm.DB) error { + return fmt.Errorf("not implemented") + }, + }, { + ID: "17", + Migrate: func(tx *gorm.DB) error { + return tx.Create(&UserRole{Name: "admin"}).Error + }, + Rollback: func(tx *gorm.DB) error { + return tx.Where("name = ?", "admin").Delete(&UserRole{}).Error + }, + }, { + ID: "18", + Migrate: func(tx *gorm.DB) error { + var adminRole UserRole + if err := db.Where("name = ?", "admin").First(&adminRole).Error; err != nil { + return err + } + + var users []User + if err := db.Preload("Roles").Where("is_admin = ?", true).Find(&users).Error; err != nil { + return err + } + + for _, user := range users { + user.Roles = append(user.Roles, &adminRole) + if err := tx.Save(&user).Error; err != nil { + return err + } + } + return nil + }, + Rollback: func(tx *gorm.DB) error { + return fmt.Errorf("not implemented") + }, + }, { + ID: "19", + Migrate: func(tx *gorm.DB) error { + type User struct { + gorm.Model + Roles []*UserRole `gorm:"many2many:user_user_roles"` + Email string `valid:"required,email"` + Name string `valid:"required,length(1|32),unix_user"` + Keys []*UserKey `gorm:"ForeignKey:UserID"` + Groups []*UserGroup `gorm:"many2many:user_user_groups;"` + Comment string `valid:"optional"` + InviteToken string `valid:"optional,length(10|60)"` + } + return tx.AutoMigrate(&User{}).Error + }, + Rollback: func(tx *gorm.DB) error { + return fmt.Errorf("not implemented") + }, + }, { + ID: "20", + Migrate: func(tx *gorm.DB) error { + return tx.Create(&UserRole{Name: "listhosts"}).Error + }, + Rollback: func(tx *gorm.DB) error { + return tx.Where("name = ?", "listhosts").Delete(&UserRole{}).Error + }, }, }) if err := m.Migrate(); err != nil { @@ -284,15 +375,21 @@ func dbInit(db *gorm.DB) error { if os.Getenv("SSHPORTAL_DEFAULT_ADMIN_INVITE_TOKEN") != "" { inviteToken = os.Getenv("SSHPORTAL_DEFAULT_ADMIN_INVITE_TOKEN") } + var adminRole UserRole + if err := db.Where("name = ?", "admin").First(&adminRole).Error; err != nil { + return err + } user := User{ Name: "Administrator", Email: "admin@sshportal", Comment: "created by sshportal", - IsAdmin: true, + Roles: []*UserRole{&adminRole}, InviteToken: inviteToken, Groups: []*UserGroup{&defaultUserGroup}, } - db.Create(&user) + if err := db.Create(&user).Error; err != nil { + return err + } log.Printf("Admin user created, use the user 'invite:%s' to associate a public key with this account", user.InviteToken) } diff --git a/main.go b/main.go index 9df20bb..5668f5a 100644 --- a/main.go +++ b/main.go @@ -119,11 +119,7 @@ func server(c *cli.Context) error { } switch username := s.User(); { - case username == c.String("config-user"): - if !currentUser.IsAdmin { - fmt.Fprintf(s, "You are not an administrator, permission denied.\n") - return - } + case username == currentUser.Name || username == currentUser.Email || username == c.String("config-user"): if err := shell(c, s, s.Command(), db); err != nil { fmt.Fprintf(s, "error: %v\n", err) } @@ -181,7 +177,7 @@ func server(c *cli.Context) error { // lookup user by key db.Where("key = ?", key.Marshal()).First(&userKey) if userKey.UserID > 0 { - db.Where("id = ?", userKey.UserID).First(&user) + db.Preload("Roles").Where("id = ?", userKey.UserID).First(&user) if strings.HasPrefix(username, "invite:") { ctx.SetValue(errorContextKey, fmt.Errorf("invites are only supported for ney SSH keys; your ssh key is already associated with the user %q.", user.Email)) } diff --git a/shell.go b/shell.go index 851bbe3..43afdf1 100644 --- a/shell.go +++ b/shell.go @@ -53,6 +53,8 @@ GLOBAL OPTIONS: app := cli.NewApp() app.Writer = s app.HideVersion = true + + myself := s.Context().Value(userContextKey).(User) app.Commands = []cli.Command{ { Name: "acl", @@ -71,6 +73,9 @@ GLOBAL OPTIONS: cli.UintFlag{Name: "weight, w", Usage: "Assigns the ACL weight (priority)"}, }, Action: func(c *cli.Context) error { + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } acl := ACL{ Comment: c.String("comment"), HostPattern: c.String("pattern"), @@ -118,6 +123,9 @@ GLOBAL OPTIONS: if c.NArg() < 1 { return cli.ShowSubcommandHelp(c) } + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } var acls []ACL if err := ACLsPreload(ACLsByIdentifiers(db, c.Args())).Find(&acls).Error; err != nil { @@ -132,6 +140,9 @@ GLOBAL OPTIONS: Name: "ls", Usage: "Lists acls", Action: func(c *cli.Context) error { + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } var acls []ACL if err := db.Preload("UserGroups").Preload("HostGroups").Find(&acls).Error; err != nil { return err @@ -171,6 +182,9 @@ GLOBAL OPTIONS: if c.NArg() < 1 { return cli.ShowSubcommandHelp(c) } + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } return ACLsByIdentifiers(db, c.Args()).Delete(&ACL{}).Error }, @@ -192,6 +206,9 @@ GLOBAL OPTIONS: if c.NArg() < 1 { return cli.ShowSubcommandHelp(c) } + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } var acls []ACL if err := ACLsByIdentifiers(db, c.Args()).Find(&acls).Error; err != nil { @@ -260,6 +277,10 @@ GLOBAL OPTIONS: }, Description: "ssh admin@portal config backup > sshportal.bkp", Action: func(c *cli.Context) error { + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } + config := Config{} if err := db.Find(&config.Hosts).Error; err != nil { return err @@ -303,6 +324,10 @@ GLOBAL OPTIONS: cli.BoolFlag{Name: "confirm", Usage: "yes, I want to replace everything with this backup!"}, }, Action: func(c *cli.Context) error { + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } + config := Config{} dec := json.NewDecoder(s) @@ -413,6 +438,11 @@ GLOBAL OPTIONS: if c.NArg() != 1 { return cli.ShowSubcommandHelp(c) } + + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } + host, err := NewHostFromURL(c.Args().First()) if err != nil { return err @@ -469,8 +499,16 @@ GLOBAL OPTIONS: return cli.ShowSubcommandHelp(c) } + if err := UserCheckRoles(myself, []string{"admin", "listhosts"}); err != nil { + return err + } + var hosts []Host - if err := HostsPreload(HostsByIdentifiers(db, c.Args())).Find(&hosts).Error; err != nil { + db = db.Preload("Groups") + if UserHasRole(myself, "admin") { + db = db.Preload("SSHKey") + } + if err := HostsByIdentifiers(db, c.Args()).Find(&hosts).Error; err != nil { return err } @@ -482,6 +520,10 @@ GLOBAL OPTIONS: Name: "ls", Usage: "Lists hosts", Action: func(c *cli.Context) error { + if err := UserCheckRoles(myself, []string{"admin", "listhosts"}); err != nil { + return err + } + var hosts []*Host if err := db.Preload("Groups").Find(&hosts).Error; err != nil { return err @@ -528,6 +570,10 @@ GLOBAL OPTIONS: return cli.ShowSubcommandHelp(c) } + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } + return HostsByIdentifiers(db, c.Args()).Delete(&Host{}).Error }, }, { @@ -548,6 +594,10 @@ GLOBAL OPTIONS: return cli.ShowSubcommandHelp(c) } + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } + var hosts []Host if err := HostsByIdentifiers(db, c.Args()).Find(&hosts).Error; err != nil { return err @@ -615,6 +665,10 @@ GLOBAL OPTIONS: cli.StringFlag{Name: "comment", Usage: "Adds a comment"}, }, Action: func(c *cli.Context) error { + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } + hostGroup := HostGroup{ Name: c.String("name"), Comment: c.String("comment"), @@ -642,6 +696,10 @@ GLOBAL OPTIONS: return cli.ShowSubcommandHelp(c) } + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } + var hostGroups []HostGroup if err := HostGroupsPreload(HostGroupsByIdentifiers(db, c.Args())).Find(&hostGroups).Error; err != nil { return err @@ -655,6 +713,10 @@ GLOBAL OPTIONS: Name: "ls", Usage: "Lists host groups", Action: func(c *cli.Context) error { + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } + var hostGroups []*HostGroup if err := db.Preload("ACLs").Preload("Hosts").Find(&hostGroups).Error; err != nil { return err @@ -685,6 +747,10 @@ GLOBAL OPTIONS: return cli.ShowSubcommandHelp(c) } + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } + return HostGroupsByIdentifiers(db, c.Args()).Delete(&HostGroup{}).Error }, }, @@ -693,6 +759,10 @@ GLOBAL OPTIONS: Name: "info", Usage: "Shows system-wide information", Action: func(c *cli.Context) error { + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } + fmt.Fprintf(s, "Debug mode (server): %v\n", globalContext.Bool("debug")) hostname, _ := os.Hostname() fmt.Fprintf(s, "Hostname: %s\n", hostname) @@ -708,7 +778,6 @@ GLOBAL OPTIONS: fmt.Fprintf(s, "Go version (build): %v\n", runtime.Version()) fmt.Fprintf(s, "Uptime: %v\n", time.Since(startTime)) - myself := s.Context().Value(userContextKey).(User) fmt.Fprintf(s, "User email: %v\n", myself.ID) fmt.Fprintf(s, "User email: %s\n", myself.Email) fmt.Fprintf(s, "Version: %s\n", VERSION) @@ -737,6 +806,10 @@ GLOBAL OPTIONS: cli.StringFlag{Name: "comment", Usage: "Adds a comment"}, }, Action: func(c *cli.Context) error { + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } + name := namesgenerator.GetRandomName(0) if c.String("name") != "" { name = c.String("name") @@ -770,6 +843,10 @@ GLOBAL OPTIONS: return cli.ShowSubcommandHelp(c) } + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } + var keys []SSHKey if err := SSHKeysByIdentifiers(db, c.Args()).Find(&keys).Error; err != nil { return err @@ -783,6 +860,10 @@ GLOBAL OPTIONS: Name: "ls", Usage: "Lists keys", Action: func(c *cli.Context) error { + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } + var keys []SSHKey if err := db.Preload("Hosts").Find(&keys).Error; err != nil { return err @@ -816,6 +897,10 @@ GLOBAL OPTIONS: return cli.ShowSubcommandHelp(c) } + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } + return SSHKeysByIdentifiers(db, c.Args()).Delete(&SSHKey{}).Error }, }, @@ -833,6 +918,10 @@ GLOBAL OPTIONS: return cli.ShowSubcommandHelp(c) } + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } + var users []User if err := UsersPreload(UsersByIdentifiers(db, c.Args())).Find(&users).Error; err != nil { return err @@ -857,6 +946,10 @@ GLOBAL OPTIONS: return cli.ShowSubcommandHelp(c) } + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } + // FIXME: validate email email := c.Args().First() @@ -896,12 +989,16 @@ GLOBAL OPTIONS: Name: "ls", Usage: "Lists users", Action: func(c *cli.Context) error { + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } + var users []User - if err := db.Preload("Groups").Preload("Keys").Find(&users).Error; err != nil { + if err := db.Preload("Groups").Preload("Roles").Preload("Keys").Find(&users).Error; err != nil { return err } table := tablewriter.NewWriter(s) - table.SetHeader([]string{"ID", "Name", "Email", "Admin", "Keys", "Groups", "Comment"}) + table.SetHeader([]string{"ID", "Name", "Email", "Roles", "Keys", "Groups", "Comment"}) table.SetBorder(false) table.SetCaption(true, fmt.Sprintf("Total: %d users.", len(users))) for _, user := range users { @@ -909,15 +1006,15 @@ GLOBAL OPTIONS: for _, userGroup := range user.Groups { groupNames = append(groupNames, userGroup.Name) } - isAdmin := "" - if user.IsAdmin { - isAdmin = "yes" + roleNames := []string{} + for _, role := range user.Roles { + roleNames = append(roleNames, role.Name) } table.Append([]string{ fmt.Sprintf("%d", user.ID), user.Name, user.Email, - isAdmin, + strings.Join(roleNames, ", "), fmt.Sprintf("%d", len(user.Keys)), strings.Join(groupNames, ", "), user.Comment, @@ -937,6 +1034,10 @@ GLOBAL OPTIONS: return cli.ShowSubcommandHelp(c) } + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } + return UsersByIdentifiers(db, c.Args()).Delete(&User{}).Error }, }, { @@ -946,16 +1047,20 @@ GLOBAL OPTIONS: Flags: []cli.Flag{ cli.StringFlag{Name: "name, n", Usage: "Renames the user"}, cli.StringFlag{Name: "email, e", Usage: "Updates the email"}, - cli.BoolFlag{Name: "set-admin", Usage: "Sets admin flag"}, - cli.BoolFlag{Name: "unset-admin", Usage: "Unsets admin flag"}, - cli.StringSliceFlag{Name: "assign-group, g", Usage: "Assign the user to a new `USERGROUPS`"}, - cli.StringSliceFlag{Name: "unassign-group", Usage: "Unassign the user from a `USERGROUPS`"}, + cli.StringSliceFlag{Name: "assign-role, r", Usage: "Assign the user to new `USERROLES`"}, + cli.StringSliceFlag{Name: "unassign-role", Usage: "Unassign the user from `USERROLES`"}, + cli.StringSliceFlag{Name: "assign-group, g", Usage: "Assign the user to new `USERGROUPS`"}, + cli.StringSliceFlag{Name: "unassign-group", Usage: "Unassign the user from `USERGROUPS`"}, }, Action: func(c *cli.Context) error { if c.NArg() < 1 { return cli.ShowSubcommandHelp(c) } + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } + // FIXME: check if unset-admin + user == myself var users []User if err := UsersByIdentifiers(db, c.Args()).Find(&users).Error; err != nil { @@ -983,27 +1088,13 @@ GLOBAL OPTIONS: } } - // special fields - if c.Bool("set-admin") { - if err := model.Updates(User{IsAdmin: true}).Error; err != nil { - tx.Rollback() - return err - } - } - if c.Bool("unset-admin") { - if err := model.Updates(map[string]interface{}{"is_admin": false}).Error; err != nil { - tx.Rollback() - return err - } - } - // associations var appendGroups []UserGroup - var deleteGroups []UserGroup if err := UserGroupsByIdentifiers(db, c.StringSlice("assign-group")).Find(&appendGroups).Error; err != nil { tx.Rollback() return err } + var deleteGroups []UserGroup if err := UserGroupsByIdentifiers(db, c.StringSlice("unassign-group")).Find(&deleteGroups).Error; err != nil { tx.Rollback() return err @@ -1012,6 +1103,21 @@ GLOBAL OPTIONS: tx.Rollback() return err } + + var appendRoles []UserRole + if err := UserRolesByIdentifiers(db, c.StringSlice("assign-role")).Find(&appendRoles).Error; err != nil { + tx.Rollback() + return err + } + var deleteRoles []UserRole + if err := UserRolesByIdentifiers(db, c.StringSlice("unassign-role")).Find(&deleteRoles).Error; err != nil { + tx.Rollback() + return err + } + if err := model.Association("Roles").Append(&appendRoles).Delete(deleteRoles).Error; err != nil { + tx.Rollback() + return err + } } return tx.Commit().Error @@ -1031,6 +1137,10 @@ GLOBAL OPTIONS: cli.StringFlag{Name: "comment", Usage: "Adds a comment"}, }, Action: func(c *cli.Context) error { + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } + userGroup := UserGroup{ Name: c.String("name"), Comment: c.String("comment"), @@ -1043,10 +1153,8 @@ GLOBAL OPTIONS: return err } // FIXME: check if name already exists + // FIXME: add myself to the new group - // add myself to the new group - myself := s.Context().Value(userContextKey).(User) - // FIXME: use foreign key with ID to avoid updating the user with the context userGroup.Users = []*User{&myself} if err := db.Create(&userGroup).Error; err != nil { @@ -1064,6 +1172,10 @@ GLOBAL OPTIONS: return cli.ShowSubcommandHelp(c) } + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } + var userGroups []UserGroup if err := UserGroupsPreload(UserGroupsByIdentifiers(db, c.Args())).Find(&userGroups).Error; err != nil { return err @@ -1077,6 +1189,10 @@ GLOBAL OPTIONS: Name: "ls", Usage: "Lists user groups", Action: func(c *cli.Context) error { + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } + var userGroups []*UserGroup if err := db.Preload("ACLs").Preload("Users").Find(&userGroups).Error; err != nil { return err @@ -1107,6 +1223,10 @@ GLOBAL OPTIONS: return cli.ShowSubcommandHelp(c) } + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } + return UserGroupsByIdentifiers(db, c.Args()).Delete(&UserGroup{}).Error }, }, @@ -1128,6 +1248,10 @@ GLOBAL OPTIONS: return cli.ShowSubcommandHelp(c) } + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } + var user User if err := UsersByIdentifiers(db, c.Args()).First(&user).Error; err != nil { return err @@ -1171,6 +1295,10 @@ GLOBAL OPTIONS: return cli.ShowSubcommandHelp(c) } + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } + var userKeys []UserKey if err := UserKeysPreload(UserKeysByIdentifiers(db, c.Args())).Find(&userKeys).Error; err != nil { return err @@ -1184,6 +1312,10 @@ GLOBAL OPTIONS: Name: "ls", Usage: "Lists userkeys", Action: func(c *cli.Context) error { + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } + var userkeys []UserKey if err := db.Preload("User").Find(&userkeys).Error; err != nil { return err @@ -1212,6 +1344,10 @@ GLOBAL OPTIONS: return cli.ShowSubcommandHelp(c) } + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } + return UserKeysByIdentifiers(db, c.Args()).Delete(&UserKey{}).Error }, },