Merge pull request #2106 from btcpayserver/email-spam-protect

Introduce Spam protection
This commit is contained in:
Nicolas Dorier 2020-12-09 16:43:49 +09:00 committed by GitHub
commit 7c88333060
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 106 additions and 18 deletions

View File

@ -31,9 +31,6 @@ namespace BTCPayServer.Tests
public class GreenfieldAPITests
{
public const int TestTimeout = TestUtils.TestTimeout;
public const string TestApiPath = "api/test/apikey";
public GreenfieldAPITests(ITestOutputHelper helper)
{
Logs.Tester = new XUnitLog(helper) { Name = "Tests" };
@ -247,6 +244,20 @@ namespace BTCPayServer.Tests
Password = "afewfoiewiou",
IsAdministrator = true
}));
// If we set DisableNonAdminCreateUserApi = true, it should always fail to create a user unless you are an admin
await settings.UpdateSetting(new PoliciesSettings() { LockSubscription = false, DisableNonAdminCreateUserApi = true});
await AssertHttpError(403,
async () =>
await unauthClient.CreateUser(
new CreateApplicationUserRequest() {Email = "test9@gmail.com", Password = "afewfoiewiou"}));
await AssertHttpError(403,
async () =>
await user1Client.CreateUser(
new CreateApplicationUserRequest() {Email = "test9@gmail.com", Password = "afewfoiewiou"}));
await adminClient.CreateUser(
new CreateApplicationUserRequest() {Email = "test9@gmail.com", Password = "afewfoiewiou"});
}
}

View File

@ -35,6 +35,7 @@ using BTCPayServer.Security.Bitpay;
using BTCPayServer.Services;
using BTCPayServer.Services.Apps;
using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Mails;
using BTCPayServer.Services.Rates;
using BTCPayServer.Tests.Logging;
using BTCPayServer.U2F.Models;
@ -3345,5 +3346,57 @@ namespace BTCPayServer.Tests
Assert.False(fn.Seen);
}
}
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public async Task EmailSenderTests()
{
using (var tester = ServerTester.Create(newDb: true))
{
await tester.StartAsync();
var acc = tester.NewAccount();
acc.GrantAccess(true);
var settings = tester.PayTester.GetService<SettingsRepository>();
var emailSenderFactory = tester.PayTester.GetService<EmailSenderFactory>();
Assert.Null(await Assert.IsType<ServerEmailSender>(emailSenderFactory.GetEmailSender()).GetEmailSettings());
Assert.Null(await Assert.IsType<StoreEmailSender>(emailSenderFactory.GetEmailSender(acc.StoreId)).GetEmailSettings());
await settings.UpdateSetting(new PoliciesSettings() { DisableStoresToUseServerEmailSettings = false });
await settings.UpdateSetting(new EmailSettings()
{
From = "admin@admin.com",
Login = "admin@admin.com",
Password = "admin@admin.com",
Port = 1234,
Server = "admin.com",
EnableSSL = true
});
Assert.Equal("admin@admin.com",(await Assert.IsType<ServerEmailSender>(emailSenderFactory.GetEmailSender()).GetEmailSettings()).Login);
Assert.Equal("admin@admin.com",(await Assert.IsType<StoreEmailSender>(emailSenderFactory.GetEmailSender(acc.StoreId)).GetEmailSettings()).Login);
await settings.UpdateSetting(new PoliciesSettings() { DisableStoresToUseServerEmailSettings = true });
Assert.Equal("admin@admin.com",(await Assert.IsType<ServerEmailSender>(emailSenderFactory.GetEmailSender()).GetEmailSettings()).Login);
Assert.Null(await Assert.IsType<StoreEmailSender>(emailSenderFactory.GetEmailSender(acc.StoreId)).GetEmailSettings());
Assert.IsType<RedirectToActionResult>(await acc.GetController<StoresController>().Emails(acc.StoreId, new EmailsViewModel(new EmailSettings()
{
From = "store@store.com",
Login = "store@store.com",
Password = "store@store.com",
Port = 1234,
Server = "store.com",
EnableSSL = true
}), ""));
Assert.Equal("store@store.com",(await Assert.IsType<StoreEmailSender>(emailSenderFactory.GetEmailSender(acc.StoreId)).GetEmailSettings()).Login);
}
}
}
}

View File

