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

This commit is contained in:
Leigh Pointer
2025-08-06 10:44:26 +02:00
13 changed files with 220 additions and 152 deletions

View File

@@ -153,7 +153,7 @@
<br />
<button type="button" class="btn btn-success" @onclick="SaveUser">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin) && PageState.Runtime != Shared.Runtime.Hybrid && !_ishost)
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin) && PageState.Runtime != Shared.Runtime.Hybrid && !_ishost && _isdeleted != "True")
{
<button type="button" class="btn btn-primary ms-1" @onclick="ImpersonateUser">@Localizer["Impersonate"]</button>
}

View File

@@ -17,8 +17,21 @@ else
{
<TabStrip>
<TabPanel Name="Users" Heading="Users" ResourceKey="Users">
<ActionLink Action="Add" Text="Add User" Security="SecurityAccessLevel.Edit" ResourceKey="AddUser" />&nbsp;
<ActionLink Text="Import Users" Class="btn btn-secondary ms-2" Action="Users" Security="SecurityAccessLevel.Admin" ResourceKey="ImportUsers"/>
<div class="container">
<div class="row mb-1 align-items-center">
<div class="col-sm-6">
<ActionLink Action="Add" Text="Add User" Security="SecurityAccessLevel.Edit" ResourceKey="AddUser" />&nbsp;
<ActionLink Text="Import Users" Class="btn btn-secondary ms-2" Action="Users" Security="SecurityAccessLevel.Admin" ResourceKey="ImportUsers" />
</div>
<div class="col-sm-6">
<select id="deleted" class="form-select custom-select" value="@_deleted" @onchange="(e => DeletedChanged(e))">
<option value="false">@Localizer["Active Users"]</option>
<option value="true">@Localizer["Deleted Users"]</option>
</select>
</div>
</div>
</div>
<br />
<Pager Items="@users" RowClass="align-middle" SearchProperties="User.Username,User.Email,User.DisplayName">
<Header>
@@ -495,6 +508,7 @@ else
@code {
private List<UserRole> users;
private string _deleted = "false";
private string _allowregistration;
private string _registerurl;
@@ -564,7 +578,7 @@ else
protected override async Task OnInitializedAsync()
{
await LoadUsersAsync(true);
await LoadUsersAsync();
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
_allowregistration = PageState.Site.AllowRegistration.ToString().ToLower();
@@ -636,20 +650,32 @@ else
_allowsitelogin = SettingService.GetSetting(settings, "LoginOptions:AllowSiteLogin", "true");
}
private async Task LoadUsersAsync(bool load)
private async Task LoadUsersAsync()
{
if (load)
users = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Registered);
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
users = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Registered);
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
var hosts = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Host);
users.AddRange(hosts);
users = users.OrderBy(u => u.User.DisplayName).ToList();
}
var hosts = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Host);
users.AddRange(hosts);
users = users.OrderBy(u => u.User.DisplayName).ToList();
}
users = users.Where(item => item.User.IsDeleted == bool.Parse(_deleted)).ToList();
}
private async void DeletedChanged(ChangeEventArgs e)
{
try
{
_deleted = e.Value.ToString();
await LoadUsersAsync();
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error On DeletedChanged");
}
}
private async Task DeleteUser(UserRole UserRole)
{
try
@@ -672,7 +698,7 @@ else
await logger.LogInformation("User {Username} Expired From Role {Role}", userrole.User.Username, userrole.Role.Name);
}
AddModuleMessage(Localizer["Success.DeleteUser"], MessageType.Success);
await LoadUsersAsync(true);
await LoadUsersAsync();
StateHasChanged();
}
catch (Exception ex)

View File

