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

This commit is contained in:
Leigh Pointer 2023-02-16 12:05:02 +01:00
commit c49af06e57
116 changed files with 1133 additions and 985 deletions

View File

@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2018-2022 .NET Foundation Copyright (c) 2018-2023 .NET Foundation
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@ -49,7 +49,6 @@ namespace Microsoft.Extensions.DependencyInjection
services.AddScoped<IUrlMappingService, UrlMappingService>(); services.AddScoped<IUrlMappingService, UrlMappingService>();
services.AddScoped<IVisitorService, VisitorService>(); services.AddScoped<IVisitorService, VisitorService>();
services.AddScoped<ISyncService, SyncService>(); services.AddScoped<ISyncService, SyncService>();
services.AddScoped<IApiService, ApiService>();
return services; return services;
} }

View File

@ -1,75 +0,0 @@
@namespace Oqtane.Modules.Admin.Apis
@inherits ModuleBase
@inject IApiService ApiService
@inject NavigationManager NavigationManager
@inject IStringLocalizer<Edit> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="entityname" HelpText="The name of the entity" ResourceKey="EntityName">Entity: </Label>
<div class="col-sm-9">
<input id="entityname" class="form-control" @bind="@_entityname" readonly />
</div>
</div>
<br />
<div class="row mb-1 align-items-center">
@if (_permissions != null)
{
<PermissionGrid EntityName="@_entityname" PermissionNames="@_permissionnames" Permissions="@_permissions" @ref="_permissionGrid" />
}
</div>
</div>
<button type="button" class="btn btn-success" @onclick="SaveModuleDefinition">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
@code {
private string _entityname;
private string _permissionnames;
private string _permissions;
#pragma warning disable 649
private PermissionGrid _permissionGrid;
#pragma warning restore 649
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
protected override async Task OnInitializedAsync()
{
try
{
_entityname = PageState.QueryString["entity"];
var api = await ApiService.GetApiAsync(PageState.Site.SiteId, _entityname);
if (api != null)
{
var apis = await ApiService.GetApisAsync(PageState.Site.SiteId);
_permissionnames = apis.SingleOrDefault(item => item.EntityName == _entityname).Permissions;
_permissions = api.Permissions;
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading API {EntityName} {Error}", _entityname, ex.Message);
AddModuleMessage(Localizer["Error.Module.Load"], MessageType.Error);
}
}
private async Task SaveModuleDefinition()
{
try
{
var api = new Api();
api.SiteId = PageState.Site.SiteId;
api.EntityName = _entityname;
api.Permissions = _permissionGrid.GetPermissions();
await ApiService.UpdateApiAsync(api);
await logger.LogInformation("API Saved {Api}", api);
NavigationManager.NavigateTo(NavigateUrl());
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Saving Api {EntityName} {Error}", _entityname, ex.Message);
AddModuleMessage(Localizer["Error.Module.Save"], MessageType.Error);
}
}
}

View File

@ -1,36 +0,0 @@
@namespace Oqtane.Modules.Admin.Apis
@inherits ModuleBase
@inject IApiService ApiService
@inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_apis == null)
{
<p><em>@SharedLocalizer["Loading"]</em></p>
}
else
{
<Pager Items="@_apis">
<Header>
<th style="width: 1px;">&nbsp;</th>
<th>@Localizer["Entity"]</th>
<th>@Localizer["Permissions"]</th>
</Header>
<Row>
<td><ActionLink Action="Edit" Parameters="@($"entity=" + context.EntityName)" ResourceKey="Edit" /></td>
<td>@context.EntityName</td>
<td>@context.Permissions</td>
</Row>
</Pager>
}
@code {
private List<Api> _apis;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
protected override async Task OnParametersSetAsync()
{
_apis = await ApiService.GetApisAsync(PageState.Site.SiteId);
}
}

View File

@ -22,11 +22,14 @@
@code { @code {
private List<Page> _pages; private List<Page> _pages;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous;
protected override void OnInitialized() protected override void OnInitialized()
{ {
var admin = PageState.Pages.FirstOrDefault(item => item.Path == "admin"); var admin = PageState.Pages.FirstOrDefault(item => item.Path == "admin");
if (admin != null)
{
_pages = PageState.Pages.Where(item => item.ParentId == admin?.PageId).ToList(); _pages = PageState.Pages.Where(item => item.ParentId == admin?.PageId).ToList();
} }
}
} }

View File

@ -1,3 +1,4 @@
@using System.Net
@namespace Oqtane.Modules.Admin.Login @namespace Oqtane.Modules.Admin.Login
@inherits ModuleBase @inherits ModuleBase
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@ -205,7 +206,7 @@
var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider
.GetService(typeof(IdentityAuthenticationStateProvider)); .GetService(typeof(IdentityAuthenticationStateProvider));
authstateprovider.NotifyAuthenticationChanged(); authstateprovider.NotifyAuthenticationChanged();
NavigationManager.NavigateTo(NavigateUrl(_returnUrl, true)); NavigationManager.NavigateTo(NavigateUrl(WebUtility.UrlDecode(_returnUrl), true));
} }
else else
{ {

View File

@ -106,12 +106,6 @@ else
{ {
try try
{ {
// external link to log item will display Details component
if (PageState.QueryString.ContainsKey("id") && int.TryParse(PageState.QueryString["id"], out int id))
{
NavigationManager.NavigateTo(EditUrl(PageState.Page.Path, ModuleState.ModuleId, "Detail", $"/{id}"));
}
if (UrlParameters.ContainsKey("level")) if (UrlParameters.ContainsKey("level"))
{ {
_level = UrlParameters["level"]; _level = UrlParameters["level"];
@ -241,4 +235,15 @@ else
_page = page; _page = page;
} }
protected override void OnAfterRender(bool firstRender)
{
if (firstRender)
{
// external link to log item will display Details component
if (PageState.QueryString.ContainsKey("id") && int.TryParse(PageState.QueryString["id"], out int id))
{
NavigationManager.NavigateTo(EditUrl(PageState.Page.Path, ModuleState.ModuleId, "Detail", $"/{id}"));
}
}
}
} }

View File

@ -131,7 +131,7 @@ else
moduleDefinition = await ModuleDefinitionService.CreateModuleDefinitionAsync(moduleDefinition); moduleDefinition = await ModuleDefinitionService.CreateModuleDefinitionAsync(moduleDefinition);
var settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId); var settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId);
SettingService.SetSetting(settings, "ModuleDefinitionName", moduleDefinition.ModuleDefinitionName); settings = SettingService.SetSetting(settings, "ModuleDefinitionName", moduleDefinition.ModuleDefinitionName);
await SettingService.UpdateModuleSettingsAsync(settings, ModuleState.ModuleId); await SettingService.UpdateModuleSettingsAsync(settings, ModuleState.ModuleId);
GetLocation(); GetLocation();
@ -174,7 +174,7 @@ else
private bool IsValid(string name) private bool IsValid(string name)
{ {
// must contain letters, underscores and digits and first character must be letter or underscore // must contain letters, underscores and digits and first character must be letter or underscore
return !string.IsNullOrEmpty(name) && name.ToLower() != "module" && Regex.IsMatch(name, "^[A-Za-z_][A-Za-z0-9_]*$"); return !string.IsNullOrEmpty(name) && name.ToLower() != "module" && !name.ToLower().Contains("oqtane") && Regex.IsMatch(name, "^[A-Za-z_][A-Za-z0-9_]*$");
} }
private void TemplateChanged(ChangeEventArgs e) private void TemplateChanged(ChangeEventArgs e)

View File

@ -139,7 +139,7 @@
private bool IsValid(string name) private bool IsValid(string name)
{ {
// must contain letters, underscores and digits and first character must be letter or underscore // must contain letters, underscores and digits and first character must be letter or underscore
return !string.IsNullOrEmpty(name) && name.ToLower() != "module" && Regex.IsMatch(name, "^[A-Za-z_][A-Za-z0-9_]*$"); return !string.IsNullOrEmpty(name) && name.ToLower() != "module" && !name.ToLower().Contains("oqtane") && Regex.IsMatch(name, "^[A-Za-z_][A-Za-z0-9_]*$");
} }
private void TemplateChanged(ChangeEventArgs e) private void TemplateChanged(ChangeEventArgs e)

View File

@ -157,6 +157,7 @@
</TabPanel> </TabPanel>
} }
</TabStrip> </TabStrip>
<br />
<button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button> <button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button> <button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
</form> </form>

View File

@ -148,7 +148,8 @@
</div> </div>
</div> </div>
</Section> </Section>
<br /><br /> <br />
<br />
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon" DeletedBy="@_deletedby" DeletedOn="@_deletedon"></AuditInfo> <AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon" DeletedBy="@_deletedby" DeletedOn="@_deletedon"></AuditInfo>
} }
</TabPanel> </TabPanel>
@ -189,6 +190,7 @@
<br /> <br />
} }
</TabStrip> </TabStrip>
<br />
<button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button> <button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button> <button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
</form> </form>

View File

@ -0,0 +1,20 @@
using Oqtane.Documentation;
using Oqtane.Models;
using Oqtane.Shared;
namespace Oqtane.Modules.Admin.Profiles
{
[PrivateApi("Mark this as private, since it's not very useful in the public docs")]
public class ModuleInfo : IModule
{
public ModuleDefinition ModuleDefinition => new ModuleDefinition
{
Name = "Profiles",
Description = "Manage Profiles",
Categories = "Admin",
Version = Constants.Version,
PermissionNames = $"{PermissionNames.View},{PermissionNames.Edit}," +
$"{EntityNames.Profile}:{PermissionNames.Write}:{RoleNames.Admin}"
};
}
}

View File

