Files
mixtape/zero.Core/Api/SetupApi.cs
T

353 lines
11 KiB
C#
Raw Normal View History

2020-04-03 16:45:47 +02:00
using FluentValidation.Results;
2020-04-15 01:42:06 +02:00
using Microsoft.AspNetCore.Identity;
2020-04-04 19:31:01 +02:00
using Newtonsoft.Json;
2020-04-03 16:45:47 +02:00
using Raven.Client.Documents;
using Raven.Client.Documents.Conventions;
using Raven.Client.Documents.Session;
using System;
2020-04-04 19:31:01 +02:00
using System.Collections.Generic;
using System.IO;
2020-04-13 12:14:19 +02:00
using System.Linq;
2020-12-02 15:45:44 +01:00
using System.Security.Cryptography;
2020-04-03 16:45:47 +02:00
using System.Threading.Tasks;
2020-12-02 15:45:44 +01:00
using zero.Core.Database;
2020-04-03 16:45:47 +02:00
using zero.Core.Entities;
using zero.Core.Entities.Setup;
2020-05-15 14:23:47 +02:00
using zero.Core.Extensions;
using zero.Core.Identity;
using zero.Core.Options;
2020-12-02 15:45:44 +01:00
using zero.Core.Utils;
2020-04-03 16:45:47 +02:00
using zero.Core.Validation;
2020-04-04 19:31:01 +02:00
2020-04-03 16:45:47 +02:00
namespace zero.Core.Api
{
public class SetupApi : ISetupApi
{
protected IZeroOptions Options { get; private set; }
2020-04-03 18:03:36 +02:00
2020-12-02 15:45:44 +01:00
protected IPasswordHasher<BackofficeUser> PasswordHasher { get; private set; }
2020-04-03 18:03:36 +02:00
2021-03-18 15:54:29 +01:00
protected IZeroDocumentConventionsBuilder ConventionsBuilder { get; private set; }
2020-04-03 18:03:36 +02:00
2021-03-18 15:54:29 +01:00
public SetupApi(IZeroOptions options, IPasswordHasher<BackofficeUser> passwordHasher, IZeroDocumentConventionsBuilder conventionsBuilder)
2020-04-03 16:45:47 +02:00
{
Options = options;
2020-12-02 15:45:44 +01:00
PasswordHasher = passwordHasher;
2021-03-18 15:54:29 +01:00
ConventionsBuilder = conventionsBuilder;
2020-04-03 16:45:47 +02:00
}
2020-04-15 15:13:38 +02:00
public async Task<EntityResult<SetupModel>> Install(SetupModel model)
2020-04-03 16:45:47 +02:00
{
ValidationResult validation = await new SetupModelValidator().ValidateAsync(model);
if (!validation.IsValid)
{
2020-04-15 15:13:38 +02:00
return EntityResult<SetupModel>.Fail(validation);
2020-04-03 16:45:47 +02:00
}
2020-04-03 18:03:36 +02:00
DocumentStore raven = null;
2020-04-03 16:45:47 +02:00
2020-04-03 18:03:36 +02:00
try
2020-04-03 16:45:47 +02:00
{
2020-04-03 18:03:36 +02:00
// test read & write permissions on folders
// TODO
2020-04-03 16:45:47 +02:00
2020-04-03 18:03:36 +02:00
// create temporary instance of database
raven = new DocumentStore()
{
Urls = model.Database.Url.Split(','),
Database = model.Database.Name
};
2020-04-03 16:45:47 +02:00
2021-03-18 15:54:29 +01:00
ConventionsBuilder.Run(raven.Conventions);
raven.Initialize();
2020-04-03 16:45:47 +02:00
2020-04-03 18:03:36 +02:00
// create application
2020-12-02 15:45:44 +01:00
Application app = Prepare(new Application()
2020-04-03 18:03:36 +02:00
{
2020-12-02 15:45:44 +01:00
Id = "app." + Safenames.Alias(model.AppName),
2020-04-03 18:03:36 +02:00
CreatedDate = DateTimeOffset.Now,
IsActive = true,
Name = model.AppName,
2020-12-02 15:45:44 +01:00
Alias = Safenames.Alias(model.AppName),
Database = model.Database.Name
});
2020-04-03 18:03:36 +02:00
// create user
2020-12-02 15:45:44 +01:00
BackofficeUser user = Prepare(new BackofficeUser()
2020-04-03 18:03:36 +02:00
{
IsSuper = true,
CreatedDate = DateTimeOffset.Now,
Email = model.User.Email,
2020-12-02 15:45:44 +01:00
Username = model.User.Email,
2020-04-03 18:03:36 +02:00
Name = model.User.Name,
IsActive = true,
2020-10-12 13:14:23 +02:00
LanguageId = Options.DefaultLanguage,
2020-05-31 15:01:28 +02:00
Alias = Safenames.Alias(model.User.Name),
2020-12-02 15:45:44 +01:00
IsEmailConfirmed = true,
SecurityStamp = NewSecurityStamp()
});
user.PasswordHash = PasswordHasher.HashPassword(user, model.User.Password);
2020-04-03 18:03:36 +02:00
// create default language
2020-12-02 15:45:44 +01:00
Language language = Prepare(new Language() // TODO get default language selection from setup UI
{
Name = "English",
2020-05-31 15:01:28 +02:00
Alias = Safenames.Alias("English"),
CreatedDate = DateTimeOffset.Now,
Code = "en-US",
IsActive = true,
IsDefault = true
2020-12-02 15:45:44 +01:00
});
2020-12-02 15:45:44 +01:00
using IAsyncDocumentSession session = raven.OpenAsyncSession();
await session.StoreAsync(user);
2020-04-15 01:42:06 +02:00
// user creation failed
2020-12-02 15:45:44 +01:00
//if (!result.Succeeded)
//{
// EntityResult<SetupModel> entityResult = EntityResult<SetupModel>.Fail();
// foreach (IdentityError error in result.Errors)
// {
// entityResult.AddError(error.Code, error.Description);
// }
// return entityResult;
//}
await session.StoreAsync(app);
// save default user roles
IList<BackofficeUserRole> roles = GetRoles(model);
foreach (BackofficeUserRole role in roles)
2020-04-15 01:42:06 +02:00
{
2020-12-02 15:45:44 +01:00
await session.StoreAsync(role);
2020-04-15 01:42:06 +02:00
}
2020-04-13 12:14:19 +02:00
2020-12-02 15:45:44 +01:00
// add admin role to super user
// set app-id for user and store it
user.AppId = session.Advanced.GetDocumentId(app);
user.CurrentAppId = user.AppId;
user.RoleIds.Add(roles.First(role => role.Name == "Standard").Id);
user.RoleIds.Add(roles.First(role => role.Name == "Administrator").Id);
await session.StoreAsync(user);
// create language
await session.StoreAsync(language);
// set countries
using (Raven.Client.Documents.BulkInsert.BulkInsertOperation bulkInsert = raven.BulkInsert())
2020-04-03 18:03:36 +02:00
{
2020-12-02 15:45:44 +01:00
foreach (Country country in GetCountries(model, language))
{
2020-12-02 15:45:44 +01:00
await bulkInsert.StoreAsync(country);
}
2020-04-03 18:03:36 +02:00
}
2020-12-02 15:45:44 +01:00
// update settings file. if this fails the changes won't be stored
UpdateSettingsFile(model);
await session.SaveChangesAsync();
2020-04-03 16:45:47 +02:00
}
2020-11-14 12:34:26 +01:00
catch (Exception)
2020-04-03 18:03:36 +02:00
{
// TODO revert
2020-11-14 12:34:26 +01:00
throw;
2020-04-03 18:03:36 +02:00
}
finally
{
raven?.Dispose();
}
2020-04-03 16:45:47 +02:00
2020-04-15 15:13:38 +02:00
return EntityResult<SetupModel>.Success(model);
2020-04-03 16:45:47 +02:00
}
2020-04-04 19:31:01 +02:00
/// <summary>
/// Updates the settings file with the new data (database connection and version)
/// </summary>
void UpdateSettingsFile(SetupModel model)
{
// TODO should we write this into appSettings.json now?
// or let the user do it in the code editor?
2020-04-04 19:31:01 +02:00
//var filePath = Path.Combine(model.ContentRootPath, "zeroSettings.json");
//string json = File.ReadAllText(filePath);
2020-04-04 19:31:01 +02:00
//ZeroConfiguration config = JsonConvert.DeserializeObject<ZeroConfiguration>(json);
2020-04-04 19:31:01 +02:00
//config.Raven = new RavenOptions()
//{
// Database = model.Database.Name,
// Url = model.Database.Url
//};
2020-04-04 19:31:01 +02:00
//config.ZeroVersion = System.Reflection.Assembly.GetEntryAssembly().GetName().Version.ToString();
2020-04-04 19:31:01 +02:00
//json = JsonConvert.SerializeObject(config, Formatting.Indented);
//File.WriteAllText(filePath, json);
2020-04-04 19:31:01 +02:00
}
2020-04-13 12:14:19 +02:00
/// <summary>
/// Get countries for supported backoffice languages
/// </summary>
IList<Country> GetCountries(SetupModel model, Language defaultLanguage)
2020-04-13 12:14:19 +02:00
{
List<Country> countries = new List<Country>();
//string[] isoCodes = Config.SupportedLanguages;
string[] isoCodes = new string[1] { "en-US" };
2020-04-13 12:14:19 +02:00
foreach (string languageISO in isoCodes)
{
// country list from: https://github.com/umpirsky/country-list/tree/master/data
var filePath = Path.Combine(model.ContentRootPath, "Resources", "Countries", "countries." + languageISO.ToLowerInvariant() + ".json");
string json = File.ReadAllText(filePath);
2020-12-02 15:45:44 +01:00
countries.AddRange(JsonConvert.DeserializeObject<Dictionary<string, string>>(json).Select(country => Prepare(new Country()
2020-04-13 12:14:19 +02:00
{
CreatedDate = DateTimeOffset.Now,
IsActive = true,
2020-11-14 12:34:26 +01:00
//AppId = Constants.Database.SharedAppId, // TODO appx fix
2020-05-31 15:01:28 +02:00
Alias = Safenames.Alias(country.Value),
2020-09-17 13:08:16 +02:00
//LanguageId = defaultLanguage.Id,
2020-05-24 18:23:56 +02:00
Code = country.Key.ToLowerInvariant(),
2020-04-13 12:14:19 +02:00
Name = country.Value
2020-12-02 15:45:44 +01:00
})).ToList());
2020-04-13 12:14:19 +02:00
}
return countries;
}
/// <summary>
/// Create default roles
/// </summary>
IList<BackofficeUserRole> GetRoles(SetupModel model)
{
string type = Constants.Auth.Claims.Permission;
2020-12-02 15:45:44 +01:00
BackofficeUserRole adminRole = Prepare(new BackofficeUserRole()
{
Name = "Administrator",
2020-05-31 15:01:28 +02:00
Alias = Safenames.Alias("Administrator"),
Sort = 0,
Icon = "fth-award",
CreatedDate = DateTimeOffset.Now,
IsActive = true,
2021-05-04 17:23:52 +02:00
Claims = new List<UserClaim>()
{
2020-05-20 16:09:56 +02:00
//new UserClaim(type, Permissions.Applications, PermissionsValue.Write),
2020-04-25 19:38:45 +02:00
new UserClaim(type, Permissions.Sections.Dashboard, PermissionsValue.True),
new UserClaim(type, Permissions.Sections.Spaces, PermissionsValue.True),
2020-04-25 19:38:45 +02:00
new UserClaim(type, Permissions.Sections.Pages, PermissionsValue.True),
new UserClaim(type, Permissions.Sections.Media, PermissionsValue.True),
new UserClaim(type, Permissions.Sections.Settings, PermissionsValue.True),
new UserClaim(type, Permissions.Settings.Applications, PermissionsValue.None),
2020-08-11 16:01:10 +02:00
new UserClaim(type, Permissions.Settings.Countries, PermissionsValue.Update),
new UserClaim(type, Permissions.Settings.Logging, PermissionsValue.Update),
new UserClaim(type, Permissions.Settings.Translations, PermissionsValue.Update),
new UserClaim(type, Permissions.Settings.Updates, PermissionsValue.Update),
new UserClaim(type, Permissions.Settings.Users, PermissionsValue.Update),
},
2020-12-02 15:45:44 +01:00
});
2020-12-02 15:45:44 +01:00
BackofficeUserRole editorRole = Prepare(new BackofficeUserRole()
{
Name = "Editor",
2020-05-31 15:01:28 +02:00
Alias = Safenames.Alias("Editor"),
Sort = 1,
Icon = "fth-feather",
CreatedDate = DateTimeOffset.Now,
IsActive = true,
2021-05-04 17:23:52 +02:00
Claims = new List<UserClaim>()
{
2020-04-25 19:38:45 +02:00
new UserClaim(type, Permissions.Sections.Dashboard, PermissionsValue.True),
new UserClaim(type, Permissions.Sections.Spaces, PermissionsValue.True),
2020-04-25 19:38:45 +02:00
new UserClaim(type, Permissions.Sections.Pages, PermissionsValue.True),
new UserClaim(type, Permissions.Sections.Media, PermissionsValue.True),
new UserClaim(type, Permissions.Sections.Settings, PermissionsValue.True),
new UserClaim(type, Permissions.Settings.Translations, PermissionsValue.True)
}
2020-12-02 15:45:44 +01:00
});
2020-12-02 15:45:44 +01:00
BackofficeUserRole defaultRole = Prepare(new BackofficeUserRole()
{
Name = "Standard",
2020-05-31 15:01:28 +02:00
Alias = Safenames.Alias("Standard"),
Sort = 2,
Icon = "fth-users",
CreatedDate = DateTimeOffset.Now,
IsActive = true,
2021-05-04 17:23:52 +02:00
Claims = new List<UserClaim>()
{
2020-04-25 19:38:45 +02:00
new UserClaim(type, Permissions.Sections.Dashboard, PermissionsValue.True)
}
2020-12-02 15:45:44 +01:00
});
return new List<BackofficeUserRole>() { adminRole, editorRole, defaultRole };
}
2020-12-02 15:45:44 +01:00
2021-05-04 17:23:52 +02:00
T Prepare<T>(T model, string languageId = null) where T : ZeroIdEntity
2020-12-02 15:45:44 +01:00
{
2021-05-04 17:23:52 +02:00
ZeroEntity zeroEntity = model as ZeroEntity;
2020-12-02 15:45:44 +01:00
// set default properties
if (zeroEntity != null && zeroEntity.CreatedDate == default)
{
zeroEntity.CreatedDate = DateTimeOffset.Now;
}
if (zeroEntity != null && zeroEntity.CreatedById == default)
{
zeroEntity.CreatedById = Constants.Auth.SystemUser;
}
2021-05-04 17:23:52 +02:00
if (zeroEntity != null && zeroEntity.LanguageId == default)
2020-12-02 15:45:44 +01:00
{
2021-05-04 17:23:52 +02:00
zeroEntity.LanguageId = languageId;
2020-12-02 15:45:44 +01:00
}
// update name alias and last modified
if (zeroEntity != null)
{
zeroEntity.Alias = Safenames.Alias(zeroEntity.Name);
zeroEntity.LastModifiedById = Constants.Auth.SystemUser;
zeroEntity.LastModifiedDate = DateTimeOffset.Now;
zeroEntity.Hash ??= IdGenerator.Create();
zeroEntity.IsActive = true;
}
return model;
}
/// <summary>
/// Creates a new security stamp
/// </summary>
string NewSecurityStamp()
{
byte[] bytes = new byte[20];
RandomNumberGenerator.Fill(bytes);
return Base32.ToBase32(bytes);
}
2020-04-03 16:45:47 +02:00
}
public interface ISetupApi
{
2020-04-15 15:13:38 +02:00
Task<EntityResult<SetupModel>> Install(SetupModel model);
2020-04-03 16:45:47 +02:00
}
}