Update Azure SDK and support additional authentication schemes
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>
This commit is contained in:
parent
e5d5810851
commit
ba4a6bbe02
365 changed files with 44060 additions and 21016 deletions
21
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/LICENSE
generated
vendored
Normal file
21
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) Microsoft Corporation.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE
|
39
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/cache/cache.go
generated
vendored
Normal file
39
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/cache/cache.go
generated
vendored
Normal file
|
@ -0,0 +1,39 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
/*
|
||||
Package cache allows third parties to implement external storage for caching token data
|
||||
for distributed systems or multiple local applications access.
|
||||
|
||||
The data stored and extracted will represent the entire cache. Therefore it is recommended
|
||||
one msal instance per user. This data is considered opaque and there are no guarantees to
|
||||
implementers on the format being passed.
|
||||
*/
|
||||
package cache
|
||||
|
||||
// Marshaler marshals data from an internal cache to bytes that can be stored.
|
||||
type Marshaler interface {
|
||||
Marshal() ([]byte, error)
|
||||
}
|
||||
|
||||
// Unmarshaler unmarshals data from a storage medium into the internal cache, overwriting it.
|
||||
type Unmarshaler interface {
|
||||
Unmarshal([]byte) error
|
||||
}
|
||||
|
||||
// Serializer can serialize the cache to binary or from binary into the cache.
|
||||
type Serializer interface {
|
||||
Marshaler
|
||||
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)
|
||||
}
|
773
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential/confidential.go
generated
vendored
Normal file
773
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential/confidential.go
generated
vendored
Normal file
|
@ -0,0 +1,773 @@
|
|||
// 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
|
||||
}
|
111
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/errors/error_design.md
generated
vendored
Normal file
111
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/errors/error_design.md
generated
vendored
Normal file
|
@ -0,0 +1,111 @@
|
|||
# MSAL Error Design
|
||||
|
||||
Author: Abhidnya Patil(abhidnya.patil@microsoft.com)
|
||||
|
||||
Contributors:
|
||||
|
||||
- John Doak(jdoak@microsoft.com)
|
||||
- Keegan Caruso(Keegan.Caruso@microsoft.com)
|
||||
- Joel Hendrix(jhendrix@microsoft.com)
|
||||
|
||||
## Background
|
||||
|
||||
Errors in MSAL are intended for app developers to troubleshoot and not for displaying to end-users.
|
||||
|
||||
### Go error handling vs other MSAL languages
|
||||
|
||||
Most modern languages use exception based errors. Simply put, you "throw" an exception and it must be caught at some routine in the upper stack or it will eventually crash the program.
|
||||
|
||||
Go doesn't use exceptions, instead it relies on multiple return values, one of which can be the builtin error interface type. It is up to the user to decide what to do.
|
||||
|
||||
### Go custom error types
|
||||
|
||||
Errors can be created in Go by simply using errors.New() or fmt.Errorf() to create an "error".
|
||||
|
||||
Custom errors can be created in multiple ways. One of the more robust ways is simply to satisfy the error interface:
|
||||
|
||||
```go
|
||||
type MyCustomErr struct {
|
||||
Msg string
|
||||
}
|
||||
func (m MyCustomErr) Error() string { // This implements "error"
|
||||
return m.Msg
|
||||
}
|
||||
```
|
||||
|
||||
### MSAL Error Goals
|
||||
|
||||
- Provide diagnostics to the user and for tickets that can be used to track down bugs or client misconfigurations
|
||||
- Detect errors that are transitory and can be retried
|
||||
- Allow the user to identify certain errors that the program can respond to, such a informing the user for the need to do an enrollment
|
||||
|
||||
## Implementing Client Side Errors
|
||||
|
||||
Client side errors indicate a misconfiguration or passing of bad arguments that is non-recoverable. Retrying isn't possible.
|
||||
|
||||
These errors can simply be standard Go errors created by errors.New() or fmt.Errorf(). If down the line we need a custom error, we can introduce it, but for now the error messages just need to be clear on what the issue was.
|
||||
|
||||
## Implementing Service Side Errors
|
||||
|
||||
Service side errors occur when an external RPC responds either with an HTTP error code or returns a message that includes an error.
|
||||
|
||||
These errors can be transitory (please slow down) or permanent (HTTP 404). To provide our diagnostic goals, we require the ability to differentiate these errors from other errors.
|
||||
|
||||
The current implementation includes a specialized type that captures any error from the server:
|
||||
|
||||
```go
|
||||
// CallErr represents an HTTP call error. Has a Verbose() method that allows getting the
|
||||
// http.Request and Response objects. Implements error.
|
||||
type CallErr struct {
|
||||
Req *http.Request
|
||||
Resp *http.Response
|
||||
Err error
|
||||
}
|
||||
|
||||
// Errors implements error.Error().
|
||||
func (e CallErr) Error() string {
|
||||
return e.Err.Error()
|
||||
}
|
||||
|
||||
// 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.TLS = nil // Same
|
||||
return fmt.Sprintf("%s:\nRequest:\n%s\nResponse:\n%s", e.Err, prettyConf.Sprint(e.Req), prettyConf.Sprint(e.Resp))
|
||||
}
|
||||
```
|
||||
|
||||
A user will always receive the most concise error we provide. They can tell if it is a server side error using Go error package:
|
||||
|
||||
```go
|
||||
var callErr CallErr
|
||||
if errors.As(err, &callErr) {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
We provide a Verbose() function that can retrieve the most verbose message from any error we provide:
|
||||
|
||||
```go
|
||||
fmt.Println(errors.Verbose(err))
|
||||
```
|
||||
|
||||
If further differentiation is required, we can add custom errors that use Go error wrapping on top of CallErr to achieve our diagnostic goals (such as detecting when to retry a call due to transient errors).
|
||||
|
||||
CallErr is always thrown from the comm package (which handles all http requests) and looks similar to:
|
||||
|
||||
```go
|
||||
return nil, errors.CallErr{
|
||||
Req: req,
|
||||
Resp: reply,
|
||||
Err: fmt.Errorf("http call(%s)(%s) error: reply status code was %d:\n%s", req.URL.String(), req.Method, reply.StatusCode, ErrorResponse), //ErrorResponse is the json body extracted from the http response
|
||||
}
|
||||
```
|
||||
|
||||
## Future Decisions
|
||||
|
||||
The ability to retry calls needs to have centralized responsibility. Either the user is doing it or the client is doing it.
|
||||
|
||||
If the user should be responsible, our errors package will include a CanRetry() function that will inform the user if the error provided to them is retryable. This is based on the http error code and possibly the type of error that was returned. It would also include a sleep time if the server returned an amount of time to wait.
|
||||
|
||||
Otherwise we will do this internally and retries will be left to us.
|
89
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/errors/errors.go
generated
vendored
Normal file
89
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/errors/errors.go
generated
vendored
Normal file
|
@ -0,0 +1,89 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
package errors
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/kylelemons/godebug/pretty"
|
||||
)
|
||||
|
||||
var prettyConf = &pretty.Config{
|
||||
IncludeUnexported: false,
|
||||
SkipZeroFields: true,
|
||||
TrackCycles: true,
|
||||
Formatter: map[reflect.Type]interface{}{
|
||||
reflect.TypeOf((*io.Reader)(nil)).Elem(): func(r io.Reader) string {
|
||||
b, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
return "could not read io.Reader content"
|
||||
}
|
||||
return string(b)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
type verboser interface {
|
||||
Verbose() string
|
||||
}
|
||||
|
||||
// Verbose prints the most verbose error that the error message has.
|
||||
func Verbose(err error) string {
|
||||
build := strings.Builder{}
|
||||
for {
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
if v, ok := err.(verboser); ok {
|
||||
build.WriteString(v.Verbose())
|
||||
} else {
|
||||
build.WriteString(err.Error())
|
||||
}
|
||||
err = errors.Unwrap(err)
|
||||
}
|
||||
return build.String()
|
||||
}
|
||||
|
||||
// New is equivalent to errors.New().
|
||||
func New(text string) error {
|
||||
return errors.New(text)
|
||||
}
|
||||
|
||||
// CallErr represents an HTTP call error. Has a Verbose() method that allows getting the
|
||||
// http.Request and Response objects. Implements error.
|
||||
type CallErr struct {
|
||||
Req *http.Request
|
||||
// Resp contains response body
|
||||
Resp *http.Response
|
||||
Err error
|
||||
}
|
||||
|
||||
// Errors implements error.Error().
|
||||
func (e CallErr) Error() string {
|
||||
return e.Err.Error()
|
||||
}
|
||||
|
||||
// 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.TLS = nil // Same
|
||||
return fmt.Sprintf("%s:\nRequest:\n%s\nResponse:\n%s", e.Err, prettyConf.Sprint(e.Req), prettyConf.Sprint(e.Resp))
|
||||
}
|
||||
|
||||
// Is reports whether any error in errors chain matches target.
|
||||
func Is(err, target error) bool {
|
||||
return errors.Is(err, target)
|
||||
}
|
||||
|
||||
// As finds the first error in errors chain that matches target,
|
||||
// and if so, sets target to that error value and returns true.
|
||||
// Otherwise, it returns false.
|
||||
func As(err error, target interface{}) bool {
|
||||
return errors.As(err, target)
|
||||
}
|
467
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/base/base.go
generated
vendored
Normal file
467
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/base/base.go
generated
vendored
Normal file
|
@ -0,0 +1,467 @@
|
|||
// Package base contains a "Base" client that is used by the external public.Client and confidential.Client.
|
||||
// Base holds shared attributes that must be available to both clients and methods that act as
|
||||
// shared calls.
|
||||
package base
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/cache"
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/base/internal/storage"
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth"
|
||||
"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/shared"
|
||||
)
|
||||
|
||||
const (
|
||||
// AuthorityPublicCloud is the default AAD authority host
|
||||
AuthorityPublicCloud = "https://login.microsoftonline.com/common"
|
||||
scopeSeparator = " "
|
||||
)
|
||||
|
||||
// manager provides an internal cache. It is defined to allow faking the cache in tests.
|
||||
// In all production use it is a *storage.Manager.
|
||||
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)
|
||||
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
|
||||
Account shared.Account
|
||||
RequestType accesstokens.AppType
|
||||
Credential *accesstokens.Credential
|
||||
IsAppCache bool
|
||||
TenantID string
|
||||
UserAssertion string
|
||||
AuthorizationType authority.AuthorizeType
|
||||
Claims string
|
||||
}
|
||||
|
||||
// AcquireTokenAuthCodeParameters contains the parameters required to acquire an access token using the auth code flow.
|
||||
// To use PKCE, set the CodeChallengeParameter.
|
||||
// Code challenges are used to secure authorization code grants; for more information, visit
|
||||
// https://tools.ietf.org/html/rfc7636.
|
||||
type AcquireTokenAuthCodeParameters struct {
|
||||
Scopes []string
|
||||
Code string
|
||||
Challenge string
|
||||
Claims string
|
||||
RedirectURI string
|
||||
AppType accesstokens.AppType
|
||||
Credential *accesstokens.Credential
|
||||
TenantID string
|
||||
}
|
||||
|
||||
type AcquireTokenOnBehalfOfParameters struct {
|
||||
Scopes []string
|
||||
Claims string
|
||||
Credential *accesstokens.Credential
|
||||
TenantID string
|
||||
UserAssertion string
|
||||
}
|
||||
|
||||
// AuthResult contains the results of one token acquisition operation in PublicClientApplication
|
||||
// or ConfidentialClientApplication. For details see https://aka.ms/msal-net-authenticationresult
|
||||
type AuthResult struct {
|
||||
Account shared.Account
|
||||
IDToken accesstokens.IDToken
|
||||
AccessToken string
|
||||
ExpiresOn time.Time
|
||||
GrantedScopes []string
|
||||
DeclinedScopes []string
|
||||
}
|
||||
|
||||
// AuthResultFromStorage creates an AuthResult from a storage token response (which is generated from the cache).
|
||||
func AuthResultFromStorage(storageTokenResponse storage.TokenResponse) (AuthResult, error) {
|
||||
if err := storageTokenResponse.AccessToken.Validate(); err != nil {
|
||||
return AuthResult{}, fmt.Errorf("problem with access token in StorageTokenResponse: %w", err)
|
||||
}
|
||||
|
||||
account := storageTokenResponse.Account
|
||||
accessToken := storageTokenResponse.AccessToken.Secret
|
||||
grantedScopes := strings.Split(storageTokenResponse.AccessToken.Scopes, scopeSeparator)
|
||||
|
||||
// Checking if there was an ID token in the cache; this will throw an error in the case of confidential client applications.
|
||||
var idToken accesstokens.IDToken
|
||||
if !storageTokenResponse.IDToken.IsZero() {
|
||||
err := idToken.UnmarshalJSON([]byte(storageTokenResponse.IDToken.Secret))
|
||||
if err != nil {
|
||||
return AuthResult{}, fmt.Errorf("problem decoding JWT token: %w", err)
|
||||
}
|
||||
}
|
||||
return AuthResult{account, idToken, accessToken, storageTokenResponse.AccessToken.ExpiresOn.T, grantedScopes, nil}, nil
|
||||
}
|
||||
|
||||
// NewAuthResult creates an AuthResult.
|
||||
func NewAuthResult(tokenResponse accesstokens.TokenResponse, account shared.Account) (AuthResult, error) {
|
||||
if len(tokenResponse.DeclinedScopes) > 0 {
|
||||
return AuthResult{}, fmt.Errorf("token response failed because declined scopes are present: %s", strings.Join(tokenResponse.DeclinedScopes, ","))
|
||||
}
|
||||
return AuthResult{
|
||||
Account: account,
|
||||
IDToken: tokenResponse.IDToken,
|
||||
AccessToken: tokenResponse.AccessToken,
|
||||
ExpiresOn: tokenResponse.ExpiresOn.T,
|
||||
GrantedScopes: tokenResponse.GrantedScopes.Slice,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
AuthParams authority.AuthParams // DO NOT EVER MAKE THIS A POINTER! See "Note" in New().
|
||||
cacheAccessor cache.ExportReplace
|
||||
}
|
||||
|
||||
// Option is an optional argument to the New constructor.
|
||||
type Option func(c *Client) error
|
||||
|
||||
// WithCacheAccessor allows you to set some type of cache for storing authentication tokens.
|
||||
func WithCacheAccessor(ca cache.ExportReplace) Option {
|
||||
return func(c *Client) error {
|
||||
if ca != nil {
|
||||
c.cacheAccessor = ca
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithClientCapabilities allows configuring one or more client capabilities such as "CP1"
|
||||
func WithClientCapabilities(capabilities []string) Option {
|
||||
return func(c *Client) error {
|
||||
var err error
|
||||
if len(capabilities) > 0 {
|
||||
cc, err := authority.NewClientCapabilities(capabilities)
|
||||
if err == nil {
|
||||
c.AuthParams.Capabilities = cc
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// WithKnownAuthorityHosts specifies hosts Client shouldn't validate or request metadata for because they're known to the user
|
||||
func WithKnownAuthorityHosts(hosts []string) Option {
|
||||
return func(c *Client) error {
|
||||
cp := make([]string, len(hosts))
|
||||
copy(cp, hosts)
|
||||
c.AuthParams.KnownAuthorityHosts = cp
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithX5C specifies if x5c claim(public key of the certificate) should be sent to STS to enable Subject Name Issuer Authentication.
|
||||
func WithX5C(sendX5C bool) Option {
|
||||
return func(c *Client) error {
|
||||
c.AuthParams.SendX5C = sendX5C
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithRegionDetection(region string) Option {
|
||||
return func(c *Client) error {
|
||||
c.AuthParams.AuthorityInfo.Region = region
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithInstanceDiscovery(instanceDiscoveryEnabled bool) Option {
|
||||
return func(c *Client) error {
|
||||
c.AuthParams.AuthorityInfo.ValidateAuthority = instanceDiscoveryEnabled
|
||||
c.AuthParams.AuthorityInfo.InstanceDiscoveryDisabled = !instanceDiscoveryEnabled
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// New is the constructor for Base.
|
||||
func New(clientID string, authorityURI string, token *oauth.Client, options ...Option) (Client, error) {
|
||||
//By default, validateAuthority is set to true and instanceDiscoveryDisabled is set to false
|
||||
authInfo, err := authority.NewInfoFromAuthorityURI(authorityURI, true, false)
|
||||
if err != nil {
|
||||
return Client{}, err
|
||||
}
|
||||
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),
|
||||
}
|
||||
for _, o := range options {
|
||||
if err = o(&client); err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
return client, err
|
||||
|
||||
}
|
||||
|
||||
// AuthCodeURL creates a URL used to acquire an authorization code.
|
||||
func (b Client) AuthCodeURL(ctx context.Context, clientID, redirectURI string, scopes []string, authParams authority.AuthParams) (string, error) {
|
||||
endpoints, err := b.Token.ResolveEndpoints(ctx, authParams.AuthorityInfo, "")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
baseURL, err := url.Parse(endpoints.AuthorizationEndpoint)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
claims, err := authParams.MergeCapabilitiesAndClaims()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
v := url.Values{}
|
||||
v.Add("client_id", clientID)
|
||||
v.Add("response_type", "code")
|
||||
v.Add("redirect_uri", redirectURI)
|
||||
v.Add("scope", strings.Join(scopes, scopeSeparator))
|
||||
if authParams.State != "" {
|
||||
v.Add("state", authParams.State)
|
||||
}
|
||||
if claims != "" {
|
||||
v.Add("claims", claims)
|
||||
}
|
||||
if authParams.CodeChallenge != "" {
|
||||
v.Add("code_challenge", authParams.CodeChallenge)
|
||||
}
|
||||
if authParams.CodeChallengeMethod != "" {
|
||||
v.Add("code_challenge_method", authParams.CodeChallengeMethod)
|
||||
}
|
||||
if authParams.LoginHint != "" {
|
||||
v.Add("login_hint", authParams.LoginHint)
|
||||
}
|
||||
if authParams.Prompt != "" {
|
||||
v.Add("prompt", authParams.Prompt)
|
||||
}
|
||||
if authParams.DomainHint != "" {
|
||||
v.Add("domain_hint", authParams.DomainHint)
|
||||
}
|
||||
// There were left over from an implementation that didn't use any of these. We may
|
||||
// need to add them later, but as of now aren't needed.
|
||||
/*
|
||||
if p.ResponseMode != "" {
|
||||
urlParams.Add("response_mode", p.ResponseMode)
|
||||
}
|
||||
*/
|
||||
baseURL.RawQuery = v.Encode()
|
||||
return baseURL.String(), nil
|
||||
}
|
||||
|
||||
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
|
||||
tenant := silent.TenantID
|
||||
authParams, err := b.AuthParams.WithTenant(tenant)
|
||||
if err != nil {
|
||||
return AuthResult{}, err
|
||||
}
|
||||
authParams.Scopes = silent.Scopes
|
||||
authParams.HomeAccountID = silent.Account.HomeAccountID
|
||||
authParams.AuthorizationType = silent.AuthorizationType
|
||||
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)
|
||||
}
|
||||
authParams.AuthorizationType = authority.ATRefreshToken
|
||||
storageTokenResponse, err = b.manager.Read(ctx, authParams, silent.Account)
|
||||
if err != nil {
|
||||
return AuthResult{}, err
|
||||
}
|
||||
}
|
||||
|
||||
// ignore cached access tokens when given claims
|
||||
if silent.Claims == "" {
|
||||
result, err := AuthResultFromStorage(storageTokenResponse)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
|
||||
// redeem a cached refresh token, if available
|
||||
if reflect.ValueOf(storageTokenResponse.RefreshToken).IsZero() {
|
||||
return AuthResult{}, 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 b.AuthResultFromToken(ctx, authParams, token, true)
|
||||
}
|
||||
|
||||
func (b Client) AcquireTokenByAuthCode(ctx context.Context, authCodeParams AcquireTokenAuthCodeParameters) (AuthResult, error) {
|
||||
authParams, err := b.AuthParams.WithTenant(authCodeParams.TenantID)
|
||||
if err != nil {
|
||||
return AuthResult{}, err
|
||||
}
|
||||
authParams.Claims = authCodeParams.Claims
|
||||
authParams.Scopes = authCodeParams.Scopes
|
||||
authParams.Redirecturi = authCodeParams.RedirectURI
|
||||
authParams.AuthorizationType = authority.ATAuthCode
|
||||
|
||||
var cc *accesstokens.Credential
|
||||
if authCodeParams.AppType == accesstokens.ATConfidential {
|
||||
cc = authCodeParams.Credential
|
||||
authParams.IsConfidentialClient = true
|
||||
}
|
||||
|
||||
req, err := accesstokens.NewCodeChallengeRequest(authParams, authCodeParams.AppType, cc, authCodeParams.Code, authCodeParams.Challenge)
|
||||
if err != nil {
|
||||
return AuthResult{}, err
|
||||
}
|
||||
|
||||
token, err := b.Token.AuthCode(ctx, req)
|
||||
if err != nil {
|
||||
return AuthResult{}, err
|
||||
}
|
||||
|
||||
return b.AuthResultFromToken(ctx, authParams, token, true)
|
||||
}
|
||||
|
||||
// AcquireTokenOnBehalfOf acquires a security token for an app using middle tier apps access token.
|
||||
func (b Client) AcquireTokenOnBehalfOf(ctx context.Context, onBehalfOfParams AcquireTokenOnBehalfOfParameters) (AuthResult, error) {
|
||||
var ar AuthResult
|
||||
silentParameters := AcquireTokenSilentParameters{
|
||||
Scopes: onBehalfOfParams.Scopes,
|
||||
RequestType: accesstokens.ATConfidential,
|
||||
Credential: onBehalfOfParams.Credential,
|
||||
UserAssertion: onBehalfOfParams.UserAssertion,
|
||||
AuthorizationType: authority.ATOnBehalfOf,
|
||||
TenantID: onBehalfOfParams.TenantID,
|
||||
Claims: onBehalfOfParams.Claims,
|
||||
}
|
||||
ar, err := b.AcquireTokenSilent(ctx, silentParameters)
|
||||
if err == nil {
|
||||
return ar, err
|
||||
}
|
||||
authParams, err := b.AuthParams.WithTenant(onBehalfOfParams.TenantID)
|
||||
if err != nil {
|
||||
return AuthResult{}, err
|
||||
}
|
||||
authParams.AuthorizationType = authority.ATOnBehalfOf
|
||||
authParams.Claims = onBehalfOfParams.Claims
|
||||
authParams.Scopes = onBehalfOfParams.Scopes
|
||||
authParams.UserAssertion = onBehalfOfParams.UserAssertion
|
||||
token, err := b.Token.OnBehalfOf(ctx, authParams, onBehalfOfParams.Credential)
|
||||
if err == nil {
|
||||
ar, err = b.AuthResultFromToken(ctx, authParams, token, true)
|
||||
}
|
||||
return ar, err
|
||||
}
|
||||
|
||||
func (b Client) AuthResultFromToken(ctx context.Context, authParams authority.AuthParams, token accesstokens.TokenResponse, cacheWrite bool) (AuthResult, error) {
|
||||
if !cacheWrite {
|
||||
return NewAuthResult(token, shared.Account{})
|
||||
}
|
||||
|
||||
var account shared.Account
|
||||
var err error
|
||||
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)
|
||||
if err != nil {
|
||||
return AuthResult{}, err
|
||||
}
|
||||
}
|
||||
return NewAuthResult(token, account)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
accounts := b.manager.AllAccounts()
|
||||
return accounts
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
account := b.manager.Account(homeAccountID)
|
||||
return account
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
b.manager.RemoveAccount(account, b.AuthParams.ClientID)
|
||||
}
|
200
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/base/internal/storage/items.go
generated
vendored
Normal file
200
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/base/internal/storage/items.go
generated
vendored
Normal file
|
@ -0,0 +1,200 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
package storage
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
internalTime "github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json/types/time"
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/accesstokens"
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/shared"
|
||||
)
|
||||
|
||||
// Contract is the JSON structure that is written to any storage medium when serializing
|
||||
// the internal cache. This design is shared between MSAL versions in many languages.
|
||||
// This cannot be changed without design that includes other SDKs.
|
||||
type Contract struct {
|
||||
AccessTokens map[string]AccessToken `json:"AccessToken,omitempty"`
|
||||
RefreshTokens map[string]accesstokens.RefreshToken `json:"RefreshToken,omitempty"`
|
||||
IDTokens map[string]IDToken `json:"IdToken,omitempty"`
|
||||
Accounts map[string]shared.Account `json:"Account,omitempty"`
|
||||
AppMetaData map[string]AppMetaData `json:"AppMetadata,omitempty"`
|
||||
|
||||
AdditionalFields map[string]interface{}
|
||||
}
|
||||
|
||||
// Contract is the JSON structure that is written to any storage medium when serializing
|
||||
// the internal cache. This design is shared between MSAL versions in many languages.
|
||||
// This cannot be changed without design that includes other SDKs.
|
||||
type InMemoryContract struct {
|
||||
AccessTokensPartition map[string]map[string]AccessToken
|
||||
RefreshTokensPartition map[string]map[string]accesstokens.RefreshToken
|
||||
IDTokensPartition map[string]map[string]IDToken
|
||||
AccountsPartition map[string]map[string]shared.Account
|
||||
AppMetaData map[string]AppMetaData
|
||||
}
|
||||
|
||||
// NewContract is the constructor for Contract.
|
||||
func NewInMemoryContract() *InMemoryContract {
|
||||
return &InMemoryContract{
|
||||
AccessTokensPartition: map[string]map[string]AccessToken{},
|
||||
RefreshTokensPartition: map[string]map[string]accesstokens.RefreshToken{},
|
||||
IDTokensPartition: map[string]map[string]IDToken{},
|
||||
AccountsPartition: map[string]map[string]shared.Account{},
|
||||
AppMetaData: map[string]AppMetaData{},
|
||||
}
|
||||
}
|
||||
|
||||
// NewContract is the constructor for Contract.
|
||||
func NewContract() *Contract {
|
||||
return &Contract{
|
||||
AccessTokens: map[string]AccessToken{},
|
||||
RefreshTokens: map[string]accesstokens.RefreshToken{},
|
||||
IDTokens: map[string]IDToken{},
|
||||
Accounts: map[string]shared.Account{},
|
||||
AppMetaData: map[string]AppMetaData{},
|
||||
AdditionalFields: map[string]interface{}{},
|
||||
}
|
||||
}
|
||||
|
||||
// AccessToken is the JSON representation of a MSAL access token for encoding to storage.
|
||||
type AccessToken struct {
|
||||
HomeAccountID string `json:"home_account_id,omitempty"`
|
||||
Environment string `json:"environment,omitempty"`
|
||||
Realm string `json:"realm,omitempty"`
|
||||
CredentialType string `json:"credential_type,omitempty"`
|
||||
ClientID string `json:"client_id,omitempty"`
|
||||
Secret string `json:"secret,omitempty"`
|
||||
Scopes string `json:"target,omitempty"`
|
||||
ExpiresOn internalTime.Unix `json:"expires_on,omitempty"`
|
||||
ExtendedExpiresOn internalTime.Unix `json:"extended_expires_on,omitempty"`
|
||||
CachedAt internalTime.Unix `json:"cached_at,omitempty"`
|
||||
UserAssertionHash string `json:"user_assertion_hash,omitempty"`
|
||||
|
||||
AdditionalFields map[string]interface{}
|
||||
}
|
||||
|
||||
// NewAccessToken is the constructor for AccessToken.
|
||||
func NewAccessToken(homeID, env, realm, clientID string, cachedAt, expiresOn, extendedExpiresOn time.Time, scopes, token string) AccessToken {
|
||||
return AccessToken{
|
||||
HomeAccountID: homeID,
|
||||
Environment: env,
|
||||
Realm: realm,
|
||||
CredentialType: "AccessToken",
|
||||
ClientID: clientID,
|
||||
Secret: token,
|
||||
Scopes: scopes,
|
||||
CachedAt: internalTime.Unix{T: cachedAt.UTC()},
|
||||
ExpiresOn: internalTime.Unix{T: expiresOn.UTC()},
|
||||
ExtendedExpiresOn: internalTime.Unix{T: extendedExpiresOn.UTC()},
|
||||
}
|
||||
}
|
||||
|
||||
// Key outputs the key that can be used to uniquely look up this entry in a map.
|
||||
func (a AccessToken) Key() string {
|
||||
return strings.Join(
|
||||
[]string{a.HomeAccountID, a.Environment, a.CredentialType, a.ClientID, a.Realm, a.Scopes},
|
||||
shared.CacheKeySeparator,
|
||||
)
|
||||
}
|
||||
|
||||
// FakeValidate enables tests to fake access token validation
|
||||
var FakeValidate func(AccessToken) error
|
||||
|
||||
// Validate validates that this AccessToken can be used.
|
||||
func (a AccessToken) Validate() error {
|
||||
if FakeValidate != nil {
|
||||
return FakeValidate(a)
|
||||
}
|
||||
if a.CachedAt.T.After(time.Now()) {
|
||||
return errors.New("access token isn't valid, it was cached at a future time")
|
||||
}
|
||||
if a.ExpiresOn.T.Before(time.Now().Add(5 * time.Minute)) {
|
||||
return fmt.Errorf("access token is expired")
|
||||
}
|
||||
if a.CachedAt.T.IsZero() {
|
||||
return fmt.Errorf("access token does not have CachedAt set")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IDToken is the JSON representation of an MSAL id token for encoding to storage.
|
||||
type IDToken struct {
|
||||
HomeAccountID string `json:"home_account_id,omitempty"`
|
||||
Environment string `json:"environment,omitempty"`
|
||||
Realm string `json:"realm,omitempty"`
|
||||
CredentialType string `json:"credential_type,omitempty"`
|
||||
ClientID string `json:"client_id,omitempty"`
|
||||
Secret string `json:"secret,omitempty"`
|
||||
UserAssertionHash string `json:"user_assertion_hash,omitempty"`
|
||||
AdditionalFields map[string]interface{}
|
||||
}
|
||||
|
||||
// IsZero determines if IDToken is the zero value.
|
||||
func (i IDToken) IsZero() bool {
|
||||
v := reflect.ValueOf(i)
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
field := v.Field(i)
|
||||
if !field.IsZero() {
|
||||
switch field.Kind() {
|
||||
case reflect.Map, reflect.Slice:
|
||||
if field.Len() == 0 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// NewIDToken is the constructor for IDToken.
|
||||
func NewIDToken(homeID, env, realm, clientID, idToken string) IDToken {
|
||||
return IDToken{
|
||||
HomeAccountID: homeID,
|
||||
Environment: env,
|
||||
Realm: realm,
|
||||
CredentialType: "IDToken",
|
||||
ClientID: clientID,
|
||||
Secret: idToken,
|
||||
}
|
||||
}
|
||||
|
||||
// Key outputs the key that can be used to uniquely look up this entry in a map.
|
||||
func (id IDToken) Key() string {
|
||||
return strings.Join(
|
||||
[]string{id.HomeAccountID, id.Environment, id.CredentialType, id.ClientID, id.Realm},
|
||||
shared.CacheKeySeparator,
|
||||
)
|
||||
}
|
||||
|
||||
// AppMetaData is the JSON representation of application metadata for encoding to storage.
|
||||
type AppMetaData struct {
|
||||
FamilyID string `json:"family_id,omitempty"`
|
||||
ClientID string `json:"client_id,omitempty"`
|
||||
Environment string `json:"environment,omitempty"`
|
||||
|
||||
AdditionalFields map[string]interface{}
|
||||
}
|
||||
|
||||
// NewAppMetaData is the constructor for AppMetaData.
|
||||
func NewAppMetaData(familyID, clientID, environment string) AppMetaData {
|
||||
return AppMetaData{
|
||||
FamilyID: familyID,
|
||||
ClientID: clientID,
|
||||
Environment: environment,
|
||||
}
|
||||
}
|
||||
|
||||
// Key outputs the key that can be used to uniquely look up this entry in a map.
|
||||
func (a AppMetaData) Key() string {
|
||||
return strings.Join(
|
||||
[]string{"AppMetaData", a.Environment, a.ClientID},
|
||||
shared.CacheKeySeparator,
|
||||
)
|
||||
}
|
436
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/base/internal/storage/partitioned_storage.go
generated
vendored
Normal file
436
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/base/internal/storage/partitioned_storage.go
generated
vendored
Normal file
|
@ -0,0 +1,436 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json"
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth"
|
||||
"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/shared"
|
||||
)
|
||||
|
||||
// PartitionedManager is a partitioned in-memory cache of access tokens, accounts and meta data.
|
||||
type PartitionedManager struct {
|
||||
contract *InMemoryContract
|
||||
contractMu sync.RWMutex
|
||||
requests aadInstanceDiscoveryer // *oauth.Token
|
||||
|
||||
aadCacheMu sync.RWMutex
|
||||
aadCache map[string]authority.InstanceDiscoveryMetadata
|
||||
}
|
||||
|
||||
// NewPartitionedManager is the constructor for PartitionedManager.
|
||||
func NewPartitionedManager(requests *oauth.Client) *PartitionedManager {
|
||||
m := &PartitionedManager{requests: requests, aadCache: make(map[string]authority.InstanceDiscoveryMetadata)}
|
||||
m.contract = NewInMemoryContract()
|
||||
return m
|
||||
}
|
||||
|
||||
// Read reads a storage token from the cache if it exists.
|
||||
func (m *PartitionedManager) Read(ctx context.Context, authParameters authority.AuthParams) (TokenResponse, error) {
|
||||
tr := TokenResponse{}
|
||||
realm := authParameters.AuthorityInfo.Tenant
|
||||
clientID := authParameters.ClientID
|
||||
scopes := authParameters.Scopes
|
||||
|
||||
// fetch metadata if instanceDiscovery is enabled
|
||||
aliases := []string{authParameters.AuthorityInfo.Host}
|
||||
if !authParameters.AuthorityInfo.InstanceDiscoveryDisabled {
|
||||
metadata, err := m.getMetadataEntry(ctx, authParameters.AuthorityInfo)
|
||||
if err != nil {
|
||||
return TokenResponse{}, err
|
||||
}
|
||||
aliases = metadata.Aliases
|
||||
}
|
||||
|
||||
userAssertionHash := authParameters.AssertionHash()
|
||||
partitionKeyFromRequest := userAssertionHash
|
||||
|
||||
// errors returned by read* methods indicate a cache miss and are therefore non-fatal. We continue populating
|
||||
// TokenResponse fields so that e.g. lack of an ID token doesn't prevent the caller from receiving a refresh token.
|
||||
accessToken, err := m.readAccessToken(aliases, realm, clientID, userAssertionHash, scopes, partitionKeyFromRequest)
|
||||
if err == nil {
|
||||
tr.AccessToken = accessToken
|
||||
}
|
||||
idToken, err := m.readIDToken(aliases, realm, clientID, userAssertionHash, getPartitionKeyIDTokenRead(accessToken))
|
||||
if err == nil {
|
||||
tr.IDToken = idToken
|
||||
}
|
||||
|
||||
if appMetadata, err := m.readAppMetaData(aliases, clientID); err == nil {
|
||||
// we need the family ID to identify the correct refresh token, if any
|
||||
familyID := appMetadata.FamilyID
|
||||
refreshToken, err := m.readRefreshToken(aliases, familyID, clientID, userAssertionHash, partitionKeyFromRequest)
|
||||
if err == nil {
|
||||
tr.RefreshToken = refreshToken
|
||||
}
|
||||
}
|
||||
|
||||
account, err := m.readAccount(aliases, realm, userAssertionHash, idToken.HomeAccountID)
|
||||
if err == nil {
|
||||
tr.Account = account
|
||||
}
|
||||
return tr, nil
|
||||
}
|
||||
|
||||
// Write writes a token response to the cache and returns the account information the token is stored with.
|
||||
func (m *PartitionedManager) Write(authParameters authority.AuthParams, tokenResponse accesstokens.TokenResponse) (shared.Account, error) {
|
||||
authParameters.HomeAccountID = tokenResponse.ClientInfo.HomeAccountID()
|
||||
homeAccountID := authParameters.HomeAccountID
|
||||
environment := authParameters.AuthorityInfo.Host
|
||||
realm := authParameters.AuthorityInfo.Tenant
|
||||
clientID := authParameters.ClientID
|
||||
target := strings.Join(tokenResponse.GrantedScopes.Slice, scopeSeparator)
|
||||
userAssertionHash := authParameters.AssertionHash()
|
||||
cachedAt := time.Now()
|
||||
|
||||
var account shared.Account
|
||||
|
||||
if len(tokenResponse.RefreshToken) > 0 {
|
||||
refreshToken := accesstokens.NewRefreshToken(homeAccountID, environment, clientID, tokenResponse.RefreshToken, tokenResponse.FamilyID)
|
||||
if authParameters.AuthorizationType == authority.ATOnBehalfOf {
|
||||
refreshToken.UserAssertionHash = userAssertionHash
|
||||
}
|
||||
if err := m.writeRefreshToken(refreshToken, getPartitionKeyRefreshToken(refreshToken)); err != nil {
|
||||
return account, err
|
||||
}
|
||||
}
|
||||
|
||||
if len(tokenResponse.AccessToken) > 0 {
|
||||
accessToken := NewAccessToken(
|
||||
homeAccountID,
|
||||
environment,
|
||||
realm,
|
||||
clientID,
|
||||
cachedAt,
|
||||
tokenResponse.ExpiresOn.T,
|
||||
tokenResponse.ExtExpiresOn.T,
|
||||
target,
|
||||
tokenResponse.AccessToken,
|
||||
)
|
||||
if authParameters.AuthorizationType == authority.ATOnBehalfOf {
|
||||
accessToken.UserAssertionHash = userAssertionHash // get Hash method on this
|
||||
}
|
||||
|
||||
// Since we have a valid access token, cache it before moving on.
|
||||
if err := accessToken.Validate(); err == nil {
|
||||
if err := m.writeAccessToken(accessToken, getPartitionKeyAccessToken(accessToken)); err != nil {
|
||||
return account, err
|
||||
}
|
||||
} else {
|
||||
return shared.Account{}, err
|
||||
}
|
||||
}
|
||||
|
||||
idTokenJwt := tokenResponse.IDToken
|
||||
if !idTokenJwt.IsZero() {
|
||||
idToken := NewIDToken(homeAccountID, environment, realm, clientID, idTokenJwt.RawToken)
|
||||
if authParameters.AuthorizationType == authority.ATOnBehalfOf {
|
||||
idToken.UserAssertionHash = userAssertionHash
|
||||
}
|
||||
if err := m.writeIDToken(idToken, getPartitionKeyIDToken(idToken)); err != nil {
|
||||
return shared.Account{}, err
|
||||
}
|
||||
|
||||
localAccountID := idTokenJwt.LocalAccountID()
|
||||
authorityType := authParameters.AuthorityInfo.AuthorityType
|
||||
|
||||
preferredUsername := idTokenJwt.UPN
|
||||
if idTokenJwt.PreferredUsername != "" {
|
||||
preferredUsername = idTokenJwt.PreferredUsername
|
||||
}
|
||||
|
||||
account = shared.NewAccount(
|
||||
homeAccountID,
|
||||
environment,
|
||||
realm,
|
||||
localAccountID,
|
||||
authorityType,
|
||||
preferredUsername,
|
||||
)
|
||||
if authParameters.AuthorizationType == authority.ATOnBehalfOf {
|
||||
account.UserAssertionHash = userAssertionHash
|
||||
}
|
||||
if err := m.writeAccount(account, getPartitionKeyAccount(account)); err != nil {
|
||||
return shared.Account{}, err
|
||||
}
|
||||
}
|
||||
|
||||
AppMetaData := NewAppMetaData(tokenResponse.FamilyID, clientID, environment)
|
||||
|
||||
if err := m.writeAppMetaData(AppMetaData); err != nil {
|
||||
return shared.Account{}, err
|
||||
}
|
||||
return account, nil
|
||||
}
|
||||
|
||||
func (m *PartitionedManager) getMetadataEntry(ctx context.Context, authorityInfo authority.Info) (authority.InstanceDiscoveryMetadata, error) {
|
||||
md, err := m.aadMetadataFromCache(ctx, authorityInfo)
|
||||
if err != nil {
|
||||
// not in the cache, retrieve it
|
||||
md, err = m.aadMetadata(ctx, authorityInfo)
|
||||
}
|
||||
return md, err
|
||||
}
|
||||
|
||||
func (m *PartitionedManager) aadMetadataFromCache(ctx context.Context, authorityInfo authority.Info) (authority.InstanceDiscoveryMetadata, error) {
|
||||
m.aadCacheMu.RLock()
|
||||
defer m.aadCacheMu.RUnlock()
|
||||
metadata, ok := m.aadCache[authorityInfo.Host]
|
||||
if ok {
|
||||
return metadata, nil
|
||||
}
|
||||
return metadata, errors.New("not found")
|
||||
}
|
||||
|
||||
func (m *PartitionedManager) aadMetadata(ctx context.Context, authorityInfo authority.Info) (authority.InstanceDiscoveryMetadata, error) {
|
||||
discoveryResponse, err := m.requests.AADInstanceDiscovery(ctx, authorityInfo)
|
||||
if err != nil {
|
||||
return authority.InstanceDiscoveryMetadata{}, err
|
||||
}
|
||||
|
||||
m.aadCacheMu.Lock()
|
||||
defer m.aadCacheMu.Unlock()
|
||||
|
||||
for _, metadataEntry := range discoveryResponse.Metadata {
|
||||
for _, aliasedAuthority := range metadataEntry.Aliases {
|
||||
m.aadCache[aliasedAuthority] = metadataEntry
|
||||
}
|
||||
}
|
||||
if _, ok := m.aadCache[authorityInfo.Host]; !ok {
|
||||
m.aadCache[authorityInfo.Host] = authority.InstanceDiscoveryMetadata{
|
||||
PreferredNetwork: authorityInfo.Host,
|
||||
PreferredCache: authorityInfo.Host,
|
||||
}
|
||||
}
|
||||
return m.aadCache[authorityInfo.Host], nil
|
||||
}
|
||||
|
||||
func (m *PartitionedManager) readAccessToken(envAliases []string, realm, clientID, userAssertionHash string, scopes []string, partitionKey string) (AccessToken, error) {
|
||||
m.contractMu.RLock()
|
||||
defer m.contractMu.RUnlock()
|
||||
if accessTokens, ok := m.contract.AccessTokensPartition[partitionKey]; ok {
|
||||
// TODO: linear search (over a map no less) is slow for a large number (thousands) of tokens.
|
||||
// this shows up as the dominating node in a profile. for real-world scenarios this likely isn't
|
||||
// an issue, however if it does become a problem then we know where to look.
|
||||
for _, at := range accessTokens {
|
||||
if at.Realm == realm && at.ClientID == clientID && at.UserAssertionHash == userAssertionHash {
|
||||
if checkAlias(at.Environment, envAliases) {
|
||||
if isMatchingScopes(scopes, at.Scopes) {
|
||||
return at, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return AccessToken{}, fmt.Errorf("access token not found")
|
||||
}
|
||||
|
||||
func (m *PartitionedManager) writeAccessToken(accessToken AccessToken, partitionKey string) error {
|
||||
m.contractMu.Lock()
|
||||
defer m.contractMu.Unlock()
|
||||
key := accessToken.Key()
|
||||
if m.contract.AccessTokensPartition[partitionKey] == nil {
|
||||
m.contract.AccessTokensPartition[partitionKey] = make(map[string]AccessToken)
|
||||
}
|
||||
m.contract.AccessTokensPartition[partitionKey][key] = accessToken
|
||||
return nil
|
||||
}
|
||||
|
||||
func matchFamilyRefreshTokenObo(rt accesstokens.RefreshToken, userAssertionHash string, envAliases []string) bool {
|
||||
return rt.UserAssertionHash == userAssertionHash && checkAlias(rt.Environment, envAliases) && rt.FamilyID != ""
|
||||
}
|
||||
|
||||
func matchClientIDRefreshTokenObo(rt accesstokens.RefreshToken, userAssertionHash string, envAliases []string, clientID string) bool {
|
||||
return rt.UserAssertionHash == userAssertionHash && checkAlias(rt.Environment, envAliases) && rt.ClientID == clientID
|
||||
}
|
||||
|
||||
func (m *PartitionedManager) readRefreshToken(envAliases []string, familyID, clientID, userAssertionHash, partitionKey string) (accesstokens.RefreshToken, error) {
|
||||
byFamily := func(rt accesstokens.RefreshToken) bool {
|
||||
return matchFamilyRefreshTokenObo(rt, userAssertionHash, envAliases)
|
||||
}
|
||||
byClient := func(rt accesstokens.RefreshToken) bool {
|
||||
return matchClientIDRefreshTokenObo(rt, userAssertionHash, envAliases, clientID)
|
||||
}
|
||||
|
||||
var matchers []func(rt accesstokens.RefreshToken) bool
|
||||
if familyID == "" {
|
||||
matchers = []func(rt accesstokens.RefreshToken) bool{
|
||||
byClient, byFamily,
|
||||
}
|
||||
} else {
|
||||
matchers = []func(rt accesstokens.RefreshToken) bool{
|
||||
byFamily, byClient,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(keegan): All the tests here pass, but Bogdan says this is
|
||||
// more complicated. I'm opening an issue for this to have him
|
||||
// review the tests and suggest tests that would break this so
|
||||
// we can re-write against good tests. His comments as follow:
|
||||
// The algorithm is a bit more complex than this, I assume there are some tests covering everything. I would keep the order as is.
|
||||
// The algorithm is:
|
||||
// If application is NOT part of the family, search by client_ID
|
||||
// If app is part of the family or if we DO NOT KNOW if it's part of the family, search by family ID, then by client_id (we will know if an app is part of the family after the first token response).
|
||||
// https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/blob/311fe8b16e7c293462806f397e189a6aa1159769/src/client/Microsoft.Identity.Client/Internal/Requests/Silent/CacheSilentStrategy.cs#L95
|
||||
m.contractMu.RLock()
|
||||
defer m.contractMu.RUnlock()
|
||||
for _, matcher := range matchers {
|
||||
for _, rt := range m.contract.RefreshTokensPartition[partitionKey] {
|
||||
if matcher(rt) {
|
||||
return rt, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return accesstokens.RefreshToken{}, fmt.Errorf("refresh token not found")
|
||||
}
|
||||
|
||||
func (m *PartitionedManager) writeRefreshToken(refreshToken accesstokens.RefreshToken, partitionKey string) error {
|
||||
m.contractMu.Lock()
|
||||
defer m.contractMu.Unlock()
|
||||
key := refreshToken.Key()
|
||||
if m.contract.AccessTokensPartition[partitionKey] == nil {
|
||||
m.contract.RefreshTokensPartition[partitionKey] = make(map[string]accesstokens.RefreshToken)
|
||||
}
|
||||
m.contract.RefreshTokensPartition[partitionKey][key] = refreshToken
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *PartitionedManager) readIDToken(envAliases []string, realm, clientID, userAssertionHash, partitionKey string) (IDToken, error) {
|
||||
m.contractMu.RLock()
|
||||
defer m.contractMu.RUnlock()
|
||||
for _, idt := range m.contract.IDTokensPartition[partitionKey] {
|
||||
if idt.Realm == realm && idt.ClientID == clientID && idt.UserAssertionHash == userAssertionHash {
|
||||
if checkAlias(idt.Environment, envAliases) {
|
||||
return idt, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return IDToken{}, fmt.Errorf("token not found")
|
||||
}
|
||||
|
||||
func (m *PartitionedManager) writeIDToken(idToken IDToken, partitionKey string) error {
|
||||
key := idToken.Key()
|
||||
m.contractMu.Lock()
|
||||
defer m.contractMu.Unlock()
|
||||
if m.contract.IDTokensPartition[partitionKey] == nil {
|
||||
m.contract.IDTokensPartition[partitionKey] = make(map[string]IDToken)
|
||||
}
|
||||
m.contract.IDTokensPartition[partitionKey][key] = idToken
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *PartitionedManager) readAccount(envAliases []string, realm, UserAssertionHash, partitionKey string) (shared.Account, error) {
|
||||
m.contractMu.RLock()
|
||||
defer m.contractMu.RUnlock()
|
||||
|
||||
// You might ask why, if cache.Accounts is a map, we would loop through all of these instead of using a key.
|
||||
// We only use a map because the storage contract shared between all language implementations says use a map.
|
||||
// We can't change that. The other is because the keys are made using a specific "env", but here we are allowing
|
||||
// a match in multiple envs (envAlias). That means we either need to hash each possible keyand do the lookup
|
||||
// or just statically check. Since the design is to have a storage.Manager per user, the amount of keys stored
|
||||
// is really low (say 2). Each hash is more expensive than the entire iteration.
|
||||
for _, acc := range m.contract.AccountsPartition[partitionKey] {
|
||||
if checkAlias(acc.Environment, envAliases) && acc.UserAssertionHash == UserAssertionHash && acc.Realm == realm {
|
||||
return acc, nil
|
||||
}
|
||||
}
|
||||
return shared.Account{}, fmt.Errorf("account not found")
|
||||
}
|
||||
|
||||
func (m *PartitionedManager) writeAccount(account shared.Account, partitionKey string) error {
|
||||
key := account.Key()
|
||||
m.contractMu.Lock()
|
||||
defer m.contractMu.Unlock()
|
||||
if m.contract.AccountsPartition[partitionKey] == nil {
|
||||
m.contract.AccountsPartition[partitionKey] = make(map[string]shared.Account)
|
||||
}
|
||||
m.contract.AccountsPartition[partitionKey][key] = account
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *PartitionedManager) readAppMetaData(envAliases []string, clientID string) (AppMetaData, error) {
|
||||
m.contractMu.RLock()
|
||||
defer m.contractMu.RUnlock()
|
||||
|
||||
for _, app := range m.contract.AppMetaData {
|
||||
if checkAlias(app.Environment, envAliases) && app.ClientID == clientID {
|
||||
return app, nil
|
||||
}
|
||||
}
|
||||
return AppMetaData{}, fmt.Errorf("not found")
|
||||
}
|
||||
|
||||
func (m *PartitionedManager) writeAppMetaData(AppMetaData AppMetaData) error {
|
||||
key := AppMetaData.Key()
|
||||
m.contractMu.Lock()
|
||||
defer m.contractMu.Unlock()
|
||||
m.contract.AppMetaData[key] = AppMetaData
|
||||
return nil
|
||||
}
|
||||
|
||||
// update updates the internal cache object. This is for use in tests, other uses are not
|
||||
// supported.
|
||||
func (m *PartitionedManager) update(cache *InMemoryContract) {
|
||||
m.contractMu.Lock()
|
||||
defer m.contractMu.Unlock()
|
||||
m.contract = cache
|
||||
}
|
||||
|
||||
// Marshal implements cache.Marshaler.
|
||||
func (m *PartitionedManager) Marshal() ([]byte, error) {
|
||||
return json.Marshal(m.contract)
|
||||
}
|
||||
|
||||
// Unmarshal implements cache.Unmarshaler.
|
||||
func (m *PartitionedManager) Unmarshal(b []byte) error {
|
||||
m.contractMu.Lock()
|
||||
defer m.contractMu.Unlock()
|
||||
|
||||
contract := NewInMemoryContract()
|
||||
|
||||
err := json.Unmarshal(b, contract)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m.contract = contract
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getPartitionKeyAccessToken(item AccessToken) string {
|
||||
if item.UserAssertionHash != "" {
|
||||
return item.UserAssertionHash
|
||||
}
|
||||
return item.HomeAccountID
|
||||
}
|
||||
|
||||
func getPartitionKeyRefreshToken(item accesstokens.RefreshToken) string {
|
||||
if item.UserAssertionHash != "" {
|
||||
return item.UserAssertionHash
|
||||
}
|
||||
return item.HomeAccountID
|
||||
}
|
||||
|
||||
func getPartitionKeyIDToken(item IDToken) string {
|
||||
return item.HomeAccountID
|
||||
}
|
||||
|
||||
func getPartitionKeyAccount(item shared.Account) string {
|
||||
return item.HomeAccountID
|
||||
}
|
||||
|
||||
func getPartitionKeyIDTokenRead(item AccessToken) string {
|
||||
return item.HomeAccountID
|
||||
}
|
514
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/base/internal/storage/storage.go
generated
vendored
Normal file
514
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/base/internal/storage/storage.go
generated
vendored
Normal file
|
@ -0,0 +1,514 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
// Package storage holds all cached token information for MSAL. This storage can be
|
||||
// augmented with third-party extensions to provide persistent storage. In that case,
|
||||
// reads and writes in upper packages will call Marshal() to take the entire in-memory
|
||||
// representation and write it to storage and Unmarshal() to update the entire in-memory
|
||||
// storage with what was in the persistent storage. The persistent storage can only be
|
||||
// accessed in this way because multiple MSAL clients written in multiple languages can
|
||||
// access the same storage and must adhere to the same method that was defined
|
||||
// previously.
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json"
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth"
|
||||
"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/shared"
|
||||
)
|
||||
|
||||
// aadInstanceDiscoveryer allows faking in tests.
|
||||
// It is implemented in production by ops/authority.Client
|
||||
type aadInstanceDiscoveryer interface {
|
||||
AADInstanceDiscovery(ctx context.Context, authorityInfo authority.Info) (authority.InstanceDiscoveryResponse, error)
|
||||
}
|
||||
|
||||
// TokenResponse mimics a token response that was pulled from the cache.
|
||||
type TokenResponse struct {
|
||||
RefreshToken accesstokens.RefreshToken
|
||||
IDToken IDToken // *Credential
|
||||
AccessToken AccessToken
|
||||
Account shared.Account
|
||||
}
|
||||
|
||||
// Manager is an in-memory cache of access tokens, accounts and meta data. This data is
|
||||
// updated on read/write calls. Unmarshal() replaces all data stored here with whatever
|
||||
// was given to it on each call.
|
||||
type Manager struct {
|
||||
contract *Contract
|
||||
contractMu sync.RWMutex
|
||||
requests aadInstanceDiscoveryer // *oauth.Token
|
||||
|
||||
aadCacheMu sync.RWMutex
|
||||
aadCache map[string]authority.InstanceDiscoveryMetadata
|
||||
}
|
||||
|
||||
// New is the constructor for Manager.
|
||||
func New(requests *oauth.Client) *Manager {
|
||||
m := &Manager{requests: requests, aadCache: make(map[string]authority.InstanceDiscoveryMetadata)}
|
||||
m.contract = NewContract()
|
||||
return m
|
||||
}
|
||||
|
||||
func checkAlias(alias string, aliases []string) bool {
|
||||
for _, v := range aliases {
|
||||
if alias == v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isMatchingScopes(scopesOne []string, scopesTwo string) bool {
|
||||
newScopesTwo := strings.Split(scopesTwo, scopeSeparator)
|
||||
scopeCounter := 0
|
||||
for _, scope := range scopesOne {
|
||||
for _, otherScope := range newScopesTwo {
|
||||
if strings.EqualFold(scope, otherScope) {
|
||||
scopeCounter++
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
return scopeCounter == len(scopesOne)
|
||||
}
|
||||
|
||||
// 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) {
|
||||
tr := TokenResponse{}
|
||||
homeAccountID := authParameters.HomeAccountID
|
||||
realm := authParameters.AuthorityInfo.Tenant
|
||||
clientID := authParameters.ClientID
|
||||
scopes := authParameters.Scopes
|
||||
|
||||
// fetch metadata if instanceDiscovery is enabled
|
||||
aliases := []string{authParameters.AuthorityInfo.Host}
|
||||
if !authParameters.AuthorityInfo.InstanceDiscoveryDisabled {
|
||||
metadata, err := m.getMetadataEntry(ctx, authParameters.AuthorityInfo)
|
||||
if err != nil {
|
||||
return TokenResponse{}, err
|
||||
}
|
||||
aliases = metadata.Aliases
|
||||
}
|
||||
|
||||
accessToken := m.readAccessToken(homeAccountID, aliases, realm, clientID, scopes)
|
||||
tr.AccessToken = accessToken
|
||||
|
||||
if account.IsZero() {
|
||||
return tr, nil
|
||||
}
|
||||
// errors returned by read* methods indicate a cache miss and are therefore non-fatal. We continue populating
|
||||
// TokenResponse fields so that e.g. lack of an ID token doesn't prevent the caller from receiving a refresh token.
|
||||
idToken, err := m.readIDToken(homeAccountID, aliases, realm, clientID)
|
||||
if err == nil {
|
||||
tr.IDToken = idToken
|
||||
}
|
||||
|
||||
if appMetadata, err := m.readAppMetaData(aliases, clientID); err == nil {
|
||||
// we need the family ID to identify the correct refresh token, if any
|
||||
familyID := appMetadata.FamilyID
|
||||
refreshToken, err := m.readRefreshToken(homeAccountID, aliases, familyID, clientID)
|
||||
if err == nil {
|
||||
tr.RefreshToken = refreshToken
|
||||
}
|
||||
}
|
||||
|
||||
account, err = m.readAccount(homeAccountID, aliases, realm)
|
||||
if err == nil {
|
||||
tr.Account = account
|
||||
}
|
||||
return tr, nil
|
||||
}
|
||||
|
||||
const scopeSeparator = " "
|
||||
|
||||
// Write writes a token response to the cache and returns the account information the token is stored with.
|
||||
func (m *Manager) Write(authParameters authority.AuthParams, tokenResponse accesstokens.TokenResponse) (shared.Account, error) {
|
||||
authParameters.HomeAccountID = tokenResponse.ClientInfo.HomeAccountID()
|
||||
homeAccountID := authParameters.HomeAccountID
|
||||
environment := authParameters.AuthorityInfo.Host
|
||||
realm := authParameters.AuthorityInfo.Tenant
|
||||
clientID := authParameters.ClientID
|
||||
target := strings.Join(tokenResponse.GrantedScopes.Slice, scopeSeparator)
|
||||
cachedAt := time.Now()
|
||||
|
||||
var account shared.Account
|
||||
|
||||
if len(tokenResponse.RefreshToken) > 0 {
|
||||
refreshToken := accesstokens.NewRefreshToken(homeAccountID, environment, clientID, tokenResponse.RefreshToken, tokenResponse.FamilyID)
|
||||
if err := m.writeRefreshToken(refreshToken); err != nil {
|
||||
return account, err
|
||||
}
|
||||
}
|
||||
|
||||
if len(tokenResponse.AccessToken) > 0 {
|
||||
accessToken := NewAccessToken(
|
||||
homeAccountID,
|
||||
environment,
|
||||
realm,
|
||||
clientID,
|
||||
cachedAt,
|
||||
tokenResponse.ExpiresOn.T,
|
||||
tokenResponse.ExtExpiresOn.T,
|
||||
target,
|
||||
tokenResponse.AccessToken,
|
||||
)
|
||||
|
||||
// Since we have a valid access token, cache it before moving on.
|
||||
if err := accessToken.Validate(); err == nil {
|
||||
if err := m.writeAccessToken(accessToken); err != nil {
|
||||
return account, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
idTokenJwt := tokenResponse.IDToken
|
||||
if !idTokenJwt.IsZero() {
|
||||
idToken := NewIDToken(homeAccountID, environment, realm, clientID, idTokenJwt.RawToken)
|
||||
if err := m.writeIDToken(idToken); err != nil {
|
||||
return shared.Account{}, err
|
||||
}
|
||||
|
||||
localAccountID := idTokenJwt.LocalAccountID()
|
||||
authorityType := authParameters.AuthorityInfo.AuthorityType
|
||||
|
||||
preferredUsername := idTokenJwt.UPN
|
||||
if idTokenJwt.PreferredUsername != "" {
|
||||
preferredUsername = idTokenJwt.PreferredUsername
|
||||
}
|
||||
|
||||
account = shared.NewAccount(
|
||||
homeAccountID,
|
||||
environment,
|
||||
realm,
|
||||
localAccountID,
|
||||
authorityType,
|
||||
preferredUsername,
|
||||
)
|
||||
if err := m.writeAccount(account); err != nil {
|
||||
return shared.Account{}, err
|
||||
}
|
||||
}
|
||||
|
||||
AppMetaData := NewAppMetaData(tokenResponse.FamilyID, clientID, environment)
|
||||
|
||||
if err := m.writeAppMetaData(AppMetaData); err != nil {
|
||||
return shared.Account{}, err
|
||||
}
|
||||
return account, nil
|
||||
}
|
||||
|
||||
func (m *Manager) getMetadataEntry(ctx context.Context, authorityInfo authority.Info) (authority.InstanceDiscoveryMetadata, error) {
|
||||
md, err := m.aadMetadataFromCache(ctx, authorityInfo)
|
||||
if err != nil {
|
||||
// not in the cache, retrieve it
|
||||
md, err = m.aadMetadata(ctx, authorityInfo)
|
||||
}
|
||||
return md, err
|
||||
}
|
||||
|
||||
func (m *Manager) aadMetadataFromCache(ctx context.Context, authorityInfo authority.Info) (authority.InstanceDiscoveryMetadata, error) {
|
||||
m.aadCacheMu.RLock()
|
||||
defer m.aadCacheMu.RUnlock()
|
||||
metadata, ok := m.aadCache[authorityInfo.Host]
|
||||
if ok {
|
||||
return metadata, nil
|
||||
}
|
||||
return metadata, errors.New("not found")
|
||||
}
|
||||
|
||||
func (m *Manager) aadMetadata(ctx context.Context, authorityInfo authority.Info) (authority.InstanceDiscoveryMetadata, error) {
|
||||
m.aadCacheMu.Lock()
|
||||
defer m.aadCacheMu.Unlock()
|
||||
discoveryResponse, err := m.requests.AADInstanceDiscovery(ctx, authorityInfo)
|
||||
if err != nil {
|
||||
return authority.InstanceDiscoveryMetadata{}, err
|
||||
}
|
||||
|
||||
for _, metadataEntry := range discoveryResponse.Metadata {
|
||||
for _, aliasedAuthority := range metadataEntry.Aliases {
|
||||
m.aadCache[aliasedAuthority] = metadataEntry
|
||||
}
|
||||
}
|
||||
if _, ok := m.aadCache[authorityInfo.Host]; !ok {
|
||||
m.aadCache[authorityInfo.Host] = authority.InstanceDiscoveryMetadata{
|
||||
PreferredNetwork: authorityInfo.Host,
|
||||
PreferredCache: authorityInfo.Host,
|
||||
}
|
||||
}
|
||||
return m.aadCache[authorityInfo.Host], nil
|
||||
}
|
||||
|
||||
func (m *Manager) readAccessToken(homeID string, envAliases []string, realm, clientID string, scopes []string) AccessToken {
|
||||
m.contractMu.RLock()
|
||||
defer m.contractMu.RUnlock()
|
||||
// TODO: linear search (over a map no less) is slow for a large number (thousands) of tokens.
|
||||
// this shows up as the dominating node in a profile. for real-world scenarios this likely isn't
|
||||
// an issue, however if it does become a problem then we know where to look.
|
||||
for _, at := range m.contract.AccessTokens {
|
||||
if at.HomeAccountID == homeID && at.Realm == realm && at.ClientID == clientID {
|
||||
if checkAlias(at.Environment, envAliases) {
|
||||
if isMatchingScopes(scopes, at.Scopes) {
|
||||
return at
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return AccessToken{}
|
||||
}
|
||||
|
||||
func (m *Manager) writeAccessToken(accessToken AccessToken) error {
|
||||
m.contractMu.Lock()
|
||||
defer m.contractMu.Unlock()
|
||||
key := accessToken.Key()
|
||||
m.contract.AccessTokens[key] = accessToken
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) readRefreshToken(homeID string, envAliases []string, familyID, clientID string) (accesstokens.RefreshToken, error) {
|
||||
byFamily := func(rt accesstokens.RefreshToken) bool {
|
||||
return matchFamilyRefreshToken(rt, homeID, envAliases)
|
||||
}
|
||||
byClient := func(rt accesstokens.RefreshToken) bool {
|
||||
return matchClientIDRefreshToken(rt, homeID, envAliases, clientID)
|
||||
}
|
||||
|
||||
var matchers []func(rt accesstokens.RefreshToken) bool
|
||||
if familyID == "" {
|
||||
matchers = []func(rt accesstokens.RefreshToken) bool{
|
||||
byClient, byFamily,
|
||||
}
|
||||
} else {
|
||||
matchers = []func(rt accesstokens.RefreshToken) bool{
|
||||
byFamily, byClient,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(keegan): All the tests here pass, but Bogdan says this is
|
||||
// more complicated. I'm opening an issue for this to have him
|
||||
// review the tests and suggest tests that would break this so
|
||||
// we can re-write against good tests. His comments as follow:
|
||||
// The algorithm is a bit more complex than this, I assume there are some tests covering everything. I would keep the order as is.
|
||||
// The algorithm is:
|
||||
// If application is NOT part of the family, search by client_ID
|
||||
// If app is part of the family or if we DO NOT KNOW if it's part of the family, search by family ID, then by client_id (we will know if an app is part of the family after the first token response).
|
||||
// https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/blob/311fe8b16e7c293462806f397e189a6aa1159769/src/client/Microsoft.Identity.Client/Internal/Requests/Silent/CacheSilentStrategy.cs#L95
|
||||
m.contractMu.RLock()
|
||||
defer m.contractMu.RUnlock()
|
||||
for _, matcher := range matchers {
|
||||
for _, rt := range m.contract.RefreshTokens {
|
||||
if matcher(rt) {
|
||||
return rt, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return accesstokens.RefreshToken{}, fmt.Errorf("refresh token not found")
|
||||
}
|
||||
|
||||
func matchFamilyRefreshToken(rt accesstokens.RefreshToken, homeID string, envAliases []string) bool {
|
||||
return rt.HomeAccountID == homeID && checkAlias(rt.Environment, envAliases) && rt.FamilyID != ""
|
||||
}
|
||||
|
||||
func matchClientIDRefreshToken(rt accesstokens.RefreshToken, homeID string, envAliases []string, clientID string) bool {
|
||||
return rt.HomeAccountID == homeID && checkAlias(rt.Environment, envAliases) && rt.ClientID == clientID
|
||||
}
|
||||
|
||||
func (m *Manager) writeRefreshToken(refreshToken accesstokens.RefreshToken) error {
|
||||
key := refreshToken.Key()
|
||||
m.contractMu.Lock()
|
||||
defer m.contractMu.Unlock()
|
||||
m.contract.RefreshTokens[key] = refreshToken
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) readIDToken(homeID string, envAliases []string, realm, clientID string) (IDToken, error) {
|
||||
m.contractMu.RLock()
|
||||
defer m.contractMu.RUnlock()
|
||||
for _, idt := range m.contract.IDTokens {
|
||||
if idt.HomeAccountID == homeID && idt.Realm == realm && idt.ClientID == clientID {
|
||||
if checkAlias(idt.Environment, envAliases) {
|
||||
return idt, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return IDToken{}, fmt.Errorf("token not found")
|
||||
}
|
||||
|
||||
func (m *Manager) writeIDToken(idToken IDToken) error {
|
||||
key := idToken.Key()
|
||||
m.contractMu.Lock()
|
||||
defer m.contractMu.Unlock()
|
||||
m.contract.IDTokens[key] = idToken
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) AllAccounts() []shared.Account {
|
||||
m.contractMu.RLock()
|
||||
defer m.contractMu.RUnlock()
|
||||
|
||||
var accounts []shared.Account
|
||||
for _, v := range m.contract.Accounts {
|
||||
accounts = append(accounts, v)
|
||||
}
|
||||
|
||||
return accounts
|
||||
}
|
||||
|
||||
func (m *Manager) Account(homeAccountID string) shared.Account {
|
||||
m.contractMu.RLock()
|
||||
defer m.contractMu.RUnlock()
|
||||
|
||||
for _, v := range m.contract.Accounts {
|
||||
if v.HomeAccountID == homeAccountID {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
return shared.Account{}
|
||||
}
|
||||
|
||||
func (m *Manager) readAccount(homeAccountID string, envAliases []string, realm string) (shared.Account, error) {
|
||||
m.contractMu.RLock()
|
||||
defer m.contractMu.RUnlock()
|
||||
|
||||
// You might ask why, if cache.Accounts is a map, we would loop through all of these instead of using a key.
|
||||
// We only use a map because the storage contract shared between all language implementations says use a map.
|
||||
// We can't change that. The other is because the keys are made using a specific "env", but here we are allowing
|
||||
// a match in multiple envs (envAlias). That means we either need to hash each possible keyand do the lookup
|
||||
// or just statically check. Since the design is to have a storage.Manager per user, the amount of keys stored
|
||||
// is really low (say 2). Each hash is more expensive than the entire iteration.
|
||||
for _, acc := range m.contract.Accounts {
|
||||
if acc.HomeAccountID == homeAccountID && checkAlias(acc.Environment, envAliases) && acc.Realm == realm {
|
||||
return acc, nil
|
||||
}
|
||||
}
|
||||
return shared.Account{}, fmt.Errorf("account not found")
|
||||
}
|
||||
|
||||
func (m *Manager) writeAccount(account shared.Account) error {
|
||||
key := account.Key()
|
||||
m.contractMu.Lock()
|
||||
defer m.contractMu.Unlock()
|
||||
m.contract.Accounts[key] = account
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) readAppMetaData(envAliases []string, clientID string) (AppMetaData, error) {
|
||||
m.contractMu.RLock()
|
||||
defer m.contractMu.RUnlock()
|
||||
|
||||
for _, app := range m.contract.AppMetaData {
|
||||
if checkAlias(app.Environment, envAliases) && app.ClientID == clientID {
|
||||
return app, nil
|
||||
}
|
||||
}
|
||||
return AppMetaData{}, fmt.Errorf("not found")
|
||||
}
|
||||
|
||||
func (m *Manager) writeAppMetaData(AppMetaData AppMetaData) error {
|
||||
key := AppMetaData.Key()
|
||||
m.contractMu.Lock()
|
||||
defer m.contractMu.Unlock()
|
||||
m.contract.AppMetaData[key] = AppMetaData
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveAccount removes all the associated ATs, RTs and IDTs from the cache associated with this account.
|
||||
func (m *Manager) RemoveAccount(account shared.Account, clientID string) {
|
||||
m.removeRefreshTokens(account.HomeAccountID, account.Environment, clientID)
|
||||
m.removeAccessTokens(account.HomeAccountID, account.Environment)
|
||||
m.removeIDTokens(account.HomeAccountID, account.Environment)
|
||||
m.removeAccounts(account.HomeAccountID, account.Environment)
|
||||
}
|
||||
|
||||
func (m *Manager) removeRefreshTokens(homeID string, env string, clientID string) {
|
||||
m.contractMu.Lock()
|
||||
defer m.contractMu.Unlock()
|
||||
for key, rt := range m.contract.RefreshTokens {
|
||||
// Check for RTs associated with the account.
|
||||
if rt.HomeAccountID == homeID && rt.Environment == env {
|
||||
// Do RT's app ownership check as a precaution, in case family apps
|
||||
// and 3rd-party apps share same token cache, although they should not.
|
||||
if rt.ClientID == clientID || rt.FamilyID != "" {
|
||||
delete(m.contract.RefreshTokens, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) removeAccessTokens(homeID string, env string) {
|
||||
m.contractMu.Lock()
|
||||
defer m.contractMu.Unlock()
|
||||
for key, at := range m.contract.AccessTokens {
|
||||
// Remove AT's associated with the account
|
||||
if at.HomeAccountID == homeID && at.Environment == env {
|
||||
// # To avoid the complexity of locating sibling family app's AT, we skip AT's app ownership check.
|
||||
// It means ATs for other apps will also be removed, it is OK because:
|
||||
// non-family apps are not supposed to share token cache to begin with;
|
||||
// Even if it happens, we keep other app's RT already, so SSO still works.
|
||||
delete(m.contract.AccessTokens, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) removeIDTokens(homeID string, env string) {
|
||||
m.contractMu.Lock()
|
||||
defer m.contractMu.Unlock()
|
||||
for key, idt := range m.contract.IDTokens {
|
||||
// Remove ID tokens associated with the account.
|
||||
if idt.HomeAccountID == homeID && idt.Environment == env {
|
||||
delete(m.contract.IDTokens, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) removeAccounts(homeID string, env string) {
|
||||
m.contractMu.Lock()
|
||||
defer m.contractMu.Unlock()
|
||||
for key, acc := range m.contract.Accounts {
|
||||
// Remove the specified account.
|
||||
if acc.HomeAccountID == homeID && acc.Environment == env {
|
||||
delete(m.contract.Accounts, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// update updates the internal cache object. This is for use in tests, other uses are not
|
||||
// supported.
|
||||
func (m *Manager) update(cache *Contract) {
|
||||
m.contractMu.Lock()
|
||||
defer m.contractMu.Unlock()
|
||||
m.contract = cache
|
||||
}
|
||||
|
||||
// Marshal implements cache.Marshaler.
|
||||
func (m *Manager) Marshal() ([]byte, error) {
|
||||
return json.Marshal(m.contract)
|
||||
}
|
||||
|
||||
// Unmarshal implements cache.Unmarshaler.
|
||||
func (m *Manager) Unmarshal(b []byte) error {
|
||||
m.contractMu.Lock()
|
||||
defer m.contractMu.Unlock()
|
||||
|
||||
contract := NewContract()
|
||||
|
||||
err := json.Unmarshal(b, contract)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m.contract = contract
|
||||
|
||||
return nil
|
||||
}
|
56
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/base/internal/storage/test_serialized_cache.json
generated
vendored
Normal file
56
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/base/internal/storage/test_serialized_cache.json
generated
vendored
Normal file
|
@ -0,0 +1,56 @@
|
|||
{
|
||||
"Account": {
|
||||
"uid.utid-login.windows.net-contoso": {
|
||||
"username": "John Doe",
|
||||
"local_account_id": "object1234",
|
||||
"realm": "contoso",
|
||||
"environment": "login.windows.net",
|
||||
"home_account_id": "uid.utid",
|
||||
"authority_type": "MSSTS"
|
||||
}
|
||||
},
|
||||
"RefreshToken": {
|
||||
"uid.utid-login.windows.net-refreshtoken-my_client_id--s2 s1 s3": {
|
||||
"target": "s2 s1 s3",
|
||||
"environment": "login.windows.net",
|
||||
"credential_type": "RefreshToken",
|
||||
"secret": "a refresh token",
|
||||
"client_id": "my_client_id",
|
||||
"home_account_id": "uid.utid"
|
||||
}
|
||||
},
|
||||
"AccessToken": {
|
||||
"an-entry": {
|
||||
"foo": "bar"
|
||||
},
|
||||
"uid.utid-login.windows.net-accesstoken-my_client_id-contoso-s2 s1 s3": {
|
||||
"environment": "login.windows.net",
|
||||
"credential_type": "AccessToken",
|
||||
"secret": "an access token",
|
||||
"realm": "contoso",
|
||||
"target": "s2 s1 s3",
|
||||
"client_id": "my_client_id",
|
||||
"cached_at": "1000",
|
||||
"home_account_id": "uid.utid",
|
||||
"extended_expires_on": "4600",
|
||||
"expires_on": "4600"
|
||||
}
|
||||
},
|
||||
"IdToken": {
|
||||
"uid.utid-login.windows.net-idtoken-my_client_id-contoso-": {
|
||||
"realm": "contoso",
|
||||
"environment": "login.windows.net",
|
||||
"credential_type": "IdToken",
|
||||
"secret": "header.eyJvaWQiOiAib2JqZWN0MTIzNCIsICJwcmVmZXJyZWRfdXNlcm5hbWUiOiAiSm9obiBEb2UiLCAic3ViIjogInN1YiJ9.signature",
|
||||
"client_id": "my_client_id",
|
||||
"home_account_id": "uid.utid"
|
||||
}
|
||||
},
|
||||
"unknownEntity": {"field1":"1","field2":"whats"},
|
||||
"AppMetadata": {
|
||||
"AppMetadata-login.windows.net-my_client_id": {
|
||||
"environment": "login.windows.net",
|
||||
"client_id": "my_client_id"
|
||||
}
|
||||
}
|
||||
}
|
34
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/exported/exported.go
generated
vendored
Normal file
34
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/exported/exported.go
generated
vendored
Normal file
|
@ -0,0 +1,34 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
// package exported contains internal types that are re-exported from a public package
|
||||
package exported
|
||||
|
||||
// AssertionRequestOptions has information required to generate a client assertion
|
||||
type AssertionRequestOptions struct {
|
||||
// ClientID identifies the application for which an assertion is requested. Used as the assertion's "iss" and "sub" claims.
|
||||
ClientID string
|
||||
|
||||
// TokenEndpoint is the intended token endpoint. Used as the assertion's "aud" claim.
|
||||
TokenEndpoint string
|
||||
}
|
||||
|
||||
// TokenProviderParameters is the authentication parameters passed to token providers
|
||||
type TokenProviderParameters struct {
|
||||
// Claims contains any additional claims requested for the token
|
||||
Claims string
|
||||
// CorrelationID of the authentication request
|
||||
CorrelationID string
|
||||
// Scopes requested for the token
|
||||
Scopes []string
|
||||
// TenantID identifies the tenant in which to authenticate
|
||||
TenantID string
|
||||
}
|
||||
|
||||
// TokenProviderResult is the authentication result returned by custom token providers
|
||||
type TokenProviderResult struct {
|
||||
// AccessToken is the requested token
|
||||
AccessToken string
|
||||
// ExpiresInSeconds is the lifetime of the token in seconds
|
||||
ExpiresInSeconds int
|
||||
}
|
140
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json/design.md
generated
vendored
Normal file
140
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json/design.md
generated
vendored
Normal file
|
@ -0,0 +1,140 @@
|
|||
# JSON Package Design
|
||||
Author: John Doak(jdoak@microsoft.com)
|
||||
|
||||
## Why?
|
||||
|
||||
This project needs a special type of marshal/unmarshal not directly supported
|
||||
by the encoding/json package.
|
||||
|
||||
The need revolves around a few key wants/needs:
|
||||
- unmarshal and marshal structs representing JSON messages
|
||||
- fields in the messgage not in the struct must be maintained when unmarshalled
|
||||
- those same fields must be marshalled back when encoded again
|
||||
|
||||
The initial version used map[string]interface{} to put in the keys that
|
||||
were known and then any other keys were put into a field called AdditionalFields.
|
||||
|
||||
This has a few negatives:
|
||||
- Dual marshaling/unmarshalling is required
|
||||
- Adding a struct field requires manually adding a key by name to be encoded/decoded from the map (which is a loosely coupled construct), which can lead to bugs that aren't detected or have bad side effects
|
||||
- Tests can become quickly disconnected if those keys aren't put
|
||||
in tests as well. So you think you have support working, but you
|
||||
don't. Existing tests were found that didn't test the marshalling output.
|
||||
- There is no enforcement that if AdditionalFields is required on one struct, it should be on all containers
|
||||
that don't have custom marshal/unmarshal.
|
||||
|
||||
This package aims to support our needs by providing custom Marshal()/Unmarshal() functions.
|
||||
|
||||
This prevents all the negatives in the initial solution listed above. However, it does add its own negative:
|
||||
- Custom encoding/decoding via reflection is messy (as can be seen in encoding/json itself)
|
||||
|
||||
Go proverb: Reflection is never clear
|
||||
Suggested reading: https://blog.golang.org/laws-of-reflection
|
||||
|
||||
## Important design decisions
|
||||
|
||||
- We don't want to understand all JSON decoding rules
|
||||
- We don't want to deal with all the quoting, commas, etc on decode
|
||||
- Need support for json.Marshaler/Unmarshaler, so we can support types like time.Time
|
||||
- If struct does not implement json.Unmarshaler, it must have AdditionalFields defined
|
||||
- We only support root level objects that are \*struct or struct
|
||||
|
||||
To faciliate these goals, we will utilize the json.Encoder and json.Decoder.
|
||||
They provide streaming processing (efficient) and return errors on bad JSON.
|
||||
|
||||
Support for json.Marshaler/Unmarshaler allows for us to use non-basic types
|
||||
that must be specially encoded/decoded (like time.Time objects).
|
||||
|
||||
We don't support types that can't customer unmarshal or have AdditionalFields
|
||||
in order to prevent future devs from forgetting that important field and
|
||||
generating bad return values.
|
||||
|
||||
Support for root level objects of \*struct or struct simply acknowledges the
|
||||
fact that this is designed only for the purposes listed in the Introduction.
|
||||
Outside that (like encoding a lone number) should be done with the
|
||||
regular json package (as it will not have additional fields).
|
||||
|
||||
We don't support a few things on json supported reference types and structs:
|
||||
- \*map: no need for pointers to maps
|
||||
- \*slice: no need for pointers to slices
|
||||
- any further pointers on struct after \*struct
|
||||
|
||||
There should never be a need for this in Go.
|
||||
|
||||
## Design
|
||||
|
||||
## State Machines
|
||||
|
||||
This uses state machine designs that based upon the Rob Pike talk on
|
||||
lexers and parsers: https://www.youtube.com/watch?v=HxaD_trXwRE
|
||||
|
||||
This is the most common pattern for state machines in Go and
|
||||
the model to follow closesly when dealing with streaming
|
||||
processing of textual data.
|
||||
|
||||
Our state machines are based on the type:
|
||||
```go
|
||||
type stateFn func() (stateFn, error)
|
||||
```
|
||||
|
||||
The state machine itself is simply a struct that has methods that
|
||||
satisfy stateFn.
|
||||
|
||||
Our state machines have a few standard calls
|
||||
- run(): runs the state machine
|
||||
- start(): always the first stateFn to be called
|
||||
|
||||
All state machines have the following logic:
|
||||
* run() is called
|
||||
* start() is called and returns the next stateFn or error
|
||||
* stateFn is called
|
||||
- If returned stateFn(next state) is non-nil, call it
|
||||
- If error is non-nil, run() returns the error
|
||||
- If stateFn == nil and err == nil, run() return err == nil
|
||||
|
||||
## Supporting types
|
||||
|
||||
Marshalling/Unmarshalling must support(within top level struct):
|
||||
- struct
|
||||
- \*struct
|
||||
- []struct
|
||||
- []\*struct
|
||||
- []map[string]structContainer
|
||||
- [][]structContainer
|
||||
|
||||
**Term note:** structContainer == type that has a struct or \*struct inside it
|
||||
|
||||
We specifically do not support []interface or map[string]interface
|
||||
where the interface value would hold some value with a struct in it.
|
||||
|
||||
Those will still marshal/unmarshal, but without support for
|
||||
AdditionalFields.
|
||||
|
||||
## Marshalling
|
||||
|
||||
The marshalling design will be based around a statemachine design.
|
||||
|
||||
The basic logic is as follows:
|
||||
|
||||
* If struct has custom marshaller, call it and return
|
||||
* If struct has field "AdditionalFields", it must be a map[string]interface{}
|
||||
* If struct does not have "AdditionalFields", give an error
|
||||
* Get struct tag detailing json names to go names, create mapping
|
||||
* For each public field name
|
||||
- Write field name out
|
||||
- If field value is a struct, recursively call our state machine
|
||||
- Otherwise, use the json.Encoder to write out the value
|
||||
|
||||
## Unmarshalling
|
||||
|
||||
The unmarshalling desin is also based around a statemachine design. The
|
||||
basic logic is as follows:
|
||||
|
||||
* If struct has custom marhaller, call it
|
||||
* If struct has field "AdditionalFields", it must be a map[string]interface{}
|
||||
* Get struct tag detailing json names to go names, create mapping
|
||||
* For each key found
|
||||
- If key exists,
|
||||
- If value is basic type, extract value into struct field using Decoder
|
||||
- If value is struct type, recursively call statemachine
|
||||
- If key doesn't exist, add it to AdditionalFields if it exists using Decoder
|
184
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json/json.go
generated
vendored
Normal file
184
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json/json.go
generated
vendored
Normal file
|
@ -0,0 +1,184 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
// Package json provide functions for marshalling an unmarshalling types to JSON. These functions are meant to
|
||||
// be utilized inside of structs that implement json.Unmarshaler and json.Marshaler interfaces.
|
||||
// This package provides the additional functionality of writing fields that are not in the struct when marshalling
|
||||
// to a field called AdditionalFields if that field exists and is a map[string]interface{}.
|
||||
// When marshalling, if the struct has all the same prerequisites, it will uses the keys in AdditionalFields as
|
||||
// extra fields. This package uses encoding/json underneath.
|
||||
package json
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const addField = "AdditionalFields"
|
||||
const (
|
||||
marshalJSON = "MarshalJSON"
|
||||
unmarshalJSON = "UnmarshalJSON"
|
||||
)
|
||||
|
||||
var (
|
||||
leftBrace = []byte("{")[0]
|
||||
rightBrace = []byte("}")[0]
|
||||
comma = []byte(",")[0]
|
||||
leftParen = []byte("[")[0]
|
||||
rightParen = []byte("]")[0]
|
||||
)
|
||||
|
||||
var mapStrInterType = reflect.TypeOf(map[string]interface{}{})
|
||||
|
||||
// stateFn defines a state machine function. This will be used in all state
|
||||
// machines in this package.
|
||||
type stateFn func() (stateFn, error)
|
||||
|
||||
// Marshal is used to marshal a type into its JSON representation. It
|
||||
// wraps the stdlib calls in order to marshal a struct or *struct so
|
||||
// that a field called "AdditionalFields" of type map[string]interface{}
|
||||
// with "-" used inside struct tag `json:"-"` can be marshalled as if
|
||||
// they were fields within the struct.
|
||||
func Marshal(i interface{}) ([]byte, error) {
|
||||
buff := bytes.Buffer{}
|
||||
enc := json.NewEncoder(&buff)
|
||||
enc.SetEscapeHTML(false)
|
||||
enc.SetIndent("", "")
|
||||
|
||||
v := reflect.ValueOf(i)
|
||||
if v.Kind() != reflect.Ptr && v.CanAddr() {
|
||||
v = v.Addr()
|
||||
}
|
||||
err := marshalStruct(v, &buff, enc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buff.Bytes(), nil
|
||||
}
|
||||
|
||||
// Unmarshal unmarshals a []byte representing JSON into i, which must be a *struct. In addition, if the struct has
|
||||
// a field called AdditionalFields of type map[string]interface{}, JSON data representing fields not in the struct
|
||||
// will be written as key/value pairs to AdditionalFields.
|
||||
func Unmarshal(b []byte, i interface{}) error {
|
||||
if len(b) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
jdec := json.NewDecoder(bytes.NewBuffer(b))
|
||||
jdec.UseNumber()
|
||||
return unmarshalStruct(jdec, i)
|
||||
}
|
||||
|
||||
// MarshalRaw marshals i into a json.RawMessage. If I cannot be marshalled,
|
||||
// this will panic. This is exposed to help test AdditionalField values
|
||||
// which are stored as json.RawMessage.
|
||||
func MarshalRaw(i interface{}) json.RawMessage {
|
||||
b, err := json.Marshal(i)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return json.RawMessage(b)
|
||||
}
|
||||
|
||||
// isDelim simply tests to see if a json.Token is a delimeter.
|
||||
func isDelim(got json.Token) bool {
|
||||
switch got.(type) {
|
||||
case json.Delim:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// delimIs tests got to see if it is want.
|
||||
func delimIs(got json.Token, want rune) bool {
|
||||
switch v := got.(type) {
|
||||
case json.Delim:
|
||||
if v == json.Delim(want) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// hasMarshalJSON will determine if the value or a pointer to this value has
|
||||
// the MarshalJSON method.
|
||||
func hasMarshalJSON(v reflect.Value) bool {
|
||||
if method := v.MethodByName(marshalJSON); method.Kind() != reflect.Invalid {
|
||||
_, ok := v.Interface().(json.Marshaler)
|
||||
return ok
|
||||
}
|
||||
|
||||
if v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
} else {
|
||||
if !v.CanAddr() {
|
||||
return false
|
||||
}
|
||||
v = v.Addr()
|
||||
}
|
||||
|
||||
if method := v.MethodByName(marshalJSON); method.Kind() != reflect.Invalid {
|
||||
_, ok := v.Interface().(json.Marshaler)
|
||||
return ok
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// callMarshalJSON will call MarshalJSON() method on the value or a pointer to this value.
|
||||
// This will panic if the method is not defined.
|
||||
func callMarshalJSON(v reflect.Value) ([]byte, error) {
|
||||
if method := v.MethodByName(marshalJSON); method.Kind() != reflect.Invalid {
|
||||
marsh := v.Interface().(json.Marshaler)
|
||||
return marsh.MarshalJSON()
|
||||
}
|
||||
|
||||
if v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
} else {
|
||||
if v.CanAddr() {
|
||||
v = v.Addr()
|
||||
}
|
||||
}
|
||||
|
||||
if method := v.MethodByName(unmarshalJSON); method.Kind() != reflect.Invalid {
|
||||
marsh := v.Interface().(json.Marshaler)
|
||||
return marsh.MarshalJSON()
|
||||
}
|
||||
|
||||
panic(fmt.Sprintf("callMarshalJSON called on type %T that does not have MarshalJSON defined", v.Interface()))
|
||||
}
|
||||
|
||||
// hasUnmarshalJSON will determine if the value or a pointer to this value has
|
||||
// the UnmarshalJSON method.
|
||||
func hasUnmarshalJSON(v reflect.Value) bool {
|
||||
// You can't unmarshal on a non-pointer type.
|
||||
if v.Kind() != reflect.Ptr {
|
||||
if !v.CanAddr() {
|
||||
return false
|
||||
}
|
||||
v = v.Addr()
|
||||
}
|
||||
|
||||
if method := v.MethodByName(unmarshalJSON); method.Kind() != reflect.Invalid {
|
||||
_, ok := v.Interface().(json.Unmarshaler)
|
||||
return ok
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// hasOmitEmpty indicates if the field has instructed us to not output
|
||||
// the field if omitempty is set on the tag. tag is the string
|
||||
// returned by reflect.StructField.Tag().Get().
|
||||
func hasOmitEmpty(tag string) bool {
|
||||
sl := strings.Split(tag, ",")
|
||||
for _, str := range sl {
|
||||
if str == "omitempty" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
333
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json/mapslice.go
generated
vendored
Normal file
333
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json/mapslice.go
generated
vendored
Normal file
|
@ -0,0 +1,333 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
package json
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// unmarshalMap unmarshal's a map.
|
||||
func unmarshalMap(dec *json.Decoder, m reflect.Value) error {
|
||||
if m.Kind() != reflect.Ptr || m.Elem().Kind() != reflect.Map {
|
||||
panic("unmarshalMap called on non-*map value")
|
||||
}
|
||||
mapValueType := m.Elem().Type().Elem()
|
||||
walk := mapWalk{dec: dec, m: m, valueType: mapValueType}
|
||||
if err := walk.run(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type mapWalk struct {
|
||||
dec *json.Decoder
|
||||
key string
|
||||
m reflect.Value
|
||||
valueType reflect.Type
|
||||
}
|
||||
|
||||
// run runs our decoder state machine.
|
||||
func (m *mapWalk) run() error {
|
||||
var state = m.start
|
||||
var err error
|
||||
for {
|
||||
state, err = state()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if state == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *mapWalk) start() (stateFn, error) {
|
||||
// maps can have custom unmarshaler's.
|
||||
if hasUnmarshalJSON(m.m) {
|
||||
err := m.dec.Decode(m.m.Interface())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// We only want to use this if the map value is:
|
||||
// *struct/struct/map/slice
|
||||
// otherwise use standard decode
|
||||
t, _ := m.valueBaseType()
|
||||
switch t.Kind() {
|
||||
case reflect.Struct, reflect.Map, reflect.Slice:
|
||||
delim, err := m.dec.Token()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// This indicates the value was set to JSON null.
|
||||
if delim == nil {
|
||||
return nil, nil
|
||||
}
|
||||
if !delimIs(delim, '{') {
|
||||
return nil, fmt.Errorf("Unmarshal expected opening {, received %v", delim)
|
||||
}
|
||||
return m.next, nil
|
||||
case reflect.Ptr:
|
||||
return nil, fmt.Errorf("do not support maps with values of '**type' or '*reference")
|
||||
}
|
||||
|
||||
// This is a basic map type, so just use Decode().
|
||||
if err := m.dec.Decode(m.m.Interface()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *mapWalk) next() (stateFn, error) {
|
||||
if m.dec.More() {
|
||||
key, err := m.dec.Token()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.key = key.(string)
|
||||
return m.storeValue, nil
|
||||
}
|
||||
// No more entries, so remove final }.
|
||||
_, err := m.dec.Token()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *mapWalk) storeValue() (stateFn, error) {
|
||||
v := m.valueType
|
||||
for {
|
||||
switch v.Kind() {
|
||||
case reflect.Ptr:
|
||||
v = v.Elem()
|
||||
continue
|
||||
case reflect.Struct:
|
||||
return m.storeStruct, nil
|
||||
case reflect.Map:
|
||||
return m.storeMap, nil
|
||||
case reflect.Slice:
|
||||
return m.storeSlice, nil
|
||||
}
|
||||
return nil, fmt.Errorf("bug: mapWalk.storeValue() called on unsupported type: %v", v.Kind())
|
||||
}
|
||||
}
|
||||
|
||||
func (m *mapWalk) storeStruct() (stateFn, error) {
|
||||
v := newValue(m.valueType)
|
||||
if err := unmarshalStruct(m.dec, v.Interface()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if m.valueType.Kind() == reflect.Ptr {
|
||||
m.m.Elem().SetMapIndex(reflect.ValueOf(m.key), v)
|
||||
return m.next, nil
|
||||
}
|
||||
m.m.Elem().SetMapIndex(reflect.ValueOf(m.key), v.Elem())
|
||||
|
||||
return m.next, nil
|
||||
}
|
||||
|
||||
func (m *mapWalk) storeMap() (stateFn, error) {
|
||||
v := reflect.MakeMap(m.valueType)
|
||||
ptr := newValue(v.Type())
|
||||
ptr.Elem().Set(v)
|
||||
if err := unmarshalMap(m.dec, ptr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
m.m.Elem().SetMapIndex(reflect.ValueOf(m.key), v)
|
||||
|
||||
return m.next, nil
|
||||
}
|
||||
|
||||
func (m *mapWalk) storeSlice() (stateFn, error) {
|
||||
v := newValue(m.valueType)
|
||||
if err := unmarshalSlice(m.dec, v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
m.m.Elem().SetMapIndex(reflect.ValueOf(m.key), v.Elem())
|
||||
|
||||
return m.next, nil
|
||||
}
|
||||
|
||||
// valueType returns the underlying Type. So a *struct would yield
|
||||
// struct, etc...
|
||||
func (m *mapWalk) valueBaseType() (reflect.Type, bool) {
|
||||
ptr := false
|
||||
v := m.valueType
|
||||
if v.Kind() == reflect.Ptr {
|
||||
ptr = true
|
||||
v = v.Elem()
|
||||
}
|
||||
return v, ptr
|
||||
}
|
||||
|
||||
// unmarshalSlice unmarshal's the next value, which must be a slice, into
|
||||
// ptrSlice, which must be a pointer to a slice. newValue() can be use to
|
||||
// create the slice.
|
||||
func unmarshalSlice(dec *json.Decoder, ptrSlice reflect.Value) error {
|
||||
if ptrSlice.Kind() != reflect.Ptr || ptrSlice.Elem().Kind() != reflect.Slice {
|
||||
panic("unmarshalSlice called on non-*[]slice value")
|
||||
}
|
||||
sliceValueType := ptrSlice.Elem().Type().Elem()
|
||||
walk := sliceWalk{
|
||||
dec: dec,
|
||||
s: ptrSlice,
|
||||
valueType: sliceValueType,
|
||||
}
|
||||
if err := walk.run(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type sliceWalk struct {
|
||||
dec *json.Decoder
|
||||
s reflect.Value // *[]slice
|
||||
valueType reflect.Type
|
||||
}
|
||||
|
||||
// run runs our decoder state machine.
|
||||
func (s *sliceWalk) run() error {
|
||||
var state = s.start
|
||||
var err error
|
||||
for {
|
||||
state, err = state()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if state == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sliceWalk) start() (stateFn, error) {
|
||||
// slices can have custom unmarshaler's.
|
||||
if hasUnmarshalJSON(s.s) {
|
||||
err := s.dec.Decode(s.s.Interface())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// We only want to use this if the slice value is:
|
||||
// []*struct/[]struct/[]map/[]slice
|
||||
// otherwise use standard decode
|
||||
t := s.valueBaseType()
|
||||
|
||||
switch t.Kind() {
|
||||
case reflect.Ptr:
|
||||
return nil, fmt.Errorf("cannot unmarshal into a **<type> or *<reference>")
|
||||
case reflect.Struct, reflect.Map, reflect.Slice:
|
||||
delim, err := s.dec.Token()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// This indicates the value was set to nil.
|
||||
if delim == nil {
|
||||
return nil, nil
|
||||
}
|
||||
if !delimIs(delim, '[') {
|
||||
return nil, fmt.Errorf("Unmarshal expected opening [, received %v", delim)
|
||||
}
|
||||
return s.next, nil
|
||||
}
|
||||
|
||||
if err := s.dec.Decode(s.s.Interface()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (s *sliceWalk) next() (stateFn, error) {
|
||||
if s.dec.More() {
|
||||
return s.storeValue, nil
|
||||
}
|
||||
// Nothing left in the slice, remove closing ]
|
||||
_, err := s.dec.Token()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (s *sliceWalk) storeValue() (stateFn, error) {
|
||||
t := s.valueBaseType()
|
||||
switch t.Kind() {
|
||||
case reflect.Ptr:
|
||||
return nil, fmt.Errorf("do not support 'pointer to pointer' or 'pointer to reference' types")
|
||||
case reflect.Struct:
|
||||
return s.storeStruct, nil
|
||||
case reflect.Map:
|
||||
return s.storeMap, nil
|
||||
case reflect.Slice:
|
||||
return s.storeSlice, nil
|
||||
}
|
||||
return nil, fmt.Errorf("bug: sliceWalk.storeValue() called on unsupported type: %v", t.Kind())
|
||||
}
|
||||
|
||||
func (s *sliceWalk) storeStruct() (stateFn, error) {
|
||||
v := newValue(s.valueType)
|
||||
if err := unmarshalStruct(s.dec, v.Interface()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if s.valueType.Kind() == reflect.Ptr {
|
||||
s.s.Elem().Set(reflect.Append(s.s.Elem(), v))
|
||||
return s.next, nil
|
||||
}
|
||||
|
||||
s.s.Elem().Set(reflect.Append(s.s.Elem(), v.Elem()))
|
||||
return s.next, nil
|
||||
}
|
||||
|
||||
func (s *sliceWalk) storeMap() (stateFn, error) {
|
||||
v := reflect.MakeMap(s.valueType)
|
||||
ptr := newValue(v.Type())
|
||||
ptr.Elem().Set(v)
|
||||
|
||||
if err := unmarshalMap(s.dec, ptr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.s.Elem().Set(reflect.Append(s.s.Elem(), v))
|
||||
|
||||
return s.next, nil
|
||||
}
|
||||
|
||||
func (s *sliceWalk) storeSlice() (stateFn, error) {
|
||||
v := newValue(s.valueType)
|
||||
if err := unmarshalSlice(s.dec, v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.s.Elem().Set(reflect.Append(s.s.Elem(), v.Elem()))
|
||||
|
||||
return s.next, nil
|
||||
}
|
||||
|
||||
// valueType returns the underlying Type. So a *struct would yield
|
||||
// struct, etc...
|
||||
func (s *sliceWalk) valueBaseType() reflect.Type {
|
||||
v := s.valueType
|
||||
if v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// newValue() returns a new *type that represents type passed.
|
||||
func newValue(valueType reflect.Type) reflect.Value {
|
||||
if valueType.Kind() == reflect.Ptr {
|
||||
return reflect.New(valueType.Elem())
|
||||
}
|
||||
return reflect.New(valueType)
|
||||
}
|
346
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json/marshal.go
generated
vendored
Normal file
346
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json/marshal.go
generated
vendored
Normal file
|
@ -0,0 +1,346 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
package json
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// marshalStruct takes in i, which must be a *struct or struct and marshals its content
|
||||
// as JSON into buff (sometimes with writes to buff directly, sometimes via enc).
|
||||
// This call is recursive for all fields of *struct or struct type.
|
||||
func marshalStruct(v reflect.Value, buff *bytes.Buffer, enc *json.Encoder) error {
|
||||
if v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
}
|
||||
// We only care about custom Marshalling a struct.
|
||||
if v.Kind() != reflect.Struct {
|
||||
return fmt.Errorf("bug: marshal() received a non *struct or struct, received type %T", v.Interface())
|
||||
}
|
||||
|
||||
if hasMarshalJSON(v) {
|
||||
b, err := callMarshalJSON(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
buff.Write(b)
|
||||
return nil
|
||||
}
|
||||
|
||||
t := v.Type()
|
||||
|
||||
// If it has an AdditionalFields field make sure its the right type.
|
||||
f := v.FieldByName(addField)
|
||||
if f.Kind() != reflect.Invalid {
|
||||
if f.Kind() != reflect.Map {
|
||||
return fmt.Errorf("type %T has field 'AdditionalFields' that is not a map[string]interface{}", v.Interface())
|
||||
}
|
||||
if !f.Type().AssignableTo(mapStrInterType) {
|
||||
return fmt.Errorf("type %T has field 'AdditionalFields' that is not a map[string]interface{}", v.Interface())
|
||||
}
|
||||
}
|
||||
|
||||
translator, err := findFields(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
buff.WriteByte(leftBrace)
|
||||
for x := 0; x < v.NumField(); x++ {
|
||||
field := v.Field(x)
|
||||
|
||||
// We don't access private fields.
|
||||
if unicode.IsLower(rune(t.Field(x).Name[0])) {
|
||||
continue
|
||||
}
|
||||
|
||||
if t.Field(x).Name == addField {
|
||||
if v.Field(x).Len() > 0 {
|
||||
if err := writeAddFields(field.Interface(), buff, enc); err != nil {
|
||||
return err
|
||||
}
|
||||
buff.WriteByte(comma)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// If they have omitempty set, we don't write out the field if
|
||||
// it is the zero value.
|
||||
if hasOmitEmpty(t.Field(x).Tag.Get("json")) {
|
||||
if v.Field(x).IsZero() {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Write out the field name part.
|
||||
jsonName := translator.jsonName(t.Field(x).Name)
|
||||
buff.WriteString(fmt.Sprintf("%q:", jsonName))
|
||||
|
||||
if field.Kind() == reflect.Ptr {
|
||||
field = field.Elem()
|
||||
}
|
||||
|
||||
if err := marshalStructField(field, buff, enc); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
buff.Truncate(buff.Len() - 1) // Remove final comma
|
||||
buff.WriteByte(rightBrace)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func marshalStructField(field reflect.Value, buff *bytes.Buffer, enc *json.Encoder) error {
|
||||
// Determine if we need a trailing comma.
|
||||
defer buff.WriteByte(comma)
|
||||
|
||||
switch field.Kind() {
|
||||
// If it was a *struct or struct, we need to recursively all marshal().
|
||||
case reflect.Struct:
|
||||
if field.CanAddr() {
|
||||
field = field.Addr()
|
||||
}
|
||||
return marshalStruct(field, buff, enc)
|
||||
case reflect.Map:
|
||||
return marshalMap(field, buff, enc)
|
||||
case reflect.Slice:
|
||||
return marshalSlice(field, buff, enc)
|
||||
}
|
||||
|
||||
// It is just a basic type, so encode it.
|
||||
if err := enc.Encode(field.Interface()); err != nil {
|
||||
return err
|
||||
}
|
||||
buff.Truncate(buff.Len() - 1) // Remove Encode() added \n
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func marshalMap(v reflect.Value, buff *bytes.Buffer, enc *json.Encoder) error {
|
||||
if v.Kind() != reflect.Map {
|
||||
return fmt.Errorf("bug: marshalMap() called on %T", v.Interface())
|
||||
}
|
||||
if v.Len() == 0 {
|
||||
buff.WriteByte(leftBrace)
|
||||
buff.WriteByte(rightBrace)
|
||||
return nil
|
||||
}
|
||||
encoder := mapEncode{m: v, buff: buff, enc: enc}
|
||||
return encoder.run()
|
||||
}
|
||||
|
||||
type mapEncode struct {
|
||||
m reflect.Value
|
||||
buff *bytes.Buffer
|
||||
enc *json.Encoder
|
||||
|
||||
valueBaseType reflect.Type
|
||||
}
|
||||
|
||||
// run runs our encoder state machine.
|
||||
func (m *mapEncode) run() error {
|
||||
var state = m.start
|
||||
var err error
|
||||
for {
|
||||
state, err = state()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if state == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *mapEncode) start() (stateFn, error) {
|
||||
if hasMarshalJSON(m.m) {
|
||||
b, err := callMarshalJSON(m.m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.buff.Write(b)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
valueBaseType := m.m.Type().Elem()
|
||||
if valueBaseType.Kind() == reflect.Ptr {
|
||||
valueBaseType = valueBaseType.Elem()
|
||||
}
|
||||
m.valueBaseType = valueBaseType
|
||||
|
||||
switch valueBaseType.Kind() {
|
||||
case reflect.Ptr:
|
||||
return nil, fmt.Errorf("Marshal does not support **<type> or *<reference>")
|
||||
case reflect.Struct, reflect.Map, reflect.Slice:
|
||||
return m.encode, nil
|
||||
}
|
||||
|
||||
// If the map value doesn't have a struct/map/slice, just Encode() it.
|
||||
if err := m.enc.Encode(m.m.Interface()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.buff.Truncate(m.buff.Len() - 1) // Remove Encode() added \n
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *mapEncode) encode() (stateFn, error) {
|
||||
m.buff.WriteByte(leftBrace)
|
||||
|
||||
iter := m.m.MapRange()
|
||||
for iter.Next() {
|
||||
// Write the key.
|
||||
k := iter.Key()
|
||||
m.buff.WriteString(fmt.Sprintf("%q:", k.String()))
|
||||
|
||||
v := iter.Value()
|
||||
switch m.valueBaseType.Kind() {
|
||||
case reflect.Struct:
|
||||
if v.CanAddr() {
|
||||
v = v.Addr()
|
||||
}
|
||||
if err := marshalStruct(v, m.buff, m.enc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case reflect.Map:
|
||||
if err := marshalMap(v, m.buff, m.enc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case reflect.Slice:
|
||||
if err := marshalSlice(v, m.buff, m.enc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("critical bug: mapEncode.encode() called with value base type: %v", m.valueBaseType.Kind()))
|
||||
}
|
||||
m.buff.WriteByte(comma)
|
||||
}
|
||||
m.buff.Truncate(m.buff.Len() - 1) // Remove final comma
|
||||
m.buff.WriteByte(rightBrace)
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func marshalSlice(v reflect.Value, buff *bytes.Buffer, enc *json.Encoder) error {
|
||||
if v.Kind() != reflect.Slice {
|
||||
return fmt.Errorf("bug: marshalSlice() called on %T", v.Interface())
|
||||
}
|
||||
if v.Len() == 0 {
|
||||
buff.WriteByte(leftParen)
|
||||
buff.WriteByte(rightParen)
|
||||
return nil
|
||||
}
|
||||
encoder := sliceEncode{s: v, buff: buff, enc: enc}
|
||||
return encoder.run()
|
||||
}
|
||||
|
||||
type sliceEncode struct {
|
||||
s reflect.Value
|
||||
buff *bytes.Buffer
|
||||
enc *json.Encoder
|
||||
|
||||
valueBaseType reflect.Type
|
||||
}
|
||||
|
||||
// run runs our encoder state machine.
|
||||
func (s *sliceEncode) run() error {
|
||||
var state = s.start
|
||||
var err error
|
||||
for {
|
||||
state, err = state()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if state == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sliceEncode) start() (stateFn, error) {
|
||||
if hasMarshalJSON(s.s) {
|
||||
b, err := callMarshalJSON(s.s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.buff.Write(b)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
valueBaseType := s.s.Type().Elem()
|
||||
if valueBaseType.Kind() == reflect.Ptr {
|
||||
valueBaseType = valueBaseType.Elem()
|
||||
}
|
||||
s.valueBaseType = valueBaseType
|
||||
|
||||
switch valueBaseType.Kind() {
|
||||
case reflect.Ptr:
|
||||
return nil, fmt.Errorf("Marshal does not support **<type> or *<reference>")
|
||||
case reflect.Struct, reflect.Map, reflect.Slice:
|
||||
return s.encode, nil
|
||||
}
|
||||
|
||||
// If the map value doesn't have a struct/map/slice, just Encode() it.
|
||||
if err := s.enc.Encode(s.s.Interface()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.buff.Truncate(s.buff.Len() - 1) // Remove Encode added \n
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (s *sliceEncode) encode() (stateFn, error) {
|
||||
s.buff.WriteByte(leftParen)
|
||||
for i := 0; i < s.s.Len(); i++ {
|
||||
v := s.s.Index(i)
|
||||
switch s.valueBaseType.Kind() {
|
||||
case reflect.Struct:
|
||||
if v.CanAddr() {
|
||||
v = v.Addr()
|
||||
}
|
||||
if err := marshalStruct(v, s.buff, s.enc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case reflect.Map:
|
||||
if err := marshalMap(v, s.buff, s.enc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case reflect.Slice:
|
||||
if err := marshalSlice(v, s.buff, s.enc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("critical bug: mapEncode.encode() called with value base type: %v", s.valueBaseType.Kind()))
|
||||
}
|
||||
s.buff.WriteByte(comma)
|
||||
}
|
||||
s.buff.Truncate(s.buff.Len() - 1) // Remove final comma
|
||||
s.buff.WriteByte(rightParen)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// writeAddFields writes the AdditionalFields struct field out to JSON as field
|
||||
// values. i must be a map[string]interface{} or this will panic.
|
||||
func writeAddFields(i interface{}, buff *bytes.Buffer, enc *json.Encoder) error {
|
||||
m := i.(map[string]interface{})
|
||||
|
||||
x := 0
|
||||
for k, v := range m {
|
||||
buff.WriteString(fmt.Sprintf("%q:", k))
|
||||
if err := enc.Encode(v); err != nil {
|
||||
return err
|
||||
}
|
||||
buff.Truncate(buff.Len() - 1) // Remove Encode() added \n
|
||||
|
||||
if x+1 != len(m) {
|
||||
buff.WriteByte(comma)
|
||||
}
|
||||
x++
|
||||
}
|
||||
return nil
|
||||
}
|
290
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json/struct.go
generated
vendored
Normal file
290
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json/struct.go
generated
vendored
Normal file
|
@ -0,0 +1,290 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
package json
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func unmarshalStruct(jdec *json.Decoder, i interface{}) error {
|
||||
v := reflect.ValueOf(i)
|
||||
if v.Kind() != reflect.Ptr {
|
||||
return fmt.Errorf("Unmarshal() received type %T, which is not a *struct", i)
|
||||
}
|
||||
v = v.Elem()
|
||||
if v.Kind() != reflect.Struct {
|
||||
return fmt.Errorf("Unmarshal() received type %T, which is not a *struct", i)
|
||||
}
|
||||
|
||||
if hasUnmarshalJSON(v) {
|
||||
// Indicates that this type has a custom Unmarshaler.
|
||||
return jdec.Decode(v.Addr().Interface())
|
||||
}
|
||||
|
||||
f := v.FieldByName(addField)
|
||||
if f.Kind() == reflect.Invalid {
|
||||
return fmt.Errorf("Unmarshal(%T) only supports structs that have the field AdditionalFields or implements json.Unmarshaler", i)
|
||||
}
|
||||
|
||||
if f.Kind() != reflect.Map || !f.Type().AssignableTo(mapStrInterType) {
|
||||
return fmt.Errorf("type %T has field 'AdditionalFields' that is not a map[string]interface{}", i)
|
||||
}
|
||||
|
||||
dec := newDecoder(jdec, v)
|
||||
return dec.run()
|
||||
}
|
||||
|
||||
type decoder struct {
|
||||
dec *json.Decoder
|
||||
value reflect.Value // This will be a reflect.Struct
|
||||
translator translateFields
|
||||
key string
|
||||
}
|
||||
|
||||
func newDecoder(dec *json.Decoder, value reflect.Value) *decoder {
|
||||
return &decoder{value: value, dec: dec}
|
||||
}
|
||||
|
||||
// run runs our decoder state machine.
|
||||
func (d *decoder) run() error {
|
||||
var state = d.start
|
||||
var err error
|
||||
for {
|
||||
state, err = state()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if state == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// start looks for our opening delimeter '{' and then transitions to looping through our fields.
|
||||
func (d *decoder) start() (stateFn, error) {
|
||||
var err error
|
||||
d.translator, err = findFields(d.value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
delim, err := d.dec.Token()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !delimIs(delim, '{') {
|
||||
return nil, fmt.Errorf("Unmarshal expected opening {, received %v", delim)
|
||||
}
|
||||
|
||||
return d.next, nil
|
||||
}
|
||||
|
||||
// next gets the next struct field name from the raw json or stops the machine if we get our closing }.
|
||||
func (d *decoder) next() (stateFn, error) {
|
||||
if !d.dec.More() {
|
||||
// Remove the closing }.
|
||||
if _, err := d.dec.Token(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
key, err := d.dec.Token()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
d.key = key.(string)
|
||||
return d.storeValue, nil
|
||||
}
|
||||
|
||||
// storeValue takes the next value and stores it our struct. If the field can't be found
|
||||
// in the struct, it pushes the operation to storeAdditional().
|
||||
func (d *decoder) storeValue() (stateFn, error) {
|
||||
goName := d.translator.goName(d.key)
|
||||
if goName == "" {
|
||||
goName = d.key
|
||||
}
|
||||
|
||||
// We don't have the field in the struct, so it goes in AdditionalFields.
|
||||
f := d.value.FieldByName(goName)
|
||||
if f.Kind() == reflect.Invalid {
|
||||
return d.storeAdditional, nil
|
||||
}
|
||||
|
||||
// Indicates that this type has a custom Unmarshaler.
|
||||
if hasUnmarshalJSON(f) {
|
||||
err := d.dec.Decode(f.Addr().Interface())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.next, nil
|
||||
}
|
||||
|
||||
t, isPtr, err := fieldBaseType(d.value, goName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("type(%s) had field(%s) %w", d.value.Type().Name(), goName, err)
|
||||
}
|
||||
|
||||
switch t.Kind() {
|
||||
// We need to recursively call ourselves on any *struct or struct.
|
||||
case reflect.Struct:
|
||||
if isPtr {
|
||||
if f.IsNil() {
|
||||
f.Set(reflect.New(t))
|
||||
}
|
||||
} else {
|
||||
f = f.Addr()
|
||||
}
|
||||
if err := unmarshalStruct(d.dec, f.Interface()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.next, nil
|
||||
case reflect.Map:
|
||||
v := reflect.MakeMap(f.Type())
|
||||
ptr := newValue(f.Type())
|
||||
ptr.Elem().Set(v)
|
||||
if err := unmarshalMap(d.dec, ptr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f.Set(ptr.Elem())
|
||||
return d.next, nil
|
||||
case reflect.Slice:
|
||||
v := reflect.MakeSlice(f.Type(), 0, 0)
|
||||
ptr := newValue(f.Type())
|
||||
ptr.Elem().Set(v)
|
||||
if err := unmarshalSlice(d.dec, ptr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f.Set(ptr.Elem())
|
||||
return d.next, nil
|
||||
}
|
||||
|
||||
if !isPtr {
|
||||
f = f.Addr()
|
||||
}
|
||||
|
||||
// For values that are pointers, we need them to be non-nil in order
|
||||
// to decode into them.
|
||||
if f.IsNil() {
|
||||
f.Set(reflect.New(t))
|
||||
}
|
||||
|
||||
if err := d.dec.Decode(f.Interface()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return d.next, nil
|
||||
}
|
||||
|
||||
// storeAdditional pushes the key/value into our .AdditionalFields map.
|
||||
func (d *decoder) storeAdditional() (stateFn, error) {
|
||||
rw := json.RawMessage{}
|
||||
if err := d.dec.Decode(&rw); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
field := d.value.FieldByName(addField)
|
||||
if field.IsNil() {
|
||||
field.Set(reflect.MakeMap(field.Type()))
|
||||
}
|
||||
field.SetMapIndex(reflect.ValueOf(d.key), reflect.ValueOf(rw))
|
||||
return d.next, nil
|
||||
}
|
||||
|
||||
func fieldBaseType(v reflect.Value, fieldName string) (t reflect.Type, isPtr bool, err error) {
|
||||
sf, ok := v.Type().FieldByName(fieldName)
|
||||
if !ok {
|
||||
return nil, false, fmt.Errorf("bug: fieldBaseType() lookup of field(%s) on type(%s): do not have field", fieldName, v.Type().Name())
|
||||
}
|
||||
t = sf.Type
|
||||
if t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
isPtr = true
|
||||
}
|
||||
if t.Kind() == reflect.Ptr {
|
||||
return nil, isPtr, fmt.Errorf("received pointer to pointer type, not supported")
|
||||
}
|
||||
return t, isPtr, nil
|
||||
}
|
||||
|
||||
type translateField struct {
|
||||
jsonName string
|
||||
goName string
|
||||
}
|
||||
|
||||
// translateFields is a list of translateFields with a handy lookup method.
|
||||
type translateFields []translateField
|
||||
|
||||
// goName loops through a list of fields looking for one contaning the jsonName and
|
||||
// returning the goName. If not found, returns the empty string.
|
||||
// Note: not a map because at this size slices are faster even in tight loops.
|
||||
func (t translateFields) goName(jsonName string) string {
|
||||
for _, entry := range t {
|
||||
if entry.jsonName == jsonName {
|
||||
return entry.goName
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// jsonName loops through a list of fields looking for one contaning the goName and
|
||||
// returning the jsonName. If not found, returns the empty string.
|
||||
// Note: not a map because at this size slices are faster even in tight loops.
|
||||
func (t translateFields) jsonName(goName string) string {
|
||||
for _, entry := range t {
|
||||
if entry.goName == goName {
|
||||
return entry.jsonName
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var umarshalerType = reflect.TypeOf((*json.Unmarshaler)(nil)).Elem()
|
||||
|
||||
// findFields parses a struct and writes the field tags for lookup. It will return an error
|
||||
// if any field has a type of *struct or struct that does not implement json.Marshaler.
|
||||
func findFields(v reflect.Value) (translateFields, error) {
|
||||
if v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
}
|
||||
if v.Kind() != reflect.Struct {
|
||||
return nil, fmt.Errorf("findFields received a %s type, expected *struct or struct", v.Type().Name())
|
||||
}
|
||||
tfs := make([]translateField, 0, v.NumField())
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
tf := translateField{
|
||||
goName: v.Type().Field(i).Name,
|
||||
jsonName: parseTag(v.Type().Field(i).Tag.Get("json")),
|
||||
}
|
||||
switch tf.jsonName {
|
||||
case "", "-":
|
||||
tf.jsonName = tf.goName
|
||||
}
|
||||
tfs = append(tfs, tf)
|
||||
|
||||
f := v.Field(i)
|
||||
if f.Kind() == reflect.Ptr {
|
||||
f = f.Elem()
|
||||
}
|
||||
if f.Kind() == reflect.Struct {
|
||||
if f.Type().Implements(umarshalerType) {
|
||||
return nil, fmt.Errorf("struct type %q which has field %q which "+
|
||||
"doesn't implement json.Unmarshaler", v.Type().Name(), v.Type().Field(i).Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
return tfs, nil
|
||||
}
|
||||
|
||||
// parseTag just returns the first entry in the tag. tag is the string
|
||||
// returned by reflect.StructField.Tag().Get().
|
||||
func parseTag(tag string) string {
|
||||
if idx := strings.Index(tag, ","); idx != -1 {
|
||||
return tag[:idx]
|
||||
}
|
||||
return tag
|
||||
}
|
70
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json/types/time/time.go
generated
vendored
Normal file
70
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json/types/time/time.go
generated
vendored
Normal file
|
@ -0,0 +1,70 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
// Package time provides for custom types to translate time from JSON and other formats
|
||||
// into time.Time objects.
|
||||
package time
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Unix provides a type that can marshal and unmarshal a string representation
|
||||
// of the unix epoch into a time.Time object.
|
||||
type Unix struct {
|
||||
T time.Time
|
||||
}
|
||||
|
||||
// MarshalJSON implements encoding/json.MarshalJSON().
|
||||
func (u Unix) MarshalJSON() ([]byte, error) {
|
||||
if u.T.IsZero() {
|
||||
return []byte(""), nil
|
||||
}
|
||||
return []byte(fmt.Sprintf("%q", strconv.FormatInt(u.T.Unix(), 10))), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements encoding/json.UnmarshalJSON().
|
||||
func (u *Unix) UnmarshalJSON(b []byte) error {
|
||||
i, err := strconv.Atoi(strings.Trim(string(b), `"`))
|
||||
if err != nil {
|
||||
return fmt.Errorf("unix time(%s) could not be converted from string to int: %w", string(b), err)
|
||||
}
|
||||
u.T = time.Unix(int64(i), 0)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DurationTime provides a type that can marshal and unmarshal a string representation
|
||||
// of a duration from now into a time.Time object.
|
||||
// Note: I'm not sure this is the best way to do this. What happens is we get a field
|
||||
// called "expires_in" that represents the seconds from now that this expires. We
|
||||
// turn that into a time we call .ExpiresOn. But maybe we should be recording
|
||||
// when the token was received at .TokenRecieved and .ExpiresIn should remain as a duration.
|
||||
// Then we could have a method called ExpiresOn(). Honestly, the whole thing is
|
||||
// bad because the server doesn't return a concrete time. I think this is
|
||||
// cleaner, but its not great either.
|
||||
type DurationTime struct {
|
||||
T time.Time
|
||||
}
|
||||
|
||||
// MarshalJSON implements encoding/json.MarshalJSON().
|
||||
func (d DurationTime) MarshalJSON() ([]byte, error) {
|
||||
if d.T.IsZero() {
|
||||
return []byte(""), nil
|
||||
}
|
||||
|
||||
dt := time.Until(d.T)
|
||||
return []byte(fmt.Sprintf("%d", int64(dt*time.Second))), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements encoding/json.UnmarshalJSON().
|
||||
func (d *DurationTime) UnmarshalJSON(b []byte) error {
|
||||
i, err := strconv.Atoi(strings.Trim(string(b), `"`))
|
||||
if err != nil {
|
||||
return fmt.Errorf("unix time(%s) could not be converted from string to int: %w", string(b), err)
|
||||
}
|
||||
d.T = time.Now().Add(time.Duration(i) * time.Second)
|
||||
return nil
|
||||
}
|
177
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/local/server.go
generated
vendored
Normal file
177
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/local/server.go
generated
vendored
Normal file
|
@ -0,0 +1,177 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
// Package local contains a local HTTP server used with interactive authentication.
|
||||
package local
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var okPage = []byte(`
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Authentication Complete</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>Authentication complete. You can return to the application. Feel free to close this browser tab.</p>
|
||||
</body>
|
||||
</html>
|
||||
`)
|
||||
|
||||
const failPage = `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Authentication Failed</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>Authentication failed. You can return to the application. Feel free to close this browser tab.</p>
|
||||
<p>Error details: error %s error_description: %s</p>
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
|
||||
// Result is the result from the redirect.
|
||||
type Result struct {
|
||||
// Code is the code sent by the authority server.
|
||||
Code string
|
||||
// Err is set if there was an error.
|
||||
Err error
|
||||
}
|
||||
|
||||
// Server is an HTTP server.
|
||||
type Server struct {
|
||||
// Addr is the address the server is listening on.
|
||||
Addr string
|
||||
resultCh chan Result
|
||||
s *http.Server
|
||||
reqState string
|
||||
}
|
||||
|
||||
// New creates a local HTTP server and starts it.
|
||||
func New(reqState string, port int) (*Server, error) {
|
||||
var l net.Listener
|
||||
var err error
|
||||
var portStr string
|
||||
if port > 0 {
|
||||
// use port provided by caller
|
||||
l, err = net.Listen("tcp", fmt.Sprintf("localhost:%d", port))
|
||||
portStr = strconv.FormatInt(int64(port), 10)
|
||||
} else {
|
||||
// find a free port
|
||||
for i := 0; i < 10; i++ {
|
||||
l, err = net.Listen("tcp", "localhost:0")
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
addr := l.Addr().String()
|
||||
portStr = addr[strings.LastIndex(addr, ":")+1:]
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
serv := &Server{
|
||||
Addr: fmt.Sprintf("http://localhost:%s", portStr),
|
||||
s: &http.Server{Addr: "localhost:0", ReadHeaderTimeout: time.Second},
|
||||
reqState: reqState,
|
||||
resultCh: make(chan Result, 1),
|
||||
}
|
||||
serv.s.Handler = http.HandlerFunc(serv.handler)
|
||||
|
||||
if err := serv.start(l); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return serv, nil
|
||||
}
|
||||
|
||||
func (s *Server) start(l net.Listener) error {
|
||||
go func() {
|
||||
err := s.s.Serve(l)
|
||||
if err != nil {
|
||||
select {
|
||||
case s.resultCh <- Result{Err: err}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Result gets the result of the redirect operation. Once a single result is returned, the server
|
||||
// is shutdown. ctx deadline will be honored.
|
||||
func (s *Server) Result(ctx context.Context) Result {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return Result{Err: ctx.Err()}
|
||||
case r := <-s.resultCh:
|
||||
return r
|
||||
}
|
||||
}
|
||||
|
||||
// Shutdown shuts down the server.
|
||||
func (s *Server) Shutdown() {
|
||||
// Note: You might get clever and think you can do this in handler() as a defer, you can't.
|
||||
_ = s.s.Shutdown(context.Background())
|
||||
}
|
||||
|
||||
func (s *Server) putResult(r Result) {
|
||||
select {
|
||||
case s.resultCh <- r:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) handler(w http.ResponseWriter, r *http.Request) {
|
||||
q := r.URL.Query()
|
||||
|
||||
headerErr := q.Get("error")
|
||||
if headerErr != "" {
|
||||
desc := q.Get("error_description")
|
||||
// Note: It is a little weird we handle some errors by not going to the failPage. If they all should,
|
||||
// change this to s.error() and make s.error() write the failPage instead of an error code.
|
||||
_, _ = w.Write([]byte(fmt.Sprintf(failPage, headerErr, desc)))
|
||||
s.putResult(Result{Err: fmt.Errorf(desc)})
|
||||
return
|
||||
}
|
||||
|
||||
respState := q.Get("state")
|
||||
switch respState {
|
||||
case s.reqState:
|
||||
case "":
|
||||
s.error(w, http.StatusInternalServerError, "server didn't send OAuth state")
|
||||
return
|
||||
default:
|
||||
s.error(w, http.StatusInternalServerError, "mismatched OAuth state, req(%s), resp(%s)", s.reqState, respState)
|
||||
return
|
||||
}
|
||||
|
||||
code := q.Get("code")
|
||||
if code == "" {
|
||||
s.error(w, http.StatusInternalServerError, "authorization code missing in query string")
|
||||
return
|
||||
}
|
||||
|
||||
_, _ = w.Write(okPage)
|
||||
s.putResult(Result{Code: code})
|
||||
}
|
||||
|
||||
func (s *Server) error(w http.ResponseWriter, code int, str string, i ...interface{}) {
|
||||
err := fmt.Errorf(str, i...)
|
||||
http.Error(w, err.Error(), code)
|
||||
s.putResult(Result{Err: err})
|
||||
}
|
297
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/oauth.go
generated
vendored
Normal file
297
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/oauth.go
generated
vendored
Normal file
|
@ -0,0 +1,297 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
package oauth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/errors"
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/exported"
|
||||
internalTime "github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json/types/time"
|
||||
"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/oauth/ops/wstrust"
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/wstrust/defs"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// ResolveEndpointer contains the methods for resolving authority endpoints.
|
||||
type ResolveEndpointer interface {
|
||||
ResolveEndpoints(ctx context.Context, authorityInfo authority.Info, userPrincipalName string) (authority.Endpoints, error)
|
||||
}
|
||||
|
||||
// AccessTokens contains the methods for fetching tokens from different sources.
|
||||
type AccessTokens interface {
|
||||
DeviceCodeResult(ctx context.Context, authParameters authority.AuthParams) (accesstokens.DeviceCodeResult, error)
|
||||
FromUsernamePassword(ctx context.Context, authParameters authority.AuthParams) (accesstokens.TokenResponse, error)
|
||||
FromAuthCode(ctx context.Context, req accesstokens.AuthCodeRequest) (accesstokens.TokenResponse, error)
|
||||
FromRefreshToken(ctx context.Context, appType accesstokens.AppType, authParams authority.AuthParams, cc *accesstokens.Credential, refreshToken string) (accesstokens.TokenResponse, error)
|
||||
FromClientSecret(ctx context.Context, authParameters authority.AuthParams, clientSecret string) (accesstokens.TokenResponse, error)
|
||||
FromAssertion(ctx context.Context, authParameters authority.AuthParams, assertion string) (accesstokens.TokenResponse, error)
|
||||
FromUserAssertionClientSecret(ctx context.Context, authParameters authority.AuthParams, userAssertion string, clientSecret string) (accesstokens.TokenResponse, error)
|
||||
FromUserAssertionClientCertificate(ctx context.Context, authParameters authority.AuthParams, userAssertion string, assertion string) (accesstokens.TokenResponse, error)
|
||||
FromDeviceCodeResult(ctx context.Context, authParameters authority.AuthParams, deviceCodeResult accesstokens.DeviceCodeResult) (accesstokens.TokenResponse, error)
|
||||
FromSamlGrant(ctx context.Context, authParameters authority.AuthParams, samlGrant wstrust.SamlTokenInfo) (accesstokens.TokenResponse, error)
|
||||
}
|
||||
|
||||
// FetchAuthority will be implemented by authority.Authority.
|
||||
type FetchAuthority interface {
|
||||
UserRealm(context.Context, authority.AuthParams) (authority.UserRealm, error)
|
||||
AADInstanceDiscovery(context.Context, authority.Info) (authority.InstanceDiscoveryResponse, error)
|
||||
}
|
||||
|
||||
// FetchWSTrust contains the methods for interacting with WSTrust endpoints.
|
||||
type FetchWSTrust interface {
|
||||
Mex(ctx context.Context, federationMetadataURL string) (defs.MexDocument, error)
|
||||
SAMLTokenInfo(ctx context.Context, authParameters authority.AuthParams, cloudAudienceURN string, endpoint defs.Endpoint) (wstrust.SamlTokenInfo, error)
|
||||
}
|
||||
|
||||
// Client provides tokens for various types of token requests.
|
||||
type Client struct {
|
||||
Resolver ResolveEndpointer
|
||||
AccessTokens AccessTokens
|
||||
Authority FetchAuthority
|
||||
WSTrust FetchWSTrust
|
||||
}
|
||||
|
||||
// New is the constructor for Token.
|
||||
func New(httpClient ops.HTTPClient) *Client {
|
||||
r := ops.New(httpClient)
|
||||
return &Client{
|
||||
Resolver: newAuthorityEndpoint(r),
|
||||
AccessTokens: r.AccessTokens(),
|
||||
Authority: r.Authority(),
|
||||
WSTrust: r.WSTrust(),
|
||||
}
|
||||
}
|
||||
|
||||
// ResolveEndpoints gets the authorization and token endpoints and creates an AuthorityEndpoints instance.
|
||||
func (t *Client) ResolveEndpoints(ctx context.Context, authorityInfo authority.Info, userPrincipalName string) (authority.Endpoints, error) {
|
||||
return t.Resolver.ResolveEndpoints(ctx, authorityInfo, userPrincipalName)
|
||||
}
|
||||
|
||||
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 := t.resolveEndpoint(ctx, &req.AuthParams, ""); err != nil {
|
||||
return accesstokens.TokenResponse{}, err
|
||||
}
|
||||
|
||||
tResp, err := t.AccessTokens.FromAuthCode(ctx, req)
|
||||
if err != nil {
|
||||
return accesstokens.TokenResponse{}, fmt.Errorf("could not retrieve token from auth code: %w", err)
|
||||
}
|
||||
return tResp, nil
|
||||
}
|
||||
|
||||
// Credential acquires a token from the authority using a client credentials grant.
|
||||
func (t *Client) Credential(ctx context.Context, authParams authority.AuthParams, cred *accesstokens.Credential) (accesstokens.TokenResponse, error) {
|
||||
if cred.TokenProvider != nil {
|
||||
now := time.Now()
|
||||
scopes := make([]string, len(authParams.Scopes))
|
||||
copy(scopes, authParams.Scopes)
|
||||
params := exported.TokenProviderParameters{
|
||||
Claims: authParams.Claims,
|
||||
CorrelationID: uuid.New().String(),
|
||||
Scopes: scopes,
|
||||
TenantID: authParams.AuthorityInfo.Tenant,
|
||||
}
|
||||
tr, err := cred.TokenProvider(ctx, params)
|
||||
if err != nil {
|
||||
return accesstokens.TokenResponse{}, err
|
||||
}
|
||||
return accesstokens.TokenResponse{
|
||||
AccessToken: tr.AccessToken,
|
||||
ExpiresOn: internalTime.DurationTime{
|
||||
T: now.Add(time.Duration(tr.ExpiresInSeconds) * time.Second),
|
||||
},
|
||||
GrantedScopes: accesstokens.Scopes{Slice: authParams.Scopes},
|
||||
}, nil
|
||||
}
|
||||
|
||||
if err := t.resolveEndpoint(ctx, &authParams, ""); err != nil {
|
||||
return accesstokens.TokenResponse{}, err
|
||||
}
|
||||
|
||||
if cred.Secret != "" {
|
||||
return t.AccessTokens.FromClientSecret(ctx, authParams, cred.Secret)
|
||||
}
|
||||
jwt, err := cred.JWT(ctx, authParams)
|
||||
if err != nil {
|
||||
return accesstokens.TokenResponse{}, err
|
||||
}
|
||||
return t.AccessTokens.FromAssertion(ctx, authParams, jwt)
|
||||
}
|
||||
|
||||
// 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 := t.resolveEndpoint(ctx, &authParams, ""); err != nil {
|
||||
return accesstokens.TokenResponse{}, err
|
||||
}
|
||||
|
||||
if cred.Secret != "" {
|
||||
return t.AccessTokens.FromUserAssertionClientSecret(ctx, authParams, authParams.UserAssertion, cred.Secret)
|
||||
}
|
||||
jwt, err := cred.JWT(ctx, authParams)
|
||||
if err != nil {
|
||||
return accesstokens.TokenResponse{}, err
|
||||
}
|
||||
return t.AccessTokens.FromUserAssertionClientCertificate(ctx, authParams, authParams.UserAssertion, jwt)
|
||||
}
|
||||
|
||||
func (t *Client) Refresh(ctx context.Context, reqType accesstokens.AppType, authParams authority.AuthParams, cc *accesstokens.Credential, refreshToken accesstokens.RefreshToken) (accesstokens.TokenResponse, error) {
|
||||
if err := t.resolveEndpoint(ctx, &authParams, ""); err != nil {
|
||||
return accesstokens.TokenResponse{}, err
|
||||
}
|
||||
|
||||
return t.AccessTokens.FromRefreshToken(ctx, reqType, authParams, cc, refreshToken.Secret)
|
||||
}
|
||||
|
||||
// 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 authParams.AuthorityInfo.AuthorityType == authority.ADFS {
|
||||
if err := t.resolveEndpoint(ctx, &authParams, authParams.Username); err != nil {
|
||||
return accesstokens.TokenResponse{}, err
|
||||
}
|
||||
return t.AccessTokens.FromUsernamePassword(ctx, authParams)
|
||||
}
|
||||
if err := t.resolveEndpoint(ctx, &authParams, ""); err != nil {
|
||||
return accesstokens.TokenResponse{}, err
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
return t.AccessTokens.FromSamlGrant(ctx, authParams, saml)
|
||||
case authority.Managed:
|
||||
return t.AccessTokens.FromUsernamePassword(ctx, authParams)
|
||||
}
|
||||
return accesstokens.TokenResponse{}, errors.New("unknown account type")
|
||||
}
|
||||
|
||||
// DeviceCode is the result of a call to Token.DeviceCode().
|
||||
type DeviceCode struct {
|
||||
// Result is the device code result from the first call in the device code flow. This allows
|
||||
// the caller to retrieve the displayed code that is used to authorize on the second device.
|
||||
Result accesstokens.DeviceCodeResult
|
||||
authParams authority.AuthParams
|
||||
|
||||
accessTokens AccessTokens
|
||||
}
|
||||
|
||||
// Token returns a token AFTER the user uses the user code on the second device. This will block
|
||||
// until either: (1) the code is input by the user and the service releases a token, (2) the token
|
||||
// expires, (3) the Context passed to .DeviceCode() is cancelled or expires, (4) some other service
|
||||
// error occurs.
|
||||
func (d DeviceCode) Token(ctx context.Context) (accesstokens.TokenResponse, error) {
|
||||
if d.accessTokens == nil {
|
||||
return accesstokens.TokenResponse{}, fmt.Errorf("DeviceCode was either created outside its package or the creating method had an error. DeviceCode is not valid")
|
||||
}
|
||||
|
||||
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 {
|
||||
ctx, cancel = context.WithCancel(ctx)
|
||||
}
|
||||
defer cancel()
|
||||
|
||||
var interval = 50 * time.Millisecond
|
||||
timer := time.NewTimer(interval)
|
||||
defer timer.Stop()
|
||||
|
||||
for {
|
||||
timer.Reset(interval)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return accesstokens.TokenResponse{}, ctx.Err()
|
||||
case <-timer.C:
|
||||
interval += interval * 2
|
||||
if interval > 5*time.Second {
|
||||
interval = 5 * time.Second
|
||||
}
|
||||
}
|
||||
|
||||
token, err := d.accessTokens.FromDeviceCodeResult(ctx, d.authParams, d.Result)
|
||||
if err != nil && isWaitDeviceCodeErr(err) {
|
||||
continue
|
||||
}
|
||||
return token, err // This handles if it was a non-wait error or success
|
||||
}
|
||||
}
|
||||
|
||||
type deviceCodeError struct {
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
func isWaitDeviceCodeErr(err error) bool {
|
||||
var c errors.CallErr
|
||||
if !errors.As(err, &c) {
|
||||
return false
|
||||
}
|
||||
if c.Resp.StatusCode != 400 {
|
||||
return false
|
||||
}
|
||||
var dCErr deviceCodeError
|
||||
defer c.Resp.Body.Close()
|
||||
body, err := io.ReadAll(c.Resp.Body)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
err = json.Unmarshal(body, &dCErr)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if dCErr.Error == "authorization_pending" || dCErr.Error == "slow_down" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 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 := t.resolveEndpoint(ctx, &authParams, ""); err != nil {
|
||||
return DeviceCode{}, err
|
||||
}
|
||||
|
||||
dcr, err := t.AccessTokens.DeviceCodeResult(ctx, authParams)
|
||||
if err != nil {
|
||||
return DeviceCode{}, err
|
||||
}
|
||||
|
||||
return DeviceCode{Result: dcr, authParams: authParams, accessTokens: t.AccessTokens}, nil
|
||||
}
|
||||
|
||||
func (t *Client) resolveEndpoint(ctx context.Context, authParams *authority.AuthParams, userPrincipalName string) error {
|
||||
endpoints, err := t.Resolver.ResolveEndpoints(ctx, authParams.AuthorityInfo, userPrincipalName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to resolve an endpoint: %s", err)
|
||||
}
|
||||
authParams.Endpoints = endpoints
|
||||
return nil
|
||||
}
|
451
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/accesstokens/accesstokens.go
generated
vendored
Normal file
451
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/accesstokens/accesstokens.go
generated
vendored
Normal file
|
@ -0,0 +1,451 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
/*
|
||||
Package accesstokens exposes a REST client for querying backend systems to get various types of
|
||||
access tokens (oauth) for use in authentication.
|
||||
|
||||
These calls are of type "application/x-www-form-urlencoded". This means we use url.Values to
|
||||
represent arguments and then encode them into the POST body message. We receive JSON in
|
||||
return for the requests. The request definition is defined in https://tools.ietf.org/html/rfc7521#section-4.2 .
|
||||
*/
|
||||
package accesstokens
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
|
||||
/* #nosec */
|
||||
"crypto/sha1"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/exported"
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/authority"
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/internal/grant"
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/wstrust"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const (
|
||||
grantType = "grant_type"
|
||||
deviceCode = "device_code"
|
||||
clientID = "client_id"
|
||||
clientInfo = "client_info"
|
||||
clientInfoVal = "1"
|
||||
username = "username"
|
||||
password = "password"
|
||||
)
|
||||
|
||||
//go:generate stringer -type=AppType
|
||||
|
||||
// AppType is whether the authorization code flow is for a public or confidential client.
|
||||
type AppType int8
|
||||
|
||||
const (
|
||||
// ATUnknown is the zero value when the type hasn't been set.
|
||||
ATUnknown AppType = iota
|
||||
// ATPublic indicates this if for the Public.Client.
|
||||
ATPublic
|
||||
// ATConfidential indicates this if for the Confidential.Client.
|
||||
ATConfidential
|
||||
)
|
||||
|
||||
type urlFormCaller interface {
|
||||
URLFormCall(ctx context.Context, endpoint string, qv url.Values, resp interface{}) error
|
||||
}
|
||||
|
||||
// DeviceCodeResponse represents the HTTP response received from the device code endpoint
|
||||
type DeviceCodeResponse struct {
|
||||
authority.OAuthResponseBase
|
||||
|
||||
UserCode string `json:"user_code"`
|
||||
DeviceCode string `json:"device_code"`
|
||||
VerificationURL string `json:"verification_url"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
Interval int `json:"interval"`
|
||||
Message string `json:"message"`
|
||||
|
||||
AdditionalFields map[string]interface{}
|
||||
}
|
||||
|
||||
// Convert converts the DeviceCodeResponse to a DeviceCodeResult
|
||||
func (dcr DeviceCodeResponse) Convert(clientID string, scopes []string) DeviceCodeResult {
|
||||
expiresOn := time.Now().UTC().Add(time.Duration(dcr.ExpiresIn) * time.Second)
|
||||
return NewDeviceCodeResult(dcr.UserCode, dcr.DeviceCode, dcr.VerificationURL, expiresOn, dcr.Interval, dcr.Message, clientID, scopes)
|
||||
}
|
||||
|
||||
// Credential represents the credential used in confidential client flows. This can be either
|
||||
// a Secret or Cert/Key.
|
||||
type Credential struct {
|
||||
// Secret contains the credential secret if we are doing auth by secret.
|
||||
Secret string
|
||||
|
||||
// Cert is the public certificate, if we're authenticating by certificate.
|
||||
Cert *x509.Certificate
|
||||
// Key is the private key for signing, if we're authenticating by certificate.
|
||||
Key crypto.PrivateKey
|
||||
// X5c is the JWT assertion's x5c header value, required for SN/I authentication.
|
||||
X5c []string
|
||||
|
||||
// AssertionCallback is a function provided by the application, if we're authenticating by assertion.
|
||||
AssertionCallback func(context.Context, exported.AssertionRequestOptions) (string, error)
|
||||
|
||||
// TokenProvider is a function provided by the application that implements custom authentication
|
||||
// logic for a confidential client
|
||||
TokenProvider func(context.Context, exported.TokenProviderParameters) (exported.TokenProviderResult, error)
|
||||
}
|
||||
|
||||
// JWT gets the jwt assertion when the credential is not using a secret.
|
||||
func (c *Credential) JWT(ctx context.Context, authParams authority.AuthParams) (string, error) {
|
||||
if c.AssertionCallback != nil {
|
||||
options := exported.AssertionRequestOptions{
|
||||
ClientID: authParams.ClientID,
|
||||
TokenEndpoint: authParams.Endpoints.TokenEndpoint,
|
||||
}
|
||||
return c.AssertionCallback(ctx, options)
|
||||
}
|
||||
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.MapClaims{
|
||||
"aud": authParams.Endpoints.TokenEndpoint,
|
||||
"exp": json.Number(strconv.FormatInt(time.Now().Add(10*time.Minute).Unix(), 10)),
|
||||
"iss": authParams.ClientID,
|
||||
"jti": uuid.New().String(),
|
||||
"nbf": json.Number(strconv.FormatInt(time.Now().Unix(), 10)),
|
||||
"sub": authParams.ClientID,
|
||||
})
|
||||
token.Header = map[string]interface{}{
|
||||
"alg": "RS256",
|
||||
"typ": "JWT",
|
||||
"x5t": base64.StdEncoding.EncodeToString(thumbprint(c.Cert)),
|
||||
}
|
||||
|
||||
if authParams.SendX5C {
|
||||
token.Header["x5c"] = c.X5c
|
||||
}
|
||||
|
||||
assertion, err := token.SignedString(c.Key)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unable to sign a JWT token using private key: %w", err)
|
||||
}
|
||||
return assertion, nil
|
||||
}
|
||||
|
||||
// thumbprint runs the asn1.Der bytes through sha1 for use in the x5t parameter of JWT.
|
||||
// https://tools.ietf.org/html/rfc7517#section-4.8
|
||||
func thumbprint(cert *x509.Certificate) []byte {
|
||||
/* #nosec */
|
||||
a := sha1.Sum(cert.Raw)
|
||||
return a[:]
|
||||
}
|
||||
|
||||
// Client represents the REST calls to get tokens from token generator backends.
|
||||
type Client struct {
|
||||
// Comm provides the HTTP transport client.
|
||||
Comm urlFormCaller
|
||||
|
||||
testing bool
|
||||
}
|
||||
|
||||
// FromUsernamePassword uses a username and password to get an access token.
|
||||
func (c Client) FromUsernamePassword(ctx context.Context, authParameters authority.AuthParams) (TokenResponse, error) {
|
||||
qv := url.Values{}
|
||||
if err := addClaims(qv, authParameters); err != nil {
|
||||
return TokenResponse{}, err
|
||||
}
|
||||
qv.Set(grantType, grant.Password)
|
||||
qv.Set(username, authParameters.Username)
|
||||
qv.Set(password, authParameters.Password)
|
||||
qv.Set(clientID, authParameters.ClientID)
|
||||
qv.Set(clientInfo, clientInfoVal)
|
||||
addScopeQueryParam(qv, authParameters)
|
||||
|
||||
return c.doTokenResp(ctx, authParameters, qv)
|
||||
}
|
||||
|
||||
// AuthCodeRequest stores the values required to request a token from the authority using an authorization code
|
||||
type AuthCodeRequest struct {
|
||||
AuthParams authority.AuthParams
|
||||
Code string
|
||||
CodeChallenge string
|
||||
Credential *Credential
|
||||
AppType AppType
|
||||
}
|
||||
|
||||
// NewCodeChallengeRequest returns an AuthCodeRequest that uses a code challenge..
|
||||
func NewCodeChallengeRequest(params authority.AuthParams, appType AppType, cc *Credential, code, challenge string) (AuthCodeRequest, error) {
|
||||
if appType == ATUnknown {
|
||||
return AuthCodeRequest{}, fmt.Errorf("bug: NewCodeChallengeRequest() called with AppType == ATUnknown")
|
||||
}
|
||||
return AuthCodeRequest{
|
||||
AuthParams: params,
|
||||
AppType: appType,
|
||||
Code: code,
|
||||
CodeChallenge: challenge,
|
||||
Credential: cc,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// FromAuthCode uses an authorization code to retrieve an access token.
|
||||
func (c Client) FromAuthCode(ctx context.Context, req AuthCodeRequest) (TokenResponse, error) {
|
||||
var qv url.Values
|
||||
|
||||
switch req.AppType {
|
||||
case ATUnknown:
|
||||
return TokenResponse{}, fmt.Errorf("bug: Token.AuthCode() received request with AppType == ATUnknown")
|
||||
case ATConfidential:
|
||||
var err error
|
||||
if req.Credential == nil {
|
||||
return TokenResponse{}, fmt.Errorf("AuthCodeRequest had nil Credential for Confidential app")
|
||||
}
|
||||
qv, err = prepURLVals(ctx, req.Credential, req.AuthParams)
|
||||
if err != nil {
|
||||
return TokenResponse{}, err
|
||||
}
|
||||
case ATPublic:
|
||||
qv = url.Values{}
|
||||
default:
|
||||
return TokenResponse{}, fmt.Errorf("bug: Token.AuthCode() received request with AppType == %v, which we do not recongnize", req.AppType)
|
||||
}
|
||||
|
||||
qv.Set(grantType, grant.AuthCode)
|
||||
qv.Set("code", req.Code)
|
||||
qv.Set("code_verifier", req.CodeChallenge)
|
||||
qv.Set("redirect_uri", req.AuthParams.Redirecturi)
|
||||
qv.Set(clientID, req.AuthParams.ClientID)
|
||||
qv.Set(clientInfo, clientInfoVal)
|
||||
addScopeQueryParam(qv, req.AuthParams)
|
||||
if err := addClaims(qv, req.AuthParams); err != nil {
|
||||
return TokenResponse{}, err
|
||||
}
|
||||
|
||||
return c.doTokenResp(ctx, req.AuthParams, qv)
|
||||
}
|
||||
|
||||
// FromRefreshToken uses a refresh token (for refreshing credentials) to get a new access token.
|
||||
func (c Client) FromRefreshToken(ctx context.Context, appType AppType, authParams authority.AuthParams, cc *Credential, refreshToken string) (TokenResponse, error) {
|
||||
qv := url.Values{}
|
||||
if appType == ATConfidential {
|
||||
var err error
|
||||
qv, err = prepURLVals(ctx, cc, authParams)
|
||||
if err != nil {
|
||||
return TokenResponse{}, err
|
||||
}
|
||||
}
|
||||
if err := addClaims(qv, authParams); err != nil {
|
||||
return TokenResponse{}, err
|
||||
}
|
||||
qv.Set(grantType, grant.RefreshToken)
|
||||
qv.Set(clientID, authParams.ClientID)
|
||||
qv.Set(clientInfo, clientInfoVal)
|
||||
qv.Set("refresh_token", refreshToken)
|
||||
addScopeQueryParam(qv, authParams)
|
||||
|
||||
return c.doTokenResp(ctx, authParams, qv)
|
||||
}
|
||||
|
||||
// FromClientSecret uses a client's secret (aka password) to get a new token.
|
||||
func (c Client) FromClientSecret(ctx context.Context, authParameters authority.AuthParams, clientSecret string) (TokenResponse, error) {
|
||||
qv := url.Values{}
|
||||
if err := addClaims(qv, authParameters); err != nil {
|
||||
return TokenResponse{}, err
|
||||
}
|
||||
qv.Set(grantType, grant.ClientCredential)
|
||||
qv.Set("client_secret", clientSecret)
|
||||
qv.Set(clientID, authParameters.ClientID)
|
||||
addScopeQueryParam(qv, authParameters)
|
||||
|
||||
token, err := c.doTokenResp(ctx, authParameters, qv)
|
||||
if err != nil {
|
||||
return token, fmt.Errorf("FromClientSecret(): %w", err)
|
||||
}
|
||||
return token, nil
|
||||
}
|
||||
|
||||
func (c Client) FromAssertion(ctx context.Context, authParameters authority.AuthParams, assertion string) (TokenResponse, error) {
|
||||
qv := url.Values{}
|
||||
if err := addClaims(qv, authParameters); err != nil {
|
||||
return TokenResponse{}, err
|
||||
}
|
||||
qv.Set(grantType, grant.ClientCredential)
|
||||
qv.Set("client_assertion_type", grant.ClientAssertion)
|
||||
qv.Set("client_assertion", assertion)
|
||||
qv.Set(clientID, authParameters.ClientID)
|
||||
qv.Set(clientInfo, clientInfoVal)
|
||||
addScopeQueryParam(qv, authParameters)
|
||||
|
||||
token, err := c.doTokenResp(ctx, authParameters, qv)
|
||||
if err != nil {
|
||||
return token, fmt.Errorf("FromAssertion(): %w", err)
|
||||
}
|
||||
return token, nil
|
||||
}
|
||||
|
||||
func (c Client) FromUserAssertionClientSecret(ctx context.Context, authParameters authority.AuthParams, userAssertion string, clientSecret string) (TokenResponse, error) {
|
||||
qv := url.Values{}
|
||||
if err := addClaims(qv, authParameters); err != nil {
|
||||
return TokenResponse{}, err
|
||||
}
|
||||
qv.Set(grantType, grant.JWT)
|
||||
qv.Set(clientID, authParameters.ClientID)
|
||||
qv.Set("client_secret", clientSecret)
|
||||
qv.Set("assertion", userAssertion)
|
||||
qv.Set(clientInfo, clientInfoVal)
|
||||
qv.Set("requested_token_use", "on_behalf_of")
|
||||
addScopeQueryParam(qv, authParameters)
|
||||
|
||||
return c.doTokenResp(ctx, authParameters, qv)
|
||||
}
|
||||
|
||||
func (c Client) FromUserAssertionClientCertificate(ctx context.Context, authParameters authority.AuthParams, userAssertion string, assertion string) (TokenResponse, error) {
|
||||
qv := url.Values{}
|
||||
if err := addClaims(qv, authParameters); err != nil {
|
||||
return TokenResponse{}, err
|
||||
}
|
||||
qv.Set(grantType, grant.JWT)
|
||||
qv.Set("client_assertion_type", grant.ClientAssertion)
|
||||
qv.Set("client_assertion", assertion)
|
||||
qv.Set(clientID, authParameters.ClientID)
|
||||
qv.Set("assertion", userAssertion)
|
||||
qv.Set(clientInfo, clientInfoVal)
|
||||
qv.Set("requested_token_use", "on_behalf_of")
|
||||
addScopeQueryParam(qv, authParameters)
|
||||
|
||||
return c.doTokenResp(ctx, authParameters, qv)
|
||||
}
|
||||
|
||||
func (c Client) DeviceCodeResult(ctx context.Context, authParameters authority.AuthParams) (DeviceCodeResult, error) {
|
||||
qv := url.Values{}
|
||||
if err := addClaims(qv, authParameters); err != nil {
|
||||
return DeviceCodeResult{}, err
|
||||
}
|
||||
qv.Set(clientID, authParameters.ClientID)
|
||||
addScopeQueryParam(qv, authParameters)
|
||||
|
||||
endpoint := strings.Replace(authParameters.Endpoints.TokenEndpoint, "token", "devicecode", -1)
|
||||
|
||||
resp := DeviceCodeResponse{}
|
||||
err := c.Comm.URLFormCall(ctx, endpoint, qv, &resp)
|
||||
if err != nil {
|
||||
return DeviceCodeResult{}, err
|
||||
}
|
||||
|
||||
return resp.Convert(authParameters.ClientID, authParameters.Scopes), nil
|
||||
}
|
||||
|
||||
func (c Client) FromDeviceCodeResult(ctx context.Context, authParameters authority.AuthParams, deviceCodeResult DeviceCodeResult) (TokenResponse, error) {
|
||||
qv := url.Values{}
|
||||
if err := addClaims(qv, authParameters); err != nil {
|
||||
return TokenResponse{}, err
|
||||
}
|
||||
qv.Set(grantType, grant.DeviceCode)
|
||||
qv.Set(deviceCode, deviceCodeResult.DeviceCode)
|
||||
qv.Set(clientID, authParameters.ClientID)
|
||||
qv.Set(clientInfo, clientInfoVal)
|
||||
addScopeQueryParam(qv, authParameters)
|
||||
|
||||
return c.doTokenResp(ctx, authParameters, qv)
|
||||
}
|
||||
|
||||
func (c Client) FromSamlGrant(ctx context.Context, authParameters authority.AuthParams, samlGrant wstrust.SamlTokenInfo) (TokenResponse, error) {
|
||||
qv := url.Values{}
|
||||
if err := addClaims(qv, authParameters); err != nil {
|
||||
return TokenResponse{}, err
|
||||
}
|
||||
qv.Set(username, authParameters.Username)
|
||||
qv.Set(password, authParameters.Password)
|
||||
qv.Set(clientID, authParameters.ClientID)
|
||||
qv.Set(clientInfo, clientInfoVal)
|
||||
qv.Set("assertion", base64.StdEncoding.WithPadding(base64.StdPadding).EncodeToString([]byte(samlGrant.Assertion)))
|
||||
addScopeQueryParam(qv, authParameters)
|
||||
|
||||
switch samlGrant.AssertionType {
|
||||
case grant.SAMLV1:
|
||||
qv.Set(grantType, grant.SAMLV1)
|
||||
case grant.SAMLV2:
|
||||
qv.Set(grantType, grant.SAMLV2)
|
||||
default:
|
||||
return TokenResponse{}, fmt.Errorf("GetAccessTokenFromSamlGrant returned unknown SAML assertion type: %q", samlGrant.AssertionType)
|
||||
}
|
||||
|
||||
return c.doTokenResp(ctx, authParameters, qv)
|
||||
}
|
||||
|
||||
func (c Client) doTokenResp(ctx context.Context, authParams authority.AuthParams, qv url.Values) (TokenResponse, error) {
|
||||
resp := TokenResponse{}
|
||||
err := c.Comm.URLFormCall(ctx, authParams.Endpoints.TokenEndpoint, qv, &resp)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
resp.ComputeScope(authParams)
|
||||
if c.testing {
|
||||
return resp, nil
|
||||
}
|
||||
return resp, resp.Validate()
|
||||
}
|
||||
|
||||
// prepURLVals returns an url.Values that sets various key/values if we are doing secrets
|
||||
// or JWT assertions.
|
||||
func prepURLVals(ctx context.Context, cc *Credential, authParams authority.AuthParams) (url.Values, error) {
|
||||
params := url.Values{}
|
||||
if cc.Secret != "" {
|
||||
params.Set("client_secret", cc.Secret)
|
||||
return params, nil
|
||||
}
|
||||
|
||||
jwt, err := cc.JWT(ctx, authParams)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
params.Set("client_assertion", jwt)
|
||||
params.Set("client_assertion_type", grant.ClientAssertion)
|
||||
return params, nil
|
||||
}
|
||||
|
||||
// openid required to get an id token
|
||||
// offline_access required to get a refresh token
|
||||
// profile required to get the client_info field back
|
||||
var detectDefaultScopes = map[string]bool{
|
||||
"openid": true,
|
||||
"offline_access": true,
|
||||
"profile": true,
|
||||
}
|
||||
|
||||
var defaultScopes = []string{"openid", "offline_access", "profile"}
|
||||
|
||||
func AppendDefaultScopes(authParameters authority.AuthParams) []string {
|
||||
scopes := make([]string, 0, len(authParameters.Scopes)+len(defaultScopes))
|
||||
for _, scope := range authParameters.Scopes {
|
||||
s := strings.TrimSpace(scope)
|
||||
if s == "" {
|
||||
continue
|
||||
}
|
||||
if detectDefaultScopes[scope] {
|
||||
continue
|
||||
}
|
||||
scopes = append(scopes, scope)
|
||||
}
|
||||
scopes = append(scopes, defaultScopes...)
|
||||
return scopes
|
||||
}
|
||||
|
||||
// addClaims adds client capabilities and claims from AuthParams to the given url.Values
|
||||
func addClaims(v url.Values, ap authority.AuthParams) error {
|
||||
claims, err := ap.MergeCapabilitiesAndClaims()
|
||||
if err == nil && claims != "" {
|
||||
v.Set("claims", claims)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func addScopeQueryParam(queryParams url.Values, authParameters authority.AuthParams) {
|
||||
scopes := AppendDefaultScopes(authParameters)
|
||||
queryParams.Set("scope", strings.Join(scopes, " "))
|
||||
}
|
25
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/accesstokens/apptype_string.go
generated
vendored
Normal file
25
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/accesstokens/apptype_string.go
generated
vendored
Normal file
|
@ -0,0 +1,25 @@
|
|||
// Code generated by "stringer -type=AppType"; DO NOT EDIT.
|
||||
|
||||
package accesstokens
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[ATUnknown-0]
|
||||
_ = x[ATPublic-1]
|
||||
_ = x[ATConfidential-2]
|
||||
}
|
||||
|
||||
const _AppType_name = "ATUnknownATPublicATConfidential"
|
||||
|
||||
var _AppType_index = [...]uint8{0, 9, 17, 31}
|
||||
|
||||
func (i AppType) String() string {
|
||||
if i < 0 || i >= AppType(len(_AppType_index)-1) {
|
||||
return "AppType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _AppType_name[_AppType_index[i]:_AppType_index[i+1]]
|
||||
}
|
335
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/accesstokens/tokens.go
generated
vendored
Normal file
335
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/accesstokens/tokens.go
generated
vendored
Normal file
|
@ -0,0 +1,335 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
package accesstokens
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
internalTime "github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json/types/time"
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/authority"
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/shared"
|
||||
)
|
||||
|
||||
// IDToken consists of all the information used to validate a user.
|
||||
// https://docs.microsoft.com/azure/active-directory/develop/id-tokens .
|
||||
type IDToken struct {
|
||||
PreferredUsername string `json:"preferred_username,omitempty"`
|
||||
GivenName string `json:"given_name,omitempty"`
|
||||
FamilyName string `json:"family_name,omitempty"`
|
||||
MiddleName string `json:"middle_name,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Oid string `json:"oid,omitempty"`
|
||||
TenantID string `json:"tid,omitempty"`
|
||||
Subject string `json:"sub,omitempty"`
|
||||
UPN string `json:"upn,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
AlternativeID string `json:"alternative_id,omitempty"`
|
||||
Issuer string `json:"iss,omitempty"`
|
||||
Audience string `json:"aud,omitempty"`
|
||||
ExpirationTime int64 `json:"exp,omitempty"`
|
||||
IssuedAt int64 `json:"iat,omitempty"`
|
||||
NotBefore int64 `json:"nbf,omitempty"`
|
||||
RawToken string
|
||||
|
||||
AdditionalFields map[string]interface{}
|
||||
}
|
||||
|
||||
var null = []byte("null")
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaler.
|
||||
func (i *IDToken) UnmarshalJSON(b []byte) error {
|
||||
if bytes.Equal(null, b) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Because we have a custom unmarshaler, you
|
||||
// cannot directly call json.Unmarshal here. If you do, it will call this function
|
||||
// recursively until reach our recursion limit. We have to create a new type
|
||||
// that doesn't have this method in order to use json.Unmarshal.
|
||||
type idToken2 IDToken
|
||||
|
||||
jwt := strings.Trim(string(b), `"`)
|
||||
jwtArr := strings.Split(jwt, ".")
|
||||
if len(jwtArr) < 2 {
|
||||
return errors.New("IDToken returned from server is invalid")
|
||||
}
|
||||
|
||||
jwtPart := jwtArr[1]
|
||||
jwtDecoded, err := decodeJWT(jwtPart)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to unmarshal IDToken, problem decoding JWT: %w", err)
|
||||
}
|
||||
|
||||
token := idToken2{}
|
||||
err = json.Unmarshal(jwtDecoded, &token)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to unmarshal IDToken: %w", err)
|
||||
}
|
||||
token.RawToken = jwt
|
||||
|
||||
*i = IDToken(token)
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsZero indicates if the IDToken is the zero value.
|
||||
func (i IDToken) IsZero() bool {
|
||||
v := reflect.ValueOf(i)
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
field := v.Field(i)
|
||||
if !field.IsZero() {
|
||||
switch field.Kind() {
|
||||
case reflect.Map, reflect.Slice:
|
||||
if field.Len() == 0 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// LocalAccountID extracts an account's local account ID from an ID token.
|
||||
func (i IDToken) LocalAccountID() string {
|
||||
if i.Oid != "" {
|
||||
return i.Oid
|
||||
}
|
||||
return i.Subject
|
||||
}
|
||||
|
||||
// jwtDecoder is provided to allow tests to provide their own.
|
||||
var jwtDecoder = decodeJWT
|
||||
|
||||
// ClientInfo is used to create a Home Account ID for an account.
|
||||
type ClientInfo struct {
|
||||
UID string `json:"uid"`
|
||||
UTID string `json:"utid"`
|
||||
|
||||
AdditionalFields map[string]interface{}
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaler.s
|
||||
func (c *ClientInfo) UnmarshalJSON(b []byte) error {
|
||||
s := strings.Trim(string(b), `"`)
|
||||
// Client info may be empty in some flows, e.g. certificate exchange.
|
||||
if len(s) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Because we have a custom unmarshaler, you
|
||||
// cannot directly call json.Unmarshal here. If you do, it will call this function
|
||||
// recursively until reach our recursion limit. We have to create a new type
|
||||
// that doesn't have this method in order to use json.Unmarshal.
|
||||
type clientInfo2 ClientInfo
|
||||
|
||||
raw, err := jwtDecoder(s)
|
||||
if err != nil {
|
||||
return fmt.Errorf("TokenResponse client_info field had JWT decode error: %w", err)
|
||||
}
|
||||
|
||||
var c2 clientInfo2
|
||||
|
||||
err = json.Unmarshal(raw, &c2)
|
||||
if err != nil {
|
||||
return fmt.Errorf("was unable to unmarshal decoded JWT in TokenRespone to ClientInfo: %w", err)
|
||||
}
|
||||
|
||||
*c = ClientInfo(c2)
|
||||
return nil
|
||||
}
|
||||
|
||||
// HomeAccountID creates the home account ID.
|
||||
func (c ClientInfo) HomeAccountID() string {
|
||||
if c.UID == "" {
|
||||
return ""
|
||||
} else if c.UTID == "" {
|
||||
return fmt.Sprintf("%s.%s", c.UID, c.UID)
|
||||
} else {
|
||||
return fmt.Sprintf("%s.%s", c.UID, c.UTID)
|
||||
}
|
||||
}
|
||||
|
||||
// Scopes represents scopes in a TokenResponse.
|
||||
type Scopes struct {
|
||||
Slice []string
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshal.
|
||||
func (s *Scopes) UnmarshalJSON(b []byte) error {
|
||||
str := strings.Trim(string(b), `"`)
|
||||
if len(str) == 0 {
|
||||
return nil
|
||||
}
|
||||
sl := strings.Split(str, " ")
|
||||
s.Slice = sl
|
||||
return nil
|
||||
}
|
||||
|
||||
// TokenResponse is the information that is returned from a token endpoint during a token acquisition flow.
|
||||
type TokenResponse struct {
|
||||
authority.OAuthResponseBase
|
||||
|
||||
AccessToken string `json:"access_token"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
|
||||
FamilyID string `json:"foci"`
|
||||
IDToken IDToken `json:"id_token"`
|
||||
ClientInfo ClientInfo `json:"client_info"`
|
||||
ExpiresOn internalTime.DurationTime `json:"expires_in"`
|
||||
ExtExpiresOn internalTime.DurationTime `json:"ext_expires_in"`
|
||||
GrantedScopes Scopes `json:"scope"`
|
||||
DeclinedScopes []string // This is derived
|
||||
|
||||
AdditionalFields map[string]interface{}
|
||||
|
||||
scopesComputed bool
|
||||
}
|
||||
|
||||
// ComputeScope computes the final scopes based on what was granted by the server and
|
||||
// what our AuthParams were from the authority server. Per OAuth spec, if no scopes are returned, the response should be treated as if all scopes were granted
|
||||
// This behavior can be observed in client assertion flows, but can happen at any time, this check ensures we treat
|
||||
// those special responses properly Link to spec: https://tools.ietf.org/html/rfc6749#section-3.3
|
||||
func (tr *TokenResponse) ComputeScope(authParams authority.AuthParams) {
|
||||
if len(tr.GrantedScopes.Slice) == 0 {
|
||||
tr.GrantedScopes = Scopes{Slice: authParams.Scopes}
|
||||
} else {
|
||||
tr.DeclinedScopes = findDeclinedScopes(authParams.Scopes, tr.GrantedScopes.Slice)
|
||||
}
|
||||
tr.scopesComputed = true
|
||||
}
|
||||
|
||||
// Validate validates the TokenResponse has basic valid values. It must be called
|
||||
// after ComputeScopes() is called.
|
||||
func (tr *TokenResponse) Validate() error {
|
||||
if tr.Error != "" {
|
||||
return fmt.Errorf("%s: %s", tr.Error, tr.ErrorDescription)
|
||||
}
|
||||
|
||||
if tr.AccessToken == "" {
|
||||
return errors.New("response is missing access_token")
|
||||
}
|
||||
|
||||
if !tr.scopesComputed {
|
||||
return fmt.Errorf("TokenResponse hasn't had ScopesComputed() called")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tr *TokenResponse) CacheKey(authParams authority.AuthParams) string {
|
||||
if authParams.AuthorizationType == authority.ATOnBehalfOf {
|
||||
return authParams.AssertionHash()
|
||||
}
|
||||
if authParams.AuthorizationType == authority.ATClientCredentials {
|
||||
return authParams.AppKey()
|
||||
}
|
||||
if authParams.IsConfidentialClient || authParams.AuthorizationType == authority.ATRefreshToken {
|
||||
return tr.ClientInfo.HomeAccountID()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func findDeclinedScopes(requestedScopes []string, grantedScopes []string) []string {
|
||||
declined := []string{}
|
||||
grantedMap := map[string]bool{}
|
||||
for _, s := range grantedScopes {
|
||||
grantedMap[strings.ToLower(s)] = true
|
||||
}
|
||||
// Comparing the requested scopes with the granted scopes to see if there are any scopes that have been declined.
|
||||
for _, r := range requestedScopes {
|
||||
if !grantedMap[strings.ToLower(r)] {
|
||||
declined = append(declined, r)
|
||||
}
|
||||
}
|
||||
return declined
|
||||
}
|
||||
|
||||
// decodeJWT decodes a JWT and converts it to a byte array representing a JSON object
|
||||
// JWT has headers and payload base64url encoded without padding
|
||||
// https://tools.ietf.org/html/rfc7519#section-3 and
|
||||
// https://tools.ietf.org/html/rfc7515#section-2
|
||||
func decodeJWT(data string) ([]byte, error) {
|
||||
// https://tools.ietf.org/html/rfc7515#appendix-C
|
||||
return base64.RawURLEncoding.DecodeString(data)
|
||||
}
|
||||
|
||||
// RefreshToken is the JSON representation of a MSAL refresh token for encoding to storage.
|
||||
type RefreshToken struct {
|
||||
HomeAccountID string `json:"home_account_id,omitempty"`
|
||||
Environment string `json:"environment,omitempty"`
|
||||
CredentialType string `json:"credential_type,omitempty"`
|
||||
ClientID string `json:"client_id,omitempty"`
|
||||
FamilyID string `json:"family_id,omitempty"`
|
||||
Secret string `json:"secret,omitempty"`
|
||||
Realm string `json:"realm,omitempty"`
|
||||
Target string `json:"target,omitempty"`
|
||||
UserAssertionHash string `json:"user_assertion_hash,omitempty"`
|
||||
|
||||
AdditionalFields map[string]interface{}
|
||||
}
|
||||
|
||||
// NewRefreshToken is the constructor for RefreshToken.
|
||||
func NewRefreshToken(homeID, env, clientID, refreshToken, familyID string) RefreshToken {
|
||||
return RefreshToken{
|
||||
HomeAccountID: homeID,
|
||||
Environment: env,
|
||||
CredentialType: "RefreshToken",
|
||||
ClientID: clientID,
|
||||
FamilyID: familyID,
|
||||
Secret: refreshToken,
|
||||
}
|
||||
}
|
||||
|
||||
// Key outputs the key that can be used to uniquely look up this entry in a map.
|
||||
func (rt RefreshToken) Key() string {
|
||||
var fourth = rt.FamilyID
|
||||
if fourth == "" {
|
||||
fourth = rt.ClientID
|
||||
}
|
||||
|
||||
return strings.Join(
|
||||
[]string{rt.HomeAccountID, rt.Environment, rt.CredentialType, fourth},
|
||||
shared.CacheKeySeparator,
|
||||
)
|
||||
}
|
||||
|
||||
func (rt RefreshToken) GetSecret() string {
|
||||
return rt.Secret
|
||||
}
|
||||
|
||||
// DeviceCodeResult stores the response from the STS device code endpoint.
|
||||
type DeviceCodeResult struct {
|
||||
// UserCode is the code the user needs to provide when authentication at the verification URI.
|
||||
UserCode string
|
||||
// DeviceCode is the code used in the access token request.
|
||||
DeviceCode string
|
||||
// VerificationURL is the the URL where user can authenticate.
|
||||
VerificationURL string
|
||||
// ExpiresOn is the expiration time of device code in seconds.
|
||||
ExpiresOn time.Time
|
||||
// Interval is the interval at which the STS should be polled at.
|
||||
Interval int
|
||||
// Message is the message which should be displayed to the user.
|
||||
Message string
|
||||
// ClientID is the UUID issued by the authorization server for your application.
|
||||
ClientID string
|
||||
// Scopes is the OpenID scopes used to request access a protected API.
|
||||
Scopes []string
|
||||
}
|
||||
|
||||
// NewDeviceCodeResult creates a DeviceCodeResult instance.
|
||||
func NewDeviceCodeResult(userCode, deviceCode, verificationURL string, expiresOn time.Time, interval int, message, clientID string, scopes []string) DeviceCodeResult {
|
||||
return DeviceCodeResult{userCode, deviceCode, verificationURL, expiresOn, interval, message, clientID, scopes}
|
||||
}
|
||||
|
||||
func (dcr DeviceCodeResult) String() string {
|
||||
return fmt.Sprintf("UserCode: (%v)\nDeviceCode: (%v)\nURL: (%v)\nMessage: (%v)\n", dcr.UserCode, dcr.DeviceCode, dcr.VerificationURL, dcr.Message)
|
||||
|
||||
}
|
545
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/authority/authority.go
generated
vendored
Normal file
545
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/authority/authority.go
generated
vendored
Normal file
|
@ -0,0 +1,545 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
package authority
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const (
|
||||
authorizationEndpoint = "https://%v/%v/oauth2/v2.0/authorize"
|
||||
instanceDiscoveryEndpoint = "https://%v/common/discovery/instance"
|
||||
tenantDiscoveryEndpointWithRegion = "https://%s.%s/%s/v2.0/.well-known/openid-configuration"
|
||||
regionName = "REGION_NAME"
|
||||
defaultAPIVersion = "2021-10-01"
|
||||
imdsEndpoint = "http://169.254.169.254/metadata/instance/compute/location?format=text&api-version=" + defaultAPIVersion
|
||||
defaultHost = "login.microsoftonline.com"
|
||||
autoDetectRegion = "TryAutoDetect"
|
||||
)
|
||||
|
||||
type jsonCaller interface {
|
||||
JSONCall(ctx context.Context, endpoint string, headers http.Header, qv url.Values, body, resp interface{}) error
|
||||
}
|
||||
|
||||
var aadTrustedHostList = map[string]bool{
|
||||
"login.windows.net": true, // Microsoft Azure Worldwide - Used in validation scenarios where host is not this list
|
||||
"login.chinacloudapi.cn": true, // Microsoft Azure China
|
||||
"login.microsoftonline.de": true, // Microsoft Azure Blackforest
|
||||
"login-us.microsoftonline.com": true, // Microsoft Azure US Government - Legacy
|
||||
"login.microsoftonline.us": true, // Microsoft Azure US Government
|
||||
"login.microsoftonline.com": true, // Microsoft Azure Worldwide
|
||||
"login.cloudgovapi.us": true, // Microsoft Azure US Government
|
||||
}
|
||||
|
||||
// TrustedHost checks if an AAD host is trusted/valid.
|
||||
func TrustedHost(host string) bool {
|
||||
if _, ok := aadTrustedHostList[host]; ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type OAuthResponseBase struct {
|
||||
Error string `json:"error"`
|
||||
SubError string `json:"suberror"`
|
||||
ErrorDescription string `json:"error_description"`
|
||||
ErrorCodes []int `json:"error_codes"`
|
||||
CorrelationID string `json:"correlation_id"`
|
||||
Claims string `json:"claims"`
|
||||
}
|
||||
|
||||
// TenantDiscoveryResponse is the tenant endpoints from the OpenID configuration endpoint.
|
||||
type TenantDiscoveryResponse struct {
|
||||
OAuthResponseBase
|
||||
|
||||
AuthorizationEndpoint string `json:"authorization_endpoint"`
|
||||
TokenEndpoint string `json:"token_endpoint"`
|
||||
Issuer string `json:"issuer"`
|
||||
|
||||
AdditionalFields map[string]interface{}
|
||||
}
|
||||
|
||||
// Validate validates that the response had the correct values required.
|
||||
func (r *TenantDiscoveryResponse) Validate() error {
|
||||
switch "" {
|
||||
case r.AuthorizationEndpoint:
|
||||
return errors.New("TenantDiscoveryResponse: authorize endpoint was not found in the openid configuration")
|
||||
case r.TokenEndpoint:
|
||||
return errors.New("TenantDiscoveryResponse: token endpoint was not found in the openid configuration")
|
||||
case r.Issuer:
|
||||
return errors.New("TenantDiscoveryResponse: issuer was not found in the openid configuration")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type InstanceDiscoveryMetadata struct {
|
||||
PreferredNetwork string `json:"preferred_network"`
|
||||
PreferredCache string `json:"preferred_cache"`
|
||||
Aliases []string `json:"aliases"`
|
||||
|
||||
AdditionalFields map[string]interface{}
|
||||
}
|
||||
|
||||
type InstanceDiscoveryResponse struct {
|
||||
TenantDiscoveryEndpoint string `json:"tenant_discovery_endpoint"`
|
||||
Metadata []InstanceDiscoveryMetadata `json:"metadata"`
|
||||
|
||||
AdditionalFields map[string]interface{}
|
||||
}
|
||||
|
||||
//go:generate stringer -type=AuthorizeType
|
||||
|
||||
// AuthorizeType represents the type of token flow.
|
||||
type AuthorizeType int
|
||||
|
||||
// These are all the types of token flows.
|
||||
const (
|
||||
ATUnknown AuthorizeType = iota
|
||||
ATUsernamePassword
|
||||
ATWindowsIntegrated
|
||||
ATAuthCode
|
||||
ATInteractive
|
||||
ATClientCredentials
|
||||
ATDeviceCode
|
||||
ATRefreshToken
|
||||
AccountByID
|
||||
ATOnBehalfOf
|
||||
)
|
||||
|
||||
// These are all authority types
|
||||
const (
|
||||
AAD = "MSSTS"
|
||||
ADFS = "ADFS"
|
||||
)
|
||||
|
||||
// AuthParams represents the parameters used for authorization for token acquisition.
|
||||
type AuthParams struct {
|
||||
AuthorityInfo Info
|
||||
CorrelationID string
|
||||
Endpoints Endpoints
|
||||
ClientID string
|
||||
// Redirecturi is used for auth flows that specify a redirect URI (e.g. local server for interactive auth flow).
|
||||
Redirecturi string
|
||||
HomeAccountID string
|
||||
// Username is the user-name portion for username/password auth flow.
|
||||
Username string
|
||||
// Password is the password portion for username/password auth flow.
|
||||
Password string
|
||||
// Scopes is the list of scopes the user consents to.
|
||||
Scopes []string
|
||||
// AuthorizationType specifies the auth flow being used.
|
||||
AuthorizationType AuthorizeType
|
||||
// State is a random value used to prevent cross-site request forgery attacks.
|
||||
State string
|
||||
// CodeChallenge is derived from a code verifier and is sent in the auth request.
|
||||
CodeChallenge string
|
||||
// CodeChallengeMethod describes the method used to create the CodeChallenge.
|
||||
CodeChallengeMethod string
|
||||
// Prompt specifies the user prompt type during interactive auth.
|
||||
Prompt string
|
||||
// IsConfidentialClient specifies if it is a confidential client.
|
||||
IsConfidentialClient bool
|
||||
// SendX5C specifies if x5c claim(public key of the certificate) should be sent to STS.
|
||||
SendX5C bool
|
||||
// UserAssertion is the access token used to acquire token on behalf of user
|
||||
UserAssertion string
|
||||
// Capabilities the client will include with each token request, for example "CP1".
|
||||
// Call [NewClientCapabilities] to construct a value for this field.
|
||||
Capabilities ClientCapabilities
|
||||
// Claims required for an access token to satisfy a conditional access policy
|
||||
Claims string
|
||||
// KnownAuthorityHosts don't require metadata discovery because they're known to the user
|
||||
KnownAuthorityHosts []string
|
||||
// LoginHint is a username with which to pre-populate account selection during interactive auth
|
||||
LoginHint string
|
||||
// DomainHint is a directive that can be used to accelerate the user to their federated IdP sign-in page
|
||||
DomainHint string
|
||||
}
|
||||
|
||||
// NewAuthParams creates an authorization parameters object.
|
||||
func NewAuthParams(clientID string, authorityInfo Info) AuthParams {
|
||||
return AuthParams{
|
||||
ClientID: clientID,
|
||||
AuthorityInfo: authorityInfo,
|
||||
CorrelationID: uuid.New().String(),
|
||||
}
|
||||
}
|
||||
|
||||
// WithTenant returns a copy of the AuthParams having the specified tenant ID. If the given
|
||||
// ID is empty, the copy is identical to the original. This function returns an error in
|
||||
// several cases:
|
||||
// - ID isn't specific (for example, it's "common")
|
||||
// - ID is non-empty and the authority doesn't support tenants (for example, it's an ADFS authority)
|
||||
// - the client is configured to authenticate only Microsoft accounts via the "consumers" endpoint
|
||||
// - the resulting authority URL is invalid
|
||||
func (p AuthParams) WithTenant(ID string) (AuthParams, error) {
|
||||
switch ID {
|
||||
case "", p.AuthorityInfo.Tenant:
|
||||
// keep the default tenant because the caller didn't override it
|
||||
return p, nil
|
||||
case "common", "consumers", "organizations":
|
||||
if p.AuthorityInfo.AuthorityType == AAD {
|
||||
return p, fmt.Errorf(`tenant ID must be a specific tenant, not "%s"`, ID)
|
||||
}
|
||||
// else we'll return a better error below
|
||||
}
|
||||
if p.AuthorityInfo.AuthorityType != AAD {
|
||||
return p, errors.New("the authority doesn't support tenants")
|
||||
}
|
||||
if p.AuthorityInfo.Tenant == "consumers" {
|
||||
return p, errors.New(`client is configured to authenticate only personal Microsoft accounts, via the "consumers" endpoint`)
|
||||
}
|
||||
authority := "https://" + path.Join(p.AuthorityInfo.Host, ID)
|
||||
info, err := NewInfoFromAuthorityURI(authority, p.AuthorityInfo.ValidateAuthority, p.AuthorityInfo.InstanceDiscoveryDisabled)
|
||||
if err == nil {
|
||||
info.Region = p.AuthorityInfo.Region
|
||||
p.AuthorityInfo = info
|
||||
}
|
||||
return p, err
|
||||
}
|
||||
|
||||
// MergeCapabilitiesAndClaims combines client capabilities and challenge claims into a value suitable for an authentication request's "claims" parameter.
|
||||
func (p AuthParams) MergeCapabilitiesAndClaims() (string, error) {
|
||||
claims := p.Claims
|
||||
if len(p.Capabilities.asMap) > 0 {
|
||||
if claims == "" {
|
||||
// without claims the result is simply the capabilities
|
||||
return p.Capabilities.asJSON, nil
|
||||
}
|
||||
// Otherwise, merge claims and capabilties into a single JSON object.
|
||||
// We handle the claims challenge as a map because we don't know its structure.
|
||||
var challenge map[string]any
|
||||
if err := json.Unmarshal([]byte(claims), &challenge); err != nil {
|
||||
return "", fmt.Errorf(`claims must be JSON. Are they base64 encoded? json.Unmarshal returned "%v"`, err)
|
||||
}
|
||||
if err := merge(p.Capabilities.asMap, challenge); err != nil {
|
||||
return "", err
|
||||
}
|
||||
b, err := json.Marshal(challenge)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
claims = string(b)
|
||||
}
|
||||
return claims, nil
|
||||
}
|
||||
|
||||
// merges a into b without overwriting b's values. Returns an error when a and b share a key for which either has a non-object value.
|
||||
func merge(a, b map[string]any) error {
|
||||
for k, av := range a {
|
||||
if bv, ok := b[k]; !ok {
|
||||
// b doesn't contain this key => simply set it to a's value
|
||||
b[k] = av
|
||||
} else {
|
||||
// b does contain this key => recursively merge a[k] into b[k], provided both are maps. If a[k] or b[k] isn't
|
||||
// a map, return an error because merging would overwrite some value in b. Errors shouldn't occur in practice
|
||||
// because the challenge will be from AAD, which knows the capabilities format.
|
||||
if A, ok := av.(map[string]any); ok {
|
||||
if B, ok := bv.(map[string]any); ok {
|
||||
return merge(A, B)
|
||||
} else {
|
||||
// b[k] isn't a map
|
||||
return errors.New("challenge claims conflict with client capabilities")
|
||||
}
|
||||
} else {
|
||||
// a[k] isn't a map
|
||||
return errors.New("challenge claims conflict with client capabilities")
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ClientCapabilities stores capabilities in the formats used by AuthParams.MergeCapabilitiesAndClaims.
|
||||
// [NewClientCapabilities] precomputes these representations because capabilities are static for the
|
||||
// lifetime of a client and are included with every authentication request i.e., these computations
|
||||
// always have the same result and would otherwise have to be repeated for every request.
|
||||
type ClientCapabilities struct {
|
||||
// asJSON is for the common case: adding the capabilities to an auth request with no challenge claims
|
||||
asJSON string
|
||||
// asMap is for merging the capabilities with challenge claims
|
||||
asMap map[string]any
|
||||
}
|
||||
|
||||
func NewClientCapabilities(capabilities []string) (ClientCapabilities, error) {
|
||||
c := ClientCapabilities{}
|
||||
var err error
|
||||
if len(capabilities) > 0 {
|
||||
cpbs := make([]string, len(capabilities))
|
||||
for i := 0; i < len(cpbs); i++ {
|
||||
cpbs[i] = fmt.Sprintf(`"%s"`, capabilities[i])
|
||||
}
|
||||
c.asJSON = fmt.Sprintf(`{"access_token":{"xms_cc":{"values":[%s]}}}`, strings.Join(cpbs, ","))
|
||||
// note our JSON is valid but we can't stop users breaking it with garbage like "}"
|
||||
err = json.Unmarshal([]byte(c.asJSON), &c.asMap)
|
||||
}
|
||||
return c, err
|
||||
}
|
||||
|
||||
// Info consists of information about the authority.
|
||||
type Info struct {
|
||||
Host string
|
||||
CanonicalAuthorityURI string
|
||||
AuthorityType string
|
||||
UserRealmURIPrefix string
|
||||
ValidateAuthority bool
|
||||
Tenant string
|
||||
Region string
|
||||
InstanceDiscoveryDisabled bool
|
||||
}
|
||||
|
||||
func firstPathSegment(u *url.URL) (string, error) {
|
||||
pathParts := strings.Split(u.EscapedPath(), "/")
|
||||
if len(pathParts) >= 2 {
|
||||
return pathParts[1], nil
|
||||
}
|
||||
|
||||
return "", errors.New("authority does not have two segments")
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
tenant, err := firstPathSegment(u)
|
||||
if tenant == "adfs" {
|
||||
authorityType = ADFS
|
||||
} else {
|
||||
authorityType = AAD
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return Info{}, err
|
||||
}
|
||||
|
||||
// u.Host includes the port, if any, which is required for private cloud deployments
|
||||
return Info{
|
||||
Host: u.Host,
|
||||
CanonicalAuthorityURI: fmt.Sprintf("https://%v/%v/", u.Host, tenant),
|
||||
AuthorityType: authorityType,
|
||||
UserRealmURIPrefix: fmt.Sprintf("https://%v/common/userrealm/", u.Hostname()),
|
||||
ValidateAuthority: validateAuthority,
|
||||
Tenant: tenant,
|
||||
InstanceDiscoveryDisabled: instanceDiscoveryDisabled,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Endpoints consists of the endpoints from the tenant discovery response.
|
||||
type Endpoints struct {
|
||||
AuthorizationEndpoint string
|
||||
TokenEndpoint string
|
||||
selfSignedJwtAudience string
|
||||
authorityHost string
|
||||
}
|
||||
|
||||
// NewEndpoints creates an Endpoints object.
|
||||
func NewEndpoints(authorizationEndpoint string, tokenEndpoint string, selfSignedJwtAudience string, authorityHost string) Endpoints {
|
||||
return Endpoints{authorizationEndpoint, tokenEndpoint, selfSignedJwtAudience, authorityHost}
|
||||
}
|
||||
|
||||
// UserRealmAccountType refers to the type of user realm.
|
||||
type UserRealmAccountType string
|
||||
|
||||
// These are the different types of user realms.
|
||||
const (
|
||||
Unknown UserRealmAccountType = ""
|
||||
Federated UserRealmAccountType = "Federated"
|
||||
Managed UserRealmAccountType = "Managed"
|
||||
)
|
||||
|
||||
// UserRealm is used for the username password request to determine user type
|
||||
type UserRealm struct {
|
||||
AccountType UserRealmAccountType `json:"account_type"`
|
||||
DomainName string `json:"domain_name"`
|
||||
CloudInstanceName string `json:"cloud_instance_name"`
|
||||
CloudAudienceURN string `json:"cloud_audience_urn"`
|
||||
|
||||
// required if accountType is Federated
|
||||
FederationProtocol string `json:"federation_protocol"`
|
||||
FederationMetadataURL string `json:"federation_metadata_url"`
|
||||
|
||||
AdditionalFields map[string]interface{}
|
||||
}
|
||||
|
||||
func (u UserRealm) validate() error {
|
||||
switch "" {
|
||||
case string(u.AccountType):
|
||||
return errors.New("the account type (Federated or Managed) is missing")
|
||||
case u.DomainName:
|
||||
return errors.New("domain name of user realm is missing")
|
||||
case u.CloudInstanceName:
|
||||
return errors.New("cloud instance name of user realm is missing")
|
||||
case u.CloudAudienceURN:
|
||||
return errors.New("cloud Instance URN is missing")
|
||||
}
|
||||
|
||||
if u.AccountType == Federated {
|
||||
switch "" {
|
||||
case u.FederationProtocol:
|
||||
return errors.New("federation protocol of user realm is missing")
|
||||
case u.FederationMetadataURL:
|
||||
return errors.New("federation metadata URL of user realm is missing")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Client represents the REST calls to authority backends.
|
||||
type Client struct {
|
||||
// Comm provides the HTTP transport client.
|
||||
Comm jsonCaller // *comm.Client
|
||||
}
|
||||
|
||||
func (c Client) UserRealm(ctx context.Context, authParams AuthParams) (UserRealm, error) {
|
||||
endpoint := fmt.Sprintf("https://%s/common/UserRealm/%s", authParams.Endpoints.authorityHost, url.PathEscape(authParams.Username))
|
||||
qv := url.Values{
|
||||
"api-version": []string{"1.0"},
|
||||
}
|
||||
|
||||
resp := UserRealm{}
|
||||
err := c.Comm.JSONCall(
|
||||
ctx,
|
||||
endpoint,
|
||||
http.Header{"client-request-id": []string{authParams.CorrelationID}},
|
||||
qv,
|
||||
nil,
|
||||
&resp,
|
||||
)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
return resp, resp.validate()
|
||||
}
|
||||
|
||||
func (c Client) GetTenantDiscoveryResponse(ctx context.Context, openIDConfigurationEndpoint string) (TenantDiscoveryResponse, error) {
|
||||
resp := TenantDiscoveryResponse{}
|
||||
err := c.Comm.JSONCall(
|
||||
ctx,
|
||||
openIDConfigurationEndpoint,
|
||||
http.Header{},
|
||||
nil,
|
||||
nil,
|
||||
&resp,
|
||||
)
|
||||
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (c Client) AADInstanceDiscovery(ctx context.Context, authorityInfo Info) (InstanceDiscoveryResponse, error) {
|
||||
region := ""
|
||||
var err error
|
||||
resp := InstanceDiscoveryResponse{}
|
||||
if authorityInfo.Region != "" && authorityInfo.Region != autoDetectRegion {
|
||||
region = authorityInfo.Region
|
||||
} else if authorityInfo.Region == autoDetectRegion {
|
||||
region = detectRegion(ctx)
|
||||
}
|
||||
if region != "" {
|
||||
environment := authorityInfo.Host
|
||||
switch environment {
|
||||
case "login.microsoft.com", "login.windows.net", "sts.windows.net", defaultHost:
|
||||
environment = "r." + defaultHost
|
||||
}
|
||||
resp.TenantDiscoveryEndpoint = fmt.Sprintf(tenantDiscoveryEndpointWithRegion, region, environment, authorityInfo.Tenant)
|
||||
metadata := InstanceDiscoveryMetadata{
|
||||
PreferredNetwork: fmt.Sprintf("%v.%v", region, authorityInfo.Host),
|
||||
PreferredCache: authorityInfo.Host,
|
||||
Aliases: []string{fmt.Sprintf("%v.%v", region, authorityInfo.Host), authorityInfo.Host},
|
||||
}
|
||||
resp.Metadata = []InstanceDiscoveryMetadata{metadata}
|
||||
} else {
|
||||
qv := url.Values{}
|
||||
qv.Set("api-version", "1.1")
|
||||
qv.Set("authorization_endpoint", fmt.Sprintf(authorizationEndpoint, authorityInfo.Host, authorityInfo.Tenant))
|
||||
|
||||
discoveryHost := defaultHost
|
||||
if TrustedHost(authorityInfo.Host) {
|
||||
discoveryHost = authorityInfo.Host
|
||||
}
|
||||
|
||||
endpoint := fmt.Sprintf(instanceDiscoveryEndpoint, discoveryHost)
|
||||
err = c.Comm.JSONCall(ctx, endpoint, http.Header{}, qv, nil, &resp)
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func detectRegion(ctx context.Context) string {
|
||||
region := os.Getenv(regionName)
|
||||
if region != "" {
|
||||
region = strings.ReplaceAll(region, " ", "")
|
||||
return strings.ToLower(region)
|
||||
}
|
||||
// HTTP call to IMDS endpoint to get region
|
||||
// Refer : https://identitydivision.visualstudio.com/DevEx/_git/AuthLibrariesApiReview?path=%2FPinAuthToRegion%2FAAD%20SDK%20Proposal%20to%20Pin%20Auth%20to%20region.md&_a=preview&version=GBdev
|
||||
// Set a 2 second timeout for this http client which only does calls to IMDS endpoint
|
||||
client := http.Client{
|
||||
Timeout: time.Duration(2 * time.Second),
|
||||
}
|
||||
req, _ := http.NewRequest("GET", imdsEndpoint, nil)
|
||||
req.Header.Set("Metadata", "true")
|
||||
resp, err := client.Do(req)
|
||||
// If the request times out or there is an error, it is retried once
|
||||
if err != nil || resp.StatusCode != 200 {
|
||||
resp, err = client.Do(req)
|
||||
if err != nil || resp.StatusCode != 200 {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
response, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return string(response)
|
||||
}
|
||||
|
||||
func (a *AuthParams) CacheKey(isAppCache bool) string {
|
||||
if a.AuthorizationType == ATOnBehalfOf {
|
||||
return a.AssertionHash()
|
||||
}
|
||||
if a.AuthorizationType == ATClientCredentials || isAppCache {
|
||||
return a.AppKey()
|
||||
}
|
||||
if a.AuthorizationType == ATRefreshToken || a.AuthorizationType == AccountByID {
|
||||
return a.HomeAccountID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
func (a *AuthParams) AssertionHash() string {
|
||||
hasher := sha256.New()
|
||||
// Per documentation this never returns an error : https://pkg.go.dev/hash#pkg-types
|
||||
_, _ = hasher.Write([]byte(a.UserAssertion))
|
||||
sha := base64.URLEncoding.EncodeToString(hasher.Sum(nil))
|
||||
return sha
|
||||
}
|
||||
|
||||
func (a *AuthParams) AppKey() string {
|
||||
if a.AuthorityInfo.Tenant != "" {
|
||||
return fmt.Sprintf("%s_%s_AppTokenCache", a.ClientID, a.AuthorityInfo.Tenant)
|
||||
}
|
||||
return fmt.Sprintf("%s__AppTokenCache", a.ClientID)
|
||||
}
|
30
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/authority/authorizetype_string.go
generated
vendored
Normal file
30
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/authority/authorizetype_string.go
generated
vendored
Normal file
|
@ -0,0 +1,30 @@
|
|||
// Code generated by "stringer -type=AuthorizeType"; DO NOT EDIT.
|
||||
|
||||
package authority
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[ATUnknown-0]
|
||||
_ = x[ATUsernamePassword-1]
|
||||
_ = x[ATWindowsIntegrated-2]
|
||||
_ = x[ATAuthCode-3]
|
||||
_ = x[ATInteractive-4]
|
||||
_ = x[ATClientCredentials-5]
|
||||
_ = x[ATDeviceCode-6]
|
||||
_ = x[ATRefreshToken-7]
|
||||
}
|
||||
|
||||
const _AuthorizeType_name = "ATUnknownATUsernamePasswordATWindowsIntegratedATAuthCodeATInteractiveATClientCredentialsATDeviceCodeATRefreshToken"
|
||||
|
||||
var _AuthorizeType_index = [...]uint8{0, 9, 27, 46, 56, 69, 88, 100, 114}
|
||||
|
||||
func (i AuthorizeType) String() string {
|
||||
if i < 0 || i >= AuthorizeType(len(_AuthorizeType_index)-1) {
|
||||
return "AuthorizeType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _AuthorizeType_name[_AuthorizeType_index[i]:_AuthorizeType_index[i+1]]
|
||||
}
|
320
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/internal/comm/comm.go
generated
vendored
Normal file
320
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/internal/comm/comm.go
generated
vendored
Normal file
|
@ -0,0 +1,320 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
// Package comm provides helpers for communicating with HTTP backends.
|
||||
package comm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/errors"
|
||||
customJSON "github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json"
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/version"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// HTTPClient represents an HTTP client.
|
||||
// It's usually an *http.Client from the standard library.
|
||||
type HTTPClient interface {
|
||||
// Do sends an HTTP request and returns an HTTP response.
|
||||
Do(req *http.Request) (*http.Response, error)
|
||||
|
||||
// CloseIdleConnections closes any idle connections in a "keep-alive" state.
|
||||
CloseIdleConnections()
|
||||
}
|
||||
|
||||
// Client provides a wrapper to our *http.Client that handles compression and serialization needs.
|
||||
type Client struct {
|
||||
client HTTPClient
|
||||
}
|
||||
|
||||
// New returns a new Client object.
|
||||
func New(httpClient HTTPClient) *Client {
|
||||
if httpClient == nil {
|
||||
panic("http.Client cannot == nil")
|
||||
}
|
||||
|
||||
return &Client{client: httpClient}
|
||||
}
|
||||
|
||||
// JSONCall connects to the REST endpoint passing the HTTP query values, headers and JSON conversion
|
||||
// of body in the HTTP body. It automatically handles compression and decompression with gzip. The response is JSON
|
||||
// unmarshalled into resp. resp must be a pointer to a struct. If the body struct contains a field called
|
||||
// "AdditionalFields" we use a custom marshal/unmarshal engine.
|
||||
func (c *Client) JSONCall(ctx context.Context, endpoint string, headers http.Header, qv url.Values, body, resp interface{}) error {
|
||||
if qv == nil {
|
||||
qv = url.Values{}
|
||||
}
|
||||
|
||||
v := reflect.ValueOf(resp)
|
||||
if err := c.checkResp(v); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Choose a JSON marshal/unmarshal depending on if we have AdditionalFields attribute.
|
||||
var marshal = json.Marshal
|
||||
var unmarshal = json.Unmarshal
|
||||
if _, ok := v.Elem().Type().FieldByName("AdditionalFields"); ok {
|
||||
marshal = customJSON.Marshal
|
||||
unmarshal = customJSON.Unmarshal
|
||||
}
|
||||
|
||||
u, err := url.Parse(endpoint)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse path URL(%s): %w", endpoint, err)
|
||||
}
|
||||
u.RawQuery = qv.Encode()
|
||||
|
||||
addStdHeaders(headers)
|
||||
|
||||
req := &http.Request{Method: http.MethodGet, URL: u, Header: headers}
|
||||
|
||||
if body != nil {
|
||||
// Note: In case your wondering why we are not gzip encoding....
|
||||
// I'm not sure if these various services support gzip on send.
|
||||
headers.Add("Content-Type", "application/json; charset=utf-8")
|
||||
data, err := marshal(body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bug: conn.Call(): could not marshal the body object: %w", err)
|
||||
}
|
||||
req.Body = io.NopCloser(bytes.NewBuffer(data))
|
||||
req.Method = http.MethodPost
|
||||
}
|
||||
|
||||
data, err := c.do(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp != nil {
|
||||
if err := unmarshal(data, resp); err != nil {
|
||||
return fmt.Errorf("json decode error: %w\njson message bytes were: %s", err, string(data))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// XMLCall connects to an endpoint and decodes the XML response into resp. This is used when
|
||||
// sending application/xml . If sending XML via SOAP, use SOAPCall().
|
||||
func (c *Client) XMLCall(ctx context.Context, endpoint string, headers http.Header, qv url.Values, resp interface{}) error {
|
||||
if err := c.checkResp(reflect.ValueOf(resp)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if qv == nil {
|
||||
qv = url.Values{}
|
||||
}
|
||||
|
||||
u, err := url.Parse(endpoint)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse path URL(%s): %w", endpoint, err)
|
||||
}
|
||||
u.RawQuery = qv.Encode()
|
||||
|
||||
headers.Set("Content-Type", "application/xml; charset=utf-8") // This was not set in he original Mex(), but...
|
||||
addStdHeaders(headers)
|
||||
|
||||
return c.xmlCall(ctx, u, headers, "", resp)
|
||||
}
|
||||
|
||||
// SOAPCall returns the SOAP message given an endpoint, action, body of the request and the response object to marshal into.
|
||||
func (c *Client) SOAPCall(ctx context.Context, endpoint, action string, headers http.Header, qv url.Values, body string, resp interface{}) error {
|
||||
if body == "" {
|
||||
return fmt.Errorf("cannot make a SOAP call with body set to empty string")
|
||||
}
|
||||
|
||||
if err := c.checkResp(reflect.ValueOf(resp)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if qv == nil {
|
||||
qv = url.Values{}
|
||||
}
|
||||
|
||||
u, err := url.Parse(endpoint)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse path URL(%s): %w", endpoint, err)
|
||||
}
|
||||
u.RawQuery = qv.Encode()
|
||||
|
||||
headers.Set("Content-Type", "application/soap+xml; charset=utf-8")
|
||||
headers.Set("SOAPAction", action)
|
||||
addStdHeaders(headers)
|
||||
|
||||
return c.xmlCall(ctx, u, headers, body, resp)
|
||||
}
|
||||
|
||||
// xmlCall sends an XML in body and decodes into resp. This simply does the transport and relies on
|
||||
// an upper level call to set things such as SOAP parameters and Content-Type, if required.
|
||||
func (c *Client) xmlCall(ctx context.Context, u *url.URL, headers http.Header, body string, resp interface{}) error {
|
||||
req := &http.Request{Method: http.MethodGet, URL: u, Header: headers}
|
||||
|
||||
if len(body) > 0 {
|
||||
req.Method = http.MethodPost
|
||||
req.Body = io.NopCloser(strings.NewReader(body))
|
||||
}
|
||||
|
||||
data, err := c.do(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return xml.Unmarshal(data, resp)
|
||||
}
|
||||
|
||||
// URLFormCall is used to make a call where we need to send application/x-www-form-urlencoded data
|
||||
// to the backend and receive JSON back. qv will be encoded into the request body.
|
||||
func (c *Client) URLFormCall(ctx context.Context, endpoint string, qv url.Values, resp interface{}) error {
|
||||
if len(qv) == 0 {
|
||||
return fmt.Errorf("URLFormCall() requires qv to have non-zero length")
|
||||
}
|
||||
|
||||
if err := c.checkResp(reflect.ValueOf(resp)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
u, err := url.Parse(endpoint)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse path URL(%s): %w", endpoint, err)
|
||||
}
|
||||
|
||||
headers := http.Header{}
|
||||
headers.Set("Content-Type", "application/x-www-form-urlencoded; charset=utf-8")
|
||||
addStdHeaders(headers)
|
||||
|
||||
enc := qv.Encode()
|
||||
|
||||
req := &http.Request{
|
||||
Method: http.MethodPost,
|
||||
URL: u,
|
||||
Header: headers,
|
||||
ContentLength: int64(len(enc)),
|
||||
Body: io.NopCloser(strings.NewReader(enc)),
|
||||
GetBody: func() (io.ReadCloser, error) {
|
||||
return io.NopCloser(strings.NewReader(enc)), nil
|
||||
},
|
||||
}
|
||||
|
||||
data, err := c.do(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
v := reflect.ValueOf(resp)
|
||||
if err := c.checkResp(v); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var unmarshal = json.Unmarshal
|
||||
if _, ok := v.Elem().Type().FieldByName("AdditionalFields"); ok {
|
||||
unmarshal = customJSON.Unmarshal
|
||||
}
|
||||
if resp != nil {
|
||||
if err := unmarshal(data, resp); err != nil {
|
||||
return fmt.Errorf("json decode error: %w\nraw message was: %s", err, string(data))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// do makes the HTTP call to the server and returns the contents of the body.
|
||||
func (c *Client) do(ctx context.Context, req *http.Request) ([]byte, error) {
|
||||
if _, ok := ctx.Deadline(); !ok {
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithTimeout(ctx, 30*time.Second)
|
||||
defer cancel()
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
reply, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("server response error:\n %w", err)
|
||||
}
|
||||
defer reply.Body.Close()
|
||||
|
||||
data, err := c.readBody(reply)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read the body of an HTTP Response: %w", err)
|
||||
}
|
||||
reply.Body = io.NopCloser(bytes.NewBuffer(data))
|
||||
|
||||
// NOTE: This doesn't happen immediately after the call so that we can get an error message
|
||||
// from the server and include it in our error.
|
||||
switch reply.StatusCode {
|
||||
case 200, 201:
|
||||
default:
|
||||
sd := strings.TrimSpace(string(data))
|
||||
if sd != "" {
|
||||
// We probably have the error in the body.
|
||||
return nil, errors.CallErr{
|
||||
Req: req,
|
||||
Resp: reply,
|
||||
Err: fmt.Errorf("http call(%s)(%s) error: reply status code was %d:\n%s", req.URL.String(), req.Method, reply.StatusCode, sd),
|
||||
}
|
||||
}
|
||||
return nil, errors.CallErr{
|
||||
Req: req,
|
||||
Resp: reply,
|
||||
Err: fmt.Errorf("http call(%s)(%s) error: reply status code was %d", req.URL.String(), req.Method, reply.StatusCode),
|
||||
}
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// checkResp checks a response object o make sure it is a pointer to a struct.
|
||||
func (c *Client) checkResp(v reflect.Value) error {
|
||||
if v.Kind() != reflect.Ptr {
|
||||
return fmt.Errorf("bug: resp argument must a *struct, was %T", v.Interface())
|
||||
}
|
||||
v = v.Elem()
|
||||
if v.Kind() != reflect.Struct {
|
||||
return fmt.Errorf("bug: resp argument must be a *struct, was %T", v.Interface())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// readBody reads the body out of an *http.Response. It supports gzip encoded responses.
|
||||
func (c *Client) readBody(resp *http.Response) ([]byte, error) {
|
||||
var reader io.Reader = resp.Body
|
||||
switch resp.Header.Get("Content-Encoding") {
|
||||
case "":
|
||||
// Do nothing
|
||||
case "gzip":
|
||||
reader = gzipDecompress(resp.Body)
|
||||
default:
|
||||
return nil, fmt.Errorf("bug: comm.Client.JSONCall(): content was send with unsupported content-encoding %s", resp.Header.Get("Content-Encoding"))
|
||||
}
|
||||
return io.ReadAll(reader)
|
||||
}
|
||||
|
||||
var testID string
|
||||
|
||||
// addStdHeaders adds the standard headers we use on all calls.
|
||||
func addStdHeaders(headers http.Header) http.Header {
|
||||
headers.Set("Accept-Encoding", "gzip")
|
||||
// So that I can have a static id for tests.
|
||||
if testID != "" {
|
||||
headers.Set("client-request-id", testID)
|
||||
headers.Set("Return-Client-Request-Id", "false")
|
||||
} else {
|
||||
headers.Set("client-request-id", uuid.New().String())
|
||||
headers.Set("Return-Client-Request-Id", "false")
|
||||
}
|
||||
headers.Set("x-client-sku", "MSAL.Go")
|
||||
headers.Set("x-client-os", runtime.GOOS)
|
||||
headers.Set("x-client-cpu", runtime.GOARCH)
|
||||
headers.Set("x-client-ver", version.Version)
|
||||
return headers
|
||||
}
|
33
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/internal/comm/compress.go
generated
vendored
Normal file
33
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/internal/comm/compress.go
generated
vendored
Normal file
|
@ -0,0 +1,33 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
package comm
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"io"
|
||||
)
|
||||
|
||||
func gzipDecompress(r io.Reader) io.Reader {
|
||||
gzipReader, _ := gzip.NewReader(r)
|
||||
|
||||
pipeOut, pipeIn := io.Pipe()
|
||||
go func() {
|
||||
// decompression bomb would have to come from Azure services.
|
||||
// If we want to limit, we should do that in comm.do().
|
||||
_, err := io.Copy(pipeIn, gzipReader) //nolint
|
||||
if err != nil {
|
||||
// don't need the error.
|
||||
pipeIn.CloseWithError(err) //nolint
|
||||
gzipReader.Close()
|
||||
return
|
||||
}
|
||||
if err := gzipReader.Close(); err != nil {
|
||||
// don't need the error.
|
||||
pipeIn.CloseWithError(err) //nolint
|
||||
return
|
||||
}
|
||||
pipeIn.Close()
|
||||
}()
|
||||
return pipeOut
|
||||
}
|
17
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/internal/grant/grant.go
generated
vendored
Normal file
17
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/internal/grant/grant.go
generated
vendored
Normal file
|
@ -0,0 +1,17 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
// Package grant holds types of grants issued by authorization services.
|
||||
package grant
|
||||
|
||||
const (
|
||||
Password = "password"
|
||||
JWT = "urn:ietf:params:oauth:grant-type:jwt-bearer"
|
||||
SAMLV1 = "urn:ietf:params:oauth:grant-type:saml1_1-bearer"
|
||||
SAMLV2 = "urn:ietf:params:oauth:grant-type:saml2-bearer"
|
||||
DeviceCode = "device_code"
|
||||
AuthCode = "authorization_code"
|
||||
RefreshToken = "refresh_token"
|
||||
ClientCredential = "client_credentials"
|
||||
ClientAssertion = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
|
||||
)
|
56
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/ops.go
generated
vendored
Normal file
56
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/ops.go
generated
vendored
Normal file
|
@ -0,0 +1,56 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
/*
|
||||
Package ops provides operations to various backend services using REST clients.
|
||||
|
||||
The REST type provides several clients that can be used to communicate to backends.
|
||||
Usage is simple:
|
||||
|
||||
rest := ops.New()
|
||||
|
||||
// Creates an authority client and calls the UserRealm() method.
|
||||
userRealm, err := rest.Authority().UserRealm(ctx, authParameters)
|
||||
if err != nil {
|
||||
// Do something
|
||||
}
|
||||
*/
|
||||
package ops
|
||||
|
||||
import (
|
||||
"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/oauth/ops/internal/comm"
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/wstrust"
|
||||
)
|
||||
|
||||
// HTTPClient represents an HTTP client.
|
||||
// It's usually an *http.Client from the standard library.
|
||||
type HTTPClient = comm.HTTPClient
|
||||
|
||||
// REST provides REST clients for communicating with various backends used by MSAL.
|
||||
type REST struct {
|
||||
client *comm.Client
|
||||
}
|
||||
|
||||
// New is the constructor for REST.
|
||||
func New(httpClient HTTPClient) *REST {
|
||||
return &REST{client: comm.New(httpClient)}
|
||||
}
|
||||
|
||||
// Authority returns a client for querying information about various authorities.
|
||||
func (r *REST) Authority() authority.Client {
|
||||
return authority.Client{Comm: r.client}
|
||||
}
|
||||
|
||||
// AccessTokens returns a client that can be used to get various access tokens for
|
||||
// authorization purposes.
|
||||
func (r *REST) AccessTokens() accesstokens.Client {
|
||||
return accesstokens.Client{Comm: r.client}
|
||||
}
|
||||
|
||||
// WSTrust provides access to various metadata in a WSTrust service. This data can
|
||||
// be used to gain tokens based on SAML data using the client provided by AccessTokens().
|
||||
func (r *REST) WSTrust() wstrust.Client {
|
||||
return wstrust.Client{Comm: r.client}
|
||||
}
|
25
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/wstrust/defs/endpointtype_string.go
generated
vendored
Normal file
25
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/wstrust/defs/endpointtype_string.go
generated
vendored
Normal file
|
@ -0,0 +1,25 @@
|
|||
// Code generated by "stringer -type=endpointType"; DO NOT EDIT.
|
||||
|
||||
package defs
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[etUnknown-0]
|
||||
_ = x[etUsernamePassword-1]
|
||||
_ = x[etWindowsTransport-2]
|
||||
}
|
||||
|
||||
const _endpointType_name = "etUnknownetUsernamePasswordetWindowsTransport"
|
||||
|
||||
var _endpointType_index = [...]uint8{0, 9, 27, 45}
|
||||
|
||||
func (i endpointType) String() string {
|
||||
if i < 0 || i >= endpointType(len(_endpointType_index)-1) {
|
||||
return "endpointType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _endpointType_name[_endpointType_index[i]:_endpointType_index[i+1]]
|
||||
}
|
394
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/wstrust/defs/mex_document_definitions.go
generated
vendored
Normal file
394
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/wstrust/defs/mex_document_definitions.go
generated
vendored
Normal file
|
@ -0,0 +1,394 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
package defs
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
type Definitions struct {
|
||||
XMLName xml.Name `xml:"definitions"`
|
||||
Text string `xml:",chardata"`
|
||||
Name string `xml:"name,attr"`
|
||||
TargetNamespace string `xml:"targetNamespace,attr"`
|
||||
WSDL string `xml:"wsdl,attr"`
|
||||
XSD string `xml:"xsd,attr"`
|
||||
T string `xml:"t,attr"`
|
||||
SOAPENC string `xml:"soapenc,attr"`
|
||||
SOAP string `xml:"soap,attr"`
|
||||
TNS string `xml:"tns,attr"`
|
||||
MSC string `xml:"msc,attr"`
|
||||
WSAM string `xml:"wsam,attr"`
|
||||
SOAP12 string `xml:"soap12,attr"`
|
||||
WSA10 string `xml:"wsa10,attr"`
|
||||
WSA string `xml:"wsa,attr"`
|
||||
WSAW string `xml:"wsaw,attr"`
|
||||
WSX string `xml:"wsx,attr"`
|
||||
WSAP string `xml:"wsap,attr"`
|
||||
WSU string `xml:"wsu,attr"`
|
||||
Trust string `xml:"trust,attr"`
|
||||
WSP string `xml:"wsp,attr"`
|
||||
Policy []Policy `xml:"Policy"`
|
||||
Types Types `xml:"types"`
|
||||
Message []Message `xml:"message"`
|
||||
PortType []PortType `xml:"portType"`
|
||||
Binding []Binding `xml:"binding"`
|
||||
Service Service `xml:"service"`
|
||||
}
|
||||
|
||||
type Policy struct {
|
||||
Text string `xml:",chardata"`
|
||||
ID string `xml:"Id,attr"`
|
||||
ExactlyOne ExactlyOne `xml:"ExactlyOne"`
|
||||
}
|
||||
|
||||
type ExactlyOne struct {
|
||||
Text string `xml:",chardata"`
|
||||
All All `xml:"All"`
|
||||
}
|
||||
|
||||
type All struct {
|
||||
Text string `xml:",chardata"`
|
||||
NegotiateAuthentication NegotiateAuthentication `xml:"NegotiateAuthentication"`
|
||||
TransportBinding TransportBinding `xml:"TransportBinding"`
|
||||
UsingAddressing Text `xml:"UsingAddressing"`
|
||||
EndorsingSupportingTokens EndorsingSupportingTokens `xml:"EndorsingSupportingTokens"`
|
||||
WSS11 WSS11 `xml:"Wss11"`
|
||||
Trust10 Trust10 `xml:"Trust10"`
|
||||
SignedSupportingTokens SignedSupportingTokens `xml:"SignedSupportingTokens"`
|
||||
Trust13 WSTrust13 `xml:"Trust13"`
|
||||
SignedEncryptedSupportingTokens SignedEncryptedSupportingTokens `xml:"SignedEncryptedSupportingTokens"`
|
||||
}
|
||||
|
||||
type NegotiateAuthentication struct {
|
||||
Text string `xml:",chardata"`
|
||||
HTTP string `xml:"http,attr"`
|
||||
XMLName xml.Name
|
||||
}
|
||||
|
||||
type TransportBinding struct {
|
||||
Text string `xml:",chardata"`
|
||||
SP string `xml:"sp,attr"`
|
||||
Policy TransportBindingPolicy `xml:"Policy"`
|
||||
}
|
||||
|
||||
type TransportBindingPolicy struct {
|
||||
Text string `xml:",chardata"`
|
||||
TransportToken TransportToken `xml:"TransportToken"`
|
||||
AlgorithmSuite AlgorithmSuite `xml:"AlgorithmSuite"`
|
||||
Layout Layout `xml:"Layout"`
|
||||
IncludeTimestamp Text `xml:"IncludeTimestamp"`
|
||||
}
|
||||
|
||||
type TransportToken struct {
|
||||
Text string `xml:",chardata"`
|
||||
Policy TransportTokenPolicy `xml:"Policy"`
|
||||
}
|
||||
|
||||
type TransportTokenPolicy struct {
|
||||
Text string `xml:",chardata"`
|
||||
HTTPSToken HTTPSToken `xml:"HttpsToken"`
|
||||
}
|
||||
|
||||
type HTTPSToken struct {
|
||||
Text string `xml:",chardata"`
|
||||
RequireClientCertificate string `xml:"RequireClientCertificate,attr"`
|
||||
}
|
||||
|
||||
type AlgorithmSuite struct {
|
||||
Text string `xml:",chardata"`
|
||||
Policy AlgorithmSuitePolicy `xml:"Policy"`
|
||||
}
|
||||
|
||||
type AlgorithmSuitePolicy struct {
|
||||
Text string `xml:",chardata"`
|
||||
Basic256 Text `xml:"Basic256"`
|
||||
Basic128 Text `xml:"Basic128"`
|
||||
}
|
||||
|
||||
type Layout struct {
|
||||
Text string `xml:",chardata"`
|
||||
Policy LayoutPolicy `xml:"Policy"`
|
||||
}
|
||||
|
||||
type LayoutPolicy struct {
|
||||
Text string `xml:",chardata"`
|
||||
Strict Text `xml:"Strict"`
|
||||
}
|
||||
|
||||
type EndorsingSupportingTokens struct {
|
||||
Text string `xml:",chardata"`
|
||||
SP string `xml:"sp,attr"`
|
||||
Policy EndorsingSupportingTokensPolicy `xml:"Policy"`
|
||||
}
|
||||
|
||||
type EndorsingSupportingTokensPolicy struct {
|
||||
Text string `xml:",chardata"`
|
||||
X509Token X509Token `xml:"X509Token"`
|
||||
RSAToken RSAToken `xml:"RsaToken"`
|
||||
SignedParts SignedParts `xml:"SignedParts"`
|
||||
KerberosToken KerberosToken `xml:"KerberosToken"`
|
||||
IssuedToken IssuedToken `xml:"IssuedToken"`
|
||||
KeyValueToken KeyValueToken `xml:"KeyValueToken"`
|
||||
}
|
||||
|
||||
type X509Token struct {
|
||||
Text string `xml:",chardata"`
|
||||
IncludeToken string `xml:"IncludeToken,attr"`
|
||||
Policy X509TokenPolicy `xml:"Policy"`
|
||||
}
|
||||
|
||||
type X509TokenPolicy struct {
|
||||
Text string `xml:",chardata"`
|
||||
RequireThumbprintReference Text `xml:"RequireThumbprintReference"`
|
||||
WSSX509V3Token10 Text `xml:"WssX509V3Token10"`
|
||||
}
|
||||
|
||||
type RSAToken struct {
|
||||
Text string `xml:",chardata"`
|
||||
IncludeToken string `xml:"IncludeToken,attr"`
|
||||
Optional string `xml:"Optional,attr"`
|
||||
MSSP string `xml:"mssp,attr"`
|
||||
}
|
||||
|
||||
type SignedParts struct {
|
||||
Text string `xml:",chardata"`
|
||||
Header SignedPartsHeader `xml:"Header"`
|
||||
}
|
||||
|
||||
type SignedPartsHeader struct {
|
||||
Text string `xml:",chardata"`
|
||||
Name string `xml:"Name,attr"`
|
||||
Namespace string `xml:"Namespace,attr"`
|
||||
}
|
||||
|
||||
type KerberosToken struct {
|
||||
Text string `xml:",chardata"`
|
||||
IncludeToken string `xml:"IncludeToken,attr"`
|
||||
Policy KerberosTokenPolicy `xml:"Policy"`
|
||||
}
|
||||
|
||||
type KerberosTokenPolicy struct {
|
||||
Text string `xml:",chardata"`
|
||||
WSSGSSKerberosV5ApReqToken11 Text `xml:"WssGssKerberosV5ApReqToken11"`
|
||||
}
|
||||
|
||||
type IssuedToken struct {
|
||||
Text string `xml:",chardata"`
|
||||
IncludeToken string `xml:"IncludeToken,attr"`
|
||||
RequestSecurityTokenTemplate RequestSecurityTokenTemplate `xml:"RequestSecurityTokenTemplate"`
|
||||
Policy IssuedTokenPolicy `xml:"Policy"`
|
||||
}
|
||||
|
||||
type RequestSecurityTokenTemplate struct {
|
||||
Text string `xml:",chardata"`
|
||||
KeyType Text `xml:"KeyType"`
|
||||
EncryptWith Text `xml:"EncryptWith"`
|
||||
SignatureAlgorithm Text `xml:"SignatureAlgorithm"`
|
||||
CanonicalizationAlgorithm Text `xml:"CanonicalizationAlgorithm"`
|
||||
EncryptionAlgorithm Text `xml:"EncryptionAlgorithm"`
|
||||
KeySize Text `xml:"KeySize"`
|
||||
KeyWrapAlgorithm Text `xml:"KeyWrapAlgorithm"`
|
||||
}
|
||||
|
||||
type IssuedTokenPolicy struct {
|
||||
Text string `xml:",chardata"`
|
||||
RequireInternalReference Text `xml:"RequireInternalReference"`
|
||||
}
|
||||
|
||||
type KeyValueToken struct {
|
||||
Text string `xml:",chardata"`
|
||||
IncludeToken string `xml:"IncludeToken,attr"`
|
||||
Optional string `xml:"Optional,attr"`
|
||||
}
|
||||
|
||||
type WSS11 struct {
|
||||
Text string `xml:",chardata"`
|
||||
SP string `xml:"sp,attr"`
|
||||
Policy Wss11Policy `xml:"Policy"`
|
||||
}
|
||||
|
||||
type Wss11Policy struct {
|
||||
Text string `xml:",chardata"`
|
||||
MustSupportRefThumbprint Text `xml:"MustSupportRefThumbprint"`
|
||||
}
|
||||
|
||||
type Trust10 struct {
|
||||
Text string `xml:",chardata"`
|
||||
SP string `xml:"sp,attr"`
|
||||
Policy Trust10Policy `xml:"Policy"`
|
||||
}
|
||||
|
||||
type Trust10Policy struct {
|
||||
Text string `xml:",chardata"`
|
||||
MustSupportIssuedTokens Text `xml:"MustSupportIssuedTokens"`
|
||||
RequireClientEntropy Text `xml:"RequireClientEntropy"`
|
||||
RequireServerEntropy Text `xml:"RequireServerEntropy"`
|
||||
}
|
||||
|
||||
type SignedSupportingTokens struct {
|
||||
Text string `xml:",chardata"`
|
||||
SP string `xml:"sp,attr"`
|
||||
Policy SupportingTokensPolicy `xml:"Policy"`
|
||||
}
|
||||
|
||||
type SupportingTokensPolicy struct {
|
||||
Text string `xml:",chardata"`
|
||||
UsernameToken UsernameToken `xml:"UsernameToken"`
|
||||
}
|
||||
type UsernameToken struct {
|
||||
Text string `xml:",chardata"`
|
||||
IncludeToken string `xml:"IncludeToken,attr"`
|
||||
Policy UsernameTokenPolicy `xml:"Policy"`
|
||||
}
|
||||
|
||||
type UsernameTokenPolicy struct {
|
||||
Text string `xml:",chardata"`
|
||||
WSSUsernameToken10 WSSUsernameToken10 `xml:"WssUsernameToken10"`
|
||||
}
|
||||
|
||||
type WSSUsernameToken10 struct {
|
||||
Text string `xml:",chardata"`
|
||||
XMLName xml.Name
|
||||
}
|
||||
|
||||
type WSTrust13 struct {
|
||||
Text string `xml:",chardata"`
|
||||
SP string `xml:"sp,attr"`
|
||||
Policy WSTrust13Policy `xml:"Policy"`
|
||||
}
|
||||
|
||||
type WSTrust13Policy struct {
|
||||
Text string `xml:",chardata"`
|
||||
MustSupportIssuedTokens Text `xml:"MustSupportIssuedTokens"`
|
||||
RequireClientEntropy Text `xml:"RequireClientEntropy"`
|
||||
RequireServerEntropy Text `xml:"RequireServerEntropy"`
|
||||
}
|
||||
|
||||
type SignedEncryptedSupportingTokens struct {
|
||||
Text string `xml:",chardata"`
|
||||
SP string `xml:"sp,attr"`
|
||||
Policy SupportingTokensPolicy `xml:"Policy"`
|
||||
}
|
||||
|
||||
type Types struct {
|
||||
Text string `xml:",chardata"`
|
||||
Schema Schema `xml:"schema"`
|
||||
}
|
||||
|
||||
type Schema struct {
|
||||
Text string `xml:",chardata"`
|
||||
TargetNamespace string `xml:"targetNamespace,attr"`
|
||||
Import []Import `xml:"import"`
|
||||
}
|
||||
|
||||
type Import struct {
|
||||
Text string `xml:",chardata"`
|
||||
SchemaLocation string `xml:"schemaLocation,attr"`
|
||||
Namespace string `xml:"namespace,attr"`
|
||||
}
|
||||
|
||||
type Message struct {
|
||||
Text string `xml:",chardata"`
|
||||
Name string `xml:"name,attr"`
|
||||
Part Part `xml:"part"`
|
||||
}
|
||||
|
||||
type Part struct {
|
||||
Text string `xml:",chardata"`
|
||||
Name string `xml:"name,attr"`
|
||||
Element string `xml:"element,attr"`
|
||||
}
|
||||
|
||||
type PortType struct {
|
||||
Text string `xml:",chardata"`
|
||||
Name string `xml:"name,attr"`
|
||||
Operation Operation `xml:"operation"`
|
||||
}
|
||||
|
||||
type Operation struct {
|
||||
Text string `xml:",chardata"`
|
||||
Name string `xml:"name,attr"`
|
||||
Input OperationIO `xml:"input"`
|
||||
Output OperationIO `xml:"output"`
|
||||
}
|
||||
|
||||
type OperationIO struct {
|
||||
Text string `xml:",chardata"`
|
||||
Action string `xml:"Action,attr"`
|
||||
Message string `xml:"message,attr"`
|
||||
Body OperationIOBody `xml:"body"`
|
||||
}
|
||||
|
||||
type OperationIOBody struct {
|
||||
Text string `xml:",chardata"`
|
||||
Use string `xml:"use,attr"`
|
||||
}
|
||||
|
||||
type Binding struct {
|
||||
Text string `xml:",chardata"`
|
||||
Name string `xml:"name,attr"`
|
||||
Type string `xml:"type,attr"`
|
||||
PolicyReference PolicyReference `xml:"PolicyReference"`
|
||||
Binding DefinitionsBinding `xml:"binding"`
|
||||
Operation BindingOperation `xml:"operation"`
|
||||
}
|
||||
|
||||
type PolicyReference struct {
|
||||
Text string `xml:",chardata"`
|
||||
URI string `xml:"URI,attr"`
|
||||
}
|
||||
|
||||
type DefinitionsBinding struct {
|
||||
Text string `xml:",chardata"`
|
||||
Transport string `xml:"transport,attr"`
|
||||
}
|
||||
|
||||
type BindingOperation struct {
|
||||
Text string `xml:",chardata"`
|
||||
Name string `xml:"name,attr"`
|
||||
Operation BindingOperationOperation `xml:"operation"`
|
||||
Input BindingOperationIO `xml:"input"`
|
||||
Output BindingOperationIO `xml:"output"`
|
||||
}
|
||||
|
||||
type BindingOperationOperation struct {
|
||||
Text string `xml:",chardata"`
|
||||
SoapAction string `xml:"soapAction,attr"`
|
||||
Style string `xml:"style,attr"`
|
||||
}
|
||||
|
||||
type BindingOperationIO struct {
|
||||
Text string `xml:",chardata"`
|
||||
Body OperationIOBody `xml:"body"`
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
Text string `xml:",chardata"`
|
||||
Name string `xml:"name,attr"`
|
||||
Port []Port `xml:"port"`
|
||||
}
|
||||
|
||||
type Port struct {
|
||||
Text string `xml:",chardata"`
|
||||
Name string `xml:"name,attr"`
|
||||
Binding string `xml:"binding,attr"`
|
||||
Address Address `xml:"address"`
|
||||
EndpointReference PortEndpointReference `xml:"EndpointReference"`
|
||||
}
|
||||
|
||||
type Address struct {
|
||||
Text string `xml:",chardata"`
|
||||
Location string `xml:"location,attr"`
|
||||
}
|
||||
|
||||
type PortEndpointReference struct {
|
||||
Text string `xml:",chardata"`
|
||||
Address Text `xml:"Address"`
|
||||
Identity Identity `xml:"Identity"`
|
||||
}
|
||||
|
||||
type Identity struct {
|
||||
Text string `xml:",chardata"`
|
||||
XMLNS string `xml:"xmlns,attr"`
|
||||
SPN Text `xml:"Spn"`
|
||||
}
|
230
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/wstrust/defs/saml_assertion_definitions.go
generated
vendored
Normal file
230
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/wstrust/defs/saml_assertion_definitions.go
generated
vendored
Normal file
|
@ -0,0 +1,230 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
package defs
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
// TODO(msal): Someone (and it ain't gonna be me) needs to document these attributes or
|
||||
// at the least put a link to RFC.
|
||||
|
||||
type SAMLDefinitions struct {
|
||||
XMLName xml.Name `xml:"Envelope"`
|
||||
Text string `xml:",chardata"`
|
||||
S string `xml:"s,attr"`
|
||||
A string `xml:"a,attr"`
|
||||
U string `xml:"u,attr"`
|
||||
Header Header `xml:"Header"`
|
||||
Body Body `xml:"Body"`
|
||||
}
|
||||
|
||||
type Header struct {
|
||||
Text string `xml:",chardata"`
|
||||
Action Action `xml:"Action"`
|
||||
Security Security `xml:"Security"`
|
||||
}
|
||||
|
||||
type Action struct {
|
||||
Text string `xml:",chardata"`
|
||||
MustUnderstand string `xml:"mustUnderstand,attr"`
|
||||
}
|
||||
|
||||
type Security struct {
|
||||
Text string `xml:",chardata"`
|
||||
MustUnderstand string `xml:"mustUnderstand,attr"`
|
||||
O string `xml:"o,attr"`
|
||||
Timestamp Timestamp `xml:"Timestamp"`
|
||||
}
|
||||
|
||||
type Timestamp struct {
|
||||
Text string `xml:",chardata"`
|
||||
ID string `xml:"Id,attr"`
|
||||
Created Text `xml:"Created"`
|
||||
Expires Text `xml:"Expires"`
|
||||
}
|
||||
|
||||
type Text struct {
|
||||
Text string `xml:",chardata"`
|
||||
}
|
||||
|
||||
type Body struct {
|
||||
Text string `xml:",chardata"`
|
||||
RequestSecurityTokenResponseCollection RequestSecurityTokenResponseCollection `xml:"RequestSecurityTokenResponseCollection"`
|
||||
}
|
||||
|
||||
type RequestSecurityTokenResponseCollection struct {
|
||||
Text string `xml:",chardata"`
|
||||
Trust string `xml:"trust,attr"`
|
||||
RequestSecurityTokenResponse []RequestSecurityTokenResponse `xml:"RequestSecurityTokenResponse"`
|
||||
}
|
||||
|
||||
type RequestSecurityTokenResponse struct {
|
||||
Text string `xml:",chardata"`
|
||||
Lifetime Lifetime `xml:"Lifetime"`
|
||||
AppliesTo AppliesTo `xml:"AppliesTo"`
|
||||
RequestedSecurityToken RequestedSecurityToken `xml:"RequestedSecurityToken"`
|
||||
RequestedAttachedReference RequestedAttachedReference `xml:"RequestedAttachedReference"`
|
||||
RequestedUnattachedReference RequestedUnattachedReference `xml:"RequestedUnattachedReference"`
|
||||
TokenType Text `xml:"TokenType"`
|
||||
RequestType Text `xml:"RequestType"`
|
||||
KeyType Text `xml:"KeyType"`
|
||||
}
|
||||
|
||||
type Lifetime struct {
|
||||
Text string `xml:",chardata"`
|
||||
Created WSUTimestamp `xml:"Created"`
|
||||
Expires WSUTimestamp `xml:"Expires"`
|
||||
}
|
||||
|
||||
type WSUTimestamp struct {
|
||||
Text string `xml:",chardata"`
|
||||
Wsu string `xml:"wsu,attr"`
|
||||
}
|
||||
|
||||
type AppliesTo struct {
|
||||
Text string `xml:",chardata"`
|
||||
Wsp string `xml:"wsp,attr"`
|
||||
EndpointReference EndpointReference `xml:"EndpointReference"`
|
||||
}
|
||||
|
||||
type EndpointReference struct {
|
||||
Text string `xml:",chardata"`
|
||||
Wsa string `xml:"wsa,attr"`
|
||||
Address Text `xml:"Address"`
|
||||
}
|
||||
|
||||
type RequestedSecurityToken struct {
|
||||
Text string `xml:",chardata"`
|
||||
AssertionRawXML string `xml:",innerxml"`
|
||||
Assertion Assertion `xml:"Assertion"`
|
||||
}
|
||||
|
||||
type Assertion struct {
|
||||
XMLName xml.Name // Normally its `xml:"Assertion"`, but I think they want to capture the xmlns
|
||||
Text string `xml:",chardata"`
|
||||
MajorVersion string `xml:"MajorVersion,attr"`
|
||||
MinorVersion string `xml:"MinorVersion,attr"`
|
||||
AssertionID string `xml:"AssertionID,attr"`
|
||||
Issuer string `xml:"Issuer,attr"`
|
||||
IssueInstant string `xml:"IssueInstant,attr"`
|
||||
Saml string `xml:"saml,attr"`
|
||||
Conditions Conditions `xml:"Conditions"`
|
||||
AttributeStatement AttributeStatement `xml:"AttributeStatement"`
|
||||
AuthenticationStatement AuthenticationStatement `xml:"AuthenticationStatement"`
|
||||
Signature Signature `xml:"Signature"`
|
||||
}
|
||||
|
||||
type Conditions struct {
|
||||
Text string `xml:",chardata"`
|
||||
NotBefore string `xml:"NotBefore,attr"`
|
||||
NotOnOrAfter string `xml:"NotOnOrAfter,attr"`
|
||||
AudienceRestrictionCondition AudienceRestrictionCondition `xml:"AudienceRestrictionCondition"`
|
||||
}
|
||||
|
||||
type AudienceRestrictionCondition struct {
|
||||
Text string `xml:",chardata"`
|
||||
Audience Text `xml:"Audience"`
|
||||
}
|
||||
|
||||
type AttributeStatement struct {
|
||||
Text string `xml:",chardata"`
|
||||
Subject Subject `xml:"Subject"`
|
||||
Attribute []Attribute `xml:"Attribute"`
|
||||
}
|
||||
|
||||
type Subject struct {
|
||||
Text string `xml:",chardata"`
|
||||
NameIdentifier NameIdentifier `xml:"NameIdentifier"`
|
||||
SubjectConfirmation SubjectConfirmation `xml:"SubjectConfirmation"`
|
||||
}
|
||||
|
||||
type NameIdentifier struct {
|
||||
Text string `xml:",chardata"`
|
||||
Format string `xml:"Format,attr"`
|
||||
}
|
||||
|
||||
type SubjectConfirmation struct {
|
||||
Text string `xml:",chardata"`
|
||||
ConfirmationMethod Text `xml:"ConfirmationMethod"`
|
||||
}
|
||||
|
||||
type Attribute struct {
|
||||
Text string `xml:",chardata"`
|
||||
AttributeName string `xml:"AttributeName,attr"`
|
||||
AttributeNamespace string `xml:"AttributeNamespace,attr"`
|
||||
AttributeValue Text `xml:"AttributeValue"`
|
||||
}
|
||||
|
||||
type AuthenticationStatement struct {
|
||||
Text string `xml:",chardata"`
|
||||
AuthenticationMethod string `xml:"AuthenticationMethod,attr"`
|
||||
AuthenticationInstant string `xml:"AuthenticationInstant,attr"`
|
||||
Subject Subject `xml:"Subject"`
|
||||
}
|
||||
|
||||
type Signature struct {
|
||||
Text string `xml:",chardata"`
|
||||
Ds string `xml:"ds,attr"`
|
||||
SignedInfo SignedInfo `xml:"SignedInfo"`
|
||||
SignatureValue Text `xml:"SignatureValue"`
|
||||
KeyInfo KeyInfo `xml:"KeyInfo"`
|
||||
}
|
||||
|
||||
type SignedInfo struct {
|
||||
Text string `xml:",chardata"`
|
||||
CanonicalizationMethod Method `xml:"CanonicalizationMethod"`
|
||||
SignatureMethod Method `xml:"SignatureMethod"`
|
||||
Reference Reference `xml:"Reference"`
|
||||
}
|
||||
|
||||
type Method struct {
|
||||
Text string `xml:",chardata"`
|
||||
Algorithm string `xml:"Algorithm,attr"`
|
||||
}
|
||||
|
||||
type Reference struct {
|
||||
Text string `xml:",chardata"`
|
||||
URI string `xml:"URI,attr"`
|
||||
Transforms Transforms `xml:"Transforms"`
|
||||
DigestMethod Method `xml:"DigestMethod"`
|
||||
DigestValue Text `xml:"DigestValue"`
|
||||
}
|
||||
|
||||
type Transforms struct {
|
||||
Text string `xml:",chardata"`
|
||||
Transform []Method `xml:"Transform"`
|
||||
}
|
||||
|
||||
type KeyInfo struct {
|
||||
Text string `xml:",chardata"`
|
||||
Xmlns string `xml:"xmlns,attr"`
|
||||
X509Data X509Data `xml:"X509Data"`
|
||||
}
|
||||
|
||||
type X509Data struct {
|
||||
Text string `xml:",chardata"`
|
||||
X509Certificate Text `xml:"X509Certificate"`
|
||||
}
|
||||
|
||||
type RequestedAttachedReference struct {
|
||||
Text string `xml:",chardata"`
|
||||
SecurityTokenReference SecurityTokenReference `xml:"SecurityTokenReference"`
|
||||
}
|
||||
|
||||
type SecurityTokenReference struct {
|
||||
Text string `xml:",chardata"`
|
||||
TokenType string `xml:"TokenType,attr"`
|
||||
O string `xml:"o,attr"`
|
||||
K string `xml:"k,attr"`
|
||||
KeyIdentifier KeyIdentifier `xml:"KeyIdentifier"`
|
||||
}
|
||||
|
||||
type KeyIdentifier struct {
|
||||
Text string `xml:",chardata"`
|
||||
ValueType string `xml:"ValueType,attr"`
|
||||
}
|
||||
|
||||
type RequestedUnattachedReference struct {
|
||||
Text string `xml:",chardata"`
|
||||
SecurityTokenReference SecurityTokenReference `xml:"SecurityTokenReference"`
|
||||
}
|
25
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/wstrust/defs/version_string.go
generated
vendored
Normal file
25
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/wstrust/defs/version_string.go
generated
vendored
Normal file
|
@ -0,0 +1,25 @@
|
|||
// Code generated by "stringer -type=Version"; DO NOT EDIT.
|
||||
|
||||
package defs
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[TrustUnknown-0]
|
||||
_ = x[Trust2005-1]
|
||||
_ = x[Trust13-2]
|
||||
}
|
||||
|
||||
const _Version_name = "TrustUnknownTrust2005Trust13"
|
||||
|
||||
var _Version_index = [...]uint8{0, 12, 21, 28}
|
||||
|
||||
func (i Version) String() string {
|
||||
if i < 0 || i >= Version(len(_Version_index)-1) {
|
||||
return "Version(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _Version_name[_Version_index[i]:_Version_index[i+1]]
|
||||
}
|
199
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/wstrust/defs/wstrust_endpoint.go
generated
vendored
Normal file
199
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/wstrust/defs/wstrust_endpoint.go
generated
vendored
Normal file
|
@ -0,0 +1,199 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
package defs
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/authority"
|
||||
uuid "github.com/google/uuid"
|
||||
)
|
||||
|
||||
//go:generate stringer -type=Version
|
||||
|
||||
type Version int
|
||||
|
||||
const (
|
||||
TrustUnknown Version = iota
|
||||
Trust2005
|
||||
Trust13
|
||||
)
|
||||
|
||||
// Endpoint represents a WSTrust endpoint.
|
||||
type Endpoint struct {
|
||||
// Version is the version of the endpoint.
|
||||
Version Version
|
||||
// URL is the URL of the endpoint.
|
||||
URL string
|
||||
}
|
||||
|
||||
type wsTrustTokenRequestEnvelope struct {
|
||||
XMLName xml.Name `xml:"s:Envelope"`
|
||||
Text string `xml:",chardata"`
|
||||
S string `xml:"xmlns:s,attr"`
|
||||
Wsa string `xml:"xmlns:wsa,attr"`
|
||||
Wsu string `xml:"xmlns:wsu,attr"`
|
||||
Header struct {
|
||||
Text string `xml:",chardata"`
|
||||
Action struct {
|
||||
Text string `xml:",chardata"`
|
||||
MustUnderstand string `xml:"s:mustUnderstand,attr"`
|
||||
} `xml:"wsa:Action"`
|
||||
MessageID struct {
|
||||
Text string `xml:",chardata"`
|
||||
} `xml:"wsa:messageID"`
|
||||
ReplyTo struct {
|
||||
Text string `xml:",chardata"`
|
||||
Address struct {
|
||||
Text string `xml:",chardata"`
|
||||
} `xml:"wsa:Address"`
|
||||
} `xml:"wsa:ReplyTo"`
|
||||
To struct {
|
||||
Text string `xml:",chardata"`
|
||||
MustUnderstand string `xml:"s:mustUnderstand,attr"`
|
||||
} `xml:"wsa:To"`
|
||||
Security struct {
|
||||
Text string `xml:",chardata"`
|
||||
MustUnderstand string `xml:"s:mustUnderstand,attr"`
|
||||
Wsse string `xml:"xmlns:wsse,attr"`
|
||||
Timestamp struct {
|
||||
Text string `xml:",chardata"`
|
||||
ID string `xml:"wsu:Id,attr"`
|
||||
Created struct {
|
||||
Text string `xml:",chardata"`
|
||||
} `xml:"wsu:Created"`
|
||||
Expires struct {
|
||||
Text string `xml:",chardata"`
|
||||
} `xml:"wsu:Expires"`
|
||||
} `xml:"wsu:Timestamp"`
|
||||
UsernameToken struct {
|
||||
Text string `xml:",chardata"`
|
||||
ID string `xml:"wsu:Id,attr"`
|
||||
Username struct {
|
||||
Text string `xml:",chardata"`
|
||||
} `xml:"wsse:Username"`
|
||||
Password struct {
|
||||
Text string `xml:",chardata"`
|
||||
} `xml:"wsse:Password"`
|
||||
} `xml:"wsse:UsernameToken"`
|
||||
} `xml:"wsse:Security"`
|
||||
} `xml:"s:Header"`
|
||||
Body struct {
|
||||
Text string `xml:",chardata"`
|
||||
RequestSecurityToken struct {
|
||||
Text string `xml:",chardata"`
|
||||
Wst string `xml:"xmlns:wst,attr"`
|
||||
AppliesTo struct {
|
||||
Text string `xml:",chardata"`
|
||||
Wsp string `xml:"xmlns:wsp,attr"`
|
||||
EndpointReference struct {
|
||||
Text string `xml:",chardata"`
|
||||
Address struct {
|
||||
Text string `xml:",chardata"`
|
||||
} `xml:"wsa:Address"`
|
||||
} `xml:"wsa:EndpointReference"`
|
||||
} `xml:"wsp:AppliesTo"`
|
||||
KeyType struct {
|
||||
Text string `xml:",chardata"`
|
||||
} `xml:"wst:KeyType"`
|
||||
RequestType struct {
|
||||
Text string `xml:",chardata"`
|
||||
} `xml:"wst:RequestType"`
|
||||
} `xml:"wst:RequestSecurityToken"`
|
||||
} `xml:"s:Body"`
|
||||
}
|
||||
|
||||
func buildTimeString(t time.Time) string {
|
||||
// Golang time formats are weird: https://stackoverflow.com/questions/20234104/how-to-format-current-time-using-a-yyyymmddhhmmss-format
|
||||
return t.Format("2006-01-02T15:04:05.000Z")
|
||||
}
|
||||
|
||||
func (wte *Endpoint) buildTokenRequestMessage(authType authority.AuthorizeType, cloudAudienceURN string, username string, password string) (string, error) {
|
||||
var soapAction string
|
||||
var trustNamespace string
|
||||
var keyType string
|
||||
var requestType string
|
||||
|
||||
createdTime := time.Now().UTC()
|
||||
expiresTime := createdTime.Add(10 * time.Minute)
|
||||
|
||||
switch wte.Version {
|
||||
case Trust2005:
|
||||
soapAction = trust2005Spec
|
||||
trustNamespace = "http://schemas.xmlsoap.org/ws/2005/02/trust"
|
||||
keyType = "http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey"
|
||||
requestType = "http://schemas.xmlsoap.org/ws/2005/02/trust/Issue"
|
||||
case Trust13:
|
||||
soapAction = trust13Spec
|
||||
trustNamespace = "http://docs.oasis-open.org/ws-sx/ws-trust/200512"
|
||||
keyType = "http://docs.oasis-open.org/ws-sx/ws-trust/200512/Bearer"
|
||||
requestType = "http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue"
|
||||
default:
|
||||
return "", fmt.Errorf("buildTokenRequestMessage had Version == %q, which is not recognized", wte.Version)
|
||||
}
|
||||
|
||||
var envelope wsTrustTokenRequestEnvelope
|
||||
|
||||
messageUUID := uuid.New()
|
||||
|
||||
envelope.S = "http://www.w3.org/2003/05/soap-envelope"
|
||||
envelope.Wsa = "http://www.w3.org/2005/08/addressing"
|
||||
envelope.Wsu = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
|
||||
|
||||
envelope.Header.Action.MustUnderstand = "1"
|
||||
envelope.Header.Action.Text = soapAction
|
||||
envelope.Header.MessageID.Text = "urn:uuid:" + messageUUID.String()
|
||||
envelope.Header.ReplyTo.Address.Text = "http://www.w3.org/2005/08/addressing/anonymous"
|
||||
envelope.Header.To.MustUnderstand = "1"
|
||||
envelope.Header.To.Text = wte.URL
|
||||
|
||||
switch authType {
|
||||
case authority.ATUnknown:
|
||||
return "", fmt.Errorf("buildTokenRequestMessage had no authority type(%v)", authType)
|
||||
case authority.ATUsernamePassword:
|
||||
endpointUUID := uuid.New()
|
||||
|
||||
var trustID string
|
||||
if wte.Version == Trust2005 {
|
||||
trustID = "UnPwSecTok2005-" + endpointUUID.String()
|
||||
} else {
|
||||
trustID = "UnPwSecTok13-" + endpointUUID.String()
|
||||
}
|
||||
|
||||
envelope.Header.Security.MustUnderstand = "1"
|
||||
envelope.Header.Security.Wsse = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
|
||||
envelope.Header.Security.Timestamp.ID = "MSATimeStamp"
|
||||
envelope.Header.Security.Timestamp.Created.Text = buildTimeString(createdTime)
|
||||
envelope.Header.Security.Timestamp.Expires.Text = buildTimeString(expiresTime)
|
||||
envelope.Header.Security.UsernameToken.ID = trustID
|
||||
envelope.Header.Security.UsernameToken.Username.Text = username
|
||||
envelope.Header.Security.UsernameToken.Password.Text = password
|
||||
default:
|
||||
// This is just to note that we don't do anything for other cases.
|
||||
// We aren't missing anything I know of.
|
||||
}
|
||||
|
||||
envelope.Body.RequestSecurityToken.Wst = trustNamespace
|
||||
envelope.Body.RequestSecurityToken.AppliesTo.Wsp = "http://schemas.xmlsoap.org/ws/2004/09/policy"
|
||||
envelope.Body.RequestSecurityToken.AppliesTo.EndpointReference.Address.Text = cloudAudienceURN
|
||||
envelope.Body.RequestSecurityToken.KeyType.Text = keyType
|
||||
envelope.Body.RequestSecurityToken.RequestType.Text = requestType
|
||||
|
||||
output, err := xml.Marshal(envelope)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(output), nil
|
||||
}
|
||||
|
||||
func (wte *Endpoint) BuildTokenRequestMessageWIA(cloudAudienceURN string) (string, error) {
|
||||
return wte.buildTokenRequestMessage(authority.ATWindowsIntegrated, cloudAudienceURN, "", "")
|
||||
}
|
||||
|
||||
func (wte *Endpoint) BuildTokenRequestMessageUsernamePassword(cloudAudienceURN string, username string, password string) (string, error) {
|
||||
return wte.buildTokenRequestMessage(authority.ATUsernamePassword, cloudAudienceURN, username, password)
|
||||
}
|
159
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/wstrust/defs/wstrust_mex_document.go
generated
vendored
Normal file
159
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/wstrust/defs/wstrust_mex_document.go
generated
vendored
Normal file
|
@ -0,0 +1,159 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
package defs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
//go:generate stringer -type=endpointType
|
||||
|
||||
type endpointType int
|
||||
|
||||
const (
|
||||
etUnknown endpointType = iota
|
||||
etUsernamePassword
|
||||
etWindowsTransport
|
||||
)
|
||||
|
||||
type wsEndpointData struct {
|
||||
Version Version
|
||||
EndpointType endpointType
|
||||
}
|
||||
|
||||
const trust13Spec string = "http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue"
|
||||
const trust2005Spec string = "http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue"
|
||||
|
||||
type MexDocument struct {
|
||||
UsernamePasswordEndpoint Endpoint
|
||||
WindowsTransportEndpoint Endpoint
|
||||
policies map[string]endpointType
|
||||
bindings map[string]wsEndpointData
|
||||
}
|
||||
|
||||
func updateEndpoint(cached *Endpoint, found Endpoint) {
|
||||
if cached == nil || cached.Version == TrustUnknown {
|
||||
*cached = found
|
||||
return
|
||||
}
|
||||
if (*cached).Version == Trust2005 && found.Version == Trust13 {
|
||||
*cached = found
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(msal): Someone needs to write tests for everything below.
|
||||
|
||||
// NewFromDef creates a new MexDocument.
|
||||
func NewFromDef(defs Definitions) (MexDocument, error) {
|
||||
policies, err := policies(defs)
|
||||
if err != nil {
|
||||
return MexDocument{}, err
|
||||
}
|
||||
|
||||
bindings, err := bindings(defs, policies)
|
||||
if err != nil {
|
||||
return MexDocument{}, err
|
||||
}
|
||||
|
||||
userPass, windows, err := endpoints(defs, bindings)
|
||||
if err != nil {
|
||||
return MexDocument{}, err
|
||||
}
|
||||
|
||||
return MexDocument{
|
||||
UsernamePasswordEndpoint: userPass,
|
||||
WindowsTransportEndpoint: windows,
|
||||
policies: policies,
|
||||
bindings: bindings,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func policies(defs Definitions) (map[string]endpointType, error) {
|
||||
policies := make(map[string]endpointType, len(defs.Policy))
|
||||
|
||||
for _, policy := range defs.Policy {
|
||||
if policy.ExactlyOne.All.NegotiateAuthentication.XMLName.Local != "" {
|
||||
if policy.ExactlyOne.All.TransportBinding.SP != "" && policy.ID != "" {
|
||||
policies["#"+policy.ID] = etWindowsTransport
|
||||
}
|
||||
}
|
||||
|
||||
if policy.ExactlyOne.All.SignedEncryptedSupportingTokens.Policy.UsernameToken.Policy.WSSUsernameToken10.XMLName.Local != "" {
|
||||
if policy.ExactlyOne.All.TransportBinding.SP != "" && policy.ID != "" {
|
||||
policies["#"+policy.ID] = etUsernamePassword
|
||||
}
|
||||
}
|
||||
if policy.ExactlyOne.All.SignedSupportingTokens.Policy.UsernameToken.Policy.WSSUsernameToken10.XMLName.Local != "" {
|
||||
if policy.ExactlyOne.All.TransportBinding.SP != "" && policy.ID != "" {
|
||||
policies["#"+policy.ID] = etUsernamePassword
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(policies) == 0 {
|
||||
return policies, errors.New("no policies for mex document")
|
||||
}
|
||||
|
||||
return policies, nil
|
||||
}
|
||||
|
||||
func bindings(defs Definitions, policies map[string]endpointType) (map[string]wsEndpointData, error) {
|
||||
bindings := make(map[string]wsEndpointData, len(defs.Binding))
|
||||
|
||||
for _, binding := range defs.Binding {
|
||||
policyName := binding.PolicyReference.URI
|
||||
transport := binding.Binding.Transport
|
||||
|
||||
if transport == "http://schemas.xmlsoap.org/soap/http" {
|
||||
if policy, ok := policies[policyName]; ok {
|
||||
bindingName := binding.Name
|
||||
specVersion := binding.Operation.Operation.SoapAction
|
||||
|
||||
if specVersion == trust13Spec {
|
||||
bindings[bindingName] = wsEndpointData{Trust13, policy}
|
||||
} else if specVersion == trust2005Spec {
|
||||
bindings[bindingName] = wsEndpointData{Trust2005, policy}
|
||||
} else {
|
||||
return nil, errors.New("found unknown spec version in mex document")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return bindings, nil
|
||||
}
|
||||
|
||||
func endpoints(defs Definitions, bindings map[string]wsEndpointData) (userPass, windows Endpoint, err error) {
|
||||
for _, port := range defs.Service.Port {
|
||||
bindingName := port.Binding
|
||||
|
||||
index := strings.Index(bindingName, ":")
|
||||
if index != -1 {
|
||||
bindingName = bindingName[index+1:]
|
||||
}
|
||||
|
||||
if binding, ok := bindings[bindingName]; ok {
|
||||
url := strings.TrimSpace(port.EndpointReference.Address.Text)
|
||||
if url == "" {
|
||||
return Endpoint{}, Endpoint{}, fmt.Errorf("MexDocument cannot have blank URL endpoint")
|
||||
}
|
||||
if binding.Version == TrustUnknown {
|
||||
return Endpoint{}, Endpoint{}, fmt.Errorf("endpoint version unknown")
|
||||
}
|
||||
endpoint := Endpoint{Version: binding.Version, URL: url}
|
||||
|
||||
switch binding.EndpointType {
|
||||
case etUsernamePassword:
|
||||
updateEndpoint(&userPass, endpoint)
|
||||
case etWindowsTransport:
|
||||
updateEndpoint(&windows, endpoint)
|
||||
default:
|
||||
return Endpoint{}, Endpoint{}, errors.New("found unknown port type in MEX document")
|
||||
}
|
||||
}
|
||||
}
|
||||
return userPass, windows, nil
|
||||
}
|
136
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/wstrust/wstrust.go
generated
vendored
Normal file
136
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/wstrust/wstrust.go
generated
vendored
Normal file
|
@ -0,0 +1,136 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
/*
|
||||
Package wstrust provides a client for communicating with a WSTrust (https://en.wikipedia.org/wiki/WS-Trust#:~:text=WS%2DTrust%20is%20a%20WS,in%20a%20secure%20message%20exchange.)
|
||||
for the purposes of extracting metadata from the service. This data can be used to acquire
|
||||
tokens using the accesstokens.Client.GetAccessTokenFromSamlGrant() call.
|
||||
*/
|
||||
package wstrust
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/authority"
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/internal/grant"
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/wstrust/defs"
|
||||
)
|
||||
|
||||
type xmlCaller interface {
|
||||
XMLCall(ctx context.Context, endpoint string, headers http.Header, qv url.Values, resp interface{}) error
|
||||
SOAPCall(ctx context.Context, endpoint, action string, headers http.Header, qv url.Values, body string, resp interface{}) error
|
||||
}
|
||||
|
||||
type SamlTokenInfo struct {
|
||||
AssertionType string // Should be either constants SAMLV1Grant or SAMLV2Grant.
|
||||
Assertion string
|
||||
}
|
||||
|
||||
// Client represents the REST calls to get tokens from token generator backends.
|
||||
type Client struct {
|
||||
// Comm provides the HTTP transport client.
|
||||
Comm xmlCaller
|
||||
}
|
||||
|
||||
// TODO(msal): This allows me to call Mex without having a real Def file on line 45.
|
||||
// This would fail because policies() would not find a policy. This is easy enough to
|
||||
// fix in test data, but.... Definitions is defined with built in structs. That needs
|
||||
// to be pulled apart and until then I have this hack in.
|
||||
var newFromDef = defs.NewFromDef
|
||||
|
||||
// Mex provides metadata about a wstrust service.
|
||||
func (c Client) Mex(ctx context.Context, federationMetadataURL string) (defs.MexDocument, error) {
|
||||
resp := defs.Definitions{}
|
||||
err := c.Comm.XMLCall(
|
||||
ctx,
|
||||
federationMetadataURL,
|
||||
http.Header{},
|
||||
nil,
|
||||
&resp,
|
||||
)
|
||||
if err != nil {
|
||||
return defs.MexDocument{}, err
|
||||
}
|
||||
|
||||
return newFromDef(resp)
|
||||
}
|
||||
|
||||
const (
|
||||
SoapActionDefault = "http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue"
|
||||
|
||||
// Note: Commented out because this action is not supported. It was in the original code
|
||||
// but only used in a switch where it errored. Since there was only one value, a default
|
||||
// worked better. However, buildTokenRequestMessage() had 2005 support. I'm not actually
|
||||
// sure what's going on here. It like we have half support. For now this is here just
|
||||
// for documentation purposes in case we are going to add support.
|
||||
//
|
||||
// SoapActionWSTrust2005 = "http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue"
|
||||
)
|
||||
|
||||
// SAMLTokenInfo provides SAML information that is used to generate a SAML token.
|
||||
func (c Client) SAMLTokenInfo(ctx context.Context, authParameters authority.AuthParams, cloudAudienceURN string, endpoint defs.Endpoint) (SamlTokenInfo, error) {
|
||||
var wsTrustRequestMessage string
|
||||
var err error
|
||||
|
||||
switch authParameters.AuthorizationType {
|
||||
case authority.ATWindowsIntegrated:
|
||||
wsTrustRequestMessage, err = endpoint.BuildTokenRequestMessageWIA(cloudAudienceURN)
|
||||
if err != nil {
|
||||
return SamlTokenInfo{}, err
|
||||
}
|
||||
case authority.ATUsernamePassword:
|
||||
wsTrustRequestMessage, err = endpoint.BuildTokenRequestMessageUsernamePassword(
|
||||
cloudAudienceURN, authParameters.Username, authParameters.Password)
|
||||
if err != nil {
|
||||
return SamlTokenInfo{}, err
|
||||
}
|
||||
default:
|
||||
return SamlTokenInfo{}, fmt.Errorf("unknown auth type %v", authParameters.AuthorizationType)
|
||||
}
|
||||
|
||||
var soapAction string
|
||||
switch endpoint.Version {
|
||||
case defs.Trust13:
|
||||
soapAction = SoapActionDefault
|
||||
case defs.Trust2005:
|
||||
return SamlTokenInfo{}, errors.New("WS Trust 2005 support is not implemented")
|
||||
default:
|
||||
return SamlTokenInfo{}, fmt.Errorf("the SOAP endpoint for a wstrust call had an invalid version: %v", endpoint.Version)
|
||||
}
|
||||
|
||||
resp := defs.SAMLDefinitions{}
|
||||
err = c.Comm.SOAPCall(ctx, endpoint.URL, soapAction, http.Header{}, nil, wsTrustRequestMessage, &resp)
|
||||
if err != nil {
|
||||
return SamlTokenInfo{}, err
|
||||
}
|
||||
|
||||
return c.samlAssertion(resp)
|
||||
}
|
||||
|
||||
const (
|
||||
samlv1Assertion = "urn:oasis:names:tc:SAML:1.0:assertion"
|
||||
samlv2Assertion = "urn:oasis:names:tc:SAML:2.0:assertion"
|
||||
)
|
||||
|
||||
func (c Client) samlAssertion(def defs.SAMLDefinitions) (SamlTokenInfo, error) {
|
||||
for _, tokenResponse := range def.Body.RequestSecurityTokenResponseCollection.RequestSecurityTokenResponse {
|
||||
token := tokenResponse.RequestedSecurityToken
|
||||
if token.Assertion.XMLName.Local != "" {
|
||||
assertion := token.AssertionRawXML
|
||||
|
||||
samlVersion := token.Assertion.Saml
|
||||
switch samlVersion {
|
||||
case samlv1Assertion:
|
||||
return SamlTokenInfo{AssertionType: grant.SAMLV1, Assertion: assertion}, nil
|
||||
case samlv2Assertion:
|
||||
return SamlTokenInfo{AssertionType: grant.SAMLV2, Assertion: assertion}, nil
|
||||
}
|
||||
return SamlTokenInfo{}, fmt.Errorf("couldn't parse SAML assertion, version unknown: %q", samlVersion)
|
||||
}
|
||||
}
|
||||
return SamlTokenInfo{}, errors.New("unknown WS-Trust version")
|
||||
}
|
149
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/resolvers.go
generated
vendored
Normal file
149
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/resolvers.go
generated
vendored
Normal file
|
@ -0,0 +1,149 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
// TODO(msal): Write some tests. The original code this came from didn't have tests and I'm too
|
||||
// tired at this point to do it. It, like many other *Manager code I found was broken because
|
||||
// they didn't have mutex protection.
|
||||
|
||||
package oauth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops"
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/authority"
|
||||
)
|
||||
|
||||
// ADFS is an active directory federation service authority type.
|
||||
const ADFS = "ADFS"
|
||||
|
||||
type cacheEntry struct {
|
||||
Endpoints authority.Endpoints
|
||||
ValidForDomainsInList map[string]bool
|
||||
}
|
||||
|
||||
func createcacheEntry(endpoints authority.Endpoints) cacheEntry {
|
||||
return cacheEntry{endpoints, map[string]bool{}}
|
||||
}
|
||||
|
||||
// AuthorityEndpoint retrieves endpoints from an authority for auth and token acquisition.
|
||||
type authorityEndpoint struct {
|
||||
rest *ops.REST
|
||||
|
||||
mu sync.Mutex
|
||||
cache map[string]cacheEntry
|
||||
}
|
||||
|
||||
// newAuthorityEndpoint is the constructor for AuthorityEndpoint.
|
||||
func newAuthorityEndpoint(rest *ops.REST) *authorityEndpoint {
|
||||
m := &authorityEndpoint{rest: rest, cache: map[string]cacheEntry{}}
|
||||
return m
|
||||
}
|
||||
|
||||
// ResolveEndpoints gets the authorization and token endpoints and creates an AuthorityEndpoints instance
|
||||
func (m *authorityEndpoint) ResolveEndpoints(ctx context.Context, authorityInfo authority.Info, userPrincipalName string) (authority.Endpoints, error) {
|
||||
|
||||
if endpoints, found := m.cachedEndpoints(authorityInfo, userPrincipalName); found {
|
||||
return endpoints, nil
|
||||
}
|
||||
|
||||
endpoint, err := m.openIDConfigurationEndpoint(ctx, authorityInfo, userPrincipalName)
|
||||
if err != nil {
|
||||
return authority.Endpoints{}, err
|
||||
}
|
||||
|
||||
resp, err := m.rest.Authority().GetTenantDiscoveryResponse(ctx, endpoint)
|
||||
if err != nil {
|
||||
return authority.Endpoints{}, err
|
||||
}
|
||||
if err := resp.Validate(); err != nil {
|
||||
return authority.Endpoints{}, fmt.Errorf("ResolveEndpoints(): %w", err)
|
||||
}
|
||||
|
||||
tenant := authorityInfo.Tenant
|
||||
|
||||
endpoints := authority.NewEndpoints(
|
||||
strings.Replace(resp.AuthorizationEndpoint, "{tenant}", tenant, -1),
|
||||
strings.Replace(resp.TokenEndpoint, "{tenant}", tenant, -1),
|
||||
strings.Replace(resp.Issuer, "{tenant}", tenant, -1),
|
||||
authorityInfo.Host)
|
||||
|
||||
m.addCachedEndpoints(authorityInfo, userPrincipalName, endpoints)
|
||||
|
||||
return endpoints, nil
|
||||
}
|
||||
|
||||
// cachedEndpoints returns a the cached endpoints if they exists. If not, we return false.
|
||||
func (m *authorityEndpoint) cachedEndpoints(authorityInfo authority.Info, userPrincipalName string) (authority.Endpoints, bool) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
if cacheEntry, ok := m.cache[authorityInfo.CanonicalAuthorityURI]; ok {
|
||||
if authorityInfo.AuthorityType == ADFS {
|
||||
domain, err := adfsDomainFromUpn(userPrincipalName)
|
||||
if err == nil {
|
||||
if _, ok := cacheEntry.ValidForDomainsInList[domain]; ok {
|
||||
return cacheEntry.Endpoints, true
|
||||
}
|
||||
}
|
||||
}
|
||||
return cacheEntry.Endpoints, true
|
||||
}
|
||||
return authority.Endpoints{}, false
|
||||
}
|
||||
|
||||
func (m *authorityEndpoint) addCachedEndpoints(authorityInfo authority.Info, userPrincipalName string, endpoints authority.Endpoints) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
updatedCacheEntry := createcacheEntry(endpoints)
|
||||
|
||||
if authorityInfo.AuthorityType == ADFS {
|
||||
// Since we're here, we've made a call to the backend. We want to ensure we're caching
|
||||
// the latest values from the server.
|
||||
if cacheEntry, ok := m.cache[authorityInfo.CanonicalAuthorityURI]; ok {
|
||||
for k := range cacheEntry.ValidForDomainsInList {
|
||||
updatedCacheEntry.ValidForDomainsInList[k] = true
|
||||
}
|
||||
}
|
||||
domain, err := adfsDomainFromUpn(userPrincipalName)
|
||||
if err == nil {
|
||||
updatedCacheEntry.ValidForDomainsInList[domain] = true
|
||||
}
|
||||
}
|
||||
|
||||
m.cache[authorityInfo.CanonicalAuthorityURI] = updatedCacheEntry
|
||||
}
|
||||
|
||||
func (m *authorityEndpoint) openIDConfigurationEndpoint(ctx context.Context, authorityInfo authority.Info, userPrincipalName string) (string, error) {
|
||||
if authorityInfo.Tenant == "adfs" {
|
||||
return fmt.Sprintf("https://%s/adfs/.well-known/openid-configuration", authorityInfo.Host), nil
|
||||
} else if authorityInfo.ValidateAuthority && !authority.TrustedHost(authorityInfo.Host) {
|
||||
resp, err := m.rest.Authority().AADInstanceDiscovery(ctx, authorityInfo)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return resp.TenantDiscoveryEndpoint, nil
|
||||
} else if authorityInfo.Region != "" {
|
||||
resp, err := m.rest.Authority().AADInstanceDiscovery(ctx, authorityInfo)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return resp.TenantDiscoveryEndpoint, nil
|
||||
|
||||
}
|
||||
|
||||
return authorityInfo.CanonicalAuthorityURI + "v2.0/.well-known/openid-configuration", nil
|
||||
}
|
||||
|
||||
func adfsDomainFromUpn(userPrincipalName string) (string, error) {
|
||||
parts := strings.Split(userPrincipalName, "@")
|
||||
if len(parts) < 2 {
|
||||
return "", errors.New("no @ present in user principal name")
|
||||
}
|
||||
return parts[1], nil
|
||||
}
|
52
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/options/options.go
generated
vendored
Normal file
52
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/options/options.go
generated
vendored
Normal file
|
@ -0,0 +1,52 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
package options
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// CallOption implements an optional argument to a method call. See
|
||||
// https://blog.devgenius.io/go-call-option-that-can-be-used-with-multiple-methods-6c81734f3dbe
|
||||
// for an explanation of the usage pattern.
|
||||
type CallOption interface {
|
||||
Do(any) error
|
||||
callOption()
|
||||
}
|
||||
|
||||
// ApplyOptions applies all the callOptions to options. options must be a pointer to a struct and
|
||||
// callOptions must be a list of objects that implement CallOption.
|
||||
func ApplyOptions[O, C any](options O, callOptions []C) error {
|
||||
for _, o := range callOptions {
|
||||
if t, ok := any(o).(CallOption); !ok {
|
||||
return fmt.Errorf("unexpected option type %T", o)
|
||||
} else if err := t.Do(options); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewCallOption returns a new CallOption whose Do() method calls function "f".
|
||||
func NewCallOption(f func(any) error) CallOption {
|
||||
if f == nil {
|
||||
// This isn't a practical concern because only an MSAL maintainer can get
|
||||
// us here, by implementing a do-nothing option. But if someone does that,
|
||||
// the below ensures the method invoked with the option returns an error.
|
||||
return callOption(func(any) error {
|
||||
return errors.New("invalid option: missing implementation")
|
||||
})
|
||||
}
|
||||
return callOption(f)
|
||||
}
|
||||
|
||||
// callOption is an adapter for a function to a CallOption
|
||||
type callOption func(any) error
|
||||
|
||||
func (c callOption) Do(a any) error {
|
||||
return c(a)
|
||||
}
|
||||
|
||||
func (callOption) callOption() {}
|
71
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/shared/shared.go
generated
vendored
Normal file
71
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/shared/shared.go
generated
vendored
Normal file
|
@ -0,0 +1,71 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
package shared
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// CacheKeySeparator is used in creating the keys of the cache.
|
||||
CacheKeySeparator = "-"
|
||||
)
|
||||
|
||||
type Account struct {
|
||||
HomeAccountID string `json:"home_account_id,omitempty"`
|
||||
Environment string `json:"environment,omitempty"`
|
||||
Realm string `json:"realm,omitempty"`
|
||||
LocalAccountID string `json:"local_account_id,omitempty"`
|
||||
AuthorityType string `json:"authority_type,omitempty"`
|
||||
PreferredUsername string `json:"username,omitempty"`
|
||||
GivenName string `json:"given_name,omitempty"`
|
||||
FamilyName string `json:"family_name,omitempty"`
|
||||
MiddleName string `json:"middle_name,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
AlternativeID string `json:"alternative_account_id,omitempty"`
|
||||
RawClientInfo string `json:"client_info,omitempty"`
|
||||
UserAssertionHash string `json:"user_assertion_hash,omitempty"`
|
||||
|
||||
AdditionalFields map[string]interface{}
|
||||
}
|
||||
|
||||
// NewAccount creates an account.
|
||||
func NewAccount(homeAccountID, env, realm, localAccountID, authorityType, username string) Account {
|
||||
return Account{
|
||||
HomeAccountID: homeAccountID,
|
||||
Environment: env,
|
||||
Realm: realm,
|
||||
LocalAccountID: localAccountID,
|
||||
AuthorityType: authorityType,
|
||||
PreferredUsername: username,
|
||||
}
|
||||
}
|
||||
|
||||
// Key creates the key for storing accounts in the cache.
|
||||
func (acc Account) Key() string {
|
||||
return strings.Join([]string{acc.HomeAccountID, acc.Environment, acc.Realm}, CacheKeySeparator)
|
||||
}
|
||||
|
||||
// IsZero checks the zero value of account.
|
||||
func (acc Account) IsZero() bool {
|
||||
v := reflect.ValueOf(acc)
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
field := v.Field(i)
|
||||
if !field.IsZero() {
|
||||
switch field.Kind() {
|
||||
case reflect.Map, reflect.Slice:
|
||||
if field.Len() == 0 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// DefaultClient is our default shared HTTP client.
|
||||
var DefaultClient = &http.Client{}
|
8
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/version/version.go
generated
vendored
Normal file
8
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/version/version.go
generated
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
// Package version keeps the version number of the client package.
|
||||
package version
|
||||
|
||||
// Version is the version of this client package that is communicated to the server.
|
||||
const Version = "0.8.1"
|
716
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/public/public.go
generated
vendored
Normal file
716
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/public/public.go
generated
vendored
Normal file
|
@ -0,0 +1,716 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
/*
|
||||
Package public provides a client for authentication of "public" applications. A "public"
|
||||
application is defined as an app that runs on client devices (android, ios, windows, linux, ...).
|
||||
These devices are "untrusted" and access resources via web APIs that must authenticate.
|
||||
*/
|
||||
package public
|
||||
|
||||
/*
|
||||
Design note:
|
||||
|
||||
public.Client uses client.Base as an embedded type. client.Base 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.
|
||||
*/
|
||||
|
||||
// TODO(msal): This should have example code for each method on client using Go's example doc framework.
|
||||
// base usage details should be includee in the package documentation.
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
"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/local"
|
||||
"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"
|
||||
"github.com/google/uuid"
|
||||
"github.com/pkg/browser"
|
||||
)
|
||||
|
||||
// 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
|
||||
|
||||
// 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
|
||||
|
||||
disableInstanceDiscovery bool
|
||||
}
|
||||
|
||||
func (p *Options) validate() error {
|
||||
u, err := url.Parse(p.Authority)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Authority options cannot be URL parsed: %w", err)
|
||||
}
|
||||
if u.Scheme != "https" {
|
||||
return fmt.Errorf("Authority(%s) did not start with https://", u.String())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Option is an optional argument to the New constructor.
|
||||
type Option func(o *Options)
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
// WithCache allows you to set some type of cache for storing authentication tokens.
|
||||
func WithCache(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
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
// Client is a representation of authentication client for public applications as defined in the
|
||||
// package doc. For more information, visit https://docs.microsoft.com/azure/active-directory/develop/msal-client-applications.
|
||||
type Client struct {
|
||||
base base.Client
|
||||
}
|
||||
|
||||
// New is the constructor for Client.
|
||||
func New(clientID string, options ...Option) (Client, error) {
|
||||
opts := Options{
|
||||
Authority: base.AuthorityPublicCloud,
|
||||
HTTPClient: shared.DefaultClient,
|
||||
}
|
||||
|
||||
for _, o := range options {
|
||||
o(&opts)
|
||||
}
|
||||
if err := opts.validate(); err != nil {
|
||||
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))
|
||||
if err != nil {
|
||||
return Client{}, err
|
||||
}
|
||||
return Client{base}, nil
|
||||
}
|
||||
|
||||
// createAuthCodeURLOptions contains options for CreateAuthCodeURL
|
||||
type createAuthCodeURLOptions struct {
|
||||
claims, loginHint, tenantID, domainHint string
|
||||
}
|
||||
|
||||
// CreateAuthCodeURLOption is implemented by options for CreateAuthCodeURL
|
||||
type CreateAuthCodeURLOption interface {
|
||||
createAuthCodeURLOption()
|
||||
}
|
||||
|
||||
// CreateAuthCodeURL 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{}
|
||||
if err := options.ApplyOptions(&o, opts); err != nil {
|
||||
return "", err
|
||||
}
|
||||
ap, err := pca.base.AuthParams.WithTenant(o.tenantID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
ap.Claims = o.claims
|
||||
ap.LoginHint = o.loginHint
|
||||
ap.DomainHint = o.domainHint
|
||||
return pca.base.AuthCodeURL(ctx, clientID, redirectURI, scopes, ap)
|
||||
}
|
||||
|
||||
// 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
|
||||
AcquireByDeviceCodeOption
|
||||
AcquireByUsernamePasswordOption
|
||||
AcquireInteractiveOption
|
||||
AcquireSilentOption
|
||||
CreateAuthCodeURLOption
|
||||
options.CallOption
|
||||
} {
|
||||
return struct {
|
||||
AcquireByAuthCodeOption
|
||||
AcquireByDeviceCodeOption
|
||||
AcquireByUsernamePasswordOption
|
||||
AcquireInteractiveOption
|
||||
AcquireSilentOption
|
||||
CreateAuthCodeURLOption
|
||||
options.CallOption
|
||||
}{
|
||||
CallOption: options.NewCallOption(
|
||||
func(a any) error {
|
||||
switch t := a.(type) {
|
||||
case *AcquireTokenByAuthCodeOptions:
|
||||
t.claims = claims
|
||||
case *acquireTokenByDeviceCodeOptions:
|
||||
t.claims = claims
|
||||
case *acquireTokenByUsernamePasswordOptions:
|
||||
t.claims = claims
|
||||
case *AcquireTokenSilentOptions:
|
||||
t.claims = claims
|
||||
case *createAuthCodeURLOptions:
|
||||
t.claims = claims
|
||||
case *InteractiveAuthOptions:
|
||||
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
|
||||
AcquireByDeviceCodeOption
|
||||
AcquireByUsernamePasswordOption
|
||||
AcquireInteractiveOption
|
||||
AcquireSilentOption
|
||||
CreateAuthCodeURLOption
|
||||
options.CallOption
|
||||
} {
|
||||
return struct {
|
||||
AcquireByAuthCodeOption
|
||||
AcquireByDeviceCodeOption
|
||||
AcquireByUsernamePasswordOption
|
||||
AcquireInteractiveOption
|
||||
AcquireSilentOption
|
||||
CreateAuthCodeURLOption
|
||||
options.CallOption
|
||||
}{
|
||||
CallOption: options.NewCallOption(
|
||||
func(a any) error {
|
||||
switch t := a.(type) {
|
||||
case *AcquireTokenByAuthCodeOptions:
|
||||
t.tenantID = tenantID
|
||||
case *acquireTokenByDeviceCodeOptions:
|
||||
t.tenantID = tenantID
|
||||
case *acquireTokenByUsernamePasswordOptions:
|
||||
t.tenantID = tenantID
|
||||
case *AcquireTokenSilentOptions:
|
||||
t.tenantID = tenantID
|
||||
case *createAuthCodeURLOptions:
|
||||
t.tenantID = tenantID
|
||||
case *InteractiveAuthOptions:
|
||||
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 (pca Client) AcquireTokenSilent(ctx context.Context, scopes []string, opts ...AcquireSilentOption) (AuthResult, error) {
|
||||
o := AcquireTokenSilentOptions{}
|
||||
if err := options.ApplyOptions(&o, opts); err != nil {
|
||||
return AuthResult{}, err
|
||||
}
|
||||
|
||||
silentParameters := base.AcquireTokenSilentParameters{
|
||||
Scopes: scopes,
|
||||
Account: o.Account,
|
||||
Claims: o.claims,
|
||||
RequestType: accesstokens.ATPublic,
|
||||
IsAppCache: false,
|
||||
TenantID: o.tenantID,
|
||||
}
|
||||
|
||||
return pca.base.AcquireTokenSilent(ctx, silentParameters)
|
||||
}
|
||||
|
||||
// acquireTokenByUsernamePasswordOptions contains optional configuration for AcquireTokenByUsernamePassword
|
||||
type acquireTokenByUsernamePasswordOptions struct {
|
||||
claims, tenantID string
|
||||
}
|
||||
|
||||
// AcquireByUsernamePasswordOption is implemented by options for AcquireTokenByUsernamePassword
|
||||
type AcquireByUsernamePasswordOption interface {
|
||||
acquireByUsernamePasswordOption()
|
||||
}
|
||||
|
||||
// AcquireTokenByUsernamePassword acquires a security token from the authority, via Username/Password Authentication.
|
||||
// NOTE: this flow is NOT recommended.
|
||||
//
|
||||
// Options: [WithClaims], [WithTenantID]
|
||||
func (pca Client) AcquireTokenByUsernamePassword(ctx context.Context, scopes []string, username, password string, opts ...AcquireByUsernamePasswordOption) (AuthResult, error) {
|
||||
o := acquireTokenByUsernamePasswordOptions{}
|
||||
if err := options.ApplyOptions(&o, opts); err != nil {
|
||||
return AuthResult{}, err
|
||||
}
|
||||
authParams, err := pca.base.AuthParams.WithTenant(o.tenantID)
|
||||
if err != nil {
|
||||
return AuthResult{}, err
|
||||
}
|
||||
authParams.Scopes = scopes
|
||||
authParams.AuthorizationType = authority.ATUsernamePassword
|
||||
authParams.Claims = o.claims
|
||||
authParams.Username = username
|
||||
authParams.Password = password
|
||||
|
||||
token, err := pca.base.Token.UsernamePassword(ctx, authParams)
|
||||
if err != nil {
|
||||
return AuthResult{}, err
|
||||
}
|
||||
return pca.base.AuthResultFromToken(ctx, authParams, token, true)
|
||||
}
|
||||
|
||||
type DeviceCodeResult = accesstokens.DeviceCodeResult
|
||||
|
||||
// DeviceCode provides the results of the device code flows first stage (containing the code)
|
||||
// that must be entered on the second device and provides a method to retrieve the AuthenticationResult
|
||||
// once that code has been entered and verified.
|
||||
type DeviceCode struct {
|
||||
// Result holds the information about the device code (such as the code).
|
||||
Result DeviceCodeResult
|
||||
|
||||
authParams authority.AuthParams
|
||||
client Client
|
||||
dc oauth.DeviceCode
|
||||
}
|
||||
|
||||
// AuthenticationResult retreives the AuthenticationResult once the user enters the code
|
||||
// on the second device. Until then it blocks until the .AcquireTokenByDeviceCode() context
|
||||
// is cancelled or the token expires.
|
||||
func (d DeviceCode) AuthenticationResult(ctx context.Context) (AuthResult, error) {
|
||||
token, err := d.dc.Token(ctx)
|
||||
if err != nil {
|
||||
return AuthResult{}, err
|
||||
}
|
||||
return d.client.base.AuthResultFromToken(ctx, d.authParams, token, true)
|
||||
}
|
||||
|
||||
// acquireTokenByDeviceCodeOptions contains optional configuration for AcquireTokenByDeviceCode
|
||||
type acquireTokenByDeviceCodeOptions struct {
|
||||
claims, tenantID string
|
||||
}
|
||||
|
||||
// AcquireByDeviceCodeOption is implemented by options for AcquireTokenByDeviceCode
|
||||
type AcquireByDeviceCodeOption interface {
|
||||
acquireByDeviceCodeOptions()
|
||||
}
|
||||
|
||||
// AcquireTokenByDeviceCode acquires a security token from the authority, by acquiring a device code and using that to acquire the token.
|
||||
// Users need to create an AcquireTokenDeviceCodeParameters instance and pass it in.
|
||||
//
|
||||
// Options: [WithClaims], [WithTenantID]
|
||||
func (pca Client) AcquireTokenByDeviceCode(ctx context.Context, scopes []string, opts ...AcquireByDeviceCodeOption) (DeviceCode, error) {
|
||||
o := acquireTokenByDeviceCodeOptions{}
|
||||
if err := options.ApplyOptions(&o, opts); err != nil {
|
||||
return DeviceCode{}, err
|
||||
}
|
||||
authParams, err := pca.base.AuthParams.WithTenant(o.tenantID)
|
||||
if err != nil {
|
||||
return DeviceCode{}, err
|
||||
}
|
||||
authParams.Scopes = scopes
|
||||
authParams.AuthorizationType = authority.ATDeviceCode
|
||||
authParams.Claims = o.claims
|
||||
|
||||
dc, err := pca.base.Token.DeviceCode(ctx, authParams)
|
||||
if err != nil {
|
||||
return DeviceCode{}, err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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 code 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 (pca 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.ATPublic,
|
||||
RedirectURI: redirectURI,
|
||||
TenantID: o.tenantID,
|
||||
}
|
||||
|
||||
return pca.base.AcquireTokenByAuthCode(ctx, params)
|
||||
}
|
||||
|
||||
// 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()
|
||||
}
|
||||
|
||||
// RemoveAccount signs the account out and forgets account from token cache.
|
||||
func (pca Client) RemoveAccount(account Account) error {
|
||||
pca.base.RemoveAccount(account)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// AcquireInteractiveOption is implemented by options for AcquireTokenInteractive
|
||||
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
|
||||
options.CallOption
|
||||
} {
|
||||
return struct {
|
||||
AcquireInteractiveOption
|
||||
CreateAuthCodeURLOption
|
||||
options.CallOption
|
||||
}{
|
||||
CallOption: options.NewCallOption(
|
||||
func(a any) error {
|
||||
switch t := a.(type) {
|
||||
case *createAuthCodeURLOptions:
|
||||
t.loginHint = username
|
||||
case *InteractiveAuthOptions:
|
||||
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 {
|
||||
AcquireInteractiveOption
|
||||
CreateAuthCodeURLOption
|
||||
options.CallOption
|
||||
} {
|
||||
return struct {
|
||||
AcquireInteractiveOption
|
||||
CreateAuthCodeURLOption
|
||||
options.CallOption
|
||||
}{
|
||||
CallOption: options.NewCallOption(
|
||||
func(a any) error {
|
||||
switch t := a.(type) {
|
||||
case *createAuthCodeURLOptions:
|
||||
t.domainHint = domain
|
||||
case *InteractiveAuthOptions:
|
||||
t.domainHint = domain
|
||||
default:
|
||||
return fmt.Errorf("unexpected options type %T", a)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
// WithRedirectURI uses the specified redirect URI for interactive auth.
|
||||
func WithRedirectURI(redirectURI string) interface {
|
||||
AcquireInteractiveOption
|
||||
options.CallOption
|
||||
} {
|
||||
return struct {
|
||||
AcquireInteractiveOption
|
||||
options.CallOption
|
||||
}{
|
||||
CallOption: options.NewCallOption(
|
||||
func(a any) error {
|
||||
switch t := a.(type) {
|
||||
case *InteractiveAuthOptions:
|
||||
t.RedirectURI = redirectURI
|
||||
default:
|
||||
return fmt.Errorf("unexpected options type %T", a)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
// AcquireTokenInteractive acquires a security token from the authority using the default web browser to select the account.
|
||||
// https://docs.microsoft.com/en-us/azure/active-directory/develop/msal-authentication-flows#interactive-and-non-interactive-authentication
|
||||
//
|
||||
// Options: [WithDomainHint], [WithLoginHint], [WithRedirectURI], [WithTenantID]
|
||||
func (pca Client) AcquireTokenInteractive(ctx context.Context, scopes []string, opts ...AcquireInteractiveOption) (AuthResult, error) {
|
||||
o := InteractiveAuthOptions{}
|
||||
if err := options.ApplyOptions(&o, opts); err != nil {
|
||||
return AuthResult{}, err
|
||||
}
|
||||
// the code verifier is a random 32-byte sequence that's been base-64 encoded without padding.
|
||||
// it's used to prevent MitM attacks during auth code flow, see https://tools.ietf.org/html/rfc7636
|
||||
cv, challenge, err := codeVerifier()
|
||||
if err != nil {
|
||||
return AuthResult{}, err
|
||||
}
|
||||
var redirectURL *url.URL
|
||||
if o.RedirectURI != "" {
|
||||
redirectURL, err = url.Parse(o.RedirectURI)
|
||||
if err != nil {
|
||||
return AuthResult{}, err
|
||||
}
|
||||
}
|
||||
authParams, err := pca.base.AuthParams.WithTenant(o.tenantID)
|
||||
if err != nil {
|
||||
return AuthResult{}, err
|
||||
}
|
||||
authParams.Scopes = scopes
|
||||
authParams.AuthorizationType = authority.ATInteractive
|
||||
authParams.Claims = o.claims
|
||||
authParams.CodeChallenge = challenge
|
||||
authParams.CodeChallengeMethod = "S256"
|
||||
authParams.LoginHint = o.loginHint
|
||||
authParams.DomainHint = o.domainHint
|
||||
authParams.State = uuid.New().String()
|
||||
authParams.Prompt = "select_account"
|
||||
res, err := pca.browserLogin(ctx, redirectURL, authParams)
|
||||
if err != nil {
|
||||
return AuthResult{}, err
|
||||
}
|
||||
authParams.Redirecturi = res.redirectURI
|
||||
|
||||
req, err := accesstokens.NewCodeChallengeRequest(authParams, accesstokens.ATPublic, nil, res.authCode, cv)
|
||||
if err != nil {
|
||||
return AuthResult{}, err
|
||||
}
|
||||
|
||||
token, err := pca.base.Token.AuthCode(ctx, req)
|
||||
if err != nil {
|
||||
return AuthResult{}, err
|
||||
}
|
||||
|
||||
return pca.base.AuthResultFromToken(ctx, authParams, token, true)
|
||||
}
|
||||
|
||||
type interactiveAuthResult struct {
|
||||
authCode string
|
||||
redirectURI string
|
||||
}
|
||||
|
||||
// provides a test hook to simulate opening a browser
|
||||
var browserOpenURL = func(authURL string) error {
|
||||
return browser.OpenURL(authURL)
|
||||
}
|
||||
|
||||
// parses the port number from the provided URL.
|
||||
// returns 0 if nil or no port is specified.
|
||||
func parsePort(u *url.URL) (int, error) {
|
||||
if u == nil {
|
||||
return 0, nil
|
||||
}
|
||||
p := u.Port()
|
||||
if p == "" {
|
||||
return 0, nil
|
||||
}
|
||||
return strconv.Atoi(p)
|
||||
}
|
||||
|
||||
// browserLogin launches the system browser for interactive login
|
||||
func (pca Client) browserLogin(ctx context.Context, redirectURI *url.URL, params authority.AuthParams) (interactiveAuthResult, error) {
|
||||
// start local redirect server so login can call us back
|
||||
port, err := parsePort(redirectURI)
|
||||
if err != nil {
|
||||
return interactiveAuthResult{}, err
|
||||
}
|
||||
srv, err := local.New(params.State, port)
|
||||
if err != nil {
|
||||
return interactiveAuthResult{}, err
|
||||
}
|
||||
defer srv.Shutdown()
|
||||
params.Scopes = accesstokens.AppendDefaultScopes(params)
|
||||
authURL, err := pca.base.AuthCodeURL(ctx, params.ClientID, srv.Addr, params.Scopes, params)
|
||||
if err != nil {
|
||||
return interactiveAuthResult{}, err
|
||||
}
|
||||
// open browser window so user can select credentials
|
||||
if err := browserOpenURL(authURL); err != nil {
|
||||
return interactiveAuthResult{}, err
|
||||
}
|
||||
// now wait until the logic calls us back
|
||||
res := srv.Result(ctx)
|
||||
if res.Err != nil {
|
||||
return interactiveAuthResult{}, res.Err
|
||||
}
|
||||
return interactiveAuthResult{
|
||||
authCode: res.Code,
|
||||
redirectURI: srv.Addr,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// creates a code verifier string along with its SHA256 hash which
|
||||
// is used as the challenge when requesting an auth code.
|
||||
// used in interactive auth flow for PKCE.
|
||||
func codeVerifier() (codeVerifier string, challenge string, err error) {
|
||||
cvBytes := make([]byte, 32)
|
||||
if _, err = rand.Read(cvBytes); err != nil {
|
||||
return
|
||||
}
|
||||
codeVerifier = base64.RawURLEncoding.EncodeToString(cvBytes)
|
||||
// for PKCE, create a hash of the code verifier
|
||||
cvh := sha256.Sum256([]byte(codeVerifier))
|
||||
challenge = base64.RawURLEncoding.EncodeToString(cvh[:])
|
||||
return
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue