From d24aec6b53e693ae04a329d778aea9787be0a056 Mon Sep 17 00:00:00 2001 From: Benedikt Kulmann Date: Wed, 2 Sep 2020 16:40:22 +0200 Subject: [PATCH] Revert "Revert "Roles manager"" --- changelog/unreleased/roles-manager.md | 6 +++ middleware/roles.go | 66 ------------------------- roles/cache.go | 52 ++++++-------------- roles/manager.go | 69 +++++++++++++++++++++++++++ roles/option.go | 31 +++++++++--- roles/util.go | 22 +++++++++ 6 files changed, 137 insertions(+), 109 deletions(-) create mode 100644 changelog/unreleased/roles-manager.md delete mode 100644 middleware/roles.go create mode 100644 roles/manager.go create mode 100644 roles/util.go diff --git a/changelog/unreleased/roles-manager.md b/changelog/unreleased/roles-manager.md new file mode 100644 index 000000000..924a4d633 --- /dev/null +++ b/changelog/unreleased/roles-manager.md @@ -0,0 +1,6 @@ +Change: Roles manager + +We combined the roles middleware and cache into a roles manager. The manager doesn't expose the cache anymore and manages +the state of the cache by fetching roles from the role service which don't exist in the cache, yet. + +https://github.com/owncloud/ocis-pkg/pull/60 diff --git a/middleware/roles.go b/middleware/roles.go deleted file mode 100644 index cb64a0ca6..000000000 --- a/middleware/roles.go +++ /dev/null @@ -1,66 +0,0 @@ -package middleware - -import ( - "context" - "encoding/json" - "net/http" - - "github.com/micro/go-micro/v2/metadata" - "github.com/owncloud/ocis-pkg/v2/log" - "github.com/owncloud/ocis-pkg/v2/roles" - settings "github.com/owncloud/ocis-settings/pkg/proto/v0" -) - -// Roles manages a roles.Cache by fetching and inserting roles unknown by the cache. -// Relevant roleIDs are extracted from the metadata context, i.e. the roleIDs of the authenticated user. -func Roles(log log.Logger, rs settings.RoleService, cache *roles.Cache) func(next http.Handler) http.Handler { - return func(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // get roleIDs from context - roleIDs, ok := ReadRoleIDsFromContext(r.Context()) - if !ok { - log.Debug().Msg("failed to read roleIDs from context") - next.ServeHTTP(w, r) - return - } - - // check which roles are not cached, yet - lookup := make([]string, 0) - for _, roleID := range roleIDs { - if hit := cache.Get(roleID); hit == nil { - lookup = append(lookup, roleID) - } - } - - // fetch roles - if len(lookup) > 0 { - request := &settings.ListBundlesRequest{ - BundleIds: lookup, - } - res, err := rs.ListRoles(r.Context(), request) - if err != nil { - log.Debug().Err(err).Msg("failed to fetch roles by roleIDs") - next.ServeHTTP(w, r) - return - } - for _, role := range res.Bundles { - cache.Set(role.Id, role) - } - } - next.ServeHTTP(w, r) - }) - } -} - -// ReadRoleIDsFromContext extracts roleIDs from the metadata context and returns them as []string -func ReadRoleIDsFromContext(ctx context.Context) (roleIDs []string, ok bool) { - roleIDsJSON, ok := metadata.Get(ctx, RoleIDs) - if !ok { - return nil, false - } - err := json.Unmarshal([]byte(roleIDsJSON), &roleIDs) - if err != nil { - return nil, false - } - return roleIDs, true -} diff --git a/roles/cache.go b/roles/cache.go index aaf09b69c..4de4ea1a7 100644 --- a/roles/cache.go +++ b/roles/cache.go @@ -13,27 +13,25 @@ type entry struct { inserted time.Time } -// Cache is a cache implementation for roles, keyed by roleIDs. -type Cache struct { +// cache is a cache implementation for roles, keyed by roleIDs. +type cache struct { entries map[string]entry size int ttl time.Duration m sync.Mutex } -// NewCache returns a new instance of Cache. -func NewCache(o ...Option) Cache { - opts := newOptions(o...) - - return Cache{ - size: opts.size, - ttl: opts.ttl, +// newCache returns a new instance of Cache. +func newCache(size int, ttl time.Duration) cache { + return cache{ + size: size, + ttl: ttl, entries: map[string]entry{}, } } -// Get gets a role-bundle by a given `roleID`. -func (c *Cache) Get(roleID string) *settings.Bundle { +// get gets a role-bundle by a given `roleID`. +func (c *cache) get(roleID string) *settings.Bundle { c.m.Lock() defer c.m.Unlock() @@ -43,23 +41,8 @@ func (c *Cache) Get(roleID string) *settings.Bundle { return nil } -// FindPermissionByID searches for a permission-setting by the permissionID, but limited to the given roleIDs -func (c *Cache) FindPermissionByID(roleIDs []string, permissionID string) *settings.Setting { - for _, roleID := range roleIDs { - role := c.Get(roleID) - if role != nil { - for _, setting := range role.Settings { - if setting.Id == permissionID { - return setting - } - } - } - } - return nil -} - -// Set sets a roleID / role-bundle. -func (c *Cache) Set(roleID string, value *settings.Bundle) { +// set sets a roleID / role-bundle. +func (c *cache) set(roleID string, value *settings.Bundle) { c.m.Lock() defer c.m.Unlock() @@ -73,8 +56,8 @@ func (c *Cache) Set(roleID string, value *settings.Bundle) { } } -// Evict frees memory from the cache by removing entries that exceeded the cache TTL. -func (c *Cache) evict() { +// evict frees memory from the cache by removing entries that exceeded the cache TTL. +func (c *cache) evict() { for i := range c.entries { if c.entries[i].inserted.Add(c.ttl).Before(time.Now()) { delete(c.entries, i) @@ -82,12 +65,7 @@ func (c *Cache) evict() { } } -// Length returns the amount of entries. -func (c *Cache) Length() int { - return len(c.entries) -} - -// fits returns whether the cache is at full capacity. -func (c *Cache) fits() bool { +// fits returns whether the cache fits more entries. +func (c *cache) fits() bool { return c.size > len(c.entries) } diff --git a/roles/manager.go b/roles/manager.go new file mode 100644 index 000000000..253bdfd43 --- /dev/null +++ b/roles/manager.go @@ -0,0 +1,69 @@ +package roles + +import ( + "context" + + "github.com/owncloud/ocis-pkg/v2/log" + settings "github.com/owncloud/ocis-settings/pkg/proto/v0" +) + +// Manager manages a cache of roles by fetching unknown roles from the settings.RoleService. +type Manager struct { + logger log.Logger + cache cache + roleService settings.RoleService +} + +// NewManager returns a new instance of Manager. +func NewManager(o ...Option) Manager { + opts := newOptions(o...) + + return Manager{ + cache: newCache(opts.size, opts.ttl), + roleService: opts.roleService, + } +} + +// List returns all roles that match the given roleIDs. +func (m *Manager) List(ctx context.Context, roleIDs []string) []*settings.Bundle { + // get from cache + result := make([]*settings.Bundle, 0) + lookup := make([]string, 0) + for _, roleID := range roleIDs { + if hit := m.cache.get(roleID); hit == nil { + lookup = append(lookup, roleID) + } else { + result = append(result, hit) + } + } + + // if there are roles missing, fetch them from the RoleService + if len(lookup) > 0 { + request := &settings.ListBundlesRequest{ + BundleIds: lookup, + } + res, err := m.roleService.ListRoles(ctx, request) + if err != nil { + m.logger.Debug().Err(err).Msg("failed to fetch roles by roleIDs") + } + for _, role := range res.Bundles { + m.cache.set(role.Id, role) + result = append(result, role) + } + } + + return result +} + +// FindPermissionByID searches for a permission-setting by the permissionID, but limited to the given roleIDs +func (m *Manager) FindPermissionByID(ctx context.Context, roleIDs []string, permissionID string) *settings.Setting { + for _, role := range m.List(ctx, roleIDs) { + for _, setting := range role.Settings { + if setting.Id == permissionID { + return setting + } + } + } + return nil +} + diff --git a/roles/option.go b/roles/option.go index e5fe9511b..20f2be31f 100644 --- a/roles/option.go +++ b/roles/option.go @@ -2,31 +2,50 @@ package roles import ( "time" + + "github.com/owncloud/ocis-pkg/v2/log" + settings "github.com/owncloud/ocis-settings/pkg/proto/v0" ) // Options are all the possible options. type Options struct { - size int - ttl time.Duration + size int + ttl time.Duration + logger log.Logger + roleService settings.RoleService } // Option mutates option type Option func(*Options) -// Size configures the size of the cache in items. -func Size(s int) Option { +// CacheSize configures the size of the cache in items. +func CacheSize(s int) Option { return func(o *Options) { o.size = s } } -// TTL rebuilds the cache after the configured duration. -func TTL(ttl time.Duration) Option { +// CacheTTL rebuilds the cache after the configured duration. +func CacheTTL(ttl time.Duration) Option { return func(o *Options) { o.ttl = ttl } } +// Logger sets a preconfigured logger +func Logger(logger log.Logger) Option { + return func(o *Options) { + o.logger = logger + } +} + +// RoleService provides endpoints for fetching roles. +func RoleService(rs settings.RoleService) Option { + return func(o *Options) { + o.roleService = rs + } +} + func newOptions(opts ...Option) Options { o := Options{} diff --git a/roles/util.go b/roles/util.go new file mode 100644 index 000000000..b3d3c01e9 --- /dev/null +++ b/roles/util.go @@ -0,0 +1,22 @@ +package roles + +import ( + "context" + "encoding/json" + + "github.com/micro/go-micro/v2/metadata" + "github.com/owncloud/ocis-pkg/v2/middleware" +) + +// ReadRoleIDsFromContext extracts roleIDs from the metadata context and returns them as []string +func ReadRoleIDsFromContext(ctx context.Context) (roleIDs []string, ok bool) { + roleIDsJSON, ok := metadata.Get(ctx, middleware.RoleIDs) + if !ok { + return nil, false + } + err := json.Unmarshal([]byte(roleIDsJSON), &roleIDs) + if err != nil { + return nil, false + } + return roleIDs, true +}