using System;
using System.Collections.Specialized;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Umbraco.Core;
using Umbraco.Core.Configuration.Models;
using Umbraco.Core.Migrations.Install;
using Umbraco.Core.Security;
using Umbraco.Core.Services;
using Umbraco.Extensions;
using Umbraco.Web.Install.Models;
namespace Umbraco.Web.Install.InstallSteps
{
///
/// This is the first UI step for a brand new install
///
///
/// By default this will show the user view which is the most basic information to configure a new install, but if an install get's interrupted because of an
/// error, etc... and the end-user refreshes the installer then we cannot show the user screen because they've already entered that information so instead we'll
/// display a simple continue installation view.
///
[InstallSetupStep(InstallationType.NewInstall, "User", 20, "")]
internal class NewInstallStep : InstallSetupStep
{
private readonly IUserService _userService;
private readonly DatabaseBuilder _databaseBuilder;
private static HttpClient _httpClient;
private readonly UserPasswordConfigurationSettings _passwordConfiguration;
private readonly SecuritySettings _securitySettings;
private readonly ConnectionStrings _connectionStrings;
private readonly ICookieManager _cookieManager;
private readonly IBackOfficeUserManager _userManager;
public NewInstallStep(
IUserService userService,
DatabaseBuilder databaseBuilder,
IOptions passwordConfiguration,
IOptions securitySettings,
IOptions connectionStrings,
ICookieManager cookieManager,
IBackOfficeUserManager userManager)
{
_userService = userService ?? throw new ArgumentNullException(nameof(userService));
_databaseBuilder = databaseBuilder ?? throw new ArgumentNullException(nameof(databaseBuilder));
_passwordConfiguration = passwordConfiguration.Value ?? throw new ArgumentNullException(nameof(passwordConfiguration));
_securitySettings = securitySettings.Value ?? throw new ArgumentNullException(nameof(securitySettings));
_connectionStrings = connectionStrings.Value ?? throw new ArgumentNullException(nameof(connectionStrings));
_cookieManager = cookieManager;
_userManager = userManager ?? throw new ArgumentNullException(nameof(userManager));
}
public override async Task ExecuteAsync(UserModel user)
{
var admin = _userService.GetUserById(Constants.Security.SuperUserId);
if (admin == null)
{
throw new InvalidOperationException("Could not find the super user!");
}
admin.Email = user.Email.Trim();
admin.Name = user.Name.Trim();
admin.Username = user.Email.Trim();
_userService.Save(admin);
var membershipUser = await _userManager.FindByIdAsync(Constants.Security.SuperUserId.ToString());
if (membershipUser == null)
{
throw new InvalidOperationException(
$"No user found in membership provider with id of {Constants.Security.SuperUserId}.");
}
//To change the password here we actually need to reset it since we don't have an old one to use to change
var resetToken = await _userManager.GeneratePasswordResetTokenAsync(membershipUser);
if (string.IsNullOrWhiteSpace(resetToken))
throw new InvalidOperationException("Could not reset password: unable to generate internal reset token");
var resetResult = await _userManager.ChangePasswordWithResetAsync(membershipUser.Id, resetToken, user.Password.Trim());
if (!resetResult.Succeeded)
throw new InvalidOperationException("Could not reset password: " + string.Join(", ", resetResult.Errors.ToErrorMessage()));
if (user.SubscribeToNewsLetter)
{
if (_httpClient == null)
_httpClient = new HttpClient();
var values = new NameValueCollection { { "name", admin.Name }, { "email", admin.Email } };
var content = new StringContent(JsonConvert.SerializeObject(values), Encoding.UTF8, "application/json");
try
{
var response = _httpClient.PostAsync("https://shop.umbraco.com/base/Ecom/SubmitEmail/installer.aspx", content).Result;
}
catch { /* fail in silence */ }
}
return null;
}
///
/// Return a custom view model for this step
///
public override object ViewModel
{
get
{
return new
{
minCharLength = _passwordConfiguration.RequiredLength,
minNonAlphaNumericLength = _passwordConfiguration.RequireNonLetterOrDigit ? 1 : 0,
quickInstallAvailable = DatabaseConfigureStep.IsSqlCeAvailable()
};
}
}
public override string View
{
get
{
return RequiresExecution(null)
//the user UI
? "user"
//the continue install UI
: "continueinstall";
}
}
public override bool RequiresExecution(UserModel model)
{
//now we have to check if this is really a new install, the db might be configured and might contain data
var databaseSettings = _connectionStrings.UmbracoConnectionString;
if (databaseSettings.IsConnectionStringConfigured() && _databaseBuilder.IsDatabaseConfigured)
return _databaseBuilder.HasSomeNonDefaultUser() == false;
// In this one case when it's a brand new install and nothing has been configured, make sure the
// back office cookie is cleared so there's no old cookies lying around causing problems
_cookieManager.ExpireCookie(_securitySettings.AuthCookieName);
return true;
}
}
}