Skip to content

Commit 8ac5ff5

Browse files
authored
Adding support for FMI (Federated Managed Identity) (#586)
* added the implementation for extensible cache * Added support for WithExtraBodyParams for silent client * Updated failing test for cache * Updated and separated cache components and extra body params Added withExtraBodyParameters to OBO and Auth code for confidentialClients * added withFMIPath api * resolve comments * Pulled from main and merged * Resolved issues with e2e Tests * Removed un used test Added withFMIPath example * removed unused code. * Removed some unnecessary boiler plate for some function * Added withAttribute for AcquireTokenByCredential * Update confidential_test.go Removed un necessary conditions * Update federated_managed_identity.md added example how to use withAttribute. * Removed example that wont compile Example for same test is already there in example_test.go * suppressed the warning for tests * Updated API method desc * Updated the test for Claims to use the public API
1 parent f7579f7 commit 8ac5ff5

File tree

15 files changed

+1655
-183
lines changed

15 files changed

+1655
-183
lines changed

apps/confidential/confidential.go

Lines changed: 93 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -601,21 +601,23 @@ func (cca Client) AcquireTokenSilent(ctx context.Context, scopes []string, opts
601601
return AuthResult{}, errors.New("WithSilentAccount option is required")
602602
}
603603

604-
return cca.acquireTokenSilentInternal(ctx, scopes, o.account, o.claims, o.tenantID, o.authnScheme)
605-
}
606-
607-
// acquireTokenSilentInternal is the internal implementation shared by AcquireTokenSilent and AcquireTokenByCredential
608-
func (cca Client) acquireTokenSilentInternal(ctx context.Context, scopes []string, account Account, claims, tenantID string, authnScheme AuthenticationScheme) (AuthResult, error) {
609604
silentParameters := base.AcquireTokenSilentParameters{
610605
Scopes: scopes,
611-
Account: account,
606+
Account: o.account,
612607
RequestType: accesstokens.ATConfidential,
613608
Credential: cca.cred,
614-
IsAppCache: account.IsZero(),
615-
TenantID: tenantID,
616-
AuthnScheme: authnScheme,
617-
Claims: claims,
609+
IsAppCache: o.account.IsZero(),
610+
TenantID: o.tenantID,
611+
AuthnScheme: o.authnScheme,
612+
Claims: o.claims,
618613
}
614+
615+
return cca.acquireTokenSilentInternal(ctx, silentParameters)
616+
}
617+
618+
// acquireTokenSilentInternal is the internal implementation shared by AcquireTokenSilent and AcquireTokenByCredential
619+
func (cca Client) acquireTokenSilentInternal(ctx context.Context, silentParameters base.AcquireTokenSilentParameters) (AuthResult, error) {
620+
619621
return cca.base.AcquireTokenSilent(ctx, silentParameters)
620622
}
621623

