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("client secret or client secret
stream MUST be set"), |
| 61 "User MUST specify client secrets!"); |
| 62 } |
| 63 } |
| 64 |
| 65 [Test] |
| 66 public void TestConstructor_DefaultValues() |
| 67 { |
| 68 var flow = CreateFlow(); |
| 69 Assert.NotNull(flow.AccessMethod); |
| 70 Assert.That(flow.AccessMethod, Is.InstanceOf<BearerToken.Authorizati
onHeaderAccessMethod>()); |
| 71 Assert.That(flow.AuthorizationServerUrl, Is.EqualTo("https://authori
zation.com")); |
| 72 Assert.NotNull(flow.ClientSecrets); |
| 73 Assert.That(flow.ClientSecrets.ClientId, Is.EqualTo("id")); |
| 74 Assert.That(flow.ClientSecrets.ClientSecret, Is.EqualTo("secret")); |
| 75 Assert.That(flow.Clock, Is.InstanceOf<SystemClock>()); |
| 76 Assert.Null(flow.DataStore); |
| 77 Assert.NotNull(flow.HttpClient); |
| 78 Assert.NotNull(flow.Scopes); |
| 79 Assert.That(flow.TokenServerEncodedUrl, Is.EqualTo("https://token.co
m")); |
| 80 |
| 81 Assert.That(flow.HttpClient.MessageHandler.UnsuccessfulResponseHandl
ers.Count(), Is.EqualTo(1)); |
| 82 Assert.That(flow.HttpClient.MessageHandler.UnsuccessfulResponseHandl
ers.First(), |
| 83 Is.InstanceOf<BackOffHandler>()); |
| 84 } |
| 85 |
| 86 #endregion |
| 87 |
| 88 #region LoadToken |
| 89 |
| 90 [Test] |
| 91 public void LoadToken_NoDataStore() |
| 92 { |
| 93 var flow = CreateFlow(); |
| 94 Assert.Null(flow.LoadToken("user", CancellationToken.None).Result); |
| 95 } |
| 96 |
| 97 [Test] |
| 98 public void LoadToken_NullResponse() |
| 99 { |
| 100 TaskCompletionSource<TokenResponse> tcs = new TaskCompletionSource<T
okenResponse>(); |
| 101 tcs.SetResult(null); |
| 102 Assert.Null(SubtestLoadToken(tcs)); |
| 103 } |
| 104 |
| 105 [Test] |
| 106 public void LoadToken_TokenResponse() |
| 107 { |
| 108 TokenResponse response = new TokenResponse |
| 109 { |
| 110 AccessToken = "access" |
| 111 }; |
| 112 |
| 113 TaskCompletionSource<TokenResponse> tcs = new TaskCompletionSource<T
okenResponse>(); |
| 114 tcs.SetResult(response); |
| 115 var result = SubtestLoadToken(tcs); |
| 116 Assert.That(result, Is.EqualTo(response)); |
| 117 } |
| 118 |
| 119 private TokenResponse SubtestLoadToken(TaskCompletionSource<TokenRespons
e> tcs) |
| 120 { |
| 121 var mock = new Mock<IDataStore>(); |
| 122 mock.Setup(ds => ds.Get<TokenResponse>("user")).Returns(tcs.Task); |
| 123 var flow = CreateFlow(dataStore: mock.Object); |
| 124 var result = flow.LoadToken("user", CancellationToken.None).Result; |
| 125 // Verify Get("user") was called. |
| 126 mock.Verify(ds => ds.Get<TokenResponse>("user")); |
| 127 return result; |
| 128 } |
| 129 |
| 130 #endregion |
| 131 |
| 132 #region CreateAuthorizationCodeRequest |
| 133 |
| 134 [Test] |
| 135 public void TestCreateAuthorizationCodeRequest() |
| 136 { |
| 137 var request = CreateFlow(scopes: new[] { "a", "b" }).CreateAuthoriza
tionCodeRequest("redirect"); |
| 138 Assert.That(request.AuthorizationServerUrl, Is.EqualTo(new Uri(Autho
rizationCodeUrl))); |
| 139 Assert.That(request.ClientId, Is.EqualTo("id")); |
| 140 Assert.That(request.RedirectUri, Is.EqualTo("redirect")); |
| 141 Assert.That(request.ResponseType, Is.EqualTo("code")); |
| 142 Assert.That(request.Scope, Is.EqualTo("a b")); |
| 143 Assert.Null(request.State); |
| 144 } |
| 145 |
| 146 #endregion |
| 147 |
| 148 [Test] |
| 149 public void TestExchangeCodeForToken() |
| 150 { |
| 151 var mock = new Mock<IDataStore>(); |
| 152 var handler = new FetchTokenMessageHandler(); |
| 153 handler.AuthorizationCodeTokenRequest = new AuthorizationCodeTokenRe
quest() |
| 154 { |
| 155 Code = "c0de", |
| 156 RedirectUri = "redIrect", |
| 157 Scope = "a" |
| 158 }; |
| 159 MockHttpClientFactory mockFactory = new MockHttpClientFactory(handle
r); |
| 160 |
| 161 TaskCompletionSource<object> tcs = new TaskCompletionSource<object>(
); |
| 162 tcs.SetResult(null); |
| 163 mock.Setup(ds => ds.Store("uSer", It.IsAny<TokenResponse>())).Return
s(tcs.Task); |
| 164 |
| 165 var flow = CreateFlow(httpClientFactory: mockFactory, scopes: new[]
{ "a" }, dataStore: mock.Object); |
| 166 var response = flow.ExchangeCodeForToken("uSer", "c0de", "redIrect",
CancellationToken.None).Result; |
| 167 SubtestTokenResponse(response); |
| 168 |
| 169 mock.Verify(ds => ds.Store("uSer", It.IsAny<TokenResponse>())); |
| 170 } |
| 171 |
| 172 [Test] |
| 173 public void TestRefreshToken() |
| 174 { |
| 175 var mock = new Mock<IDataStore>(); |
| 176 var handler = new FetchTokenMessageHandler(); |
| 177 handler.RefreshTokenRequest = new RefreshTokenRequest() |
| 178 { |
| 179 RefreshToken = "REFRESH", |
| 180 Scope = "a" |
| 181 }; |
| 182 MockHttpClientFactory mockFactory = new MockHttpClientFactory(handle
r); |
| 183 |
| 184 TaskCompletionSource<object> tcs = new TaskCompletionSource<object>(
); |
| 185 tcs.SetResult(null); |
| 186 mock.Setup(ds => ds.Store("uSer", It.IsAny<TokenResponse>())).Return
s(tcs.Task); |
| 187 |
| 188 var flow = CreateFlow(httpClientFactory: mockFactory, scopes: new[]
{ "a" }, dataStore: mock.Object); |
| 189 var response = flow.RefreshToken("uSer", "REFRESH", CancellationToke
n.None).Result; |
| 190 SubtestTokenResponse(response); |
| 191 |
| 192 |
| 193 mock.Verify(ds => ds.Store("uSer", It.IsAny<TokenResponse>())); |
| 194 } |
| 195 |
| 196 #region FetchToken |
| 197 |
| 198 /// <summary> |
| 199 /// Fetch token message handler, which expects an authorization code tok
en request or a refresh token request. |
| 200 /// It verifies all the query parameters are valid and return an error r
esponse in case <see cref="Error"/>· |
| 201 /// is <c>true</c>. |
| 202 /// </summary> |
| 203 public class FetchTokenMessageHandler : CountableMessageHandler |
| 204 { |
| 205 internal AuthorizationCodeTokenRequest AuthorizationCodeTokenRequest
{ get; set; } |
| 206 internal RefreshTokenRequest RefreshTokenRequest { get; set; } |
| 207 internal bool Error { get; set; } |
| 208 |
| 209 protected override async Task<HttpResponseMessage> SendAsyncCore(Htt
pRequestMessage request, |
| 210 CancellationToken taskCancellationToken) |
| 211 { |
| 212 Assert.That(request.RequestUri, Is.EqualTo(new Uri(TokenUrl))); |
| 213 |
| 214 if (AuthorizationCodeTokenRequest != null) |
| 215 { |
| 216 // Verify right parameters. |
| 217 var content = await request.Content.ReadAsStringAsync(); |
| 218 foreach (var parameter in content.Split('&')) |
| 219 { |
| 220 var keyValue = parameter.Split('='); |
| 221 switch (keyValue[0]) |
| 222 { |
| 223 case "code": |
| 224 Assert.That(keyValue[1], Is.EqualTo("c0de")); |
| 225 break; |
| 226 case "redirect_uri": |
| 227 Assert.That(keyValue[1], Is.EqualTo("redIrect"))
; |
| 228 break; |
| 229 case "scope": |
| 230 Assert.That(keyValue[1], Is.EqualTo("a")); |
| 231 break; |
| 232 case "grant_type": |
| 233 Assert.That(keyValue[1], Is.EqualTo("authorizati
on_code")); |
| 234 break; |
| 235 case "client_id": |
| 236 Assert.That(keyValue[1], Is.EqualTo("id")); |
| 237 break; |
| 238 case "client_secret": |
| 239 Assert.That(keyValue[1], Is.EqualTo("secret")); |
| 240 break; |
| 241 default: |
| 242 throw new ArgumentOutOfRangeException("Invalid p
arameter!"); |
| 243 } |
| 244 } |
| 245 } |
| 246 else |
| 247 { |
| 248 // Verify right parameters. |
| 249 var content = await request.Content.ReadAsStringAsync(); |
| 250 foreach (var parameter in content.Split('&')) |
| 251 { |
| 252 var keyValue = parameter.Split('='); |
| 253 switch (keyValue[0]) |
| 254 { |
| 255 case "refresh_token": |
| 256 Assert.That(keyValue[1], Is.EqualTo("REFRESH")); |
| 257 break; |
| 258 case "scope": |
| 259 Assert.That(keyValue[1], Is.EqualTo("a")); |
| 260 break; |
| 261 case "grant_type": |
| 262 Assert.That(keyValue[1], Is.EqualTo("refresh_tok
en")); |
| 263 break; |
| 264 case "client_id": |
| 265 Assert.That(keyValue[1], Is.EqualTo("id")); |
| 266 break; |
| 267 case "client_secret": |
| 268 Assert.That(keyValue[1], Is.EqualTo("secret")); |
| 269 break; |
| 270 default: |
| 271 throw new ArgumentOutOfRangeException("Invalid p
arameter!"); |
| 272 } |
| 273 } |
| 274 } |
| 275 |
| 276 var response = new HttpResponseMessage(); |
| 277 if (Error) |
| 278 { |
| 279 response.StatusCode = System.Net.HttpStatusCode.BadRequest; |
| 280 var serializedObject = NewtonsoftJsonSerializer.Instance.Ser
ialize(new TokenErrorResponse |
| 281 { |
| 282 Error = "error", |
| 283 ErrorDescription = "desc", |
| 284 ErrorUri = "uri" |
| 285 }); |
| 286 response.Content = new StringContent(serializedObject, Encod
ing.UTF8); |
| 287 } |
| 288 else |
| 289 { |
| 290 var serializedObject = NewtonsoftJsonSerializer.Instance.Ser
ialize(new TokenResponse |
| 291 { |
| 292 AccessToken = "a", |
| 293 RefreshToken = "r", |
| 294 ExpiresInSeconds = 100, |
| 295 Scope = "b", |
| 296 }); |
| 297 response.Content = new StringContent(serializedObject, Encod
ing.UTF8); |
| 298 } |
| 299 |
| 300 return response; |
| 301 } |
| 302 } |
| 303 |
| 304 [Test] |
| 305 public void TestFetchToken_AuthorizationCodeRequest() |
| 306 { |
| 307 var handler = new FetchTokenMessageHandler(); |
| 308 handler.AuthorizationCodeTokenRequest = new AuthorizationCodeTokenRe
quest() |
| 309 { |
| 310 Code = "c0de", |
| 311 RedirectUri = "redIrect", |
| 312 Scope = "a" |
| 313 }; |
| 314 MockHttpClientFactory mockFactory = new MockHttpClientFactory(handle
r); |
| 315 |
| 316 var flow = CreateFlow(httpClientFactory: mockFactory); |
| 317 var response = flow.FetchToken("user", handler.AuthorizationCodeToke
nRequest, |
| 318 CancellationToken.None).Result; |
| 319 SubtestTokenResponse(response); |
| 320 } |
| 321 |
| 322 [Test] |
| 323 public void TestFetchToken_RefreshTokenRequest() |
| 324 { |
| 325 var handler = new FetchTokenMessageHandler(); |
| 326 handler.RefreshTokenRequest = new RefreshTokenRequest() |
| 327 { |
| 328 RefreshToken = "REFRESH", |
| 329 Scope = "a" |
| 330 }; |
| 331 |
| 332 MockHttpClientFactory mockFactory = new MockHttpClientFactory(handle
r); |
| 333 |
| 334 var flow = CreateFlow(httpClientFactory: mockFactory); |
| 335 var response = flow.FetchToken("user", handler.RefreshTokenRequest,
CancellationToken.None).Result; |
| 336 SubtestTokenResponse(response); |
| 337 } |
| 338 |
| 339 [Test] |
| 340 public void TestFetchToken_AuthorizationCodeRequest_Error() |
| 341 { |
| 342 var handler = new FetchTokenMessageHandler(); |
| 343 handler.AuthorizationCodeTokenRequest = new AuthorizationCodeTokenRe
quest() |
| 344 { |
| 345 Code = "c0de", |
| 346 RedirectUri = "redIrect", |
| 347 Scope = "a" |
| 348 }; |
| 349 handler.Error = true; |
| 350 SubtestFetchToken_Error(handler); |
| 351 } |
| 352 |
| 353 [Test] |
| 354 public void TestFetchToken_RefreshTokenRequest_Error() |
| 355 { |
| 356 var handler = new FetchTokenMessageHandler(); |
| 357 handler.RefreshTokenRequest = new RefreshTokenRequest() |
| 358 { |
| 359 RefreshToken = "REFRESH", |
| 360 Scope = "a" |
| 361 }; |
| 362 handler.Error = true; |
| 363 SubtestFetchToken_Error(handler); |
| 364 } |
| 365 |
| 366 /// <summary>Subtest for receiving an error token response.</summary> |
| 367 /// <param name="handler">The message handler</param> |
| 368 private void SubtestFetchToken_Error(FetchTokenMessageHandler handler) |
| 369 { |
| 370 MockHttpClientFactory mockFactory = new MockHttpClientFactory(handle
r); |
| 371 var flow = CreateFlow(httpClientFactory: mockFactory); |
| 372 try |
| 373 { |
| 374 var request = |
| 375 (TokenRequest)handler.AuthorizationCodeTokenRequest ?? (Toke
nRequest)handler.RefreshTokenRequest; |
| 376 var result = flow.FetchToken("user", request, CancellationToken.
None).Result; |
| 377 Assert.Fail(); |
| 378 } |
| 379 catch (AggregateException aex) |
| 380 { |
| 381 var ex = aex.InnerException as TokenResponseException; |
| 382 Assert.IsNotNull(ex); |
| 383 var result = ex.Error; |
| 384 Assert.That(result.Error, Is.EqualTo("error")); |
| 385 Assert.That(result.ErrorDescription, Is.EqualTo("desc")); |
| 386 Assert.That(result.ErrorUri, Is.EqualTo("uri")); |
| 387 } |
| 388 } |
| 389 |
| 390 #endregion |
| 391 |
| 392 /// <summary>Creates an authorization code flow with the given parameter
s.</summary> |
| 393 /// <param name="dataStore">The data store.</param> |
| 394 /// <param name="scopes">The Scopes</param> |
| 395 /// <param name="httpClientFactory">The HTTP client factory. If not set
the default will be used</param> |
| 396 /// <returns>Authorization code flow</returns> |
| 397 private AuthorizationCodeFlow CreateFlow(IDataStore dataStore = null, IE
numerable<string> scopes = null, |
| 398 IHttpClientFactory httpClientFactory = null) |
| 399 { |
| 400 var secrets = new ClientSecrets() { ClientId = "id", ClientSecret =
"secret" }; |
| 401 var initializer = new AuthorizationCodeFlow.Initializer(Authorizatio
nCodeUrl, TokenUrl) |
| 402 { |
| 403 ClientSecrets = secrets, |
| 404 HttpClientFactory = httpClientFactory |
| 405 }; |
| 406 |
| 407 if (dataStore != null) |
| 408 { |
| 409 initializer.DataStore = dataStore; |
| 410 } |
| 411 if (scopes != null) |
| 412 { |
| 413 initializer.Scopes = scopes; |
| 414 } |
| 415 return new AuthorizationCodeFlow(initializer); |
| 416 } |
| 417 |
| 418 /// <summary>Verifies that the token response contains the expected data
.</summary> |
| 419 /// <param name="response">The token response</param> |
| 420 private void SubtestTokenResponse(TokenResponse response) |
| 421 { |
| 422 Assert.That(response.RefreshToken, Is.EqualTo("r")); |
| 423 Assert.That(response.ExpiresInSeconds, Is.EqualTo(100)); |
| 424 Assert.That(response.Scope, Is.EqualTo("b")); |
| 425 } |
| 426 } |
| 427 } |
OLD | NEW |