Skip to content

Commit

Permalink
Closes #17 - Manage user permissions via the web interface
Browse files Browse the repository at this point in the history
The old permission settings in the toml file will be accepted,
but ignored. They're used to migrate to the new database
driven system but after that they're not used.

UI and API rights are separate as they were conceptually with
the toml file. All users are in the "default" ui group and the
"disable" api group unless set otherwise. The status API is still
behind a flag as it requires the view debug permission.
  • Loading branch information
lfkeitel committed Mar 12, 2018
1 parent 1179388 commit bee5d98
Show file tree
Hide file tree
Showing 12 changed files with 241 additions and 61 deletions.
4 changes: 4 additions & 0 deletions cmd/pg/main.go
Expand Up @@ -107,6 +107,10 @@ func main() {
e.Log.WithField("error", err).Fatal("Error loading frontend templates")
}

if err := common.RunSystemInits(e); err != nil {
e.Log.WithField("error", err).Fatal("System initialization failed")
}

go tasks.StartTaskScheduler(e)

// Start web server
Expand Down
5 changes: 4 additions & 1 deletion public/assets/js/admin-user.js
Expand Up @@ -159,7 +159,10 @@ $.onReady(function() {
"valid_start": 0,
"valid_end": 0,
"can_manage": $('[name=can-manage]').prop('checked') ? 1 : 0,
"can_autoreg": $('[name=can-autoreg]').prop('checked') ? 1 : 0
"can_autoreg": $('[name=can-autoreg]').prop('checked') ? 1 : 0,
"allow_status_api": $('[name=user-api-status]').prop('checked') ? 1 : 0,
"ui_group": $('[name=user-ui-group]').value(),
"api_group": $('[name=user-api-group]').value()
};

if ($('[name=clear-pass]').prop("checked")) {
Expand Down
9 changes: 0 additions & 9 deletions src/common/config.go
Expand Up @@ -235,15 +235,6 @@ func setSensibleDefaults(c *Config) (*Config, error) {
if len(c.Auth.AuthMethod) == 0 {
c.Auth.AuthMethod = []string{"local"}
}
if len(c.Auth.AdminUsers) == 0 {
c.Auth.AdminUsers = []string{"admin"}
}
if len(c.Auth.HelpDeskUsers) == 0 {
c.Auth.HelpDeskUsers = []string{"helpdesk"}
}
if len(c.Auth.ReadOnlyUsers) == 0 {
c.Auth.ReadOnlyUsers = []string{"readonly"}
}

// DHCP
c.DHCP.ConfigFile = setStringOrDefault(c.DHCP.ConfigFile, "config/dhcp.conf")
Expand Down
20 changes: 20 additions & 0 deletions src/common/environment.go
Expand Up @@ -114,3 +114,23 @@ func (d *DatabaseAccessor) SchemaVersion() int {
verRow.Scan(&currDBVer)
return currDBVer
}

type SystemInitFunc func(*Environment) error

var systemInitFuncs []SystemInitFunc

func RegisterSystemInitFunc(f SystemInitFunc) {
if systemInitFuncs == nil {
systemInitFuncs = make([]SystemInitFunc, 0, 1)
}
systemInitFuncs = append(systemInitFuncs, f)
}

func RunSystemInits(e *Environment) error {
for _, f := range systemInitFuncs {
if err := f(e); err != nil {
return err
}
}
return nil
}
22 changes: 22 additions & 0 deletions src/controllers/api/user.go
Expand Up @@ -59,6 +59,28 @@ func (u *UserController) saveUserHandler(w http.ResponseWriter, r *http.Request)
return
}

// Permission groups
uiGroup := r.FormValue("ui_group")
apiGroup := r.FormValue("api_group")
allowStatusAPI := r.FormValue("allow_status_api") == "1"
if (user.UIGroup != uiGroup || user.APIGroup != apiGroup || user.AllowStatusAPI != allowStatusAPI) && !sessionUser.Can(models.EditUserPermissions) {
common.NewAPIResponse("Permission denied", nil).WriteResponse(w, http.StatusForbidden)
return
}

if !common.StringInSlice(uiGroup, []string{"default", "admin", "helpdesk", "readonly"}) {
common.NewAPIResponse("Unknown ui group", nil).WriteResponse(w, http.StatusBadRequest)
return
}
user.UIGroup = uiGroup

if !common.StringInSlice(apiGroup, []string{"disable", "readonly-api", "readwrite-api"}) {
common.NewAPIResponse("Unknown api group", nil).WriteResponse(w, http.StatusBadRequest)
return
}
user.APIGroup = apiGroup
user.AllowStatusAPI = allowStatusAPI

// Password
password := r.FormValue("password")
if password != "" {
Expand Down
4 changes: 3 additions & 1 deletion src/db/databaseCommon.go
Expand Up @@ -12,14 +12,16 @@ import (
"github.com/packet-guardian/packet-guardian/src/common"
)

const DBVersion = 2
const DBVersion = 3

type dbInit interface {
init(*common.DatabaseAccessor, *common.Config) error
}

var dbInits = make(map[string]dbInit)

type migrateFunc func(*common.DatabaseAccessor, *common.Config) error

func RegisterDatabaseAccessor(name string, db dbInit) {
dbInits[name] = db
}
Expand Down
89 changes: 78 additions & 11 deletions src/db/databaseMySQL.go
Expand Up @@ -12,6 +12,8 @@ import (
"fmt"
"strings"

"github.com/packet-guardian/packet-guardian/src/models/stores"

"github.com/go-sql-driver/mysql" // MySQL driver
"github.com/lfkeitel/verbose"
"github.com/packet-guardian/packet-guardian/src/common"
Expand All @@ -23,7 +25,7 @@ func init() {

type mySQLDB struct {
createFuncs map[string]func(*common.DatabaseAccessor) error
migrateFuncs []func(*common.DatabaseAccessor) error
migrateFuncs []migrateFunc
}

func newmySQLDBInit() *mySQLDB {
Expand All @@ -38,8 +40,9 @@ func newmySQLDBInit() *mySQLDB {
"user": m.createUserTable,
}

m.migrateFuncs = []func(*common.DatabaseAccessor) error{
m.migrateFuncs = []migrateFunc{
1: m.migrate1,
2: m.migrate2,
}

return m
Expand Down Expand Up @@ -106,6 +109,7 @@ func (m *mySQLDB) createTables(d *common.DatabaseAccessor) error {

for table, create := range m.createFuncs {
if !tables[table] {
fmt.Printf("Creating table %s\n", table)
if err := create(d); err != nil {
return err
}
Expand All @@ -114,7 +118,7 @@ func (m *mySQLDB) createTables(d *common.DatabaseAccessor) error {
return nil
}

func (m *mySQLDB) migrateTables(d *common.DatabaseAccessor) error {
func (m *mySQLDB) migrateTables(d *common.DatabaseAccessor, c *common.Config) error {
var currDBVer int
verRow := d.DB.QueryRow(`SELECT "value" FROM "settings" WHERE "id" = 'db_version'`)
if verRow == nil {
Expand All @@ -141,7 +145,7 @@ func (m *mySQLDB) migrateTables(d *common.DatabaseAccessor) error {
if migrate == nil {
continue
}
if err := migrate(d); err != nil {
if err := migrate(d, c); err != nil {
return err
}
}
Expand All @@ -160,7 +164,7 @@ func (m *mySQLDB) init(d *common.DatabaseAccessor, c *common.Config) error {
return err
}

return m.migrateTables(d)
return m.migrateTables(d, c)
}

func (m *mySQLDB) createBlacklistTable(d *common.DatabaseAccessor) error {
Expand Down Expand Up @@ -250,22 +254,25 @@ func (m *mySQLDB) createUserTable(d *common.DatabaseAccessor) error {
"can_autoreg" TINYINT DEFAULT 1,
"valid_start" INTEGER DEFAULT 0,
"valid_end" INTEGER DEFAULT 0,
"valid_forever" TINYINT DEFAULT 1
"valid_forever" TINYINT DEFAULT 1,
"ui_group" VARCHAR(20) NOT NULL DEFAULT 'default',
"api_group" VARCHAR(20) NOT NULL DEFAULT 'disable',
"allow_status_api" TINYINT DEFAULT 0
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=4;`

if _, err := d.DB.Exec(sql); err != nil {
return err
}

_, err := d.DB.Exec(`INSERT INTO "user"
("id", "username", "password") VALUES
(1, 'admin', '$2a$10$rZfN/gdXZdGYyLtUb6LF.eHOraDes3ibBECmWic2I3SocMC0L2Lxa'),
(2, 'helpdesk', '$2a$10$ICCdq/OyZBBoNPTRmfgntOnujD6INGv7ZAtA/Xq6JIdRMO65xCuNC'),
(3, 'readonly', '$2a$10$02NG6kQV.4UicpCnz8hyeefBD4JHKAlZToL2K0EN1HV.u6sXpP1Xy')`)
("id", "username", "password", "ui_group") VALUES
(1, 'admin', '$2a$10$rZfN/gdXZdGYyLtUb6LF.eHOraDes3ibBECmWic2I3SocMC0L2Lxa', 'admin'),
(2, 'helpdesk', '$2a$10$ICCdq/OyZBBoNPTRmfgntOnujD6INGv7ZAtA/Xq6JIdRMO65xCuNC', 'helpdesk'),
(3, 'readonly', '$2a$10$02NG6kQV.4UicpCnz8hyeefBD4JHKAlZToL2K0EN1HV.u6sXpP1Xy', 'readonly')`)
return err
}

func (m *mySQLDB) migrate1(d *common.DatabaseAccessor) error {
func (m *mySQLDB) migrate1(d *common.DatabaseAccessor, c *common.Config) error {
// Move device blacklist to blacklist table
bd, err := d.DB.Query(`SELECT "mac" FROM "device" WHERE "blacklisted" = 1`)
if err != nil {
Expand Down Expand Up @@ -295,3 +302,63 @@ func (m *mySQLDB) migrate1(d *common.DatabaseAccessor) error {
}
return nil
}

func (m *mySQLDB) migrate2(d *common.DatabaseAccessor, c *common.Config) error {
sql := `ALTER TABLE "user" ADD COLUMN (
"ui_group" VARCHAR(20) NOT NULL DEFAULT 'default',
"api_group" VARCHAR(20) NOT NULL DEFAULT 'disable',
"allow_status_api" TINYINT DEFAULT 0
);`

if _, err := d.DB.Exec(sql); err != nil {
return err
}

common.RegisterSystemInitFunc(migrateUserPermissions)
return nil
}

func migrateUserPermissions(e *common.Environment) error {
if err := migrateUserGroup(e, e.Config.Auth.AdminUsers, "ui", "admin"); err != nil {
return err
}
if err := migrateUserGroup(e, e.Config.Auth.HelpDeskUsers, "ui", "helpdesk"); err != nil {
return err
}
if err := migrateUserGroup(e, e.Config.Auth.ReadOnlyUsers, "ui", "readonly"); err != nil {
return err
}
if err := migrateUserGroup(e, e.Config.Auth.APIReadOnlyUsers, "api", "readonly-api"); err != nil {
return err
}
if err := migrateUserGroup(e, e.Config.Auth.APIReadWriteUsers, "api", "readwrite-api"); err != nil {
return err
}
if err := migrateUserGroup(e, e.Config.Auth.APIStatusUsers, "api-status", ""); err != nil {
return err
}
return nil
}

func migrateUserGroup(e *common.Environment, members []string, group, groupName string) error {
for _, username := range members {
user, err := stores.GetUserStore(e).GetUserByUsername(username)
if err != nil {
return err
}

switch group {
case "ui":
user.UIGroup = groupName
case "api":
user.APIGroup = groupName
case "api-status":
user.AllowStatusAPI = true
}

if err := user.Save(); err != nil {
return err
}
}
return nil
}
43 changes: 32 additions & 11 deletions src/db/databaseSQLite.go
Expand Up @@ -24,7 +24,7 @@ func init() {

type sqliteDB struct {
createFuncs map[string]func(*common.DatabaseAccessor) error
migrateFuncs []func(*common.DatabaseAccessor) error
migrateFuncs []migrateFunc
}

func newSQLiteDBInit() *sqliteDB {
Expand All @@ -39,8 +39,9 @@ func newSQLiteDBInit() *sqliteDB {
"user": s.createUserTable,
}

s.migrateFuncs = []func(*common.DatabaseAccessor) error{
s.migrateFuncs = []migrateFunc{
1: s.migrate1,
2: s.migrate2,
}

return s
Expand Down Expand Up @@ -86,6 +87,7 @@ func (s *sqliteDB) createTables(d *common.DatabaseAccessor) error {

for table, create := range s.createFuncs {
if !tables[table] {
fmt.Printf("Creating table %s\n", table)
if err := create(d); err != nil {
return err
}
Expand All @@ -94,7 +96,7 @@ func (s *sqliteDB) createTables(d *common.DatabaseAccessor) error {
return nil
}

func (s *sqliteDB) migrateTables(d *common.DatabaseAccessor) error {
func (s *sqliteDB) migrateTables(d *common.DatabaseAccessor, c *common.Config) error {
var currDBVer int
verRow := d.DB.QueryRow(`SELECT "value" FROM "settings" WHERE "id" = 'db_version'`)
if verRow == nil {
Expand All @@ -117,7 +119,7 @@ func (s *sqliteDB) migrateTables(d *common.DatabaseAccessor) error {
if migrate == nil {
continue
}
if err := migrate(d); err != nil {
if err := migrate(d, c); err != nil {
return err
}
}
Expand All @@ -137,7 +139,7 @@ func (s *sqliteDB) init(d *common.DatabaseAccessor, c *common.Config) error {
return err
}

return s.migrateTables(d)
return s.migrateTables(d, c)
}

func (s *sqliteDB) createBlacklistTable(d *common.DatabaseAccessor) error {
Expand Down Expand Up @@ -226,22 +228,25 @@ func (s *sqliteDB) createUserTable(d *common.DatabaseAccessor) error {
"can_autoreg" INTEGER DEFAULT 1,
"valid_start" INTEGER DEFAULT 0,
"valid_end" INTEGER DEFAULT 0,
"valid_forever" INTEGER DEFAULT 1
"valid_forever" INTEGER DEFAULT 1,
"ui_group" VARCHAR(20) NOT NULL DEFAULT 'default',
"api_group" VARCHAR(20) NOT NULL DEFAULT 'disable',
"allow_status_api" INTEGER DEFAULT 0
)`

if _, err := d.DB.Exec(sql); err != nil {
return err
}

_, err := d.DB.Exec(`INSERT INTO "user"
("id", "username", "password") VALUES
(1, 'admin', '$2a$10$rZfN/gdXZdGYyLtUb6LF.eHOraDes3ibBECmWic2I3SocMC0L2Lxa'),
(2, 'helpdesk', '$2a$10$ICCdq/OyZBBoNPTRmfgntOnujD6INGv7ZAtA/Xq6JIdRMO65xCuNC'),
(3, 'readonly', '$2a$10$02NG6kQV.4UicpCnz8hyeefBD4JHKAlZToL2K0EN1HV.u6sXpP1Xy')`)
("id", "username", "password", "ui_group") VALUES
(1, 'admin', '$2a$10$rZfN/gdXZdGYyLtUb6LF.eHOraDes3ibBECmWic2I3SocMC0L2Lxa', 'admin'),
(2, 'helpdesk', '$2a$10$ICCdq/OyZBBoNPTRmfgntOnujD6INGv7ZAtA/Xq6JIdRMO65xCuNC', 'helpdesk'),
(3, 'readonly', '$2a$10$02NG6kQV.4UicpCnz8hyeefBD4JHKAlZToL2K0EN1HV.u6sXpP1Xy', 'readonly')`)
return err
}

func (s *sqliteDB) migrate1(d *common.DatabaseAccessor) error {
func (s *sqliteDB) migrate1(d *common.DatabaseAccessor, c *common.Config) error {
// Move device blacklist to blacklist table
bd, err := d.DB.Query(`SELECT "mac" FROM "device" WHERE "blacklisted" = 1`)
if err != nil {
Expand Down Expand Up @@ -271,3 +276,19 @@ func (s *sqliteDB) migrate1(d *common.DatabaseAccessor) error {
}
return nil
}

func (s *sqliteDB) migrate2(d *common.DatabaseAccessor, c *common.Config) error {
sql := `ALTER TABLE "user" ADD COLUMN (
"ui_group" VARCHAR(20) NOT NULL DEFAULT 'default',
"api_group" VARCHAR(20) NOT NULL DEFAULT 'disable',
"allow_status_api" INTEGER DEFAULT 0
);`

if _, err := d.DB.Exec(sql); err != nil {
return err
}

// migrateUserPermissions is defined in the MySQL file, there's nothing DB specific
common.RegisterSystemInitFunc(migrateUserPermissions)
return nil
}

0 comments on commit bee5d98

Please sign in to comment.