vikunja-api/vendor/github.com/jcmturner/gokrb5/v8/service/cache.go

129 lines
3.4 KiB
Go
Raw Normal View History

// Package service provides server side integrations for Kerberos authentication.
package service
import (
"github.com/jcmturner/gokrb5/v8/types"
"sync"
"time"
)
// Replay cache is required as specified in RFC 4120 section 3.2.3
// Cache for tickets received from clients keyed by fully qualified client name. Used to track replay of tickets.
type Cache struct {
entries map[string]clientEntries
mux sync.RWMutex
}
// clientEntries holds entries of client details sent to the service.
type clientEntries struct {
replayMap map[time.Time]replayCacheEntry
seqNumber int64
subKey types.EncryptionKey
}
// Cache entry tracking client time values of tickets sent to the service.
type replayCacheEntry struct {
presentedTime time.Time
sName types.PrincipalName
cTime time.Time // This combines the ticket's CTime and Cusec
}
func (c *Cache) getClientEntries(cname types.PrincipalName) (clientEntries, bool) {
c.mux.RLock()
defer c.mux.RUnlock()
ce, ok := c.entries[cname.PrincipalNameString()]
return ce, ok
}
func (c *Cache) getClientEntry(cname types.PrincipalName, t time.Time) (replayCacheEntry, bool) {
if ce, ok := c.getClientEntries(cname); ok {
c.mux.RLock()
defer c.mux.RUnlock()
if e, ok := ce.replayMap[t]; ok {
return e, true
}
}
return replayCacheEntry{}, false
}
// Instance of the ServiceCache. This needs to be a singleton.
var replayCache Cache
var once sync.Once
// GetReplayCache returns a pointer to the Cache singleton.
func GetReplayCache(d time.Duration) *Cache {
// Create a singleton of the ReplayCache and start a background thread to regularly clean out old entries
once.Do(func() {
replayCache = Cache{
entries: make(map[string]clientEntries),
}
go func() {
for {
// TODO consider using a context here.
time.Sleep(d)
replayCache.ClearOldEntries(d)
}
}()
})
return &replayCache
}
// AddEntry adds an entry to the Cache.
func (c *Cache) AddEntry(sname types.PrincipalName, a types.Authenticator) {
ct := a.CTime.Add(time.Duration(a.Cusec) * time.Microsecond)
if ce, ok := c.getClientEntries(a.CName); ok {
c.mux.Lock()
defer c.mux.Unlock()
ce.replayMap[ct] = replayCacheEntry{
presentedTime: time.Now().UTC(),
sName: sname,
cTime: ct,
}
ce.seqNumber = a.SeqNumber
ce.subKey = a.SubKey
} else {
c.mux.Lock()
defer c.mux.Unlock()
c.entries[a.CName.PrincipalNameString()] = clientEntries{
replayMap: map[time.Time]replayCacheEntry{
ct: {
presentedTime: time.Now().UTC(),
sName: sname,
cTime: ct,
},
},
seqNumber: a.SeqNumber,
subKey: a.SubKey,
}
}
}
// ClearOldEntries clears entries from the Cache that are older than the duration provided.
func (c *Cache) ClearOldEntries(d time.Duration) {
c.mux.Lock()
defer c.mux.Unlock()
for ke, ce := range c.entries {
for k, e := range ce.replayMap {
if time.Now().UTC().Sub(e.presentedTime) > d {
delete(ce.replayMap, k)
}
}
if len(ce.replayMap) == 0 {
delete(c.entries, ke)
}
}
}
// IsReplay tests if the Authenticator provided is a replay within the duration defined. If this is not a replay add the entry to the cache for tracking.
func (c *Cache) IsReplay(sname types.PrincipalName, a types.Authenticator) bool {
ct := a.CTime.Add(time.Duration(a.Cusec) * time.Microsecond)
if e, ok := c.getClientEntry(a.CName, ct); ok {
if e.sName.Equal(sname) {
return true
}
}
c.AddEntry(sname, a)
return false
}