Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f6ba06298d | ||
|
|
31a8cef59f | ||
|
|
beeba0551b | ||
|
|
a36bb68957 | ||
|
|
9cd9152a91 | ||
|
|
09c1e0504e | ||
|
|
37d7c839dd | ||
|
|
8ba418308e | ||
|
|
cfcf124d83 | ||
|
|
ccb0071d12 | ||
|
|
681f59c1e6 | ||
|
|
1bdee1a107 | ||
|
|
a2f3a51fe5 |
@@ -1,5 +1,14 @@
|
||||
# Changelog
|
||||
|
||||
## v1.3.0 (2017-11-23)
|
||||
|
||||
* More details in 'ls' commands
|
||||
* 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)
|
||||
|
||||
* Support adding multiple `--group` links on `host create` and `user create`
|
||||
|
||||
5
Makefile
5
Makefile
@@ -3,6 +3,7 @@ GIT_TAG ?= $(shell git describe --tags --always)
|
||||
GIT_BRANCH ?= $(shell git rev-parse --abbrev-ref HEAD)
|
||||
LDFLAGS ?= -X main.GIT_SHA=$(GIT_SHA) -X main.GIT_TAG=$(GIT_TAG) -X main.GIT_BRANCH=$(GIT_BRANCH)
|
||||
VERSION ?= $(shell grep 'VERSION =' main.go | cut -d'"' -f2)
|
||||
PORT ?= 2222
|
||||
|
||||
.PHONY: install
|
||||
install:
|
||||
@@ -14,7 +15,7 @@ docker.build:
|
||||
|
||||
.PHONY: integration
|
||||
integration:
|
||||
bash ./examples/integration/test.sh
|
||||
PORT="$(PORT)" bash ./examples/integration/test.sh
|
||||
|
||||
.PHONY: _docker_install
|
||||
_docker_install:
|
||||
@@ -23,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" .
|
||||
CompileDaemon -exclude-dir=.git -exclude=".#*" -color=true -command="./sshportal --demo --debug --bind-address=:$(PORT)" .
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
|
||||
39
README.md
39
README.md
@@ -139,10 +139,11 @@ You can enter in interactive mode using this syntax: `ssh admin@portal.example.o
|
||||
```sh
|
||||
# acl management
|
||||
acl help
|
||||
acl create [-h] [--hostgroup=<value>...] [--usergroup=<value>...] [--pattern=<value>] [--comment=<value>] [--action=<value>] [--weight=value]
|
||||
acl inspect [-h] <id>...
|
||||
acl create [-h] [--hostgroup=HOSTGROUP...] [--usergroup=USERGROUP...] [--pattern=<value>] [--comment=<value>] [--action=<value>] [--weight=value]
|
||||
acl inspect [-h] ACL...
|
||||
acl ls [-h]
|
||||
acl rm [-h] <id>...
|
||||
acl rm [-h] ACL...
|
||||
acl update [-h] [--comment=<value>] [--action=<value>] [--weight=<value>] [--assign-hostgroup=HOSTGROUP...] [--unassign-hostgroup=HOSTGROUP...] [--assign-usergroup=USERGROUP...] [--unassign-usergroup=USERGROUP...] ACL...
|
||||
|
||||
# config management
|
||||
config help
|
||||
@@ -151,38 +152,40 @@ config restore [-h] [--confirm]
|
||||
|
||||
# host management
|
||||
host help
|
||||
host create [-h] [--name=<value>] [--password=<value>] [--fingerprint=<value>] [--comment=<value>] [--key=<value>] [--group=<value>...] <user>[:<password>]@<host>[:<port>]
|
||||
host inspect [-h] <id or name>...
|
||||
host create [-h] [--name=<value>] [--password=<value>] [--fingerprint=<value>] [--comment=<value>] [--key=KEY] [--group=HOSTGROUP...] <username>[:<password>]@<host>[:<port>]
|
||||
host inspect [-h] HOST...
|
||||
host ls [-h]
|
||||
host rm [-h] <id or name>...
|
||||
host rm [-h] HOST...
|
||||
host update [-h] [--name=<value>] [--comment=<value>] [--fingerprint=<value>] [--key=KEY] [--assign-group=HOSTGROUP...] [--unassign-group=HOSTGROUP...] HOST...
|
||||
|
||||
# hostgroup management
|
||||
hostgroup help
|
||||
hostgroup create [-h] [--name=<value>] [--comment=<value>]
|
||||
hostgroup inspect [-h] <id or name>...
|
||||
hostgroup inspect [-h] HOSTGROUP...
|
||||
hostgroup ls [-h]
|
||||
hostgroup rm [-h] <id or name>...
|
||||
hostgroup rm [-h] HOSTGROUP...
|
||||
|
||||
# key management
|
||||
key help
|
||||
key create [-h] [--name=<value>] [--type=<value>] [--length=<value>] [--comment=<value>]
|
||||
key inspect [-h] <id or name>...
|
||||
key inspect [-h] KEY...
|
||||
key ls [-h]
|
||||
key rm [-h] <id or name>...
|
||||
key rm [-h] KEY...
|
||||
|
||||
# user management
|
||||
user help
|
||||
user invite [-h] [--name=<value>] [--comment=<value>] [--group=<value>...] <email>
|
||||
user inspect [-h] <id or email>...
|
||||
user invite [-h] [--name=<value>] [--comment=<value>] [--group=USERGROUP...] <email>
|
||||
user inspect [-h] USER...
|
||||
user ls [-h]
|
||||
user rm [-h] <id or email>...
|
||||
user rm [-h] USER...
|
||||
user update [-h] [--name=<value>] [--email=<value>] [--set-admin] [--unset-admin] [--assign-group=USERGROUP...] [--unassign-group=USERGROUP...] USER...
|
||||
|
||||
# usergroup management
|
||||
usergroup help
|
||||
hostgroup create [-h] [--name=<value>] [--comment=<value>]
|
||||
usergroup inspect [-h] <id or name>...
|
||||
usergroup inspect [-h] USERGROUP...
|
||||
usergroup ls [-h]
|
||||
usergroup rm [-h] <id or name>...
|
||||
usergroup rm [-h] USERGROUP...
|
||||
|
||||
# other
|
||||
exit [-h]
|
||||
@@ -200,7 +203,7 @@ An [automated build is setup on the Docker Hub](https://hub.docker.com/r/moul/ss
|
||||
```console
|
||||
# Start a server in background
|
||||
# mount `pwd` to persist the sqlite database file
|
||||
docker run -p 2222:2222 -d --name=sshportal -v "$(pwd):$(pwd)" -w "$(pwd)" moul/sshportal:v1.2.0
|
||||
docker run -p 2222:2222 -d --name=sshportal -v "$(pwd):$(pwd)" -w "$(pwd)" moul/sshportal:v1.3.0
|
||||
|
||||
# check logs (mandatory on first run to get the administrator invite token)
|
||||
docker logs -f sshportal
|
||||
@@ -209,7 +212,7 @@ docker logs -f sshportal
|
||||
The easier way to upgrade sshportal is to do the following:
|
||||
|
||||
```sh
|
||||
# we consider you were using the version v1.1.0 and you want to use the new version v1.2.0
|
||||
# we consider you were using the version v1.2.0 and you want to use the new version v1.3.0
|
||||
|
||||
# stop and rename the last working container + backup the database
|
||||
docker stop sshportal
|
||||
@@ -217,7 +220,7 @@ docker rename sshportal sshportal_old
|
||||
cp sshportal.db sshportal.db.bkp
|
||||
|
||||
# run the new version
|
||||
docker run -p 2222:2222 -d --name=sshportal -v "$(pwd):$(pwd)" -w "$(pwd)" moul/sshportal:v1.2.0
|
||||
docker run -p 2222:2222 -d --name=sshportal -v "$(pwd):$(pwd)" -w "$(pwd)" moul/sshportal:v1.3.0
|
||||
# check the logs for migration or cross-version incompabitility errors
|
||||
docker logs -f sshportal
|
||||
```
|
||||
|
||||
178
db.go
178
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"`
|
||||
@@ -155,148 +161,86 @@ func (host *Host) Hostname() string {
|
||||
}
|
||||
|
||||
// Host helpers
|
||||
|
||||
func FindHostByIdOrName(db *gorm.DB, query string) (*Host, error) {
|
||||
var host Host
|
||||
if err := db.Preload("Groups").Preload("SSHKey").Where("id = ?", query).Or("name = ?", query).First(&host).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &host, nil
|
||||
func HostsPreload(db *gorm.DB) *gorm.DB {
|
||||
return db.Preload("Groups").Preload("SSHKey")
|
||||
}
|
||||
func FindHostsByIdOrName(db *gorm.DB, queries []string) ([]*Host, error) {
|
||||
var hosts []*Host
|
||||
for _, query := range queries {
|
||||
host, err := FindHostByIdOrName(db, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hosts = append(hosts, host)
|
||||
}
|
||||
return hosts, nil
|
||||
func HostsByIdentifiers(db *gorm.DB, identifiers []string) *gorm.DB {
|
||||
return db.Where("id IN (?)", identifiers).Or("name IN (?)", identifiers)
|
||||
}
|
||||
|
||||
// SSHKey helpers
|
||||
|
||||
func FindKeyByIdOrName(db *gorm.DB, query string) (*SSHKey, error) {
|
||||
var key SSHKey
|
||||
if err := db.Preload("Hosts").Where("id = ?", query).Or("name = ?", query).First(&key).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &key, nil
|
||||
func SSHKeysPreload(db *gorm.DB) *gorm.DB {
|
||||
return db.Preload("Hosts")
|
||||
}
|
||||
func FindKeysByIdOrName(db *gorm.DB, queries []string) ([]*SSHKey, error) {
|
||||
var keys []*SSHKey
|
||||
for _, query := range queries {
|
||||
key, err := FindKeyByIdOrName(db, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keys = append(keys, key)
|
||||
}
|
||||
return keys, nil
|
||||
func SSHKeysByIdentifiers(db *gorm.DB, identifiers []string) *gorm.DB {
|
||||
return db.Where("id IN (?)", identifiers).Or("name IN (?)", identifiers)
|
||||
}
|
||||
|
||||
// HostGroup helpers
|
||||
|
||||
func FindHostGroupByIdOrName(db *gorm.DB, query string) (*HostGroup, error) {
|
||||
var hostGroup HostGroup
|
||||
if err := db.Preload("ACLs").Preload("Hosts").Where("id = ?", query).Or("name = ?", query).First(&hostGroup).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &hostGroup, nil
|
||||
func HostGroupsPreload(db *gorm.DB) *gorm.DB {
|
||||
return db.Preload("ACLs").Preload("Hosts")
|
||||
}
|
||||
func FindHostGroupsByIdOrName(db *gorm.DB, queries []string) ([]*HostGroup, error) {
|
||||
var hostGroups []*HostGroup
|
||||
for _, query := range queries {
|
||||
hostGroup, err := FindHostGroupByIdOrName(db, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hostGroups = append(hostGroups, hostGroup)
|
||||
}
|
||||
return hostGroups, nil
|
||||
func HostGroupsByIdentifiers(db *gorm.DB, identifiers []string) *gorm.DB {
|
||||
return db.Where("id IN (?)", identifiers).Or("name IN (?)", identifiers)
|
||||
}
|
||||
|
||||
// UserGroup heleprs
|
||||
|
||||
func FindUserGroupByIdOrName(db *gorm.DB, query string) (*UserGroup, error) {
|
||||
var userGroup UserGroup
|
||||
if err := db.Preload("ACLs").Preload("Users").Where("id = ?", query).Or("name = ?", query).First(&userGroup).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &userGroup, nil
|
||||
func UserGroupsPreload(db *gorm.DB) *gorm.DB {
|
||||
return db.Preload("ACLs").Preload("Users")
|
||||
}
|
||||
func FindUserGroupsByIdOrName(db *gorm.DB, queries []string) ([]*UserGroup, error) {
|
||||
var userGroups []*UserGroup
|
||||
for _, query := range queries {
|
||||
userGroup, err := FindUserGroupByIdOrName(db, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userGroups = append(userGroups, userGroup)
|
||||
}
|
||||
return userGroups, nil
|
||||
func UserGroupsByIdentifiers(db *gorm.DB, identifiers []string) *gorm.DB {
|
||||
return db.Where("id IN (?)", identifiers).Or("name IN (?)", identifiers)
|
||||
}
|
||||
|
||||
// User helpers
|
||||
|
||||
func FindUserByIdOrEmail(db *gorm.DB, query string) (*User, error) {
|
||||
var user User
|
||||
if err := db.Preload("Groups").Preload("Keys").Where("id = ?", query).Or("email = ?", query).First(&user).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user, nil
|
||||
func UsersPreload(db *gorm.DB) *gorm.DB {
|
||||
return db.Preload("Groups").Preload("Keys").Preload("Roles")
|
||||
}
|
||||
func FindUsersByIdOrEmail(db *gorm.DB, queries []string) ([]*User, error) {
|
||||
var users []*User
|
||||
for _, query := range queries {
|
||||
user, err := FindUserByIdOrEmail(db, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func UsersByIdentifiers(db *gorm.DB, identifiers []string) *gorm.DB {
|
||||
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
|
||||
}
|
||||
users = append(users, user)
|
||||
}
|
||||
return users, nil
|
||||
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
|
||||
|
||||
func FindACLById(db *gorm.DB, query string) (*ACL, error) {
|
||||
var acl ACL
|
||||
if err := db.Preload("UserGroups").Preload("HostGroups").Where("id = ?", query).First(&acl).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &acl, nil
|
||||
func ACLsPreload(db *gorm.DB) *gorm.DB {
|
||||
return db.Preload("UserGroups").Preload("HostGroups")
|
||||
}
|
||||
func FindACLsById(db *gorm.DB, queries []string) ([]*ACL, error) {
|
||||
var acls []*ACL
|
||||
for _, query := range queries {
|
||||
acl, err := FindACLById(db, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
acls = append(acls, acl)
|
||||
}
|
||||
return acls, nil
|
||||
func ACLsByIdentifiers(db *gorm.DB, identifiers []string) *gorm.DB {
|
||||
return db.Where("id IN (?)", identifiers)
|
||||
}
|
||||
|
||||
// UserKey helpers
|
||||
func UserKeysPreload(db *gorm.DB) *gorm.DB {
|
||||
return db.Preload("User")
|
||||
}
|
||||
func UserKeysByIdentifiers(db *gorm.DB, identifiers []string) *gorm.DB {
|
||||
return db.Where("id IN (?)", identifiers)
|
||||
}
|
||||
|
||||
func FindUserkeyById(db *gorm.DB, query string) (*UserKey, error) {
|
||||
var userkey UserKey
|
||||
if err := db.Preload("User").Where("id = ?", query).First(&userkey).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &userkey, nil
|
||||
// UserRole helpers
|
||||
func UserRolesPreload(db *gorm.DB) *gorm.DB {
|
||||
return db.Preload("Users")
|
||||
}
|
||||
func FindUserkeysById(db *gorm.DB, queries []string) ([]*UserKey, error) {
|
||||
var userkeys []*UserKey
|
||||
for _, query := range queries {
|
||||
userkey, err := FindUserkeyById(db, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userkeys = append(userkeys, userkey)
|
||||
}
|
||||
return userkeys, nil
|
||||
func UserRolesByIdentifiers(db *gorm.DB, identifiers []string) *gorm.DB {
|
||||
return db.Where("id IN (?)", identifiers).Or("name IN (?)", identifiers)
|
||||
}
|
||||
|
||||
115
dbinit.go
115
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)
|
||||
}
|
||||
|
||||
@@ -315,20 +412,20 @@ func dbInit(db *gorm.DB) error {
|
||||
}
|
||||
|
||||
func dbDemo(db *gorm.DB) error {
|
||||
hostGroup, err := FindHostGroupByIdOrName(db, "default")
|
||||
if err != nil {
|
||||
var hostGroup HostGroup
|
||||
if err := HostGroupsByIdentifiers(db, []string{"default"}).First(&hostGroup).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
key, err := FindKeyByIdOrName(db, "default")
|
||||
if err != nil {
|
||||
var key SSHKey
|
||||
if err := SSHKeysByIdentifiers(db, []string{"default"}).First(&key).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var (
|
||||
host1 = Host{Name: "sdf", Addr: "sdf.org:22", User: "new", SSHKeyID: key.ID, Groups: []*HostGroup{hostGroup}}
|
||||
host2 = Host{Name: "whoami", Addr: "whoami.filippo.io:22", User: "test", SSHKeyID: key.ID, Groups: []*HostGroup{hostGroup}}
|
||||
host3 = Host{Name: "ssh-chat", Addr: "chat.shazow.net:22", User: "test", SSHKeyID: key.ID, Fingerprint: "MD5:e5:d5:d1:75:90:38:42:f6:c7:03:d7:d0:56:7d:6a:db", Groups: []*HostGroup{hostGroup}}
|
||||
host1 = Host{Name: "sdf", Addr: "sdf.org:22", User: "new", SSHKeyID: key.ID, Groups: []*HostGroup{&hostGroup}}
|
||||
host2 = Host{Name: "whoami", Addr: "whoami.filippo.io:22", User: "test", SSHKeyID: key.ID, Groups: []*HostGroup{&hostGroup}}
|
||||
host3 = Host{Name: "ssh-chat", Addr: "chat.shazow.net:22", User: "test", SSHKeyID: key.ID, Fingerprint: "MD5:e5:d5:d1:75:90:38:42:f6:c7:03:d7:d0:56:7d:6a:db", Groups: []*HostGroup{&hostGroup}}
|
||||
)
|
||||
|
||||
// FIXME: check if hosts exist to avoid `UNIQUE constraint` error
|
||||
|
||||
14
main.go
14
main.go
@@ -20,7 +20,7 @@ import (
|
||||
|
||||
var (
|
||||
// VERSION should be updated by hand at each release
|
||||
VERSION = "1.2.0"
|
||||
VERSION = "1.3.0"
|
||||
// GIT_TAG will be overwritten automatically by the build system
|
||||
GIT_TAG string
|
||||
// GIT_SHA will be overwritten automatically by the build system
|
||||
@@ -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))
|
||||
}
|
||||
@@ -223,8 +219,8 @@ func server(c *cli.Context) error {
|
||||
}))
|
||||
|
||||
opts = append(opts, func(srv *ssh.Server) error {
|
||||
key, err := FindKeyByIdOrName(db, "host")
|
||||
if err != nil {
|
||||
var key SSHKey
|
||||
if err := SSHKeysByIdentifiers(db, []string{"host"}).First(&key).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
signer, err := gossh.ParsePrivateKey([]byte(key.PrivKey))
|
||||
|
||||
566
shell.go
566
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",
|
||||
@@ -63,14 +65,17 @@ GLOBAL OPTIONS:
|
||||
Usage: "Creates a new ACL",
|
||||
Description: "$> acl create -",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringSliceFlag{Name: "hostgroup, hg", Usage: "Assigns host groups to the acl"},
|
||||
cli.StringSliceFlag{Name: "usergroup, ug", Usage: "Assigns host groups to the acl"},
|
||||
cli.StringSliceFlag{Name: "hostgroup, hg", Usage: "Assigns `HOSTGROUPS` to the acl"},
|
||||
cli.StringSliceFlag{Name: "usergroup, ug", Usage: "Assigns `HOSTGROUPS` to the acl"},
|
||||
cli.StringFlag{Name: "pattern", Usage: "Assigns a host pattern to the acl"},
|
||||
cli.StringFlag{Name: "comment"},
|
||||
cli.StringFlag{Name: "comment", Usage: "Adds a comment"},
|
||||
cli.StringFlag{Name: "action", Usage: "Assigns the ACL action (allow,deny)", Value: "allow"},
|
||||
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"),
|
||||
@@ -86,20 +91,16 @@ GLOBAL OPTIONS:
|
||||
return err
|
||||
}
|
||||
|
||||
for _, name := range c.StringSlice("usergroup") {
|
||||
userGroup, err := FindUserGroupByIdOrName(db, name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unknown user group %q: %v", name, err)
|
||||
}
|
||||
acl.UserGroups = append(acl.UserGroups, userGroup)
|
||||
var userGroups []*UserGroup
|
||||
if err := UserGroupsPreload(UserGroupsByIdentifiers(db, c.StringSlice("usergroup"))).Find(&userGroups).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
for _, name := range c.StringSlice("hostgroup") {
|
||||
hostGroup, err := FindHostGroupByIdOrName(db, name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unknown host group %q: %v", name, err)
|
||||
}
|
||||
acl.HostGroups = append(acl.HostGroups, hostGroup)
|
||||
acl.UserGroups = append(acl.UserGroups, userGroups...)
|
||||
var hostGroups []*HostGroup
|
||||
if err := HostGroupsPreload(HostGroupsByIdentifiers(db, c.StringSlice("hostgroup"))).Find(&hostGroups).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
acl.HostGroups = append(acl.HostGroups, hostGroups...)
|
||||
|
||||
if len(acl.UserGroups) == 0 {
|
||||
return fmt.Errorf("an ACL must have at least one user group")
|
||||
@@ -117,15 +118,18 @@ GLOBAL OPTIONS:
|
||||
}, {
|
||||
Name: "inspect",
|
||||
Usage: "Shows detailed information on one or more acls",
|
||||
ArgsUsage: "<id> [<id> [<id>...]]",
|
||||
ArgsUsage: "ACL...",
|
||||
Action: func(c *cli.Context) error {
|
||||
if c.NArg() < 1 {
|
||||
return cli.ShowSubcommandHelp(c)
|
||||
}
|
||||
if err := UserCheckRoles(myself, []string{"admin"}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
acls, err := FindACLsById(db, c.Args())
|
||||
if err != nil {
|
||||
return nil
|
||||
var acls []ACL
|
||||
if err := ACLsPreload(ACLsByIdentifiers(db, c.Args())).Find(&acls).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
enc := json.NewEncoder(s)
|
||||
@@ -136,12 +140,15 @@ 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
|
||||
}
|
||||
table := tablewriter.NewWriter(s)
|
||||
table.SetHeader([]string{"ID", "User groups", "Host groups", "Host pattern", "Action", "Comment"})
|
||||
table.SetHeader([]string{"ID", "Weight", "User groups", "Host groups", "Host pattern", "Action", "Comment"})
|
||||
table.SetBorder(false)
|
||||
table.SetCaption(true, fmt.Sprintf("Total: %d acls.", len(acls)))
|
||||
for _, acl := range acls {
|
||||
@@ -156,6 +163,7 @@ GLOBAL OPTIONS:
|
||||
|
||||
table.Append([]string{
|
||||
fmt.Sprintf("%d", acl.ID),
|
||||
fmt.Sprintf("%d", acl.Weight),
|
||||
strings.Join(userGroups, ", "),
|
||||
strings.Join(hostGroups, ", "),
|
||||
acl.HostPattern,
|
||||
@@ -169,22 +177,91 @@ GLOBAL OPTIONS:
|
||||
}, {
|
||||
Name: "rm",
|
||||
Usage: "Removes one or more acls",
|
||||
ArgsUsage: "<id or name> [<id or name> [<id or name>...]]",
|
||||
ArgsUsage: "ACL...",
|
||||
Action: func(c *cli.Context) error {
|
||||
if c.NArg() < 1 {
|
||||
return cli.ShowSubcommandHelp(c)
|
||||
}
|
||||
|
||||
acls, err := FindACLsById(db, c.Args())
|
||||
if err != nil {
|
||||
return nil
|
||||
if err := UserCheckRoles(myself, []string{"admin"}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ACLsByIdentifiers(db, c.Args()).Delete(&ACL{}).Error
|
||||
},
|
||||
}, {
|
||||
Name: "update",
|
||||
Usage: "Updates an existing acl",
|
||||
ArgsUsage: "ACL...",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{Name: "action, a", Usage: "Update action"},
|
||||
cli.StringFlag{Name: "pattern, p", Usage: "Update host-pattern"},
|
||||
cli.UintFlag{Name: "weight, w", Usage: "Update weight"},
|
||||
cli.StringFlag{Name: "comment, c", Usage: "Update comment"},
|
||||
cli.StringSliceFlag{Name: "assign-usergroup, ug", Usage: "Assign the ACL to new `USERGROUPS`"},
|
||||
cli.StringSliceFlag{Name: "unassign-usergroup", Usage: "Unassign the ACL from `USERGROUPS`"},
|
||||
cli.StringSliceFlag{Name: "assign-hostgroup, hg", Usage: "Assign the ACL to new `HOSTGROUPS`"},
|
||||
cli.StringSliceFlag{Name: "unassign-hostgroup", Usage: "Unassign the ACL from `HOSTGROUPS`"},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
||||
tx := db.Begin()
|
||||
for _, acl := range acls {
|
||||
db.Where("id = ?", acl.ID).Delete(&ACL{})
|
||||
fmt.Fprintf(s, "%d\n", acl.ID)
|
||||
model := tx.Model(&acl)
|
||||
update := ACL{
|
||||
Action: c.String("action"),
|
||||
HostPattern: c.String("pattern"),
|
||||
Weight: c.Uint("weight"),
|
||||
Comment: c.String("comment"),
|
||||
}
|
||||
if err := model.Updates(update).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
// associations
|
||||
var appendUserGroups []UserGroup
|
||||
var deleteUserGroups []UserGroup
|
||||
if err := UserGroupsByIdentifiers(db, c.StringSlice("assign-usergroup")).Find(&appendUserGroups).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
if err := UserGroupsByIdentifiers(db, c.StringSlice("unassign-usergroup")).Find(&deleteUserGroups).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
if err := model.Association("UserGroups").Append(&appendUserGroups).Delete(deleteUserGroups).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
var appendHostGroups []HostGroup
|
||||
var deleteHostGroups []HostGroup
|
||||
if err := HostGroupsByIdentifiers(db, c.StringSlice("assign-hostgroup")).Find(&appendHostGroups).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
if err := HostGroupsByIdentifiers(db, c.StringSlice("unassign-hostgroup")).Find(&deleteHostGroups).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
if err := model.Association("HostGroups").Append(&appendHostGroups).Delete(deleteHostGroups).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
return tx.Commit().Error
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -200,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
|
||||
@@ -240,9 +321,13 @@ GLOBAL OPTIONS:
|
||||
Usage: "Restores a backup",
|
||||
Description: "ssh admin@portal config restore < sshportal.bkp",
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{Name: "confirm", Usage: "automatically confirms"},
|
||||
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)
|
||||
@@ -346,13 +431,18 @@ GLOBAL OPTIONS:
|
||||
cli.StringFlag{Name: "password, p", Usage: "If present, sshportal will use password-based authentication"},
|
||||
cli.StringFlag{Name: "fingerprint, f", Usage: "SSH host key fingerprint"},
|
||||
cli.StringFlag{Name: "comment, c"},
|
||||
cli.StringFlag{Name: "key, k", Usage: "ID or name of the key to use for authentication"},
|
||||
cli.StringSliceFlag{Name: "group, g", Usage: "Names or IDs of host groups (default: \"default\")"},
|
||||
cli.StringFlag{Name: "key, k", Usage: "`KEY` to use for authentication"},
|
||||
cli.StringSliceFlag{Name: "group, g", Usage: "Assigns the host to `HOSTGROUPS` (default: \"default\")"},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
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
|
||||
@@ -378,8 +468,8 @@ GLOBAL OPTIONS:
|
||||
inputKey = "default"
|
||||
}
|
||||
if inputKey != "" {
|
||||
key, err := FindKeyByIdOrName(db, inputKey)
|
||||
if err != nil {
|
||||
var key SSHKey
|
||||
if err := SSHKeysByIdentifiers(db, []string{inputKey}).First(&key).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
host.SSHKeyID = key.ID
|
||||
@@ -390,11 +480,9 @@ GLOBAL OPTIONS:
|
||||
if len(inputGroups) == 0 {
|
||||
inputGroups = []string{"default"}
|
||||
}
|
||||
hostGroups, err := FindHostGroupsByIdOrName(db, inputGroups)
|
||||
if err != nil {
|
||||
if err := HostGroupsByIdentifiers(db, inputGroups).Find(&host.Groups).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
host.Groups = hostGroups
|
||||
|
||||
if err := db.Create(&host).Error; err != nil {
|
||||
return err
|
||||
@@ -405,15 +493,23 @@ GLOBAL OPTIONS:
|
||||
}, {
|
||||
Name: "inspect",
|
||||
Usage: "Shows detailed information on one or more hosts",
|
||||
ArgsUsage: "<id or name> [<id or name> [<id or name>...]]",
|
||||
ArgsUsage: "HOST...",
|
||||
Action: func(c *cli.Context) error {
|
||||
if c.NArg() < 1 {
|
||||
return cli.ShowSubcommandHelp(c)
|
||||
}
|
||||
|
||||
hosts, err := FindHostsByIdOrName(db, c.Args())
|
||||
if err != nil {
|
||||
return nil
|
||||
if err := UserCheckRoles(myself, []string{"admin", "listhosts"}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var hosts []Host
|
||||
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
|
||||
}
|
||||
|
||||
enc := json.NewEncoder(s)
|
||||
@@ -424,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
|
||||
@@ -435,20 +535,24 @@ GLOBAL OPTIONS:
|
||||
for _, host := range hosts {
|
||||
authKey, authPass := "", ""
|
||||
if host.Password != "" {
|
||||
authPass = "X"
|
||||
authPass = "yes"
|
||||
}
|
||||
if host.SSHKeyID > 0 {
|
||||
var key SSHKey
|
||||
db.Model(&host).Related(&key)
|
||||
authKey = key.Name
|
||||
}
|
||||
groupNames := []string{}
|
||||
for _, hostGroup := range host.Groups {
|
||||
groupNames = append(groupNames, hostGroup.Name)
|
||||
}
|
||||
table.Append([]string{
|
||||
fmt.Sprintf("%d", host.ID),
|
||||
host.Name,
|
||||
host.URL(),
|
||||
authKey,
|
||||
authPass,
|
||||
fmt.Sprintf("%d", len(host.Groups)),
|
||||
strings.Join(groupNames, ", "),
|
||||
host.Comment,
|
||||
//FIXME: add some stats about last access time etc
|
||||
//FIXME: add creation date
|
||||
@@ -460,22 +564,91 @@ GLOBAL OPTIONS:
|
||||
}, {
|
||||
Name: "rm",
|
||||
Usage: "Removes one or more hosts",
|
||||
ArgsUsage: "<id or name> [<id or name> [<id or name>...]]",
|
||||
ArgsUsage: "HOST...",
|
||||
Action: func(c *cli.Context) error {
|
||||
if c.NArg() < 1 {
|
||||
return cli.ShowSubcommandHelp(c)
|
||||
}
|
||||
|
||||
hosts, err := FindHostsByIdOrName(db, c.Args())
|
||||
if err != nil {
|
||||
return nil
|
||||
if err := UserCheckRoles(myself, []string{"admin"}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, host := range hosts {
|
||||
db.Where("id = ?", host.ID).Delete(&Host{})
|
||||
fmt.Fprintf(s, "%d\n", host.ID)
|
||||
return HostsByIdentifiers(db, c.Args()).Delete(&Host{}).Error
|
||||
},
|
||||
}, {
|
||||
Name: "update",
|
||||
Usage: "Updates an existing host",
|
||||
ArgsUsage: "HOST...",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{Name: "name, n", Usage: "Rename the host"},
|
||||
cli.StringFlag{Name: "password, p", Usage: "Update/set a password, use \"none\" to unset"},
|
||||
cli.StringFlag{Name: "fingerprint, f", Usage: "Update/set a host fingerprint, use \"none\" to unset"},
|
||||
cli.StringFlag{Name: "comment, c", Usage: "Update/set a host comment"},
|
||||
cli.StringFlag{Name: "key, k", Usage: "Link a `KEY` to use for authentication"},
|
||||
cli.StringSliceFlag{Name: "assign-group, g", Usage: "Assign the host to a new `HOSTGROUPS`"},
|
||||
cli.StringSliceFlag{Name: "unassign-group", Usage: "Unassign the host from a `HOSTGROUPS`"},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
if c.NArg() < 1 {
|
||||
return cli.ShowSubcommandHelp(c)
|
||||
}
|
||||
return nil
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
if len(hosts) > 1 && c.String("name") != "" {
|
||||
return fmt.Errorf("cannot set --name when editing multiple hosts at once")
|
||||
}
|
||||
|
||||
tx := db.Begin()
|
||||
for _, host := range hosts {
|
||||
model := tx.Model(&host)
|
||||
// simple fields
|
||||
for _, fieldname := range []string{"name", "comment", "password", "fingerprint"} {
|
||||
if c.String(fieldname) != "" {
|
||||
if err := model.Update(fieldname, c.String(fieldname)).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// associations
|
||||
if c.String("key") != "" {
|
||||
var key SSHKey
|
||||
if err := SSHKeysByIdentifiers(db, []string{c.String("key")}).First(&key).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
if err := model.Association("SSHKey").Replace(&key).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
}
|
||||
var appendGroups []HostGroup
|
||||
var deleteGroups []HostGroup
|
||||
if err := HostGroupsByIdentifiers(db, c.StringSlice("assign-group")).Find(&appendGroups).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
if err := HostGroupsByIdentifiers(db, c.StringSlice("unassign-group")).Find(&deleteGroups).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
if err := model.Association("Groups").Append(&appendGroups).Delete(deleteGroups).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return tx.Commit().Error
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -489,9 +662,13 @@ GLOBAL OPTIONS:
|
||||
Description: "$> hostgroup create --name=prod",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{Name: "name", Usage: "Assigns a name to the host group"},
|
||||
cli.StringFlag{Name: "comment"},
|
||||
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"),
|
||||
@@ -513,15 +690,19 @@ GLOBAL OPTIONS:
|
||||
}, {
|
||||
Name: "inspect",
|
||||
Usage: "Shows detailed information on one or more host groups",
|
||||
ArgsUsage: "<id or name> [<id or name> [<id or name>...]]",
|
||||
ArgsUsage: "HOSTGROUP...",
|
||||
Action: func(c *cli.Context) error {
|
||||
if c.NArg() < 1 {
|
||||
return cli.ShowSubcommandHelp(c)
|
||||
}
|
||||
|
||||
hostGroups, err := FindHostGroupsByIdOrName(db, c.Args())
|
||||
if err != nil {
|
||||
return nil
|
||||
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
|
||||
}
|
||||
|
||||
enc := json.NewEncoder(s)
|
||||
@@ -532,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
|
||||
@@ -556,22 +741,17 @@ GLOBAL OPTIONS:
|
||||
}, {
|
||||
Name: "rm",
|
||||
Usage: "Removes one or more host groups",
|
||||
ArgsUsage: "<id or name> [<id or name> [<id or name>...]]",
|
||||
ArgsUsage: "HOSTGROUP...",
|
||||
Action: func(c *cli.Context) error {
|
||||
if c.NArg() < 1 {
|
||||
return cli.ShowSubcommandHelp(c)
|
||||
}
|
||||
|
||||
hostGroups, err := FindHostGroupsByIdOrName(db, c.Args())
|
||||
if err != nil {
|
||||
return nil
|
||||
if err := UserCheckRoles(myself, []string{"admin"}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, hostGroup := range hostGroups {
|
||||
db.Where("id = ?", hostGroup.ID).Delete(&HostGroup{})
|
||||
fmt.Fprintf(s, "%d\n", hostGroup.ID)
|
||||
}
|
||||
return nil
|
||||
return HostGroupsByIdentifiers(db, c.Args()).Delete(&HostGroup{}).Error
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -579,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)
|
||||
@@ -594,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)
|
||||
@@ -620,9 +803,13 @@ GLOBAL OPTIONS:
|
||||
cli.StringFlag{Name: "name", Usage: "Assigns a name to the key"},
|
||||
cli.StringFlag{Name: "type", Value: "rsa"},
|
||||
cli.UintFlag{Name: "length", Value: 2048},
|
||||
cli.StringFlag{Name: "comment"},
|
||||
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")
|
||||
@@ -650,15 +837,19 @@ GLOBAL OPTIONS:
|
||||
}, {
|
||||
Name: "inspect",
|
||||
Usage: "Shows detailed information on one or more keys",
|
||||
ArgsUsage: "<id or name> [<id or name> [<id or name>...]]",
|
||||
ArgsUsage: "KEY...",
|
||||
Action: func(c *cli.Context) error {
|
||||
if c.NArg() < 1 {
|
||||
return cli.ShowSubcommandHelp(c)
|
||||
}
|
||||
|
||||
keys, err := FindKeysByIdOrName(db, c.Args())
|
||||
if err != nil {
|
||||
return nil
|
||||
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
|
||||
}
|
||||
|
||||
enc := json.NewEncoder(s)
|
||||
@@ -669,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
|
||||
@@ -696,22 +891,17 @@ GLOBAL OPTIONS:
|
||||
}, {
|
||||
Name: "rm",
|
||||
Usage: "Removes one or more keys",
|
||||
ArgsUsage: "<id or name> [<id or name> [<id or name>...]]",
|
||||
ArgsUsage: "KEY...",
|
||||
Action: func(c *cli.Context) error {
|
||||
if c.NArg() < 1 {
|
||||
return cli.ShowSubcommandHelp(c)
|
||||
}
|
||||
|
||||
keys, err := FindKeysByIdOrName(db, c.Args())
|
||||
if err != nil {
|
||||
return nil
|
||||
if err := UserCheckRoles(myself, []string{"admin"}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, key := range keys {
|
||||
db.Where("id = ?", key.ID).Delete(&SSHKey{})
|
||||
fmt.Fprintf(s, "%d\n", key.ID)
|
||||
}
|
||||
return nil
|
||||
return SSHKeysByIdentifiers(db, c.Args()).Delete(&SSHKey{}).Error
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -722,15 +912,19 @@ GLOBAL OPTIONS:
|
||||
{
|
||||
Name: "inspect",
|
||||
Usage: "Shows detailed information on one or more users",
|
||||
ArgsUsage: "<id or email> [<id or email> [<id or email>...]]",
|
||||
ArgsUsage: "USER...",
|
||||
Action: func(c *cli.Context) error {
|
||||
if c.NArg() < 1 {
|
||||
return cli.ShowSubcommandHelp(c)
|
||||
}
|
||||
|
||||
users, err := FindUsersByIdOrEmail(db, c.Args())
|
||||
if err != nil {
|
||||
return nil
|
||||
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
|
||||
}
|
||||
|
||||
enc := json.NewEncoder(s)
|
||||
@@ -744,14 +938,18 @@ GLOBAL OPTIONS:
|
||||
Description: "$> user invite bob@example.com\n $> user invite --name=Robert bob@example.com",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{Name: "name", Usage: "Assigns a name to the user"},
|
||||
cli.StringFlag{Name: "comment"},
|
||||
cli.StringSliceFlag{Name: "group, g", Usage: "Names or IDs of user groups (default: \"default\")"},
|
||||
cli.StringFlag{Name: "comment", Usage: "Adds a comment"},
|
||||
cli.StringSliceFlag{Name: "group, g", Usage: "Names or IDs of `USERGROUPS` (default: \"default\")"},
|
||||
},
|
||||
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: validate email
|
||||
|
||||
email := c.Args().First()
|
||||
@@ -776,11 +974,9 @@ GLOBAL OPTIONS:
|
||||
if len(inputGroups) == 0 {
|
||||
inputGroups = []string{"default"}
|
||||
}
|
||||
userGroups, err := FindUserGroupsByIdOrName(db, inputGroups)
|
||||
if err != nil {
|
||||
if err := UserGroupsByIdentifiers(db, inputGroups).Find(&user.Groups).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
user.Groups = userGroups
|
||||
|
||||
// save the user in database
|
||||
if err := db.Create(&user).Error; err != nil {
|
||||
@@ -793,21 +989,34 @@ 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", "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 {
|
||||
groupNames := []string{}
|
||||
for _, userGroup := range user.Groups {
|
||||
groupNames = append(groupNames, userGroup.Name)
|
||||
}
|
||||
roleNames := []string{}
|
||||
for _, role := range user.Roles {
|
||||
roleNames = append(roleNames, role.Name)
|
||||
}
|
||||
table.Append([]string{
|
||||
fmt.Sprintf("%d", user.ID),
|
||||
user.Name,
|
||||
user.Email,
|
||||
strings.Join(roleNames, ", "),
|
||||
fmt.Sprintf("%d", len(user.Keys)),
|
||||
fmt.Sprintf("%d", len(user.Groups)),
|
||||
strings.Join(groupNames, ", "),
|
||||
user.Comment,
|
||||
//FIXME: add some stats about last access time etc
|
||||
//FIXME: add creation date
|
||||
@@ -819,22 +1028,99 @@ GLOBAL OPTIONS:
|
||||
}, {
|
||||
Name: "rm",
|
||||
Usage: "Removes one or more users",
|
||||
ArgsUsage: "<id or email> [<id or email> [<id or email>...]]",
|
||||
ArgsUsage: "USER...",
|
||||
Action: func(c *cli.Context) error {
|
||||
if c.NArg() < 1 {
|
||||
return cli.ShowSubcommandHelp(c)
|
||||
}
|
||||
|
||||
users, err := FindUsersByIdOrEmail(db, c.Args())
|
||||
if err != nil {
|
||||
return nil
|
||||
if err := UserCheckRoles(myself, []string{"admin"}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, user := range users {
|
||||
db.Where("id = ?", user.ID).Delete(&User{})
|
||||
fmt.Fprintf(s, "%d\n", user.ID)
|
||||
return UsersByIdentifiers(db, c.Args()).Delete(&User{}).Error
|
||||
},
|
||||
}, {
|
||||
Name: "update",
|
||||
Usage: "Updates an existing user",
|
||||
ArgsUsage: "USER...",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{Name: "name, n", Usage: "Renames the user"},
|
||||
cli.StringFlag{Name: "email, e", Usage: "Updates the email"},
|
||||
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)
|
||||
}
|
||||
return nil
|
||||
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.Bool("set-admin") && c.Bool("unset-admin") {
|
||||
return fmt.Errorf("cannot use --set-admin and --unset-admin altogether")
|
||||
}
|
||||
|
||||
if len(users) > 1 && c.String("email") != "" {
|
||||
return fmt.Errorf("cannot set --email when editing multiple users at once")
|
||||
}
|
||||
|
||||
tx := db.Begin()
|
||||
for _, user := range users {
|
||||
model := tx.Model(&user)
|
||||
// simple fields
|
||||
for _, fieldname := range []string{"name", "email", "comment"} {
|
||||
if c.String(fieldname) != "" {
|
||||
if err := model.Update(fieldname, c.String(fieldname)).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// associations
|
||||
var appendGroups []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
|
||||
}
|
||||
if err := model.Association("Groups").Append(&appendGroups).Delete(deleteGroups).Error; err != nil {
|
||||
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
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -848,9 +1134,13 @@ GLOBAL OPTIONS:
|
||||
Description: "$> usergroup create --name=prod",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{Name: "name", Usage: "Assigns a name to the user group"},
|
||||
cli.StringFlag{Name: "comment"},
|
||||
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"),
|
||||
@@ -863,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 {
|
||||
@@ -878,15 +1166,19 @@ GLOBAL OPTIONS:
|
||||
}, {
|
||||
Name: "inspect",
|
||||
Usage: "Shows detailed information on one or more user groups",
|
||||
ArgsUsage: "<id or name> [<id or name> [<id or name>...]]",
|
||||
ArgsUsage: "USERGROUP...",
|
||||
Action: func(c *cli.Context) error {
|
||||
if c.NArg() < 1 {
|
||||
return cli.ShowSubcommandHelp(c)
|
||||
}
|
||||
|
||||
userGroups, err := FindUserGroupsByIdOrName(db, c.Args())
|
||||
if err != nil {
|
||||
return nil
|
||||
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
|
||||
}
|
||||
|
||||
enc := json.NewEncoder(s)
|
||||
@@ -897,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
|
||||
@@ -921,22 +1217,17 @@ GLOBAL OPTIONS:
|
||||
}, {
|
||||
Name: "rm",
|
||||
Usage: "Removes one or more user groups",
|
||||
ArgsUsage: "<id or name> [<id or name> [<id or name>...]]",
|
||||
ArgsUsage: "USERGROUP...",
|
||||
Action: func(c *cli.Context) error {
|
||||
if c.NArg() < 1 {
|
||||
return cli.ShowSubcommandHelp(c)
|
||||
}
|
||||
|
||||
userGroups, err := FindUserGroupsByIdOrName(db, c.Args())
|
||||
if err != nil {
|
||||
return nil
|
||||
if err := UserCheckRoles(myself, []string{"admin"}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, userGroup := range userGroups {
|
||||
db.Where("id = ?", userGroup.ID).Delete(&UserGroup{})
|
||||
fmt.Fprintf(s, "%d\n", userGroup.ID)
|
||||
}
|
||||
return nil
|
||||
return UserGroupsByIdentifiers(db, c.Args()).Delete(&UserGroup{}).Error
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -950,15 +1241,19 @@ GLOBAL OPTIONS:
|
||||
Usage: "Creates a new userkey",
|
||||
Description: "$> userkey create bob\n $> user create --name=mykey bob",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{Name: "comment"},
|
||||
cli.StringFlag{Name: "comment", Usage: "Adds a comment"},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
if c.NArg() != 1 {
|
||||
return cli.ShowSubcommandHelp(c)
|
||||
}
|
||||
|
||||
user, err := FindUserByIdOrEmail(db, c.Args().First())
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -972,7 +1267,7 @@ GLOBAL OPTIONS:
|
||||
}
|
||||
|
||||
userkey := UserKey{
|
||||
UserID: user.ID,
|
||||
User: &user,
|
||||
Key: key.Marshal(),
|
||||
Comment: comment,
|
||||
}
|
||||
@@ -994,25 +1289,33 @@ GLOBAL OPTIONS:
|
||||
}, {
|
||||
Name: "inspect",
|
||||
Usage: "Shows detailed information on one or more userkeys",
|
||||
ArgsUsage: "<id> [<id> [<id>...]]",
|
||||
ArgsUsage: "USERKEY...",
|
||||
Action: func(c *cli.Context) error {
|
||||
if c.NArg() < 1 {
|
||||
return cli.ShowSubcommandHelp(c)
|
||||
}
|
||||
|
||||
userkeys, err := FindUserkeysById(db, c.Args())
|
||||
if err != nil {
|
||||
return nil
|
||||
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
|
||||
}
|
||||
|
||||
enc := json.NewEncoder(s)
|
||||
enc.SetIndent("", " ")
|
||||
return enc.Encode(userkeys)
|
||||
return enc.Encode(userKeys)
|
||||
},
|
||||
}, {
|
||||
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
|
||||
@@ -1035,22 +1338,17 @@ GLOBAL OPTIONS:
|
||||
}, {
|
||||
Name: "rm",
|
||||
Usage: "Removes one or more userkeys",
|
||||
ArgsUsage: "<id> [<id> [<id>...]]",
|
||||
ArgsUsage: "USERKEY...",
|
||||
Action: func(c *cli.Context) error {
|
||||
if c.NArg() < 1 {
|
||||
return cli.ShowSubcommandHelp(c)
|
||||
}
|
||||
|
||||
userkeys, err := FindUserkeysById(db, c.Args())
|
||||
if err != nil {
|
||||
return nil
|
||||
if err := UserCheckRoles(myself, []string{"admin"}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, userkey := range userkeys {
|
||||
db.Where("id = ?", userkey.ID).Delete(&UserKey{})
|
||||
fmt.Fprintf(s, "%d\n", userkey.ID)
|
||||
}
|
||||
return nil
|
||||
return UserKeysByIdentifiers(db, c.Args()).Delete(&UserKey{}).Error
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user