@@ -718,8 +720,10 @@ func (cca Client) AcquireTokenByAuthCode(ctx context.Context, code string, redir
718720

719721
// acquireTokenByCredentialOptions contains optional configuration for AcquireTokenByCredential
720722
type acquireTokenByCredentialOptions struct {
721-
claims, tenantID string
722-
authnScheme AuthenticationScheme
723+
claims, tenantID string
724+
authnScheme AuthenticationScheme
725+
extraBodyParameters map[string]string
726+
cacheKeyComponents map[string]string
723727
}
724728

725729
// AcquireByCredentialOption is implemented by options for AcquireTokenByCredential
@@ -729,7 +733,7 @@ type AcquireByCredentialOption interface {
729733

730734
// AcquireTokenByCredential acquires a security token from the authority, using the client credentials grant.
731735
//
732-
// Options: [WithClaims], [WithTenantID]
736+
// Options: [WithClaims], [WithTenantID], [WithFMIPath], [WithAttribute]
733737
func (cca Client) AcquireTokenByCredential(ctx context.Context, scopes []string, opts ...AcquireByCredentialOption) (AuthResult, error) {
734738
o := acquireTokenByCredentialOptions{}
735739
err := options.ApplyOptions(&o, opts)
@@ -746,9 +750,24 @@ func (cca Client) AcquireTokenByCredential(ctx context.Context, scopes []string,
746750
if o.authnScheme != nil {
747751
authParams.AuthnScheme = o.authnScheme
748752
}
753+
authParams.ExtraBodyParameters = o.extraBodyParameters
754+
authParams.CacheKeyComponents = o.cacheKeyComponents
749755
if o.claims == "" {
756+
silentParameters := base.AcquireTokenSilentParameters{
757+
Scopes: scopes,
758+
Account: Account{}, // empty account for app token
759+
RequestType: accesstokens.ATConfidential,
760+
Credential: cca.cred,
761+
IsAppCache: true,
762+
TenantID: o.tenantID,
763+
AuthnScheme: o.authnScheme,
764+
Claims: o.claims,
765+
ExtraBodyParameters: o.extraBodyParameters,
766+
CacheKeyComponents: o.cacheKeyComponents,
767+
}
768+
750769
// Use internal method with empty account (service principal scenario)
751-
cache, err := cca.acquireTokenSilentInternal(ctx, scopes, Account{}, o.claims, o.tenantID, authParams.AuthnScheme)
770+
cache, err := cca.acquireTokenSilentInternal(ctx, silentParameters)
752771
if err == nil {
753772
return cache, nil
754773
}
@@ -799,3 +818,63 @@ func (cca Client) Account(ctx context.Context, accountID string) (Account, error
799818
func (cca Client) RemoveAccount(ctx context.Context, account Account) error {
800819
return cca.base.RemoveAccount(ctx, account)
801820
}
821+
822+
// WithFMIPath specifies the path to a federated managed identity.
823+
// The path should point to a valid FMI configuration file that contains the necessary
824+
// identity information for authentication.
825+
func WithFMIPath(path string) interface {
826+
AcquireByCredentialOption
827+
options.CallOption
828+
} {
829+
return struct {
830+
AcquireByCredentialOption
831+
options.CallOption
832+
}{
833+
CallOption: options.NewCallOption(
834+
func(a any) error {
835+
switch t := a.(type) {
836+
case *acquireTokenByCredentialOptions:
837+
if t.extraBodyParameters == nil {
838+
t.extraBodyParameters = make(map[string]string)
839+
}
840+
if t.cacheKeyComponents == nil {
841+
t.cacheKeyComponents = make(map[string]string)
842+
}
843+
t.cacheKeyComponents["fmi_path"] = path
844+
t.extraBodyParameters["fmi_path"] = path
845+
default:
846+
return fmt.Errorf("unexpected options type %T", a)
847+
}
848+
return nil
849+
},
850+
),
851+
}
852+
}
853+
854+
// WithAttribute specifies an identity attribute to include in the token request.
855+
// The attribute is sent as "attributes" in the request body and returned as "xmc_attr"
856+
// in the access token claims. This is sometimes used withFMIPath
857+
func WithAttribute(attrValue string) interface {
858+
AcquireByCredentialOption
859+
options.CallOption
860+
} {
861+
return struct {
862+
AcquireByCredentialOption
863+
options.CallOption
864+
}{
865+
CallOption: options.NewCallOption(
866+
func(a any) error {
867+
switch t := a.(type) {
868+
case *acquireTokenByCredentialOptions:
869+
if t.extraBodyParameters == nil {
870+
t.extraBodyParameters = make(map[string]string)
871+
}
872+
t.extraBodyParameters["attributes"] = attrValue
873+
default:
874+
return fmt.Errorf("unexpected options type %T", a)
875+
}
876+
return nil
877+
},
878+
),
879+
}
880+
}

0 commit comments

Comments
 (0)