KeyPressed(e))">
@@ -112,11 +118,10 @@
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 on SignalR
+ // 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 };
- string url = "/pages/login/";
- if (!string.IsNullOrEmpty(PageState.Alias.Path)) url = "/" + PageState.Alias.Path + url;
+ string url = Utilities.TenantUrl(PageState.Alias, "/pages/login/");
await interop.SubmitForm(url, fields);
}
else
diff --git a/Oqtane.Client/Modules/Admin/Register/Index.razor b/Oqtane.Client/Modules/Admin/Register/Index.razor
index a9789507..e78e4c7c 100644
--- a/Oqtane.Client/Modules/Admin/Register/Index.razor
+++ b/Oqtane.Client/Modules/Admin/Register/Index.razor
@@ -6,7 +6,7 @@
@if (PageState.Site.AllowRegistration)
{
-
+
...
diff --git a/Oqtane.Client/Modules/Admin/Sites/Add.razor b/Oqtane.Client/Modules/Admin/Sites/Add.razor
index 2be07b2c..f21f7f1e 100644
--- a/Oqtane.Client/Modules/Admin/Sites/Add.razor
+++ b/Oqtane.Client/Modules/Admin/Sites/Add.razor
@@ -301,15 +301,15 @@ else
connectionString = databaseConfigControl.GetConnectionString();
}
var database = _databases.SingleOrDefault(d => d.Name == _databaseName);
-
+
if (connectionString != "")
{
+ config.TenantName = _tenantName;
config.DatabaseType = database.DBType;
config.ConnectionString = connectionString;
- config.HostPassword = _hostpassword;
config.HostEmail = user.Email;
+ config.HostPassword = _hostpassword;
config.HostName = user.DisplayName;
- config.TenantName = _tenantName;
config.IsNewTenant = true;
}
else
@@ -333,6 +333,7 @@ else
if (tenant != null)
{
config.TenantName = tenant.Name;
+ config.DatabaseType = tenant.DBType;
config.ConnectionString = tenant.DBConnectionString;
config.IsNewTenant = false;
}
diff --git a/Oqtane.Client/Providers/IdentityAuthenticationStateProvider.cs b/Oqtane.Client/Providers/IdentityAuthenticationStateProvider.cs
index cbe48a4b..8bc0dd98 100644
--- a/Oqtane.Client/Providers/IdentityAuthenticationStateProvider.cs
+++ b/Oqtane.Client/Providers/IdentityAuthenticationStateProvider.cs
@@ -1,29 +1,27 @@
using System;
+using System.Net;
using System.Net.Http;
using System.Net.Http.Json;
using System.Security.Claims;
-using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.Extensions.DependencyInjection;
using Oqtane.Models;
-using Oqtane.Services;
+using Oqtane.Security;
using Oqtane.Shared;
namespace Oqtane.Providers
{
public class IdentityAuthenticationStateProvider : AuthenticationStateProvider
{
- private readonly NavigationManager _navigationManager;
- private readonly SiteState _siteState;
private readonly IServiceProvider _serviceProvider;
-
- public IdentityAuthenticationStateProvider(NavigationManager navigationManager, SiteState siteState, IServiceProvider serviceProvider)
+ private readonly NavigationManager _navigationManager;
+
+ public IdentityAuthenticationStateProvider(IServiceProvider serviceProvider, NavigationManager navigationManager)
{
- _navigationManager = navigationManager;
- _siteState = siteState;
_serviceProvider = serviceProvider;
+ _navigationManager = navigationManager;
}
public override async Task GetAuthenticationStateAsync()
@@ -32,17 +30,14 @@ 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();
- string apiurl = "/api/User/authenticate";
- User user = await http.GetFromJsonAsync(apiurl);
+ // 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"));
if (user.IsAuthenticated)
{
- identity = new ClaimsIdentity("Identity.Application");
- identity.AddClaim(new Claim(ClaimTypes.Name, user.Username));
- identity.AddClaim(new Claim(ClaimTypes.PrimarySid, user.UserId.ToString()));
- foreach (string role in user.Roles.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
- {
- identity.AddClaim(new Claim(ClaimTypes.Role, role));
- }
+ identity = UserSecurity.CreateClaimsIdentity(alias, user);
}
return new AuthenticationState(new ClaimsPrincipal(identity));
diff --git a/Oqtane.Client/Services/AliasService.cs b/Oqtane.Client/Services/AliasService.cs
index b88e533f..55aee8c9 100644
--- a/Oqtane.Client/Services/AliasService.cs
+++ b/Oqtane.Client/Services/AliasService.cs
@@ -19,36 +19,37 @@ namespace Oqtane.Services
_siteState = siteState;
}
- private string Apiurl => CreateApiUrl("Alias", _siteState.Alias);
+ private string ApiUrl => CreateApiUrl("Alias", _siteState.Alias);
public async Task> GetAliasesAsync()
{
- List aliases = await GetJsonAsync>(Apiurl);
+ List aliases = await GetJsonAsync>(ApiUrl);
return aliases.OrderBy(item => item.Name).ToList();
}
public async Task GetAliasAsync(int aliasId)
{
- return await GetJsonAsync($"{Apiurl}/{aliasId}");
+ return await GetJsonAsync($"{ApiUrl}/{aliasId}");
}
public async Task GetAliasAsync(string path, DateTime lastSyncDate)
{
- return await GetJsonAsync($"{Apiurl}/name/?path={WebUtility.UrlEncode(path)}&sync={lastSyncDate.ToString("yyyyMMddHHmmssfff")}");
+ // 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)
{
- return await PostJsonAsync(Apiurl, alias);
+ return await PostJsonAsync(ApiUrl, alias);
}
public async Task UpdateAliasAsync(Alias alias)
{
- return await PutJsonAsync($"{Apiurl}/{alias.AliasId}", alias);
+ return await PutJsonAsync($"{ApiUrl}/{alias.AliasId}", alias);
}
public async Task DeleteAliasAsync(int aliasId)
{
- await DeleteAsync($"{Apiurl}/{aliasId}");
+ await DeleteAsync($"{ApiUrl}/{aliasId}");
}
}
}
diff --git a/Oqtane.Client/Services/InstallationService.cs b/Oqtane.Client/Services/InstallationService.cs
index b56444d7..0874ed42 100644
--- a/Oqtane.Client/Services/InstallationService.cs
+++ b/Oqtane.Client/Services/InstallationService.cs
@@ -14,7 +14,7 @@ namespace Oqtane.Services
_siteState = siteState;
}
- private string ApiUrl => CreateApiUrl("Installation", _siteState.Alias);
+ private string ApiUrl => CreateApiUrl("Installation", null); // tenant agnostic as SiteState does not exist
public async Task IsInstalled()
{
diff --git a/Oqtane.Client/Services/ServiceBase.cs b/Oqtane.Client/Services/ServiceBase.cs
index 798621f3..f4ca76f5 100644
--- a/Oqtane.Client/Services/ServiceBase.cs
+++ b/Oqtane.Client/Services/ServiceBase.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Json;
@@ -81,7 +81,6 @@ namespace Oqtane.Services
}
catch (Exception e)
{
- //TODO replace with logging
Console.WriteLine(e);
}
@@ -169,8 +168,6 @@ namespace Oqtane.Services
if (response.IsSuccessStatusCode) return true;
if (response.StatusCode != HttpStatusCode.NoContent && response.StatusCode != HttpStatusCode.NotFound)
{
- //TODO: Log errors here
-
Console.WriteLine($"Request: {response.RequestMessage.RequestUri}");
Console.WriteLine($"Response status: {response.StatusCode} {response.ReasonPhrase}");
}
@@ -182,7 +179,6 @@ namespace Oqtane.Services
{
var mediaType = content?.Headers.ContentType?.MediaType;
return mediaType != null && mediaType.Equals("application/json", StringComparison.OrdinalIgnoreCase);
- //TODO Missing content JSON validation
}
[Obsolete("This method is obsolete. Use CreateApiUrl(Alias alias, string serviceName) instead.", false)]
diff --git a/Oqtane.Client/Themes/Controls/Theme/ControlPanel.razor b/Oqtane.Client/Themes/Controls/Theme/ControlPanel.razor
index e53b97a1..a4bf5d16 100644
--- a/Oqtane.Client/Themes/Controls/Theme/ControlPanel.razor
+++ b/Oqtane.Client/Themes/Controls/Theme/ControlPanel.razor
@@ -203,7 +203,7 @@
}
-@if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.Permissions) || (PageState.Page.IsPersonalizable && PageState.User != null))
+@if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.Permissions) || (PageState.Page.IsPersonalizable && PageState.User != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Registered)))
{
if (PageState.EditMode)
{
diff --git a/Oqtane.Client/Themes/Controls/Theme/Login.razor b/Oqtane.Client/Themes/Controls/Theme/Login.razor
index f4c415c9..f572cf30 100644
--- a/Oqtane.Client/Themes/Controls/Theme/Login.razor
+++ b/Oqtane.Client/Themes/Controls/Theme/Login.razor
@@ -3,7 +3,7 @@
@inject IStringLocalizer Localizer
-
+
...
diff --git a/Oqtane.Client/Themes/Controls/Theme/LoginBase.cs b/Oqtane.Client/Themes/Controls/Theme/LoginBase.cs
index dfa86f01..af80d518 100644
--- a/Oqtane.Client/Themes/Controls/Theme/LoginBase.cs
+++ b/Oqtane.Client/Themes/Controls/Theme/LoginBase.cs
@@ -39,8 +39,7 @@ namespace Oqtane.Themes.Controls
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 };
- string url = "/pages/logout/";
- if (!string.IsNullOrEmpty(PageState.Alias.Path)) url = "/" + PageState.Alias.Path + url;
+ string url = Utilities.TenantUrl(PageState.Alias, "/pages/logout/");
await interop.SubmitForm(url, fields);
}
else
diff --git a/Oqtane.Client/Themes/Controls/Theme/UserProfile.razor b/Oqtane.Client/Themes/Controls/Theme/UserProfile.razor
index e939d633..e70e3562 100644
--- a/Oqtane.Client/Themes/Controls/Theme/UserProfile.razor
+++ b/Oqtane.Client/Themes/Controls/Theme/UserProfile.razor
@@ -5,7 +5,7 @@
@inject NavigationManager NavigationManager
-
+
...
diff --git a/Oqtane.Client/UI/SiteRouter.razor b/Oqtane.Client/UI/SiteRouter.razor
index 66aa79d6..18a85452 100644
--- a/Oqtane.Client/UI/SiteRouter.razor
+++ b/Oqtane.Client/UI/SiteRouter.razor
@@ -12,6 +12,7 @@
@inject IUserService UserService
@inject IModuleService ModuleService
@inject ILogService LogService
+@inject IJSRuntime JSRuntime
@implements IHandleAfterRender
@DynamicComponent
@@ -107,7 +108,7 @@
SiteState.Alias = alias; // set state for services
lastsyncdate = alias.SyncDate;
- // process any sync events
+ // process any sync events
if (reload != Reload.Site && alias.SyncEvents.Any())
{
// if running on WebAssembly reload the client application if the server application was restarted
@@ -330,15 +331,13 @@
await Refresh();
}
- Task IHandleAfterRender.OnAfterRenderAsync()
+ async Task IHandleAfterRender.OnAfterRenderAsync()
{
if (!_navigationInterceptionEnabled)
{
_navigationInterceptionEnabled = true;
- return NavigationInterception.EnableNavigationInterceptionAsync();
+ await NavigationInterception.EnableNavigationInterceptionAsync();
}
-
- return Task.CompletedTask;
}
private Dictionary ParseQueryString(string query)
@@ -556,4 +555,5 @@
=> RuntimeInformation.IsOSPlatform(OSPlatform.Create("BROWSER"))
? Oqtane.Shared.Runtime.WebAssembly
: Oqtane.Shared.Runtime.Server;
+
}
diff --git a/Oqtane.Server/Controllers/UserController.cs b/Oqtane.Server/Controllers/UserController.cs
index bd36b6df..19a79949 100644
--- a/Oqtane.Server/Controllers/UserController.cs
+++ b/Oqtane.Server/Controllers/UserController.cs
@@ -358,7 +358,7 @@ namespace Oqtane.Controllers
[Authorize]
public async Task Logout([FromBody] User user)
{
- await HttpContext.SignOutAsync(IdentityConstants.ApplicationScheme);
+ await HttpContext.SignOutAsync(Constants.AuthenticationScheme);
_logger.Log(LogLevel.Information, this, LogFunction.Security, "User Logout {Username}", (user != null) ? user.Username : "");
}
diff --git a/Oqtane.Server/Pages/Login.cshtml.cs b/Oqtane.Server/Pages/Login.cshtml.cs
index 54958a67..b1a8a66f 100644
--- a/Oqtane.Server/Pages/Login.cshtml.cs
+++ b/Oqtane.Server/Pages/Login.cshtml.cs
@@ -1,4 +1,4 @@
-using System.Threading.Tasks;
+using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
@@ -21,20 +21,23 @@ namespace Oqtane.Pages
public async Task OnPostAsync(string username, string password, bool remember, string returnurl)
{
- bool validuser = false;
- IdentityUser identityuser = await _identityUserManager.FindByNameAsync(username);
- if (identityuser != null)
+ if (!string.IsNullOrEmpty(username) && !string.IsNullOrEmpty(password))
{
- var result = await _identitySignInManager.CheckPasswordSignInAsync(identityuser, password, false);
- if (result.Succeeded)
+ bool validuser = false;
+ IdentityUser identityuser = await _identityUserManager.FindByNameAsync(username);
+ if (identityuser != null)
{
- validuser = true;
+ var result = await _identitySignInManager.CheckPasswordSignInAsync(identityuser, password, false);
+ if (result.Succeeded)
+ {
+ validuser = true;
+ }
}
- }
- if (validuser)
- {
- await _identitySignInManager.SignInAsync(identityuser, remember);
+ if (validuser)
+ {
+ await _identitySignInManager.SignInAsync(identityuser, remember);
+ }
}
if (returnurl == null)
@@ -49,4 +52,4 @@ namespace Oqtane.Pages
return LocalRedirect(Url.Content("~" + returnurl));
}
}
-}
\ No newline at end of file
+}
diff --git a/Oqtane.Server/Pages/Logout.cshtml.cs b/Oqtane.Server/Pages/Logout.cshtml.cs
index 35440769..fc9691b8 100644
--- a/Oqtane.Server/Pages/Logout.cshtml.cs
+++ b/Oqtane.Server/Pages/Logout.cshtml.cs
@@ -1,9 +1,9 @@
-using System.Threading.Tasks;
+using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
-using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
+using Oqtane.Shared;
namespace Oqtane.Pages
{
@@ -12,7 +12,10 @@ namespace Oqtane.Pages
{
public async Task OnPostAsync(string returnurl)
{
- await HttpContext.SignOutAsync(IdentityConstants.ApplicationScheme);
+ if (HttpContext.User.Identity.IsAuthenticated)
+ {
+ await HttpContext.SignOutAsync(Constants.AuthenticationScheme);
+ }
if (returnurl == null)
{
@@ -26,4 +29,4 @@ namespace Oqtane.Pages
return LocalRedirect(Url.Content("~" + returnurl));
}
}
-}
\ No newline at end of file
+}
diff --git a/Oqtane.Server/Pages/_Host.cshtml.cs b/Oqtane.Server/Pages/_Host.cshtml.cs
index 5bec6d08..533cda83 100644
--- a/Oqtane.Server/Pages/_Host.cshtml.cs
+++ b/Oqtane.Server/Pages/_Host.cshtml.cs
@@ -48,21 +48,24 @@ namespace Oqtane.Pages
}
// if culture not specified and framework is installed
- if (HttpContext.Request.Cookies[CookieRequestCultureProvider.DefaultCookieName] == null && !string.IsNullOrEmpty(_configuration.GetConnectionString("DefaultConnection")))
+ if (!string.IsNullOrEmpty(_configuration.GetConnectionString("DefaultConnection")))
{
var alias = _tenantManager.GetAlias();
if (alias != null)
{
- // set default language for site if the culture is not supported
- var languages = _languages.GetLanguages(alias.SiteId);
- if (languages.Any() && languages.All(l => l.Code != CultureInfo.CurrentUICulture.Name))
+ if (HttpContext.Request.Cookies[CookieRequestCultureProvider.DefaultCookieName] == null)
{
- var defaultLanguage = languages.Where(l => l.IsDefault).SingleOrDefault() ?? languages.First();
- SetLocalizationCookie(defaultLanguage.Code);
- }
- else
- {
- SetLocalizationCookie(_localizationManager.GetDefaultCulture());
+ // set default language for site if the culture is not supported
+ var languages = _languages.GetLanguages(alias.SiteId);
+ if (languages.Any() && languages.All(l => l.Code != CultureInfo.CurrentUICulture.Name))
+ {
+ var defaultLanguage = languages.Where(l => l.IsDefault).SingleOrDefault() ?? languages.First();
+ SetLocalizationCookie(defaultLanguage.Code);
+ }
+ else
+ {
+ SetLocalizationCookie(_localizationManager.GetDefaultCulture());
+ }
}
}
}
diff --git a/Oqtane.Server/Security/ClaimsPrincipalFactory.cs b/Oqtane.Server/Security/ClaimsPrincipalFactory.cs
index 6ac81731..00e072fa 100644
--- a/Oqtane.Server/Security/ClaimsPrincipalFactory.cs
+++ b/Oqtane.Server/Security/ClaimsPrincipalFactory.cs
@@ -1,25 +1,23 @@
-using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
using System.Security.Claims;
using System.Threading.Tasks;
using Oqtane.Models;
-using Oqtane.Shared;
using System.Collections.Generic;
using System.Linq;
using Oqtane.Repository;
+using Oqtane.Infrastructure;
namespace Oqtane.Security
{
public class ClaimsPrincipalFactory : UserClaimsPrincipalFactory where TUser : IdentityUser
{
- private readonly IdentityOptions _options;
- private readonly ITenantResolver _tenants;
+ private readonly ITenantManager _tenants;
private readonly IUserRepository _users;
private readonly IUserRoleRepository _userRoles;
- public ClaimsPrincipalFactory(UserManager userManager, IOptions optionsAccessor, ITenantResolver tenants, IUserRepository users, IUserRoleRepository userroles) : base(userManager, optionsAccessor)
+ public ClaimsPrincipalFactory(UserManager userManager, IOptions optionsAccessor, ITenantManager tenants, IUserRepository users, IUserRoleRepository userroles) : base(userManager, optionsAccessor)
{
- _options = optionsAccessor.Value;
_tenants = tenants;
_users = users;
_userRoles = userroles;
@@ -27,33 +25,20 @@ namespace Oqtane.Security
protected override async Task GenerateClaimsAsync(TUser identityuser)
{
- var id = await base.GenerateClaimsAsync(identityuser);
+ var identity = await base.GenerateClaimsAsync(identityuser);
User user = _users.GetUser(identityuser.UserName);
if (user != null)
{
- id.AddClaim(new Claim(ClaimTypes.PrimarySid, user.UserId.ToString()));
Alias alias = _tenants.GetAlias();
- List userroles = _userRoles.GetUserRoles(user.UserId, alias.SiteId).ToList();
- foreach (UserRole userrole in userroles)
+ if (alias != null)
{
- id.AddClaim(new Claim(_options.ClaimsIdentity.RoleClaimType, userrole.Role.Name));
- // host users are members of every site
- if (userrole.Role.Name == RoleNames.Host)
- {
- if (userroles.Where(item => item.Role.Name == RoleNames.Registered).FirstOrDefault() == null)
- {
- id.AddClaim(new Claim(_options.ClaimsIdentity.RoleClaimType, RoleNames.Registered));
- }
- if (userroles.Where(item => item.Role.Name == RoleNames.Admin).FirstOrDefault() == null)
- {
- id.AddClaim(new Claim(_options.ClaimsIdentity.RoleClaimType, RoleNames.Admin));
- }
- }
+ List userroles = _userRoles.GetUserRoles(user.UserId, alias.SiteId).ToList();
+ identity = UserSecurity.CreateClaimsIdentity(alias, user, userroles);
}
}
- return id;
+ return identity;
}
}
diff --git a/Oqtane.Server/Security/PrincipalValidator.cs b/Oqtane.Server/Security/PrincipalValidator.cs
new file mode 100644
index 00000000..1762dab8
--- /dev/null
+++ b/Oqtane.Server/Security/PrincipalValidator.cs
@@ -0,0 +1,64 @@
+using System.Linq;
+using System.Security.Claims;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authentication.Cookies;
+using Oqtane.Infrastructure;
+using Oqtane.Repository;
+using Oqtane.Models;
+using System.Collections.Generic;
+using Microsoft.Extensions.Configuration;
+
+namespace Oqtane.Security
+{
+ public static class PrincipalValidator
+ {
+ public static Task ValidateAsync(CookieValidatePrincipalContext context)
+ {
+ if (context != null && context.Principal.Identity.IsAuthenticated)
+ {
+ // check if framework is installed
+ var config = context.HttpContext.RequestServices.GetService(typeof(IConfiguration)) as IConfiguration;
+ if (!string.IsNullOrEmpty(config.GetConnectionString("DefaultConnection")))
+ {
+ var tenantManager = context.HttpContext.RequestServices.GetService(typeof(ITenantManager)) as ITenantManager;
+ var alias = tenantManager.GetAlias();
+ if (alias != null)
+ {
+ // verify principal was authenticated for current tenant
+ if (context.Principal.Claims.First(item => item.Type == ClaimTypes.GroupSid)?.Value != alias.AliasId.ToString())
+ {
+ // 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/"))
+ {
+ return Task.CompletedTask;
+ }
+
+ // refresh principal
+ var userRepository = context.HttpContext.RequestServices.GetService(typeof(IUserRepository)) as IUserRepository;
+ var userRoleRepository = context.HttpContext.RequestServices.GetService(typeof(IUserRoleRepository)) as IUserRoleRepository;
+
+ User user = userRepository.GetUser(context.Principal.Identity.Name);
+ if (user != null)
+ {
+ List userroles = userRoleRepository.GetUserRoles(user.UserId, alias.SiteId).ToList();
+ var identity = UserSecurity.CreateClaimsIdentity(alias, user, userroles);
+ context.ReplacePrincipal(new ClaimsPrincipal(identity));
+ context.ShouldRenew = true;
+ }
+ else
+ {
+ context.RejectPrincipal();
+ }
+ }
+ }
+ else
+ {
+ context.RejectPrincipal();
+ }
+ }
+ }
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/Oqtane.Server/Security/UserPermissions.cs b/Oqtane.Server/Security/UserPermissions.cs
index 1e89c4cd..f58c4d7e 100644
--- a/Oqtane.Server/Security/UserPermissions.cs
+++ b/Oqtane.Server/Security/UserPermissions.cs
@@ -1,4 +1,4 @@
-using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http;
using Oqtane.Models;
using System.Linq;
using System.Security.Claims;
@@ -17,40 +17,41 @@ namespace Oqtane.Security
_accessor = accessor;
}
- public bool IsAuthorized(ClaimsPrincipal user, string entityName, int entityId, string permissionName)
+ public bool IsAuthorized(ClaimsPrincipal principal, string entityName, int entityId, string permissionName)
{
- return IsAuthorized(user, permissionName, _permissions.GetPermissionString(entityName, entityId, permissionName));
+ return IsAuthorized(principal, permissionName, _permissions.GetPermissionString(entityName, entityId, permissionName));
}
- public bool IsAuthorized(ClaimsPrincipal user, string permissionName, string permissions)
+ public bool IsAuthorized(ClaimsPrincipal principal, string permissionName, string permissions)
{
- return UserSecurity.IsAuthorized(GetUser(user), permissionName, permissions);
+ return UserSecurity.IsAuthorized(GetUser(principal), permissionName, permissions);
}
- public User GetUser(ClaimsPrincipal user)
+ public User GetUser(ClaimsPrincipal principal)
{
- User resultUser = new User();
- resultUser.Username = "";
- resultUser.IsAuthenticated = false;
- resultUser.UserId = -1;
- resultUser.Roles = "";
+ User user = new User();
+ user.IsAuthenticated = false;
+ user.Username = "";
+ user.UserId = -1;
+ user.Roles = "";
- if (user == null) return resultUser;
+ if (principal == null) return user;
- resultUser.Username = user.Identity.Name;
- resultUser.IsAuthenticated = user.Identity.IsAuthenticated;
- var idclaim = user.Claims.FirstOrDefault(item => item.Type == ClaimTypes.PrimarySid);
- if (idclaim != null)
+ user.IsAuthenticated = principal.Identity.IsAuthenticated;
+ if (user.IsAuthenticated)
{
- resultUser.UserId = int.Parse(idclaim.Value);
- foreach (var claim in user.Claims.Where(item => item.Type == ClaimTypes.Role))
+ user.Username = principal.Identity.Name;
+ if (principal.Claims.Any(item => item.Type == ClaimTypes.PrimarySid))
{
- resultUser.Roles += claim.Value + ";";
+ user.UserId = int.Parse(principal.Claims.First(item => item.Type == ClaimTypes.PrimarySid).Value);
}
-
- if (resultUser.Roles != "") resultUser.Roles = ";" + resultUser.Roles;
+ foreach (var claim in principal.Claims.Where(item => item.Type == ClaimTypes.Role))
+ {
+ user.Roles += claim.Value + ";";
+ }
+ if (user.Roles != "") user.Roles = ";" + user.Roles;
}
- return resultUser;
+ return user;
}
public User GetUser()
diff --git a/Oqtane.Server/Startup.cs b/Oqtane.Server/Startup.cs
index 8f21243f..8ddb3c10 100644
--- a/Oqtane.Server/Startup.cs
+++ b/Oqtane.Server/Startup.cs
@@ -71,14 +71,15 @@ namespace Oqtane
{
// creating the URI helper needs to wait until the JS Runtime is initialized, so defer it.
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
var httpContextAccessor = s.GetRequiredService();
- var authToken = httpContextAccessor.HttpContext.Request.Cookies[".AspNetCore.Identity.Application"];
- var client = new HttpClient(new HttpClientHandler {UseCookies = false});
+ var authToken = httpContextAccessor.HttpContext.Request.Cookies[".AspNetCore." + Constants.AuthenticationScheme];
if (authToken != null)
{
- client.DefaultRequestHeaders.Add("Cookie", ".AspNetCore.Identity.Application=" + authToken);
+ client.DefaultRequestHeaders.Add("Cookie", ".AspNetCore." + Constants.AuthenticationScheme + "=" + authToken);
}
- client.BaseAddress = new Uri(navigationManager.Uri);
return client;
});
}
@@ -131,7 +132,8 @@ namespace Oqtane
services.AddIdentityCore(options => { })
.AddEntityFrameworkStores()
.AddSignInManager()
- .AddDefaultTokenProviders();
+ .AddDefaultTokenProviders()
+ .AddClaimsPrincipalFactory>(); // role claims
services.Configure(options =>
{
@@ -151,8 +153,8 @@ namespace Oqtane
options.User.RequireUniqueEmail = false;
});
- services.AddAuthentication(IdentityConstants.ApplicationScheme)
- .AddCookie(IdentityConstants.ApplicationScheme);
+ services.AddAuthentication(Constants.AuthenticationScheme)
+ .AddCookie(Constants.AuthenticationScheme);
services.ConfigureApplicationCookie(options =>
{
@@ -162,11 +164,9 @@ namespace Oqtane
context.Response.StatusCode = 401;
return Task.CompletedTask;
};
+ options.Events.OnValidatePrincipal = PrincipalValidator.ValidateAsync;
});
- // register custom claims principal factory for role claims
- services.AddTransient, ClaimsPrincipalFactory>();
-
// register singleton scoped core services
services.AddSingleton(Configuration);
services.AddSingleton();
@@ -249,11 +249,12 @@ namespace Oqtane
app.UseHttpsRedirection();
app.UseStaticFiles();
- app.UseTenantResolution(); // must be declared directly after static files
+ app.UseTenantResolution();
app.UseBlazorFrameworkFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
+
if (_useSwagger)
{
app.UseSwagger();
diff --git a/Oqtane.Shared/Security/UserSecurity.cs b/Oqtane.Shared/Security/UserSecurity.cs
index ec2d0db0..c052e903 100644
--- a/Oqtane.Shared/Security/UserSecurity.cs
+++ b/Oqtane.Shared/Security/UserSecurity.cs
@@ -1,6 +1,7 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
+using System.Security.Claims;
using System.Text.Json;
using Oqtane.Models;
using Oqtane.Shared;
@@ -114,5 +115,42 @@ namespace Oqtane.Security
}
return false;
}
+
+ public static ClaimsIdentity CreateClaimsIdentity(Alias alias, User user, List userroles)
+ {
+ user.Roles = "";
+ foreach (UserRole userrole in userroles)
+ {
+ user.Roles += userrole.Role.Name + ";";
+ }
+ if (user.Roles != "") user.Roles = ";" + user.Roles;
+ return CreateClaimsIdentity(alias, user);
+ }
+
+ public static ClaimsIdentity CreateClaimsIdentity(Alias alias, User user)
+ {
+ ClaimsIdentity identity = new ClaimsIdentity(Constants.AuthenticationScheme);
+ if (alias != null && user != null && !user.IsDeleted)
+ {
+ identity.AddClaim(new Claim(ClaimTypes.Name, user.Username));
+ identity.AddClaim(new Claim(ClaimTypes.PrimarySid, user.UserId.ToString()));
+ identity.AddClaim(new Claim(ClaimTypes.GroupSid, alias.AliasId.ToString()));
+ if (user.Roles.Contains(RoleNames.Host))
+ {
+ // host users are site admins by default
+ identity.AddClaim(new Claim(ClaimTypes.Role, RoleNames.Host));
+ identity.AddClaim(new Claim(ClaimTypes.Role, RoleNames.Admin));
+ identity.AddClaim(new Claim(ClaimTypes.Role, RoleNames.Registered));
+ }
+ foreach (string role in user.Roles.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
+ {
+ if (!identity.Claims.Any(item => item.Type == ClaimTypes.Role && item.Value == role))
+ {
+ identity.AddClaim(new Claim(ClaimTypes.Role, role));
+ }
+ }
+ }
+ return identity;
+ }
}
}
diff --git a/Oqtane.Shared/Shared/Constants.cs b/Oqtane.Shared/Shared/Constants.cs
index 8d8f64ec..94aa3066 100644
--- a/Oqtane.Shared/Shared/Constants.cs
+++ b/Oqtane.Shared/Shared/Constants.cs
@@ -1,5 +1,4 @@
using System;
-using System.Globalization;
namespace Oqtane.Shared {
@@ -72,5 +71,7 @@ namespace Oqtane.Shared {
public static readonly string SatelliteAssemblyExtension = ".resources.dll";
public static readonly string DefaultCulture = "en";
+
+ public static readonly string AuthenticationScheme = "Identity.Application";
}
}
diff --git a/Oqtane.Shared/Shared/Utilities.cs b/Oqtane.Shared/Shared/Utilities.cs
index ec7e2d4a..44b293f7 100644
--- a/Oqtane.Shared/Shared/Utilities.cs
+++ b/Oqtane.Shared/Shared/Utilities.cs
@@ -108,6 +108,11 @@ namespace Oqtane.Shared
return $"{aliasUrl}{Constants.ContentUrl}{fileId}{method}";
}
+ public static string TenantUrl(Alias alias, string url)
+ {
+ url = (!url.StartsWith("/")) ? "/" + url : url;
+ return (alias != null && !string.IsNullOrEmpty(alias.Path)) ? "/" + alias.Path + url : url;
+ }
public static string GetTypeName(string fullyqualifiedtypename)
{
if (fullyqualifiedtypename.Contains(","))