Move role middleware and cache logic into a manager

This commit is contained in:
Benedikt Kulmann committed 2020-09-02 15:36:31 +02:00
1 parent d3b859484b
commit 1676f3541c
5 files changed
+129 -109

No files matched your search

-66
View File
@@ -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
}
+15 -37
View File
@@ -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)
}
+67
View File
@@ -0,0 +1,67 @@
package roles
import (
"context"
"github.com/owncloud/ocis-pkg/v2/log"
settings "github.com/owncloud/ocis-settings/pkg/proto/v0"
)
type Manager struct {
logger log.Logger
cache cache
roleService settings.RoleService
}
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
}
+25 -6
View File
@@ -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{}
+22
View File
@@ -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
}