github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/api/crossmodelrelations/macarooncache.go (about) 1 // Copyright 2017 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package crossmodelrelations 5 6 import ( 7 "runtime" 8 "sync" 9 "time" 10 11 "github.com/juju/clock" 12 "gopkg.in/macaroon-bakery.v2-unstable/bakery/checkers" 13 "gopkg.in/macaroon.v2-unstable" 14 ) 15 16 // MacaroonCache contains macaroons which are removed at a specified interval. 17 type MacaroonCache struct { 18 // We use a separate struct for the actual cache and a wrapper to export to the 19 // user so that the expiry worker which references the internal cache doesn't 20 // prevent the exported cache from being garbage collected. 21 *cacheInternal 22 } 23 24 // NewMacaroonCache returns a cache containing macaroons which are removed 25 // after the macaroons' expiry time. 26 func NewMacaroonCache(clock clock.Clock) *MacaroonCache { 27 c := &cacheInternal{clock: clock, macaroons: make(map[string]*macaroonEntry)} 28 cache := &MacaroonCache{c} 29 // The interval to run the expiry worker is somewhat arbitrary. 30 // Expired macaroons will be re-issued as needed; we just want to ensure 31 // that those which fall out of use are eventually cleaned up. 32 c.runExpiryWorker(10 * time.Minute) 33 runtime.SetFinalizer(cache, stopMacaroonCacheExpiryWorker) 34 return cache 35 } 36 37 type expiryWorker struct { 38 clock clock.Clock 39 interval time.Duration 40 stop chan bool 41 } 42 43 func (w *expiryWorker) loop(c *cacheInternal) { 44 for { 45 select { 46 case <-w.clock.After(w.interval): 47 c.deleteExpired() 48 case <-w.stop: 49 return 50 } 51 } 52 } 53 54 type macaroonEntry struct { 55 ms macaroon.Slice 56 expiryTime *time.Time 57 } 58 59 func (i *macaroonEntry) expired(clock clock.Clock) bool { 60 return i.expiryTime != nil && i.expiryTime.Before(clock.Now()) 61 } 62 63 type cacheInternal struct { 64 sync.Mutex 65 clock clock.Clock 66 67 macaroons map[string]*macaroonEntry 68 expiryWorker *expiryWorker 69 } 70 71 // Upsert inserts or updates a macaroon slice in the cache. 72 func (c *cacheInternal) Upsert(token string, ms macaroon.Slice) { 73 c.Lock() 74 defer c.Unlock() 75 76 var et *time.Time 77 if expiryTime, ok := checkers.MacaroonsExpiryTime(ms); ok { 78 et = &expiryTime 79 } 80 c.macaroons[token] = &macaroonEntry{ 81 ms: ms, 82 expiryTime: et, 83 } 84 } 85 86 // Get returns a macaroon slice from the cache, and a bool indicating 87 // if the slice for the key was found. 88 func (c *cacheInternal) Get(k string) (macaroon.Slice, bool) { 89 c.Lock() 90 defer c.Unlock() 91 92 entry, found := c.macaroons[k] 93 if !found { 94 return nil, false 95 } 96 if entry.expired(c.clock) { 97 delete(c.macaroons, k) 98 return nil, false 99 } 100 return entry.ms, true 101 } 102 103 func (c *cacheInternal) deleteExpired() { 104 c.Lock() 105 defer c.Unlock() 106 107 for k, v := range c.macaroons { 108 if v.expired(c.clock) { 109 delete(c.macaroons, k) 110 } 111 } 112 } 113 114 func stopMacaroonCacheExpiryWorker(mc *MacaroonCache) { 115 mc.expiryWorker.stop <- true 116 } 117 118 func (c *cacheInternal) runExpiryWorker(interval time.Duration) { 119 w := &expiryWorker{ 120 interval: interval, 121 clock: c.clock, 122 stop: make(chan bool), 123 } 124 c.expiryWorker = w 125 go w.loop(c) 126 }