Skip to content

Commit

Permalink
#1 - support for Forward Proxy
Browse files Browse the repository at this point in the history
  • Loading branch information
mikocot committed Sep 8, 2022
1 parent 1e953e5 commit b1d1086
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 16 deletions.
10 changes: 10 additions & 0 deletions docs/Griesoft.AspNetCore.ReCaptcha.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions src/ReCaptcha/Clients/IRecaptchaClientFactory.cs
@@ -0,0 +1,16 @@
using System.Net.Http;

namespace Griesoft.AspNetCore.ReCaptcha.Clients
{
/// <summary>
/// Constructs http client used for verifying captcha result with Google
/// </summary>
public interface IRecaptchaHttpClientFactory
{
/// <summary>
/// Create HttpClient with preconfigured options to communicate with Google reCatpcha REST service
/// </summary>
/// <returns></returns>
HttpClient CreateClient();
}
}
39 changes: 39 additions & 0 deletions src/ReCaptcha/Clients/RecaptchaClientFactory.cs
@@ -0,0 +1,39 @@
using System;
using System.Net;
using System.Net.Http;
using System.Runtime.CompilerServices;
using Griesoft.AspNetCore.ReCaptcha.Configuration;
using Microsoft.Extensions.Options;

[assembly: InternalsVisibleTo("ReCaptcha.Tests")]
namespace Griesoft.AspNetCore.ReCaptcha.Clients
{
internal class RecaptchaClientFactory : IRecaptchaHttpClientFactory
{
private readonly RecaptchaOptions _options;

public RecaptchaClientFactory(IOptionsMonitor<RecaptchaOptions> options)
{
_options = options.CurrentValue;
}


public HttpClient CreateClient()
{
var httpClientHandler = new HttpClientHandler();
if (_options.UseProxy.HasValue && _options.UseProxy.Value && !String.IsNullOrEmpty(_options.ProxyAddress))
{
httpClientHandler.UseProxy = true;
httpClientHandler.Proxy = new WebProxy(_options.ProxyAddress, false);
}

var httpClient = new HttpClient(httpClientHandler)
{
BaseAddress = new Uri(RecaptchaServiceConstants.GoogleRecaptchaEndpoint)
};

return httpClient;
}

}
}
10 changes: 10 additions & 0 deletions src/ReCaptcha/Configuration/RecaptchaOptions.cs
Expand Up @@ -46,5 +46,15 @@ public ValidationFailedAction ValidationFailedAction
/// The global default badge value for an invisible reCAPTCHA tag.
/// </summary>
public BadgePosition Badge { get; set; } = BadgePosition.BottomRight;

/// <summary>
/// Indicates if proxy server should be used to forward http client requests
/// </summary>
public bool? UseProxy { get; set; }