@@ -107,7 +107,7 @@
@code {
private bool _initialized = false;
private List<Folder> _folders;
private List<Folder> _folders = new List<Folder>();
private List<File> _files = new List<File>();
private string _fileinputid = string.Empty;
private string _progressinfoid = string.Empty;
@@ -198,19 +198,22 @@
Filter = "nupkg";
ShowSuccess = true;
}
if (!string.IsNullOrEmpty(Folder) && Folder != Constants.PackagesFolder)
else
{
Folder folder = await FolderService.GetFolderAsync(ModuleState.SiteId, Folder);
if (folder != null)
// folder path specified rather than folderid
if (!string.IsNullOrEmpty(Folder))
{
FolderId = folder.FolderId;
}
else
{
FolderId = -1;
_message = "Folder Path " + Folder + " Does Not Exist";
_messagetype = MessageType.Error;
Folder folder = await FolderService.GetFolderAsync(ModuleState.SiteId, Folder);
if (folder != null)
{
FolderId = folder.FolderId;
}
else
{
FolderId = -1;
_message = "Folder Path " + Folder + " Does Not Exist";
_messagetype = MessageType.Error;
}
}
}
@@ -245,25 +248,24 @@
}
}
await SetImage();
if (!string.IsNullOrEmpty(Filter))
{
_filter = "." + Filter.Replace(",", ",.");
}
GetFolderPermission();
await SetImage();
await GetFiles();
_initialized = true;
}
private async Task GetFiles()
private void GetFolderPermission()
{
_haseditpermission = false;
if (Folder == Constants.PackagesFolder)
{
_haseditpermission = UserSecurity.IsAuthorized(PageState.User, RoleNames.Host);
_files = new List<File>();
}
else
{
@@ -271,69 +273,14 @@
if (folder != null)
{
_haseditpermission = UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, folder.PermissionList);
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Browse, folder.PermissionList))
{
_files = await FileService.GetFilesAsync(FolderId);
}
else
{
_files = new List<File>();
}
}
else
{
_haseditpermission = false;
_files = new List<File>();
}
if (_filter != "*")
{
List<File> filtered = new List<File>();
foreach (File file in _files)
{
if (_filter.ToUpper().IndexOf("." + file.Extension.ToUpper()) != -1)
{
filtered.Add(file);
}
}
_files = filtered;
}
}
}
private async Task FolderChanged(ChangeEventArgs e)
{
_message = string.Empty;
try
{
FolderId = int.Parse((string)e.Value);
await GetFiles();
FileId = -1;
_file = null;
_image = string.Empty;
await OnSelectFolder.InvokeAsync(FolderId);
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Files {Error}", ex.Message);
_message = Localizer["Error.File.Load"];
_messagetype = MessageType.Error;
}
}
private async Task FileChanged(ChangeEventArgs e)
{
_message = string.Empty;
FileId = int.Parse((string)e.Value);
await SetImage();
#pragma warning disable CS0618
await OnSelect.InvokeAsync(FileId);
#pragma warning restore CS0618
await OnSelectFile.InvokeAsync(FileId);
StateHasChanged();
}
private async Task SetImage()
{
_image = string.Empty;
@@ -357,6 +304,74 @@
}
}
private async Task GetFiles()
{
if (ShowFiles)
{
Folder folder = _folders.FirstOrDefault(item => item.FolderId == FolderId);
if (folder != null)
{
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Browse, folder.PermissionList))
{
_files = await FileService.GetFilesAsync(FolderId);
}
else
{
_files = new List<File>();
}
}
else
{
_files = new List<File>();
}
if (_filter != "*")
{
List<File> filtered = new List<File>();
foreach (File file in _files)
{
if (_filter.ToUpper().IndexOf("." + file.Extension.ToUpper()) != -1)
{
filtered.Add(file);
}
}
_files = filtered;
}
}
}
private async Task FolderChanged(ChangeEventArgs e)
{
_message = string.Empty;
try
{
FolderId = int.Parse((string)e.Value);
await OnSelectFolder.InvokeAsync(FolderId);
FileId = -1;
GetFolderPermission();
await SetImage();
await GetFiles();
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Files {Error}", ex.Message);
_message = Localizer["Error.File.Load"];
_messagetype = MessageType.Error;
}
}
private async Task FileChanged(ChangeEventArgs e)
{
_message = string.Empty;
FileId = int.Parse((string)e.Value);
#pragma warning disable CS0618
await OnSelect.InvokeAsync(FileId);
#pragma warning restore CS0618
await OnSelectFile.InvokeAsync(FileId);
await SetImage();
StateHasChanged();
}
private async Task UploadFiles()
{
_message = string.Empty;
@@ -433,6 +448,27 @@
_message = Localizer["Success.File.Upload"];
_messagetype = MessageType.Success;
}
FileId = -1;
if (Folder != Constants.PackagesFolder && !AnonymizeUploadFilenames)
{
// set FileId to first file in upload collection
var file = await FileService.GetFileAsync(int.Parse(folder), uploads[0].Split(":")[0]);
if (file != null)
{
FileId = file.FileId;
}
}
await OnUpload.InvokeAsync(FileId);
#pragma warning disable CS0618
await OnSelect.InvokeAsync(FileId);
#pragma warning restore CS0618
await OnSelectFile.InvokeAsync(FileId);
await SetImage();
await GetFiles();
StateHasChanged();
}
else
{
@@ -440,28 +476,6 @@
_message = Localizer["Error.File.Upload"];
_messagetype = MessageType.Error;
}
if (Folder == Constants.PackagesFolder)
{
await OnUpload.InvokeAsync(-1);
}
else
{
// set FileId to first file in upload collection
var file = await FileService.GetFileAsync(int.Parse(folder), uploads[0].Split(":")[0]);
if (file != null)
{
FileId = file.FileId;
await SetImage();
#pragma warning disable CS0618
await OnSelect.InvokeAsync(FileId);
#pragma warning restore CS0618
await OnSelectFile.InvokeAsync(FileId);
await OnUpload.InvokeAsync(FileId);
}
await GetFiles();
StateHasChanged();
}
}
catch (Exception ex)
{
@@ -474,7 +488,6 @@
finally {
tokenSource.Dispose();
}
}
else
{
@@ -504,47 +517,49 @@
_messagetype = MessageType.Success;
}
await GetFiles();
FileId = -1;
await SetImage();
FileId = -1;
#pragma warning disable CS0618
await OnSelect.InvokeAsync(FileId);
#pragma warning restore CS0618
await OnSelectFile.InvokeAsync(FileId);
await SetImage();
await GetFiles();
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Deleting File {File} {Error}", FileId, ex.Message);
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Deleting File {File} {Error}", FileId, ex.Message);
_message = Localizer["Error.File.Delete"];
_messagetype = MessageType.Error;
}
}
_message = Localizer["Error.File.Delete"];
_messagetype = MessageType.Error;
}
}
public int GetFileId() => FileId;
public int GetFileId() => FileId;
public int GetFolderId() => FolderId;
public int GetFolderId() => FolderId;
public File GetFile() => _file;
public File GetFile() => _file;
public async Task Refresh()
{
await Refresh(-1);
}
public async Task Refresh()
{
await Refresh(-1);
}
public async Task Refresh(int fileId)
{
await GetFiles();
if (fileId != -1)
public async Task Refresh(int fileId)
{
await GetFiles();
FileId = -1;
if (fileId != -1)
{
var file = _files.Where(item => item.FileId == fileId).FirstOrDefault();
if (file != null)
{
FileId = file.FileId;
await SetImage();
}
}
StateHasChanged();
await SetImage();
StateHasChanged();
}
}

