diff --git a/changelog/unreleased/roles-manager.md b/changelog/unreleased/roles-manager.md deleted file mode 100644 index 924a4d633f..0000000000 --- a/changelog/unreleased/roles-manager.md +++ /dev/null @@ -1,6 +0,0 @@ -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 new file mode 100644 index 0000000000..cb64a0ca64 --- /dev/null +++ b/middleware/roles.go @@ -0,0 +1,66 @@ +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 4de4ea1a76..aaf09b69c7 100644 --- a/roles/cache.go +++ b/roles/cache.go @@ -13,25 +13,27 @@ 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(size int, ttl time.Duration) cache { - return cache{ - size: size, - ttl: ttl, +// NewCache returns a new instance of Cache. +func NewCache(o ...Option) Cache { + opts := newOptions(o...) + + return Cache{ + size: opts.size, + ttl: opts.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() @@ -41,8 +43,23 @@ func (c *cache) get(roleID string) *settings.Bundle { return nil } -// set sets a roleID / role-bundle. -func (c *cache) set(roleID string, value *settings.Bundle) { +// 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) { c.m.Lock() defer c.m.Unlock() @@ -56,8 +73,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) @@ -65,7 +82,12 @@ func (c *cache) evict() { } } -// fits returns whether the cache fits more entries. -func (c *cache) fits() bool { +// 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 { return c.size > len(c.entries) } diff --git a/roles/manager.go b/roles/manager.go deleted file mode 100644 index 253bdfd43e..0000000000 --- a/roles/manager.go +++ /dev/null @@ -1,69 +0,0 @@ -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 20f2be31f6..e5fe9511ba 100644 --- a/roles/option.go +++ b/roles/option.go @@ -2,50 +2,31 @@ 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 - logger log.Logger - roleService settings.RoleService + size int + ttl time.Duration } // Option mutates option type Option func(*Options) -// CacheSize configures the size of the cache in items. -func CacheSize(s int) Option { +// Size configures the size of the cache in items. +func Size(s int) Option { return func(o *Options) { o.size = s } } -// CacheTTL rebuilds the cache after the configured duration. -func CacheTTL(ttl time.Duration) Option { +// TTL rebuilds the cache after the configured duration. +func TTL(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 deleted file mode 100644 index b3d3c01e98..0000000000 --- a/roles/util.go +++ /dev/null @@ -1,22 +0,0 @@ -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 -}