Compare commits

..

126 Commits

Author SHA1 Message Date
731fd46ea2 Merge pull request #4747 from oqtane/master
5.2.4 release
2024-10-17 13:46:40 -04:00
859759d691 Merge pull request #4746 from oqtane/dev
5.2.4 release
2024-10-17 13:46:22 -04:00
5e9567158f Merge pull request #4744 from sbwalker/dev
add additional external login providers
2024-10-16 08:35:14 -04:00
51d244f3aa add additional external login providers 2024-10-16 08:35:01 -04:00
4c5a07edd5 Merge pull request #4743 from sbwalker/dev
add missing localization keys
2024-10-16 07:52:49 -04:00
8113a754a1 add missing localization keys 2024-10-16 07:52:36 -04:00
3cd40c6195 Merge pull request #4742 from sbwalker/dev
fix sorting of Site.Languages property
2024-10-15 16:46:16 -04:00
56cfb2ce06 fix sorting of Site.Languages property 2024-10-15 16:46:05 -04:00
72087823ac Merge pull request #4741 from sbwalker/dev
fix #4733 - remove Name column from Language table and populate value dynamically
2024-10-15 16:44:44 -04:00
bcf7866fe2 fix #4733 - remove Name column from Language table and populate value dynamically 2024-10-15 15:58:17 -04:00
b64772e484 Merge pull request #4739 from thabaum/add-discord-community
Fixes #4738 - Add Discord community button to README.md
2024-10-15 15:26:32 -04:00
088d665942 Update Discord Community Link For Consistency 2024-10-15 10:27:59 -07:00
7d6c10befb Add Discord community button to README.md
- Added a button for joining the Oqtane Discord server
- Included a brief description encouraging community engagement
2024-10-15 10:17:01 -07:00
f88e3d04b8 Merge pull request #4736 from thabaum/mysql.data-9.1.0
Fixes #4735 - Update MySQL.Data to 9.1.0
2024-10-15 12:06:50 -04:00
f57676a22b Update MySQL.Data to 9.1.0 2024-10-15 08:15:52 -07:00
8618cb62e4 Merge pull request #4732 from sbwalker/dev
fix #4711 - full page refresh required to affect language changes
2024-10-15 08:32:08 -04:00
c31c88ed1f fix #4711 - full page refresh required to affect language changes 2024-10-15 08:31:54 -04:00
6022acd21f Merge pull request #4731 from sbwalker/dev
fix #4716 - sort recycle bin items by DeletedOn date descending
2024-10-15 07:56:07 -04:00
b3071b9272 fix #4716 - sort recycle bin items by DeletedOn date descending 2024-10-15 07:55:54 -04:00
ec6a6d6653 Merge pull request #4729 from sbwalker/dev
localize names of languages based on user's UI culture
2024-10-14 17:18:09 -04:00
52f552b4de localize names of languages based on user's UI culture 2024-10-14 17:17:54 -04:00
2643d3396b Merge pull request #4728 from sbwalker/dev
set HttpOnly to false for Localization cookie in static rendering
2024-10-14 16:49:27 -04:00
62d59a09cf set HttpOnly to false for Localization cookie in static rendering 2024-10-14 16:49:14 -04:00
a68ff8a4f0 Merge pull request #4727 from sbwalker/dev
When displaying Database Type use SQL Server rather than LocalDB to avoid confusion
2024-10-14 16:21:25 -04:00
93d4bfcd7a When displaying Database Type use SQL Server rather than LocalDB to avoid confusion 2024-10-14 16:21:03 -04:00
5fb80c1a7b Merge pull request #4726 from sbwalker/dev
fix #4722 - support PrincipalSchema when creating foreign keys (credit @Hypnodude)
2024-10-14 15:36:54 -04:00
04b38444ce fix #4722 - support PrincipalSchema when creating foreign keys (credit @Hypnodude) 2024-10-14 15:36:32 -04:00
ca3df02002 Merge pull request #4723 from mdmontesinos/dev
Image Resizing and Format via QueryString in Files Page (enhances #4692)
2024-10-14 15:20:37 -04:00
d952c33fab Merge pull request #4725 from sbwalker/dev
fix #4714 as well as breaking change in #4712
2024-10-14 15:05:59 -04:00
93bc1cd5af fix #4714 as well as breaking change in #4712 2024-10-14 15:05:46 -04:00
0e5b370ee8 Merge pull request #4712 from thabaum/language-switcher-cookie
Fix #4710 - Adds language switcher component cookie set options for secure, httpOnly, sameSite + interop.cs/interop.js methods samesite and secure options
2024-10-14 14:41:18 -04:00
f4fd4e28c9 Merge pull request #4724 from sbwalker/dev
update MAUI project to .NET 8.0.10
2024-10-14 13:12:09 -04:00
ec8433eb45 update MAUI project to .NET 8.0.10 2024-10-14 13:11:55 -04:00
0d4a40e9bb Merge pull request #4715 from leigh-pointer/CompUpdate-8.0.10
Updated Microsoft Components for 8.0.8 to 8.0.10
2024-10-14 13:04:54 -04:00
c3668f4179 Merge pull request #4708 from thabaum/patch-10
fix typo in startup.cs
2024-10-14 12:48:38 -04:00
3adb7ecb1c Enhances image manipulation with format (webp encoder, defaults to png)
- computes etag with all manipulation parameters
2024-10-13 17:20:18 +02:00
aa5b84a214 Implements Image Manipulation in Files Page via QueryString
- Extracts the image creation into a service
- Refactors Files Page GET action for better readability and cyclomatic complexity
2024-10-13 12:38:43 +02:00
3d83fccbf1 remove appsettings 2024-10-09 15:07:57 +02:00
4c4255be6b Updated Template files with new component version 8.0.10 2024-10-09 14:42:07 +02:00
ed6054b082 Updated Microsoft Components for 8.0.8 to 8.0.10
Tested on upgrade and new Oqtane instance creation.
2024-10-09 14:35:27 +02:00
f60f7a4dc1 Remove httpOnly setting from setCookie 2024-10-05 14:17:20 -07:00
998dc95cb2 Removed extra attribute for interop.SetCookie 2024-10-05 14:08:16 -07:00
12f5d7b846 Remove extra attribute for SetCookie 2024-10-05 14:06:35 -07:00
906ae0a43e Remove extra attribute for SetCookie 2024-10-05 14:06:18 -07:00
485b774876 Remove httpOnly cooking attribute from SetCookie 2024-10-05 14:03:20 -07:00
3121cf5b75 Remove unnecessary httpOnly setCookie option 2024-10-05 14:00:18 -07:00
ce7570dae2 Remove Unnecessary httpOnly setting setCookie Option 2024-10-05 13:58:55 -07:00
b5ea0dfbc7 Update Cleanup "setCookie" function notes options: secure, httpOnly, Samesite 2024-10-05 13:57:36 -07:00
dd0f8f4772 Update SetCookie function to include secure, httpOnly and sameSite 2024-10-05 13:55:35 -07:00
9d0ab34274 Update 'SetCookie" option settings "secure, httpOnly, sameSite" 2024-10-05 13:28:33 -07:00
e6038be6f7 Update SetCookie Option Settings Secure, HttpOnly, SameSite 2024-10-05 13:27:14 -07:00
bd2153a0ed Update cookie options to set SameSite, HttpOnly, Secure settings 2024-10-05 13:23:09 -07:00
e526deac20 Update Cookie Settings Secure, httpOnly, sameSite 2024-10-05 13:19:52 -07:00
b65f165dcf Update adds SameSite, Secure and httpOnly SetCookie Settings 2024-10-05 13:17:31 -07:00
f9fbe5adc2 Update NavigateTo() to use "true" instead of "forceLoad: true" 2024-10-05 12:11:52 -07:00
d1e73571a1 fix typo 2024-10-05 11:12:01 -07:00
1047058676 Merge pull request #4705 from maurocavallin/dev
Hard deletion of page more robust use of entity framework contexts
2024-10-04 17:14:00 -04:00
29a1e77da8 Hard deletion of page more robust use of contexts (fixes issue: presence of stale records of deleted page on db). 2024-10-04 22:46:40 +02:00
290547e482 Merge pull request #4702 from sbwalker/dev
add defensive logic if ModuleState is null  in ModuleMessage
2024-10-02 16:53:46 -04:00
3df45ca20f add defensive logic if ModuleState is null in ModuleMessage 2024-10-02 16:53:36 -04:00
cc06258484 Merge pull request #4701 from sbwalker/dev
updated version to 5.2.4
2024-10-02 16:39:45 -04:00
2c262d0655 updated version to 5.2.4 2024-10-02 16:39:31 -04:00
1875e1e158 Merge pull request #4696 from sbwalker/dev
fix #4695 - null reference exception deleting a setting which does not exist
2024-10-02 08:30:58 -04:00
1c95967b31 fix #4695 - null reference exception deleting a setting which does not exist 2024-10-02 08:30:34 -04:00
2ae98929de Merge pull request #4689 from thabaum/Update-Dependencies-and-Prepare-Release-5.2.4
Fixes #4688 - Update dependencies and prepare release 5.2.4
2024-10-01 11:21:35 -04:00
6a1dd38cbb Merge pull request #4691 from zyhfish/task/fix-4690
Fix #4690: prevent invalid parsing.
2024-10-01 11:20:01 -04:00
Ben
c458a77d27 Fix #4690: prevent invalid parsing. 2024-10-01 22:13:32 +08:00
352c23f389 Prepare Release 5.2.4 2024-10-01 06:15:46 -07:00
6c5a1dc2e1 Prepare Release 5.2.4 2024-10-01 06:14:57 -07:00
004ff1e91d Update Dependencies and Prepare Release 5.2.4 2024-10-01 06:14:07 -07:00
f7de4c567b Prepare Release 5.2.4 2024-10-01 06:12:07 -07:00
b2ad1010ac Prepare Release 5.2.4 2024-10-01 06:11:36 -07:00
660e164ff8 Prepare Release 5.2.4 2024-10-01 06:11:04 -07:00
eb6dc80b50 Prepare Release 5.2.4 - removed whitespace 2024-10-01 06:10:21 -07:00
a9882cc96a Update Oqtane.Shared.nuspec 2024-10-01 06:09:35 -07:00
fb5a2ce178 Prepare Release 5.2.4 2024-10-01 06:09:12 -07:00
d441b31dc7 Prepare Release 5.2.4 2024-10-01 06:08:46 -07:00
d4239fe7e0 Prepare Release 5.2.4 2024-10-01 06:07:55 -07:00
6a98999105 Prepare Release 5.2.4 2024-10-01 06:06:03 -07:00
5caa1fe7d4 Prepare Release 5.2.4 2024-10-01 06:05:07 -07:00
ef33bdb65e Prepare Release 5.2.4 2024-10-01 06:04:11 -07:00
14a463382b Prepare Release 5.2.4 2024-10-01 06:03:20 -07:00
1ad79874c8 Update Dependencies and Prepare Release 5.2.4 2024-10-01 06:02:40 -07:00
1a61a58d28 Prepare Release 5.2.4 2024-10-01 06:00:20 -07:00
3e3c973679 Prepare Release 5.2.4 2024-10-01 05:59:47 -07:00
9dede84d20 Update Dependencies and Prepare Release 5.2.4 2024-10-01 05:57:17 -07:00
e78b11cf62 Merge pull request #4687 from sbwalker/dev
add some clarity to the database fields help text
2024-09-30 13:32:08 -04:00
341ca5a330 add some clarity to the database fields help text 2024-09-30 13:31:57 -04:00
e5d8c02def Merge pull request #4686 from sbwalker/dev
add validation of recipient email address to Notification job
2024-09-30 11:27:59 -04:00
947aa08c42 add validation of recipient email address to Notification job 2024-09-30 11:27:47 -04:00
9a04d436bd Merge pull request #4683 from sbwalker/dev
simplify configuration of external login providers
2024-09-27 16:21:23 -04:00
be0754f568 simplify configuration of external login providers 2024-09-27 16:21:06 -04:00
93c4bbc0d1 Merge pull request #4682 from sbwalker/dev
fix #4654 - show progress indicator during download
2024-09-27 12:01:07 -04:00
b98535810b fix #4654 - show progress indicator during download 2024-09-27 12:00:49 -04:00
393cf8da1f Merge pull request #4681 from sbwalker/dev
add disclaimer to System Update feature
2024-09-27 09:00:15 -04:00
ea2846973a add disclaimer to System Update feature 2024-09-27 09:00:04 -04:00
3398c1cbfe Merge pull request #4678 from sbwalker/dev
remove unnecessary using statement
2024-09-26 15:54:35 -04:00
39c79ea68e remove unnecessary using statement 2024-09-26 15:54:22 -04:00
66900f4a32 Merge pull request #4677 from sbwalker/dev
sign out the principal when it is rejected due to security stamp changes
2024-09-26 15:53:55 -04:00
df71dd14f7 sign out the principal when it is rejected due to security stamp changes 2024-09-26 15:53:14 -04:00
8113ca3069 Merge pull request #4676 from sbwalker/dev
fix #4657 - Cannot add new site to existing installation using separate database On IIS
2024-09-26 14:07:11 -04:00
d468e675c2 fix #4657 - Cannot add new site to existing installation using separate database On IIS 2024-09-26 14:06:51 -04:00
1e84cedf82 Merge pull request #4674 from sbwalker/dev
fix localization spelling mistake
2024-09-26 13:46:41 -04:00
7f4087e3de fix localization spelling mistake 2024-09-26 13:46:29 -04:00
facd3c8956 Merge pull request #4673 from sbwalker/dev
fix remote login issue which could occut if multiple users have the same email address
2024-09-26 13:38:30 -04:00
3e50deecb7 fix remote login issue which could occut if multiple users have the same email address 2024-09-26 13:37:39 -04:00
628c504f84 Merge pull request #4672 from sbwalker/dev
fix #4667 - installation issues when running on IIS
2024-09-26 13:33:31 -04:00
e1ada78c1f fix #4667 - installation issues when running on IIS 2024-09-26 13:33:16 -04:00
09fa1e365c Merge pull request #4670 from thabaum/remove-unnecessary-usings
Fixes #4669 - Remove unnecessary usings in IMultiDatabase.cs
2024-09-26 13:32:33 -04:00
28b6b03d06 Remove unnecessary usings 2024-09-25 15:13:06 -07:00
a4395b62ff Merge pull request #4668 from sbwalker/dev
fix #4666 - scroll position in enhanced navigation
2024-09-25 17:06:03 -04:00
4511acf273 fix #4666 - scroll position in enhanced navigation 2024-09-25 17:05:53 -04:00
fde53a2d83 Update README.md 2024-09-23 16:33:41 -04:00
f6cd04fdb8 Merge pull request #4662 from oqtane/master
Merge pull request #4661 from oqtane/dev
2024-09-23 16:26:39 -04:00
a5eede6c7a Merge pull request #4661 from oqtane/dev
5.2.3 release
2024-09-23 16:26:24 -04:00
2e83817c83 Merge pull request #4660 from sbwalker/dev
prepare for 5.2.3
2024-09-23 16:18:10 -04:00
82aea40ae4 prepare for 5.2.3 2024-09-23 16:17:55 -04:00
1b289eae24 Merge pull request #4659 from sbwalker/dev
use RoleName rather than RoleId for consistency
2024-09-23 16:08:02 -04:00
81420b2c88 use RoleName rather than RoleId for consistency 2024-09-23 16:07:49 -04:00
775731b745 Merge pull request #4657 from thabaum/patch-9
FIX #4655 - autocomplete="off" for SMTP Username/Password
2024-09-23 15:44:06 -04:00
489e7d4a67 Merge pull request #4658 from sbwalker/dev
fix RoleId missing from Permission Clone method
2024-09-23 15:38:51 -04:00
b6508764d8 fix RoleId missing from Permission Clone method 2024-09-23 15:38:35 -04:00
6dedd87305 autocomplete="off" for SMTP Username/Password 2024-09-23 12:19:49 -07:00
89fa29b310 Merge pull request #4653 from sbwalker/dev
improve scroll position script
2024-09-23 09:55:59 -04:00
be5df9c22a improve scroll position script 2024-09-23 09:55:44 -04:00
db17739716 Update README.md 2024-09-23 07:48:14 -04:00
67 changed files with 971 additions and 481 deletions

View File

@ -94,7 +94,6 @@ else
var language = new Language
{
SiteId = PageState.Page.SiteId,
Name = CultureInfo.GetCultureInfo(_code).DisplayName,
Code = _code,
IsDefault = (_default == null ? false : Boolean.Parse(_default))
};
@ -130,7 +129,7 @@ else
{
var interop = new Interop(JSRuntime);
var localizationCookieValue = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture));
await interop.SetCookie(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, 360);
await interop.SetCookie(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, 360, true, "Lax");
}
}

