diff --git a/action.go b/action.go index bba4ad7..fdeff9c 100644 --- a/action.go +++ b/action.go @@ -61,7 +61,11 @@ func (s Service) HasPermission(userID, permission string) bool { // CreateRole ... func (s Service) CreateRole(payload model.RoleCreateOptions) error { - return role.Create(payload) + err := role.Create(payload) + if err == nil { + role.CacheRoles() + } + return err } // UpdateRole ... @@ -82,12 +86,20 @@ func (s Service) GetAllRoles(query model.RoleAllQuery) model.RoleAll { // CreatePermission ... func (s Service) CreatePermission(payload model.PermissionCreateOptions) error { - return permission.Create(payload) + err := permission.Create(payload) + if err == nil { + role.CacheRoles() + } + return err } // UpdatePermission ... func (s Service) UpdatePermission(permissionID string, payload model.PermissionUpdateOptions) error { - return permission.Update(permissionID, payload) + err := permission.Update(permissionID, payload) + if err == nil { + role.CacheRoles() + } + return err } // GetAllPermissions ... diff --git a/cache/cache.go b/cache/cache.go new file mode 100644 index 0000000..25cdd03 --- /dev/null +++ b/cache/cache.go @@ -0,0 +1,25 @@ +package cache + +import ( + "log" + "time" + + "github.com/allegro/bigcache/v3" +) + +var cache *bigcache.BigCache + +// Init ... +func Init() { + // The time after which entries can be evicted is 5 years + c, err := bigcache.NewBigCache(bigcache.DefaultConfig(43800 * time.Hour)) + if err != nil { + log.Fatalf("Cannot init Cache %v", err) + } + cache = c +} + +// GetInstance ... +func GetInstance() *bigcache.BigCache { + return cache +} diff --git a/go.mod b/go.mod index 70aa30c..a2de3aa 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,10 @@ go 1.17 require ( github.com/Selly-Modules/logger v0.0.0-20210809034923-140a51f39ec9 github.com/Selly-Modules/mongodb v0.0.0-20211013094205-a8ab24a96c4c + github.com/allegro/bigcache/v3 v3.0.1 + github.com/thoas/go-funk v0.9.1 go.mongodb.org/mongo-driver v1.7.4 + golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 ) require ( @@ -32,7 +35,6 @@ require ( go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.18.1 // indirect - golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 // indirect golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 // indirect golang.org/x/mod v0.3.0 // indirect golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect diff --git a/go.sum b/go.sum index 7fdc319..7bdbc2f 100644 --- a/go.sum +++ b/go.sum @@ -3,6 +3,8 @@ github.com/Selly-Modules/logger v0.0.0-20210809034923-140a51f39ec9 h1:AuJ/IIZ7yp github.com/Selly-Modules/logger v0.0.0-20210809034923-140a51f39ec9/go.mod h1:RWhSQ3F01an8KD00VjzRBZOMcE5eV2Cy0/l4ZkeieyU= github.com/Selly-Modules/mongodb v0.0.0-20211013094205-a8ab24a96c4c h1:1l6QmAl43maG9zFyUXrPQVUjyVt0vy/2Saz992UR+Sc= github.com/Selly-Modules/mongodb v0.0.0-20211013094205-a8ab24a96c4c/go.mod h1:C9O0Bgl9i6szjntMjBdEvaFSqG2UPOgHUspIWIJ93JQ= +github.com/allegro/bigcache/v3 v3.0.1 h1:Q4Xl3chywXuJNOw7NV+MeySd3zGQDj4KCpkCg0te8mc= +github.com/allegro/bigcache/v3 v3.0.1/go.mod h1:aPyh7jEvrog9zAwx5N7+JUQX5dZTSGpxF1LAR4dr35I= github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= @@ -91,6 +93,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/thoas/go-funk v0.9.1 h1:O549iLZqPpTUQ10ykd26sZhzD+rmR5pWhuElrhbC20M= +github.com/thoas/go-funk v0.9.1/go.mod h1:+IWnUfUmFO1+WVYQWQtIJHeRRdaIyyYglZN7xzUPe4Q= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= diff --git a/internal/constant.go b/internal/constant.go index c048f83..1a97219 100644 --- a/internal/constant.go +++ b/internal/constant.go @@ -7,4 +7,6 @@ const ( passwordHashingCost = 14 TablePrefixDefault = "usermngmt" + + RoleTypeAdmin = "admin" ) diff --git a/role/db.go b/role/db.go index 55209e0..c27883c 100644 --- a/role/db.go +++ b/role/db.go @@ -54,6 +54,33 @@ func updateOneByCondition(ctx context.Context, cond interface{}, payload interfa return err } +func permissionFindByCondition(ctx context.Context, cond interface{}, opts ...*options.FindOptions) (docs []model.DBPermission) { + var ( + col = database.GetPermissionCol() + ) + docs = make([]model.DBPermission, 0) + + cursor, err := col.Find(ctx, cond, opts...) + if err != nil { + logger.Error("usermngmt - Permission - Find", logger.LogData{ + "cond": cond, + "opts": opts, + "err": err.Error(), + }) + return + } + defer cursor.Close(ctx) + if err = cursor.All(ctx, &docs); err != nil { + logger.Error("usermngmt - Permission - Decode", logger.LogData{ + "cond": cond, + "opts": opts, + "err": err.Error(), + }) + return + } + return +} + func findByCondition(ctx context.Context, cond interface{}, opts ...*options.FindOptions) (docs []model.DBRole) { var ( col = database.GetRoleCol() diff --git a/role/handle.go b/role/handle.go index e4145a6..7ba14ee 100644 --- a/role/handle.go +++ b/role/handle.go @@ -3,11 +3,15 @@ package role import ( "context" "errors" + "strings" "sync" + "github.com/Selly-Modules/logger" "github.com/Selly-Modules/mongodb" + "github.com/Selly-Modules/usermngmt/cache" "github.com/Selly-Modules/usermngmt/internal" "github.com/Selly-Modules/usermngmt/model" + "github.com/thoas/go-funk" "go.mongodb.org/mongo-driver/bson" ) @@ -132,3 +136,49 @@ func getResponseList(roles []model.DBRole) []model.Role { return res } + +// CacheRoles ... +func CacheRoles() { + var ( + ctx = context.Background() + wg sync.WaitGroup + ) + + // Find + roles := findByCondition(ctx, bson.M{}) + + wg.Add(len(roles)) + for _, value := range roles { + go func(role model.DBRole) { + defer wg.Done() + + // Check and set role admin: admin + if role.IsAdmin { + if err := cache.GetInstance().Set(role.ID.Hex(), []byte(internal.RoleTypeAdmin)); err != nil { + logger.Error("usermngmt - CacheRole", logger.LogData{ + "err": err.Error(), + }) + return + } + } + + // Set role by permission with format: permissionCode,permissionCode,... + permissions := permissionFindByCondition(ctx, bson.M{ + "roleId": role.ID, + }) + permissionCodes := funk.Map(permissions, func(i model.DBPermission) string { + return i.Code + }).([]string) + permissionCodeString := strings.Join(permissionCodes, ",") + if err := cache.GetInstance().Set(role.ID.Hex(), []byte(permissionCodeString)); err != nil { + logger.Error("usermngmt - CacheRole", logger.LogData{ + "err": err.Error(), + }) + return + } + }(value) + } + + wg.Done() + return +} diff --git a/user/handle.go b/user/handle.go index 96ed3c0..ac98f98 100644 --- a/user/handle.go +++ b/user/handle.go @@ -3,12 +3,15 @@ package user import ( "context" "errors" + "strings" "sync" "github.com/Selly-Modules/logger" "github.com/Selly-Modules/mongodb" + "github.com/Selly-Modules/usermngmt/cache" "github.com/Selly-Modules/usermngmt/internal" "github.com/Selly-Modules/usermngmt/model" + "github.com/thoas/go-funk" "go.mongodb.org/mongo-driver/bson" ) @@ -328,7 +331,7 @@ func HasPermission(userID, permission string) (result bool) { // Validate userID, permission if userID == "" || permission == "" { - logger.Error("usermngmt - IsPermission: email or password cannot be empty", logger.LogData{ + logger.Error("usermngmt - HasPermission: email or password cannot be empty", logger.LogData{ "userID": userID, "permission": permission, }) @@ -336,7 +339,7 @@ func HasPermission(userID, permission string) (result bool) { } id, isValid := mongodb.NewIDFromString(userID) if !isValid { - logger.Error("usermngmt - IsPermission: invalid user id", logger.LogData{ + logger.Error("usermngmt - HasPermission: invalid user id", logger.LogData{ "userID": userID, "permission": permission, }) @@ -346,26 +349,23 @@ func HasPermission(userID, permission string) (result bool) { // Find user user, _ := findByID(ctx, id) if user.ID.IsZero() { - logger.Error("usermngmt - IsPermission: user not found", logger.LogData{ + logger.Error("usermngmt - HasPermission: user not found", logger.LogData{ "userID": userID, "permission": permission, }) return } - // Check isAdmin - if role, _ := roleFindByID(ctx, user.RoleID); role.IsAdmin { - result = true - return - } + // Get rolePermissions + // Role is saved with the value "admin" or "permissionCode,permissionCode,..." + entry, _ := cache.GetInstance().Get(user.RoleID.Hex()) + rolePermissions := strings.Split(string(entry), ",") - // Check permission - if total := permissionCountByCondition(ctx, bson.M{ - "roleId": user.RoleID, - "code": permission, - }); total > 0 { - result = true - return + // Check Permission + if _, isValid = funk.FindString(rolePermissions, func(s string) bool { + return s == permission || s == internal.RoleTypeAdmin + }); isValid { + return isValid } return diff --git a/usermngmt.go b/usermngmt.go index b2a2236..112a1a0 100644 --- a/usermngmt.go +++ b/usermngmt.go @@ -5,8 +5,10 @@ import ( "fmt" "github.com/Selly-Modules/mongodb" + "github.com/Selly-Modules/usermngmt/cache" "github.com/Selly-Modules/usermngmt/database" "github.com/Selly-Modules/usermngmt/internal" + "github.com/Selly-Modules/usermngmt/role" ) // MongoDBConfig ... @@ -54,6 +56,9 @@ func Init(config Config) (*Service, error) { return nil, err } + // Init cache + cache.Init() + // Set database database.Set(db, config.TablePrefix) @@ -61,6 +66,9 @@ func Init(config Config) (*Service, error) { config: config, } + // Cache role + role.CacheRoles() + return s, nil }