@ -42,7 +42,7 @@
private string _description = string.Empty; private string _description = string.Empty;
private string _isautoassigned = "False"; private string _isautoassigned = "False";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
private async Task SaveRole() private async Task SaveRole()
{ {

View File

@ -49,7 +49,7 @@
private string _modifiedby; private string _modifiedby;
private DateTime _modifiedon; private DateTime _modifiedon;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {

View File

@ -10,7 +10,7 @@
} }
else else
{ {
<ActionLink Action="Add" Text="Add Role" ResourceKey="AddRole" /> <ActionLink Action="Add" Text="Add Role" Security="SecurityAccessLevel.Edit" ResourceKey="AddRole" />
<Pager Items="@_roles"> <Pager Items="@_roles">
<Header> <Header>
@ -20,9 +20,9 @@ else
<th>@SharedLocalizer["Name"]</th> <th>@SharedLocalizer["Name"]</th>
</Header> </Header>
<Row> <Row>
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.RoleId.ToString())" Disabled="@(context.IsSystem)" ResourceKey="Edit" /></td> <td><ActionLink Action="Edit" Parameters="@($"id=" + context.RoleId.ToString())" Security="SecurityAccessLevel.Edit" Disabled="@(context.IsSystem)" ResourceKey="Edit" /></td>
<td><ActionDialog Header="Delete Role" Message="@string.Format(Localizer["Confirm.DeleteUser"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteRole(context))" Disabled="@(context.IsSystem)" ResourceKey="DeleteRole" /></td> <td><ActionDialog Header="Delete Role" Message="@string.Format(Localizer["Confirm.DeleteUser"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await DeleteRole(context))" Disabled="@(context.IsSystem)" ResourceKey="DeleteRole" /></td>
<td><ActionLink Action="Users" Parameters="@($"id=" + context.RoleId.ToString())" ResourceKey="Users" /></td> <td><ActionLink Action="Users" Parameters="@($"id=" + context.RoleId.ToString())" Security="SecurityAccessLevel.Edit" ResourceKey="Users" /></td>
<td>@context.Name</td> <td>@context.Name</td>
</Row> </Row>
</Pager> </Pager>
@ -31,7 +31,7 @@ else
@code { @code {
private List<Role> _roles; private List<Role> _roles;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
protected override async Task OnParametersSetAsync() protected override async Task OnParametersSetAsync()
{ {

View File

@ -0,0 +1,21 @@
using Oqtane.Documentation;
using Oqtane.Models;
using Oqtane.Shared;
namespace Oqtane.Modules.Admin.Roles
{
[PrivateApi("Mark this as private, since it's not very useful in the public docs")]
public class ModuleInfo : IModule
{
public ModuleDefinition ModuleDefinition => new ModuleDefinition
{
Name = "Roles",
Description = "Manage Roles",
Categories = "Admin",
Version = Constants.Version,
PermissionNames = $"{PermissionNames.View},{PermissionNames.Edit}," +
$"{EntityNames.Role}:{PermissionNames.Write}:{RoleNames.Admin}," +
$"{EntityNames.UserRole}:{PermissionNames.Write}:{RoleNames.Admin}"
};
}
}

View File

@ -23,13 +23,7 @@ else
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="user" HelpText="Select a user" ResourceKey="User">User: </Label> <Label Class="col-sm-3" For="user" HelpText="Select a user" ResourceKey="User">User: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
<select id="user" class="form-select" @bind="@userid" required> <AutoComplete OnSearch="GetUsers" Placeholder="@Localizer["User.Select"]" @ref="user" />
<option value="-1">&lt;@Localizer["User.Select"]&gt;</option>
@foreach (UserRole userrole in users)
{
<option value="@(userrole.UserId)">@userrole.User.DisplayName</option>
}
</select>
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
@ -64,7 +58,7 @@ else
<td>@context.EffectiveDate</td> <td>@context.EffectiveDate</td>
<td>@context.ExpiryDate</td> <td>@context.ExpiryDate</td>
<td> <td>
<ActionDialog Header="Remove User" Message="@string.Format(Localizer["Confirm.User.DeleteRole"], context.User.DisplayName)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteUserRole(context.UserRoleId))" Disabled="@(context.Role.IsAutoAssigned || PageState.User.Username == UserNames.Host)" ResourceKey="DeleteUserRole" /> <ActionDialog Header="Remove User" Message="@string.Format(Localizer["Confirm.User.DeleteRole"], context.User.DisplayName)" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await DeleteUserRole(context.UserRoleId))" Disabled="@(context.Role.IsAutoAssigned || context.User.Username == UserNames.Host || context.User.UserId == PageState.User.UserId)" ResourceKey="DeleteUserRole" />
</td> </td>
</Row> </Row>
</Pager> </Pager>
@ -80,13 +74,12 @@ else
private int roleid; private int roleid;
private string name = string.Empty; private string name = string.Empty;
private List<UserRole> users; private AutoComplete user;
private int userid = -1;
private DateTime? effectivedate = null; private DateTime? effectivedate = null;
private DateTime? expirydate = null; private DateTime? expirydate = null;
private List<UserRole> userroles; private List<UserRole> userroles;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
@ -95,7 +88,6 @@ else
roleid = Int32.Parse(PageState.QueryString["id"]); roleid = Int32.Parse(PageState.QueryString["id"]);
Role role = await RoleService.GetRoleAsync(roleid); Role role = await RoleService.GetRoleAsync(roleid);
name = role.Name; name = role.Name;
users = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Registered);
await GetUserRoles(); await GetUserRoles();
} }
catch (Exception ex) catch (Exception ex)
@ -105,6 +97,22 @@ else
} }
} }
private async Task<Dictionary<string, string>> GetUsers(string filter)
{
try
{
var users = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Registered);
return users.Where(item => item.User.DisplayName.Contains(filter, StringComparison.OrdinalIgnoreCase))
.ToDictionary(item => item.UserId.ToString(), item => item.User.DisplayName);
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Users {filter} {Error}", filter, ex.Message);
AddModuleMessage(Localizer["Error.User.Load"], MessageType.Error);
}
return new Dictionary<string, string>();
}
private async Task GetUserRoles() private async Task GetUserRoles()
{ {
try try
@ -126,7 +134,7 @@ else
{ {
try try
{ {
if (userid != -1) if (!string.IsNullOrEmpty(user.Key) && int.TryParse(user.Key, out int userid))
{ {
var userrole = userroles.Where(item => item.UserId == userid && item.RoleId == roleid).FirstOrDefault(); var userrole = userroles.Where(item => item.UserId == userid && item.RoleId == roleid).FirstOrDefault();
if (userrole != null) if (userrole != null)
@ -149,6 +157,7 @@ else
await logger.LogInformation("User Assigned To Role {UserRole}", userrole); await logger.LogInformation("User Assigned To Role {UserRole}", userrole);
AddModuleMessage(Localizer["Success.User.AssignedRole"], MessageType.Success); AddModuleMessage(Localizer["Success.User.AssignedRole"], MessageType.Success);
await GetUserRoles(); await GetUserRoles();
user.Clear();
StateHasChanged(); StateHasChanged();
} }
else else

View File

@ -127,7 +127,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="username" HelpText="Enter the username for your SMTP account" ResourceKey="SmptUsername">Username: </Label> <Label Class="col-sm-3" For="username" HelpText="Enter the username for your SMTP account" ResourceKey="SmtpUsername">Username: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
<input id="username" class="form-control" @bind="@_smtpusername" /> <input id="username" class="form-control" @bind="@_smtpusername" />
</div> </div>
@ -142,10 +142,19 @@
</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="sender" HelpText="Enter the email which emails will be sent from. Please note that this email address may need to be authorized with the SMTP server." ResourceKey="SmptSender">Email Sender: </Label> <Label Class="col-sm-3" For="sender" HelpText="Enter the email which emails will be sent from. Please note that this email address may need to be authorized with the SMTP server." ResourceKey="SmtpSender">Email Sender: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
<input id="sender" class="form-control" @bind="@_smtpsender" /> <input id="sender" class="form-control" @bind="@_smtpsender" />
</div> </div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="relay" HelpText="Only specify this option if you have properly configured an SMTP Relay Service to route your outgoing mail. This option will send notifications from the user's email rather than from the Email Sender specified above." ResourceKey="SmtpRelay">Relay Configured? </Label>
<div class="col-sm-9">
<select id="relay" class="form-select" @bind="@_smtprelay" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</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="retention" HelpText="Number of days of notifications to retain" ResourceKey="Retention">Retention (Days): </Label> <Label Class="col-sm-3" For="retention" HelpText="Number of days of notifications to retain" ResourceKey="Retention">Retention (Days): </Label>
@ -275,7 +284,10 @@
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="connectionstring" HelpText="The connection information for the database" ResourceKey="ConnectionString">Connection: </Label> <Label Class="col-sm-3" For="connectionstring" HelpText="The connection information for the database" ResourceKey="ConnectionString">Connection: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
<textarea id="connectionstring" class="form-control" @bind="@_connectionstring" rows="2" readonly></textarea> <div class="input-group">
<input id="connectionstring" type="@_connectionstringtype" class="form-control" @bind="@_connectionstring" readonly />
<button type="button" class="btn btn-secondary" @onclick="@ToggleConnectionString">@_connectionstringtoggle</button>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -320,6 +332,7 @@
private string _smtppasswordtype = "password"; private string _smtppasswordtype = "password";
private string _togglesmtppassword = string.Empty; private string _togglesmtppassword = string.Empty;
private string _smtpsender = string.Empty; private string _smtpsender = string.Empty;
private string _smtprelay = "False";
private string _retention = string.Empty; private string _retention = string.Empty;
private string _pwaisenabled; private string _pwaisenabled;
private int _pwaappiconfileid = -1; private int _pwaappiconfileid = -1;
@ -329,6 +342,8 @@
private string _tenant = string.Empty; private string _tenant = string.Empty;
private string _database = string.Empty; private string _database = string.Empty;
private string _connectionstring = string.Empty; private string _connectionstring = string.Empty;
private string _connectionstringtype = "password";
private string _connectionstringtoggle = string.Empty;
private string _createdby; private string _createdby;
private DateTime _createdon; private DateTime _createdon;
private string _modifiedby; private string _modifiedby;
@ -343,6 +358,7 @@
{ {
try try
{ {
_connectionstringtoggle = SharedLocalizer["ShowPassword"];
_themeList = await ThemeService.GetThemesAsync(); _themeList = await ThemeService.GetThemesAsync();
Site site = await SiteService.GetSiteAsync(PageState.Site.SiteId); Site site = await SiteService.GetSiteAsync(PageState.Site.SiteId);
if (site != null) if (site != null)
@ -393,6 +409,7 @@
_smtppassword = SettingService.GetSetting(settings, "SMTPPassword", string.Empty); _smtppassword = SettingService.GetSetting(settings, "SMTPPassword", string.Empty);
_togglesmtppassword = SharedLocalizer["ShowPassword"]; _togglesmtppassword = SharedLocalizer["ShowPassword"];
_smtpsender = SettingService.GetSetting(settings, "SMTPSender", string.Empty); _smtpsender = SettingService.GetSetting(settings, "SMTPSender", string.Empty);
_smtprelay = SettingService.GetSetting(settings, "SMTPRelay", "False");
_retention = SettingService.GetSetting(settings, "NotificationRetention", "30"); _retention = SettingService.GetSetting(settings, "NotificationRetention", "30");
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
@ -449,6 +466,20 @@
} }
} }
private void ToggleConnectionString()
{
if (_connectionstringtype == "password")
{
_connectionstringtype = "text";
_connectionstringtoggle = SharedLocalizer["HidePassword"];
}
else
{
_connectionstringtype = "password";
_connectionstringtoggle = SharedLocalizer["ShowPassword"];
}
}
private async Task SaveSite() private async Task SaveSite()
{ {
validated = true; validated = true;
@ -532,6 +563,7 @@
settings = SettingService.SetSetting(settings, "SMTPUsername", _smtpusername, true); settings = SettingService.SetSetting(settings, "SMTPUsername", _smtpusername, true);
settings = SettingService.SetSetting(settings, "SMTPPassword", _smtppassword, true); settings = SettingService.SetSetting(settings, "SMTPPassword", _smtppassword, true);
settings = SettingService.SetSetting(settings, "SMTPSender", _smtpsender, true); settings = SettingService.SetSetting(settings, "SMTPSender", _smtpsender, true);
settings = SettingService.SetSetting(settings, "SMTPRelay", _smtprelay, true);
settings = SettingService.SetSetting(settings, "NotificationRetention", _retention, true); settings = SettingService.SetSetting(settings, "NotificationRetention", _retention, true);
await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId); await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId);
@ -602,12 +634,12 @@
try try
{ {
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId); var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
SettingService.SetSetting(settings, "SMTPHost", _smtphost, true); settings = SettingService.SetSetting(settings, "SMTPHost", _smtphost, true);
SettingService.SetSetting(settings, "SMTPPort", _smtpport, true); settings = SettingService.SetSetting(settings, "SMTPPort", _smtpport, true);
SettingService.SetSetting(settings, "SMTPSSL", _smtpssl, true); settings = SettingService.SetSetting(settings, "SMTPSSL", _smtpssl, true);
SettingService.SetSetting(settings, "SMTPUsername", _smtpusername, true); settings = SettingService.SetSetting(settings, "SMTPUsername", _smtpusername, true);
SettingService.SetSetting(settings, "SMTPPassword", _smtppassword, true); settings = SettingService.SetSetting(settings, "SMTPPassword", _smtppassword, true);
SettingService.SetSetting(settings, "SMTPSender", _smtpsender, true); settings = SettingService.SetSetting(settings, "SMTPSender", _smtpsender, true);
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId); await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
await logger.LogInformation("Site SMTP Settings Saved"); await logger.LogInformation("Site SMTP Settings Saved");

View File

@ -315,7 +315,7 @@ else
_urls = Regex.Replace(_urls, @"\r\n?|\n", ","); _urls = Regex.Replace(_urls, @"\r\n?|\n", ",");
var duplicates = new List<string>(); var duplicates = new List<string>();
var aliases = await AliasService.GetAliasesAsync(); var aliases = await AliasService.GetAliasesAsync();
foreach (string name in _urls.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) foreach (string name in _urls.Split(',', StringSplitOptions.RemoveEmptyEntries))
{ {
if (aliases.Exists(item => item.Name == name)) if (aliases.Exists(item => item.Name == name))
{ {

View File

@ -16,7 +16,7 @@ else
<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="tenant" HelpText="Select the tenant for the SQL server" ResourceKey="Tenant">Tenant: </Label> <Label Class="col-sm-3" For="tenant" HelpText="Select the tenant associated with the database server" ResourceKey="Tenant">Tenant: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
<select id="tenant" class="form-select" value="@_tenantid" @onchange="(e => TenantChanged(e))"> <select id="tenant" class="form-select" value="@_tenantid" @onchange="(e => TenantChanged(e))">
<option value="-1">&lt;@Localizer["Tenant.Select"]&gt;</option> <option value="-1">&lt;@Localizer["Tenant.Select"]&gt;</option>
@ -45,9 +45,9 @@ else
</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="sqlQeury" HelpText="Enter the query for the SQL server" ResourceKey="SqlQuery">SQL Query: </Label> <Label Class="col-sm-3" For="sqlQuery" HelpText="Enter the SQL query for the database server" ResourceKey="SqlQuery">SQL Query: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
<textarea id="sqlQeury" class="form-control" @bind="@_sql" rows="3"></textarea> <textarea id="sqlQuery" class="form-control" @bind="@_sql" rows="3"></textarea>
</div> </div>
</div> </div>
} }

View File

@ -147,6 +147,18 @@
<a class="btn btn-primary" href="swagger/index.html" target="_new">@Localizer["Access.ApiFramework"]</a>&nbsp; <a class="btn btn-primary" href="swagger/index.html" target="_new">@Localizer["Access.ApiFramework"]</a>&nbsp;
<ActionDialog Header="Restart Application" Message="Are You Sure You Wish To Restart The Application?" Action="Restart Application" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await RestartApplication())" ResourceKey="RestartApplication" /> <ActionDialog Header="Restart Application" Message="Are You Sure You Wish To Restart The Application?" Action="Restart Application" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await RestartApplication())" ResourceKey="RestartApplication" />
</TabPanel> </TabPanel>
<TabPanel Name="Log" Heading="Log" ResourceKey="Log">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="log" HelpText="System log information for current day" ResourceKey="Log">Log: </Label>
<div class="col-sm-9">
<textarea id="log" class="form-control" rows="10" @bind="@_log" readonly />
</div>
</div>
</div>
<br /><br />
<button type="button" class="btn btn-danger" @onclick="ClearLog">@Localizer["Clear"]</button>
</TabPanel>
</TabStrip> </TabStrip>
<br /><br /> <br /><br />
@ -172,6 +184,8 @@
private string _swagger = string.Empty; private string _swagger = string.Empty;
private string _packageservice = string.Empty; private string _packageservice = string.Empty;
private string _log = string.Empty;
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
_version = Constants.Version; _version = Constants.Version;
@ -191,7 +205,7 @@
_workingset = (Convert.ToInt64(systeminfo["WorkingSet"].ToString()) / 1000000).ToString() + " MB"; _workingset = (Convert.ToInt64(systeminfo["WorkingSet"].ToString()) / 1000000).ToString() + " MB";
} }
systeminfo = await SystemService.GetSystemInfoAsync(); systeminfo = await SystemService.GetSystemInfoAsync("configuration");
if (systeminfo != null) if (systeminfo != null)
{ {
_installationid = systeminfo["InstallationId"].ToString(); _installationid = systeminfo["InstallationId"].ToString();
@ -201,6 +215,12 @@
_swagger = systeminfo["UseSwagger"].ToString(); _swagger = systeminfo["UseSwagger"].ToString();
_packageservice = systeminfo["PackageService"].ToString(); _packageservice = systeminfo["PackageService"].ToString();
} }
systeminfo = await SystemService.GetSystemInfoAsync("log");
if (systeminfo != null)
{
_log = systeminfo["Log"].ToString();
}
} }
private async Task SaveConfig() private async Task SaveConfig()
@ -223,6 +243,21 @@
} }
} }
private async Task ClearLog()
{
try
{
await SystemService.UpdateSystemInfoAsync("Log", "Clear");
_log = string.Empty;
AddModuleMessage(Localizer["Success.ClearLog"], MessageType.Success);
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Clearing Log");
AddModuleMessage(Localizer["Error.ClearLog"], MessageType.Error);
}
}
private async Task RestartApplication() private async Task RestartApplication()
{ {
try try

View File

@ -121,7 +121,7 @@
private bool IsValid(string name) private bool IsValid(string name)
{ {
// must contain letters, underscores and digits and first character must be letter or underscore // must contain letters, underscores and digits and first character must be letter or underscore
return !string.IsNullOrEmpty(name) && name.ToLower() != "theme" && Regex.IsMatch(name, "^[A-Za-z_][A-Za-z0-9_]*$"); return !string.IsNullOrEmpty(name) && name.ToLower() != "theme" && !name.ToLower().Contains("oqtane") && Regex.IsMatch(name, "^[A-Za-z_][A-Za-z0-9_]*$");
} }
private void TemplateChanged(ChangeEventArgs e) private void TemplateChanged(ChangeEventArgs e)

View File

@ -305,7 +305,14 @@ else
} }
private string GetProfileValue(string SettingName, string DefaultValue) private string GetProfileValue(string SettingName, string DefaultValue)
=> SettingService.GetSetting(settings, SettingName, DefaultValue); {
string value = SettingService.GetSetting(settings, SettingName, DefaultValue);
if (value.Contains("]"))
{
value = value.Substring(value.IndexOf("]") + 1);
}
return value;
}
private async Task Save() private async Task Save()
{ {

View File

@ -104,7 +104,7 @@
private Dictionary<string, string> settings; private Dictionary<string, string> settings;
private string category = string.Empty; private string category = string.Empty;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
@ -122,7 +122,14 @@
} }
private string GetProfileValue(string SettingName, string DefaultValue) private string GetProfileValue(string SettingName, string DefaultValue)
=> SettingService.GetSetting(settings, SettingName, DefaultValue); {
string value = SettingService.GetSetting(settings, SettingName, DefaultValue);
if (value.Contains("]"))
{
value = value.Substring(value.IndexOf("]") + 1);
}
return value;
}
private async Task SaveUser() private async Task SaveUser()
{ {

View File

@ -174,7 +174,7 @@ else
private string deletedby; private string deletedby;
private DateTime? deletedon; private DateTime? deletedon;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
protected override async Task OnParametersSetAsync() protected override async Task OnParametersSetAsync()
{ {
@ -223,7 +223,14 @@ else
} }
private string GetProfileValue(string SettingName, string DefaultValue) private string GetProfileValue(string SettingName, string DefaultValue)
=> SettingService.GetSetting(settings, SettingName, DefaultValue); {
string value = SettingService.GetSetting(settings, SettingName, DefaultValue);
if (value.Contains("]"))
{
value = value.Substring(value.IndexOf("]") + 1);
}
return value;
}
private async Task SaveUser() private async Task SaveUser()
{ {

View File

@ -20,7 +20,7 @@ else
<div class="container"> <div class="container">
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<div class="col-sm-4"> <div class="col-sm-4">
<ActionLink Action="Add" Text="Add User" ResourceKey="AddUser" /> <ActionLink Action="Add" Text="Add User" Security="SecurityAccessLevel.Edit" ResourceKey="AddUser" />
</div> </div>
<div class="col-sm-4"> <div class="col-sm-4">
<input class="form-control" @bind="@_search" /> <input class="form-control" @bind="@_search" />
@ -41,21 +41,21 @@ else
</Header> </Header>
<Row> <Row>
<td> <td>
<ActionLink Action="Edit" Parameters="@($"id=" + context.UserId.ToString())" ResourceKey="EditUser" /> <ActionLink Action="Edit" Parameters="@($"id=" + context.UserId.ToString())" Security="SecurityAccessLevel.Edit" ResourceKey="EditUser" />
</td> </td>
<td> <td>
<ActionDialog Header="Delete User" Message="@string.Format(Localizer["Confirm.User.Delete"], context.User.DisplayName)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteUser(context))" Disabled="@(context.UserId == PageState.User.UserId)" ResourceKey="DeleteUser" /> <ActionDialog Header="Delete User" Message="@string.Format(Localizer["Confirm.User.Delete"], context.User.DisplayName)" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await DeleteUser(context))" Disabled="@(context.UserId == PageState.User.UserId)" ResourceKey="DeleteUser" />
</td> </td>
<td> <td>
<ActionLink Action="Roles" Parameters="@($"id=" + context.UserId.ToString())" ResourceKey="Roles" /> <ActionLink Action="Roles" Parameters="@($"id=" + context.UserId.ToString())" Security="SecurityAccessLevel.Edit" ResourceKey="Roles" />
</td> </td>
<td>@context.User.Username</td> <td>@context.User.Username</td>
<td>@((MarkupString)string.Format("<a href=\"mailto:{0}\">{1}</a>", @context.User.Email, @context.User.DisplayName))</td> <td>@((MarkupString)string.Format("<a href=\"mailto:{0}\">{1}</a>", @context.User.Email, @context.User.DisplayName))</td>
<td>@string.Format("{0:dd-MMM-yyyy HH:mm:ss}", context.User.LastLoginOn)</td> <td>@((context.User.LastLoginOn != DateTime.MinValue) ? string.Format("{0:dd-MMM-yyyy HH:mm:ss}", context.User.LastLoginOn) : "")</td>
</Row> </Row>
</Pager> </Pager>
</TabPanel> </TabPanel>
<TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings"> <TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings" Security="SecurityAccessLevel.Admin">
<div class="container"> <div class="container">
<Section Name="User" Heading="User Settings" ResourceKey="UserSettings"> <Section Name="User" Heading="User Settings" ResourceKey="UserSettings">
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
@ -406,7 +406,7 @@ else
private string _lifetime; private string _lifetime;
private string _token; private string _token;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
@ -456,7 +456,8 @@ else
_togglesecret = SharedLocalizer["ShowPassword"]; _togglesecret = SharedLocalizer["ShowPassword"];
_issuer = SettingService.GetSetting(settings, "JwtOptions:Issuer", PageState.Uri.Scheme + "://" + PageState.Alias.Name); _issuer = SettingService.GetSetting(settings, "JwtOptions:Issuer", PageState.Uri.Scheme + "://" + PageState.Alias.Name);
_audience = SettingService.GetSetting(settings, "JwtOptions:Audience", ""); _audience = SettingService.GetSetting(settings, "JwtOptions:Audience", "");
_lifetime = SettingService.GetSetting(settings, "JwtOptions:Lifetime", "20"); } _lifetime = SettingService.GetSetting(settings, "JwtOptions:Lifetime", "20");
}
} }
private async Task LoadUsersAsync(bool load) private async Task LoadUsersAsync(bool load)
@ -522,7 +523,7 @@ else
private async Task UpdateUserSettingsAsync() private async Task UpdateUserSettingsAsync()
{ {
Dictionary<string, string> settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId); Dictionary<string, string> settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId);
SettingService.SetSetting(settings, settingSearch, _search); settings = SettingService.SetSetting(settings, settingSearch, _search);
await SettingService.UpdateUserSettingsAsync(settings, PageState.User.UserId); await SettingService.UpdateUserSettingsAsync(settings, PageState.User.UserId);
} }

View File

@ -0,0 +1,21 @@
using Oqtane.Documentation;
using Oqtane.Models;
using Oqtane.Shared;
namespace Oqtane.Modules.Admin.Users
{
[PrivateApi("Mark this as private, since it's not very useful in the public docs")]
public class ModuleInfo : IModule
{
public ModuleDefinition ModuleDefinition => new ModuleDefinition
{
Name = "Users",
Description = "Manage Users",
Categories = "Admin",
Version = Constants.Version,
PermissionNames = $"{PermissionNames.View},{PermissionNames.Edit}," +
$"{EntityNames.User}:{PermissionNames.Write}:{RoleNames.Admin}," +
$"{EntityNames.UserRole}:{PermissionNames.Write}:{RoleNames.Admin}"
};
}
}

View File

@ -63,7 +63,7 @@ else
<td>@context.EffectiveDate</td> <td>@context.EffectiveDate</td>
<td>@context.ExpiryDate</td> <td>@context.ExpiryDate</td>
<td> <td>
<ActionDialog Header="Remove Role" Message="@string.Format(Localizer["Confirm.User.RemoveRole"], context.Role.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteUserRole(context.UserRoleId))" Disabled="@(context.Role.IsAutoAssigned || (context.Role.Name == RoleNames.Host && userid == PageState.User.UserId))" ResourceKey="DeleteUserRole" /> <ActionDialog Header="Remove Role" Message="@string.Format(Localizer["Confirm.User.RemoveRole"], context.Role.Name)" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await DeleteUserRole(context.UserRoleId))" Disabled="@(context.Role.IsAutoAssigned || (context.Role.Name == RoleNames.Host && userid == PageState.User.UserId))" ResourceKey="DeleteUserRole" />
</td> </td>
</Row> </Row>
</Pager> </Pager>
@ -79,7 +79,7 @@ else
private string expirydate = string.Empty; private string expirydate = string.Empty;
private List<UserRole> userroles; private List<UserRole> userroles;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {

View File

@ -6,7 +6,7 @@
@inject IStringLocalizer<FileManager> Localizer @inject IStringLocalizer<FileManager> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer @inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_folders != null) @if (_initialized)
{ {
<div id="@Id" class="container-fluid px-0"> <div id="@Id" class="container-fluid px-0">
<div class="row"> <div class="row">
@ -87,6 +87,7 @@
} }
@code { @code {
private bool _initialized = false;
private List<Folder> _folders; private List<Folder> _folders;
private List<File> _files = new List<File>(); private List<File> _files = new List<File>();
private string _fileinputid = string.Empty; private string _fileinputid = string.Empty;
@ -205,6 +206,8 @@
_fileinputid = "FileInput_" + _guid; _fileinputid = "FileInput_" + _guid;
_progressinfoid = "ProgressInfo_" + _guid; _progressinfoid = "ProgressInfo_" + _guid;
_progressbarid = "ProgressBar_" + _guid; _progressbarid = "ProgressBar_" + _guid;
_initialized = true;
} }
private async Task GetFiles() private async Task GetFiles()

View File