View File

@ -103,7 +103,7 @@ else
{
var interop = new Interop(JSRuntime);
var localizationCookieValue = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture));
await interop.SetCookie(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, 360);
await interop.SetCookie(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, 360, true, "Lax");
}
}

View File

@ -22,7 +22,7 @@ else
}
else
{
<Pager Items="@_pages.Where(item => item.IsDeleted)" CurrentPage="@_pagePage.ToString()" OnPageChange="OnPageChangePage">
<Pager Items="@_pages.Where(item => item.IsDeleted).OrderByDescending(item => item.DeletedOn)" CurrentPage="@_pagePage.ToString()" OnPageChange="OnPageChangePage">
<Header>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
@ -50,7 +50,7 @@ else
}
else
{
<Pager Items="@_modules.Where(item => item.IsDeleted)" CurrentPage="@_pageModule.ToString()" OnPageChange="OnPageChangeModule">
<Pager Items="@_modules.Where(item => item.IsDeleted).OrderByDescending(item => item.DeletedOn)" CurrentPage="@_pageModule.ToString()" OnPageChange="OnPageChangeModule">
<Header>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>

View File

@ -207,14 +207,14 @@
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="username" HelpText="Enter the username for your SMTP account" ResourceKey="SmtpUsername">Username: </Label>
<div class="col-sm-9">
<input id="username" class="form-control" @bind="@_smtpusername" />
<input id="username" class="form-control" @bind="@_smtpusername" autocomplete="off"/>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="password" HelpText="Enter the password for your SMTP account" ResourceKey="SmtpPassword">Password: </Label>
<div class="col-sm-9">
<div class="input-group">
<input id="password" type="@_smtppasswordtype" class="form-control" @bind="@_smtppassword" />
<input id="password" type="@_smtppasswordtype" class="form-control" @bind="@_smtppassword" autocomplete="off"/>
<button type="button" class="btn btn-secondary" @onclick="@ToggleSMTPPassword" tabindex="-1">@_togglesmtppassword</button>
</div>
</div>
@ -376,7 +376,7 @@
<Section Name="TenantInformation" Heading="Database" ResourceKey="TenantInformation">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="tenant" HelpText="The name of the database used for the site" ResourceKey="Tenant">Database: </Label>
<Label Class="col-sm-3" For="tenant" HelpText="The name of the database used for the site. Note that this is not the physical database name but rather the tenant name which is used within the framework to identify a database." ResourceKey="Tenant">Database: </Label>
<div class="col-sm-9">
<input id="tenant" class="form-control" @bind="@_tenant" readonly />
</div>
@ -388,7 +388,7 @@
</div>
</div>
<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 name of the connection string in appsettings.json which will be used to connect to the database" ResourceKey="ConnectionString">Connection: </Label>
<div class="col-sm-9">
<input id="connectionstring" class="form-control" @bind="@_connectionstring" readonly />
</div>
@ -571,7 +571,7 @@
if (tenant != null)
{
_tenant = tenant.Name;
_database = _databases.Find(item => item.DBType == tenant.DBType)?.Name;
_database = _databases.Find(item => item.DBType == tenant.DBType && item.Name != "LocalDB")?.Name;
_connectionstring = tenant.DBConnectionString;
}
}

View File

@ -109,7 +109,7 @@ else
<hr class="app-rule" />
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="name" HelpText="Enter the name for the database" ResourceKey="TenantName">Name: </Label>
<Label Class="col-sm-3" For="name" HelpText="Enter the name for the database. Note that this will be the tenant name which is used within the framework to identify the database." ResourceKey="TenantName">Name: </Label>
<div class="col-sm-9">
<input id="name" class="form-control" @bind="@_tenantName" maxlength="100" required />
</div>

View File

