From cbd1984c7d8999967a608aceca9d2de369f31089 Mon Sep 17 00:00:00 2001 From: "red-hat-konflux[bot]" <126015336+red-hat-konflux[bot]@users.noreply.github.com> Date: Thu, 30 Jul 2026 22:37:13 +0000 Subject: [PATCH] Update azure-ad-microsoft-authentication-library-for-go monorepo to v1.8.0 Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 +- .../apps/confidential/confidential.go | 282 ++++++++++++++++-- .../apps/internal/base/base.go | 96 ++++-- .../base/storage/partitioned_storage.go | 12 +- .../apps/internal/exported/exported.go | 4 + .../apps/internal/json/json.go | 21 +- .../apps/internal/oauth/oauth.go | 12 + .../oauth/ops/accesstokens/accesstokens.go | 39 +++ .../internal/oauth/ops/accesstokens/tokens.go | 6 +- .../internal/oauth/ops/authority/authority.go | 138 ++++++++- .../ops/authority/authorizetype_string.go | 12 +- .../oauth/ops/internal/grant/grant.go | 1 + .../apps/internal/oauth/resolvers.go | 64 ++-- .../apps/internal/version/version.go | 2 +- vendor/modules.txt | 2 +- 16 files changed, 594 insertions(+), 103 deletions(-) diff --git a/go.mod b/go.mod index 4bc52cb67..eb29dff06 100644 --- a/go.mod +++ b/go.mod @@ -44,7 +44,7 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.14.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect - github.com/AzureAD/microsoft-authentication-library-for-go v1.7.2 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.8.0 // indirect github.com/BurntSushi/toml v1.6.0 // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect diff --git a/go.sum b/go.sum index 6e3996835..d462d8d6b 100644 --- a/go.sum +++ b/go.sum @@ -22,8 +22,8 @@ github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEK github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= -github.com/AzureAD/microsoft-authentication-library-for-go v1.7.2 h1:RHK7bS+HQMslb1sZpAokUt+zTVmue0hKSs2C791hhzU= -github.com/AzureAD/microsoft-authentication-library-for-go v1.7.2/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= +github.com/AzureAD/microsoft-authentication-library-for-go v1.8.0 h1:Nljr4q1GRA/5vCrMONS+g4u4LRHNgOXVSh3O43J2CnI= +github.com/AzureAD/microsoft-authentication-library-for-go v1.8.0/go.mod h1:Y33QHnf0FfdVewFFISOGe20mkZbxX4H839o955/PoeI= github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= diff --git a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential/confidential.go b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential/confidential.go index 1b3e599b1..fe4599e3d 100644 --- a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential/confidential.go +++ b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential/confidential.go @@ -57,6 +57,10 @@ As such I've put a PEM decoder into here. // TODO(msal): This should have example code for each method on client using Go's example doc framework. // base usage details should be include in the package documentation. +// clientClaimsCacheKey is the CacheKeyComponents key used to partition the token cache by +// client-originated claims (see WithClaimsFromClient). The component value is the raw claims string. +const clientClaimsCacheKey = "client_claims" + // AuthResult contains the results of one token acquisition operation. // For details see https://aka.ms/msal-net-authenticationresult type AuthResult = base.AuthResult @@ -465,6 +469,7 @@ func WithClaims(claims string) interface { AcquireByUsernamePasswordOption AcquireSilentOption AuthCodeURLOption + AcquireByUserFICOption options.CallOption } { return struct { @@ -474,6 +479,7 @@ func WithClaims(claims string) interface { AcquireByUsernamePasswordOption AcquireSilentOption AuthCodeURLOption + AcquireByUserFICOption options.CallOption }{ CallOption: options.NewCallOption( @@ -491,6 +497,84 @@ func WithClaims(claims string) interface { t.claims = claims case *authCodeURLOptions: t.claims = claims + case *acquireTokenByUserFICOptions: + t.claims = claims + default: + return fmt.Errorf("unexpected options type %T", a) + } + return nil + }, + ), + } +} + +// WithClaimsFromClient specifies client-originated claims (a JSON object) to include in the token +// request. +// +// Unlike [WithClaims] (for server-issued claims challenges, which bypass the token cache), tokens +// acquired with client claims ARE cached and the cache entry is keyed on the claims value. Different +// claims values produce separate cache entries, so callers should pass stable, non-dynamic values to +// avoid unbounded cache growth. The exact same string MUST be included on every request: the raw +// string is used verbatim as part of the cache key (MSAL does not normalize it), so omitting it or +// changing it on a later call silently moves to a different cache partition. +// +// The claims are sent to the authority as the standard OAuth "claims" body parameter (merged with any +// server-issued claims and client capabilities); they are not embedded in the client assertion JWT. +// +// The argument must be a JSON object, but the confidential client does not enforce this locally in all +// cases: the value is forwarded to the authority verbatim and is validated locally only when it is +// merged with server-issued claims or client capabilities. Otherwise a malformed or non-object value +// is not rejected locally and instead surfaces as a server-side error. An empty or whitespace-only +// value is ignored. +func WithClaimsFromClient(claims string) interface { + AcquireByAuthCodeOption + AcquireByCredentialOption + AcquireOnBehalfOfOption + AcquireByUsernamePasswordOption + AcquireSilentOption + AcquireByUserFICOption + options.CallOption +} { + return struct { + AcquireByAuthCodeOption + AcquireByCredentialOption + AcquireOnBehalfOfOption + AcquireByUsernamePasswordOption + AcquireSilentOption + AcquireByUserFICOption + options.CallOption + }{ + CallOption: options.NewCallOption( + func(a any) error { + if strings.TrimSpace(claims) == "" { + // Ignore empty/whitespace claims so callers can pass a value unconditionally. + return nil + } + addCacheKey := func(m *map[string]string) { + if *m == nil { + *m = make(map[string]string) + } + (*m)[clientClaimsCacheKey] = claims + } + switch t := a.(type) { + case *acquireTokenByAuthCodeOptions: + t.clientClaims = claims + addCacheKey(&t.cacheKeyComponents) + case *acquireTokenByCredentialOptions: + t.clientClaims = claims + addCacheKey(&t.cacheKeyComponents) + case *acquireTokenOnBehalfOfOptions: + t.clientClaims = claims + addCacheKey(&t.cacheKeyComponents) + case *acquireTokenByUsernamePasswordOptions: + t.clientClaims = claims + addCacheKey(&t.cacheKeyComponents) + case *acquireTokenSilentOptions: + t.clientClaims = claims + addCacheKey(&t.cacheKeyComponents) + case *acquireTokenByUserFICOptions: + t.clientClaims = claims + addCacheKey(&t.cacheKeyComponents) default: return fmt.Errorf("unexpected options type %T", a) } @@ -500,7 +584,6 @@ func WithClaims(claims string) interface { } } -// WithAuthenticationScheme is an extensibility mechanism designed to be used only by Azure Arc for proof of possession access tokens. func WithAuthenticationScheme(authnScheme AuthenticationScheme) interface { AcquireSilentOption AcquireByCredentialOption @@ -536,6 +619,7 @@ func WithTenantID(tenantID string) interface { AcquireByUsernamePasswordOption AcquireSilentOption AuthCodeURLOption + AcquireByUserFICOption options.CallOption } { return struct { @@ -545,6 +629,7 @@ func WithTenantID(tenantID string) interface { AcquireByUsernamePasswordOption AcquireSilentOption AuthCodeURLOption + AcquireByUserFICOption options.CallOption }{ CallOption: options.NewCallOption( @@ -562,6 +647,8 @@ func WithTenantID(tenantID string) interface { t.tenantID = tenantID case *authCodeURLOptions: t.tenantID = tenantID + case *acquireTokenByUserFICOptions: + t.tenantID = tenantID default: return fmt.Errorf("unexpected options type %T", a) } @@ -574,9 +661,11 @@ func WithTenantID(tenantID string) interface { // acquireTokenSilentOptions are all the optional settings to an AcquireTokenSilent() call. // These are set by using various AcquireTokenSilentOption functions. type acquireTokenSilentOptions struct { - account Account - claims, tenantID string - authnScheme AuthenticationScheme + account Account + claims, tenantID string + clientClaims string + authnScheme AuthenticationScheme + cacheKeyComponents map[string]string } // AcquireSilentOption is implemented by options for AcquireTokenSilent @@ -609,7 +698,7 @@ func WithSilentAccount(account Account) interface { // AcquireTokenSilent acquires a token from either the cache or using a refresh token. // -// Options: [WithClaims], [WithSilentAccount], [WithTenantID] +// Options: [WithClaims], [WithClaimsFromClient], [WithSilentAccount], [WithTenantID] func (cca Client) AcquireTokenSilent(ctx context.Context, scopes []string, opts ...AcquireSilentOption) (AuthResult, error) { o := acquireTokenSilentOptions{} if err := options.ApplyOptions(&o, opts); err != nil { @@ -626,14 +715,16 @@ func (cca Client) AcquireTokenSilent(ctx context.Context, scopes []string, opts } silentParameters := base.AcquireTokenSilentParameters{ - Scopes: scopes, - Account: o.account, - RequestType: accesstokens.ATConfidential, - Credential: cca.cred, - IsAppCache: o.account.IsZero(), - TenantID: o.tenantID, - AuthnScheme: o.authnScheme, - Claims: o.claims, + Scopes: scopes, + Account: o.account, + RequestType: accesstokens.ATConfidential, + Credential: cca.cred, + IsAppCache: o.account.IsZero(), + TenantID: o.tenantID, + AuthnScheme: o.authnScheme, + Claims: o.claims, + ClientClaims: o.clientClaims, + CacheKeyComponents: o.cacheKeyComponents, } return cca.acquireTokenSilentInternal(ctx, silentParameters) @@ -647,8 +738,10 @@ func (cca Client) acquireTokenSilentInternal(ctx context.Context, silentParamete // acquireTokenByUsernamePasswordOptions contains optional configuration for AcquireTokenByUsernamePassword type acquireTokenByUsernamePasswordOptions struct { - claims, tenantID string - authnScheme AuthenticationScheme + claims, tenantID string + clientClaims string + authnScheme AuthenticationScheme + cacheKeyComponents map[string]string } // AcquireByUsernamePasswordOption is implemented by options for AcquireTokenByUsernamePassword @@ -659,7 +752,7 @@ type AcquireByUsernamePasswordOption interface { // AcquireTokenByUsernamePassword acquires a security token from the authority, via Username/Password Authentication. // NOTE: this flow is NOT recommended. // -// Options: [WithClaims], [WithTenantID] +// Options: [WithClaims], [WithClaimsFromClient], [WithTenantID] func (cca Client) AcquireTokenByUsernamePassword(ctx context.Context, scopes []string, username, password string, opts ...AcquireByUsernamePasswordOption) (AuthResult, error) { o := acquireTokenByUsernamePasswordOptions{} if err := options.ApplyOptions(&o, opts); err != nil { @@ -672,8 +765,12 @@ func (cca Client) AcquireTokenByUsernamePassword(ctx context.Context, scopes []s authParams.Scopes = scopes authParams.AuthorizationType = authority.ATUsernamePassword authParams.Claims = o.claims + authParams.ClientClaims = o.clientClaims authParams.Username = username authParams.Password = password + if o.cacheKeyComponents != nil { + authParams.CacheKeyComponents = o.cacheKeyComponents + } if o.authnScheme != nil { authParams.AuthnScheme = o.authnScheme } @@ -688,6 +785,8 @@ func (cca Client) AcquireTokenByUsernamePassword(ctx context.Context, scopes []s // acquireTokenByAuthCodeOptions contains the optional parameters used to acquire an access token using the authorization code flow. type acquireTokenByAuthCodeOptions struct { challenge, claims, tenantID string + clientClaims string + cacheKeyComponents map[string]string } // AcquireByAuthCodeOption is implemented by options for AcquireTokenByAuthCode @@ -721,7 +820,7 @@ func WithChallenge(challenge string) interface { // AcquireTokenByAuthCode is a request to acquire a security token from the authority, using an authorization code. // The specified redirect URI must be the same URI that was used when the authorization code was requested. // -// Options: [WithChallenge], [WithClaims], [WithTenantID] +// Options: [WithChallenge], [WithClaims], [WithClaimsFromClient], [WithTenantID] func (cca Client) AcquireTokenByAuthCode(ctx context.Context, code string, redirectURI string, scopes []string, opts ...AcquireByAuthCodeOption) (AuthResult, error) { o := acquireTokenByAuthCodeOptions{} if err := options.ApplyOptions(&o, opts); err != nil { @@ -729,14 +828,16 @@ func (cca Client) AcquireTokenByAuthCode(ctx context.Context, code string, redir } params := base.AcquireTokenAuthCodeParameters{ - Scopes: scopes, - Code: code, - Challenge: o.challenge, - Claims: o.claims, - AppType: accesstokens.ATConfidential, - Credential: cca.cred, // This setting differs from public.Client.AcquireTokenByAuthCode - RedirectURI: redirectURI, - TenantID: o.tenantID, + Scopes: scopes, + Code: code, + Challenge: o.challenge, + Claims: o.claims, + ClientClaims: o.clientClaims, + AppType: accesstokens.ATConfidential, + Credential: cca.cred, // This setting differs from public.Client.AcquireTokenByAuthCode + RedirectURI: redirectURI, + TenantID: o.tenantID, + CacheKeyComponents: o.cacheKeyComponents, } return cca.base.AcquireTokenByAuthCode(ctx, params) @@ -745,6 +846,7 @@ func (cca Client) AcquireTokenByAuthCode(ctx context.Context, code string, redir // acquireTokenByCredentialOptions contains optional configuration for AcquireTokenByCredential type acquireTokenByCredentialOptions struct { claims, tenantID string + clientClaims string authnScheme AuthenticationScheme extraBodyParameters map[string]string cacheKeyComponents map[string]string @@ -757,7 +859,7 @@ type AcquireByCredentialOption interface { // AcquireTokenByCredential acquires a security token from the authority, using the client credentials grant. // -// Options: [WithClaims], [WithTenantID], [WithFMIPath], [WithAttribute] +// Options: [WithClaims], [WithClaimsFromClient], [WithTenantID], [WithFMIPath], [WithAttribute] func (cca Client) AcquireTokenByCredential(ctx context.Context, scopes []string, opts ...AcquireByCredentialOption) (AuthResult, error) { o := acquireTokenByCredentialOptions{} err := options.ApplyOptions(&o, opts) @@ -771,6 +873,7 @@ func (cca Client) AcquireTokenByCredential(ctx context.Context, scopes []string, authParams.Scopes = scopes authParams.AuthorizationType = authority.ATClientCredentials authParams.Claims = o.claims + authParams.ClientClaims = o.clientClaims if o.authnScheme != nil { authParams.AuthnScheme = o.authnScheme } @@ -786,6 +889,7 @@ func (cca Client) AcquireTokenByCredential(ctx context.Context, scopes []string, TenantID: o.tenantID, AuthnScheme: o.authnScheme, Claims: o.claims, + ClientClaims: o.clientClaims, ExtraBodyParameters: o.extraBodyParameters, CacheKeyComponents: o.cacheKeyComponents, } @@ -806,7 +910,9 @@ func (cca Client) AcquireTokenByCredential(ctx context.Context, scopes []string, // acquireTokenOnBehalfOfOptions contains optional configuration for AcquireTokenOnBehalfOf type acquireTokenOnBehalfOfOptions struct { - claims, tenantID string + claims, tenantID string + clientClaims string + cacheKeyComponents map[string]string } // AcquireOnBehalfOfOption is implemented by options for AcquireTokenOnBehalfOf @@ -817,18 +923,20 @@ type AcquireOnBehalfOfOption interface { // AcquireTokenOnBehalfOf acquires a security token for an app using middle tier apps access token. // Refer https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-on-behalf-of-flow. // -// Options: [WithClaims], [WithTenantID] +// Options: [WithClaims], [WithClaimsFromClient], [WithTenantID] func (cca Client) AcquireTokenOnBehalfOf(ctx context.Context, userAssertion string, scopes []string, opts ...AcquireOnBehalfOfOption) (AuthResult, error) { o := acquireTokenOnBehalfOfOptions{} if err := options.ApplyOptions(&o, opts); err != nil { return AuthResult{}, err } params := base.AcquireTokenOnBehalfOfParameters{ - Scopes: scopes, - UserAssertion: userAssertion, - Claims: o.claims, - Credential: cca.cred, - TenantID: o.tenantID, + Scopes: scopes, + UserAssertion: userAssertion, + Claims: o.claims, + ClientClaims: o.clientClaims, + Credential: cca.cred, + TenantID: o.tenantID, + CacheKeyComponents: o.cacheKeyComponents, } return cca.base.AcquireTokenOnBehalfOf(ctx, params) } @@ -902,3 +1010,111 @@ func WithAttribute(attrValue string) interface { ), } } + +// AcquireByUserFICOption is implemented by options for AcquireTokenByUserFederatedIdentityCredential. +type AcquireByUserFICOption interface { + acquireByUserFICOption() +} + +// acquireTokenByUserFICOptions contains optional configuration for AcquireTokenByUserFederatedIdentityCredential. +type acquireTokenByUserFICOptions struct { + claims, tenantID string + clientClaims string + username string + userObjectID string + cacheKeyComponents map[string]string +} + +// acquireByUserFICOption is a marker method that restricts option types to the user_fic API. +func (acquireTokenByUserFICOptions) acquireByUserFICOption() {} + +// WithUserObjectID specifies the target user by their object ID (OID) for the user_fic flow. +// This is mutually exclusive with WithUserFICUsername. +func WithUserObjectID(oid string) interface { + AcquireByUserFICOption + options.CallOption +} { + return struct { + AcquireByUserFICOption + options.CallOption + }{ + CallOption: options.NewCallOption( + func(a any) error { + switch t := a.(type) { + case *acquireTokenByUserFICOptions: + t.userObjectID = oid + default: + return fmt.Errorf("unexpected options type %T", a) + } + return nil + }, + ), + } +} + +// WithUserFICUsername specifies the target user by their UPN (username) for the user_fic flow. +// This is mutually exclusive with WithUserObjectID. +func WithUserFICUsername(username string) interface { + AcquireByUserFICOption + options.CallOption +} { + return struct { + AcquireByUserFICOption + options.CallOption + }{ + CallOption: options.NewCallOption( + func(a any) error { + switch t := a.(type) { + case *acquireTokenByUserFICOptions: + t.username = username + default: + return fmt.Errorf("unexpected options type %T", a) + } + return nil + }, + ), + } +} + +// AcquireTokenByUserFederatedIdentityCredential acquires a user-scoped token using the user_fic grant type. +// This exchanges a federated identity credential (assertion) for a user token, enabling an agent +// to act on behalf of a user. The result includes an Account that can be used with +// [Client.AcquireTokenSilent] for subsequent cached access. +// +// Parameters: +// - ctx: Context for the request. +// - scopes: Scopes requested for the token. +// - assertion: The federated identity credential (instance token) to exchange. +// - opts: Options including user identification (exactly one of WithUserObjectID or WithUserFICUsername +// is required), [WithClaims], [WithClaimsFromClient], [WithTenantID]. +// +// Options: [WithUserObjectID], [WithUserFICUsername], [WithClaims], [WithClaimsFromClient], [WithTenantID] +func (cca Client) AcquireTokenByUserFederatedIdentityCredential(ctx context.Context, scopes []string, assertion string, opts ...AcquireByUserFICOption) (AuthResult, error) { + o := acquireTokenByUserFICOptions{} + if err := options.ApplyOptions(&o, opts); err != nil { + return AuthResult{}, err + } + + if assertion == "" { + return AuthResult{}, errors.New("assertion must not be empty") + } + if o.username == "" && o.userObjectID == "" { + return AuthResult{}, errors.New("exactly one of WithUserObjectID or WithUserFICUsername must be specified") + } + if o.username != "" && o.userObjectID != "" { + return AuthResult{}, errors.New("WithUserObjectID and WithUserFICUsername are mutually exclusive") + } + + params := base.AcquireTokenByUserFICParameters{ + Scopes: scopes, + Claims: o.claims, + ClientClaims: o.clientClaims, + Credential: cca.cred, + TenantID: o.tenantID, + UserFederatedIdentityCredential: assertion, + Username: o.username, + UserObjectID: o.userObjectID, + CacheKeyComponents: o.cacheKeyComponents, + } + return cca.base.AcquireTokenByUserFIC(ctx, params) +} diff --git a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/base/base.go b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/base/base.go index 9bd96090e..ed622e5ec 100644 --- a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/base/base.go +++ b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/base/base.go @@ -55,6 +55,7 @@ type AcquireTokenSilentParameters struct { UserAssertion string AuthorizationType authority.AuthorizeType Claims string + ClientClaims string AuthnScheme authority.AuthenticationScheme ExtraBodyParameters map[string]string CacheKeyComponents map[string]string @@ -65,22 +66,39 @@ type AcquireTokenSilentParameters struct { // Code challenges are used to secure authorization code grants; for more information, visit // https://tools.ietf.org/html/rfc7636. type AcquireTokenAuthCodeParameters struct { - Scopes []string - Code string - Challenge string - Claims string - RedirectURI string - AppType accesstokens.AppType - Credential *accesstokens.Credential - TenantID string + Scopes []string + Code string + Challenge string + Claims string + ClientClaims string + RedirectURI string + AppType accesstokens.AppType + Credential *accesstokens.Credential + TenantID string + CacheKeyComponents map[string]string } type AcquireTokenOnBehalfOfParameters struct { - Scopes []string - Claims string - Credential *accesstokens.Credential - TenantID string - UserAssertion string + Scopes []string + Claims string + ClientClaims string + Credential *accesstokens.Credential + TenantID string + UserAssertion string + CacheKeyComponents map[string]string +} + +// AcquireTokenByUserFICParameters contains the parameters to acquire a user token via the user_fic flow. +type AcquireTokenByUserFICParameters struct { + Scopes []string + Claims string + ClientClaims string + Credential *accesstokens.Credential + TenantID string + UserFederatedIdentityCredential string + Username string + UserObjectID string + CacheKeyComponents map[string]string } // AuthResult contains the results of one token acquisition operation in PublicClientApplication @@ -322,7 +340,9 @@ func (b Client) AcquireTokenSilent(ctx context.Context, silent AcquireTokenSilen authParams.HomeAccountID = silent.Account.HomeAccountID authParams.AuthorizationType = silent.AuthorizationType authParams.Claims = silent.Claims + authParams.ClientClaims = silent.ClientClaims authParams.UserAssertion = silent.UserAssertion + authParams.IsAppTokenCache = silent.IsAppCache if silent.AuthnScheme != nil { authParams.AuthnScheme = silent.AuthnScheme } @@ -414,6 +434,10 @@ func (b Client) AcquireTokenByAuthCode(ctx context.Context, authCodeParams Acqui return AuthResult{}, err } authParams.Claims = authCodeParams.Claims + authParams.ClientClaims = authCodeParams.ClientClaims + if authCodeParams.CacheKeyComponents != nil { + authParams.CacheKeyComponents = authCodeParams.CacheKeyComponents + } authParams.Scopes = authCodeParams.Scopes authParams.Redirecturi = authCodeParams.RedirectURI authParams.AuthorizationType = authority.ATAuthCode @@ -441,13 +465,15 @@ func (b Client) AcquireTokenByAuthCode(ctx context.Context, authCodeParams Acqui func (b Client) AcquireTokenOnBehalfOf(ctx context.Context, onBehalfOfParams AcquireTokenOnBehalfOfParameters) (AuthResult, error) { var ar AuthResult silentParameters := AcquireTokenSilentParameters{ - Scopes: onBehalfOfParams.Scopes, - RequestType: accesstokens.ATConfidential, - Credential: onBehalfOfParams.Credential, - UserAssertion: onBehalfOfParams.UserAssertion, - AuthorizationType: authority.ATOnBehalfOf, - TenantID: onBehalfOfParams.TenantID, - Claims: onBehalfOfParams.Claims, + Scopes: onBehalfOfParams.Scopes, + RequestType: accesstokens.ATConfidential, + Credential: onBehalfOfParams.Credential, + UserAssertion: onBehalfOfParams.UserAssertion, + AuthorizationType: authority.ATOnBehalfOf, + TenantID: onBehalfOfParams.TenantID, + Claims: onBehalfOfParams.Claims, + ClientClaims: onBehalfOfParams.ClientClaims, + CacheKeyComponents: onBehalfOfParams.CacheKeyComponents, } ar, err := b.AcquireTokenSilent(ctx, silentParameters) if err == nil { @@ -459,8 +485,12 @@ func (b Client) AcquireTokenOnBehalfOf(ctx context.Context, onBehalfOfParams Acq } authParams.AuthorizationType = authority.ATOnBehalfOf authParams.Claims = onBehalfOfParams.Claims + authParams.ClientClaims = onBehalfOfParams.ClientClaims authParams.Scopes = onBehalfOfParams.Scopes authParams.UserAssertion = onBehalfOfParams.UserAssertion + if onBehalfOfParams.CacheKeyComponents != nil { + authParams.CacheKeyComponents = onBehalfOfParams.CacheKeyComponents + } if authParams.ExtraBodyParameters != nil { authParams.ExtraBodyParameters = silentParameters.ExtraBodyParameters } @@ -471,6 +501,30 @@ func (b Client) AcquireTokenOnBehalfOf(ctx context.Context, onBehalfOfParams Acq return ar, err } +// AcquireTokenByUserFIC acquires a user-scoped token using the user_fic grant type. +func (b Client) AcquireTokenByUserFIC(ctx context.Context, params AcquireTokenByUserFICParameters) (AuthResult, error) { + authParams, err := b.AuthParams.WithTenant(params.TenantID) + if err != nil { + return AuthResult{}, err + } + authParams.AuthorizationType = authority.ATUserFIC + authParams.Claims = params.Claims + authParams.ClientClaims = params.ClientClaims + if params.CacheKeyComponents != nil { + authParams.CacheKeyComponents = params.CacheKeyComponents + } + authParams.Scopes = params.Scopes + authParams.UserFederatedIdentityCredential = params.UserFederatedIdentityCredential + authParams.Username = params.Username + authParams.UserObjectID = params.UserObjectID + + token, err := b.Token.UserFederatedIdentityCredential(ctx, authParams, params.Credential) + if err != nil { + return AuthResult{}, err + } + return b.AuthResultFromToken(ctx, authParams, token) +} + func (b Client) AuthResultFromToken(ctx context.Context, authParams authority.AuthParams, token accesstokens.TokenResponse) (AuthResult, error) { var m manager = b.manager if authParams.AuthorizationType == authority.ATOnBehalfOf { @@ -525,7 +579,7 @@ func (b Client) Account(ctx context.Context, homeAccountID string) (shared.Accou authParams := b.AuthParams // This is a copy, as we don't have a pointer receiver and .AuthParams is not a pointer. authParams.AuthorizationType = authority.AccountByID authParams.HomeAccountID = homeAccountID - key := b.AuthParams.CacheKey(false) + key := authParams.CacheKey(false) err := b.cacheAccessor.Replace(ctx, b.manager, cache.ReplaceHints{PartitionKey: key}) if err != nil { return shared.Account{}, err diff --git a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/base/storage/partitioned_storage.go b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/base/storage/partitioned_storage.go index 5d018e117..0042a1e96 100644 --- a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/base/storage/partitioned_storage.go +++ b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/base/storage/partitioned_storage.go @@ -59,7 +59,7 @@ func (m *PartitionedManager) Read(ctx context.Context, authParameters authority. // errors returned by read* methods indicate a cache miss and are therefore non-fatal. We continue populating // TokenResponse fields so that e.g. lack of an ID token doesn't prevent the caller from receiving a refresh token. - accessToken, err := m.readAccessToken(aliases, realm, clientID, userAssertionHash, scopes, partitionKeyFromRequest, tokenType, authnSchemeKeyID) + accessToken, err := m.readAccessToken(aliases, realm, clientID, userAssertionHash, scopes, partitionKeyFromRequest, tokenType, authnSchemeKeyID, authParameters.CacheExtKeyGenerator()) if err == nil { tr.AccessToken = accessToken } @@ -125,6 +125,7 @@ func (m *PartitionedManager) Write(authParameters authority.AuthParams, tokenRes if authParameters.AuthorizationType == authority.ATOnBehalfOf { accessToken.UserAssertionHash = userAssertionHash // get Hash method on this } + accessToken.ExtCacheKey = authParameters.CacheExtKeyGenerator() // Since we have a valid access token, cache it before moving on. if err := accessToken.Validate(); err == nil { @@ -250,7 +251,7 @@ func (m *PartitionedManager) fallbackMetadata(host string) authority.InstanceDis return fallback } -func (m *PartitionedManager) readAccessToken(envAliases []string, realm, clientID, userAssertionHash string, scopes []string, partitionKey, tokenType, authnSchemeKeyID string) (AccessToken, error) { +func (m *PartitionedManager) readAccessToken(envAliases []string, realm, clientID, userAssertionHash string, scopes []string, partitionKey, tokenType, authnSchemeKeyID, extCacheKey string) (AccessToken, error) { m.contractMu.RLock() defer m.contractMu.RUnlock() if accessTokens, ok := m.contract.AccessTokensPartition[partitionKey]; ok { @@ -262,6 +263,13 @@ func (m *PartitionedManager) readAccessToken(envAliases []string, realm, clientI if at.TokenType == tokenType && at.AuthnSchemeKeyID == authnSchemeKeyID { if checkAlias(at.Environment, envAliases) { if isMatchingScopes(scopes, at.Scopes) { + // Tokens acquired with extra cache-key components (e.g. client claims + // via WithClaimsFromClient) are partitioned by ExtCacheKey. Only return a + // token whose ExtCacheKey matches the request's; this also ensures a + // request without extra components never returns a hashed token. + if at.ExtCacheKey != extCacheKey { + continue + } return at, nil } } diff --git a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/exported/exported.go b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/exported/exported.go index de1bf381f..a1c6ed4da 100644 --- a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/exported/exported.go +++ b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/exported/exported.go @@ -11,6 +11,10 @@ type AssertionRequestOptions struct { // TokenEndpoint is the intended token endpoint. Used as the assertion's "aud" claim. TokenEndpoint string + + // FMIPath is the federated managed identity path for the current request, if any. + // Assertion providers can use this to scope the credential they return. + FMIPath string } // TokenProviderParameters is the authentication parameters passed to token providers diff --git a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json/json.go b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json/json.go index 2134e57c9..b0311ca6d 100644 --- a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json/json.go +++ b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json/json.go @@ -58,7 +58,26 @@ func Marshal(i interface{}) ([]byte, error) { // Unmarshal unmarshals a []byte representing JSON into i, which must be a *struct. In addition, if the struct has // a field called AdditionalFields of type map[string]interface{}, JSON data representing fields not in the struct // will be written as key/value pairs to AdditionalFields. -func Unmarshal(b []byte, i interface{}) error { +// +// Any panic that escapes the underlying reflect-based decoder (for example +// "reflect: New of type that may not be allocated in heap") is recovered and +// returned as an error so that callers are not crashed by malformed or +// otherwise unexpected input. +// +// IMPORTANT: when Unmarshal returns a non-nil error, the destination i may +// have been partially populated (the decoder writes fields sequentially and a +// panic mid-decode does not roll back earlier writes). Callers that need +// all-or-nothing semantics — particularly those handling untrusted input — +// must decode into a temporary value and only copy/assign it on success. +// MSAL's own token-cache callers already follow this pattern (see +// apps/internal/base/storage.Manager.Unmarshal). +func Unmarshal(b []byte, i interface{}) (err error) { + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("json: panic during Unmarshal: %v", r) + } + }() + if len(b) == 0 { return nil } diff --git a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/oauth.go b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/oauth.go index 738a29eb9..9b7d3a5f4 100644 --- a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/oauth.go +++ b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/oauth.go @@ -39,6 +39,7 @@ type AccessTokens interface { FromUserAssertionClientCertificate(ctx context.Context, authParameters authority.AuthParams, userAssertion string, assertion string) (accesstokens.TokenResponse, error) FromDeviceCodeResult(ctx context.Context, authParameters authority.AuthParams, deviceCodeResult accesstokens.DeviceCodeResult) (accesstokens.TokenResponse, error) FromSamlGrant(ctx context.Context, authParameters authority.AuthParams, samlGrant wstrust.SamlTokenInfo) (accesstokens.TokenResponse, error) + FromUserFederatedIdentityCredential(ctx context.Context, authParameters authority.AuthParams, cred *accesstokens.Credential) (accesstokens.TokenResponse, error) } // FetchAuthority will be implemented by authority.Authority. @@ -170,6 +171,17 @@ func (t *Client) OnBehalfOf(ctx context.Context, authParams authority.AuthParams return tr, nil } +// UserFederatedIdentityCredential acquires a user-scoped token using the user_fic grant type. +func (t *Client) UserFederatedIdentityCredential(ctx context.Context, authParams authority.AuthParams, cred *accesstokens.Credential) (accesstokens.TokenResponse, error) { + if err := scopeError(authParams); err != nil { + return accesstokens.TokenResponse{}, err + } + if err := t.resolveEndpoint(ctx, &authParams, ""); err != nil { + return accesstokens.TokenResponse{}, err + } + return t.AccessTokens.FromUserFederatedIdentityCredential(ctx, authParams, cred) +} + func (t *Client) Refresh(ctx context.Context, reqType accesstokens.AppType, authParams authority.AuthParams, cc *accesstokens.Credential, refreshToken accesstokens.RefreshToken) (accesstokens.TokenResponse, error) { if err := scopeError(authParams); err != nil { return accesstokens.TokenResponse{}, err diff --git a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/accesstokens/accesstokens.go b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/accesstokens/accesstokens.go index 481f9e434..dc07c558d 100644 --- a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/accesstokens/accesstokens.go +++ b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/accesstokens/accesstokens.go @@ -110,6 +110,7 @@ func (c *Credential) JWT(ctx context.Context, authParams authority.AuthParams) ( options := exported.AssertionRequestOptions{ ClientID: authParams.ClientID, TokenEndpoint: authParams.Endpoints.TokenEndpoint, + FMIPath: authParams.ExtraBodyParameters["fmi_path"], } return c.AssertionCallback(ctx, options) } @@ -340,6 +341,44 @@ func (c Client) FromUserAssertionClientCertificate(ctx context.Context, authPara return c.doTokenResp(ctx, authParameters, qv) } +// FromUserFederatedIdentityCredential acquires a user-scoped token using the user_fic grant type. +// This exchanges a federated identity credential for a user token. +func (c Client) FromUserFederatedIdentityCredential(ctx context.Context, authParameters authority.AuthParams, cred *Credential) (TokenResponse, error) { + if cred.Secret == "" && cred.Cert == nil && cred.AssertionCallback == nil { + return TokenResponse{}, fmt.Errorf("user_fic requires a client secret or assertion credential; token provider credentials are not supported") + } + qv := url.Values{} + if err := addClaims(qv, authParameters); err != nil { + return TokenResponse{}, err + } + qv.Set(grantType, grant.UserFIC) + qv.Set(clientID, authParameters.ClientID) + qv.Set("user_federated_identity_credential", authParameters.UserFederatedIdentityCredential) + qv.Set(clientInfo, clientInfoVal) + + // Set user identifier: either user_id (OID) or username (UPN) + if authParameters.UserObjectID != "" { + qv.Set("user_id", authParameters.UserObjectID) + } else if authParameters.Username != "" { + qv.Set("username", authParameters.Username) + } + + addScopeQueryParam(qv, authParameters) + addExtraBodyParameters(ctx, qv, authParameters) + + credParams, err := prepURLVals(ctx, cred, authParameters) + if err != nil { + return TokenResponse{}, err + } + for k, vs := range credParams { + for _, v := range vs { + qv.Set(k, v) + } + } + + return c.doTokenResp(ctx, authParameters, qv) +} + func (c Client) DeviceCodeResult(ctx context.Context, authParameters authority.AuthParams) (DeviceCodeResult, error) { qv := url.Values{} if err := addClaims(qv, authParameters); err != nil { diff --git a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/accesstokens/tokens.go b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/accesstokens/tokens.go index 32dde7b76..eeb149697 100644 --- a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/accesstokens/tokens.go +++ b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/accesstokens/tokens.go @@ -292,7 +292,11 @@ func (tr *TokenResponse) CacheKey(authParams authority.AuthParams) string { if authParams.AuthorizationType == authority.ATOnBehalfOf { return authParams.AssertionHash() } - if authParams.AuthorizationType == authority.ATClientCredentials { + // An app-only (client credentials) request keys on the app-token-cache partition. + // AcquireTokenSilent overrides AuthorizationType to ATRefreshToken before the + // proactive-refresh write-back, so check IsAppTokenCache here to keep the write key + // aligned with the read key (authParams.CacheKey). See issue #630. + if authParams.AuthorizationType == authority.ATClientCredentials || authParams.IsAppTokenCache { return authParams.AppKey() } if authParams.IsConfidentialClient || authParams.AuthorizationType == authority.ATRefreshToken { diff --git a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/authority/authority.go b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/authority/authority.go index db6f8b42e..07702ef77 100644 --- a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/authority/authority.go +++ b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/authority/authority.go @@ -14,7 +14,7 @@ import ( "net/http" "net/url" "os" - "path" + "regexp" "sort" "strings" "time" @@ -29,8 +29,8 @@ const ( aadInstanceDiscoveryEndpoint = "https://%v/common/discovery/instance" tenantDiscoveryEndpointWithRegion = "https://%s.%s/%s/v2.0/.well-known/openid-configuration" regionName = "REGION_NAME" - defaultAPIVersion = "2021-10-01" - imdsEndpoint = "http://169.254.169.254/metadata/instance/compute/location?format=text&api-version=" + defaultAPIVersion + defaultAPIVersion = "2021-02-01" + imdsEndpoint = "http://169.254.169.254/metadata/instance/compute?api-version=" + defaultAPIVersion autoDetectRegion = "TryAutoDetect" AccessTokenTypeBearer = "Bearer" ) @@ -44,6 +44,9 @@ const ( loginMicrosoftOnline = defaultHost ) +// validRegion matches Azure region names: lowercase alphanumeric and hyphens only. +var validRegion = regexp.MustCompile(`^[a-z][a-z0-9-]*$`) + // jsonCaller is an interface that allows us to mock the JSONCall method. type jsonCaller interface { JSONCall(ctx context.Context, endpoint string, headers http.Header, qv url.Values, body, resp interface{}) error @@ -188,6 +191,7 @@ const ( ATRefreshToken AccountByID ATOnBehalfOf + ATUserFIC ) // These are all authority types @@ -269,6 +273,11 @@ type AuthParams struct { Capabilities ClientCapabilities // Claims required for an access token to satisfy a conditional access policy Claims string + // ClientClaims are client-originated claims set via the request-level WithClaimsFromClient option. + // Unlike Claims (server-issued challenge claims, which bypass the cache), ClientClaims participate + // in the token cache and are keyed on the raw claims string as passed by the caller. They are merged + // with Claims and Capabilities into the request's "claims" parameter. + ClientClaims string // KnownAuthorityHosts don't require metadata discovery because they're known to the user KnownAuthorityHosts []string // LoginHint is a username with which to pre-populate account selection during interactive auth @@ -283,6 +292,15 @@ type AuthParams struct { ExtraBodyParameters map[string]string // CacheKeyComponents are additional components to include in the cache key. CacheKeyComponents map[string]string + // IsAppTokenCache indicates the request targets the app-only (client credentials) + // token cache partition. It is propagated onto silent requests so the proactive-refresh + // write-back computes the same partition key as the read path, even though + // AcquireTokenSilent overrides AuthorizationType to ATRefreshToken. See issue #630. + IsAppTokenCache bool + // UserFederatedIdentityCredential is the federated credential token for user_fic flow. + UserFederatedIdentityCredential string + // UserObjectID is the target user's object ID for user_fic flow (mutually exclusive with Username). + UserObjectID string } // NewAuthParams creates an authorization parameters object. @@ -316,7 +334,11 @@ func (p AuthParams) WithTenant(ID string) (AuthParams, error) { if p.AuthorityInfo.Tenant == "consumers" { return p, errors.New(`client is configured to authenticate only personal Microsoft accounts, via the "consumers" endpoint`) } - authority = "https://" + path.Join(p.AuthorityInfo.Host, ID) + authority = (&url.URL{ + Scheme: "https", + Host: p.AuthorityInfo.Host, + Path: "/", + }).ResolveReference(&url.URL{Path: ID}).String() case ADFS: return p, errors.New("ADFS authority doesn't support tenants") case DSTS: @@ -331,9 +353,15 @@ func (p AuthParams) WithTenant(ID string) (AuthParams, error) { return p, err } -// MergeCapabilitiesAndClaims combines client capabilities and challenge claims into a value suitable for an authentication request's "claims" parameter. +// MergeCapabilitiesAndClaims combines client capabilities, server-issued challenge claims and +// client-originated claims into a value suitable for an authentication request's "claims" parameter. func (p AuthParams) MergeCapabilitiesAndClaims() (string, error) { - claims := p.Claims + // Combine server-issued claims (from WithClaims) with client-originated claims + // (from WithClaimsFromClient). When both set the same key, the client claims win. + claims, err := mergeClaims(p.Claims, p.ClientClaims) + if err != nil { + return "", err + } if len(p.Capabilities.asMap) > 0 { if claims == "" { // without claims the result is simply the capabilities @@ -357,6 +385,65 @@ func (p AuthParams) MergeCapabilitiesAndClaims() (string, error) { return claims, nil } +// mergeClaims merges two JSON claims objects into one. If either side is empty the other is returned +// verbatim and unvalidated (the common case; this keeps the value byte-for-byte identical to what the +// caller passed and mirrors MSAL .NET's MergeClaimsObjects). Only when both sides are present are they +// parsed as JSON objects (anything that is not a JSON object is an error), deep-merged with the second +// object's values winning on conflicting keys, and re-serialized. +func mergeClaims(claims1, claims2 string) (string, error) { + if claims1 == "" { + return claims2, nil + } + if claims2 == "" { + return claims1, nil + } + m1, err := parseClaimsObject(claims1) + if err != nil { + return "", err + } + m2, err := parseClaimsObject(claims2) + if err != nil { + return "", err + } + b, err := json.Marshal(deepMergeClaims(m1, m2)) + if err != nil { + return "", err + } + return string(b), nil +} + +// parseClaimsObject unmarshals a non-empty claims string into a JSON object. A value that is valid +// JSON but not an object (e.g. an array, a scalar, or the literal "null") is rejected, mirroring the +// behavior of the other MSAL libraries. +func parseClaimsObject(claims string) (map[string]any, error) { + var m map[string]any + if err := json.Unmarshal([]byte(claims), &m); err != nil { + // Don't include the parser error or the raw value in the message: claims may carry sensitive data. + return nil, errors.New("claims must be a JSON object") + } + if m == nil { + return nil, errors.New("claims must be a JSON object") + } + return m, nil +} + +// deepMergeClaims merges src into dst, with src's values winning on conflicting keys. When both +// values for a key are JSON objects the merge recurses; otherwise src's value overwrites dst's. +func deepMergeClaims(dst, src map[string]any) map[string]any { + for k, sv := range src { + if dv, ok := dst[k]; ok { + if dm, dok := dv.(map[string]any); dok { + if sm, sok := sv.(map[string]any); sok { + dst[k] = deepMergeClaims(dm, sm) + continue + } + } + } + dst[k] = sv + } + return dst +} + // merges a into b without overwriting b's values. Returns an error when a and b share a key for which either has a non-object value. func merge(a, b map[string]any) error { for k, av := range a { @@ -587,6 +674,9 @@ func (c Client) AADInstanceDiscovery(ctx context.Context, authorityInfo Info) (I region = detectRegion(ctx) } if region != "" { + if !validRegion.MatchString(region) { + return resp, fmt.Errorf("invalid region %q: region must contain only lowercase alphanumeric characters and hyphens", region) + } environment := authorityInfo.Host switch environment { case loginMicrosoft, loginWindows, loginSTSWindows, defaultHost: @@ -653,7 +743,24 @@ func detectRegion(ctx context.Context) string { if err != nil { return "" } - return string(response) + return parseRegionFromIMDSResponse(response) +} + +// imdsComputeResponse models the subset of the IMDS compute metadata response +// (http://169.254.169.254/metadata/instance/compute) used for region detection. +type imdsComputeResponse struct { + Location string `json:"location"` +} + +// parseRegionFromIMDSResponse extracts the Azure region from an IMDS compute +// metadata JSON response body. It returns an empty string when the body cannot +// be parsed or the location field is absent. +func parseRegionFromIMDSResponse(body []byte) string { + var parsed imdsComputeResponse + if err := json.Unmarshal(body, &parsed); err != nil { + return "" + } + return parsed.Location } func (a *AuthParams) CacheKey(isAppCache bool) string { @@ -663,7 +770,7 @@ func (a *AuthParams) CacheKey(isAppCache bool) string { if a.AuthorizationType == ATClientCredentials || isAppCache { return a.AppKey() } - if a.AuthorizationType == ATRefreshToken || a.AuthorizationType == AccountByID { + if a.AuthorizationType == ATRefreshToken || a.AuthorizationType == AccountByID || a.AuthorizationType == ATUserFIC { return a.HomeAccountID } return "" @@ -706,13 +813,18 @@ func (a *AuthParams) CacheExtKeyGenerator() string { } sort.Strings(keys) - // Create a string by concatenating key+value pairs - keyStr := "" + // Concatenate length-prefixed key/value pairs so the boundaries between + // components are unambiguous. A plain key+value concatenation with no + // separators can collide when a value happens to contain another component's + // key or value (client_claims, for example, is arbitrary caller-supplied + // JSON), which would map two distinct component sets to the same hash and + // return the wrong cached token. Length prefixes make the encoding injective. + var sb strings.Builder for _, key := range keys { - // Append key followed by its value with no separator - keyStr += key + a.CacheKeyComponents[key] + val := a.CacheKeyComponents[key] + fmt.Fprintf(&sb, "%d:%s%d:%s", len(key), key, len(val), val) } - hash := sha256.Sum256([]byte(keyStr)) + hash := sha256.Sum256([]byte(sb.String())) return strings.ToLower(base64.RawURLEncoding.EncodeToString(hash[:])) } diff --git a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/authority/authorizetype_string.go b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/authority/authorizetype_string.go index 10039773b..7d93930cc 100644 --- a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/authority/authorizetype_string.go +++ b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/authority/authorizetype_string.go @@ -16,15 +16,19 @@ func _() { _ = x[ATClientCredentials-5] _ = x[ATDeviceCode-6] _ = x[ATRefreshToken-7] + _ = x[AccountByID-8] + _ = x[ATOnBehalfOf-9] + _ = x[ATUserFIC-10] } -const _AuthorizeType_name = "ATUnknownATUsernamePasswordATWindowsIntegratedATAuthCodeATInteractiveATClientCredentialsATDeviceCodeATRefreshToken" +const _AuthorizeType_name = "ATUnknownATUsernamePasswordATWindowsIntegratedATAuthCodeATInteractiveATClientCredentialsATDeviceCodeATRefreshTokenAccountByIDATOnBehalfOfATUserFIC" -var _AuthorizeType_index = [...]uint8{0, 9, 27, 46, 56, 69, 88, 100, 114} +var _AuthorizeType_index = [...]uint8{0, 9, 27, 46, 56, 69, 88, 100, 114, 125, 137, 146} func (i AuthorizeType) String() string { - if i < 0 || i >= AuthorizeType(len(_AuthorizeType_index)-1) { + idx := int(i) - 0 + if i < 0 || idx >= len(_AuthorizeType_index)-1 { return "AuthorizeType(" + strconv.FormatInt(int64(i), 10) + ")" } - return _AuthorizeType_name[_AuthorizeType_index[i]:_AuthorizeType_index[i+1]] + return _AuthorizeType_name[_AuthorizeType_index[idx]:_AuthorizeType_index[idx+1]] } diff --git a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/internal/grant/grant.go b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/internal/grant/grant.go index b628f61ac..f09bff390 100644 --- a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/internal/grant/grant.go +++ b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/internal/grant/grant.go @@ -14,4 +14,5 @@ const ( RefreshToken = "refresh_token" ClientCredential = "client_credentials" ClientAssertion = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" + UserFIC = "user_fic" ) diff --git a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/resolvers.go b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/resolvers.go index d220a9946..92170a762 100644 --- a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/resolvers.go +++ b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/resolvers.go @@ -16,6 +16,7 @@ import ( "github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops" "github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/authority" + "golang.org/x/sync/singleflight" ) type cacheEntry struct { @@ -35,6 +36,8 @@ type authorityEndpoint struct { mu sync.Mutex cache map[string]cacheEntry + + resolveGroup singleflight.Group } // newAuthorityEndpoint is the constructor for AuthorityEndpoint. @@ -50,35 +53,49 @@ func (m *authorityEndpoint) ResolveEndpoints(ctx context.Context, authorityInfo return endpoints, nil } - endpoint, err := m.openIDConfigurationEndpoint(ctx, authorityInfo) - if err != nil { - return authority.Endpoints{}, err - } + key := authorityInfo.CanonicalAuthorityURI + v, err, _ := m.resolveGroup.Do(key, func() (interface{}, error) { + // Double-check inside the singleflight group: another goroutine may + // have populated the cache while we were waiting. + if endpoints, found := m.cachedEndpoints(authorityInfo, userPrincipalName); found { + return endpoints, nil + } - resp, err := m.rest.Authority().GetTenantDiscoveryResponse(ctx, endpoint) - if err != nil { - return authority.Endpoints{}, err - } - if err := resp.Validate(); err != nil { - return authority.Endpoints{}, fmt.Errorf("ResolveEndpoints(): %w", err) - } + endpoint, err := m.openIDConfigurationEndpoint(ctx, authorityInfo) + if err != nil { + return authority.Endpoints{}, err + } + + resp, err := m.rest.Authority().GetTenantDiscoveryResponse(ctx, endpoint) + if err != nil { + return authority.Endpoints{}, err + } + if err := resp.Validate(); err != nil { + return authority.Endpoints{}, fmt.Errorf("ResolveEndpoints(): %w", err) + } - tenant := authorityInfo.Tenant + tenant := authorityInfo.Tenant - endpoints := authority.NewEndpoints( - strings.Replace(resp.AuthorizationEndpoint, "{tenant}", tenant, -1), - strings.Replace(resp.TokenEndpoint, "{tenant}", tenant, -1), - strings.Replace(resp.Issuer, "{tenant}", tenant, -1), - authorityInfo.Host) + endpoints := authority.NewEndpoints( + strings.Replace(resp.AuthorizationEndpoint, "{tenant}", tenant, -1), + strings.Replace(resp.TokenEndpoint, "{tenant}", tenant, -1), + strings.Replace(resp.Issuer, "{tenant}", tenant, -1), + authorityInfo.Host) - m.addCachedEndpoints(authorityInfo, userPrincipalName, endpoints) + aliases := m.addCachedEndpoints(authorityInfo, userPrincipalName, endpoints) - if err := resp.ValidateIssuerMatchesAuthority(authorityInfo.CanonicalAuthorityURI, - m.cache[authorityInfo.CanonicalAuthorityURI].Aliases); err != nil { - return authority.Endpoints{}, fmt.Errorf("ResolveEndpoints(): %w", err) + if err := resp.ValidateIssuerMatchesAuthority(authorityInfo.CanonicalAuthorityURI, + aliases); err != nil { + return authority.Endpoints{}, fmt.Errorf("ResolveEndpoints(): %w", err) + } + + return endpoints, nil + }) + if err != nil { + return authority.Endpoints{}, err } - return endpoints, nil + return v.(authority.Endpoints), nil } // cachedEndpoints returns the cached endpoints if they exist. If not, we return false. @@ -100,7 +117,7 @@ func (m *authorityEndpoint) cachedEndpoints(authorityInfo authority.Info, userPr return authority.Endpoints{}, false } -func (m *authorityEndpoint) addCachedEndpoints(authorityInfo authority.Info, userPrincipalName string, endpoints authority.Endpoints) { +func (m *authorityEndpoint) addCachedEndpoints(authorityInfo authority.Info, userPrincipalName string, endpoints authority.Endpoints) map[string]bool { m.mu.Lock() defer m.mu.Unlock() @@ -128,6 +145,7 @@ func (m *authorityEndpoint) addCachedEndpoints(authorityInfo authority.Info, use } m.cache[authorityInfo.CanonicalAuthorityURI] = updatedCacheEntry + return updatedCacheEntry.Aliases } func (m *authorityEndpoint) openIDConfigurationEndpoint(ctx context.Context, authorityInfo authority.Info) (string, error) { diff --git a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/version/version.go b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/version/version.go index 40da7d5d1..d19d8376a 100644 --- a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/version/version.go +++ b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/version/version.go @@ -8,4 +8,4 @@ package version const SKU = "MSAL.Go" // Version is the version of this client package that is communicated to the server. -const Version = "1.7.2" +const Version = "1.8.0" diff --git a/vendor/modules.txt b/vendor/modules.txt index b2234ac6f..af3afa1d0 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -55,7 +55,7 @@ github.com/Azure/azure-sdk-for-go/sdk/internal/uuid ## explicit; go 1.16 github.com/Azure/go-ansiterm github.com/Azure/go-ansiterm/winterm -# github.com/AzureAD/microsoft-authentication-library-for-go v1.7.2 +# github.com/AzureAD/microsoft-authentication-library-for-go v1.8.0 ## explicit; go 1.18 github.com/AzureAD/microsoft-authentication-library-for-go/apps/cache github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential