From d17a6fb8aaf3aa6b05015e95d1391198ec01da9d Mon Sep 17 00:00:00 2001 From: Tit Petric Date: Wed, 16 Jan 2019 15:47:23 +0100 Subject: [PATCH] add(internal): rules feature - add make test.rules target - add rules api implementation - add sql migration for rules table - add unit tests to verify it works --- Makefile | 4 + internal/rules/interfaces.go | 16 +++ internal/rules/main_test.go | 64 +++++++++ internal/rules/resources.go | 123 ++++++++++++++++++ internal/rules/resources_test.go | 64 +++++++++ system/db/mysql/statik.go | 2 +- .../schema/mysql/20190116102104.rules.up.sql | 8 ++ system/types/rules.go | 8 ++ 8 files changed, 288 insertions(+), 1 deletion(-) create mode 100644 internal/rules/interfaces.go create mode 100644 internal/rules/main_test.go create mode 100644 internal/rules/resources.go create mode 100644 internal/rules/resources_test.go create mode 100644 system/db/schema/mysql/20190116102104.rules.up.sql create mode 100644 system/types/rules.go diff --git a/Makefile b/Makefile index 796d21684..756071aa1 100644 --- a/Makefile +++ b/Makefile @@ -114,6 +114,10 @@ test.rbac: $(GOTEST) $(GOTEST) -covermode count -coverprofile .cover.out -v ./internal/rbac/... $(GO) tool cover -func=.cover.out | grep --color "^\|[^0-9]0.0%" +test.rules: $(GOTEST) + $(GOTEST) -covermode count -coverprofile .cover.out -v ./internal/rules/... + $(GO) tool cover -func=.cover.out | grep --color "^\|[^0-9]0.0%" + test.mail: $(GOTEST) $(GOTEST) -covermode count -coverprofile .cover.out -v ./internal/mail/... $(GO) tool cover -func=.cover.out | grep --color "^\|[^0-9]0.0%" diff --git a/internal/rules/interfaces.go b/internal/rules/interfaces.go new file mode 100644 index 000000000..0b7a6a8dd --- /dev/null +++ b/internal/rules/interfaces.go @@ -0,0 +1,16 @@ +package rules + +import ( + "context" + + "github.com/titpetric/factory" +) + +type ResourcesInterface interface { + With(ctx context.Context, db *factory.DB) ResourcesInterface + + CheckAccessMulti(resource string, operation string) error + CheckAccess(resource string, operation string) error + + Grant(resource string, teamID uint64, operations []string, value Access) error +} diff --git a/internal/rules/main_test.go b/internal/rules/main_test.go new file mode 100644 index 000000000..09b285fec --- /dev/null +++ b/internal/rules/main_test.go @@ -0,0 +1,64 @@ +package rules_test + +import ( + "log" + "os" + "testing" + + "github.com/joho/godotenv" + "github.com/namsral/flag" + "github.com/titpetric/factory" + + systemMigrate "github.com/crusttech/crust/system/db" +) + +func TestMain(m *testing.M) { + // @todo this is a very optimistic initialization, make it more robust + godotenv.Load("../../.env") + + prefix := "system" + dsn := "" + + p := func(s string) string { + return prefix + "-" + s + } + + flag.StringVar(&dsn, p("db-dsn"), "crust:crust@tcp(db1:3306)/crust?collation=utf8mb4_general_ci", "DSN for database connection") + flag.Parse() + + if testing.Short() { + return + } + + factory.Database.Add("default", dsn) + + db := factory.Database.MustGet() + db.Profiler = &factory.Database.ProfilerStdout + + // migrate database schema + if err := systemMigrate.Migrate(db); err != nil { + log.Printf("Error running migrations: %+v\n", err) + return + } + + os.Exit(m.Run()) +} + +func assert(t *testing.T, ok bool, format string, args ...interface{}) bool { + if !ok { + t.Fatalf(format, args...) + } + return ok +} + +func must(t *testing.T, err error, message ...string) { + if len(message) > 0 { + assert(t, err == nil, message[0]+": %+v", err) + return + } + assert(t, err == nil, "Error: %+v", err) +} + +func mustFail(t *testing.T, err error) { + assert(t, err != nil, "Expected error, got nil") +} diff --git a/internal/rules/resources.go b/internal/rules/resources.go new file mode 100644 index 000000000..e57e2b4ee --- /dev/null +++ b/internal/rules/resources.go @@ -0,0 +1,123 @@ +package rules + +import ( + "context" + "strings" + + "github.com/pkg/errors" + "github.com/titpetric/factory" + + "github.com/crusttech/crust/internal/auth" + "github.com/crusttech/crust/system/types" +) + +type Access string + +const ( + Allow Access = "yes" + Deny = "no" + Inherit = "" +) + +type resources struct { + ctx context.Context + db *factory.DB +} + +func NewResources(ctx context.Context, db *factory.DB) ResourcesInterface { + return (&resources{}).With(ctx, db) +} + +func (r *resources) With(ctx context.Context, db *factory.DB) ResourcesInterface { + return &resources{ + ctx: ctx, + db: db, + } +} + +func (r *resources) identity() uint64 { + return auth.GetIdentityFromContext(r.ctx).Identity() +} + +func (r *resources) CheckAccessMulti(resource string, operation string) error { + user := r.identity() + result := []Access{} + query := []string{ + // select rules + "select r.value from sys_rules r", + // join members + "inner join sys_team_member m on (m.rel_team = r.rel_team and m.rel_user=?)", + // add conditions + "where r.resource LIKE ? and r.operation=?", + } + resource = strings.Replace(resource, "*", "%", -1) + queryString := strings.Join(query, " ") + if err := r.db.Select(&result, queryString, user, resource, operation); err != nil { + return err + } + + // order by deny, allow + for _, val := range result { + if val == Deny { + return errors.New("Access not allowed") + } + } + for _, val := range result { + if val == Allow { + return nil + } + } + return errors.New("Access not allowed") +} + +func (r *resources) CheckAccess(resource string, operation string) error { + user := r.identity() + result := []Access{} + query := []string{ + // select rules + "select r.value from sys_rules r", + // join members + "inner join sys_team_member m on (m.rel_team = r.rel_team and m.rel_user=?)", + // add conditions + "where r.resource=? and r.operation=?", + } + queryString := strings.Join(query, " ") + if err := r.db.Select(&result, queryString, user, resource, operation); err != nil { + return err + } + + // order by deny, allow + for _, val := range result { + if val == Deny { + return errors.New("Access not allowed") + } + } + for _, val := range result { + if val == Allow { + return nil + } + } + return errors.New("Access not allowed") +} + +func (r *resources) Grant(resource string, teamID uint64, operations []string, value Access) error { + row := types.Rules{ + TeamID: teamID, + Resource: resource, + Value: string(value), + } + var err error + for _, operation := range operations { + row.Operation = operation + switch value { + case Inherit: + _, err = r.db.NamedExec("delete from sys_rules where rel_team=:rel_team and resource=:resource and operation=:operation", row) + default: + err = r.db.Replace("sys_rules", row) + } + if err != nil { + break + } + } + return err +} diff --git a/internal/rules/resources_test.go b/internal/rules/resources_test.go new file mode 100644 index 000000000..4a4bd13b4 --- /dev/null +++ b/internal/rules/resources_test.go @@ -0,0 +1,64 @@ +package rules_test + +import ( + "context" + "errors" + "fmt" + "testing" + + "github.com/titpetric/factory" + + "github.com/crusttech/crust/internal/auth" + "github.com/crusttech/crust/internal/rules" + "github.com/crusttech/crust/system/types" +) + +func TestRules(t *testing.T) { + user := &types.User{ID: 1337} + ctx := auth.SetIdentityToContext(context.Background(), user) + + db := factory.Database.MustGet() + mustFail(t, db.Transaction(func() error { + db.Insert("sys_user", user) + var i uint64 = 0 + for i < 5 { + db.Insert("sys_team", types.Team{ID: i, Name: fmt.Sprintf("Team %d", i)}) + i++ + } + db.Insert("sys_team_member", types.TeamMember{TeamID: 1, UserID: user.ID}) + db.Insert("sys_team_member", types.TeamMember{TeamID: 2, UserID: user.ID}) + + resources := rules.NewResources(ctx, db) + + // default (unset=deny) + { + mustFail(t, resources.CheckAccess("channel:1", "edit")) + mustFail(t, resources.CheckAccessMulti("channel:*", "edit")) + } + + // allow channel:2 group:2 (default deny, multi=allow) + { + resources.Grant("channel:2", 2, []string{"edit", "delete"}, rules.Allow) + mustFail(t, resources.CheckAccess("channel:1", "edit")) + must(t, resources.CheckAccess("channel:2", "edit")) + must(t, resources.CheckAccessMulti("channel:*", "edit")) + } + + // deny channel:1 group:1 (explicit deny, multi=deny) + { + resources.Grant("channel:1", 1, []string{"edit"}, rules.Deny) + mustFail(t, resources.CheckAccess("channel:1", "edit")) + must(t, resources.CheckAccess("channel:2", "edit")) + mustFail(t, resources.CheckAccessMulti("channel:*", "edit")) + } + + // reset (unset=deny) + { + resources.Grant("channel:2", 2, []string{"edit", "delete"}, rules.Inherit) + resources.Grant("channel:1", 1, []string{"edit", "delete"}, rules.Inherit) + mustFail(t, resources.CheckAccess("channel:1", "edit")) + mustFail(t, resources.CheckAccessMulti("channel:*", "edit")) + } + return errors.New("Rollback") + })) +} diff --git a/system/db/mysql/statik.go b/system/db/mysql/statik.go index 1b47d1046..64ecf86cb 100644 --- a/system/db/mysql/statik.go +++ b/system/db/mysql/statik.go @@ -8,7 +8,7 @@ import ( ) func Data() string { - return "PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1a\x00 \x0020180704080000.base.up.sqlUT\x05\x00\x01\x80Cm8-- all known organisations (crust instances) and our relation towards them\nCREATE TABLE organisations (\n id BIGINT UNSIGNED NOT NULL,\n fqn TEXT NOT NULL, -- fully qualified name of the organisation\n name TEXT NOT NULL, -- display name of the organisation\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n updated_at DATETIME NULL,\n archived_at DATETIME NULL,\n deleted_at DATETIME NULL, -- organisation soft delete\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE settings (\n name VARCHAR(200) NOT NULL COMMENT 'Unique set of setting keys',\n value TEXT COMMENT 'Setting value',\n\n PRIMARY KEY (name)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\n-- Keeps all known users, home and external organisation\n-- changes are stored in audit log\nCREATE TABLE users (\n id BIGINT UNSIGNED NOT NULL,\n email TEXT NOT NULL,\n username TEXT NOT NULL,\n password TEXT NOT NULL,\n name TEXT NOT NULL,\n handle TEXT NOT NULL,\n meta JSON NOT NULL,\n satosa_id CHAR(36) NULL,\n\n rel_organisation BIGINT UNSIGNED NOT NULL,\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n updated_at DATETIME NULL,\n suspended_at DATETIME NULL,\n deleted_at DATETIME NULL, -- user soft delete\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE UNIQUE INDEX uid_satosa ON users (satosa_id);\n\n-- Keeps all known teams\nCREATE TABLE teams (\n id BIGINT UNSIGNED NOT NULL,\n name TEXT NOT NULL, -- display name of the team\n handle TEXT NOT NULL, -- team handle string\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n updated_at DATETIME NULL,\n archived_at DATETIME NULL,\n deleted_at DATETIME NULL, -- team soft delete\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\n-- Keeps team memberships\nCREATE TABLE team_members (\n rel_team BIGINT UNSIGNED NOT NULL REFERENCES organisation(id),\n rel_user BIGINT UNSIGNED NOT NULL,\n\n PRIMARY KEY (rel_team, rel_user)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\nPK\x07\x08\xedzU\x8am \x00\x00m \x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00.\x00 \x0020181124181811.rename_and_prefix_tables.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE teams RENAME TO sys_team;\nALTER TABLE organisations RENAME TO sys_organisation;\nALTER TABLE team_members RENAME TO sys_team_member;\nALTER TABLE users RENAME TO sys_user;PK\x07\x08\xf2\xc4\x87\xe8\xb5\x00\x00\x00\xb5\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00-\x00 \x0020181125100429.add_user_kind_and_owner.up.sqlUT\x05\x00\x01\x80Cm8# add field to manage user type (bot support)\nALTER TABLE `sys_user` ADD `kind` VARCHAR(8) NOT NULL DEFAULT '' AFTER `handle`;\n\n# add field to manage \"ownership\" (get all bots created by user)\nALTER TABLE `sys_user` ADD `rel_user_id` BIGINT UNSIGNED NOT NULL AFTER `rel_organisation`, ADD INDEX (`rel_user_id`);\nPK\x07\x089\xa0\xdat8\x01\x00\x008\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00-\x00 \x0020181125153544.satosa_index_not_unique.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `sys_user` DROP INDEX `uid_satosa`, ADD INDEX `uid_satosa` (`satosa_id`) USING BTREE;PK\x07\x08\x0d\xf9\xd3ga\x00\x00\x00a\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00!\x00 \x0020181208140000.credentials.up.sqlUT\x05\x00\x01\x80Cm8-- Keeps all known users, home and external organisation\n-- changes are stored in audit log\nCREATE TABLE sys_credentials (\n id BIGINT UNSIGNED NOT NULL,\n rel_owner BIGINT UNSIGNED NOT NULL REFERENCES sys_users(id),\n label TEXT NOT NULL COMMENT 'something we can differentiate credentials by',\n kind VARCHAR(128) NOT NULL COMMENT 'hash, facebook, gplus, github, linkedin ...',\n credentials TEXT NOT NULL COMMENT 'crypted/hashed passwords, secrets, social profile ID',\n meta JSON NOT NULL,\n expires_at DATETIME NULL,\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n updated_at DATETIME NULL,\n deleted_at DATETIME NULL, -- user soft delete\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE INDEX idx_owner ON sys_credentials (rel_owner);\nPK\x07\x08f\x1f\x08\xd0\x9a\x03\x00\x00\x9a\x03\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00)\x00 \x0020190103203201.users-password-null.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `sys_user` MODIFY `password` TEXT NULL;\nPK\x07\x080V\x13\x0f4\x00\x00\x004\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00 \x00migrations.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE IF NOT EXISTS `migrations` (\n `project` varchar(16) NOT NULL COMMENT 'sam, crm, ...',\n `filename` varchar(255) NOT NULL COMMENT 'yyyymmddHHMMSS.sql',\n `statement_index` int(11) NOT NULL COMMENT 'Statement number from SQL file',\n `status` TEXT NOT NULL COMMENT 'ok or full error message',\n PRIMARY KEY (`project`,`filename`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nPK\x07\x08\x0d\xa5T2x\x01\x00\x00x\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00 \x00new.shUT\x05\x00\x01\x80Cm8#!/bin/bash\ntouch $(date +%Y%m%d%H%M%S).up.sqlPK\x07\x08s\xd4N*.\x00\x00\x00.\x00\x00\x00PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xedzU\x8am \x00\x00m \x00\x00\x1a\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x00\x00\x00\x0020180704080000.base.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xf2\xc4\x87\xe8\xb5\x00\x00\x00\xb5\x00\x00\x00.\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xbe \x00\x0020181124181811.rename_and_prefix_tables.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(9\xa0\xdat8\x01\x00\x008\x01\x00\x00-\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xd8\n\x00\x0020181125100429.add_user_kind_and_owner.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\x0d\xf9\xd3ga\x00\x00\x00a\x00\x00\x00-\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81t\x0c\x00\x0020181125153544.satosa_index_not_unique.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(f\x1f\x08\xd0\x9a\x03\x00\x00\x9a\x03\x00\x00!\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x819\x0d\x00\x0020181208140000.credentials.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(0V\x13\x0f4\x00\x00\x004\x00\x00\x00)\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81+\x11\x00\x0020190103203201.users-password-null.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\x0d\xa5T2x\x01\x00\x00x\x01\x00\x00\x0e\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xbf\x11\x00\x00migrations.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(s\xd4N*.\x00\x00\x00.\x00\x00\x00\x06\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xed\x81|\x13\x00\x00new.shUT\x05\x00\x01\x80Cm8PK\x05\x06\x00\x00\x00\x00\x08\x00\x08\x00\xb8\x02\x00\x00\xe7\x13\x00\x00\x00\x00" + return "PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1a\x00 \x0020180704080000.base.up.sqlUT\x05\x00\x01\x80Cm8-- all known organisations (crust instances) and our relation towards them\nCREATE TABLE organisations (\n id BIGINT UNSIGNED NOT NULL,\n fqn TEXT NOT NULL, -- fully qualified name of the organisation\n name TEXT NOT NULL, -- display name of the organisation\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n updated_at DATETIME NULL,\n archived_at DATETIME NULL,\n deleted_at DATETIME NULL, -- organisation soft delete\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE settings (\n name VARCHAR(200) NOT NULL COMMENT 'Unique set of setting keys',\n value TEXT COMMENT 'Setting value',\n\n PRIMARY KEY (name)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\n-- Keeps all known users, home and external organisation\n-- changes are stored in audit log\nCREATE TABLE users (\n id BIGINT UNSIGNED NOT NULL,\n email TEXT NOT NULL,\n username TEXT NOT NULL,\n password TEXT NOT NULL,\n name TEXT NOT NULL,\n handle TEXT NOT NULL,\n meta JSON NOT NULL,\n satosa_id CHAR(36) NULL,\n\n rel_organisation BIGINT UNSIGNED NOT NULL,\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n updated_at DATETIME NULL,\n suspended_at DATETIME NULL,\n deleted_at DATETIME NULL, -- user soft delete\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE UNIQUE INDEX uid_satosa ON users (satosa_id);\n\n-- Keeps all known teams\nCREATE TABLE teams (\n id BIGINT UNSIGNED NOT NULL,\n name TEXT NOT NULL, -- display name of the team\n handle TEXT NOT NULL, -- team handle string\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n updated_at DATETIME NULL,\n archived_at DATETIME NULL,\n deleted_at DATETIME NULL, -- team soft delete\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\n-- Keeps team memberships\nCREATE TABLE team_members (\n rel_team BIGINT UNSIGNED NOT NULL REFERENCES organisation(id),\n rel_user BIGINT UNSIGNED NOT NULL,\n\n PRIMARY KEY (rel_team, rel_user)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\nPK\x07\x08\xedzU\x8am \x00\x00m \x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00.\x00 \x0020181124181811.rename_and_prefix_tables.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE teams RENAME TO sys_team;\nALTER TABLE organisations RENAME TO sys_organisation;\nALTER TABLE team_members RENAME TO sys_team_member;\nALTER TABLE users RENAME TO sys_user;PK\x07\x08\xf2\xc4\x87\xe8\xb5\x00\x00\x00\xb5\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00-\x00 \x0020181125100429.add_user_kind_and_owner.up.sqlUT\x05\x00\x01\x80Cm8# add field to manage user type (bot support)\nALTER TABLE `sys_user` ADD `kind` VARCHAR(8) NOT NULL DEFAULT '' AFTER `handle`;\n\n# add field to manage \"ownership\" (get all bots created by user)\nALTER TABLE `sys_user` ADD `rel_user_id` BIGINT UNSIGNED NOT NULL AFTER `rel_organisation`, ADD INDEX (`rel_user_id`);\nPK\x07\x089\xa0\xdat8\x01\x00\x008\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00-\x00 \x0020181125153544.satosa_index_not_unique.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `sys_user` DROP INDEX `uid_satosa`, ADD INDEX `uid_satosa` (`satosa_id`) USING BTREE;PK\x07\x08\x0d\xf9\xd3ga\x00\x00\x00a\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00!\x00 \x0020181208140000.credentials.up.sqlUT\x05\x00\x01\x80Cm8-- Keeps all known users, home and external organisation\n-- changes are stored in audit log\nCREATE TABLE sys_credentials (\n id BIGINT UNSIGNED NOT NULL,\n rel_owner BIGINT UNSIGNED NOT NULL REFERENCES sys_users(id),\n label TEXT NOT NULL COMMENT 'something we can differentiate credentials by',\n kind VARCHAR(128) NOT NULL COMMENT 'hash, facebook, gplus, github, linkedin ...',\n credentials TEXT NOT NULL COMMENT 'crypted/hashed passwords, secrets, social profile ID',\n meta JSON NOT NULL,\n expires_at DATETIME NULL,\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n updated_at DATETIME NULL,\n deleted_at DATETIME NULL, -- user soft delete\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE INDEX idx_owner ON sys_credentials (rel_owner);\nPK\x07\x08f\x1f\x08\xd0\x9a\x03\x00\x00\x9a\x03\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00)\x00 \x0020190103203201.users-password-null.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `sys_user` MODIFY `password` TEXT NULL;\nPK\x07\x080V\x13\x0f4\x00\x00\x004\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1b\x00 \x0020190116102104.rules.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `sys_rules` (\n `rel_team` BIGINT UNSIGNED NOT NULL,\n `resource` VARCHAR(128) NOT NULL,\n `operation` VARCHAR(128) NOT NULL,\n `value` ENUM('no', 'yes') NOT NULL,\n\n PRIMARY KEY (`rel_team`, `resource`, `operation`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\nPK\x07\x08#\xa4\xea\x17\x0c\x01\x00\x00\x0c\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00 \x00migrations.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE IF NOT EXISTS `migrations` (\n `project` varchar(16) NOT NULL COMMENT 'sam, crm, ...',\n `filename` varchar(255) NOT NULL COMMENT 'yyyymmddHHMMSS.sql',\n `statement_index` int(11) NOT NULL COMMENT 'Statement number from SQL file',\n `status` TEXT NOT NULL COMMENT 'ok or full error message',\n PRIMARY KEY (`project`,`filename`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nPK\x07\x08\x0d\xa5T2x\x01\x00\x00x\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00 \x00new.shUT\x05\x00\x01\x80Cm8#!/bin/bash\ntouch $(date +%Y%m%d%H%M%S).up.sqlPK\x07\x08s\xd4N*.\x00\x00\x00.\x00\x00\x00PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xedzU\x8am \x00\x00m \x00\x00\x1a\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x00\x00\x00\x0020180704080000.base.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xf2\xc4\x87\xe8\xb5\x00\x00\x00\xb5\x00\x00\x00.\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xbe \x00\x0020181124181811.rename_and_prefix_tables.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(9\xa0\xdat8\x01\x00\x008\x01\x00\x00-\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xd8\n\x00\x0020181125100429.add_user_kind_and_owner.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\x0d\xf9\xd3ga\x00\x00\x00a\x00\x00\x00-\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81t\x0c\x00\x0020181125153544.satosa_index_not_unique.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(f\x1f\x08\xd0\x9a\x03\x00\x00\x9a\x03\x00\x00!\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x819\x0d\x00\x0020181208140000.credentials.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(0V\x13\x0f4\x00\x00\x004\x00\x00\x00)\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81+\x11\x00\x0020190103203201.users-password-null.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(#\xa4\xea\x17\x0c\x01\x00\x00\x0c\x01\x00\x00\x1b\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xbf\x11\x00\x0020190116102104.rules.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\x0d\xa5T2x\x01\x00\x00x\x01\x00\x00\x0e\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x1d\x13\x00\x00migrations.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(s\xd4N*.\x00\x00\x00.\x00\x00\x00\x06\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xed\x81\xda\x14\x00\x00new.shUT\x05\x00\x01\x80Cm8PK\x05\x06\x00\x00\x00\x00 \x00 \x00\n\x03\x00\x00E\x15\x00\x00\x00\x00" } func init() { diff --git a/system/db/schema/mysql/20190116102104.rules.up.sql b/system/db/schema/mysql/20190116102104.rules.up.sql new file mode 100644 index 000000000..fb54cfe6f --- /dev/null +++ b/system/db/schema/mysql/20190116102104.rules.up.sql @@ -0,0 +1,8 @@ +CREATE TABLE `sys_rules` ( + `rel_team` BIGINT UNSIGNED NOT NULL, + `resource` VARCHAR(128) NOT NULL, + `operation` VARCHAR(128) NOT NULL, + `value` ENUM('no', 'yes') NOT NULL, + + PRIMARY KEY (`rel_team`, `resource`, `operation`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; diff --git a/system/types/rules.go b/system/types/rules.go new file mode 100644 index 000000000..f4524c042 --- /dev/null +++ b/system/types/rules.go @@ -0,0 +1,8 @@ +package types + +type Rules struct { + TeamID uint64 `db:"rel_team"` + Resource string `db:"resource"` + Operation string `db:"operation"` + Value string `db:"value"` +}