@ -83,24 +83,15 @@ else
{
@if (_connection != "-")
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="databasetype" HelpText="The database type" ResourceKey="DatabaseType">Type: </Label>
<div class="col-sm-9">
@if (_databases != null)
{
<select id="databasetype" class="form-select" @bind="@_databasetype" required>
<option value="-">&lt;@Localizer["Type.Select"]&gt;</option>
@foreach (var database in _databases)
{
<option value="@database.Name">@Localizer[@database.Name]</option>
}
</select>
}
</div>
</div>
@if (!string.IsNullOrEmpty(_tenant))
{
<div class="row mb-1 align-items-center">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="databasetype" HelpText="The database type" ResourceKey="DatabaseType">Type: </Label>
<div class="col-sm-9">
<input id="databasetype" class="form-control" @bind="@_databasetype" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="tenant" HelpText="The database using this connection" ResourceKey="Tenant">Database: </Label>
<div class="col-sm-9">
<input id="tenant" class="form-control" @bind="@_tenant" readonly />
@ -204,12 +195,12 @@ else
{
_connectionstring = _connections[_connection].ToString();
_tenant = "";
_databasetype = "-";
_databasetype = "";
var tenant = _tenants.FirstOrDefault(item => item.DBConnectionString == _connection);
if (tenant != null)
{
_tenant = tenant.Name;
_databasetype = _databases.FirstOrDefault(item => item.DBType == tenant.DBType).Name;
_databasetype = _databases.FirstOrDefault(item => item.DBType == tenant.DBType && item.Name != "LocalDB").Name;
}
}
else

View File

@ -54,6 +54,8 @@
}
else
{
AddModuleMessage(Localizer["Disclaimer.Text"], MessageType.Warning);
List<Package> packages = await PackageService.GetPackagesAsync("framework", "", "", "");
if (packages != null)
{
@ -97,13 +99,16 @@
{
try
{
ShowProgressIndicator();
await PackageService.DownloadPackageAsync(packageid, version);
await PackageService.DownloadPackageAsync(Constants.UpdaterPackageId, version);
HideProgressIndicator();
AddModuleMessage(Localizer["Success.Framework.Download"], MessageType.Success);
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Downloading Framework Package {Error}", ex.Message);
HideProgressIndicator();
AddModuleMessage(Localizer["Error.Framework.Download"], MessageType.Error);
}
}

View File

@ -182,13 +182,31 @@ else
</div>
</Section>
<Section Name="ExternalLogin" Heading="External Login Settings" ResourceKey="ExternalLoginSettings">
<div class="row mb-1 align-items-center">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="provider" HelpText="Select the external login provider" ResourceKey="Provider">Provider:</Label>
<div class="col-sm-9">
<div class="input-group">
<select id="provider" class="form-select" value="@_provider" @onchange="(e => ProviderChanged(e))">
@foreach (var provider in Shared.ExternalLoginProviders.Providers)
{
<option value="@provider.Name">@Localizer[provider.Name]</option>
}
</select>
@if (!string.IsNullOrEmpty(_providerurl))
{
<a href="@_providerurl" class="btn btn-secondary" target="_new">@Localizer["Info"]</a>
}
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="providertype" HelpText="Select the external login provider type" ResourceKey="ProviderType">Provider Type:</Label>
<div class="col-sm-9">
<select id="providertype" class="form-select" value="@_providertype" @onchange="(e => ProviderTypeChanged(e))">
<option value="" selected>@Localizer["Not Specified"]</option>
<option value="@AuthenticationProviderTypes.OpenIDConnect">@Localizer["OpenID Connect"]</option>
<option value="@AuthenticationProviderTypes.OAuth2">@Localizer["OAuth 2.0"]</option>
<option value="" selected>&lt;@Localizer["Not Specified"]&gt;</option>
<option value="@AuthenticationProviderTypes.OpenIDConnect">@Localizer["OIDC"]</option>
<option value="@AuthenticationProviderTypes.OAuth2">@Localizer["OAuth2"]</option>
</select>
</div>
</div>
@ -452,6 +470,8 @@ else
private string _maximumfailures;
private string _lockoutduration;
private string _provider;
private string _providerurl;
private string _providertype;
private string _providername;
private string _authority;
@ -519,33 +539,7 @@ else
_maximumfailures = SettingService.GetSetting(settings, "IdentityOptions:Lockout:MaxFailedAccessAttempts", "5");
_lockoutduration = TimeSpan.Parse(SettingService.GetSetting(settings, "IdentityOptions:Lockout:DefaultLockoutTimeSpan", "00:05:00")).TotalMinutes.ToString();
_providertype = SettingService.GetSetting(settings, "ExternalLogin:ProviderType", "");
_providername = SettingService.GetSetting(settings, "ExternalLogin:ProviderName", "");
_authority = SettingService.GetSetting(settings, "ExternalLogin:Authority", "");
_metadataurl = SettingService.GetSetting(settings, "ExternalLogin:MetadataUrl", "");
_authorizationurl = SettingService.GetSetting(settings, "ExternalLogin:AuthorizationUrl", "");
_tokenurl = SettingService.GetSetting(settings, "ExternalLogin:TokenUrl", "");
_userinfourl = SettingService.GetSetting(settings, "ExternalLogin:UserInfoUrl", "");
_clientid = SettingService.GetSetting(settings, "ExternalLogin:ClientId", "");
_clientsecret = SettingService.GetSetting(settings, "ExternalLogin:ClientSecret", "");
_toggleclientsecret = SharedLocalizer["ShowPassword"];
_authresponsetype = SettingService.GetSetting(settings, "ExternalLogin:AuthResponseType", "code");
_scopes = SettingService.GetSetting(settings, "ExternalLogin:Scopes", "");
_parameters = SettingService.GetSetting(settings, "ExternalLogin:Parameters", "");
_pkce = SettingService.GetSetting(settings, "ExternalLogin:PKCE", "false");
_redirecturl = PageState.Uri.Scheme + "://" + PageState.Alias.Name + "/signin-" + _providertype;
_reviewclaims = SettingService.GetSetting(settings, "ExternalLogin:ReviewClaims", "false");
_externalloginurl = Utilities.TenantUrl(PageState.Alias, "/pages/external");
_identifierclaimtype = SettingService.GetSetting(settings, "ExternalLogin:IdentifierClaimType", "sub");
_nameclaimtype = SettingService.GetSetting(settings, "ExternalLogin:NameClaimType", "name");
_emailclaimtype = SettingService.GetSetting(settings, "ExternalLogin:EmailClaimType", "email");
_roleclaimtype = SettingService.GetSetting(settings, "ExternalLogin:RoleClaimType", "");
_roleclaimmappings = SettingService.GetSetting(settings, "ExternalLogin:RoleClaimMappings", "");
_synchronizeroles = SettingService.GetSetting(settings, "ExternalLogin:SynchronizeRoles", "false");
_profileclaimtypes = SettingService.GetSetting(settings, "ExternalLogin:ProfileClaimTypes", "");
_domainfilter = SettingService.GetSetting(settings, "ExternalLogin:DomainFilter", "");
_createusers = SettingService.GetSetting(settings, "ExternalLogin:CreateUsers", "true");
_verifyusers = SettingService.GetSetting(settings, "ExternalLogin:VerifyUsers", "true");
LoadExternalLoginSettings(settings);
_secret = SettingService.GetSetting(settings, "JwtOptions:Secret", "");
_togglesecret = SharedLocalizer["ShowPassword"];
@ -555,6 +549,39 @@ else
}
}
private void LoadExternalLoginSettings(Dictionary<string, string> settings)
{
_provider = SettingService.GetSetting(settings, "ExternalLogin:Provider", "<Custom>");
_providerurl = SettingService.GetSetting(settings, "ExternalLogin:ProviderUrl", "");
_providertype = SettingService.GetSetting(settings, "ExternalLogin:ProviderType", "");
_providername = SettingService.GetSetting(settings, "ExternalLogin:ProviderName", "");
_authority = SettingService.GetSetting(settings, "ExternalLogin:Authority", "");
_metadataurl = SettingService.GetSetting(settings, "ExternalLogin:MetadataUrl", "");
_authorizationurl = SettingService.GetSetting(settings, "ExternalLogin:AuthorizationUrl", "");
_tokenurl = SettingService.GetSetting(settings, "ExternalLogin:TokenUrl", "");
_userinfourl = SettingService.GetSetting(settings, "ExternalLogin:UserInfoUrl", "");
_clientid = SettingService.GetSetting(settings, "ExternalLogin:ClientId", "");
_clientsecret = SettingService.GetSetting(settings, "ExternalLogin:ClientSecret", "");
_toggleclientsecret = SharedLocalizer["ShowPassword"];
_authresponsetype = SettingService.GetSetting(settings, "ExternalLogin:AuthResponseType", "code");
_scopes = SettingService.GetSetting(settings, "ExternalLogin:Scopes", "");
_parameters = SettingService.GetSetting(settings, "ExternalLogin:Parameters", "");
_pkce = SettingService.GetSetting(settings, "ExternalLogin:PKCE", "false");
_redirecturl = PageState.Uri.Scheme + "://" + PageState.Alias.Name + "/signin-" + _providertype;
_reviewclaims = SettingService.GetSetting(settings, "ExternalLogin:ReviewClaims", "false");
_externalloginurl = Utilities.TenantUrl(PageState.Alias, "/pages/external");
_identifierclaimtype = SettingService.GetSetting(settings, "ExternalLogin:IdentifierClaimType", "sub");
_nameclaimtype = SettingService.GetSetting(settings, "ExternalLogin:NameClaimType", "name");
_emailclaimtype = SettingService.GetSetting(settings, "ExternalLogin:EmailClaimType", "email");
_roleclaimtype = SettingService.GetSetting(settings, "ExternalLogin:RoleClaimType", "");
_roleclaimmappings = SettingService.GetSetting(settings, "ExternalLogin:RoleClaimMappings", "");
_synchronizeroles = SettingService.GetSetting(settings, "ExternalLogin:SynchronizeRoles", "false");
_profileclaimtypes = SettingService.GetSetting(settings, "ExternalLogin:ProfileClaimTypes", "");
_domainfilter = SettingService.GetSetting(settings, "ExternalLogin:DomainFilter", "");
_createusers = SettingService.GetSetting(settings, "ExternalLogin:CreateUsers", "true");
_verifyusers = SettingService.GetSetting(settings, "ExternalLogin:VerifyUsers", "true");
}
private async Task LoadUsersAsync(bool load)
{
if (load)
@ -567,105 +594,117 @@ else
users = users.OrderBy(u => u.User.DisplayName).ToList();
}
}
}
}
private async Task DeleteUser(UserRole UserRole)
{
try
{
var user = await UserService.GetUserAsync(UserRole.UserId, PageState.Site.SiteId);
if (user != null)
{
await UserService.DeleteUserAsync(user.UserId, PageState.Site.SiteId);
await logger.LogInformation("User Deleted {User}", UserRole.User);
await LoadUsersAsync(true);
StateHasChanged();
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Deleting User {User} {Error}", UserRole.User, ex.Message);
AddModuleMessage(ex.Message, MessageType.Error);
}
}
private async Task DeleteUser(UserRole UserRole)
{
try
{
var user = await UserService.GetUserAsync(UserRole.UserId, PageState.Site.SiteId);
if (user != null)
{
await UserService.DeleteUserAsync(user.UserId, PageState.Site.SiteId);
await logger.LogInformation("User Deleted {User}", UserRole.User);
await LoadUsersAsync(true);
StateHasChanged();
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Deleting User {User} {Error}", UserRole.User, ex.Message);
AddModuleMessage(ex.Message, MessageType.Error);
}
}
private async Task SaveSiteSettings()
{
try
{
var site = PageState.Site;
site.AllowRegistration = bool.Parse(_allowregistration);
await SiteService.UpdateSiteAsync(site);
private async Task SaveSiteSettings()
{
try
{
var site = PageState.Site;
site.AllowRegistration = bool.Parse(_allowregistration);
await SiteService.UpdateSiteAsync(site);
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
settings = SettingService.SetSetting(settings, "LoginOptions:AllowSiteLogin", _allowsitelogin, false);
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
settings = SettingService.SetSetting(settings, "LoginOptions:AllowSiteLogin", _allowsitelogin, false);
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
settings = SettingService.SetSetting(settings, "LoginOptions:TwoFactor", _twofactor, false);
settings = SettingService.SetSetting(settings, "LoginOptions:CookieName", _cookiename, true);
settings = SettingService.SetSetting(settings, "LoginOptions:CookieExpiration", _cookieexpiration, true);
settings = SettingService.SetSetting(settings, "LoginOptions:AlwaysRemember", _alwaysremember, false);
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
settings = SettingService.SetSetting(settings, "LoginOptions:TwoFactor", _twofactor, false);
settings = SettingService.SetSetting(settings, "LoginOptions:CookieName", _cookiename, true);
settings = SettingService.SetSetting(settings, "LoginOptions:CookieExpiration", _cookieexpiration, true);
settings = SettingService.SetSetting(settings, "LoginOptions:AlwaysRemember", _alwaysremember, false);
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequiredLength", _minimumlength, true);
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequiredUniqueChars", _uniquecharacters, true);
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequireDigit", _requiredigit, true);
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequireUppercase", _requireupper, true);
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequireLowercase", _requirelower, true);
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequireNonAlphanumeric", _requirepunctuation, true);
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequiredLength", _minimumlength, true);
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequiredUniqueChars", _uniquecharacters, true);
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequireDigit", _requiredigit, true);
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequireUppercase", _requireupper, true);
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequireLowercase", _requirelower, true);
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequireNonAlphanumeric", _requirepunctuation, true);
settings = SettingService.SetSetting(settings, "IdentityOptions:Lockout:MaxFailedAccessAttempts", _maximumfailures, true);
settings = SettingService.SetSetting(settings, "IdentityOptions:Lockout:DefaultLockoutTimeSpan", TimeSpan.FromMinutes(Convert.ToInt64(_lockoutduration)).ToString(), true);
settings = SettingService.SetSetting(settings, "IdentityOptions:Lockout:MaxFailedAccessAttempts", _maximumfailures, true);
settings = SettingService.SetSetting(settings, "IdentityOptions:Lockout:DefaultLockoutTimeSpan", TimeSpan.FromMinutes(Convert.ToInt64(_lockoutduration)).ToString(), true);
settings = SettingService.SetSetting(settings, "ExternalLogin:ProviderType", _providertype, false);
settings = SettingService.SetSetting(settings, "ExternalLogin:ProviderName", _providername, false);
settings = SettingService.SetSetting(settings, "ExternalLogin:Authority", _authority, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:MetadataUrl", _metadataurl, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:AuthorizationUrl", _authorizationurl, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:TokenUrl", _tokenurl, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:UserInfoUrl", _userinfourl, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:ClientId", _clientid, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:ClientSecret", _clientsecret, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:Provider", _provider, false);
settings = SettingService.SetSetting(settings, "ExternalLogin:ProviderType", _providertype, false);
settings = SettingService.SetSetting(settings, "ExternalLogin:ProviderName", _providername, false);
settings = SettingService.SetSetting(settings, "ExternalLogin:Authority", _authority, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:MetadataUrl", _metadataurl, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:AuthorizationUrl", _authorizationurl, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:TokenUrl", _tokenurl, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:UserInfoUrl", _userinfourl, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:ClientId", _clientid, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:ClientSecret", _clientsecret, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:AuthResponseType", _authresponsetype, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:Scopes", _scopes, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:Parameters", _parameters, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:PKCE", _pkce, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:Parameters", _parameters, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:PKCE", _pkce, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:ReviewClaims", _reviewclaims, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:IdentifierClaimType", _identifierclaimtype, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:NameClaimType", _nameclaimtype, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:EmailClaimType", _emailclaimtype, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:RoleClaimType", _roleclaimtype, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:RoleClaimType", _roleclaimtype, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:RoleClaimMappings", _roleclaimmappings, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:SynchronizeRoles", _synchronizeroles, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:ProfileClaimTypes", _profileclaimtypes, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:DomainFilter", _domainfilter, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:CreateUsers", _createusers, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:DomainFilter", _domainfilter, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:CreateUsers", _createusers, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:VerifyUsers", _verifyusers, true);
settings = SettingService.SetSetting(settings, "JwtOptions:Secret", _secret, true);
settings = SettingService.SetSetting(settings, "JwtOptions:Issuer", _issuer, true);
settings = SettingService.SetSetting(settings, "JwtOptions:Audience", _audience, true);
settings = SettingService.SetSetting(settings, "JwtOptions:Lifetime", _lifetime, true);
}
settings = SettingService.SetSetting(settings, "JwtOptions:Secret", _secret, true);
settings = SettingService.SetSetting(settings, "JwtOptions:Issuer", _issuer, true);
settings = SettingService.SetSetting(settings, "JwtOptions:Audience", _audience, true);
settings = SettingService.SetSetting(settings, "JwtOptions:Lifetime", _lifetime, true);
}
await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId);
await SettingService.ClearSiteSettingsCacheAsync();
await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId);
await SettingService.ClearSiteSettingsCacheAsync();
if (!string.IsNullOrEmpty(_secret))
{
SiteState.AuthorizationToken = await UserService.GetTokenAsync();
}
if (!string.IsNullOrEmpty(_secret))
{
SiteState.AuthorizationToken = await UserService.GetTokenAsync();
}
AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success);
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Saving Site Settings {Error}", ex.Message);
AddModuleMessage(Localizer["Error.SaveSiteSettings"], MessageType.Error);
}
AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success);
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Saving Site Settings {Error}", ex.Message);
AddModuleMessage(Localizer["Error.SaveSiteSettings"], MessageType.Error);
}
}
private void ProviderChanged(ChangeEventArgs e)
{
_provider = (string)e.Value;
var provider = Shared.ExternalLoginProviders.Providers.FirstOrDefault(item => item.Name == _provider);
if (provider != null)
{
LoadExternalLoginSettings(provider.Settings);
}
StateHasChanged();
}
private void ProviderTypeChanged(ChangeEventArgs e)
private void ProviderTypeChanged(ChangeEventArgs e)
{
_providertype = (string)e.Value;
if (string.IsNullOrEmpty(_providername))

View File

@ -10,13 +10,16 @@
{
<NavLink class="ms-2" href="@NavigateUrl("admin/log")">View Details</NavLink>
}
@if (ModuleState.RenderMode == RenderModes.Static)
@if (ModuleState != null)
{
<a href="@NavigationManager.Uri" class="btn-close" data-dismiss="alert" aria-label="close"></a>
}
else
{
<button type="button" class="btn-close" data-dismiss="alert" aria-label="close" @onclick="CloseMessage"></button>
@if (ModuleState.RenderMode == RenderModes.Static)
{
<a href="@NavigationManager.Uri" class="btn-close" data-dismiss="alert" aria-label="close"></a>
}
else
{
<button type="button" class="btn-close" data-dismiss="alert" aria-label="close" @onclick="CloseMessage"></button>
}
}
</div>
}

View File

@ -452,9 +452,9 @@
_displayPages = int.Parse(DisplayPages);
}
if (PageState.QueryString.ContainsKey("page"))
if (PageState.QueryString.ContainsKey("page") && int.TryParse(PageState.QueryString["page"], out int page))
{
_page = int.Parse(PageState.QueryString["page"]);
_page = page;
}
else
{

View File

@ -4,7 +4,7 @@
<TargetFramework>net8.0</TargetFramework>
<OutputType>Exe</OutputType>
<Configurations>Debug;Release</Configurations>
<Version>5.2.2</Version>
<Version>5.2.4</Version>
<Product>Oqtane</Product>
<Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company>
@ -12,7 +12,7 @@
<Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.2</PackageReleaseNotes>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.4</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<RootNamespace>Oqtane</RootNamespace>
@ -22,10 +22,10 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.8" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="8.0.8" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.8" />
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.10" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="8.0.10" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.10" />
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Localization" Version="2.2.0" />
</ItemGroup>

View File

@ -163,7 +163,7 @@
<value>Enter the site name</value>
</data>
<data name="Tenant.HelpText" xml:space="preserve">
<value>The name of the database used for the site</value>
<value>The name of the database used for the site. Note that this is not the physical database name but rather the tenant name which is used within the framework to identify a database.</value>
</data>
<data name="Aliases.HelpText" xml:space="preserve">
<value>The urls for the site. This can include domain names (ie. domain.com), subdomains (ie. sub.domain.com) or virtual folders (ie. domain.com/folder).</value>
@ -307,7 +307,7 @@
<value>Type:</value>
</data>
<data name="ConnectionString.HelpText" xml:space="preserve">
<value>The connection information for the database</value>
<value>The name of the connection string in appsettings.json which will be used to connect to the database</value>
</data>
<data name="Database.HelpText" xml:space="preserve">
<value>The type of database</value>

View File

@ -187,7 +187,7 @@
<value>Select the database for the site</value>
</data>
<data name="TenantName.HelpText" xml:space="preserve">
<value>Enter the name for the database</value>
<value>Enter the name for the database. Note that this will be the tenant name which is used within the framework to identify the database.</value>
</data>
<data name="DatabaseType.HelpText" xml:space="preserve">
<value>Select the database type</value>

View File

@ -150,4 +150,7 @@
<data name="Localhost.Text" xml:space="preserve">
<value>You Cannot Perform A System Update In A Development Environment</value>
</data>
<data name="Disclaimer.Text" xml:space="preserve">
<value>Please Note That The System Update Capability Is A Simplified Upgrade Process Intended For Small To Medium Sized Installations. For Larger Enterprise Installations You Will Want To Use A Manual Upgrade Process. Also Note That The System Update Capability Is Not Recommended When Using Microsoft Azure Due To The Limitations Of That Environment. </value>
</data>
</root>

View File

@ -471,13 +471,28 @@
<data name="ReviewClaims.Text" xml:space="preserve">
<value>Review Claims?</value>
</data>
<data name="ReviewClaims.HelpText" xml:space="preserve">
<data name="ReviewClaims.HelpText" xml:space="preserve">
<value>This option will record the full list of Claims returned by the Provider in the Event Log. It should only be used for testing purposes. External Login will be restricted when this option is enabled.</value>
</data>
<data name="NameClaimType.HelpText" xml:space="preserve">
<value>Optionally specify the type name of the user's name claim provided by the identity provider. The typical value is 'name'.</value>
</data>
<data name="NameClaimType.Text" xml:space="preserve">
<data name="NameClaimType.Text" xml:space="preserve">
<value>Name Claim:</value>
</data>
<data name="Provider.HelpText" xml:space="preserve">
<value>Select the external login provider</value>
</data>
<data name="Provider.Text" xml:space="preserve">
<value>Provider:</value>
</data>
<data name="Info" xml:space="preserve">
<value>Info</value>
</data>
<data name="OAuth2" xml:space="preserve">
<value>OAuth 2.0</value>
</data>
<data name="OIDC" xml:space="preserve">
<value>OpenID Connect (OIDC)</value>
</data>
</root>

View File

@ -454,7 +454,7 @@
{
foreach (var permission in PageState.Page.PermissionList.Where(item => item.PermissionName == pagePermission))
{
permissions.Add(new Permission { SiteId = siteId, EntityName = EntityNames.Module, PermissionName = modulePermission, RoleId = permission.RoleId, UserId = permission.UserId, IsAuthorized = permission.IsAuthorized });
permissions.Add(new Permission { SiteId = siteId, EntityName = EntityNames.Module, PermissionName = modulePermission, RoleName = permission.RoleName, UserId = permission.UserId, IsAuthorized = permission.IsAuthorized });
}
return permissions;
}

View File

@ -22,7 +22,7 @@
}
else
{
<a class="dropdown-item @(CultureInfo.CurrentUICulture.Name == culture.Name ? "active" : String.Empty)" href="@NavigateUrl(PageState.Page.Path, "culture=" + culture.Name)">@culture.DisplayName</a>
<a class="dropdown-item @(CultureInfo.CurrentUICulture.Name == culture.Name ? "active" : String.Empty)" href="@NavigateUrl(PageState.Page.Path, "culture=" + culture.Name)" data-enhance-nav="false">@culture.DisplayName</a>
}
}
</div>
@ -45,8 +45,7 @@
{
MenuAlignment = DropdownAlignment.ToLower() == "right" ? "dropdown-menu-end" : string.Empty;
var languages = PageState.Languages;
_supportedCultures = languages.Select(l => new Culture { Name = l.Code, DisplayName = l.Name });
_supportedCultures = PageState.Languages.Select(l => new Culture { Name = l.Code, DisplayName = l.Name });
if (PageState.QueryString.ContainsKey("culture"))
{
@ -54,9 +53,18 @@
if (_supportedCultures.Any(item => item.Name == culture))
{
var localizationCookieValue = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture));
HttpContext.Response.Cookies.Append(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, new CookieOptions { Path = "/", Expires = DateTimeOffset.UtcNow.AddYears(365) });
HttpContext.Response.Cookies.Append(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, new CookieOptions
{
Path = "/",
Expires = DateTimeOffset.UtcNow.AddYears(365),
SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Lax, // Set SameSite attribute
Secure = true, // Ensure the cookie is only sent over HTTPS
HttpOnly = false // cookie is updated using JS Interop in Interactive render mode
});
}
NavigationManager.NavigateTo(NavigationManager.Uri.Replace($"?culture={culture}", ""), forceLoad: true);
NavigationManager.NavigateTo(NavigationManager.Uri.Replace($"?culture={culture}", ""));
}
}
@ -66,8 +74,8 @@
{
var localizationCookieValue = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture));
var interop = new Interop(JSRuntime);
await interop.SetCookie(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, 360);
NavigationManager.NavigateTo(NavigationManager.Uri, forceLoad: true);
await interop.SetCookie(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, 360, true, "Lax");
NavigationManager.NavigateTo(NavigationManager.Uri, true);
}
}
}

View File

@ -16,13 +16,18 @@ namespace Oqtane.UI
_jsRuntime = jsRuntime;
}
public Task SetCookie(string name, string value, int days)
public async Task SetCookie(string name, string value, int days)
{
await SetCookie(name, value, days, true, "Lax");
}
public Task SetCookie(string name, string value, int days, bool secure, string sameSite)
{
try
{
_jsRuntime.InvokeVoidAsync(
"Oqtane.Interop.setCookie",
name, value, days);
name, value, days, secure, sameSite);
return Task.CompletedTask;
}
catch

View File

@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Version>5.2.2</Version>
<Version>5.2.4</Version>
<Product>Oqtane</Product>
<Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company>
@ -10,7 +10,7 @@
<Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.2</PackageReleaseNotes>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.4</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
@ -34,7 +34,7 @@
<ItemGroup>
<PackageReference Include="MySql.EntityFrameworkCore" Version="8.0.5" />
<PackageReference Include="MySql.Data" Version="9.0.0" />
<PackageReference Include="MySql.Data" Version="9.1.0" />
</ItemGroup>
<ItemGroup>

View File

@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Version>5.2.2</Version>
<Version>5.2.4</Version>
<Product>Oqtane</Product>
<Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company>
@ -10,7 +10,7 @@
<Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.2</PackageReleaseNotes>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.4</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
@ -34,8 +34,8 @@
<ItemGroup>
<PackageReference Include="EFCore.NamingConventions" Version="8.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.8" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.10" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.8" />
</ItemGroup>
<ItemGroup>

View File

@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Version>5.2.2</Version>
<Version>5.2.4</Version>
<Product>Oqtane</Product>
<Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company>
@ -10,7 +10,7 @@
<Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.2</PackageReleaseNotes>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.4</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
@ -33,7 +33,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.10" />
</ItemGroup>
<ItemGroup>

View File

@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Version>5.2.2</Version>
<Version>5.2.4</Version>
<Product>Oqtane</Product>
<Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company>
@ -10,7 +10,7 @@
<Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.2</PackageReleaseNotes>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.4</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
@ -33,7 +33,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.10" />
</ItemGroup>
<ItemGroup>

View File

@ -6,7 +6,7 @@
<!-- <TargetFrameworks>net8.0-android;net8.0-ios;net8.0-maccatalyst</TargetFrameworks> -->
<!-- <TargetFrameworks>$(TargetFrameworks);net8.0-tizen</TargetFrameworks> -->
<OutputType>Exe</OutputType>
<Version>5.2.2</Version>
<Version>5.2.4</Version>
<Product>Oqtane</Product>
<Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company>
@ -14,7 +14,7 @@
<Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.2</PackageReleaseNotes>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.4</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<RootNamespace>Oqtane.Maui</RootNamespace>
@ -31,7 +31,7 @@
<ApplicationIdGuid>0E29FC31-1B83-48ED-B6E0-9F3C67B775D4</ApplicationIdGuid>
<!-- Versions -->
<ApplicationDisplayVersion>5.2.2</ApplicationDisplayVersion>
<ApplicationDisplayVersion>5.2.4</ApplicationDisplayVersion>
<ApplicationVersion>1</ApplicationVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">14.2</SupportedOSPlatformVersion>
@ -65,15 +65,15 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="8.0.8" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.8" />
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="8.0.10" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.10" />
<PackageReference Include="Microsoft.AspNetCore.Localization" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.8" />
<PackageReference Include="System.Net.Http.Json" Version="8.0.0" />
<PackageReference Include="Microsoft.Maui.Controls" Version="8.0.80" />
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="8.0.80" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebView.Maui" Version="8.0.80" />
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.10" />
<PackageReference Include="System.Net.Http.Json" Version="8.0.1" />
<PackageReference Include="Microsoft.Maui.Controls" Version="8.0.91" />
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="8.0.91" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebView.Maui" Version="8.0.91" />
</ItemGroup>
<ItemGroup>

View File

