forked from TrueCloudLab/distribution
ba4a6bbe02
Microsoft has updated the golang Azure SDK significantly. Update the azure storage driver to use the new SDK. Add support for client secret and MSI authentication schemes in addition to shared key authentication. Implement rootDirectory support for the azure storage driver to mirror the S3 driver. Signed-off-by: Kirat Singh <kirat.singh@beacon.io> Co-authored-by: Cory Snider <corhere@gmail.com>
773 lines
26 KiB
Go
773 lines
26 KiB
Go
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT license.
|
|
|
|
/*
|
|
Package confidential provides a client for authentication of "confidential" applications.
|
|
A "confidential" application is defined as an app that run on servers. They are considered
|
|
difficult to access and for that reason capable of keeping an application secret.
|
|
Confidential clients can hold configuration-time secrets.
|
|
*/
|
|
package confidential
|
|
|
|
import (
|
|
"context"
|
|
"crypto"
|
|
"crypto/rsa"
|
|
"crypto/x509"
|
|
"encoding/base64"
|
|
"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"
|
|
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/exported"
|
|
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth"
|
|
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops"
|
|
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/accesstokens"
|
|
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/authority"
|
|
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/options"
|
|
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/shared"
|
|
)
|
|
|
|
/*
|
|
Design note:
|
|
|
|
confidential.Client uses base.Client as an embedded type. base.Client statically assigns its attributes
|
|
during creation. As it doesn't have any pointers in it, anything borrowed from it, such as
|
|
Base.AuthParams is a copy that is free to be manipulated here.
|
|
|
|
Duplicate Calls shared between public.Client and this package:
|
|
There is some duplicate call options provided here that are the same as in public.Client . This
|
|
is a design choices. Go proverb(https://www.youtube.com/watch?v=PAAkCSZUG1c&t=9m28s):
|
|
"a little copying is better than a little dependency". Yes, we could have another package with
|
|
shared options (fail). That divides like 2 options from all others which makes the user look
|
|
through more docs. We can have all clients in one package, but I think separate packages
|
|
here makes for better naming (public.Client vs client.PublicClient). So I chose a little
|
|
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.
|
|
*/
|
|
|
|
// TODO(msal): This should have example code for each method on client using Go's example doc framework.
|
|
// base usage details should be include in the package documentation.
|
|
|
|
// AuthResult contains the results of one token acquisition operation.
|
|
// For details see https://aka.ms/msal-net-authenticationresult
|
|
type AuthResult = base.AuthResult
|
|
|
|
type Account = shared.Account
|
|
|
|
// 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.
|
|
func CertFromPEM(pemData []byte, password string) ([]*x509.Certificate, crypto.PrivateKey, error) {
|
|
var certs []*x509.Certificate
|
|
var priv crypto.PrivateKey
|
|
for {
|
|
block, rest := pem.Decode(pemData)
|
|
if block == nil {
|
|
break
|
|
}
|
|
|
|
//nolint:staticcheck // x509.IsEncryptedPEMBlock and x509.DecryptPEMBlock are deprecated. They are used here only to support a usecase.
|
|
if x509.IsEncryptedPEMBlock(block) {
|
|
b, err := x509.DecryptPEMBlock(block, []byte(password))
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("could not decrypt encrypted PEM block: %v", err)
|
|
}
|
|
block, _ = pem.Decode(b)
|
|
if block == nil {
|
|
return nil, nil, fmt.Errorf("encounter encrypted PEM block that did not decode")
|
|
}
|
|
}
|
|
|
|
switch block.Type {
|
|
case "CERTIFICATE":
|
|
cert, err := x509.ParseCertificate(block.Bytes)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("block labelled 'CERTIFICATE' could not be parsed by x509: %v", err)
|
|
}
|
|
certs = append(certs, cert)
|
|
case "PRIVATE KEY":
|
|
if priv != nil {
|
|
return nil, nil, errors.New("found multiple private key blocks")
|
|
}
|
|
|
|
var err error
|
|
priv, err = x509.ParsePKCS8PrivateKey(block.Bytes)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("could not decode private key: %v", err)
|
|
}
|
|
case "RSA PRIVATE KEY":
|
|
if priv != nil {
|
|
return nil, nil, errors.New("found multiple private key blocks")
|
|
}
|
|
var err error
|
|
priv, err = x509.ParsePKCS1PrivateKey(block.Bytes)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("could not decode private key: %v", err)
|
|
}
|
|
}
|
|
pemData = rest
|
|
}
|
|
|
|
if len(certs) == 0 {
|
|
return nil, nil, fmt.Errorf("no certificates found")
|
|
}
|
|
|
|
if priv == nil {
|
|
return nil, nil, fmt.Errorf("no private key found")
|
|
}
|
|
|
|
return certs, priv, nil
|
|
}
|
|
|
|
// AssertionRequestOptions has required information for client assertion claims
|
|
type AssertionRequestOptions = exported.AssertionRequestOptions
|
|
|
|
// Credential represents the credential used in confidential client flows.
|
|
type Credential struct {
|
|
secret string
|
|
|
|
cert *x509.Certificate
|
|
key crypto.PrivateKey
|
|
x5c []string
|
|
|
|
assertionCallback func(context.Context, AssertionRequestOptions) (string, error)
|
|
|
|
tokenProvider func(context.Context, TokenProviderParameters) (TokenProviderResult, error)
|
|
}
|
|
|
|
// toInternal returns the accesstokens.Credential that is used internally. The current structure of the
|
|
// code requires that client.go, requests.go and confidential.go share a credential type without
|
|
// having import recursion. That requires the type used between is in a shared package. Therefore
|
|
// we have this.
|
|
func (c Credential) toInternal() (*accesstokens.Credential, error) {
|
|
if c.secret != "" {
|
|
return &accesstokens.Credential{Secret: c.secret}, nil
|
|
}
|
|
if c.cert != nil {
|
|
if c.key == nil {
|
|
return nil, errors.New("missing private key for certificate")
|
|
}
|
|
return &accesstokens.Credential{Cert: c.cert, Key: c.key, X5c: c.x5c}, nil
|
|
}
|
|
if c.key != nil {
|
|
return nil, errors.New("missing certificate for private key")
|
|
}
|
|
if c.assertionCallback != nil {
|
|
return &accesstokens.Credential{AssertionCallback: c.assertionCallback}, nil
|
|
}
|
|
if c.tokenProvider != nil {
|
|
return &accesstokens.Credential{TokenProvider: c.tokenProvider}, nil
|
|
}
|
|
return nil, errors.New("invalid credential")
|
|
}
|
|
|
|
// NewCredFromSecret creates a Credential from a secret.
|
|
func NewCredFromSecret(secret string) (Credential, error) {
|
|
if secret == "" {
|
|
return Credential{}, errors.New("secret can't be empty string")
|
|
}
|
|
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) {
|
|
cred := Credential{key: key}
|
|
k, ok := key.(*rsa.PrivateKey)
|
|
if !ok {
|
|
return cred, errors.New("key must be an RSA key")
|
|
}
|
|
for _, cert := range certs {
|
|
if cert == nil {
|
|
// not returning an error here because certs may still contain a sufficient cert/key pair
|
|
continue
|
|
}
|
|
certKey, ok := cert.PublicKey.(*rsa.PublicKey)
|
|
if ok && k.E == certKey.E && k.N.Cmp(certKey.N) == 0 {
|
|
// We know this is the signing cert because its public key matches the given private key.
|
|
// This cert must be first in x5c.
|
|
cred.cert = cert
|
|
cred.x5c = append([]string{base64.StdEncoding.EncodeToString(cert.Raw)}, cred.x5c...)
|
|
} else {
|
|
cred.x5c = append(cred.x5c, base64.StdEncoding.EncodeToString(cert.Raw))
|
|
}
|
|
}
|
|
if cred.cert == nil {
|
|
return cred, errors.New("key doesn't match any certificate")
|
|
}
|
|
return cred, nil
|
|
}
|
|
|
|
// TokenProviderParameters is the authentication parameters passed to token providers
|
|
type TokenProviderParameters = exported.TokenProviderParameters
|
|
|
|
// TokenProviderResult is the authentication result returned by custom token providers
|
|
type TokenProviderResult = exported.TokenProviderResult
|
|
|
|
// NewCredFromTokenProvider creates a Credential from a function that provides access tokens. The function
|
|
// must be concurrency safe. This is intended only to allow the Azure SDK to cache MSI tokens. It isn't
|
|
// useful to applications in general because the token provider must implement all authentication logic.
|
|
func NewCredFromTokenProvider(provider func(context.Context, TokenProviderParameters) (TokenProviderResult, error)) Credential {
|
|
return Credential{tokenProvider: provider}
|
|
}
|
|
|
|
// AutoDetectRegion instructs MSAL Go to auto detect region for Azure regional token service.
|
|
func AutoDetectRegion() string {
|
|
return "TryAutoDetect"
|
|
}
|
|
|
|
// Client is a representation of authentication client for confidential applications as defined in the
|
|
// package doc. A new Client should be created PER SERVICE USER.
|
|
// 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
|
|
// 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
|
|
}
|
|
|
|
// Option is an optional argument to New().
|
|
type Option func(o *Options)
|
|
|
|
// 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
|
|
}
|
|
}
|
|
|
|
// WithClientCapabilities allows configuring one or more client capabilities such as "CP1"
|
|
func WithClientCapabilities(capabilities []string) Option {
|
|
return func(o *Options) {
|
|
// 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
|
|
}
|
|
}
|
|
|
|
// WithHTTPClient allows for a custom HTTP client to be set.
|
|
func WithHTTPClient(httpClient ops.HTTPClient) Option {
|
|
return func(o *Options) {
|
|
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
|
|
}
|
|
}
|
|
|
|
// WithInstanceDiscovery set to false to disable authority validation (to support private cloud scenarios)
|
|
func WithInstanceDiscovery(enabled bool) Option {
|
|
return func(o *Options) {
|
|
o.disableInstanceDiscovery = !enabled
|
|
}
|
|
}
|
|
|
|
// WithAzureRegion sets the region(preferred) or Confidential.AutoDetectRegion() for auto detecting region.
|
|
// Region names as per https://azure.microsoft.com/en-ca/global-infrastructure/geographies/.
|
|
// See https://aka.ms/region-map for more details on region names.
|
|
// The region value should be short region name for the region where the service is deployed.
|
|
// For example "centralus" is short name for region Central US.
|
|
// Not all auth flows can use the regional token service.
|
|
// Service To Service (client credential flow) tokens can be obtained from the regional service.
|
|
// Requires configuration at the tenant level.
|
|
// Auto-detection works on a limited number of Azure artifacts (VMs, Azure functions).
|
|
// 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
|
|
}
|
|
}
|
|
|
|
// 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) {
|
|
internalCred, err := cred.toInternal()
|
|
if err != nil {
|
|
return Client{}, err
|
|
}
|
|
|
|
opts := Options{
|
|
Authority: base.AuthorityPublicCloud,
|
|
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.WithClientCapabilities(opts.capabilities),
|
|
base.WithRegionDetection(opts.AzureRegion),
|
|
base.WithX5C(opts.SendX5C),
|
|
base.WithInstanceDiscovery(!opts.disableInstanceDiscovery),
|
|
}
|
|
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...)
|
|
if err != nil {
|
|
return Client{}, err
|
|
}
|
|
base.AuthParams.IsConfidentialClient = true
|
|
|
|
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
|
|
}
|
|
|
|
// AuthCodeURLOption is implemented by options for AuthCodeURL
|
|
type AuthCodeURLOption interface {
|
|
authCodeURLOption()
|
|
}
|
|
|
|
// AuthCodeURL creates a URL used to acquire an authorization code. Users need to call CreateAuthorizationCodeURLParameters and pass it in.
|
|
//
|
|
// Options: [WithClaims], [WithDomainHint], [WithLoginHint], [WithTenantID]
|
|
func (cca 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
|
|
}
|
|
ap, err := cca.base.AuthParams.WithTenant(o.tenantID)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
ap.Claims = o.claims
|
|
ap.LoginHint = o.loginHint
|
|
ap.DomainHint = o.domainHint
|
|
return cca.base.AuthCodeURL(ctx, clientID, redirectURI, scopes, ap)
|
|
}
|
|
|
|
// WithLoginHint pre-populates the login prompt with a username.
|
|
func WithLoginHint(username string) interface {
|
|
AuthCodeURLOption
|
|
options.CallOption
|
|
} {
|
|
return struct {
|
|
AuthCodeURLOption
|
|
options.CallOption
|
|
}{
|
|
CallOption: options.NewCallOption(
|
|
func(a any) error {
|
|
switch t := a.(type) {
|
|
case *authCodeURLOptions:
|
|
t.loginHint = username
|
|
default:
|
|
return fmt.Errorf("unexpected options type %T", a)
|
|
}
|
|
return nil
|
|
},
|
|
),
|
|
}
|
|
}
|
|
|
|
// WithDomainHint adds the IdP domain as domain_hint query parameter in the auth url.
|
|
func WithDomainHint(domain string) interface {
|
|
AuthCodeURLOption
|
|
options.CallOption
|
|
} {
|
|
return struct {
|
|
AuthCodeURLOption
|
|
options.CallOption
|
|
}{
|
|
CallOption: options.NewCallOption(
|
|
func(a any) error {
|
|
switch t := a.(type) {
|
|
case *authCodeURLOptions:
|
|
t.domainHint = domain
|
|
default:
|
|
return fmt.Errorf("unexpected options type %T", a)
|
|
}
|
|
return nil
|
|
},
|
|
),
|
|
}
|
|
}
|
|
|
|
// WithClaims sets additional claims to request for the token, such as those required by conditional access policies.
|
|
// Use this option when Azure AD returned a claims challenge for a prior request. The argument must be decoded.
|
|
// This option is valid for any token acquisition method.
|
|
func WithClaims(claims string) interface {
|
|
AcquireByAuthCodeOption
|
|
AcquireByCredentialOption
|
|
AcquireOnBehalfOfOption
|
|
AcquireSilentOption
|
|
AuthCodeURLOption
|
|
options.CallOption
|
|
} {
|
|
return struct {
|
|
AcquireByAuthCodeOption
|
|
AcquireByCredentialOption
|
|
AcquireOnBehalfOfOption
|
|
AcquireSilentOption
|
|
AuthCodeURLOption
|
|
options.CallOption
|
|
}{
|
|
CallOption: options.NewCallOption(
|
|
func(a any) error {
|
|
switch t := a.(type) {
|
|
case *AcquireTokenByAuthCodeOptions:
|
|
t.claims = claims
|
|
case *acquireTokenByCredentialOptions:
|
|
t.claims = claims
|
|
case *acquireTokenOnBehalfOfOptions:
|
|
t.claims = claims
|
|
case *AcquireTokenSilentOptions:
|
|
t.claims = claims
|
|
case *authCodeURLOptions:
|
|
t.claims = claims
|
|
default:
|
|
return fmt.Errorf("unexpected options type %T", a)
|
|
}
|
|
return nil
|
|
},
|
|
),
|
|
}
|
|
}
|
|
|
|
// WithTenantID specifies a tenant for a single authentication. It may be different than the tenant set in [New] by [WithAuthority].
|
|
// This option is valid for any token acquisition method.
|
|
func WithTenantID(tenantID string) interface {
|
|
AcquireByAuthCodeOption
|
|
AcquireByCredentialOption
|
|
AcquireOnBehalfOfOption
|
|
AcquireSilentOption
|
|
AuthCodeURLOption
|
|
options.CallOption
|
|
} {
|
|
return struct {
|
|
AcquireByAuthCodeOption
|
|
AcquireByCredentialOption
|
|
AcquireOnBehalfOfOption
|
|
AcquireSilentOption
|
|
AuthCodeURLOption
|
|
options.CallOption
|
|
}{
|
|
CallOption: options.NewCallOption(
|
|
func(a any) error {
|
|
switch t := a.(type) {
|
|
case *AcquireTokenByAuthCodeOptions:
|
|
t.tenantID = tenantID
|
|
case *acquireTokenByCredentialOptions:
|
|
t.tenantID = tenantID
|
|
case *acquireTokenOnBehalfOfOptions:
|
|
t.tenantID = tenantID
|
|
case *AcquireTokenSilentOptions:
|
|
t.tenantID = tenantID
|
|
case *authCodeURLOptions:
|
|
t.tenantID = tenantID
|
|
default:
|
|
return fmt.Errorf("unexpected options type %T", a)
|
|
}
|
|
return nil
|
|
},
|
|
),
|
|
}
|
|
}
|
|
|
|
// 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
|
|
|
|
claims, tenantID string
|
|
}
|
|
|
|
// AcquireSilentOption is implemented by options for AcquireTokenSilent
|
|
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
|
|
options.CallOption
|
|
} {
|
|
return struct {
|
|
AcquireSilentOption
|
|
options.CallOption
|
|
}{
|
|
CallOption: options.NewCallOption(
|
|
func(a any) error {
|
|
switch t := a.(type) {
|
|
case *AcquireTokenSilentOptions:
|
|
t.Account = account
|
|
default:
|
|
return fmt.Errorf("unexpected options type %T", a)
|
|
}
|
|
return nil
|
|
},
|
|
),
|
|
}
|
|
}
|
|
|
|
// AcquireTokenSilent acquires a token from either the cache or using a refresh token.
|
|
//
|
|
// Options: [WithClaims], [WithSilentAccount], [WithTenantID]
|
|
func (cca Client) AcquireTokenSilent(ctx context.Context, scopes []string, opts ...AcquireSilentOption) (AuthResult, error) {
|
|
o := AcquireTokenSilentOptions{}
|
|
if err := options.ApplyOptions(&o, opts); err != nil {
|
|
return AuthResult{}, err
|
|
}
|
|
|
|
if o.claims != "" {
|
|
return AuthResult{}, errors.New("call another AcquireToken method to request a new token having these claims")
|
|
}
|
|
|
|
silentParameters := base.AcquireTokenSilentParameters{
|
|
Scopes: scopes,
|
|
Account: o.Account,
|
|
RequestType: accesstokens.ATConfidential,
|
|
Credential: cca.cred,
|
|
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
|
|
}
|
|
|
|
// AcquireByAuthCodeOption is implemented by options for AcquireTokenByAuthCode
|
|
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
|
|
options.CallOption
|
|
} {
|
|
return struct {
|
|
AcquireByAuthCodeOption
|
|
options.CallOption
|
|
}{
|
|
CallOption: options.NewCallOption(
|
|
func(a any) error {
|
|
switch t := a.(type) {
|
|
case *AcquireTokenByAuthCodeOptions:
|
|
t.Challenge = challenge
|
|
default:
|
|
return fmt.Errorf("unexpected options type %T", a)
|
|
}
|
|
return nil
|
|
},
|
|
),
|
|
}
|
|
}
|
|
|
|
// AcquireTokenByAuthCode is a request to acquire a security token from the authority, using an authorization code.
|
|
// The specified redirect URI must be the same URI that was used when the authorization code was requested.
|
|
//
|
|
// Options: [WithChallenge], [WithClaims], [WithTenantID]
|
|
func (cca Client) AcquireTokenByAuthCode(ctx context.Context, code string, redirectURI string, scopes []string, opts ...AcquireByAuthCodeOption) (AuthResult, error) {
|
|
o := AcquireTokenByAuthCodeOptions{}
|
|
if err := options.ApplyOptions(&o, opts); err != nil {
|
|
return AuthResult{}, err
|
|
}
|
|
|
|
params := base.AcquireTokenAuthCodeParameters{
|
|
Scopes: scopes,
|
|
Code: code,
|
|
Challenge: o.Challenge,
|
|
Claims: o.claims,
|
|
AppType: accesstokens.ATConfidential,
|
|
Credential: cca.cred, // This setting differs from public.Client.AcquireTokenByAuthCode
|
|
RedirectURI: redirectURI,
|
|
TenantID: o.tenantID,
|
|
}
|
|
|
|
return cca.base.AcquireTokenByAuthCode(ctx, params)
|
|
}
|
|
|
|
// acquireTokenByCredentialOptions contains optional configuration for AcquireTokenByCredential
|
|
type acquireTokenByCredentialOptions struct {
|
|
claims, tenantID string
|
|
}
|
|
|
|
// AcquireByCredentialOption is implemented by options for AcquireTokenByCredential
|
|
type AcquireByCredentialOption interface {
|
|
acquireByCredOption()
|
|
}
|
|
|
|
// AcquireTokenByCredential acquires a security token from the authority, using the client credentials grant.
|
|
//
|
|
// Options: [WithClaims], [WithTenantID]
|
|
func (cca Client) AcquireTokenByCredential(ctx context.Context, scopes []string, opts ...AcquireByCredentialOption) (AuthResult, error) {
|
|
o := acquireTokenByCredentialOptions{}
|
|
err := options.ApplyOptions(&o, opts)
|
|
if err != nil {
|
|
return AuthResult{}, err
|
|
}
|
|
authParams, err := cca.base.AuthParams.WithTenant(o.tenantID)
|
|
if err != nil {
|
|
return AuthResult{}, err
|
|
}
|
|
authParams.Scopes = scopes
|
|
authParams.AuthorizationType = authority.ATClientCredentials
|
|
authParams.Claims = o.claims
|
|
|
|
token, err := cca.base.Token.Credential(ctx, authParams, cca.cred)
|
|
if err != nil {
|
|
return AuthResult{}, err
|
|
}
|
|
return cca.base.AuthResultFromToken(ctx, authParams, token, true)
|
|
}
|
|
|
|
// acquireTokenOnBehalfOfOptions contains optional configuration for AcquireTokenOnBehalfOf
|
|
type acquireTokenOnBehalfOfOptions struct {
|
|
claims, tenantID string
|
|
}
|
|
|
|
// AcquireOnBehalfOfOption is implemented by options for AcquireTokenOnBehalfOf
|
|
type AcquireOnBehalfOfOption interface {
|
|
acquireOBOOption()
|
|
}
|
|
|
|
// AcquireTokenOnBehalfOf acquires a security token for an app using middle tier apps access token.
|
|
// Refer https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-on-behalf-of-flow.
|
|
//
|
|
// Options: [WithClaims], [WithTenantID]
|
|
func (cca Client) AcquireTokenOnBehalfOf(ctx context.Context, userAssertion string, scopes []string, opts ...AcquireOnBehalfOfOption) (AuthResult, error) {
|
|
o := acquireTokenOnBehalfOfOptions{}
|
|
if err := options.ApplyOptions(&o, opts); err != nil {
|
|
return AuthResult{}, err
|
|
}
|
|
params := base.AcquireTokenOnBehalfOfParameters{
|
|
Scopes: scopes,
|
|
UserAssertion: userAssertion,
|
|
Claims: o.claims,
|
|
Credential: cca.cred,
|
|
TenantID: o.tenantID,
|
|
}
|
|
return cca.base.AcquireTokenOnBehalfOf(ctx, params)
|
|
}
|
|
|
|
// Account gets the account in the token cache with the specified homeAccountID.
|
|
func (cca Client) Account(homeAccountID string) Account {
|
|
return cca.base.Account(homeAccountID)
|
|
}
|
|
|
|
// RemoveAccount signs the account out and forgets account from token cache.
|
|
func (cca Client) RemoveAccount(account Account) error {
|
|
cca.base.RemoveAccount(account)
|
|
return nil
|
|
}
|