Switch from IsAdmin boolean to Roles

This commit is contained in:
Manfred Touron 2017-11-23 16:22:23 +01:00
parent 09c1e0504e
commit 9cd9152a91
5 changed files with 145 additions and 33 deletions

View file

@ -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:

28
db.go
View file

@ -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,18 @@ 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
}
// ACL helpers
@ -209,3 +223,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)
}

View file

@ -1,6 +1,7 @@
package main
import (
"fmt"
"log"
"os"
@ -201,6 +202,88 @@ 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")
},
},
})
if err := m.Migrate(); err != nil {
@ -284,15 +367,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)
}

View file

@ -120,7 +120,7 @@ func server(c *cli.Context) error {
switch username := s.User(); {
case username == c.String("config-user"):
if !currentUser.IsAdmin {
if !UserHasRole(currentUser, "admin") {
fmt.Fprintf(s, "You are not an administrator, permission denied.\n")
return
}
@ -181,7 +181,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))
}

View file

@ -897,11 +897,11 @@ GLOBAL OPTIONS:
Usage: "Lists users",
Action: func(c *cli.Context) error {
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 +909,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,
@ -946,10 +946,10 @@ 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 {
@ -983,27 +983,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 +998,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