@ -17,7 +17,7 @@
<th scope="col">@Localizer["Role"]</th> <th scope="col">@Localizer["Role"]</th>
@foreach (PermissionString permission in _permissions) @foreach (PermissionString permission in _permissions)
{ {
<th style="text-align: center; width: 1px;">@Localizer[permission.PermissionName]</th> <th style="text-align: center; width: 1px;">@((MarkupString)GetPermissionName(permission).Replace(" ", "<br />"))</th>
} }
</tr> </tr>
@foreach (Role role in _roles) @foreach (Role role in _roles)
@ -28,7 +28,7 @@
{ {
var p = permission; var p = permission;
<td style="text-align: center;"> <td style="text-align: center;">
<TriStateCheckBox Value=@GetPermissionValue(p.Permissions, role.Name) Disabled=@GetPermissionDisabled(role.Name) OnChange="@(e => PermissionChanged(e, p.PermissionName, role.Name))" /> <TriStateCheckBox Value=@GetPermissionValue(p.Permissions, role.Name) Disabled="@GetPermissionDisabled(p.EntityName, p.PermissionName, role.Name)" OnChange="@(e => PermissionChanged(e, p.EntityName, p.PermissionName, role.Name))" />
</td> </td>
} }
</tr> </tr>
@ -66,7 +66,7 @@
{ {
var p = permission; var p = permission;
<td style="text-align: center; width: 1px;"> <td style="text-align: center; width: 1px;">
<TriStateCheckBox Value=@GetPermissionValue(p.Permissions, userid) Disabled=false OnChange="@(e => PermissionChanged(e, p.PermissionName, userid))" /> <TriStateCheckBox Value=@GetPermissionValue(p.Permissions, userid) Disabled="@GetPermissionDisabled(p.EntityName, p.PermissionName, "")" OnChange="@(e => PermissionChanged(e, p.EntityName, p.PermissionName, userid))" />
</td> </td>
} }
</tr> </tr>
@ -129,10 +129,25 @@
_permissions = new List<PermissionString>(); _permissions = new List<PermissionString>();
foreach (string permissionname in _permissionnames.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) foreach (string permissionname in _permissionnames.Split(',', StringSplitOptions.RemoveEmptyEntries))
{ {
// initialize with admin role // permission names can be in the form of "EntityName:PermissionName:Roles"
_permissions.Add(new PermissionString { PermissionName = permissionname, Permissions = RoleNames.Admin }); if (permissionname.Contains(":"))
{
var segments = permissionname.Split(':');
if (segments.Length == 3)
{
if (!segments[2].Contains(RoleNames.Admin))
{
segments[2] = RoleNames.Admin + ";" + segments[2]; // ensure admin access
}
_permissions.Add(new PermissionString { EntityName = segments[0], PermissionName = segments[1], Permissions = segments[2] });
}
}
else
{
_permissions.Add(new PermissionString { EntityName = EntityName, PermissionName = permissionname, Permissions = RoleNames.Admin });
}
} }
if (!string.IsNullOrEmpty(Permissions)) if (!string.IsNullOrEmpty(Permissions))
@ -140,14 +155,15 @@
// populate permissions // populate permissions
foreach (PermissionString permissionstring in UserSecurity.GetPermissionStrings(Permissions)) foreach (PermissionString permissionstring in UserSecurity.GetPermissionStrings(Permissions))
{ {
if (_permissions.Find(item => item.PermissionName == permissionstring.PermissionName) != null) int index = _permissions.FindIndex(item => item.EntityName == permissionstring.EntityName && item.PermissionName == permissionstring.PermissionName);
if (index != -1)
{ {
_permissions[_permissions.FindIndex(item => item.PermissionName == permissionstring.PermissionName)].Permissions = permissionstring.Permissions; _permissions[index].Permissions = permissionstring.Permissions;
} }
if (permissionstring.Permissions.Contains("[")) if (permissionstring.Permissions.Contains("["))
{ {
foreach (string user in permissionstring.Permissions.Split(new char[] { '[' }, StringSplitOptions.RemoveEmptyEntries)) foreach (string user in permissionstring.Permissions.Split('[', StringSplitOptions.RemoveEmptyEntries))
{ {
if (user.Contains("]")) if (user.Contains("]"))
{ {
@ -163,6 +179,16 @@
} }
} }
private string GetPermissionName(PermissionString permission)
{
var permissionname = Localizer[permission.PermissionName].ToString();
if (!string.IsNullOrEmpty(EntityName))
{
permissionname += " " + Localizer[permission.EntityName].ToString();
}
return permissionname;
}
private bool? GetPermissionValue(string permissions, string securityKey) private bool? GetPermissionValue(string permissions, string securityKey)
{ {
if ((";" + permissions + ";").Contains(";" + "!" + securityKey + ";")) if ((";" + permissions + ";").Contains(";" + "!" + securityKey + ";"))
@ -182,8 +208,24 @@
} }
} }
private bool GetPermissionDisabled(string roleName) private bool GetPermissionDisabled(string entityName, string permissionName, string roleName)
=> (roleName == RoleNames.Admin && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) ? true : false; {
if (roleName == RoleNames.Admin && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
return true;
}
else
{
if (entityName != EntityName && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
{
return true;
}
else
{
return false;
}
}
}
private async Task<Dictionary<string, string>> GetUsers(string filter) private async Task<Dictionary<string, string>> GetUsers(string filter)
{ {
@ -209,14 +251,15 @@
_user.Clear(); _user.Clear();
} }
private void PermissionChanged(bool? value, string permissionName, string securityId) private void PermissionChanged(bool? value, string entityName, string permissionName, string securityId)
{ {
var selected = value; var selected = value;
var permission = _permissions.Find(item => item.PermissionName == permissionName); int index = _permissions.FindIndex(item => item.EntityName == entityName && item.PermissionName == permissionName);
if (permission != null) if (index != -1)
{ {
var ids = permission.Permissions.Split(';').ToList(); var permission = _permissions[index];
var ids = permission.Permissions.Split(';').ToList();
ids.Remove(securityId); // remove grant permission ids.Remove(securityId); // remove grant permission
ids.Remove("!" + securityId); // remove deny permission ids.Remove("!" + securityId); // remove deny permission
@ -232,7 +275,7 @@
break; // permission not specified break; // permission not specified
} }
_permissions[_permissions.FindIndex(item => item.PermissionName == permissionName)].Permissions = string.Join(";", ids.ToArray()); _permissions[index].Permissions = string.Join(";", ids.ToArray());
} }
} }
@ -245,9 +288,9 @@
private void ValidatePermissions() private void ValidatePermissions()
{ {
PermissionString permission; PermissionString permission;
for (int i = 0; i < _permissions.Count; i++) for (int index = 0; index < _permissions.Count; index++)
{ {
permission = _permissions[i]; permission = _permissions[index];
List<string> ids = permission.Permissions.Split(';', StringSplitOptions.RemoveEmptyEntries).ToList(); List<string> ids = permission.Permissions.Split(';', StringSplitOptions.RemoveEmptyEntries).ToList();
ids.Remove("!" + RoleNames.Everyone); // remove deny all users ids.Remove("!" + RoleNames.Everyone); // remove deny all users
ids.Remove("!" + RoleNames.Unauthenticated); // remove deny unauthenticated ids.Remove("!" + RoleNames.Unauthenticated); // remove deny unauthenticated
@ -263,7 +306,7 @@
} }
} }
permission.Permissions = string.Join(";", ids.ToArray()); permission.Permissions = string.Join(";", ids.ToArray());
_permissions[i] = permission; _permissions[index] = permission;
} }
} }
} }

View File

@ -315,15 +315,10 @@ namespace Oqtane.Modules
{ {
int pageId = ModuleState.PageId; int pageId = ModuleState.PageId;
int moduleId = ModuleState.ModuleId; int moduleId = ModuleState.ModuleId;
int? userId = null;
if (PageState.User != null)
{
userId = PageState.User.UserId;
}
string category = GetType().AssemblyQualifiedName; string category = GetType().AssemblyQualifiedName;
string feature = Utilities.GetTypeNameLastSegment(category, 1); string feature = Utilities.GetTypeNameLastSegment(category, 1);
await LoggingService.Log(alias, pageId, moduleId, userId, category, feature, function, level, exception, message, args); await LoggingService.Log(alias, pageId, moduleId, PageState.User?.UserId, category, feature, function, level, exception, message, args);
} }
public class Logger public class Logger

View File

@ -5,7 +5,7 @@
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<RazorLangVersion>3.0</RazorLangVersion> <RazorLangVersion>3.0</RazorLangVersion>
<Configurations>Debug;Release</Configurations> <Configurations>Debug;Release</Configurations>
<Version>3.2.1</Version> <Version>3.3.1</Version>
<Product>Oqtane</Product> <Product>Oqtane</Product>
<Authors>Shaun Walker</Authors> <Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company> <Company>.NET Foundation</Company>
@ -13,7 +13,7 @@
<Copyright>.NET Foundation</Copyright> <Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl> <PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl> <PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.3.1</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl> <RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType> <RepositoryType>Git</RepositoryType>
<RootNamespace>Oqtane</RootNamespace> <RootNamespace>Oqtane</RootNamespace>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<root> <root>
<!-- <!--
Microsoft ResX Schema Microsoft ResX Schema
@ -121,7 +121,7 @@
<value>User: </value> <value>User: </value>
</data> </data>
<data name="User.Select" xml:space="preserve"> <data name="User.Select" xml:space="preserve">
<value>Select User</value> <value>Enter User's Name</value>
</data> </data>
<data name="Users" xml:space="preserve"> <data name="Users" xml:space="preserve">
<value>Users</value> <value>Users</value>
@ -129,9 +129,6 @@
<data name="Error.User.Load" xml:space="preserve"> <data name="Error.User.Load" xml:space="preserve">
<value>Error Loading Users</value> <value>Error Loading Users</value>
</data> </data>
<data name="Error.User.LoadRole" xml:space="preserve">
<value>Error Loading User Roles</value>
</data>
<data name="Success.User.AssignedRole" xml:space="preserve"> <data name="Success.User.AssignedRole" xml:space="preserve">
<value>User Assigned To Role</value> <value>User Assigned To Role</value>
</data> </data>
@ -151,7 +148,7 @@
<value>The role you are assigning users to</value> <value>The role you are assigning users to</value>
</data> </data>
<data name="User.HelpText" xml:space="preserve"> <data name="User.HelpText" xml:space="preserve">
<value>Select a user</value> <value>Enter the name of a user</value>
</data> </data>
<data name="EffectiveDate.HelpText" xml:space="preserve"> <data name="EffectiveDate.HelpText" xml:space="preserve">
<value>The date that this role assignment is active</value> <value>The date that this role assignment is active</value>

View File

@ -195,13 +195,13 @@
<data name="UseSsl.HelpText" xml:space="preserve"> <data name="UseSsl.HelpText" xml:space="preserve">
<value>Specify if SSL is required for your SMTP server</value> <value>Specify if SSL is required for your SMTP server</value>
</data> </data>
<data name="SmptUsername.HelpText" xml:space="preserve"> <data name="SmtpUsername.HelpText" xml:space="preserve">
<value>Enter the username for your SMTP account</value> <value>Enter the username for your SMTP account</value>
</data> </data>
<data name="SmtpPassword.HelpText" xml:space="preserve"> <data name="SmtpPassword.HelpText" xml:space="preserve">
<value>Enter the password for your SMTP account</value> <value>Enter the password for your SMTP account</value>
</data> </data>
<data name="SmptSender.HelpText" xml:space="preserve"> <data name="SmtpSender.HelpText" xml:space="preserve">
<value>Enter the email which emails will be sent from. Please note that this email address may need to be authorized with the SMTP server.</value> <value>Enter the email which emails will be sent from. Please note that this email address may need to be authorized with the SMTP server.</value>
</data> </data>
<data name="EnablePWA.HelpText" xml:space="preserve"> <data name="EnablePWA.HelpText" xml:space="preserve">
@ -243,13 +243,13 @@
<data name="UseSsl.Text" xml:space="preserve"> <data name="UseSsl.Text" xml:space="preserve">
<value>SSL Enabled: </value> <value>SSL Enabled: </value>
</data> </data>
<data name="SmptUsername.Text" xml:space="preserve"> <data name="SmtpUsername.Text" xml:space="preserve">
<value>Username: </value> <value>Username: </value>
</data> </data>
<data name="SmtpPassword.Text" xml:space="preserve"> <data name="SmtpPassword.Text" xml:space="preserve">
<value>Password: </value> <value>Password: </value>
</data> </data>
<data name="SmptSender.Text" xml:space="preserve"> <data name="SmtpSender.Text" xml:space="preserve">
<value>Email Sender: </value> <value>Email Sender: </value>
</data> </data>
<data name="EnablePWA.Text" xml:space="preserve"> <data name="EnablePWA.Text" xml:space="preserve">
@ -339,4 +339,10 @@
<data name="HomePage.Text" xml:space="preserve"> <data name="HomePage.Text" xml:space="preserve">
<value>Home Page:</value> <value>Home Page:</value>
</data> </data>
<data name="SmtpRelay.HelpText" xml:space="preserve">
<value>Only specify this option if you have properly configured an SMTP Relay Service to route your outgoing mail. This option will send notifications from the user's email rather than from the Email Sender specified above.</value>
</data>
<data name="SmtpRelay.Text" xml:space="preserve">
<value>Relay Configured?</value>
</data>
</root> </root>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<root> <root>
<!-- <!--
Microsoft ResX Schema Microsoft ResX Schema
@ -133,10 +133,10 @@
<value>No Results Returned</value> <value>No Results Returned</value>
</data> </data>
<data name="Tenant.HelpText" xml:space="preserve"> <data name="Tenant.HelpText" xml:space="preserve">
<value>Select the tenant for the SQL server</value> <value>Select the tenant associated with the database server</value>
</data> </data>
<data name="SqlQuery.HelpText" xml:space="preserve"> <data name="SqlQuery.HelpText" xml:space="preserve">
<value>Enter the query for the SQL server</value> <value>Enter the SQL query for the database server</value>
</data> </data>
<data name="SqlQuery.Text" xml:space="preserve"> <data name="SqlQuery.Text" xml:space="preserve">
<value>SQL Query: </value> <value>SQL Query: </value>

View File

@ -209,6 +209,9 @@
</data> </data>
<data name="Options.Heading" xml:space="preserve"> <data name="Options.Heading" xml:space="preserve">
<value>Options</value> <value>Options</value>
</data>
<data name="Log.Heading" xml:space="preserve">
<value>Log</value>
</data> </data>
<data name="Register" xml:space="preserve"> <data name="Register" xml:space="preserve">
<value>Please Register Me For Major Product Updates And Security Bulletins</value> <value>Please Register Me For Major Product Updates And Security Bulletins</value>
@ -276,4 +279,19 @@
<data name="Environment.Text" xml:space="preserve"> <data name="Environment.Text" xml:space="preserve">
<value>Environment:</value> <value>Environment:</value>
</data> </data>
<data name="Log.Text" xml:space="preserve">
<value>Log:</value>
</data>
<data name="Log.HelpText" xml:space="preserve">
<value>System log information for current day</value>
</data>
<data name="Clear" xml:space="preserve">
<value>Clear</value>
</data>
<data name="Success.ClearLog" xml:space="preserve">
<value>System Log Has Been Successfully Cleared</value>
</data>
<data name="Error.ClearLog" xml:space="preserve">
<value>Ann Error Occurred Clearing The System Log</value>
</data>
</root> </root>

View File

@ -1,32 +0,0 @@
using System.Net.Http;
using System.Threading.Tasks;
using System.Collections.Generic;
using Oqtane.Documentation;
using Oqtane.Shared;
using Oqtane.Models;
namespace Oqtane.Services
{
[PrivateApi("Don't show in the documentation, as everything should use the Interface")]
public class ApiService : ServiceBase, IApiService
{
public ApiService(HttpClient http, SiteState siteState) : base(http, siteState) { }
private string Apiurl => CreateApiUrl("Api");
public async Task<List<Api>> GetApisAsync(int siteId)
{
return await GetJsonAsync<List<Api>>($"{Apiurl}?siteid={siteId}");
}
public async Task<Api> GetApiAsync(int siteId, string entityName)
{
return await GetJsonAsync<Api>($"{Apiurl}/{siteId}/{entityName}");
}
public async Task UpdateApiAsync(Api api)
{
await PostJsonAsync(Apiurl, api);
}
}
}

View File

@ -1,31 +0,0 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Oqtane.Models;
namespace Oqtane.Services
{
/// <summary>
/// Service to retrieve and update API information.
/// </summary>
public interface IApiService
{
/// <summary>
/// returns a list of APIs
/// </summary>
/// <returns></returns>
Task<List<Api>> GetApisAsync(int siteId);
/// <summary>
/// returns a specific API
/// </summary>
/// <returns></returns>
Task<Api> GetApiAsync(int siteId, string entityName);
/// <summary>
/// Updates an API
/// </summary>
/// <param name="api"></param>
/// <returns></returns>
Task UpdateApiAsync(Api api);
}
}

View File

@ -62,7 +62,7 @@ namespace Oqtane.Services
/// <summary> /// <summary>
/// Returns a key-value dictionary of all page module settings for the given page module /// Returns a key-value dictionary of all page module settings for the given page module
/// </summary> /// </summary>
/// <param name="pageId"></param> /// <param name="pageModuleId"></param>
/// <returns></returns> /// <returns></returns>
Task<Dictionary<string, string>> GetPageModuleSettingsAsync(int pageModuleId); Task<Dictionary<string, string>> GetPageModuleSettingsAsync(int pageModuleId);
@ -107,7 +107,7 @@ namespace Oqtane.Services
/// <summary> /// <summary>
/// Returns a key-value dictionary of all user settings for the given user /// Returns a key-value dictionary of all user settings for the given user
/// </summary> /// </summary>
/// <param name="pageId"></param> /// <param name="userId"></param>
/// <returns></returns> /// <returns></returns>
Task<Dictionary<string, string>> GetUserSettingsAsync(int userId); Task<Dictionary<string, string>> GetUserSettingsAsync(int userId);
@ -122,7 +122,7 @@ namespace Oqtane.Services
/// <summary> /// <summary>
/// Returns a key-value dictionary of all folder settings for the given folder /// Returns a key-value dictionary of all folder settings for the given folder
/// </summary> /// </summary>
/// <param name="pageId"></param> /// <param name="folderId"></param>
/// <returns></returns> /// <returns></returns>
Task<Dictionary<string, string>> GetFolderSettingsAsync(int folderId); Task<Dictionary<string, string>> GetFolderSettingsAsync(int folderId);
@ -148,6 +148,21 @@ namespace Oqtane.Services
/// <returns></returns> /// <returns></returns>
Task UpdateHostSettingsAsync(Dictionary<string, string> hostSettings); Task UpdateHostSettingsAsync(Dictionary<string, string> hostSettings);
/// <summary>
/// Returns a key-value dictionary of all settings for the given visitor
/// </summary>
/// <param name="visitorId"></param>
/// <returns></returns>
Task<Dictionary<string, string>> GetVisitorSettingsAsync(int visitorId);
/// <summary>
/// Updates a visitor setting
/// </summary>
/// <param name="visitorSettings"></param>
/// <param name="visitorId"></param>
/// <returns></returns>
Task UpdateVisitorSettingsAsync(Dictionary<string, string> visitorSettings, int visitorId);
/// <summary> /// <summary>
/// Returns a key-value dictionary of all settings for the given entityName /// Returns a key-value dictionary of all settings for the given entityName
/// </summary> /// </summary>

View File