@ -1,11 +1,18 @@
var Oqtane = Oqtane || {};
Oqtane.Interop = {
setCookie: function (name, value, days) {
setCookie: function (name, value, days, secure, sameSite) {
var d = new Date();
d.setTime(d.getTime() + (days * 24 * 60 * 60 * 1000));
var expires = "expires=" + d.toUTCString();
document.cookie = name + "=" + value + ";" + expires + ";path=/";
var cookieString = name + "=" + value + ";" + expires + ";path=/";
if (secure) {
cookieString += "; secure";
}
if (sameSite === "Lax" || sameSite === "Strict" || sameSite === "None") {
cookieString += "; SameSite=" + sameSite;
}
document.cookie = cookieString;
},
getCookie: function (name) {
name = name + "=";

View File

@ -2,7 +2,7 @@
<package>
<metadata>
<id>Oqtane.Client</id>
<version>5.2.2</version>
<version>5.2.4</version>
<authors>Shaun Walker</authors>
<owners>.NET Foundation</owners>
<title>Oqtane Framework</title>
@ -12,7 +12,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.2</releaseNotes>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.4</releaseNotes>
<readme>readme.md</readme>
<icon>icon.png</icon>
<tags>oqtane</tags>

View File

@ -2,7 +2,7 @@
<package>
<metadata>
<id>Oqtane.Framework</id>
<version>5.2.2</version>
<version>5.2.4</version>
<authors>Shaun Walker</authors>
<owners>.NET Foundation</owners>
<title>Oqtane Framework</title>
@ -11,8 +11,8 @@
<copyright>.NET Foundation</copyright>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework/releases/download/v5.2.2/Oqtane.Framework.5.2.2.Upgrade.zip</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.2</releaseNotes>
<projectUrl>https://github.com/oqtane/oqtane.framework/releases/download/v5.2.4/Oqtane.Framework.5.2.4.Upgrade.zip</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.4</releaseNotes>
<readme>readme.md</readme>
<icon>icon.png</icon>
<tags>oqtane framework</tags>

View File

@ -2,7 +2,7 @@
<package>
<metadata>
<id>Oqtane.Server</id>
<version>5.2.2</version>
<version>5.2.4</version>
<authors>Shaun Walker</authors>
<owners>.NET Foundation</owners>
<title>Oqtane Framework</title>
@ -12,7 +12,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.2</releaseNotes>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.4</releaseNotes>
<readme>readme.md</readme>
<icon>icon.png</icon>
<tags>oqtane</tags>

View File

@ -2,7 +2,7 @@
<package>
<metadata>
<id>Oqtane.Shared</id>
<version>5.2.2</version>
<version>5.2.4</version>
<authors>Shaun Walker</authors>
<owners>.NET Foundation</owners>
<title>Oqtane Framework</title>
@ -12,7 +12,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.2</releaseNotes>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.4</releaseNotes>
<readme>readme.md</readme>
<icon>icon.png</icon>
<tags>oqtane</tags>

View File

@ -2,7 +2,7 @@
<package>
<metadata>
<id>Oqtane.Updater</id>
<version>5.2.2</version>
<version>5.2.4</version>
<authors>Shaun Walker</authors>
<owners>.NET Foundation</owners>
<title>Oqtane Framework</title>
@ -12,7 +12,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.2</releaseNotes>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.4</releaseNotes>
<readme>readme.md</readme>
<icon>icon.png</icon>
<tags>oqtane</tags>

View File

@ -1 +1 @@
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net8.0\publish\*" -DestinationPath "Oqtane.Framework.5.2.2.Install.zip" -Force
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net8.0\publish\*" -DestinationPath "Oqtane.Framework.5.2.4.Install.zip" -Force

View File

@ -1 +1 @@
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net8.0\publish\*" -DestinationPath "Oqtane.Framework.5.2.2.Upgrade.zip" -Force
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net8.0\publish\*" -DestinationPath "Oqtane.Framework.5.2.4.Upgrade.zip" -Force

View File

@ -522,7 +522,7 @@
" let currentUrl = window.location.pathname;" + Environment.NewLine +
" Blazor.addEventListener('enhancedload', () => {" + Environment.NewLine +
" let newUrl = window.location.pathname;" + Environment.NewLine +
" if (currentUrl != newUrl) {" + Environment.NewLine +
" if (currentUrl !== newUrl || window.location.hash === '#top') {" + Environment.NewLine +
" window.scrollTo({ top: 0, left: 0, behavior: 'instant' });" + Environment.NewLine +
" }" + Environment.NewLine +
" currentUrl = newUrl;" + Environment.NewLine +
@ -609,7 +609,7 @@
Expires = DateTimeOffset.UtcNow.AddYears(1),
SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Lax, // Set SameSite attribute
Secure = true, // Ensure the cookie is only sent over HTTPS
HttpOnly = true // Optional: Helps mitigate XSS attacks
HttpOnly = false // cookie is updated using JS Interop in Interactive render mode
};
Context.Response.Cookies.Append(

View File

@ -17,11 +17,10 @@ using Oqtane.Infrastructure;
using Oqtane.Repository;
using Oqtane.Extensions;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Formats.Png;
using System.Net.Http;
using Microsoft.AspNetCore.Cors;
using System.IO.Compression;
using Oqtane.Services;
// ReSharper disable StringIndexOfIsCultureSpecific.1
@ -38,7 +37,9 @@ namespace Oqtane.Controllers
private readonly ILogManager _logger;
private readonly Alias _alias;
private readonly ISettingRepository _settingRepository;
public FileController(IWebHostEnvironment environment, IFileRepository files, IFolderRepository folders, IUserPermissions userPermissions, ISettingRepository settingRepository, ISyncManager syncManager, ILogManager logger, ITenantManager tenantManager)
private readonly IImageService _imageService;
public FileController(IWebHostEnvironment environment, IFileRepository files, IFolderRepository folders, IUserPermissions userPermissions, ISettingRepository settingRepository, ISyncManager syncManager, ILogManager logger, ITenantManager tenantManager, IImageService imageService)
{
_environment = environment;
_files = files;
@ -48,6 +49,7 @@ namespace Oqtane.Controllers
_logger = logger;
_alias = tenantManager.GetAlias();
_settingRepository = settingRepository;
_imageService = imageService;
}
// GET: api/<controller>?folder=x
@ -681,22 +683,18 @@ namespace Oqtane.Controllers
var filepath = _files.GetFilePath(file);
if (System.IO.File.Exists(filepath))
{
// validation
if (!Enum.TryParse(mode, true, out ResizeMode _)) mode = "crop";
if (!Enum.TryParse(position, true, out AnchorPositionMode _)) position = "center";
if (!Color.TryParseHex("#" + background, out _)) background = "transparent";
if (!int.TryParse(rotate, out _)) rotate = "0";
rotate = (int.Parse(rotate) < 0 || int.Parse(rotate) > 360) ? "0" : rotate;
if (!bool.TryParse(recreate, out _)) recreate = "false";
string imagepath = filepath.Replace(Path.GetExtension(filepath), "." + width.ToString() + "x" + height.ToString() + ".png");
string format = "png";
string imagepath = filepath.Replace(Path.GetExtension(filepath), "." + width.ToString() + "x" + height.ToString() + "." + format);
if (!System.IO.File.Exists(imagepath) || bool.Parse(recreate))
{
// user has edit access to folder or folder supports the image size being created
if (_userPermissions.IsAuthorized(User, PermissionNames.Edit, file.Folder.PermissionList) ||
(!string.IsNullOrEmpty(file.Folder.ImageSizes) && (file.Folder.ImageSizes == "*" || file.Folder.ImageSizes.ToLower().Split(",").Contains(width.ToString() + "x" + height.ToString()))))
{
imagepath = CreateImage(filepath, width, height, mode, position, background, rotate, imagepath);
imagepath = _imageService.CreateImage(filepath, width, height, mode, position, background, rotate, format, imagepath);
}
else
{
@ -743,70 +741,6 @@ namespace Oqtane.Controllers
return System.IO.File.Exists(errorPath) ? PhysicalFile(errorPath, MimeUtilities.GetMimeType(errorPath)) : null;
}
private string CreateImage(string filepath, int width, int height, string mode, string position, string background, string rotate, string imagepath)
{
try
{
using (var stream = new FileStream(filepath, FileMode.Open, FileAccess.Read))
{
stream.Position = 0;
using (var image = Image.Load(stream))
{
int.TryParse(rotate, out int angle);
Enum.TryParse(mode, true, out ResizeMode resizemode);
Enum.TryParse(position, true, out AnchorPositionMode anchorpositionmode);
PngEncoder encoder;
if (background != "transparent")
{
image.Mutate(x => x
.AutoOrient() // auto orient the image
.Rotate(angle)
.Resize(new ResizeOptions
{
Mode = resizemode,
Position = anchorpositionmode,
Size = new Size(width, height),
PadColor = Color.ParseHex("#" + background)
}));
encoder = new PngEncoder();
}
else
{
image.Mutate(x => x
.AutoOrient() // auto orient the image
.Rotate(angle)
.Resize(new ResizeOptions
{
Mode = resizemode,
Position = anchorpositionmode,
Size = new Size(width, height)
}));
encoder = new PngEncoder
{
ColorType = PngColorType.RgbWithAlpha,
TransparentColorMode = PngTransparentColorMode.Preserve,
BitDepth = PngBitDepth.Bit8,
CompressionLevel = PngCompressionLevel.BestSpeed
};
}
image.Save(imagepath, encoder);
}
}
}
catch (Exception ex)
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, ex, "Error Creating Image For File {FilePath} {Width} {Height} {Mode} {Rotate} {Error}", filepath, width, height, mode, rotate, ex.Message);
imagepath = "";
}
return imagepath;
}
private string GetFolderPath(string folder)
{
return Utilities.PathCombine(_environment.ContentRootPath, folder);

View File

@ -55,6 +55,10 @@ namespace Oqtane.Controllers
else
{
languages = _languages.GetLanguages(SiteId).ToList();
foreach (Language language in languages)
{
language.Name = CultureInfo.GetCultureInfo(language.Code).DisplayName;
}
if (!string.IsNullOrEmpty(packagename))
{
foreach (var file in Directory.EnumerateFiles(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), $"{packagename}*{Constants.SatelliteAssemblyExtension}", SearchOption.AllDirectories))
@ -85,6 +89,7 @@ namespace Oqtane.Controllers
var language = _languages.GetLanguage(id);
if (language != null && language.SiteId == _alias.SiteId)
{
language.Name = CultureInfo.GetCultureInfo(language.Code).DisplayName;
return language;
}
else

View File

@ -8,10 +8,6 @@ using Oqtane.Infrastructure;
using Oqtane.Repository;
using Oqtane.Security;
using System.Net;
using System.Reflection.Metadata;
using Microsoft.Extensions.Localization;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using System.Linq;
namespace Oqtane.Controllers
{

View File

@ -189,7 +189,7 @@ namespace Oqtane.Controllers
public void Delete(string entityName, int entityId, string settingName)
{
Setting setting = _settings.GetSetting(entityName, entityId, settingName);
if (IsAuthorized(setting.EntityName, setting.EntityId, PermissionNames.Edit))
if (setting != null && IsAuthorized(setting.EntityName, setting.EntityId, PermissionNames.Edit))
{
_settings.DeleteSetting(setting.EntityName, setting.SettingId);
AddSyncEvent(setting.EntityName, setting.EntityId, setting.SettingId, SyncEventActions.Delete);
@ -199,7 +199,7 @@ namespace Oqtane.Controllers
{
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, "Setting Does Not Exist Or User Not Authorized To Delete Setting For Entity {EntityName} Id {EntityId} Name {SettingName}", entityName, entityId, settingName);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
}
}

View File

@ -1,6 +1,4 @@
using System.Collections.Generic;
using Oqtane.Databases.Interfaces;
using Oqtane.Interfaces;
namespace Oqtane.Repository.Databases.Interfaces
{

View File

@ -102,6 +102,7 @@ namespace Microsoft.Extensions.DependencyInjection
services.AddScoped<ISearchResultsService, SearchResultsService>();
services.AddScoped<ISearchService, SearchService>();
services.AddScoped<ISearchProvider, DatabaseSearchProvider>();
services.AddScoped<IImageService, ImageService>();
// providers
services.AddScoped<ITextEditor, Oqtane.Modules.Controls.QuillJSTextEditor>();
@ -169,6 +170,7 @@ namespace Microsoft.Extensions.DependencyInjection
options.Cookie.HttpOnly = true;
options.Cookie.SameSite = SameSiteMode.Lax;
options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
options.LoginPath = "/login"; // overrides .NET Identity default of /Account/Login
options.Events.OnRedirectToLogin = context =>
{
context.Response.StatusCode = (int)HttpStatusCode.Forbidden;

View File

@ -592,7 +592,7 @@ namespace Oqtane.Extensions
}
// create claims identity
identityuser = await _identityUserManager.FindByEmailAsync(user.Username);
identityuser = await _identityUserManager.FindByNameAsync(user.Username);
user.SecurityStamp = identityuser.SecurityStamp;
identity = UserSecurity.CreateClaimsIdentity(alias, user, userRoles);
identity.Label = ExternalLoginStatus.Success;
@ -645,13 +645,13 @@ namespace Oqtane.Extensions
}
}
_logger.Log(LogLevel.Information, "ExternalLogin", Enums.LogFunction.Security, "External User Login Successful For {Username} From IP Address {IPAddress} Using Provider {Provider}", user.Username, httpContext.Connection.RemoteIpAddress, providerName);
_logger.Log(LogLevel.Information, "ExternalLogin", Enums.LogFunction.Security, "External User Login Successful For {Username} From IP Address {IPAddress} Using Provider {Provider}", user.Username, httpContext.Connection.RemoteIpAddress.ToString(), providerName);
}
}
else // claims invalid
{
identity.Label = ExternalLoginStatus.MissingClaims;
_logger.Log(LogLevel.Error, "ExternalLogin", Enums.LogFunction.Security, "Provider Did Not Return All Of The Claims Types Specified Or Email Address Does Not Saitisfy Domain Filter. The Actual Claims Returned Were {Claims}. Login Was Denied.", claims);
_logger.Log(LogLevel.Error, "ExternalLogin", Enums.LogFunction.Security, "Provider Did Not Return All Of The Claims Types Specified Or Email Address Does Not Satisfy Domain Filter. The Actual Claims Returned Were {Claims}. Login Was Denied.", claims);
}
return identity;

View File

@ -155,7 +155,7 @@ namespace Oqtane.Infrastructure
// add new site
if (install.TenantName != TenantNames.Master && install.ConnectionString.Contains("="))
{
_configManager.AddOrUpdateSetting($"{SettingKeys.ConnectionStringsSection}:{install.TenantName}", install.ConnectionString, false);
_configManager.AddOrUpdateSetting($"{SettingKeys.ConnectionStringsSection}:{install.TenantName}", install.ConnectionString, true);
}
if (install.TenantName == TenantNames.Master && !install.ConnectionString.Contains("="))
{
@ -375,7 +375,6 @@ namespace Oqtane.Infrastructure
AddEFMigrationsHistory(sql, _configManager.GetSetting($"{SettingKeys.ConnectionStringsSection}:{tenant.DBConnectionString}", ""), tenant.DBType, tenant.Version, false);
// push latest model into database
tenantDbContext.Database.Migrate();
result.Success = true;
}
}
catch (Exception ex)
@ -384,35 +383,35 @@ namespace Oqtane.Infrastructure
_filelogger.LogError(Utilities.LogMessage(this, result.Message));
}
// execute any version specific upgrade logic
var version = tenant.Version;
var index = Array.FindIndex(versions, item => item == version);
if (index != (versions.Length - 1))
if (string.IsNullOrEmpty(result.Message))
{
try
// execute any version specific upgrade logic
var version = tenant.Version;
var index = Array.FindIndex(versions, item => item == version);
if (index != (versions.Length - 1))
{
for (var i = (index + 1); i < versions.Length; i++)
try
{
upgrades.Upgrade(tenant, versions[i]);
for (var i = (index + 1); i < versions.Length; i++)
{
upgrades.Upgrade(tenant, versions[i]);
}
tenant.Version = versions[versions.Length - 1];
db.Entry(tenant).State = EntityState.Modified;
db.SaveChanges();
}
catch (Exception ex)
{
result.Message = "An Error Occurred Executing Upgrade Logic On Tenant " + tenant.Name + ". " + ex.ToString();
_filelogger.LogError(Utilities.LogMessage(this, result.Message));
}
tenant.Version = versions[versions.Length - 1];
db.Entry(tenant).State = EntityState.Modified;
db.SaveChanges();
}
catch (Exception ex)
{
result.Message = "An Error Occurred Executing Upgrade Logic On Tenant " + tenant.Name + ". " + ex.ToString();
_filelogger.LogError(Utilities.LogMessage(this, result.Message));
}
}
}
}
}
if (string.IsNullOrEmpty(result.Message))
{
result.Success = true;
}
result.Success = string.IsNullOrEmpty(result.Message);
return result;
}
@ -588,7 +587,7 @@ namespace Oqtane.Infrastructure
// add host role
var hostRoleId = roles.GetRoles(user.SiteId, true).FirstOrDefault(item => item.Name == RoleNames.Host)?.RoleId ?? 0;
var userRole = new UserRole { UserId = user.UserId, RoleId = hostRoleId, EffectiveDate = null, ExpiryDate = null };
var userRole = new UserRole { UserId = user.UserId, RoleId = hostRoleId, EffectiveDate = null, ExpiryDate = null, IgnoreSecurityStamp = true };
userRoles.AddUserRole(userRole);
}
}

View File

@ -89,9 +89,9 @@ namespace Oqtane.Infrastructure
}
// validate recipient
if (string.IsNullOrEmpty(notification.ToEmail))
if (string.IsNullOrEmpty(notification.ToEmail) || !MailAddress.TryCreate(notification.ToEmail, out _))
{
log += "Recipient Missing For NotificationId: " + notification.NotificationId + "<br />";
log += $"NotificationId: {notification.NotificationId} - Has Missing Or Invalid Recipient {notification.ToEmail}<br />";
notification.IsDeleted = true;
notificationRepository.UpdateNotification(notification);
}

View File

@ -319,6 +319,19 @@ namespace Oqtane.Migrations.EntityBuilders
schema: Schema);
}
public virtual void AddForeignKey(string foreignKeyName, string columnName, string principalTable, string principalColumn, string principalSchema, ReferentialAction onDelete)
{
_migrationBuilder.AddForeignKey(
name: RewriteName(foreignKeyName),
table: RewriteName(EntityTableName),
column: RewriteName(columnName),
principalTable: RewriteName(principalTable),
principalColumn: RewriteName(principalColumn),
principalSchema: RewriteName(principalSchema),
onDelete: onDelete,
schema: Schema);
}
/// <summary>
/// Creates a Migration to add an Index to the Entity (table)
/// </summary>
@ -368,6 +381,7 @@ namespace Oqtane.Migrations.EntityBuilders
column: foreignKey.Column,
principalTable: RewriteName(foreignKey.PrincipalTable),
principalColumn: RewriteName(foreignKey.PrincipalColumn),
principalSchema: RewriteName(foreignKey.PrincipalSchema),
onDelete: foreignKey.OnDeleteAction);
}
@ -381,6 +395,7 @@ namespace Oqtane.Migrations.EntityBuilders
column: RewriteName(foreignKey.ColumnName),
principalTable: RewriteName(foreignKey.PrincipalTable),
principalColumn: RewriteName(foreignKey.PrincipalColumn),
principalSchema: RewriteName(foreignKey.PrincipalSchema),
onDelete: foreignKey.OnDeleteAction,
schema: Schema);
}

View File

@ -16,6 +16,16 @@ namespace Oqtane.Migrations
OnDeleteAction = onDeleteAction;
}
public ForeignKey(string name, Expression<Func<TEntityBuilder, object>> column, string principalTable, string principalColumn, string principalSchema, ReferentialAction onDeleteAction)
{
Name = name;
Column = column;
PrincipalTable = principalTable;
PrincipalColumn = principalColumn;
PrincipalSchema = principalSchema;
OnDeleteAction = onDeleteAction;
}
public string Name { get; }
public Expression<Func<TEntityBuilder, object>> Column { get;}
@ -34,6 +44,8 @@ namespace Oqtane.Migrations
public string PrincipalColumn { get; }
public string PrincipalSchema { get; }
}
}

View File

@ -0,0 +1,28 @@
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.05.02.04.01")]
public class RemoveLanguageName : MultiDatabaseMigration
{
public RemoveLanguageName(IDatabase database) : base(database)
{
}
protected override void Up(MigrationBuilder migrationBuilder)
{
var languageEntityBuilder = new LanguageEntityBuilder(migrationBuilder, ActiveDatabase);
languageEntityBuilder.DropColumn("Name");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
// not implemented
}
}
}

View File

@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Configurations>Debug;Release</Configurations>
<Version>5.2.2</Version>
<Version>5.2.4</Version>
<Product>Oqtane</Product>
<Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company>
@ -11,7 +11,7 @@
<Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.2</PackageReleaseNotes>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.4</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<RootNamespace>Oqtane</RootNamespace>
@ -33,21 +33,21 @@
<EmbeddedResource Include="Scripts\MigrateTenant.sql" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="HtmlAgilityPack" Version="1.11.62" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="8.0.8" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.8" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.2.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.8">
<PackageReference Include="HtmlAgilityPack" Version="1.11.67" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="8.0.10" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.10" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.2.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.10" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.10">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.8" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.10" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.5" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.7.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="8.0.8" />
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="8.0.8" />
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.9" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.8.1" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="8.0.10" />
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="8.0.10" />
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.10" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Oqtane.Client\Oqtane.Client.csproj" />

View File

@ -1,5 +1,6 @@
using System;
using System.IO;
using System.Linq;
using System.Net;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Hosting;
@ -14,6 +15,7 @@ using Oqtane.Infrastructure;
using Oqtane.Models;
using Oqtane.Repository;
using Oqtane.Security;
using Oqtane.Services;
using Oqtane.Shared;
namespace Oqtane.Pages
@ -28,8 +30,10 @@ namespace Oqtane.Pages
private readonly ISyncManager _syncManager;
private readonly ILogManager _logger;
private readonly Alias _alias;
private readonly IImageService _imageService;
private readonly ISettingRepository _settingRepository;
public FilesModel(IWebHostEnvironment environment, IFileRepository files, IUserPermissions userPermissions, IUrlMappingRepository urlMappings, ISyncManager syncManager, ILogManager logger, ITenantManager tenantManager)
public FilesModel(IWebHostEnvironment environment, IFileRepository files, IUserPermissions userPermissions, IUrlMappingRepository urlMappings, ISyncManager syncManager, ILogManager logger, ITenantManager tenantManager, IImageService imageService, ISettingRepository settingRepository)
{
_environment = environment;
_files = files;
@ -38,111 +42,228 @@ namespace Oqtane.Pages
_syncManager = syncManager;
_logger = logger;
_alias = tenantManager.GetAlias();
_imageService = imageService;
_settingRepository = settingRepository;
}
public IActionResult OnGet(string path)
{
if (!string.IsNullOrEmpty(path))
{
path = path.Replace("\\", "/");
var folderpath = "";
var filename = "";
bool download = false;
if (Request.Query.ContainsKey("download"))
{
download = true;
}
var segments = path.Split('/');
if (segments.Length > 0)
{
filename = segments[segments.Length - 1].ToLower();
if (segments.Length > 1)
{
folderpath = string.Join("/", segments, 0, segments.Length - 1).ToLower() + "/";
}
}
Models.File file;
if (folderpath == "id/" && int.TryParse(filename, out int fileid))
{
file = _files.GetFile(fileid, false);
}
else
{
file = _files.GetFile(_alias.SiteId, folderpath, filename);
}
if (file != null)
{
if (file.Folder.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, PermissionNames.View, file.Folder.PermissionList))
{
// calculate ETag using last modified date and file size
var etag = Convert.ToString(file.ModifiedOn.Ticks ^ file.Size, 16);
var header = "";
if (HttpContext.Request.Headers.ContainsKey(HeaderNames.IfNoneMatch))
{
header = HttpContext.Request.Headers[HeaderNames.IfNoneMatch].ToString();
}
if (!header.Equals(etag))
{
var filepath = _files.GetFilePath(file);
if (System.IO.File.Exists(filepath))
{
if (download)
{
_syncManager.AddSyncEvent(_alias, EntityNames.File, file.FileId, "Download");
return PhysicalFile(filepath, file.GetMimeType(), file.Name);
}
else
{
HttpContext.Response.Headers.Append(HeaderNames.ETag, etag);
return PhysicalFile(filepath, file.GetMimeType());
}
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Read, "File Does Not Exist {FilePath}", filepath);
HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound;
}
}
else
{
HttpContext.Response.StatusCode = (int)HttpStatusCode.NotModified;
return Content(String.Empty);
}
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized File Access Attempt For Site {SiteId} And Path {Path}", _alias.SiteId, path);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
}
}
else
{
// look for url mapping
var urlMapping = _urlMappings.GetUrlMapping(_alias.SiteId, "files/" + folderpath + filename);
if (urlMapping != null && !string.IsNullOrEmpty(urlMapping.MappedUrl))
{
var url = urlMapping.MappedUrl;
if (!url.StartsWith("http"))
{
var uri = new Uri(HttpContext.Request.GetEncodedUrl());
url = uri.Scheme + "://" + uri.Authority + ((!string.IsNullOrEmpty(_alias.Path)) ? "/" + _alias.Path : "") + "/" + url;
}
return RedirectPermanent(url);
}
}
}
else
if (string.IsNullOrWhiteSpace(path))
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized File Access Attempt - Path Not Specified For Site {SiteId}", _alias.SiteId);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return BrokenFile();
}
path = path.Replace("\\", "/");
var folderpath = "";
var filename = "";
bool download = false;
if (Request.Query.ContainsKey("download"))
{
download = true;
}
var segments = path.Split('/');
if (segments.Length > 0)
{
filename = segments[segments.Length - 1].ToLower();
if (segments.Length > 1)
{
folderpath = string.Join("/", segments, 0, segments.Length - 1).ToLower() + "/";
}
}
Models.File file;
if (folderpath == "id/" && int.TryParse(filename, out int fileid))
{
file = _files.GetFile(fileid, false);
}
else
{
file = _files.GetFile(_alias.SiteId, folderpath, filename);
}
if (file == null)
{
// look for url mapping
var urlMapping = _urlMappings.GetUrlMapping(_alias.SiteId, "files/" + folderpath + filename);
if (urlMapping != null && !string.IsNullOrEmpty(urlMapping.MappedUrl))
{
var url = urlMapping.MappedUrl;
if (!url.StartsWith("http"))
{
var uri = new Uri(HttpContext.Request.GetEncodedUrl());
url = uri.Scheme + "://" + uri.Authority + ((!string.IsNullOrEmpty(_alias.Path)) ? "/" + _alias.Path : "") + "/" + url;
}
// appends the query string to the redirect url
if (Request.QueryString.HasValue && !string.IsNullOrWhiteSpace(Request.QueryString.Value))
{
if (url.Contains('?'))
{
url += "&";
}
else
{
url += "?";
}
url += Request.QueryString.Value.Substring(1);
}
return RedirectPermanent(url);
}
return BrokenFile();
}
if (file.Folder.SiteId != _alias.SiteId || !_userPermissions.IsAuthorized(User, PermissionNames.View, file.Folder.PermissionList))
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized File Access Attempt For Site {SiteId} And Path {Path}", _alias.SiteId, path);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return BrokenFile();
}
string etag;
string downloadName = file.Name;
string filepath = _files.GetFilePath(file);
var etagValue = file.ModifiedOn.Ticks ^ file.Size;
bool isRequestingImageManipulation = false;
int width = 0;
int height = 0;
if (Request.Query.TryGetValue("width", out var widthStr) && int.TryParse(widthStr, out width) && width > 0)
{
isRequestingImageManipulation = true;
etagValue ^= (width * 31);
}
if (Request.Query.TryGetValue("height", out var heightStr) && int.TryParse(heightStr, out height) && height > 0)
{
isRequestingImageManipulation = true;
etagValue ^= (height * 17);
}
Request.Query.TryGetValue("mode", out var mode);
Request.Query.TryGetValue("position", out var position);
Request.Query.TryGetValue("background", out var background);
if (width > 0 || height > 0)
{
if (!string.IsNullOrWhiteSpace(mode)) etagValue ^= mode.ToString().GetHashCode();
if (!string.IsNullOrWhiteSpace(position)) etagValue ^= position.ToString().GetHashCode();
if (!string.IsNullOrWhiteSpace(background)) etagValue ^= background.ToString().GetHashCode();
}
int rotate;
if (Request.Query.TryGetValue("rotate", out var rotateStr) && int.TryParse(rotateStr, out rotate) && 360 > rotate && rotate > 0)
{
isRequestingImageManipulation = true;
etagValue ^= (rotate * 13);
}
if (Request.Query.TryGetValue("format", out var format) && _imageService.GetAvailableFormats().Contains(format.ToString()))
{
isRequestingImageManipulation = true;
etagValue ^= format.ToString().GetHashCode();
}
etag = Convert.ToString(etagValue, 16);
var header = "";
if (HttpContext.Request.Headers.TryGetValue(HeaderNames.IfNoneMatch, out var ifNoneMatch))
{
header = ifNoneMatch.ToString();
}
if (header.Equals(etag))
{
HttpContext.Response.StatusCode = (int)HttpStatusCode.NotModified;
return Content(String.Empty);
}
if (!System.IO.File.Exists(filepath))
{
_logger.Log(LogLevel.Error, this, LogFunction.Read, "File Does Not Exist {FilePath}", filepath);
HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound;
return BrokenFile();
}
if (isRequestingImageManipulation)
{
var _ImageFiles = _settingRepository.GetSetting(EntityNames.Site, _alias.SiteId, "ImageFiles")?.SettingValue;
_ImageFiles = (string.IsNullOrEmpty(_ImageFiles)) ? Constants.ImageFiles : _ImageFiles;
if (!_ImageFiles.Split(',').Contains(file.Extension.ToLower()))
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "File Is Not An Image {File}", file);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return BrokenFile();
}
Request.Query.TryGetValue("recreate", out var recreate);
if (!bool.TryParse(recreate, out _)) recreate = "false";
if (!_imageService.GetAvailableFormats().Contains(format.ToString())) format = "png";
if (width == 0 && height == 0)
{
width = file.ImageWidth;
height = file.ImageHeight;
}
string imagepath = filepath.Replace(Path.GetExtension(filepath), "." + width.ToString() + "x" + height.ToString() + "." + format);
if (!System.IO.File.Exists(imagepath) || bool.Parse(recreate))
{
// user has edit access to folder or folder supports the image size being created
if (_userPermissions.IsAuthorized(User, PermissionNames.Edit, file.Folder.PermissionList) ||
(!string.IsNullOrEmpty(file.Folder.ImageSizes) && (file.Folder.ImageSizes == "*" || file.Folder.ImageSizes.ToLower().Split(",").Contains(width.ToString() + "x" + height.ToString()))))
{
imagepath = _imageService.CreateImage(filepath, width, height, mode, position, background, rotateStr, format, imagepath);
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Invalid Image Size For Folder {Folder} {Width} {Height}", file.Folder, width, height);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return BrokenFile();
}
}
if (string.IsNullOrWhiteSpace(imagepath))
{
_logger.Log(LogLevel.Error, this, LogFunction.Create, "Error Displaying Image For File {File} {Width} {Height}", file, widthStr, heightStr);
HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound;
return BrokenFile();
}
downloadName = file.Name.Replace(Path.GetExtension(filepath), "." + width.ToString() + "x" + height.ToString() + "." + format);
filepath = imagepath;
}
if (!System.IO.File.Exists(filepath))
{
_logger.Log(LogLevel.Error, this, LogFunction.Read, "File Does Not Exist {FilePath}", filepath);
HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound;
return BrokenFile();
}
if (download)
{
_syncManager.AddSyncEvent(_alias, EntityNames.File, file.FileId, "Download");
return PhysicalFile(filepath, file.GetMimeType(), downloadName);
}
else
{
HttpContext.Response.Headers.Append(HeaderNames.ETag, etag);
return PhysicalFile(filepath, file.GetMimeType());
}
}
private PhysicalFileResult BrokenFile()
{
// broken link
string errorPath = Path.Combine(Utilities.PathCombine(_environment.ContentRootPath, "wwwroot/images"), "error.png");
return PhysicalFile(errorPath, MimeUtilities.GetMimeType(errorPath));

View File

@ -91,18 +91,29 @@ namespace Oqtane.Repository
public void DeletePage(int pageId)
{
using var db = _dbContextFactory.CreateDbContext();
var page = db.Page.Find(pageId);
_permissions.DeletePermissions(page.SiteId, EntityNames.Page, pageId);
_settings.DeleteSettings(EntityNames.Page, pageId);
// remove page modules for page
var pageModules = db.PageModule.Where(item => item.PageId == pageId).ToList();
foreach (var pageModule in pageModules)
{
_pageModules.DeletePageModule(pageModule.PageModuleId);
var page = db.Page.Find(pageId);
_permissions.DeletePermissions(page.SiteId, EntityNames.Page, pageId);
_settings.DeleteSettings(EntityNames.Page, pageId);
// remove page modules for page
var pageModules = db.PageModule.Where(item => item.PageId == pageId).ToList();
foreach (var pageModule in pageModules)
{
_pageModules.DeletePageModule(pageModule.PageModuleId);
}
// At this point the page item is unaware of changes happened in other
// contexts (i.e.: the contex opened and closed in each DeletePageModule).
// Workin on page item may result in unxpected behaviour:
// better close and reopen context to work on a fresh page item.
}
using var dbContext = _dbContextFactory.CreateDbContext();
{
var page = dbContext.Page.Find(pageId);
dbContext.Page.Remove(page);
dbContext.SaveChanges();
}
// must occur after page modules are deleted because of cascading delete relationship
db.Page.Remove(page);
db.SaveChanges();
}
}
}

View File

@ -75,6 +75,7 @@ namespace Oqtane.Repository
userrole.RoleId = role.RoleId;
userrole.EffectiveDate = null;
userrole.ExpiryDate = null;
userrole.IgnoreSecurityStamp = true;
_userroles.AddUserRole(userrole);
}

View File

@ -72,7 +72,12 @@ namespace Oqtane.Repository
DeleteUserRoles(userRole.UserId);
}
UpdateSecurityStamp(userRole.UserId);
if (!userRole.IgnoreSecurityStamp)
{
UpdateSecurityStamp(userRole.UserId);
}
RefreshCache(userRole.UserId);
return userRole;
}
@ -83,7 +88,12 @@ namespace Oqtane.Repository
db.Entry(userRole).State = EntityState.Modified;
db.SaveChanges();
UpdateSecurityStamp(userRole.UserId);
if (!userRole.IgnoreSecurityStamp)
{
UpdateSecurityStamp(userRole.UserId);
}
RefreshCache(userRole.UserId);
return userRole;
}
@ -144,6 +154,7 @@ namespace Oqtane.Repository
db.SaveChanges();
UpdateSecurityStamp(userRole.UserId);
RefreshCache(userRole.UserId);
}
public void DeleteUserRoles(int userId)
@ -156,11 +167,11 @@ namespace Oqtane.Repository
db.SaveChanges();
UpdateSecurityStamp(userId);
RefreshCache(userId);
}
private void UpdateSecurityStamp(int userId)
{
// update user security stamp
using var db = _dbContextFactory.CreateDbContext();
var user = db.User.Find(userId);
if (user != null)
@ -168,11 +179,13 @@ namespace Oqtane.Repository
var identityuser = _identityUserManager.FindByNameAsync(user.Username).GetAwaiter().GetResult();
if (identityuser != null)
{
_identityUserManager.UpdateSecurityStampAsync(identityuser);
_identityUserManager.UpdateSecurityStampAsync(identityuser).GetAwaiter().GetResult();
}
}
}
// refresh cache
private void RefreshCache(int userId)
{
var alias = _tenantManager.GetAlias();
if (alias != null)
{

View File

@ -7,13 +7,13 @@ using Oqtane.Models;
using Oqtane.Extensions;
using Oqtane.Shared;
using Oqtane.Managers;
using Microsoft.AspNetCore.Authentication;
namespace Oqtane.Security
{
public static class PrincipalValidator
{
public static Task ValidateAsync(CookieValidatePrincipalContext context)
public static async Task ValidateAsync(CookieValidatePrincipalContext context)
{
if (context != null && context.Principal.Identity.IsAuthenticated && context.Principal.Identity.Name != null)
{
@ -49,6 +49,7 @@ namespace Oqtane.Security
// remove principal (ie. log user out)
Log(_logger, alias, "Permissions Removed For User {Username} Accessing {Url}", context.Principal.Identity.Name, path);
context.RejectPrincipal();
await context.HttpContext.SignOutAsync(Constants.AuthenticationScheme);
}
}
else
@ -58,7 +59,6 @@ namespace Oqtane.Security
}
}
}
return Task.CompletedTask;
}
private static void Log (ILogManager logger, Alias alias, string message, string username, string path)

View File

@ -0,0 +1,124 @@
using Oqtane.Enums;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Processing;
using System.IO;
using System;
using SixLabors.ImageSharp;
using Oqtane.Infrastructure;
using Oqtane.Shared;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Webp;
using System.Linq;
namespace Oqtane.Services
{
public class ImageService : IImageService
{
private readonly ILogManager _logger;
private static readonly string[] _formats = ["png", "webp"];
public ImageService(ILogManager logger)
{
_logger = logger;
}
public string[] GetAvailableFormats()
{
return _formats;
}
public string CreateImage(string filepath, int width, int height, string mode, string position, string background, string rotate, string format, string imagepath)
{
try
{
// params validation
if (!Enum.TryParse(mode, true, out ResizeMode _)) mode = "crop";
if (!Enum.TryParse(position, true, out AnchorPositionMode _)) position = "center";
if (!Color.TryParseHex("#" + background, out _)) background = "transparent";
if (!int.TryParse(rotate, out _)) rotate = "0";
rotate = (int.Parse(rotate) < 0 || int.Parse(rotate) > 360) ? "0" : rotate;
if (!_formats.Contains(format)) format = "png";
using (var stream = new FileStream(filepath, FileMode.Open, FileAccess.Read))
{
stream.Position = 0;
using (var image = Image.Load(stream))
{
int.TryParse(rotate, out int angle);
Enum.TryParse(mode, true, out ResizeMode resizemode);
Enum.TryParse(position, true, out AnchorPositionMode anchorpositionmode);
if (width == 0 && height == 0)
{
width = image.Width;
height = image.Height;
}
IImageEncoder encoder;
var resizeOptions = new ResizeOptions
{
Mode = resizemode,
Position = anchorpositionmode,
Size = new Size(width, height)
};
if (background != "transparent")
{
resizeOptions.PadColor = Color.ParseHex("#" + background);
encoder = GetEncoder(format, transparent: false);
}
else
{
encoder = GetEncoder(format, transparent: true);
}
image.Mutate(x => x
.AutoOrient() // auto orient the image
.Rotate(angle)
.Resize(resizeOptions));
image.Save(imagepath, encoder);
}
}
}
catch (Exception ex)
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, ex, "Error Creating Image For File {FilePath} {Width} {Height} {Mode} {Rotate} {Error}", filepath, width, height, mode, rotate, ex.Message);
imagepath = "";
}
return imagepath;
}
private static IImageEncoder GetEncoder(string format, bool transparent)
{
return format switch
{
"png" => GetPngEncoder(transparent),
"webp" => GetWebpEncoder(transparent),
_ => GetPngEncoder(transparent),
};
}
private static PngEncoder GetPngEncoder(bool transparent)
{
return new PngEncoder()
{
ColorType = transparent ? PngColorType.RgbWithAlpha : PngColorType.Rgb,
TransparentColorMode = transparent ? PngTransparentColorMode.Preserve : PngTransparentColorMode.Clear,
BitDepth = PngBitDepth.Bit8,
CompressionLevel = PngCompressionLevel.BestSpeed
};
}
private static WebpEncoder GetWebpEncoder(bool transparent)
{
return new WebpEncoder()
{
FileFormat = WebpFileFormatType.Lossy,
Quality = 60,
TransparentColorMode = transparent ? WebpTransparentColorMode.Preserve : WebpTransparentColorMode.Clear,
};
}
}
}

View File

