1package adal 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 "context" 19 "encoding/json" 20 "fmt" 21 "net/http" 22 "strings" 23 "testing" 24 "time" 25 26 "github.com/Azure/go-autorest/autorest/mocks" 27) 28 29const ( 30 TestResource = "SomeResource" 31 TestClientID = "SomeClientID" 32 TestTenantID = "SomeTenantID" 33 TestActiveDirectoryEndpoint = "https://login.test.com/" 34) 35 36var ( 37 testOAuthConfig, _ = NewOAuthConfig(TestActiveDirectoryEndpoint, TestTenantID) 38 TestOAuthConfig = *testOAuthConfig 39) 40 41const MockDeviceCodeResponse = ` 42{ 43 "device_code": "10000-40-1234567890", 44 "user_code": "ABCDEF", 45 "verification_url": "http://aka.ms/deviceauth", 46 "expires_in": "900", 47 "interval": "0" 48} 49` 50 51const MockDeviceTokenResponse = `{ 52 "access_token": "accessToken", 53 "refresh_token": "refreshToken", 54 "expires_in": "1000", 55 "expires_on": "2000", 56 "not_before": "3000", 57 "resource": "resource", 58 "token_type": "type" 59} 60` 61 62func TestDeviceCodeIncludesResource(t *testing.T) { 63 sender := mocks.NewSender() 64 sender.AppendResponse(mocks.NewResponseWithContent(MockDeviceCodeResponse)) 65 66 code, err := InitiateDeviceAuth(sender, TestOAuthConfig, TestClientID, TestResource) 67 if err != nil { 68 t.Fatalf("adal: unexpected error initiating device auth") 69 } 70 71 if code.Resource != TestResource { 72 t.Fatalf("adal: InitiateDeviceAuth failed to stash the resource in the DeviceCode struct") 73 } 74} 75 76func TestDeviceCodeReturnsErrorIfSendingFails(t *testing.T) { 77 sender := mocks.NewSender() 78 sender.SetError(fmt.Errorf("this is an error")) 79 80 _, err := InitiateDeviceAuth(sender, TestOAuthConfig, TestClientID, TestResource) 81 if err == nil || !strings.Contains(err.Error(), errCodeSendingFails) { 82 t.Fatalf("adal: failed to get correct error expected(%s) actual(%s)", errCodeSendingFails, err.Error()) 83 } 84} 85 86func TestDeviceCodeReturnsErrorIfBadRequest(t *testing.T) { 87 sender := mocks.NewSender() 88 body := mocks.NewBody("doesn't matter") 89 sender.AppendResponse(mocks.NewResponseWithBodyAndStatus(body, http.StatusBadRequest, "Bad Request")) 90 91 _, err := InitiateDeviceAuth(sender, TestOAuthConfig, TestClientID, TestResource) 92 if err == nil || !strings.Contains(err.Error(), errCodeHandlingFails) { 93 t.Fatalf("adal: failed to get correct error expected(%s) actual(%s)", errCodeHandlingFails, err.Error()) 94 } 95 96 if body.IsOpen() { 97 t.Fatalf("response body was left open!") 98 } 99} 100 101func TestDeviceCodeReturnsErrorIfCannotDeserializeDeviceCode(t *testing.T) { 102 gibberishJSON := strings.Replace(MockDeviceCodeResponse, "expires_in", "\":, :gibberish", -1) 103 sender := mocks.NewSender() 104 body := mocks.NewBody(gibberishJSON) 105 sender.AppendResponse(mocks.NewResponseWithBodyAndStatus(body, http.StatusOK, "OK")) 106 107 _, err := InitiateDeviceAuth(sender, TestOAuthConfig, TestClientID, TestResource) 108 if err == nil || !strings.Contains(err.Error(), errCodeHandlingFails) { 109 t.Fatalf("adal: failed to get correct error expected(%s) actual(%s)", errCodeHandlingFails, err.Error()) 110 } 111 112 if body.IsOpen() { 113 t.Fatalf("response body was left open!") 114 } 115} 116 117func TestDeviceCodeReturnsErrorIfEmptyDeviceCode(t *testing.T) { 118 sender := mocks.NewSender() 119 body := mocks.NewBody("") 120 sender.AppendResponse(mocks.NewResponseWithBodyAndStatus(body, http.StatusOK, "OK")) 121 122 _, err := InitiateDeviceAuth(sender, TestOAuthConfig, TestClientID, TestResource) 123 if err != ErrDeviceCodeEmpty { 124 t.Fatalf("adal: failed to get correct error expected(%s) actual(%s)", ErrDeviceCodeEmpty, err.Error()) 125 } 126 127 if body.IsOpen() { 128 t.Fatalf("response body was left open!") 129 } 130} 131 132func deviceCode() *DeviceCode { 133 var deviceCode DeviceCode 134 _ = json.Unmarshal([]byte(MockDeviceCodeResponse), &deviceCode) 135 deviceCode.Resource = TestResource 136 deviceCode.ClientID = TestClientID 137 return &deviceCode 138} 139 140func TestDeviceTokenReturns(t *testing.T) { 141 sender := mocks.NewSender() 142 body := mocks.NewBody(MockDeviceTokenResponse) 143 sender.AppendResponse(mocks.NewResponseWithBodyAndStatus(body, http.StatusOK, "OK")) 144 145 _, err := WaitForUserCompletion(sender, deviceCode()) 146 if err != nil { 147 t.Fatalf("adal: got error unexpectedly") 148 } 149 150 if body.IsOpen() { 151 t.Fatalf("response body was left open!") 152 } 153} 154 155func TestDeviceTokenReturnsErrorIfSendingFails(t *testing.T) { 156 sender := mocks.NewSender() 157 sender.SetError(fmt.Errorf("this is an error")) 158 159 _, err := WaitForUserCompletion(sender, deviceCode()) 160 if err == nil || !strings.Contains(err.Error(), errTokenSendingFails) { 161 t.Fatalf("adal: failed to get correct error expected(%s) actual(%s)", errTokenSendingFails, err.Error()) 162 } 163} 164 165func TestDeviceTokenReturnsErrorIfServerError(t *testing.T) { 166 sender := mocks.NewSender() 167 body := mocks.NewBody("") 168 sender.AppendResponse(mocks.NewResponseWithBodyAndStatus(body, http.StatusInternalServerError, "Internal Server Error")) 169 170 _, err := WaitForUserCompletion(sender, deviceCode()) 171 if err == nil || !strings.Contains(err.Error(), errTokenHandlingFails) { 172 t.Fatalf("adal: failed to get correct error expected(%s) actual(%s)", errTokenHandlingFails, err.Error()) 173 } 174 175 if body.IsOpen() { 176 t.Fatalf("response body was left open!") 177 } 178} 179 180func TestDeviceTokenReturnsErrorIfCannotDeserializeDeviceToken(t *testing.T) { 181 gibberishJSON := strings.Replace(MockDeviceTokenResponse, "expires_in", ";:\"gibberish", -1) 182 sender := mocks.NewSender() 183 body := mocks.NewBody(gibberishJSON) 184 sender.AppendResponse(mocks.NewResponseWithBodyAndStatus(body, http.StatusOK, "OK")) 185 186 _, err := WaitForUserCompletion(sender, deviceCode()) 187 if err == nil || !strings.Contains(err.Error(), errTokenHandlingFails) { 188 t.Fatalf("adal: failed to get correct error expected(%s) actual(%s)", errTokenHandlingFails, err.Error()) 189 } 190 191 if body.IsOpen() { 192 t.Fatalf("response body was left open!") 193 } 194} 195 196func errorDeviceTokenResponse(message string) string { 197 return `{ "error": "` + message + `" }` 198} 199 200func TestDeviceTokenReturnsErrorIfAuthorizationPending(t *testing.T) { 201 sender := mocks.NewSender() 202 body := mocks.NewBody(errorDeviceTokenResponse("authorization_pending")) 203 sender.AppendResponse(mocks.NewResponseWithBodyAndStatus(body, http.StatusBadRequest, "Bad Request")) 204 205 _, err := CheckForUserCompletion(sender, deviceCode()) 206 if err != ErrDeviceAuthorizationPending { 207 t.Fatalf("!!!") 208 } 209 210 if body.IsOpen() { 211 t.Fatalf("response body was left open!") 212 } 213} 214 215func TestDeviceTokenReturnsErrorIfSlowDown(t *testing.T) { 216 sender := mocks.NewSender() 217 body := mocks.NewBody(errorDeviceTokenResponse("slow_down")) 218 sender.AppendResponse(mocks.NewResponseWithBodyAndStatus(body, http.StatusBadRequest, "Bad Request")) 219 220 _, err := CheckForUserCompletion(sender, deviceCode()) 221 if err != ErrDeviceSlowDown { 222 t.Fatalf("!!!") 223 } 224 225 if body.IsOpen() { 226 t.Fatalf("response body was left open!") 227 } 228} 229 230type deviceTokenSender struct { 231 errorString string 232 attempts int 233} 234 235func newDeviceTokenSender(deviceErrorString string) *deviceTokenSender { 236 return &deviceTokenSender{errorString: deviceErrorString, attempts: 0} 237} 238 239func (s *deviceTokenSender) Do(req *http.Request) (*http.Response, error) { 240 var resp *http.Response 241 if s.attempts < 1 { 242 s.attempts++ 243 resp = mocks.NewResponseWithContent(errorDeviceTokenResponse(s.errorString)) 244 } else { 245 resp = mocks.NewResponseWithContent(MockDeviceTokenResponse) 246 } 247 return resp, nil 248} 249 250// since the above only exercise CheckForUserCompletion, we repeat the test here, 251// but with the intent of showing that WaitForUserCompletion loops properly. 252func TestDeviceTokenSucceedsWithIntermediateAuthPending(t *testing.T) { 253 sender := newDeviceTokenSender("authorization_pending") 254 255 _, err := WaitForUserCompletion(sender, deviceCode()) 256 if err != nil { 257 t.Fatalf("unexpected error occurred") 258 } 259} 260 261// same as above but with SlowDown now 262func TestDeviceTokenSucceedsWithIntermediateSlowDown(t *testing.T) { 263 sender := newDeviceTokenSender("slow_down") 264 265 _, err := WaitForUserCompletion(sender, deviceCode()) 266 if err != nil { 267 t.Fatalf("unexpected error occurred") 268 } 269} 270 271func TestDeviceTokenReturnsErrorIfAccessDenied(t *testing.T) { 272 sender := mocks.NewSender() 273 body := mocks.NewBody(errorDeviceTokenResponse("access_denied")) 274 sender.AppendResponse(mocks.NewResponseWithBodyAndStatus(body, http.StatusBadRequest, "Bad Request")) 275 276 _, err := WaitForUserCompletion(sender, deviceCode()) 277 if err != ErrDeviceAccessDenied { 278 t.Fatalf("adal: got wrong error expected(%s) actual(%s)", ErrDeviceAccessDenied.Error(), err.Error()) 279 } 280 281 if body.IsOpen() { 282 t.Fatalf("response body was left open!") 283 } 284} 285 286func TestDeviceTokenReturnsErrorIfCodeExpired(t *testing.T) { 287 sender := mocks.NewSender() 288 body := mocks.NewBody(errorDeviceTokenResponse("code_expired")) 289 sender.AppendResponse(mocks.NewResponseWithBodyAndStatus(body, http.StatusBadRequest, "Bad Request")) 290 291 _, err := WaitForUserCompletion(sender, deviceCode()) 292 if err != ErrDeviceCodeExpired { 293 t.Fatalf("adal: got wrong error expected(%s) actual(%s)", ErrDeviceCodeExpired.Error(), err.Error()) 294 } 295 296 if body.IsOpen() { 297 t.Fatalf("response body was left open!") 298 } 299} 300 301func TestDeviceTokenReturnsErrorForUnknownError(t *testing.T) { 302 sender := mocks.NewSender() 303 body := mocks.NewBody(errorDeviceTokenResponse("unknown_error")) 304 sender.AppendResponse(mocks.NewResponseWithBodyAndStatus(body, http.StatusBadRequest, "Bad Request")) 305 306 _, err := WaitForUserCompletion(sender, deviceCode()) 307 if err == nil { 308 t.Fatalf("failed to get error") 309 } 310 if err != ErrDeviceGeneric { 311 t.Fatalf("adal: got wrong error expected(%s) actual(%s)", ErrDeviceGeneric.Error(), err.Error()) 312 } 313 314 if body.IsOpen() { 315 t.Fatalf("response body was left open!") 316 } 317} 318 319func TestDeviceTokenReturnsErrorIfTokenEmptyAndStatusOK(t *testing.T) { 320 sender := mocks.NewSender() 321 body := mocks.NewBody("") 322 sender.AppendResponse(mocks.NewResponseWithBodyAndStatus(body, http.StatusOK, "OK")) 323 324 _, err := WaitForUserCompletion(sender, deviceCode()) 325 if err != ErrOAuthTokenEmpty { 326 t.Fatalf("adal: got wrong error expected(%s) actual(%s)", ErrOAuthTokenEmpty.Error(), err.Error()) 327 } 328 329 if body.IsOpen() { 330 t.Fatalf("response body was left open!") 331 } 332} 333 334func TestWaitForUserCompletionWithContext(t *testing.T) { 335 sender := SenderFunc(func(*http.Request) (*http.Response, error) { 336 return mocks.NewResponseWithContent(`{"error":"authorization_pending"}`), nil 337 }) 338 ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) 339 defer cancel() 340 _, err := WaitForUserCompletionWithContext(ctx, sender, deviceCode()) 341 if err != context.DeadlineExceeded { 342 t.Fatalf("adal: got wrong error expected(%s) actual(%s)", context.DeadlineExceeded.Error(), err.Error()) 343 } 344} 345