diff --git a/Oqtane.Client/App.razor b/Oqtane.Client/App.razor index 701faf81..fa1d4e8d 100644 --- a/Oqtane.Client/App.razor +++ b/Oqtane.Client/App.razor @@ -2,7 +2,6 @@ @inject IInstallationService InstallationService @inject IJSRuntime JSRuntime @inject SiteState SiteState -@inject IServiceProvider ServiceProvider @if (_initialized) { @@ -50,29 +49,21 @@ [Parameter] public string AuthorizationToken { get; set; } + [CascadingParameter] + HttpContext HttpContext { get; set; } + private bool _initialized = false; private string _display = "display: none;"; private Installation _installation = new Installation { Success = false, Message = "" }; private PageState PageState { get; set; } - private IHttpContextAccessor accessor; - protected override async Task OnParametersSetAsync() { SiteState.RemoteIPAddress = RemoteIPAddress; SiteState.AntiForgeryToken = AntiForgeryToken; SiteState.AuthorizationToken = AuthorizationToken; - - accessor = (IHttpContextAccessor)ServiceProvider.GetService(typeof(IHttpContextAccessor)); - if (accessor != null) - { - SiteState.IsPrerendering = !accessor.HttpContext.Response.HasStarted; - } - else - { - SiteState.IsPrerendering = true; - } + SiteState.IsPrerendering = (HttpContext != null) ? true : false; _installation = await InstallationService.IsInstalled(); if (_installation.Alias != null) diff --git a/Oqtane.Client/Installer/Installer.razor b/Oqtane.Client/Installer/Installer.razor index a3969946..eb4ab1a8 100644 --- a/Oqtane.Client/Installer/Installer.razor +++ b/Oqtane.Client/Installer/Installer.razor @@ -15,7 +15,7 @@
-
@SharedLocalizer["Version"] @Constants.Version (.NET 7)
+
@SharedLocalizer["Version"] @Constants.Version (.NET 8)

diff --git a/Oqtane.Client/Modules/Admin/Dashboard/Index.razor b/Oqtane.Client/Modules/Admin/Dashboard/Index.razor index 6ba9014e..98729f76 100644 --- a/Oqtane.Client/Modules/Admin/Dashboard/Index.razor +++ b/Oqtane.Client/Modules/Admin/Dashboard/Index.razor @@ -12,11 +12,12 @@ if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList)) { string url = NavigateUrl(p.Path); -
+

-

@SharedLocalizer[p.Name] +

+

@((MarkupString)SharedLocalizer[p.Name].ToString().Replace(" ", "
"))

-
+

} } diff --git a/Oqtane.Client/Modules/Admin/Files/Edit.razor b/Oqtane.Client/Modules/Admin/Files/Edit.razor index e69b44aa..05990fe4 100644 --- a/Oqtane.Client/Modules/Admin/Files/Edit.razor +++ b/Oqtane.Client/Modules/Admin/Files/Edit.razor @@ -67,6 +67,7 @@ +

@if (!_isSystem) { @@ -79,8 +80,7 @@ @((MarkupString)" ") } -
-
+

@if (PageState.QueryString.ContainsKey("id")) { diff --git a/Oqtane.Client/Modules/Admin/Files/Index.razor b/Oqtane.Client/Modules/Admin/Files/Index.razor index 11684f3f..504a888f 100644 --- a/Oqtane.Client/Modules/Admin/Files/Index.razor +++ b/Oqtane.Client/Modules/Admin/Files/Index.razor @@ -8,27 +8,28 @@ @if (_files != null) { -
-
-
- -
-
+
+
+ +
+
+
+ @Localizer["Folder"]: -
-
  -   -
+
+ +
- + +
    diff --git a/Oqtane.Client/Modules/Admin/Jobs/Index.razor b/Oqtane.Client/Modules/Admin/Jobs/Index.razor index 9b217d07..ee9fb6fe 100644 --- a/Oqtane.Client/Modules/Admin/Jobs/Index.razor +++ b/Oqtane.Client/Modules/Admin/Jobs/Index.razor @@ -15,7 +15,7 @@ else

- +
    diff --git a/Oqtane.Client/Modules/Admin/Languages/Index.razor b/Oqtane.Client/Modules/Admin/Languages/Index.razor index 19b3be19..18c98317 100644 --- a/Oqtane.Client/Modules/Admin/Languages/Index.razor +++ b/Oqtane.Client/Modules/Admin/Languages/Index.razor @@ -14,7 +14,7 @@ else { - +
  @SharedLocalizer["Name"] diff --git a/Oqtane.Client/Modules/Admin/Login/Index.razor b/Oqtane.Client/Modules/Admin/Login/Index.razor index 850072f1..73b22c94 100644 --- a/Oqtane.Client/Modules/Admin/Login/Index.razor +++ b/Oqtane.Client/Modules/Admin/Login/Index.razor @@ -3,6 +3,7 @@ @inherits ModuleBase @inject NavigationManager NavigationManager @inject IUserService UserService +@inject ISettingService SettingService @inject IServiceProvider ServiceProvider @inject IStringLocalizer Localizer @inject IStringLocalizer SharedLocalizer @@ -35,10 +36,13 @@
+ @if (!_alwaysremember) + {
+ }
@@ -77,6 +81,7 @@ private string _passwordtype = "password"; private string _togglepassword = string.Empty; private bool _remember = false; + private bool _alwaysremember = false; private string _code = string.Empty; private string _returnUrl = string.Empty; @@ -92,18 +97,11 @@ { try { + _allowexternallogin = (SettingService.GetSetting(PageState.Site.Settings, "ExternalLogin:ProviderType", "") != "") ? true : false; + _allowsitelogin = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AllowSiteLogin", "true")); + _togglepassword = SharedLocalizer["ShowPassword"]; - if (PageState.Site.Settings.ContainsKey("LoginOptions:AllowSiteLogin") && !string.IsNullOrEmpty(PageState.Site.Settings["LoginOptions:AllowSiteLogin"])) - { - _allowsitelogin = bool.Parse(PageState.Site.Settings["LoginOptions:AllowSiteLogin"]); - } - - if (PageState.Site.Settings.ContainsKey("ExternalLogin:ProviderType") && !string.IsNullOrEmpty(PageState.Site.Settings["ExternalLogin:ProviderType"])) - { - _allowexternallogin = true; - } - if (PageState.QueryString.ContainsKey("returnurl")) { _returnUrl = PageState.QueryString["returnurl"]; @@ -157,6 +155,10 @@ AddModuleMessage(Localizer["ExternalLoginStatus." + PageState.QueryString["status"]], MessageType.Info); } } + if (PageState.Site.Settings.TryGetValue("LoginOptions:AlwaysRemember", out string alwaysRememberStr)) + { + _alwaysremember = Convert.ToBoolean(alwaysRememberStr); + } } catch (Exception ex) { @@ -167,7 +169,7 @@ protected override async Task OnAfterRenderAsync(bool firstRender) { - if (firstRender && PageState.User == null) + if (firstRender && PageState.User == null && _allowsitelogin) { await username.FocusAsync(); } @@ -191,7 +193,14 @@ var user = new User { SiteId = PageState.Site.SiteId, Username = _username, Password = _password, LastIPAddress = SiteState.RemoteIPAddress}; if (!twofactor) - { + { + bool alwaysRemember = false; + if (PageState.Site.Settings.TryGetValue("LoginOptions:AlwaysRemember", out string alwaysRememberStr)) + { + alwaysRemember = Convert.ToBoolean(alwaysRememberStr); + } + bool remember = alwaysRemember || _remember; + _remember = remember; user = await UserService.LoginUserAsync(user, hybrid, _remember); } else @@ -206,8 +215,7 @@ if (hybrid) { // hybrid apps utilize an interactive login - var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider - .GetService(typeof(IdentityAuthenticationStateProvider)); + var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider.GetService(typeof(IdentityAuthenticationStateProvider)); authstateprovider.NotifyAuthenticationChanged(); NavigationManager.NavigateTo(NavigateUrl(WebUtility.UrlDecode(_returnUrl), true)); } @@ -221,7 +229,7 @@ } else { - if ((PageState.Site.Settings.ContainsKey("LoginOptions:TwoFactor") && PageState.Site.Settings["LoginOptions:TwoFactor"] == "required") || user.TwoFactorRequired) + if (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:TwoFactor", "false") == "required" || user.TwoFactorRequired) { twofactor = true; validated = false; diff --git a/Oqtane.Client/Modules/Admin/ModuleDefinitions/Add.razor b/Oqtane.Client/Modules/Admin/ModuleDefinitions/Add.razor index 8a4e2850..9aa03a1f 100644 --- a/Oqtane.Client/Modules/Admin/ModuleDefinitions/Add.razor +++ b/Oqtane.Client/Modules/Admin/ModuleDefinitions/Add.razor @@ -66,6 +66,7 @@
@SharedLocalizer["Search.Version"]: @context.Version diff --git a/Oqtane.Client/Modules/Admin/ModuleDefinitions/Create.razor b/Oqtane.Client/Modules/Admin/ModuleDefinitions/Create.razor index 40a9b1e2..94770893 100644 --- a/Oqtane.Client/Modules/Admin/ModuleDefinitions/Create.razor +++ b/Oqtane.Client/Modules/Admin/ModuleDefinitions/Create.razor @@ -89,7 +89,10 @@ protected override void OnInitialized() { - AddModuleMessage(Localizer["Info.Module.Development"], MessageType.Info); + if (!NavigationManager.BaseUri.Contains("localhost:")) + { + AddModuleMessage(Localizer["Info.Module.Development"], MessageType.Info); + } } protected override async Task OnParametersSetAsync() @@ -115,11 +118,18 @@ { if (IsValid(_owner) && IsValid(_module) && _owner != _module && _template != "-") { - var template = _templates.FirstOrDefault(item => item.Name == _template); - var moduleDefinition = new ModuleDefinition { Owner = _owner, Name = _module, Description = _description, Template = _template, Version = _reference, ModuleDefinitionName = template.Namespace }; - moduleDefinition = await ModuleDefinitionService.CreateModuleDefinitionAsync(moduleDefinition); - GetLocation(); - AddModuleMessage(string.Format(Localizer["Success.Module.Create"], NavigateUrl("admin/system")), MessageType.Success); + if (IsValidXML(_description)) + { + var template = _templates.FirstOrDefault(item => item.Name == _template); + var moduleDefinition = new ModuleDefinition { Owner = _owner, Name = _module, Description = _description, Template = _template, Version = _reference, ModuleDefinitionName = template.Namespace }; + moduleDefinition = await ModuleDefinitionService.CreateModuleDefinitionAsync(moduleDefinition); + GetLocation(); + AddModuleMessage(string.Format(Localizer["Success.Module.Create"], NavigateUrl("admin/system")), MessageType.Success); + } + else + { + AddModuleMessage(Localizer["Message.Require.ValidDescription"], MessageType.Warning); + } } else { @@ -143,6 +153,12 @@ return !string.IsNullOrEmpty(name) && name.ToLower() != "module" && !name.ToLower().Contains("oqtane") && Regex.IsMatch(name, "^[A-Za-z_][A-Za-z0-9_]*$"); } + private bool IsValidXML(string description) + { + // must contain letters, digits, or spaces + return Regex.IsMatch(description, "^[A-Za-z0-9 .,!?]+$"); + } + private void TemplateChanged(ChangeEventArgs e) { _template = (string)e.Value; diff --git a/Oqtane.Client/Modules/Admin/Modules/Settings.razor b/Oqtane.Client/Modules/Admin/Modules/Settings.razor index 9798c2f9..7f4f35f1 100644 --- a/Oqtane.Client/Modules/Admin/Modules/Settings.razor +++ b/Oqtane.Client/Modules/Admin/Modules/Settings.razor @@ -26,6 +26,17 @@
+
+ +
+ +
+
@@ -112,6 +123,7 @@ private List _containers = new List(); private string _module; private string _title; + private string _pane; private string _containerType; private string _allPages = "false"; private string _permissionNames = ""; @@ -134,80 +146,82 @@ { _module = ModuleState.ModuleDefinition.Name; _title = ModuleState.Title; + _pane = ModuleState.Pane; _containers = ThemeService.GetContainerControls(PageState.Site.Themes, PageState.Page.ThemeType); - _containerType = ModuleState.ContainerType; - _allPages = ModuleState.AllPages.ToString(); - _permissions = ModuleState.PermissionList; - _pageId = ModuleState.PageId.ToString(); - createdby = ModuleState.CreatedBy; - createdon = ModuleState.CreatedOn; - modifiedby = ModuleState.ModifiedBy; - modifiedon = ModuleState.ModifiedOn; + _containerType = ModuleState.ContainerType; + _allPages = ModuleState.AllPages.ToString(); + _permissions = ModuleState.PermissionList; + _pageId = ModuleState.PageId.ToString(); + createdby = ModuleState.CreatedBy; + createdon = ModuleState.CreatedOn; + modifiedby = ModuleState.ModifiedBy; + modifiedon = ModuleState.ModifiedOn; - if (ModuleState.ModuleDefinition != null) - { - _permissionNames = ModuleState.ModuleDefinition?.PermissionNames; + if (ModuleState.ModuleDefinition != null) + { + _permissionNames = ModuleState.ModuleDefinition?.PermissionNames; - if (!string.IsNullOrEmpty(ModuleState.ModuleDefinition.SettingsType)) - { - // module settings type explicitly declared in IModule interface - _moduleSettingsType = Type.GetType(ModuleState.ModuleDefinition.SettingsType); - } - else - { - // legacy support - module settings type determined by convention ( ie. existence of a "Settings.razor" component in module ) - _moduleSettingsType = Type.GetType(ModuleState.ModuleDefinition.ControlTypeTemplate.Replace(Constants.ActionToken, PageState.Action), false, true); - } - if (_moduleSettingsType != null) - { - var moduleobject = Activator.CreateInstance(_moduleSettingsType) as IModuleControl; - if (!string.IsNullOrEmpty(moduleobject.Title)) - { - _moduleSettingsTitle = moduleobject.Title; - } + if (!string.IsNullOrEmpty(ModuleState.ModuleDefinition.SettingsType)) + { + // module settings type explicitly declared in IModule interface + _moduleSettingsType = Type.GetType(ModuleState.ModuleDefinition.SettingsType); + } + else + { + // legacy support - module settings type determined by convention ( ie. existence of a "Settings.razor" component in module ) + _moduleSettingsType = Type.GetType(ModuleState.ModuleDefinition.ControlTypeTemplate.Replace(Constants.ActionToken, PageState.Action), false, true); + } + if (_moduleSettingsType != null) + { + var moduleobject = Activator.CreateInstance(_moduleSettingsType) as IModuleControl; + if (!string.IsNullOrEmpty(moduleobject.Title)) + { + _moduleSettingsTitle = moduleobject.Title; + } - ModuleSettingsComponent = builder => - { - builder.OpenComponent(0, _moduleSettingsType); - builder.AddComponentReferenceCapture(1, inst => { _moduleSettings = Convert.ChangeType(inst, _moduleSettingsType); }); - builder.CloseComponent(); - }; - } - } - else - { - AddModuleMessage(string.Format(Localizer["Error.Module.Load"], ModuleState.ModuleDefinitionName), MessageType.Error); - } + ModuleSettingsComponent = builder => + { + builder.OpenComponent(0, _moduleSettingsType); + builder.AddComponentReferenceCapture(1, inst => { _moduleSettings = Convert.ChangeType(inst, _moduleSettingsType); }); + builder.CloseComponent(); + }; + } + } + else + { + AddModuleMessage(string.Format(Localizer["Error.Module.Load"], ModuleState.ModuleDefinitionName), MessageType.Error); + } var theme = PageState.Site.Themes.FirstOrDefault(item => item.Containers.Any(themecontrol => themecontrol.TypeName.Equals(_containerType))); - if (theme != null && !string.IsNullOrEmpty(theme.ContainerSettingsType)) - { - _containerSettingsType = Type.GetType(theme.ContainerSettingsType); - if (_containerSettingsType != null) - { - ContainerSettingsComponent = builder => - { - builder.OpenComponent(0, _containerSettingsType); - builder.AddComponentReferenceCapture(1, inst => { _containerSettings = Convert.ChangeType(inst, _containerSettingsType); }); - builder.CloseComponent(); - }; - } - } - } + if (theme != null && !string.IsNullOrEmpty(theme.ContainerSettingsType)) + { + _containerSettingsType = Type.GetType(theme.ContainerSettingsType); + if (_containerSettingsType != null) + { + ContainerSettingsComponent = builder => + { + builder.OpenComponent(0, _containerSettingsType); + builder.AddComponentReferenceCapture(1, inst => { _containerSettings = Convert.ChangeType(inst, _containerSettingsType); }); + builder.CloseComponent(); + }; + } + } + } - private async Task SaveModule() - { - validated = true; - var interop = new Interop(JSRuntime); - if (await interop.FormValid(form)) - { - if (!string.IsNullOrEmpty(_title)) - { - var pagemodule = await PageModuleService.GetPageModuleAsync(ModuleState.PageModuleId); - pagemodule.PageId = int.Parse(_pageId); - pagemodule.Title = _title; - pagemodule.ContainerType = (_containerType != "-") ? _containerType : string.Empty; - if (!string.IsNullOrEmpty(pagemodule.ContainerType) && pagemodule.ContainerType == PageState.Page.DefaultContainerType) + private async Task SaveModule() + { + validated = true; + var interop = new Interop(JSRuntime); + if (await interop.FormValid(form)) + { + if (!string.IsNullOrEmpty(_title)) + { + var pagemodule = await PageModuleService.GetPageModuleAsync(ModuleState.PageModuleId); + pagemodule.PageId = int.Parse(_pageId); + pagemodule.Title = _title; + pagemodule.Pane = _pane; + pagemodule.ContainerType = (_containerType != "-") ? _containerType : string.Empty; + if (!string.IsNullOrEmpty(pagemodule.ContainerType) && pagemodule.ContainerType == PageState.Page.DefaultContainerType) { pagemodule.ContainerType = string.Empty; } diff --git a/Oqtane.Client/Modules/Admin/Pages/Add.razor b/Oqtane.Client/Modules/Admin/Pages/Add.razor index 78bdd021..c55d4ec0 100644 --- a/Oqtane.Client/Modules/Admin/Pages/Add.razor +++ b/Oqtane.Client/Modules/Admin/Pages/Add.razor @@ -40,9 +40,9 @@
@if (_children != null && _children.Count > 0 && (_insert == "<" || _insert == ">")) { @@ -324,7 +327,6 @@ { try { - _children = PageState.Pages.Where(item => item.ParentId == null).ToList(); _pageId = Int32.Parse(PageState.QueryString["id"]); _page = await PageService.GetPageAsync(_pageId); _icons = await SystemService.GetIconsAsync(); @@ -342,6 +344,14 @@ _parentid = _page.ParentId.ToString(); _parent = PageState.Pages.FirstOrDefault(item => item.PageId == _page.ParentId); } + _children = new List(); + foreach (Page p in PageState.Pages.Where(item => (_parentid == "-1" && item.ParentId == null) || (item.ParentId == int.Parse(_parentid)))) + { + if (p.PageId != _pageId && UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList)) + { + _children.Add(p); + } + } _currentparentid = _parentid; _isnavigation = _page.IsNavigation.ToString(); _isclickable = _page.IsClickable.ToString(); @@ -365,7 +375,7 @@ // appearance _title = _page.Title; _themetype = _page.ThemeType; - if (string.IsNullOrEmpty(_themetype) || ThemeService.GetTheme(PageState.Site.Themes, _themetype)?.ThemeName != ThemeService.GetTheme(PageState.Site.Themes, PageState.Site.DefaultThemeType)?.ThemeName) + if (string.IsNullOrEmpty(_themetype)) { _themetype = PageState.Site.DefaultThemeType; } @@ -417,34 +427,14 @@ { _parentid = (string)e.Value; _children = new List(); - if (_parentid == "-1") + foreach (Page p in PageState.Pages.Where(item => (_parentid == "-1" && item.ParentId == null) || (item.ParentId == int.Parse(_parentid)))) { - foreach (Page p in PageState.Pages.Where(item => item.ParentId == null)) + if (p.PageId != _pageId && UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList)) { - if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList)) - { - _children.Add(p); - } + _children.Add(p); } } - else - { - foreach (Page p in PageState.Pages.Where(item => item.ParentId == int.Parse(_parentid))) - { - if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList)) - { - _children.Add(p); - } - } - } - if (_parentid == _currentparentid) - { - _insert = "="; - } - else - { - _insert = ">>"; - } + _insert = (_parentid == _currentparentid) ? "=" : ">>"; StateHasChanged(); } catch (Exception ex) diff --git a/Oqtane.Client/Modules/Admin/Pages/Index.razor b/Oqtane.Client/Modules/Admin/Pages/Index.razor index d71dc964..f1b9e9b4 100644 --- a/Oqtane.Client/Modules/Admin/Pages/Index.razor +++ b/Oqtane.Client/Modules/Admin/Pages/Index.razor @@ -9,7 +9,7 @@ { - +
    diff --git a/Oqtane.Client/Modules/Admin/Profiles/Edit.razor b/Oqtane.Client/Modules/Admin/Profiles/Edit.razor index 9620a396..91fe98ec 100644 --- a/Oqtane.Client/Modules/Admin/Profiles/Edit.razor +++ b/Oqtane.Client/Modules/Admin/Profiles/Edit.razor @@ -22,7 +22,7 @@
- +
@@ -34,19 +34,25 @@
- +
- + +
+
+
+ +
+
- +
@@ -101,6 +107,7 @@ private string _category = string.Empty; private string _vieworder = "0"; private string _maxlength = "0"; + private string _rows = "1"; private string _defaultvalue = string.Empty; private string _options = string.Empty; private string _validation = string.Empty; @@ -131,6 +138,7 @@ _category = profile.Category; _vieworder = profile.ViewOrder.ToString(); _maxlength = profile.MaxLength.ToString(); + _rows = profile.Rows.ToString(); _defaultvalue = profile.DefaultValue; _options = profile.Options; _validation = profile.Validation; @@ -175,6 +183,7 @@ profile.Category = _category; profile.ViewOrder = int.Parse(_vieworder); profile.MaxLength = int.Parse(_maxlength); + profile.Rows = int.Parse(_rows); profile.DefaultValue = _defaultvalue; profile.Options = _options; profile.Validation = _validation; diff --git a/Oqtane.Client/Modules/Admin/Profiles/Index.razor b/Oqtane.Client/Modules/Admin/Profiles/Index.razor index c6b7cb8a..587fa452 100644 --- a/Oqtane.Client/Modules/Admin/Profiles/Index.razor +++ b/Oqtane.Client/Modules/Admin/Profiles/Index.razor @@ -12,16 +12,22 @@ else { - +
    @SharedLocalizer["Name"] + @Localizer["Title"] + @Localizer["Category"] + @Localizer["Order"]
@context.Name + @context.Title + @context.Category + @context.ViewOrder
} diff --git a/Oqtane.Client/Modules/Admin/Reset/Index.razor b/Oqtane.Client/Modules/Admin/Reset/Index.razor index 0905bb62..151f4aa9 100644 --- a/Oqtane.Client/Modules/Admin/Reset/Index.razor +++ b/Oqtane.Client/Modules/Admin/Reset/Index.razor @@ -6,6 +6,7 @@ @inject IStringLocalizer SharedLocalizer
+
@@ -45,12 +46,14 @@ private string _passwordtype = "password"; private string _togglepassword = string.Empty; private string _confirm = string.Empty; + private string _passwordrequirements; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous; protected override async Task OnInitializedAsync() { _togglepassword = SharedLocalizer["ShowPassword"]; + _passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId); if (PageState.QueryString.ContainsKey("name") && PageState.QueryString.ContainsKey("token")) { diff --git a/Oqtane.Client/Modules/Admin/Roles/Index.razor b/Oqtane.Client/Modules/Admin/Roles/Index.razor index 6677548d..6b0e4610 100644 --- a/Oqtane.Client/Modules/Admin/Roles/Index.razor +++ b/Oqtane.Client/Modules/Admin/Roles/Index.razor @@ -12,7 +12,7 @@ else { - +
    diff --git a/Oqtane.Client/Modules/Admin/Sites/Add.razor b/Oqtane.Client/Modules/Admin/Sites/Add.razor index 009fc4b4..b940c151 100644 --- a/Oqtane.Client/Modules/Admin/Sites/Add.razor +++ b/Oqtane.Client/Modules/Admin/Sites/Add.razor @@ -37,7 +37,7 @@ else
- @foreach (var theme in _themes) { @@ -58,19 +58,6 @@ else
-
- -
- -
-
@@ -105,7 +92,7 @@ else
- @foreach (Tenant tenant in _tenants) @@ -188,46 +175,59 @@ else } @code { - private List _databases; - private ElementReference form; - private bool validated = false; - private string _databaseName; - private Type _databaseConfigType; - private object _databaseConfig; - private RenderFragment DatabaseConfigComponent { get; set; } - private bool _showConnectionString = false; - private string _connectionString = string.Empty; + private List _databases; + private ElementReference form; + private bool validated = false; + private string _databaseName; + private Type _databaseConfigType; + private object _databaseConfig; + private RenderFragment DatabaseConfigComponent { get; set; } + private bool _showConnectionString = false; + private string _connectionString = string.Empty; - private List _themeList; - private List _themes = new List(); - private List _containers = new List(); - private List _siteTemplates; - private List _tenants; - private string _tenantid = "-"; + private List _themeList; + private List _themes = new List(); + private List _containers = new List(); + private List _siteTemplates; + private List _tenants; + private string _tenantid = "-"; - private string _tenantName = string.Empty; + private string _tenantName = string.Empty; - private string _hostusername = string.Empty; - private string _hostpassword = string.Empty; + private string _hostusername = string.Empty; + private string _hostpassword = string.Empty; - private string _name = string.Empty; - private string _urls = string.Empty; - private string _themetype = "-"; - private string _containertype = "-"; - private string _admincontainertype = ""; - private string _sitetemplatetype = "-"; - private string _runtime = "Server"; - private string _prerender = "Prerendered"; + private string _name = string.Empty; + private string _urls = string.Empty; + private string _themetype = "-"; + private string _containertype = "-"; + private string _sitetemplatetype = "-"; + private string _runtime = "Server"; + private string _prerender = "Prerendered"; - public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host; + public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host; - protected override async Task OnInitializedAsync() - { - _tenants = await TenantService.GetTenantsAsync(); - _urls = PageState.Alias.Name; - _themeList = await ThemeService.GetThemesAsync(); - _themes = ThemeService.GetThemeControls(_themeList); - _siteTemplates = await SiteTemplateService.GetSiteTemplatesAsync(); + protected override async Task OnInitializedAsync() + { + _tenants = await TenantService.GetTenantsAsync(); + if (_tenants.Any(item => item.Name == TenantNames.Master)) + { + _tenantid = _tenants.First(item => item.Name == TenantNames.Master).TenantId.ToString(); + } + _urls = PageState.Alias.Name; + _themeList = await ThemeService.GetThemesAsync(); + _themes = ThemeService.GetThemeControls(_themeList); + if (_themes.Any(item => item.TypeName == Constants.DefaultTheme)) + { + _themetype = Constants.DefaultTheme; + _containers = ThemeService.GetContainerControls(_themeList, _themetype); + _containertype = _containers.First().TypeName; + } + _siteTemplates = await SiteTemplateService.GetSiteTemplatesAsync(); + if (_siteTemplates.Any(item => item.TypeName == Constants.DefaultSiteTemplate)) + { + _sitetemplatetype = Constants.DefaultSiteTemplate; + } _databases = await DatabaseService.GetDatabasesAsync(); if (_databases.Exists(item => item.IsDefault)) @@ -295,7 +295,6 @@ else _containers = new List(); _containertype = "-"; } - _admincontainertype = ""; StateHasChanged(); } catch (Exception ex) @@ -399,7 +398,7 @@ else config.Aliases = _urls; config.DefaultTheme = _themetype; config.DefaultContainer = _containertype; - config.DefaultAdminContainer = _admincontainertype; + config.DefaultAdminContainer = ""; config.SiteTemplate = _sitetemplatetype; config.Runtime = _runtime; config.RenderMode = _runtime + _prerender; diff --git a/Oqtane.Client/Modules/Admin/Sites/Index.razor b/Oqtane.Client/Modules/Admin/Sites/Index.razor index 8075d4d6..20a652ee 100644 --- a/Oqtane.Client/Modules/Admin/Sites/Index.razor +++ b/Oqtane.Client/Modules/Admin/Sites/Index.razor @@ -14,7 +14,7 @@ else { - +
    diff --git a/Oqtane.Client/Modules/Admin/Themes/Add.razor b/Oqtane.Client/Modules/Admin/Themes/Add.razor index 3b22ef3c..a7be5907 100644 --- a/Oqtane.Client/Modules/Admin/Themes/Add.razor +++ b/Oqtane.Client/Modules/Admin/Themes/Add.razor @@ -66,6 +66,7 @@
@SharedLocalizer["Search.Version"]: @context.Version diff --git a/Oqtane.Client/Modules/Admin/Themes/Create.razor b/Oqtane.Client/Modules/Admin/Themes/Create.razor index 04fa0049..127a0415 100644 --- a/Oqtane.Client/Modules/Admin/Themes/Create.razor +++ b/Oqtane.Client/Modules/Admin/Themes/Create.razor @@ -80,7 +80,10 @@ protected override void OnInitialized() { - AddModuleMessage(Localizer["Info.Theme.CreatorIntent"], MessageType.Info); + if (!NavigationManager.BaseUri.Contains("localhost:")) + { + AddModuleMessage(Localizer["Info.Theme.CreatorIntent"], MessageType.Info); + } } protected override async Task OnParametersSetAsync() diff --git a/Oqtane.Client/Modules/Admin/Upgrade/Index.razor b/Oqtane.Client/Modules/Admin/Upgrade/Index.razor index 2ea14802..2c91688e 100644 --- a/Oqtane.Client/Modules/Admin/Upgrade/Index.razor +++ b/Oqtane.Client/Modules/Admin/Upgrade/Index.razor @@ -7,34 +7,38 @@ @inject IStringLocalizer Localizer @inject IStringLocalizer SharedLocalizer - - - @if (_package != null && _upgradeavailable) - { - - - - } - else - { - - } - - - -
-
- -
- +@if (_initialized) +{ + + + @if (_package != null && _upgradeavailable) + { + + + + } + else + { + + } + + + +
+
+ +
+ +
-
- - - + + + +} @code { + private bool _initialized = false; private Package _package; private bool _upgradeavailable = false; @@ -44,18 +48,26 @@ { try { - List packages = await PackageService.GetPackagesAsync("framework", "", "", ""); - if (packages != null) + if (NavigationManager.BaseUri.Contains("localhost:")) { - _package = packages.Where(item => item.PackageId.StartsWith(Constants.PackageId)).FirstOrDefault(); - if (_package != null) + AddModuleMessage(Localizer["Localhost.Text"], MessageType.Info); + } + else + { + List packages = await PackageService.GetPackagesAsync("framework", "", "", ""); + if (packages != null) { - _upgradeavailable = (Version.Parse(_package.Version).CompareTo(Version.Parse(Constants.Version)) > 0); - } - else - { - _package = new Package { Name = Constants.PackageId, Version = Constants.Version }; + _package = packages.Where(item => item.PackageId.StartsWith(Constants.PackageId)).FirstOrDefault(); + if (_package != null) + { + _upgradeavailable = (Version.Parse(_package.Version).CompareTo(Version.Parse(Constants.Version)) > 0); + } + else + { + _package = new Package { Name = Constants.PackageId, Version = Constants.Version }; + } } + _initialized = true; } } catch diff --git a/Oqtane.Client/Modules/Admin/UrlMappings/Index.razor b/Oqtane.Client/Modules/Admin/UrlMappings/Index.razor index 28478589..6ed8c769 100644 --- a/Oqtane.Client/Modules/Admin/UrlMappings/Index.razor +++ b/Oqtane.Client/Modules/Admin/UrlMappings/Index.razor @@ -28,7 +28,7 @@ else

- +
    diff --git a/Oqtane.Client/Modules/Admin/UserProfile/Add.razor b/Oqtane.Client/Modules/Admin/UserProfile/Add.razor index ceeb2713..fca4ddc9 100644 --- a/Oqtane.Client/Modules/Admin/UserProfile/Add.razor +++ b/Oqtane.Client/Modules/Admin/UserProfile/Add.razor @@ -2,6 +2,7 @@ @inherits ModuleBase @inject NavigationManager NavigationManager @inject IUserService UserService +@inject IUserRoleService UserRoleService @inject INotificationService NotificationService @inject IStringLocalizer Localizer @inject IStringLocalizer SharedLocalizer @@ -10,10 +11,10 @@ {
- +
- -
+ +
@@ -30,11 +31,11 @@

- @SharedLocalizer["Cancel"] + @SharedLocalizer["Cancel"] } @code { - private string username = ""; + private AutoComplete username; private string subject = ""; private string body = ""; @@ -42,21 +43,35 @@ public override string Title => "Send Notification"; + private async Task> GetUsers(string filter) + { + var users = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Registered); + return users.Where(item => item.User.Username.Contains(filter, StringComparison.OrdinalIgnoreCase)) + .ToDictionary(item => item.UserId.ToString(), item => item.User.Username); + } + private async Task Send() { try { - var user = await UserService.GetUserAsync(username, PageState.Site.SiteId); - if (user != null) + if (!string.IsNullOrEmpty(username.Key) && !string.IsNullOrEmpty(subject)) { - var notification = new Notification(PageState.Site.SiteId, PageState.User, user, subject, body); - notification = await NotificationService.AddNotificationAsync(notification); - await logger.LogInformation("Notification Created {NotificationId}", notification.NotificationId); - NavigationManager.NavigateTo(NavigateUrl()); + var user = await UserService.GetUserAsync(int.Parse(username.Key), ModuleState.SiteId); + if (user != null) + { + var notification = new Notification(PageState.Site.SiteId, PageState.User, user, subject, body); + notification = await NotificationService.AddNotificationAsync(notification); + await logger.LogInformation("Notification Created {NotificationId}", notification.NotificationId); + NavigationManager.NavigateTo(PageState.ReturnUrl); + } + else + { + AddModuleMessage(Localizer["Message.User.Invalid"], MessageType.Warning); + } } else { - AddModuleMessage(Localizer["Message.User.Invalid"], MessageType.Warning); + AddModuleMessage(Localizer["Message.Required"], MessageType.Warning); } } catch (Exception ex) diff --git a/Oqtane.Client/Modules/Admin/UserProfile/Index.razor b/Oqtane.Client/Modules/Admin/UserProfile/Index.razor index b763b08b..58cc901e 100644 --- a/Oqtane.Client/Modules/Admin/UserProfile/Index.razor +++ b/Oqtane.Client/Modules/Admin/UserProfile/Index.razor @@ -11,18 +11,18 @@ @inject IStringLocalizer Localizer @inject IStringLocalizer SharedLocalizer -@if (PageState.User != null && photo != null) +@if (_initialized) { - @displayname -} -else -{ -
-} - - - @if (profiles != null && settings != null) - { + @if (PageState.User != null && photo != null) + { + @displayname + } + else + { +
+ } + +
@@ -33,34 +33,34 @@ else
-
+
- + -
+
- + -
+
- @if (allowtwofactor) - { -
- -
- -
-
- } + @if (allowtwofactor) + { +
+ +
+ +
+
+ }
@@ -83,11 +83,8 @@ else
- } - - - @if (profiles != null && settings != null) - { + +
@foreach (Profile profile in profiles) @@ -104,8 +101,8 @@ else }
-
- @if (!string.IsNullOrEmpty(p.Options)) +
+ @if (!string.IsNullOrEmpty(p.Options)) { + @if (p.IsRequired) + { + + } + else + { + + } } else { - + @if (p.IsRequired) + { + + } + else + { + + } } }
@@ -140,134 +151,151 @@ else
- } - - - @if (notifications != null) - { + + + +
+

- -

@if (filter == "to") { - -
+ @if (notifications.Any()) + { + +
    @Localizer["From"] @Localizer["Subject"] @Localizer["Received"] -
- - - - - @if (context.IsRead) - { - @context.FromDisplayName - @context.Subject - @string.Format("{0:dd-MMM-yyyy HH:mm:ss}", @context.CreatedOn) - } - else - { - @context.FromDisplayName - @context.Subject - @string.Format("{0:dd-MMM-yyyy HH:mm:ss}", @context.CreatedOn) - } - - - - - @{ - string input = "___"; - if (context.Body.Contains(input)) - { - context.Body = context.Body.Split(input)[0]; - context.Body = context.Body.Replace("\n", ""); - context.Body = context.Body.Replace("\r", ""); - } - notificationSummary = context.Body.Length > 100 ? (context.Body.Substring(0, 97) + "...") : context.Body; - } +
+ + + + @if (context.IsRead) { - @notificationSummary + @context.FromDisplayName + @context.Subject + @string.Format("{0:dd-MMM-yyyy HH:mm:ss}", @context.CreatedOn) } else { - @notificationSummary + @context.FromDisplayName + @context.Subject + @string.Format("{0:dd-MMM-yyyy HH:mm:ss}", @context.CreatedOn) } - - -
+ + + + + @{ + string input = "___"; + if (context.Body.Contains(input)) + { + context.Body = context.Body.Split(input)[0]; + context.Body = context.Body.Replace("\n", ""); + context.Body = context.Body.Replace("\r", ""); + } + notificationSummary = context.Body.Length > 100 ? (context.Body.Substring(0, 97) + "...") : context.Body; + } + @if (context.IsRead) + { + @notificationSummary + } + else + { + @notificationSummary + } + + + +
+ + } + else + { +
+ @Localizer["NoNotificationsReceived.Text"] +
+ } } else { - -
-   -   + @if (notifications.Any()) + { + +
+ + @Localizer["To"] @Localizer["Subject"] @Localizer["Sent"] -
- - - +
+ + + - @if (context.IsRead) - { - @context.ToDisplayName - @context.Subject - @string.Format("{0:dd-MMM-yyyy HH:mm:ss}", @context.CreatedOn) - } - else - { - @context.ToDisplayName - @context.Subject - @string.Format("{0:dd-MMM-yyyy HH:mm:ss}", @context.CreatedOn) - } - - - - - - @{ - string input = "___"; - if (context.Body.Contains(input)) - { - context.Body = context.Body.Split(input)[0]; - context.Body = context.Body.Replace("\n", ""); - context.Body = context.Body.Replace("\r", ""); - } - notificationSummary = context.Body.Length > 100 ? (context.Body.Substring(0, 97) + "...") : context.Body; - } @if (context.IsRead) { - @notificationSummary + @context.ToDisplayName + @context.Subject + @string.Format("{0:dd-MMM-yyyy HH:mm:ss}", @context.CreatedOn) } - else + else { - @notificationSummary - } - - -
+ @context.ToDisplayName + @context.Subject + @string.Format("{0:dd-MMM-yyyy HH:mm:ss}", @context.CreatedOn) + } + + + + + + @{ + string input = "___"; + if (context.Body.Contains(input)) + { + context.Body = context.Body.Split(input)[0]; + context.Body = context.Body.Replace("\n", ""); + context.Body = context.Body.Replace("\r", ""); + } + notificationSummary = context.Body.Length > 100 ? (context.Body.Substring(0, 97) + "...") : context.Body; + } + @if (context.IsRead) + { + @notificationSummary + } + else + { + @notificationSummary + } + + + +
+ + } + else + { +
+ @Localizer["NoNotificationsSent.Text"] +
+ } } - @if (notifications.Any()) - { -
- - } - } -
- -

+ + +
+
+} @code { + private bool _initialized = false; private string _passwordrequirements; private string username = string.Empty; private string _password = string.Empty; @@ -282,27 +310,25 @@ else private int folderid = -1; private int photofileid = -1; private File photo = null; + private List profiles; private Dictionary settings; private string category = string.Empty; + private string filter = "to"; private List notifications; private string notificationSummary = string.Empty; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View; - protected override async Task OnParametersSetAsync() + protected override async Task OnInitializedAsync() { try { _passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId); - _togglepassword = SharedLocalizer["ShowPassword"]; - - if (PageState.Site.Settings.ContainsKey("LoginOptions:TwoFactor") && !string.IsNullOrEmpty(PageState.Site.Settings["LoginOptions:TwoFactor"])) - { - allowtwofactor = (PageState.Site.Settings["LoginOptions:TwoFactor"] == "true"); - } + allowtwofactor = (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:TwoFactor", "false") == "true"); + profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId); if (PageState.User != null) { @@ -329,10 +355,11 @@ else photo = null; } - profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId); settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId); await LoadNotificationsAsync(); + + _initialized = true; } else { @@ -366,44 +393,47 @@ else { try { - if (username != string.Empty && email != string.Empty && ValidateProfiles()) + if (username != string.Empty && email != string.Empty) { if (_password == confirm) { - var user = PageState.User; - user.Username = username; - user.Password = _password; - user.TwoFactorRequired = bool.Parse(twofactor); - user.Email = email; - user.DisplayName = (displayname == string.Empty ? username : displayname); - user.PhotoFileId = filemanager.GetFileId(); - if (user.PhotoFileId == -1) + if (ValidateProfiles()) { - user.PhotoFileId = null; - } - if (user.PhotoFileId != null) - { - photofileid = user.PhotoFileId.Value; - photo = await FileService.GetFileAsync(photofileid); - } - else - { - photofileid = -1; - photo = null; - } + var user = PageState.User; + user.Username = username; + user.Password = _password; + user.TwoFactorRequired = bool.Parse(twofactor); + user.Email = email; + user.DisplayName = (displayname == string.Empty ? username : displayname); + user.PhotoFileId = filemanager.GetFileId(); + if (user.PhotoFileId == -1) + { + user.PhotoFileId = null; + } + if (user.PhotoFileId != null) + { + photofileid = user.PhotoFileId.Value; + photo = await FileService.GetFileAsync(photofileid); + } + else + { + photofileid = -1; + photo = null; + } - user = await UserService.UpdateUserAsync(user); - if (user != null) - { - await SettingService.UpdateUserSettingsAsync(settings, PageState.User.UserId); - await logger.LogInformation("User Profile Saved"); + user = await UserService.UpdateUserAsync(user); + if (user != null) + { + await SettingService.UpdateUserSettingsAsync(settings, PageState.User.UserId); + await logger.LogInformation("User Profile Saved"); - AddModuleMessage(Localizer["Success.Profile.Update"], MessageType.Success); - StateHasChanged(); - } - else - { - AddModuleMessage(Localizer["Message.Password.Complexity"], MessageType.Error); + AddModuleMessage(Localizer["Success.Profile.Update"], MessageType.Success); + StateHasChanged(); + } + else + { + AddModuleMessage(Localizer["Message.Password.Complexity"], MessageType.Error); + } } } else @@ -427,27 +457,33 @@ else private bool ValidateProfiles() { - bool valid = true; foreach (Profile profile in profiles) { - if (string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty)) && !string.IsNullOrEmpty(profile.DefaultValue)) + var value = GetProfileValue(profile.Name, string.Empty); + if (string.IsNullOrEmpty(value) && !string.IsNullOrEmpty(profile.DefaultValue)) { settings = SettingService.SetSetting(settings, profile.Name, profile.DefaultValue); } if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin)) { - if (valid == true && profile.IsRequired && string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty))) + if (profile.IsRequired && string.IsNullOrEmpty(value)) { - valid = false; + AddModuleMessage(string.Format(SharedLocalizer["ProfileRequired"], profile.Title), MessageType.Warning); + return false; } - if (valid == true && !string.IsNullOrEmpty(profile.Validation)) + if (!string.IsNullOrEmpty(profile.Validation)) { Regex regex = new Regex(profile.Validation); - valid = regex.Match(SettingService.GetSetting(settings, profile.Name, string.Empty)).Success; + bool valid = regex.Match(value).Success; + if (!valid) + { + AddModuleMessage(string.Format(SharedLocalizer["ProfileInvalid"], profile.Title), MessageType.Warning); + return false; + } } } } - return valid; + return true; } private void Cancel() @@ -489,7 +525,6 @@ else private async void FilterChanged(ChangeEventArgs e) { filter = (string)e.Value; - await LoadNotificationsAsync(); StateHasChanged(); } diff --git a/Oqtane.Client/Modules/Admin/UserProfile/View.razor b/Oqtane.Client/Modules/Admin/UserProfile/View.razor index 58a3d211..3f104710 100644 --- a/Oqtane.Client/Modules/Admin/UserProfile/View.razor +++ b/Oqtane.Client/Modules/Admin/UserProfile/View.razor @@ -8,94 +8,71 @@ @if (PageState.User != null) { -
-
- - @if (title == "From") - { -
- -
- } - @if (title == "To") - { -
- -
- } -
-
- - @if (title == "From") - { -
- -
- } - @if (title == "To") - { -
- -
- } -
-
-
- @if (title == "From") - { + @if (title == "From") + { +
- +
- +
- } - @if (title == "From") - { -
- -
- + } }
} -
- } - - -
-
- -@SharedLocalizer["Cancel"] + + +
+
+ + @SharedLocalizer["Cancel"] +} + @code { + private bool _initialized = false; private string _passwordrequirements; - private string username = string.Empty; + private string _username = string.Empty; private string _password = string.Empty; private string _passwordtype = "password"; private string _togglepassword = string.Empty; - private string confirm = string.Empty; - private string email = string.Empty; - private string displayname = string.Empty; + private string _confirm = string.Empty; + private string _email = string.Empty; + private string _displayname = string.Empty; + private string _notify = "True"; private List profiles; private Dictionary settings; private string category = string.Empty; @@ -117,6 +146,7 @@ _togglepassword = SharedLocalizer["ShowPassword"]; profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId); settings = new Dictionary(); + _initialized = true; } catch (Exception ex) { @@ -139,30 +169,34 @@ { try { - if (username != string.Empty && _password != string.Empty && confirm != string.Empty && email != string.Empty && ValidateProfiles()) + if (_username != string.Empty && _password != string.Empty && _confirm != string.Empty && _email != string.Empty) { - if (_password == confirm) + if (_password == _confirm) { - var user = new User(); - user.SiteId = PageState.Site.SiteId; - user.Username = username; - user.Password = _password; - user.Email = email; - user.DisplayName = string.IsNullOrWhiteSpace(displayname) ? username : displayname; - user.PhotoFileId = null; - - user = await UserService.AddUserAsync(user); - - if (user != null) + if (ValidateProfiles()) { - await SettingService.UpdateUserSettingsAsync(settings, user.UserId); - await logger.LogInformation("User Created {User}", user); - NavigationManager.NavigateTo(NavigateUrl()); - } - else - { - await logger.LogError("Error Adding User {Username} {Email}", username, email); - AddModuleMessage(Localizer["Error.User.AddCheckPass"], MessageType.Error); + var user = new User(); + user.SiteId = PageState.Site.SiteId; + user.Username = _username; + user.Password = _password; + user.Email = _email; + user.DisplayName = string.IsNullOrWhiteSpace(_displayname) ? _username : _displayname; + user.PhotoFileId = null; + user.SuppressNotification = !bool.Parse(_notify); + + user = await UserService.AddUserAsync(user); + + if (user != null) + { + await SettingService.UpdateUserSettingsAsync(settings, user.UserId); + await logger.LogInformation("User Created {User}", user); + NavigationManager.NavigateTo(NavigateUrl()); + } + else + { + await logger.LogError("Error Adding User {Username} {Email}", _username, _email); + AddModuleMessage(Localizer["Error.User.AddCheckPass"], MessageType.Error); + } } } else @@ -177,34 +211,40 @@ } catch (Exception ex) { - await logger.LogError(ex, "Error Adding User {Username} {Email} {Error}", username, email, ex.Message); + await logger.LogError(ex, "Error Adding User {Username} {Email} {Error}", _username, _email, ex.Message); AddModuleMessage(Localizer["Error.User.Add"], MessageType.Error); } } private bool ValidateProfiles() { - bool valid = true; foreach (Profile profile in profiles) { - if (string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty)) && !string.IsNullOrEmpty(profile.DefaultValue)) + var value = GetProfileValue(profile.Name, string.Empty); + if (string.IsNullOrEmpty(value) && !string.IsNullOrEmpty(profile.DefaultValue)) { settings = SettingService.SetSetting(settings, profile.Name, profile.DefaultValue); } if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin)) { - if (valid == true && profile.IsRequired && string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty))) + if (profile.IsRequired && string.IsNullOrEmpty(value)) { - valid = false; + AddModuleMessage(string.Format(SharedLocalizer["ProfileRequired"], profile.Title), MessageType.Warning); + return false; } - if (valid == true && !string.IsNullOrEmpty(profile.Validation)) + if (!string.IsNullOrEmpty(profile.Validation)) { Regex regex = new Regex(profile.Validation); - valid = regex.Match(SettingService.GetSetting(settings, profile.Name, string.Empty)).Success; + bool valid = regex.Match(value).Success; + if (!valid) + { + AddModuleMessage(string.Format(SharedLocalizer["ProfileInvalid"], profile.Title), MessageType.Warning); + return false; + } } } } - return valid; + return true; } private void ProfileChanged(ChangeEventArgs e, string SettingName) diff --git a/Oqtane.Client/Modules/Admin/Users/Edit.razor b/Oqtane.Client/Modules/Admin/Users/Edit.razor index 19fbbdd1..beb66e60 100644 --- a/Oqtane.Client/Modules/Admin/Users/Edit.razor +++ b/Oqtane.Client/Modules/Admin/Users/Edit.razor @@ -9,18 +9,10 @@ @inject IStringLocalizer Localizer @inject IStringLocalizer SharedLocalizer -@if (PageState.User != null && photo != null) +@if (_initialized) { - @displayname -} -else -{ -
-} - - - @if (profiles != null) - { + +
@@ -31,20 +23,20 @@ else
-
+
- + -
+
- + -
+
@@ -59,12 +51,6 @@ else
-
- -
- -
-
@@ -87,11 +73,8 @@ else
- } - - - @if (profiles != null) - { + +
@foreach (Profile profile in profiles) @@ -106,8 +89,8 @@ else }
-
- @if (!string.IsNullOrEmpty(p.Options)) +
+ @if (!string.IsNullOrEmpty(p.Options)) { + } else { - + } }
@@ -139,17 +122,18 @@ else }
- } - - + + - -@SharedLocalizer["Cancel"] -
-
- + + @SharedLocalizer["Cancel"] +
+
+ +} -@code { + @code { + private bool _initialized = false; private string _passwordrequirements; private int userid; private string username = string.Empty; @@ -159,9 +143,6 @@ else private string confirm = string.Empty; private string email = string.Empty; private string displayname = string.Empty; - private FileManager filemanager; - private int photofileid = -1; - private File photo = null; private string isdeleted; private string lastlogin; private string lastipaddress; @@ -179,32 +160,23 @@ else public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit; - protected override async Task OnParametersSetAsync() + protected override async Task OnInitializedAsync() { try { - if (PageState.QueryString.ContainsKey("id")) + _passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId); + _togglepassword = SharedLocalizer["ShowPassword"]; + profiles = await ProfileService.GetProfilesAsync(PageState.Site.SiteId); + + if (PageState.QueryString.ContainsKey("id") && int.TryParse(PageState.QueryString["id"], out int UserId)) { - _passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId); - _togglepassword = SharedLocalizer["ShowPassword"]; - profiles = await ProfileService.GetProfilesAsync(PageState.Site.SiteId); - userid = Int32.Parse(PageState.QueryString["id"]); + userid = UserId; var user = await UserService.GetUserAsync(userid, PageState.Site.SiteId); if (user != null) { username = user.Username; email = user.Email; displayname = user.DisplayName; - if (user.PhotoFileId != null) - { - photofileid = user.PhotoFileId.Value; - photo = await FileService.GetFileAsync(photofileid); - } - else - { - photofileid = -1; - photo = null; - } isdeleted = user.IsDeleted.ToString(); lastlogin = string.Format("{0:MMM dd yyyy HH:mm:ss}", user.LastLoginOn); lastipaddress = user.LastIPAddress; @@ -218,6 +190,8 @@ else deletedon = user.DeletedOn; } } + + _initialized = true; } catch (Exception ex) { @@ -240,35 +214,37 @@ else { try { - if (username != string.Empty && email != string.Empty && ValidateProfiles()) + if (username != string.Empty && email != string.Empty) { if (_password == confirm) { - var user = await UserService.GetUserAsync(userid, PageState.Site.SiteId); - user.SiteId = PageState.Site.SiteId; - user.Username = username; - user.Password = _password; - user.Email = email; - user.DisplayName = string.IsNullOrWhiteSpace(displayname) ? username : displayname; - user.PhotoFileId = null; - user.PhotoFileId = filemanager.GetFileId(); - if (user.PhotoFileId == -1) + if (ValidateProfiles()) { + var user = await UserService.GetUserAsync(userid, PageState.Site.SiteId); + user.SiteId = PageState.Site.SiteId; + user.Username = username; + user.Password = _password; + user.Email = email; + user.DisplayName = string.IsNullOrWhiteSpace(displayname) ? username : displayname; user.PhotoFileId = null; - } + if (user.PhotoFileId == -1) + { + user.PhotoFileId = null; + } - user.IsDeleted = (isdeleted == null ? true : Boolean.Parse(isdeleted)); + user.IsDeleted = (isdeleted == null ? true : Boolean.Parse(isdeleted)); - user = await UserService.UpdateUserAsync(user); - if (user != null) - { - await SettingService.UpdateUserSettingsAsync(settings, user.UserId); - await logger.LogInformation("User Saved {User}", user); - NavigationManager.NavigateTo(NavigateUrl()); - } - else - { - AddModuleMessage(Localizer["Message.Password.Complexity"], MessageType.Error); + user = await UserService.UpdateUserAsync(user); + if (user != null) + { + await SettingService.UpdateUserSettingsAsync(settings, user.UserId); + await logger.LogInformation("User Saved {User}", user); + NavigationManager.NavigateTo(NavigateUrl()); + } + else + { + AddModuleMessage(Localizer["Message.Password.Complexity"], MessageType.Error); + } } } else @@ -290,27 +266,33 @@ else private bool ValidateProfiles() { - bool valid = true; foreach (Profile profile in profiles) { - if (string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty)) && !string.IsNullOrEmpty(profile.DefaultValue)) + var value = GetProfileValue(profile.Name, string.Empty); + if (string.IsNullOrEmpty(value) && !string.IsNullOrEmpty(profile.DefaultValue)) { settings = SettingService.SetSetting(settings, profile.Name, profile.DefaultValue); } if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin)) { - if (valid == true && profile.IsRequired && string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty))) + if (profile.IsRequired && string.IsNullOrEmpty(value)) { - valid = false; + AddModuleMessage(string.Format(SharedLocalizer["ProfileRequired"], profile.Title), MessageType.Warning); + return false; } - if (valid == true && !string.IsNullOrEmpty(profile.Validation)) + if (!string.IsNullOrEmpty(profile.Validation)) { Regex regex = new Regex(profile.Validation); - valid = regex.Match(SettingService.GetSetting(settings, profile.Name, string.Empty)).Success; + bool valid = regex.Match(value).Success; + if (!valid) + { + AddModuleMessage(string.Format(SharedLocalizer["ProfileInvalid"], profile.Title), MessageType.Warning); + return false; + } } } } - return valid; + return true; } private void ProfileChanged(ChangeEventArgs e, string SettingName) diff --git a/Oqtane.Client/Modules/Admin/Users/Index.razor b/Oqtane.Client/Modules/Admin/Users/Index.razor index 6957e3e6..82862c12 100644 --- a/Oqtane.Client/Modules/Admin/Users/Index.razor +++ b/Oqtane.Client/Modules/Admin/Users/Index.razor @@ -17,26 +17,17 @@ else { -
-
-
- -
-
- -
-
- -
-
-
- +   + + +
      @Localizer["Username"] - @Localizer["Name"] + @Localizer["Name"] + @Localizer["Email"] @Localizer["LastLoginOn"]
@@ -50,11 +41,12 @@ else @context.User.Username - @((MarkupString)string.Format("{1}", @context.User.Email, @context.User.DisplayName)) + @context.User.DisplayName + @((MarkupString)string.Format("{1}", @context.User.Email, @context.User.Email)) @((context.User.LastLoginOn != DateTime.MinValue) ? string.Format("{0:dd-MMM-yyyy HH:mm:ss}", context.User.LastLoginOn) : "")
-
+
@@ -106,6 +98,21 @@ else
+
+ +
+ +
+
+
+ +
+ +
+
} @if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) @@ -259,7 +266,22 @@ else
-
+
+ +
+ +
+
+
- } +
+ +
+ +
+
+ }
@@ -360,19 +391,19 @@ else

- + } @code { - private List allusers; private List users; - private string _search = ""; private string _allowregistration; private string _allowsitelogin; private string _twofactor; private string _cookiename; + private string _cookieexpiration; + private string _alwaysremember; private string _minimumlength; private string _uniquecharacters; @@ -397,6 +428,7 @@ else private string _scopes; private string _parameters; private string _pkce; + private string _authresponsetype; private string _redirecturl; private string _identifierclaimtype; private string _emailclaimtype; @@ -404,6 +436,7 @@ else private string _profileclaimtypes; private string _domainfilter; private string _createusers; + private string _verifyusers; private string _secret; private string _secrettype = "password"; @@ -420,7 +453,6 @@ else protected override async Task OnInitializedAsync() { - await LoadUserSettingsAsync(); await LoadUsersAsync(true); var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId); @@ -431,6 +463,8 @@ else { _twofactor = SettingService.GetSetting(settings, "LoginOptions:TwoFactor", "false"); _cookiename = SettingService.GetSetting(settings, "LoginOptions:CookieName", ".AspNetCore.Identity.Application"); + _cookieexpiration = SettingService.GetSetting(settings, "LoginOptions:CookieExpiration", ""); + _alwaysremember = SettingService.GetSetting(settings, "LoginOptions:AlwaysRemember", "false"); _minimumlength = SettingService.GetSetting(settings, "IdentityOptions:Password:RequiredLength", "6"); _uniquecharacters = SettingService.GetSetting(settings, "IdentityOptions:Password:RequiredUniqueChars", "1"); @@ -455,6 +489,7 @@ else _scopes = SettingService.GetSetting(settings, "ExternalLogin:Scopes", ""); _parameters = SettingService.GetSetting(settings, "ExternalLogin:Parameters", ""); _pkce = SettingService.GetSetting(settings, "ExternalLogin:PKCE", "false"); + _authresponsetype = SettingService.GetSetting(settings, "ExternalLogin:AuthResponseType", "code"); _redirecturl = PageState.Uri.Scheme + "://" + PageState.Alias.Name + "/signin-" + _providertype; _identifierclaimtype = SettingService.GetSetting(settings, "ExternalLogin:IdentifierClaimType", "sub"); _emailclaimtype = SettingService.GetSetting(settings, "ExternalLogin:EmailClaimType", "email"); @@ -462,6 +497,7 @@ else _profileclaimtypes = SettingService.GetSetting(settings, "ExternalLogin:ProfileClaimTypes", ""); _domainfilter = SettingService.GetSetting(settings, "ExternalLogin:DomainFilter", ""); _createusers = SettingService.GetSetting(settings, "ExternalLogin:CreateUsers", "true"); + _verifyusers = SettingService.GetSetting(settings, "ExternalLogin:VerifyUsers", "true"); _secret = SettingService.GetSetting(settings, "JwtOptions:Secret", ""); _togglesecret = SharedLocalizer["ShowPassword"]; @@ -475,32 +511,14 @@ else { if (load) { - allusers = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Registered); + users = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Registered); if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) { var hosts = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Host); - allusers.AddRange(hosts); - allusers = allusers.OrderBy(u => u.User.DisplayName).ToList(); + users.AddRange(hosts); + users = users.OrderBy(u => u.User.DisplayName).ToList(); } } - - users = allusers; - if (!string.IsNullOrEmpty(_search)) - { - users = users.Where(item => - ( - item.User.Username.Contains(_search, StringComparison.OrdinalIgnoreCase) || - item.User.Email.Contains(_search, StringComparison.OrdinalIgnoreCase) || - item.User.DisplayName.Contains(_search, StringComparison.OrdinalIgnoreCase) - ) - ).ToList(); - } - } - - private async Task OnSearch() - { - await UpdateUserSettingsAsync(); - await LoadUsersAsync(false); } private async Task DeleteUser(UserRole UserRole) @@ -523,21 +541,6 @@ else } } - private string settingSearch = "AU-search"; - - private async Task LoadUserSettingsAsync() - { - Dictionary settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId); - _search = SettingService.GetSetting(settings, settingSearch, ""); - } - - private async Task UpdateUserSettingsAsync() - { - Dictionary settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId); - settings = SettingService.SetSetting(settings, settingSearch, _search); - await SettingService.UpdateUserSettingsAsync(settings, PageState.User.UserId); - } - private async Task SaveSiteSettings() { try @@ -553,6 +556,8 @@ else { settings = SettingService.SetSetting(settings, "LoginOptions:TwoFactor", _twofactor, false); settings = SettingService.SetSetting(settings, "LoginOptions:CookieName", _cookiename, true); + settings = SettingService.SetSetting(settings, "LoginOptions:CookieExpiration", _cookieexpiration, true); + settings = SettingService.SetSetting(settings, "LoginOptions:AlwaysRemember", _alwaysremember, false); settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequiredLength", _minimumlength, true); settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequiredUniqueChars", _uniquecharacters, true); @@ -576,12 +581,14 @@ else settings = SettingService.SetSetting(settings, "ExternalLogin:Scopes", _scopes, true); settings = SettingService.SetSetting(settings, "ExternalLogin:Parameters", _parameters, true); settings = SettingService.SetSetting(settings, "ExternalLogin:PKCE", _pkce, true); + settings = SettingService.SetSetting(settings, "ExternalLogin:AuthResponseType", _authresponsetype, true); settings = SettingService.SetSetting(settings, "ExternalLogin:IdentifierClaimType", _identifierclaimtype, true); settings = SettingService.SetSetting(settings, "ExternalLogin:EmailClaimType", _emailclaimtype, true); settings = SettingService.SetSetting(settings, "ExternalLogin:RoleClaimType", _roleclaimtype, true); settings = SettingService.SetSetting(settings, "ExternalLogin:ProfileClaimTypes", _profileclaimtypes, true); settings = SettingService.SetSetting(settings, "ExternalLogin:DomainFilter", _domainfilter, true); settings = SettingService.SetSetting(settings, "ExternalLogin:CreateUsers", _createusers, true); + settings = SettingService.SetSetting(settings, "ExternalLogin:VerifyUsers", _verifyusers, true); if (!string.IsNullOrEmpty(_secret) && _secret.Length < 16) _secret = (_secret + "????????????????").Substring(0, 16); settings = SettingService.SetSetting(settings, "JwtOptions:Secret", _secret, true); diff --git a/Oqtane.Client/Modules/Admin/Users/Users.razor b/Oqtane.Client/Modules/Admin/Users/Users.razor new file mode 100644 index 00000000..a3ac65d3 --- /dev/null +++ b/Oqtane.Client/Modules/Admin/Users/Users.razor @@ -0,0 +1,69 @@ +@namespace Oqtane.Modules.Admin.Users +@inherits ModuleBase +@inject NavigationManager NavigationManager +@inject IUserService UserService +@inject IStringLocalizer Localizer +@inject IStringLocalizer SharedLocalizer + +
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+  +@SharedLocalizer["Cancel"]  +@Localizer["Template"] + +@code { + private FileManager _filemanager; + + public override string Title => "Import Users"; + + public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; + + private string _notify = "True"; + + private async Task ImportUsers() + { + try + { + var fileid = _filemanager.GetFileId(); + if (fileid != -1) + { + ShowProgressIndicator(); + var results = await UserService.ImportUsersAsync(PageState.Site.SiteId, fileid, bool.Parse(_notify)); + if (bool.Parse(results["Success"])) + { + AddModuleMessage(string.Format(Localizer["Message.Import.Success"], results["Users"]), MessageType.Success); + } + else + { + AddModuleMessage(Localizer["Message.Import.Failure"], MessageType.Error); + } + HideProgressIndicator(); + } + else + { + AddModuleMessage(Localizer["Message.Import.Validation"], MessageType.Warning); + } + } + catch (Exception ex) + { + await logger.LogError(ex, "Error Importing Users {Error}", ex.Message); + AddModuleMessage(Localizer["Error.Import"], MessageType.Error); + } + } +} diff --git a/Oqtane.Client/Modules/Controls/FileManager.razor b/Oqtane.Client/Modules/Controls/FileManager.razor index 80426de8..5162c1f0 100644 --- a/Oqtane.Client/Modules/Controls/FileManager.razor +++ b/Oqtane.Client/Modules/Controls/FileManager.razor @@ -40,10 +40,17 @@
} + else + { + if (FileId != -1 && _file != null && !UploadMultiple) + { + + } + } @if (ShowUpload && _haseditpermission) { -
-
+
+
@if (UploadMultiple) { @@ -53,9 +60,9 @@ }
-
+
- @if (GetFileId() != -1) + @if (FileId != -1 && !UploadMultiple) { } @@ -341,7 +348,8 @@ string restricted = ""; foreach (var upload in uploads) { - var extension = (upload.LastIndexOf(".") != -1) ? upload.Substring(upload.LastIndexOf(".") + 1) : ""; + var filename = upload.Split(':')[0]; + var extension = (filename.LastIndexOf(".") != -1) ? filename.Substring(filename.LastIndexOf(".") + 1) : ""; if (!Constants.UploadableFiles.Split(',').Contains(extension.ToLower())) { restricted += (restricted == "" ? "" : ",") + extension; @@ -368,24 +376,32 @@ while (upload < uploads.Length && success) { success = false; - // note that progressive retry will only wait a maximum of 15 seconds which may not be long enough for very large file uploads + var filename = uploads[upload].Split(':')[0]; + + var size = Int64.Parse(uploads[upload].Split(':')[1]); // bytes + var megabits = (size / 1048576.0) * 8; // binary conversion + var uploadspeed = 2; // 2 Mbps (3G ranges from 300Kbps to 3Mbps) + var uploadtime = (megabits / uploadspeed); // seconds + var maxattempts = 5; // polling (minimum timeout duration will be 5 seconds) + var sleep = (int)Math.Ceiling(uploadtime / maxattempts) * 1000; // milliseconds + int attempts = 0; - while (attempts < 5 && !success) + while (attempts < maxattempts && !success) { attempts += 1; - Thread.Sleep(1000 * attempts); // progressive retry + Thread.Sleep(sleep); if (Folder == Constants.PackagesFolder) { var files = await FileService.GetFilesAsync(folder); - if (files != null && files.Any(item => item.Name == uploads[upload])) + if (files != null && files.Any(item => item.Name == filename)) { success = true; } } else { - var file = await FileService.GetFileAsync(int.Parse(folder), uploads[upload]); + var file = await FileService.GetFileAsync(int.Parse(folder), filename); if (file != null) { success = true; @@ -433,7 +449,7 @@ else { // set FileId to first file in upload collection - var file = await FileService.GetFileAsync(int.Parse(folder), uploads[0]); + var file = await FileService.GetFileAsync(int.Parse(folder), uploads[0].Split(":")[0]); if (file != null) { FileId = file.FileId; diff --git a/Oqtane.Client/Modules/Controls/Pager.razor b/Oqtane.Client/Modules/Controls/Pager.razor index bf735801..79642eaa 100644 --- a/Oqtane.Client/Modules/Controls/Pager.razor +++ b/Oqtane.Client/Modules/Controls/Pager.razor @@ -1,10 +1,20 @@ @namespace Oqtane.Modules.Controls @inherits ModuleControlBase @inject IStringLocalizerFactory LocalizerFactory +@inject IStringLocalizer SharedLocalizer @typeparam TableItem @if (ItemList != null) { + @if (!string.IsNullOrEmpty(SearchProperties)) + { +
+ + + +
+ } + @if ((Toolbar == "Top" || Toolbar == "Both") && _pages > 0 && Items.Count() > _maxItems) {
    @@ -175,6 +185,9 @@ private int _startPage = 0; private int _endPage = 0; private int _columns = 0; + private string _search = ""; + + private IEnumerable AllItems; [Parameter] public string Format { get; set; } // Table or Grid @@ -221,6 +234,9 @@ [Parameter] public Action OnPageChange { get; set; } // a method to be executed in the calling component when the page changes + [Parameter] + public string SearchProperties { get; set; } // comma delimited list of property names to include in search + private IEnumerable ItemList { get; set; } protected override void OnInitialized() @@ -276,6 +292,15 @@ } } + if (!string.IsNullOrEmpty(SearchProperties)) + { + AllItems = Items; // only used in search + if (!string.IsNullOrEmpty(_search)) + { + Search(); + } + } + if (!string.IsNullOrEmpty(PageSize)) { _maxItems = int.Parse(PageSize); @@ -323,10 +348,10 @@ { _endPage = _pages; } - ItemList = Items.Skip((_page - 1) * _maxItems).Take(_maxItems); - StateHasChanged(); - OnPageChange?.Invoke(_page); - } + ItemList = Items.Skip((_page - 1) * _maxItems).Take(_maxItems); + StateHasChanged(); + OnPageChange?.Invoke(_page); + } public void UpdateList(int page) { @@ -369,4 +394,75 @@ UpdateList(_page); } + + public void Search() + { + if (!string.IsNullOrEmpty(_search)) + { + Items = AllItems.Where(item => + { + var values = SearchProperties.Split(',') + .Select(itemType => GetPropertyValue(item, itemType)) + .Where(value => value != null) + .Select(value => value.ToString().ToLower()); + + return values.Any(value => value.Contains(_search.ToLower())); + }).ToList(); + } + else + { + Items = AllItems; + } + _pages = (int)Math.Ceiling(Items.Count() / (decimal)_maxItems); + UpdateList(1); + } + + private object GetPropertyValue(object obj, string propertyName) + { + var index = propertyName.IndexOf("."); + if (index != -1) + { + var propertyInfo = obj.GetType().GetProperty(propertyName.Substring(0, index)); + if (propertyInfo != null) + { + return GetPropertyValue(propertyInfo.GetValue(obj), propertyName.Substring(index + 1)); + } + return null; + } + else + { + var propertyInfo = obj.GetType().GetProperty(propertyName); + if (propertyInfo != null) + { + return propertyInfo.GetValue(obj); + } + return null; + } + } + + public void Reset() + { + _search = ""; + Items = AllItems; + _pages = (int)Math.Ceiling(Items.Count() / (decimal)_maxItems); + UpdateList(1); + } + + private string FormatSearchProperties() + { + var properties = new List(); + foreach (var property in SearchProperties.Split(',', StringSplitOptions.RemoveEmptyEntries)) + { + var index = property.LastIndexOf("."); + if (index != -1) + { + properties.Add(property.Substring(index + 1)); + } + else + { + properties.Add(property); + } + } + return string.Join(",", properties); + } } diff --git a/Oqtane.Client/Oqtane.Client.csproj b/Oqtane.Client/Oqtane.Client.csproj index ecf1b61c..eea64f14 100644 --- a/Oqtane.Client/Oqtane.Client.csproj +++ b/Oqtane.Client/Oqtane.Client.csproj @@ -1,10 +1,10 @@ - net7.0 + net8.0 Exe Debug;Release - 4.0.3 + 5.0.0 Oqtane Shaun Walker .NET Foundation @@ -12,7 +12,7 @@ .NET Foundation https://www.oqtane.org https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE - https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.3 + https://github.com/oqtane/oqtane.framework/releases/tag/v5.0.0 https://github.com/oqtane/oqtane.framework Git Oqtane @@ -21,12 +21,12 @@ - - - - - - + + + + + + diff --git a/Oqtane.Client/Resources/Modules/Admin/ModuleDefinitions/Create.resx b/Oqtane.Client/Resources/Modules/Admin/ModuleDefinitions/Create.resx index 8ab77a00..cac13d97 100644 --- a/Oqtane.Client/Resources/Modules/Admin/ModuleDefinitions/Create.resx +++ b/Oqtane.Client/Resources/Modules/Admin/ModuleDefinitions/Create.resx @@ -132,6 +132,9 @@ You Must Provide A Valid Owner Name And Module Name ( ie. No Punctuation Or Spaces And The Values Cannot Be The Same ) And Choose A Template + + You Must Provide A Valid Description (ie. No Punctuation) + Enter the name of the organization who is developing this module. It should not contain spaces or punctuation. diff --git a/Oqtane.Client/Resources/Modules/Admin/Modules/Settings.resx b/Oqtane.Client/Resources/Modules/Admin/Modules/Settings.resx index cead365f..5b7849ec 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Modules/Settings.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Modules/Settings.resx @@ -159,4 +159,10 @@ Module Settings + + The pane where the module will be displayed + + + Pane: + \ No newline at end of file diff --git a/Oqtane.Client/Resources/Modules/Admin/Profiles/Edit.resx b/Oqtane.Client/Resources/Modules/Admin/Profiles/Edit.resx index 8a3a23ca..973f8719 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Profiles/Edit.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Profiles/Edit.resx @@ -187,6 +187,12 @@ Optionally provide a regular expression (RegExp) for validating the value entered - Validation: + Validation: - \ No newline at end of file + + The number of rows for text entry (one is the default) + + + Rows: + + diff --git a/Oqtane.Client/Resources/Modules/Admin/Profiles/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Profiles/Index.resx index ebd495eb..98f8530c 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Profiles/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Profiles/Index.resx @@ -138,4 +138,13 @@ Edit + + Category + + + Order + + + Title + \ No newline at end of file diff --git a/Oqtane.Client/Resources/Modules/Admin/Register/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Register/Index.resx index 9b06fc94..1895cbef 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Register/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Register/Index.resx @@ -136,7 +136,7 @@ User Account Created. Please Check Your Email For Verification Instructions. - Error Adding User. Please Ensure Password Meets Complexity Requirements And Username Is Not Already In Use. + Error Adding User. Please Ensure Password Meets Complexity Requirements And Username And Email Is Not Already In Use. Passwords Entered Do Not Match diff --git a/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx index 5a05fd80..0d9b6c65 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx @@ -396,4 +396,10 @@ ID: + + Number of days of notifications to retain + + + Retention (Days): + \ No newline at end of file diff --git a/Oqtane.Client/Resources/Modules/Admin/Sites/Add.resx b/Oqtane.Client/Resources/Modules/Admin/Sites/Add.resx index bc840f28..fed4bd6a 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Sites/Add.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Sites/Add.resx @@ -132,9 +132,6 @@ Select Theme - - Default Admin Container - The urls for the site (comman delimited). This can include domain names (ie. domain.com), subdomains (ie. sub.domain.com) or a virtual folder (ie. domain.com/folder). @@ -183,9 +180,6 @@ Select the default theme for the site - - Select the admin container for the site - Select the site template @@ -207,9 +201,6 @@ Site Name: - - Admin Container: - Site Template: diff --git a/Oqtane.Client/Resources/Modules/Admin/Upgrade/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Upgrade/Index.resx index 5f1fa146..40d8af4c 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Upgrade/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Upgrade/Index.resx @@ -144,7 +144,10 @@ Framework Is Already Up To Date - + Upload A Framework Package (Oqtane.Framework.version.nupkg) And Then Select Upgrade + + You Cannot Perform A System Update In A Development Environment + \ No newline at end of file diff --git a/Oqtane.Client/Resources/Modules/Admin/UserProfile/Add.resx b/Oqtane.Client/Resources/Modules/Admin/UserProfile/Add.resx index e8c57007..4f287b18 100644 --- a/Oqtane.Client/Resources/Modules/Admin/UserProfile/Add.resx +++ b/Oqtane.Client/Resources/Modules/Admin/UserProfile/Add.resx @@ -121,7 +121,7 @@ Message: - User Does Not Exist. Please Verify That The Username Provided Is Correct. + The User Specified Does Not Exist Error Adding Notification @@ -133,7 +133,7 @@ Enter the subject of the message - Enter the message + Enter the message content To: @@ -144,4 +144,10 @@ Send Notification + + You Must Enter All Required Information + + + Enter Username + \ No newline at end of file diff --git a/Oqtane.Client/Resources/Modules/Admin/UserProfile/Index.resx b/Oqtane.Client/Resources/Modules/Admin/UserProfile/Index.resx index 7021f4a8..8bcffb8c 100644 --- a/Oqtane.Client/Resources/Modules/Admin/UserProfile/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/UserProfile/Index.resx @@ -233,5 +233,11 @@ Delete - + + + No notifications have been received + + + No notifications have been sent + \ No newline at end of file diff --git a/Oqtane.Client/Resources/Modules/Admin/UserProfile/View.resx b/Oqtane.Client/Resources/Modules/Admin/UserProfile/View.resx index 601caf6d..07e894f3 100644 --- a/Oqtane.Client/Resources/Modules/Admin/UserProfile/View.resx +++ b/Oqtane.Client/Resources/Modules/Admin/UserProfile/View.resx @@ -126,25 +126,43 @@ Error Adding Notification - - Title: - - - Subject: - - - Date: - - - Message: - - - Reply - - - Original Message - View Notification + + The date the message was sent + + + Sent: + + + The user who sent the message + + + From: + + + The content of the message + + + Message: + + + RE: + + + The subject of the message + + + Subject: + + + System + + + The user who will be the recipient of the message + + + To: + \ No newline at end of file diff --git a/Oqtane.Client/Resources/Modules/Admin/Users/Add.resx b/Oqtane.Client/Resources/Modules/Admin/Users/Add.resx index 810f2b9d..1ad645d9 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Users/Add.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Users/Add.resx @@ -118,7 +118,7 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Error Adding User. Please Ensure Password Meets Complexity Requirements And Username Is Not Already In Use. + Error Adding User. Please Ensure Password Meets Complexity Requirements And Username And Email Is Not Already In Use. Passwords Entered Do Not Match @@ -171,4 +171,10 @@ Password + + Indicate if new users should receive an email notification + + + Notify? + \ No newline at end of file diff --git a/Oqtane.Client/Resources/Modules/Admin/Users/Edit.resx b/Oqtane.Client/Resources/Modules/Admin/Users/Edit.resx index b0263e50..f56d3798 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Users/Edit.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Users/Edit.resx @@ -168,12 +168,6 @@ Password: - - A photo of the user - - - Photo: - The unique username for a user. Note that this field can not be modified. diff --git a/Oqtane.Client/Resources/Modules/Admin/Users/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Users/Index.resx index a8ede28b..e6d4792b 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Users/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Users/Index.resx @@ -390,7 +390,7 @@ Role Claim: - + Optionally provide a comma delimited list of user profile claims provided by the identity provider, as well as mappings to your user profile definition. For example if the identity provider includes a 'given_name' claim and you have a 'FirstName' user profile definition you should specify 'given_name:FirstName'. @@ -402,4 +402,55 @@ Name + + Email + + + Import Users + + + code + + + code id_token + + + code id_token token + + + code token + + + id_token + + + id_token token + + + none + + + token + + + Authorization Response Type + + + Do you want existing users to perform an additional email verification step to link their external login? If you disable this option, existing users will be linked automatically. + + + Verify Existing Users? + + + Enabling this option will set a permanent cookie in conjunction with the Cookie Expiration Timespan, which will automatically sign in users the next time they visit the site. By default the site will use session cookies. + + + Always Remember User? + + + You can choose to use a custom authentication cookie expiration timespan for each site (e.g. '08:00:00' for 8 hours). The default is 14 days if not specified. + + + Cookie Expiration Timespan: + \ No newline at end of file diff --git a/Oqtane.Client/Resources/Modules/Admin/ModuleCreator/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Users/Users.resx similarity index 68% rename from Oqtane.Client/Resources/Modules/Admin/ModuleCreator/Index.resx rename to Oqtane.Client/Resources/Modules/Admin/Users/Users.resx index 16000d3e..02e73f23 100644 --- a/Oqtane.Client/Resources/Modules/Admin/ModuleCreator/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Users/Users.resx @@ -117,61 +117,34 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - Template: + + Upload or select a tab delimited text file containing user information. The file must be in the Template format specified (Roles can be specified as a comma delimited list). - - Select Template + + Import File: - - Create Module + + Error Importing Users - - Activate Module + + Import - - Please Note That The Module Creator Is Only Intended To Be Used In A Development Environment + + User Import Failed. Please Review Your Event Log For More Detailed Information. - - Once You Have Compiled The Module And Restarted The Application You Can Activate The Module Below + + User Import Successful. {0} Users Imported. - - The Source Code For Your Module Has Been Created At The Location Specified Below And Must Be Compiled In Order To Make It Functional. Once It Has Been Compiled You Must <a href={0}>Restart</a> Your Application To Apply These Changes. + + You Must Specify A User File For Import - - You Must Provide A Valid Owner Name And Module Name ( ie. No Punctuation Or Spaces And The Values Cannot Be The Same ) And Choose A Template + + Template - - Enter the name of the organization who is developing this module. It should not contain spaces or punctuation. + + Indicate if new users should receive an email notification - - Enter a name for this module. It should not contain spaces or punctuation. - - - Enter a short description for the module - - - Select a module template. Templates are located in the wwwroot/Modules/Templates folder on the server. - - - Select a framework reference version - - - Location where the module will be created - - - Owner Name: - - - Module Name: - - - Description: - - - Framework Reference: - - - Location: + + Notify? \ No newline at end of file diff --git a/Oqtane.Client/Resources/Modules/Controls/Pager.resx b/Oqtane.Client/Resources/Modules/Controls/Pager.resx index 710d041d..2dfdd092 100644 --- a/Oqtane.Client/Resources/Modules/Controls/Pager.resx +++ b/Oqtane.Client/Resources/Modules/Controls/Pager.resx @@ -1,4 +1,4 @@ - + - - + + Exe - 4.0.3 + 5.0.0 Oqtane Shaun Walker .NET Foundation @@ -14,7 +14,7 @@ .NET Foundation https://www.oqtane.org https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE - https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.3 + https://github.com/oqtane/oqtane.framework/releases/tag/v5.0.0 https://github.com/oqtane/oqtane.framework Git Oqtane.Maui @@ -31,7 +31,7 @@ 0E29FC31-1B83-48ED-B6E0-9F3C67B775D4 - 4.0.3 + 5.0.0 1 14.2 @@ -65,20 +65,22 @@ - - + + - - - + + + + + - ..\Oqtane.Server\bin\Debug\net7.0\Oqtane.Client.dll + ..\Oqtane.Server\bin\Debug\net8.0\Oqtane.Client.dll - ..\Oqtane.Server\bin\Debug\net7.0\Oqtane.Shared.dll + ..\Oqtane.Server\bin\Debug\net8.0\Oqtane.Shared.dll diff --git a/Oqtane.Package/Oqtane.Client.nuspec b/Oqtane.Package/Oqtane.Client.nuspec index 878d5496..c24d4c24 100644 --- a/Oqtane.Package/Oqtane.Client.nuspec +++ b/Oqtane.Package/Oqtane.Client.nuspec @@ -1,8 +1,8 @@ - + Oqtane.Client - 4.0.3 + 5.0.0 Shaun Walker .NET Foundation Oqtane Framework @@ -12,13 +12,14 @@ false MIT https://github.com/oqtane/oqtane.framework - https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.3 + https://github.com/oqtane/oqtane.framework/releases/tag/v5.0.0 icon.png oqtane - - + + + \ No newline at end of file diff --git a/Oqtane.Package/Oqtane.Framework.nuspec b/Oqtane.Package/Oqtane.Framework.nuspec index b3ccb5eb..ac7b0882 100644 --- a/Oqtane.Package/Oqtane.Framework.nuspec +++ b/Oqtane.Package/Oqtane.Framework.nuspec @@ -2,7 +2,7 @@ Oqtane.Framework - 4.0.3 + 5.0.0 Shaun Walker .NET Foundation Oqtane Framework @@ -11,8 +11,8 @@ .NET Foundation false MIT - https://github.com/oqtane/oqtane.framework/releases/download/v4.0.3/Oqtane.Framework.4.0.3.Upgrade.zip - https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.3 + https://github.com/oqtane/oqtane.framework/releases/download/v5.0.0Oqtane.Framework.5.0.0.Upgrade.zip + https://github.com/oqtane/oqtane.framework/releases/tag/v5.0.0 icon.png oqtane framework diff --git a/Oqtane.Package/Oqtane.Server.nuspec b/Oqtane.Package/Oqtane.Server.nuspec index b8e56b64..fac5a6cb 100644 --- a/Oqtane.Package/Oqtane.Server.nuspec +++ b/Oqtane.Package/Oqtane.Server.nuspec @@ -1,8 +1,8 @@ - + Oqtane.Server - 4.0.3 + 5.0.0 Shaun Walker .NET Foundation Oqtane Framework @@ -12,13 +12,13 @@ false MIT https://github.com/oqtane/oqtane.framework - https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.3 + https://github.com/oqtane/oqtane.framework/releases/tag/v5.0.0 icon.png oqtane - - + + \ No newline at end of file diff --git a/Oqtane.Package/Oqtane.Shared.nuspec b/Oqtane.Package/Oqtane.Shared.nuspec index afa99300..b5b5d5f4 100644 --- a/Oqtane.Package/Oqtane.Shared.nuspec +++ b/Oqtane.Package/Oqtane.Shared.nuspec @@ -1,8 +1,8 @@ - + Oqtane.Shared - 4.0.3 + 5.0.0 Shaun Walker .NET Foundation Oqtane Framework @@ -12,13 +12,14 @@ false MIT https://github.com/oqtane/oqtane.framework - https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.3 + https://github.com/oqtane/oqtane.framework/releases/tag/v5.0.0 icon.png oqtane - - + + + \ No newline at end of file diff --git a/Oqtane.Package/Oqtane.Updater.nuspec b/Oqtane.Package/Oqtane.Updater.nuspec index 81d106e8..dfd80f2a 100644 --- a/Oqtane.Package/Oqtane.Updater.nuspec +++ b/Oqtane.Package/Oqtane.Updater.nuspec @@ -2,7 +2,7 @@ Oqtane.Updater - 4.0.3 + 5.0.0 Shaun Walker .NET Foundation Oqtane Framework @@ -12,12 +12,12 @@ false MIT https://github.com/oqtane/oqtane.framework - https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.3 + https://github.com/oqtane/oqtane.framework/releases/tag/v5.0.0 icon.png oqtane - + \ No newline at end of file diff --git a/Oqtane.Package/install.ps1 b/Oqtane.Package/install.ps1 index 6e6a50ca..ebb62c0e 100644 --- a/Oqtane.Package/install.ps1 +++ b/Oqtane.Package/install.ps1 @@ -1 +1 @@ -Compress-Archive -Path "..\Oqtane.Server\bin\Release\net7.0\publish\*" -DestinationPath "Oqtane.Framework.4.0.3.Install.zip" -Force \ No newline at end of file +Compress-Archive -Path "..\Oqtane.Server\bin\Release\net8.0\publish\*" -DestinationPath "Oqtane.Framework.5.0.0.Install.zip" -Force \ No newline at end of file diff --git a/Oqtane.Package/release.cmd b/Oqtane.Package/release.cmd index 299aa439..d22478d5 100644 --- a/Oqtane.Package/release.cmd +++ b/Oqtane.Package/release.cmd @@ -8,14 +8,14 @@ nuget.exe pack Oqtane.Client.nuspec nuget.exe pack Oqtane.Server.nuspec nuget.exe pack Oqtane.Shared.nuspec nuget.exe pack Oqtane.Framework.nuspec -del /F/Q/S "..\Oqtane.Server\bin\Release\net7.0\publish" > NUL -rmdir /Q/S "..\Oqtane.Server\bin\Release\net7.0\publish" +del /F/Q/S "..\Oqtane.Server\bin\Release\net8.0\publish" > NUL +rmdir /Q/S "..\Oqtane.Server\bin\Release\net8.0\publish" dotnet publish ..\Oqtane.Server\Oqtane.Server.csproj /p:Configuration=Release -del /F/Q/S "..\Oqtane.Server\bin\Release\net7.0\publish\wwwroot\Content" > NUL -rmdir /Q/S "..\Oqtane.Server\bin\Release\net7.0\publish\wwwroot\Content" +del /F/Q/S "..\Oqtane.Server\bin\Release\net8.0\publish\wwwroot\Content" > NUL +rmdir /Q/S "..\Oqtane.Server\bin\Release\net8.0\publish\wwwroot\Content" setlocal ENABLEDELAYEDEXPANSION set retain=Oqtane.Modules.Admin.Login,Oqtane.Modules.HtmlText -for /D %%i in ("..\Oqtane.Server\bin\Release\net7.0\publish\wwwroot\Modules\*") do ( +for /D %%i in ("..\Oqtane.Server\bin\Release\net8.0\publish\wwwroot\Modules\*") do ( set /A found=0 for %%j in (%retain%) do ( if "%%~nxi" == "%%j" set /A found=1 @@ -23,18 +23,18 @@ if "%%~nxi" == "%%j" set /A found=1 if not !found! == 1 rmdir /Q/S "%%i" ) set retain=Oqtane.Themes.BlazorTheme,Oqtane.Themes.OqtaneTheme -for /D %%i in ("..\Oqtane.Server\bin\Release\net7.0\publish\wwwroot\Themes\*") do ( +for /D %%i in ("..\Oqtane.Server\bin\Release\net8.0\publish\wwwroot\Themes\*") do ( set /A found=0 for %%j in (%retain%) do ( if "%%~nxi" == "%%j" set /A found=1 ) if not !found! == 1 rmdir /Q/S "%%i" ) -del "..\Oqtane.Server\bin\Release\net7.0\publish\appsettings.json" -ren "..\Oqtane.Server\bin\Release\net7.0\publish\appsettings.release.json" "appsettings.json" +del "..\Oqtane.Server\bin\Release\net8.0\publish\appsettings.json" +ren "..\Oqtane.Server\bin\Release\net8.0\publish\appsettings.release.json" "appsettings.json" C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe ".\install.ps1" -del "..\Oqtane.Server\bin\Release\net7.0\publish\appsettings.json" -del "..\Oqtane.Server\bin\Release\net7.0\publish\web.config" +del "..\Oqtane.Server\bin\Release\net8.0\publish\appsettings.json" +del "..\Oqtane.Server\bin\Release\net8.0\publish\web.config" C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe ".\upgrade.ps1" dotnet clean -c Release ..\Oqtane.Updater.sln dotnet build -c Release ..\Oqtane.Updater.sln diff --git a/Oqtane.Package/upgrade.ps1 b/Oqtane.Package/upgrade.ps1 index eaaf892a..f0c92eea 100644 --- a/Oqtane.Package/upgrade.ps1 +++ b/Oqtane.Package/upgrade.ps1 @@ -1 +1 @@ -Compress-Archive -Path "..\Oqtane.Server\bin\Release\net7.0\publish\*" -DestinationPath "Oqtane.Framework.4.0.3.Upgrade.zip" -Force \ No newline at end of file +Compress-Archive -Path "..\Oqtane.Server\bin\Release\net8.0\publish\*" -DestinationPath "Oqtane.Framework.5.0.0.Upgrade.zip" -Force \ No newline at end of file diff --git a/Oqtane.Server/Controllers/FolderController.cs b/Oqtane.Server/Controllers/FolderController.cs index 616b24d2..71190c2d 100644 --- a/Oqtane.Server/Controllers/FolderController.cs +++ b/Oqtane.Server/Controllers/FolderController.cs @@ -95,6 +95,33 @@ namespace Oqtane.Controllers folderPath += "/"; } Folder folder = _folders.GetFolder(siteId, folderPath); + if (folder == null && User.IsInRole(RoleNames.Host) && path.StartsWith("Users/")) + { + // create the user folder on this site for the host user + var userId = int.Parse(path.ReplaceMultiple(new string[] { "Users", "/" }, "")); + folder = _folders.GetFolder(siteId, "Users/"); + if (folder != null) + { + folder = _folders.AddFolder(new Folder + { + SiteId = folder.SiteId, + ParentId = folder.FolderId, + Name = "My Folder", + Type = FolderTypes.Private, + Path = path, + Order = 1, + ImageSizes = "", + Capacity = Constants.UserFolderCapacity, + IsSystem = true, + PermissionList = new List + { + new Permission(PermissionNames.Browse, userId, true), + new Permission(PermissionNames.View, RoleNames.Everyone, true), + new Permission(PermissionNames.Edit, userId, true) + } + }); + } + } if (folder != null && folder.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, PermissionNames.View, folder.PermissionList)) { return folder; diff --git a/Oqtane.Server/Controllers/ModuleDefinitionController.cs b/Oqtane.Server/Controllers/ModuleDefinitionController.cs index 7afc125e..94c4c085 100644 --- a/Oqtane.Server/Controllers/ModuleDefinitionController.cs +++ b/Oqtane.Server/Controllers/ModuleDefinitionController.cs @@ -350,9 +350,9 @@ namespace Oqtane.Controllers if (moduleDefinition.Version == "local") { text = text.Replace("[FrameworkVersion]", Constants.Version); - text = text.Replace("[ClientReference]", $"..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net7.0\\Oqtane.Client.dll"); - text = text.Replace("[ServerReference]", $"..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net7.0\\Oqtane.Server.dll"); - text = text.Replace("[SharedReference]", $"..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net7.0\\Oqtane.Shared.dll"); + text = text.Replace("[ClientReference]", $"..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net8.0\\Oqtane.Client.dll"); + text = text.Replace("[ServerReference]", $"..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net8.0\\Oqtane.Server.dll"); + text = text.Replace("[SharedReference]", $"..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net8.0\\Oqtane.Shared.dll"); } else { diff --git a/Oqtane.Server/Controllers/NotificationController.cs b/Oqtane.Server/Controllers/NotificationController.cs index e2bfd14e..b5fc97d4 100644 --- a/Oqtane.Server/Controllers/NotificationController.cs +++ b/Oqtane.Server/Controllers/NotificationController.cs @@ -179,7 +179,7 @@ namespace Oqtane.Controllers [Authorize(Roles = RoleNames.Registered)] public Notification Put(int id, [FromBody] Notification notification) { - if (ModelState.IsValid && notification.SiteId == _alias.SiteId && _notifications.GetNotification(notification.NotificationId, false) != null && IsAuthorized(notification.FromUserId)) + if (ModelState.IsValid && notification.SiteId == _alias.SiteId && _notifications.GetNotification(notification.NotificationId, false) != null && (IsAuthorized(notification.FromUserId) || IsAuthorized(notification.ToUserId))) { notification = _notifications.UpdateNotification(notification); _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Notification, notification.NotificationId, SyncEventActions.Update); diff --git a/Oqtane.Server/Controllers/PageController.cs b/Oqtane.Server/Controllers/PageController.cs index 5c7c5e8a..85085954 100644 --- a/Oqtane.Server/Controllers/PageController.cs +++ b/Oqtane.Server/Controllers/PageController.cs @@ -280,16 +280,10 @@ namespace Oqtane.Controllers if (currentPage.Path != page.Path) { var urlMapping = _urlMappings.GetUrlMapping(page.SiteId, currentPage.Path); - if (urlMapping == null) + if (urlMapping != null) { - urlMapping = new UrlMapping(); - urlMapping.SiteId = page.SiteId; - urlMapping.Url = currentPage.Path; urlMapping.MappedUrl = page.Path; - urlMapping.Requests = 0; - urlMapping.CreatedOn = System.DateTime.UtcNow; - urlMapping.RequestedOn = System.DateTime.UtcNow; - _urlMappings.AddUrlMapping(urlMapping); + _urlMappings.UpdateUrlMapping(urlMapping); } } diff --git a/Oqtane.Server/Controllers/PageModuleController.cs b/Oqtane.Server/Controllers/PageModuleController.cs index 3c445772..3b1db3cf 100644 --- a/Oqtane.Server/Controllers/PageModuleController.cs +++ b/Oqtane.Server/Controllers/PageModuleController.cs @@ -9,7 +9,7 @@ using Oqtane.Infrastructure; using Oqtane.Repository; using Oqtane.Security; using System.Net; -using Microsoft.AspNetCore.Mvc.RazorPages; +using System; namespace Oqtane.Controllers { @@ -133,14 +133,22 @@ namespace Oqtane.Controllers var page = _pages.GetPage(pageid); if (page != null && page.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, page.SiteId, EntityNames.Page, pageid, PermissionNames.Edit)) { + var panes = pane; + if (pane == PaneNames.Default || pane == PaneNames.Admin) + { + // treat default and admin panes as a single pane + panes = PaneNames.Default + "," + PaneNames.Admin; + pane = PaneNames.Default; + } int order = 1; List pagemodules = _pageModules.GetPageModules(page.SiteId) - .Where(item => item.PageId == pageid && item.Pane == pane).OrderBy(item => item.Order).ToList(); + .Where(item => item.PageId == pageid && panes.Split(',').Contains(item.Pane)).OrderBy(item => item.Order).ToList(); foreach (PageModule pagemodule in pagemodules) { - if (pagemodule.Order != order) + if (pagemodule.Order != order || pagemodule.Pane != pane) { pagemodule.Order = order; + pagemodule.Pane = pane; _pageModules.UpdatePageModule(pagemodule); _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.PageModule, pagemodule.PageModuleId, SyncEventActions.Update); } diff --git a/Oqtane.Server/Controllers/ThemeController.cs b/Oqtane.Server/Controllers/ThemeController.cs index cec332fe..131c4b1d 100644 --- a/Oqtane.Server/Controllers/ThemeController.cs +++ b/Oqtane.Server/Controllers/ThemeController.cs @@ -237,8 +237,8 @@ namespace Oqtane.Controllers if (theme.Version == "local") { text = text.Replace("[FrameworkVersion]", Constants.Version); - text = text.Replace("[ClientReference]", $"..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net7.0\\Oqtane.Client.dll"); - text = text.Replace("[SharedReference]", $"..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net7.0\\Oqtane.Shared.dll"); + text = text.Replace("[ClientReference]", $"..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net8.0\\Oqtane.Client.dll"); + text = text.Replace("[SharedReference]", $"..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net8.0\\Oqtane.Shared.dll"); } else { diff --git a/Oqtane.Server/Controllers/UserController.cs b/Oqtane.Server/Controllers/UserController.cs index e57635cb..bb033977 100644 --- a/Oqtane.Server/Controllers/UserController.cs +++ b/Oqtane.Server/Controllers/UserController.cs @@ -28,9 +28,10 @@ namespace Oqtane.Controllers private readonly IUserPermissions _userPermissions; private readonly ISettingRepository _settings; private readonly IJwtManager _jwtManager; + private readonly IFileRepository _files; private readonly ILogManager _logger; - public UserController(IUserRepository users, ITenantManager tenantManager, IUserManager userManager, ISiteRepository sites, IUserPermissions userPermissions, ISettingRepository settings, IJwtManager jwtManager, ILogManager logger) + public UserController(IUserRepository users, ITenantManager tenantManager, IUserManager userManager, ISiteRepository sites, IUserPermissions userPermissions, ISettingRepository settings, IJwtManager jwtManager, IFileRepository files, ILogManager logger) { _users = users; _tenantManager = tenantManager; @@ -39,6 +40,7 @@ namespace Oqtane.Controllers _userPermissions = userPermissions; _settings = settings; _jwtManager = jwtManager; + _files = files; _logger = logger; } @@ -369,5 +371,41 @@ namespace Oqtane.Controllers return requirements; } + + // POST api//import?siteid=x&fileid=y¬ify=z + [HttpPost("import")] + [Authorize(Roles = RoleNames.Admin)] + public async Task> Import(string siteid, string fileid, string notify) + { + if (int.TryParse(siteid, out int SiteId) && SiteId == _tenantManager.GetAlias().SiteId && int.TryParse(fileid, out int FileId) && bool.TryParse(notify, out bool Notify)) + { + var file = _files.GetFile(FileId); + if (file != null) + { + if (_userPermissions.IsAuthorized(User, PermissionNames.View, file.Folder.PermissionList)) + { + return await _userManager.ImportUsers(SiteId, _files.GetFilePath(file), Notify); + } + else + { + _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized User Import Attempt {SiteId} {FileId}", siteid, fileid); + HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; + return null; + } + } + else + { + _logger.Log(LogLevel.Error, this, LogFunction.Security, "Import File Does Not Exist {SiteId} {FileId}", siteid, fileid); + HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound; + return null; + } + } + else + { + _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized User Import Attempt {SiteId} {FileId}", siteid, fileid); + HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; + return null; + } + } } } diff --git a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs index 22d04941..06ceaf69 100644 --- a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs +++ b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs @@ -121,6 +121,7 @@ namespace Microsoft.Extensions.DependencyInjection public static IServiceCollection ConfigureOqtaneCookieOptions(this IServiceCollection services) { + // note that ConfigureApplicationCookie internally uses an ApplicationScheme of "Identity.Application" services.ConfigureApplicationCookie(options => { options.Cookie.HttpOnly = false; diff --git a/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs b/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs index f89d8bde..8ef2f7ad 100644 --- a/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs +++ b/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs @@ -28,9 +28,15 @@ namespace Oqtane.Extensions public static OqtaneSiteOptionsBuilder WithSiteAuthentication(this OqtaneSiteOptionsBuilder builder) { // site cookie authentication options - builder.AddSiteOptions((options, alias, sitesettings) => + builder.AddSiteNamedOptions(Constants.AuthenticationScheme, (options, alias, sitesettings) => { options.Cookie.Name = sitesettings.GetValue("LoginOptions:CookieName", ".AspNetCore.Identity.Application"); + string cookieExpStr = sitesettings.GetValue("LoginOptions:CookieExpiration", ""); + if (!string.IsNullOrEmpty(cookieExpStr) && TimeSpan.TryParse(cookieExpStr, out TimeSpan cookieExpTS)) + { + options.Cookie.Expiration = cookieExpTS; + options.ExpireTimeSpan = cookieExpTS; + } }); // site OpenId Connect options @@ -44,7 +50,7 @@ namespace Oqtane.Extensions options.SaveTokens = false; options.GetClaimsFromUserInfoEndpoint = true; options.CallbackPath = string.IsNullOrEmpty(alias.Path) ? "/signin-" + AuthenticationProviderTypes.OpenIDConnect : "/" + alias.Path + "/signin-" + AuthenticationProviderTypes.OpenIDConnect; - options.ResponseType = OpenIdConnectResponseType.Code; // authorization code flow + options.ResponseType = sitesettings.GetValue("ExternalLogin:AuthResponseType", "code"); // authorization code flow options.ResponseMode = OpenIdConnectResponseMode.FormPost; // recommended as most secure // cookie config is required to avoid Correlation Failed errors @@ -298,6 +304,7 @@ namespace Oqtane.Extensions if (identityuser != null) { user = _users.GetUser(identityuser.UserName); + user.SiteId = alias.SiteId; } else { @@ -351,7 +358,7 @@ namespace Oqtane.Extensions _notifications.AddNotification(notification); // add user login - await _identityUserManager.AddLoginAsync(identityuser, new UserLoginInfo(providerType + ":" + alias.SiteId.ToString(), id, providerName)); + await _identityUserManager.AddLoginAsync(identityuser, new UserLoginInfo(providerType + ":" + user.SiteId.ToString(), id, providerName)); _logger.Log(user.SiteId, LogLevel.Information, "ExternalLogin", Enums.LogFunction.Create, "User Added {User}", user); } @@ -380,18 +387,38 @@ namespace Oqtane.Extensions var login = logins.FirstOrDefault(item => item.LoginProvider == (providerType + ":" + alias.SiteId.ToString())); if (login == null) { - // new external login using existing user account - verification required - var _notifications = httpContext.RequestServices.GetRequiredService(); - string token = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser); - string url = httpContext.Request.Scheme + "://" + alias.Name; - url += $"/login?name={identityuser.UserName}&token={WebUtility.UrlEncode(token)}&key={WebUtility.UrlEncode(id)}"; - string body = $"You Recently Signed In To Our Site With {providerName} Using The Email Address {email}. "; - body += "In Order To Complete The Linkage Of Your User Account Please Click The Link Displayed Below:\n\n" + url + "\n\nThank You!"; - var notification = new Notification(alias.SiteId, email, email, "External Login Linkage", body); - _notifications.AddNotification(notification); + if (bool.Parse(httpContext.GetSiteSettings().GetValue("ExternalLogin:VerifyUsers", "true"))) + { + // external login using existing user account - verification required + var _notifications = httpContext.RequestServices.GetRequiredService(); + string token = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser); + string url = httpContext.Request.Scheme + "://" + alias.Name; + url += $"/login?name={identityuser.UserName}&token={WebUtility.UrlEncode(token)}&key={WebUtility.UrlEncode(id)}"; + string body = $"You Recently Signed In To Our Site With {providerName} Using The Email Address {email}. "; + body += "In Order To Complete The Linkage Of Your User Account Please Click The Link Displayed Below:\n\n" + url + "\n\nThank You!"; + var notification = new Notification(alias.SiteId, email, email, "External Login Linkage", body); + _notifications.AddNotification(notification); - identity.Label = ExternalLoginStatus.VerificationRequired; - _logger.Log(alias.SiteId, LogLevel.Information, "ExternalLogin", Enums.LogFunction.Create, "External Login Linkage Verification For Provider {Provider} Sent To {Email}", providerName, email); + identity.Label = ExternalLoginStatus.VerificationRequired; + _logger.Log(alias.SiteId, LogLevel.Information, "ExternalLogin", Enums.LogFunction.Create, "External Login Linkage Verification For Provider {Provider} Sent To {Email}", providerName, email); + } + else + { + // external login using existing user account - link automatically + user = _users.GetUser(identityuser.UserName); + user.SiteId = alias.SiteId; + + var _notifications = httpContext.RequestServices.GetRequiredService(); + string url = httpContext.Request.Scheme + "://" + alias.Name; + string body = "You Recently Used An External Account To Sign In To Our Site.\n\n" + url + "\n\nThank You!"; + var notification = new Notification(user.SiteId, user, "User Account Notification", body); + _notifications.AddNotification(notification); + + // add user login + await _identityUserManager.AddLoginAsync(identityuser, new UserLoginInfo(providerType + ":" + user.SiteId.ToString(), id, providerName)); + + _logger.Log(user.SiteId, LogLevel.Information, "ExternalLogin", Enums.LogFunction.Create, "External Login Linkage Created For User {Username} And Provider {Provider}", user.Username, providerName); + } } else { diff --git a/Oqtane.Server/Extensions/OqtaneSiteOptionsBuilder.cs b/Oqtane.Server/Extensions/OqtaneSiteOptionsBuilder.cs index 8f281883..d34cd319 100644 --- a/Oqtane.Server/Extensions/OqtaneSiteOptionsBuilder.cs +++ b/Oqtane.Server/Extensions/OqtaneSiteOptionsBuilder.cs @@ -17,10 +17,22 @@ namespace Microsoft.Extensions.DependencyInjection } public OqtaneSiteOptionsBuilder AddSiteOptions( - Action> action) where TOptions : class, new() + Action> configureOptions) where TOptions : class, new() { Services.TryAddSingleton, SiteOptionsCache>(); - Services.AddSingleton, SiteOptions> (sp => new SiteOptions(action)); + Services.AddSingleton, SiteOptions> (_ => new SiteOptions(configureOptions)); + Services.TryAddTransient, SiteOptionsFactory>(); + Services.TryAddScoped>(sp => BuildOptionsManager(sp)); + Services.TryAddSingleton>(sp => BuildOptionsManager(sp)); + + return this; + } + + public OqtaneSiteOptionsBuilder AddSiteNamedOptions(string name, + Action> configureOptions) where TOptions : class, new() + { + Services.TryAddSingleton, SiteOptionsCache>(); + Services.AddSingleton, SiteNamedOptions>(_ => new SiteNamedOptions(name, configureOptions)); Services.TryAddTransient, SiteOptionsFactory>(); Services.TryAddScoped>(sp => BuildOptionsManager(sp)); Services.TryAddSingleton>(sp => BuildOptionsManager(sp)); diff --git a/Oqtane.Server/Infrastructure/InstallationManager.cs b/Oqtane.Server/Infrastructure/InstallationManager.cs index 835350b6..f29b3aba 100644 --- a/Oqtane.Server/Infrastructure/InstallationManager.cs +++ b/Oqtane.Server/Infrastructure/InstallationManager.cs @@ -251,10 +251,14 @@ namespace Oqtane.Infrastructure string filepath = asset.StartsWith("\\") ? Path.Combine(_environment.ContentRootPath, asset.Substring(1)) : asset; if (File.Exists(filepath)) { - File.Delete(filepath); - if (!Directory.EnumerateFiles(Path.GetDirectoryName(filepath)).Any()) + // do not remove licensing assemblies - this is a temporary fix until a more robust dependency management solution is available + if (!filepath.Contains("Oqtane.Licensing.")) { - Directory.Delete(Path.GetDirectoryName(filepath), true); + File.Delete(filepath); + if (!Directory.EnumerateFiles(Path.GetDirectoryName(filepath)).Any()) + { + Directory.Delete(Path.GetDirectoryName(filepath), true); + } } } } diff --git a/Oqtane.Server/Infrastructure/Options/ISiteNamedOptions.cs b/Oqtane.Server/Infrastructure/Options/ISiteNamedOptions.cs new file mode 100644 index 00000000..41ed97c4 --- /dev/null +++ b/Oqtane.Server/Infrastructure/Options/ISiteNamedOptions.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using Oqtane.Models; + +namespace Oqtane.Infrastructure +{ + public interface ISiteNamedOptions + where TOptions : class, new() + { + void Configure(string name, TOptions options, Alias alias, Dictionary sitesettings); + } +} diff --git a/Oqtane.Server/Infrastructure/Options/SiteNamedOptions.cs b/Oqtane.Server/Infrastructure/Options/SiteNamedOptions.cs new file mode 100644 index 00000000..1027cc54 --- /dev/null +++ b/Oqtane.Server/Infrastructure/Options/SiteNamedOptions.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using Oqtane.Models; + +namespace Oqtane.Infrastructure +{ + public class SiteNamedOptions : ISiteNamedOptions + where TOptions : class, new() + { + public string Name { get; } + + private readonly Action> configureOptions; + + public SiteNamedOptions(string name, Action> configureOptions) + { + Name = name; + this.configureOptions = configureOptions; + } + + public void Configure(string name, TOptions options, Alias alias, Dictionary sitesettings) + { + if (name == Name) + { + configureOptions(options, alias, sitesettings); + } + } + } +} diff --git a/Oqtane.Server/Infrastructure/Options/SiteOptionsFactory.cs b/Oqtane.Server/Infrastructure/Options/SiteOptionsFactory.cs index 05236eb7..f68ed438 100644 --- a/Oqtane.Server/Infrastructure/Options/SiteOptionsFactory.cs +++ b/Oqtane.Server/Infrastructure/Options/SiteOptionsFactory.cs @@ -11,13 +11,15 @@ namespace Oqtane.Infrastructure private readonly IConfigureOptions[] _configureOptions; private readonly IPostConfigureOptions[] _postConfigureOptions; private readonly ISiteOptions[] _siteOptions; + private readonly ISiteNamedOptions[] _siteNamedOptions; private readonly IHttpContextAccessor _accessor; - public SiteOptionsFactory(IEnumerable> configureOptions, IEnumerable> postConfigureOptions, IEnumerable> siteOptions, IHttpContextAccessor accessor) + public SiteOptionsFactory(IEnumerable> configureOptions, IEnumerable> postConfigureOptions, IEnumerable> siteOptions, IEnumerable> siteNamedOptions, IHttpContextAccessor accessor) { _configureOptions = configureOptions as IConfigureOptions[] ?? new List>(configureOptions).ToArray(); _postConfigureOptions = postConfigureOptions as IPostConfigureOptions[] ?? new List>(postConfigureOptions).ToArray(); _siteOptions = siteOptions as ISiteOptions[] ?? new List>(siteOptions).ToArray(); + _siteNamedOptions = siteNamedOptions as ISiteNamedOptions[] ?? new List>(siteNamedOptions).ToArray(); _accessor = accessor; } @@ -44,6 +46,11 @@ namespace Oqtane.Infrastructure { siteOption.Configure(options, _accessor.HttpContext.GetAlias(), _accessor.HttpContext.GetSiteSettings()); } + + foreach (var siteNamedOption in _siteNamedOptions) + { + siteNamedOption.Configure(name, options, _accessor.HttpContext.GetAlias(), _accessor.HttpContext.GetSiteSettings()); + } } // post configuration diff --git a/Oqtane.Server/Managers/Interfaces/IUserManager.cs b/Oqtane.Server/Managers/Interfaces/IUserManager.cs index 82cf7090..afcf8a0a 100644 --- a/Oqtane.Server/Managers/Interfaces/IUserManager.cs +++ b/Oqtane.Server/Managers/Interfaces/IUserManager.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Threading.Tasks; using Oqtane.Models; @@ -18,5 +19,6 @@ namespace Oqtane.Managers User VerifyTwoFactor(User user, string token); Task LinkExternalAccount(User user, string token, string type, string key, string name); Task ValidatePassword(string password); + Task> ImportUsers(int siteId, string filePath, bool notify); } } diff --git a/Oqtane.Server/Managers/UserManager.cs b/Oqtane.Server/Managers/UserManager.cs index 07304679..dc9c596d 100644 --- a/Oqtane.Server/Managers/UserManager.cs +++ b/Oqtane.Server/Managers/UserManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Net; @@ -16,24 +17,32 @@ namespace Oqtane.Managers public class UserManager : IUserManager { private readonly IUserRepository _users; + private readonly IRoleRepository _roles; private readonly IUserRoleRepository _userRoles; private readonly UserManager _identityUserManager; private readonly SignInManager _identitySignInManager; private readonly ITenantManager _tenantManager; private readonly INotificationRepository _notifications; private readonly IFolderRepository _folders; + private readonly IFileRepository _files; + private readonly IProfileRepository _profiles; + private readonly ISettingRepository _settings; private readonly ISyncManager _syncManager; private readonly ILogManager _logger; - public UserManager(IUserRepository users, IUserRoleRepository userRoles, UserManager identityUserManager, SignInManager identitySignInManager, ITenantManager tenantManager, INotificationRepository notifications, IFolderRepository folders, ISyncManager syncManager, ILogManager logger) + public UserManager(IUserRepository users, IRoleRepository roles, IUserRoleRepository userRoles, UserManager identityUserManager, SignInManager identitySignInManager, ITenantManager tenantManager, INotificationRepository notifications, IFolderRepository folders, IFileRepository files, IProfileRepository profiles, ISettingRepository settings, ISyncManager syncManager, ILogManager logger) { _users = users; + _roles = roles; _userRoles = userRoles; _identityUserManager = identityUserManager; _identitySignInManager = identitySignInManager; _tenantManager = tenantManager; _notifications = notifications; _folders = folders; + _files = files; + _profiles = profiles; + _settings = settings; _syncManager = syncManager; _logger = logger; } @@ -95,6 +104,13 @@ namespace Oqtane.Managers IdentityUser identityuser = await _identityUserManager.FindByNameAsync(user.Username); if (identityuser == null) { + if (string.IsNullOrEmpty(user.Password)) + { + // create random interal password based on random date and punctuation ie. Jan-23-1981+14:43:12! + Random rnd = new Random(); + var date = DateTime.UtcNow.AddDays(-rnd.Next(50 * 365)).AddHours(rnd.Next(0, 24)).AddMinutes(rnd.Next(0, 60)).AddSeconds(rnd.Next(0, 60)); + user.Password = date.ToString("MMM-dd-yyyy+HH:mm:ss", CultureInfo.InvariantCulture) + (char)rnd.Next(33, 47); + } identityuser = new IdentityUser(); identityuser.UserName = user.Username; identityuser.Email = user.Email; @@ -142,10 +158,13 @@ namespace Oqtane.Managers } else { - string url = alias.Protocol + alias.Name; - string body = "Dear " + user.DisplayName + ",\n\nA User Account Has Been Successfully Created For You With The Username " + user.Username + ". Please Visit " + url + " And Use The Login Option To Sign In. If You Do Not Know Your Password, Use The Forgot Password Option On The Login Page To Reset Your Account.\n\nThank You!"; - var notification = new Notification(user.SiteId, User, "User Account Notification", body); - _notifications.AddNotification(notification); + if (!user.SuppressNotification) + { + string url = alias.Protocol + alias.Name; + string body = "Dear " + user.DisplayName + ",\n\nA User Account Has Been Successfully Created For You With The Username " + user.Username + ". Please Visit " + url + " And Use The Login Option To Sign In. If You Do Not Know Your Password, Use The Forgot Password Option On The Login Page To Reset Your Account.\n\nThank You!"; + var notification = new Notification(user.SiteId, User, "User Account Notification", body); + _notifications.AddNotification(notification); + } } User.Password = ""; // remove sensitive information @@ -165,9 +184,8 @@ namespace Oqtane.Managers IdentityUser identityuser = await _identityUserManager.FindByNameAsync(user.Username); if (identityuser != null) { - identityuser.Email = user.Email; var valid = true; - if (user.Password != "") + if (!string.IsNullOrEmpty(user.Password)) { var validator = new PasswordValidator(); var result = await validator.ValidateAsync(_identityUserManager, null, user.Password); @@ -179,7 +197,17 @@ namespace Oqtane.Managers } if (valid) { - await _identityUserManager.UpdateAsync(identityuser); + if (!string.IsNullOrEmpty(user.Password)) + { + await _identityUserManager.UpdateAsync(identityuser); // requires password to be provided + } + + if (user.Email != identityuser.Email) + { + await _identityUserManager.SetEmailAsync(identityuser, user.Email); + var emailConfirmationToken = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser); + await _identityUserManager.ConfirmEmailAsync(identityuser, emailConfirmationToken); + } user = _users.UpdateUser(user); _syncManager.AddSyncEvent(_tenantManager.GetAlias().TenantId, EntityNames.User, user.UserId, SyncEventActions.Update); @@ -257,45 +285,52 @@ namespace Oqtane.Managers var LastIPAddress = user.LastIPAddress ?? ""; user = _users.GetUser(user.Username); - if (user.TwoFactorRequired) + if (!user.IsDeleted) { - var token = await _identityUserManager.GenerateTwoFactorTokenAsync(identityuser, "Email"); - user.TwoFactorCode = token; - user.TwoFactorExpiry = DateTime.UtcNow.AddMinutes(10); - _users.UpdateUser(user); + if (user.TwoFactorRequired) + { + var token = await _identityUserManager.GenerateTwoFactorTokenAsync(identityuser, "Email"); + user.TwoFactorCode = token; + user.TwoFactorExpiry = DateTime.UtcNow.AddMinutes(10); + _users.UpdateUser(user); - string body = "Dear " + user.DisplayName + ",\n\nYou requested a secure verification code to log in to your account. Please enter the secure verification code on the site:\n\n" + token + - "\n\nPlease note that the code is only valid for 10 minutes so if you are unable to take action within that time period, you should initiate a new login on the site." + - "\n\nThank You!"; - var notification = new Notification(user.SiteId, user, "User Verification Code", body); - _notifications.AddNotification(notification); + string body = "Dear " + user.DisplayName + ",\n\nYou requested a secure verification code to log in to your account. Please enter the secure verification code on the site:\n\n" + token + + "\n\nPlease note that the code is only valid for 10 minutes so if you are unable to take action within that time period, you should initiate a new login on the site." + + "\n\nThank You!"; + var notification = new Notification(user.SiteId, user, "User Verification Code", body); + _notifications.AddNotification(notification); - _logger.Log(LogLevel.Information, this, LogFunction.Security, "User Verification Notification Sent For {Username}", user.Username); - user.TwoFactorRequired = true; + _logger.Log(LogLevel.Information, this, LogFunction.Security, "User Verification Notification Sent For {Username}", user.Username); + user.TwoFactorRequired = true; + } + else + { + user = _users.GetUser(identityuser.UserName); + if (user != null) + { + if (identityuser.EmailConfirmed) + { + user.IsAuthenticated = true; + user.LastLoginOn = DateTime.UtcNow; + user.LastIPAddress = LastIPAddress; + _users.UpdateUser(user); + _logger.Log(LogLevel.Information, this, LogFunction.Security, "User Login Successful {Username}", user.Username); + + if (setCookie) + { + await _identitySignInManager.SignInAsync(identityuser, isPersistent); + } + } + else + { + _logger.Log(LogLevel.Information, this, LogFunction.Security, "User Not Verified {Username}", user.Username); + } + } + } } else { - user = _users.GetUser(identityuser.UserName); - if (user != null) - { - if (identityuser.EmailConfirmed) - { - user.IsAuthenticated = true; - user.LastLoginOn = DateTime.UtcNow; - user.LastIPAddress = LastIPAddress; - _users.UpdateUser(user); - _logger.Log(LogLevel.Information, this, LogFunction.Security, "User Login Successful {Username}", user.Username); - - if (setCookie) - { - await _identitySignInManager.SignInAsync(identityuser, isPersistent); - } - } - else - { - _logger.Log(LogLevel.Information, this, LogFunction.Security, "User Not Verified {Username}", user.Username); - } - } + _logger.Log(LogLevel.Information, this, LogFunction.Security, "User Login Failed - Account Deleted {Username}", user.Username); } } else @@ -436,5 +471,189 @@ namespace Oqtane.Managers var result = await validator.ValidateAsync(_identityUserManager, null, password); return result.Succeeded; } + + public async Task> ImportUsers(int siteId, string filePath, bool notify) + { + var success = true; + int rows = 0; + int users = 0; + + if (System.IO.File.Exists(filePath)) + { + var roles = _roles.GetRoles(siteId).ToList(); + var profiles = _profiles.GetProfiles(siteId).ToList(); + + try + { + string row = ""; + using (var reader = new StreamReader(filePath)) + { + // header row + if (reader.Peek() > -1) + { + row = reader.ReadLine(); + } + + if (!string.IsNullOrEmpty(row.Trim())) + { + var header = row.Replace("\"", "").Split('\t'); + if (header[0].Trim() == "Email") + { + for (int index = 4; index < header.Length - 1; index++) + { + if (!string.IsNullOrEmpty(header[index].Trim()) && !profiles.Any(item => item.Name == header[index].Trim())) + { + _logger.Log(LogLevel.Error, this, LogFunction.Create, "User Import Contains Profile Name {Profile} Which Does Not Exist", header[index]); + success = false; + } + } + } + else + { + _logger.Log(LogLevel.Error, this, LogFunction.Create, "User Import File Is Not In Correct Format. Please Use Template Provided."); + success = false; + } + + if (success) + { + // detail rows + while (reader.Peek() > -1) + { + row = reader.ReadLine(); + rows++; + + if (!string.IsNullOrEmpty(row.Trim())) + { + var values = row.Replace("\"", "").Split('\t'); + + // user + var email = (values.Length > 0) ? values[0].Trim() : ""; + var username = (values.Length > 1) ? values[1].Trim() : ""; + var displayname = (values.Length > 2) ? values[2].Trim() : ""; + + var user = _users.GetUser(username, email); + if (user == null) + { + user = new User(); + user.SiteId = siteId; + user.Email = values[0]; + user.Username = (!string.IsNullOrEmpty(username)) ? username : user.Email; + user.DisplayName = (!string.IsNullOrEmpty(displayname)) ? displayname : user.Username; + user.EmailConfirmed = true; + user.SuppressNotification = !notify; + user = await AddUser(user); + if (user == null) + { + _logger.Log(LogLevel.Error, this, LogFunction.Create, "User Import Error Importing User {Email} {Username} {DisplayName}", email, username, displayname); + success = false; + } + } + else + { + if (!string.IsNullOrEmpty(displayname)) + { + user.DisplayName = displayname; + user.Password = ""; + user = await UpdateUser(user); + } + } + + var rolenames = (values.Length > 3) ? values[3].Trim() : ""; + if (user != null && !string.IsNullOrEmpty(rolenames)) + { + // roles (comma delimited) + foreach (var rolename in rolenames.Split(',')) + { + var role = roles.FirstOrDefault(item => item.Name == rolename.Trim()); + if (role == null) + { + role = new Role(); + role.SiteId = siteId; + role.Name = rolename.Trim(); + role.Description = rolename.Trim(); + role = _roles.AddRole(role); + roles.Add(role); + } + if (role != null) + { + var userrole = _userRoles.GetUserRole(user.UserId, role.RoleId, false); + if (userrole == null) + { + userrole = new UserRole(); + userrole.UserId = user.UserId; + userrole.RoleId = role.RoleId; + _userRoles.AddUserRole(userrole); + } + } + } + } + + if (user != null && values.Length > 4) + { + // profiles + var settings = _settings.GetSettings(EntityNames.User, user.UserId); + for (int index = 4; index < values.Length - 1; index++) + { + if (header.Length > index && !string.IsNullOrEmpty(values[index].Trim())) + { + var profile = profiles.FirstOrDefault(item => item.Name == header[index].Trim()); + if (profile != null) + { + var setting = settings.FirstOrDefault(item => item.SettingName == profile.Name); + if (setting == null) + { + setting = new Setting(); + setting.EntityName = EntityNames.User; + setting.EntityId = user.UserId; + setting.SettingName = profile.Name; + setting.SettingValue = values[index].Trim(); + _settings.AddSetting(setting); + } + else + { + if (setting.SettingValue != values[index].Trim()) + { + setting.SettingValue = values[index].Trim(); + _settings.UpdateSetting(setting); + } + } + } + } + } + } + + users++; + } + } + } + } + else + { + success = false; + _logger.Log(LogLevel.Error, this, LogFunction.Create, "User Import File Contains No Header Row"); + } + } + + _logger.Log(LogLevel.Information, this, LogFunction.Create, "User Import: {Rows} Rows Processed, {Users} Users Imported", rows, users); + } + catch (Exception ex) + { + success = false; + _logger.Log(LogLevel.Error, this, LogFunction.Create, ex, "Error Importing User Import File {SiteId} {FilePath} {Notify}", siteId, filePath, notify); + } + } + else + { + success = false; + _logger.Log(LogLevel.Error, this, LogFunction.Create, "User Import File Does Not Exist {FilePath}", filePath); + } + + // return results + var result = new Dictionary(); + result.Add("Success", success.ToString()); + result.Add("Users", users.ToString()); + + return result; + } } } diff --git a/Oqtane.Server/Migrations/Tenant/04000601_AddProfileRows.cs b/Oqtane.Server/Migrations/Tenant/04000601_AddProfileRows.cs new file mode 100644 index 00000000..976706ce --- /dev/null +++ b/Oqtane.Server/Migrations/Tenant/04000601_AddProfileRows.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Oqtane.Databases.Interfaces; +using Oqtane.Migrations.EntityBuilders; +using Oqtane.Repository; + +namespace Oqtane.Migrations.Tenant +{ + [DbContext(typeof(TenantDBContext))] + [Migration("Tenant.04.00.06.01")] + public class AddProfileRows : MultiDatabaseMigration + { + public AddProfileRows(IDatabase database) : base(database) + { + } + + protected override void Up(MigrationBuilder migrationBuilder) + { + var profileEntityBuilder = new ProfileEntityBuilder(migrationBuilder, ActiveDatabase); + profileEntityBuilder.AddIntegerColumn("Rows", true); + profileEntityBuilder.UpdateColumn("Rows", "1"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + // not implemented + } + } +} diff --git a/Oqtane.Server/Oqtane.Server.csproj b/Oqtane.Server/Oqtane.Server.csproj index cfd7a1e7..b1ddd42d 100644 --- a/Oqtane.Server/Oqtane.Server.csproj +++ b/Oqtane.Server/Oqtane.Server.csproj @@ -1,9 +1,9 @@ - net7.0 + net8.0 Debug;Release - 4.0.3 + 5.0.0 Oqtane Shaun Walker .NET Foundation @@ -11,7 +11,7 @@ .NET Foundation https://www.oqtane.org https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE - https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.3 + https://github.com/oqtane/oqtane.framework/releases/tag/v5.0.0 https://github.com/oqtane/oqtane.framework Git Oqtane @@ -32,20 +32,21 @@ - - - - - - + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - + - + + diff --git a/Oqtane.Server/Pages/Files.cshtml.cs b/Oqtane.Server/Pages/Files.cshtml.cs index ab65c32c..f47ce1d2 100644 --- a/Oqtane.Server/Pages/Files.cshtml.cs +++ b/Oqtane.Server/Pages/Files.cshtml.cs @@ -3,6 +3,7 @@ using System.IO; using System.Net; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; @@ -96,7 +97,7 @@ namespace Oqtane.Pages } else { - HttpContext.Response.Headers.Add(HeaderNames.ETag, etag); + HttpContext.Response.Headers.Append(HeaderNames.ETag, etag); return PhysicalFile(filepath, file.GetMimeType()); } } diff --git a/Oqtane.Server/Pages/Login.cshtml.cs b/Oqtane.Server/Pages/Login.cshtml.cs index 7fa4bb87..87fceedb 100644 --- a/Oqtane.Server/Pages/Login.cshtml.cs +++ b/Oqtane.Server/Pages/Login.cshtml.cs @@ -4,6 +4,9 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; +using Oqtane.Extensions; +using Oqtane.Managers; +using Oqtane.Shared; namespace Oqtane.Pages { @@ -12,14 +15,16 @@ namespace Oqtane.Pages { private readonly UserManager _identityUserManager; private readonly SignInManager _identitySignInManager; + private readonly IUserManager _userManager; - public LoginModel(UserManager identityUserManager, SignInManager identitySignInManager) + public LoginModel(UserManager identityUserManager, SignInManager identitySignInManager, IUserManager userManager) { _identityUserManager = identityUserManager; _identitySignInManager = identitySignInManager; + _userManager = userManager; } - public async Task OnPostAsync(string username, string password, bool remember, string returnurl) + public async Task OnPostAsync(string username, string password, bool remember, string returnurl) { if (!User.Identity.IsAuthenticated && !string.IsNullOrEmpty(username) && !string.IsNullOrEmpty(password)) { @@ -30,12 +35,18 @@ namespace Oqtane.Pages var result = await _identitySignInManager.CheckPasswordSignInAsync(identityuser, password, true); if (result.Succeeded) { - validuser = true; + var alias = HttpContext.GetAlias(); + var user = _userManager.GetUser(identityuser.UserName, alias.SiteId); + if (user != null && !user.IsDeleted) + { + validuser = true; + } } } if (validuser) { + // note that .NET Identity uses a hardcoded ApplicationScheme of "Identity.Application" in SignInAsync await _identitySignInManager.SignInAsync(identityuser, remember); } } diff --git a/Oqtane.Server/Pages/_Host.cshtml.cs b/Oqtane.Server/Pages/_Host.cshtml.cs index ddee53c8..50fb2575 100644 --- a/Oqtane.Server/Pages/_Host.cshtml.cs +++ b/Oqtane.Server/Pages/_Host.cshtml.cs @@ -128,11 +128,6 @@ namespace Oqtane.Pages RenderMode = site.RenderMode; } - if (site.VisitorTracking) - { - TrackVisitor(site.SiteId); - } - var page = _pages.GetPage(route.PagePath, site.SiteId); if (page == null && route.PagePath == "" && site.HomePageId != null) { @@ -156,6 +151,11 @@ namespace Oqtane.Pages } } + if (site.VisitorTracking) + { + TrackVisitor(site.SiteId); + } + // get jwt token for downstream APIs if (User.Identity.IsAuthenticated) { @@ -437,17 +437,17 @@ namespace Oqtane.Pages ""; } - private string ParseScripts(string headcontent) + private string ParseScripts(string content) { // iterate scripts var scripts = ""; - if (!string.IsNullOrEmpty(headcontent)) + if (!string.IsNullOrEmpty(content)) { - var index = headcontent.IndexOf("= 0) { - scripts += headcontent.Substring(index, headcontent.IndexOf("", index) + 9 - index); - index = headcontent.IndexOf("", index) + 9 - index); + index = content.IndexOf(" GetFiles(int folderId, bool tracking) { - var alias = _tenants.GetAlias(); - IEnumerable permissions = _permissions.GetPermissions(alias.SiteId, EntityNames.Folder, folderId).ToList(); + var folder = _folderRepository.GetFolder(folderId, false); + IEnumerable permissions = _permissions.GetPermissions(folder.SiteId, EntityNames.Folder, folderId).ToList(); + IEnumerable files; if (tracking) { @@ -46,6 +47,7 @@ namespace Oqtane.Repository foreach (File file in files) { file.Folder.PermissionList = permissions.ToList(); + var alias = _tenants.GetAlias(); file.Url = GetFileUrl(file, alias); } return files; diff --git a/Oqtane.Server/Repository/Interfaces/IUserRoleRepository.cs b/Oqtane.Server/Repository/Interfaces/IUserRoleRepository.cs index 0c4a2290..7bed5f51 100644 --- a/Oqtane.Server/Repository/Interfaces/IUserRoleRepository.cs +++ b/Oqtane.Server/Repository/Interfaces/IUserRoleRepository.cs @@ -11,6 +11,8 @@ namespace Oqtane.Repository UserRole UpdateUserRole(UserRole userRole); UserRole GetUserRole(int userRoleId); UserRole GetUserRole(int userRoleId, bool tracking); + UserRole GetUserRole(int userId, int roleId); + UserRole GetUserRole(int userId, int roleId, bool tracking); void DeleteUserRole(int userRoleId); void DeleteUserRoles(int userId); } diff --git a/Oqtane.Server/Repository/ModuleDefinitionRepository.cs b/Oqtane.Server/Repository/ModuleDefinitionRepository.cs index 1607b811..935525ee 100644 --- a/Oqtane.Server/Repository/ModuleDefinitionRepository.cs +++ b/Oqtane.Server/Repository/ModuleDefinitionRepository.cs @@ -4,7 +4,6 @@ using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; -using System.Xml; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Memory; using Oqtane.Infrastructure; @@ -148,10 +147,11 @@ namespace Oqtane.Repository if (moduledefinition == null) { - // new module definition (version is explicitly not set because it is updated as part of module migrations at startup) + // new module definition moduledefinition = new ModuleDefinition { ModuleDefinitionName = ModuleDefinition.ModuleDefinitionName }; _db.ModuleDefinition.Add(moduledefinition); _db.SaveChanges(); + // version is explicitly not set because it is updated as part of module migrations at startup ModuleDefinition.Version = ""; } else @@ -160,6 +160,8 @@ namespace Oqtane.Repository ModuleDefinition.Name = (!string.IsNullOrEmpty(moduledefinition.Name)) ? moduledefinition.Name : ModuleDefinition.Name; ModuleDefinition.Description = (!string.IsNullOrEmpty(moduledefinition.Description)) ? moduledefinition.Description : ModuleDefinition.Description; ModuleDefinition.Categories = (!string.IsNullOrEmpty(moduledefinition.Categories)) ? moduledefinition.Categories : ModuleDefinition.Categories; + // get current version + ModuleDefinition.Version = moduledefinition.Version; // remove module definition from list as it is already synced moduledefinitions.Remove(moduledefinition); diff --git a/Oqtane.Server/Repository/SettingRepository.cs b/Oqtane.Server/Repository/SettingRepository.cs index 3b354dd2..2fdcbae1 100644 --- a/Oqtane.Server/Repository/SettingRepository.cs +++ b/Oqtane.Server/Repository/SettingRepository.cs @@ -148,7 +148,7 @@ namespace Oqtane.Repository private void ManageCache(string EntityName) { - if (EntityName == EntityNames.Site) + if (EntityName == EntityNames.Site && _tenantManager.GetAlias() != null) { _cache.Remove(Constants.HttpContextSiteSettingsKey + _tenantManager.GetAlias().SiteKey); } diff --git a/Oqtane.Server/Repository/SiteRepository.cs b/Oqtane.Server/Repository/SiteRepository.cs index b583d90b..7ae2d43f 100644 --- a/Oqtane.Server/Repository/SiteRepository.cs +++ b/Oqtane.Server/Repository/SiteRepository.cs @@ -224,21 +224,21 @@ namespace Oqtane.Repository _roleRepository.AddRole(new Role {SiteId = site.SiteId, Name = RoleNames.Admin, Description = RoleNames.Admin, IsAutoAssigned = false, IsSystem = true}); _profileRepository.AddProfile(new Profile - {SiteId = site.SiteId, Name = "FirstName", Title = "First Name", Description = "Your First Or Given Name", Category = "Name", ViewOrder = 1, MaxLength = 50, DefaultValue = "", IsRequired = false, IsPrivate = false, Options = ""}); + { SiteId = site.SiteId, Name = "FirstName", Title = "First Name", Description = "Your First Or Given Name", Category = "Name", ViewOrder = 1, MaxLength = 50, DefaultValue = "", IsRequired = false, IsPrivate = false, Options = "", Rows = 1 }); _profileRepository.AddProfile(new Profile - {SiteId = site.SiteId, Name = "LastName", Title = "Last Name", Description = "Your Last Or Family Name", Category = "Name", ViewOrder = 2, MaxLength = 50, DefaultValue = "", IsRequired = false, IsPrivate = false, Options = "" }); + { SiteId = site.SiteId, Name = "LastName", Title = "Last Name", Description = "Your Last Or Family Name", Category = "Name", ViewOrder = 2, MaxLength = 50, DefaultValue = "", IsRequired = false, IsPrivate = false, Options = "", Rows = 1 }); _profileRepository.AddProfile(new Profile - {SiteId = site.SiteId, Name = "Street", Title = "Street", Description = "Street Or Building Address", Category = "Address", ViewOrder = 3, MaxLength = 50, DefaultValue = "", IsRequired = false, IsPrivate = false, Options = "" }); - _profileRepository.AddProfile( - new Profile {SiteId = site.SiteId, Name = "City", Title = "City", Description = "City", Category = "Address", ViewOrder = 4, MaxLength = 50, DefaultValue = "", IsRequired = false, IsPrivate = false, Options = "" }); + { SiteId = site.SiteId, Name = "Street", Title = "Street", Description = "Street Or Building Address", Category = "Address", ViewOrder = 3, MaxLength = 50, DefaultValue = "", IsRequired = false, IsPrivate = false, Options = "", Rows = 1 }); + _profileRepository.AddProfile(new Profile + { SiteId = site.SiteId, Name = "City", Title = "City", Description = "City", Category = "Address", ViewOrder = 4, MaxLength = 50, DefaultValue = "", IsRequired = false, IsPrivate = false, Options = "", Rows = 1 }); _profileRepository.AddProfile(new Profile - {SiteId = site.SiteId, Name = "Region", Title = "Region", Description = "State Or Province", Category = "Address", ViewOrder = 5, MaxLength = 50, DefaultValue = "", IsRequired = false, IsPrivate = false, Options = "" }); + { SiteId = site.SiteId, Name = "Region", Title = "Region", Description = "State Or Province", Category = "Address", ViewOrder = 5, MaxLength = 50, DefaultValue = "", IsRequired = false, IsPrivate = false, Options = "", Rows = 1 }); _profileRepository.AddProfile(new Profile - {SiteId = site.SiteId, Name = "Country", Title = "Country", Description = "Country", Category = "Address", ViewOrder = 6, MaxLength = 50, DefaultValue = "", IsRequired = false, IsPrivate = false, Options = "" }); + { SiteId = site.SiteId, Name = "Country", Title = "Country", Description = "Country", Category = "Address", ViewOrder = 6, MaxLength = 50, DefaultValue = "", IsRequired = false, IsPrivate = false, Options = "", Rows = 1 }); _profileRepository.AddProfile(new Profile - {SiteId = site.SiteId, Name = "PostalCode", Title = "Postal Code", Description = "Postal Code Or Zip Code", Category = "Address", ViewOrder = 7, MaxLength = 50, DefaultValue = "", IsRequired = false, IsPrivate = false, Options = "" }); + { SiteId = site.SiteId, Name = "PostalCode", Title = "Postal Code", Description = "Postal Code Or Zip Code", Category = "Address", ViewOrder = 7, MaxLength = 50, DefaultValue = "", IsRequired = false, IsPrivate = false, Options = "", Rows = 1 }); _profileRepository.AddProfile(new Profile - {SiteId = site.SiteId, Name = "Phone", Title = "Phone Number", Description = "Phone Number", Category = "Contact", ViewOrder = 8, MaxLength = 50, DefaultValue = "", IsRequired = false, IsPrivate = false, Options = "" }); + { SiteId = site.SiteId, Name = "Phone", Title = "Phone Number", Description = "Phone Number", Category = "Contact", ViewOrder = 8, MaxLength = 50, DefaultValue = "", IsRequired = false, IsPrivate = false, Options = "", Rows = 1 }); Folder folder = _folderRepository.AddFolder(new Folder { diff --git a/Oqtane.Server/Repository/UserRoleRepository.cs b/Oqtane.Server/Repository/UserRoleRepository.cs index 93acbe18..730009e1 100644 --- a/Oqtane.Server/Repository/UserRoleRepository.cs +++ b/Oqtane.Server/Repository/UserRoleRepository.cs @@ -78,6 +78,29 @@ namespace Oqtane.Repository } } + public UserRole GetUserRole(int userId, int roleId) + { + return GetUserRole(userId, roleId, true); + } + + public UserRole GetUserRole(int userId, int roleId, bool tracking) + { + if (tracking) + { + return _db.UserRole + .Include(item => item.Role) // eager load roles + .Include(item => item.User) // eager load users + .FirstOrDefault(item => item.UserId == userId && item.RoleId == roleId); + } + else + { + return _db.UserRole.AsNoTracking() + .Include(item => item.Role) // eager load roles + .Include(item => item.User) // eager load users + .FirstOrDefault(item => item.UserId == userId && item.RoleId == roleId); + } + } + public void DeleteUserRole(int userRoleId) { UserRole userRole = _db.UserRole.Find(userRoleId); diff --git a/Oqtane.Server/Startup.cs b/Oqtane.Server/Startup.cs index 9e008722..8d8026d6 100644 --- a/Oqtane.Server/Startup.cs +++ b/Oqtane.Server/Startup.cs @@ -114,14 +114,12 @@ namespace Oqtane services.ConfigureOqtaneIdentityOptions(Configuration); services.AddAuthentication(options => - { - options.DefaultAuthenticateScheme = Constants.AuthenticationScheme; - options.DefaultChallengeScheme = Constants.AuthenticationScheme; - options.DefaultSignOutScheme = Constants.AuthenticationScheme; - }) - .AddCookie(Constants.AuthenticationScheme) - .AddOpenIdConnect(AuthenticationProviderTypes.OpenIDConnect, options => { }) - .AddOAuth(AuthenticationProviderTypes.OAuth2, options => { }); + { + options.DefaultScheme = Constants.AuthenticationScheme; + }) + .AddCookie(Constants.AuthenticationScheme) + .AddOpenIdConnect(AuthenticationProviderTypes.OpenIDConnect, options => { }) + .AddOAuth(AuthenticationProviderTypes.OAuth2, options => { }); services.ConfigureOqtaneCookieOptions(); services.ConfigureOqtaneAuthenticationOptions(Configuration); diff --git a/Oqtane.Server/appsettings.json b/Oqtane.Server/appsettings.json index a518f1a2..ccf00c5c 100644 --- a/Oqtane.Server/appsettings.json +++ b/Oqtane.Server/appsettings.json @@ -1,7 +1,7 @@ { "Runtime": "Server", "RenderMode": "ServerPrerendered", - "Database": { + "Database": { "DefaultDBType": "" }, "ConnectionStrings": { @@ -44,5 +44,15 @@ "ControlType": "Oqtane.Installer.Controls.PostgreSQLConfig, Oqtane.Client", "DBType": "Oqtane.Database.PostgreSQL.PostgreSQLDatabase, Oqtane.Database.PostgreSQL" } - ] + ], + "Logging": { + "FileLogger": { + "LogLevel": { + "Default": "Error" + } + }, + "LogLevel": { + "Default": "Information" + } + } } diff --git a/Oqtane.Server/wwwroot/Modules/Templates/External/Client/[Owner].Module.[Module].Client.csproj b/Oqtane.Server/wwwroot/Modules/Templates/External/Client/[Owner].Module.[Module].Client.csproj index 68e4160c..32e6c1a1 100644 --- a/Oqtane.Server/wwwroot/Modules/Templates/External/Client/[Owner].Module.[Module].Client.csproj +++ b/Oqtane.Server/wwwroot/Modules/Templates/External/Client/[Owner].Module.[Module].Client.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 1.0.0 [Owner] [Owner] @@ -9,14 +9,15 @@ [Owner].Module.[Module] [Owner] [Owner].Module.[Module].Client.Oqtane + true - - - - - + + + + + diff --git a/Oqtane.Server/wwwroot/Modules/Templates/External/Package/[Owner].Module.[Module].Package.csproj b/Oqtane.Server/wwwroot/Modules/Templates/External/Package/[Owner].Module.[Module].Package.csproj index 22da0751..c7fe75a7 100644 --- a/Oqtane.Server/wwwroot/Modules/Templates/External/Package/[Owner].Module.[Module].Package.csproj +++ b/Oqtane.Server/wwwroot/Modules/Templates/External/Package/[Owner].Module.[Module].Package.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 false diff --git a/Oqtane.Server/wwwroot/Modules/Templates/External/Package/[Owner].Module.[Module].nuspec b/Oqtane.Server/wwwroot/Modules/Templates/External/Package/[Owner].Module.[Module].nuspec index f148f2d3..a09ebb95 100644 --- a/Oqtane.Server/wwwroot/Modules/Templates/External/Package/[Owner].Module.[Module].nuspec +++ b/Oqtane.Server/wwwroot/Modules/Templates/External/Package/[Owner].Module.[Module].nuspec @@ -20,13 +20,13 @@ - - - - - - + + + + + + - \ No newline at end of file + diff --git a/Oqtane.Server/wwwroot/Modules/Templates/External/Package/debug.cmd b/Oqtane.Server/wwwroot/Modules/Templates/External/Package/debug.cmd index 6f8b554a..e59e74cd 100644 --- a/Oqtane.Server/wwwroot/Modules/Templates/External/Package/debug.cmd +++ b/Oqtane.Server/wwwroot/Modules/Templates/External/Package/debug.cmd @@ -1,7 +1,7 @@ -XCOPY "..\Client\bin\Debug\net7.0\[Owner].Module.[Module].Client.Oqtane.dll" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\net7.0\" /Y -XCOPY "..\Client\bin\Debug\net7.0\[Owner].Module.[Module].Client.Oqtane.pdb" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\net7.0\" /Y -XCOPY "..\Server\bin\Debug\net7.0\[Owner].Module.[Module].Server.Oqtane.dll" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\net7.0\" /Y -XCOPY "..\Server\bin\Debug\net7.0\[Owner].Module.[Module].Server.Oqtane.pdb" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\net7.0\" /Y -XCOPY "..\Shared\bin\Debug\net7.0\[Owner].Module.[Module].Shared.Oqtane.dll" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\net7.0\" /Y -XCOPY "..\Shared\bin\Debug\net7.0\[Owner].Module.[Module].Shared.Oqtane.pdb" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\net7.0\" /Y +XCOPY "..\Client\bin\Debug\net8.0\[Owner].Module.[Module].Client.Oqtane.dll" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\net8.0\" /Y +XCOPY "..\Client\bin\Debug\net8.0\[Owner].Module.[Module].Client.Oqtane.pdb" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\net8.0\" /Y +XCOPY "..\Server\bin\Debug\net8.0\[Owner].Module.[Module].Server.Oqtane.dll" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\net8.0\" /Y +XCOPY "..\Server\bin\Debug\net8.0\[Owner].Module.[Module].Server.Oqtane.pdb" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\net8.0\" /Y +XCOPY "..\Shared\bin\Debug\net8.0\[Owner].Module.[Module].Shared.Oqtane.dll" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\net8.0\" /Y +XCOPY "..\Shared\bin\Debug\net8.0\[Owner].Module.[Module].Shared.Oqtane.pdb" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\net8.0\" /Y XCOPY "..\Server\wwwroot\*" "..\..\[RootFolder]\Oqtane.Server\wwwroot\" /Y /S /I diff --git a/Oqtane.Server/wwwroot/Modules/Templates/External/Package/debug.sh b/Oqtane.Server/wwwroot/Modules/Templates/External/Package/debug.sh index fbf7c393..792ce75c 100644 --- a/Oqtane.Server/wwwroot/Modules/Templates/External/Package/debug.sh +++ b/Oqtane.Server/wwwroot/Modules/Templates/External/Package/debug.sh @@ -1,7 +1,7 @@ -cp -f "../Client/bin/Debug/net7.0/[Owner].Module.[Module].Client.Oqtane.dll" "../../oqtane.framework/Oqtane.Server/bin/Debug/net7.0/" -cp -f "../Client/bin/Debug/net7.0/[Owner].Module.[Module].Client.Oqtane.pdb" "../../oqtane.framework/Oqtane.Server/bin/Debug/net7.0/" -cp -f "../Server/bin/Debug/net7.0/[Owner].Module.[Module].Server.Oqtane.dll" "../../oqtane.framework/Oqtane.Server/bin/Debug/net7.0/" -cp -f "../Server/bin/Debug/net7.0/[Owner].Module.[Module].Server.Oqtane.pdb" "../../oqtane.framework/Oqtane.Server/bin/Debug/net7.0/" -cp -f "../Shared/bin/Debug/net7.0/[Owner].Module.[Module].Shared.Oqtane.dll" "../../oqtane.framework/Oqtane.Server/bin/Debug/net7.0/" -cp -f "../Shared/bin/Debug/net7.0/[Owner].Module.[Module].Shared.Oqtane.pdb" "../../oqtane.framework/Oqtane.Server/bin/Debug/net7.0/" -cp -rf "../Server/wwwroot/"* "../../oqtane.framework/Oqtane.Server/wwwroot/" \ No newline at end of file +cp -f "../Client/bin/Debug/net8.0/[Owner].Module.[Module].Client.Oqtane.dll" "../../oqtane.framework/Oqtane.Server/bin/Debug/net8.0/" +cp -f "../Client/bin/Debug/net8.0/[Owner].Module.[Module].Client.Oqtane.pdb" "../../oqtane.framework/Oqtane.Server/bin/Debug/net8.0/" +cp -f "../Server/bin/Debug/net8.0/[Owner].Module.[Module].Server.Oqtane.dll" "../../oqtane.framework/Oqtane.Server/bin/Debug/net8.0/" +cp -f "../Server/bin/Debug/net8.0/[Owner].Module.[Module].Server.Oqtane.pdb" "../../oqtane.framework/Oqtane.Server/bin/Debug/net8.0/" +cp -f "../Shared/bin/Debug/net8.0/[Owner].Module.[Module].Shared.Oqtane.dll" "../../oqtane.framework/Oqtane.Server/bin/Debug/net8.0/" +cp -f "../Shared/bin/Debug/net8.0/[Owner].Module.[Module].Shared.Oqtane.pdb" "../../oqtane.framework/Oqtane.Server/bin/Debug/net8.0/" +cp -rf "../Server/wwwroot/"* "../../oqtane.framework/Oqtane.Server/wwwroot/" diff --git a/Oqtane.Server/wwwroot/Modules/Templates/External/Server/[Owner].Module.[Module].Server.csproj b/Oqtane.Server/wwwroot/Modules/Templates/External/Server/[Owner].Module.[Module].Server.csproj index d77b6676..7062f5ac 100644 --- a/Oqtane.Server/wwwroot/Modules/Templates/External/Server/[Owner].Module.[Module].Server.csproj +++ b/Oqtane.Server/wwwroot/Modules/Templates/External/Server/[Owner].Module.[Module].Server.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 true 1.0.0 [Owner].Module.[Module] @@ -19,10 +19,10 @@ - - - - + + + + diff --git a/Oqtane.Server/wwwroot/Modules/Templates/External/Shared/[Owner].Module.[Module].Shared.csproj b/Oqtane.Server/wwwroot/Modules/Templates/External/Shared/[Owner].Module.[Module].Shared.csproj index e59810e0..a4cc724b 100644 --- a/Oqtane.Server/wwwroot/Modules/Templates/External/Shared/[Owner].Module.[Module].Shared.csproj +++ b/Oqtane.Server/wwwroot/Modules/Templates/External/Shared/[Owner].Module.[Module].Shared.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 1.0.0 [Owner].Module.[Module] [Owner] diff --git a/Oqtane.Server/wwwroot/Modules/Templates/External/template.json b/Oqtane.Server/wwwroot/Modules/Templates/External/template.json index 493c825e..1b2aab40 100644 --- a/Oqtane.Server/wwwroot/Modules/Templates/External/template.json +++ b/Oqtane.Server/wwwroot/Modules/Templates/External/template.json @@ -1,6 +1,6 @@ { "Title": "Default Module Template", "Type": "External", - "Version": "4.0.3", + "Version": "5.0.0", "Namespace": "[Owner].Module.[Module]" } diff --git a/Oqtane.Server/wwwroot/Packages/Oqtane.Database.MySQL.nupkg.bak b/Oqtane.Server/wwwroot/Packages/Oqtane.Database.MySQL.nupkg.bak index baf59fdd..63028fb6 100644 Binary files a/Oqtane.Server/wwwroot/Packages/Oqtane.Database.MySQL.nupkg.bak and b/Oqtane.Server/wwwroot/Packages/Oqtane.Database.MySQL.nupkg.bak differ diff --git a/Oqtane.Server/wwwroot/Packages/Oqtane.Database.PostgreSQL.nupkg.bak b/Oqtane.Server/wwwroot/Packages/Oqtane.Database.PostgreSQL.nupkg.bak index f4a6ccdd..2b6babc0 100644 Binary files a/Oqtane.Server/wwwroot/Packages/Oqtane.Database.PostgreSQL.nupkg.bak and b/Oqtane.Server/wwwroot/Packages/Oqtane.Database.PostgreSQL.nupkg.bak differ diff --git a/Oqtane.Server/wwwroot/Packages/Oqtane.Database.SqlServer.nupkg.bak b/Oqtane.Server/wwwroot/Packages/Oqtane.Database.SqlServer.nupkg.bak index 77208775..dad4f9b6 100644 Binary files a/Oqtane.Server/wwwroot/Packages/Oqtane.Database.SqlServer.nupkg.bak and b/Oqtane.Server/wwwroot/Packages/Oqtane.Database.SqlServer.nupkg.bak differ diff --git a/Oqtane.Server/wwwroot/Packages/Oqtane.Database.Sqlite.nupkg.bak b/Oqtane.Server/wwwroot/Packages/Oqtane.Database.Sqlite.nupkg.bak index ad62505b..77495a18 100644 Binary files a/Oqtane.Server/wwwroot/Packages/Oqtane.Database.Sqlite.nupkg.bak and b/Oqtane.Server/wwwroot/Packages/Oqtane.Database.Sqlite.nupkg.bak differ diff --git a/Oqtane.Server/wwwroot/Themes/Templates/External/Client/[Owner].Theme.[Theme].Client.csproj b/Oqtane.Server/wwwroot/Themes/Templates/External/Client/[Owner].Theme.[Theme].Client.csproj index 0799ae41..727b74dd 100644 --- a/Oqtane.Server/wwwroot/Themes/Templates/External/Client/[Owner].Theme.[Theme].Client.csproj +++ b/Oqtane.Server/wwwroot/Themes/Templates/External/Client/[Owner].Theme.[Theme].Client.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 1.0.0 [Owner] [Owner] @@ -12,11 +12,11 @@ - - - - - + + + + + diff --git a/Oqtane.Server/wwwroot/Themes/Templates/External/Client/wwwroot/Themes/[Owner].Theme.[Theme]/Theme.css b/Oqtane.Server/wwwroot/Themes/Templates/External/Client/wwwroot/Themes/[Owner].Theme.[Theme]/Theme.css index 0d325604..129a0b9c 100644 --- a/Oqtane.Server/wwwroot/Themes/Templates/External/Client/wwwroot/Themes/[Owner].Theme.[Theme]/Theme.css +++ b/Oqtane.Server/wwwroot/Themes/Templates/External/Client/wwwroot/Themes/[Owner].Theme.[Theme]/Theme.css @@ -79,6 +79,10 @@ div.app-moduleactions a.dropdown-toggle, div.app-moduleactions div.dropdown-menu color: #000000; } +.dropdown-menu span { + mix-blend-mode: difference; +} + @media (max-width: 767px) { .app-menu { diff --git a/Oqtane.Server/wwwroot/Themes/Templates/External/Package/[Owner].Theme.[Theme].Package.csproj b/Oqtane.Server/wwwroot/Themes/Templates/External/Package/[Owner].Theme.[Theme].Package.csproj index 72abdcf5..908a23b4 100644 --- a/Oqtane.Server/wwwroot/Themes/Templates/External/Package/[Owner].Theme.[Theme].Package.csproj +++ b/Oqtane.Server/wwwroot/Themes/Templates/External/Package/[Owner].Theme.[Theme].Package.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 false diff --git a/Oqtane.Server/wwwroot/Themes/Templates/External/Package/[Owner].Theme.[Theme].nuspec b/Oqtane.Server/wwwroot/Themes/Templates/External/Package/[Owner].Theme.[Theme].nuspec index ea341aee..584fc6a6 100644 --- a/Oqtane.Server/wwwroot/Themes/Templates/External/Package/[Owner].Theme.[Theme].nuspec +++ b/Oqtane.Server/wwwroot/Themes/Templates/External/Package/[Owner].Theme.[Theme].nuspec @@ -20,9 +20,9 @@ - - + + - \ No newline at end of file + diff --git a/Oqtane.Server/wwwroot/Themes/Templates/External/Package/debug.cmd b/Oqtane.Server/wwwroot/Themes/Templates/External/Package/debug.cmd index 1cda25a6..2ece53b9 100644 --- a/Oqtane.Server/wwwroot/Themes/Templates/External/Package/debug.cmd +++ b/Oqtane.Server/wwwroot/Themes/Templates/External/Package/debug.cmd @@ -1,3 +1,3 @@ -XCOPY "..\Client\bin\Debug\net7.0\[Owner].Theme.[Theme].Client.Oqtane.dll" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\net7.0\" /Y -XCOPY "..\Client\bin\Debug\net7.0\[Owner].Theme.[Theme].Client.Oqtane.pdb" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\net7.0\" /Y +XCOPY "..\Client\bin\Debug\net8.0\[Owner].Theme.[Theme].Client.Oqtane.dll" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\net8.0\" /Y +XCOPY "..\Client\bin\Debug\net8.0\[Owner].Theme.[Theme].Client.Oqtane.pdb" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\net8.0\" /Y XCOPY "..\Client\wwwroot\*" "..\..\[RootFolder]\Oqtane.Server\wwwroot\" /Y /S /I diff --git a/Oqtane.Server/wwwroot/Themes/Templates/External/Package/debug.sh b/Oqtane.Server/wwwroot/Themes/Templates/External/Package/debug.sh index 29b5ef17..52ec3384 100644 --- a/Oqtane.Server/wwwroot/Themes/Templates/External/Package/debug.sh +++ b/Oqtane.Server/wwwroot/Themes/Templates/External/Package/debug.sh @@ -1,3 +1,3 @@ -cp -f "../Client/bin/Debug/net7.0/[Owner].Theme.[Theme].Client.Oqtane.dll" "../../oqtane.framework/Oqtane.Server/bin/Debug/net7.0/" -cp -f "../Client/bin/Debug/net7.0/[Owner].Theme.[Theme].Client.Oqtane.pdb" "../../oqtane.framework/Oqtane.Server/bin/Debug/net7.0/" -cp -rf "../Server/wwwroot/"* "../../oqtane.framework/Oqtane.Server/wwwroot/" \ No newline at end of file +cp -f "../Client/bin/Debug/net8.0/[Owner].Theme.[Theme].Client.Oqtane.dll" "../../oqtane.framework/Oqtane.Server/bin/Debug/net8.0/" +cp -f "../Client/bin/Debug/net8.0/[Owner].Theme.[Theme].Client.Oqtane.pdb" "../../oqtane.framework/Oqtane.Server/bin/Debug/net8.0/" +cp -rf "../Server/wwwroot/"* "../../oqtane.framework/Oqtane.Server/wwwroot/" diff --git a/Oqtane.Server/wwwroot/Themes/Templates/External/template.json b/Oqtane.Server/wwwroot/Themes/Templates/External/template.json index 7c3b80bb..17900439 100644 --- a/Oqtane.Server/wwwroot/Themes/Templates/External/template.json +++ b/Oqtane.Server/wwwroot/Themes/Templates/External/template.json @@ -1,6 +1,6 @@ { "Title": "Default Theme Template", "Type": "External", - "Version": "4.0.3", + "Version": "5.0.0", "Namespace": "[Owner].Theme.[Theme]" } diff --git a/Oqtane.Server/wwwroot/css/app.css b/Oqtane.Server/wwwroot/css/app.css index 35deeb07..c1d0b44b 100644 --- a/Oqtane.Server/wwwroot/css/app.css +++ b/Oqtane.Server/wwwroot/css/app.css @@ -214,7 +214,7 @@ app { top: 0.5rem; } -/* Oqtane Conrol Styles */ +/* Oqtane Control Styles */ /* Pager */ .app-pager-pointer { @@ -227,4 +227,4 @@ app { .app-fas { margin-left: 5px; -} \ No newline at end of file +} diff --git a/Oqtane.Server/wwwroot/js/interop.js b/Oqtane.Server/wwwroot/js/interop.js index 792eb464..7daf41c1 100644 --- a/Oqtane.Server/wwwroot/js/interop.js +++ b/Oqtane.Server/wwwroot/js/interop.js @@ -279,7 +279,7 @@ Oqtane.Interop = { var fileinput = document.getElementById(id); if (fileinput !== null) { for (var i = 0; i < fileinput.files.length; i++) { - files.push(fileinput.files[i].name); + files.push(fileinput.files[i].name + ":" + fileinput.files[i].size); } } return files; diff --git a/Oqtane.Server/wwwroot/users.txt b/Oqtane.Server/wwwroot/users.txt new file mode 100644 index 00000000..b1d55c74 --- /dev/null +++ b/Oqtane.Server/wwwroot/users.txt @@ -0,0 +1 @@ +Email Username DisplayName Roles FirstName LastName Street City Region Country PostalCode Phone diff --git a/Oqtane.Shared/Models/Profile.cs b/Oqtane.Shared/Models/Profile.cs index e5586a13..336cd262 100644 --- a/Oqtane.Shared/Models/Profile.cs +++ b/Oqtane.Shared/Models/Profile.cs @@ -73,5 +73,10 @@ namespace Oqtane.Models /// Optional RegExp validation expression /// public string Validation { get; set; } + + /// + /// Optional number of rows (textarea) + /// + public int Rows { get; set; } } } diff --git a/Oqtane.Shared/Models/Route.cs b/Oqtane.Shared/Models/Route.cs index 43385d8f..7d5b8833 100644 --- a/Oqtane.Shared/Models/Route.cs +++ b/Oqtane.Shared/Models/Route.cs @@ -122,7 +122,7 @@ namespace Oqtane.Models public string UrlParameters { get; set; } /// - /// A route may contain querystring parameters located after the ? delimiter + /// All querystring parameters (prefixed with a ? delimiter) /// public string Query { get; set; } diff --git a/Oqtane.Shared/Models/User.cs b/Oqtane.Shared/Models/User.cs index 1b9a518d..51fd325c 100644 --- a/Oqtane.Shared/Models/User.cs +++ b/Oqtane.Shared/Models/User.cs @@ -107,6 +107,12 @@ namespace Oqtane.Models [NotMapped] public bool EmailConfirmed { get; set; } + /// + /// Indicates if new user should be notified by email (set during user creation) + /// + [NotMapped] + public bool SuppressNotification { get; set; } + /// /// Public User Settings /// diff --git a/Oqtane.Shared/Oqtane.Shared.csproj b/Oqtane.Shared/Oqtane.Shared.csproj index 7dd6677a..ad830b82 100644 --- a/Oqtane.Shared/Oqtane.Shared.csproj +++ b/Oqtane.Shared/Oqtane.Shared.csproj @@ -1,9 +1,9 @@ - net7.0 + net8.0 Debug;Release - 4.0.3 + 5.0.0 Oqtane Shaun Walker .NET Foundation @@ -11,7 +11,7 @@ .NET Foundation https://www.oqtane.org https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE - https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.3 + https://github.com/oqtane/oqtane.framework/releases/tag/v5.0.0 https://github.com/oqtane/oqtane.framework Git Oqtane @@ -19,11 +19,11 @@ - - - + + + - + diff --git a/Oqtane.Shared/Shared/Constants.cs b/Oqtane.Shared/Shared/Constants.cs index a4c68174..45bb7c91 100644 --- a/Oqtane.Shared/Shared/Constants.cs +++ b/Oqtane.Shared/Shared/Constants.cs @@ -7,8 +7,8 @@ namespace Oqtane.Shared { public class Constants { - public static readonly string Version = "4.0.3"; - public const string ReleaseVersions = "1.0.0,1.0.1,1.0.2,1.0.3,1.0.4,2.0.0,2.0.1,2.0.2,2.1.0,2.2.0,2.3.0,2.3.1,3.0.0,3.0.1,3.0.2,3.0.3,3.1.0,3.1.1,3.1.2,3.1.3,3.1.4,3.2.0,3.2.1,3.3.0,3.3.1,3.4.0,3.4.1,3.4.2,3.4.3,4.0.0,4.0.1,4.0.2,4.0.3"; + public static readonly string Version = "5.0.0"; + public const string ReleaseVersions = "1.0.0,1.0.1,1.0.2,1.0.3,1.0.4,2.0.0,2.0.1,2.0.2,2.1.0,2.2.0,2.3.0,2.3.1,3.0.0,3.0.1,3.0.2,3.0.3,3.1.0,3.1.1,3.1.2,3.1.3,3.1.4,3.2.0,3.2.1,3.3.0,3.3.1,3.4.0,3.4.1,3.4.2,3.4.3,4.0.0,4.0.1,4.0.2,4.0.3,4.0.4,4.0.5,4.0.6,5.0.0"; public const string PackageId = "Oqtane.Framework"; public const string ClientId = "Oqtane.Client"; public const string UpdaterPackageId = "Oqtane.Updater"; diff --git a/Oqtane.Shared/Shared/SiteState.cs b/Oqtane.Shared/Shared/SiteState.cs index 50481f65..5cb9d19a 100644 --- a/Oqtane.Shared/Shared/SiteState.cs +++ b/Oqtane.Shared/Shared/SiteState.cs @@ -9,7 +9,7 @@ namespace Oqtane.Shared public string AntiForgeryToken { get; set; } // passed from server for use in service calls on client public string AuthorizationToken { get; set; } // passed from server for use in service calls on client public string RemoteIPAddress { get; set; } // passed from server as cannot be reliably retrieved on client - public bool IsPrerendering{ get; set; } + public bool IsPrerendering { get; set; } private dynamic _properties; public dynamic Properties => _properties ?? (_properties = new PropertyDictionary()); diff --git a/Oqtane.Shared/Shared/Utilities.cs b/Oqtane.Shared/Shared/Utilities.cs index b5a7493e..82b32796 100644 --- a/Oqtane.Shared/Shared/Utilities.cs +++ b/Oqtane.Shared/Shared/Utilities.cs @@ -1,8 +1,6 @@ using Oqtane.Models; using System; -using System.Collections; using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; using System.Globalization; using System.IO; using System.Linq; @@ -389,13 +387,20 @@ namespace Oqtane.Shared } public static string UrlCombine(params string[] segments) -{ - segments = segments.Where(item => !string.IsNullOrEmpty(item) && item != "/" && item != "\\").ToArray(); - for (int i = 1; i < segments.Length; i++) + { + var url = new List(); + for (int i = 0; i < segments.Length; i++) { segments[i] = segments[i].Replace("\\", "/"); + if (!string.IsNullOrEmpty(segments[i]) && segments[i] != "/") + { + foreach (var segment in segments[i].Split('/', StringSplitOptions.RemoveEmptyEntries)) + { + url.Add(segment); + } + } } - return string.Join("/", segments); + return string.Join("/", url); } public static bool IsPathValid(this Folder folder) diff --git a/Oqtane.Updater/Oqtane.Updater.csproj b/Oqtane.Updater/Oqtane.Updater.csproj index 69bf2b6a..6218d42f 100644 --- a/Oqtane.Updater/Oqtane.Updater.csproj +++ b/Oqtane.Updater/Oqtane.Updater.csproj @@ -1,9 +1,9 @@ - net7.0 + net8.0 Exe - 4.0.3 + 5.0.0 Oqtane Shaun Walker .NET Foundation @@ -11,7 +11,7 @@ .NET Foundation https://www.oqtane.org https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE - https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.3 + https://github.com/oqtane/oqtane.framework/releases/tag/v5.0.0 https://github.com/oqtane/oqtane.framework Git Oqtane diff --git a/README.md b/README.md index e5dba005..7d6922eb 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Latest Release -[4.0.3](https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.3) was released on Aug 29, 2023 and is primary focused on stabilization. This release includes 50 pull requests by 6 different contributors, pushing the total number of project commits all-time over 4000. The Oqtane framework continues to evolve at a rapid pace to meet the needs of .NET developers. +[5.0.0](https://github.com/oqtane/oqtane.framework/releases/tag/v5.0.0) was released on Nov 16, 2023 and is a major release targeted at .NET 8. This release includes 45 pull requests by 4 different contributors, pushing the total number of project commits all-time to over 4300. The Oqtane framework continues to evolve at a rapid pace to meet the needs of .NET developers. [![Deploy to Azure](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Foqtane%2Foqtane.framework%2Fmaster%2Fazuredeploy.json) @@ -16,11 +16,11 @@ Please note that this project is owned by the .NET Foundation and is governed by # Getting Started -**Using Version 4:** +**Using Version 5:** -- Install **[.NET 7 SDK](https://dotnet.microsoft.com/download/dotnet/7.0)**. +- Install **[.NET 8 SDK](https://dotnet.microsoft.com/download/dotnet/8.0)**. -- Install the latest edition (v17.0 or higher) of [Visual Studio 2022](https://visualstudio.microsoft.com/vs/preview/#download-preview) with the **ASP.NET and web development** workload enabled. Oqtane works with ALL editions of Visual Studio from Community to Enterprise. If you wish to use LocalDB for development ( not a requirement as Oqtane supports SQLite, mySQL, and PostgreSQL ) you must also install the **Data storage and processing**. +- Install the latest edition (v17.8 or higher) of [Visual Studio 2022](https://visualstudio.microsoft.com/downloads) with the **ASP.NET and web development** workload enabled. Oqtane works with ALL editions of Visual Studio from Community to Enterprise. If you wish to use LocalDB for development ( not a requirement as Oqtane supports SQLite, mySQL, and PostgreSQL ) you must also install the **Data storage and processing**. - clone the Oqtane dev branch source code to your local system. Open the **Oqtane.sln** solution file and Build the solution. Make sure you specify Oqtane.Server as the Startup Project and then Run the application. @@ -50,8 +50,21 @@ Backlog (TBD) - [ ] Folder Providers - [ ] Generative AI Integration -5.0.0 (Q4 2023) -- [ ] Migration to .NET 8 +5.1.0 (Q1 2024) +- [ ] Full Stack Blazor (Static Server-Side Rendering) + +[5.0.0](https://github.com/oqtane/oqtane.framework/releases/tag/v5.0.0) (Nov 16, 2023) +- [x] Migration to .NET 8 + +[4.0.6](https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.6) ( Oct 16, 2023 ) +- [x] Stabilization improvements + +[4.0.5](https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.5) ( Sep 26, 2023 ) +- [x] Stabilization improvements + +[4.0.4](https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.4) ( Sep 25, 2023 ) +- [x] Stabilization improvements +- [x] User Import [4.0.3](https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.3) ( Aug 29, 2023 ) - [x] Stabilization improvements @@ -213,6 +226,8 @@ Oqtane was created by [Shaun Walker](https://www.linkedin.com/in/shaunbrucewalke # Release Announcements +[Oqtane 5.0](https://www.oqtane.org/blog/!/75/announcing-oqtane-5-0-for-net-8) + [Oqtane 4.0](https://www.oqtane.org/blog/!/63/announcing-oqtane-4-0-for-net-7) [Oqtane 3.4](https://www.oqtane.org/blog/!/56/oqtane-3-4-0-released)