bump azure sdk

v1.3.0 of azidentity introduces support to workload identity.

Signed-off-by: Flavian Missi <fmissi@redhat.com>
This commit is contained in:
Flavian Missi
2023-05-11 15:23:47 +02:00
parent 8e29e870a4
commit 7caf058a65
169 changed files with 2892 additions and 1433 deletions

View File

@@ -11,6 +11,8 @@ implementers on the format being passed.
*/
package cache
import "context"
// Marshaler marshals data from an internal cache to bytes that can be stored.
type Marshaler interface {
Marshal() ([]byte, error)
@@ -27,13 +29,26 @@ type Serializer interface {
Unmarshaler
}
// ExportReplace is used export or replace what is in the cache.
type ExportReplace interface {
// Replace replaces the cache with what is in external storage.
// key is the suggested key which can be used for partioning the cache
Replace(cache Unmarshaler, key string)
// Export writes the binary representation of the cache (cache.Marshal()) to
// external storage. This is considered opaque.
// key is the suggested key which can be used for partioning the cache
Export(cache Marshaler, key string)
// ExportHints are suggestions for storing data.
type ExportHints struct {
// PartitionKey is a suggested key for partitioning the cache
PartitionKey string
}
// ReplaceHints are suggestions for loading data.
type ReplaceHints struct {
// PartitionKey is a suggested key for partitioning the cache
PartitionKey string
}
// ExportReplace exports and replaces in-memory cache data. It doesn't support nil Context or
// define the outcome of passing one. A Context without a timeout must receive a default timeout
// specified by the implementor. Retries must be implemented inside the implementation.
type ExportReplace interface {
// Replace replaces the cache with what is in external storage. Implementors should honor
// Context cancellations and return context.Canceled or context.DeadlineExceeded in those cases.
Replace(ctx context.Context, cache Unmarshaler, hints ReplaceHints) error
// Export writes the binary representation of the cache (cache.Marshal()) to external storage.
// This is considered opaque. Context cancellations should be honored as in Replace.
Export(ctx context.Context, cache Marshaler, hints ExportHints) error
}

View File

@@ -18,7 +18,6 @@ import (
"encoding/pem"
"errors"
"fmt"
"net/url"
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/cache"
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/base"
@@ -50,8 +49,7 @@ duplication.
.Net People, Take note on X509:
This uses x509.Certificates and private keys. x509 does not store private keys. .Net
has some x509.Certificate2 thing that has private keys, but that is just some bullcrap that .Net
added, it doesn't exist in real life. Seriously, "x509.Certificate2", bahahahaha. As such I've
put a PEM decoder into here.
added, it doesn't exist in real life. 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.
@@ -63,7 +61,7 @@ type AuthResult = base.AuthResult
type Account = shared.Account
// CertFromPEM converts a PEM file (.pem or .key) for use with NewCredFromCert(). The file
// CertFromPEM converts a PEM file (.pem or .key) for use with [NewCredFromCert]. The file
// must contain the public certificate and the private key. If a PEM block is encrypted and
// password is not an empty string, it attempts to decrypt the PEM blocks using the password.
// Multiple certs are due to certificate chaining for use cases like TLS that sign from root to leaf.
@@ -179,33 +177,15 @@ func NewCredFromSecret(secret string) (Credential, error) {
return Credential{secret: secret}, nil
}
// NewCredFromAssertion creates a Credential from a signed assertion.
//
// Deprecated: a Credential created by this function can't refresh the
// assertion when it expires. Use NewCredFromAssertionCallback instead.
func NewCredFromAssertion(assertion string) (Credential, error) {
if assertion == "" {
return Credential{}, errors.New("assertion can't be empty string")
}
return NewCredFromAssertionCallback(func(context.Context, AssertionRequestOptions) (string, error) { return assertion, nil }), nil
}
// NewCredFromAssertionCallback creates a Credential that invokes a callback to get assertions
// authenticating the application. The callback must be thread safe.
func NewCredFromAssertionCallback(callback func(context.Context, AssertionRequestOptions) (string, error)) Credential {
return Credential{assertionCallback: callback}
}
// NewCredFromCert creates a Credential from an x509.Certificate and an RSA private key.
// CertFromPEM() can be used to get these values from a PEM file.
func NewCredFromCert(cert *x509.Certificate, key crypto.PrivateKey) Credential {
cred, _ := NewCredFromCertChain([]*x509.Certificate{cert}, key)
return cred
}
// NewCredFromCertChain creates a Credential from a chain of x509.Certificates and an RSA private key
// as returned by CertFromPEM().
func NewCredFromCertChain(certs []*x509.Certificate, key crypto.PrivateKey) (Credential, error) {
// NewCredFromCert creates a Credential from a certificate or chain of certificates and an RSA private key
// as returned by [CertFromPEM].
func NewCredFromCert(certs []*x509.Certificate, key crypto.PrivateKey) (Credential, error) {
cred := Credential{key: key}
k, ok := key.(*rsa.PrivateKey)
if !ok {
@@ -255,73 +235,32 @@ func AutoDetectRegion() string {
// For more information, visit https://docs.microsoft.com/azure/active-directory/develop/msal-client-applications
type Client struct {
base base.Client
cred *accesstokens.Credential
// userID is some unique identifier for a user. It actually isn't used by us at all, it
// simply acts as another hint that a confidential.Client is for a single user.
userID string
}
// Options are optional settings for New(). These options are set using various functions
// clientOptions are optional settings for New(). These options are set using various functions
// returning Option calls.
type Options struct {
// Accessor controls cache persistence.
// By default there is no cache persistence. This can be set using the WithAccessor() option.
Accessor cache.ExportReplace
// The host of the Azure Active Directory authority.
// The default is https://login.microsoftonline.com/common. This can be changed using the
// WithAuthority() option.
Authority string
// The HTTP client used for making requests.
// It defaults to a shared http.Client.
HTTPClient ops.HTTPClient
// SendX5C specifies if x5c claim(public key of the certificate) should be sent to STS.
SendX5C bool
// Instructs MSAL Go to use an Azure regional token service with sepcified AzureRegion.
AzureRegion string
capabilities []string
disableInstanceDiscovery bool
}
func (o Options) validate() error {
u, err := url.Parse(o.Authority)
if err != nil {
return fmt.Errorf("the Authority(%s) does not parse as a valid URL", o.Authority)
}
if u.Scheme != "https" {
return fmt.Errorf("the Authority(%s) does not appear to use https", o.Authority)
}
return nil
type clientOptions struct {
accessor cache.ExportReplace
authority, azureRegion string
capabilities []string
disableInstanceDiscovery, sendX5C bool
httpClient ops.HTTPClient
}
// Option is an optional argument to New().
type Option func(o *Options)
type Option func(o *clientOptions)
// WithAuthority allows you to provide a custom authority for use in the client.
func WithAuthority(authority string) Option {
return func(o *Options) {
o.Authority = authority
}
}
// WithAccessor provides a cache accessor that will read and write to some externally managed cache
// that may or may not be shared with other applications.
func WithAccessor(accessor cache.ExportReplace) Option {
return func(o *Options) {
o.Accessor = accessor
// WithCache provides an accessor that will read and write authentication data to an externally managed cache.
func WithCache(accessor cache.ExportReplace) Option {
return func(o *clientOptions) {
o.accessor = accessor
}
}
// WithClientCapabilities allows configuring one or more client capabilities such as "CP1"
func WithClientCapabilities(capabilities []string) Option {
return func(o *Options) {
return func(o *clientOptions) {
// there's no danger of sharing the slice's underlying memory with the application because
// this slice is simply passed to base.WithClientCapabilities, which copies its data
o.capabilities = capabilities
@@ -330,21 +269,21 @@ func WithClientCapabilities(capabilities []string) Option {
// WithHTTPClient allows for a custom HTTP client to be set.
func WithHTTPClient(httpClient ops.HTTPClient) Option {
return func(o *Options) {
o.HTTPClient = httpClient
return func(o *clientOptions) {
o.httpClient = httpClient
}
}
// WithX5C specifies if x5c claim(public key of the certificate) should be sent to STS to enable Subject Name Issuer Authentication.
func WithX5C() Option {
return func(o *Options) {
o.SendX5C = true
return func(o *clientOptions) {
o.sendX5C = true
}
}
// WithInstanceDiscovery set to false to disable authority validation (to support private cloud scenarios)
func WithInstanceDiscovery(enabled bool) Option {
return func(o *Options) {
return func(o *clientOptions) {
o.disableInstanceDiscovery = !enabled
}
}
@@ -361,44 +300,37 @@ func WithInstanceDiscovery(enabled bool) Option {
// If auto-detection fails, the non-regional endpoint will be used.
// If an invalid region name is provided, the non-regional endpoint MIGHT be used or the token request MIGHT fail.
func WithAzureRegion(val string) Option {
return func(o *Options) {
o.AzureRegion = val
return func(o *clientOptions) {
o.azureRegion = val
}
}
// New is the constructor for Client. userID is the unique identifier of the user this client
// will store credentials for (a Client is per user). clientID is the Azure clientID and cred is
// the type of credential to use.
func New(clientID string, cred Credential, options ...Option) (Client, error) {
// New is the constructor for Client. authority is the URL of a token authority such as "https://login.microsoftonline.com/<your tenant>".
// If the Client will connect directly to AD FS, use "adfs" for the tenant. clientID is the application's client ID (also called its
// "application ID").
func New(authority, clientID string, cred Credential, options ...Option) (Client, error) {
internalCred, err := cred.toInternal()
if err != nil {
return Client{}, err
}
opts := Options{
Authority: base.AuthorityPublicCloud,
HTTPClient: shared.DefaultClient,
opts := clientOptions{
authority: authority,
// if the caller specified a token provider, it will handle all details of authentication, using Client only as a token cache
disableInstanceDiscovery: cred.tokenProvider != nil,
httpClient: shared.DefaultClient,
}
for _, o := range options {
o(&opts)
}
if err := opts.validate(); err != nil {
return Client{}, err
}
baseOpts := []base.Option{
base.WithCacheAccessor(opts.Accessor),
base.WithCacheAccessor(opts.accessor),
base.WithClientCapabilities(opts.capabilities),
base.WithRegionDetection(opts.AzureRegion),
base.WithX5C(opts.SendX5C),
base.WithInstanceDiscovery(!opts.disableInstanceDiscovery),
base.WithRegionDetection(opts.azureRegion),
base.WithX5C(opts.sendX5C),
}
if cred.tokenProvider != nil {
// The caller will handle all details of authentication, using Client only as a token cache.
baseOpts = append(baseOpts, base.WithInstanceDiscovery(false))
}
base, err := base.New(clientID, opts.Authority, oauth.New(opts.HTTPClient), baseOpts...)
base, err := base.New(clientID, opts.authority, oauth.New(opts.httpClient), baseOpts...)
if err != nil {
return Client{}, err
}
@@ -407,11 +339,6 @@ func New(clientID string, cred Credential, options ...Option) (Client, error) {
return Client{base: base, cred: internalCred}, nil
}
// UserID is the unique user identifier this client if for.
func (cca Client) UserID() string {
return cca.userID
}
// authCodeURLOptions contains options for AuthCodeURL
type authCodeURLOptions struct {
claims, loginHint, tenantID, domainHint string
@@ -508,13 +435,13 @@ func WithClaims(claims string) interface {
CallOption: options.NewCallOption(
func(a any) error {
switch t := a.(type) {
case *AcquireTokenByAuthCodeOptions:
case *acquireTokenByAuthCodeOptions:
t.claims = claims
case *acquireTokenByCredentialOptions:
t.claims = claims
case *acquireTokenOnBehalfOfOptions:
t.claims = claims
case *AcquireTokenSilentOptions:
case *acquireTokenSilentOptions:
t.claims = claims
case *authCodeURLOptions:
t.claims = claims
@@ -527,7 +454,7 @@ func WithClaims(claims string) interface {
}
}
// WithTenantID specifies a tenant for a single authentication. It may be different than the tenant set in [New] by [WithAuthority].
// WithTenantID specifies a tenant for a single authentication. It may be different than the tenant set in [New].
// This option is valid for any token acquisition method.
func WithTenantID(tenantID string) interface {
AcquireByAuthCodeOption
@@ -548,13 +475,13 @@ func WithTenantID(tenantID string) interface {
CallOption: options.NewCallOption(
func(a any) error {
switch t := a.(type) {
case *AcquireTokenByAuthCodeOptions:
case *acquireTokenByAuthCodeOptions:
t.tenantID = tenantID
case *acquireTokenByCredentialOptions:
t.tenantID = tenantID
case *acquireTokenOnBehalfOfOptions:
t.tenantID = tenantID
case *AcquireTokenSilentOptions:
case *acquireTokenSilentOptions:
t.tenantID = tenantID
case *authCodeURLOptions:
t.tenantID = tenantID
@@ -567,12 +494,10 @@ func WithTenantID(tenantID string) interface {
}
}
// AcquireTokenSilentOptions are all the optional settings to an AcquireTokenSilent() call.
// acquireTokenSilentOptions are all the optional settings to an AcquireTokenSilent() call.
// These are set by using various AcquireTokenSilentOption functions.
type AcquireTokenSilentOptions struct {
// Account represents the account to use. To set, use the WithSilentAccount() option.
Account Account
type acquireTokenSilentOptions struct {
account Account
claims, tenantID string
}
@@ -581,11 +506,6 @@ type AcquireSilentOption interface {
acquireSilentOption()
}
// AcquireTokenSilentOption changes options inside AcquireTokenSilentOptions used in .AcquireTokenSilent().
type AcquireTokenSilentOption func(a *AcquireTokenSilentOptions)
func (AcquireTokenSilentOption) acquireSilentOption() {}
// WithSilentAccount uses the passed account during an AcquireTokenSilent() call.
func WithSilentAccount(account Account) interface {
AcquireSilentOption
@@ -598,8 +518,8 @@ func WithSilentAccount(account Account) interface {
CallOption: options.NewCallOption(
func(a any) error {
switch t := a.(type) {
case *AcquireTokenSilentOptions:
t.Account = account
case *acquireTokenSilentOptions:
t.account = account
default:
return fmt.Errorf("unexpected options type %T", a)
}
@@ -613,7 +533,7 @@ func WithSilentAccount(account Account) interface {
//
// Options: [WithClaims], [WithSilentAccount], [WithTenantID]
func (cca Client) AcquireTokenSilent(ctx context.Context, scopes []string, opts ...AcquireSilentOption) (AuthResult, error) {
o := AcquireTokenSilentOptions{}
o := acquireTokenSilentOptions{}
if err := options.ApplyOptions(&o, opts); err != nil {
return AuthResult{}, err
}
@@ -624,21 +544,19 @@ func (cca Client) AcquireTokenSilent(ctx context.Context, scopes []string, opts
silentParameters := base.AcquireTokenSilentParameters{
Scopes: scopes,
Account: o.Account,
Account: o.account,
RequestType: accesstokens.ATConfidential,
Credential: cca.cred,
IsAppCache: o.Account.IsZero(),
IsAppCache: o.account.IsZero(),
TenantID: o.tenantID,
}
return cca.base.AcquireTokenSilent(ctx, silentParameters)
}
// AcquireTokenByAuthCodeOptions contains the optional parameters used to acquire an access token using the authorization code flow.
type AcquireTokenByAuthCodeOptions struct {
Challenge string
claims, tenantID string
// acquireTokenByAuthCodeOptions contains the optional parameters used to acquire an access token using the authorization code flow.
type acquireTokenByAuthCodeOptions struct {
challenge, claims, tenantID string
}
// AcquireByAuthCodeOption is implemented by options for AcquireTokenByAuthCode
@@ -646,11 +564,6 @@ type AcquireByAuthCodeOption interface {
acquireByAuthCodeOption()
}
// AcquireTokenByAuthCodeOption changes options inside AcquireTokenByAuthCodeOptions used in .AcquireTokenByAuthCode().
type AcquireTokenByAuthCodeOption func(a *AcquireTokenByAuthCodeOptions)
func (AcquireTokenByAuthCodeOption) acquireByAuthCodeOption() {}
// WithChallenge allows you to provide a challenge for the .AcquireTokenByAuthCode() call.
func WithChallenge(challenge string) interface {
AcquireByAuthCodeOption
@@ -663,8 +576,8 @@ func WithChallenge(challenge string) interface {
CallOption: options.NewCallOption(
func(a any) error {
switch t := a.(type) {
case *AcquireTokenByAuthCodeOptions:
t.Challenge = challenge
case *acquireTokenByAuthCodeOptions:
t.challenge = challenge
default:
return fmt.Errorf("unexpected options type %T", a)
}
@@ -679,7 +592,7 @@ func WithChallenge(challenge string) interface {
//
// Options: [WithChallenge], [WithClaims], [WithTenantID]
func (cca Client) AcquireTokenByAuthCode(ctx context.Context, code string, redirectURI string, scopes []string, opts ...AcquireByAuthCodeOption) (AuthResult, error) {
o := AcquireTokenByAuthCodeOptions{}
o := acquireTokenByAuthCodeOptions{}
if err := options.ApplyOptions(&o, opts); err != nil {
return AuthResult{}, err
}
@@ -687,7 +600,7 @@ func (cca Client) AcquireTokenByAuthCode(ctx context.Context, code string, redir
params := base.AcquireTokenAuthCodeParameters{
Scopes: scopes,
Code: code,
Challenge: o.Challenge,
Challenge: o.challenge,
Claims: o.claims,
AppType: accesstokens.ATConfidential,
Credential: cca.cred, // This setting differs from public.Client.AcquireTokenByAuthCode
@@ -762,12 +675,11 @@ func (cca Client) AcquireTokenOnBehalfOf(ctx context.Context, userAssertion stri
}
// Account gets the account in the token cache with the specified homeAccountID.
func (cca Client) Account(homeAccountID string) Account {
return cca.base.Account(homeAccountID)
func (cca Client) Account(ctx context.Context, accountID string) (Account, error) {
return cca.base.Account(ctx, accountID)
}
// RemoveAccount signs the account out and forgets account from token cache.
func (cca Client) RemoveAccount(account Account) error {
cca.base.RemoveAccount(account)
return nil
func (cca Client) RemoveAccount(ctx context.Context, account Account) error {
return cca.base.RemoveAccount(ctx, account)
}

View File

@@ -69,7 +69,7 @@ func (e CallErr) Error() string {
// Verbose prints a versbose error message with the request or response.
func (e CallErr) Verbose() string {
e.Resp.Request = nil // This brings in a bunch of TLS crap we don't need
e.Resp.Request = nil // This brings in a bunch of TLS stuff we don't need
e.Resp.TLS = nil // Same
return fmt.Sprintf("%s:\nRequest:\n%s\nResponse:\n%s", e.Err, prettyConf.Sprint(e.Req), prettyConf.Sprint(e.Resp))
}

View File

@@ -10,6 +10,7 @@ import (
"net/url"
"reflect"
"strings"
"sync"
"time"
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/cache"
@@ -27,27 +28,21 @@ const (
)
// manager provides an internal cache. It is defined to allow faking the cache in tests.
// In all production use it is a *storage.Manager.
// In production it's a *storage.Manager or *storage.PartitionedManager.
type manager interface {
Read(ctx context.Context, authParameters authority.AuthParams, account shared.Account) (storage.TokenResponse, error)
Write(authParameters authority.AuthParams, tokenResponse accesstokens.TokenResponse) (shared.Account, error)
cache.Serializer
Read(context.Context, authority.AuthParams) (storage.TokenResponse, error)
Write(authority.AuthParams, accesstokens.TokenResponse) (shared.Account, error)
}
// accountManager is a manager that also caches accounts. In production it's a *storage.Manager.
type accountManager interface {
manager
AllAccounts() []shared.Account
Account(homeAccountID string) shared.Account
RemoveAccount(account shared.Account, clientID string)
}
// partitionedManager provides an internal cache. It is defined to allow faking the cache in tests.
// In all production use it is a *storage.PartitionedManager.
type partitionedManager interface {
Read(ctx context.Context, authParameters authority.AuthParams) (storage.TokenResponse, error)
Write(authParameters authority.AuthParams, tokenResponse accesstokens.TokenResponse) (shared.Account, error)
}
type noopCacheAccessor struct{}
func (n noopCacheAccessor) Replace(cache cache.Unmarshaler, key string) {}
func (n noopCacheAccessor) Export(cache cache.Marshaler, key string) {}
// AcquireTokenSilentParameters contains the parameters to acquire a token silently (from cache).
type AcquireTokenSilentParameters struct {
Scopes []string
@@ -133,12 +128,14 @@ func NewAuthResult(tokenResponse accesstokens.TokenResponse, account shared.Acco
// Client is a base client that provides access to common methods and primatives that
// can be used by multiple clients.
type Client struct {
Token *oauth.Client
manager manager // *storage.Manager or fakeManager in tests
pmanager partitionedManager // *storage.PartitionedManager or fakeManager in tests
Token *oauth.Client
manager accountManager // *storage.Manager or fakeManager in tests
// pmanager is a partitioned cache for OBO authentication. *storage.PartitionedManager or fakeManager in tests
pmanager manager
AuthParams authority.AuthParams // DO NOT EVER MAKE THIS A POINTER! See "Note" in New().
cacheAccessor cache.ExportReplace
AuthParams authority.AuthParams // DO NOT EVER MAKE THIS A POINTER! See "Note" in New().
cacheAccessor cache.ExportReplace
cacheAccessorMu *sync.RWMutex
}
// Option is an optional argument to the New constructor.
@@ -210,11 +207,11 @@ func New(clientID string, authorityURI string, token *oauth.Client, options ...O
}
authParams := authority.NewAuthParams(clientID, authInfo)
client := Client{ // Note: Hey, don't even THINK about making Base into *Base. See "design notes" in public.go and confidential.go
Token: token,
AuthParams: authParams,
cacheAccessor: noopCacheAccessor{},
manager: storage.New(token),
pmanager: storage.NewPartitionedManager(token),
Token: token,
AuthParams: authParams,
cacheAccessorMu: &sync.RWMutex{},
manager: storage.New(token),
pmanager: storage.NewPartitionedManager(token),
}
for _, o := range options {
if err = o(&client); err != nil {
@@ -280,11 +277,12 @@ func (b Client) AuthCodeURL(ctx context.Context, clientID, redirectURI string, s
}
func (b Client) AcquireTokenSilent(ctx context.Context, silent AcquireTokenSilentParameters) (AuthResult, error) {
// when tenant == "", the caller didn't specify a tenant and WithTenant will use the client's configured tenant
ar := AuthResult{}
// when tenant == "", the caller didn't specify a tenant and WithTenant will choose the client's configured tenant
tenant := silent.TenantID
authParams, err := b.AuthParams.WithTenant(tenant)
if err != nil {
return AuthResult{}, err
return ar, err
}
authParams.Scopes = silent.Scopes
authParams.HomeAccountID = silent.Account.HomeAccountID
@@ -292,52 +290,45 @@ func (b Client) AcquireTokenSilent(ctx context.Context, silent AcquireTokenSilen
authParams.Claims = silent.Claims
authParams.UserAssertion = silent.UserAssertion
var storageTokenResponse storage.TokenResponse
if authParams.AuthorizationType == authority.ATOnBehalfOf {
if s, ok := b.pmanager.(cache.Serializer); ok {
suggestedCacheKey := authParams.CacheKey(silent.IsAppCache)
b.cacheAccessor.Replace(s, suggestedCacheKey)
defer b.cacheAccessor.Export(s, suggestedCacheKey)
}
storageTokenResponse, err = b.pmanager.Read(ctx, authParams)
if err != nil {
return AuthResult{}, err
}
} else {
if s, ok := b.manager.(cache.Serializer); ok {
suggestedCacheKey := authParams.CacheKey(silent.IsAppCache)
b.cacheAccessor.Replace(s, suggestedCacheKey)
defer b.cacheAccessor.Export(s, suggestedCacheKey)
}
m := b.pmanager
if authParams.AuthorizationType != authority.ATOnBehalfOf {
authParams.AuthorizationType = authority.ATRefreshToken
storageTokenResponse, err = b.manager.Read(ctx, authParams, silent.Account)
if err != nil {
return AuthResult{}, err
}
m = b.manager
}
if b.cacheAccessor != nil {
key := authParams.CacheKey(silent.IsAppCache)
b.cacheAccessorMu.RLock()
err = b.cacheAccessor.Replace(ctx, m, cache.ReplaceHints{PartitionKey: key})
b.cacheAccessorMu.RUnlock()
}
if err != nil {
return ar, err
}
storageTokenResponse, err := m.Read(ctx, authParams)
if err != nil {
return ar, err
}
// ignore cached access tokens when given claims
if silent.Claims == "" {
result, err := AuthResultFromStorage(storageTokenResponse)
ar, err = AuthResultFromStorage(storageTokenResponse)
if err == nil {
return result, nil
return ar, err
}
}
// redeem a cached refresh token, if available
if reflect.ValueOf(storageTokenResponse.RefreshToken).IsZero() {
return AuthResult{}, errors.New("no token found")
return ar, errors.New("no token found")
}
var cc *accesstokens.Credential
if silent.RequestType == accesstokens.ATConfidential {
cc = silent.Credential
}
token, err := b.Token.Refresh(ctx, silent.RequestType, authParams, cc, storageTokenResponse.RefreshToken)
if err != nil {
return AuthResult{}, err
return ar, err
}
return b.AuthResultFromToken(ctx, authParams, token, true)
}
@@ -405,63 +396,72 @@ func (b Client) AuthResultFromToken(ctx context.Context, authParams authority.Au
if !cacheWrite {
return NewAuthResult(token, shared.Account{})
}
var account shared.Account
var err error
var m manager = b.manager
if authParams.AuthorizationType == authority.ATOnBehalfOf {
if s, ok := b.pmanager.(cache.Serializer); ok {
suggestedCacheKey := token.CacheKey(authParams)
b.cacheAccessor.Replace(s, suggestedCacheKey)
defer b.cacheAccessor.Export(s, suggestedCacheKey)
}
account, err = b.pmanager.Write(authParams, token)
if err != nil {
return AuthResult{}, err
}
} else {
if s, ok := b.manager.(cache.Serializer); ok {
suggestedCacheKey := token.CacheKey(authParams)
b.cacheAccessor.Replace(s, suggestedCacheKey)
defer b.cacheAccessor.Export(s, suggestedCacheKey)
}
account, err = b.manager.Write(authParams, token)
m = b.pmanager
}
key := token.CacheKey(authParams)
if b.cacheAccessor != nil {
b.cacheAccessorMu.Lock()
defer b.cacheAccessorMu.Unlock()
err := b.cacheAccessor.Replace(ctx, m, cache.ReplaceHints{PartitionKey: key})
if err != nil {
return AuthResult{}, err
}
}
return NewAuthResult(token, account)
account, err := m.Write(authParams, token)
if err != nil {
return AuthResult{}, err
}
ar, err := NewAuthResult(token, account)
if err == nil && b.cacheAccessor != nil {
err = b.cacheAccessor.Export(ctx, b.manager, cache.ExportHints{PartitionKey: key})
}
return ar, err
}
func (b Client) AllAccounts() []shared.Account {
if s, ok := b.manager.(cache.Serializer); ok {
suggestedCacheKey := b.AuthParams.CacheKey(false)
b.cacheAccessor.Replace(s, suggestedCacheKey)
defer b.cacheAccessor.Export(s, suggestedCacheKey)
func (b Client) AllAccounts(ctx context.Context) ([]shared.Account, error) {
if b.cacheAccessor != nil {
b.cacheAccessorMu.RLock()
defer b.cacheAccessorMu.RUnlock()
key := b.AuthParams.CacheKey(false)
err := b.cacheAccessor.Replace(ctx, b.manager, cache.ReplaceHints{PartitionKey: key})
if err != nil {
return nil, err
}
}
accounts := b.manager.AllAccounts()
return accounts
return b.manager.AllAccounts(), nil
}
func (b Client) Account(homeAccountID string) shared.Account {
authParams := b.AuthParams // This is a copy, as we dont' have a pointer receiver and .AuthParams is not a pointer.
authParams.AuthorizationType = authority.AccountByID
authParams.HomeAccountID = homeAccountID
if s, ok := b.manager.(cache.Serializer); ok {
suggestedCacheKey := b.AuthParams.CacheKey(false)
b.cacheAccessor.Replace(s, suggestedCacheKey)
defer b.cacheAccessor.Export(s, suggestedCacheKey)
func (b Client) Account(ctx context.Context, homeAccountID string) (shared.Account, error) {
if b.cacheAccessor != nil {
b.cacheAccessorMu.RLock()
defer b.cacheAccessorMu.RUnlock()
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)
err := b.cacheAccessor.Replace(ctx, b.manager, cache.ReplaceHints{PartitionKey: key})
if err != nil {
return shared.Account{}, err
}
}
account := b.manager.Account(homeAccountID)
return account
return b.manager.Account(homeAccountID), nil
}
// RemoveAccount removes all the ATs, RTs and IDTs from the cache associated with this account.
func (b Client) RemoveAccount(account shared.Account) {
if s, ok := b.manager.(cache.Serializer); ok {
suggestedCacheKey := b.AuthParams.CacheKey(false)
b.cacheAccessor.Replace(s, suggestedCacheKey)
defer b.cacheAccessor.Export(s, suggestedCacheKey)
func (b Client) RemoveAccount(ctx context.Context, account shared.Account) error {
if b.cacheAccessor == nil {
b.manager.RemoveAccount(account, b.AuthParams.ClientID)
return nil
}
b.cacheAccessorMu.Lock()
defer b.cacheAccessorMu.Unlock()
key := b.AuthParams.CacheKey(false)
err := b.cacheAccessor.Replace(ctx, b.manager, cache.ReplaceHints{PartitionKey: key})
if err != nil {
return err
}
b.manager.RemoveAccount(account, b.AuthParams.ClientID)
return b.cacheAccessor.Export(ctx, b.manager, cache.ExportHints{PartitionKey: key})
}

View File

@@ -83,7 +83,7 @@ func isMatchingScopes(scopesOne []string, scopesTwo string) bool {
}
// Read reads a storage token from the cache if it exists.
func (m *Manager) Read(ctx context.Context, authParameters authority.AuthParams, account shared.Account) (TokenResponse, error) {
func (m *Manager) Read(ctx context.Context, authParameters authority.AuthParams) (TokenResponse, error) {
tr := TokenResponse{}
homeAccountID := authParameters.HomeAccountID
realm := authParameters.AuthorityInfo.Tenant
@@ -103,7 +103,8 @@ func (m *Manager) Read(ctx context.Context, authParameters authority.AuthParams,
accessToken := m.readAccessToken(homeAccountID, aliases, realm, clientID, scopes)
tr.AccessToken = accessToken
if account.IsZero() {
if homeAccountID == "" {
// caller didn't specify a user, so there's no reason to search for an ID or refresh token
return tr, nil
}
// errors returned by read* methods indicate a cache miss and are therefore non-fatal. We continue populating
@@ -122,7 +123,7 @@ func (m *Manager) Read(ctx context.Context, authParameters authority.AuthParams,
}
}
account, err = m.readAccount(homeAccountID, aliases, realm)
account, err := m.readAccount(homeAccountID, aliases, realm)
if err == nil {
tr.Account = account
}
@@ -493,6 +494,8 @@ func (m *Manager) update(cache *Contract) {
// Marshal implements cache.Marshaler.
func (m *Manager) Marshal() ([]byte, error) {
m.contractMu.RLock()
defer m.contractMu.RUnlock()
return json.Marshal(m.contract)
}

View File

@@ -76,12 +76,17 @@ func (t *Client) ResolveEndpoints(ctx context.Context, authorityInfo authority.I
return t.Resolver.ResolveEndpoints(ctx, authorityInfo, userPrincipalName)
}
// AADInstanceDiscovery attempts to discover a tenant endpoint (used in OIDC auth with an authorization endpoint).
// This is done by AAD which allows for aliasing of tenants (windows.sts.net is the same as login.windows.com).
func (t *Client) AADInstanceDiscovery(ctx context.Context, authorityInfo authority.Info) (authority.InstanceDiscoveryResponse, error) {
return t.Authority.AADInstanceDiscovery(ctx, authorityInfo)
}
// AuthCode returns a token based on an authorization code.
func (t *Client) AuthCode(ctx context.Context, req accesstokens.AuthCodeRequest) (accesstokens.TokenResponse, error) {
if err := scopeError(req.AuthParams); err != nil {
return accesstokens.TokenResponse{}, err
}
if err := t.resolveEndpoint(ctx, &req.AuthParams, ""); err != nil {
return accesstokens.TokenResponse{}, err
}
@@ -107,6 +112,10 @@ func (t *Client) Credential(ctx context.Context, authParams authority.AuthParams
}
tr, err := cred.TokenProvider(ctx, params)
if err != nil {
if len(scopes) == 0 {
err = fmt.Errorf("token request had an empty authority.AuthParams.Scopes, which may cause the following error: %w", err)
return accesstokens.TokenResponse{}, err
}
return accesstokens.TokenResponse{}, err
}
return accesstokens.TokenResponse{
@@ -134,6 +143,9 @@ func (t *Client) Credential(ctx context.Context, authParams authority.AuthParams
// Credential acquires a token from the authority using a client credentials grant.
func (t *Client) OnBehalfOf(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
}
@@ -145,20 +157,35 @@ func (t *Client) OnBehalfOf(ctx context.Context, authParams authority.AuthParams
if err != nil {
return accesstokens.TokenResponse{}, err
}
return t.AccessTokens.FromUserAssertionClientCertificate(ctx, authParams, authParams.UserAssertion, jwt)
tr, err := t.AccessTokens.FromUserAssertionClientCertificate(ctx, authParams, authParams.UserAssertion, jwt)
if err != nil {
return accesstokens.TokenResponse{}, err
}
return tr, nil
}
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
}
if err := t.resolveEndpoint(ctx, &authParams, ""); err != nil {
return accesstokens.TokenResponse{}, err
}
return t.AccessTokens.FromRefreshToken(ctx, reqType, authParams, cc, refreshToken.Secret)
tr, err := t.AccessTokens.FromRefreshToken(ctx, reqType, authParams, cc, refreshToken.Secret)
if err != nil {
return accesstokens.TokenResponse{}, err
}
return tr, nil
}
// UsernamePassword retrieves a token where a username and password is used. However, if this is
// a user realm of "Federated", this uses SAML tokens. If "Managed", uses normal username/password.
func (t *Client) UsernamePassword(ctx context.Context, authParams authority.AuthParams) (accesstokens.TokenResponse, error) {
if err := scopeError(authParams); err != nil {
return accesstokens.TokenResponse{}, err
}
if authParams.AuthorityInfo.AuthorityType == authority.ADFS {
if err := t.resolveEndpoint(ctx, &authParams, authParams.Username); err != nil {
return accesstokens.TokenResponse{}, err
@@ -171,22 +198,32 @@ func (t *Client) UsernamePassword(ctx context.Context, authParams authority.Auth
userRealm, err := t.Authority.UserRealm(ctx, authParams)
if err != nil {
return accesstokens.TokenResponse{}, fmt.Errorf("problem getting user realm(user: %s) from authority: %w", authParams.Username, err)
return accesstokens.TokenResponse{}, fmt.Errorf("problem getting user realm from authority: %w", err)
}
switch userRealm.AccountType {
case authority.Federated:
mexDoc, err := t.WSTrust.Mex(ctx, userRealm.FederationMetadataURL)
if err != nil {
return accesstokens.TokenResponse{}, fmt.Errorf("problem getting mex doc from federated url(%s): %w", userRealm.FederationMetadataURL, err)
err = fmt.Errorf("problem getting mex doc from federated url(%s): %w", userRealm.FederationMetadataURL, err)
return accesstokens.TokenResponse{}, err
}
saml, err := t.WSTrust.SAMLTokenInfo(ctx, authParams, userRealm.CloudAudienceURN, mexDoc.UsernamePasswordEndpoint)
if err != nil {
return accesstokens.TokenResponse{}, fmt.Errorf("problem getting SAML token info: %w", err)
err = fmt.Errorf("problem getting SAML token info: %w", err)
return accesstokens.TokenResponse{}, err
}
return t.AccessTokens.FromSamlGrant(ctx, authParams, saml)
tr, err := t.AccessTokens.FromSamlGrant(ctx, authParams, saml)
if err != nil {
return accesstokens.TokenResponse{}, err
}
return tr, nil
case authority.Managed:
if len(authParams.Scopes) == 0 {
err = fmt.Errorf("token request had an empty authority.AuthParams.Scopes, which may cause the following error: %w", err)
return accesstokens.TokenResponse{}, err
}
return t.AccessTokens.FromUsernamePassword(ctx, authParams)
}
return accesstokens.TokenResponse{}, errors.New("unknown account type")
@@ -212,7 +249,6 @@ func (d DeviceCode) Token(ctx context.Context) (accesstokens.TokenResponse, erro
}
var cancel context.CancelFunc
d.Result.ExpiresOn.Sub(time.Now().UTC())
if deadline, ok := ctx.Deadline(); !ok || d.Result.ExpiresOn.Before(deadline) {
ctx, cancel = context.WithDeadline(ctx, d.Result.ExpiresOn)
} else {
@@ -275,6 +311,10 @@ func isWaitDeviceCodeErr(err error) bool {
// DeviceCode returns a DeviceCode object that can be used to get the code that must be entered on the second
// device and optionally the token once the code has been entered on the second device.
func (t *Client) DeviceCode(ctx context.Context, authParams authority.AuthParams) (DeviceCode, error) {
if err := scopeError(authParams); err != nil {
return DeviceCode{}, err
}
if err := t.resolveEndpoint(ctx, &authParams, ""); err != nil {
return DeviceCode{}, err
}
@@ -295,3 +335,19 @@ func (t *Client) resolveEndpoint(ctx context.Context, authParams *authority.Auth
authParams.Endpoints = endpoints
return nil
}
// scopeError takes an authority.AuthParams and returns an error
// if len(AuthParams.Scope) == 0.
func scopeError(a authority.AuthParams) error {
// TODO(someone): we could look deeper at the message to determine if
// it's a scope error, but this is a good start.
/*
{error":"invalid_scope","error_description":"AADSTS1002012: The provided value for scope
openid offline_access profile is not valid. Client credential flows must have a scope value
with /.default suffixed to the resource identifier (application ID URI)...}
*/
if len(a.Scopes) == 0 {
return fmt.Errorf("token request had an empty authority.AuthParams.Scopes, which is invalid")
}
return nil
}

View File

@@ -28,10 +28,19 @@ const (
regionName = "REGION_NAME"
defaultAPIVersion = "2021-10-01"
imdsEndpoint = "http://169.254.169.254/metadata/instance/compute/location?format=text&api-version=" + defaultAPIVersion
defaultHost = "login.microsoftonline.com"
autoDetectRegion = "TryAutoDetect"
)
// These are various hosts that host AAD Instance discovery endpoints.
const (
defaultHost = "login.microsoftonline.com"
loginMicrosoft = "login.microsoft.com"
loginWindows = "login.windows.net"
loginSTSWindows = "sts.windows.net"
loginMicrosoftOnline = defaultHost
)
// 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
}
@@ -54,6 +63,8 @@ func TrustedHost(host string) bool {
return false
}
// OAuthResponseBase is the base JSON return message for an OAuth call.
// This is embedded in other calls to get the base fields from every response.
type OAuthResponseBase struct {
Error string `json:"error"`
SubError string `json:"suberror"`
@@ -309,31 +320,24 @@ func firstPathSegment(u *url.URL) (string, error) {
return pathParts[1], nil
}
return "", errors.New("authority does not have two segments")
return "", errors.New(`authority must be an https URL such as "https://login.microsoftonline.com/<your tenant>"`)
}
// NewInfoFromAuthorityURI creates an AuthorityInfo instance from the authority URL provided.
func NewInfoFromAuthorityURI(authorityURI string, validateAuthority bool, instanceDiscoveryDisabled bool) (Info, error) {
authorityURI = strings.ToLower(authorityURI)
var authorityType string
u, err := url.Parse(authorityURI)
if err != nil {
return Info{}, fmt.Errorf("authorityURI passed could not be parsed: %w", err)
}
if u.Scheme != "https" {
return Info{}, fmt.Errorf("authorityURI(%s) must have scheme https", authorityURI)
func NewInfoFromAuthorityURI(authority string, validateAuthority bool, instanceDiscoveryDisabled bool) (Info, error) {
u, err := url.Parse(strings.ToLower(authority))
if err != nil || u.Scheme != "https" {
return Info{}, errors.New(`authority must be an https URL such as "https://login.microsoftonline.com/<your tenant>"`)
}
tenant, err := firstPathSegment(u)
if tenant == "adfs" {
authorityType = ADFS
} else {
authorityType = AAD
}
if err != nil {
return Info{}, err
}
authorityType := AAD
if tenant == "adfs" {
authorityType = ADFS
}
// u.Host includes the port, if any, which is required for private cloud deployments
return Info{
@@ -449,6 +453,8 @@ func (c Client) GetTenantDiscoveryResponse(ctx context.Context, openIDConfigurat
return resp, err
}
// AADInstanceDiscovery attempts to discover a tenant endpoint (used in OIDC auth with an authorization endpoint).
// This is done by AAD which allows for aliasing of tenants (windows.sts.net is the same as login.windows.com).
func (c Client) AADInstanceDiscovery(ctx context.Context, authorityInfo Info) (InstanceDiscoveryResponse, error) {
region := ""
var err error
@@ -461,9 +467,10 @@ func (c Client) AADInstanceDiscovery(ctx context.Context, authorityInfo Info) (I
if region != "" {
environment := authorityInfo.Host
switch environment {
case "login.microsoft.com", "login.windows.net", "sts.windows.net", defaultHost:
environment = "r." + defaultHost
case loginMicrosoft, loginWindows, loginSTSWindows, defaultHost:
environment = loginMicrosoft
}
resp.TenantDiscoveryEndpoint = fmt.Sprintf(tenantDiscoveryEndpointWithRegion, region, environment, authorityInfo.Tenant)
metadata := InstanceDiscoveryMetadata{
PreferredNetwork: fmt.Sprintf("%v.%v", region, authorityInfo.Host),

View File

@@ -5,4 +5,4 @@
package version
// Version is the version of this client package that is communicated to the server.
const Version = "0.8.1"
const Version = "1.0.0"

View File

@@ -47,27 +47,17 @@ type AuthResult = base.AuthResult
type Account = shared.Account
// Options configures the Client's behavior.
type Options struct {
// Accessor controls cache persistence. By default there is no cache persistence.
// This can be set with the WithCache() option.
Accessor cache.ExportReplace
// The host of the Azure Active Directory authority. The default is https://login.microsoftonline.com/common.
// This can be changed with the WithAuthority() option.
Authority string
// The HTTP client used for making requests.
// It defaults to a shared http.Client.
HTTPClient ops.HTTPClient
capabilities []string
// clientOptions configures the Client's behavior.
type clientOptions struct {
accessor cache.ExportReplace
authority string
capabilities []string
disableInstanceDiscovery bool
httpClient ops.HTTPClient
}
func (p *Options) validate() error {
u, err := url.Parse(p.Authority)
func (p *clientOptions) validate() error {
u, err := url.Parse(p.authority)
if err != nil {
return fmt.Errorf("Authority options cannot be URL parsed: %w", err)
}
@@ -78,25 +68,25 @@ func (p *Options) validate() error {
}
// Option is an optional argument to the New constructor.
type Option func(o *Options)
type Option func(o *clientOptions)
// WithAuthority allows for a custom authority to be set. This must be a valid https url.
func WithAuthority(authority string) Option {
return func(o *Options) {
o.Authority = authority
return func(o *clientOptions) {
o.authority = authority
}
}
// WithCache allows you to set some type of cache for storing authentication tokens.
// WithCache provides an accessor that will read and write authentication data to an externally managed cache.
func WithCache(accessor cache.ExportReplace) Option {
return func(o *Options) {
o.Accessor = accessor
return func(o *clientOptions) {
o.accessor = accessor
}
}
// WithClientCapabilities allows configuring one or more client capabilities such as "CP1"
func WithClientCapabilities(capabilities []string) Option {
return func(o *Options) {
return func(o *clientOptions) {
// there's no danger of sharing the slice's underlying memory with the application because
// this slice is simply passed to base.WithClientCapabilities, which copies its data
o.capabilities = capabilities
@@ -105,14 +95,14 @@ func WithClientCapabilities(capabilities []string) Option {
// WithHTTPClient allows for a custom HTTP client to be set.
func WithHTTPClient(httpClient ops.HTTPClient) Option {
return func(o *Options) {
o.HTTPClient = httpClient
return func(o *clientOptions) {
o.httpClient = httpClient
}
}
// WithInstanceDiscovery set to false to disable authority validation (to support private cloud scenarios)
func WithInstanceDiscovery(enabled bool) Option {
return func(o *Options) {
return func(o *clientOptions) {
o.disableInstanceDiscovery = !enabled
}
}
@@ -125,9 +115,9 @@ type Client struct {
// New is the constructor for Client.
func New(clientID string, options ...Option) (Client, error) {
opts := Options{
Authority: base.AuthorityPublicCloud,
HTTPClient: shared.DefaultClient,
opts := clientOptions{
authority: base.AuthorityPublicCloud,
httpClient: shared.DefaultClient,
}
for _, o := range options {
@@ -137,28 +127,28 @@ func New(clientID string, options ...Option) (Client, error) {
return Client{}, err
}
base, err := base.New(clientID, opts.Authority, oauth.New(opts.HTTPClient), base.WithCacheAccessor(opts.Accessor), base.WithClientCapabilities(opts.capabilities), base.WithInstanceDiscovery(!opts.disableInstanceDiscovery))
base, err := base.New(clientID, opts.authority, oauth.New(opts.httpClient), base.WithCacheAccessor(opts.accessor), base.WithClientCapabilities(opts.capabilities), base.WithInstanceDiscovery(!opts.disableInstanceDiscovery))
if err != nil {
return Client{}, err
}
return Client{base}, nil
}
// createAuthCodeURLOptions contains options for CreateAuthCodeURL
type createAuthCodeURLOptions struct {
// authCodeURLOptions contains options for AuthCodeURL
type authCodeURLOptions struct {
claims, loginHint, tenantID, domainHint string
}
// CreateAuthCodeURLOption is implemented by options for CreateAuthCodeURL
type CreateAuthCodeURLOption interface {
createAuthCodeURLOption()
// AuthCodeURLOption is implemented by options for AuthCodeURL
type AuthCodeURLOption interface {
authCodeURLOption()
}
// CreateAuthCodeURL creates a URL used to acquire an authorization code.
// AuthCodeURL creates a URL used to acquire an authorization code.
//
// Options: [WithClaims], [WithDomainHint], [WithLoginHint], [WithTenantID]
func (pca Client) CreateAuthCodeURL(ctx context.Context, clientID, redirectURI string, scopes []string, opts ...CreateAuthCodeURLOption) (string, error) {
o := createAuthCodeURLOptions{}
func (pca Client) AuthCodeURL(ctx context.Context, clientID, redirectURI string, scopes []string, opts ...AuthCodeURLOption) (string, error) {
o := authCodeURLOptions{}
if err := options.ApplyOptions(&o, opts); err != nil {
return "", err
}
@@ -181,7 +171,7 @@ func WithClaims(claims string) interface {
AcquireByUsernamePasswordOption
AcquireInteractiveOption
AcquireSilentOption
CreateAuthCodeURLOption
AuthCodeURLOption
options.CallOption
} {
return struct {
@@ -190,23 +180,23 @@ func WithClaims(claims string) interface {
AcquireByUsernamePasswordOption
AcquireInteractiveOption
AcquireSilentOption
CreateAuthCodeURLOption
AuthCodeURLOption
options.CallOption
}{
CallOption: options.NewCallOption(
func(a any) error {
switch t := a.(type) {
case *AcquireTokenByAuthCodeOptions:
case *acquireTokenByAuthCodeOptions:
t.claims = claims
case *acquireTokenByDeviceCodeOptions:
t.claims = claims
case *acquireTokenByUsernamePasswordOptions:
t.claims = claims
case *AcquireTokenSilentOptions:
case *acquireTokenSilentOptions:
t.claims = claims
case *createAuthCodeURLOptions:
case *authCodeURLOptions:
t.claims = claims
case *InteractiveAuthOptions:
case *interactiveAuthOptions:
t.claims = claims
default:
return fmt.Errorf("unexpected options type %T", a)
@@ -225,7 +215,7 @@ func WithTenantID(tenantID string) interface {
AcquireByUsernamePasswordOption
AcquireInteractiveOption
AcquireSilentOption
CreateAuthCodeURLOption
AuthCodeURLOption
options.CallOption
} {
return struct {
@@ -234,23 +224,23 @@ func WithTenantID(tenantID string) interface {
AcquireByUsernamePasswordOption
AcquireInteractiveOption
AcquireSilentOption
CreateAuthCodeURLOption
AuthCodeURLOption
options.CallOption
}{
CallOption: options.NewCallOption(
func(a any) error {
switch t := a.(type) {
case *AcquireTokenByAuthCodeOptions:
case *acquireTokenByAuthCodeOptions:
t.tenantID = tenantID
case *acquireTokenByDeviceCodeOptions:
t.tenantID = tenantID
case *acquireTokenByUsernamePasswordOptions:
t.tenantID = tenantID
case *AcquireTokenSilentOptions:
case *acquireTokenSilentOptions:
t.tenantID = tenantID
case *createAuthCodeURLOptions:
case *authCodeURLOptions:
t.tenantID = tenantID
case *InteractiveAuthOptions:
case *interactiveAuthOptions:
t.tenantID = tenantID
default:
return fmt.Errorf("unexpected options type %T", a)
@@ -261,12 +251,10 @@ func WithTenantID(tenantID string) interface {
}
}
// AcquireTokenSilentOptions are all the optional settings to an AcquireTokenSilent() call.
// acquireTokenSilentOptions are all the optional settings to an AcquireTokenSilent() call.
// These are set by using various AcquireTokenSilentOption functions.
type AcquireTokenSilentOptions struct {
// Account represents the account to use. To set, use the WithSilentAccount() option.
Account Account
type acquireTokenSilentOptions struct {
account Account
claims, tenantID string
}
@@ -275,11 +263,6 @@ type AcquireSilentOption interface {
acquireSilentOption()
}
// AcquireTokenSilentOption changes options inside AcquireTokenSilentOptions used in .AcquireTokenSilent().
type AcquireTokenSilentOption func(a *AcquireTokenSilentOptions)
func (AcquireTokenSilentOption) acquireSilentOption() {}
// WithSilentAccount uses the passed account during an AcquireTokenSilent() call.
func WithSilentAccount(account Account) interface {
AcquireSilentOption
@@ -292,8 +275,8 @@ func WithSilentAccount(account Account) interface {
CallOption: options.NewCallOption(
func(a any) error {
switch t := a.(type) {
case *AcquireTokenSilentOptions:
t.Account = account
case *acquireTokenSilentOptions:
t.account = account
default:
return fmt.Errorf("unexpected options type %T", a)
}
@@ -307,14 +290,14 @@ func WithSilentAccount(account Account) interface {
//
// Options: [WithClaims], [WithSilentAccount], [WithTenantID]
func (pca Client) AcquireTokenSilent(ctx context.Context, scopes []string, opts ...AcquireSilentOption) (AuthResult, error) {
o := AcquireTokenSilentOptions{}
o := acquireTokenSilentOptions{}
if err := options.ApplyOptions(&o, opts); err != nil {
return AuthResult{}, err
}
silentParameters := base.AcquireTokenSilentParameters{
Scopes: scopes,
Account: o.Account,
Account: o.account,
Claims: o.claims,
RequestType: accesstokens.ATPublic,
IsAppCache: false,
@@ -420,11 +403,9 @@ func (pca Client) AcquireTokenByDeviceCode(ctx context.Context, scopes []string,
return DeviceCode{Result: dc.Result, authParams: authParams, client: pca, dc: dc}, nil
}
// AcquireTokenByAuthCodeOptions contains the optional parameters used to acquire an access token using the authorization code flow.
type AcquireTokenByAuthCodeOptions struct {
Challenge string
claims, tenantID string
// acquireTokenByAuthCodeOptions contains the optional parameters used to acquire an access token using the authorization code flow.
type acquireTokenByAuthCodeOptions struct {
challenge, claims, tenantID string
}
// AcquireByAuthCodeOption is implemented by options for AcquireTokenByAuthCode
@@ -432,11 +413,6 @@ type AcquireByAuthCodeOption interface {
acquireByAuthCodeOption()
}
// AcquireTokenByAuthCodeOption changes options inside AcquireTokenByAuthCodeOptions used in .AcquireTokenByAuthCode().
type AcquireTokenByAuthCodeOption func(a *AcquireTokenByAuthCodeOptions)
func (AcquireTokenByAuthCodeOption) acquireByAuthCodeOption() {}
// WithChallenge allows you to provide a code for the .AcquireTokenByAuthCode() call.
func WithChallenge(challenge string) interface {
AcquireByAuthCodeOption
@@ -449,8 +425,8 @@ func WithChallenge(challenge string) interface {
CallOption: options.NewCallOption(
func(a any) error {
switch t := a.(type) {
case *AcquireTokenByAuthCodeOptions:
t.Challenge = challenge
case *acquireTokenByAuthCodeOptions:
t.challenge = challenge
default:
return fmt.Errorf("unexpected options type %T", a)
}
@@ -465,7 +441,7 @@ func WithChallenge(challenge string) interface {
//
// Options: [WithChallenge], [WithClaims], [WithTenantID]
func (pca Client) AcquireTokenByAuthCode(ctx context.Context, code string, redirectURI string, scopes []string, opts ...AcquireByAuthCodeOption) (AuthResult, error) {
o := AcquireTokenByAuthCodeOptions{}
o := acquireTokenByAuthCodeOptions{}
if err := options.ApplyOptions(&o, opts); err != nil {
return AuthResult{}, err
}
@@ -473,7 +449,7 @@ func (pca Client) AcquireTokenByAuthCode(ctx context.Context, code string, redir
params := base.AcquireTokenAuthCodeParameters{
Scopes: scopes,
Code: code,
Challenge: o.Challenge,
Challenge: o.challenge,
Claims: o.claims,
AppType: accesstokens.ATPublic,
RedirectURI: redirectURI,
@@ -485,23 +461,18 @@ func (pca Client) AcquireTokenByAuthCode(ctx context.Context, code string, redir
// Accounts gets all the accounts in the token cache.
// If there are no accounts in the cache the returned slice is empty.
func (pca Client) Accounts() []Account {
return pca.base.AllAccounts()
func (pca Client) Accounts(ctx context.Context) ([]Account, error) {
return pca.base.AllAccounts(ctx)
}
// RemoveAccount signs the account out and forgets account from token cache.
func (pca Client) RemoveAccount(account Account) error {
pca.base.RemoveAccount(account)
return nil
func (pca Client) RemoveAccount(ctx context.Context, account Account) error {
return pca.base.RemoveAccount(ctx, account)
}
// InteractiveAuthOptions contains the optional parameters used to acquire an access token for interactive auth code flow.
type InteractiveAuthOptions struct {
// Used to specify a custom port for the local server. http://localhost:portnumber
// All other URI components are ignored.
RedirectURI string
claims, loginHint, tenantID, domainHint string
// interactiveAuthOptions contains the optional parameters used to acquire an access token for interactive auth code flow.
type interactiveAuthOptions struct {
claims, domainHint, loginHint, redirectURI, tenantID string
}
// AcquireInteractiveOption is implemented by options for AcquireTokenInteractive
@@ -509,28 +480,23 @@ type AcquireInteractiveOption interface {
acquireInteractiveOption()
}
// InteractiveAuthOption changes options inside InteractiveAuthOptions used in .AcquireTokenInteractive().
type InteractiveAuthOption func(*InteractiveAuthOptions)
func (InteractiveAuthOption) acquireInteractiveOption() {}
// WithLoginHint pre-populates the login prompt with a username.
func WithLoginHint(username string) interface {
AcquireInteractiveOption
CreateAuthCodeURLOption
AuthCodeURLOption
options.CallOption
} {
return struct {
AcquireInteractiveOption
CreateAuthCodeURLOption
AuthCodeURLOption
options.CallOption
}{
CallOption: options.NewCallOption(
func(a any) error {
switch t := a.(type) {
case *createAuthCodeURLOptions:
case *authCodeURLOptions:
t.loginHint = username
case *InteractiveAuthOptions:
case *interactiveAuthOptions:
t.loginHint = username
default:
return fmt.Errorf("unexpected options type %T", a)
@@ -544,20 +510,20 @@ func WithLoginHint(username string) interface {
// WithDomainHint adds the IdP domain as domain_hint query parameter in the auth url.
func WithDomainHint(domain string) interface {
AcquireInteractiveOption
CreateAuthCodeURLOption
AuthCodeURLOption
options.CallOption
} {
return struct {
AcquireInteractiveOption
CreateAuthCodeURLOption
AuthCodeURLOption
options.CallOption
}{
CallOption: options.NewCallOption(
func(a any) error {
switch t := a.(type) {
case *createAuthCodeURLOptions:
case *authCodeURLOptions:
t.domainHint = domain
case *InteractiveAuthOptions:
case *interactiveAuthOptions:
t.domainHint = domain
default:
return fmt.Errorf("unexpected options type %T", a)
@@ -568,7 +534,8 @@ func WithDomainHint(domain string) interface {
}
}
// WithRedirectURI uses the specified redirect URI for interactive auth.
// WithRedirectURI sets a port for the local server used in interactive authentication, for
// example http://localhost:port. All URI components other than the port are ignored.
func WithRedirectURI(redirectURI string) interface {
AcquireInteractiveOption
options.CallOption
@@ -580,8 +547,8 @@ func WithRedirectURI(redirectURI string) interface {
CallOption: options.NewCallOption(
func(a any) error {
switch t := a.(type) {
case *InteractiveAuthOptions:
t.RedirectURI = redirectURI
case *interactiveAuthOptions:
t.redirectURI = redirectURI
default:
return fmt.Errorf("unexpected options type %T", a)
}
@@ -596,7 +563,7 @@ func WithRedirectURI(redirectURI string) interface {
//
// Options: [WithDomainHint], [WithLoginHint], [WithRedirectURI], [WithTenantID]
func (pca Client) AcquireTokenInteractive(ctx context.Context, scopes []string, opts ...AcquireInteractiveOption) (AuthResult, error) {
o := InteractiveAuthOptions{}
o := interactiveAuthOptions{}
if err := options.ApplyOptions(&o, opts); err != nil {
return AuthResult{}, err
}
@@ -607,8 +574,8 @@ func (pca Client) AcquireTokenInteractive(ctx context.Context, scopes []string,
return AuthResult{}, err
}
var redirectURL *url.URL
if o.RedirectURI != "" {
redirectURL, err = url.Parse(o.RedirectURI)
if o.redirectURI != "" {
redirectURL, err = url.Parse(o.redirectURI)
if err != nil {
return AuthResult{}, err
}