@ -8,6 +8,7 @@ using BTCPayServer.Client.Models;
using BTCPayServer.Configuration;
using BTCPayServer.Data;
using BTCPayServer.Events;
using BTCPayServer.HostedServices;
using BTCPayServer.Logging;
using BTCPayServer.Security;
using BTCPayServer.Security.GreenField;
@ -35,6 +36,7 @@ namespace BTCPayServer.Controllers.GreenField
private readonly RateLimitService _throttleService;
private readonly BTCPayServerOptions _options;
private readonly IAuthorizationService _authorizationService;
private readonly CssThemeManager _themeManager;
public UsersController(UserManager<ApplicationUser> userManager, BTCPayServerOptions btcPayServerOptions,
RoleManager<IdentityRole> roleManager, SettingsRepository settingsRepository,
@ -42,7 +44,8 @@ namespace BTCPayServer.Controllers.GreenField
IPasswordValidator<ApplicationUser> passwordValidator,
RateLimitService throttleService,
BTCPayServerOptions options,
IAuthorizationService authorizationService)
IAuthorizationService authorizationService,
CssThemeManager themeManager)
{
_userManager = userManager;
_btcPayServerOptions = btcPayServerOptions;
@ -53,6 +56,7 @@ namespace BTCPayServer.Controllers.GreenField
_throttleService = throttleService;
_options = options;
_authorizationService = authorizationService;
_themeManager = themeManager;
}
[Authorize(Policy = Policies.CanViewProfile, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
@ -100,7 +104,7 @@ namespace BTCPayServer.Controllers.GreenField
if (request.IsAdministrator is true && !isAdmin)
return Forbid(AuthenticationSchemes.GreenfieldBasic);
if (!isAdmin && policies.LockSubscription)
if (!isAdmin && (policies.LockSubscription || _themeManager.Policies.DisableNonAdminCreateUserApi))
{
// If we are not admin and subscriptions are locked, we need to check the Policies.CanCreateUser.Key permission
var canCreateUser = (await _authorizationService.AuthorizeAsync(User, null, new PolicyRequirement(Policies.CanCreateUser))).Succeeded;

View File

@ -1,28 +1,34 @@
using BTCPayServer.HostedServices;
using BTCPayServer.Services.Stores;
namespace BTCPayServer.Services.Mails
{
public class EmailSenderFactory
{
private readonly IBackgroundJobClient _JobClient;
private readonly SettingsRepository _Repository;
private readonly StoreRepository _StoreRepository;
private readonly IBackgroundJobClient _jobClient;
private readonly SettingsRepository _repository;
private readonly StoreRepository _storeRepository;
private readonly CssThemeManager _cssThemeManager;
public EmailSenderFactory(IBackgroundJobClient jobClient,
SettingsRepository repository,
StoreRepository storeRepository)
StoreRepository storeRepository,
CssThemeManager cssThemeManager)
{
_JobClient = jobClient;
_Repository = repository;
_StoreRepository = storeRepository;
_jobClient = jobClient;
_repository = repository;
_storeRepository = storeRepository;
_cssThemeManager = cssThemeManager;
}
public IEmailSender GetEmailSender(string storeId = null)
{
var serverSender = new ServerEmailSender(_Repository, _JobClient);
var serverSender = new ServerEmailSender(_repository, _jobClient);
if (string.IsNullOrEmpty(storeId))
return serverSender;
return new StoreEmailSender(_StoreRepository, serverSender, _JobClient, storeId);
return new StoreEmailSender(_storeRepository,
!_cssThemeManager.Policies.DisableStoresToUseServerEmailSettings ? serverSender : null, _jobClient,
storeId);
}
}
}

View File

@ -12,11 +12,9 @@ namespace BTCPayServer.Services.Mails
IBackgroundJobClient backgroundJobClient,
string storeId) : base(backgroundJobClient)
{
if (storeId == null)
throw new ArgumentNullException(nameof(storeId));
StoreId = storeId ?? throw new ArgumentNullException(nameof(storeId));
StoreRepository = storeRepository;
FallbackSender = fallback;
StoreId = storeId;
}
public StoreRepository StoreRepository { get; }
@ -31,7 +29,9 @@ namespace BTCPayServer.Services.Mails
{
return emailSettings;
}
return await FallbackSender.GetEmailSettings();
if (FallbackSender != null) return await FallbackSender?.GetEmailSettings();
return null;
}
}
}

View File

@ -28,6 +28,10 @@ namespace BTCPayServer.Services
public bool CheckForNewVersions { get; set; }
[Display(Name = "Disable notifications automatically showing (no websockets)")]
public bool DisableInstantNotifications { get; set; }
[Display(Name = "Disable stores falling back to using the server's email settings")]
public bool DisableStoresToUseServerEmailSettings { get; set; }
[Display(Name = "Only allow admins to use the user creation API")]
public bool DisableNonAdminCreateUserApi { get; set; }
[Display(Name = "Display app on website root")]
public string RootAppId { get; set; }

View File

@ -63,6 +63,16 @@
<label asp-for="DisableInstantNotifications" class="form-check-label"></label>
<span asp-validation-for="DisableInstantNotifications" class="text-danger"></span>
</div>
<div class="form-check">
<input asp-for="DisableStoresToUseServerEmailSettings" type="checkbox" class="form-check-input"/>
<label asp-for="DisableStoresToUseServerEmailSettings" class="form-check-label"></label>
<span asp-validation-for="DisableStoresToUseServerEmailSettings" class="text-danger"></span>
</div>
<div class="form-check">
<input asp-for="DisableNonAdminCreateUserApi" type="checkbox" class="form-check-input"/>
<label asp-for="DisableNonAdminCreateUserApi" class="form-check-label"></label>
<span asp-validation-for="DisableNonAdminCreateUserApi" class="text-danger"></span>
</div>
@if (ViewBag.UpdateUrlPresent)
{
<div class="form-check">