@ -92,6 +92,13 @@ namespace Oqtane.Services
}
site.Pages = pages;
// get language display name for user
foreach (Language language in site.Languages)
{
language.Name = CultureInfo.GetCultureInfo(language.Code).DisplayName;
}
site.Languages = site.Languages.OrderBy(item => item.Name).ToList();
return Task.FromResult(site);
}
@ -130,7 +137,10 @@ namespace Oqtane.Services
// languages
site.Languages = _languages.GetLanguages(site.SiteId).ToList();
var defaultCulture = CultureInfo.GetCultureInfo(Constants.DefaultCulture);
site.Languages.Add(new Language { Code = defaultCulture.Name, Name = defaultCulture.DisplayName, Version = Constants.Version, IsDefault = !site.Languages.Any(l => l.IsDefault) });
if (!site.Languages.Exists(item => item.Code == defaultCulture.Name))
{
site.Languages.Add(new Language { Code = defaultCulture.Name, Name = "", Version = Constants.Version, IsDefault = !site.Languages.Any(l => l.IsDefault) });
}
// themes
site.Themes = _themes.FilterThemes(_themes.GetThemes().ToList());

View File

@ -159,7 +159,7 @@ namespace Oqtane
}
}).AddHubOptions(options =>
{
options.MaximumReceiveMessageSize = null; // no limit (for large amnounts of data ie. textarea components)
options.MaximumReceiveMessageSize = null; // no limit (for large amounts of data ie. textarea components)
})
.AddInteractiveWebAssemblyComponents();

View File

@ -13,11 +13,11 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.8" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="8.0.8" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.8" />
<PackageReference Include="System.Net.Http.Json" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.10" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="8.0.10" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.10" />
<PackageReference Include="System.Net.Http.Json" Version="8.0.5" />
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Localization" Version="2.2.0" />
</ItemGroup>

View File

@ -19,10 +19,10 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="8.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.8" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.8" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.8" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="8.0.10" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.10" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.10" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.10" />
</ItemGroup>
<ItemGroup>

View File

@ -13,9 +13,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.8" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="8.0.8" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.8" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.10" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="8.0.10" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.10" />
</ItemGroup>
<ItemGroup>

View File

@ -1,11 +1,18 @@
var Oqtane = Oqtane || {};
Oqtane.Interop = {
setCookie: function (name, value, days) {
setCookie: function (name, value, days, secure, sameSite) {
var d = new Date();
d.setTime(d.getTime() + (days * 24 * 60 * 60 * 1000));
var expires = "expires=" + d.toUTCString();
document.cookie = name + "=" + value + ";" + expires + ";path=/";
var cookieString = name + "=" + value + ";" + expires + ";path=/";
if (secure) {
cookieString += "; secure";
}
if (sameSite === "Lax" || sameSite === "Strict" || sameSite === "None") {
cookieString += "; SameSite=" + sameSite;
}
document.cookie = cookieString;
},
getCookie: function (name) {
name = name + "=";

View File

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Oqtane.Services
{
public interface IImageService
{
public string[] GetAvailableFormats();
public string CreateImage(string filepath, int width, int height, string mode, string position, string background, string rotate, string format, string imagepath);
}
}

View File

@ -0,0 +1,11 @@
using System.Collections.Generic;
namespace Oqtane.Models
{
public class ExternalLoginProvider
{
public string Name { get; set; }
public Dictionary<string, string> Settings { get; set; }
}
}

View File

@ -19,11 +19,6 @@ namespace Oqtane.Models
/// </summary>
public int? SiteId { get; set; }
/// <summary>
/// Language Name - corresponds to <see cref="Culture.DisplayName"/>, _not_ <see cref="Culture.Name"/>
/// </summary>
public string Name { get; set; }
/// <summary>
/// Language / Culture code, like 'en-US' - corresponds to <see cref="Culture.Name"/>
/// </summary>
@ -34,6 +29,12 @@ namespace Oqtane.Models
/// </summary>
public bool IsDefault { get; set; }
[NotMapped]
/// <summary>
/// Language Name - corresponds to <see cref="Culture.DisplayName"/>, _not_ <see cref="Culture.Name"/>
/// </summary>
public string Name { get; set; }
[NotMapped]
/// <summary>
/// Version of the satellite assembly

View File

@ -110,6 +110,7 @@ namespace Oqtane.Models
EntityId = EntityId,
PermissionName = PermissionName,
RoleName = RoleName,
RoleId = RoleId,
UserId = UserId,
IsAuthorized = IsAuthorized,
CreatedBy = CreatedBy,

View File

@ -1,4 +1,5 @@
using System;
using System.ComponentModel.DataAnnotations.Schema;
namespace Oqtane.Models
{
@ -26,11 +27,18 @@ namespace Oqtane.Models
/// Start of when this assignment is valid. See also <see cref="ExpiryDate"/>
/// </summary>
public DateTime? EffectiveDate { get; set; }
/// <summary>
/// End of when this assignment is valid. See also <see cref="EffectiveDate"/>
/// </summary>
public DateTime? ExpiryDate { get; set; }
/// <summary>
/// Indicates that the User Security Stamp should not be updated when this user role is added or updated
/// </summary>
[NotMapped]
public bool IgnoreSecurityStamp { get; set; }
/// <summary>
/// Direct reference to the <see cref="Role"/> object.
/// TODO: todoc - is this always populated?

View File

@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Configurations>Debug;Release</Configurations>
<Version>5.2.2</Version>
<Version>5.2.4</Version>
<Product>Oqtane</Product>
<Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company>
@ -11,7 +11,7 @@
<Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.2</PackageReleaseNotes>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.4</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<RootNamespace>Oqtane</RootNamespace>
@ -19,11 +19,11 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.8" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.10" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.10" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.2" />
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
<PackageReference Include="System.Text.Json" Version="8.0.4" />
<PackageReference Include="System.Text.Json" Version="8.0.5" />
</ItemGroup>
</Project>

View File

@ -4,8 +4,8 @@ namespace Oqtane.Shared
{
public class Constants
{
public static readonly string Version = "5.2.2";
public const string ReleaseVersions = "1.0.0,1.0.1,1.0.2,1.0.3,1.0.4,2.0.0,2.0.1,2.0.2,2.1.0,2.2.0,2.3.0,2.3.1,3.0.0,3.0.1,3.0.2,3.0.3,3.1.0,3.1.1,3.1.2,3.1.3,3.1.4,3.2.0,3.2.1,3.3.0,3.3.1,3.4.0,3.4.1,3.4.2,3.4.3,4.0.0,4.0.1,4.0.2,4.0.3,4.0.4,4.0.5,4.0.6,5.0.0,5.0.1,5.0.2,5.0.3,5.1.0,5.1.1,5.1.2,5.2.0,5.2.1,5.2.2";
public static readonly string Version = "5.2.4";
public const string ReleaseVersions = "1.0.0,1.0.1,1.0.2,1.0.3,1.0.4,2.0.0,2.0.1,2.0.2,2.1.0,2.2.0,2.3.0,2.3.1,3.0.0,3.0.1,3.0.2,3.0.3,3.1.0,3.1.1,3.1.2,3.1.3,3.1.4,3.2.0,3.2.1,3.3.0,3.3.1,3.4.0,3.4.1,3.4.2,3.4.3,4.0.0,4.0.1,4.0.2,4.0.3,4.0.4,4.0.5,4.0.6,5.0.0,5.0.1,5.0.2,5.0.3,5.1.0,5.1.1,5.1.2,5.2.0,5.2.1,5.2.2,5.2.3,5.2.4";
public const string PackageId = "Oqtane.Framework";
public const string ClientId = "Oqtane.Client";
public const string UpdaterPackageId = "Oqtane.Updater";

View File

@ -0,0 +1,89 @@
using System.Collections.Generic;
using System.Linq;
using Oqtane.Models;
namespace Oqtane.Shared
{
public class ExternalLoginProviders
{
public static List<ExternalLoginProvider> Providers
{
get
{
var providers = new List<ExternalLoginProvider>
{
new ExternalLoginProvider
{
Name = "<Custom>",
Settings = new Dictionary<string, string>()
},
// OIDC
new ExternalLoginProvider
{
Name = "Microsoft Entra",
Settings = new Dictionary<string, string>()
{
{ "ExternalLogin:ProviderUrl", "https://entra.microsoft.com" },
{ "ExternalLogin:ProviderType", "oidc" },
{ "ExternalLogin:ProviderName", "Microsoft Entra" },
{ "ExternalLogin:Authority", "https://login.microsoftonline.com/YOUR_TENANT_ID/v2.0" },
{ "ExternalLogin:ClientId", "YOUR CLIENT ID" },
{ "ExternalLogin:ClientSecret", "YOUR CLIENT SECRET" }
}
},
new ExternalLoginProvider
{
Name = "Auth0 (by Okta)",
Settings = new Dictionary<string, string>()
{
{ "ExternalLogin:ProviderUrl", "https://auth0.com/docs/get-started" },
{ "ExternalLogin:ProviderType", "oidc" },
{ "ExternalLogin:ProviderName", "Auth0" },
{ "ExternalLogin:Authority", "YOUR DOMAIN" },
{ "ExternalLogin:ClientId", "YOUR CLIENT ID" },
{ "ExternalLogin:ClientSecret", "YOUR CLIENT SECRET" }
}
},
// OAuth2
new ExternalLoginProvider
{
Name = "GitHub",
Settings = new Dictionary<string, string>()
{
{ "ExternalLogin:ProviderUrl", "https://github.com/settings/developers#oauth-apps" },
{ "ExternalLogin:ProviderType", "oauth2" },
{ "ExternalLogin:ProviderName", "GitHub" },
{ "ExternalLogin:AuthorizationUrl", "https://github.com/login/oauth/authorize" },
{ "ExternalLogin:TokenUrl", "https://github.com/login/oauth/access_token" },
{ "ExternalLogin:UserInfoUrl", "https://api.github.com/user/emails" },
{ "ExternalLogin:ClientId", "YOUR CLIENT ID" },
{ "ExternalLogin:ClientSecret", "YOUR CLIENT SECRET" },
{ "ExternalLogin:Scopes", "user:email" },
{ "ExternalLogin:IdentifierClaimType", "email" },
{ "ExternalLogin:DomainFilter", "!users.noreply.github.com" }
}
},
new ExternalLoginProvider
{
Name = "Facebook",
Settings = new Dictionary<string, string>()
{
{ "ExternalLogin:ProviderUrl", "https://developers.facebook.com/apps/" },
{ "ExternalLogin:ProviderType", "oauth2" },
{ "ExternalLogin:ProviderName", "Facebook" },
{ "ExternalLogin:AuthorizationUrl", "https://www.facebook.com/v18.0/dialog/oauth" },
{ "ExternalLogin:TokenUrl", "https://graph.facebook.com/v18.0/oauth/access_token" },
{ "ExternalLogin:UserInfoUrl", "https://graph.facebook.com/v18.0/me" },
{ "ExternalLogin:ClientId", "YOUR CLIENT ID" },
{ "ExternalLogin:ClientSecret", "YOUR CLIENT SECRET" },
{ "ExternalLogin:Scopes", "public_profile" },
{ "ExternalLogin:IdentifierClaimType", "id" }
}
}
};
return providers.OrderBy(item => item.Name).ToList();
}
}
}
}

View File

@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<OutputType>Exe</OutputType>
<Version>5.2.2</Version>
<Version>5.2.4</Version>
<Product>Oqtane</Product>
<Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company>
@ -11,7 +11,7 @@
<Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.2</PackageReleaseNotes>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.4</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<RootNamespace>Oqtane</RootNamespace>

View File

@ -1,6 +1,6 @@
# Latest Release
[5.2.1](https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.1) was released on August 22, 2024 and is a maintenance release including 41 pull requests by 5 different contributors, pushing the total number of project commits all-time to over 5700. The Oqtane framework continues to evolve at a rapid pace to meet the needs of .NET developers.
[5.2.3](https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.3) was released on September 23, 2024 and is a maintenance release including 55 pull requests by 8 different contributors, pushing the total number of project commits all-time to over 5800. The Oqtane framework continues to evolve at a rapid pace to meet the needs of .NET developers.
[![Deploy to Azure](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Foqtane%2Foqtane.framework%2Fmaster%2Fazuredeploy.json)
@ -75,6 +75,12 @@ Explore and enhance your Oqtane experience by visiting the Oqtane Marketplace. D
# Documentation
There is a separate [Documentation repository](https://github.com/oqtane/oqtane.docs) which contains a variety of types of documentation for Oqtane, including API documentation that is auto generated using Docfx. The contents of the repository is published to Githib Pages and is available at [https://docs.oqtane.org](https://docs.oqtane.org/)
# Join the Community
Connect with other developers, get support, and share ideas by joining the Oqtane community on Discord!
[![Join our Discord](https://img.shields.io/badge/Join%20Discord-7289DA?style=for-the-badge&logo=discord&logoColor=white)](https://discord.gg/BnPny88avK)
# Roadmap
This project is open source, and therefore is a work in progress...
@ -83,6 +89,14 @@ Backlog (TBD)
- [ ] Folder Providers
- [ ] Generative AI Integration
[5.2.3](https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.3) (Sep 23, 2024)
- [x] Stabilization improvements
[5.2.2](https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.2) (Sep 23, 2024)
- [x] Stabilization improvements
- [x] Support for Security Stamp to faciliate Logout Everywhere
- [x] Role synchronization from External Login identity providers
[5.2.1](https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.1) (Aug 22, 2024)
- [x] Stabilization improvements
- [x] Unzip support in File Management