diff --git a/Oqtane.Client/App.razor b/Oqtane.Client/App.razor index ca4b25bd..405bbd3a 100644 --- a/Oqtane.Client/App.razor +++ b/Oqtane.Client/App.razor @@ -1,4 +1,6 @@ @inject IInstallationService InstallationService +@inject IJSRuntime JSRuntime +@inject SiteState SiteState @if (_initialized) { @@ -20,21 +22,28 @@ {
@_installation.Message -
+ } } } @code { - private Installation _installation; - private bool _initialized; + private bool _initialized = false; + private Installation _installation = new Installation { Success = false, Message = "" }; private PageState PageState { get; set; } - protected override async Task OnParametersSetAsync() + protected override async Task OnAfterRenderAsync(bool firstRender) { - _installation = await InstallationService.IsInstalled(); - _initialized = true; + if (firstRender && !_initialized) + { + var interop = new Interop(JSRuntime); + SiteState.AntiForgeryToken = await interop.GetElementByName(Constants.RequestVerificationToken); + _installation = await InstallationService.IsInstalled(); + SiteState.Alias = _installation.Alias; + _initialized = true; + StateHasChanged(); + } } private void ChangeState(PageState pageState) diff --git a/Oqtane.Client/Modules/Admin/Login/Index.razor b/Oqtane.Client/Modules/Admin/Login/Index.razor index 3da1c2ea..45f4e246 100644 --- a/Oqtane.Client/Modules/Admin/Login/Index.razor +++ b/Oqtane.Client/Modules/Admin/Login/Index.razor @@ -3,6 +3,7 @@ @inject NavigationManager NavigationManager @inject IUserService UserService @inject IServiceProvider ServiceProvider +@inject SiteState SiteState @inject IStringLocalizer Localizer @if (_message != string.Empty) @@ -57,7 +58,7 @@ public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous; public override List Resources => new List() - { +{ new Resource { ResourceType = ResourceType.Stylesheet, Url = ModulePath() + "Module.css" } }; @@ -108,7 +109,6 @@ { if (PageState.Runtime == Oqtane.Shared.Runtime.Server) { - // server-side Blazor var user = new User(); user.SiteId = PageState.Site.SiteId; user.Username = _username; @@ -118,9 +118,8 @@ if (user.IsAuthenticated) { await logger.LogInformation("Login Successful For Username {Username}", _username); - // complete the login on the server so that the cookies are set correctly - string antiforgerytoken = await interop.GetElementByName("__RequestVerificationToken"); - var fields = new { __RequestVerificationToken = antiforgerytoken, username = _username, password = _password, remember = _remember, returnurl = _returnUrl }; + // server-side Blazor needs to post to the Login page so that the cookies are set correctly + var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, username = _username, password = _password, remember = _remember, returnurl = _returnUrl }; string url = Utilities.TenantUrl(PageState.Alias, "/pages/login/"); await interop.SubmitForm(url, fields); } diff --git a/Oqtane.Client/Modules/Admin/Site/Index.razor b/Oqtane.Client/Modules/Admin/Site/Index.razor index eb291714..f772b794 100644 --- a/Oqtane.Client/Modules/Admin/Site/Index.razor +++ b/Oqtane.Client/Modules/Admin/Site/Index.razor @@ -21,14 +21,6 @@ - - - - - - - - @@ -210,6 +202,18 @@ @if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) { +
+ + + + + +
+ + + +
+
@@ -292,16 +296,24 @@ try { _themeList = await ThemeService.GetThemesAsync(); - _aliasList = await AliasService.GetAliasesAsync(); Site site = await SiteService.GetSiteAsync(PageState.Site.SiteId); if (site != null) { _name = site.Name; - foreach (Alias alias in _aliasList.Where(item => item.SiteId == site.SiteId && item.TenantId == site.TenantId).ToList()) + _allowregistration = site.AllowRegistration.ToString(); + _isdeleted = site.IsDeleted.ToString(); + + if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) { - _urls += alias.Name + ","; + _aliasList = await AliasService.GetAliasesAsync(); + foreach (Alias alias in _aliasList.Where(item => item.SiteId == site.SiteId && item.TenantId == site.TenantId).ToList()) + { + _urls += alias.Name + ","; + } + _urls = _urls.Substring(0, _urls.Length - 1); + } - _urls = _urls.Substring(0, _urls.Length - 1); + if (site.LogoFileId != null) { _logofileid = site.LogoFileId.Value; @@ -317,7 +329,6 @@ _containers = ThemeService.GetContainerControls(_themeList, _themetype); _containertype = (!string.IsNullOrEmpty(site.DefaultContainerType)) ? site.DefaultContainerType : Constants.DefaultContainer; _admincontainertype = (!string.IsNullOrEmpty(site.AdminContainerType)) ? site.AdminContainerType : Constants.DefaultAdminContainer; - _allowregistration = site.AllowRegistration.ToString(); var settings = await SettingService.GetSiteSettingsAsync(site.SiteId); _smtphost = SettingService.GetSetting(settings, "SMTPHost", string.Empty); @@ -368,7 +379,6 @@ _modifiedon = site.ModifiedOn; _deletedby = site.DeletedBy; _deletedon = site.DeletedOn; - _isdeleted = site.IsDeleted.ToString(); _initialized = true; } @@ -427,34 +437,30 @@ bool refresh = (site.DefaultThemeType != _themetype || site.DefaultContainerType != _containertype); site.Name = _name; + site.AllowRegistration = (_allowregistration == null ? true : Boolean.Parse(_allowregistration)); + site.IsDeleted = (_isdeleted == null ? true : Boolean.Parse(_isdeleted)); + site.LogoFileId = null; var logofileid = _logofilemanager.GetFileId(); if (logofileid != -1) { site.LogoFileId = logofileid; } - - var faviconFieldId = _faviconfilemanager.GetFileId(); if (faviconFieldId != -1) { site.FaviconFileId = faviconFieldId; } - site.DefaultThemeType = _themetype; site.DefaultContainerType = _containertype; site.AdminContainerType = _admincontainertype; - site.AllowRegistration = (_allowregistration == null ? true : Boolean.Parse(_allowregistration)); - site.IsDeleted = (_isdeleted == null ? true : Boolean.Parse(_isdeleted)); site.PwaIsEnabled = (_pwaisenabled == null ? true : Boolean.Parse(_pwaisenabled)); - var pwaappiconfileid = _pwaappiconfilemanager.GetFileId(); if (pwaappiconfileid != -1) { site.PwaAppIconFileId = pwaappiconfileid; } - var pwasplashiconfileid = _pwasplashiconfilemanager.GetFileId(); if (pwasplashiconfileid != -1) { @@ -463,27 +469,6 @@ site = await SiteService.UpdateSiteAsync(site); - var names = _urls.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - foreach (Alias alias in _aliasList.Where(item => item.SiteId == site.SiteId && item.TenantId == site.TenantId).ToList()) - { - if (!names.Contains(alias.Name)) - { - await AliasService.DeleteAliasAsync(alias.AliasId); - } - } - - foreach (string name in names) - { - if (!_aliasList.Exists(item => item.Name == name)) - { - Alias alias = new Alias(); - alias.Name = name; - alias.TenantId = site.TenantId; - alias.SiteId = site.SiteId; - await AliasService.AddAliasAsync(alias); - } - } - var settings = await SettingService.GetSiteSettingsAsync(site.SiteId); SettingService.SetSetting(settings, "SMTPHost", _smtphost); SettingService.SetSetting(settings, "SMTPPort", _smtpport); @@ -493,7 +478,32 @@ SettingService.SetSetting(settings, "SMTPSender", _smtpsender); await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId); + if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) + { + var names = _urls.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + foreach (Alias alias in _aliasList.Where(item => item.SiteId == site.SiteId && item.TenantId == site.TenantId).ToList()) + { + if (!names.Contains(alias.Name)) + { + await AliasService.DeleteAliasAsync(alias.AliasId); + } + } + + foreach (string name in names) + { + if (!_aliasList.Exists(item => item.Name == name)) + { + Alias alias = new Alias(); + alias.Name = name; + alias.TenantId = site.TenantId; + alias.SiteId = site.SiteId; + await AliasService.AddAliasAsync(alias); + } + } + } + await logger.LogInformation("Site Settings Saved {Site}", site); + if (refresh) { NavigationManager.NavigateTo(NavigateUrl()); // refresh to show new theme or container diff --git a/Oqtane.Client/Modules/Admin/Sites/Index.razor b/Oqtane.Client/Modules/Admin/Sites/Index.razor index f0927b1a..5728905b 100644 --- a/Oqtane.Client/Modules/Admin/Sites/Index.razor +++ b/Oqtane.Client/Modules/Admin/Sites/Index.razor @@ -15,12 +15,14 @@ else
+
- - + + + } @@ -46,4 +48,15 @@ else } } } + + private void Edit(string name) + { + NavigationManager.NavigateTo(_scheme + name + "/admin/site", true); + } + + private void Browse(string name) + { + NavigationManager.NavigateTo(_scheme + name, true); + } + } diff --git a/Oqtane.Client/Modules/HtmlText/Services/HtmlTextService.cs b/Oqtane.Client/Modules/HtmlText/Services/HtmlTextService.cs index a90451cd..344eb8f6 100644 --- a/Oqtane.Client/Modules/HtmlText/Services/HtmlTextService.cs +++ b/Oqtane.Client/Modules/HtmlText/Services/HtmlTextService.cs @@ -1,5 +1,3 @@ -using System.Collections.Generic; -using System.Linq; using System.Net.Http; using System.Threading.Tasks; using Oqtane.Services; @@ -9,34 +7,35 @@ namespace Oqtane.Modules.HtmlText.Services { public class HtmlTextService : ServiceBase, IHtmlTextService, IService { - private readonly SiteState _siteState; + public HtmlTextService(HttpClient http, SiteState siteState) : base(http, siteState) {} - public HtmlTextService(HttpClient http, SiteState siteState) : base(http) - { - _siteState = siteState; - } - - private string ApiUrl => CreateApiUrl("HtmlText", _siteState.Alias); + private string ApiUrl => CreateApiUrl("HtmlText"); public async Task GetHtmlTextAsync(int moduleId) { - var htmltext = await GetJsonAsync>(CreateAuthorizationPolicyUrl($"{ApiUrl}/{moduleId}", new Dictionary() { { EntityNames.Module, moduleId } })); - return htmltext.FirstOrDefault(); + AddAuthorizationPolicyHeader(EntityNames.Module, moduleId); + return await GetJsonAsync($"{ApiUrl}/{moduleId}"); } public async Task AddHtmlTextAsync(Models.HtmlText htmlText) { - await PostJsonAsync(CreateAuthorizationPolicyUrl($"{ApiUrl}", new Dictionary() { { EntityNames.Module, htmlText.ModuleId } }), htmlText); + AddAntiForgeryToken(); + AddAuthorizationPolicyHeader(EntityNames.Module, htmlText.ModuleId); + await PostJsonAsync($"{ApiUrl}", htmlText); } public async Task UpdateHtmlTextAsync(Models.HtmlText htmlText) { - await PutJsonAsync(CreateAuthorizationPolicyUrl($"{ApiUrl}/{htmlText.HtmlTextId}", new Dictionary() { { EntityNames.Module, htmlText.ModuleId } }), htmlText); + AddAntiForgeryToken(); + AddAuthorizationPolicyHeader(EntityNames.Module, htmlText.ModuleId); + await PutJsonAsync($"{ApiUrl}/{htmlText.HtmlTextId}", htmlText); } public async Task DeleteHtmlTextAsync(int moduleId) { - await DeleteAsync(CreateAuthorizationPolicyUrl($"{ApiUrl}/{moduleId}", new Dictionary() { { EntityNames.Module, moduleId } })); + AddAntiForgeryToken(); + AddAuthorizationPolicyHeader(EntityNames.Module, moduleId); + await DeleteAsync($"{ApiUrl}/{moduleId}"); } } } diff --git a/Oqtane.Client/Program.cs b/Oqtane.Client/Program.cs index 3ae04bd8..26f7a286 100644 --- a/Oqtane.Client/Program.cs +++ b/Oqtane.Client/Program.cs @@ -13,7 +13,6 @@ using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using Microsoft.AspNetCore.Localization; using Microsoft.Extensions.DependencyInjection; using Microsoft.JSInterop; -using Oqtane.Interfaces; using Oqtane.Modules; using Oqtane.Providers; using Oqtane.Services; @@ -69,6 +68,8 @@ namespace Oqtane.Client builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); await LoadClientAssemblies(httpClient); diff --git a/Oqtane.Client/Providers/IdentityAuthenticationStateProvider.cs b/Oqtane.Client/Providers/IdentityAuthenticationStateProvider.cs index 8bc0dd98..146df8d4 100644 --- a/Oqtane.Client/Providers/IdentityAuthenticationStateProvider.cs +++ b/Oqtane.Client/Providers/IdentityAuthenticationStateProvider.cs @@ -30,14 +30,11 @@ namespace Oqtane.Providers // get HttpClient lazily from IServiceProvider as you cannot use standard dependency injection due to the AuthenticationStateProvider being initialized prior to NavigationManager(https://github.com/aspnet/AspNetCore/issues/11867 ) var http = _serviceProvider.GetRequiredService(); - // get alias as SiteState has not been initialized ( cannot use AliasService as it is not yet registered ) - var path = new Uri(_navigationManager.Uri).LocalPath.Substring(1); - var alias = await http.GetFromJsonAsync($"/api/Alias/name/?path={WebUtility.UrlEncode(path)}&sync={DateTime.UtcNow.ToString("yyyyMMddHHmmssfff")}"); - // get user - User user = await http.GetFromJsonAsync(Utilities.TenantUrl(alias, "/api/User/authenticate")); + var siteState = _serviceProvider.GetRequiredService(); + User user = await http.GetFromJsonAsync(Utilities.TenantUrl(siteState.Alias, "/api/User/authenticate")); if (user.IsAuthenticated) { - identity = UserSecurity.CreateClaimsIdentity(alias, user); + identity = UserSecurity.CreateClaimsIdentity(siteState.Alias, user); } return new AuthenticationState(new ClaimsPrincipal(identity)); diff --git a/Oqtane.Client/Services/AliasService.cs b/Oqtane.Client/Services/AliasService.cs index 5a72e4bb..891c5a08 100644 --- a/Oqtane.Client/Services/AliasService.cs +++ b/Oqtane.Client/Services/AliasService.cs @@ -3,8 +3,6 @@ using System.Threading.Tasks; using System.Net.Http; using System.Linq; using System.Collections.Generic; -using System.Net; -using System; using Oqtane.Documentation; using Oqtane.Shared; @@ -40,13 +38,6 @@ namespace Oqtane.Services return await GetJsonAsync($"{ApiUrl}/{aliasId}"); } - /// - public async Task GetAliasAsync(string path, DateTime lastSyncDate) - { - // tenant agnostic as SiteState does not exist - return await GetJsonAsync($"{CreateApiUrl("Alias", null)}/name/?path={WebUtility.UrlEncode(path)}&sync={lastSyncDate.ToString("yyyyMMddHHmmssfff")}"); - } - /// public async Task AddAliasAsync(Alias alias) { diff --git a/Oqtane.Client/Services/InstallationService.cs b/Oqtane.Client/Services/InstallationService.cs index 5cc8f6ec..a37db439 100644 --- a/Oqtane.Client/Services/InstallationService.cs +++ b/Oqtane.Client/Services/InstallationService.cs @@ -3,19 +3,28 @@ using System.Threading.Tasks; using System.Net.Http; using Oqtane.Documentation; using Oqtane.Shared; +using Microsoft.AspNetCore.Components; +using System; +using System.Net; namespace Oqtane.Services { [PrivateApi("Don't show in the documentation, as everything should use the Interface")] public class InstallationService : ServiceBase, IInstallationService { - public InstallationService(HttpClient http) : base(http) {} + private readonly NavigationManager _navigationManager; - private string ApiUrl => CreateApiUrl("Installation", null); // tenant agnostic as SiteState does not exist + public InstallationService(HttpClient http, NavigationManager navigationManager) : base(http) + { + _navigationManager = navigationManager; + } + + private string ApiUrl => CreateApiUrl("Installation", null, ControllerRoutes.ApiRoute); // tenant agnostic public async Task IsInstalled() { - return await GetJsonAsync($"{ApiUrl}/installed"); + var path = new Uri(_navigationManager.Uri).LocalPath.Substring(1); + return await GetJsonAsync($"{ApiUrl}/installed/?path={WebUtility.UrlEncode(path)}"); } public async Task Install(InstallConfig config) diff --git a/Oqtane.Client/Services/Interfaces/IAliasService.cs b/Oqtane.Client/Services/Interfaces/IAliasService.cs index 1ab43fa9..6b002ec7 100644 --- a/Oqtane.Client/Services/Interfaces/IAliasService.cs +++ b/Oqtane.Client/Services/Interfaces/IAliasService.cs @@ -23,14 +23,6 @@ namespace Oqtane.Services /// Task GetAliasAsync(int aliasId); - /// - /// Retrieve the Alias object of a URL. - /// - /// The URL - todoc - is this only the root, or can it be a longer path? - /// todoc - unclear what this is for - /// - Task GetAliasAsync(string url, DateTime lastSyncDate); - /// /// Save another in the DB. It must already contain all the information incl. it belongs to. /// diff --git a/Oqtane.Client/Services/Interfaces/ISyncService.cs b/Oqtane.Client/Services/Interfaces/ISyncService.cs new file mode 100644 index 00000000..65b5c226 --- /dev/null +++ b/Oqtane.Client/Services/Interfaces/ISyncService.cs @@ -0,0 +1,19 @@ +using Oqtane.Models; +using System; +using System.Threading.Tasks; + +namespace Oqtane.Services +{ + /// + /// Service to retrieve information. + /// + public interface ISyncService + { + /// + /// Get sync events + /// + /// + /// + Task GetSyncAsync(DateTime lastSyncDate); + } +} diff --git a/Oqtane.Client/Services/ServiceBase.cs b/Oqtane.Client/Services/ServiceBase.cs index 81d12dc8..805ca2b7 100644 --- a/Oqtane.Client/Services/ServiceBase.cs +++ b/Oqtane.Client/Services/ServiceBase.cs @@ -1,10 +1,12 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Json; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNetCore.Components; using Oqtane.Documentation; using Oqtane.Models; using Oqtane.Shared; @@ -15,10 +17,25 @@ namespace Oqtane.Services public class ServiceBase { private readonly HttpClient _http; + private readonly SiteState _siteState; - protected ServiceBase(HttpClient client) + protected ServiceBase(HttpClient client, SiteState siteState) { _http = client; + _siteState = siteState; + } + + // should be used with new constructor + public string CreateApiUrl(string serviceName) + { + if (_siteState != null) + { + return CreateApiUrl(serviceName, _siteState.Alias, ControllerRoutes.ApiRoute); + } + else // legacy support (before 2.1.0) + { + return CreateApiUrl(serviceName, null, ControllerRoutes.Default); + } } public string CreateApiUrl(string serviceName, Alias alias) @@ -61,10 +78,10 @@ namespace Oqtane.Services return CreateAuthorizationPolicyUrl(url, new Dictionary() { { entityName, entityId } }); } - public string CreateAuthorizationPolicyUrl(string url, Dictionary args) + public string CreateAuthorizationPolicyUrl(string url, Dictionary authEntityId) { string qs = ""; - foreach (KeyValuePair kvp in args) + foreach (KeyValuePair kvp in authEntityId) { qs += (qs != "") ? "&" : ""; qs += "auth" + kvp.Key.ToLower() + "id=" + kvp.Value.ToString(); @@ -80,6 +97,33 @@ namespace Oqtane.Services } } + protected void AddRequestHeader(string name, string value) + { + if (_http.DefaultRequestHeaders.Contains(name)) + { + _http.DefaultRequestHeaders.Remove(name); + } + _http.DefaultRequestHeaders.Add(name, value); + } + + protected void AddAntiForgeryToken() + { + AddRequestHeader(Constants.AntiForgeryTokenHeaderName, _siteState.AntiForgeryToken); + } + + public void AddAuthorizationPolicyHeader(string entityName, int entityId) + { + AddAuthorizationPolicyHeader(new Dictionary() { { entityName, entityId } }); + } + + public void AddAuthorizationPolicyHeader(Dictionary authEntityId) + { + foreach (KeyValuePair kvp in authEntityId) + { + AddRequestHeader("auth" + kvp.Key.ToLower() + "id", kvp.Value.ToString()); + } + } + protected async Task GetAsync(string uri) { var response = await _http.GetAsync(uri); @@ -194,10 +238,11 @@ namespace Oqtane.Services return mediaType != null && mediaType.Equals("application/json", StringComparison.OrdinalIgnoreCase); } - [Obsolete("This method is obsolete. Use CreateApiUrl(string serviceName, Alias alias) in conjunction with ControllerRoutes.ApiRoute in Controllers instead.", false)] - public string CreateApiUrl(string serviceName) + //[Obsolete("This constructor is obsolete. Use ServiceBase(HttpClient client, SiteState siteState) : base(http, siteState) {} instead.", false)] + // This constructor is obsolete. Use ServiceBase(HttpClient client, SiteState siteState) : base(http, siteState) {} instead. + protected ServiceBase(HttpClient client) { - return CreateApiUrl(serviceName, null, ControllerRoutes.Default); + _http = client; } [Obsolete("This method is obsolete. Use CreateApiUrl(string serviceName, Alias alias) in conjunction with ControllerRoutes.ApiRoute in Controllers instead.", false)] diff --git a/Oqtane.Client/Services/SyncService.cs b/Oqtane.Client/Services/SyncService.cs new file mode 100644 index 00000000..45d2cfca --- /dev/null +++ b/Oqtane.Client/Services/SyncService.cs @@ -0,0 +1,33 @@ +using Oqtane.Models; +using System.Threading.Tasks; +using System.Net.Http; +using System; +using Oqtane.Documentation; +using Oqtane.Shared; + +namespace Oqtane.Services +{ + /// + [PrivateApi("Don't show in the documentation, as everything should use the Interface")] + public class SyncService : ServiceBase, ISyncService + { + + private readonly SiteState _siteState; + + /// + /// Constructor - should only be used by Dependency Injection + /// + public SyncService(HttpClient http, SiteState siteState) : base(http) + { + _siteState = siteState; + } + + private string ApiUrl => CreateApiUrl("Sync", _siteState.Alias); + + /// + public async Task GetSyncAsync(DateTime lastSyncDate) + { + return await GetJsonAsync($"{ApiUrl}/{lastSyncDate.ToString("yyyyMMddHHmmssfff")}"); + } + } +} diff --git a/Oqtane.Client/Themes/Controls/Container/ModuleActionsBase.cs b/Oqtane.Client/Themes/Controls/Container/ModuleActionsBase.cs index 3551b897..f8b0b065 100644 --- a/Oqtane.Client/Themes/Controls/Container/ModuleActionsBase.cs +++ b/Oqtane.Client/Themes/Controls/Container/ModuleActionsBase.cs @@ -115,7 +115,7 @@ namespace Oqtane.Themes.Controls await PageModuleService.UpdatePageModuleAsync(pagemodule); await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane); await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, oldPane); - return url; + return NavigateUrl(url, "reload"); } private async Task DeleteModule(string url, PageModule pagemodule) @@ -123,7 +123,7 @@ namespace Oqtane.Themes.Controls pagemodule.IsDeleted = true; await PageModuleService.UpdatePageModuleAsync(pagemodule); await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane); - return url; + return NavigateUrl(url, "reload"); } private async Task Settings(string url, PageModule pagemodule) @@ -174,7 +174,7 @@ namespace Oqtane.Themes.Controls pagemodule.Order = 0; await PageModuleService.UpdatePageModuleAsync(pagemodule); await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane); - return s; + return NavigateUrl(s, "reload"); } private async Task MoveBottom(string s, PageModule pagemodule) @@ -182,7 +182,7 @@ namespace Oqtane.Themes.Controls pagemodule.Order = int.MaxValue; await PageModuleService.UpdatePageModuleAsync(pagemodule); await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane); - return s; + return NavigateUrl(s, "reload"); } private async Task MoveUp(string s, PageModule pagemodule) @@ -190,7 +190,7 @@ namespace Oqtane.Themes.Controls pagemodule.Order -= 3; await PageModuleService.UpdatePageModuleAsync(pagemodule); await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane); - return s; + return NavigateUrl(s, "reload"); } private async Task MoveDown(string s, PageModule pagemodule) @@ -198,7 +198,7 @@ namespace Oqtane.Themes.Controls pagemodule.Order += 3; await PageModuleService.UpdatePageModuleAsync(pagemodule); await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane); - return s; + return NavigateUrl(s, "reload"); } public class ActionViewModel diff --git a/Oqtane.Client/Themes/Controls/Theme/LoginBase.cs b/Oqtane.Client/Themes/Controls/Theme/LoginBase.cs index af80d518..972f005c 100644 --- a/Oqtane.Client/Themes/Controls/Theme/LoginBase.cs +++ b/Oqtane.Client/Themes/Controls/Theme/LoginBase.cs @@ -16,6 +16,7 @@ namespace Oqtane.Themes.Controls [Inject] public IUserService UserService { get; set; } [Inject] public IJSRuntime jsRuntime { get; set; } [Inject] public IServiceProvider ServiceProvider { get; set; } + [Inject] public SiteState SiteState { get; set; } protected void LoginUser() { @@ -35,11 +36,10 @@ namespace Oqtane.Themes.Controls if (PageState.Runtime == Oqtane.Shared.Runtime.Server) { - // server-side Blazor - var interop = new Interop(jsRuntime); - string antiforgerytoken = await interop.GetElementByName("__RequestVerificationToken"); - var fields = new { __RequestVerificationToken = antiforgerytoken, returnurl = !authorizedtoviewpage ? PageState.Alias.Path : PageState.Alias.Path + "/" + PageState.Page.Path }; + // server-side Blazor needs to post to the Logout page + var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, returnurl = !authorizedtoviewpage ? PageState.Alias.Path : PageState.Alias.Path + "/" + PageState.Page.Path }; string url = Utilities.TenantUrl(PageState.Alias, "/pages/logout/"); + var interop = new Interop(jsRuntime); await interop.SubmitForm(url, fields); } else diff --git a/Oqtane.Client/UI/SiteRouter.razor b/Oqtane.Client/UI/SiteRouter.razor index 0f5870f2..ed5548f4 100644 --- a/Oqtane.Client/UI/SiteRouter.razor +++ b/Oqtane.Client/UI/SiteRouter.razor @@ -5,8 +5,7 @@ @inject SiteState SiteState @inject NavigationManager NavigationManager @inject INavigationInterception NavigationInterception -@inject IAliasService AliasService -@inject ITenantService TenantService +@inject ISyncService SyncService @inject ISiteService SiteService @inject IPageService PageService @inject IUserService UserService @@ -69,7 +68,6 @@ [SuppressMessage("ReSharper", "StringIndexOfIsCultureSpecific.1")] private async Task Refresh() { - Alias alias = null; Site site; List pages; Page page; @@ -103,27 +101,25 @@ lastsyncdate = PageState.LastSyncDate; } - alias = await AliasService.GetAliasAsync(path, lastsyncdate); - SiteState.Alias = alias; // set state for services - lastsyncdate = alias.SyncDate; - // process any sync events - if (reload != Reload.Site && alias.SyncEvents.Any()) + var sync = await SyncService.GetSyncAsync(lastsyncdate); + lastsyncdate = sync.SyncDate; + if (reload != Reload.Site && sync.SyncEvents.Any()) { // if running on WebAssembly reload the client application if the server application was restarted - if (runtime == Shared.Runtime.WebAssembly && PageState != null && alias.SyncEvents.Exists(item => item.TenantId == -1)) + if (runtime == Shared.Runtime.WebAssembly && PageState != null && sync.SyncEvents.Exists(item => item.TenantId == -1)) { NavigationManager.NavigateTo(_absoluteUri + (!_absoluteUri.Contains("?") ? "?" : "&") + "reload", true); } - if (alias.SyncEvents.Exists(item => item.EntityName == EntityNames.Site && item.EntityId == alias.SiteId)) + if (sync.SyncEvents.Exists(item => item.EntityName == EntityNames.Site && item.EntityId == SiteState.Alias.SiteId)) { reload = Reload.Site; } } - if (reload == Reload.Site || PageState == null || alias.SiteId != PageState.Alias.SiteId) + if (reload == Reload.Site || PageState == null || PageState.Alias.SiteId != SiteState.Alias.SiteId) { - site = await SiteService.GetSiteAsync(alias.SiteId); + site = await SiteService.GetSiteAsync(SiteState.Alias.SiteId); reload = Reload.Site; } else @@ -148,9 +144,9 @@ } // process any sync events for user - if (reload != Reload.Site && user != null && alias.SyncEvents.Any()) + if (reload != Reload.Site && user != null && sync.SyncEvents.Any()) { - if (alias.SyncEvents.Exists(item => item.EntityName == EntityNames.User && item.EntityId == user.UserId)) + if (sync.SyncEvents.Exists(item => item.EntityName == EntityNames.User && item.EntityId == user.UserId)) { reload = Reload.Site; } @@ -173,9 +169,9 @@ path += "/"; } - if (alias.Path != "") + if (SiteState.Alias.Path != "") { - path = path.Substring(alias.Path.Length + 1); + path = path.Substring(SiteState.Alias.Path.Length + 1); } // extract admin route elements from path @@ -281,7 +277,7 @@ _pagestate = new PageState { - Alias = alias, + Alias = SiteState.Alias, Site = site, Pages = pages, Page = page, @@ -305,7 +301,7 @@ if (user == null) { // redirect to login page - NavigationManager.NavigateTo(Utilities.NavigateUrl(alias.Path, "login", "?returnurl=" + path)); + NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "login", "?returnurl=" + path)); } else { @@ -313,7 +309,7 @@ if (path != "") { // redirect to home page - NavigationManager.NavigateTo(Utilities.NavigateUrl(alias.Path, "", "")); + NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "", "")); } } } diff --git a/Oqtane.Server/Controllers/AliasController.cs b/Oqtane.Server/Controllers/AliasController.cs index 9edea4dd..62dcfc85 100644 --- a/Oqtane.Server/Controllers/AliasController.cs +++ b/Oqtane.Server/Controllers/AliasController.cs @@ -3,10 +3,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Authorization; using Oqtane.Models; using Oqtane.Shared; -using System.Linq; -using System; using System.Net; -using System.Globalization; using Oqtane.Enums; using Oqtane.Infrastructure; using Oqtane.Repository; @@ -18,21 +15,17 @@ namespace Oqtane.Controllers public class AliasController : Controller { private readonly IAliasRepository _aliases; - private readonly IHttpContextAccessor _accessor; - private readonly ISyncManager _syncManager; private readonly ILogManager _logger; - public AliasController(IAliasRepository aliases, IHttpContextAccessor accessor, ISyncManager syncManager, ILogManager logger) + public AliasController(IAliasRepository aliases, ILogManager logger) { _aliases = aliases; - _accessor = accessor; - _syncManager = syncManager; _logger = logger; } // GET: api/ [HttpGet] - [Authorize(Roles = RoleNames.Admin)] + [Authorize(Roles = RoleNames.Host)] public IEnumerable Get() { return _aliases.GetAliases(); @@ -40,37 +33,15 @@ namespace Oqtane.Controllers // GET api//5 [HttpGet("{id}")] - [Authorize(Roles = RoleNames.Admin)] + [Authorize(Roles = RoleNames.Host)] public Alias Get(int id) { return _aliases.GetAlias(id); } - - // GET api//name/?path=xxx&sync=yyyyMMddHHmmssfff - [HttpGet("name")] - public Alias Get(string path, string sync) - { - Alias alias = null; - - if (_accessor.HttpContext != null) - { - path = _accessor.HttpContext.Request.Host.Value + "/" + WebUtility.UrlDecode(path); - alias = _aliases.GetAlias(path); - } - - // get sync events - if (alias != null) - { - alias.SyncDate = DateTime.UtcNow; - alias.SyncEvents = _syncManager.GetSyncEvents(alias.TenantId, DateTime.ParseExact(sync, "yyyyMMddHHmmssfff", CultureInfo.InvariantCulture)); - } - - return alias; - } // POST api/ [HttpPost] - [Authorize(Roles = RoleNames.Admin)] + [Authorize(Roles = RoleNames.Host)] public Alias Post([FromBody] Alias alias) { if (ModelState.IsValid) @@ -78,12 +49,18 @@ namespace Oqtane.Controllers alias = _aliases.AddAlias(alias); _logger.Log(LogLevel.Information, this, LogFunction.Create, "Alias Added {Alias}", alias); } + else + { + _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Alias Post Attempt {Alias}", alias); + HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; + alias = null; + } return alias; } // PUT api//5 [HttpPut("{id}")] - [Authorize(Roles = RoleNames.Admin)] + [Authorize(Roles = RoleNames.Host)] public Alias Put(int id, [FromBody] Alias alias) { if (ModelState.IsValid) @@ -91,12 +68,18 @@ namespace Oqtane.Controllers alias = _aliases.UpdateAlias(alias); _logger.Log(LogLevel.Information, this, LogFunction.Update, "Alias Updated {Alias}", alias); } + else + { + _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Alias Put Attempt {Alias}", alias); + HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; + alias = null; + } return alias; } // DELETE api//5 [HttpDelete("{id}")] - [Authorize(Roles = RoleNames.Admin)] + [Authorize(Roles = RoleNames.Host)] public void Delete(int id) { _aliases.DeleteAlias(id); diff --git a/Oqtane.Server/Controllers/InstallationController.cs b/Oqtane.Server/Controllers/InstallationController.cs index 143150df..6c76cb24 100644 --- a/Oqtane.Server/Controllers/InstallationController.cs +++ b/Oqtane.Server/Controllers/InstallationController.cs @@ -13,6 +13,8 @@ using Oqtane.Shared; using Oqtane.Themes; using Microsoft.Extensions.Caching.Memory; using System.Net; +using Oqtane.Repository; +using Microsoft.AspNetCore.Http; namespace Oqtane.Controllers { @@ -24,14 +26,18 @@ namespace Oqtane.Controllers private readonly IDatabaseManager _databaseManager; private readonly ILocalizationManager _localizationManager; private readonly IMemoryCache _cache; + private readonly IHttpContextAccessor _accessor; + private readonly IAliasRepository _aliases; - public InstallationController(IConfigurationRoot config, IInstallationManager installationManager, IDatabaseManager databaseManager, ILocalizationManager localizationManager, IMemoryCache cache) + public InstallationController(IConfigurationRoot config, IInstallationManager installationManager, IDatabaseManager databaseManager, ILocalizationManager localizationManager, IMemoryCache cache, IHttpContextAccessor accessor, IAliasRepository aliases) { _config = config; _installationManager = installationManager; _databaseManager = databaseManager; _localizationManager = localizationManager; _cache = cache; + _accessor = accessor; + _aliases = aliases; } // POST api/ @@ -52,11 +58,17 @@ namespace Oqtane.Controllers return installation; } - // GET api//installed + // GET api//installed/?path=xxx [HttpGet("installed")] - public Installation IsInstalled() + public Installation IsInstalled(string path) { - return _databaseManager.IsInstalled(); + var installation = _databaseManager.IsInstalled(); + if (installation.Success) + { + path = _accessor.HttpContext.Request.Host.Value + "/" + WebUtility.UrlDecode(path); + installation.Alias = _aliases.GetAlias(path); + } + return installation; } [HttpGet("upgrade")] diff --git a/Oqtane.Server/Controllers/ModuleControllerBase.cs b/Oqtane.Server/Controllers/ModuleControllerBase.cs index c56a836e..14ed583a 100644 --- a/Oqtane.Server/Controllers/ModuleControllerBase.cs +++ b/Oqtane.Server/Controllers/ModuleControllerBase.cs @@ -3,7 +3,6 @@ using Microsoft.AspNetCore.Http; using Oqtane.Infrastructure; using System.Collections.Generic; using System; -using Oqtane.Shared; namespace Oqtane.Controllers { @@ -18,20 +17,41 @@ namespace Oqtane.Controllers { _logger = logger; - // populate policy authorization dictionary + // populate policy authorization dictionary from querystring and headers int value; foreach (var param in accessor.HttpContext.Request.Query) + { + if (param.Key.StartsWith("auth") && param.Key.EndsWith("id") && int.TryParse(param.Value, out value)) + { + _authEntityId.Add(param.Key.Substring(4, param.Key.Length - 6), value); + } + } + foreach (var param in accessor.HttpContext.Request.Headers) { if (param.Key.StartsWith("auth") && param.Key.EndsWith("id") && int.TryParse(param.Value, out value)) { _authEntityId.Add(param.Key.Substring(4, param.Key.Length - 6), value); } } + // legacy support if (_authEntityId.Count == 0 && accessor.HttpContext.Request.Query.ContainsKey("entityid")) { _entityId = int.Parse(accessor.HttpContext.Request.Query["entityid"]); } + + } + + protected int AuthEntityId(string entityname) + { + if (_authEntityId.ContainsKey(entityname)) + { + return _authEntityId[entityname]; + } + else + { + return -1; + } } } diff --git a/Oqtane.Server/Controllers/SyncController.cs b/Oqtane.Server/Controllers/SyncController.cs new file mode 100644 index 00000000..6c4a136d --- /dev/null +++ b/Oqtane.Server/Controllers/SyncController.cs @@ -0,0 +1,34 @@ +using Microsoft.AspNetCore.Mvc; +using Oqtane.Models; +using Oqtane.Shared; +using System; +using System.Globalization; +using Oqtane.Infrastructure; + +namespace Oqtane.Controllers +{ + [Route(ControllerRoutes.ApiRoute)] + public class SyncController : Controller + { + private readonly ISyncManager _syncManager; + private readonly Alias _alias; + + public SyncController(ISyncManager syncManager, ITenantManager tenantManager) + { + _syncManager = syncManager; + _alias = tenantManager.GetAlias(); + } + + // GET api//yyyyMMddHHmmssfff + [HttpGet("{lastSyncDate}")] + public Sync Get(string lastSyncDate) + { + Sync sync = new Sync + { + SyncDate = DateTime.UtcNow, + SyncEvents = _syncManager.GetSyncEvents(_alias.TenantId, DateTime.ParseExact(lastSyncDate, "yyyyMMddHHmmssfff", CultureInfo.InvariantCulture)) + }; + return sync; + } + } +} diff --git a/Oqtane.Server/Modules/HtmlText/Controllers/HtmlTextController.cs b/Oqtane.Server/Modules/HtmlText/Controllers/HtmlTextController.cs index 3de69445..4f0f75e2 100644 --- a/Oqtane.Server/Modules/HtmlText/Controllers/HtmlTextController.cs +++ b/Oqtane.Server/Modules/HtmlText/Controllers/HtmlTextController.cs @@ -3,11 +3,10 @@ using Microsoft.AspNetCore.Authorization; using Oqtane.Modules.HtmlText.Repository; using Microsoft.AspNetCore.Http; using Oqtane.Shared; -using System; -using System.Collections.Generic; using Oqtane.Enums; using Oqtane.Infrastructure; using Oqtane.Controllers; +using System.Net; namespace Oqtane.Modules.HtmlText.Controllers { @@ -24,85 +23,75 @@ namespace Oqtane.Modules.HtmlText.Controllers // GET api//5 [HttpGet("{id}")] [Authorize(Policy = PolicyNames.ViewModule)] - public List Get(int id) + public Models.HtmlText Get(int id) { - var list = new List(); - try + if (AuthEntityId(EntityNames.Module) == id) { - Models.HtmlText htmlText = null; - if (_authEntityId[EntityNames.Module] == id) - { - htmlText = _htmlText.GetHtmlText(id); - list.Add(htmlText); - } + return _htmlText.GetHtmlText(id); } - catch (Exception ex) + else { - _logger.Log(LogLevel.Error, this, LogFunction.Read, ex, "Get Error {Error}", ex.Message); - throw; + _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized HtmlText Get Attempt {ModuleId}", id); + HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; + return null; } - return list; } // POST api/ + [ValidateAntiForgeryToken] [HttpPost] [Authorize(Policy = PolicyNames.EditModule)] public Models.HtmlText Post([FromBody] Models.HtmlText htmlText) { - try + if (ModelState.IsValid && AuthEntityId(EntityNames.Module) == htmlText.ModuleId) { - if (ModelState.IsValid && htmlText.ModuleId == _authEntityId[EntityNames.Module]) - { - htmlText = _htmlText.AddHtmlText(htmlText); - _logger.Log(LogLevel.Information, this, LogFunction.Create, "Html/Text Added {HtmlText}", htmlText); - } + htmlText = _htmlText.AddHtmlText(htmlText); + _logger.Log(LogLevel.Information, this, LogFunction.Create, "Html/Text Added {HtmlText}", htmlText); return htmlText; } - catch (Exception ex) + else { - _logger.Log(LogLevel.Error, this, LogFunction.Create, ex, "Post Error {Error}", ex.Message); - throw; + _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized HtmlText Post Attempt {HtmlText}", htmlText); + HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; + return null; } } // PUT api//5 + [ValidateAntiForgeryToken] [HttpPut("{id}")] [Authorize(Policy = PolicyNames.EditModule)] public Models.HtmlText Put(int id, [FromBody] Models.HtmlText htmlText) { - try + if (ModelState.IsValid && AuthEntityId(EntityNames.Module) == htmlText.ModuleId) { - if (ModelState.IsValid && htmlText.ModuleId == _authEntityId[EntityNames.Module]) - { - htmlText = _htmlText.UpdateHtmlText(htmlText); - _logger.Log(LogLevel.Information, this, LogFunction.Update, "Html/Text Updated {HtmlText}", htmlText); - } + htmlText = _htmlText.UpdateHtmlText(htmlText); + _logger.Log(LogLevel.Information, this, LogFunction.Update, "Html/Text Updated {HtmlText}", htmlText); return htmlText; } - catch (Exception ex) + else { - _logger.Log(LogLevel.Error, this, LogFunction.Update, ex, "Put Error {Error}", ex.Message); - throw; + _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized HtmlText Put Attempt {HtmlText}", htmlText); + HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; + return null; } } // DELETE api//5 + [ValidateAntiForgeryToken] [HttpDelete("{id}")] [Authorize(Policy = PolicyNames.EditModule)] public void Delete(int id) { - try + if (AuthEntityId(EntityNames.Module) == id) { - if (id == _authEntityId[EntityNames.Module]) - { - _htmlText.DeleteHtmlText(id); - _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Html/Text Deleted {HtmlTextId}", id); - } + _htmlText.DeleteHtmlText(id); + _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Html/Text Deleted {HtmlTextId}", id); } - catch (Exception ex) + else { - _logger.Log(LogLevel.Error, this, LogFunction.Delete, ex, "Delete Error {Error}", ex.Message); - throw; + _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized HtmlText Delete Attempt {ModuleId}", id); + HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; } } } diff --git a/Oqtane.Server/Pages/_Host.cshtml.cs b/Oqtane.Server/Pages/_Host.cshtml.cs index 235d70cc..7537831f 100644 --- a/Oqtane.Server/Pages/_Host.cshtml.cs +++ b/Oqtane.Server/Pages/_Host.cshtml.cs @@ -60,12 +60,13 @@ namespace Oqtane.Pages ProcessThemeControls(assembly); } - // if culture not specified and framework is installed + // if framework is installed if (!string.IsNullOrEmpty(_configuration.GetConnectionString("DefaultConnection"))) { var alias = _tenantManager.GetAlias(); if (alias != null) { + // if culture not specified if (HttpContext.Request.Cookies[CookieRequestCultureProvider.DefaultCookieName] == null) { // set default language for site if the culture is not supported diff --git a/Oqtane.Server/Security/PermissionHandler.cs b/Oqtane.Server/Security/PermissionHandler.cs index bc082967..e26ddcd4 100644 --- a/Oqtane.Server/Security/PermissionHandler.cs +++ b/Oqtane.Server/Security/PermissionHandler.cs @@ -22,24 +22,44 @@ namespace Oqtane.Security protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement) { - // permission is scoped based on auth{entityname}id (ie ?authmoduleid ) which must be passed as a querystring parameter + // permission is scoped based on entitynames and ids passed as querystring parameters or headers var ctx = _httpContextAccessor.HttpContext; if (ctx != null) { + // get entityid based on a parameter format of auth{entityname}id (ie. authmoduleid ) int entityId = -1; if (ctx.Request.Query.ContainsKey("auth" + requirement.EntityName.ToLower() + "id")) { - entityId = int.Parse(ctx.Request.Query["auth" + requirement.EntityName.ToLower() + "id"]); - } - else - { - // legacy support - if (ctx.Request.Query.ContainsKey("entityid")) + if (!int.TryParse(ctx.Request.Query["auth" + requirement.EntityName.ToLower() + "id"], out entityId)) { - entityId = int.Parse(ctx.Request.Query["entityid"]); + entityId = -1; } } - if (_userPermissions.IsAuthorized(context.User, requirement.EntityName, entityId, requirement.PermissionName)) + if (entityId == -1) + { + if (ctx.Request.Headers.ContainsKey("auth" + requirement.EntityName.ToLower() + "id")) + { + if (!int.TryParse(ctx.Request.Headers["auth" + requirement.EntityName.ToLower() + "id"], out entityId)) + { + entityId = -1; + } + } + } + + // legacy support + if (entityId == -1) + { + if (ctx.Request.Query.ContainsKey("entityid")) + { + if (!int.TryParse(ctx.Request.Query["entityid"], out entityId)) + { + entityId = -1; + } + } + } + + // validate permissions + if (entityId != -1 && _userPermissions.IsAuthorized(context.User, requirement.EntityName, entityId, requirement.PermissionName)) { context.Succeed(requirement); } diff --git a/Oqtane.Server/Security/PrincipalValidator.cs b/Oqtane.Server/Security/PrincipalValidator.cs index 0523bd92..170e20ff 100644 --- a/Oqtane.Server/Security/PrincipalValidator.cs +++ b/Oqtane.Server/Security/PrincipalValidator.cs @@ -29,7 +29,7 @@ namespace Oqtane.Security { // tenant agnostic requests must be ignored string path = context.Request.Path.ToString().ToLower(); - if (path.StartsWith("/_blazor") || path.StartsWith("/api/installation/") || path.StartsWith("/api/alias/name/")) + if (path.StartsWith("/_blazor") || path.StartsWith("/api/installation/")) { return Task.CompletedTask; } diff --git a/Oqtane.Server/Startup.cs b/Oqtane.Server/Startup.cs index 01a8b331..5faa9d06 100644 --- a/Oqtane.Server/Startup.cs +++ b/Oqtane.Server/Startup.cs @@ -78,12 +78,12 @@ namespace Oqtane var navigationManager = s.GetRequiredService(); var client = new HttpClient(new HttpClientHandler { UseCookies = false }); client.BaseAddress = new Uri(navigationManager.Uri); - // set the auth cookie to allow HttpClient API calls to be authenticated + + // set the cookies to allow HttpClient API calls to be authenticated var httpContextAccessor = s.GetRequiredService(); - var authToken = httpContextAccessor.HttpContext.Request.Cookies[".AspNetCore." + Constants.AuthenticationScheme]; - if (authToken != null) + foreach (var cookie in httpContextAccessor.HttpContext.Request.Cookies) { - client.DefaultRequestHeaders.Add("Cookie", ".AspNetCore." + Constants.AuthenticationScheme + "=" + authToken); + client.DefaultRequestHeaders.Add("Cookie", cookie.Key + "=" + cookie.Value); } return client; }); @@ -131,6 +131,7 @@ namespace Oqtane services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddSingleton(); @@ -164,14 +165,30 @@ namespace Oqtane services.ConfigureApplicationCookie(options => { options.Cookie.HttpOnly = false; + options.Cookie.SameSite = SameSiteMode.Strict; + options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest; options.Events.OnRedirectToLogin = context => { context.Response.StatusCode = (int)HttpStatusCode.Forbidden; return Task.CompletedTask; }; + options.Events.OnRedirectToAccessDenied = context => + { + context.Response.StatusCode = (int)HttpStatusCode.Forbidden; + return Task.CompletedTask; + }; options.Events.OnValidatePrincipal = PrincipalValidator.ValidateAsync; }); + services.AddAntiforgery(options => + { + options.HeaderName = Constants.AntiForgeryTokenHeaderName; + options.Cookie.HttpOnly = false; + options.Cookie.Name = Constants.AntiForgeryTokenCookieName; + options.Cookie.SameSite = SameSiteMode.Strict; + options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest; + }); + // register singleton scoped core services services.AddSingleton(Configuration); services.AddSingleton(); diff --git a/Oqtane.Shared/Models/Alias.cs b/Oqtane.Shared/Models/Alias.cs index 94c340c5..0afad7d2 100644 --- a/Oqtane.Shared/Models/Alias.cs +++ b/Oqtane.Shared/Models/Alias.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; namespace Oqtane.Models @@ -43,18 +42,6 @@ namespace Oqtane.Models /// public DateTime ModifiedOn { get; set; } - /// - /// todoc - unclear what this is for - /// - [NotMapped] - public DateTime SyncDate { get; set; } - - /// - /// todoc - unclear what this is for - /// - [NotMapped] - public List SyncEvents { get; set; } - /// /// The path contains the url-part after the first slash. /// * If the Name is `oqtane.me` the Path is empty diff --git a/Oqtane.Shared/Models/Installation.cs b/Oqtane.Shared/Models/Installation.cs index efa0db31..f3f2f580 100644 --- a/Oqtane.Shared/Models/Installation.cs +++ b/Oqtane.Shared/Models/Installation.cs @@ -16,5 +16,10 @@ namespace Oqtane.Models /// Message or error in case something failed. /// public string Message { get; set; } + + /// + /// current alias value from server + /// + public Alias Alias { get; set; } } } diff --git a/Oqtane.Shared/Models/SyncEvent.cs b/Oqtane.Shared/Models/Sync.cs similarity index 57% rename from Oqtane.Shared/Models/SyncEvent.cs rename to Oqtane.Shared/Models/Sync.cs index 1d546d86..9430ab2a 100644 --- a/Oqtane.Shared/Models/SyncEvent.cs +++ b/Oqtane.Shared/Models/Sync.cs @@ -1,7 +1,14 @@ -using System; +using System; +using System.Collections.Generic; namespace Oqtane.Models { + public class Sync + { + public DateTime SyncDate { get; set; } + public List SyncEvents { get; set; } + } + public class SyncEvent { public int TenantId { get; set; } diff --git a/Oqtane.Shared/Shared/Constants.cs b/Oqtane.Shared/Shared/Constants.cs index 7590fcf6..6b655e16 100644 --- a/Oqtane.Shared/Shared/Constants.cs +++ b/Oqtane.Shared/Shared/Constants.cs @@ -76,5 +76,8 @@ namespace Oqtane.Shared { public static readonly string DefaultCulture = "en"; public static readonly string AuthenticationScheme = "Identity.Application"; + public static readonly string RequestVerificationToken = "__RequestVerificationToken"; + public static readonly string AntiForgeryTokenHeaderName = "X-XSRF-TOKEN-HEADER"; + public static readonly string AntiForgeryTokenCookieName = "X-XSRF-TOKEN-COOKIE"; } } diff --git a/Oqtane.Shared/Shared/SiteState.cs b/Oqtane.Shared/Shared/SiteState.cs index 159d29e6..475a9d59 100644 --- a/Oqtane.Shared/Shared/SiteState.cs +++ b/Oqtane.Shared/Shared/SiteState.cs @@ -1,4 +1,4 @@ -using Oqtane.Models; +using Oqtane.Models; namespace Oqtane.Shared { @@ -6,6 +6,7 @@ namespace Oqtane.Shared public class SiteState { public Alias Alias { get; set; } + public string AntiForgeryToken { get; set; } // for use in client services } }
    @Localizer["Name"] @Localizer["Edit"]@context.Name@context.Name