mirror of
https://github.com/moul/sshportal.git
synced 2025-01-06 07:32:01 +08:00
Switch from IsAdmin boolean to Roles
This commit is contained in:
parent
09c1e0504e
commit
9cd9152a91
5 changed files with 145 additions and 33 deletions
2
Makefile
2
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:
|
||||
|
|
28
db.go
28
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,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)
|
||||
}
|
||||
|
|
93
dbinit.go
93
dbinit.go
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
4
main.go
4
main.go
|
@ -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))
|
||||
}
|
||||
|
|
51
shell.go
51
shell.go
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue