Merge pull request #1938 from NicolasDorier/email-pwd

Do not show password in clear text in email configuration (Fix #1790)
This commit is contained in:
Nicolas Dorier 2020-10-08 12:01:21 +09:00 committed by GitHub
commit d9d2c7d213
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 161 additions and 54 deletions

View file

@ -384,6 +384,11 @@ namespace BTCPayServer.Tests
Driver.FindElement(By.Id($"Wallet{navPages}")).Click(); Driver.FindElement(By.Id($"Wallet{navPages}")).Click();
} }
} }
public void GoToUrl(string relativeUrl)
{
Driver.Navigate().GoToUrl(new Uri(Server.PayTester.ServerUri, relativeUrl));
}
public void GoToServer(ServerNavPages navPages = ServerNavPages.Index) public void GoToServer(ServerNavPages navPages = ServerNavPages.Index)
{ {

View file

@ -203,6 +203,46 @@ namespace BTCPayServer.Tests
} }
} }
[Fact(Timeout = TestTimeout)]
public async Task CanSetupEmailServer()
{
using (var s = SeleniumTester.Create())
{
await s.StartAsync();
var alice = s.RegisterNewUser(isAdmin: true);
s.Driver.Navigate().GoToUrl(s.Link("/server/emails"));
if (s.Driver.PageSource.Contains("Configured"))
{
s.Driver.FindElement(By.CssSelector("button[value=\"ResetPassword\"]")).Submit();
s.AssertHappyMessage();
}
CanSetupEmailCore(s);
s.CreateNewStore();
s.GoToUrl($"stores/{s.StoreId}/emails");
CanSetupEmailCore(s);
}
}
private static void CanSetupEmailCore(SeleniumTester s)
{
s.Driver.FindElement(By.ClassName("dropdown-toggle")).Click();
s.Driver.FindElement(By.ClassName("dropdown-item")).Click();
s.Driver.FindElement(By.Id("Settings_Login")).SendKeys("test@gmail.com");
s.Driver.FindElement(By.CssSelector("button[value=\"Save\"]")).Submit();
s.AssertHappyMessage();
s.Driver.FindElement(By.Id("Settings_Password")).SendKeys("mypassword");
s.Driver.FindElement(By.CssSelector("button[value=\"Save\"]")).Submit();
Assert.Contains("Configured", s.Driver.PageSource);
s.Driver.FindElement(By.Id("Settings_Login")).SendKeys("test_fix@gmail.com");
s.Driver.FindElement(By.CssSelector("button[value=\"Save\"]")).Submit();
Assert.Contains("Configured", s.Driver.PageSource);
Assert.Contains("test_fix", s.Driver.PageSource);
s.Driver.FindElement(By.CssSelector("button[value=\"ResetPassword\"]")).Submit();
s.AssertHappyMessage();
Assert.DoesNotContain("Configured", s.Driver.PageSource);
Assert.Contains("test_fix", s.Driver.PageSource);
}
[Fact(Timeout = TestTimeout)] [Fact(Timeout = TestTimeout)]
public async Task CanUseDynamicDns() public async Task CanUseDynamicDns()
{ {

View file

@ -945,23 +945,28 @@ namespace BTCPayServer.Controllers
public async Task<IActionResult> Emails() public async Task<IActionResult> Emails()
{ {
var data = (await _SettingsRepository.GetSettingAsync<EmailSettings>()) ?? new EmailSettings(); var data = (await _SettingsRepository.GetSettingAsync<EmailSettings>()) ?? new EmailSettings();
return View(new EmailsViewModel() { Settings = data }); return View(new EmailsViewModel(data));
} }
[Route("server/emails")] [Route("server/emails")]
[HttpPost] [HttpPost]
public async Task<IActionResult> Emails(EmailsViewModel model, string command) public async Task<IActionResult> Emails(EmailsViewModel model, string command)
{ {
if (!model.Settings.IsComplete())
{
TempData[WellKnownTempData.ErrorMessage] = "Required fields missing";
return View(model);
}
if (command == "Test") if (command == "Test")
{ {
try try
{ {
if (model.PasswordSet)
{
var settings = await _SettingsRepository.GetSettingAsync<EmailSettings>();
model.Settings.Password = settings.Password;
}
if (!model.Settings.IsComplete())
{
TempData[WellKnownTempData.ErrorMessage] = "Required fields missing";
return View(model);
}
using (var client = model.Settings.CreateSmtpClient()) using (var client = model.Settings.CreateSmtpClient())
using (var message = model.Settings.CreateMailMessage(new MailAddress(model.TestEmail), "BTCPay test", "BTCPay test")) using (var message = model.Settings.CreateMailMessage(new MailAddress(model.TestEmail), "BTCPay test", "BTCPay test"))
{ {
@ -975,11 +980,24 @@ namespace BTCPayServer.Controllers
} }
return View(model); return View(model);
} }
else if (command == "ResetPassword")
{
var settings = await _SettingsRepository.GetSettingAsync<EmailSettings>();
settings.Password = null;
await _SettingsRepository.UpdateSetting(model.Settings);
TempData[WellKnownTempData.SuccessMessage] = "Email server password reset";
return RedirectToAction(nameof(Emails));
}
else // if(command == "Save") else // if(command == "Save")
{ {
var oldSettings = await _SettingsRepository.GetSettingAsync<EmailSettings>();
if (new EmailsViewModel(oldSettings).PasswordSet)
{
model.Settings.Password = oldSettings.Password;
}
await _SettingsRepository.UpdateSetting(model.Settings); await _SettingsRepository.UpdateSetting(model.Settings);
TempData[WellKnownTempData.SuccessMessage] = "Email settings saved"; TempData[WellKnownTempData.SuccessMessage] = "Email settings saved";
return View(model); return RedirectToAction(nameof(Emails));
} }
} }

View file

@ -18,7 +18,7 @@ namespace BTCPayServer.Controllers
if (store == null) if (store == null)
return NotFound(); return NotFound();
var data = store.GetStoreBlob().EmailSettings ?? new EmailSettings(); var data = store.GetStoreBlob().EmailSettings ?? new EmailSettings();
return View(new EmailsViewModel() { Settings = data }); return View(new EmailsViewModel(data));
} }
[Route("{storeId}/emails")] [Route("{storeId}/emails")]
@ -32,6 +32,10 @@ namespace BTCPayServer.Controllers
{ {
try try
{ {
if (model.PasswordSet)
{
model.Settings.Password = store.GetStoreBlob().EmailSettings.Password;
}
if (!model.Settings.IsComplete()) if (!model.Settings.IsComplete())
{ {
TempData[WellKnownTempData.ErrorMessage] = "Required fields missing"; TempData[WellKnownTempData.ErrorMessage] = "Required fields missing";
@ -48,19 +52,34 @@ namespace BTCPayServer.Controllers
} }
return View(model); return View(model);
} }
else if (command == "ResetPassword")
{
var storeBlob = store.GetStoreBlob();
storeBlob.EmailSettings.Password = null;
store.SetStoreBlob(storeBlob);
await _Repo.UpdateStore(store);
TempData[WellKnownTempData.SuccessMessage] = "Email server password reset";
return RedirectToAction(nameof(Emails), new
{
storeId
});
}
else // if(command == "Save") else // if(command == "Save")
{ {
var storeBlob = store.GetStoreBlob(); var storeBlob = store.GetStoreBlob();
var oldPassword = storeBlob.EmailSettings?.Password;
if (new EmailsViewModel(storeBlob.EmailSettings).PasswordSet)
{
model.Settings.Password = storeBlob.EmailSettings.Password;
}
storeBlob.EmailSettings = model.Settings; storeBlob.EmailSettings = model.Settings;
store.SetStoreBlob(storeBlob); store.SetStoreBlob(storeBlob);
await _Repo.UpdateStore(store); await _Repo.UpdateStore(store);
TempData[WellKnownTempData.SuccessMessage] = "Email settings modified"; TempData[WellKnownTempData.SuccessMessage] = "Email settings modified";
return RedirectToAction(nameof(UpdateStore), new return RedirectToAction(nameof(Emails), new
{ {
storeId storeId
}); });
} }
} }
} }

View file

@ -5,11 +5,20 @@ namespace BTCPayServer.Models.ServerViewModels
{ {
public class EmailsViewModel public class EmailsViewModel
{ {
public EmailsViewModel()
{
}
public EmailsViewModel(EmailSettings settings)
{
Settings = settings;
PasswordSet = !string.IsNullOrEmpty(settings?.Password);
}
public EmailSettings Settings public EmailSettings Settings
{ {
get; set; get; set;
} }
public bool PasswordSet { get; set; }
[EmailAddress] [EmailAddress]
[Display(Name = "Test Email")] [Display(Name = "Test Email")]
public string TestEmail public string TestEmail

View file

@ -1,6 +1,7 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Net; using System.Net;
using System.Net.Mail; using System.Net.Mail;
using Newtonsoft.Json;
namespace BTCPayServer.Services.Mails namespace BTCPayServer.Services.Mails
{ {

View file

@ -1,4 +1,4 @@
@model BTCPayServer.Models.ServerViewModels.EmailsViewModel @model BTCPayServer.Models.ServerViewModels.EmailsViewModel
<partial name="_StatusMessage" /> <partial name="_StatusMessage" />
@ -27,36 +27,36 @@
</div> </div>
</div> </div>
<script> <script>
$(document).ready(function(){ $(document).ready(function () {
$('.row-quick-fill').show(); $('.row-quick-fill').show();
$('.dropdown.quick-fill a').click(function(e){ $('.dropdown.quick-fill a').click(function (e) {
e.preventDefault(); e.preventDefault();
var aNode = $(this); var aNode = $(this);
var data = aNode.data(); var data = aNode.data();
for(var key in data){ for (var key in data) {
var value = data[key]; var value = data[key];
var inputNodes = $('input[name*="Settings.'+key+'" i]'); var inputNodes = $('input[name*="Settings.' + key + '" i]');
if(inputNodes.length){ if (inputNodes.length) {
inputNodes.each(function(i, input){ inputNodes.each(function (i, input) {
input = $(input); input = $(input);
var type = input.attr('type'); var type = input.attr('type');
if(type === 'checkbox'){ if (type === 'checkbox') {
input.prop('checked', value); input.prop('checked', value);
}else{ } else {
input.val(value); input.val(value);
} }
}); });
}
} }
} });
}); });
});
</script> </script>
<form method="post" autocomplete="off"> <form method="post" autocomplete="off">
@ -64,17 +64,17 @@ $(document).ready(function(){
<div class="col-md-6"> <div class="col-md-6">
<div class="form-group"> <div class="form-group">
<label asp-for="Settings.Server"></label> <label asp-for="Settings.Server"></label>
<input asp-for="Settings.Server" class="form-control"/> <input asp-for="Settings.Server" class="form-control" />
<span asp-validation-for="Settings.Server" class="text-danger"></span> <span asp-validation-for="Settings.Server" class="text-danger"></span>
</div> </div>
<div class="form-group"> <div class="form-group">
<label asp-for="Settings.Port"></label> <label asp-for="Settings.Port"></label>
<input asp-for="Settings.Port" class="form-control"/> <input asp-for="Settings.Port" class="form-control" />
<span asp-validation-for="Settings.Port" class="text-danger"></span> <span asp-validation-for="Settings.Port" class="text-danger"></span>
</div> </div>
<div class="form-group"> <div class="form-group">
<label asp-for="Settings.FromDisplay"></label> <label asp-for="Settings.FromDisplay"></label>
<input asp-for="Settings.FromDisplay" class="form-control"/> <input asp-for="Settings.FromDisplay" class="form-control" />
<small class="form-text text-muted"> <small class="form-text text-muted">
Some email providers (like Gmail) don't allow you to set your display name, so this setting may not have any effect. Some email providers (like Gmail) don't allow you to set your display name, so this setting may not have any effect.
</small> </small>
@ -82,28 +82,43 @@ $(document).ready(function(){
</div> </div>
<div class="form-group"> <div class="form-group">
<label asp-for="Settings.From"></label> <label asp-for="Settings.From"></label>
<input asp-for="Settings.From" class="form-control"/> <input asp-for="Settings.From" class="form-control" />
<span asp-validation-for="Settings.From" class="text-danger"></span> <span asp-validation-for="Settings.From" class="text-danger"></span>
</div> </div>
<div class="form-group"> <div class="form-group">
<label asp-for="Settings.Login"></label> <label asp-for="Settings.Login"></label>
<input asp-for="Settings.Login" class="form-control"/> <input asp-for="Settings.Login" class="form-control" />
<small class="form-text text-muted"> <small class="form-text text-muted">
For many email providers (like Gmail) your login is your email address. For many email providers (like Gmail) your login is your email address.
</small> </small>
<span asp-validation-for="Settings.Login" class="text-danger"></span> <span asp-validation-for="Settings.Login" class="text-danger"></span>
</div> </div>
<div class="form-group"> <div class="form-group">
<label asp-for="Settings.Password"></label> @if (!Model.PasswordSet)
<input asp-for="Settings.Password" value="@Model.Settings.Password" class="form-control"/> {
<span asp-validation-for="Settings.Password" class="text-danger"></span>
<label asp-for="Settings.Password"></label>
<input asp-for="Settings.Password" type="password" class="form-control" />
<span asp-validation-for="Settings.Password" class="text-danger"></span>
}
else
{
<label asp-for="Settings.Password"></label>
<div class="input-group">
<input value="Configured" type="text" readonly class="form-control" />
<div class="input-group-append">
<button type="submit" class="btn btn-danger" name="command" value="ResetPassword">Reset</button>
</div>
</div>
}
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="form-check"> <div class="form-check">
<input asp-for="Settings.EnableSSL" type="checkbox" class="form-check-input"/> <input asp-for="Settings.EnableSSL" type="checkbox" class="form-check-input" />
<label asp-for="Settings.EnableSSL" class="form-check-label"></label> <label asp-for="Settings.EnableSSL" class="form-check-label"></label>
</div> </div>
</div> </div>
<input asp-for="PasswordSet" type="hidden" />
<button type="submit" class="btn btn-primary" name="command" value="Save">Save</button> <button type="submit" class="btn btn-primary" name="command" value="Save">Save</button>
</div> </div>
</div> </div>
@ -116,11 +131,11 @@ $(document).ready(function(){
<p class="form-text text-muted"> <p class="form-text text-muted">
If you want to test your settings, enter an email address here and click "Send Test Email". If you want to test your settings, enter an email address here and click "Send Test Email".
<strong>Your settings won't be saved</strong>, only a test email will be sent. <strong>Your settings won't be saved</strong>, only a test email will be sent.
<br/> <br />
After a successful test, you can click "Save". After a successful test, you can click "Save".
</p> </p>
<label asp-for="TestEmail"></label> <label asp-for="TestEmail"></label>
<input asp-for="TestEmail" class="form-control"/> <input asp-for="TestEmail" class="form-control" />
<span asp-validation-for="TestEmail" class="text-danger"></span> <span asp-validation-for="TestEmail" class="text-danger"></span>
</div> </div>
<button type="submit" class="btn btn-secondary" name="command" value="Test">Send Test Email</button> <button type="submit" class="btn btn-secondary" name="command" value="Test">Send Test Email</button>