Merge remote-tracking branch 'upstream/dev' into dev

This commit is contained in:
Leigh Pointer
2025-05-19 11:24:47 +02:00
13 changed files with 157 additions and 115 deletions

View File

@@ -144,7 +144,7 @@ else
user = await UserService.VerifyEmailAsync(user, PageState.QueryString["token"]); user = await UserService.VerifyEmailAsync(user, PageState.QueryString["token"]);
if (user != null) if (user != null)
{ {
await logger.LogInformation(LogFunction.Security, "Email Verified For For Username {Username}", _username); await logger.LogInformation(LogFunction.Security, "Email Verified For Username {Username}", _username);
AddModuleMessage(Localizer["Success.Account.Verified"], MessageType.Info); AddModuleMessage(Localizer["Success.Account.Verified"], MessageType.Info);
} }
else else

View File

@@ -50,6 +50,11 @@
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
public override string Title => "Export Content"; public override string Title => "Export Content";
protected override void OnInitialized()
{
_filename = Utilities.GetFriendlyUrl(ModuleState.Title);
}
private async Task ExportText() private async Task ExportText()
{ {
try try
@@ -71,8 +76,8 @@
var folderid = _filemanager.GetFolderId(); var folderid = _filemanager.GetFolderId();
if (folderid != -1 && !string.IsNullOrEmpty(_filename)) if (folderid != -1 && !string.IsNullOrEmpty(_filename))
{ {
var result = await ModuleService.ExportModuleAsync(ModuleState.ModuleId, PageState.Page.PageId, folderid, _filename); var fileid = await ModuleService.ExportModuleAsync(ModuleState.ModuleId, PageState.Page.PageId, folderid, _filename);
if (result.Success) if (fileid != -1)
{ {
AddModuleMessage(Localizer["Success.Content.Export"], MessageType.Success); AddModuleMessage(Localizer["Success.Content.Export"], MessageType.Success);
} }

View File

@@ -8,88 +8,92 @@
@inject IStringLocalizer<SharedResources> SharedLocalizer @inject IStringLocalizer<SharedResources> SharedLocalizer
@inject ISettingService SettingService @inject ISettingService SettingService
@if (PageState.Site.AllowRegistration) @if (_initialized)
{ {
if (!_userCreated) @if (PageState.Site.AllowRegistration)
{ {
if (PageState.User != null) if (!_userCreated)
{ {
<ModuleMessage Message="@Localizer["Info.Registration.Exists"]" Type="MessageType.Info" /> if (PageState.User != null)
} {
else <ModuleMessage Message="@Localizer["Info.Registration.Exists"]" Type="MessageType.Info" />
{ }
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" /> else
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate> {
<div class="container"> <ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
<div class="row mb-1 align-items-center"> <form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<Label Class="col-sm-3" For="username" HelpText="Your username. Note that this field can not be modified once it is saved." ResourceKey="Username"></Label> <div class="container">
<div class="col-sm-9"> <div class="row mb-1 align-items-center">
<input id="username" class="form-control" @bind="@_username" maxlength="256" required /> <Label Class="col-sm-3" For="username" HelpText="Your username. Note that this field can not be modified once it is saved." ResourceKey="Username"></Label>
<div class="col-sm-9">
<input id="username" class="form-control" @bind="@_username" maxlength="256" required />
</div>
</div> </div>
</div> <div class="row mb-1 align-items-center">
<div class="row mb-1 align-items-center"> <Label Class="col-sm-3" For="password" HelpText="Please choose a sufficiently secure password and enter it here" ResourceKey="Password"></Label>
<Label Class="col-sm-3" For="password" HelpText="Please choose a sufficiently secure password and enter it here" ResourceKey="Password"></Label> <div class="col-sm-9">
<div class="col-sm-9"> <div class="input-group">
<div class="input-group"> <input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" required />
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" required /> <button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button> </div>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="confirm" HelpText="Enter your password again to confirm it matches the value entered above" ResourceKey="Confirm"></Label>
<div class="col-sm-9">
<div class="input-group">
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@_confirm" autocomplete="new-password" required />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="email" HelpText="Your email address where you wish to receive notifications" ResourceKey="Email"></Label>
<div class="col-sm-9">
<input id="email" class="form-control" @bind="@_email" maxlength="256" required />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="displayname" HelpText="Your full name" ResourceKey="DisplayName"></Label>
<div class="col-sm-9">
<input id="displayname" class="form-control" @bind="@_displayname" maxlength="50" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="timezone" HelpText="Your time zone" ResourceKey="TimeZone">Time Zone:</Label>
<div class="col-sm-9">
<select id="timezone" class="form-select" @bind="@_timezoneid">
<option value="">&lt;@SharedLocalizer["Not Specified"]&gt;</option>
@foreach (var timezone in _timezones)
{
<option value="@timezone.Id">@timezone.DisplayName</option>
}
</select>
</div> </div>
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="confirm" HelpText="Enter your password again to confirm it matches the value entered above" ResourceKey="Confirm"></Label>
<div class="col-sm-9">
<div class="input-group">
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@_confirm" autocomplete="new-password" required />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="email" HelpText="Your email address where you wish to receive notifications" ResourceKey="Email"></Label>
<div class="col-sm-9">
<input id="email" class="form-control" @bind="@_email" maxlength="256" required />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="displayname" HelpText="Your full name" ResourceKey="DisplayName"></Label>
<div class="col-sm-9">
<input id="displayname" class="form-control" @bind="@_displayname" maxlength="50" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="timezone" HelpText="Your time zone" ResourceKey="TimeZone">Time Zone:</Label>
<div class="col-sm-9">
<select id="timezone" class="form-select" @bind="@_timezoneid">
<option value="">&lt;@SharedLocalizer["Not Specified"]&gt;</option>
@foreach (var timezone in _timezones)
{
<option value="@timezone.Id">@timezone.DisplayName</option>
}
</select>
</div>
</div>
</div>
<br />
<button type="button" class="btn btn-primary" @onclick="Register">@Localizer["Register"]</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
@if (_allowsitelogin)
{
<br /> <br />
<button type="button" class="btn btn-primary" @onclick="Register">@Localizer["Register"]</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
@if (_allowsitelogin)
{
<br />
<br /> <br />
<NavLink href="@NavigateUrl("login")">@Localizer["Login"]</NavLink> <NavLink href="@NavigateUrl("login")">@Localizer["Login"]</NavLink>
} }
</form> </form>
}
} }
} }
} else
else {
{ <ModuleMessage Message="@Localizer["Info.Registration.Disabled"]" Type="MessageType.Info" />
<ModuleMessage Message="@Localizer["Info.Registration.Disabled"]" Type="MessageType.Info" /> }
} }
@code { @code {
private bool _initialized = false;
private List<Models.TimeZone> _timezones; private List<Models.TimeZone> _timezones;
private string _passwordrequirements; private string _passwordrequirements;
private string _username = string.Empty; private string _username = string.Empty;
@@ -113,6 +117,7 @@ else
_allowsitelogin = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AllowSiteLogin", "true")); _allowsitelogin = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AllowSiteLogin", "true"));
_timezones = await TimeZoneService.GetTimeZonesAsync(); _timezones = await TimeZoneService.GetTimeZonesAsync();
_timezoneid = PageState.Site.TimeZoneId; _timezoneid = PageState.Site.TimeZoneId;
_initialized = true;
} }
protected override void OnParametersSet() protected override void OnParametersSet()

View File

@@ -18,13 +18,13 @@
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" /> <ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
<div class="container"> <div class="container">
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="username" HelpText="The unique username for a user. Note that this field can not be modified." ResourceKey="Username"></Label> <Label Class="col-sm-3" For="username" HelpText="The unique username for a user. Note that this field can not be modified." ResourceKey="Username">Username:</Label>
<div class="col-sm-9"> <div class="col-sm-9">
<input id="username" class="form-control" @bind="@_username" readonly /> <input id="username" class="form-control" @bind="@_username" readonly />
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="password" HelpText="The user's password. Please choose a password which is sufficiently secure." ResourceKey="Password"></Label> <Label Class="col-sm-3" For="password" HelpText="The user's password. Please choose a password which is sufficiently secure." ResourceKey="Password">Password:</Label>
<div class="col-sm-9"> <div class="col-sm-9">
<div class="input-group"> <div class="input-group">
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" /> <input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" />
@@ -33,7 +33,7 @@
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="confirm" HelpText="Please enter the password again to confirm it matches with the value above" ResourceKey="Confirm"></Label> <Label Class="col-sm-3" For="confirm" HelpText="Please enter the password again to confirm it matches with the value above" ResourceKey="Confirm">Confirm Password:</Label>
<div class="col-sm-9"> <div class="col-sm-9">
<div class="input-group"> <div class="input-group">
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@_confirm" autocomplete="new-password" /> <input id="confirm" type="@_passwordtype" class="form-control" @bind="@_confirm" autocomplete="new-password" />
@@ -42,13 +42,22 @@
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="email" HelpText="The email address where the user will receive notifications" ResourceKey="Email"></Label> <Label Class="col-sm-3" For="email" HelpText="The email address where the user will receive notifications" ResourceKey="Email">Email:</Label>
<div class="col-sm-9"> <div class="col-sm-9">
<input id="email" class="form-control" @bind="@_email" /> <input id="email" class="form-control" @bind="@_email" />
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="displayname" HelpText="The full name of the user" ResourceKey="DisplayName"></Label> <Label Class="col-sm-3" For="confirmed" HelpText="Indicates if the user's email is verified" ResourceKey="Confirmed">Confirmed?</Label>
<div class="col-sm-9">
<select id="confirmed" class="form-select" @bind="@_confirmed">
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="displayname" HelpText="The full name of the user" ResourceKey="DisplayName">Full Name:</Label>
<div class="col-sm-9"> <div class="col-sm-9">
<input id="displayname" class="form-control" @bind="@_displayname" /> <input id="displayname" class="form-control" @bind="@_displayname" />
</div> </div>
@@ -68,7 +77,7 @@
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) @if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{ {
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="isdeleted" HelpText="Indicate if the user is active" ResourceKey="IsDeleted"></Label> <Label Class="col-sm-3" For="isdeleted" HelpText="Indicate if the user is active" ResourceKey="IsDeleted">Deleted?</Label>
<div class="col-sm-9"> <div class="col-sm-9">
<select id="isdeleted" class="form-select" @bind="@_isdeleted"> <select id="isdeleted" class="form-select" @bind="@_isdeleted">
<option value="True">@SharedLocalizer["Yes"]</option> <option value="True">@SharedLocalizer["Yes"]</option>
@@ -78,13 +87,13 @@
</div> </div>
} }
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="lastlogin" HelpText="The date and time when the user last signed in" ResourceKey="LastLogin"></Label> <Label Class="col-sm-3" For="lastlogin" HelpText="The date and time when the user last signed in" ResourceKey="LastLogin">Last Login:</Label>
<div class="col-sm-9"> <div class="col-sm-9">
<input id="lastlogin" class="form-control" @bind="@_lastlogin" readonly /> <input id="lastlogin" class="form-control" @bind="@_lastlogin" readonly />
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="lastipaddress" HelpText="The IP Address of the user recorded during their last login" ResourceKey="LastIPAddress"></Label> <Label Class="col-sm-3" For="lastipaddress" HelpText="The IP Address of the user recorded during their last login" ResourceKey="LastIPAddress">Last IP Address:</Label>
<div class="col-sm-9"> <div class="col-sm-9">
<input id="lastipaddress" class="form-control" @bind="@_lastipaddress" readonly /> <input id="lastipaddress" class="form-control" @bind="@_lastipaddress" readonly />
</div> </div>
@@ -167,6 +176,7 @@
private string _togglepassword = string.Empty; private string _togglepassword = string.Empty;
private string _confirm = string.Empty; private string _confirm = string.Empty;
private string _email = string.Empty; private string _email = string.Empty;
private string _confirmed = string.Empty;
private string _displayname = string.Empty; private string _displayname = string.Empty;
private string _timezoneid = string.Empty; private string _timezoneid = string.Empty;
private string _isdeleted; private string _isdeleted;
@@ -204,6 +214,7 @@
{ {
_username = user.Username; _username = user.Username;
_email = user.Email; _email = user.Email;
_confirmed = user.EmailConfirmed.ToString();
_displayname = user.DisplayName; _displayname = user.DisplayName;
_timezoneid = PageState.User.TimeZoneId; _timezoneid = PageState.User.TimeZoneId;
_isdeleted = user.IsDeleted.ToString(); _isdeleted = user.IsDeleted.ToString();
@@ -255,6 +266,7 @@
user.Username = _username; user.Username = _username;
user.Password = _password; user.Password = _password;
user.Email = _email; user.Email = _email;
user.EmailConfirmed = bool.Parse(_confirmed);
user.DisplayName = string.IsNullOrWhiteSpace(_displayname) ? _username : _displayname; user.DisplayName = string.IsNullOrWhiteSpace(_displayname) ? _username : _displayname;
user.TimeZoneId = _timezoneid; user.TimeZoneId = _timezoneid;
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))

View File

@@ -121,10 +121,10 @@
<value>Forgot Password</value> <value>Forgot Password</value>
</data> </data>
<data name="Success.Account.Verified" xml:space="preserve"> <data name="Success.Account.Verified" xml:space="preserve">
<value>User Account Verified Successfully. You Can Now Login With Your Username And Password Below.</value> <value>User Account Email Address Verified Successfully. You Can Now Login With Your Username And Password.</value>
</data> </data>
<data name="Message.Account.NotVerified" xml:space="preserve"> <data name="Message.Account.NotVerified" xml:space="preserve">
<value>User Account Could Not Be Verified. Please Contact Your Administrator For Further Instructions.</value> <value>User Account Email Address Could Not Be Verified. Please Contact Your Administrator For Further Instructions.</value>
</data> </data>
<data name="Success.Account.Linked" xml:space="preserve"> <data name="Success.Account.Linked" xml:space="preserve">
<value>User Account Linked Successfully. You Can Now Login With Your External Login Below.</value> <value>User Account Linked Successfully. You Can Now Login With Your External Login Below.</value>
@@ -133,7 +133,7 @@
<value>External Login Could Not Be Linked. Please Contact Your Administrator For Further Instructions.</value> <value>External Login Could Not Be Linked. Please Contact Your Administrator For Further Instructions.</value>
</data> </data>
<data name="Error.Login.Fail" xml:space="preserve"> <data name="Error.Login.Fail" xml:space="preserve">
<value>Login Failed. Please Remember That Passwords Are Case Sensitive. If You Have Attempted To Sign In Multiple Times Unsuccessfully, Your Account Will Be Locked Out For A Period Of Time. Note That User Accounts Require Verification When They Are Initially Created So You May Wish To Check Your Email If You Are A New User.</value> <value>Login Failed. Please Remember That Passwords Are Case Sensitive. If You Have Attempted To Sign In Multiple Times Unsuccessfully, Your Account Will Be Locked Out For A Period Of Time. Note That User Accounts Often Require Email Address Verification So You May Wish To Check Your Email For A Notification.</value>
</data> </data>
<data name="Message.Required.UserInfo" xml:space="preserve"> <data name="Message.Required.UserInfo" xml:space="preserve">
<value>Please Provide All Required Fields</value> <value>Please Provide All Required Fields</value>

View File

@@ -216,4 +216,10 @@
<data name="TimeZone.HelpText" xml:space="preserve"> <data name="TimeZone.HelpText" xml:space="preserve">
<value>The user's time zone</value> <value>The user's time zone</value>
</data> </data>
<data name="Confirmed.Text" xml:space="preserve">
<value>Confirmed?</value>
</data>
<data name="Confirmed.HelpText" xml:space="preserve">
<value>Indicates if the user's email is verified</value>
</data>
</root> </root>

View File

@@ -67,7 +67,7 @@ namespace Oqtane.Services
/// <param name="pageId"></param> /// <param name="pageId"></param>
/// <param name="folderId"></param> /// <param name="folderId"></param>
/// <param name="filename"></param> /// <param name="filename"></param>
/// <returns>success/failure</returns> /// <returns>file id</returns>
Task<Result> ExportModuleAsync(int moduleId, int pageId, int folderId, string filename); Task<int> ExportModuleAsync(int moduleId, int pageId, int folderId, string filename);
} }
} }

