OLD | NEW |
(Empty) | |
| 1 /* |
| 2 Copyright 2013 Google Inc |
| 3 |
| 4 Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 you may not use this file except in compliance with the License. |
| 6 You may obtain a copy of the License at |
| 7 |
| 8 http://www.apache.org/licenses/LICENSE-2.0 |
| 9 |
| 10 Unless required by applicable law or agreed to in writing, software |
| 11 distributed under the License is distributed on an "AS IS" BASIS, |
| 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 See the License for the specific language governing permissions and |
| 14 limitations under the License. |
| 15 */ |
| 16 |
| 17 using System; |
| 18 using System.Collections.Generic; |
| 19 using System.Linq; |
| 20 using System.Net.Http; |
| 21 using System.Text; |
| 22 using System.Threading; |
| 23 using System.Threading.Tasks; |
| 24 |
| 25 using Moq; |
| 26 using NUnit.Framework; |
| 27 |
| 28 using Google.Apis.Auth.OAuth2.Requests; |
| 29 using Google.Apis.Auth.OAuth2.Responses; |
| 30 using Google.Apis.Http; |
| 31 using Google.Apis.Json; |
| 32 using Google.Apis.Testing; |
| 33 using Google.Apis.Tests; |
| 34 using Google.Apis.Util; |
| 35 using Google.Apis.Util.Store; |
| 36 |
| 37 namespace Google.Apis.Auth.OAuth2 |
| 38 { |
| 39 /// <summary>Tests for <see cref="Google.Apis.Auth.OAuth2.AuthorizationCodeF
low"/>.</summary> |
| 40 [TestFixture] |
| 41 public class AuthorizationCodeFlowTests |
| 42 { |
| 43 private const string TokenUrl = "https://token.com"; |
| 44 private const string AuthorizationCodeUrl = "https://authorization.com"; |
| 45 |
| 46 #region Constructor |
| 47 |
| 48 [Test] |
| 49 public void TestConstructor_ArgumentException() |
| 50 { |
| 51 // ClientSecrets are missing. |
| 52 try |
| 53 { |
| 54 new AuthorizationCodeFlow(new AuthorizationCodeFlow.Initializer( |
| 55 "https://authorization_code.com", "https://token.com")); |
| 56 Assert.Fail(); |
| 57 } |
| 58 catch (ArgumentException ex) |
| 59 { |
| 60 Assert.True(ex.Message.Contains("You MUST set ClientSecret or Cl
ientSecretStream")); |
| 61 } |
| 62 } |
| 63 |
| 64 [Test] |
| 65 public void TestConstructor_DefaultValues() |
| 66 { |
| 67 var flow = CreateFlow(); |
| 68 Assert.NotNull(flow.AccessMethod); |
| 69 Assert.That(flow.AccessMethod, Is.InstanceOf<BearerToken.Authorizati
onHeaderAccessMethod>()); |
| 70 Assert.That(flow.AuthorizationServerUrl, Is.EqualTo("https://authori
zation.com")); |
| 71 Assert.NotNull(flow.ClientSecrets); |
| 72 Assert.That(flow.ClientSecrets.ClientId, Is.EqualTo("id")); |
| 73 Assert.That(flow.ClientSecrets.ClientSecret, Is.EqualTo("secret")); |
| 74 Assert.That(flow.Clock, Is.InstanceOf<SystemClock>()); |
| 75 Assert.Null(flow.DataStore); |
| 76 Assert.NotNull(flow.HttpClient); |
| 77 Assert.NotNull(flow.Scopes); |
| 78 Assert.That(flow.TokenServerUrl, Is.EqualTo("https://token.com")); |
| 79 |
| 80 Assert.That(flow.HttpClient.MessageHandler.UnsuccessfulResponseHandl
ers.Count(), Is.EqualTo(1)); |
| 81 Assert.That(flow.HttpClient.MessageHandler.UnsuccessfulResponseHandl
ers.First(), |
| 82 Is.InstanceOf<BackOffHandler>()); |
| 83 } |
| 84 |
| 85 #endregion |
| 86 |
| 87 #region LoadToken |
| 88 |
| 89 [Test] |
| 90 public void LoadTokenAsync_NoDataStore() |
| 91 { |
| 92 var flow = CreateFlow(); |
| 93 Assert.Null(flow.LoadTokenAsync("user", CancellationToken.None).Resu
lt); |
| 94 } |
| 95 |
| 96 [Test] |
| 97 public void LoadTokenAsync_NullResponse() |
| 98 { |
| 99 TaskCompletionSource<TokenResponse> tcs = new TaskCompletionSource<T
okenResponse>(); |
| 100 tcs.SetResult(null); |
| 101 Assert.Null(SubtestLoadTokenAsync(tcs)); |
| 102 } |
| 103 |
| 104 [Test] |
| 105 public void LoadTokenAsync_TokenResponse() |
| 106 { |
| 107 TokenResponse response = new TokenResponse |
| 108 { |
| 109 AccessToken = "access" |
| 110 }; |
| 111 |
| 112 TaskCompletionSource<TokenResponse> tcs = new TaskCompletionSource<T
okenResponse>(); |
| 113 tcs.SetResult(response); |
| 114 var result = SubtestLoadTokenAsync(tcs); |
| 115 Assert.That(result, Is.EqualTo(response)); |
| 116 } |
| 117 |
| 118 private TokenResponse SubtestLoadTokenAsync(TaskCompletionSource<TokenRe
sponse> tcs) |
| 119 { |
| 120 var mock = new Mock<IDataStore>(); |
| 121 mock.Setup(ds => ds.GetAsync<TokenResponse>("user")).Returns(tcs.Tas
k); |
| 122 var flow = CreateFlow(dataStore: mock.Object); |
| 123 var result = flow.LoadTokenAsync("user", CancellationToken.None).Res
ult; |
| 124 mock.Verify(ds => ds.GetAsync<TokenResponse>("user")); |
| 125 return result; |
| 126 } |
| 127 |
| 128 #endregion |
| 129 |
| 130 #region CreateAuthorizationCodeRequest |
| 131 |
| 132 [Test] |
| 133 public void TestCreateAuthorizationCodeRequest() |
| 134 { |
| 135 var request = CreateFlow(scopes: new[] { "a", "b" }).CreateAuthoriza
tionCodeRequest("redirect"); |
| 136 Assert.That(request.AuthorizationServerUrl, Is.EqualTo(new Uri(Autho
rizationCodeUrl))); |
| 137 Assert.That(request.ClientId, Is.EqualTo("id")); |
| 138 Assert.That(request.RedirectUri, Is.EqualTo("redirect")); |
| 139 Assert.That(request.ResponseType, Is.EqualTo("code")); |
| 140 Assert.That(request.Scope, Is.EqualTo("a b")); |
| 141 Assert.Null(request.State); |
| 142 } |
| 143 |
| 144 #endregion |
| 145 |
| 146 [Test] |
| 147 public void TestExchangeCodeForTokenAsync() |
| 148 { |
| 149 var mock = new Mock<IDataStore>(); |
| 150 var handler = new FetchTokenMessageHandler(); |
| 151 handler.AuthorizationCodeTokenRequest = new AuthorizationCodeTokenRe
quest() |
| 152 { |
| 153 Code = "c0de", |
| 154 RedirectUri = "redIrect", |
| 155 Scope = "a" |
| 156 }; |
| 157 MockHttpClientFactory mockFactory = new MockHttpClientFactory(handle
r); |
| 158 |
| 159 TaskCompletionSource<object> tcs = new TaskCompletionSource<object>(
); |
| 160 tcs.SetResult(null); |
| 161 mock.Setup(ds => ds.StoreAsync("uSer", It.IsAny<TokenResponse>())).R
eturns(tcs.Task); |
| 162 |
| 163 var flow = CreateFlow(httpClientFactory: mockFactory, scopes: new[]
{ "a" }, dataStore: mock.Object); |
| 164 var response = flow.ExchangeCodeForTokenAsync("uSer", "c0de", "redIr
ect", CancellationToken.None).Result; |
| 165 SubtestTokenResponse(response); |
| 166 |
| 167 mock.Verify(ds => ds.StoreAsync("uSer", It.IsAny<TokenResponse>())); |
| 168 } |
| 169 |
| 170 [Test] |
| 171 public void TestRefreshTokenAsync() |
| 172 { |
| 173 var mock = new Mock<IDataStore>(); |
| 174 var handler = new FetchTokenMessageHandler(); |
| 175 handler.RefreshTokenRequest = new RefreshTokenRequest() |
| 176 { |
| 177 RefreshToken = "REFRESH", |
| 178 Scope = "a" |
| 179 }; |
| 180 MockHttpClientFactory mockFactory = new MockHttpClientFactory(handle
r); |
| 181 |
| 182 TaskCompletionSource<object> tcs = new TaskCompletionSource<object>(
); |
| 183 tcs.SetResult(null); |
| 184 mock.Setup(ds => ds.StoreAsync("uSer", It.IsAny<TokenResponse>())).R
eturns(tcs.Task); |
| 185 |
| 186 var flow = CreateFlow(httpClientFactory: mockFactory, scopes: new[]
{ "a" }, dataStore: mock.Object); |
| 187 var response = flow.RefreshTokenAsync("uSer", "REFRESH", Cancellatio
nToken.None).Result; |
| 188 SubtestTokenResponse(response); |
| 189 |
| 190 |
| 191 mock.Verify(ds => ds.StoreAsync("uSer", It.IsAny<TokenResponse>())); |
| 192 } |
| 193 |
| 194 #region FetchToken |
| 195 |
| 196 /// <summary> |
| 197 /// Fetch token message handler, which expects an authorization code tok
en request or a refresh token request. |
| 198 /// It verifies all the query parameters are valid and return an error r
esponse in case <see cref="Error"/>· |
| 199 /// is <c>true</c>. |
| 200 /// </summary> |
| 201 public class FetchTokenMessageHandler : CountableMessageHandler |
| 202 { |
| 203 internal AuthorizationCodeTokenRequest AuthorizationCodeTokenRequest
{ get; set; } |
| 204 internal RefreshTokenRequest RefreshTokenRequest { get; set; } |
| 205 internal bool Error { get; set; } |
| 206 |
| 207 protected override async Task<HttpResponseMessage> SendAsyncCore(Htt
pRequestMessage request, |
| 208 CancellationToken taskCancellationToken) |
| 209 { |
| 210 Assert.That(request.RequestUri, Is.EqualTo(new Uri(TokenUrl))); |
| 211 |
| 212 if (AuthorizationCodeTokenRequest != null) |
| 213 { |
| 214 // Verify right parameters. |
| 215 var content = await request.Content.ReadAsStringAsync(); |
| 216 foreach (var parameter in content.Split('&')) |
| 217 { |
| 218 var keyValue = parameter.Split('='); |
| 219 switch (keyValue[0]) |
| 220 { |
| 221 case "code": |
| 222 Assert.That(keyValue[1], Is.EqualTo("c0de")); |
| 223 break; |
| 224 case "redirect_uri": |
| 225 Assert.That(keyValue[1], Is.EqualTo("redIrect"))
; |
| 226 break; |
| 227 case "scope": |
| 228 Assert.That(keyValue[1], Is.EqualTo("a")); |
| 229 break; |
| 230 case "grant_type": |
| 231 Assert.That(keyValue[1], Is.EqualTo("authorizati
on_code")); |
| 232 break; |
| 233 case "client_id": |
| 234 Assert.That(keyValue[1], Is.EqualTo("id")); |
| 235 break; |
| 236 case "client_secret": |
| 237 Assert.That(keyValue[1], Is.EqualTo("secret")); |
| 238 break; |
| 239 default: |
| 240 throw new ArgumentOutOfRangeException("Invalid p
arameter!"); |
| 241 } |
| 242 } |
| 243 } |
| 244 else |
| 245 { |
| 246 // Verify right parameters. |
| 247 var content = await request.Content.ReadAsStringAsync(); |
| 248 foreach (var parameter in content.Split('&')) |
| 249 { |
| 250 var keyValue = parameter.Split('='); |
| 251 switch (keyValue[0]) |
| 252 { |
| 253 case "refresh_token": |
| 254 Assert.That(keyValue[1], Is.EqualTo("REFRESH")); |
| 255 break; |
| 256 case "scope": |
| 257 Assert.That(keyValue[1], Is.EqualTo("a")); |
| 258 break; |
| 259 case "grant_type": |
| 260 Assert.That(keyValue[1], Is.EqualTo("refresh_tok
en")); |
| 261 break; |
| 262 case "client_id": |
| 263 Assert.That(keyValue[1], Is.EqualTo("id")); |
| 264 break; |
| 265 case "client_secret": |
| 266 Assert.That(keyValue[1], Is.EqualTo("secret")); |
| 267 break; |
| 268 default: |
| 269 throw new ArgumentOutOfRangeException("Invalid p
arameter!"); |
| 270 } |
| 271 } |
| 272 } |
| 273 |
| 274 var response = new HttpResponseMessage(); |
| 275 if (Error) |
| 276 { |
| 277 response.StatusCode = System.Net.HttpStatusCode.BadRequest; |
| 278 var serializedObject = NewtonsoftJsonSerializer.Instance.Ser
ialize(new TokenErrorResponse |
| 279 { |
| 280 Error = "error", |
| 281 ErrorDescription = "desc", |
| 282 ErrorUri = "uri" |
| 283 }); |
| 284 response.Content = new StringContent(serializedObject, Encod
ing.UTF8); |
| 285 } |
| 286 else |
| 287 { |
| 288 var serializedObject = NewtonsoftJsonSerializer.Instance.Ser
ialize(new TokenResponse |
| 289 { |
| 290 AccessToken = "a", |
| 291 RefreshToken = "r", |
| 292 ExpiresInSeconds = 100, |
| 293 Scope = "b", |
| 294 }); |
| 295 response.Content = new StringContent(serializedObject, Encod
ing.UTF8); |
| 296 } |
| 297 |
| 298 return response; |
| 299 } |
| 300 } |
| 301 |
| 302 [Test] |
| 303 public void TestFetchTokenAsync_AuthorizationCodeRequest() |
| 304 { |
| 305 var handler = new FetchTokenMessageHandler(); |
| 306 handler.AuthorizationCodeTokenRequest = new AuthorizationCodeTokenRe
quest() |
| 307 { |
| 308 Code = "c0de", |
| 309 RedirectUri = "redIrect", |
| 310 Scope = "a" |
| 311 }; |
| 312 MockHttpClientFactory mockFactory = new MockHttpClientFactory(handle
r); |
| 313 |
| 314 var flow = CreateFlow(httpClientFactory: mockFactory); |
| 315 var response = flow.FetchTokenAsync("user", handler.AuthorizationCod
eTokenRequest, |
| 316 CancellationToken.None).Result; |
| 317 SubtestTokenResponse(response); |
| 318 } |
| 319 |
| 320 [Test] |
| 321 public void TestFetchTokenAsync_RefreshTokenRequest() |
| 322 { |
| 323 var handler = new FetchTokenMessageHandler(); |
| 324 handler.RefreshTokenRequest = new RefreshTokenRequest() |
| 325 { |
| 326 RefreshToken = "REFRESH", |
| 327 Scope = "a" |
| 328 }; |
| 329 |
| 330 MockHttpClientFactory mockFactory = new MockHttpClientFactory(handle
r); |
| 331 |
| 332 var flow = CreateFlow(httpClientFactory: mockFactory); |
| 333 var response = flow.FetchTokenAsync("user", handler.RefreshTokenRequ
est, CancellationToken.None).Result; |
| 334 SubtestTokenResponse(response); |
| 335 } |
| 336 |
| 337 [Test] |
| 338 public void TestFetchTokenAsync_AuthorizationCodeRequest_Error() |
| 339 { |
| 340 var handler = new FetchTokenMessageHandler(); |
| 341 handler.AuthorizationCodeTokenRequest = new AuthorizationCodeTokenRe
quest() |
| 342 { |
| 343 Code = "c0de", |
| 344 RedirectUri = "redIrect", |
| 345 Scope = "a" |
| 346 }; |
| 347 handler.Error = true; |
| 348 SubtestFetchTokenAsync_Error(handler); |
| 349 } |
| 350 |
| 351 [Test] |
| 352 public void TestFetchTokenAsync_RefreshTokenRequest_Error() |
| 353 { |
| 354 var handler = new FetchTokenMessageHandler(); |
| 355 handler.RefreshTokenRequest = new RefreshTokenRequest() |
| 356 { |
| 357 RefreshToken = "REFRESH", |
| 358 Scope = "a" |
| 359 }; |
| 360 handler.Error = true; |
| 361 SubtestFetchTokenAsync_Error(handler); |
| 362 } |
| 363 |
| 364 /// <summary>Subtest for receiving an error token response.</summary> |
| 365 /// <param name="handler">The message handler.</param> |
| 366 private void SubtestFetchTokenAsync_Error(FetchTokenMessageHandler handl
er) |
| 367 { |
| 368 MockHttpClientFactory mockFactory = new MockHttpClientFactory(handle
r); |
| 369 var flow = CreateFlow(httpClientFactory: mockFactory); |
| 370 try |
| 371 { |
| 372 var request = |
| 373 (TokenRequest)handler.AuthorizationCodeTokenRequest ?? (Toke
nRequest)handler.RefreshTokenRequest; |
| 374 var result = flow.FetchTokenAsync("user", request, CancellationT
oken.None).Result; |
| 375 Assert.Fail(); |
| 376 } |
| 377 catch (AggregateException aex) |
| 378 { |
| 379 var ex = aex.InnerException as TokenResponseException; |
| 380 Assert.IsNotNull(ex); |
| 381 var result = ex.Error; |
| 382 Assert.That(result.Error, Is.EqualTo("error")); |
| 383 Assert.That(result.ErrorDescription, Is.EqualTo("desc")); |
| 384 Assert.That(result.ErrorUri, Is.EqualTo("uri")); |
| 385 } |
| 386 } |
| 387 |
| 388 #endregion |
| 389 |
| 390 /// <summary>Creates an authorization code flow with the given parameter
s.</summary> |
| 391 /// <param name="dataStore">The data store.</param> |
| 392 /// <param name="scopes">The Scopes.</param> |
| 393 /// <param name="httpClientFactory">The HTTP client factory. If not set
the default will be used.</param> |
| 394 /// <returns>Authorization code flow</returns> |
| 395 private AuthorizationCodeFlow CreateFlow(IDataStore dataStore = null, IE
numerable<string> scopes = null, |
| 396 IHttpClientFactory httpClientFactory = null) |
| 397 { |
| 398 var secrets = new ClientSecrets() { ClientId = "id", ClientSecret =
"secret" }; |
| 399 var initializer = new AuthorizationCodeFlow.Initializer(Authorizatio
nCodeUrl, TokenUrl) |
| 400 { |
| 401 ClientSecrets = secrets, |
| 402 HttpClientFactory = httpClientFactory |
| 403 }; |
| 404 |
| 405 if (dataStore != null) |
| 406 { |
| 407 initializer.DataStore = dataStore; |
| 408 } |
| 409 if (scopes != null) |
| 410 { |
| 411 initializer.Scopes = scopes; |
| 412 } |
| 413 return new AuthorizationCodeFlow(initializer); |
| 414 } |
| 415 |
| 416 /// <summary>Verifies that the token response contains the expected data
.</summary> |
| 417 /// <param name="response">The token response</param> |
| 418 private void SubtestTokenResponse(TokenResponse response) |
| 419 { |
| 420 Assert.That(response.RefreshToken, Is.EqualTo("r")); |
| 421 Assert.That(response.ExpiresInSeconds, Is.EqualTo(100)); |
| 422 Assert.That(response.Scope, Is.EqualTo("b")); |
| 423 } |
| 424 } |
| 425 } |
OLD | NEW |