summaryrefslogtreecommitdiffstats
path: root/store
diff options
context:
space:
mode:
Diffstat (limited to 'store')
-rw-r--r--store/layered_store.go34
-rw-r--r--store/layered_store_supplier.go6
-rw-r--r--store/local_cache_supplier.go8
-rw-r--r--store/local_cache_supplier_roles.go70
-rw-r--r--store/redis_supplier.go47
-rw-r--r--store/redis_supplier_reactions.go57
-rw-r--r--store/redis_supplier_roles.go89
-rw-r--r--store/sqlstore/role_store_test.go14
-rw-r--r--store/sqlstore/role_supplier.go175
-rw-r--r--store/sqlstore/store.go1
-rw-r--r--store/sqlstore/supplier.go6
-rw-r--r--store/sqlstore/upgrade.go4
-rw-r--r--store/store.go8
-rw-r--r--store/storetest/mocks/LayeredStoreDatabaseLayer.go108
-rw-r--r--store/storetest/mocks/LayeredStoreSupplier.go92
-rw-r--r--store/storetest/mocks/RoleStore.go78
-rw-r--r--store/storetest/mocks/SqlStore.go16
-rw-r--r--store/storetest/mocks/Store.go16
-rw-r--r--store/storetest/role_store.go244
-rw-r--r--store/storetest/store.go3
20 files changed, 1028 insertions, 48 deletions
diff --git a/store/layered_store.go b/store/layered_store.go
index 65b4670c0..cac0f61d3 100644
--- a/store/layered_store.go
+++ b/store/layered_store.go
@@ -23,6 +23,7 @@ type LayeredStoreDatabaseLayer interface {
type LayeredStore struct {
TmpContext context.Context
ReactionStore ReactionStore
+ RoleStore RoleStore
DatabaseLayer LayeredStoreDatabaseLayer
LocalCacheLayer *LocalCacheSupplier
RedisLayer *RedisSupplier
@@ -37,6 +38,7 @@ func NewLayeredStore(db LayeredStoreDatabaseLayer, metrics einterfaces.MetricsIn
}
store.ReactionStore = &LayeredReactionStore{store}
+ store.RoleStore = &LayeredRoleStore{store}
// Setup the chain
if ENABLE_EXPERIMENTAL_REDIS {
@@ -161,6 +163,10 @@ func (s *LayeredStore) Plugin() PluginStore {
return s.DatabaseLayer.Plugin()
}
+func (s *LayeredStore) Role() RoleStore {
+ return s.RoleStore
+}
+
func (s *LayeredStore) MarkSystemRanUnitTests() {
s.DatabaseLayer.MarkSystemRanUnitTests()
}
@@ -218,3 +224,31 @@ func (s *LayeredReactionStore) PermanentDeleteBatch(endTime int64, limit int64)
return supplier.ReactionPermanentDeleteBatch(s.TmpContext, endTime, limit)
})
}
+
+type LayeredRoleStore struct {
+ *LayeredStore
+}
+
+func (s *LayeredRoleStore) Save(role *model.Role) StoreChannel {
+ return s.RunQuery(func(supplier LayeredStoreSupplier) *LayeredStoreSupplierResult {
+ return supplier.RoleSave(s.TmpContext, role)
+ })
+}
+
+func (s *LayeredRoleStore) Get(roleId string) StoreChannel {
+ return s.RunQuery(func(supplier LayeredStoreSupplier) *LayeredStoreSupplierResult {
+ return supplier.RoleGet(s.TmpContext, roleId)
+ })
+}
+
+func (s *LayeredRoleStore) GetByName(name string) StoreChannel {
+ return s.RunQuery(func(supplier LayeredStoreSupplier) *LayeredStoreSupplierResult {
+ return supplier.RoleGetByName(s.TmpContext, name)
+ })
+}
+
+func (s *LayeredRoleStore) GetByNames(names []string) StoreChannel {
+ return s.RunQuery(func(supplier LayeredStoreSupplier) *LayeredStoreSupplierResult {
+ return supplier.RoleGetByNames(s.TmpContext, names)
+ })
+}
diff --git a/store/layered_store_supplier.go b/store/layered_store_supplier.go
index d5e654019..80fe3cb24 100644
--- a/store/layered_store_supplier.go
+++ b/store/layered_store_supplier.go
@@ -29,4 +29,10 @@ type LayeredStoreSupplier interface {
ReactionGetForPost(ctx context.Context, postId string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult
ReactionDeleteAllWithEmojiName(ctx context.Context, emojiName string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult
ReactionPermanentDeleteBatch(ctx context.Context, endTime int64, limit int64, hints ...LayeredStoreHint) *LayeredStoreSupplierResult
+
+ // Roles
+ RoleSave(ctx context.Context, role *model.Role, hints ...LayeredStoreHint) *LayeredStoreSupplierResult
+ RoleGet(ctx context.Context, roleId string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult
+ RoleGetByName(ctx context.Context, name string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult
+ RoleGetByNames(ctx context.Context, names []string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult
}
diff --git a/store/local_cache_supplier.go b/store/local_cache_supplier.go
index 3627c5b39..2343f10a7 100644
--- a/store/local_cache_supplier.go
+++ b/store/local_cache_supplier.go
@@ -13,7 +13,10 @@ import (
const (
REACTION_CACHE_SIZE = 20000
- REACTION_CACHE_SEC = 1800 // 30 minutes
+ REACTION_CACHE_SEC = 30 * 60
+
+ ROLE_CACHE_SIZE = 20000
+ ROLE_CACHE_SEC = 30 * 60
CLEAR_CACHE_MESSAGE_DATA = ""
)
@@ -21,6 +24,7 @@ const (
type LocalCacheSupplier struct {
next LayeredStoreSupplier
reactionCache *utils.Cache
+ roleCache *utils.Cache
metrics einterfaces.MetricsInterface
cluster einterfaces.ClusterInterface
}
@@ -28,12 +32,14 @@ type LocalCacheSupplier struct {
func NewLocalCacheSupplier(metrics einterfaces.MetricsInterface, cluster einterfaces.ClusterInterface) *LocalCacheSupplier {
supplier := &LocalCacheSupplier{
reactionCache: utils.NewLruWithParams(REACTION_CACHE_SIZE, "Reaction", REACTION_CACHE_SEC, model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_REACTIONS),
+ roleCache: utils.NewLruWithParams(ROLE_CACHE_SIZE, "Role", ROLE_CACHE_SEC, model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_ROLES),
metrics: metrics,
cluster: cluster,
}
if cluster != nil {
cluster.RegisterClusterMessageHandler(model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_REACTIONS, supplier.handleClusterInvalidateReaction)
+ cluster.RegisterClusterMessageHandler(model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_ROLES, supplier.handleClusterInvalidateRole)
}
return supplier
diff --git a/store/local_cache_supplier_roles.go b/store/local_cache_supplier_roles.go
new file mode 100644
index 000000000..8cbde0a23
--- /dev/null
+++ b/store/local_cache_supplier_roles.go
@@ -0,0 +1,70 @@
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package store
+
+import (
+ "context"
+
+ "github.com/mattermost/mattermost-server/model"
+)
+
+func (s *LocalCacheSupplier) handleClusterInvalidateRole(msg *model.ClusterMessage) {
+ if msg.Data == CLEAR_CACHE_MESSAGE_DATA {
+ s.roleCache.Purge()
+ } else {
+ s.roleCache.Remove(msg.Data)
+ }
+}
+
+func (s *LocalCacheSupplier) RoleSave(ctx context.Context, role *model.Role, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
+ if len(role.Id) != 0 {
+ defer s.doInvalidateCacheCluster(s.roleCache, role.Name)
+ }
+ return s.Next().RoleSave(ctx, role, hints...)
+}
+
+func (s *LocalCacheSupplier) RoleGet(ctx context.Context, roleId string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
+ // Roles are cached by name, as that is most commonly how they are looked up.
+ // This means that no caching is supported on roles being looked up by ID.
+ return s.Next().RoleGet(ctx, roleId, hints...)
+}
+
+func (s *LocalCacheSupplier) RoleGetByName(ctx context.Context, name string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
+ if result := s.doStandardReadCache(ctx, s.roleCache, name, hints...); result != nil {
+ return result
+ }
+
+ result := s.Next().RoleGetByName(ctx, name, hints...)
+
+ s.doStandardAddToCache(ctx, s.roleCache, name, result, hints...)
+
+ return result
+}
+
+func (s *LocalCacheSupplier) RoleGetByNames(ctx context.Context, roleNames []string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
+ var foundRoles []*model.Role
+ var rolesToQuery []string
+
+ for _, roleName := range roleNames {
+ if result := s.doStandardReadCache(ctx, s.roleCache, roleName, hints...); result != nil {
+ foundRoles = append(foundRoles, result.Data.(*model.Role))
+ } else {
+ rolesToQuery = append(rolesToQuery, roleName)
+ }
+ }
+
+ result := s.Next().RoleGetByNames(ctx, rolesToQuery, hints...)
+
+ if result.Data != nil {
+ rolesFound := result.Data.([]*model.Role)
+ for _, role := range rolesFound {
+ res := NewSupplierResult()
+ res.Data = role
+ s.doStandardAddToCache(ctx, s.roleCache, role.Name, res, hints...)
+ }
+ result.Data = append(foundRoles, result.Data.([]*model.Role)...)
+ }
+
+ return result
+}
diff --git a/store/redis_supplier.go b/store/redis_supplier.go
index 32dc12033..751227be9 100644
--- a/store/redis_supplier.go
+++ b/store/redis_supplier.go
@@ -5,14 +5,12 @@ package store
import (
"bytes"
- "context"
"encoding/gob"
"time"
l4g "github.com/alecthomas/log4go"
"github.com/go-redis/redis"
- "github.com/mattermost/mattermost-server/model"
)
const REDIS_EXPIRY_TIME = 30 * time.Minute
@@ -87,48 +85,3 @@ func (s *RedisSupplier) SetChainNext(next LayeredStoreSupplier) {
func (s *RedisSupplier) Next() LayeredStoreSupplier {
return s.next
}
-
-func (s *RedisSupplier) ReactionSave(ctx context.Context, reaction *model.Reaction, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
- if err := s.client.Del("reactions:" + reaction.PostId).Err(); err != nil {
- l4g.Error("Redis failed to remove key reactions:" + reaction.PostId + " Error: " + err.Error())
- }
- return s.Next().ReactionSave(ctx, reaction, hints...)
-}
-
-func (s *RedisSupplier) ReactionDelete(ctx context.Context, reaction *model.Reaction, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
- if err := s.client.Del("reactions:" + reaction.PostId).Err(); err != nil {
- l4g.Error("Redis failed to remove key reactions:" + reaction.PostId + " Error: " + err.Error())
- }
- return s.Next().ReactionDelete(ctx, reaction, hints...)
-}
-
-func (s *RedisSupplier) ReactionGetForPost(ctx context.Context, postId string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
- var resultdata []*model.Reaction
- found, err := s.load("reactions:"+postId, &resultdata)
- if found {
- result := NewSupplierResult()
- result.Data = resultdata
- return result
- }
- if err != nil {
- l4g.Error("Redis encountered an error on read: " + err.Error())
- }
-
- result := s.Next().ReactionGetForPost(ctx, postId, hints...)
-
- if err := s.save("reactions:"+postId, result.Data, REDIS_EXPIRY_TIME); err != nil {
- l4g.Error("Redis encountered and error on write: " + err.Error())
- }
-
- return result
-}
-
-func (s *RedisSupplier) ReactionDeleteAllWithEmojiName(ctx context.Context, emojiName string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
- // Ignoring this. It's probably OK to have the emoji slowly expire from Redis.
- return s.Next().ReactionDeleteAllWithEmojiName(ctx, emojiName, hints...)
-}
-
-func (s *RedisSupplier) ReactionPermanentDeleteBatch(ctx context.Context, endTime int64, limit int64, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
- // Ignoring this. It's probably OK to have the emoji slowly expire from Redis.
- return s.Next().ReactionPermanentDeleteBatch(ctx, endTime, limit, hints...)
-}
diff --git a/store/redis_supplier_reactions.go b/store/redis_supplier_reactions.go
new file mode 100644
index 000000000..cece8113d
--- /dev/null
+++ b/store/redis_supplier_reactions.go
@@ -0,0 +1,57 @@
+// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package store
+
+import (
+ "context"
+
+ l4g "github.com/alecthomas/log4go"
+
+ "github.com/mattermost/mattermost-server/model"
+)
+
+func (s *RedisSupplier) ReactionSave(ctx context.Context, reaction *model.Reaction, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
+ if err := s.client.Del("reactions:" + reaction.PostId).Err(); err != nil {
+ l4g.Error("Redis failed to remove key reactions:" + reaction.PostId + " Error: " + err.Error())
+ }
+ return s.Next().ReactionSave(ctx, reaction, hints...)
+}
+
+func (s *RedisSupplier) ReactionDelete(ctx context.Context, reaction *model.Reaction, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
+ if err := s.client.Del("reactions:" + reaction.PostId).Err(); err != nil {
+ l4g.Error("Redis failed to remove key reactions:" + reaction.PostId + " Error: " + err.Error())
+ }
+ return s.Next().ReactionDelete(ctx, reaction, hints...)
+}
+
+func (s *RedisSupplier) ReactionGetForPost(ctx context.Context, postId string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
+ var resultdata []*model.Reaction
+ found, err := s.load("reactions:"+postId, &resultdata)
+ if found {
+ result := NewSupplierResult()
+ result.Data = resultdata
+ return result
+ }
+ if err != nil {
+ l4g.Error("Redis encountered an error on read: " + err.Error())
+ }
+
+ result := s.Next().ReactionGetForPost(ctx, postId, hints...)
+
+ if err := s.save("reactions:"+postId, result.Data, REDIS_EXPIRY_TIME); err != nil {
+ l4g.Error("Redis encountered and error on write: " + err.Error())
+ }
+
+ return result
+}
+
+func (s *RedisSupplier) ReactionDeleteAllWithEmojiName(ctx context.Context, emojiName string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
+ // Ignoring this. It's probably OK to have the emoji slowly expire from Redis.
+ return s.Next().ReactionDeleteAllWithEmojiName(ctx, emojiName, hints...)
+}
+
+func (s *RedisSupplier) ReactionPermanentDeleteBatch(ctx context.Context, endTime int64, limit int64, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
+ // Ignoring this. It's probably OK to have the emoji slowly expire from Redis.
+ return s.Next().ReactionPermanentDeleteBatch(ctx, endTime, limit, hints...)
+}
diff --git a/store/redis_supplier_roles.go b/store/redis_supplier_roles.go
new file mode 100644
index 000000000..170420f1f
--- /dev/null
+++ b/store/redis_supplier_roles.go
@@ -0,0 +1,89 @@
+// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package store
+
+import (
+ "context"
+ "fmt"
+
+ l4g "github.com/alecthomas/log4go"
+
+ "github.com/mattermost/mattermost-server/model"
+)
+
+func (s *RedisSupplier) RoleSave(ctx context.Context, role *model.Role, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
+ key := buildRedisKeyForRoleName(role.Name)
+
+ if err := s.client.Del(key).Err(); err != nil {
+ l4g.Error("Redis failed to remove key " + key + " Error: " + err.Error())
+ }
+
+ return s.Next().RoleSave(ctx, role, hints...)
+}
+
+func (s *RedisSupplier) RoleGet(ctx context.Context, roleId string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
+ // Roles are cached by name, as that is most commonly how they are looked up.
+ // This means that no caching is supported on roles being looked up by ID.
+ return s.Next().RoleGet(ctx, roleId, hints...)
+}
+
+func (s *RedisSupplier) RoleGetByName(ctx context.Context, name string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
+ key := buildRedisKeyForRoleName(name)
+
+ var role *model.Role
+ found, err := s.load(key, &role)
+ if err != nil {
+ l4g.Error("Redis encountered an error on read: " + err.Error())
+ } else if found {
+ result := NewSupplierResult()
+ result.Data = role
+ return result
+ }
+
+ result := s.Next().RoleGetByName(ctx, name, hints...)
+
+ if result.Err == nil {
+ if err := s.save(key, result.Data, REDIS_EXPIRY_TIME); err != nil {
+ l4g.Error("Redis encountered and error on write: " + err.Error())
+ }
+ }
+
+ return result
+}
+
+func (s *RedisSupplier) RoleGetByNames(ctx context.Context, roleNames []string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
+ var foundRoles []*model.Role
+ var rolesToQuery []string
+
+ for _, roleName := range roleNames {
+ var role *model.Role
+ found, err := s.load(buildRedisKeyForRoleName(roleName), &role)
+ if err == nil && found {
+ foundRoles = append(foundRoles, role)
+ } else {
+ rolesToQuery = append(rolesToQuery, roleName)
+ if err != nil {
+ l4g.Error("Redis encountered an error on read: " + err.Error())
+ }
+ }
+ }
+
+ result := s.Next().RoleGetByNames(ctx, rolesToQuery, hints...)
+
+ if result.Err == nil {
+ rolesFound := result.Data.([]*model.Role)
+ for _, role := range rolesFound {
+ if err := s.save(buildRedisKeyForRoleName(role.Name), role, REDIS_EXPIRY_TIME); err != nil {
+ l4g.Error("Redis encountered and error on write: " + err.Error())
+ }
+ }
+ result.Data = append(foundRoles, result.Data.([]*model.Role)...)
+ }
+
+ return result
+}
+
+func buildRedisKeyForRoleName(roleName string) string {
+ return fmt.Sprintf("roles:%s", roleName)
+}
diff --git a/store/sqlstore/role_store_test.go b/store/sqlstore/role_store_test.go
new file mode 100644
index 000000000..e89930f71
--- /dev/null
+++ b/store/sqlstore/role_store_test.go
@@ -0,0 +1,14 @@
+// Copyright (c) 2018-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package sqlstore
+
+import (
+ "testing"
+
+ "github.com/mattermost/mattermost-server/store/storetest"
+)
+
+func TestRoleStore(t *testing.T) {
+ StoreTest(t, storetest.TestRoleStore)
+}
diff --git a/store/sqlstore/role_supplier.go b/store/sqlstore/role_supplier.go
new file mode 100644
index 000000000..f9ce53788
--- /dev/null
+++ b/store/sqlstore/role_supplier.go
@@ -0,0 +1,175 @@
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package sqlstore
+
+import (
+ "context"
+ "database/sql"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/mattermost/mattermost-server/model"
+ "github.com/mattermost/mattermost-server/store"
+)
+
+type Role struct {
+ Id string
+ Name string
+ DisplayName string
+ Description string
+ CreateAt int64
+ UpdateAt int64
+ DeleteAt int64
+ Permissions string
+ SchemeManaged bool
+}
+
+func NewRoleFromModel(role *model.Role) *Role {
+ permissionsMap := make(map[string]bool)
+ permissions := ""
+
+ for _, permission := range role.Permissions {
+ if !permissionsMap[permission] {
+ permissions += fmt.Sprintf(" %v", permission)
+ permissionsMap[permission] = true
+ }
+ }
+
+ return &Role{
+ Id: role.Id,
+ Name: role.Name,
+ DisplayName: role.DisplayName,
+ Description: role.Description,
+ CreateAt: role.CreateAt,
+ UpdateAt: role.UpdateAt,
+ DeleteAt: role.DeleteAt,
+ Permissions: permissions,
+ SchemeManaged: role.SchemeManaged,
+ }
+}
+
+func (role Role) ToModel() *model.Role {
+ return &model.Role{
+ Id: role.Id,
+ Name: role.Name,
+ DisplayName: role.DisplayName,
+ Description: role.Description,
+ CreateAt: role.CreateAt,
+ UpdateAt: role.UpdateAt,
+ DeleteAt: role.DeleteAt,
+ Permissions: strings.Fields(role.Permissions),
+ SchemeManaged: role.SchemeManaged,
+ }
+}
+
+func initSqlSupplierRoles(sqlStore SqlStore) {
+ for _, db := range sqlStore.GetAllConns() {
+ table := db.AddTableWithName(Role{}, "Roles").SetKeys(false, "Id")
+ table.ColMap("Name").SetMaxSize(64).SetUnique(true)
+ table.ColMap("DisplayName").SetMaxSize(128)
+ table.ColMap("Description").SetMaxSize(1024)
+ table.ColMap("Permissions").SetMaxSize(4096)
+ }
+}
+
+func (s *SqlSupplier) RoleSave(ctx context.Context, role *model.Role, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
+ result := store.NewSupplierResult()
+
+ // Check the role is valid before proceeding.
+ if !role.IsValidWithoutId() {
+ result.Err = model.NewAppError("SqlRoleStore.Save", "store.sql_role.save.invalid_role.app_error", nil, "", http.StatusBadRequest)
+ return result
+ }
+
+ dbRole := NewRoleFromModel(role)
+ if len(dbRole.Id) == 0 {
+ dbRole.Id = model.NewId()
+ dbRole.CreateAt = model.GetMillis()
+ dbRole.UpdateAt = dbRole.CreateAt
+ if err := s.GetMaster().Insert(dbRole); err != nil {
+ result.Err = model.NewAppError("SqlRoleStore.Save", "store.sql_role.save.insert.app_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ } else {
+ dbRole.UpdateAt = model.GetMillis()
+ if rowsChanged, err := s.GetMaster().Update(dbRole); err != nil {
+ result.Err = model.NewAppError("SqlRoleStore.Save", "store.sql_role.save.update.app_error", nil, err.Error(), http.StatusInternalServerError)
+ } else if rowsChanged != 1 {
+ result.Err = model.NewAppError("SqlRoleStore.Save", "store.sql_role.save.update.app_error", nil, "no record to update", http.StatusInternalServerError)
+ }
+ }
+
+ result.Data = dbRole.ToModel()
+
+ return result
+}
+
+func (s *SqlSupplier) RoleGet(ctx context.Context, roleId string, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
+ result := store.NewSupplierResult()
+
+ var dbRole Role
+
+ if err := s.GetReplica().SelectOne(&dbRole, "SELECT * from Roles WHERE Id = :Id", map[string]interface{}{"Id": roleId}); err != nil {
+ if err == sql.ErrNoRows {
+ result.Err = model.NewAppError("SqlRoleStore.Get", "store.sql_role.get.app_error", nil, "Id="+roleId+", "+err.Error(), http.StatusNotFound)
+ } else {
+ result.Err = model.NewAppError("SqlRoleStore.Get", "store.sql_role.get.app_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ }
+
+ result.Data = dbRole.ToModel()
+
+ return result
+}
+
+func (s *SqlSupplier) RoleGetByName(ctx context.Context, name string, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
+ result := store.NewSupplierResult()
+
+ var dbRole Role
+
+ if err := s.GetReplica().SelectOne(&dbRole, "SELECT * from Roles WHERE Name = :Name", map[string]interface{}{"Name": name}); err != nil {
+ if err == sql.ErrNoRows {
+ result.Err = model.NewAppError("SqlRoleStore.GetByName", "store.sql_role.get_by_name.app_error", nil, "name="+name+",err="+err.Error(), http.StatusNotFound)
+ } else {
+ result.Err = model.NewAppError("SqlRoleStore.GetByName", "store.sql_role.get_by_name.app_error", nil, "name="+name+",err="+err.Error(), http.StatusInternalServerError)
+ }
+ }
+
+ result.Data = dbRole.ToModel()
+
+ return result
+}
+
+func (s *SqlSupplier) RoleGetByNames(ctx context.Context, names []string, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
+ result := store.NewSupplierResult()
+
+ var dbRoles []*Role
+
+ if len(names) == 0 {
+ result.Data = []*model.Role{}
+ return result
+ }
+
+ var searchPlaceholders []string
+ var parameters = map[string]interface{}{}
+ for i, value := range names {
+ searchPlaceholders = append(searchPlaceholders, fmt.Sprintf(":Name%d", i))
+ parameters[fmt.Sprintf("Name%d", i)] = value
+ }
+
+ searchTerm := "Name IN (" + strings.Join(searchPlaceholders, ", ") + ")"
+
+ if _, err := s.GetReplica().Select(&dbRoles, "SELECT * from Roles WHERE "+searchTerm, parameters); err != nil {
+ result.Err = model.NewAppError("SqlRoleStore.GetByNames", "store.sql_role.get_by_names.app_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+
+ var roles []*model.Role
+ for _, dbRole := range dbRoles {
+ roles = append(roles, dbRole.ToModel())
+ }
+
+ result.Data = roles
+
+ return result
+}
diff --git a/store/sqlstore/store.go b/store/sqlstore/store.go
index cfdd7a552..1c623f0b1 100644
--- a/store/sqlstore/store.go
+++ b/store/sqlstore/store.go
@@ -87,4 +87,5 @@ type SqlStore interface {
Job() store.JobStore
Plugin() store.PluginStore
UserAccessToken() store.UserAccessTokenStore
+ Role() store.RoleStore
}
diff --git a/store/sqlstore/supplier.go b/store/sqlstore/supplier.go
index 3b9528578..5e43ee0f0 100644
--- a/store/sqlstore/supplier.go
+++ b/store/sqlstore/supplier.go
@@ -86,6 +86,7 @@ type SqlSupplierOldStores struct {
userAccessToken store.UserAccessTokenStore
plugin store.PluginStore
channelMemberHistory store.ChannelMemberHistoryStore
+ role store.RoleStore
}
type SqlSupplier struct {
@@ -135,6 +136,7 @@ func NewSqlSupplier(settings model.SqlSettings, metrics einterfaces.MetricsInter
supplier.oldStores.plugin = NewSqlPluginStore(supplier)
initSqlSupplierReactions(supplier)
+ initSqlSupplierRoles(supplier)
err := supplier.GetMaster().CreateTablesIfNotExists()
if err != nil {
@@ -811,6 +813,10 @@ func (ss *SqlSupplier) Plugin() store.PluginStore {
return ss.oldStores.plugin
}
+func (ss *SqlSupplier) Role() store.RoleStore {
+ return ss.oldStores.role
+}
+
func (ss *SqlSupplier) DropAllTables() {
ss.master.TruncateTables()
}
diff --git a/store/sqlstore/upgrade.go b/store/sqlstore/upgrade.go
index 58de85c7f..2b4532817 100644
--- a/store/sqlstore/upgrade.go
+++ b/store/sqlstore/upgrade.go
@@ -373,6 +373,10 @@ func UpgradeDatabaseToVersion48(sqlStore SqlStore) {
}
func UpgradeDatabaseToVersion49(sqlStore SqlStore) {
+ // This version of Mattermost includes an App-Layer migration which migrates from hard-coded roles configured by
+ // a number of parameters in `config.json` to a `Roles` table in the database. The migration code can be seen
+ // in the file `app/app.go` in the function `DoAdvancedPermissionsMigration()`.
+
//TODO: Uncomment the following condition when version 4.9.0 is released
//if shouldPerformUpgrade(sqlStore, VERSION_4_8_0, VERSION_4_9_0) {
sqlStore.CreateColumnIfNotExists("Teams", "LastTeamIconUpdate", "bigint", "bigint", "0")
diff --git a/store/store.go b/store/store.go
index f070a45db..a4b66d2d1 100644
--- a/store/store.go
+++ b/store/store.go
@@ -61,6 +61,7 @@ type Store interface {
Status() StatusStore
FileInfo() FileInfoStore
Reaction() ReactionStore
+ Role() RoleStore
Job() JobStore
UserAccessToken() UserAccessTokenStore
ChannelMemberHistory() ChannelMemberHistoryStore
@@ -468,3 +469,10 @@ type PluginStore interface {
Get(pluginId, key string) StoreChannel
Delete(pluginId, key string) StoreChannel
}
+
+type RoleStore interface {
+ Save(role *model.Role) StoreChannel
+ Get(roleId string) StoreChannel
+ GetByName(name string) StoreChannel
+ GetByNames(names []string) StoreChannel
+}
diff --git a/store/storetest/mocks/LayeredStoreDatabaseLayer.go b/store/storetest/mocks/LayeredStoreDatabaseLayer.go
index 9c66c4aac..d0162a01e 100644
--- a/store/storetest/mocks/LayeredStoreDatabaseLayer.go
+++ b/store/storetest/mocks/LayeredStoreDatabaseLayer.go
@@ -416,6 +416,114 @@ func (_m *LayeredStoreDatabaseLayer) ReactionSave(ctx context.Context, reaction
return r0
}
+// Role provides a mock function with given fields:
+func (_m *LayeredStoreDatabaseLayer) Role() store.RoleStore {
+ ret := _m.Called()
+
+ var r0 store.RoleStore
+ if rf, ok := ret.Get(0).(func() store.RoleStore); ok {
+ r0 = rf()
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(store.RoleStore)
+ }
+ }
+
+ return r0
+}
+
+// RoleGet provides a mock function with given fields: ctx, roleId, hints
+func (_m *LayeredStoreDatabaseLayer) RoleGet(ctx context.Context, roleId string, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
+ _va := make([]interface{}, len(hints))
+ for _i := range hints {
+ _va[_i] = hints[_i]
+ }
+ var _ca []interface{}
+ _ca = append(_ca, ctx, roleId)
+ _ca = append(_ca, _va...)
+ ret := _m.Called(_ca...)
+
+ var r0 *store.LayeredStoreSupplierResult
+ if rf, ok := ret.Get(0).(func(context.Context, string, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok {
+ r0 = rf(ctx, roleId, hints...)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*store.LayeredStoreSupplierResult)
+ }
+ }
+
+ return r0
+}
+
+// RoleGetByName provides a mock function with given fields: ctx, name, hints
+func (_m *LayeredStoreDatabaseLayer) RoleGetByName(ctx context.Context, name string, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
+ _va := make([]interface{}, len(hints))
+ for _i := range hints {
+ _va[_i] = hints[_i]
+ }
+ var _ca []interface{}
+ _ca = append(_ca, ctx, name)
+ _ca = append(_ca, _va...)
+ ret := _m.Called(_ca...)
+
+ var r0 *store.LayeredStoreSupplierResult
+ if rf, ok := ret.Get(0).(func(context.Context, string, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok {
+ r0 = rf(ctx, name, hints...)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*store.LayeredStoreSupplierResult)
+ }
+ }
+
+ return r0
+}
+
+// RoleGetByNames provides a mock function with given fields: ctx, names, hints
+func (_m *LayeredStoreDatabaseLayer) RoleGetByNames(ctx context.Context, names []string, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
+ _va := make([]interface{}, len(hints))
+ for _i := range hints {
+ _va[_i] = hints[_i]
+ }
+ var _ca []interface{}
+ _ca = append(_ca, ctx, names)
+ _ca = append(_ca, _va...)
+ ret := _m.Called(_ca...)
+
+ var r0 *store.LayeredStoreSupplierResult
+ if rf, ok := ret.Get(0).(func(context.Context, []string, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok {
+ r0 = rf(ctx, names, hints...)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*store.LayeredStoreSupplierResult)
+ }
+ }
+
+ return r0
+}
+
+// RoleSave provides a mock function with given fields: ctx, role, hints
+func (_m *LayeredStoreDatabaseLayer) RoleSave(ctx context.Context, role *model.Role, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
+ _va := make([]interface{}, len(hints))
+ for _i := range hints {
+ _va[_i] = hints[_i]
+ }
+ var _ca []interface{}
+ _ca = append(_ca, ctx, role)
+ _ca = append(_ca, _va...)
+ ret := _m.Called(_ca...)
+
+ var r0 *store.LayeredStoreSupplierResult
+ if rf, ok := ret.Get(0).(func(context.Context, *model.Role, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok {
+ r0 = rf(ctx, role, hints...)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*store.LayeredStoreSupplierResult)
+ }
+ }
+
+ return r0
+}
+
// Session provides a mock function with given fields:
func (_m *LayeredStoreDatabaseLayer) Session() store.SessionStore {
ret := _m.Called()
diff --git a/store/storetest/mocks/LayeredStoreSupplier.go b/store/storetest/mocks/LayeredStoreSupplier.go
index f4187dae9..59fd31cb8 100644
--- a/store/storetest/mocks/LayeredStoreSupplier.go
+++ b/store/storetest/mocks/LayeredStoreSupplier.go
@@ -145,6 +145,98 @@ func (_m *LayeredStoreSupplier) ReactionSave(ctx context.Context, reaction *mode
return r0
}
+// RoleGet provides a mock function with given fields: ctx, roleId, hints
+func (_m *LayeredStoreSupplier) RoleGet(ctx context.Context, roleId string, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
+ _va := make([]interface{}, len(hints))
+ for _i := range hints {
+ _va[_i] = hints[_i]
+ }
+ var _ca []interface{}
+ _ca = append(_ca, ctx, roleId)
+ _ca = append(_ca, _va...)
+ ret := _m.Called(_ca...)
+
+ var r0 *store.LayeredStoreSupplierResult
+ if rf, ok := ret.Get(0).(func(context.Context, string, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok {
+ r0 = rf(ctx, roleId, hints...)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*store.LayeredStoreSupplierResult)
+ }
+ }
+
+ return r0
+}
+
+// RoleGetByName provides a mock function with given fields: ctx, name, hints
+func (_m *LayeredStoreSupplier) RoleGetByName(ctx context.Context, name string, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
+ _va := make([]interface{}, len(hints))
+ for _i := range hints {
+ _va[_i] = hints[_i]
+ }
+ var _ca []interface{}
+ _ca = append(_ca, ctx, name)
+ _ca = append(_ca, _va...)
+ ret := _m.Called(_ca...)
+
+ var r0 *store.LayeredStoreSupplierResult
+ if rf, ok := ret.Get(0).(func(context.Context, string, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok {
+ r0 = rf(ctx, name, hints...)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*store.LayeredStoreSupplierResult)
+ }
+ }
+
+ return r0
+}
+
+// RoleGetByNames provides a mock function with given fields: ctx, names, hints
+func (_m *LayeredStoreSupplier) RoleGetByNames(ctx context.Context, names []string, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
+ _va := make([]interface{}, len(hints))
+ for _i := range hints {
+ _va[_i] = hints[_i]
+ }
+ var _ca []interface{}
+ _ca = append(_ca, ctx, names)
+ _ca = append(_ca, _va...)
+ ret := _m.Called(_ca...)
+
+ var r0 *store.LayeredStoreSupplierResult
+ if rf, ok := ret.Get(0).(func(context.Context, []string, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok {
+ r0 = rf(ctx, names, hints...)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*store.LayeredStoreSupplierResult)
+ }
+ }
+
+ return r0
+}
+
+// RoleSave provides a mock function with given fields: ctx, role, hints
+func (_m *LayeredStoreSupplier) RoleSave(ctx context.Context, role *model.Role, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
+ _va := make([]interface{}, len(hints))
+ for _i := range hints {
+ _va[_i] = hints[_i]
+ }
+ var _ca []interface{}
+ _ca = append(_ca, ctx, role)
+ _ca = append(_ca, _va...)
+ ret := _m.Called(_ca...)
+
+ var r0 *store.LayeredStoreSupplierResult
+ if rf, ok := ret.Get(0).(func(context.Context, *model.Role, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok {
+ r0 = rf(ctx, role, hints...)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*store.LayeredStoreSupplierResult)
+ }
+ }
+
+ return r0
+}
+
// SetChainNext provides a mock function with given fields: _a0
func (_m *LayeredStoreSupplier) SetChainNext(_a0 store.LayeredStoreSupplier) {
_m.Called(_a0)
diff --git a/store/storetest/mocks/RoleStore.go b/store/storetest/mocks/RoleStore.go
new file mode 100644
index 000000000..8150460ae
--- /dev/null
+++ b/store/storetest/mocks/RoleStore.go
@@ -0,0 +1,78 @@
+// Code generated by mockery v1.0.0
+
+// Regenerate this file using `make store-mocks`.
+
+package mocks
+
+import mock "github.com/stretchr/testify/mock"
+import model "github.com/mattermost/mattermost-server/model"
+import store "github.com/mattermost/mattermost-server/store"
+
+// RoleStore is an autogenerated mock type for the RoleStore type
+type RoleStore struct {
+ mock.Mock
+}
+
+// Get provides a mock function with given fields: roleId
+func (_m *RoleStore) Get(roleId string) store.StoreChannel {
+ ret := _m.Called(roleId)
+
+ var r0 store.StoreChannel
+ if rf, ok := ret.Get(0).(func(string) store.StoreChannel); ok {
+ r0 = rf(roleId)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(store.StoreChannel)
+ }
+ }
+
+ return r0
+}
+
+// GetByName provides a mock function with given fields: name
+func (_m *RoleStore) GetByName(name string) store.StoreChannel {
+ ret := _m.Called(name)
+
+ var r0 store.StoreChannel
+ if rf, ok := ret.Get(0).(func(string) store.StoreChannel); ok {
+ r0 = rf(name)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(store.StoreChannel)
+ }
+ }
+
+ return r0
+}
+
+// GetByNames provides a mock function with given fields: names
+func (_m *RoleStore) GetByNames(names []string) store.StoreChannel {
+ ret := _m.Called(names)
+
+ var r0 store.StoreChannel
+ if rf, ok := ret.Get(0).(func([]string) store.StoreChannel); ok {
+ r0 = rf(names)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(store.StoreChannel)
+ }
+ }
+
+ return r0
+}
+
+// Save provides a mock function with given fields: role
+func (_m *RoleStore) Save(role *model.Role) store.StoreChannel {
+ ret := _m.Called(role)
+
+ var r0 store.StoreChannel
+ if rf, ok := ret.Get(0).(func(*model.Role) store.StoreChannel); ok {
+ r0 = rf(role)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(store.StoreChannel)
+ }
+ }
+
+ return r0
+}
diff --git a/store/storetest/mocks/SqlStore.go b/store/storetest/mocks/SqlStore.go
index b9b962101..43709fc0e 100644
--- a/store/storetest/mocks/SqlStore.go
+++ b/store/storetest/mocks/SqlStore.go
@@ -538,6 +538,22 @@ func (_m *SqlStore) RenameColumnIfExists(tableName string, oldColumnName string,
return r0
}
+// Role provides a mock function with given fields:
+func (_m *SqlStore) Role() store.RoleStore {
+ ret := _m.Called()
+
+ var r0 store.RoleStore
+ if rf, ok := ret.Get(0).(func() store.RoleStore); ok {
+ r0 = rf()
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(store.RoleStore)
+ }
+ }
+
+ return r0
+}
+
// Session provides a mock function with given fields:
func (_m *SqlStore) Session() store.SessionStore {
ret := _m.Called()
diff --git a/store/storetest/mocks/Store.go b/store/storetest/mocks/Store.go
index 40b50a554..cb7e511f6 100644
--- a/store/storetest/mocks/Store.go
+++ b/store/storetest/mocks/Store.go
@@ -283,6 +283,22 @@ func (_m *Store) Reaction() store.ReactionStore {
return r0
}
+// Role provides a mock function with given fields:
+func (_m *Store) Role() store.RoleStore {
+ ret := _m.Called()
+
+ var r0 store.RoleStore
+ if rf, ok := ret.Get(0).(func() store.RoleStore); ok {
+ r0 = rf()
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(store.RoleStore)
+ }
+ }
+
+ return r0
+}
+
// Session provides a mock function with given fields:
func (_m *Store) Session() store.SessionStore {
ret := _m.Called()
diff --git a/store/storetest/role_store.go b/store/storetest/role_store.go
new file mode 100644
index 000000000..499e36e1e
--- /dev/null
+++ b/store/storetest/role_store.go
@@ -0,0 +1,244 @@
+// Copyright (c) 2018-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package storetest
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+
+ "github.com/mattermost/mattermost-server/model"
+ "github.com/mattermost/mattermost-server/store"
+)
+
+func TestRoleStore(t *testing.T, ss store.Store) {
+ t.Run("Save", func(t *testing.T) { testRoleStoreSave(t, ss) })
+ t.Run("Get", func(t *testing.T) { testRoleStoreGet(t, ss) })
+ t.Run("GetByName", func(t *testing.T) { testRoleStoreGetByName(t, ss) })
+ t.Run("GetNames", func(t *testing.T) { testRoleStoreGetByNames(t, ss) })
+}
+
+func testRoleStoreSave(t *testing.T, ss store.Store) {
+ // Save a new role.
+ r1 := &model.Role{
+ Name: model.NewId(),
+ DisplayName: model.NewId(),
+ Description: model.NewId(),
+ Permissions: []string{
+ "invite_user",
+ "create_public_channel",
+ "add_user_to_team",
+ },
+ SchemeManaged: false,
+ }
+
+ res1 := <-ss.Role().Save(r1)
+ assert.Nil(t, res1.Err)
+ d1 := res1.Data.(*model.Role)
+ assert.Len(t, d1.Id, 26)
+ assert.Equal(t, r1.Name, d1.Name)
+ assert.Equal(t, r1.DisplayName, d1.DisplayName)
+ assert.Equal(t, r1.Description, d1.Description)
+ assert.Equal(t, r1.Permissions, d1.Permissions)
+ assert.Equal(t, r1.SchemeManaged, d1.SchemeManaged)
+
+ // Change the role permissions and update.
+ d1.Permissions = []string{
+ "invite_user",
+ "add_user_to_team",
+ "delete_public_channel",
+ }
+
+ res2 := <-ss.Role().Save(d1)
+ assert.Nil(t, res2.Err)
+ d2 := res2.Data.(*model.Role)
+ assert.Len(t, d2.Id, 26)
+ assert.Equal(t, r1.Name, d2.Name)
+ assert.Equal(t, r1.DisplayName, d2.DisplayName)
+ assert.Equal(t, r1.Description, d2.Description)
+ assert.Equal(t, d1.Permissions, d2.Permissions)
+ assert.Equal(t, r1.SchemeManaged, d2.SchemeManaged)
+
+ // Try saving one with an invalid ID set.
+ r3 := &model.Role{
+ Id: model.NewId(),
+ Name: model.NewId(),
+ DisplayName: model.NewId(),
+ Description: model.NewId(),
+ Permissions: []string{
+ "invite_user",
+ "create_public_channel",
+ "add_user_to_team",
+ },
+ SchemeManaged: false,
+ }
+
+ res3 := <-ss.Role().Save(r3)
+ assert.NotNil(t, res3.Err)
+
+ // Try saving one with a duplicate "name" field.
+ r4 := &model.Role{
+ Name: r1.Name,
+ DisplayName: model.NewId(),
+ Description: model.NewId(),
+ Permissions: []string{
+ "invite_user",
+ "create_public_channel",
+ "add_user_to_team",
+ },
+ SchemeManaged: false,
+ }
+
+ res4 := <-ss.Role().Save(r4)
+ assert.NotNil(t, res4.Err)
+}
+
+func testRoleStoreGet(t *testing.T, ss store.Store) {
+ // Save a role to test with.
+ r1 := &model.Role{
+ Name: model.NewId(),
+ DisplayName: model.NewId(),
+ Description: model.NewId(),
+ Permissions: []string{
+ "invite_user",
+ "create_public_channel",
+ "add_user_to_team",
+ },
+ SchemeManaged: false,
+ }
+
+ res1 := <-ss.Role().Save(r1)
+ assert.Nil(t, res1.Err)
+ d1 := res1.Data.(*model.Role)
+ assert.Len(t, d1.Id, 26)
+
+ // Get a valid role
+ res2 := <-ss.Role().Get(d1.Id)
+ assert.Nil(t, res2.Err)
+ d2 := res1.Data.(*model.Role)
+ assert.Equal(t, d1.Id, d2.Id)
+ assert.Equal(t, r1.Name, d2.Name)
+ assert.Equal(t, r1.DisplayName, d2.DisplayName)
+ assert.Equal(t, r1.Description, d2.Description)
+ assert.Equal(t, r1.Permissions, d2.Permissions)
+ assert.Equal(t, r1.SchemeManaged, d2.SchemeManaged)
+
+ // Get an invalid role
+ res3 := <-ss.Role().Get(model.NewId())
+ assert.NotNil(t, res3.Err)
+}
+
+func testRoleStoreGetByName(t *testing.T, ss store.Store) {
+ // Save a role to test with.
+ r1 := &model.Role{
+ Name: model.NewId(),
+ DisplayName: model.NewId(),
+ Description: model.NewId(),
+ Permissions: []string{
+ "invite_user",
+ "create_public_channel",
+ "add_user_to_team",
+ },
+ SchemeManaged: false,
+ }
+
+ res1 := <-ss.Role().Save(r1)
+ assert.Nil(t, res1.Err)
+ d1 := res1.Data.(*model.Role)
+ assert.Len(t, d1.Id, 26)
+
+ // Get a valid role
+ res2 := <-ss.Role().GetByName(d1.Name)
+ assert.Nil(t, res2.Err)
+ d2 := res1.Data.(*model.Role)
+ assert.Equal(t, d1.Id, d2.Id)
+ assert.Equal(t, r1.Name, d2.Name)
+ assert.Equal(t, r1.DisplayName, d2.DisplayName)
+ assert.Equal(t, r1.Description, d2.Description)
+ assert.Equal(t, r1.Permissions, d2.Permissions)
+ assert.Equal(t, r1.SchemeManaged, d2.SchemeManaged)
+
+ // Get an invalid role
+ res3 := <-ss.Role().GetByName(model.NewId())
+ assert.NotNil(t, res3.Err)
+}
+
+func testRoleStoreGetByNames(t *testing.T, ss store.Store) {
+ // Save some roles to test with.
+ r1 := &model.Role{
+ Name: model.NewId(),
+ DisplayName: model.NewId(),
+ Description: model.NewId(),
+ Permissions: []string{
+ "invite_user",
+ "create_public_channel",
+ "add_user_to_team",
+ },
+ SchemeManaged: false,
+ }
+ r2 := &model.Role{
+ Name: model.NewId(),
+ DisplayName: model.NewId(),
+ Description: model.NewId(),
+ Permissions: []string{
+ "read_channel",
+ "create_public_channel",
+ "add_user_to_team",
+ },
+ SchemeManaged: false,
+ }
+ r3 := &model.Role{
+ Name: model.NewId(),
+ DisplayName: model.NewId(),
+ Description: model.NewId(),
+ Permissions: []string{
+ "invite_user",
+ "delete_private_channel",
+ "add_user_to_team",
+ },
+ SchemeManaged: false,
+ }
+
+ res1 := <-ss.Role().Save(r1)
+ assert.Nil(t, res1.Err)
+ d1 := res1.Data.(*model.Role)
+ assert.Len(t, d1.Id, 26)
+
+ res2 := <-ss.Role().Save(r2)
+ assert.Nil(t, res2.Err)
+ d2 := res2.Data.(*model.Role)
+ assert.Len(t, d2.Id, 26)
+
+ res3 := <-ss.Role().Save(r3)
+ assert.Nil(t, res3.Err)
+ d3 := res3.Data.(*model.Role)
+ assert.Len(t, d3.Id, 26)
+
+ // Get two valid roles.
+ n4 := []string{r1.Name, r2.Name}
+ res4 := <-ss.Role().GetByNames(n4)
+ assert.Nil(t, res4.Err)
+ roles4 := res4.Data.([]*model.Role)
+ assert.Len(t, roles4, 2)
+ assert.Contains(t, roles4, d1)
+ assert.Contains(t, roles4, d2)
+ assert.NotContains(t, roles4, d3)
+
+ // Get two invalid roles.
+ n5 := []string{model.NewId(), model.NewId()}
+ res5 := <-ss.Role().GetByNames(n5)
+ assert.Nil(t, res5.Err)
+ roles5 := res5.Data.([]*model.Role)
+ assert.Len(t, roles5, 0)
+
+ // Get one valid one and one invalid one.
+ n6 := []string{r1.Name, model.NewId()}
+ res6 := <-ss.Role().GetByNames(n6)
+ assert.Nil(t, res6.Err)
+ roles6 := res6.Data.([]*model.Role)
+ assert.Len(t, roles6, 1)
+ assert.Contains(t, roles6, d1)
+ assert.NotContains(t, roles6, d2)
+ assert.NotContains(t, roles6, d3)
+}
diff --git a/store/storetest/store.go b/store/storetest/store.go
index 367c5f441..44f426075 100644
--- a/store/storetest/store.go
+++ b/store/storetest/store.go
@@ -43,6 +43,7 @@ type Store struct {
UserAccessTokenStore mocks.UserAccessTokenStore
PluginStore mocks.PluginStore
ChannelMemberHistoryStore mocks.ChannelMemberHistoryStore
+ RoleStore mocks.RoleStore
}
func (s *Store) Team() store.TeamStore { return &s.TeamStore }
@@ -68,6 +69,7 @@ func (s *Store) Reaction() store.ReactionStore { return &s.React
func (s *Store) Job() store.JobStore { return &s.JobStore }
func (s *Store) UserAccessToken() store.UserAccessTokenStore { return &s.UserAccessTokenStore }
func (s *Store) Plugin() store.PluginStore { return &s.PluginStore }
+func (s *Store) Role() store.RoleStore { return &s.RoleStore }
func (s *Store) ChannelMemberHistory() store.ChannelMemberHistoryStore {
return &s.ChannelMemberHistoryStore
}
@@ -104,5 +106,6 @@ func (s *Store) AssertExpectations(t mock.TestingT) bool {
&s.UserAccessTokenStore,
&s.ChannelMemberHistoryStore,
&s.PluginStore,
+ &s.RoleStore,
)
}