View File

@@ -51,9 +51,9 @@ namespace Oqtane.Services
return await GetStringAsync($"{Apiurl}/export?moduleid={moduleId}&pageid={pageId}"); return await GetStringAsync($"{Apiurl}/export?moduleid={moduleId}&pageid={pageId}");
} }
public async Task<Result> ExportModuleAsync(int moduleId, int pageId, int folderId, string filename) public async Task<int> ExportModuleAsync(int moduleId, int pageId, int folderId, string filename)
{ {
return await PostJsonAsync<Result>($"{Apiurl}/export?moduleid={moduleId}&pageid={pageId}&folderid={folderId}&filename={filename}", null); return await PostJsonAsync<string,int>($"{Apiurl}/export?moduleid={moduleId}&pageid={pageId}&folderid={folderId}&filename={filename}", null);
} }
} }
} }

View File

@@ -22,7 +22,6 @@ using Microsoft.AspNetCore.Cors;
using System.IO.Compression; using System.IO.Compression;
using Oqtane.Services; using Oqtane.Services;
using Microsoft.Extensions.Primitives; using Microsoft.Extensions.Primitives;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.Net.Http.Headers; using Microsoft.Net.Http.Headers;
// ReSharper disable StringIndexOfIsCultureSpecific.1 // ReSharper disable StringIndexOfIsCultureSpecific.1