View File

@@ -537,4 +537,10 @@
<data name="AllowHostRole.HelpText" xml:space="preserve">
<value>Indicate if host roles are supported from the identity provider. Please use caution with this option as it allows the host user to administrate every site within your installation.</value>
</data>
<data name="Active Users" xml:space="preserve">
<value>Active Users</value>
</data>
<data name="Deleted Users" xml:space="preserve">
<value>Deleted Users</value>
</data>
</root>

View File

@@ -64,8 +64,6 @@
protected override void OnParametersSet()
{
_messageContent = "";
if (ShouldRender())
{
if (!string.IsNullOrEmpty(ModuleState.ModuleType))
@@ -107,7 +105,7 @@
public void AddModuleMessage(string message, MessageType type, string position)
{
if(message != _messageContent
if (message != _messageContent
|| type != _messageType
|| position != _messagePosition)
{

View File

@@ -48,7 +48,7 @@
private bool _initialized = false;
private bool _installed = false;
private string _display = "display: none;"; // prevents flash on initial interactive page load when using prerendering
private string _display = "display: none;"; // prevents flash on initial interactive page load
private PageState _pageState { get; set; }
@@ -83,10 +83,10 @@
protected override void OnAfterRender(bool firstRender)
{
if (firstRender && _display == "display: none;")
if (firstRender)
{
_display = "";
StateHasChanged(); // required or else the UI will not refresh
StateHasChanged();
}
}

View File

@@ -298,7 +298,7 @@ namespace Oqtane.Controllers
if (!authorized)
{
var visitorCookieName = Constants.VisitorCookiePrefix + _alias.SiteId.ToString();
authorized = (entityId == GetVisitorCookieId(Request.Cookies[visitorCookieName]));
authorized = (entityId == GetVisitorCookieId(HttpContext.Request.Cookies[visitorCookieName]));
}
break;
default: // custom entity
@@ -352,9 +352,14 @@ namespace Oqtane.Controllers
private int GetVisitorCookieId(string visitorCookie)
{
// visitor cookies contain the visitor id and an expiry date separated by a pipe symbol
visitorCookie = (visitorCookie.Contains("|")) ? visitorCookie.Split('|')[0] : visitorCookie;
return (int.TryParse(visitorCookie, out int visitorId)) ? visitorId : -1;
var visitorId = -1;
if (visitorCookie != null)
{
// visitor cookies now contain the visitor id and an expiry date separated by a pipe symbol
visitorCookie = (visitorCookie.Contains("|")) ? visitorCookie.Split('|')[0] : visitorCookie;
visitorId = int.TryParse(visitorCookie, out int _visitorId) ? _visitorId : -1;
}
return visitorId;
}
private void AddSyncEvent(string EntityName, int EntityId, int SettingId, string Action)

View File

@@ -77,9 +77,14 @@ namespace Oqtane.Controllers
private int GetVisitorCookieId(string visitorCookie)
{
// visitor cookies contain the visitor id and an expiry date separated by a pipe symbol
visitorCookie = (visitorCookie.Contains("|")) ? visitorCookie.Split('|')[0] : visitorCookie;
return (int.TryParse(visitorCookie, out int visitorId)) ? visitorId : -1;
var visitorId = -1;
if (visitorCookie != null)
{
// visitor cookies now contain the visitor id and an expiry date separated by a pipe symbol
visitorCookie = (visitorCookie.Contains("|")) ? visitorCookie.Split('|')[0] : visitorCookie;
visitorId = int.TryParse(visitorCookie, out int _visitorId) ? _visitorId : -1;
}
return visitorId;
}
}
}

View File

@@ -257,7 +257,7 @@ namespace Microsoft.Extensions.DependencyInjection
// set the cookies to allow HttpClient API calls to be authenticated
foreach (var cookie in httpContextAccessor.HttpContext.Request.Cookies)
{
client.DefaultRequestHeaders.Add("Cookie", cookie.Key + "=" + cookie.Value);
client.DefaultRequestHeaders.Add("Cookie", cookie.Key + "=" + WebUtility.UrlEncode(cookie.Value));
}
}
@@ -275,7 +275,7 @@ namespace Microsoft.Extensions.DependencyInjection
// set the cookies to allow HttpClient API calls to be authenticated
foreach (var cookie in httpContextAccessor.HttpContext.Request.Cookies)
{
client.DefaultRequestHeaders.Add("Cookie", cookie.Key + "=" + cookie.Value);
client.DefaultRequestHeaders.Add("Cookie", cookie.Key + "=" + WebUtility.UrlEncode(cookie.Value));
}
}
});

