mirror of
https://github.com/ceph/ceph-csi.git
synced 2025-06-14 18:53:35 +00:00
rebase: Azure key vault module dependency update
This commit adds the Azure SDK for Azure key vault KMS integration to the Ceph CSI driver. Signed-off-by: Praveen M <m.praveen@ibm.com>
This commit is contained in:
477
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/base/base.go
generated
vendored
Normal file
477
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/base/base.go
generated
vendored
Normal file
@ -0,0 +1,477 @@
|
||||
// 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"
|
||||
"sync"
|
||||
"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 production it's a *storage.Manager or *storage.PartitionedManager.
|
||||
type manager interface {
|
||||
cache.Serializer
|
||||
Read(context.Context, authority.AuthParams) (storage.TokenResponse, error)
|
||||
Write(authority.AuthParams, accesstokens.TokenResponse) (shared.Account, error)
|
||||
}
|
||||
|
||||
// accountManager is a manager that also caches accounts. In production it's a *storage.Manager.
|
||||
type accountManager interface {
|
||||
manager
|
||||
AllAccounts() []shared.Account
|
||||
Account(homeAccountID string) shared.Account
|
||||
RemoveAccount(account shared.Account, clientID string)
|
||||
}
|
||||
|
||||
// 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
|
||||
AuthnScheme authority.AuthenticationScheme
|
||||
}
|
||||
|
||||
// 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 accountManager // *storage.Manager or fakeManager in tests
|
||||
// pmanager is a partitioned cache for OBO authentication. *storage.PartitionedManager or fakeManager in tests
|
||||
pmanager manager
|
||||
|
||||
AuthParams authority.AuthParams // DO NOT EVER MAKE THIS A POINTER! See "Note" in New().
|
||||
cacheAccessor cache.ExportReplace
|
||||
cacheAccessorMu *sync.RWMutex
|
||||
}
|
||||
|
||||
// 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,
|
||||
cacheAccessorMu: &sync.RWMutex{},
|
||||
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) {
|
||||
ar := AuthResult{}
|
||||
// when tenant == "", the caller didn't specify a tenant and WithTenant will choose the client's configured tenant
|
||||
tenant := silent.TenantID
|
||||
authParams, err := b.AuthParams.WithTenant(tenant)
|
||||
if err != nil {
|
||||
return ar, err
|
||||
}
|
||||
authParams.Scopes = silent.Scopes
|
||||
authParams.HomeAccountID = silent.Account.HomeAccountID
|
||||
authParams.AuthorizationType = silent.AuthorizationType
|
||||
authParams.Claims = silent.Claims
|
||||
authParams.UserAssertion = silent.UserAssertion
|
||||
if silent.AuthnScheme != nil {
|
||||
authParams.AuthnScheme = silent.AuthnScheme
|
||||
}
|
||||
|
||||
m := b.pmanager
|
||||
if authParams.AuthorizationType != authority.ATOnBehalfOf {
|
||||
authParams.AuthorizationType = authority.ATRefreshToken
|
||||
m = b.manager
|
||||
}
|
||||
if b.cacheAccessor != nil {
|
||||
key := authParams.CacheKey(silent.IsAppCache)
|
||||
b.cacheAccessorMu.RLock()
|
||||
err = b.cacheAccessor.Replace(ctx, m, cache.ReplaceHints{PartitionKey: key})
|
||||
b.cacheAccessorMu.RUnlock()
|
||||
}
|
||||
if err != nil {
|
||||
return ar, err
|
||||
}
|
||||
storageTokenResponse, err := m.Read(ctx, authParams)
|
||||
if err != nil {
|
||||
return ar, err
|
||||
}
|
||||
|
||||
// ignore cached access tokens when given claims
|
||||
if silent.Claims == "" {
|
||||
ar, err = AuthResultFromStorage(storageTokenResponse)
|
||||
if err == nil {
|
||||
ar.AccessToken, err = authParams.AuthnScheme.FormatAccessToken(ar.AccessToken)
|
||||
return ar, err
|
||||
}
|
||||
}
|
||||
|
||||
// redeem a cached refresh token, if available
|
||||
if reflect.ValueOf(storageTokenResponse.RefreshToken).IsZero() {
|
||||
return ar, errors.New("no token found")
|
||||
}
|
||||
var cc *accesstokens.Credential
|
||||
if silent.RequestType == accesstokens.ATConfidential {
|
||||
cc = silent.Credential
|
||||
}
|
||||
token, err := b.Token.Refresh(ctx, silent.RequestType, authParams, cc, storageTokenResponse.RefreshToken)
|
||||
if err != nil {
|
||||
return ar, 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 m manager = b.manager
|
||||
if authParams.AuthorizationType == authority.ATOnBehalfOf {
|
||||
m = b.pmanager
|
||||
}
|
||||
key := token.CacheKey(authParams)
|
||||
if b.cacheAccessor != nil {
|
||||
b.cacheAccessorMu.Lock()
|
||||
defer b.cacheAccessorMu.Unlock()
|
||||
err := b.cacheAccessor.Replace(ctx, m, cache.ReplaceHints{PartitionKey: key})
|
||||
if err != nil {
|
||||
return AuthResult{}, err
|
||||
}
|
||||
}
|
||||
account, err := m.Write(authParams, token)
|
||||
if err != nil {
|
||||
return AuthResult{}, err
|
||||
}
|
||||
ar, err := NewAuthResult(token, account)
|
||||
if err == nil && b.cacheAccessor != nil {
|
||||
err = b.cacheAccessor.Export(ctx, b.manager, cache.ExportHints{PartitionKey: key})
|
||||
}
|
||||
if err != nil {
|
||||
return AuthResult{}, err
|
||||
}
|
||||
|
||||
ar.AccessToken, err = authParams.AuthnScheme.FormatAccessToken(ar.AccessToken)
|
||||
return ar, err
|
||||
}
|
||||
|
||||
func (b Client) AllAccounts(ctx context.Context) ([]shared.Account, error) {
|
||||
if b.cacheAccessor != nil {
|
||||
b.cacheAccessorMu.RLock()
|
||||
defer b.cacheAccessorMu.RUnlock()
|
||||
key := b.AuthParams.CacheKey(false)
|
||||
err := b.cacheAccessor.Replace(ctx, b.manager, cache.ReplaceHints{PartitionKey: key})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return b.manager.AllAccounts(), nil
|
||||
}
|
||||
|
||||
func (b Client) Account(ctx context.Context, homeAccountID string) (shared.Account, error) {
|
||||
if b.cacheAccessor != nil {
|
||||
b.cacheAccessorMu.RLock()
|
||||
defer b.cacheAccessorMu.RUnlock()
|
||||
authParams := b.AuthParams // This is a copy, as we don't have a pointer receiver and .AuthParams is not a pointer.
|
||||
authParams.AuthorizationType = authority.AccountByID
|
||||
authParams.HomeAccountID = homeAccountID
|
||||
key := b.AuthParams.CacheKey(false)
|
||||
err := b.cacheAccessor.Replace(ctx, b.manager, cache.ReplaceHints{PartitionKey: key})
|
||||
if err != nil {
|
||||
return shared.Account{}, err
|
||||
}
|
||||
}
|
||||
return b.manager.Account(homeAccountID), nil
|
||||
}
|
||||
|
||||
// RemoveAccount removes all the ATs, RTs and IDTs from the cache associated with this account.
|
||||
func (b Client) RemoveAccount(ctx context.Context, account shared.Account) error {
|
||||
if b.cacheAccessor == nil {
|
||||
b.manager.RemoveAccount(account, b.AuthParams.ClientID)
|
||||
return nil
|
||||
}
|
||||
b.cacheAccessorMu.Lock()
|
||||
defer b.cacheAccessorMu.Unlock()
|
||||
key := b.AuthParams.CacheKey(false)
|
||||
err := b.cacheAccessor.Replace(ctx, b.manager, cache.ReplaceHints{PartitionKey: key})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.manager.RemoveAccount(account, b.AuthParams.ClientID)
|
||||
return b.cacheAccessor.Export(ctx, b.manager, cache.ExportHints{PartitionKey: key})
|
||||
}
|
213
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/base/internal/storage/items.go
generated
vendored
Normal file
213
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/base/internal/storage/items.go
generated
vendored
Normal file
@ -0,0 +1,213 @@
|
||||
// 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/oauth/ops/authority"
|
||||
"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"`
|
||||
TokenType string `json:"token_type,omitempty"`
|
||||
AuthnSchemeKeyID string `json:"keyid,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, tokenType, authnSchemeKeyID 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()},
|
||||
TokenType: tokenType,
|
||||
AuthnSchemeKeyID: authnSchemeKeyID,
|
||||
}
|
||||
}
|
||||
|
||||
// Key outputs the key that can be used to uniquely look up this entry in a map.
|
||||
func (a AccessToken) Key() string {
|
||||
key := strings.Join(
|
||||
[]string{a.HomeAccountID, a.Environment, a.CredentialType, a.ClientID, a.Realm, a.Scopes},
|
||||
shared.CacheKeySeparator,
|
||||
)
|
||||
// add token type to key for new access tokens types. skip for bearer token type to
|
||||
// preserve fwd and back compat between a common cache and msal clients
|
||||
if !strings.EqualFold(a.TokenType, authority.AccessTokenTypeBearer) {
|
||||
key = strings.Join([]string{key, a.TokenType}, shared.CacheKeySeparator)
|
||||
}
|
||||
return strings.ToLower(key)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
key := strings.Join(
|
||||
[]string{id.HomeAccountID, id.Environment, id.CredentialType, id.ClientID, id.Realm},
|
||||
shared.CacheKeySeparator,
|
||||
)
|
||||
return strings.ToLower(key)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
key := strings.Join(
|
||||
[]string{"AppMetaData", a.Environment, a.ClientID},
|
||||
shared.CacheKeySeparator,
|
||||
)
|
||||
return strings.ToLower(key)
|
||||
}
|
442
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/base/internal/storage/partitioned_storage.go
generated
vendored
Normal file
442
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/base/internal/storage/partitioned_storage.go
generated
vendored
Normal file
@ -0,0 +1,442 @@
|
||||
// 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
|
||||
authnSchemeKeyID := authParameters.AuthnScheme.KeyID()
|
||||
tokenType := authParameters.AuthnScheme.AccessTokenType()
|
||||
|
||||
// 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, tokenType, authnSchemeKeyID)
|
||||
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.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()
|
||||
authnSchemeKeyID := authParameters.AuthnScheme.KeyID()
|
||||
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,
|
||||
tokenResponse.TokenType,
|
||||
authnSchemeKeyID,
|
||||
)
|
||||
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, tokenType, authnSchemeKeyID 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 at.TokenType == tokenType && at.AuthnSchemeKeyID == authnSchemeKeyID {
|
||||
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
|
||||
}
|
583
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/base/internal/storage/storage.go
generated
vendored
Normal file
583
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/base/internal/storage/storage.go
generated
vendored
Normal file
@ -0,0 +1,583 @@
|
||||
// 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)
|
||||
}
|
||||
|
||||
// needsUpgrade returns true if the given key follows the v1.0 schema i.e.,
|
||||
// it contains an uppercase character (v1.1+ keys are all lowercase)
|
||||
func needsUpgrade(key string) bool {
|
||||
for _, r := range key {
|
||||
if 'A' <= r && r <= 'Z' {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// upgrade a v1.0 cache item by adding a v1.1+ item having the same value and deleting
|
||||
// the v1.0 item. Callers must hold an exclusive lock on m.
|
||||
func upgrade[T any](m map[string]T, k string) T {
|
||||
v1_1Key := strings.ToLower(k)
|
||||
v, ok := m[k]
|
||||
if !ok {
|
||||
// another goroutine did the upgrade while this one was waiting for the write lock
|
||||
return m[v1_1Key]
|
||||
}
|
||||
if v2, ok := m[v1_1Key]; ok {
|
||||
// cache has an equivalent v1.1+ item, which we prefer because we know it was added
|
||||
// by a newer version of the module and is therefore more likely to remain valid.
|
||||
// The v1.0 item may have expired because only v1.0 or earlier would update it.
|
||||
v = v2
|
||||
} else {
|
||||
// add an equivalent item according to the v1.1 schema
|
||||
m[v1_1Key] = v
|
||||
}
|
||||
delete(m, k)
|
||||
return v
|
||||
}
|
||||
|
||||
// Read reads a storage token from the cache if it exists.
|
||||
func (m *Manager) Read(ctx context.Context, authParameters authority.AuthParams) (TokenResponse, error) {
|
||||
tr := TokenResponse{}
|
||||
homeAccountID := authParameters.HomeAccountID
|
||||
realm := authParameters.AuthorityInfo.Tenant
|
||||
clientID := authParameters.ClientID
|
||||
scopes := authParameters.Scopes
|
||||
authnSchemeKeyID := authParameters.AuthnScheme.KeyID()
|
||||
tokenType := authParameters.AuthnScheme.AccessTokenType()
|
||||
|
||||
// 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, tokenType, authnSchemeKeyID)
|
||||
tr.AccessToken = accessToken
|
||||
|
||||
if homeAccountID == "" {
|
||||
// caller didn't specify a user, so there's no reason to search for an ID or refresh token
|
||||
return tr, nil
|
||||
}
|
||||
// errors returned by read* methods indicate a cache miss and are therefore non-fatal. We continue populating
|
||||
// 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) {
|
||||
homeAccountID := tokenResponse.HomeAccountID()
|
||||
environment := authParameters.AuthorityInfo.Host
|
||||
realm := authParameters.AuthorityInfo.Tenant
|
||||
clientID := authParameters.ClientID
|
||||
target := strings.Join(tokenResponse.GrantedScopes.Slice, scopeSeparator)
|
||||
cachedAt := time.Now()
|
||||
authnSchemeKeyID := authParameters.AuthnScheme.KeyID()
|
||||
|
||||
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,
|
||||
tokenResponse.TokenType,
|
||||
authnSchemeKeyID,
|
||||
)
|
||||
|
||||
// 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, tokenType, authnSchemeKeyID string) AccessToken {
|
||||
m.contractMu.RLock()
|
||||
// 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 k, at := range m.contract.AccessTokens {
|
||||
if at.HomeAccountID == homeID && at.Realm == realm && at.ClientID == clientID {
|
||||
if (strings.EqualFold(at.TokenType, tokenType) && at.AuthnSchemeKeyID == authnSchemeKeyID) || (at.TokenType == "" && (tokenType == "" || tokenType == "Bearer")) {
|
||||
if checkAlias(at.Environment, envAliases) && isMatchingScopes(scopes, at.Scopes) {
|
||||
m.contractMu.RUnlock()
|
||||
if needsUpgrade(k) {
|
||||
m.contractMu.Lock()
|
||||
defer m.contractMu.Unlock()
|
||||
at = upgrade(m.contract.AccessTokens, k)
|
||||
}
|
||||
return at
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
m.contractMu.RUnlock()
|
||||
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()
|
||||
for _, matcher := range matchers {
|
||||
for k, rt := range m.contract.RefreshTokens {
|
||||
if matcher(rt) {
|
||||
m.contractMu.RUnlock()
|
||||
if needsUpgrade(k) {
|
||||
m.contractMu.Lock()
|
||||
defer m.contractMu.Unlock()
|
||||
rt = upgrade(m.contract.RefreshTokens, k)
|
||||
}
|
||||
return rt, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m.contractMu.RUnlock()
|
||||
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()
|
||||
for k, idt := range m.contract.IDTokens {
|
||||
if idt.HomeAccountID == homeID && idt.Realm == realm && idt.ClientID == clientID {
|
||||
if checkAlias(idt.Environment, envAliases) {
|
||||
m.contractMu.RUnlock()
|
||||
if needsUpgrade(k) {
|
||||
m.contractMu.Lock()
|
||||
defer m.contractMu.Unlock()
|
||||
idt = upgrade(m.contract.IDTokens, k)
|
||||
}
|
||||
return idt, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
m.contractMu.RUnlock()
|
||||
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()
|
||||
|
||||
// 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 k, acc := range m.contract.Accounts {
|
||||
if acc.HomeAccountID == homeAccountID && checkAlias(acc.Environment, envAliases) && acc.Realm == realm {
|
||||
m.contractMu.RUnlock()
|
||||
if needsUpgrade(k) {
|
||||
m.contractMu.Lock()
|
||||
defer m.contractMu.Unlock()
|
||||
acc = upgrade(m.contract.Accounts, k)
|
||||
}
|
||||
return acc, nil
|
||||
}
|
||||
}
|
||||
m.contractMu.RUnlock()
|
||||
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()
|
||||
for k, app := range m.contract.AppMetaData {
|
||||
if checkAlias(app.Environment, envAliases) && app.ClientID == clientID {
|
||||
m.contractMu.RUnlock()
|
||||
if needsUpgrade(k) {
|
||||
m.contractMu.Lock()
|
||||
defer m.contractMu.Unlock()
|
||||
app = upgrade(m.contract.AppMetaData, k)
|
||||
}
|
||||
return app, nil
|
||||
}
|
||||
}
|
||||
m.contractMu.RUnlock()
|
||||
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) {
|
||||
m.contractMu.RLock()
|
||||
defer m.contractMu.RUnlock()
|
||||
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
|
||||
}
|
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})
|
||||
}
|
354
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/oauth.go
generated
vendored
Normal file
354
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/oauth.go
generated
vendored
Normal file
@ -0,0 +1,354 @@
|
||||
// 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)
|
||||
}
|
||||
|
||||
// AADInstanceDiscovery attempts to discover a tenant endpoint (used in OIDC auth with an authorization endpoint).
|
||||
// This is done by AAD which allows for aliasing of tenants (windows.sts.net is the same as login.windows.com).
|
||||
func (t *Client) AADInstanceDiscovery(ctx context.Context, authorityInfo authority.Info) (authority.InstanceDiscoveryResponse, error) {
|
||||
return t.Authority.AADInstanceDiscovery(ctx, authorityInfo)
|
||||
}
|
||||
|
||||
// AuthCode returns a token based on an authorization code.
|
||||
func (t *Client) AuthCode(ctx context.Context, req accesstokens.AuthCodeRequest) (accesstokens.TokenResponse, error) {
|
||||
if err := scopeError(req.AuthParams); err != nil {
|
||||
return accesstokens.TokenResponse{}, err
|
||||
}
|
||||
if err := t.resolveEndpoint(ctx, &req.AuthParams, ""); err != nil {
|
||||
return accesstokens.TokenResponse{}, err
|
||||
}
|
||||
|
||||
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 {
|
||||
if len(scopes) == 0 {
|
||||
err = fmt.Errorf("token request had an empty authority.AuthParams.Scopes, which may cause the following error: %w", err)
|
||||
return accesstokens.TokenResponse{}, err
|
||||
}
|
||||
return accesstokens.TokenResponse{}, err
|
||||
}
|
||||
return accesstokens.TokenResponse{
|
||||
TokenType: authParams.AuthnScheme.AccessTokenType(),
|
||||
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 := scopeError(authParams); err != nil {
|
||||
return accesstokens.TokenResponse{}, err
|
||||
}
|
||||
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
|
||||
}
|
||||
tr, err := t.AccessTokens.FromUserAssertionClientCertificate(ctx, authParams, authParams.UserAssertion, jwt)
|
||||
if err != nil {
|
||||
return accesstokens.TokenResponse{}, err
|
||||
}
|
||||
return tr, nil
|
||||
}
|
||||
|
||||
func (t *Client) Refresh(ctx context.Context, reqType accesstokens.AppType, authParams authority.AuthParams, cc *accesstokens.Credential, refreshToken accesstokens.RefreshToken) (accesstokens.TokenResponse, error) {
|
||||
if err := scopeError(authParams); err != nil {
|
||||
return accesstokens.TokenResponse{}, err
|
||||
}
|
||||
if err := t.resolveEndpoint(ctx, &authParams, ""); err != nil {
|
||||
return accesstokens.TokenResponse{}, err
|
||||
}
|
||||
|
||||
tr, err := t.AccessTokens.FromRefreshToken(ctx, reqType, authParams, cc, refreshToken.Secret)
|
||||
if err != nil {
|
||||
return accesstokens.TokenResponse{}, err
|
||||
}
|
||||
return tr, nil
|
||||
}
|
||||
|
||||
// UsernamePassword retrieves a token where a username and password is used. However, if this is
|
||||
// a user realm of "Federated", this uses SAML tokens. If "Managed", uses normal username/password.
|
||||
func (t *Client) UsernamePassword(ctx context.Context, authParams authority.AuthParams) (accesstokens.TokenResponse, error) {
|
||||
if err := scopeError(authParams); err != nil {
|
||||
return accesstokens.TokenResponse{}, err
|
||||
}
|
||||
|
||||
if authParams.AuthorityInfo.AuthorityType == authority.ADFS {
|
||||
if err := t.resolveEndpoint(ctx, &authParams, authParams.Username); err != nil {
|
||||
return accesstokens.TokenResponse{}, err
|
||||
}
|
||||
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 from authority: %w", err)
|
||||
}
|
||||
|
||||
switch userRealm.AccountType {
|
||||
case authority.Federated:
|
||||
mexDoc, err := t.WSTrust.Mex(ctx, userRealm.FederationMetadataURL)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("problem getting mex doc from federated url(%s): %w", userRealm.FederationMetadataURL, err)
|
||||
return accesstokens.TokenResponse{}, err
|
||||
}
|
||||
|
||||
saml, err := t.WSTrust.SAMLTokenInfo(ctx, authParams, userRealm.CloudAudienceURN, mexDoc.UsernamePasswordEndpoint)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("problem getting SAML token info: %w", err)
|
||||
return accesstokens.TokenResponse{}, err
|
||||
}
|
||||
tr, err := t.AccessTokens.FromSamlGrant(ctx, authParams, saml)
|
||||
if err != nil {
|
||||
return accesstokens.TokenResponse{}, err
|
||||
}
|
||||
return tr, nil
|
||||
case authority.Managed:
|
||||
if len(authParams.Scopes) == 0 {
|
||||
err = fmt.Errorf("token request had an empty authority.AuthParams.Scopes, which may cause the following error: %w", err)
|
||||
return accesstokens.TokenResponse{}, err
|
||||
}
|
||||
return t.AccessTokens.FromUsernamePassword(ctx, authParams)
|
||||
}
|
||||
return accesstokens.TokenResponse{}, errors.New("unknown account type")
|
||||
}
|
||||
|
||||
// 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
|
||||
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 := scopeError(authParams); err != nil {
|
||||
return DeviceCode{}, err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// scopeError takes an authority.AuthParams and returns an error
|
||||
// if len(AuthParams.Scope) == 0.
|
||||
func scopeError(a authority.AuthParams) error {
|
||||
// TODO(someone): we could look deeper at the message to determine if
|
||||
// it's a scope error, but this is a good start.
|
||||
/*
|
||||
{error":"invalid_scope","error_description":"AADSTS1002012: The provided value for scope
|
||||
openid offline_access profile is not valid. Client credential flows must have a scope value
|
||||
with /.default suffixed to the resource identifier (application ID URI)...}
|
||||
*/
|
||||
if len(a.Scopes) == 0 {
|
||||
return fmt.Errorf("token request had an empty authority.AuthParams.Scopes, which is invalid")
|
||||
}
|
||||
return nil
|
||||
}
|
457
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/accesstokens/accesstokens.go
generated
vendored
Normal file
457
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/accesstokens/accesstokens.go
generated
vendored
Normal file
@ -0,0 +1,457 @@
|
||||
// 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/v5"
|
||||
"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{}
|
||||
if authParams.AuthnScheme != nil {
|
||||
trParams := authParams.AuthnScheme.TokenRequestParams()
|
||||
for k, v := range trParams {
|
||||
qv.Set(k, v)
|
||||
}
|
||||
}
|
||||
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]]
|
||||
}
|
339
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/accesstokens/tokens.go
generated
vendored
Normal file
339
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/accesstokens/tokens.go
generated
vendored
Normal file
@ -0,0 +1,339 @@
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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"`
|
||||
TokenType string `json:"token_type"`
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// HomeAccountID uniquely identifies the authenticated account, if any. It's "" when the token is an app token.
|
||||
func (tr *TokenResponse) HomeAccountID() string {
|
||||
id := tr.IDToken.Subject
|
||||
if uid := tr.ClientInfo.UID; uid != "" {
|
||||
utid := tr.ClientInfo.UTID
|
||||
if utid == "" {
|
||||
utid = uid
|
||||
}
|
||||
id = fmt.Sprintf("%s.%s", uid, utid)
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
// 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.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
|
||||
}
|
||||
|
||||
key := strings.Join(
|
||||
[]string{rt.HomeAccountID, rt.Environment, rt.CredentialType, fourth},
|
||||
shared.CacheKeySeparator,
|
||||
)
|
||||
return strings.ToLower(key)
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
}
|
589
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/authority/authority.go
generated
vendored
Normal file
589
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/authority/authority.go
generated
vendored
Normal file
@ -0,0 +1,589 @@
|
||||
// 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
|
||||
autoDetectRegion = "TryAutoDetect"
|
||||
AccessTokenTypeBearer = "Bearer"
|
||||
)
|
||||
|
||||
// These are various hosts that host AAD Instance discovery endpoints.
|
||||
const (
|
||||
defaultHost = "login.microsoftonline.com"
|
||||
loginMicrosoft = "login.microsoft.com"
|
||||
loginWindows = "login.windows.net"
|
||||
loginSTSWindows = "sts.windows.net"
|
||||
loginMicrosoftOnline = defaultHost
|
||||
)
|
||||
|
||||
// jsonCaller is an interface that allows us to mock the JSONCall method.
|
||||
type jsonCaller interface {
|
||||
JSONCall(ctx context.Context, endpoint string, headers http.Header, qv url.Values, body, resp interface{}) error
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// OAuthResponseBase is the base JSON return message for an OAuth call.
|
||||
// This is embedded in other calls to get the base fields from every response.
|
||||
type OAuthResponseBase struct {
|
||||
Error string `json:"error"`
|
||||
SubError string `json:"suberror"`
|
||||
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"
|
||||
)
|
||||
|
||||
// AuthenticationScheme is an extensibility mechanism designed to be used only by Azure Arc for proof of possession access tokens.
|
||||
type AuthenticationScheme interface {
|
||||
// Extra parameters that are added to the request to the /token endpoint.
|
||||
TokenRequestParams() map[string]string
|
||||
// Key ID of the public / private key pair used by the encryption algorithm, if any.
|
||||
// Tokens obtained by authentication schemes that use this are bound to the KeyId, i.e.
|
||||
// if a different kid is presented, the access token cannot be used.
|
||||
KeyID() string
|
||||
// Creates the access token that goes into an Authorization HTTP header.
|
||||
FormatAccessToken(accessToken string) (string, error)
|
||||
//Expected to match the token_type parameter returned by ESTS. Used to disambiguate
|
||||
// between ATs of different types (e.g. Bearer and PoP) when loading from cache etc.
|
||||
AccessTokenType() string
|
||||
}
|
||||
|
||||
// default authn scheme realizing AuthenticationScheme for "Bearer" tokens
|
||||
type BearerAuthenticationScheme struct{}
|
||||
|
||||
var bearerAuthnScheme BearerAuthenticationScheme
|
||||
|
||||
func (ba *BearerAuthenticationScheme) TokenRequestParams() map[string]string {
|
||||
return nil
|
||||
}
|
||||
func (ba *BearerAuthenticationScheme) KeyID() string {
|
||||
return ""
|
||||
}
|
||||
func (ba *BearerAuthenticationScheme) FormatAccessToken(accessToken string) (string, error) {
|
||||
return accessToken, nil
|
||||
}
|
||||
func (ba *BearerAuthenticationScheme) AccessTokenType() string {
|
||||
return AccessTokenTypeBearer
|
||||
}
|
||||
|
||||
// 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
|
||||
// AuthnScheme is an optional scheme for formatting access tokens
|
||||
AuthnScheme AuthenticationScheme
|
||||
}
|
||||
|
||||
// NewAuthParams creates an authorization parameters object.
|
||||
func NewAuthParams(clientID string, authorityInfo Info) AuthParams {
|
||||
return AuthParams{
|
||||
ClientID: clientID,
|
||||
AuthorityInfo: authorityInfo,
|
||||
CorrelationID: uuid.New().String(),
|
||||
AuthnScheme: &bearerAuthnScheme,
|
||||
}
|
||||
}
|
||||
|
||||
// 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 must be an https URL such as "https://login.microsoftonline.com/<your tenant>"`)
|
||||
}
|
||||
|
||||
// NewInfoFromAuthorityURI creates an AuthorityInfo instance from the authority URL provided.
|
||||
func NewInfoFromAuthorityURI(authority string, validateAuthority bool, instanceDiscoveryDisabled bool) (Info, error) {
|
||||
u, err := url.Parse(strings.ToLower(authority))
|
||||
if err != nil || u.Scheme != "https" {
|
||||
return Info{}, errors.New(`authority must be an https URL such as "https://login.microsoftonline.com/<your tenant>"`)
|
||||
}
|
||||
|
||||
tenant, err := firstPathSegment(u)
|
||||
if err != nil {
|
||||
return Info{}, err
|
||||
}
|
||||
authorityType := AAD
|
||||
if tenant == "adfs" {
|
||||
authorityType = ADFS
|
||||
}
|
||||
|
||||
// u.Host includes the port, if any, which is required for private cloud deployments
|
||||
return Info{
|
||||
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
|
||||
}
|
||||
|
||||
// AADInstanceDiscovery attempts to discover a tenant endpoint (used in OIDC auth with an authorization endpoint).
|
||||
// This is done by AAD which allows for aliasing of tenants (windows.sts.net is the same as login.windows.com).
|
||||
func (c Client) AADInstanceDiscovery(ctx context.Context, authorityInfo Info) (InstanceDiscoveryResponse, error) {
|
||||
region := ""
|
||||
var err error
|
||||
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 loginMicrosoft, loginWindows, loginSTSWindows, defaultHost:
|
||||
environment = loginMicrosoft
|
||||
}
|
||||
|
||||
resp.TenantDiscoveryEndpoint = fmt.Sprintf(tenantDiscoveryEndpointWithRegion, region, environment, authorityInfo.Tenant)
|
||||
metadata := InstanceDiscoveryMetadata{
|
||||
PreferredNetwork: fmt.Sprintf("%v.%v", region, authorityInfo.Host),
|
||||
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() {}
|
72
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/shared/shared.go
generated
vendored
Normal file
72
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/shared/shared.go
generated
vendored
Normal file
@ -0,0 +1,72 @@
|
||||
// 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 {
|
||||
key := strings.Join([]string{acc.HomeAccountID, acc.Environment, acc.Realm}, CacheKeySeparator)
|
||||
return strings.ToLower(key)
|
||||
}
|
||||
|
||||
// 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 = "1.2.0"
|
Reference in New Issue
Block a user