View File

@@ -10,9 +10,6 @@ using Oqtane.Repository;
using Oqtane.Security; using Oqtane.Security;
using System.Net; using System.Net;
using System.IO; using System.IO;
using System;
using static System.Net.WebRequestMethods;
using System.Net.Http;
namespace Oqtane.Controllers namespace Oqtane.Controllers
{ {
@@ -259,9 +256,9 @@ namespace Oqtane.Controllers
// POST api/<controller>/export?moduleid=x&pageid=y&folderid=z&filename=a // POST api/<controller>/export?moduleid=x&pageid=y&folderid=z&filename=a
[HttpPost("export")] [HttpPost("export")]
[Authorize(Roles = RoleNames.Registered)] [Authorize(Roles = RoleNames.Registered)]
public Result Export(int moduleid, int pageid, int folderid, string filename) public int Export(int moduleid, int pageid, int folderid, string filename)
{ {
var result = new Result(false); var fileid = -1;
var module = _modules.GetModule(moduleid); var module = _modules.GetModule(moduleid);
if (module != null && module.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, module.SiteId, EntityNames.Page, pageid, PermissionNames.Edit) && if (module != null && module.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, module.SiteId, EntityNames.Page, pageid, PermissionNames.Edit) &&
_userPermissions.IsAuthorized(User, module.SiteId, EntityNames.Folder, folderid, PermissionNames.Edit) && !string.IsNullOrEmpty(filename)) _userPermissions.IsAuthorized(User, module.SiteId, EntityNames.Folder, folderid, PermissionNames.Edit) && !string.IsNullOrEmpty(filename))
@@ -278,7 +275,7 @@ namespace Oqtane.Controllers
} }
// create json file // create json file
filename = Path.GetFileNameWithoutExtension(filename) + ".json"; filename = Utilities.GetFriendlyUrl(Path.GetFileNameWithoutExtension(filename)) + ".json";
string filepath = Path.Combine(folderPath, filename); string filepath = Path.Combine(folderPath, filename);
if (System.IO.File.Exists(filepath)) if (System.IO.File.Exists(filepath))
{ {
@@ -298,9 +295,7 @@ namespace Oqtane.Controllers
file.Size = (int)new FileInfo(filepath).Length; file.Size = (int)new FileInfo(filepath).Length;
_files.UpdateFile(file); _files.UpdateFile(file);
} }
fileid = file.FileId;
result.Success = true;
result.Message = filename;
_logger.Log(LogLevel.Information, this, LogFunction.Read, "Content Exported For Module {ModuleId} To Folder {FolderId}", moduleid, folderid); _logger.Log(LogLevel.Information, this, LogFunction.Read, "Content Exported For Module {ModuleId} To Folder {FolderId}", moduleid, folderid);
} }
@@ -309,7 +304,8 @@ namespace Oqtane.Controllers
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Export Attempt For Module {Module} To Folder {FolderId}", moduleid, folderid); _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Export Attempt For Module {Module} To Folder {FolderId}", moduleid, folderid);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
} }
return result;
return fileid;
} }
// POST api/<controller>/import?moduleid=x&pageid=y // POST api/<controller>/import?moduleid=x&pageid=y

