diff --git a/src/Umbraco.Core/Events/UnattendedInstallEventArgs.cs b/src/Umbraco.Core/Events/UnattendedInstallEventArgs.cs new file mode 100644 index 0000000000..3029126dea --- /dev/null +++ b/src/Umbraco.Core/Events/UnattendedInstallEventArgs.cs @@ -0,0 +1,9 @@ +namespace Umbraco.Core.Events +{ + /// + /// Used to notify that an Unattended install has completed + /// + public class UnattendedInstallEventArgs : System.ComponentModel.CancelEventArgs + { + } +} diff --git a/src/Umbraco.Core/Runtime/CoreRuntime.cs b/src/Umbraco.Core/Runtime/CoreRuntime.cs index 25bb5d3151..4345469f54 100644 --- a/src/Umbraco.Core/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Core/Runtime/CoreRuntime.cs @@ -1,13 +1,17 @@ -using System; +using Newtonsoft.Json; +using System; using System.Collections.Generic; using System.Diagnostics; +using System.IO; using System.Linq; +using System.Runtime.Serialization; using System.Threading; using System.Web; using System.Web.Hosting; using Umbraco.Core.Cache; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; +using Umbraco.Core.Events; using Umbraco.Core.Exceptions; using Umbraco.Core.IO; using Umbraco.Core.Logging; @@ -16,6 +20,9 @@ using Umbraco.Core.Migrations.Install; using Umbraco.Core.Migrations.Upgrade; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Mappers; +using Umbraco.Core.Scoping; +using Umbraco.Core.Security; +using Umbraco.Core.Services; using Umbraco.Core.Sync; namespace Umbraco.Core.Runtime @@ -119,6 +126,9 @@ namespace Umbraco.Core.Runtime try { + // Setup event listener + UnattendedInstalled += CoreRuntime_UnattendedInstalled; + // throws if not full-trust new AspNetHostingPermission(AspNetHostingPermissionLevel.Unrestricted).Demand(); @@ -162,8 +172,7 @@ namespace Umbraco.Core.Runtime // run handlers RuntimeOptions.DoRuntimeEssentials(composition, appCaches, typeLoader, databaseFactory); - // determines if unattended install is enabled and performs it if required - DoUnattendedInstall(databaseFactory); + // register runtime-level services // there should be none, really - this is here "just in case" @@ -190,6 +199,13 @@ namespace Umbraco.Core.Runtime // create the factory _factory = Current.Factory = composition.CreateFactory(); + // determines if unattended install is enabled and performs it if required + DoUnattendedInstall(databaseFactory); + + // determine our runtime level (AFTER UNATTENDED INSTALL) + // TODO: Feels kinda weird to call this again + DetermineRuntimeLevel(databaseFactory, ProfilingLogger); + // if level is Run and reason is UpgradeMigrations, that means we need to perform an unattended upgrade if (_state.Reason == RuntimeLevelReason.UpgradeMigrations && _state.Level == RuntimeLevel.Run) { @@ -203,8 +219,6 @@ namespace Umbraco.Core.Runtime // create & initialize the components _components = _factory.GetInstance(); _components.Initialize(); - - } catch (Exception e) { @@ -242,6 +256,93 @@ namespace Umbraco.Core.Runtime return _factory; } + private void CoreRuntime_UnattendedInstalled(IRuntime sender, UnattendedInstallEventArgs e) + { + var unattendedName = Environment.GetEnvironmentVariable("UnattendedUserName"); + var unattendedEmail = Environment.GetEnvironmentVariable("UnattendedUserEmail"); + var unattendedPassword = Environment.GetEnvironmentVariable("UnattendedUserPassword"); + + var fileExists = false; + var filePath = IOHelper.MapPath("~/App_Data/unattended.user.json"); + + // No values store in ENV vars - try fallback file of /app_data/unattended.user.json + if (unattendedName.IsNullOrWhiteSpace() + || unattendedEmail.IsNullOrWhiteSpace() + || unattendedPassword.IsNullOrWhiteSpace()) + { + + fileExists = File.Exists(filePath); + if (fileExists == false) + { + return; + } + + // Attempt to deserialize JSON + try + { + var fileContents = File.ReadAllText(filePath); + var credentials = JsonConvert.DeserializeObject(fileContents); + + unattendedName = credentials.Name; + unattendedEmail = credentials.Email; + unattendedPassword = credentials.Password; + } + catch (Exception ex) + { + + throw; + } + } + + // ENV Variables & JSON still empty + if (unattendedName.IsNullOrWhiteSpace() + || unattendedEmail.IsNullOrWhiteSpace() + || unattendedPassword.IsNullOrWhiteSpace()) + { + return; + } + + + // Update user details + var currentProvider = MembershipProviderExtensions.GetUsersMembershipProvider(); + var admin = Current.Services.UserService.GetUserById(Constants.Security.SuperUserId); + if (admin == null) + { + throw new InvalidOperationException("Could not find the super user!"); + } + + var membershipUser = currentProvider.GetUser(Constants.Security.SuperUserId, true); + if (membershipUser == null) + { + throw new InvalidOperationException($"No user found in membership provider with id of {Constants.Security.SuperUserId}."); + } + + try + { + var success = membershipUser.ChangePassword("default", unattendedPassword.Trim()); + if (success == false) + { + throw new FormatException("Password must be at least " + currentProvider.MinRequiredPasswordLength + " characters long and contain at least " + currentProvider.MinRequiredNonAlphanumericCharacters + " symbols"); + } + } + catch (Exception) + { + throw new FormatException("Password must be at least " + currentProvider.MinRequiredPasswordLength + " characters long and contain at least " + currentProvider.MinRequiredNonAlphanumericCharacters + " symbols"); + } + + admin.Email = unattendedEmail.Trim(); + admin.Name = unattendedName.Trim(); + admin.Username = unattendedEmail.Trim(); + + Current.Services.UserService.Save(admin); + + // Delete JSON file if it existed to tidy + if (fileExists) + { + File.Delete(filePath); + } + } + private void DoUnattendedInstall(IUmbracoDatabaseFactory databaseFactory) { // unattended install is not enabled @@ -285,6 +386,11 @@ namespace Umbraco.Core.Runtime var creator = new DatabaseSchemaCreator(database, Logger); creator.InitializeDatabaseSchema(); database.CompleteTransaction(); + + // Emit an event that unattended install completed + // Then this event can be listened for and create an unattended user + UnattendedInstalled?.Invoke(this, new UnattendedInstallEventArgs()); + Logger.Info("Unattended install completed."); } catch (Exception ex) @@ -397,6 +503,7 @@ namespace Umbraco.Core.Runtime public virtual void Terminate() { _components?.Terminate(); + UnattendedInstalled -= CoreRuntime_UnattendedInstalled; } /// @@ -404,7 +511,7 @@ namespace Umbraco.Core.Runtime /// public virtual void Compose(Composition composition) { - // nothing + // Nothing } #region Getters @@ -465,5 +572,23 @@ namespace Umbraco.Core.Runtime } #endregion + + /// + /// Event to be used to notify when the Unattended Install has finished + /// + public static event TypedEventHandler UnattendedInstalled; + + [DataContract] + public class UnattendedUserConfig + { + [DataMember(Name = "name")] + public string Name { get; set; } + + [DataMember(Name = "email")] + public string Email { get; set; } + + [DataMember(Name = "password")] + public string Password { get; set; } + } } } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 0a453ad75f..14444f5d59 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -131,6 +131,7 @@ +