@ -111,6 +111,26 @@ namespace Oqtane.Services
await UpdateSettingsAsync(hostSettings, EntityNames.Host, -1); await UpdateSettingsAsync(hostSettings, EntityNames.Host, -1);
} }
public async Task<Dictionary<string, string>> GetVisitorSettingsAsync(int visitorId)
{
if (visitorId != -1)
{
return await GetSettingsAsync(EntityNames.Visitor, visitorId);
}
else
{
return new Dictionary<string, string>();
}
}
public async Task UpdateVisitorSettingsAsync(Dictionary<string, string> visitorSettings, int visitorId)
{
if (visitorId != -1)
{
await UpdateSettingsAsync(visitorSettings, EntityNames.Visitor, visitorId);
}
}
public async Task<Dictionary<string, string>> GetSettingsAsync(string entityName, int entityId) public async Task<Dictionary<string, string>> GetSettingsAsync(string entityName, int entityId)
{ {
var dictionary = new Dictionary<string, string>(); var dictionary = new Dictionary<string, string>();

View File

@ -33,7 +33,7 @@
} }
} }
@if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.Permissions)) @if (_canViewAdminDashboard || UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.Permissions))
{ {
<button type="button" class="btn @ButtonClass" data-bs-toggle="offcanvas" data-bs-target="#offcanvasControlPanel" aria-controls="offcanvasControlPanel"> <button type="button" class="btn @ButtonClass" data-bs-toggle="offcanvas" data-bs-target="#offcanvasControlPanel" aria-controls="offcanvasControlPanel">
<span class="oi oi-cog"></span> <span class="oi oi-cog"></span>
@ -46,16 +46,17 @@
</div> </div>
<div class="@BodyClass"> <div class="@BodyClass">
<div class="container-fluid"> <div class="container-fluid">
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin)) @if (_canViewAdminDashboard)
{ {
<div class="row d-flex"> <div class="row d-flex">
<div class="col"> <div class="col">
<button type="button" data-bs-dismiss="offcanvas" class="btn btn-primary col-12" @onclick=@(async () => Navigate("Admin"))>@Localizer["AdminDash"]</button> <button type="button" data-bs-dismiss="offcanvas" class="btn btn-primary col-12" @onclick=@(async () => Navigate("Admin"))>@Localizer["AdminDash"]</button>
</div> </div>
</div> </div>
<hr class="app-rule" /> <hr class="app-rule" />
}
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
{
<div class="row"> <div class="row">
<div class="col text-center"> <div class="col text-center">
<label class="control-label">@Localizer["Page.Manage"] </label> <label class="control-label">@Localizer["Page.Manage"] </label>
@ -80,7 +81,7 @@
} }
</div> </div>
</div> </div>
} <hr class="app-rule" />
@if (_deleteConfirmation) @if (_deleteConfirmation)
{ {
@ -104,7 +105,10 @@
</div> </div>
</div> </div>
} }
<hr class="app-rule" /> }
@if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.Permissions))
{
<div class="row"> <div class="row">
<div class="col text-center"> <div class="col text-center">
<label for="Module" class="control-label">@Localizer["Module.Manage"] </label> <label for="Module" class="control-label">@Localizer["Module.Manage"] </label>
@ -211,13 +215,15 @@
</div> </div>
</div> </div>
<button type="button" class="btn btn-primary col-12 mt-4" @onclick="@AddModule">@Localizer["Page.Module.Add"]</button> <button type="button" class="btn btn-primary col-12 mt-4" @onclick="@AddModule">@Localizer["Page.Module.Add"]</button>
@((MarkupString) Message) @((MarkupString)Message)
}
</div> </div>
</div> </div>
</div> </div>
} }
@code{ @code{
private bool _canViewAdminDashboard = false;
private bool _showEditMode = false; private bool _showEditMode = false;
private bool _deleteConfirmation = false; private bool _deleteConfirmation = false;
private List<string> _categories = new List<string>(); private List<string> _categories = new List<string>();
@ -286,6 +292,7 @@
protected override async Task OnParametersSetAsync() protected override async Task OnParametersSetAsync()
{ {
_canViewAdminDashboard = CanViewAdminDashboard();
_showEditMode = false; _showEditMode = false;
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.Permissions)) if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.Permissions))
{ {
@ -321,6 +328,22 @@
} }
} }
private bool CanViewAdminDashboard()
{
var admin = PageState.Pages.FirstOrDefault(item => item.Path == "admin");
if (admin != null)
{
foreach (var page in PageState.Pages.Where(item => item.ParentId == admin?.PageId))
{
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, page.Permissions))
{
return true;
}
}
}
return false;
}
private void CategoryChanged(ChangeEventArgs e) private void CategoryChanged(ChangeEventArgs e)
{ {
Category = (string)e.Value; Category = (string)e.Value;
@ -380,6 +403,8 @@
// set module view permissions to page edit permissions // set module view permissions to page edit permissions
permissions.Find(p => p.PermissionName == PermissionNames.View).Permissions = permissions.Find(p => p.PermissionName == PermissionNames.Edit).Permissions; permissions.Find(p => p.PermissionName == PermissionNames.View).Permissions = permissions.Find(p => p.PermissionName == PermissionNames.Edit).Permissions;
} }
// set entityname
permissions.ForEach(item => item.EntityName = EntityNames.Module);
module.Permissions = UserSecurity.SetPermissionStrings(permissions); module.Permissions = UserSecurity.SetPermissionStrings(permissions);
module = await ModuleService.AddModuleAsync(module); module = await ModuleService.AddModuleAsync(module);
@ -465,12 +490,10 @@
case "Admin": case "Admin":
// get admin dashboard moduleid // get admin dashboard moduleid
module = PageState.Modules.FirstOrDefault(item => item.ModuleDefinitionName == Constants.AdminDashboardModule); module = PageState.Modules.FirstOrDefault(item => item.ModuleDefinitionName == Constants.AdminDashboardModule);
if (module != null) if (module != null)
{ {
NavigationManager.NavigateTo(EditUrl(PageState.Page.Path, module.ModuleId, "Index", "")); NavigationManager.NavigateTo(EditUrl(PageState.Page.Path, module.ModuleId, "Index", ""));
} }
break; break;
case "Add": case "Add":
case "Edit": case "Edit":
@ -551,19 +574,19 @@
{ {
page.IsDeleted = true; page.IsDeleted = true;
await PageService.UpdatePageAsync(page); await PageService.UpdatePageAsync(page);
await logger.Log(page.PageId, null, PageState.User.UserId, GetType().AssemblyQualifiedName, "ControlPanel", LogFunction.Delete, LogLevel.Information, null, "Page Deleted {Page}", page); await logger.Log(page.PageId, null, PageState.User?.UserId, GetType().AssemblyQualifiedName, "ControlPanel", LogFunction.Delete, LogLevel.Information, null, "Page Deleted {Page}", page);
NavigationManager.NavigateTo(NavigateUrl("")); NavigationManager.NavigateTo(NavigateUrl(""));
} }
else // personalized page else // personalized page
{ {
await PageService.DeletePageAsync(page.PageId); await PageService.DeletePageAsync(page.PageId);
await logger.Log(page.PageId, null, PageState.User.UserId, GetType().AssemblyQualifiedName, "ControlPanel", LogFunction.Delete, LogLevel.Information, null, "Page Deleted {Page}", page); await logger.Log(page.PageId, null, PageState.User?.UserId, GetType().AssemblyQualifiedName, "ControlPanel", LogFunction.Delete, LogLevel.Information, null, "Page Deleted {Page}", page);
NavigationManager.NavigateTo(NavigateUrl()); NavigationManager.NavigateTo(NavigateUrl());
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.Log(page.PageId, null, PageState.User.UserId, GetType().AssemblyQualifiedName, "ControlPanel", LogFunction.Delete, LogLevel.Information, ex, "Page Deleted {Page} {Error}", page, ex.Message); await logger.Log(page.PageId, null, PageState.User?.UserId, GetType().AssemblyQualifiedName, "ControlPanel", LogFunction.Delete, LogLevel.Information, ex, "Page Deleted {Page} {Error}", page, ex.Message);
} }
} }
@ -596,8 +619,8 @@
private async Task UpdateSettingsAsync() private async Task UpdateSettingsAsync()
{ {
Dictionary<string, string> settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId); Dictionary<string, string> settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId);
SettingService.SetSetting(settings, settingCategory, _category); settings = SettingService.SetSetting(settings, settingCategory, _category);
SettingService.SetSetting(settings, settingPane, _pane); settings = SettingService.SetSetting(settings, settingPane, _pane);
await SettingService.UpdateUserSettingsAsync(settings, PageState.User.UserId); await SettingService.UpdateUserSettingsAsync(settings, PageState.User.UserId);
} }

View File

