1//go:build go1.18
2// +build go1.18
3
4// Copyright (c) Microsoft Corporation. All rights reserved.
5// Licensed under the MIT License.
6
7package azidentity
8
9import (
10 "bytes"
11 "context"
12 "errors"
13 "fmt"
14 "io"
15 "net/http"
16 "net/url"
17 "os"
18
19 "github.com/Azure/azure-sdk-for-go/sdk/azcore"
20 "github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud"
21 "github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime"
22 "github.com/Azure/azure-sdk-for-go/sdk/azcore/streaming"
23 "github.com/Azure/azure-sdk-for-go/sdk/azidentity/internal"
24 "github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential"
25 "github.com/AzureAD/microsoft-authentication-library-for-go/apps/public"
26)
27
28const (
29 azureAdditionallyAllowedTenants = "AZURE_ADDITIONALLY_ALLOWED_TENANTS"
30 azureAuthorityHost = "AZURE_AUTHORITY_HOST"
31 azureClientCertificatePassword = "AZURE_CLIENT_CERTIFICATE_PASSWORD"
32 azureClientCertificatePath = "AZURE_CLIENT_CERTIFICATE_PATH"
33 azureClientID = "AZURE_CLIENT_ID"
34 azureClientSecret = "AZURE_CLIENT_SECRET"
35 azureFederatedTokenFile = "AZURE_FEDERATED_TOKEN_FILE"
36 azurePassword = "AZURE_PASSWORD"
37 azureRegionalAuthorityName = "AZURE_REGIONAL_AUTHORITY_NAME"
38 azureTenantID = "AZURE_TENANT_ID"
39 azureUsername = "AZURE_USERNAME"
40
41 organizationsTenantID = "organizations"
42 developerSignOnClientID = "04b07795-8ddb-461a-bbee-02f9e1bf7b46"
43 defaultSuffix = "/.default"
44
45 traceNamespace = "Microsoft.Entra"
46 traceOpGetToken = "GetToken"
47 traceOpAuthenticate = "Authenticate"
48)
49
50var (
51 // capability CP1 indicates the client application is capable of handling CAE claims challenges
52 cp1 = []string{"CP1"}
53 errInvalidTenantID = errors.New("invalid tenantID. You can locate your tenantID by following the instructions listed here: https://learn.microsoft.com/partner-center/find-ids-and-domain-names")
54)
55
56// tokenCachePersistenceOptions contains options for persistent token caching
57type tokenCachePersistenceOptions = internal.TokenCachePersistenceOptions
58
59// setAuthorityHost initializes the authority host for credentials. Precedence is:
60// 1. cloud.Configuration.ActiveDirectoryAuthorityHost value set by user
61// 2. value of AZURE_AUTHORITY_HOST
62// 3. default: Azure Public Cloud
63func setAuthorityHost(cc cloud.Configuration) (string, error) {
64 host := cc.ActiveDirectoryAuthorityHost
65 if host == "" {
66 if len(cc.Services) > 0 {
67 return "", errors.New("missing ActiveDirectoryAuthorityHost for specified cloud")
68 }
69 host = cloud.AzurePublic.ActiveDirectoryAuthorityHost
70 if envAuthorityHost := os.Getenv(azureAuthorityHost); envAuthorityHost != "" {
71 host = envAuthorityHost
72 }
73 }
74 u, err := url.Parse(host)
75 if err != nil {
76 return "", err
77 }
78 if u.Scheme != "https" {
79 return "", errors.New("cannot use an authority host without https")
80 }
81 return host, nil
82}
83
84// resolveAdditionalTenants returns a copy of tenants, simplified when tenants contains a wildcard
85func resolveAdditionalTenants(tenants []string) []string {
86 if len(tenants) == 0 {
87 return nil
88 }
89 for _, t := range tenants {
90 // a wildcard makes all other values redundant
91 if t == "*" {
92 return []string{"*"}
93 }
94 }
95 cp := make([]string, len(tenants))
96 copy(cp, tenants)
97 return cp
98}
99
100// resolveTenant returns the correct tenant for a token request
101func resolveTenant(defaultTenant, specified, credName string, additionalTenants []string) (string, error) {
102 if specified == "" || specified == defaultTenant {
103 return defaultTenant, nil
104 }
105 if defaultTenant == "adfs" {
106 return "", errors.New("ADFS doesn't support tenants")
107 }
108 if !validTenantID(specified) {
109 return "", errInvalidTenantID
110 }
111 for _, t := range additionalTenants {
112 if t == "*" || t == specified {
113 return specified, nil
114 }
115 }
116 return "", fmt.Errorf(`%s isn't configured to acquire tokens for tenant %q. To enable acquiring tokens for this tenant add it to the AdditionallyAllowedTenants on the credential options, or add "*" to allow acquiring tokens for any tenant`, credName, specified)
117}
118
119func alphanumeric(r rune) bool {
120 return ('0' <= r && r <= '9') || ('a' <= r && r <= 'z') || ('A' <= r && r <= 'Z')
121}
122
123func validTenantID(tenantID string) bool {
124 if len(tenantID) < 1 {
125 return false
126 }
127 for _, r := range tenantID {
128 if !(alphanumeric(r) || r == '.' || r == '-') {
129 return false
130 }
131 }
132 return true
133}
134
135func doForClient(client *azcore.Client, r *http.Request) (*http.Response, error) {
136 req, err := runtime.NewRequest(r.Context(), r.Method, r.URL.String())
137 if err != nil {
138 return nil, err
139 }
140 if r.Body != nil && r.Body != http.NoBody {
141 // create a rewindable body from the existing body as required
142 var body io.ReadSeekCloser
143 if rsc, ok := r.Body.(io.ReadSeekCloser); ok {
144 body = rsc
145 } else {
146 b, err := io.ReadAll(r.Body)
147 if err != nil {
148 return nil, err
149 }
150 body = streaming.NopCloser(bytes.NewReader(b))
151 }
152 err = req.SetBody(body, r.Header.Get("Content-Type"))
153 if err != nil {
154 return nil, err
155 }
156 }
157
158 // copy headers to the new request, ignoring any for which the new request has a value
159 h := req.Raw().Header
160 for key, vals := range r.Header {
161 if _, has := h[key]; !has {
162 for _, val := range vals {
163 h.Add(key, val)
164 }
165 }
166 }
167
168 resp, err := client.Pipeline().Do(req)
169 if err != nil {
170 return nil, err
171 }
172 return resp, err
173}
174
175// enables fakes for test scenarios
176type msalConfidentialClient interface {
177 AcquireTokenSilent(ctx context.Context, scopes []string, options ...confidential.AcquireSilentOption) (confidential.AuthResult, error)
178 AcquireTokenByAuthCode(ctx context.Context, code string, redirectURI string, scopes []string, options ...confidential.AcquireByAuthCodeOption) (confidential.AuthResult, error)
179 AcquireTokenByCredential(ctx context.Context, scopes []string, options ...confidential.AcquireByCredentialOption) (confidential.AuthResult, error)
180 AcquireTokenOnBehalfOf(ctx context.Context, userAssertion string, scopes []string, options ...confidential.AcquireOnBehalfOfOption) (confidential.AuthResult, error)
181}
182
183// enables fakes for test scenarios
184type msalPublicClient interface {
185 AcquireTokenSilent(ctx context.Context, scopes []string, options ...public.AcquireSilentOption) (public.AuthResult, error)
186 AcquireTokenByUsernamePassword(ctx context.Context, scopes []string, username string, password string, options ...public.AcquireByUsernamePasswordOption) (public.AuthResult, error)
187 AcquireTokenByDeviceCode(ctx context.Context, scopes []string, options ...public.AcquireByDeviceCodeOption) (public.DeviceCode, error)
188 AcquireTokenByAuthCode(ctx context.Context, code string, redirectURI string, scopes []string, options ...public.AcquireByAuthCodeOption) (public.AuthResult, error)
189 AcquireTokenInteractive(ctx context.Context, scopes []string, options ...public.AcquireInteractiveOption) (public.AuthResult, error)
190}