View File

@@ -261,7 +261,8 @@ namespace Oqtane.Managers
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.";
string url = alias.Protocol + alias.Name + "/login?name=" + user.Username;
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 Using The Link Displayed Below:\n\n" + url + "\n\nThank You!";
var notification = new Notification(user.SiteId, user, "User Account Verification", body);
_notifications.AddNotification(notification);
}

View File

@@ -36,7 +36,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="9.0.7" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="9.0.7" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.0.2" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.7" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.7">
<PrivateAssets>all</PrivateAssets>
@@ -46,7 +46,7 @@
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="9.0.7" />
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="9.0.7" />
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.11" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.10" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.11" />
<PackageReference Include="HtmlAgilityPack" Version="1.12.2" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.3" />
<PackageReference Include="MailKit" Version="4.13.0" />

View File

@@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Oqtane.Enums;
using Oqtane.Extensions;
using Oqtane.Infrastructure;
using Oqtane.Managers;
@@ -16,11 +17,13 @@ namespace Oqtane.Pages
{
private readonly IUserManager _userManager;
private readonly ISyncManager _syncManager;
private readonly ILogManager _logger;
public LogoutModel(IUserManager userManager, ISyncManager syncManager)
public LogoutModel(IUserManager userManager, ISyncManager syncManager, ILogManager logger)
{
_userManager = userManager;
_syncManager = syncManager;
_logger = logger;
}
public async Task<IActionResult> OnPostAsync(string returnurl, string everywhere)
@@ -37,6 +40,7 @@ namespace Oqtane.Pages
}
_syncManager.AddSyncEvent(alias, EntityNames.User, user.UserId, "Logout");
_syncManager.AddSyncEvent(alias, EntityNames.User, user.UserId, SyncEventActions.Reload);
_logger.Log(LogLevel.Information, this, LogFunction.Security, "User Logout For Username {Username}", user.Username);
}
await HttpContext.SignOutAsync(Constants.AuthenticationScheme);

View File

@@ -68,6 +68,7 @@ namespace Oqtane.Repository
public UrlMapping GetUrlMapping(int siteId, string url)
{
using var db = _dbContextFactory.CreateDbContext();
url = (url.StartsWith("/")) ? url.Substring(1) : url;
url = (url.Length > 750) ? url.Substring(0, 750) : url;
var urlMapping = db.UrlMapping.Where(item => item.SiteId == siteId && item.Url == url).FirstOrDefault();
if (urlMapping == null)
@@ -82,7 +83,14 @@ namespace Oqtane.Repository
urlMapping.Requests = 1;
urlMapping.CreatedOn = DateTime.UtcNow;
urlMapping.RequestedOn = DateTime.UtcNow;
urlMapping = AddUrlMapping(urlMapping);
try
{
urlMapping = AddUrlMapping(urlMapping);
}
catch
{
// ignore duplicate key exception which can be caused by a race condition
}
}
}
else