Skip to content

Commit

Permalink
Merge pull request #33 from michaelvs97/logging
Browse files Browse the repository at this point in the history
Add logging of error codes in recaptcha response
  • Loading branch information
sleeuwen authored Feb 11, 2022
2 parents 6109fe3 + 13434a3 commit 77f1dfa
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 5 deletions.
60 changes: 57 additions & 3 deletions AspNetCore.ReCaptcha.Tests/ReCaptchaServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Moq;
using Moq.Protected;
Expand All @@ -14,7 +16,7 @@ namespace AspNetCore.ReCaptcha.Tests
{
public class ReCaptchaServiceTests
{
private ReCaptchaService CreateService(HttpClient httpClient = null, Mock<IOptions<ReCaptchaSettings>> reCaptchaSettingsMock = null)
private ReCaptchaService CreateService(HttpClient httpClient = null, Mock<IOptions<ReCaptchaSettings>> reCaptchaSettingsMock = null, ILogger<ReCaptchaService> logger = null)
{
httpClient ??= new HttpClient();

Expand All @@ -31,7 +33,9 @@ private ReCaptchaService CreateService(HttpClient httpClient = null, Mock<IOptio
reCaptchaSettingsMock.Setup(x => x.Value).Returns(reCaptchaSettings);
}

return new ReCaptchaService(httpClient, reCaptchaSettingsMock.Object);
logger ??= new NullLogger<ReCaptchaService>();

return new ReCaptchaService(httpClient, reCaptchaSettingsMock.Object, logger);
}

[Theory]
Expand Down Expand Up @@ -67,7 +71,57 @@ public void TestVerifyAsync(bool successResult)
var result = reCaptchaService.VerifyAsync("123").Result;

mockHttpMessageHandler.Protected().Verify("SendAsync", Times.Exactly(1), ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>());
Assert.Equal(reCaptchaResponse.Success, result);
Assert.Equal(successResult, result);
}

[Theory]
[InlineData("missing-input-secret", LogLevel.Warning, "recaptcha verify returned error code missing-input-secret, this could indicate an invalid secretkey.")]
[InlineData("invalid-input-secret", LogLevel.Warning, "recaptcha verify returned error code invalid-input-secret, this could indicate an invalid secretkey.")]
[InlineData("missing-input-response", LogLevel.Debug, "recaptcha verify returned error code missing-input-response, this indicates the user didn't succeed the captcha.")]
[InlineData("invalid-input-response", LogLevel.Debug, "recaptcha verify returned error code invalid-input-response, this indicates the user didn't succeed the captcha.")]
[InlineData("bad-request", LogLevel.Debug, "recaptcha verify returned error code bad-request.")]
[InlineData("timeout-or-duplicate", LogLevel.Debug, "recaptcha verify returned error code timeout-or-duplicate.")]
public void TestVerifyWithErrorAsync(string errorCode, LogLevel expectedLogLevel, string expectedLogMessage)
{
var reCaptchaResponse = new ReCaptchaResponse()
{
Action = "Test",
ChallengeTimestamp = new DateTime(2022, 2, 10, 15, 14, 13),
Hostname = "Test",
Success = false,
ErrorCodes = new[] { errorCode }
};

var mockHttpMessageHandler = new Mock<HttpMessageHandler>();

mockHttpMessageHandler.Protected()
.Setup<Task<HttpResponseMessage>>(
"SendAsync",
ItExpr.IsAny<HttpRequestMessage>(),
ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(new HttpResponseMessage { StatusCode = HttpStatusCode.OK, Content = new StringContent(JsonSerializer.Serialize(reCaptchaResponse), Encoding.UTF8,"application/json")});

var httpClient = new HttpClient(mockHttpMessageHandler.Object)
{
BaseAddress = new Uri("https://www.google.com/recaptcha/"),
};

var logger = new TestLogger<ReCaptchaService>();

var reCaptchaService = CreateService(httpClient, logger: logger);

var result = reCaptchaService.VerifyAsync("123").Result;

mockHttpMessageHandler.Protected().Verify("SendAsync", Times.Exactly(1), ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>());
Assert.False(reCaptchaResponse.Success);
Assert.False(result);

Assert.Equal(2, logger.LogEntries.Count);
Assert.Equal(LogLevel.Trace, logger.LogEntries[0].LogLevel);
Assert.Equal(@$"recaptcha response: {{""success"":false,""score"":0,""action"":""Test"",""challenge_ts"":""2022-02-10T15:14:13"",""hostname"":""Test"",""error-codes"":[""{errorCode}""]}}", logger.LogEntries[0].Message);

Assert.Equal(expectedLogLevel, logger.LogEntries[1].LogLevel);
Assert.Equal(expectedLogMessage, logger.LogEntries[1].Message);
}
}
}
33 changes: 33 additions & 0 deletions AspNetCore.ReCaptcha.Tests/TestLogger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using Microsoft.Extensions.Logging;