@ -1,8 +1,10 @@
using System; using System;
using System.Net;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop; using Microsoft.JSInterop;
using Oqtane.Enums; using Oqtane.Enums;
using Oqtane.Models;
using Oqtane.Providers; using Oqtane.Providers;
using Oqtane.Security; using Oqtane.Security;
using Oqtane.Services; using Oqtane.Services;
@ -22,20 +24,18 @@ namespace Oqtane.Themes.Controls
protected void LoginUser() protected void LoginUser()
{ {
var returnurl = PageState.Alias.Path; Route route = new Route(PageState.Uri.AbsoluteUri, PageState.Alias.Path);
if (PageState.Page.Path != "/") NavigationManager.NavigateTo(NavigateUrl("login", "?returnurl=" + WebUtility.UrlEncode(route.PathAndQuery)));
{
returnurl += "/" + PageState.Page.Path;
}
NavigationManager.NavigateTo(NavigateUrl("login", "?returnurl=" + returnurl));
} }
protected async Task LogoutUser() protected async Task LogoutUser()
{ {
await LoggingService.Log(PageState.Alias, PageState.Page.PageId, null, PageState.User.UserId, GetType().AssemblyQualifiedName, "Logout", LogFunction.Security, LogLevel.Information, null, "User Logout For Username {Username}", PageState.User.Username); await LoggingService.Log(PageState.Alias, PageState.Page.PageId, null, PageState.User?.UserId, GetType().AssemblyQualifiedName, "Logout", LogFunction.Security, LogLevel.Information, null, "User Logout For Username {Username}", PageState.User?.Username);
// check if anonymous user can access page Route route = new Route(PageState.Uri.AbsoluteUri, PageState.Alias.Path);
var url = PageState.Alias.Path + "/" + PageState.Page.Path; var url = route.PathAndQuery;
// verify if anonymous users can access page
if (!UserSecurity.IsAuthorized(null, PermissionNames.View, PageState.Page.Permissions)) if (!UserSecurity.IsAuthorized(null, PermissionNames.View, PageState.Page.Permissions))
{ {
url = PageState.Alias.Path; url = PageState.Alias.Path;

View File

@ -87,10 +87,9 @@ else
// retrieve friendly localized error // retrieve friendly localized error
_error = Localizer["Error.Module.Exception"]; _error = Localizer["Error.Module.Exception"];
// log error // log error
int? userId = (PageState.User != null) ? PageState.User.UserId : null;
string category = GetType().AssemblyQualifiedName; string category = GetType().AssemblyQualifiedName;
string feature = Utilities.GetTypeNameLastSegment(category, 1); string feature = Utilities.GetTypeNameLastSegment(category, 1);
await LoggingService.Log(null, ModuleState.PageId, ModuleState.ModuleId, userId, category, feature, LogFunction.Other, LogLevel.Error, exception, "An Unexpected Error Has Occurred In {ModuleDefinitionName}: {Error}", ModuleState.ModuleDefinitionName, exception.Message); await LoggingService.Log(null, ModuleState.PageId, ModuleState.ModuleId, PageState.User?.UserId, category, feature, LogFunction.Other, LogLevel.Error, exception, "An Unexpected Error Has Occurred In {ModuleDefinitionName}: {Error}", ModuleState.ModuleDefinitionName, exception.Message);
await base.OnErrorAsync(exception); await base.OnErrorAsync(exception);
return; return;
} }

View File

@ -250,7 +250,7 @@
if (user == null) if (user == null)
{ {
// redirect to login page if user not logged in as they may need to be authenticated // redirect to login page if user not logged in as they may need to be authenticated
NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "login", "?returnurl=" + route.AbsolutePath)); NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "login", "?returnurl=" + WebUtility.UrlEncode(route.PathAndQuery)));
} }
else else
{ {
@ -299,22 +299,28 @@
{ {
query = query.Substring(1); // ignore "?" query = query.Substring(1); // ignore "?"
} }
foreach (string kvp in query.Split(new[] { '&' }, StringSplitOptions.RemoveEmptyEntries)) foreach (string kvp in query.Split('&', StringSplitOptions.RemoveEmptyEntries))
{ {
if (kvp != "") if (kvp != "")
{ {
if (kvp.Contains("=")) if (kvp.Contains("="))
{ {
string[] pair = kvp.Split('='); string[] pair = kvp.Split('=');
if (!querystring.ContainsKey(pair[0]))
{
querystring.Add(pair[0], pair[1]); querystring.Add(pair[0], pair[1]);
} }
}
else else
{
if (!querystring.ContainsKey(kvp))
{ {
querystring.Add(kvp, "true"); // default parameter when no value is provided querystring.Add(kvp, "true"); // default parameter when no value is provided
} }
} }
} }
} }
}
return querystring; return querystring;
} }
@ -358,7 +364,7 @@
} }
if (!string.IsNullOrEmpty(panes)) if (!string.IsNullOrEmpty(panes))
{ {
page.Panes = panes.Replace(";", ",").Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(); page.Panes = panes.Replace(";", ",").Split(',', StringSplitOptions.RemoveEmptyEntries).ToList();
if (!page.Panes.Contains(PaneNames.Default) && !page.Panes.Contains(PaneNames.Admin)) if (!page.Panes.Contains(PaneNames.Default) && !page.Panes.Contains(PaneNames.Admin))
{ {
_error = "The Current Theme Does Not Contain A Default Or Admin Pane"; _error = "The Current Theme Does Not Contain A Default Or Admin Pane";
@ -407,7 +413,7 @@
// check if the module defines custom action routes // check if the module defines custom action routes
if (module.ModuleDefinition.ControlTypeRoutes != "") if (module.ModuleDefinition.ControlTypeRoutes != "")
{ {
foreach (string route in module.ModuleDefinition.ControlTypeRoutes.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries)) foreach (string route in module.ModuleDefinition.ControlTypeRoutes.Split(';', StringSplitOptions.RemoveEmptyEntries))
{ {
if (route.StartsWith(action + "=")) if (route.StartsWith(action + "="))
{ {

View File

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<Version>3.2.1</Version> <Version>3.3.1</Version>
<Product>Oqtane</Product> <Product>Oqtane</Product>
<Authors>Shaun Walker</Authors> <Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company> <Company>.NET Foundation</Company>
@ -10,7 +10,7 @@
<Copyright>.NET Foundation</Copyright> <Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl> <PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl> <PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.3.1</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl> <RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType> <RepositoryType>Git</RepositoryType>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata> <metadata>
<id>Oqtane.Database.MySQL</id> <id>Oqtane.Database.MySQL</id>
<version>3.2.1</version> <version>3.3.1</version>
<authors>Shaun Walker</authors> <authors>Shaun Walker</authors>
<owners>.NET Foundation</owners> <owners>.NET Foundation</owners>
<title>Oqtane MySQL Provider</title> <title>Oqtane MySQL Provider</title>
@ -12,7 +12,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license> <license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl> <projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1</releaseNotes> <releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.3.1</releaseNotes>
<icon>icon.png</icon> <icon>icon.png</icon>
<tags>oqtane</tags> <tags>oqtane</tags>
</metadata> </metadata>

View File

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<Version>3.2.1</Version> <Version>3.3.1</Version>
<Product>Oqtane</Product> <Product>Oqtane</Product>
<Authors>Shaun Walker</Authors> <Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company> <Company>.NET Foundation</Company>
@ -10,7 +10,7 @@
<Copyright>.NET Foundation</Copyright> <Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl> <PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl> <PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.3.1</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl> <RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType> <RepositoryType>Git</RepositoryType>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata> <metadata>
<id>Oqtane.Database.PostgreSQL</id> <id>Oqtane.Database.PostgreSQL</id>
<version>3.2.1</version> <version>3.3.1</version>
<authors>Shaun Walker</authors> <authors>Shaun Walker</authors>
<owners>.NET Foundation</owners> <owners>.NET Foundation</owners>
<title>Oqtane PostgreSQL Provider</title> <title>Oqtane PostgreSQL Provider</title>
@ -12,7 +12,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license> <license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl> <projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1</releaseNotes> <releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.3.1</releaseNotes>
<icon>icon.png</icon> <icon>icon.png</icon>
<tags>oqtane</tags> <tags>oqtane</tags>
</metadata> </metadata>

View File

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<Version>3.2.1</Version> <Version>3.3.1</Version>
<Product>Oqtane</Product> <Product>Oqtane</Product>
<Authors>Shaun Walker</Authors> <Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company> <Company>.NET Foundation</Company>
@ -10,7 +10,7 @@
<Copyright>.NET Foundation</Copyright> <Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl> <PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl> <PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.3.1</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl> <RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType> <RepositoryType>Git</RepositoryType>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata> <metadata>
<id>Oqtane.Database.SqlServer</id> <id>Oqtane.Database.SqlServer</id>
<version>3.2.1</version> <version>3.3.1</version>
<authors>Shaun Walker</authors> <authors>Shaun Walker</authors>
<owners>.NET Foundation</owners> <owners>.NET Foundation</owners>
<title>Oqtane SQL Server Provider</title> <title>Oqtane SQL Server Provider</title>
@ -12,7 +12,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license> <license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl> <projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1</releaseNotes> <releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.3.1</releaseNotes>
<icon>icon.png</icon> <icon>icon.png</icon>
<tags>oqtane</tags> <tags>oqtane</tags>
</metadata> </metadata>

View File

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<Version>3.2.1</Version> <Version>3.3.1</Version>
<Product>Oqtane</Product> <Product>Oqtane</Product>
<Authors>Shaun Walker</Authors> <Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company> <Company>.NET Foundation</Company>
@ -10,7 +10,7 @@
<Copyright>.NET Foundation</Copyright> <Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl> <PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl> <PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.3.1</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl> <RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType> <RepositoryType>Git</RepositoryType>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata> <metadata>
<id>Oqtane.Database.Sqlite</id> <id>Oqtane.Database.Sqlite</id>
<version>3.2.1</version> <version>3.3.1</version>
<authors>Shaun Walker</authors> <authors>Shaun Walker</authors>
<owners>.NET Foundation</owners> <owners>.NET Foundation</owners>
<title>Oqtane SQLite Provider</title> <title>Oqtane SQLite Provider</title>
@ -12,7 +12,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license> <license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl> <projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1</releaseNotes> <releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.3.1</releaseNotes>
<icon>icon.png</icon> <icon>icon.png</icon>
<tags>oqtane</tags> <tags>oqtane</tags>
</metadata> </metadata>

View File

@ -6,7 +6,7 @@
<!-- Uncomment to also build the tizen app. You will need to install tizen by following this: https://github.com/Samsung/Tizen.NET --> <!-- Uncomment to also build the tizen app. You will need to install tizen by following this: https://github.com/Samsung/Tizen.NET -->
<!-- <TargetFrameworks>$(TargetFrameworks);net6.0-tizen</TargetFrameworks> --> <!-- <TargetFrameworks>$(TargetFrameworks);net6.0-tizen</TargetFrameworks> -->
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<Version>3.2.1</Version> <Version>3.3.1</Version>
<Product>Oqtane</Product> <Product>Oqtane</Product>
<Authors>Shaun Walker</Authors> <Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company> <Company>.NET Foundation</Company>
@ -14,7 +14,7 @@
<Copyright>.NET Foundation</Copyright> <Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl> <PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl> <PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.3.1</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl> <RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType> <RepositoryType>Git</RepositoryType>
<RootNamespace>Oqtane.Maui</RootNamespace> <RootNamespace>Oqtane.Maui</RootNamespace>
@ -31,7 +31,7 @@
<ApplicationIdGuid>0E29FC31-1B83-48ED-B6E0-9F3C67B775D4</ApplicationIdGuid> <ApplicationIdGuid>0E29FC31-1B83-48ED-B6E0-9F3C67B775D4</ApplicationIdGuid>
<!-- Versions --> <!-- Versions -->
<ApplicationDisplayVersion>3.2.1</ApplicationDisplayVersion> <ApplicationDisplayVersion>3.3.1</ApplicationDisplayVersion>
<ApplicationVersion>1</ApplicationVersion> <ApplicationVersion>1</ApplicationVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">14.2</SupportedOSPlatformVersion> <SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">14.2</SupportedOSPlatformVersion>
@ -71,8 +71,8 @@
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="6.0.3" /> <PackageReference Include="Microsoft.Extensions.Localization" Version="6.0.3" />
<PackageReference Include="System.Net.Http.Json" Version="6.0.0" /> <PackageReference Include="System.Net.Http.Json" Version="6.0.0" />
<PackageReference Include="Oqtane.Client" Version="3.2.1" /> <PackageReference Include="Oqtane.Client" Version="3.3.1" />
<PackageReference Include="Oqtane.Shared" Version="3.2.1" /> <PackageReference Include="Oqtane.Shared" Version="3.3.1" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata> <metadata>
<id>Oqtane.Client</id> <id>Oqtane.Client</id>
<version>3.2.1</version> <version>3.3.1</version>
<authors>Shaun Walker</authors> <authors>Shaun Walker</authors>
<owners>.NET Foundation</owners> <owners>.NET Foundation</owners>
<title>Oqtane Framework</title> <title>Oqtane Framework</title>
@ -12,7 +12,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license> <license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl> <projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1</releaseNotes> <releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.3.1</releaseNotes>
<icon>icon.png</icon> <icon>icon.png</icon>
<tags>oqtane</tags> <tags>oqtane</tags>
</metadata> </metadata>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata> <metadata>
<id>Oqtane.Framework</id> <id>Oqtane.Framework</id>
<version>3.2.1</version> <version>3.3.1</version>
<authors>Shaun Walker</authors> <authors>Shaun Walker</authors>
<owners>.NET Foundation</owners> <owners>.NET Foundation</owners>
<title>Oqtane Framework</title> <title>Oqtane Framework</title>
@ -11,8 +11,8 @@
<copyright>.NET Foundation</copyright> <copyright>.NET Foundation</copyright>
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license> <license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework/releases/download/v3.2.1/Oqtane.Framework.3.2.1.Upgrade.zip</projectUrl> <projectUrl>https://github.com/oqtane/oqtane.framework/releases/download/v3.3.1/Oqtane.Framework.3.3.1.Upgrade.zip</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1</releaseNotes> <releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.3.1</releaseNotes>
<icon>icon.png</icon> <icon>icon.png</icon>
<tags>oqtane framework</tags> <tags>oqtane framework</tags>
</metadata> </metadata>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata> <metadata>
<id>Oqtane.Server</id> <id>Oqtane.Server</id>
<version>3.2.1</version> <version>3.3.1</version>
<authors>Shaun Walker</authors> <authors>Shaun Walker</authors>
<owners>.NET Foundation</owners> <owners>.NET Foundation</owners>
<title>Oqtane Framework</title> <title>Oqtane Framework</title>
@ -12,7 +12,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license> <license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl> <projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1</releaseNotes> <releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.3.1</releaseNotes>
<icon>icon.png</icon> <icon>icon.png</icon>
<tags>oqtane</tags> <tags>oqtane</tags>
</metadata> </metadata>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata> <metadata>
<id>Oqtane.Shared</id> <id>Oqtane.Shared</id>
<version>3.2.1</version> <version>3.3.1</version>
<authors>Shaun Walker</authors> <authors>Shaun Walker</authors>
<owners>.NET Foundation</owners> <owners>.NET Foundation</owners>
<title>Oqtane Framework</title> <title>Oqtane Framework</title>
@ -12,7 +12,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license> <license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl> <projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1</releaseNotes> <releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.3.1</releaseNotes>
<icon>icon.png</icon> <icon>icon.png</icon>
<tags>oqtane</tags> <tags>oqtane</tags>
</metadata> </metadata>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata> <metadata>
<id>Oqtane.Updater</id> <id>Oqtane.Updater</id>
<version>3.2.1</version> <version>3.3.1</version>
<authors>Shaun Walker</authors> <authors>Shaun Walker</authors>
<owners>.NET Foundation</owners> <owners>.NET Foundation</owners>
<title>Oqtane Framework</title> <title>Oqtane Framework</title>
@ -12,7 +12,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license> <license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl> <projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1</releaseNotes> <releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.3.1</releaseNotes>
<icon>icon.png</icon> <icon>icon.png</icon>
<tags>oqtane</tags> <tags>oqtane</tags>
</metadata> </metadata>

View File

@ -1 +1 @@
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net6.0\publish\*" -DestinationPath "Oqtane.Framework.3.2.1.Install.zip" -Force Compress-Archive -Path "..\Oqtane.Server\bin\Release\net6.0\publish\*" -DestinationPath "Oqtane.Framework.3.3.1.Install.zip" -Force

View File

@ -13,6 +13,23 @@ rmdir /Q/S "..\Oqtane.Server\bin\Release\net6.0\publish"
dotnet publish ..\Oqtane.Server\Oqtane.Server.csproj /p:Configuration=Release dotnet publish ..\Oqtane.Server\Oqtane.Server.csproj /p:Configuration=Release
del /F/Q/S "..\Oqtane.Server\bin\Release\net6.0\publish\wwwroot\Content" > NUL del /F/Q/S "..\Oqtane.Server\bin\Release\net6.0\publish\wwwroot\Content" > NUL
rmdir /Q/S "..\Oqtane.Server\bin\Release\net6.0\publish\wwwroot\Content" rmdir /Q/S "..\Oqtane.Server\bin\Release\net6.0\publish\wwwroot\Content"
setlocal ENABLEDELAYEDEXPANSION
set retain=Oqtane.Modules.Admin.Login,Oqtane.Modules.HtmlText,Templates
for /D %%i in ("..\Oqtane.Server\bin\Release\net6.0\publish\wwwroot\Modules\*") do (
set /A found=0
for %%j in (%retain%) do (
if "%%~nxi" == "%%j" set /A found=1
)
if not !found! == 1 rmdir /Q/S "%%i"
)
set retain=Oqtane.Themes.BlazorTheme,Oqtane.Themes.OqtaneTheme,Templates
for /D %%i in ("..\Oqtane.Server\bin\Release\net6.0\publish\wwwroot\Themes\*") do (
set /A found=0
for %%j in (%retain%) do (
if "%%~nxi" == "%%j" set /A found=1
)
if not !found! == 1 rmdir /Q/S "%%i"
)
del "..\Oqtane.Server\bin\Release\net6.0\publish\appsettings.json" del "..\Oqtane.Server\bin\Release\net6.0\publish\appsettings.json"
ren "..\Oqtane.Server\bin\Release\net6.0\publish\appsettings.release.json" "appsettings.json" ren "..\Oqtane.Server\bin\Release\net6.0\publish\appsettings.release.json" "appsettings.json"
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe ".\install.ps1" C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe ".\install.ps1"

View File

@ -1 +1 @@
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net6.0\publish\*" -DestinationPath "Oqtane.Framework.3.2.1.Upgrade.zip" -Force Compress-Archive -Path "..\Oqtane.Server\bin\Release\net6.0\publish\*" -DestinationPath "Oqtane.Framework.3.3.1.Upgrade.zip" -Force

View File

@ -1,172 +0,0 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using System.Collections.Generic;
using Oqtane.Shared;
using Oqtane.Models;
using Oqtane.Infrastructure;
using Oqtane.Enums;
using System.Net;
using Oqtane.Repository;
using Oqtane.Extensions;
using System.Reflection;
using System;
using System.Linq;
namespace Oqtane.Controllers
{
[Route(ControllerRoutes.ApiRoute)]
public class ApiController : Controller
{
private readonly IPermissionRepository _permissions;
private readonly IRoleRepository _roles;
private readonly ILogManager _logger;
private readonly Alias _alias;
public ApiController(IPermissionRepository permissions, IRoleRepository roles, ILogManager logger, ITenantManager tenantManager)
{
_permissions = permissions;
_roles = roles;
_logger = logger;
_alias = tenantManager.GetAlias();
}
// GET: api/<controller>?siteid=x
[HttpGet]
[Authorize(Roles = RoleNames.Admin)]
public List<Api> Get(string siteid)
{
int SiteId;
if (int.TryParse(siteid, out SiteId) && SiteId == _alias.SiteId)
{
var apis = new List<Api>();
var assemblies = AppDomain.CurrentDomain.GetOqtaneAssemblies();
foreach (var assembly in assemblies)
{
// iterate controllers
foreach (var type in assembly.GetTypes().Where(type => typeof(Controller).IsAssignableFrom(type)))
{
// iterate controller methods with authorize attribute
var actions = type.GetMethods(BindingFlags.Public | BindingFlags.Instance)
.Where(m => m.GetCustomAttributes<AuthorizeAttribute>().Any());
foreach(var action in actions)
{
// get policy
var policy = action.GetCustomAttribute<AuthorizeAttribute>().Policy;
if (!string.IsNullOrEmpty(policy) && policy.Contains(":") && !policy.Contains(Constants.RequireEntityId))
{
// parse policy
var segments = policy.Split(':');
if (!apis.Any(item => item.EntityName == segments[0]))
{
apis.Add(new Api { SiteId = SiteId, EntityName = segments[0], Permissions = segments[1] });
}
else
{
// concatenate permissions
var permissions = apis.SingleOrDefault(item => item.EntityName == segments[0]).Permissions;
if (!permissions.Split(',').Contains(segments[1]))
{
apis.SingleOrDefault(item => item.EntityName == segments[0]).Permissions += "," + segments[1];
}
}
}
}
}
}
return apis;
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Api Get Attempt {SiteId}", siteid);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return null;
}
}
// GET: api/<controller>/1/user
[HttpGet("{siteid}/{entityname}")]
[Authorize(Roles = RoleNames.Admin)]
public Api Get(int siteid, string entityname)
{
if (siteid == _alias.SiteId)
{
var permissions = _permissions.GetPermissions(siteid, entityname);
if (permissions == null || permissions.ToList().Count == 0)
{
permissions = GetPermissions(siteid, entityname);
}
return new Api { SiteId = siteid, EntityName = entityname, Permissions = permissions.EncodePermissions() };
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Api Get Attempt {SiteId} {EntityName}", siteid, entityname);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return null;
}
}
// POST: api/<controller>
[HttpPost]
[Authorize(Roles = RoleNames.Admin)]
public void Post([FromBody] Api api)
{
if (ModelState.IsValid && api.SiteId == _alias.SiteId)
{
_permissions.UpdatePermissions(api.SiteId, api.EntityName, -1, api.Permissions);
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Api Updated {Api}", api);
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Api Post Attempt {Api}", api);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
}
}
private List<Permission> GetPermissions(int siteid, string entityname)
{
var permissions = new List<Permission>();
var assemblies = AppDomain.CurrentDomain.GetOqtaneAssemblies();
foreach (var assembly in assemblies)
{
// iterate controllers
foreach (var type in assembly.GetTypes().Where(type => typeof(Controller).IsAssignableFrom(type)))
{
// iterate controller methods with authorize attribute
var actions = type.GetMethods(BindingFlags.Public | BindingFlags.Instance)
.Where(m => m.GetCustomAttributes<AuthorizeAttribute>().Any());
foreach (var action in actions)
{
// get policy
var policy = action.GetCustomAttribute<AuthorizeAttribute>().Policy;
if (!string.IsNullOrEmpty(policy) && policy.Contains(":") && !policy.Contains(Constants.RequireEntityId))
{
// parse policy
var segments = policy.Split(':');
// entity match
if (segments[0] == entityname && segments.Length > 2)
{
var roles = _roles.GetRoles(siteid);
foreach (var rolename in (segments[2]).Split(','))
{
var role = roles.FirstOrDefault(item => item.Name == rolename);
if (role != null)
{
if (!permissions.Any(item => item.EntityName == entityname && item.PermissionName == segments[1] && item.RoleId == role.RoleId))
{
permissions.Add(new Permission { SiteId = siteid, EntityName = entityname, EntityId = -1, PermissionName = segments[1], RoleId = role.RoleId, Role = role, UserId = null, IsAuthorized = true });
}
}
}
}
}
}
}
}
return permissions;
}
}
}

View File

@ -20,7 +20,6 @@ using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Png;
using System.Net.Http; using System.Net.Http;
using Oqtane.Migrations.Tenant;
// ReSharper disable StringIndexOfIsCultureSpecific.1 // ReSharper disable StringIndexOfIsCultureSpecific.1
@ -142,7 +141,7 @@ namespace Oqtane.Controllers
{ {
if (File.Name != file.Name || File.FolderId != file.FolderId) if (File.Name != file.Name || File.FolderId != file.FolderId)
{ {
file.Folder = _folders.GetFolder(file.FolderId); file.Folder = _folders.GetFolder(file.FolderId, false);
string folderpath = _folders.GetFolderPath(file.Folder); string folderpath = _folders.GetFolderPath(file.Folder);
if (!Directory.Exists(folderpath)) if (!Directory.Exists(folderpath))
{ {
@ -151,7 +150,7 @@ namespace Oqtane.Controllers
System.IO.File.Move(_files.GetFilePath(File), Path.Combine(folderpath, file.Name)); System.IO.File.Move(_files.GetFilePath(File), Path.Combine(folderpath, file.Name));
} }
var newfile = CreateFile(file.Name, file.Folder.FolderId, _files.GetFilePath(file)); var newfile = CreateFile(File.Name, file.Folder.FolderId, _files.GetFilePath(file));
if (newfile != null) if (newfile != null)
{ {
file.Extension = newfile.Extension; file.Extension = newfile.Extension;
@ -659,10 +658,10 @@ namespace Oqtane.Controllers
var file = _files.GetFile(folderid, filename); var file = _files.GetFile(folderid, filename);
int size = 0; int size = 0;
var folder = _folders.GetFolder(folderid); var folder = _folders.GetFolder(folderid, false);
if (folder.Capacity != 0) if (folder.Capacity != 0)
{ {
foreach (var f in _files.GetFiles(folderid)) foreach (var f in _files.GetFiles(folderid, false))
{ {
size += f.Size; size += f.Size;
} }

View File

@ -47,7 +47,6 @@ namespace Oqtane.Controllers
int SiteId; int SiteId;
if (int.TryParse(siteid, out SiteId) && SiteId == _alias.SiteId) if (int.TryParse(siteid, out SiteId) && SiteId == _alias.SiteId)
{ {
List<ModuleDefinition> moduledefinitions = _moduleDefinitions.GetModuleDefinitions(SiteId).ToList();
List<Setting> settings = _settings.GetSettings(EntityNames.Module).ToList(); List<Setting> settings = _settings.GetSettings(EntityNames.Module).ToList();
foreach (PageModule pagemodule in _pageModules.GetPageModules(SiteId)) foreach (PageModule pagemodule in _pageModules.GetPageModules(SiteId))
@ -75,7 +74,6 @@ namespace Oqtane.Controllers
module.Order = pagemodule.Order; module.Order = pagemodule.Order;
module.ContainerType = pagemodule.ContainerType; module.ContainerType = pagemodule.ContainerType;
module.ModuleDefinition = moduledefinitions.Find(item => item.ModuleDefinitionName == module.ModuleDefinitionName);
module.Settings = settings.Where(item => item.EntityId == pagemodule.ModuleId) module.Settings = settings.Where(item => item.EntityId == pagemodule.ModuleId)
.Where(item => !item.IsPrivate || _userPermissions.IsAuthorized(User, PermissionNames.Edit, pagemodule.Module.Permissions)) .Where(item => !item.IsPrivate || _userPermissions.IsAuthorized(User, PermissionNames.Edit, pagemodule.Module.Permissions))
.ToDictionary(setting => setting.SettingName, setting => setting.SettingValue); .ToDictionary(setting => setting.SettingName, setting => setting.SettingValue);

View File

@ -281,7 +281,7 @@ namespace Oqtane.Controllers
// synchronize module permissions // synchronize module permissions
if (added.Count > 0 || removed.Count > 0) if (added.Count > 0 || removed.Count > 0)
{ {
foreach (PageModule pageModule in _pageModules.GetPageModules(page.PageId, "").ToList()) foreach (PageModule pageModule in _pageModules.GetPageModules(page.SiteId).Where(item => item.PageId == page.PageId).ToList())
{ {
var modulePermissions = _permissionRepository.GetPermissions(pageModule.Module.SiteId, EntityNames.Module, pageModule.Module.ModuleId).ToList(); var modulePermissions = _permissionRepository.GetPermissions(pageModule.Module.SiteId, EntityNames.Module, pageModule.Module.ModuleId).ToList();
// permissions added // permissions added

View File

@ -120,7 +120,8 @@ namespace Oqtane.Controllers
if (page != null && page.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, page.SiteId, EntityNames.Page, pageid, PermissionNames.Edit)) if (page != null && page.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, page.SiteId, EntityNames.Page, pageid, PermissionNames.Edit))
{ {
int order = 1; int order = 1;
List<PageModule> pagemodules = _pageModules.GetPageModules(pageid, pane).OrderBy(item => item.Order).ToList(); List<PageModule> pagemodules = _pageModules.GetPageModules(page.SiteId)
.Where(item => item.PageId == pageid && item.Pane == pane).OrderBy(item => item.Order).ToList();
foreach (PageModule pagemodule in pagemodules) foreach (PageModule pagemodule in pagemodules)
{ {
if (pagemodule.Order != order) if (pagemodule.Order != order)

View File

@ -28,7 +28,7 @@ namespace Oqtane.Controllers
// GET: api/<controller>?siteid=x // GET: api/<controller>?siteid=x
[HttpGet] [HttpGet]
[Authorize(Policy = $"{EntityNames.Profile}:{PermissionNames.Read}:{RoleNames.Registered}")] [Authorize(Roles = RoleNames.Registered)]
public IEnumerable<Profile> Get(string siteid) public IEnumerable<Profile> Get(string siteid)
{ {
int SiteId; int SiteId;
@ -46,7 +46,7 @@ namespace Oqtane.Controllers
// GET api/<controller>/5 // GET api/<controller>/5
[HttpGet("{id}")] [HttpGet("{id}")]
[Authorize(Policy = $"{EntityNames.Profile}:{PermissionNames.Read}:{RoleNames.Registered}")] [Authorize(Roles = RoleNames.Registered)]
public Profile Get(int id) public Profile Get(int id)
{ {
var profile = _profiles.GetProfile(id); var profile = _profiles.GetProfile(id);

View File

@ -28,7 +28,7 @@ namespace Oqtane.Controllers
// GET: api/<controller>?siteid=x&global=true/false // GET: api/<controller>?siteid=x&global=true/false
[HttpGet] [HttpGet]
[Authorize(Policy = $"{EntityNames.Role}:{PermissionNames.Read}:{RoleNames.Registered}")] [Authorize(Roles = RoleNames.Registered)]
public IEnumerable<Role> Get(string siteid, string global) public IEnumerable<Role> Get(string siteid, string global)
{ {
int SiteId; int SiteId;
@ -50,7 +50,7 @@ namespace Oqtane.Controllers
// GET api/<controller>/5 // GET api/<controller>/5
[HttpGet("{id}")] [HttpGet("{id}")]
[Authorize(Policy = $"{EntityNames.Role}:{PermissionNames.Read}:{RoleNames.Registered}")] [Authorize(Roles = RoleNames.Registered)]
public Role Get(int id) public Role Get(int id)
{ {
var role = _roles.GetRole(id); var role = _roles.GetRole(id);

View File

@ -46,7 +46,7 @@ namespace Oqtane.Controllers
_identityCache = identityCache; _identityCache = identityCache;
_logger = logger; _logger = logger;
_alias = tenantManager.GetAlias(); _alias = tenantManager.GetAlias();
_visitorCookie = "APP_VISITOR_" + _alias.SiteId.ToString(); _visitorCookie = Constants.VisitorCookiePrefix + _alias.SiteId.ToString();
} }
// GET: api/<controller> // GET: api/<controller>
@ -63,10 +63,14 @@ namespace Oqtane.Controllers
} }
} }
else else
{
// suppress unauthorized visitor logging as it is usually caused by clients that do not support cookies
if (entityName != EntityNames.Visitor)
{ {
_logger.Log(LogLevel.Error, this, LogFunction.Read, "User Not Authorized To Access Settings {EntityName} {EntityId}", entityName, entityId); _logger.Log(LogLevel.Error, this, LogFunction.Read, "User Not Authorized To Access Settings {EntityName} {EntityId}", entityName, entityId);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
} }
}
return settings; return settings;
} }
@ -84,9 +88,12 @@ namespace Oqtane.Controllers
return setting; return setting;
} }
else else
{
if (entityName != EntityNames.Visitor)
{ {
_logger.Log(LogLevel.Error, this, LogFunction.Read, "User Not Authorized To Access Setting {EntityName} {SettingId}", entityName, id); _logger.Log(LogLevel.Error, this, LogFunction.Read, "User Not Authorized To Access Setting {EntityName} {SettingId}", entityName, id);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
}
return null; return null;
} }
} }
@ -102,9 +109,12 @@ namespace Oqtane.Controllers
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Setting Added {Setting}", setting); _logger.Log(LogLevel.Information, this, LogFunction.Create, "Setting Added {Setting}", setting);
} }
else else
{
if (setting.EntityName != EntityNames.Visitor)
{ {
_logger.Log(LogLevel.Error, this, LogFunction.Create, "User Not Authorized To Add Setting {Setting}", setting); _logger.Log(LogLevel.Error, this, LogFunction.Create, "User Not Authorized To Add Setting {Setting}", setting);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
}
setting = null; setting = null;
} }
return setting; return setting;
@ -121,9 +131,12 @@ namespace Oqtane.Controllers
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Setting Updated {Setting}", setting); _logger.Log(LogLevel.Information, this, LogFunction.Update, "Setting Updated {Setting}", setting);
} }
else else
{
if (setting.EntityName != EntityNames.Visitor)
{ {
_logger.Log(LogLevel.Error, this, LogFunction.Update, "User Not Authorized To Update Setting {Setting}", setting); _logger.Log(LogLevel.Error, this, LogFunction.Update, "User Not Authorized To Update Setting {Setting}", setting);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
}
setting = null; setting = null;
} }
return setting; return setting;
@ -141,11 +154,14 @@ namespace Oqtane.Controllers
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Setting Deleted {Setting}", setting); _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Setting Deleted {Setting}", setting);
} }
else else
{
if (entityName != EntityNames.Visitor)
{ {
_logger.Log(LogLevel.Error, this, LogFunction.Delete, "User Not Authorized To Delete Setting {Setting}", setting); _logger.Log(LogLevel.Error, this, LogFunction.Delete, "User Not Authorized To Delete Setting {Setting}", setting);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
} }
} }
}
// DELETE api/<controller>/clear // DELETE api/<controller>/clear
[HttpDelete("clear")] [HttpDelete("clear")]
@ -212,13 +228,14 @@ namespace Oqtane.Controllers
authorized = true; authorized = true;
if (permissionName == PermissionNames.Edit) if (permissionName == PermissionNames.Edit)
{ {
authorized = User.IsInRole(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; break;
case EntityNames.Visitor: case EntityNames.Visitor:
authorized = User.IsInRole(RoleNames.Admin); authorized = User.IsInRole(RoleNames.Admin);
if (!authorized) if (!authorized)
{ {
// a visitor may have cookies disabled
if (int.TryParse(Request.Cookies[_visitorCookie], out int visitorId)) if (int.TryParse(Request.Cookies[_visitorCookie], out int visitorId))
{ {
authorized = (visitorId == entityId); authorized = (visitorId == entityId);
@ -226,13 +243,11 @@ namespace Oqtane.Controllers
} }
break; break;
default: // custom entity default: // custom entity
authorized = true;
if (permissionName == PermissionNames.Edit) if (permissionName == PermissionNames.Edit)
{ {
authorized = User.IsInRole(RoleNames.Admin) || _userPermissions.IsAuthorized(User, _alias.SiteId, entityName, entityId, permissionName); authorized = _userPermissions.IsAuthorized(User, _alias.SiteId, entityName, entityId, permissionName) ||
} _userPermissions.IsAuthorized(User, _alias.SiteId, entityName, -1, PermissionNames.Write, RoleNames.Admin);
else
{
authorized = true;
} }
break; break;
} }

View File

@ -53,7 +53,7 @@ namespace Oqtane.Controllers
catch (Exception ex) catch (Exception ex)
{ {
results.Add(new Dictionary<string, string>() { { "Error", ex.Message } }); results.Add(new Dictionary<string, string>() { { "Error", ex.Message } });
_logger.Log(LogLevel.Error, this, LogFunction.Other, ex, "Sql Query {Query} Executed on Tenant {TenantId} Resulted In An Error {Error}", sqlquery.Query, sqlquery.TenantId, ex.Message); _logger.Log(LogLevel.Warning, this, LogFunction.Other, "Sql Query {Query} Executed on Tenant {TenantId} Resulted In An Error {Error}", sqlquery.Query, sqlquery.TenantId, ex.Message);
} }
sqlquery.Results = results; sqlquery.Results = results;
return sqlquery; return sqlquery;

View File

@ -6,6 +6,7 @@ using System;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Oqtane.Infrastructure; using Oqtane.Infrastructure;
using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Http.Features;
using System.IO;
namespace Oqtane.Controllers namespace Oqtane.Controllers
{ {
@ -53,6 +54,15 @@ namespace Oqtane.Controllers
systeminfo.Add("UseSwagger", _configManager.GetSetting("UseSwagger", "true")); systeminfo.Add("UseSwagger", _configManager.GetSetting("UseSwagger", "true"));
systeminfo.Add("PackageService", _configManager.GetSetting("PackageService", "true")); systeminfo.Add("PackageService", _configManager.GetSetting("PackageService", "true"));
break; break;
case "log":
string log = "";
string path = Path.Combine(_environment.ContentRootPath, "Content", "Log", "error.log");
if (System.IO.File.Exists(path))
{
log = System.IO.File.ReadAllText(path);
}
systeminfo.Add("Log", log);
break;
} }
return systeminfo; return systeminfo;
@ -74,7 +84,7 @@ namespace Oqtane.Controllers
{ {
foreach(KeyValuePair<string, object> kvp in settings) foreach(KeyValuePair<string, object> kvp in settings)
{ {
_configManager.AddOrUpdateSetting(kvp.Key, kvp.Value, false); UpdateSetting(kvp.Key, kvp.Value);
} }
} }
@ -83,7 +93,24 @@ namespace Oqtane.Controllers
[Authorize(Roles = RoleNames.Host)] [Authorize(Roles = RoleNames.Host)]
public void Put(string key, object value) public void Put(string key, object value)
{ {
UpdateSetting(key, value);
}
private void UpdateSetting(string key, object value)
{
switch (key)
{
case "Log":
string path = Path.Combine(_environment.ContentRootPath, "Content", "Log", "error.log");
if (System.IO.File.Exists(path))
{
System.IO.File.Delete(path);
}
break;
default:
_configManager.AddOrUpdateSetting(key, value, false); _configManager.AddOrUpdateSetting(key, value, false);
break;
}
} }
} }
} }

View File

@ -29,23 +29,25 @@ namespace Oqtane.Controllers
private readonly ITenantManager _tenantManager; private readonly ITenantManager _tenantManager;
private readonly INotificationRepository _notifications; private readonly INotificationRepository _notifications;
private readonly IFolderRepository _folders; private readonly IFolderRepository _folders;
private readonly ISyncManager _syncManager;
private readonly ISiteRepository _sites; private readonly ISiteRepository _sites;
private readonly IUserPermissions _userPermissions;
private readonly IJwtManager _jwtManager; private readonly IJwtManager _jwtManager;
private readonly ISyncManager _syncManager;
private readonly ILogManager _logger; private readonly ILogManager _logger;
public UserController(IUserRepository users, IUserRoleRepository userRoles, UserManager<IdentityUser> identityUserManager, SignInManager<IdentityUser> identitySignInManager, ITenantManager tenantManager, INotificationRepository notifications, IFolderRepository folders, ISyncManager syncManager, ISiteRepository sites, IJwtManager jwtManager, ILogManager logger) public UserController(IUserRepository users, IUserRoleRepository userRoles, UserManager<IdentityUser> identityUserManager, SignInManager<IdentityUser> identitySignInManager, ITenantManager tenantManager, INotificationRepository notifications, IFolderRepository folders, ISiteRepository sites, IUserPermissions userPermissions, IJwtManager jwtManager, ISyncManager syncManager, ILogManager logger)
{ {
_users = users; _users = users;
_userRoles = userRoles; _userRoles = userRoles;
_identityUserManager = identityUserManager; _identityUserManager = identityUserManager;
_identitySignInManager = identitySignInManager; _identitySignInManager = identitySignInManager;
_tenantManager = tenantManager; _tenantManager = tenantManager;
_folders = folders;
_notifications = notifications; _notifications = notifications;
_syncManager = syncManager; _folders = folders;
_sites = sites; _sites = sites;
_userPermissions = userPermissions;
_jwtManager = jwtManager; _jwtManager = jwtManager;
_syncManager = syncManager;
_logger = logger; _logger = logger;
} }
@ -105,7 +107,7 @@ namespace Oqtane.Controllers
user.TwoFactorCode = ""; user.TwoFactorCode = "";
user.TwoFactorExpiry = null; user.TwoFactorExpiry = null;
if (!User.IsInRole(RoleNames.Admin) && User.Identity.Name?.ToLower() != user.Username.ToLower()) if (!_userPermissions.IsAuthorized(User, user.SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin) && User.Identity.Name?.ToLower() != user.Username.ToLower())
{ {
user.Email = ""; user.Email = "";
user.PhotoFileId = null; user.PhotoFileId = null;
@ -149,7 +151,7 @@ namespace Oqtane.Controllers
bool verified; bool verified;
bool allowregistration; bool allowregistration;
if (User.IsInRole(RoleNames.Admin)) if (_userPermissions.IsAuthorized(User, user.SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin))
{ {
verified = true; verified = true;
allowregistration = true; allowregistration = true;
@ -241,7 +243,8 @@ 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 && _users.GetUser(user.UserId, false) != null && (User.IsInRole(RoleNames.Admin) || User.Identity.Name == user.Username)) if (ModelState.IsValid && user.SiteId == _tenantManager.GetAlias().SiteId && _users.GetUser(user.UserId, false) != null
&& (_userPermissions.IsAuthorized(User, user.SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin) || User.Identity.Name == user.Username))
{ {
IdentityUser identityuser = await _identityUserManager.FindByNameAsync(user.Username); IdentityUser identityuser = await _identityUserManager.FindByNameAsync(user.Username);
if (identityuser != null) if (identityuser != null)
@ -287,7 +290,7 @@ namespace Oqtane.Controllers
// DELETE api/<controller>/5?siteid=x // DELETE api/<controller>/5?siteid=x
[HttpDelete("{id}")] [HttpDelete("{id}")]
[Authorize(Roles = RoleNames.Admin)] [Authorize(Policy = $"{EntityNames.User}:{PermissionNames.Write}:{RoleNames.Admin}")]
public async Task Delete(int id, string siteid) public async Task Delete(int id, string siteid)
{ {
int SiteId; int SiteId;

View File

@ -10,6 +10,7 @@ using System.Linq;
using System.Net; using System.Net;
using Oqtane.Security; using Oqtane.Security;
using System; using System;
using Oqtane.Modules.Admin.Roles;
namespace Oqtane.Controllers namespace Oqtane.Controllers
{ {
@ -93,7 +94,7 @@ namespace Oqtane.Controllers
userrole.User.TwoFactorCode = ""; userrole.User.TwoFactorCode = "";
userrole.User.TwoFactorExpiry = null; userrole.User.TwoFactorExpiry = null;
if (!User.IsInRole(RoleNames.Admin) && userid != userrole.User.UserId) if (!_userPermissions.IsAuthorized(User, userrole.User.SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin) && userid != userrole.User.UserId)
{ {
userrole.User.Email = ""; userrole.User.Email = "";
userrole.User.PhotoFileId = null; userrole.User.PhotoFileId = null;
@ -115,7 +116,7 @@ namespace Oqtane.Controllers
// POST api/<controller> // POST api/<controller>
[HttpPost] [HttpPost]
[Authorize(Roles = RoleNames.Admin)] [Authorize(Policy = $"{EntityNames.UserRole}:{PermissionNames.Write}:{RoleNames.Admin}")]
public UserRole Post([FromBody] UserRole userRole) public UserRole Post([FromBody] UserRole userRole)
{ {
var role = _roles.GetRole(userRole.RoleId); var role = _roles.GetRole(userRole.RoleId);
@ -138,7 +139,7 @@ namespace Oqtane.Controllers
// PUT api/<controller>/5 // PUT api/<controller>/5
[HttpPut("{id}")] [HttpPut("{id}")]
[Authorize(Roles = RoleNames.Admin)] [Authorize(Policy = $"{EntityNames.UserRole}:{PermissionNames.Write}:{RoleNames.Admin}")]
public UserRole Put(int id, [FromBody] UserRole userRole) public UserRole Put(int id, [FromBody] UserRole userRole)
{ {
var role = _roles.GetRole(userRole.RoleId); var role = _roles.GetRole(userRole.RoleId);
@ -160,7 +161,7 @@ namespace Oqtane.Controllers
// DELETE api/<controller>/5 // DELETE api/<controller>/5
[HttpDelete("{id}")] [HttpDelete("{id}")]
[Authorize(Roles = RoleNames.Admin)] [Authorize(Policy = $"{EntityNames.UserRole}:{PermissionNames.Write}:{RoleNames.Admin}")]
public void Delete(int id) public void Delete(int id)
{ {
UserRole userrole = _userRoles.GetUserRole(id); UserRole userrole = _userRoles.GetUserRole(id);

View File

@ -50,7 +50,7 @@ namespace Oqtane.Controllers
bool authorized = User.IsInRole(RoleNames.Admin); bool authorized = User.IsInRole(RoleNames.Admin);
if (!authorized) if (!authorized)
{ {
var visitorCookie = "APP_VISITOR_" + _alias.SiteId.ToString(); var visitorCookie = Constants.VisitorCookiePrefix + _alias.SiteId.ToString();
if (int.TryParse(Request.Cookies[visitorCookie], out int visitorId)) if (int.TryParse(Request.Cookies[visitorCookie], out int visitorId))
{ {
authorized = (visitorId == id); authorized = (visitorId == id);

View File

@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Text.Json; using System.Text.Json;
@ -11,20 +11,22 @@ namespace Oqtane.Extensions
public static string EncodePermissions(this IEnumerable<Permission> permissionList) public static string EncodePermissions(this IEnumerable<Permission> permissionList)
{ {
List<PermissionString> permissionstrings = new List<PermissionString>(); List<PermissionString> permissionstrings = new List<PermissionString>();
string entityname = "";
string permissionname = ""; string permissionname = "";
string permissions = ""; string permissions = "";
StringBuilder permissionsbuilder = new StringBuilder(); StringBuilder permissionsbuilder = new StringBuilder();
string securityid = ""; string securityid = "";
foreach (Permission permission in permissionList.OrderBy(item => item.PermissionName)) foreach (Permission permission in permissionList.OrderBy(item => item.EntityName).ThenBy(item => item.PermissionName))
{ {
// permission collections are grouped by permissionname // permission collections are grouped by entityname and permissionname
if (permissionname != permission.PermissionName) if (entityname != permission.EntityName || permissionname != permission.PermissionName)
{ {
permissions = permissionsbuilder.ToString(); permissions = permissionsbuilder.ToString();
if (permissions != "") if (permissions != "")
{ {
permissionstrings.Add(new PermissionString { PermissionName = permissionname, Permissions = permissions.Substring(0, permissions.Length - 1) }); permissionstrings.Add(new PermissionString { EntityName = entityname, PermissionName = permissionname, Permissions = permissions.Substring(0, permissions.Length - 1) });
} }
entityname = permission.EntityName;
permissionname = permission.PermissionName; permissionname = permission.PermissionName;
permissionsbuilder = new StringBuilder(); permissionsbuilder = new StringBuilder();
} }
@ -56,7 +58,7 @@ namespace Oqtane.Extensions
permissions = permissionsbuilder.ToString(); permissions = permissionsbuilder.ToString();
if (permissions != "") if (permissions != "")
{ {
permissionstrings.Add(new PermissionString { PermissionName = permissionname, Permissions = permissions.Substring(0, permissions.Length - 1) }); permissionstrings.Add(new PermissionString { EntityName = entityname, PermissionName = permissionname, Permissions = permissions.Substring(0, permissions.Length - 1) });
} }
return JsonSerializer.Serialize(permissionstrings); return JsonSerializer.Serialize(permissionstrings);
} }

View File

@ -62,7 +62,7 @@ namespace Oqtane.Infrastructure
} }
catch (Exception ex) catch (Exception ex)
{ {
result.Message = "Master Database Not Installed Correctly. " + ex.Message; result.Message = "Master Database Not Installed Correctly. " + ex.ToString();
} }
} }
else // cannot connect else // cannot connect
@ -74,7 +74,7 @@ namespace Oqtane.Infrastructure
} }
catch (Exception ex) catch (Exception ex)
{ {
result.Message = "Cannot Connect To Master Database. " + ex.Message; result.Message = "Cannot Connect To Master Database. " + ex.ToString();
} }
} }
} }
@ -247,7 +247,7 @@ namespace Oqtane.Infrastructure
} }
catch (Exception ex) catch (Exception ex)
{ {
result.Message = ex.Message; result.Message = ex.ToString();
_filelogger.LogError(Utilities.LogMessage(this, result.Message)); _filelogger.LogError(Utilities.LogMessage(this, result.Message));
} }
@ -286,7 +286,7 @@ namespace Oqtane.Infrastructure
} }
catch (Exception ex) catch (Exception ex)
{ {
result.Message = "An Error Occurred Creating The Database. This Is Usually Related To Your User Not Having Sufficient Rights To Perform This Operation. Please Note That You Can Also Create The Database Manually Prior To Initiating The Install Wizard. " + ex.Message; result.Message = "An Error Occurred Creating The Database. This Is Usually Related To Your User Not Having Sufficient Rights To Perform This Operation. Please Note That You Can Also Create The Database Manually Prior To Initiating The Install Wizard. " + ex.ToString();
_filelogger.LogError(Utilities.LogMessage(this, result.Message)); _filelogger.LogError(Utilities.LogMessage(this, result.Message));
} }
} }
@ -327,7 +327,7 @@ namespace Oqtane.Infrastructure
} }
catch (Exception ex) catch (Exception ex)
{ {
result.Message = "An Error Occurred Provisioning The Master Database. This Is Usually Related To The Master Database Not Being In A Supported State. " + ex.Message; result.Message = "An Error Occurred Provisioning The Master Database. This Is Usually Related To The Master Database Not Being In A Supported State. " + ex.ToString();
_filelogger.LogError(Utilities.LogMessage(this, result.Message)); _filelogger.LogError(Utilities.LogMessage(this, result.Message));
} }
} }
@ -370,7 +370,7 @@ namespace Oqtane.Infrastructure
tenant = db.Tenant.FirstOrDefault(item => item.Name == install.TenantName); tenant = db.Tenant.FirstOrDefault(item => item.Name == install.TenantName);
} }
var aliasNames = install.Aliases.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(sValue => sValue.Trim()).ToArray(); var aliasNames = install.Aliases.Split(',', StringSplitOptions.RemoveEmptyEntries).Select(sValue => sValue.Trim()).ToArray();
var firstAlias = aliasNames[0]; var firstAlias = aliasNames[0];
foreach (var aliasName in aliasNames) foreach (var aliasName in aliasNames)
{ {
@ -406,7 +406,7 @@ namespace Oqtane.Infrastructure
{ {
var result = new Installation { Success = false, Message = string.Empty }; var result = new Installation { Success = false, Message = string.Empty };
var versions = Constants.ReleaseVersions.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); var versions = Constants.ReleaseVersions.Split(',', StringSplitOptions.RemoveEmptyEntries);
using (var scope = _serviceScopeFactory.CreateScope()) using (var scope = _serviceScopeFactory.CreateScope())
{ {
@ -435,7 +435,7 @@ namespace Oqtane.Infrastructure
} }
catch (Exception ex) catch (Exception ex)
{ {
result.Message = "An Error Occurred Migrating A Tenant Database. This Is Usually Related To A Tenant Database Not Being In A Supported State. " + ex.Message; result.Message = "An Error Occurred Migrating A Tenant Database. This Is Usually Related To A Tenant Database Not Being In A Supported State. " + ex.ToString();
_filelogger.LogError(Utilities.LogMessage(this, result.Message)); _filelogger.LogError(Utilities.LogMessage(this, result.Message));
} }
@ -456,7 +456,7 @@ namespace Oqtane.Infrastructure
} }
catch (Exception ex) catch (Exception ex)
{ {
result.Message = "An Error Occurred Executing Upgrade Logic. " + ex.Message; result.Message = "An Error Occurred Executing Upgrade Logic. " + ex.ToString();
_filelogger.LogError(Utilities.LogMessage(this, result.Message)); _filelogger.LogError(Utilities.LogMessage(this, result.Message));
} }
} }
@ -486,7 +486,7 @@ namespace Oqtane.Infrastructure
{ {
if (!string.IsNullOrEmpty(moduleDefinition.ReleaseVersions)) if (!string.IsNullOrEmpty(moduleDefinition.ReleaseVersions))
{ {
var versions = moduleDefinition.ReleaseVersions.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); var versions = moduleDefinition.ReleaseVersions.Split(',', StringSplitOptions.RemoveEmptyEntries);
using (var db = GetInstallationContext()) using (var db = GetInstallationContext())
{ {
if (!string.IsNullOrEmpty(moduleDefinition.ServerManagerType)) if (!string.IsNullOrEmpty(moduleDefinition.ServerManagerType))
@ -526,7 +526,7 @@ namespace Oqtane.Infrastructure
} }
catch (Exception ex) catch (Exception ex)
{ {
result.Message = "An Error Occurred Installing " + moduleDefinition.Name + " Version " + versions[i] + " - " + ex.Message; result.Message = "An Error Occurred Installing " + moduleDefinition.Name + " Version " + versions[i] + " - " + ex.ToString();
} }
} }
} }
@ -575,7 +575,7 @@ namespace Oqtane.Infrastructure
{ {
// set the alias explicitly so the tenant can be resolved // set the alias explicitly so the tenant can be resolved
var aliases = scope.ServiceProvider.GetRequiredService<IAliasRepository>(); var aliases = scope.ServiceProvider.GetRequiredService<IAliasRepository>();
var aliasNames = install.Aliases.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(sValue => sValue.Trim()).ToArray(); var aliasNames = install.Aliases.Split(',', StringSplitOptions.RemoveEmptyEntries).Select(sValue => sValue.Trim()).ToArray();
var firstAlias = aliasNames[0]; var firstAlias = aliasNames[0];
var alias = aliases.GetAliases().FirstOrDefault(item => item.Name == firstAlias); var alias = aliases.GetAliases().FirstOrDefault(item => item.Name == firstAlias);
var tenantManager = scope.ServiceProvider.GetRequiredService<ITenantManager>(); var tenantManager = scope.ServiceProvider.GetRequiredService<ITenantManager>();
@ -664,7 +664,7 @@ namespace Oqtane.Infrastructure
} }
catch (Exception ex) catch (Exception ex)
{ {
result.Message = "An Error Occurred Creating Site. " + ex.Message; result.Message = "An Error Occurred Creating Site. " + ex.ToString();
} }
} }
@ -737,7 +737,7 @@ namespace Oqtane.Infrastructure
} }
catch (Exception ex) catch (Exception ex)
{ {
logger.Log(alias.SiteId, Shared.LogLevel.Error, "Site Migration", LogFunction.Other, "An Error Occurred Executing Site Migration {Type} For {Alias} And Version {Version} {Error}", upgrade.Value, alias.Name, version, ex.Message); logger.Log(alias.SiteId, Shared.LogLevel.Error, "Site Migration", LogFunction.Other, ex, "An Error Occurred Executing Site Migration {Type} For {Alias} And Version {Version}", upgrade.Value, alias.Name, version);
} }
} }
} }

View File

@ -66,7 +66,7 @@ namespace Oqtane.Infrastructure
List<Notification> notifications = notificationRepository.GetNotifications(site.SiteId, -1, -1).ToList(); List<Notification> notifications = notificationRepository.GetNotifications(site.SiteId, -1, -1).ToList();
foreach (Notification notification in notifications) foreach (Notification notification in notifications)
{ {
// get sender and receiver information if not provided // get sender and receiver information from user object if not provided
if ((string.IsNullOrEmpty(notification.FromEmail) || string.IsNullOrEmpty(notification.FromDisplayName)) && notification.FromUserId != null) if ((string.IsNullOrEmpty(notification.FromEmail) || string.IsNullOrEmpty(notification.FromDisplayName)) && notification.FromUserId != null)
{ {
var user = userRepository.GetUser(notification.FromUserId.Value); var user = userRepository.GetUser(notification.FromUserId.Value);
@ -96,31 +96,41 @@ namespace Oqtane.Infrastructure
else else
{ {
MailMessage mailMessage = new MailMessage(); MailMessage mailMessage = new MailMessage();
mailMessage.From = new MailAddress(settings["SMTPSender"], site.Name);
mailMessage.Subject = notification.Subject; // sender
if (!string.IsNullOrEmpty(notification.FromEmail) && !string.IsNullOrEmpty(notification.FromDisplayName)) if (settings.ContainsKey("SMTPRelay") && settings["SMTPRelay"] == "True" && !string.IsNullOrEmpty(notification.FromEmail))
{ {
mailMessage.Body = "From: " + notification.FromDisplayName + "<" + notification.FromEmail + ">" + "\n"; if (!string.IsNullOrEmpty(notification.FromDisplayName))
{
mailMessage.From = new MailAddress(notification.FromEmail, notification.FromDisplayName);
} }
else else
{ {
mailMessage.Body = "From: " + site.Name + "\n"; mailMessage.From = new MailAddress(notification.FromEmail);
} }
mailMessage.Body += "Sent: " + notification.CreatedOn + "\n"; }
if (!string.IsNullOrEmpty(notification.ToEmail) && !string.IsNullOrEmpty(notification.ToDisplayName)) else
{
mailMessage.From = new MailAddress(settings["SMTPSender"], (!string.IsNullOrEmpty(notification.FromDisplayName)) ? notification.FromDisplayName : site.Name);
}
// recipient
if (!string.IsNullOrEmpty(notification.ToDisplayName))
{ {
mailMessage.To.Add(new MailAddress(notification.ToEmail, notification.ToDisplayName)); mailMessage.To.Add(new MailAddress(notification.ToEmail, notification.ToDisplayName));
mailMessage.Body += "To: " + notification.ToDisplayName + "<" + notification.ToEmail + ">" + "\n";
} }
else else
{ {
mailMessage.To.Add(new MailAddress(notification.ToEmail)); mailMessage.To.Add(new MailAddress(notification.ToEmail));
mailMessage.Body += "To: " + notification.ToEmail + "\n";
} }
mailMessage.Body += "Subject: " + notification.Subject + "\n\n";
mailMessage.Body += notification.Body;
// set encoding // subject
mailMessage.Subject = notification.Subject;
//body
mailMessage.Body = notification.Body;
// encoding
mailMessage.SubjectEncoding = System.Text.Encoding.UTF8; mailMessage.SubjectEncoding = System.Text.Encoding.UTF8;
mailMessage.BodyEncoding = System.Text.Encoding.UTF8; mailMessage.BodyEncoding = System.Text.Encoding.UTF8;

View File

@ -43,10 +43,10 @@ namespace Oqtane.Infrastructure
}); });
context.Items.Add(Constants.HttpContextSiteSettingsKey, sitesettings); context.Items.Add(Constants.HttpContextSiteSettingsKey, sitesettings);
// rewrite path by removing alias path prefix from api and pages requests (for consistent routing) // rewrite path by removing alias path prefix from reserved route (api,pages,files) requests for consistent routes
if (!string.IsNullOrEmpty(alias.Path)) if (!string.IsNullOrEmpty(alias.Path))
{ {
if (path.StartsWith("/" + alias.Path) && (path.Contains("/api/") || path.Contains("/pages/"))) if (path.StartsWith("/" + alias.Path) && (Constants.ReservedRoutes.Any(item => path.Contains("/" + item + "/"))))
{ {
context.Request.Path = path.Replace("/" + alias.Path, ""); context.Request.Path = path.Replace("/" + alias.Path, "");
} }

View File

@ -68,7 +68,7 @@ namespace Oqtane.SiteTemplates
new Permission(PermissionNames.View, RoleNames.Admin, true), new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true) new Permission(PermissionNames.Edit, RoleNames.Admin, true)
}.EncodePermissions(), }.EncodePermissions(),
Content = "<p>Copyright (c) 2018-2022 .NET Foundation</p>" + Content = "<p>Copyright (c) 2018-2023 .NET Foundation</p>" +
"<p>Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:</p>" + "<p>Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:</p>" +
"<p>The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.</p>" + "<p>The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.</p>" +
"<p>THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.</p>" "<p>THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.</p>"

View File

@ -37,7 +37,7 @@ namespace Oqtane.Infrastructure
{ {
// legacy support for client api requests which would include the alias as a path prefix ( ie. {alias}/api/[controller] ) // legacy support for client api requests which would include the alias as a path prefix ( ie. {alias}/api/[controller] )
int aliasId; int aliasId;
string[] segments = httpcontext.Request.Path.Value.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); string[] segments = httpcontext.Request.Path.Value.Split('/', StringSplitOptions.RemoveEmptyEntries);
if (segments.Length > 1 && Shared.Constants.ReservedRoutes.Contains(segments[1]) && int.TryParse(segments[0], out aliasId)) if (segments.Length > 1 && Shared.Constants.ReservedRoutes.Contains(segments[1]) && int.TryParse(segments[0], out aliasId))
{ {
alias = _aliasRepository.GetAliases().ToList().FirstOrDefault(item => item.AliasId == aliasId); alias = _aliasRepository.GetAliases().ToList().FirstOrDefault(item => item.AliasId == aliasId);

View File

@ -308,43 +308,45 @@ namespace Oqtane.Infrastructure
private void Upgrade_3_3_0(Tenant tenant, IServiceScope scope) private void Upgrade_3_3_0(Tenant tenant, IServiceScope scope)
{ {
var pageTemplates = new List<PageTemplate>(); try
pageTemplates.Add(new PageTemplate
{ {
Name = "API Management", var roles = scope.ServiceProvider.GetRequiredService<IRoleRepository>();
Parent = "Admin",
Order = 35,
Path = "admin/apis",
Icon = Icons.CloudDownload,
IsNavigation = true,
IsPersonalizable = false,
PagePermissions = new List<Permission>
{
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
}.EncodePermissions(),
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule
{
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Visitors.Index).ToModuleDefinitionName(), Title = "Visitor Management", Pane = PaneNames.Default,
ModulePermissions = new List<Permission>
{
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
}.EncodePermissions(),
Content = ""
}
}
});
var pages = scope.ServiceProvider.GetRequiredService<IPageRepository>(); var pages = scope.ServiceProvider.GetRequiredService<IPageRepository>();
var modules = scope.ServiceProvider.GetRequiredService<IModuleRepository>();
var sites = scope.ServiceProvider.GetRequiredService<ISiteRepository>(); var permissions = scope.ServiceProvider.GetRequiredService<IPermissionRepository>();
foreach (Site site in sites.GetSites().ToList()) var siteRepository = scope.ServiceProvider.GetRequiredService<ISiteRepository>();
foreach (Site site in siteRepository.GetSites().ToList())
{ {
sites.CreatePages(site, pageTemplates); int roleid = roles.GetRoles(site.SiteId).FirstOrDefault(item => item.Name == RoleNames.Registered).RoleId;
int pageid = pages.GetPages(site.SiteId).FirstOrDefault(item => item.Path == "admin").PageId;
var permission = new Permission
{
SiteId = site.SiteId,
EntityName = EntityNames.Page,
EntityId = pageid,
PermissionName = PermissionNames.View,
RoleId = roleid,
IsAuthorized = true
};
permissions.AddPermission(permission);
int moduleid = modules.GetModules(site.SiteId).FirstOrDefault(item => item.ModuleDefinitionName == "Oqtane.Modules.Admin.Dashboard, Oqtane.Client").ModuleId;
permission = new Permission
{
SiteId = site.SiteId,
EntityName = EntityNames.Module,
EntityId = moduleid,
PermissionName = PermissionNames.View,
RoleId = roleid,
IsAuthorized = true
};
permissions.AddPermission(permission);
}
}
catch (Exception ex)
{
Debug.WriteLine($"Oqtane Error: Error In 3.3.0 Upgrade Logic - {ex}");
} }
} }
} }

View File

@ -0,0 +1,35 @@
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.03.03.02.01")]
public class AddFolderFileIsDeletedColumns : MultiDatabaseMigration
{
public AddFolderFileIsDeletedColumns(IDatabase database) : base(database)
{
}
protected override void Up(MigrationBuilder migrationBuilder)
{
// IsDeleted columns were removed in 3.2.2 however SQLite does not support column removal so they had to be restored
if (ActiveDatabase.Name != "Sqlite")
{
var folderEntityBuilder = new FolderEntityBuilder(migrationBuilder, ActiveDatabase);
folderEntityBuilder.AddBooleanColumn("IsDeleted", true);
var fileEntityBuilder = new FileEntityBuilder(migrationBuilder, ActiveDatabase);
fileEntityBuilder.AddBooleanColumn("IsDeleted", true);
}
}
protected override void Down(MigrationBuilder migrationBuilder)
{
// not implemented
}
}
}

View File

@ -3,7 +3,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<Configurations>Debug;Release</Configurations> <Configurations>Debug;Release</Configurations>
<Version>3.2.1</Version> <Version>3.3.1</Version>
<Product>Oqtane</Product> <Product>Oqtane</Product>
<Authors>Shaun Walker</Authors> <Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company> <Company>.NET Foundation</Company>
@ -11,7 +11,7 @@
<Copyright>.NET Foundation</Copyright> <Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl> <PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl> <PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.3.1</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl> <RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType> <RepositoryType>Git</RepositoryType>
<RootNamespace>Oqtane</RootNamespace> <RootNamespace>Oqtane</RootNamespace>

View File

@ -1,3 +1,4 @@
using System.Net;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
@ -43,6 +44,10 @@ namespace Oqtane.Pages
{ {
returnurl = ""; returnurl = "";
} }
else
{
returnurl = WebUtility.UrlDecode(returnurl);
}
if (!returnurl.StartsWith("/")) if (!returnurl.StartsWith("/"))
{ {
returnurl = "/" + returnurl; returnurl = "/" + returnurl;

View File

@ -20,7 +20,6 @@ using Oqtane.Enums;
using Oqtane.Security; using Oqtane.Security;
using Oqtane.Extensions; using Oqtane.Extensions;
using Oqtane.Themes; using Oqtane.Themes;
using Oqtane.UI;
namespace Oqtane.Pages namespace Oqtane.Pages
{ {
@ -285,7 +284,7 @@ namespace Oqtane.Pages
// check if cookie already exists // check if cookie already exists
Visitor visitor = null; Visitor visitor = null;
bool addcookie = false; bool addcookie = false;
var VisitorCookie = "APP_VISITOR_" + SiteId.ToString(); var VisitorCookie = Constants.VisitorCookiePrefix + SiteId.ToString();
if (!int.TryParse(Request.Cookies[VisitorCookie], out VisitorId)) if (!int.TryParse(Request.Cookies[VisitorCookie], out VisitorId))
{ {
// if enabled use IP Address correlation // if enabled use IP Address correlation

View File

@ -67,7 +67,7 @@ namespace Oqtane.Repository
Alias alias = null; Alias alias = null;
List<Alias> aliases = GetAliases().ToList(); List<Alias> aliases = GetAliases().ToList();
var segments = url.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); var segments = url.Split('/', StringSplitOptions.RemoveEmptyEntries);
// iterate segments to find keywords // iterate segments to find keywords
int start = segments.Length; int start = segments.Length;

View File

@ -31,7 +31,7 @@ namespace Oqtane.Repository
_accessor = httpContextAccessor; _accessor = httpContextAccessor;
} }
public IDatabase ActiveDatabase { get; private set; } public IDatabase ActiveDatabase { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{ {

View File

@ -1,4 +1,3 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@ -27,10 +26,23 @@ namespace Oqtane.Repository
} }
public IEnumerable<File> GetFiles(int folderId) public IEnumerable<File> GetFiles(int folderId)
{
return GetFiles(folderId, true);
}
public IEnumerable<File> GetFiles(int folderId, bool tracking)
{ {
var alias = _tenants.GetAlias(); var alias = _tenants.GetAlias();
IEnumerable<Permission> permissions = _permissions.GetPermissions(alias.SiteId, EntityNames.Folder, folderId).ToList(); IEnumerable<Permission> permissions = _permissions.GetPermissions(alias.SiteId, EntityNames.Folder, folderId).ToList();
IEnumerable<File> files = _db.File.Where(item => item.FolderId == folderId).Include(item => item.Folder); IEnumerable<File> files;
if (tracking)
{
files = _db.File.Where(item => item.FolderId == folderId).Include(item => item.Folder);
}
else
{
files = _db.File.AsNoTracking().Where(item => item.FolderId == folderId).Include(item => item.Folder);
}
foreach (File file in files) foreach (File file in files)
{ {
file.Folder.Permissions = permissions.EncodePermissions(); file.Folder.Permissions = permissions.EncodePermissions();
@ -41,6 +53,7 @@ namespace Oqtane.Repository
public File AddFile(File file) public File AddFile(File file)
{ {
file.IsDeleted = false;
_db.File.Add(file); _db.File.Add(file);
_db.SaveChanges(); _db.SaveChanges();
file.Folder = _folderRepository.GetFolder(file.FolderId); file.Folder = _folderRepository.GetFolder(file.FolderId);
@ -134,7 +147,7 @@ namespace Oqtane.Repository
public string GetFilePath(File file) public string GetFilePath(File file)
{ {
if (file == null) return null; if (file == null) return null;
var folder = file.Folder ?? _db.Folder.Find(file.FolderId); var folder = file.Folder ?? _db.Folder.AsNoTracking().FirstOrDefault(item => item.FolderId == file.FolderId);
var filepath = Path.Combine(_folderRepository.GetFolderPath(folder), file.Name); var filepath = Path.Combine(_folderRepository.GetFolderPath(folder), file.Name);
return filepath; return filepath;
} }

View File

@ -37,6 +37,7 @@ namespace Oqtane.Repository
public Folder AddFolder(Folder folder) public Folder AddFolder(Folder folder)
{ {
folder.IsDeleted = false;
_db.Folder.Add(folder); _db.Folder.Add(folder);
_db.SaveChanges(); _db.SaveChanges();
_permissions.UpdatePermissions(folder.SiteId, EntityNames.Folder, folder.FolderId, folder.Permissions); _permissions.UpdatePermissions(folder.SiteId, EntityNames.Folder, folder.FolderId, folder.Permissions);

View File

@ -6,6 +6,7 @@ namespace Oqtane.Repository
public interface IFileRepository public interface IFileRepository
{ {
IEnumerable<File> GetFiles(int folderId); IEnumerable<File> GetFiles(int folderId);
IEnumerable<File> GetFiles(int folderId, bool tracking);
File AddFile(File file); File AddFile(File file);
File UpdateFile(File file); File UpdateFile(File file);
File GetFile(int fileId); File GetFile(int fileId);

View File

@ -6,7 +6,6 @@ namespace Oqtane.Repository
public interface IPageModuleRepository public interface IPageModuleRepository
{ {
IEnumerable<PageModule> GetPageModules(int siteId); IEnumerable<PageModule> GetPageModules(int siteId);
IEnumerable<PageModule> GetPageModules(int pageId, string pane);
PageModule AddPageModule(PageModule pageModule); PageModule AddPageModule(PageModule pageModule);
PageModule UpdatePageModule(PageModule pageModule); PageModule UpdatePageModule(PageModule pageModule);
PageModule GetPageModule(int pageModuleId); PageModule GetPageModule(int pageModuleId);

View File

@ -10,46 +10,28 @@ namespace Oqtane.Repository
public class PageModuleRepository : IPageModuleRepository public class PageModuleRepository : IPageModuleRepository
{ {
private TenantDBContext _db; private TenantDBContext _db;
private readonly IModuleDefinitionRepository _moduleDefinitions;
private readonly IPermissionRepository _permissions; private readonly IPermissionRepository _permissions;
public PageModuleRepository(TenantDBContext context, IPermissionRepository permissions) public PageModuleRepository(TenantDBContext context, IModuleDefinitionRepository moduleDefinitions, IPermissionRepository permissions)
{ {
_db = context; _db = context;
_moduleDefinitions = moduleDefinitions;
_permissions = permissions; _permissions = permissions;
} }
public IEnumerable<PageModule> GetPageModules(int siteId) public IEnumerable<PageModule> GetPageModules(int siteId)
{ {
IEnumerable<PageModule> pagemodules = _db.PageModule var pagemodules = _db.PageModule
.Include(item => item.Module) // eager load modules .Include(item => item.Module) // eager load modules
.Where(item => item.Module.SiteId == siteId); .Where(item => item.Module.SiteId == siteId).ToList();
if (pagemodules.Any()) if (pagemodules.Any())
{ {
IEnumerable<Permission> permissions = _permissions.GetPermissions(siteId, EntityNames.Module).ToList(); var moduledefinitions = _moduleDefinitions.GetModuleDefinitions(siteId).ToList();
foreach (PageModule pagemodule in pagemodules) var permissions = _permissions.GetPermissions(siteId, EntityNames.Module).ToList();
for (int index = 0; index < pagemodules.Count; index++)
{ {
pagemodule.Module.Permissions = permissions.Where(item => item.EntityId == pagemodule.ModuleId).EncodePermissions(); pagemodules[index] = GetPageModule(pagemodules[index], moduledefinitions, permissions);
}
}
return pagemodules;
}
public IEnumerable<PageModule> GetPageModules(int pageId, string pane)
{
IEnumerable<PageModule> pagemodules = _db.PageModule
.Include(item => item.Module) // eager load modules
.Where(item => item.PageId == pageId);
if (pane != "" && pagemodules.Any())
{
pagemodules = pagemodules.Where(item => item.Pane == pane);
}
if (pagemodules.Any())
{
var siteId = pagemodules.FirstOrDefault().Module.SiteId;
IEnumerable<Permission> permissions = _permissions.GetPermissions(siteId, EntityNames.Module).ToList();
foreach (PageModule pagemodule in pagemodules)
{
pagemodule.Module.Permissions = permissions.Where(item => item.EntityId == pagemodule.ModuleId).EncodePermissions();
} }
} }
return pagemodules; return pagemodules;
@ -89,7 +71,9 @@ namespace Oqtane.Repository
} }
if (pagemodule != null) if (pagemodule != null)
{ {
pagemodule.Module.Permissions = _permissions.GetPermissions(pagemodule.Module.SiteId, EntityNames.Module, pagemodule.ModuleId)?.EncodePermissions(); var moduledefinitions = _moduleDefinitions.GetModuleDefinitions(pagemodule.Module.SiteId).ToList();
var permissions = _permissions.GetPermissions(pagemodule.Module.SiteId, EntityNames.Module).ToList();
pagemodule = GetPageModule(pagemodule, moduledefinitions, permissions);
} }
return pagemodule; return pagemodule;
} }
@ -100,7 +84,9 @@ namespace Oqtane.Repository
.SingleOrDefault(item => item.PageId == pageId && item.ModuleId == moduleId); .SingleOrDefault(item => item.PageId == pageId && item.ModuleId == moduleId);
if (pagemodule != null) if (pagemodule != null)
{ {
pagemodule.Module.Permissions = _permissions.GetPermissions(pagemodule.Module.SiteId, EntityNames.Module, pagemodule.ModuleId)?.EncodePermissions(); var moduledefinitions = _moduleDefinitions.GetModuleDefinitions(pagemodule.Module.SiteId).ToList();
var permissions = _permissions.GetPermissions(pagemodule.Module.SiteId, EntityNames.Module).ToList();
pagemodule = GetPageModule(pagemodule, moduledefinitions, permissions);
} }
return pagemodule; return pagemodule;
} }
@ -111,5 +97,30 @@ namespace Oqtane.Repository
_db.PageModule.Remove(pageModule); _db.PageModule.Remove(pageModule);
_db.SaveChanges(); _db.SaveChanges();
} }
private PageModule GetPageModule(PageModule pageModule, List<ModuleDefinition> moduleDefinitions, List<Permission> modulePermissions)
{
var permissions = modulePermissions.Where(item => item.EntityId == pageModule.ModuleId).ToList();
// moduledefinition permissionnames can specify permissions for other entities (ie. API permissions)
pageModule.Module.ModuleDefinition = moduleDefinitions.Find(item => item.ModuleDefinitionName == pageModule.Module.ModuleDefinitionName);
if (pageModule.Module.ModuleDefinition != null && !string.IsNullOrEmpty(pageModule.Module.ModuleDefinition.PermissionNames) && pageModule.Module.ModuleDefinition.PermissionNames.Contains(":"))
{
foreach (var permissionname in pageModule.Module.ModuleDefinition.PermissionNames.Split(",", System.StringSplitOptions.RemoveEmptyEntries))
{
if (permissionname.Contains(":"))
{
// moduledefinition permissionnames can be in the form of "EntityName:PermissionName:Roles"
var segments = permissionname.Split(':');
if (segments.Length == 3 && segments[0] != EntityNames.Module)
{
permissions.AddRange(_permissions.GetPermissions(pageModule.Module.SiteId, segments[0], segments[1]).Where(item => item.EntityId == -1));
}
}
}
}
pageModule.Module.Permissions = permissions?.EncodePermissions();
return pageModule;
}
} }
} }

View File

@ -79,23 +79,53 @@ namespace Oqtane.Repository
public void UpdatePermissions(int siteId, string entityName, int entityId, string permissionStrings) public void UpdatePermissions(int siteId, string entityName, int entityId, string permissionStrings)
{ {
// get current permissions and delete bool modified = false;
IEnumerable<Permission> permissions = _db.Permission var existing = new List<Permission>();
.Where(item => item.EntityName == entityName) var permissions = DecodePermissions(permissionStrings, siteId, entityName, entityId);
.Where(item => item.EntityId == entityId) foreach (var permission in permissions)
.Where(item => item.SiteId == siteId);
foreach (Permission permission in permissions)
{ {
_db.Permission.Remove(permission); if (!existing.Any(item => item.EntityName == permission.EntityName && item.PermissionName == permission.PermissionName))
{
existing.AddRange(GetPermissions(siteId, permission.EntityName, permission.PermissionName)
.Where(item => item.EntityId == entityId || item.EntityId == -1));
} }
// add permissions
permissions = DecodePermissions(permissionStrings, siteId, entityName, entityId); var current = existing.FirstOrDefault(item => item.EntityName == permission.EntityName && item.EntityId == permission.EntityId
foreach (Permission permission in permissions) && item.PermissionName == permission.PermissionName && item.RoleId == permission.RoleId && item.UserId == permission.UserId);
if (current != null)
{
if (current.IsAuthorized != permission.IsAuthorized)
{
current.IsAuthorized = permission.IsAuthorized;
current.Role = null; // remove linked reference to Role which can cause errors in EF Core change tracking
_db.Entry(current).State = EntityState.Modified;
modified = true;
}
}
else
{ {
_db.Permission.Add(permission); _db.Permission.Add(permission);
modified = true;
} }
}
foreach (var permission in existing)
{
if (!permissions.Any(item => item.EntityName == permission.EntityName && item.PermissionName == permission.PermissionName
&& item.EntityId == permission.EntityId && item.RoleId == permission.RoleId && item.UserId == permission.UserId))
{
permission.Role = null; // remove linked reference to Role which can cause errors in EF Core change tracking
_db.Permission.Remove(permission);
modified = true;
}
}
if (modified)
{
_db.SaveChanges(); _db.SaveChanges();
ClearCache(siteId, entityName); foreach (var entityname in permissions.Select(item => item.EntityName).Distinct())
{
ClearCache(siteId, entityname);
}
}
} }
public Permission GetPermission(int permissionId) public Permission GetPermission(int permissionId)
@ -195,13 +225,27 @@ namespace Oqtane.Repository
string securityid = ""; string securityid = "";
foreach (PermissionString permissionstring in JsonSerializer.Deserialize<List<PermissionString>>(permissionStrings)) foreach (PermissionString permissionstring in JsonSerializer.Deserialize<List<PermissionString>>(permissionStrings))
{ {
foreach (string id in permissionstring.Permissions.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries)) foreach (string id in permissionstring.Permissions.Split(';', StringSplitOptions.RemoveEmptyEntries))
{ {
securityid = id; securityid = id;
Permission permission = new Permission(); Permission permission = new Permission();
permission.SiteId = siteId; permission.SiteId = siteId;
if (!string.IsNullOrEmpty(permissionstring.EntityName))
{
permission.EntityName = permissionstring.EntityName;
}
else
{
permission.EntityName = entityName; permission.EntityName = entityName;
}
if (permission.EntityName == entityName)
{
permission.EntityId = entityId; permission.EntityId = entityId;
}
else
{
permission.EntityId = -1;
}
permission.PermissionName = permissionstring.PermissionName; permission.PermissionName = permissionstring.PermissionName;
permission.RoleId = null; permission.RoleId = null;
permission.UserId = null; permission.UserId = null;

View File

@ -431,6 +431,7 @@ namespace Oqtane.Repository
PagePermissions = new List<Permission> PagePermissions = new List<Permission>
{ {
new Permission(PermissionNames.View, RoleNames.Admin, true), new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.View, RoleNames.Registered, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true) new Permission(PermissionNames.Edit, RoleNames.Admin, true)
}.EncodePermissions(), }.EncodePermissions(),
PageTemplateModules = new List<PageTemplateModule> PageTemplateModules = new List<PageTemplateModule>
@ -441,6 +442,7 @@ namespace Oqtane.Repository
ModulePermissions = new List<Permission> ModulePermissions = new List<Permission>
{ {
new Permission(PermissionNames.View, RoleNames.Admin, true), new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.View, RoleNames.Registered, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true) new Permission(PermissionNames.Edit, RoleNames.Admin, true)
}.EncodePermissions(), }.EncodePermissions(),
Content = "" Content = ""

View File

@ -1,5 +1,4 @@
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Oqtane.Shared; using Oqtane.Shared;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -17,31 +16,33 @@ namespace Oqtane.Security
public override async Task<AuthorizationPolicy> GetPolicyAsync(string policyName) public override async Task<AuthorizationPolicy> GetPolicyAsync(string policyName)
{ {
// check static policies first // get policy
policyName = GetPolicyName(policyName); policyName = GetPolicyName(policyName);
var policy = await base.GetPolicyAsync(policyName); var policy = await base.GetPolicyAsync(policyName);
if (policy == null) if (policy == null)
{ {
// policy names must be in the form of "EntityName:PermissionName:Roles"
if (policyName.Contains(':')) if (policyName.Contains(':'))
{ {
// policy names must be in the form of "EntityName:PermissionName:Roles" ie. "Module:Edit:Administrators" (roles are comma delimited) var segments = policyName.Split(':');
var policySegments = policyName.Split(':'); if (segments.Length == 3)
if (policySegments.Length >= 3)
{ {
// check for optional RequireEntityId segment // create policy
var requireEntityId = false; var builder = new AuthorizationPolicyBuilder();
if (policySegments.Length == 4 && policySegments[3] == Constants.RequireEntityId) builder.AddRequirements(new PermissionRequirement(segments[0], segments[1], segments[2]));
{ policy = builder.Build();
requireEntityId = true;
}
policy = new AuthorizationPolicyBuilder()
.AddRequirements(new PermissionRequirement(policySegments[0], policySegments[1], policySegments[2], requireEntityId))
.Build();
// add policy to the AuthorizationOptions // add policy to the AuthorizationOptions
try
{
_options.AddPolicy(policyName, policy); _options.AddPolicy(policyName, policy);
} }
catch
{
// race condition - policy already added by another thread
}
}
} }
} }
@ -51,8 +52,8 @@ namespace Oqtane.Security
private string GetPolicyName(string policyName) private string GetPolicyName(string policyName)
{ {
// backward compatibility for legacy static policy names // backward compatibility for legacy static policy names
if (policyName == PolicyNames.ViewModule) policyName = $"{EntityNames.Module}:{PermissionNames.View}:{RoleNames.Admin}:{Constants.RequireEntityId}"; if (policyName == PolicyNames.ViewModule) policyName = $"{EntityNames.Module}:{PermissionNames.View}:{RoleNames.Admin}";
if (policyName == PolicyNames.EditModule) policyName = $"{EntityNames.Module}:{PermissionNames.Edit}:{RoleNames.Admin}:{Constants.RequireEntityId}"; if (policyName == PolicyNames.EditModule) policyName = $"{EntityNames.Module}:{PermissionNames.Edit}:{RoleNames.Admin}";
return policyName; return policyName;
} }
} }

View File

@ -34,8 +34,7 @@ namespace Oqtane.Security
} }
int entityId = -1; int entityId = -1;
if (requirement.RequireEntityId)
{
// get entityid from querystring based on a parameter format of auth{entityname}id (ie. authmoduleid ) // get entityid from querystring based on a parameter format of auth{entityname}id (ie. authmoduleid )
if (ctx.Request.Query.ContainsKey("auth" + requirement.EntityName.ToLower() + "id")) if (ctx.Request.Query.ContainsKey("auth" + requirement.EntityName.ToLower() + "id"))
{ {
@ -56,7 +55,6 @@ namespace Oqtane.Security
} }
} }
} }
}
// validate permissions // validate permissions
if (_userPermissions.IsAuthorized(context.User, siteId, requirement.EntityName, entityId, requirement.PermissionName, requirement.Roles)) if (_userPermissions.IsAuthorized(context.User, siteId, requirement.EntityName, entityId, requirement.PermissionName, requirement.Roles))

View File

@ -8,16 +8,13 @@ namespace Oqtane.Security
public string PermissionName { get; } public string PermissionName { get; }
public string Roles { get; } public string Roles { get; } // semi-colon delimited
public bool RequireEntityId { get; } public PermissionRequirement(string entityName, string permissionName, string roles)
public PermissionRequirement(string entityName, string permissionName, string roles, bool requireEntityId)
{ {
EntityName = entityName; EntityName = entityName;
PermissionName = permissionName; PermissionName = permissionName;
Roles = roles; Roles = roles;
RequireEntityId = requireEntityId;
} }
} }
} }

