Skip to content

Commit

Permalink
Merge pull request #9 from moul/dev/moul/roles
Browse files Browse the repository at this point in the history
Support multiple roles
  • Loading branch information
moul authored Nov 23, 2017
2 parents 09c1e05 + beeba05 commit 31a8cef
Show file tree
Hide file tree
Showing 6 changed files with 308 additions and 42 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
41 changes: 38 additions & 3 deletions db.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down Expand Up @@ -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
Expand All @@ -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)
}
101 changes: 99 additions & 2 deletions dbinit.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"fmt"
"log"
"os"

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
}

Expand Down
8 changes: 2 additions & 6 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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))
}
Expand Down
Loading

0 comments on commit 31a8cef

Please sign in to comment.