Merge pull request #62 from owncloud/revert-61-revert-60-roles-manager

Bring back "Roles manager" after release
This commit is contained in:
Benedikt Kulmann
2020-09-02 17:18:27 +02:00
committed by GitHub
6 changed files with 137 additions and 109 deletions

View File

@@ -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

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
}

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)
}

69
roles/manager.go Normal file
View File

@@ -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
}

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
roles/util.go Normal file
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
}