public void Handle_error(string id_, string jwtResponse, Type expectedExceptionType, int expectedCode, int expectedStatus, string expectedMessage, string expectedDeveloperMessage) { var testApiKey = ClientApiKeys.Builder() .SetId("2EV70AHRTYF0JOA7OEFO3SM29") .SetSecret("goPUHQMkS4dlKwl5wtbNd91I+UrRehCsEDJrIrMruK8") .Build(); var fakeRequestExecutor = Substitute.For<IRequestExecutor>(); fakeRequestExecutor.ApiKey.Returns(testApiKey); this.dataStore = TestDataStore.Create(fakeRequestExecutor, Caches.NewInMemoryCacheProvider().Build()); var request = new DefaultHttpRequest(HttpMethod.Get, new CanonicalUri($"https://foo.bar?{IdSiteClaims.JwtResponse}={jwtResponse}")); IIdSiteSyncCallbackHandler callbackHandler = new DefaultIdSiteSyncCallbackHandler(this.dataStore, request); try { var accountResult = callbackHandler.GetAccountResult(); throw new Exception("Should not reach here. Proper exception was not thrown."); } catch (IdSiteRuntimeException e) when (expectedExceptionType.IsAssignableFrom(e.GetType())) { e.Code.ShouldBe(expectedCode); e.HttpStatus.ShouldBe(expectedStatus); e.Message.ShouldBe(expectedMessage); e.DeveloperMessage.ShouldBe(expectedDeveloperMessage); } catch (Exception e) when (expectedExceptionType.IsAssignableFrom(e.GetType())) { e.Message.ShouldStartWith(expectedMessage); } }
public void Throws_for_empty_request_URI() { var apiKey = new DefaultClientApiKey("foo", "bar"); var request = new DefaultHttpRequest(HttpMethod.Get, null); Should.Throw<RequestAuthenticationException>(() => { this.authenticator.AuthenticateCore(request, apiKey, this.fakeNow, this.fakeNonce); }); }
public void Adds_XStormpathDate_header() { var request = new DefaultHttpRequest(HttpMethod.Get, new CanonicalUri(this.uriQualifier.EnsureFullyQualified("/bar"))); var now = new DateTimeOffset(2015, 08, 01, 06, 30, 00, TimeSpan.Zero); this.authenticator.AuthenticateCore(request, this.apiKey, now); // X-Stormpath-Date -> current time in UTC var stormpathDateHeader = Iso8601.Parse(request.Headers.GetFirst<string>("X-Stormpath-Date")); stormpathDateHeader.ShouldBe(now); }
public void Adds_Host_header() { var apiKey = new DefaultClientApiKey("foo", "bar"); var uriQualifier = new UriQualifier("http://api.foo.bar"); var request = new DefaultHttpRequest(HttpMethod.Get, new CanonicalUri(uriQualifier.EnsureFullyQualified("/stuff"))); this.authenticator.AuthenticateCore(request, apiKey, this.fakeNow, this.fakeNonce); // Host: [hostname] request.Headers.Host.ShouldBe("api.foo.bar"); }
public void Adds_XStormpathDate_header() { var apiKey = new DefaultClientApiKey("foo", "bar"); var uriQualifier = new UriQualifier("http://api.foo.bar"); var request = new DefaultHttpRequest(HttpMethod.Get, new CanonicalUri(uriQualifier.EnsureFullyQualified("/stuff"))); this.authenticator.AuthenticateCore(request, apiKey, this.fakeNow, this.fakeNonce); // X-Stormpath-Date -> current time in UTC var stormpathDateHeader = Iso8601.Parse(request.Headers.GetFirst<string>("X-Stormpath-Date")); stormpathDateHeader.ShouldBe(this.fakeNow); }
public void Adds_Basic_authorization_header() { var request = new DefaultHttpRequest(HttpMethod.Get, new CanonicalUri(this.uriQualifier.EnsureFullyQualified("/bar"))); var now = new DateTimeOffset(2015, 08, 01, 06, 30, 00, TimeSpan.Zero); this.authenticator.AuthenticateCore(request, this.apiKey, now); // Authorization: "Basic [base64 stuff]" var authenticationHeader = request.Headers.Authorization; authenticationHeader.Scheme.ShouldBe("Basic"); Base64.Decode(authenticationHeader.Parameter, Encoding.UTF8).ShouldBe($"{this.fakeApiKeyId}:{this.fakeApiKeySecret}"); }
public void Retries_request_on_recoverable_error() { // Set up a fake HttpClient that mysteriously always fails with recoverable errors var failingHttpClient = GetSynchronousClient( new DefaultHttpResponse(0, null, new HttpHeaders(), null, null, transportError: true)); var defaultBackoffStrategy = GetFakeBackoffStrategy(); var throttlingBackoffStrategy = GetFakeBackoffStrategy(); var requestExecutor = GetRequestExecutor(failingHttpClient, defaultBackoffStrategy, throttlingBackoffStrategy); var dummyRequest = new DefaultHttpRequest(HttpMethod.Delete, new CanonicalUri("http://api.foo.bar/foo")); var response = requestExecutor.Execute(dummyRequest); response.TransportError.ShouldBeTrue(); // Used default backoff strategy to pause after each retry (4 times) defaultBackoffStrategy.ReceivedWithAnyArgs(4).GetDelayMilliseconds(0); throttlingBackoffStrategy.DidNotReceiveWithAnyArgs().GetDelayMilliseconds(0); }
public async Task Retries_request_on_HTTP_503() { // Set up a fake HttpClient that awlays returns HTTP 503 var failingHttpClient = GetAsynchronousClient(new DefaultHttpResponse(503, null, new HttpHeaders(), null, null, transportError: false)); var defaultBackoffStrategy = GetFakeBackoffStrategy(); var throttlingBackoffStrategy = GetFakeBackoffStrategy(); var requestExecutor = GetRequestExecutor(failingHttpClient, defaultBackoffStrategy, throttlingBackoffStrategy); var dummyRequest = new DefaultHttpRequest(HttpMethod.Delete, new CanonicalUri("http://api.foo.bar/foo")); var response = await requestExecutor.ExecuteAsync(dummyRequest, CancellationToken.None); response.IsServerError().ShouldBeTrue(); // Used default backoff strategy to pause after each retry (4 times) defaultBackoffStrategy.ReceivedWithAnyArgs(4).GetDelayMilliseconds(0); throttlingBackoffStrategy.DidNotReceiveWithAnyArgs().GetDelayMilliseconds(0); }
public async Task Does_not_retry_when_task_is_canceled() { // Set up a fake HttpClient that throws for cancellation var throwingHttpClient = Substitute.For<IAsynchronousHttpClient>(); throwingHttpClient.IsAsynchronousSupported.Returns(true); throwingHttpClient .When(fake => fake.ExecuteAsync(Arg.Any<IHttpRequest>(), Arg.Any<CancellationToken>())) .Do(call => { call.Arg<CancellationToken>().ThrowIfCancellationRequested(); }); var requestExecutor = GetRequestExecutor(throwingHttpClient); var canceled = new CancellationTokenSource(); canceled.Cancel(); var dummyRequest = new DefaultHttpRequest(HttpMethod.Delete, new CanonicalUri("http://api.foo.bar/foo")); await Assert.ThrowsAsync<OperationCanceledException>(async () => { await requestExecutor.ExecuteAsync(dummyRequest, canceled.Token); }); // Should only have 1 call: no retries! await throwingHttpClient.Received(1).ExecuteAsync(Arg.Any<IHttpRequest>(), Arg.Any<CancellationToken>()); }
public void Retries_request_with_throttling_on_HTTP_429() { // Set up a fake HttpClient that always returns HTTP 429 var failingHttpClient = GetSynchronousClient( new DefaultHttpResponse(429, null, new HttpHeaders(), null, null, transportError: false)); var defaultBackoffStrategy = GetFakeBackoffStrategy(); var throttlingBackoffStrategy = GetFakeBackoffStrategy(); var requestExecutor = GetRequestExecutor(failingHttpClient, defaultBackoffStrategy, throttlingBackoffStrategy); var dummyRequest = new DefaultHttpRequest(HttpMethod.Delete, new CanonicalUri("http://api.foo.bar/foo")); var response = requestExecutor.Execute(dummyRequest); response.IsClientError().ShouldBeTrue(); // Used throttling backoff strategy to pause after each retry (4 times) defaultBackoffStrategy.DidNotReceiveWithAnyArgs().GetDelayMilliseconds(0); throttlingBackoffStrategy.ReceivedWithAnyArgs(4).GetDelayMilliseconds(0); }
public void Should_authenticate_request_without_query_params() { IClientApiKey apiKey = new DefaultClientApiKey("MyId", "Shush!"); var uriQualifier = new UriQualifier("http://api.stormpath.com/v1"); var request = new DefaultHttpRequest(HttpMethod.Get, new CanonicalUri(uriQualifier.EnsureFullyQualified("/"))); var now = new DateTimeOffset(2013, 7, 1, 0, 0, 0, TimeSpan.Zero); var nonce = "a43a9d25-ab06-421e-8605-33fd1e760825"; this.authenticator.AuthenticateCore(request, apiKey, now, nonce); var sauthc1Id = request.Headers.Authorization.Parameter.Split(' ')[0]; var sauthc1SignedHeaders = request.Headers.Authorization.Parameter.Split(' ')[1]; var sauthc1Signature = request.Headers.Authorization.Parameter.Split(' ')[2]; request.Headers.Authorization.Scheme.ShouldBe("SAuthc1"); sauthc1Id.ShouldBe("sauthc1Id=MyId/20130701/a43a9d25-ab06-421e-8605-33fd1e760825/sauthc1_request,"); sauthc1SignedHeaders.ShouldBe("sauthc1SignedHeaders=host;x-stormpath-date,"); sauthc1Signature.ShouldBe("sauthc1Signature=990a95aabbcbeb53e48fb721f73b75bd3ae025a2e86ad359d08558e1bbb9411c"); }
public void Adds_SAuthc1_authorization_header() { IClientApiKey apiKey = new DefaultClientApiKey("myAppId", "super-secret"); var uriQualifier = new UriQualifier("http://api.stormpath.com/v1"); var request = new DefaultHttpRequest(HttpMethod.Get, new CanonicalUri(uriQualifier.EnsureFullyQualified("/accounts"))); this.authenticator.AuthenticateCore(request, apiKey, this.fakeNow, this.fakeNonce); // Authorization: "SAuthc1 [signed hash]" var authenticationHeader = request.Headers.Authorization; authenticationHeader.Scheme.ShouldBe("SAuthc1"); authenticationHeader.Parameter.ShouldNotBe(null); // Format "sauthc1Id=[id string], sauthc1SignedHeaders=[host;x-stormpath-date;...], sauthc1Signature=[signature in hex]" var parts = authenticationHeader.Parameter.Split(' '); var sauthc1Id = parts[0].TrimEnd(',').SplitToKeyValuePair('='); var sauthc1SignedHeaders = parts[1].TrimEnd(',').SplitToKeyValuePair('='); var sauthc1Signature = parts[2].SplitToKeyValuePair('='); var dateString = this.fakeNow.ToString("yyyyMMdd", CultureInfo.InvariantCulture); sauthc1Id.Key.ShouldBe("sauthc1Id"); sauthc1Id.Value.ShouldBe($"{apiKey.GetId()}/{dateString}/{this.fakeNonce}/sauthc1_request"); sauthc1SignedHeaders.Key.ShouldBe("sauthc1SignedHeaders"); sauthc1SignedHeaders.Value.ShouldBe("host;x-stormpath-date"); sauthc1Signature.Key.ShouldBe("sauthc1Signature"); sauthc1Signature.Value.ShouldNotBe(null); }
public void Should_authenticate_request_with_multiple_query_params() { IClientApiKey apiKey = new DefaultClientApiKey("MyId", "Shush!"); var uriQualifier = new UriQualifier("http://api.stormpath.com/v1"); var request = new DefaultHttpRequest(HttpMethod.Get, new CanonicalUri(uriQualifier.EnsureFullyQualified("/applications/77JnfFiREjdfQH0SObMfjI/groups?q=group&limit=25&offset=25"))); var now = new DateTimeOffset(2013, 7, 1, 0, 0, 0, TimeSpan.Zero); var nonce = "a43a9d25-ab06-421e-8605-33fd1e760825"; this.authenticator.AuthenticateCore(request, apiKey, now, nonce); var sauthc1Id = request.Headers.Authorization.Parameter.Split(' ')[0]; var sauthc1SignedHeaders = request.Headers.Authorization.Parameter.Split(' ')[1]; var sauthc1Signature = request.Headers.Authorization.Parameter.Split(' ')[2]; request.Headers.Authorization.Scheme.ShouldBe("SAuthc1"); sauthc1Id.ShouldBe("sauthc1Id=MyId/20130701/a43a9d25-ab06-421e-8605-33fd1e760825/sauthc1_request,"); sauthc1SignedHeaders.ShouldBe("sauthc1SignedHeaders=host;x-stormpath-date,"); sauthc1Signature.ShouldBe("sauthc1Signature=e30a62c0d03ca6cb422e66039786865f3eb6269400941ede6226760553a832d3"); }
public void Should_authenticate_request_with_query_params() { IClientApiKey apiKey = new DefaultClientApiKey("MyId", "Shush!"); var uriQualifier = new UriQualifier("http://api.stormpath.com/v1"); var request = new DefaultHttpRequest(HttpMethod.Get, new CanonicalUri(uriQualifier.EnsureFullyQualified("/directories?orderBy=name+asc"))); var now = new DateTimeOffset(2013, 7, 1, 0, 0, 0, TimeSpan.Zero); var nonce = "a43a9d25-ab06-421e-8605-33fd1e760825"; this.authenticator.AuthenticateCore(request, apiKey, now, nonce); var sauthc1Id = request.Headers.Authorization.Parameter.Split(' ')[0]; var sauthc1SignedHeaders = request.Headers.Authorization.Parameter.Split(' ')[1]; var sauthc1Signature = request.Headers.Authorization.Parameter.Split(' ')[2]; request.Headers.Authorization.Scheme.ShouldBe("SAuthc1"); sauthc1Id.ShouldBe("sauthc1Id=MyId/20130701/a43a9d25-ab06-421e-8605-33fd1e760825/sauthc1_request,"); sauthc1SignedHeaders.ShouldBe("sauthc1SignedHeaders=host;x-stormpath-date,"); sauthc1Signature.ShouldBe("sauthc1Signature=fc04c5187cc017bbdf9c0bb743a52a9487ccb91c0996267988ceae3f10314176"); }
public void Handle_response(string id_, string jwtResponse, string expectedStatus, bool isNewAccount, string expectedState) { IAccountResult accountResultFromListener = null; var listener = new InlineIdSiteSyncResultListener( onAuthenticated: result => { if (expectedStatus == IdSiteResultStatus.Authenticated) { accountResultFromListener = result; } else { throw new InvalidOperationException("This method should not have been executed"); } }, onLogout: result => { if (expectedStatus == IdSiteResultStatus.Logout) { accountResultFromListener = result; } else { throw new InvalidOperationException("This method should not have been executed"); } }, onRegistered: result => { if (expectedStatus == IdSiteResultStatus.Registered) { accountResultFromListener = result; } else { throw new InvalidOperationException("This method should not have been executed"); } }); var testApiKey = ClientApiKeys.Builder().SetId("2EV70AHRTYF0JOA7OEFO3SM29").SetSecret("goPUHQMkS4dlKwl5wtbNd91I+UrRehCsEDJrIrMruK8").Build(); var fakeRequestExecutor = Substitute.For<IRequestExecutor>(); fakeRequestExecutor.ApiKey.Returns(testApiKey); this.dataStore = TestDataStore.Create(fakeRequestExecutor, Caches.NewInMemoryCacheProvider().Build()); var request = new DefaultHttpRequest(HttpMethod.Get, new CanonicalUri($"https://foo.bar?{IdSiteClaims.JwtResponse}={jwtResponse}")); IIdSiteSyncCallbackHandler callbackHandler = new DefaultIdSiteSyncCallbackHandler(this.dataStore, request); callbackHandler.SetResultListener(listener); var accountResult = callbackHandler.GetAccountResult(); // Validate result (accountResult as DefaultAccountResult).Account.Href.ShouldBe("https://api.stormpath.com/v1/accounts/7Ora8KfVDEIQP38KzrYdAs"); (accountResultFromListener as DefaultAccountResult).Account.Href.ShouldBe("https://api.stormpath.com/v1/accounts/7Ora8KfVDEIQP38KzrYdAs"); accountResult.IsNewAccount.ShouldBe(isNewAccount); accountResultFromListener.IsNewAccount.ShouldBe(isNewAccount); accountResult.State.ShouldBe(expectedState); accountResultFromListener.State.ShouldBe(expectedState); var expectedResultStatus = IdSiteResultStatus.Parse(expectedStatus); accountResult.Status.ShouldBe(expectedResultStatus); accountResultFromListener.Status.ShouldBe(expectedResultStatus); }
private async Task<IHttpResponse> CoreRequestLoopAsync( IHttpRequest request, Func<IHttpRequest, CancellationToken, Task<IHttpResponse>> executeAction, Func<int, bool, CancellationToken, Task> pauseAction, CancellationToken cancellationToken) { var attempts = 0; bool throttling = false; Uri currentUri = request.CanonicalUri.ToUri(); while (true) { var currentRequest = new DefaultHttpRequest(request, overrideUri: currentUri); // Sign and build request this.requestAuthenticator.Authenticate(currentRequest, this.apiKey); try { if (attempts > 1) { this.logger.Trace("Pausing before retry", "DefaultRequestExecutor.CoreRequestLoopAsync"); await pauseAction(attempts - 1, throttling, cancellationToken).ConfigureAwait(false); } var response = await executeAction(currentRequest, cancellationToken).ConfigureAwait(false); if (this.IsRedirect(response)) { currentUri = response.Headers.Location; this.logger.Trace($"Redirected to {currentUri}", "DefaultRequestExecutor.CoreRequestLoopAsync"); continue; // re-execute request, not counted as a retry } var statusCode = response.StatusCode; if (response.TransportError && attempts < this.maxAttemptsPerRequest) { this.logger.Warn($"Recoverable transport error during request, retrying", "DefaultRequestExecutor.CoreRequestLoopAsync"); attempts++; continue; // retry request } // HTTP 429 if (statusCode == TooManyRequests && attempts < this.maxAttemptsPerRequest) { throttling = true; this.logger.Warn($"Got HTTP 429, throttling, retrying", "DefaultRequestExecutor.CoreRequestLoopAsync"); attempts++; continue; // retry request } // HTTP 5xx if (response.IsServerError() && attempts < this.maxAttemptsPerRequest) { this.logger.Warn($"Got HTTP {statusCode}, retrying", "DefaultRequestExecutor.CoreRequestLoopAsync"); attempts++; continue; // retry request } // HTTP 409 (modified) during delete if (statusCode == Conflict && request.Method == HttpMethod.Delete) { this.logger.Warn($"Got HTTP {statusCode} during delete, retrying", "DefaultRequestExecutor.CoreRequestLoopAsync"); attempts++; continue; // retry request } return response; } catch (Exception ex) { if (this.WasCanceled(ex, cancellationToken)) { this.logger.Trace("Request task was canceled. Rethrowing TaskCanceledException", "DefaultRequestExecutor.CoreRequestLoopAsync"); throw; } else { throw new RequestException("Unable to execute HTTP request.", ex); } } } }
private async Task <IHttpResponse> CoreRequestLoopAsync( IHttpRequest request, Func <IHttpRequest, CancellationToken, Task <IHttpResponse> > executeAction, Func <int, bool, CancellationToken, Task> pauseAction, CancellationToken cancellationToken) { var attempts = 0; bool throttling = false; Uri currentUri = request.CanonicalUri.ToUri(); while (true) { var currentRequest = new DefaultHttpRequest(request, overrideUri: currentUri); // Sign and build request this.requestAuthenticator.Authenticate(currentRequest, this.apiKey); try { if (attempts > 1) { this.logger.Trace("Pausing before retry", "DefaultRequestExecutor.CoreRequestLoopAsync"); await pauseAction(attempts - 1, throttling, cancellationToken).ConfigureAwait(false); } var response = await executeAction(currentRequest, cancellationToken).ConfigureAwait(false); if (this.IsRedirect(response)) { currentUri = response.Headers.Location; this.logger.Trace($"Redirected to {currentUri}", "DefaultRequestExecutor.CoreRequestLoopAsync"); continue; // re-execute request, not counted as a retry } var statusCode = response.StatusCode; if (response.TransportError && attempts < this.maxAttemptsPerRequest) { this.logger.Warn($"Recoverable transport error during request, retrying", "DefaultRequestExecutor.CoreRequestLoopAsync"); attempts++; continue; // retry request } // HTTP 429 if (statusCode == TooManyRequests && attempts < this.maxAttemptsPerRequest) { throttling = true; this.logger.Warn($"Got HTTP 429, throttling, retrying", "DefaultRequestExecutor.CoreRequestLoopAsync"); attempts++; continue; // retry request } // HTTP 5xx if (response.IsServerError() && attempts < this.maxAttemptsPerRequest) { this.logger.Warn($"Got HTTP {statusCode}, retrying", "DefaultRequestExecutor.CoreRequestLoopAsync"); attempts++; continue; // retry request } // HTTP 409 (modified) during delete if (statusCode == Conflict && request.Method == HttpMethod.Delete) { this.logger.Warn($"Got HTTP {statusCode} during delete, retrying", "DefaultRequestExecutor.CoreRequestLoopAsync"); attempts++; continue; // retry request } return(response); } catch (Exception ex) { if (this.WasCanceled(ex, cancellationToken)) { this.logger.Trace("Request task was canceled. Rethrowing TaskCanceledException", "DefaultRequestExecutor.CoreRequestLoopAsync"); throw; } else { throw new RequestException("Unable to execute HTTP request.", ex); } } } }