diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..eef872ac --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,24 @@ +# Contributing to Oqtane + +## How to Contribute + +We track all of our issues on Github. If you want to contribute, everything starts with an issue. If you don't have an issue yet, you can add one. Then a core contributor will tag it as either an enhancement [ENH] or a bug [BUG]. Tagged issues are open for contribution. + +## Use GitHub-flow process +- Make a comment on the issue that you intend to work on it and read all the comments to gain a full understanding. +- Fork the repository +- Create a new branch and update your comment on the issue with a llink to the branch +- Make your changes and commit them +- Push to the branch +- Create a pull request + +## Reporting Bugs + +- Check if the issue has already been reported. +- Open a new issue if it hasn’t been reported. + +## Requesting Features + +- Use the feature request template in the Issues tab. + +Thank you for contributing! diff --git a/Oqtane.Client/Modules/Admin/Modules/Settings.razor b/Oqtane.Client/Modules/Admin/Modules/Settings.razor index d2ab823e..a949271f 100644 --- a/Oqtane.Client/Modules/Admin/Modules/Settings.razor +++ b/Oqtane.Client/Modules/Admin/Modules/Settings.razor @@ -12,7 +12,7 @@ @if (_initialized) {
- + @if (_containers != null) { @@ -162,6 +162,7 @@ private DateTime? _effectivedate = null; private DateTime? _expirydate = null; private List _pages; + private string _activetab = ""; protected override async Task OnInitializedAsync() { @@ -241,6 +242,7 @@ private async Task SaveModule() { + validated = true; var interop = new Interop(JSRuntime); if (await interop.FormValid(form)) @@ -261,21 +263,21 @@ pagemodule.ExpiryDate = Utilities.LocalDateAndTimeAsUtc(_expirydate); pagemodule.ContainerType = (_containerType != "-") ? _containerType : string.Empty; if (!string.IsNullOrEmpty(pagemodule.ContainerType) && pagemodule.ContainerType == PageState.Page.DefaultContainerType) - { - pagemodule.ContainerType = string.Empty; - } - if (!string.IsNullOrEmpty(pagemodule.ContainerType) && pagemodule.ContainerType == PageState.Site.DefaultContainerType) - { - pagemodule.ContainerType = string.Empty; - } - await PageModuleService.UpdatePageModuleAsync(pagemodule); - await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane); + { + pagemodule.ContainerType = string.Empty; + } + if (!string.IsNullOrEmpty(pagemodule.ContainerType) && pagemodule.ContainerType == PageState.Site.DefaultContainerType) + { + pagemodule.ContainerType = string.Empty; + } + await PageModuleService.UpdatePageModuleAsync(pagemodule); + await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane); - var module = ModuleState; - module.AllPages = bool.Parse(_allPages); - module.PageModuleId = ModuleState.PageModuleId; - module.PermissionList = _permissionGrid.GetPermissionList(); - await ModuleService.UpdateModuleAsync(module); + var module = ModuleState; + module.AllPages = bool.Parse(_allPages); + module.PageModuleId = ModuleState.PageModuleId; + module.PermissionList = _permissionGrid.GetPermissionList(); + await ModuleService.UpdateModuleAsync(module); if (_moduleSettingsType != null) { @@ -300,11 +302,13 @@ } else { + _activetab = "Settings"; AddModuleMessage(Localizer["Message.Required.Title"], MessageType.Warning); } } else { + _activetab = "Settings"; AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning); } } diff --git a/Oqtane.Client/Modules/Admin/Pages/Add.razor b/Oqtane.Client/Modules/Admin/Pages/Add.razor index ac7f064c..6deef0eb 100644 --- a/Oqtane.Client/Modules/Admin/Pages/Add.razor +++ b/Oqtane.Client/Modules/Admin/Pages/Add.razor @@ -316,10 +316,11 @@ { await logger.LogError(ex, "Error Loading Child Pages For Parent {PageId} {Error}", _parentid, ex.Message); AddModuleMessage(Localizer["Error.ChildPage.Load"], MessageType.Error); + await ScrollToPageTop(); } } - private void ThemeChanged(ChangeEventArgs e) + private async Task ThemeChanged(ChangeEventArgs e) { _themetype = (string)e.Value; _containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype); @@ -330,6 +331,7 @@ if (ThemeService.GetTheme(PageState.Site.Themes, _themetype)?.ThemeName != ThemeService.GetTheme(PageState.Site.Themes, PageState.Site.DefaultThemeType)?.ThemeName) { AddModuleMessage(Localizer["ThemeChanged.Message"], MessageType.Warning); + await ScrollToPageTop(); } } @@ -345,6 +347,7 @@ if (!Utilities.ValidateEffectiveExpiryDates(_effectivedate, _expirydate)) { AddModuleMessage(SharedLocalizer["Message.EffectiveExpiryDateError"], MessageType.Warning); + await ScrollToPageTop(); return; } if (!string.IsNullOrEmpty(_themetype) && !string.IsNullOrEmpty(_containertype)) @@ -395,12 +398,14 @@ if (_pages.Any(item => item.Path == page.Path)) { AddModuleMessage(string.Format(Localizer["Message.Page.Exists"], _path), MessageType.Warning); + await ScrollToPageTop(); return; } if (page.ParentId == null && Constants.ReservedRoutes.Contains(page.Name.ToLower())) { AddModuleMessage(string.Format(Localizer["Message.Page.Reserved"], page.Name), MessageType.Warning); + await ScrollToPageTop(); return; } @@ -468,6 +473,7 @@ else { AddModuleMessage(Localizer["Message.Required.PageInfo"], MessageType.Warning); + await ScrollToPageTop(); } } @@ -475,11 +481,13 @@ { await logger.LogError(ex, "Error Saving Page {Page} {Error}", page, ex.Message); AddModuleMessage(Localizer["Error.Page.Save"], MessageType.Error); + await ScrollToPageTop(); } } else { AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning); + await ScrollToPageTop(); } } diff --git a/Oqtane.Client/Modules/Admin/Pages/Edit.razor b/Oqtane.Client/Modules/Admin/Pages/Edit.razor index 0d408c03..78dec87a 100644 --- a/Oqtane.Client/Modules/Admin/Pages/Edit.razor +++ b/Oqtane.Client/Modules/Admin/Pages/Edit.razor @@ -466,7 +466,7 @@ _parentid = (string)e.Value; _children = new List(); foreach (Page p in _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); @@ -479,10 +479,11 @@ { await logger.LogError(ex, "Error Loading Child Pages For Parent {PageId} {Error}", _parentid, ex.Message); AddModuleMessage(Localizer["Error.ChildPage.Load"], MessageType.Error); + await ScrollToPageTop(); } } - private void ThemeChanged(ChangeEventArgs e) + private async Task ThemeChanged(ChangeEventArgs e) { _themetype = (string)e.Value; _containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype); @@ -494,6 +495,7 @@ if (ThemeService.GetTheme(PageState.Site.Themes, _themetype)?.ThemeName != ThemeService.GetTheme(PageState.Site.Themes, PageState.Site.DefaultThemeType)?.ThemeName) { AddModuleMessage(Localizer["ThemeChanged.Message"], MessageType.Warning); + await ScrollToPageTop(); } } @@ -531,6 +533,7 @@ if (!Utilities.ValidateEffectiveExpiryDates(_effectivedate, _expirydate)) { AddModuleMessage(SharedLocalizer["Message.EffectiveExpiryDateError"], MessageType.Warning); + await ScrollToPageTop(); return; } if (!string.IsNullOrEmpty(_themetype) && _containertype != "-") @@ -581,12 +584,14 @@ if (_pages.Any(item => item.Path == _page.Path && item.PageId != _page.PageId)) { AddModuleMessage(string.Format(Localizer["Mesage.Page.PathExists"], _path), MessageType.Warning); + await ScrollToPageTop(); return; } if (_page.ParentId == null && Constants.ReservedRoutes.Contains(_page.Name.ToLower())) { AddModuleMessage(string.Format(Localizer["Message.Page.Reserved"], _page.Name), MessageType.Warning); + await ScrollToPageTop(); return; } @@ -671,17 +676,20 @@ else { AddModuleMessage(Localizer["Message.Required.PageInfo"], MessageType.Warning); + await ScrollToPageTop(); } } catch (Exception ex) { await logger.LogError(ex, "Error Saving Page {Page} {Error}", _page, ex.Message); AddModuleMessage(Localizer["Error.Page.Save"], MessageType.Error); + await ScrollToPageTop(); } } else { AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning); + await ScrollToPageTop(); } } diff --git a/Oqtane.Client/Modules/Admin/Site/Index.razor b/Oqtane.Client/Modules/Admin/Site/Index.razor index ff94804a..1db3bc96 100644 --- a/Oqtane.Client/Modules/Admin/Site/Index.razor +++ b/Oqtane.Client/Modules/Admin/Site/Index.razor @@ -32,32 +32,32 @@ @foreach (Page page in _pages) { - if (UserSecurity.ContainsRole(page.PermissionList, PermissionNames.View, RoleNames.Everyone)) - { - - } + if (UserSecurity.ContainsRole(page.PermissionList, PermissionNames.View, RoleNames.Everyone)) + { + + } } -
- -
- -
-
-
- -
+
+ +
+ +
+
+
+ + -
+
@@ -159,8 +159,8 @@
-
-
+
+
@@ -213,10 +213,10 @@
-
- +
+ -
+
@@ -225,15 +225,15 @@
-
- -
- -
-
+
+ +
+ +
+
@@ -244,10 +244,10 @@
- -
+ +
-
+