namespace AspNetCore.ReCaptcha.Tests;

public class TestLogger<T> : ILogger<T>
where T : class
{
public List<(LogLevel LogLevel, string Message)> LogEntries { get; } = new List<(LogLevel, string)>();

public IDisposable BeginScope<TState>(TState state)
{
return new NullDisposable();
}

public bool IsEnabled(LogLevel logLevel)
{
return true;
}

public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
{
LogEntries.Add((logLevel, formatter(state, exception)));
}
}

internal class NullDisposable : IDisposable
{
public void Dispose()
{
}
}
2 changes: 1 addition & 1 deletion AspNetCore.ReCaptcha/AspNetCore.ReCaptcha.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFrameworks>netcoreapp3.1;net5.0;net6.0</TargetFrameworks>
<PackageId>AspNetCore.ReCaptcha</PackageId>
<Version>1.5.1</Version>
<Version>1.5.2</Version>
<Authors>Michaelvs97,sleeuwen</Authors>
<Description>Google ReCAPTCHA v2/v3 Library for .NET Core 3.1 and .NET 5.0/6.0</Description>
<PackageDescription>Google ReCAPTCHA v2/v3 Library for .NET Core 3.1 and .NET 5.0/6.0</PackageDescription>
Expand Down
2 changes: 2 additions & 0 deletions AspNetCore.ReCaptcha/ReCaptchaHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ public static class ReCaptchaHelper
{
private static IHttpClientBuilder AddReCaptchaServices(this IServiceCollection services)
{
services.AddLogging();

services.PostConfigure<ReCaptchaSettings>(settings =>
{
settings.LocalizerProvider ??= (modelType, localizerFactory) => localizerFactory.Create(modelType);
Expand Down
20 changes: 19 additions & 1 deletion AspNetCore.ReCaptcha/ReCaptchaService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,21 @@
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace AspNetCore.ReCaptcha
{
internal class ReCaptchaService : IReCaptchaService
{
private readonly HttpClient _client;
private readonly ILogger<ReCaptchaService> _logger;
private readonly ReCaptchaSettings _reCaptchaSettings;

public ReCaptchaService(HttpClient client, IOptions<ReCaptchaSettings> reCaptchaSettings)
public ReCaptchaService(HttpClient client, IOptions<ReCaptchaSettings> reCaptchaSettings, ILogger<ReCaptchaService> logger)
{
_client = client;
_logger = logger;
_reCaptchaSettings = reCaptchaSettings.Value;
}

Expand Down Expand Up @@ -42,9 +45,24 @@ public async Task<ReCaptchaResponse> GetVerifyResponseAsync(string reCaptchaResp
var result = await _client.PostAsync("api/siteverify", body);

var stringResult = await result.Content.ReadAsStringAsync();
_logger?.LogTrace("recaptcha response: {recaptchaResponse}", stringResult);

var obj = JsonSerializer.Deserialize<ReCaptchaResponse>(stringResult);

if (obj.ErrorCodes?.Length > 0 && _logger?.IsEnabled(LogLevel.Warning) == true)
{
for (var i = 0; i < obj.ErrorCodes.Length; i++)
{
var errorCode = obj.ErrorCodes[i];
if (errorCode.EndsWith("-input-secret"))
_logger?.LogWarning("recaptcha verify returned error code {ErrorCode}, this could indicate an invalid secretkey.", errorCode);
else if (errorCode.EndsWith("-input-response"))
_logger?.LogDebug("recaptcha verify returned error code {ErrorCode}, this indicates the user didn't succeed the captcha.", errorCode);
else
_logger?.LogDebug("recaptcha verify returned error code {ErrorCode}.", errorCode);
}
}

return obj;
}
}
Expand Down
2 changes: 2 additions & 0 deletions AspNetCore.ReCaptcha/RecaptchaResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,7 @@ public class ReCaptchaResponse
public DateTime ChallengeTimestamp { get; set; }
[JsonPropertyName("hostname")]
public string Hostname { get; set; }
[JsonPropertyName("error-codes")]
public string[] ErrorCodes { get; set; }
}
}

0 comments on commit 77f1dfa

Please sign in to comment.