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 }