View File

@ -40,7 +40,7 @@ namespace Oqtane.Security
} }
else else
{ {
return UserSecurity.IsAuthorized(GetUser(principal), roles.Replace(",",";")); return UserSecurity.IsAuthorized(GetUser(principal), roles);
} }
} }

View File

@ -3,6 +3,8 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16 # Visual Studio Version 16
VisualStudioVersion = 16.0.28621.142 VisualStudioVersion = 16.0.28621.142
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Oqtane.Server", "..\oqtane.framework\Oqtane.Server\Oqtane.Server.csproj", "{3AB6FCC9-EFEB-4C0E-A2CF-8103914C5196}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "[Owner].[Module].Client", "Client\[Owner].[Module].Client.csproj", "{AA8E58A1-CD09-4208-BF66-A8BB341FD669}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "[Owner].[Module].Client", "Client\[Owner].[Module].Client.csproj", "{AA8E58A1-CD09-4208-BF66-A8BB341FD669}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "[Owner].[Module].Server", "Server\[Owner].[Module].Server.csproj", "{04B05448-788F-433D-92C0-FED35122D45A}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "[Owner].[Module].Server", "Server\[Owner].[Module].Server.csproj", "{04B05448-788F-433D-92C0-FED35122D45A}"
@ -17,6 +19,10 @@ Global
Release|Any CPU = Release|Any CPU Release|Any CPU = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution GlobalSection(ProjectConfigurationPlatforms) = postSolution
{3AB6FCC9-EFEB-4C0E-A2CF-8103914C5196}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3AB6FCC9-EFEB-4C0E-A2CF-8103914C5196}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3AB6FCC9-EFEB-4C0E-A2CF-8103914C5196}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3AB6FCC9-EFEB-4C0E-A2CF-8103914C5196}.Release|Any CPU.Build.0 = Release|Any CPU
{AA8E58A1-CD09-4208-BF66-A8BB341FD669}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AA8E58A1-CD09-4208-BF66-A8BB341FD669}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AA8E58A1-CD09-4208-BF66-A8BB341FD669}.Debug|Any CPU.Build.0 = Debug|Any CPU {AA8E58A1-CD09-4208-BF66-A8BB341FD669}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AA8E58A1-CD09-4208-BF66-A8BB341FD669}.Release|Any CPU.ActiveCfg = Release|Any CPU {AA8E58A1-CD09-4208-BF66-A8BB341FD669}.Release|Any CPU.ActiveCfg = Release|Any CPU

Some files were not shown because too many files have changed in this diff Show More