confidential.go raw

   1  // Copyright (c) Microsoft Corporation.
   2  // Licensed under the MIT license.
   3  
   4  /*
   5  Package confidential provides a client for authentication of "confidential" applications.
   6  A "confidential" application is defined as an app that run on servers. They are considered
   7  difficult to access and for that reason capable of keeping an application secret.
   8  Confidential clients can hold configuration-time secrets.
   9  */
  10  package confidential
  11  
  12  import (
  13  	"context"
  14  	"crypto"
  15  	"crypto/rsa"
  16  	"crypto/x509"
  17  	"encoding/base64"
  18  	"encoding/pem"
  19  	"errors"
  20  	"fmt"
  21  	"os"
  22  	"strings"
  23  
  24  	"github.com/AzureAD/microsoft-authentication-library-for-go/apps/cache"
  25  	"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/base"
  26  	"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/exported"
  27  	"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth"
  28  	"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops"
  29  	"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/accesstokens"
  30  	"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/authority"
  31  	"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/options"
  32  	"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/shared"
  33  )
  34  
  35  /*
  36  Design note:
  37  
  38  confidential.Client uses base.Client as an embedded type. base.Client statically assigns its attributes
  39  during creation. As it doesn't have any pointers in it, anything borrowed from it, such as
  40  Base.AuthParams is a copy that is free to be manipulated here.
  41  
  42  Duplicate Calls shared between public.Client and this package:
  43  There is some duplicate call options provided here that are the same as in public.Client . This
  44  is a design choices. Go proverb(https://www.youtube.com/watch?v=PAAkCSZUG1c&t=9m28s):
  45  "a little copying is better than a little dependency". Yes, we could have another package with
  46  shared options (fail).  That divides like 2 options from all others which makes the user look
  47  through more docs.  We can have all clients in one package, but I think separate packages
  48  here makes for better naming (public.Client vs client.PublicClient).  So I chose a little
  49  duplication.
  50  
  51  .Net People, Take note on X509:
  52  This uses x509.Certificates and private keys. x509 does not store private keys. .Net
  53  has a x509.Certificate2 abstraction that has private keys, but that just a strange invention.
  54  As such I've put a PEM decoder into here.
  55  */
  56  
  57  // TODO(msal): This should have example code for each method on client using Go's example doc framework.
  58  // base usage details should be include in the package documentation.
  59  
  60  // AuthResult contains the results of one token acquisition operation.
  61  // For details see https://aka.ms/msal-net-authenticationresult
  62  type AuthResult = base.AuthResult
  63  
  64  type AuthenticationScheme = authority.AuthenticationScheme
  65  
  66  type Account = shared.Account
  67  
  68  type TokenSource = base.TokenSource
  69  
  70  const (
  71  	TokenSourceIdentityProvider = base.TokenSourceIdentityProvider
  72  	TokenSourceCache            = base.TokenSourceCache
  73  )
  74  
  75  // CertFromPEM converts a PEM file (.pem or .key) for use with [NewCredFromCert]. The file
  76  // must contain the public certificate and the private key. If a PEM block is encrypted and
  77  // password is not an empty string, it attempts to decrypt the PEM blocks using the password.
  78  // Multiple certs are due to certificate chaining for use cases like TLS that sign from root to leaf.
  79  func CertFromPEM(pemData []byte, password string) ([]*x509.Certificate, crypto.PrivateKey, error) {
  80  	var certs []*x509.Certificate
  81  	var priv crypto.PrivateKey
  82  	for {
  83  		block, rest := pem.Decode(pemData)
  84  		if block == nil {
  85  			break
  86  		}
  87  
  88  		//nolint:staticcheck // x509.IsEncryptedPEMBlock and x509.DecryptPEMBlock are deprecated. They are used here only to support a usecase.
  89  		if x509.IsEncryptedPEMBlock(block) {
  90  			b, err := x509.DecryptPEMBlock(block, []byte(password))
  91  			if err != nil {
  92  				return nil, nil, fmt.Errorf("could not decrypt encrypted PEM block: %v", err)
  93  			}
  94  			block, _ = pem.Decode(b)
  95  			if block == nil {
  96  				return nil, nil, fmt.Errorf("encounter encrypted PEM block that did not decode")
  97  			}
  98  		}
  99  
 100  		switch block.Type {
 101  		case "CERTIFICATE":
 102  			cert, err := x509.ParseCertificate(block.Bytes)
 103  			if err != nil {
 104  				return nil, nil, fmt.Errorf("block labelled 'CERTIFICATE' could not be parsed by x509: %v", err)
 105  			}
 106  			certs = append(certs, cert)
 107  		case "PRIVATE KEY":
 108  			if priv != nil {
 109  				return nil, nil, errors.New("found multiple private key blocks")
 110  			}
 111  
 112  			var err error
 113  			priv, err = x509.ParsePKCS8PrivateKey(block.Bytes)
 114  			if err != nil {
 115  				return nil, nil, fmt.Errorf("could not decode private key: %v", err)
 116  			}
 117  		case "RSA PRIVATE KEY":
 118  			if priv != nil {
 119  				return nil, nil, errors.New("found multiple private key blocks")
 120  			}
 121  			var err error
 122  			priv, err = x509.ParsePKCS1PrivateKey(block.Bytes)
 123  			if err != nil {
 124  				return nil, nil, fmt.Errorf("could not decode private key: %v", err)
 125  			}
 126  		}
 127  		pemData = rest
 128  	}
 129  
 130  	if len(certs) == 0 {
 131  		return nil, nil, fmt.Errorf("no certificates found")
 132  	}
 133  
 134  	if priv == nil {
 135  		return nil, nil, fmt.Errorf("no private key found")
 136  	}
 137  
 138  	return certs, priv, nil
 139  }
 140  
 141  // AssertionRequestOptions has required information for client assertion claims
 142  type AssertionRequestOptions = exported.AssertionRequestOptions
 143  
 144  // Credential represents the credential used in confidential client flows.
 145  type Credential struct {
 146  	secret string
 147  
 148  	cert *x509.Certificate
 149  	key  crypto.PrivateKey
 150  	x5c  []string
 151  
 152  	assertionCallback func(context.Context, AssertionRequestOptions) (string, error)
 153  
 154  	tokenProvider func(context.Context, TokenProviderParameters) (TokenProviderResult, error)
 155  }
 156  
 157  // toInternal returns the accesstokens.Credential that is used internally. The current structure of the
 158  // code requires that client.go, requests.go and confidential.go share a credential type without
 159  // having import recursion. That requires the type used between is in a shared package. Therefore
 160  // we have this.
 161  func (c Credential) toInternal() (*accesstokens.Credential, error) {
 162  	if c.secret != "" {
 163  		return &accesstokens.Credential{Secret: c.secret}, nil
 164  	}
 165  	if c.cert != nil {
 166  		if c.key == nil {
 167  			return nil, errors.New("missing private key for certificate")
 168  		}
 169  		return &accesstokens.Credential{Cert: c.cert, Key: c.key, X5c: c.x5c}, nil
 170  	}
 171  	if c.key != nil {
 172  		return nil, errors.New("missing certificate for private key")
 173  	}
 174  	if c.assertionCallback != nil {
 175  		return &accesstokens.Credential{AssertionCallback: c.assertionCallback}, nil
 176  	}
 177  	if c.tokenProvider != nil {
 178  		return &accesstokens.Credential{TokenProvider: c.tokenProvider}, nil
 179  	}
 180  	return nil, errors.New("invalid credential")
 181  }
 182  
 183  // NewCredFromSecret creates a Credential from a secret.
 184  func NewCredFromSecret(secret string) (Credential, error) {
 185  	if secret == "" {
 186  		return Credential{}, errors.New("secret can't be empty string")
 187  	}
 188  	return Credential{secret: secret}, nil
 189  }
 190  
 191  // NewCredFromAssertionCallback creates a Credential that invokes a callback to get assertions
 192  // authenticating the application. The callback must be thread safe.
 193  func NewCredFromAssertionCallback(callback func(context.Context, AssertionRequestOptions) (string, error)) Credential {
 194  	return Credential{assertionCallback: callback}
 195  }
 196  
 197  // NewCredFromCert creates a Credential from a certificate or chain of certificates and an RSA private key
 198  // as returned by [CertFromPEM].
 199  func NewCredFromCert(certs []*x509.Certificate, key crypto.PrivateKey) (Credential, error) {
 200  	cred := Credential{key: key}
 201  	k, ok := key.(*rsa.PrivateKey)
 202  	if !ok {
 203  		return cred, errors.New("key must be an RSA key")
 204  	}
 205  	for _, cert := range certs {
 206  		if cert == nil {
 207  			// not returning an error here because certs may still contain a sufficient cert/key pair
 208  			continue
 209  		}
 210  		certKey, ok := cert.PublicKey.(*rsa.PublicKey)
 211  		if ok && k.E == certKey.E && k.N.Cmp(certKey.N) == 0 {
 212  			// We know this is the signing cert because its public key matches the given private key.
 213  			// This cert must be first in x5c.
 214  			cred.cert = cert
 215  			cred.x5c = append([]string{base64.StdEncoding.EncodeToString(cert.Raw)}, cred.x5c...)
 216  		} else {
 217  			cred.x5c = append(cred.x5c, base64.StdEncoding.EncodeToString(cert.Raw))
 218  		}
 219  	}
 220  	if cred.cert == nil {
 221  		return cred, errors.New("key doesn't match any certificate")
 222  	}
 223  	return cred, nil
 224  }
 225  
 226  // TokenProviderParameters is the authentication parameters passed to token providers
 227  type TokenProviderParameters = exported.TokenProviderParameters
 228  
 229  // TokenProviderResult is the authentication result returned by custom token providers
 230  type TokenProviderResult = exported.TokenProviderResult
 231  
 232  // NewCredFromTokenProvider creates a Credential from a function that provides access tokens. The function
 233  // must be concurrency safe. This is intended only to allow the Azure SDK to cache MSI tokens. It isn't
 234  // useful to applications in general because the token provider must implement all authentication logic.
 235  func NewCredFromTokenProvider(provider func(context.Context, TokenProviderParameters) (TokenProviderResult, error)) Credential {
 236  	return Credential{tokenProvider: provider}
 237  }
 238  
 239  // AutoDetectRegion instructs MSAL Go to auto detect region for Azure regional token service.
 240  func AutoDetectRegion() string {
 241  	return "TryAutoDetect"
 242  }
 243  
 244  // Client is a representation of authentication client for confidential applications as defined in the
 245  // package doc. A new Client should be created PER SERVICE USER.
 246  // For more information, visit https://docs.microsoft.com/azure/active-directory/develop/msal-client-applications
 247  type Client struct {
 248  	base base.Client
 249  	cred *accesstokens.Credential
 250  }
 251  
 252  // clientOptions are optional settings for New(). These options are set using various functions
 253  // returning Option calls.
 254  type clientOptions struct {
 255  	accessor                          cache.ExportReplace
 256  	authority, azureRegion            string
 257  	capabilities                      []string
 258  	disableInstanceDiscovery, sendX5C bool
 259  	httpClient                        ops.HTTPClient
 260  }
 261  
 262  // Option is an optional argument to New().
 263  type Option func(o *clientOptions)
 264  
 265  // WithCache provides an accessor that will read and write authentication data to an externally managed cache.
 266  func WithCache(accessor cache.ExportReplace) Option {
 267  	return func(o *clientOptions) {
 268  		o.accessor = accessor
 269  	}
 270  }
 271  
 272  // WithClientCapabilities allows configuring one or more client capabilities such as "CP1"
 273  func WithClientCapabilities(capabilities []string) Option {
 274  	return func(o *clientOptions) {
 275  		// there's no danger of sharing the slice's underlying memory with the application because
 276  		// this slice is simply passed to base.WithClientCapabilities, which copies its data
 277  		o.capabilities = capabilities
 278  	}
 279  }
 280  
 281  // WithHTTPClient allows for a custom HTTP client to be set.
 282  func WithHTTPClient(httpClient ops.HTTPClient) Option {
 283  	return func(o *clientOptions) {
 284  		o.httpClient = httpClient
 285  	}
 286  }
 287  
 288  // WithX5C specifies if x5c claim(public key of the certificate) should be sent to STS to enable Subject Name Issuer Authentication.
 289  func WithX5C() Option {
 290  	return func(o *clientOptions) {
 291  		o.sendX5C = true
 292  	}
 293  }
 294  
 295  // WithInstanceDiscovery set to false to disable authority validation (to support private cloud scenarios)
 296  func WithInstanceDiscovery(enabled bool) Option {
 297  	return func(o *clientOptions) {
 298  		o.disableInstanceDiscovery = !enabled
 299  	}
 300  }
 301  
 302  // WithAzureRegion sets the region(preferred) or Confidential.AutoDetectRegion() for auto detecting region.
 303  // Region names as per https://azure.microsoft.com/en-ca/global-infrastructure/geographies/.
 304  // See https://aka.ms/region-map for more details on region names.
 305  // The region value should be short region name for the region where the service is deployed.
 306  // For example "centralus" is short name for region Central US.
 307  // Not all auth flows can use the regional token service.
 308  // Service To Service (client credential flow) tokens can be obtained from the regional service.
 309  // Requires configuration at the tenant level.
 310  // Auto-detection works on a limited number of Azure artifacts (VMs, Azure functions).
 311  // If auto-detection fails, the non-regional endpoint will be used.
 312  // If an invalid region name is provided, the non-regional endpoint MIGHT be used or the token request MIGHT fail.
 313  func WithAzureRegion(val string) Option {
 314  	return func(o *clientOptions) {
 315  		if val != "" {
 316  			o.azureRegion = val
 317  		}
 318  	}
 319  }
 320  
 321  // New is the constructor for Client. authority is the URL of a token authority such as "https://login.microsoftonline.com/<your tenant>".
 322  // If the Client will connect directly to AD FS, use "adfs" for the tenant. clientID is the application's client ID (also called its
 323  // "application ID").
 324  func New(authority, clientID string, cred Credential, options ...Option) (Client, error) {
 325  	internalCred, err := cred.toInternal()
 326  	if err != nil {
 327  		return Client{}, err
 328  	}
 329  	autoEnabledRegion := os.Getenv("MSAL_FORCE_REGION")
 330  	opts := clientOptions{
 331  		authority: authority,
 332  		// if the caller specified a token provider, it will handle all details of authentication, using Client only as a token cache
 333  		disableInstanceDiscovery: cred.tokenProvider != nil,
 334  		httpClient:               shared.DefaultClient,
 335  		azureRegion:              autoEnabledRegion,
 336  	}
 337  	for _, o := range options {
 338  		o(&opts)
 339  	}
 340  	if strings.EqualFold(opts.azureRegion, "DisableMsalForceRegion") {
 341  		opts.azureRegion = ""
 342  	}
 343  
 344  	baseOpts := []base.Option{
 345  		base.WithCacheAccessor(opts.accessor),
 346  		base.WithClientCapabilities(opts.capabilities),
 347  		base.WithInstanceDiscovery(!opts.disableInstanceDiscovery),
 348  		base.WithRegionDetection(opts.azureRegion),
 349  		base.WithX5C(opts.sendX5C),
 350  	}
 351  	base, err := base.New(clientID, opts.authority, oauth.New(opts.httpClient), baseOpts...)
 352  	if err != nil {
 353  		return Client{}, err
 354  	}
 355  	base.AuthParams.IsConfidentialClient = true
 356  
 357  	return Client{base: base, cred: internalCred}, nil
 358  }
 359  
 360  // authCodeURLOptions contains options for AuthCodeURL
 361  type authCodeURLOptions struct {
 362  	claims, loginHint, tenantID, domainHint string
 363  }
 364  
 365  // AuthCodeURLOption is implemented by options for AuthCodeURL
 366  type AuthCodeURLOption interface {
 367  	authCodeURLOption()
 368  }
 369  
 370  // AuthCodeURL creates a URL used to acquire an authorization code. Users need to call CreateAuthorizationCodeURLParameters and pass it in.
 371  //
 372  // Options: [WithClaims], [WithDomainHint], [WithLoginHint], [WithTenantID]
 373  func (cca Client) AuthCodeURL(ctx context.Context, clientID, redirectURI string, scopes []string, opts ...AuthCodeURLOption) (string, error) {
 374  	o := authCodeURLOptions{}
 375  	if err := options.ApplyOptions(&o, opts); err != nil {
 376  		return "", err
 377  	}
 378  	ap, err := cca.base.AuthParams.WithTenant(o.tenantID)
 379  	if err != nil {
 380  		return "", err
 381  	}
 382  	ap.Claims = o.claims
 383  	ap.LoginHint = o.loginHint
 384  	ap.DomainHint = o.domainHint
 385  	return cca.base.AuthCodeURL(ctx, clientID, redirectURI, scopes, ap)
 386  }
 387  
 388  // WithLoginHint pre-populates the login prompt with a username.
 389  func WithLoginHint(username string) interface {
 390  	AuthCodeURLOption
 391  	options.CallOption
 392  } {
 393  	return struct {
 394  		AuthCodeURLOption
 395  		options.CallOption
 396  	}{
 397  		CallOption: options.NewCallOption(
 398  			func(a any) error {
 399  				switch t := a.(type) {
 400  				case *authCodeURLOptions:
 401  					t.loginHint = username
 402  				default:
 403  					return fmt.Errorf("unexpected options type %T", a)
 404  				}
 405  				return nil
 406  			},
 407  		),
 408  	}
 409  }
 410  
 411  // WithDomainHint adds the IdP domain as domain_hint query parameter in the auth url.
 412  func WithDomainHint(domain string) interface {
 413  	AuthCodeURLOption
 414  	options.CallOption
 415  } {
 416  	return struct {
 417  		AuthCodeURLOption
 418  		options.CallOption
 419  	}{
 420  		CallOption: options.NewCallOption(
 421  			func(a any) error {
 422  				switch t := a.(type) {
 423  				case *authCodeURLOptions:
 424  					t.domainHint = domain
 425  				default:
 426  					return fmt.Errorf("unexpected options type %T", a)
 427  				}
 428  				return nil
 429  			},
 430  		),
 431  	}
 432  }
 433  
 434  // WithClaims sets additional claims to request for the token, such as those required by conditional access policies.
 435  // Use this option when Azure AD returned a claims challenge for a prior request. The argument must be decoded.
 436  // This option is valid for any token acquisition method.
 437  func WithClaims(claims string) interface {
 438  	AcquireByAuthCodeOption
 439  	AcquireByCredentialOption
 440  	AcquireOnBehalfOfOption
 441  	AcquireByUsernamePasswordOption
 442  	AcquireSilentOption
 443  	AuthCodeURLOption
 444  	options.CallOption
 445  } {
 446  	return struct {
 447  		AcquireByAuthCodeOption
 448  		AcquireByCredentialOption
 449  		AcquireOnBehalfOfOption
 450  		AcquireByUsernamePasswordOption
 451  		AcquireSilentOption
 452  		AuthCodeURLOption
 453  		options.CallOption
 454  	}{
 455  		CallOption: options.NewCallOption(
 456  			func(a any) error {
 457  				switch t := a.(type) {
 458  				case *acquireTokenByAuthCodeOptions:
 459  					t.claims = claims
 460  				case *acquireTokenByCredentialOptions:
 461  					t.claims = claims
 462  				case *acquireTokenOnBehalfOfOptions:
 463  					t.claims = claims
 464  				case *acquireTokenByUsernamePasswordOptions:
 465  					t.claims = claims
 466  				case *acquireTokenSilentOptions:
 467  					t.claims = claims
 468  				case *authCodeURLOptions:
 469  					t.claims = claims
 470  				default:
 471  					return fmt.Errorf("unexpected options type %T", a)
 472  				}
 473  				return nil
 474  			},
 475  		),
 476  	}
 477  }
 478  
 479  // WithAuthenticationScheme is an extensibility mechanism designed to be used only by Azure Arc for proof of possession access tokens.
 480  func WithAuthenticationScheme(authnScheme AuthenticationScheme) interface {
 481  	AcquireSilentOption
 482  	AcquireByCredentialOption
 483  	options.CallOption
 484  } {
 485  	return struct {
 486  		AcquireSilentOption
 487  		AcquireByCredentialOption
 488  		options.CallOption
 489  	}{
 490  		CallOption: options.NewCallOption(
 491  			func(a any) error {
 492  				switch t := a.(type) {
 493  				case *acquireTokenSilentOptions:
 494  					t.authnScheme = authnScheme
 495  				case *acquireTokenByCredentialOptions:
 496  					t.authnScheme = authnScheme
 497  				default:
 498  					return fmt.Errorf("unexpected options type %T", a)
 499  				}
 500  				return nil
 501  			},
 502  		),
 503  	}
 504  }
 505  
 506  // WithTenantID specifies a tenant for a single authentication. It may be different than the tenant set in [New].
 507  // This option is valid for any token acquisition method.
 508  func WithTenantID(tenantID string) interface {
 509  	AcquireByAuthCodeOption
 510  	AcquireByCredentialOption
 511  	AcquireOnBehalfOfOption
 512  	AcquireByUsernamePasswordOption
 513  	AcquireSilentOption
 514  	AuthCodeURLOption
 515  	options.CallOption
 516  } {
 517  	return struct {
 518  		AcquireByAuthCodeOption
 519  		AcquireByCredentialOption
 520  		AcquireOnBehalfOfOption
 521  		AcquireByUsernamePasswordOption
 522  		AcquireSilentOption
 523  		AuthCodeURLOption
 524  		options.CallOption
 525  	}{
 526  		CallOption: options.NewCallOption(
 527  			func(a any) error {
 528  				switch t := a.(type) {
 529  				case *acquireTokenByAuthCodeOptions:
 530  					t.tenantID = tenantID
 531  				case *acquireTokenByCredentialOptions:
 532  					t.tenantID = tenantID
 533  				case *acquireTokenOnBehalfOfOptions:
 534  					t.tenantID = tenantID
 535  				case *acquireTokenByUsernamePasswordOptions:
 536  					t.tenantID = tenantID
 537  				case *acquireTokenSilentOptions:
 538  					t.tenantID = tenantID
 539  				case *authCodeURLOptions:
 540  					t.tenantID = tenantID
 541  				default:
 542  					return fmt.Errorf("unexpected options type %T", a)
 543  				}
 544  				return nil
 545  			},
 546  		),
 547  	}
 548  }
 549  
 550  // acquireTokenSilentOptions are all the optional settings to an AcquireTokenSilent() call.
 551  // These are set by using various AcquireTokenSilentOption functions.
 552  type acquireTokenSilentOptions struct {
 553  	account          Account
 554  	claims, tenantID string
 555  	authnScheme      AuthenticationScheme
 556  }
 557  
 558  // AcquireSilentOption is implemented by options for AcquireTokenSilent
 559  type AcquireSilentOption interface {
 560  	acquireSilentOption()
 561  }
 562  
 563  // WithSilentAccount uses the passed account during an AcquireTokenSilent() call.
 564  func WithSilentAccount(account Account) interface {
 565  	AcquireSilentOption
 566  	options.CallOption
 567  } {
 568  	return struct {
 569  		AcquireSilentOption
 570  		options.CallOption
 571  	}{
 572  		CallOption: options.NewCallOption(
 573  			func(a any) error {
 574  				switch t := a.(type) {
 575  				case *acquireTokenSilentOptions:
 576  					t.account = account
 577  				default:
 578  					return fmt.Errorf("unexpected options type %T", a)
 579  				}
 580  				return nil
 581  			},
 582  		),
 583  	}
 584  }
 585  
 586  // AcquireTokenSilent acquires a token from either the cache or using a refresh token.
 587  //
 588  // Options: [WithClaims], [WithSilentAccount], [WithTenantID]
 589  func (cca Client) AcquireTokenSilent(ctx context.Context, scopes []string, opts ...AcquireSilentOption) (AuthResult, error) {
 590  	o := acquireTokenSilentOptions{}
 591  	if err := options.ApplyOptions(&o, opts); err != nil {
 592  		return AuthResult{}, err
 593  	}
 594  
 595  	if o.claims != "" {
 596  		return AuthResult{}, errors.New("call another AcquireToken method to request a new token having these claims")
 597  	}
 598  
 599  	// For service principal scenarios, require WithSilentAccount for public API
 600  	if o.account.IsZero() {
 601  		return AuthResult{}, errors.New("WithSilentAccount option is required")
 602  	}
 603  
 604  	silentParameters := base.AcquireTokenSilentParameters{
 605  		Scopes:      scopes,
 606  		Account:     o.account,
 607  		RequestType: accesstokens.ATConfidential,
 608  		Credential:  cca.cred,
 609  		IsAppCache:  o.account.IsZero(),
 610  		TenantID:    o.tenantID,
 611  		AuthnScheme: o.authnScheme,
 612  		Claims:      o.claims,
 613  	}
 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  
 621  	return cca.base.AcquireTokenSilent(ctx, silentParameters)
 622  }
 623  
 624  // acquireTokenByUsernamePasswordOptions contains optional configuration for AcquireTokenByUsernamePassword
 625  type acquireTokenByUsernamePasswordOptions struct {
 626  	claims, tenantID string
 627  	authnScheme      AuthenticationScheme
 628  }
 629  
 630  // AcquireByUsernamePasswordOption is implemented by options for AcquireTokenByUsernamePassword
 631  type AcquireByUsernamePasswordOption interface {
 632  	acquireByUsernamePasswordOption()
 633  }
 634  
 635  // AcquireTokenByUsernamePassword acquires a security token from the authority, via Username/Password Authentication.
 636  // NOTE: this flow is NOT recommended.
 637  //
 638  // Options: [WithClaims], [WithTenantID]
 639  func (cca Client) AcquireTokenByUsernamePassword(ctx context.Context, scopes []string, username, password string, opts ...AcquireByUsernamePasswordOption) (AuthResult, error) {
 640  	o := acquireTokenByUsernamePasswordOptions{}
 641  	if err := options.ApplyOptions(&o, opts); err != nil {
 642  		return AuthResult{}, err
 643  	}
 644  	authParams, err := cca.base.AuthParams.WithTenant(o.tenantID)
 645  	if err != nil {
 646  		return AuthResult{}, err
 647  	}
 648  	authParams.Scopes = scopes
 649  	authParams.AuthorizationType = authority.ATUsernamePassword
 650  	authParams.Claims = o.claims
 651  	authParams.Username = username
 652  	authParams.Password = password
 653  	if o.authnScheme != nil {
 654  		authParams.AuthnScheme = o.authnScheme
 655  	}
 656  
 657  	token, err := cca.base.Token.UsernamePassword(ctx, authParams)
 658  	if err != nil {
 659  		return AuthResult{}, err
 660  	}
 661  	return cca.base.AuthResultFromToken(ctx, authParams, token)
 662  }
 663  
 664  // acquireTokenByAuthCodeOptions contains the optional parameters used to acquire an access token using the authorization code flow.
 665  type acquireTokenByAuthCodeOptions struct {
 666  	challenge, claims, tenantID string
 667  }
 668  
 669  // AcquireByAuthCodeOption is implemented by options for AcquireTokenByAuthCode
 670  type AcquireByAuthCodeOption interface {
 671  	acquireByAuthCodeOption()
 672  }
 673  
 674  // WithChallenge allows you to provide a challenge for the .AcquireTokenByAuthCode() call.
 675  func WithChallenge(challenge string) interface {
 676  	AcquireByAuthCodeOption
 677  	options.CallOption
 678  } {
 679  	return struct {
 680  		AcquireByAuthCodeOption
 681  		options.CallOption
 682  	}{
 683  		CallOption: options.NewCallOption(
 684  			func(a any) error {
 685  				switch t := a.(type) {
 686  				case *acquireTokenByAuthCodeOptions:
 687  					t.challenge = challenge
 688  				default:
 689  					return fmt.Errorf("unexpected options type %T", a)
 690  				}
 691  				return nil
 692  			},
 693  		),
 694  	}
 695  }
 696  
 697  // AcquireTokenByAuthCode is a request to acquire a security token from the authority, using an authorization code.
 698  // The specified redirect URI must be the same URI that was used when the authorization code was requested.
 699  //
 700  // Options: [WithChallenge], [WithClaims], [WithTenantID]
 701  func (cca Client) AcquireTokenByAuthCode(ctx context.Context, code string, redirectURI string, scopes []string, opts ...AcquireByAuthCodeOption) (AuthResult, error) {
 702  	o := acquireTokenByAuthCodeOptions{}
 703  	if err := options.ApplyOptions(&o, opts); err != nil {
 704  		return AuthResult{}, err
 705  	}
 706  
 707  	params := base.AcquireTokenAuthCodeParameters{
 708  		Scopes:      scopes,
 709  		Code:        code,
 710  		Challenge:   o.challenge,
 711  		Claims:      o.claims,
 712  		AppType:     accesstokens.ATConfidential,
 713  		Credential:  cca.cred, // This setting differs from public.Client.AcquireTokenByAuthCode
 714  		RedirectURI: redirectURI,
 715  		TenantID:    o.tenantID,
 716  	}
 717  
 718  	return cca.base.AcquireTokenByAuthCode(ctx, params)
 719  }
 720  
 721  // acquireTokenByCredentialOptions contains optional configuration for AcquireTokenByCredential
 722  type acquireTokenByCredentialOptions struct {
 723  	claims, tenantID    string
 724  	authnScheme         AuthenticationScheme
 725  	extraBodyParameters map[string]string
 726  	cacheKeyComponents  map[string]string
 727  }
 728  
 729  // AcquireByCredentialOption is implemented by options for AcquireTokenByCredential
 730  type AcquireByCredentialOption interface {
 731  	acquireByCredOption()
 732  }
 733  
 734  // AcquireTokenByCredential acquires a security token from the authority, using the client credentials grant.
 735  //
 736  // Options: [WithClaims], [WithTenantID], [WithFMIPath], [WithAttribute]
 737  func (cca Client) AcquireTokenByCredential(ctx context.Context, scopes []string, opts ...AcquireByCredentialOption) (AuthResult, error) {
 738  	o := acquireTokenByCredentialOptions{}
 739  	err := options.ApplyOptions(&o, opts)
 740  	if err != nil {
 741  		return AuthResult{}, err
 742  	}
 743  	authParams, err := cca.base.AuthParams.WithTenant(o.tenantID)
 744  	if err != nil {
 745  		return AuthResult{}, err
 746  	}
 747  	authParams.Scopes = scopes
 748  	authParams.AuthorizationType = authority.ATClientCredentials
 749  	authParams.Claims = o.claims
 750  	if o.authnScheme != nil {
 751  		authParams.AuthnScheme = o.authnScheme
 752  	}
 753  	authParams.ExtraBodyParameters = o.extraBodyParameters
 754  	authParams.CacheKeyComponents = o.cacheKeyComponents
 755  	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  
 769  		// Use internal method with empty account (service principal scenario)
 770  		cache, err := cca.acquireTokenSilentInternal(ctx, silentParameters)
 771  		if err == nil {
 772  			return cache, nil
 773  		}
 774  	}
 775  
 776  	token, err := cca.base.Token.Credential(ctx, authParams, cca.cred)
 777  	if err != nil {
 778  		return AuthResult{}, err
 779  	}
 780  	return cca.base.AuthResultFromToken(ctx, authParams, token)
 781  }
 782  
 783  // acquireTokenOnBehalfOfOptions contains optional configuration for AcquireTokenOnBehalfOf
 784  type acquireTokenOnBehalfOfOptions struct {
 785  	claims, tenantID string
 786  }
 787  
 788  // AcquireOnBehalfOfOption is implemented by options for AcquireTokenOnBehalfOf
 789  type AcquireOnBehalfOfOption interface {
 790  	acquireOBOOption()
 791  }
 792  
 793  // AcquireTokenOnBehalfOf acquires a security token for an app using middle tier apps access token.
 794  // Refer https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-on-behalf-of-flow.
 795  //
 796  // Options: [WithClaims], [WithTenantID]
 797  func (cca Client) AcquireTokenOnBehalfOf(ctx context.Context, userAssertion string, scopes []string, opts ...AcquireOnBehalfOfOption) (AuthResult, error) {
 798  	o := acquireTokenOnBehalfOfOptions{}
 799  	if err := options.ApplyOptions(&o, opts); err != nil {
 800  		return AuthResult{}, err
 801  	}
 802  	params := base.AcquireTokenOnBehalfOfParameters{
 803  		Scopes:        scopes,
 804  		UserAssertion: userAssertion,
 805  		Claims:        o.claims,
 806  		Credential:    cca.cred,
 807  		TenantID:      o.tenantID,
 808  	}
 809  	return cca.base.AcquireTokenOnBehalfOf(ctx, params)
 810  }
 811  
 812  // Account gets the account in the token cache with the specified homeAccountID.
 813  func (cca Client) Account(ctx context.Context, accountID string) (Account, error) {
 814  	return cca.base.Account(ctx, accountID)
 815  }
 816  
 817  // RemoveAccount signs the account out and forgets account from token cache.
 818  func (cca Client) RemoveAccount(ctx context.Context, account Account) error {
 819  	return cca.base.RemoveAccount(ctx, account)
 820  }
 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  }
 881