github.com/jcmturner/gokrb5/v8@v8.4.4/client/cache.go (about)

     1  package client
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"sort"
     7  	"sync"
     8  	"time"
     9  
    10  	"github.com/jcmturner/gokrb5/v8/messages"
    11  	"github.com/jcmturner/gokrb5/v8/types"
    12  )
    13  
    14  // Cache for service tickets held by the client.
    15  type Cache struct {
    16  	Entries map[string]CacheEntry
    17  	mux     sync.RWMutex
    18  }
    19  
    20  // CacheEntry holds details for a cache entry.
    21  type CacheEntry struct {
    22  	SPN        string
    23  	Ticket     messages.Ticket `json:"-"`
    24  	AuthTime   time.Time
    25  	StartTime  time.Time
    26  	EndTime    time.Time
    27  	RenewTill  time.Time
    28  	SessionKey types.EncryptionKey `json:"-"`
    29  }
    30  
    31  // NewCache creates a new client ticket cache instance.
    32  func NewCache() *Cache {
    33  	return &Cache{
    34  		Entries: map[string]CacheEntry{},
    35  	}
    36  }
    37  
    38  // getEntry returns a cache entry that matches the SPN.
    39  func (c *Cache) getEntry(spn string) (CacheEntry, bool) {
    40  	c.mux.RLock()
    41  	defer c.mux.RUnlock()
    42  	e, ok := (*c).Entries[spn]
    43  	return e, ok
    44  }
    45  
    46  // JSON returns information about the cached service tickets in a JSON format.
    47  func (c *Cache) JSON() (string, error) {
    48  	c.mux.RLock()
    49  	defer c.mux.RUnlock()
    50  	var es []CacheEntry
    51  	keys := make([]string, 0, len(c.Entries))
    52  	for k := range c.Entries {
    53  		keys = append(keys, k)
    54  	}
    55  	sort.Strings(keys)
    56  	for _, k := range keys {
    57  		es = append(es, c.Entries[k])
    58  	}
    59  	b, err := json.MarshalIndent(&es, "", "  ")
    60  	if err != nil {
    61  		return "", err
    62  	}
    63  	return string(b), nil
    64  }
    65  
    66  // addEntry adds a ticket to the cache.
    67  func (c *Cache) addEntry(tkt messages.Ticket, authTime, startTime, endTime, renewTill time.Time, sessionKey types.EncryptionKey) CacheEntry {
    68  	spn := tkt.SName.PrincipalNameString()
    69  	c.mux.Lock()
    70  	defer c.mux.Unlock()
    71  	(*c).Entries[spn] = CacheEntry{
    72  		SPN:        spn,
    73  		Ticket:     tkt,
    74  		AuthTime:   authTime,
    75  		StartTime:  startTime,
    76  		EndTime:    endTime,
    77  		RenewTill:  renewTill,
    78  		SessionKey: sessionKey,
    79  	}
    80  	return c.Entries[spn]
    81  }
    82  
    83  // clear deletes all the cache entries
    84  func (c *Cache) clear() {
    85  	c.mux.Lock()
    86  	defer c.mux.Unlock()
    87  	for k := range c.Entries {
    88  		delete(c.Entries, k)
    89  	}
    90  }
    91  
    92  // RemoveEntry removes the cache entry for the defined SPN.
    93  func (c *Cache) RemoveEntry(spn string) {
    94  	c.mux.Lock()
    95  	defer c.mux.Unlock()
    96  	delete(c.Entries, spn)
    97  }
    98  
    99  // GetCachedTicket returns a ticket from the cache for the SPN.
   100  // Only a ticket that is currently valid will be returned.
   101  func (cl *Client) GetCachedTicket(spn string) (messages.Ticket, types.EncryptionKey, bool) {
   102  	if e, ok := cl.cache.getEntry(spn); ok {
   103  		//If within time window of ticket return it
   104  		if time.Now().UTC().After(e.StartTime) && time.Now().UTC().Before(e.EndTime) {
   105  			cl.Log("ticket received from cache for %s", spn)
   106  			return e.Ticket, e.SessionKey, true
   107  		} else if time.Now().UTC().Before(e.RenewTill) {
   108  			e, err := cl.renewTicket(e)
   109  			if err != nil {
   110  				return e.Ticket, e.SessionKey, false
   111  			}
   112  			return e.Ticket, e.SessionKey, true
   113  		}
   114  	}
   115  	var tkt messages.Ticket
   116  	var key types.EncryptionKey
   117  	return tkt, key, false
   118  }
   119  
   120  // renewTicket renews a cache entry ticket.
   121  // To renew from outside the client package use GetCachedTicket
   122  func (cl *Client) renewTicket(e CacheEntry) (CacheEntry, error) {
   123  	spn := e.Ticket.SName
   124  	_, _, err := cl.TGSREQGenerateAndExchange(spn, e.Ticket.Realm, e.Ticket, e.SessionKey, true)
   125  	if err != nil {
   126  		return e, err
   127  	}
   128  	e, ok := cl.cache.getEntry(e.Ticket.SName.PrincipalNameString())
   129  	if !ok {
   130  		return e, errors.New("ticket was not added to cache")
   131  	}
   132  	cl.Log("ticket renewed for %s (EndTime: %v)", spn.PrincipalNameString(), e.EndTime)
   133  	return e, nil
   134  }