Skip to content

Commit 8407ba5

Browse files
authored
Fix MSAL caching (#2300)
This change updates our MSAL cache adapter to no longer use the hinted partition key as a cache key. Instead, the current user has a fixed cache key equal to the empty string `""`. This creates the behavior of `~/.azd/auth/msal/cache.[bin|json]` being the single file that contains MSAL multi-account data, as defined by this [contract](https://github.com/AzureAD/microsoft-authentication-library-for-go/blob/27c98c8f9db6bc564c5be43677f3e6276b7c4fef/apps/internal/base/internal/storage/items.go#L18). Also, fix logout not resetting `cache.json` due to using `config.json` (userConfigManger.Load) and not `auth.json` (readAuthConfig). Fixes #2299
1 parent e6ece84 commit 8407ba5

File tree

3 files changed

+54
-38
lines changed

3 files changed

+54
-38
lines changed

cli/azd/pkg/auth/cache.go

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,26 @@ import (
1010
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/cache"
1111
)
1212

13+
// The MSAL cache key for the current user. The stored MSAL cached data contains
14+
// all accounts with stored credentials, across all tenants.
15+
// Currently, the underlying MSAL cache data is represented as [Contract] inside the library.
16+
//
17+
// For simplicity in naming the final cached file, which has a unique directory (see [fileCache]),
18+
// and for historical purposes, we use empty string as the key.
19+
//
20+
// It may be tempting to instead use the partition key provided by [cache.ReplaceHints],
21+
// but note that the key is a partitioning key and not a unique user key.
22+
// Also, given that the data contains auth data for all users, we only need a single key
23+
// to store all cached auth information.
24+
const cCurrentUserCacheKey = ""
25+
1326
// msalCacheAdapter adapts our interface to the one expected by cache.ExportReplace.
1427
type msalCacheAdapter struct {
1528
cache Cache
1629
}
1730

18-
func (a *msalCacheAdapter) Replace(ctx context.Context, cache cache.Unmarshaler, cacheHints cache.ReplaceHints) error {
19-
val, err := a.cache.Read(cacheHints.PartitionKey)
31+
func (a *msalCacheAdapter) Replace(ctx context.Context, cache cache.Unmarshaler, _ cache.ReplaceHints) error {
32+
val, err := a.cache.Read(cCurrentUserCacheKey)
2033
if errors.Is(err, errCacheKeyNotFound) {
2134
return nil
2235
} else if err != nil {
@@ -30,13 +43,13 @@ func (a *msalCacheAdapter) Replace(ctx context.Context, cache cache.Unmarshaler,
3043
return nil
3144
}
3245

33-
func (a *msalCacheAdapter) Export(ctx context.Context, cache cache.Marshaler, cacheHints cache.ExportHints) error {
46+
func (a *msalCacheAdapter) Export(ctx context.Context, cache cache.Marshaler, _ cache.ExportHints) error {
3447
val, err := cache.Marshal()
3548
if err != nil {
3649
return err
3750
}
3851

39-
return a.cache.Set(cacheHints.PartitionKey, val)
52+
return a.cache.Set(cCurrentUserCacheKey, val)
4053
}
4154

4255
type Cache interface {

cli/azd/pkg/auth/cache_test.go

Lines changed: 36 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -5,66 +5,69 @@ package auth
55

66
import (
77
"context"
8+
"math/rand"
89
"testing"
910

1011
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/cache"
1112
"github.com/stretchr/testify/require"
1213
)
1314

15+
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
16+
17+
func randSeq(n int, rng rand.Rand) string {
18+
b := make([]rune, n)
19+
for i := range b {
20+
b[i] = letters[rng.Intn(len(letters))]
21+
}
22+
return string(b)
23+
}
24+
1425
func TestCache(t *testing.T) {
1526
root := t.TempDir()
1627
ctx := context.Background()
1728
c := newCache(root)
29+
// weak rng is fine for testing
30+
//nolint:gosec
31+
rng := rand.New(rand.NewSource(0))
1832

19-
d1 := fixedMarshaller{
20-
val: []byte("some data"),
33+
key := func() string {
34+
return randSeq(10, *rng)
2135
}
2236

23-
d2 := fixedMarshaller{
24-
val: []byte("some different data"),
37+
data := fixedMarshaller{
38+
val: []byte("some data"),
2539
}
2640

2741
// write some data.
28-
err := c.Export(ctx, &d1, cache.ExportHints{PartitionKey: "d1"})
42+
err := c.Export(ctx, &data, cache.ExportHints{PartitionKey: key()})
2943
require.NoError(t, err)
30-
err = c.Export(ctx, &d2, cache.ExportHints{PartitionKey: "d2"})
31-
require.NoError(t, err)
32-
33-
var r1 fixedMarshaller
34-
var r2 fixedMarshaller
3544

3645
// read back that data we wrote.
37-
err = c.Replace(ctx, &r1, cache.ReplaceHints{PartitionKey: "d1"})
46+
var reader fixedMarshaller
47+
err = c.Replace(ctx, &reader, cache.ReplaceHints{PartitionKey: key()})
3848
require.NoError(t, err)
39-
err = c.Replace(ctx, &r2, cache.ReplaceHints{PartitionKey: "d2"})
40-
require.NoError(t, err)
41-
42-
require.NotNil(t, r1.val)
43-
require.NotNil(t, r2.val)
44-
require.Equal(t, d1.val, r1.val)
45-
require.Equal(t, d2.val, r2.val)
49+
require.NotNil(t, reader.val)
50+
require.Equal(t, data.val, reader.val)
4651

4752
// the data should be shared across instances.
4853
c = newCache(root)
49-
50-
err = c.Replace(ctx, &r1, cache.ReplaceHints{PartitionKey: "d1"})
51-
require.NoError(t, err)
52-
err = c.Replace(ctx, &r2, cache.ReplaceHints{PartitionKey: "d2"})
54+
reader = fixedMarshaller{}
55+
err = c.Replace(ctx, &reader, cache.ReplaceHints{PartitionKey: key()})
5356
require.NoError(t, err)
57+
require.Equal(t, data.val, reader.val)
5458

55-
require.NotNil(t, r1.val)
56-
require.NotNil(t, r2.val)
57-
require.Equal(t, d1.val, r1.val)
58-
require.Equal(t, d2.val, r2.val)
59-
60-
// read some non-existing data
61-
nonExist := fixedMarshaller{
62-
val: []byte("some data"),
59+
// update existing data
60+
otherData := fixedMarshaller{
61+
val: []byte("other data"),
6362
}
64-
err = c.Replace(ctx, &nonExist, cache.ReplaceHints{PartitionKey: "nonExist"})
63+
err = c.Export(ctx, &otherData, cache.ExportHints{PartitionKey: key()})
64+
require.NoError(t, err)
65+
66+
// read back data
67+
err = c.Replace(ctx, &reader, cache.ReplaceHints{PartitionKey: key()})
6568
require.NoError(t, err)
66-
// data should not be overwritten when key is not found.
67-
require.Equal(t, []byte("some data"), nonExist.val)
69+
require.NotNil(t, reader.val)
70+
require.Equal(t, otherData.val, reader.val)
6871
}
6972

7073
func TestCredentialCache(t *testing.T) {

cli/azd/pkg/auth/manager.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -585,7 +585,7 @@ func (m *Manager) saveLoginForServicePrincipal(tenantId, clientId string, secret
585585
// getSignedInAccount fetches the public.Account for the signed in user, or nil if one does not exist
586586
// (e.g when logged in with a service principal).
587587
func (m *Manager) getSignedInAccount(ctx context.Context) (*public.Account, error) {
588-
cfg, err := m.userConfigManager.Load()
588+
cfg, err := m.readAuthConfig()
589589
if err != nil {
590590
return nil, fmt.Errorf("fetching current user: %w", err)
591591
}

0 commit comments

Comments
 (0)