/// <summary>
/// Proxy server address to be used to http client
/// </summary>
public string? ProxyAddress { get; set; }
}
}
7 changes: 2 additions & 5 deletions src/ReCaptcha/Extensions/RecaptchaServiceExtensions.cs
@@ -1,4 +1,5 @@
using System;
using Griesoft.AspNetCore.ReCaptcha.Clients;
using Griesoft.AspNetCore.ReCaptcha.Configuration;
using Griesoft.AspNetCore.ReCaptcha.Filters;
using Griesoft.AspNetCore.ReCaptcha.Services;
Expand Down Expand Up @@ -26,11 +27,7 @@ public static IServiceCollection AddRecaptchaService(this IServiceCollection ser

services.Configure(options ??= opt => { });

services.AddHttpClient(RecaptchaServiceConstants.RecaptchaServiceHttpClientName, client =>
{
client.BaseAddress = new Uri(RecaptchaServiceConstants.GoogleRecaptchaEndpoint);
});

services.AddScoped<IRecaptchaHttpClientFactory, RecaptchaClientFactory>();
services.AddScoped<IRecaptchaService, RecaptchaService>();

services.AddTransient<IValidateRecaptchaFilter, ValidateRecaptchaFilter>();
Expand Down
8 changes: 5 additions & 3 deletions src/ReCaptcha/Services/RecaptchaService.cs
Expand Up @@ -3,6 +3,7 @@
using System.Net.Http;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Griesoft.AspNetCore.ReCaptcha.Clients;
using Griesoft.AspNetCore.ReCaptcha.Configuration;
using Griesoft.AspNetCore.ReCaptcha.Extensions;
using Griesoft.AspNetCore.ReCaptcha.Localization;
Expand All @@ -17,11 +18,11 @@ namespace Griesoft.AspNetCore.ReCaptcha.Services
internal class RecaptchaService : IRecaptchaService
{
private readonly RecaptchaSettings _settings;
private readonly IHttpClientFactory _httpClientFactory;
private readonly IRecaptchaHttpClientFactory _httpClientFactory;
private readonly ILogger<RecaptchaService> _logger;

public RecaptchaService(IOptionsMonitor<RecaptchaSettings> settings,
IHttpClientFactory httpClientFactory, ILogger<RecaptchaService> logger)
IRecaptchaHttpClientFactory httpClientFactory, ILogger<RecaptchaService> logger)
{
_settings = settings.CurrentValue;
_httpClientFactory = httpClientFactory;
Expand All @@ -38,7 +39,7 @@ public async Task<ValidationResponse> ValidateRecaptchaResponse(string token, st

try
{
var httpClient = _httpClientFactory.CreateClient(RecaptchaServiceConstants.RecaptchaServiceHttpClientName);
var httpClient = _httpClientFactory.CreateClient();
var response = await httpClient.PostAsync($"?secret={_settings.SecretKey}&response={token}{(remoteIp != null ? $"&remoteip={remoteIp}" : "")}", null!)
.ConfigureAwait(true);

Expand Down Expand Up @@ -78,3 +79,4 @@ await response.Content.ReadAsStringAsync()
}
}
}

17 changes: 9 additions & 8 deletions tests/ReCaptcha.Tests/Services/RecaptchaServiceTests.cs
Expand Up @@ -5,6 +5,7 @@
using System.Threading;
using System.Threading.Tasks;
using Griesoft.AspNetCore.ReCaptcha;
using Griesoft.AspNetCore.ReCaptcha.Clients;
using Griesoft.AspNetCore.ReCaptcha.Configuration;
using Griesoft.AspNetCore.ReCaptcha.Services;
using Microsoft.Extensions.Logging;
Expand All @@ -26,7 +27,7 @@ public class RecaptchaServiceTests
private Mock<IOptionsMonitor<RecaptchaSettings>> _settingsMock;
private Mock<HttpMessageHandler> _httpMessageHandlerMock;
private HttpClient _httpClient;
private Mock<IHttpClientFactory> _httpClientFactory;
private Mock<IRecaptchaHttpClientFactory> _httpClientFactory;

[SetUp]
public void Initialize()
Expand Down Expand Up @@ -61,8 +62,8 @@ public void Initialize()
BaseAddress = new Uri("http://test.com/"),
};

_httpClientFactory = new Mock<IHttpClientFactory>();
_httpClientFactory.Setup(instance => instance.CreateClient(It.Is<string>(val => val == RecaptchaServiceConstants.RecaptchaServiceHttpClientName)))
_httpClientFactory = new Mock<IRecaptchaHttpClientFactory>();
_httpClientFactory.Setup(instance => instance.CreateClient())
.Returns(_httpClient)
.Verifiable();
}
Expand Down Expand Up @@ -129,8 +130,8 @@ public async Task ValidateRecaptchaResponse_ShouldReturn_HttpRequestError()
BaseAddress = new Uri("http://test.com/"),
};

_httpClientFactory = new Mock<IHttpClientFactory>();
_httpClientFactory.Setup(instance => instance.CreateClient(It.Is<string>(val => val == RecaptchaServiceConstants.RecaptchaServiceHttpClientName)))
_httpClientFactory = new Mock<IRecaptchaHttpClientFactory>();
_httpClientFactory.Setup(instance => instance.CreateClient())
.Returns(_httpClient);

var service = new RecaptchaService(_settingsMock.Object, _httpClientFactory.Object, _logger);
Expand Down Expand Up @@ -163,16 +164,16 @@ public void ValidateRecaptchaResponse_ShouldThrowAnyOtherThan_HttpRequestExcepti
BaseAddress = new Uri("http://test.com/"),
};

_httpClientFactory = new Mock<IHttpClientFactory>();
_httpClientFactory.Setup(instance => instance.CreateClient(It.Is<string>(val => val == RecaptchaServiceConstants.RecaptchaServiceHttpClientName)))
_httpClientFactory = new Mock<IRecaptchaHttpClientFactory>();
_httpClientFactory.Setup(instance => instance.CreateClient())
.Returns(_httpClient);

var service = new RecaptchaService(_settingsMock.Object, _httpClientFactory.Object, _logger);

// Act


// Assert
// Assert6
Assert.ThrowsAsync<Exception>(() => service.ValidateRecaptchaResponse(Token));
_httpMessageHandlerMock.Verify();
}
Expand Down

0 comments on commit b1d1086

Please sign in to comment.