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