mirror of https://github.com/coder/coder.git
110 lines
2.8 KiB
Go
110 lines
2.8 KiB
Go
package rbac
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
)
|
|
|
|
type AuthCall struct {
|
|
Actor Subject
|
|
Action Action
|
|
Object Object
|
|
}
|
|
|
|
type cachedCalls struct {
|
|
authz Authorizer
|
|
}
|
|
|
|
// Cacher returns an Authorizer that can use a cache stored on a context
|
|
// to short circuit duplicate calls to the Authorizer. This is useful when
|
|
// multiple calls are made to the Authorizer for the same subject, action, and
|
|
// object. The cache is on each `ctx` and is not shared between requests.
|
|
// If no cache is found on the context, the Authorizer is called as normal.
|
|
//
|
|
// Cacher is safe for multiple actors.
|
|
func Cacher(authz Authorizer) Authorizer {
|
|
return &cachedCalls{authz: authz}
|
|
}
|
|
|
|
func (c *cachedCalls) Authorize(ctx context.Context, subject Subject, action Action, object Object) error {
|
|
cache := cacheFromContext(ctx)
|
|
|
|
resp, ok := cache.Load(subject, action, object)
|
|
if ok {
|
|
return resp
|
|
}
|
|
|
|
err := c.authz.Authorize(ctx, subject, action, object)
|
|
cache.Save(subject, action, object, err)
|
|
return err
|
|
}
|
|
|
|
// Prepare returns the underlying PreparedAuthorized. The cache does not apply
|
|
// to prepared authorizations. These should be using a SQL filter, and
|
|
// therefore the cache is not needed.
|
|
func (c *cachedCalls) Prepare(ctx context.Context, subject Subject, action Action, objectType string) (PreparedAuthorized, error) {
|
|
return c.authz.Prepare(ctx, subject, action, objectType)
|
|
}
|
|
|
|
type cachedAuthCall struct {
|
|
AuthCall
|
|
Err error
|
|
}
|
|
|
|
type authorizeCache struct {
|
|
sync.Mutex
|
|
// calls is a list of all calls made to the Authorizer.
|
|
// This list is cached per request context. The size of this list is expected
|
|
// to be incredibly small. Often 1 or 2 calls.
|
|
calls []cachedAuthCall
|
|
}
|
|
|
|
//nolint:revive
|
|
func (c *authorizeCache) Load(subject Subject, action Action, object Object) (error, bool) {
|
|
if c == nil {
|
|
return nil, false
|
|
}
|
|
c.Lock()
|
|
defer c.Unlock()
|
|
|
|
for _, call := range c.calls {
|
|
if call.Action == action && call.Object.Equal(object) && call.Actor.Equal(subject) {
|
|
return call.Err, true
|
|
}
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
func (c *authorizeCache) Save(subject Subject, action Action, object Object, err error) {
|
|
if c == nil {
|
|
return
|
|
}
|
|
c.Lock()
|
|
defer c.Unlock()
|
|
|
|
c.calls = append(c.calls, cachedAuthCall{
|
|
AuthCall: AuthCall{
|
|
Actor: subject,
|
|
Action: action,
|
|
Object: object,
|
|
},
|
|
Err: err,
|
|
})
|
|
}
|
|
|
|
// cacheContextKey is a context key used to store the cache in the context.
|
|
type cacheContextKey struct{}
|
|
|
|
// cacheFromContext returns the cache from the context.
|
|
// If there is no cache, a nil value is returned.
|
|
// The nil cache can still be called as a normal cache, but will not cache or
|
|
// return any values.
|
|
func cacheFromContext(ctx context.Context) *authorizeCache {
|
|
cache, _ := ctx.Value(cacheContextKey{}).(*authorizeCache)
|
|
return cache
|
|
}
|
|
|
|
func WithCacheCtx(ctx context.Context) context.Context {
|
|
return context.WithValue(ctx, cacheContextKey{}, &authorizeCache{})
|
|
}
|