@@ -280,57 +280,57 @@
@if (_aliases != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) { -
-
-
- -
- - -
-   -   - @Localizer["AliasName"] - @Localizer["AliasDefault"] -
- - @if (context.AliasId != _aliasid) - { - - @if (_aliasid == -1) - { - - } - - - @if (_aliasid == -1) - { - - } - - @context.Name +
+
+
+ +
+ + +
+   +   + @Localizer["AliasName"] + @Localizer["AliasDefault"] +
+ + @if (context.AliasId != _aliasid) + { + + @if (_aliasid == -1) + { + + } + + + @if (_aliasid == -1) + { + + } + + @context.Name @((context.IsDefault) ? SharedLocalizer["Yes"] : SharedLocalizer["No"]) - } - else - { - - - - - - - - - } - -
-
-
-
-
+ } + else + { + + + + + + + + + } +
+
+
+
+
+
@@ -376,7 +376,7 @@
- +
@@ -390,7 +390,7 @@
- +
@@ -481,6 +481,11 @@ { try { + if (PageState.QueryString.ContainsKey("updated")) + { + AddModuleMessage(Localizer["Success.Settings.SaveSite"], MessageType.Success); + } + Site site = await SiteService.GetSiteAsync(PageState.Site.SiteId); if (site != null) { @@ -736,7 +741,7 @@ await logger.LogInformation("Site Settings Saved {Site}", site); - NavigationManager.NavigateTo(NavigateUrl(), true); // reload + NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, "updated=true"), true); // reload } } else diff --git a/Oqtane.Client/Modules/Admin/UserProfile/Index.razor b/Oqtane.Client/Modules/Admin/UserProfile/Index.razor index 91a31585..63486fc7 100644 --- a/Oqtane.Client/Modules/Admin/UserProfile/Index.razor +++ b/Oqtane.Client/Modules/Admin/UserProfile/Index.razor @@ -105,7 +105,7 @@ }
-
+
@if (!string.IsNullOrEmpty(p.Options)) { @if (!string.IsNullOrEmpty(p.Autocomplete)) @@ -149,22 +149,26 @@ { @if (p.IsRequired) { - + } else { - + } } else { @if (p.IsRequired) { - + } else { - + } } } @@ -174,22 +178,26 @@ { @if (p.IsRequired) { - + } else { - + } } else { @if (p.IsRequired) { - + } else { - + } } } @@ -218,11 +226,11 @@ {
-   -   - @Localizer["From"] - @Localizer["Subject"] - @Localizer["Received"] +   +   + @Localizer["From"] + @Localizer["Subject"] + @Localizer["Received"]
@@ -230,13 +238,13 @@ @if (context.IsRead) { - @context.FromDisplayName + @(string.IsNullOrEmpty(context.FromDisplayName) ? SharedLocalizer["System"] : context.FromDisplayName) @context.Subject @string.Format("{0:dd-MMM-yyyy HH:mm:ss}", @context.CreatedOn) } else { - @context.FromDisplayName + @(string.IsNullOrEmpty(context.FromDisplayName) ? SharedLocalizer["System"] : context.FromDisplayName) @context.Subject @string.Format("{0:dd-MMM-yyyy HH:mm:ss}", @context.CreatedOn) } @@ -281,11 +289,11 @@ {
- - - @Localizer["To"] - @Localizer["Subject"] - @Localizer["Sent"] + + + @Localizer["To"] + @Localizer["Subject"] + @Localizer["Sent"]
@@ -363,7 +371,7 @@ private File photo = null; private string _ImageFiles = string.Empty; private List profiles; - private Dictionary settings; + private Dictionary userSettings; private string category = string.Empty; private string filter = "to"; @@ -411,9 +419,9 @@ photo = null; } - settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId); + userSettings = PageState.User.Settings; var sitesettings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId); - _ImageFiles = SettingService.GetSetting(settings, "ImageFiles", Constants.ImageFiles); + _ImageFiles = SettingService.GetSetting(userSettings, "ImageFiles", Constants.ImageFiles); _ImageFiles = (string.IsNullOrEmpty(_ImageFiles)) ? Constants.ImageFiles : _ImageFiles; await LoadNotificationsAsync(); @@ -440,7 +448,7 @@ private string GetProfileValue(string SettingName, string DefaultValue) { - string value = SettingService.GetSetting(settings, SettingName, DefaultValue); + string value = SettingService.GetSetting(userSettings, SettingName, DefaultValue); if (value.Contains("]")) { value = value.Substring(value.IndexOf("]") + 1); @@ -483,7 +491,7 @@ user = await UserService.UpdateUserAsync(user); if (user != null) { - await SettingService.UpdateUserSettingsAsync(settings, PageState.User.UserId); + await SettingService.UpdateUserSettingsAsync(userSettings, PageState.User.UserId); await logger.LogInformation("User Profile Saved"); if (!string.IsNullOrEmpty(PageState.ReturnUrl)) @@ -554,7 +562,7 @@ var value = GetProfileValue(profile.Name, string.Empty); if (string.IsNullOrEmpty(value) && !string.IsNullOrEmpty(profile.DefaultValue)) { - settings = SettingService.SetSetting(settings, profile.Name, profile.DefaultValue); + userSettings = SettingService.SetSetting(userSettings, profile.Name, profile.DefaultValue); } if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin)) { @@ -586,7 +594,7 @@ private void ProfileChanged(ChangeEventArgs e, string SettingName) { var value = (string)e.Value; - settings = SettingService.SetSetting(settings, SettingName, value); + userSettings = SettingService.SetSetting(userSettings, SettingName, value); } private async Task Delete(Notification Notification) diff --git a/Oqtane.Client/Modules/Admin/UserProfile/View.razor b/Oqtane.Client/Modules/Admin/UserProfile/View.razor index 3f104710..5e7205d4 100644 --- a/Oqtane.Client/Modules/Admin/UserProfile/View.razor +++ b/Oqtane.Client/Modules/Admin/UserProfile/View.razor @@ -128,7 +128,7 @@ createdon = notification.CreatedOn.ToString(); body = notification.Body; - if (title == "From") + if (title == "From" && !notification.IsRead) { notification.IsRead = true; notification = await NotificationService.UpdateNotificationAsync(notification); diff --git a/Oqtane.Client/Modules/Admin/Users/Add.razor b/Oqtane.Client/Modules/Admin/Users/Add.razor index 537a3bac..10cf19e8 100644 --- a/Oqtane.Client/Modules/Admin/Users/Add.razor +++ b/Oqtane.Client/Modules/Admin/Users/Add.razor @@ -81,11 +81,11 @@ { @if (p.Rows == 1) { - + } else { - + } }
diff --git a/Oqtane.Client/Modules/Admin/Users/Edit.razor b/Oqtane.Client/Modules/Admin/Users/Edit.razor index cf5ac069..a58af948 100644 --- a/Oqtane.Client/Modules/Admin/Users/Edit.razor +++ b/Oqtane.Client/Modules/Admin/Users/Edit.razor @@ -89,8 +89,8 @@ }
-
- @if (!string.IsNullOrEmpty(p.Options)) +
+ @if (!string.IsNullOrEmpty(p.Options)) { + } else { - + } }
@@ -132,7 +132,7 @@ } - @code { +@code { private bool _initialized = false; private string _passwordrequirements; private int userid; @@ -148,7 +148,7 @@ private string lastipaddress; private List profiles; - private Dictionary settings; + private Dictionary userSettings; private string category = string.Empty; private string createdby; @@ -181,7 +181,7 @@ lastlogin = string.Format("{0:MMM dd yyyy HH:mm:ss}", user.LastLoginOn); lastipaddress = user.LastIPAddress; - settings = await SettingService.GetUserSettingsAsync(user.UserId); + userSettings = user.Settings; createdby = user.CreatedBy; createdon = user.CreatedOn; modifiedby = user.ModifiedBy; @@ -202,7 +202,7 @@ private string GetProfileValue(string SettingName, string DefaultValue) { - string value = SettingService.GetSetting(settings, SettingName, DefaultValue); + string value = SettingService.GetSetting(userSettings, SettingName, DefaultValue); if (value.Contains("]")) { value = value.Substring(value.IndexOf("]") + 1); @@ -232,7 +232,7 @@ user = await UserService.UpdateUserAsync(user); if (user != null) { - await SettingService.UpdateUserSettingsAsync(settings, user.UserId); + await SettingService.UpdateUserSettingsAsync(userSettings, user.UserId); await logger.LogInformation("User Saved {User}", user); NavigationManager.NavigateTo(NavigateUrl()); } @@ -266,7 +266,7 @@ var value = GetProfileValue(profile.Name, string.Empty); if (string.IsNullOrEmpty(value) && !string.IsNullOrEmpty(profile.DefaultValue)) { - settings = SettingService.SetSetting(settings, profile.Name, profile.DefaultValue); + userSettings = SettingService.SetSetting(userSettings, profile.Name, profile.DefaultValue); } if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin)) { @@ -293,7 +293,7 @@ private void ProfileChanged(ChangeEventArgs e, string SettingName) { var value = (string)e.Value; - settings = SettingService.SetSetting(settings, SettingName, value); + userSettings = SettingService.SetSetting(userSettings, SettingName, value); } private void TogglePassword() diff --git a/Oqtane.Client/Modules/Controls/Pager.razor b/Oqtane.Client/Modules/Controls/Pager.razor index ab1db740..136f39ff 100644 --- a/Oqtane.Client/Modules/Controls/Pager.razor +++ b/Oqtane.Client/Modules/Controls/Pager.razor @@ -21,7 +21,7 @@ @if ((Toolbar == "Top" || Toolbar == "Both") && _pages > 0 && Items.Count() > _maxItems) { -
    +
    • UpdateList(1))>
    • @@ -84,7 +84,7 @@ @if ((Toolbar == "Top" || Toolbar == "Both") && _pages > 0 && Items.Count() > _maxItems) { -
        +
        • @@ -200,7 +200,7 @@ { @if (PageState.RenderMode == RenderModes.Interactive || ModuleState.RenderMode == RenderModes.Interactive) { -
            +
            • UpdateList(1))>
            • @@ -248,7 +248,7 @@ } else { -
                +
                • @@ -368,6 +368,12 @@ [SupplyParameterFromForm(FormName = "PagerForm")] public string _Search { get => ""; set => _search = value; } + /// + /// Accepted values are Start or Center or End. The default value is Center + /// + [Parameter] + public string PaginationAlignment { get; set; } = "center"; // Alignment of the Page Numbering start, center, end + private IEnumerable ItemList { get; set; } protected override void OnInitialized() diff --git a/Oqtane.Client/Modules/Controls/QuillJSTextEditor.razor b/Oqtane.Client/Modules/Controls/QuillJSTextEditor.razor index d6542a08..cc458f02 100644 --- a/Oqtane.Client/Modules/Controls/QuillJSTextEditor.razor +++ b/Oqtane.Client/Modules/Controls/QuillJSTextEditor.razor @@ -277,7 +277,7 @@ { // include CSS theme var interop = new Interop(JSRuntime); - await interop.IncludeLink("", "stylesheet", $"css/quill/quill.{_theme}.css", "text/css", "", "", ""); + await interop.IncludeLink("", "stylesheet", $"{PageState?.Alias.BaseUrl}/css/quill/quill.{_theme}.css", "text/css", "", "", ""); } await base.OnAfterRenderAsync(firstRender); diff --git a/Oqtane.Client/Modules/Controls/TabPanel.razor b/Oqtane.Client/Modules/Controls/TabPanel.razor index 0944df4a..cff8d9e1 100644 --- a/Oqtane.Client/Modules/Controls/TabPanel.razor +++ b/Oqtane.Client/Modules/Controls/TabPanel.razor @@ -36,14 +36,7 @@ else Parent.AddTabPanel((TabPanel)this); - if (string.IsNullOrEmpty(Heading)) - { - Heading = Localize(nameof(Name), Name); - } - else - { - Heading = Localize(nameof(Heading), Heading); - } + Heading = string.IsNullOrEmpty(Heading) ? Localize(nameof(Name), Name) : Localize(nameof(Heading), Heading); } public string DisplayHeading() diff --git a/Oqtane.Client/Modules/ModuleBase.cs b/Oqtane.Client/Modules/ModuleBase.cs index 4a794630..b99e7d29 100644 --- a/Oqtane.Client/Modules/ModuleBase.cs +++ b/Oqtane.Client/Modules/ModuleBase.cs @@ -98,17 +98,17 @@ namespace Oqtane.Modules var inline = 0; foreach (Resource resource in resources) { - if (string.IsNullOrEmpty(resource.RenderMode) || resource.RenderMode == RenderModes.Interactive) + if ((string.IsNullOrEmpty(resource.RenderMode) || resource.RenderMode == RenderModes.Interactive) && !resource.Reload) { if (!string.IsNullOrEmpty(resource.Url)) { var url = (resource.Url.Contains("://")) ? resource.Url : PageState.Alias.BaseUrl + resource.Url; - scripts.Add(new { href = url, bundle = resource.Bundle ?? "", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", es6module = resource.ES6Module, location = resource.Location.ToString().ToLower() }); + scripts.Add(new { href = url, type = resource.Type ?? "", bundle = resource.Bundle ?? "", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", location = resource.Location.ToString().ToLower(), dataAttributes = resource.DataAttributes }); } else { inline += 1; - await interop.IncludeScript(GetType().Namespace.ToLower() + inline.ToString(), "", "", "", resource.Content, resource.Location.ToString().ToLower()); + await interop.IncludeScript(GetType().Namespace.ToLower() + inline.ToString(), "", "", "", resource.Type ?? "", resource.Content, resource.Location.ToString().ToLower()); } } } diff --git a/Oqtane.Client/Oqtane.Client.csproj b/Oqtane.Client/Oqtane.Client.csproj index 4823e285..d0d4769f 100644 --- a/Oqtane.Client/Oqtane.Client.csproj +++ b/Oqtane.Client/Oqtane.Client.csproj @@ -4,7 +4,7 @@ net9.0 Exe Debug;Release - 6.0.0 + 6.0.1 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/v6.0.0 + https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.1 https://github.com/oqtane/oqtane.framework Git Oqtane diff --git a/Oqtane.Client/Resources/Modules/Admin/Pages/Add.resx b/Oqtane.Client/Resources/Modules/Admin/Pages/Add.resx index 9ebed89a..b818eacd 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Pages/Add.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Pages/Add.resx @@ -267,4 +267,4 @@ Expiry Date: - \ No newline at end of file + diff --git a/Oqtane.Client/Resources/Modules/Admin/Pages/Edit.resx b/Oqtane.Client/Resources/Modules/Admin/Pages/Edit.resx index 478e7616..b142a19c 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Pages/Edit.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Pages/Edit.resx @@ -297,4 +297,4 @@ Expiry Date: - \ No newline at end of file + diff --git a/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx index 1f3bf2c6..670a4cba 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx @@ -435,4 +435,7 @@ Functionality + + System + \ No newline at end of file diff --git a/Oqtane.Client/Resources/Modules/Admin/Upgrade/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Upgrade/Index.resx index e9a376e1..a833fa20 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Upgrade/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Upgrade/Index.resx @@ -1,4 +1,4 @@ - + Exe - 6.0.0 + 6.0.1 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/v6.0.0 + https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.1 https://github.com/oqtane/oqtane.framework Git Oqtane.Maui @@ -30,7 +30,7 @@ com.oqtane.maui - 6.0.0 + 6.0.1 1 diff --git a/Oqtane.Package/Oqtane.Client.nuspec b/Oqtane.Package/Oqtane.Client.nuspec index a01cd05d..3620b920 100644 --- a/Oqtane.Package/Oqtane.Client.nuspec +++ b/Oqtane.Package/Oqtane.Client.nuspec @@ -2,7 +2,7 @@ Oqtane.Client - 6.0.0 + 6.0.1 Shaun Walker .NET Foundation Oqtane Framework @@ -12,14 +12,14 @@ false MIT https://github.com/oqtane/oqtane.framework - https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.0 + https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.1 readme.md icon.png oqtane - - + + diff --git a/Oqtane.Package/Oqtane.Framework.nuspec b/Oqtane.Package/Oqtane.Framework.nuspec index 80b1baaa..60049a32 100644 --- a/Oqtane.Package/Oqtane.Framework.nuspec +++ b/Oqtane.Package/Oqtane.Framework.nuspec @@ -2,7 +2,7 @@ Oqtane.Framework - 6.0.0 + 6.0.1 Shaun Walker .NET Foundation Oqtane Framework @@ -11,8 +11,8 @@ .NET Foundation false MIT - https://github.com/oqtane/oqtane.framework/releases/download/v6.0.0/Oqtane.Framework.6.0.0.Upgrade.zip - https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.0 + https://github.com/oqtane/oqtane.framework/releases/download/v6.0.1/Oqtane.Framework.6.0.1.Upgrade.zip + https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.1 readme.md icon.png oqtane framework diff --git a/Oqtane.Package/Oqtane.Server.nuspec b/Oqtane.Package/Oqtane.Server.nuspec index 1be6a86d..42d74fe8 100644 --- a/Oqtane.Package/Oqtane.Server.nuspec +++ b/Oqtane.Package/Oqtane.Server.nuspec @@ -2,7 +2,7 @@ Oqtane.Server - 6.0.0 + 6.0.1 Shaun Walker .NET Foundation Oqtane Framework @@ -12,14 +12,14 @@ false MIT https://github.com/oqtane/oqtane.framework - https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.0 + https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.1 readme.md icon.png oqtane - - + + diff --git a/Oqtane.Package/Oqtane.Shared.nuspec b/Oqtane.Package/Oqtane.Shared.nuspec index 9186c018..885247ad 100644 --- a/Oqtane.Package/Oqtane.Shared.nuspec +++ b/Oqtane.Package/Oqtane.Shared.nuspec @@ -2,7 +2,7 @@ Oqtane.Shared - 6.0.0 + 6.0.1 Shaun Walker .NET Foundation Oqtane Framework @@ -12,14 +12,14 @@ false MIT https://github.com/oqtane/oqtane.framework - https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.0 + https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.1 readme.md icon.png oqtane - - + + diff --git a/Oqtane.Package/Oqtane.Updater.nuspec b/Oqtane.Package/Oqtane.Updater.nuspec index 6a7911f8..89768e12 100644 --- a/Oqtane.Package/Oqtane.Updater.nuspec +++ b/Oqtane.Package/Oqtane.Updater.nuspec @@ -2,7 +2,7 @@ Oqtane.Updater - 6.0.0 + 6.0.1 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/v6.0.0 + https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.1 readme.md icon.png oqtane - + diff --git a/Oqtane.Package/install.ps1 b/Oqtane.Package/install.ps1 index 9f4ef87b..dee9ddf2 100644 --- a/Oqtane.Package/install.ps1 +++ b/Oqtane.Package/install.ps1 @@ -1 +1 @@ -Compress-Archive -Path "..\Oqtane.Server\bin\Release\net9.0\publish\*" -DestinationPath "Oqtane.Framework.6.0.0.Install.zip" -Force +Compress-Archive -Path "..\Oqtane.Server\bin\Release\net9.0\publish\*" -DestinationPath "Oqtane.Framework.6.0.1.Install.zip" -Force diff --git a/Oqtane.Package/upgrade.ps1 b/Oqtane.Package/upgrade.ps1 index 7ece0bea..5e734637 100644 --- a/Oqtane.Package/upgrade.ps1 +++ b/Oqtane.Package/upgrade.ps1 @@ -1 +1 @@ -Compress-Archive -Path "..\Oqtane.Server\bin\Release\net9.0\publish\*" -DestinationPath "Oqtane.Framework.6.0.0.Upgrade.zip" -Force +Compress-Archive -Path "..\Oqtane.Server\bin\Release\net9.0\publish\*" -DestinationPath "Oqtane.Framework.6.0.1.Upgrade.zip" -Force diff --git a/Oqtane.Server/Components/App.razor b/Oqtane.Server/Components/App.razor index e8c7ca76..cdf5eda7 100644 --- a/Oqtane.Server/Components/App.razor +++ b/Oqtane.Server/Components/App.razor @@ -76,6 +76,10 @@ @((MarkupString)_scripts) @((MarkupString)_bodyResources) + @if (_renderMode == RenderModes.Static) + { + + } } else { @@ -188,9 +192,6 @@ _scripts += CreateScrollPositionScript(); } - _headResources += ParseScripts(site.HeadContent); - _bodyResources += ParseScripts(site.BodyContent); - // set culture if not specified string cultureCookie = Context.Request.Cookies[Shared.CookieRequestCultureProvider.DefaultCookieName]; if (cultureCookie == null) @@ -510,26 +511,10 @@ "" + Environment.NewLine; } - private string ParseScripts(string content) - { - var scripts = ""; - // in interactive render mode, parse scripts from content and inject into page - if (_renderMode == RenderModes.Interactive && !string.IsNullOrEmpty(content)) - { - var index = content.IndexOf("= 0) - { - scripts += content.Substring(index, content.IndexOf("", index) + 9 - index); - index = content.IndexOf(" 0) { - var url = (resource.Url.Contains("://")) ? resource.Url : alias.BaseUrl + resource.Url; - return ""; // src at end of element due to enhanced navigation patch algorithm - } - else - { - // use custom element which can execute script on every page transition - @if (string.IsNullOrEmpty(resource.Integrity) && string.IsNullOrEmpty(resource.CrossOrigin)) + foreach (var attribute in resource.DataAttributes) { - return ""; - } - else - { - // use modulepreload for external resources - return "\n" + - ""; + dataAttributes += " " + attribute.Key + "=\"" + attribute.Value + "\""; } } + + return ""; } else { - // inline script - return ""; + return ""; } } diff --git a/Oqtane.Server/Controllers/NotificationController.cs b/Oqtane.Server/Controllers/NotificationController.cs index 5f7ee353..8e439fd2 100644 --- a/Oqtane.Server/Controllers/NotificationController.cs +++ b/Oqtane.Server/Controllers/NotificationController.cs @@ -183,7 +183,7 @@ namespace Oqtane.Controllers { if (ModelState.IsValid && notification.SiteId == _alias.SiteId && notification.NotificationId == id && _notifications.GetNotification(notification.NotificationId, false) != null && (IsAuthorized(notification.FromUserId) || IsAuthorized(notification.ToUserId))) { - if (!User.IsInRole(RoleNames.Admin)) + if (!User.IsInRole(RoleNames.Admin) && notification.FromUserId != null) { // content must be HTML encoded for non-admins to prevent HTML injection notification.Subject = WebUtility.HtmlEncode(notification.Subject); @@ -223,7 +223,7 @@ namespace Oqtane.Controllers private bool IsAuthorized(int? userid) { - bool authorized = true; + bool authorized = false; if (userid != null) { authorized = (_userPermissions.GetUser(User).UserId == userid); diff --git a/Oqtane.Server/Controllers/PageController.cs b/Oqtane.Server/Controllers/PageController.cs index 94e1f21f..44e38808 100644 --- a/Oqtane.Server/Controllers/PageController.cs +++ b/Oqtane.Server/Controllers/PageController.cs @@ -9,7 +9,7 @@ using System.Net; using Oqtane.Enums; using Oqtane.Infrastructure; using Oqtane.Repository; -using System.IO; +using System; namespace Oqtane.Controllers { diff --git a/Oqtane.Server/Controllers/SettingController.cs b/Oqtane.Server/Controllers/SettingController.cs index d8a95cbe..298b6b01 100644 --- a/Oqtane.Server/Controllers/SettingController.cs +++ b/Oqtane.Server/Controllers/SettingController.cs @@ -269,11 +269,7 @@ namespace Oqtane.Controllers authorized = _userPermissions.IsAuthorized(User, _alias.SiteId, entityName, entityId, permissionName); break; case EntityNames.User: - authorized = true; - if (permissionName == PermissionNames.Edit) - { - authorized = _userPermissions.IsAuthorized(User, _alias.SiteId, entityName, -1, PermissionNames.Write, RoleNames.Admin) || (_userPermissions.GetUser(User).UserId == entityId); - } + authorized = _userPermissions.IsAuthorized(User, _alias.SiteId, entityName, -1, PermissionNames.Write, RoleNames.Admin) || (_userPermissions.GetUser(User).UserId == entityId); break; case EntityNames.Visitor: authorized = User.IsInRole(RoleNames.Admin); @@ -319,7 +315,7 @@ namespace Oqtane.Controllers filter = !_userPermissions.IsAuthorized(User, _alias.SiteId, entityName, entityId, PermissionNames.Edit); break; case EntityNames.User: - filter = !User.IsInRole(RoleNames.Admin) && _userPermissions.GetUser(User).UserId != entityId; + filter = !_userPermissions.IsAuthorized(User, _alias.SiteId, entityName, -1, PermissionNames.Write, RoleNames.Admin) && _userPermissions.GetUser(User).UserId != entityId; break; case EntityNames.Visitor: if (!User.IsInRole(RoleNames.Admin)) diff --git a/Oqtane.Server/Controllers/UserController.cs b/Oqtane.Server/Controllers/UserController.cs index acbcb1a1..6d146758 100644 --- a/Oqtane.Server/Controllers/UserController.cs +++ b/Oqtane.Server/Controllers/UserController.cs @@ -28,9 +28,10 @@ namespace Oqtane.Controllers private readonly IJwtManager _jwtManager; private readonly IFileRepository _files; private readonly ISettingRepository _settings; + private readonly ISyncManager _syncManager; private readonly ILogManager _logger; - public UserController(IUserRepository users, ITenantManager tenantManager, IUserManager userManager, ISiteRepository sites, IUserPermissions userPermissions, IJwtManager jwtManager, IFileRepository files, ISettingRepository settings, ILogManager logger) + public UserController(IUserRepository users, ITenantManager tenantManager, IUserManager userManager, ISiteRepository sites, IUserPermissions userPermissions, IJwtManager jwtManager, IFileRepository files, ISettingRepository settings, ISyncManager syncManager, ILogManager logger) { _users = users; _tenantManager = tenantManager; @@ -40,6 +41,7 @@ namespace Oqtane.Controllers _jwtManager = jwtManager; _files = files; _settings = settings; + _syncManager = syncManager; _logger = logger; } @@ -136,7 +138,7 @@ namespace Oqtane.Controllers filtered.PhotoFileId = user.PhotoFileId; filtered.LastLoginOn = user.LastLoginOn; filtered.LastIPAddress = user.LastIPAddress; - filtered.TwoFactorRequired = false; + filtered.TwoFactorRequired = user.TwoFactorRequired; filtered.Roles = user.Roles; filtered.CreatedBy = user.CreatedBy; filtered.CreatedOn = user.CreatedOn; @@ -145,20 +147,7 @@ namespace Oqtane.Controllers filtered.DeletedBy = user.DeletedBy; filtered.DeletedOn = user.DeletedOn; filtered.IsDeleted = user.IsDeleted; - } - - // if authenticated user is accessing their own user account - if (_userPermissions.GetUser(User).UserId == user.UserId) - { - // include all settings - filtered.Settings = user.Settings; - } - else - { - // include only public settings - filtered.Settings = _settings.GetSettings(EntityNames.User, user.UserId) - .Where(item => !item.IsPrivate) - .ToDictionary(setting => setting.SettingName, setting => setting.SettingValue); + filtered.Settings = user.Settings; // include all settings } } @@ -266,6 +255,7 @@ namespace Oqtane.Controllers if (_userPermissions.GetUser(User).UserId == user.UserId) { await HttpContext.SignOutAsync(Constants.AuthenticationScheme); + _syncManager.AddSyncEvent(_tenantManager.GetAlias(), EntityNames.User, user.UserId, "Logout"); _logger.Log(LogLevel.Information, this, LogFunction.Security, "User Logout {Username}", (user != null) ? user.Username : ""); } } @@ -279,6 +269,7 @@ namespace Oqtane.Controllers { await _userManager.LogoutUserEverywhere(user); await HttpContext.SignOutAsync(Constants.AuthenticationScheme); + _syncManager.AddSyncEvent(_tenantManager.GetAlias(), EntityNames.User, user.UserId, "Logout"); _logger.Log(LogLevel.Information, this, LogFunction.Security, "User Logout Everywhere {Username}", (user != null) ? user.Username : ""); } } diff --git a/Oqtane.Server/Controllers/UserRoleController.cs b/Oqtane.Server/Controllers/UserRoleController.cs index f4eb0864..38f89705 100644 --- a/Oqtane.Server/Controllers/UserRoleController.cs +++ b/Oqtane.Server/Controllers/UserRoleController.cs @@ -10,7 +10,6 @@ using System.Linq; using System.Net; using Oqtane.Security; using System; -using Oqtane.Modules.Admin.Roles; namespace Oqtane.Controllers { @@ -40,24 +39,34 @@ namespace Oqtane.Controllers public IEnumerable Get(string siteid, string userid = null, string rolename = null) { int SiteId; - if (int.TryParse(siteid, out SiteId) && SiteId == _alias.SiteId && (userid != null || rolename != null)) + int UserId = -1; + if (int.TryParse(siteid, out SiteId) && SiteId == _alias.SiteId && (userid != null && int.TryParse(userid, out UserId) || rolename != null)) { - var userroles = _userRoles.GetUserRoles(SiteId).ToList(); - if (userid != null) + if (IsAuthorized(UserId, rolename)) { - int UserId = int.TryParse(userid, out UserId) ? UserId : -1; - userroles = userroles.Where(item => item.UserId == UserId).ToList(); + var userroles = _userRoles.GetUserRoles(SiteId).ToList(); + if (UserId != -1) + { + userroles = userroles.Where(item => item.UserId == UserId).ToList(); + } + if (rolename != null) + { + userroles = userroles.Where(item => item.Role.Name == rolename).ToList(); + } + var user = _userPermissions.GetUser(); + for (int i = 0; i < userroles.Count(); i++) + { + userroles[i] = Filter(userroles[i], user.UserId); + } + return userroles.OrderBy(u => u.User.DisplayName); + } - if (rolename != null) + else { - userroles = userroles.Where(item => item.Role.Name == rolename).ToList(); + _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized UserRole Get Attempt For Site {SiteId} User {UserId} Role {RoleName}", siteid, userid, rolename); + HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; + return null; } - var user = _userPermissions.GetUser(); - for (int i = 0; i < userroles.Count(); i++) - { - userroles[i] = Filter(userroles[i], user.UserId); - } - return userroles.OrderBy(u => u.User.DisplayName); } else { @@ -73,7 +82,7 @@ namespace Oqtane.Controllers public UserRole Get(int id) { var userrole = _userRoles.GetUserRole(id); - if (userrole != null && SiteValid(userrole.Role.SiteId)) + if (userrole != null && SiteValid(userrole.Role.SiteId) && IsAuthorized(userrole.UserId, userrole.Role.Name)) { return Filter(userrole, _userPermissions.GetUser().UserId); } @@ -92,33 +101,57 @@ namespace Oqtane.Controllers } } + private bool IsAuthorized(int userId, string roleName) + { + bool authorized = true; + if (userId != -1) + { + authorized = _userPermissions.GetUser(User).UserId == userId; + } + if (authorized && !string.IsNullOrEmpty(roleName)) + { + authorized = User.IsInRole(roleName); + } + return authorized; + } + private UserRole Filter(UserRole userrole, int userid) { + // clone object to avoid mutating cache + UserRole filtered = null; + if (userrole != null) { - userrole.User.Password = ""; - userrole.User.IsAuthenticated = false; - userrole.User.TwoFactorCode = ""; - userrole.User.TwoFactorExpiry = null; + filtered = new UserRole(); - if (!_userPermissions.IsAuthorized(User, userrole.User.SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin) && userid != userrole.User.UserId) + // public properties + filtered.UserRoleId = userrole.UserRoleId; + filtered.UserId = userrole.UserId; + filtered.RoleId = userrole.RoleId; + + filtered.User = new User(); + filtered.User.SiteId = userrole.User.SiteId; + filtered.User.UserId = userrole.User.UserId; + filtered.User.Username = userrole.User.Username; + filtered.User.DisplayName = userrole.User.DisplayName; + + filtered.Role = new Role(); + filtered.Role.SiteId = userrole.Role.SiteId; + filtered.Role.RoleId = userrole.Role.RoleId; + filtered.Role.Name = userrole.Role.Name; + + // include private properties if administrator + if (_userPermissions.IsAuthorized(User, filtered.User.SiteId, EntityNames.UserRole, -1, PermissionNames.Write, RoleNames.Admin)) { - userrole.User.Email = ""; - userrole.User.PhotoFileId = null; - userrole.User.LastLoginOn = DateTime.MinValue; - userrole.User.LastIPAddress = ""; - userrole.User.Roles = ""; - userrole.User.CreatedBy = ""; - userrole.User.CreatedOn = DateTime.MinValue; - userrole.User.ModifiedBy = ""; - userrole.User.ModifiedOn = DateTime.MinValue; - userrole.User.DeletedBy = ""; - userrole.User.DeletedOn = DateTime.MinValue; - userrole.User.IsDeleted = false; - userrole.User.TwoFactorRequired = false; + filtered.User.Email = userrole.User.Email; + filtered.User.PhotoFileId = userrole.User.PhotoFileId; + filtered.User.LastLoginOn = userrole.User.LastLoginOn; + filtered.User.LastIPAddress = userrole.User.LastIPAddress; + filtered.User.CreatedOn = userrole.User.CreatedOn; } } - return userrole; + + return filtered; } // POST api/ diff --git a/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs b/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs index cb0303e6..56b0d3bd 100644 --- a/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs +++ b/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs @@ -645,6 +645,9 @@ namespace Oqtane.Extensions } } + var _syncManager = httpContext.RequestServices.GetRequiredService(); + _syncManager.AddSyncEvent(alias, EntityNames.User, user.UserId, "Login"); + _logger.Log(LogLevel.Information, "ExternalLogin", Enums.LogFunction.Security, "External User Login Successful For {Username} From IP Address {IPAddress} Using Provider {Provider}", user.Username, httpContext.Connection.RemoteIpAddress.ToString(), providerName); } } diff --git a/Oqtane.Server/Infrastructure/Middleware/JwtMiddleware.cs b/Oqtane.Server/Infrastructure/Middleware/JwtMiddleware.cs index 071828d5..fc25a3f1 100644 --- a/Oqtane.Server/Infrastructure/Middleware/JwtMiddleware.cs +++ b/Oqtane.Server/Infrastructure/Middleware/JwtMiddleware.cs @@ -59,7 +59,7 @@ namespace Oqtane.Infrastructure if (userid != null && username != null) { var _users = context.RequestServices.GetService(typeof(IUserManager)) as IUserManager; - var user = _users.GetUser(userid, alias.SiteId); // cached + var user = _users.GetUser(int.Parse(userid), alias.SiteId); // cached if (user != null && !user.IsDeleted) { var claimsidentity = UserSecurity.CreateClaimsIdentity(alias, user); diff --git a/Oqtane.Server/Infrastructure/UpgradeManager.cs b/Oqtane.Server/Infrastructure/UpgradeManager.cs index 79e3ae92..0ba6642b 100644 --- a/Oqtane.Server/Infrastructure/UpgradeManager.cs +++ b/Oqtane.Server/Infrastructure/UpgradeManager.cs @@ -1,11 +1,11 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Oqtane.Models; using Oqtane.Repository; using Oqtane.Shared; using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; @@ -17,12 +17,14 @@ namespace Oqtane.Infrastructure private readonly IServiceScopeFactory _serviceScopeFactory; private readonly IWebHostEnvironment _environment; private readonly IConfigManager _configManager; + private readonly ILogger _filelogger; - public UpgradeManager(IServiceScopeFactory serviceScopeFactory, IWebHostEnvironment environment, IConfigManager configManager) + public UpgradeManager(IServiceScopeFactory serviceScopeFactory, IWebHostEnvironment environment, IConfigManager configManager, ILogger filelogger) { _serviceScopeFactory = serviceScopeFactory; _environment = environment; _configManager = configManager; + _filelogger = filelogger; } public void Upgrade(Tenant tenant, string version) @@ -69,6 +71,9 @@ namespace Oqtane.Infrastructure case "5.2.1": Upgrade_5_2_1(tenant, scope); break; + case "6.0.1": + Upgrade_6_0_1(tenant, scope); + break; } } } @@ -88,7 +93,7 @@ namespace Oqtane.Infrastructure catch (Exception ex) { // error deleting directory - Debug.WriteLine($"Oqtane Error: Error In 2.0.2 Upgrade Logic - {ex}"); + _filelogger.LogError(Utilities.LogMessage(this, $"Oqtane Error: Error In 2.0.2 Upgrade Logic - {ex}")); } } } @@ -106,7 +111,7 @@ namespace Oqtane.Infrastructure catch (Exception ex) { // error populating guid - Debug.WriteLine($"Oqtane Error: Error In 2.0.2 Upgrade Logic - {ex}"); + _filelogger.LogError(Utilities.LogMessage(this, $"Oqtane Error: Error In 2.0.2 Upgrade Logic - {ex}")); } } @@ -274,7 +279,7 @@ namespace Oqtane.Infrastructure } catch (Exception ex) { - Debug.WriteLine($"Oqtane Error: Error In 3.2.0 Upgrade Logic - {ex}"); + _filelogger.LogError(Utilities.LogMessage(this, $"Oqtane Error: Error In 3.2.0 Upgrade Logic - {ex}")); } } @@ -310,7 +315,7 @@ namespace Oqtane.Infrastructure } catch (Exception ex) { - Debug.WriteLine($"Oqtane Error: Error In 3.2.1 Upgrade Logic - {ex}"); + _filelogger.LogError(Utilities.LogMessage(this, $"Oqtane Error: Error In 3.2.1 Upgrade Logic - {ex}")); } } @@ -354,7 +359,7 @@ namespace Oqtane.Infrastructure } catch (Exception ex) { - Debug.WriteLine($"Oqtane Error: Error In 3.3.0 Upgrade Logic - {ex}"); + _filelogger.LogError(Utilities.LogMessage(this, $"Oqtane Error: Error In 3.3.0 Upgrade Logic - {ex}")); } } @@ -371,7 +376,7 @@ namespace Oqtane.Infrastructure try { // delete legacy Views assemblies which will cause startup errors due to missing HostModel - // note that the following files will be deleted however the framework has already started up so a restart will be required + // note that the following files will be deleted however the framework has already started up so another restart will be required var binFolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); var filepath = Path.Combine(binFolder, "Oqtane.Server.Views.dll"); if (System.IO.File.Exists(filepath)) System.IO.File.Delete(filepath); @@ -381,7 +386,7 @@ namespace Oqtane.Infrastructure catch (Exception ex) { // error deleting file - Debug.WriteLine($"Oqtane Error: Error In 5.1.0 Upgrade Logic - {ex}"); + _filelogger.LogError(Utilities.LogMessage(this, $"Oqtane Error: Error In 5.1.0 Upgrade Logic - {ex}")); } } } @@ -441,6 +446,54 @@ namespace Oqtane.Infrastructure AddPagesToSites(scope, tenant, pageTemplates); } + private void Upgrade_6_0_1(Tenant tenant, IServiceScope scope) + { + // assemblies which have been relocated to the bin/refs folder in .NET 9 + string[] assemblies = { + "Microsoft.AspNetCore.Authorization.dll", + "Microsoft.AspNetCore.Components.Authorization.dll", + "Microsoft.AspNetCore.Components.dll", + "Microsoft.AspNetCore.Components.Forms.dll", + "Microsoft.AspNetCore.Components.Web.dll", + "Microsoft.AspNetCore.Cryptography.Internal.dll", + "Microsoft.AspNetCore.Cryptography.KeyDerivation.dll", + "Microsoft.AspNetCore.Metadata.dll", + "Microsoft.Extensions.Caching.Memory.dll", + "Microsoft.Extensions.Configuration.Binder.dll", + "Microsoft.Extensions.Configuration.FileExtensions.dll", + "Microsoft.Extensions.Configuration.Json.dll", + "Microsoft.Extensions.DependencyInjection.Abstractions.dll", + "Microsoft.Extensions.DependencyInjection.dll", + "Microsoft.Extensions.Diagnostics.Abstractions.dll", + "Microsoft.Extensions.Diagnostics.dll", + "Microsoft.Extensions.Http.dll", + "Microsoft.Extensions.Identity.Core.dll", + "Microsoft.Extensions.Identity.Stores.dll", + "Microsoft.Extensions.Localization.Abstractions.dll", + "Microsoft.Extensions.Localization.dll", + "Microsoft.Extensions.Logging.Abstractions.dll", + "Microsoft.Extensions.Logging.dll", + "Microsoft.Extensions.Options.dll", + "Microsoft.JSInterop.dll", + "System.Text.Json.dll" + }; + + foreach (var assembly in assemblies) + { + try + { + var binFolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); + var filepath = Path.Combine(binFolder, assembly); + if (System.IO.File.Exists(filepath)) System.IO.File.Delete(filepath); + } + catch (Exception ex) + { + // error deleting asesmbly + _filelogger.LogError(Utilities.LogMessage(this, $"Oqtane Error: 6.0.1 Upgrade Error Removing {assembly} - {ex}")); + } + } + } + private void AddPagesToSites(IServiceScope scope, Tenant tenant, List pageTemplates) { var tenants = scope.ServiceProvider.GetRequiredService(); diff --git a/Oqtane.Server/Managers/UserManager.cs b/Oqtane.Server/Managers/UserManager.cs index 03bff1bb..7745f429 100644 --- a/Oqtane.Server/Managers/UserManager.cs +++ b/Oqtane.Server/Managers/UserManager.cs @@ -339,20 +339,20 @@ namespace Oqtane.Managers user = _users.GetUser(user.Username); if (!user.IsDeleted) { - if (user.TwoFactorRequired) + var alias = _tenantManager.GetAlias(); + var twoFactorSetting = _settings.GetSetting(EntityNames.Site, alias.SiteId, "LoginOptions:TwoFactor")?.SettingValue ?? "false"; + var twoFactorRequired = twoFactorSetting == "required" || user.TwoFactorRequired; + if (twoFactorRequired) { var token = await _identityUserManager.GenerateTwoFactorTokenAsync(identityuser, "Email"); user.TwoFactorCode = token; user.TwoFactorExpiry = DateTime.UtcNow.AddMinutes(10); _users.UpdateUser(user); - var alias = _tenantManager.GetAlias(); - string url = alias.Protocol + alias.Name; string siteName = _sites.GetSite(alias.SiteId).Name; string subject = _localizer["TwoFactorEmailSubject"]; subject = subject.Replace("[SiteName]", siteName); string body = _localizer["TwoFactorEmailBody"].Value; body = body.Replace("[UserDisplayName]", user.DisplayName); - body = body.Replace("[URL]", url); body = body.Replace("[SiteName]", siteName); body = body.Replace("[Token]", token); var notification = new Notification(alias.SiteId, user, subject, body); @@ -374,6 +374,8 @@ namespace Oqtane.Managers _users.UpdateUser(user); _logger.Log(LogLevel.Information, this, LogFunction.Security, "User Login Successful For {Username} From IP Address {IPAddress}", user.Username, LastIPAddress); + _syncManager.AddSyncEvent(alias, EntityNames.User, user.UserId, "Login"); + if (setCookie) { await _identitySignInManager.SignInAsync(identityuser, isPersistent); diff --git a/Oqtane.Server/Migrations/Tenant/06000101_AddLanguageName.cs b/Oqtane.Server/Migrations/Tenant/06000101_AddLanguageName.cs new file mode 100644 index 00000000..76fedad6 --- /dev/null +++ b/Oqtane.Server/Migrations/Tenant/06000101_AddLanguageName.cs @@ -0,0 +1,32 @@ +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.06.00.01.01")] + public class AddLanguageName : MultiDatabaseMigration + { + public AddLanguageName(IDatabase database) : base(database) + { + } + + protected override void Up(MigrationBuilder migrationBuilder) + { + // Name column was removed in 5.2.4 however SQLite does not support column removal so it had to be restored + if (ActiveDatabase.Name != "Sqlite") + { + var languageEntityBuilder = new LanguageEntityBuilder(migrationBuilder, ActiveDatabase); + languageEntityBuilder.AddStringColumn("Name", 100, true); + } + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + // not implemented + } + } +} diff --git a/Oqtane.Server/Oqtane.Server.csproj b/Oqtane.Server/Oqtane.Server.csproj index 993d40e2..ba241482 100644 --- a/Oqtane.Server/Oqtane.Server.csproj +++ b/Oqtane.Server/Oqtane.Server.csproj @@ -3,7 +3,7 @@ net9.0 Debug;Release - 6.0.0 + 6.0.1 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/v6.0.0 + https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.1 https://github.com/oqtane/oqtane.framework Git Oqtane @@ -19,6 +19,7 @@ $(DefineConstants);OQTANE;OQTANE3 true none + false diff --git a/Oqtane.Server/Pages/Logout.cshtml.cs b/Oqtane.Server/Pages/Logout.cshtml.cs index 42334416..5d16ea84 100644 --- a/Oqtane.Server/Pages/Logout.cshtml.cs +++ b/Oqtane.Server/Pages/Logout.cshtml.cs @@ -35,6 +35,7 @@ namespace Oqtane.Pages { await _userManager.LogoutUserEverywhere(user); } + _syncManager.AddSyncEvent(alias, EntityNames.User, user.UserId, "Logout"); _syncManager.AddSyncEvent(alias, EntityNames.User, user.UserId, SyncEventActions.Reload); } diff --git a/Oqtane.Server/Repository/LanguageRepository.cs b/Oqtane.Server/Repository/LanguageRepository.cs index 6eac5559..f31a61eb 100644 --- a/Oqtane.Server/Repository/LanguageRepository.cs +++ b/Oqtane.Server/Repository/LanguageRepository.cs @@ -31,7 +31,7 @@ namespace Oqtane.Repository .ToList() .ForEach(l => l.IsDefault = false); } - + language.Name = ""; // stored in database but not used (SQLite limitation) db.Language.Add(language); db.SaveChanges(); @@ -55,6 +55,7 @@ namespace Oqtane.Repository .ForEach(l => l.IsDefault = false); } + language.Name = ""; // stored in database but not used (SQLite limitation) db.SaveChanges(); } diff --git a/Oqtane.Server/Repository/PageRepository.cs b/Oqtane.Server/Repository/PageRepository.cs index e3bc27d0..f400226f 100644 --- a/Oqtane.Server/Repository/PageRepository.cs +++ b/Oqtane.Server/Repository/PageRepository.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using Microsoft.EntityFrameworkCore; @@ -31,7 +32,47 @@ namespace Oqtane.Repository { page.PermissionList = permissions.Where(item => item.EntityId == page.PageId).ToList(); } - return pages; + return GetPagesHierarchy(pages); + } + + private static List GetPagesHierarchy(List pages) + { + List hierarchy = new List(); + Action, Page> getPath = null; + getPath = (pageList, page) => + { + IEnumerable children; + int level; + if (page == null) + { + level = -1; + children = pages.Where(item => item.ParentId == null); + } + else + { + level = page.Level; + children = pages.Where(item => item.ParentId == page.PageId); + } + foreach (Page child in children) + { + child.Level = level + 1; + child.HasChildren = pages.Any(item => item.ParentId == child.PageId && !item.IsDeleted && item.IsNavigation); + hierarchy.Add(child); + getPath(pageList, child); + } + }; + pages = pages.OrderBy(item => item.Order).ToList(); + getPath(pages, null); + + // add any non-hierarchical items to the end of the list + foreach (Page page in pages) + { + if (hierarchy.Find(item => item.PageId == page.PageId) == null) + { + hierarchy.Add(page); + } + } + return hierarchy; } public Page AddPage(Page page) diff --git a/Oqtane.Server/Resources/Managers/UserManager.resx b/Oqtane.Server/Resources/Managers/UserManager.resx index 66eec09c..ecc38fb0 100644 --- a/Oqtane.Server/Resources/Managers/UserManager.resx +++ b/Oqtane.Server/Resources/Managers/UserManager.resx @@ -1,4 +1,4 @@ - +