github.com/greenpau/go-authcrunch@v1.1.4/pkg/authn/cache/sandbox.go (about)

     1  // Copyright 2022 Paul Greenberg greenpau@outlook.com
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package cache
    16  
    17  import (
    18  	"errors"
    19  	"fmt"
    20  	"sync"
    21  	"time"
    22  
    23  	"github.com/greenpau/go-authcrunch/pkg/user"
    24  )
    25  
    26  const defaultSandboxCleanupInternal int = 60
    27  const minSandboxCleanupInternal int = 0
    28  const defaultSandboxMaxEntryLifetime int = 300
    29  const minSandboxMaxEntryLifetime int = 60
    30  
    31  // SandboxCacheEntry is an entry in SandboxCache.
    32  type SandboxCacheEntry struct {
    33  	sandboxID string
    34  	createdAt time.Time
    35  	user      *user.User
    36  	// When set to true, the sandbox entry is no longer active.
    37  	expired bool
    38  }
    39  
    40  // SandboxCache contains cached tokens
    41  type SandboxCache struct {
    42  	mu sync.RWMutex
    43  	// The interval (in seconds) at which cache maintenance task are being triggered.
    44  	// The default is 5 minutes (300 seconds)
    45  	cleanupInternal int
    46  	// The maximum number of seconds the cached entry is available to a user.
    47  	maxEntryLifetime int
    48  	// If set to true, then the cache is being managed.
    49  	managed bool
    50  	// exit channel
    51  	exit    chan bool
    52  	Entries map[string]*SandboxCacheEntry `json:"entries,omitempty" xml:"entries,omitempty" yaml:"entries,omitempty"`
    53  }
    54  
    55  // NewSandboxCache returns SandboxCache instance.
    56  func NewSandboxCache() *SandboxCache {
    57  	return &SandboxCache{
    58  		cleanupInternal:  defaultSandboxCleanupInternal,
    59  		maxEntryLifetime: defaultSandboxMaxEntryLifetime,
    60  		Entries:          make(map[string]*SandboxCacheEntry),
    61  		exit:             make(chan bool),
    62  	}
    63  }
    64  
    65  // SetCleanupInterval sets cache management interval.
    66  func (c *SandboxCache) SetCleanupInterval(i int) error {
    67  	if i < 1 {
    68  		return fmt.Errorf("sandbox cache cleanup interval must be equal to or greater than %d", minSandboxCleanupInternal)
    69  	}
    70  	c.cleanupInternal = i
    71  	return nil
    72  }
    73  
    74  // SetMaxEntryLifetime sets cache management max entry lifetime in seconds.
    75  func (c *SandboxCache) SetMaxEntryLifetime(i int) error {
    76  	if i < 60 {
    77  		return fmt.Errorf("sandbox cache max entry lifetime must be equal to or greater than %d seconds", minSandboxMaxEntryLifetime)
    78  	}
    79  	c.maxEntryLifetime = i
    80  	return nil
    81  }
    82  
    83  func manageSandboxCache(c *SandboxCache) {
    84  	c.managed = true
    85  	intervals := time.NewTicker(time.Second * time.Duration(c.cleanupInternal))
    86  	for range intervals.C {
    87  		if c == nil {
    88  			continue
    89  		}
    90  		c.mu.Lock()
    91  		select {
    92  		case <-c.exit:
    93  			c.managed = false
    94  			break
    95  		default:
    96  			break
    97  		}
    98  		if !c.managed {
    99  			c.mu.Unlock()
   100  			break
   101  		}
   102  		if c.Entries == nil {
   103  			c.mu.Unlock()
   104  			continue
   105  		}
   106  		deleteList := []string{}
   107  		for sandboxID, entry := range c.Entries {
   108  			if err := entry.Valid(c.maxEntryLifetime); err != nil {
   109  				deleteList = append(deleteList, sandboxID)
   110  				continue
   111  			}
   112  		}
   113  		if len(deleteList) > 0 {
   114  			for _, sandboxID := range deleteList {
   115  				delete(c.Entries, sandboxID)
   116  			}
   117  		}
   118  		c.mu.Unlock()
   119  	}
   120  	return
   121  }
   122  
   123  // Run starts management of SandboxCache instance.
   124  func (c *SandboxCache) Run() {
   125  	if c.managed {
   126  		return
   127  	}
   128  	go manageSandboxCache(c)
   129  }
   130  
   131  // Stop stops management of SandboxCache instance.
   132  func (c *SandboxCache) Stop() {
   133  	c.mu.Lock()
   134  	defer c.mu.Unlock()
   135  	c.managed = false
   136  }
   137  
   138  // GetCleanupInterval returns cleanup interval.
   139  func (c *SandboxCache) GetCleanupInterval() int {
   140  	return c.cleanupInternal
   141  }
   142  
   143  // GetMaxEntryLifetime returns max entry lifetime.
   144  func (c *SandboxCache) GetMaxEntryLifetime() int {
   145  	return c.maxEntryLifetime
   146  }
   147  
   148  // Add adds user to the cache.
   149  func (c *SandboxCache) Add(sandboxID string, u *user.User) error {
   150  	c.mu.Lock()
   151  	defer c.mu.Unlock()
   152  	if c.Entries == nil {
   153  		return errors.New("sandbox cache is not available")
   154  	}
   155  	c.Entries[sandboxID] = &SandboxCacheEntry{
   156  		sandboxID: sandboxID,
   157  		createdAt: time.Now().UTC(),
   158  		user:      u,
   159  	}
   160  	return nil
   161  }
   162  
   163  // Delete removes cached user entry.
   164  func (c *SandboxCache) Delete(sandboxID string) error {
   165  	c.mu.Lock()
   166  	defer c.mu.Unlock()
   167  	if c.Entries == nil {
   168  		return errors.New("sandbox cache is not available")
   169  	}
   170  	_, exists := c.Entries[sandboxID]
   171  	if !exists {
   172  		return errors.New("cached sandbox id not found")
   173  	}
   174  	delete(c.Entries, sandboxID)
   175  	return nil
   176  }
   177  
   178  // Get returns cached user entry.
   179  func (c *SandboxCache) Get(sandboxID string) (*user.User, error) {
   180  	if err := parseCacheID(sandboxID); err != nil {
   181  		return nil, err
   182  	}
   183  	c.mu.RLock()
   184  	defer c.mu.RUnlock()
   185  	if entry, exists := c.Entries[sandboxID]; exists {
   186  		if err := entry.Valid(c.maxEntryLifetime); err != nil {
   187  			return nil, err
   188  		}
   189  		return entry.user, nil
   190  	}
   191  	return nil, errors.New("cached sandbox id not found")
   192  }
   193  
   194  // Expire expires a particular sandbox entry.
   195  func (c *SandboxCache) Expire(sandboxID string) {
   196  	c.mu.Lock()
   197  	defer c.mu.Unlock()
   198  	if entry, exists := c.Entries[sandboxID]; exists {
   199  		entry.expired = true
   200  	}
   201  	return
   202  }
   203  
   204  // Valid checks whether SandboxCacheEntry is non-expired.
   205  func (e *SandboxCacheEntry) Valid(max int) error {
   206  	if e.expired {
   207  		return errors.New("sandbox cached entry is no longer in use")
   208  	}
   209  	diff := time.Now().UTC().Unix() - e.createdAt.Unix()
   210  	if diff > int64(max) {
   211  		return errors.New("sandbox cached entry expired")
   212  	}
   213  	return nil
   214  }