mirror of
https://github.com/versity/versitygw.git
synced 2025-12-23 05:05:16 +00:00
205 lines
4.6 KiB
Go
205 lines
4.6 KiB
Go
// Copyright 2023 Versity Software
|
|
// This file is licensed under the Apache License, Version 2.0
|
|
// (the "License"); you may not use this file except in compliance
|
|
// with the License. You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing,
|
|
// software distributed under the License is distributed on an
|
|
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
// KIND, either express or implied. See the License for the
|
|
// specific language governing permissions and limitations
|
|
// under the License.
|
|
|
|
package auth
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// IAMCache is an in memory cache of the IAM accounts
|
|
// with expiration. This helps to alleviate the load on
|
|
// the real IAM service if the gateway is handling
|
|
// many requests. This forwards account updates to the
|
|
// underlying service, and returns cached results while
|
|
// the in memory account is not expired.
|
|
type IAMCache struct {
|
|
service IAMService
|
|
iamcache *icache
|
|
cancel context.CancelFunc
|
|
}
|
|
|
|
var _ IAMService = &IAMCache{}
|
|
|
|
type item struct {
|
|
value Account
|
|
exp time.Time
|
|
}
|
|
|
|
type icache struct {
|
|
sync.RWMutex
|
|
expire time.Duration
|
|
items map[string]item
|
|
}
|
|
|
|
func (i *icache) set(k string, v Account) {
|
|
cpy := v
|
|
i.Lock()
|
|
i.items[k] = item{
|
|
exp: time.Now().Add(i.expire),
|
|
value: cpy,
|
|
}
|
|
i.Unlock()
|
|
}
|
|
|
|
func (i *icache) get(k string) (Account, bool) {
|
|
i.RLock()
|
|
v, ok := i.items[k]
|
|
i.RUnlock()
|
|
if !ok || !v.exp.After(time.Now()) {
|
|
return Account{}, false
|
|
}
|
|
return v.value, true
|
|
}
|
|
|
|
func (i *icache) update(k string, props MutableProps) {
|
|
i.Lock()
|
|
defer i.Unlock()
|
|
|
|
item, found := i.items[k]
|
|
if found {
|
|
updateAcc(&item.value, props)
|
|
|
|
// refresh the expiration date
|
|
item.exp = time.Now().Add(i.expire)
|
|
|
|
i.items[k] = item
|
|
}
|
|
}
|
|
|
|
func (i *icache) Delete(k string) {
|
|
i.Lock()
|
|
delete(i.items, k)
|
|
i.Unlock()
|
|
}
|
|
|
|
func (i *icache) gcCache(ctx context.Context, interval time.Duration) {
|
|
for {
|
|
if ctx.Err() != nil {
|
|
break
|
|
}
|
|
|
|
now := time.Now()
|
|
|
|
i.Lock()
|
|
// prune expired entries
|
|
for k, v := range i.items {
|
|
if now.After(v.exp) {
|
|
delete(i.items, k)
|
|
}
|
|
}
|
|
i.Unlock()
|
|
|
|
// sleep for the clean interval or context cancelation,
|
|
// whichever comes first
|
|
select {
|
|
case <-ctx.Done():
|
|
case <-time.After(interval):
|
|
}
|
|
}
|
|
}
|
|
|
|
// NewCache initializes an IAM cache for the provided service. The expireTime
|
|
// is the duration a cache entry can be valid, and the cleanupInterval is
|
|
// how often to scan cache and cleanup expired entries.
|
|
func NewCache(service IAMService, expireTime, cleanupInterval time.Duration) *IAMCache {
|
|
i := &IAMCache{
|
|
service: service,
|
|
iamcache: &icache{
|
|
items: make(map[string]item),
|
|
expire: expireTime,
|
|
},
|
|
}
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
go i.iamcache.gcCache(ctx, cleanupInterval)
|
|
i.cancel = cancel
|
|
|
|
return i
|
|
}
|
|
|
|
// CreateAccount send create to IAM service and creates an account cache entry
|
|
func (c *IAMCache) CreateAccount(account Account) error {
|
|
err := c.service.CreateAccount(account)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// we need a copy of account to be able to store beyond the
|
|
// lifetime of the request, otherwise Fiber will reuse and corrupt
|
|
// these entries
|
|
acct := Account{
|
|
Access: strings.Clone(account.Access),
|
|
Secret: strings.Clone(account.Secret),
|
|
Role: Role(strings.Clone(string(account.Role))),
|
|
}
|
|
|
|
c.iamcache.set(acct.Access, acct)
|
|
return nil
|
|
}
|
|
|
|
// GetUserAccount retrieves the cache account if it is in the cache and not
|
|
// expired. Otherwise retrieves from underlying IAM service and caches
|
|
// result for the expire duration.
|
|
func (c *IAMCache) GetUserAccount(access string) (Account, error) {
|
|
acct, found := c.iamcache.get(access)
|
|
if found {
|
|
return acct, nil
|
|
}
|
|
|
|
a, err := c.service.GetUserAccount(access)
|
|
if err != nil {
|
|
return Account{}, err
|
|
}
|
|
|
|
c.iamcache.set(access, a)
|
|
return a, nil
|
|
}
|
|
|
|
// DeleteUserAccount deletes account from IAM service and cache
|
|
func (c *IAMCache) DeleteUserAccount(access string) error {
|
|
err := c.service.DeleteUserAccount(access)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
c.iamcache.Delete(access)
|
|
return nil
|
|
}
|
|
|
|
func (c *IAMCache) UpdateUserAccount(access string, props MutableProps) error {
|
|
err := c.service.UpdateUserAccount(access, props)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
c.iamcache.update(access, props)
|
|
return nil
|
|
}
|
|
|
|
// ListUserAccounts is a passthrough to the underlying service and
|
|
// does not make use of the cache
|
|
func (c *IAMCache) ListUserAccounts() ([]Account, error) {
|
|
return c.service.ListUserAccounts()
|
|
}
|
|
|
|
// Shutdown graceful termination of service
|
|
func (c *IAMCache) Shutdown() error {
|
|
c.cancel()
|
|
return nil
|
|
}
|