cache.go raw

   1  // Copyright (c) Microsoft Corporation. All rights reserved.
   2  // Licensed under the MIT License.
   3  
   4  package internal
   5  
   6  import (
   7  	"sync"
   8  
   9  	"github.com/AzureAD/microsoft-authentication-library-for-go/apps/cache"
  10  )
  11  
  12  // Cache represents a persistent cache that makes authentication data available across processes.
  13  // Construct one with [github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache.New]. This package's
  14  // [persistent user authentication example] shows how to use a persistent cache to reuse user
  15  // logins across application runs. For service principal credential types such as
  16  // [ClientCertificateCredential], simply set the Cache field on the credential options.
  17  //
  18  // [persistent user authentication example]: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity#example-package-PersistentUserAuthentication
  19  type Cache struct {
  20  	// impl is a pointer so a Cache can carry persistent state across copies
  21  	impl *impl
  22  }
  23  
  24  // impl is a Cache's private implementation
  25  type impl struct {
  26  	// factory constructs storage implementations
  27  	factory func(bool) (cache.ExportReplace, error)
  28  	// cae and noCAE are previously constructed storage implementations. CAE
  29  	// and non-CAE tokens must be stored separately because MSAL's cache doesn't
  30  	// observe token claims. If a single storage implementation held both kinds
  31  	// of tokens, it could create a reauthentication or error loop by returning
  32  	// a non-CAE token lacking a required claim.
  33  	cae, noCAE cache.ExportReplace
  34  	// mu synchronizes around cae and noCAE
  35  	mu *sync.RWMutex
  36  }
  37  
  38  func (i *impl) exportReplace(cae bool) (cache.ExportReplace, error) {
  39  	if i == nil {
  40  		// zero-value Cache: return a nil ExportReplace and MSAL will cache in memory
  41  		return nil, nil
  42  	}
  43  	var (
  44  		err error
  45  		xr  cache.ExportReplace
  46  	)
  47  	i.mu.RLock()
  48  	xr = i.cae
  49  	if !cae {
  50  		xr = i.noCAE
  51  	}
  52  	i.mu.RUnlock()
  53  	if xr != nil {
  54  		return xr, nil
  55  	}
  56  	i.mu.Lock()
  57  	defer i.mu.Unlock()
  58  	if cae {
  59  		if i.cae == nil {
  60  			if xr, err = i.factory(cae); err == nil {
  61  				i.cae = xr
  62  			}
  63  		}
  64  		return i.cae, err
  65  	}
  66  	if i.noCAE == nil {
  67  		if xr, err = i.factory(cae); err == nil {
  68  			i.noCAE = xr
  69  		}
  70  	}
  71  	return i.noCAE, err
  72  }
  73  
  74  // NewCache is the constructor for Cache. It takes a factory instead of an instance
  75  // because it doesn't know whether the Cache will store both CAE and non-CAE tokens.
  76  func NewCache(factory func(cae bool) (cache.ExportReplace, error)) Cache {
  77  	return Cache{&impl{factory: factory, mu: &sync.RWMutex{}}}
  78  }
  79  
  80  // ExportReplace returns an implementation satisfying MSAL's ExportReplace interface.
  81  // It's a function instead of a method on Cache so packages in azidentity and
  82  // azidentity/cache can call it while applications can't. "cae" declares whether the
  83  // caller intends this implementation to store CAE tokens.
  84  func ExportReplace(c Cache, cae bool) (cache.ExportReplace, error) {
  85  	return c.impl.exportReplace(cae)
  86  }
  87