1package autorest 2 3// Copyright 2017 Microsoft Corporation 4// 5// Licensed under the Apache License, Version 2.0 (the "License"); 6// you may not use this file except in compliance with the License. 7// You may obtain a copy of the License at 8// 9// http://www.apache.org/licenses/LICENSE-2.0 10// 11// Unless required by applicable law or agreed to in writing, software 12// distributed under the License is distributed on an "AS IS" BASIS, 13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14// See the License for the specific language governing permissions and 15// limitations under the License. 16 17import ( 18 "fmt" 19 "net/http" 20 "net/url" 21 "strings" 22 23 "github.com/Azure/go-autorest/autorest/adal" 24) 25 26const ( 27 bearerChallengeHeader = "Www-Authenticate" 28 bearer = "Bearer" 29 tenantID = "tenantID" 30 apiKeyAuthorizerHeader = "Ocp-Apim-Subscription-Key" 31 bingAPISdkHeader = "X-BingApis-SDK-Client" 32 golangBingAPISdkHeaderValue = "Go-SDK" 33) 34 35// Authorizer is the interface that provides a PrepareDecorator used to supply request 36// authorization. Most often, the Authorizer decorator runs last so it has access to the full 37// state of the formed HTTP request. 38type Authorizer interface { 39 WithAuthorization() PrepareDecorator 40} 41 42// NullAuthorizer implements a default, "do nothing" Authorizer. 43type NullAuthorizer struct{} 44 45// WithAuthorization returns a PrepareDecorator that does nothing. 46func (na NullAuthorizer) WithAuthorization() PrepareDecorator { 47 return WithNothing() 48} 49 50// APIKeyAuthorizer implements API Key authorization. 51type APIKeyAuthorizer struct { 52 headers map[string]interface{} 53 queryParameters map[string]interface{} 54} 55 56// NewAPIKeyAuthorizerWithHeaders creates an ApiKeyAuthorizer with headers. 57func NewAPIKeyAuthorizerWithHeaders(headers map[string]interface{}) *APIKeyAuthorizer { 58 return NewAPIKeyAuthorizer(headers, nil) 59} 60 61// NewAPIKeyAuthorizerWithQueryParameters creates an ApiKeyAuthorizer with query parameters. 62func NewAPIKeyAuthorizerWithQueryParameters(queryParameters map[string]interface{}) *APIKeyAuthorizer { 63 return NewAPIKeyAuthorizer(nil, queryParameters) 64} 65 66// NewAPIKeyAuthorizer creates an ApiKeyAuthorizer with headers. 67func NewAPIKeyAuthorizer(headers map[string]interface{}, queryParameters map[string]interface{}) *APIKeyAuthorizer { 68 return &APIKeyAuthorizer{headers: headers, queryParameters: queryParameters} 69} 70 71// WithAuthorization returns a PrepareDecorator that adds an HTTP headers and Query Paramaters 72func (aka *APIKeyAuthorizer) WithAuthorization() PrepareDecorator { 73 return func(p Preparer) Preparer { 74 return DecoratePreparer(p, WithHeaders(aka.headers), WithQueryParameters(aka.queryParameters)) 75 } 76} 77 78// CognitiveServicesAuthorizer implements authorization for Cognitive Services. 79type CognitiveServicesAuthorizer struct { 80 subscriptionKey string 81} 82 83// NewCognitiveServicesAuthorizer is 84func NewCognitiveServicesAuthorizer(subscriptionKey string) *CognitiveServicesAuthorizer { 85 return &CognitiveServicesAuthorizer{subscriptionKey: subscriptionKey} 86} 87 88// WithAuthorization is 89func (csa *CognitiveServicesAuthorizer) WithAuthorization() PrepareDecorator { 90 headers := make(map[string]interface{}) 91 headers[apiKeyAuthorizerHeader] = csa.subscriptionKey 92 headers[bingAPISdkHeader] = golangBingAPISdkHeaderValue 93 94 return NewAPIKeyAuthorizerWithHeaders(headers).WithAuthorization() 95} 96 97// BearerAuthorizer implements the bearer authorization 98type BearerAuthorizer struct { 99 tokenProvider adal.OAuthTokenProvider 100} 101 102// NewBearerAuthorizer crates a BearerAuthorizer using the given token provider 103func NewBearerAuthorizer(tp adal.OAuthTokenProvider) *BearerAuthorizer { 104 return &BearerAuthorizer{tokenProvider: tp} 105} 106 107// WithAuthorization returns a PrepareDecorator that adds an HTTP Authorization header whose 108// value is "Bearer " followed by the token. 109// 110// By default, the token will be automatically refreshed through the Refresher interface. 111func (ba *BearerAuthorizer) WithAuthorization() PrepareDecorator { 112 return func(p Preparer) Preparer { 113 return PreparerFunc(func(r *http.Request) (*http.Request, error) { 114 r, err := p.Prepare(r) 115 if err == nil { 116 // the ordering is important here, prefer RefresherWithContext if available 117 if refresher, ok := ba.tokenProvider.(adal.RefresherWithContext); ok { 118 err = refresher.EnsureFreshWithContext(r.Context()) 119 } else if refresher, ok := ba.tokenProvider.(adal.Refresher); ok { 120 err = refresher.EnsureFresh() 121 } 122 if err != nil { 123 var resp *http.Response 124 if tokError, ok := err.(adal.TokenRefreshError); ok { 125 resp = tokError.Response() 126 } 127 return r, NewErrorWithError(err, "azure.BearerAuthorizer", "WithAuthorization", resp, 128 "Failed to refresh the Token for request to %s", r.URL) 129 } 130 return Prepare(r, WithHeader(headerAuthorization, fmt.Sprintf("Bearer %s", ba.tokenProvider.OAuthToken()))) 131 } 132 return r, err 133 }) 134 } 135} 136 137// BearerAuthorizerCallbackFunc is the authentication callback signature. 138type BearerAuthorizerCallbackFunc func(tenantID, resource string) (*BearerAuthorizer, error) 139 140// BearerAuthorizerCallback implements bearer authorization via a callback. 141type BearerAuthorizerCallback struct { 142 sender Sender 143 callback BearerAuthorizerCallbackFunc 144} 145 146// NewBearerAuthorizerCallback creates a bearer authorization callback. The callback 147// is invoked when the HTTP request is submitted. 148func NewBearerAuthorizerCallback(sender Sender, callback BearerAuthorizerCallbackFunc) *BearerAuthorizerCallback { 149 if sender == nil { 150 sender = &http.Client{} 151 } 152 return &BearerAuthorizerCallback{sender: sender, callback: callback} 153} 154 155// WithAuthorization returns a PrepareDecorator that adds an HTTP Authorization header whose value 156// is "Bearer " followed by the token. The BearerAuthorizer is obtained via a user-supplied callback. 157// 158// By default, the token will be automatically refreshed through the Refresher interface. 159func (bacb *BearerAuthorizerCallback) WithAuthorization() PrepareDecorator { 160 return func(p Preparer) Preparer { 161 return PreparerFunc(func(r *http.Request) (*http.Request, error) { 162 r, err := p.Prepare(r) 163 if err == nil { 164 // make a copy of the request and remove the body as it's not 165 // required and avoids us having to create a copy of it. 166 rCopy := *r 167 removeRequestBody(&rCopy) 168 169 resp, err := bacb.sender.Do(&rCopy) 170 if err == nil && resp.StatusCode == 401 { 171 defer resp.Body.Close() 172 if hasBearerChallenge(resp) { 173 bc, err := newBearerChallenge(resp) 174 if err != nil { 175 return r, err 176 } 177 if bacb.callback != nil { 178 ba, err := bacb.callback(bc.values[tenantID], bc.values["resource"]) 179 if err != nil { 180 return r, err 181 } 182 return Prepare(r, ba.WithAuthorization()) 183 } 184 } 185 } 186 } 187 return r, err 188 }) 189 } 190} 191 192// returns true if the HTTP response contains a bearer challenge 193func hasBearerChallenge(resp *http.Response) bool { 194 authHeader := resp.Header.Get(bearerChallengeHeader) 195 if len(authHeader) == 0 || strings.Index(authHeader, bearer) < 0 { 196 return false 197 } 198 return true 199} 200 201type bearerChallenge struct { 202 values map[string]string 203} 204 205func newBearerChallenge(resp *http.Response) (bc bearerChallenge, err error) { 206 challenge := strings.TrimSpace(resp.Header.Get(bearerChallengeHeader)) 207 trimmedChallenge := challenge[len(bearer)+1:] 208 209 // challenge is a set of key=value pairs that are comma delimited 210 pairs := strings.Split(trimmedChallenge, ",") 211 if len(pairs) < 1 { 212 err = fmt.Errorf("challenge '%s' contains no pairs", challenge) 213 return bc, err 214 } 215 216 bc.values = make(map[string]string) 217 for i := range pairs { 218 trimmedPair := strings.TrimSpace(pairs[i]) 219 pair := strings.Split(trimmedPair, "=") 220 if len(pair) == 2 { 221 // remove the enclosing quotes 222 key := strings.Trim(pair[0], "\"") 223 value := strings.Trim(pair[1], "\"") 224 225 switch key { 226 case "authorization", "authorization_uri": 227 // strip the tenant ID from the authorization URL 228 asURL, err := url.Parse(value) 229 if err != nil { 230 return bc, err 231 } 232 bc.values[tenantID] = asURL.Path[1:] 233 default: 234 bc.values[key] = value 235 } 236 } 237 } 238 239 return bc, err 240} 241 242// EventGridKeyAuthorizer implements authorization for event grid using key authentication. 243type EventGridKeyAuthorizer struct { 244 topicKey string 245} 246 247// NewEventGridKeyAuthorizer creates a new EventGridKeyAuthorizer 248// with the specified topic key. 249func NewEventGridKeyAuthorizer(topicKey string) EventGridKeyAuthorizer { 250 return EventGridKeyAuthorizer{topicKey: topicKey} 251} 252 253// WithAuthorization returns a PrepareDecorator that adds the aeg-sas-key authentication header. 254func (egta EventGridKeyAuthorizer) WithAuthorization() PrepareDecorator { 255 headers := map[string]interface{}{ 256 "aeg-sas-key": egta.topicKey, 257 } 258 return NewAPIKeyAuthorizerWithHeaders(headers).WithAuthorization() 259} 260