+}
@code {
private string _heading = string.Empty;
@@ -40,6 +43,9 @@
[Parameter]
public string Expanded { get; set; } // optional - will default to false if not provided
+ [Parameter]
+ public bool IsVisible { get; set; } = true;
+
protected override void OnParametersSet()
{
base.OnParametersSet(); // must be included to call method in LocalizableComponent
diff --git a/Oqtane.Client/Modules/ModuleBase.cs b/Oqtane.Client/Modules/ModuleBase.cs
index 41812923..f5c0bef7 100644
--- a/Oqtane.Client/Modules/ModuleBase.cs
+++ b/Oqtane.Client/Modules/ModuleBase.cs
@@ -261,7 +261,12 @@ namespace Oqtane.Modules
// UI methods
public void AddModuleMessage(string message, MessageType type)
{
- ModuleInstance.AddModuleMessage(message, type);
+ AddModuleMessage(message, type, "top");
+ }
+
+ public void AddModuleMessage(string message, MessageType type, string position)
+ {
+ ModuleInstance.AddModuleMessage(message, type, position);
}
public void ClearModuleMessage()
diff --git a/Oqtane.Client/Oqtane.Client.csproj b/Oqtane.Client/Oqtane.Client.csproj
index eea64f14..880f5e1b 100644
--- a/Oqtane.Client/Oqtane.Client.csproj
+++ b/Oqtane.Client/Oqtane.Client.csproj
@@ -4,7 +4,7 @@
net8.0ExeDebug;Release
- 5.0.0
+ 5.0.1OqtaneShaun Walker.NET Foundation
@@ -12,7 +12,7 @@
.NET Foundationhttps://www.oqtane.orghttps://github.com/oqtane/oqtane.framework/blob/dev/LICENSE
- https://github.com/oqtane/oqtane.framework/releases/tag/v5.0.0
+ https://github.com/oqtane/oqtane.framework/releases/tag/v5.0.1https://github.com/oqtane/oqtane.frameworkGitOqtane
diff --git a/Oqtane.Client/Resources/Modules/Admin/Languages/Edit.resx b/Oqtane.Client/Resources/Modules/Admin/Languages/Edit.resx
new file mode 100644
index 00000000..68458745
--- /dev/null
+++ b/Oqtane.Client/Resources/Modules/Admin/Languages/Edit.resx
@@ -0,0 +1,153 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Error Updating Language
+
+
+ Name Of The Langauage
+
+
+ Indicates Whether Or Not This Language Is The Default For The Site
+
+
+ Name:
+
+
+ Default?
+
+
+ Translation Package Saved Successfully. You Must <a href={0}>Restart</a> To Complete The Installation.
+
+
+ Upload one or more translation packages.
+
+
+ Translation
+
+
+ Manage
+
+
+ Upload
+
+
+ Error Loading Language
+
+
\ No newline at end of file
diff --git a/Oqtane.Client/Resources/Modules/Admin/Login/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Login/Index.resx
index 67dbbfed..3467911a 100644
--- a/Oqtane.Client/Resources/Modules/Admin/Login/Index.resx
+++ b/Oqtane.Client/Resources/Modules/Admin/Login/Index.resx
@@ -204,8 +204,8 @@
Multiple User Accounts Already Exist With The Email Address Of Your External Login. Please Contact Your Administrator For Further Instructions.
-
- The External Login Provider Did Not Provide A Valid Email Address For Your Account. Please Contact Your Administrator For Further Instructions.
+
+ The External Login Provider Did Not Provide All Of The Required Information. Please Contact Your Administrator For Further Instructions.An Error Occurred Verifying Your External Login. Please Contact Your Administrator For Further Instructions.
@@ -225,4 +225,7 @@
Your External Login Failed. Please Contact Your Administrator For Further Instructions.
+
+ The Review Claims Option Was Enabled In External Login Settings. Please Visit The Event Log To View The Claims Returned By The Provider.
+
\ No newline at end of file
diff --git a/Oqtane.Client/Resources/Modules/Admin/ModuleDefinitions/Edit.resx b/Oqtane.Client/Resources/Modules/Admin/ModuleDefinitions/Edit.resx
index 87cc9a12..7bb6ae59 100644
--- a/Oqtane.Client/Resources/Modules/Admin/ModuleDefinitions/Edit.resx
+++ b/Oqtane.Client/Resources/Modules/Admin/ModuleDefinitions/Edit.resx
@@ -240,4 +240,10 @@
Validate
+
+ Browse
+
+
+ Pages
+
\ No newline at end of file
diff --git a/Oqtane.Client/Resources/Modules/Admin/ModuleDefinitions/Index.resx b/Oqtane.Client/Resources/Modules/Admin/ModuleDefinitions/Index.resx
index 0ff2e74d..ce91ee3b 100644
--- a/Oqtane.Client/Resources/Modules/Admin/ModuleDefinitions/Index.resx
+++ b/Oqtane.Client/Resources/Modules/Admin/ModuleDefinitions/Index.resx
@@ -127,7 +127,7 @@
Error Downloading Module
- Are You Sure You Wish To Delete The {0} Module?
+ Are You Sure You Wish To Uninstall The {0} Module?Error Loading Modules
@@ -142,10 +142,10 @@
Install Module
- Delete Module
+ Uninstall Module
- Delete
+ UninstallIn Use?
diff --git a/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx
index 0d9b6c65..8d8efaf8 100644
--- a/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx
+++ b/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx
@@ -283,7 +283,7 @@
Prerender?
- The Blazor runtime hosting model
+ The Blazor runtime hosting model for the siteRuntime:
@@ -402,4 +402,25 @@
Retention (Days):
+
+ File Extensions
+
+
+ Enter a comma separated list of image file extensions
+
+
+ Image Extensions:
+
+
+ Enter a comma separated list of uploadable file extensions
+
+
+ Uploadable File Extensions:
+
+
+ Specifies if the site can be integrated with an external .NET MAUI hybrid application
+
+
+ Hybrid Enabled?
+
\ No newline at end of file
diff --git a/Oqtane.Client/Resources/Modules/Admin/Themes/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Themes/Index.resx
index e8e41589..e6b11297 100644
--- a/Oqtane.Client/Resources/Modules/Admin/Themes/Index.resx
+++ b/Oqtane.Client/Resources/Modules/Admin/Themes/Index.resx
@@ -124,7 +124,7 @@
Error Downloading Theme
- Are You Sure You Wish To Delete The {0} Theme?
+ Are You Sure You Wish To Uninstall The {0} Theme?Error Loading Themes
@@ -136,10 +136,10 @@
Error Deleting Theme
- Delete Theme
+ Uninstall Theme
- Delete
+ UninstallCreate Theme
diff --git a/Oqtane.Client/Resources/Modules/Admin/UserProfile/Index.resx b/Oqtane.Client/Resources/Modules/Admin/UserProfile/Index.resx
index 8bcffb8c..7e6b222d 100644
--- a/Oqtane.Client/Resources/Modules/Admin/UserProfile/Index.resx
+++ b/Oqtane.Client/Resources/Modules/Admin/UserProfile/Index.resx
@@ -147,6 +147,9 @@
Current User Is Not Logged In
+
+ You Must Provide An Email Address For Your User Account
+
Error Loading User Profile
diff --git a/Oqtane.Client/Resources/Modules/Admin/Users/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Users/Index.resx
index e6d4792b..6baae9c1 100644
--- a/Oqtane.Client/Resources/Modules/Admin/Users/Index.resx
+++ b/Oqtane.Client/Resources/Modules/Admin/Users/Index.resx
@@ -247,7 +247,7 @@
Domain Filter:
- The name of the email address claim provided by the identity provider
+ Optionally specify the type name of the email address claim provided by the identity provider. The typical value is 'email'.Email Claim:
@@ -274,7 +274,7 @@
Use PKCE?
- The external login provider name which will be displayed on the login page
+ Specify a friendly name for the external login provider which will be displayed on the Login pageProvider Name:
@@ -373,7 +373,7 @@
Last Login
- The name of the unique user identifier claim provided by the identity provider
+ Specify the type name of the unique user identifier claim provided by the identity provider. The default value is 'sub'.Identifier Claim:
@@ -385,13 +385,13 @@
Parameters:
- Optionally provide the name of the role claim provided by the identity provider. These roles will be used in addition to any internal user roles assigned within the site.
+ Optionally provide the type name of the role claim provided by the identity provider. These roles will be used in addition to any internal user roles assigned within the site.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'.
+ Optionally provide a comma delimited list of user profile claim type names 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'.User Profile Claims:
@@ -409,31 +409,34 @@
Import Users
- code
+ Authorization Code
- code id_token
+ Authorization Code + ID Token
- code id_token token
+ Authorization Code + ID Token + Access Token
- code token
+ Authorization Code + Access Token
- id_token
+ ID Token
- id_token token
+ ID Token + Access Token
- none
+ None
- token
+ Access Token
-
- Authorization Response Type
+
+ Authorization Response Type:
+
+
+ Specify the authorization response type. The default is Authorization Code which is considered to be the most secure option based on the latest OAuth specification.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.
@@ -453,4 +456,16 @@
Cookie Expiration Timespan:
+
+ Review Claims?
+
+
+ This option will record the full list of Claims returned by the Provider in the Event Log. It should only be used for testing purposes. External Login will be restricted when this option is enabled.
+
+
+ Optionally specify the type name of the user's name claim provided by the identity provider. The typical value is 'name'.
+
+
+ Name Claim:
+
\ No newline at end of file
diff --git a/Oqtane.Client/Resources/SharedResources.resx b/Oqtane.Client/Resources/SharedResources.resx
index b225811b..b00c7ce9 100644
--- a/Oqtane.Client/Resources/SharedResources.resx
+++ b/Oqtane.Client/Resources/SharedResources.resx
@@ -432,4 +432,10 @@
{0} Is Required
+
+ Uninstall
+
+
+ Test
+
\ No newline at end of file
diff --git a/Oqtane.Client/Services/Interfaces/ILanguageService.cs b/Oqtane.Client/Services/Interfaces/ILanguageService.cs
index a2da3a2f..bf7aa9a7 100644
--- a/Oqtane.Client/Services/Interfaces/ILanguageService.cs
+++ b/Oqtane.Client/Services/Interfaces/ILanguageService.cs
@@ -1,6 +1,6 @@
-using Oqtane.Models;
using System.Collections.Generic;
using System.Threading.Tasks;
+using Oqtane.Models;
namespace Oqtane.Services
{
@@ -39,6 +39,13 @@ namespace Oqtane.Services
///
Task AddLanguageAsync(Language language);
+ ///
+ /// Edits the given language
+ ///
+ ///
+ ///
+ Task EditLanguageAsync(Language language);
+
///
/// Deletes the given language
///
diff --git a/Oqtane.Client/Services/LanguageService.cs b/Oqtane.Client/Services/LanguageService.cs
index f8b432c8..7e95e952 100644
--- a/Oqtane.Client/Services/LanguageService.cs
+++ b/Oqtane.Client/Services/LanguageService.cs
@@ -1,5 +1,4 @@
using System.Collections.Generic;
-using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Oqtane.Documentation;
@@ -10,7 +9,7 @@ namespace Oqtane.Services
{
[PrivateApi("Don't show in the documentation, as everything should use the Interface")]
public class LanguageService : ServiceBase, ILanguageService
- {
+ {
public LanguageService(HttpClient http, SiteState siteState) : base(http, siteState) { }
private string Apiurl => CreateApiUrl("Language");
@@ -35,6 +34,11 @@ namespace Oqtane.Services
return await PostJsonAsync(Apiurl, language);
}
+ public async Task EditLanguageAsync(Language language)
+ {
+ await PutJsonAsync(Apiurl, language);
+ }
+
public async Task DeleteLanguageAsync(int languageId)
{
await DeleteAsync($"{Apiurl}/{languageId}");
diff --git a/Oqtane.Client/Themes/Controls/Theme/LoginBase.cs b/Oqtane.Client/Themes/Controls/Theme/LoginBase.cs
index b3de5229..f8f71ee9 100644
--- a/Oqtane.Client/Themes/Controls/Theme/LoginBase.cs
+++ b/Oqtane.Client/Themes/Controls/Theme/LoginBase.cs
@@ -26,8 +26,15 @@ namespace Oqtane.Themes.Controls
var allowexternallogin = (SettingService.GetSetting(PageState.Site.Settings, "ExternalLogin:ProviderType", "") != "") ? true : false;
var allowsitelogin = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AllowSiteLogin", "true"));
- Route route = new Route(PageState.Uri.AbsoluteUri, PageState.Alias.Path);
- var returnurl = WebUtility.UrlEncode(route.PathAndQuery);
+ var returnurl = "";
+ if (!PageState.QueryString.ContainsKey("returnurl"))
+ {
+ returnurl = WebUtility.UrlEncode(PageState.Route.PathAndQuery); // remember current url
+ }
+ else
+ {
+ returnurl = PageState.QueryString["returnurl"]; // use existing value
+ }
if (allowexternallogin && !allowsitelogin)
{
@@ -39,7 +46,6 @@ namespace Oqtane.Themes.Controls
// local login
NavigationManager.NavigateTo(NavigateUrl("login", "?returnurl=" + returnurl));
}
-
}
protected async Task LogoutUser()
diff --git a/Oqtane.Client/Themes/Controls/Theme/UserProfile.razor b/Oqtane.Client/Themes/Controls/Theme/UserProfile.razor
index 36a5e0c1..850e28c3 100644
--- a/Oqtane.Client/Themes/Controls/Theme/UserProfile.razor
+++ b/Oqtane.Client/Themes/Controls/Theme/UserProfile.razor
@@ -1,4 +1,5 @@
@namespace Oqtane.Themes.Controls
+@using System.Net
@inherits ThemeControlBase
@inject IStringLocalizer Localizer
@@ -26,14 +27,21 @@
[Parameter]
public bool ShowRegister { get; set; }
+ private string _returnurl = "";
+
+ protected override void OnParametersSet()
+ {
+ _returnurl = WebUtility.UrlEncode(PageState.Route.PathAndQuery);
+ }
+
private void RegisterUser()
{
- NavigationManager.NavigateTo(NavigateUrl("register"));
+ NavigationManager.NavigateTo(NavigateUrl("register", "returnurl=" + _returnurl));
}
private void UpdateProfile()
{
- NavigationManager.NavigateTo(NavigateUrl("profile"));
+ NavigationManager.NavigateTo(NavigateUrl("profile", "returnurl=" + _returnurl));
}
}
diff --git a/Oqtane.Client/UI/ModuleInstance.razor b/Oqtane.Client/UI/ModuleInstance.razor
index f05ef9be..78ae6d5c 100644
--- a/Oqtane.Client/UI/ModuleInstance.razor
+++ b/Oqtane.Client/UI/ModuleInstance.razor
@@ -5,7 +5,10 @@
@if (CurrentException is null)
{
-
+ if (_messagePosition == "top")
+ {
+
+ }
@if (ModuleType != null)
{
@@ -14,6 +17,10 @@
}
}
+ if (_messagePosition == "bottom")
+ {
+
+ }
}
else
{
@@ -24,49 +31,58 @@ else
}
@code {
- private string _message;
- private string _error;
- private MessageType _messageType;
- private bool _progressIndicator = false;
+ private string _message;
+ private string _error;
+ private MessageType _messageType;
+ private string _messagePosition;
+ private bool _progressIndicator = false;
- private Type ModuleType { get; set; }
- private IDictionary ModuleParameters { get; set; }
+ private Type ModuleType { get; set; }
+ private IDictionary ModuleParameters { get; set; }
- [CascadingParameter]
- protected PageState PageState { get; set; }
+ [CascadingParameter]
+ protected PageState PageState { get; set; }
- [CascadingParameter]
- private Module ModuleState { get; set; }
+ [CascadingParameter]
+ private Module ModuleState { get; set; }
- private ModuleMessage ModuleMessage { get; set; }
+ private ModuleMessage ModuleMessage { get; set; }
- protected override void OnParametersSet()
- {
- _message = "";
- if (!string.IsNullOrEmpty(ModuleState.ModuleType))
- {
- ModuleType = Type.GetType(ModuleState.ModuleType);
- if (ModuleType != null)
- {
- ModuleParameters = new Dictionary { { "ModuleInstance", this } };
- return;
- }
- // module does not exist with typename specified
- _message = string.Format(Localizer["Error.Module.InvalidName"], Utilities.GetTypeNameLastSegment(ModuleState.ModuleType, 0));
- _messageType = MessageType.Error;
- }
- else
- {
- _message = string.Format(Localizer["Error.Module.InvalidType"], ModuleState.ModuleDefinitionName);
- _messageType = MessageType.Error;
- }
- }
+ protected override void OnParametersSet()
+ {
+ _message = "";
+ if (!string.IsNullOrEmpty(ModuleState.ModuleType))
+ {
+ ModuleType = Type.GetType(ModuleState.ModuleType);
+ if (ModuleType != null)
+ {
+ ModuleParameters = new Dictionary { { "ModuleInstance", this } };
+ return;
+ }
+ // module does not exist with typename specified
+ _message = string.Format(Localizer["Error.Module.InvalidName"], Utilities.GetTypeNameLastSegment(ModuleState.ModuleType, 0));
+ _messageType = MessageType.Error;
+ _messagePosition = "top";
+ }
+ else
+ {
+ _message = string.Format(Localizer["Error.Module.InvalidType"], ModuleState.ModuleDefinitionName);
+ _messageType = MessageType.Error;
+ _messagePosition = "top";
+ }
+ }
- public void AddModuleMessage(string message, MessageType type)
- {
- _message = message;
- _messageType = type;
- _progressIndicator = false;
+ public void AddModuleMessage(string message, MessageType type)
+ {
+ AddModuleMessage(message, type, "top");
+ }
+
+ public void AddModuleMessage(string message, MessageType type, string position)
+ {
+ _message = message;
+ _messageType = type;
+ _messagePosition = position;
+ _progressIndicator = false;
StateHasChanged();
}
diff --git a/Oqtane.Client/UI/SiteRouter.razor b/Oqtane.Client/UI/SiteRouter.razor
index 8035841e..51877aa0 100644
--- a/Oqtane.Client/UI/SiteRouter.razor
+++ b/Oqtane.Client/UI/SiteRouter.razor
@@ -105,11 +105,18 @@
Route route = new Route(_absoluteUri, SiteState.Alias.Path);
int moduleid = (int.TryParse(route.ModuleId, out moduleid)) ? moduleid : -1;
var action = (!string.IsNullOrEmpty(route.Action)) ? route.Action : Constants.DefaultAction;
+
var querystring = Utilities.ParseQueryString(route.Query);
var returnurl = "";
if (querystring.ContainsKey("returnurl"))
{
returnurl = WebUtility.UrlDecode(querystring["returnurl"]);
+ if (!returnurl.StartsWith("/"))
+ {
+ // urls which are not relative are vulnerable to open redirects or XSS
+ returnurl = "";
+ querystring["returnurl"] = "";
+ }
}
// reload the client application from the server if there is a forced reload
@@ -155,7 +162,8 @@
if (PageState == null || refresh || PageState.Alias.SiteId != SiteState.Alias.SiteId)
{
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
- if (authState.User.Identity.IsAuthenticated)
+ // verify user is authenticated for current site
+ if (authState.User.Identity.IsAuthenticated && authState.User.Claims.Any(item => item.Type == "sitekey" && item.Value == SiteState.Alias.SiteKey))
{
user = await UserService.GetUserAsync(authState.User.Identity.Name, SiteState.Alias.SiteId);
if (user != null)
diff --git a/Oqtane.Client/UI/ThemeBuilder.razor b/Oqtane.Client/UI/ThemeBuilder.razor
index 920e362e..9432e2ac 100644
--- a/Oqtane.Client/UI/ThemeBuilder.razor
+++ b/Oqtane.Client/UI/ThemeBuilder.razor
@@ -1,4 +1,5 @@
@namespace Oqtane.UI
+@using System.Net
@inject IJSRuntime JSRuntime
@inject NavigationManager NavigationManager
@inject SiteState SiteState
@@ -87,6 +88,13 @@
protected override async Task OnAfterRenderAsync(bool firstRender)
{
+ // force authenticated user to provide email address (email may be missing if using external login)
+ if (PageState.User != null && PageState.User.IsAuthenticated && string.IsNullOrEmpty(PageState.User.Email) && PageState.Route.PagePath != "profile")
+ {
+ NavigationManager.NavigateTo(Utilities.NavigateUrl(PageState.Alias.Path, "profile", "returnurl=" + WebUtility.UrlEncode(PageState.Route.PathAndQuery)));
+ return;
+ }
+
if (!firstRender)
{
if (!string.IsNullOrEmpty(PageState.Page.HeadContent) && PageState.Page.HeadContent.Contains("