View File

@@ -131,7 +131,7 @@ namespace Oqtane.Controllers
filtered.TwoFactorCode = ""; filtered.TwoFactorCode = "";
filtered.SecurityStamp = ""; filtered.SecurityStamp = "";
// include private properties if authenticated user is accessing their own user account os is an administrator // include private properties if authenticated user is accessing their own user account or is an administrator
if (_userPermissions.IsAuthorized(User, user.SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin) || _userPermissions.GetUser(User).UserId == user.UserId) if (_userPermissions.IsAuthorized(User, user.SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin) || _userPermissions.GetUser(User).UserId == user.UserId)
{ {
filtered.Email = user.Email; filtered.Email = user.Email;
@@ -140,6 +140,7 @@ namespace Oqtane.Controllers
filtered.LastLoginOn = user.LastLoginOn; filtered.LastLoginOn = user.LastLoginOn;
filtered.LastIPAddress = user.LastIPAddress; filtered.LastIPAddress = user.LastIPAddress;
filtered.TwoFactorRequired = user.TwoFactorRequired; filtered.TwoFactorRequired = user.TwoFactorRequired;
filtered.EmailConfirmed = user.EmailConfirmed;
filtered.Roles = user.Roles; filtered.Roles = user.Roles;
filtered.CreatedBy = user.CreatedBy; filtered.CreatedBy = user.CreatedBy;
filtered.CreatedOn = user.CreatedOn; filtered.CreatedOn = user.CreatedOn;
@@ -200,10 +201,15 @@ namespace Oqtane.Controllers
[Authorize] [Authorize]
public async Task<User> Put(int id, [FromBody] User user) public async Task<User> Put(int id, [FromBody] User user)
{ {
if (ModelState.IsValid && user.SiteId == _tenantManager.GetAlias().SiteId && user.UserId == id && _users.GetUser(user.UserId, false) != null var existing = _userManager.GetUser(user.UserId, user.SiteId);
if (ModelState.IsValid && user.SiteId == _tenantManager.GetAlias().SiteId && user.UserId == id && existing != null
&& (_userPermissions.IsAuthorized(User, user.SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin) || User.Identity.Name == user.Username)) && (_userPermissions.IsAuthorized(User, user.SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin) || User.Identity.Name == user.Username))
{ {
user.EmailConfirmed = User.IsInRole(RoleNames.Admin); // only authorized users can update the email confirmation
if (!_userPermissions.IsAuthorized(User, user.SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin))
{
user.EmailConfirmed = existing.EmailConfirmed;
}
user = await _userManager.UpdateUser(user); user = await _userManager.UpdateUser(user);
} }
else else

View File

@@ -65,7 +65,12 @@ namespace Oqtane.Managers
{ {
user.SiteId = siteid; user.SiteId = siteid;
user.Roles = GetUserRoles(user.UserId, user.SiteId); user.Roles = GetUserRoles(user.UserId, user.SiteId);
user.SecurityStamp = _identityUserManager.FindByNameAsync(user.Username).GetAwaiter().GetResult()?.SecurityStamp; var identityuser = _identityUserManager.FindByNameAsync(user.Username).GetAwaiter().GetResult();
if (identityuser != null)
{
user.SecurityStamp = identityuser.SecurityStamp;
user.EmailConfirmed = identityuser.EmailConfirmed;
}
user.Settings = _settings.GetSettings(EntityNames.User, user.UserId) user.Settings = _settings.GetSettings(EntityNames.User, user.UserId)
.ToDictionary(setting => setting.SettingName, setting => setting.SettingValue); .ToDictionary(setting => setting.SettingName, setting => setting.SettingValue);
} }
@@ -245,22 +250,30 @@ namespace Oqtane.Managers
{ {
identityuser.Email = user.Email; identityuser.Email = user.Email;
await _identityUserManager.UpdateAsync(identityuser); // security stamp not updated await _identityUserManager.UpdateAsync(identityuser); // security stamp not updated
// if email address changed and it is not confirmed, verification is required for new email address
if (!user.EmailConfirmed)
{
string token = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser);
string url = alias.Protocol + alias.Name + "/login?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token);
string body = "Dear " + user.DisplayName + ",\n\nIn Order To Verify The Email Address Associated To Your User Account Please Click The Link Displayed Below:\n\n" + url + "\n\nThank You!";
var notification = new Notification(user.SiteId, user, "User Account Verification", body);
_notifications.AddNotification(notification);
}
} }
if (user.EmailConfirmed) if (user.EmailConfirmed)
{ {
var emailConfirmationToken = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser); if (!identityuser.EmailConfirmed)
await _identityUserManager.ConfirmEmailAsync(identityuser, emailConfirmationToken); {
var emailConfirmationToken = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser);
await _identityUserManager.ConfirmEmailAsync(identityuser, emailConfirmationToken);
string body = "Dear " + user.DisplayName + ",\n\nThe Email Address For Your User Account Has Been Verified. You Can Now Login With Your Username And Password.";
var notification = new Notification(user.SiteId, user, "User Account Verification", body);
_notifications.AddNotification(notification);
}
}
else
{
identityuser.EmailConfirmed = false;
await _identityUserManager.UpdateAsync(identityuser); // security stamp not updated
string token = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser);
string url = alias.Protocol + alias.Name + "/login?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token);
string body = "Dear " + user.DisplayName + ",\n\nIn Order To Verify The Email Address Associated To Your User Account Please Click The Link Displayed Below:\n\n" + url + "\n\nThank You!";
var notification = new Notification(user.SiteId, user, "User Account Verification", body);
_notifications.AddNotification(notification);
} }
user = _users.UpdateUser(user); user = _users.UpdateUser(user);

View File

@@ -43,7 +43,7 @@ namespace Oqtane.Models
public string Path { get; set; } public string Path { get; set; }
/// <summary> /// <summary>
/// Sorting order of the folder /// Sorting order of the folder ** not used as folders are sorted in alphabetical order **
/// </summary> /// </summary>
public int Order { get; set; } public int Order { get; set; }