From f037898c6e7776497eecd03da3836869ddd14610 Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Sun, 25 Aug 2019 14:52:25 -0400 Subject: [PATCH] Multi-tenant role authorization --- Oqtane.Client/Modules/Admin/Login/Index.razor | 46 +++--- Oqtane.Client/Modules/Admin/Pages/Add.razor | 15 +- .../Modules/Admin/Pages/Delete.razor | 18 ++- Oqtane.Client/Modules/Admin/Pages/Edit.razor | 18 ++- .../Modules/Controls/AuditInfo.razor | 1 - .../IdentityAuthenticationStateProvider.cs | 22 ++- .../Services/Interfaces/IUserService.cs | 2 +- Oqtane.Client/Services/ServiceBase.cs | 2 +- Oqtane.Client/Services/UserService.cs | 4 +- Oqtane.Client/Shared/Utilities.cs | 10 +- Oqtane.Client/Themes/Controls/Login.razor | 2 +- Oqtane.Server/Controllers/AliasController.cs | 4 + Oqtane.Server/Controllers/ModuleController.cs | 4 + Oqtane.Server/Controllers/PageController.cs | 4 + .../Controllers/PageModuleController.cs | 4 + Oqtane.Server/Controllers/RoleController.cs | 4 + .../Controllers/SettingController.cs | 4 + Oqtane.Server/Controllers/SiteController.cs | 4 + Oqtane.Server/Controllers/TenantController.cs | 4 + Oqtane.Server/Controllers/UserController.cs | 92 +++++++----- .../Controllers/UserRoleController.cs | 4 + .../Controllers/HtmlTextController.cs | 4 + Oqtane.Server/Pages/Login.cshtml.cs | 25 +--- Oqtane.Server/Pages/Logout.cshtml.cs | 7 +- .../Repository/Context/DBContextBase.cs | 2 +- .../Repository/Interfaces/ITenantResolver.cs | 1 + .../Interfaces/IUserRoleRepository.cs | 1 + Oqtane.Server/Repository/TenantResolver.cs | 17 ++- .../Repository/UserRoleRepository.cs | 15 ++ Oqtane.Server/Scripts/00.00.00.sql | 4 +- Oqtane.Server/Scripts/00.00.01.sql | 140 ++---------------- .../Security/ClaimsPrincipalFactory.cs | 50 +++++++ Oqtane.Server/Startup.cs | 27 +++- Oqtane.Shared/Models/User.cs | 3 - 34 files changed, 312 insertions(+), 252 deletions(-) create mode 100644 Oqtane.Server/Security/ClaimsPrincipalFactory.cs diff --git a/Oqtane.Client/Modules/Admin/Login/Index.razor b/Oqtane.Client/Modules/Admin/Login/Index.razor index 1e0dde3e..5f852efa 100644 --- a/Oqtane.Client/Modules/Admin/Login/Index.razor +++ b/Oqtane.Client/Modules/Admin/Login/Index.razor @@ -51,20 +51,20 @@ private async Task Login() { - User user = new User(); - user.SiteId = PageState.Site.SiteId; - user.Username = Username; - user.Password = Password; - user.IsPersistent = Remember; - user = await UserService.LoginUserAsync(user); - if (user.IsAuthenticated) - { - string ReturnUrl = PageState.QueryString["returnurl"]; + string ReturnUrl = PageState.QueryString["returnurl"]; - var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider.GetService(typeof(IdentityAuthenticationStateProvider)); - if (authstateprovider == null) + var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider.GetService(typeof(IdentityAuthenticationStateProvider)); + if (authstateprovider == null) + { + // server-side Blazor + User user = new User(); + user.SiteId = PageState.Site.SiteId; + user.Username = Username; + user.Password = Password; + user = await UserService.LoginUserAsync(user, false, false); + if (user.IsAuthenticated) { - // server-side Blazor + // complete the login on the server so that the cookies are set correctly on SignalR var interop = new Interop(jsRuntime); string antiforgerytoken = await interop.GetElementByName("__RequestVerificationToken"); var fields = new { __RequestVerificationToken = antiforgerytoken, username = Username, password = Password, remember = Remember, returnurl = ReturnUrl }; @@ -72,15 +72,27 @@ } else { - // client-side Blazor - authstateprovider.NotifyAuthenticationChanged(); - PageState.Reload = Constants.ReloadPage; - UriHelper.NavigateTo(NavigateUrl(ReturnUrl)); + Message = "
Login Failed. Please Remember That Passwords Are Case Sensitive.
"; } } else { - Message = "
Login Failed. Please Remember That Passwords Are Case Sensitive.
"; + // client-side Blazor + User user = new User(); + user.SiteId = PageState.Site.SiteId; + user.Username = Username; + user.Password = Password; + user = await UserService.LoginUserAsync(user, true, Remember); + if (user.IsAuthenticated) + { + authstateprovider.NotifyAuthenticationChanged(); + PageState.Reload = Constants.ReloadSite; + UriHelper.NavigateTo(NavigateUrl(ReturnUrl)); + } + else + { + Message = "
Login Failed. Please Remember That Passwords Are Case Sensitive.
"; + } } } diff --git a/Oqtane.Client/Modules/Admin/Pages/Add.razor b/Oqtane.Client/Modules/Admin/Pages/Add.razor index 8c07b2bc..ab6fbca1 100644 --- a/Oqtane.Client/Modules/Admin/Pages/Add.razor +++ b/Oqtane.Client/Modules/Admin/Pages/Add.razor @@ -8,7 +8,7 @@ @inject IPageService PageService @inject IThemeService ThemeService -@message +@((MarkupString)message) @@ -144,7 +144,7 @@ } catch (Exception ex) { - message = ex.Message; + message = "
" + ex.Message + "


"; } } @@ -185,11 +185,18 @@ await PageService.AddPageAsync(page); PageState.Reload = Constants.ReloadSite; - UriHelper.NavigateTo(NavigateUrl(path)); + if (PageState.Page.Name == "Page Management") + { + UriHelper.NavigateTo(NavigateUrl()); + } + else + { + UriHelper.NavigateTo(NavigateUrl(path)); + } } catch (Exception ex) { - message = ex.Message; + message = "
" + ex.Message + "


"; } } } diff --git a/Oqtane.Client/Modules/Admin/Pages/Delete.razor b/Oqtane.Client/Modules/Admin/Pages/Delete.razor index 85703eb7..97d56b58 100644 --- a/Oqtane.Client/Modules/Admin/Pages/Delete.razor +++ b/Oqtane.Client/Modules/Admin/Pages/Delete.razor @@ -9,7 +9,7 @@ @inject IPageService PageService @inject IThemeService ThemeService -@message +@((MarkupString)message)
@@ -116,7 +116,8 @@
Cancel -

+
+
@code { @@ -172,7 +173,7 @@ } catch (Exception ex) { - message = ex.Message; + message = "
" + ex.Message + "


"; } } @@ -182,11 +183,18 @@ { await PageService.DeletePageAsync(Int32.Parse(PageState.QueryString["id"])); PageState.Reload = Constants.ReloadSite; - UriHelper.NavigateTo(NavigateUrl()); + if (PageState.Page.Name == "Page Management") + { + UriHelper.NavigateTo(NavigateUrl()); + } + else + { + UriHelper.NavigateTo(NavigateUrl("")); + } } catch (Exception ex) { - message = ex.Message; + message = "
" + ex.Message + "


"; } } } diff --git a/Oqtane.Client/Modules/Admin/Pages/Edit.razor b/Oqtane.Client/Modules/Admin/Pages/Edit.razor index 88163633..5661b344 100644 --- a/Oqtane.Client/Modules/Admin/Pages/Edit.razor +++ b/Oqtane.Client/Modules/Admin/Pages/Edit.razor @@ -9,7 +9,7 @@ @inject IPageService PageService @inject IThemeService ThemeService -@message +@((MarkupString)message) @@ -116,7 +116,8 @@
Cancel -

+
+
@code { @@ -179,7 +180,7 @@ } catch (Exception ex) { - message = ex.Message; + message = "
" + ex.Message + "


"; } } @@ -220,11 +221,18 @@ await PageService.UpdatePageAsync(page); PageState.Reload = Constants.ReloadSite; - UriHelper.NavigateTo(NavigateUrl(path)); + if (PageState.Page.Name == "Page Management") + { + UriHelper.NavigateTo(NavigateUrl()); + } + else + { + UriHelper.NavigateTo(NavigateUrl(path)); + } } catch (Exception ex) { - message = ex.Message; + message = "
" + ex.Message + "


"; } } } diff --git a/Oqtane.Client/Modules/Controls/AuditInfo.razor b/Oqtane.Client/Modules/Controls/AuditInfo.razor index d4358070..f03a832b 100644 --- a/Oqtane.Client/Modules/Controls/AuditInfo.razor +++ b/Oqtane.Client/Modules/Controls/AuditInfo.razor @@ -20,7 +20,6 @@ public string Style { get; set; } string text = ""; - string style = ""; protected override void OnInitialized() { diff --git a/Oqtane.Client/Providers/IdentityAuthenticationStateProvider.cs b/Oqtane.Client/Providers/IdentityAuthenticationStateProvider.cs index 9ad5b1c7..083aa7c5 100644 --- a/Oqtane.Client/Providers/IdentityAuthenticationStateProvider.cs +++ b/Oqtane.Client/Providers/IdentityAuthenticationStateProvider.cs @@ -4,29 +4,39 @@ using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Components; using Oqtane.Models; +using Oqtane.Services; +using Oqtane.Shared; namespace Oqtane.Providers { public class IdentityAuthenticationStateProvider : AuthenticationStateProvider { private readonly IUriHelper urihelper; + private readonly SiteState sitestate; - public IdentityAuthenticationStateProvider(IUriHelper urihelper) + public IdentityAuthenticationStateProvider(IUriHelper urihelper, SiteState sitestate) { this.urihelper = urihelper; + this.sitestate = sitestate; } public override async Task GetAuthenticationStateAsync() { // hack: create a new HttpClient rather than relying on the registered service as the AuthenticationStateProvider is initialized prior to IUriHelper ( https://github.com/aspnet/AspNetCore/issues/11867 ) HttpClient http = new HttpClient(); - Uri uri = new Uri(urihelper.GetAbsoluteUri()); - string apiurl = uri.Scheme + "://" + uri.Authority + "/~/api/User/authenticate"; + string apiurl = ServiceBase.CreateApiUrl(sitestate.Alias, urihelper.GetAbsoluteUri(), "User") + "/authenticate"; User user = await http.GetJsonAsync(apiurl); - var identity = user.IsAuthenticated - ? new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, user.Username) }, "Identity.Application") - : new ClaimsIdentity(); + ClaimsIdentity identity = new ClaimsIdentity(); + if (user.IsAuthenticated) + { + identity = new ClaimsIdentity("Identity.Application"); + identity.AddClaim(new Claim(ClaimTypes.Name, user.Username)); + foreach(string role in user.Roles.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries)) + { + identity.AddClaim(new Claim(ClaimTypes.Role, role)); + } + } return new AuthenticationState(new ClaimsPrincipal(identity)); } diff --git a/Oqtane.Client/Services/Interfaces/IUserService.cs b/Oqtane.Client/Services/Interfaces/IUserService.cs index 6ffc4991..e1626201 100644 --- a/Oqtane.Client/Services/Interfaces/IUserService.cs +++ b/Oqtane.Client/Services/Interfaces/IUserService.cs @@ -18,7 +18,7 @@ namespace Oqtane.Services Task DeleteUserAsync(int UserId); - Task LoginUserAsync(User User); + Task LoginUserAsync(User User, bool SetCookie, bool IsPersistent); Task LogoutUserAsync(); diff --git a/Oqtane.Client/Services/ServiceBase.cs b/Oqtane.Client/Services/ServiceBase.cs index b970c373..b7cf911b 100644 --- a/Oqtane.Client/Services/ServiceBase.cs +++ b/Oqtane.Client/Services/ServiceBase.cs @@ -8,7 +8,7 @@ namespace Oqtane.Services public class ServiceBase { - public string CreateApiUrl(Alias alias, string absoluteUri, string serviceName) + public static string CreateApiUrl(Alias alias, string absoluteUri, string serviceName) { string apiurl = ""; if (alias != null) diff --git a/Oqtane.Client/Services/UserService.cs b/Oqtane.Client/Services/UserService.cs index 333f1cdf..ec35fc39 100644 --- a/Oqtane.Client/Services/UserService.cs +++ b/Oqtane.Client/Services/UserService.cs @@ -57,9 +57,9 @@ namespace Oqtane.Services await http.DeleteAsync(apiurl + "/" + UserId.ToString()); } - public async Task LoginUserAsync(User User) + public async Task LoginUserAsync(User User, bool SetCookie, bool IsPersistent) { - return await http.PostJsonAsync(apiurl + "/login", User); + return await http.PostJsonAsync(apiurl + "/login?setcookie=" + SetCookie.ToString() + "&persistent =" + IsPersistent.ToString(), User); } public async Task LogoutUserAsync() diff --git a/Oqtane.Client/Shared/Utilities.cs b/Oqtane.Client/Shared/Utilities.cs index 72518271..fc4524f7 100644 --- a/Oqtane.Client/Shared/Utilities.cs +++ b/Oqtane.Client/Shared/Utilities.cs @@ -13,10 +13,14 @@ namespace Oqtane.Shared { url += alias + "/"; } - if (path != "") + if (path != "" && path != "/") { url += path + "/"; } + if (url.EndsWith("/")) + { + url = url.Substring(0, url.Length - 1); + } if (!string.IsNullOrEmpty(parameters)) { url += "?" + parameters; @@ -31,10 +35,6 @@ namespace Oqtane.Shared public static string EditUrl(string alias, string path, int moduleid, string action, string parameters) { string url = NavigateUrl(alias, path, ""); - if ( url == "/" ) - { - url = ""; - } if (moduleid != -1) { url += "/" + moduleid.ToString(); diff --git a/Oqtane.Client/Themes/Controls/Login.razor b/Oqtane.Client/Themes/Controls/Login.razor index 811febd9..faba904d 100644 --- a/Oqtane.Client/Themes/Controls/Login.razor +++ b/Oqtane.Client/Themes/Controls/Login.razor @@ -51,7 +51,7 @@ // client-side Blazor authstateprovider.NotifyAuthenticationChanged(); PageState.Reload = Constants.ReloadSite; - UriHelper.NavigateTo(NavigateUrl(PageState.Page.Path)); + UriHelper.NavigateTo(NavigateUrl(PageState.Page.Path, "logout")); } } } diff --git a/Oqtane.Server/Controllers/AliasController.cs b/Oqtane.Server/Controllers/AliasController.cs index 86ff6a04..f09fa92d 100644 --- a/Oqtane.Server/Controllers/AliasController.cs +++ b/Oqtane.Server/Controllers/AliasController.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Authorization; using Oqtane.Repository; using Oqtane.Models; @@ -31,6 +32,7 @@ namespace Oqtane.Controllers // POST api/ [HttpPost] + [Authorize] public Alias Post([FromBody] Alias Alias) { if (ModelState.IsValid) @@ -42,6 +44,7 @@ namespace Oqtane.Controllers // PUT api//5 [HttpPut("{id}")] + [Authorize] public Alias Put(int id, [FromBody] Alias Alias) { if (ModelState.IsValid) @@ -52,6 +55,7 @@ namespace Oqtane.Controllers } // DELETE api//5 + [Authorize] [HttpDelete("{id}")] public void Delete(int id) { diff --git a/Oqtane.Server/Controllers/ModuleController.cs b/Oqtane.Server/Controllers/ModuleController.cs index ef819edc..2a9045db 100644 --- a/Oqtane.Server/Controllers/ModuleController.cs +++ b/Oqtane.Server/Controllers/ModuleController.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Authorization; using Oqtane.Repository; using Oqtane.Models; @@ -53,6 +54,7 @@ namespace Oqtane.Controllers // POST api/ [HttpPost] + [Authorize] public Module Post([FromBody] Module Module) { if (ModelState.IsValid) @@ -64,6 +66,7 @@ namespace Oqtane.Controllers // PUT api//5 [HttpPut("{id}")] + [Authorize] public Module Put(int id, [FromBody] Module Module) { if (ModelState.IsValid) @@ -75,6 +78,7 @@ namespace Oqtane.Controllers // DELETE api//5 [HttpDelete("{id}")] + [Authorize] public void Delete(int id) { Modules.DeleteModule(id); diff --git a/Oqtane.Server/Controllers/PageController.cs b/Oqtane.Server/Controllers/PageController.cs index 96ef61af..03c140e6 100644 --- a/Oqtane.Server/Controllers/PageController.cs +++ b/Oqtane.Server/Controllers/PageController.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Authorization; using Oqtane.Repository; using Oqtane.Models; @@ -38,6 +39,7 @@ namespace Oqtane.Controllers // POST api/ [HttpPost] + [Authorize(Roles = "Administrators")] public Page Post([FromBody] Page Page) { if (ModelState.IsValid) @@ -49,6 +51,7 @@ namespace Oqtane.Controllers // PUT api//5 [HttpPut("{id}")] + [Authorize(Roles = "Administrators")] public Page Put(int id, [FromBody] Page Page) { if (ModelState.IsValid) @@ -60,6 +63,7 @@ namespace Oqtane.Controllers // DELETE api//5 [HttpDelete("{id}")] + [Authorize(Roles = "Administrators")] public void Delete(int id) { Pages.DeletePage(id); diff --git a/Oqtane.Server/Controllers/PageModuleController.cs b/Oqtane.Server/Controllers/PageModuleController.cs index fa99465e..ae4f41e8 100644 --- a/Oqtane.Server/Controllers/PageModuleController.cs +++ b/Oqtane.Server/Controllers/PageModuleController.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Authorization; using Oqtane.Repository; using Oqtane.Models; @@ -31,6 +32,7 @@ namespace Oqtane.Controllers // POST api/ [HttpPost] + [Authorize] public PageModule Post([FromBody] PageModule PageModule) { if (ModelState.IsValid) @@ -42,6 +44,7 @@ namespace Oqtane.Controllers // PUT api//5 [HttpPut("{id}")] + [Authorize] public PageModule Put(int id, [FromBody] PageModule PageModule) { if (ModelState.IsValid) @@ -53,6 +56,7 @@ namespace Oqtane.Controllers // DELETE api//5 [HttpDelete("{id}")] + [Authorize] public void Delete(int id) { PageModules.DeletePageModule(id); diff --git a/Oqtane.Server/Controllers/RoleController.cs b/Oqtane.Server/Controllers/RoleController.cs index 85f32f55..f94dbcb9 100644 --- a/Oqtane.Server/Controllers/RoleController.cs +++ b/Oqtane.Server/Controllers/RoleController.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Authorization; using Oqtane.Repository; using Oqtane.Models; @@ -38,6 +39,7 @@ namespace Oqtane.Controllers // POST api/ [HttpPost] + [Authorize] public Role Post([FromBody] Role Role) { if (ModelState.IsValid) @@ -49,6 +51,7 @@ namespace Oqtane.Controllers // PUT api//5 [HttpPut("{id}")] + [Authorize] public Role Put(int id, [FromBody] Role Role) { if (ModelState.IsValid) @@ -60,6 +63,7 @@ namespace Oqtane.Controllers // DELETE api//5 [HttpDelete("{id}")] + [Authorize] public void Delete(int id) { Roles.DeleteRole(id); diff --git a/Oqtane.Server/Controllers/SettingController.cs b/Oqtane.Server/Controllers/SettingController.cs index 2f90efd9..4c674138 100644 --- a/Oqtane.Server/Controllers/SettingController.cs +++ b/Oqtane.Server/Controllers/SettingController.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Authorization; using Oqtane.Repository; using Oqtane.Models; @@ -31,6 +32,7 @@ namespace Oqtane.Controllers // POST api/ [HttpPost] + [Authorize] public Setting Post([FromBody] Setting Setting) { if (ModelState.IsValid) @@ -42,6 +44,7 @@ namespace Oqtane.Controllers // PUT api//5 [HttpPut("{id}")] + [Authorize] public Setting Put(int id, [FromBody] Setting Setting) { if (ModelState.IsValid) @@ -53,6 +56,7 @@ namespace Oqtane.Controllers // DELETE api//5 [HttpDelete("{id}")] + [Authorize] public void Delete(int id) { Settings.DeleteSetting(id); diff --git a/Oqtane.Server/Controllers/SiteController.cs b/Oqtane.Server/Controllers/SiteController.cs index b5892ec6..4377b67a 100644 --- a/Oqtane.Server/Controllers/SiteController.cs +++ b/Oqtane.Server/Controllers/SiteController.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Authorization; using Oqtane.Repository; using Oqtane.Models; @@ -31,6 +32,7 @@ namespace Oqtane.Controllers // POST api/ [HttpPost] + [Authorize] public Site Post([FromBody] Site Site) { if (ModelState.IsValid) @@ -42,6 +44,7 @@ namespace Oqtane.Controllers // PUT api//5 [HttpPut("{id}")] + [Authorize] public Site Put(int id, [FromBody] Site Site) { if (ModelState.IsValid) @@ -53,6 +56,7 @@ namespace Oqtane.Controllers // DELETE api//5 [HttpDelete("{id}")] + [Authorize] public void Delete(int id) { Sites.DeleteSite(id); diff --git a/Oqtane.Server/Controllers/TenantController.cs b/Oqtane.Server/Controllers/TenantController.cs index e56d86ee..c3f41289 100644 --- a/Oqtane.Server/Controllers/TenantController.cs +++ b/Oqtane.Server/Controllers/TenantController.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Authorization; using Oqtane.Repository; using Oqtane.Models; using System.Collections.Generic; @@ -31,6 +32,7 @@ namespace Oqtane.Controllers // POST api/ [HttpPost] + [Authorize] public Tenant Post([FromBody] Tenant Tenant) { if (ModelState.IsValid) @@ -42,6 +44,7 @@ namespace Oqtane.Controllers // PUT api//5 [HttpPut("{id}")] + [Authorize] public Tenant Put(int id, [FromBody] Tenant Tenant) { if (ModelState.IsValid) @@ -53,6 +56,7 @@ namespace Oqtane.Controllers // DELETE api//5 [HttpDelete("{id}")] + [Authorize] public void Delete(int id) { Tenants.DeleteTenant(id); diff --git a/Oqtane.Server/Controllers/UserController.cs b/Oqtane.Server/Controllers/UserController.cs index 914e23e7..c64fd905 100644 --- a/Oqtane.Server/Controllers/UserController.cs +++ b/Oqtane.Server/Controllers/UserController.cs @@ -1,10 +1,13 @@ using System.Collections.Generic; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authorization; using Oqtane.Repository; using Oqtane.Models; using Microsoft.AspNetCore.Identity; using System.Threading.Tasks; using System.Linq; +using System.Security.Claims; namespace Oqtane.Controllers { @@ -105,21 +108,23 @@ namespace Oqtane.Controllers if (result.Succeeded) { user = Users.AddUser(User); - - SiteUser siteuser = new SiteUser(); - siteuser.SiteId = User.SiteId; - siteuser.UserId = user.UserId; - SiteUsers.AddSiteUser(siteuser); - - List roles = Roles.GetRoles(user.SiteId).Where(item => item.IsAutoAssigned == true).ToList(); - foreach (Role role in roles) + if (!user.IsSuperUser) { - UserRole userrole = new UserRole(); - userrole.UserId = user.UserId; - userrole.RoleId = role.RoleId; - userrole.EffectiveDate = null; - userrole.ExpiryDate = null; - UserRoles.AddUserRole(userrole); + SiteUser siteuser = new SiteUser(); + siteuser.SiteId = User.SiteId; + siteuser.UserId = user.UserId; + SiteUsers.AddSiteUser(siteuser); + + List roles = Roles.GetRoles(user.SiteId).Where(item => item.IsAutoAssigned == true).ToList(); + foreach (Role role in roles) + { + UserRole userrole = new UserRole(); + userrole.UserId = user.UserId; + userrole.RoleId = role.RoleId; + userrole.EffectiveDate = null; + userrole.ExpiryDate = null; + UserRoles.AddUserRole(userrole); + } } } } @@ -129,20 +134,23 @@ namespace Oqtane.Controllers SiteUser siteuser = SiteUsers.GetSiteUser(User.SiteId, user.UserId); if (siteuser == null) { - siteuser = new SiteUser(); - siteuser.SiteId = User.SiteId; - siteuser.UserId = user.UserId; - SiteUsers.AddSiteUser(siteuser); - - List roles = Roles.GetRoles(User.SiteId).Where(item => item.IsAutoAssigned == true).ToList(); - foreach (Role role in roles) + if (!user.IsSuperUser) { - UserRole userrole = new UserRole(); - userrole.UserId = user.UserId; - userrole.RoleId = role.RoleId; - userrole.EffectiveDate = null; - userrole.ExpiryDate = null; - UserRoles.AddUserRole(userrole); + siteuser = new SiteUser(); + siteuser.SiteId = User.SiteId; + siteuser.UserId = user.UserId; + SiteUsers.AddSiteUser(siteuser); + + List roles = Roles.GetRoles(User.SiteId).Where(item => item.IsAutoAssigned == true).ToList(); + foreach (Role role in roles) + { + UserRole userrole = new UserRole(); + userrole.UserId = user.UserId; + userrole.RoleId = role.RoleId; + userrole.EffectiveDate = null; + userrole.ExpiryDate = null; + UserRoles.AddUserRole(userrole); + } } } } @@ -153,6 +161,7 @@ namespace Oqtane.Controllers // PUT api//5 [HttpPut("{id}")] + [Authorize] public User Put(int id, [FromBody] User User) { if (ModelState.IsValid) @@ -164,6 +173,7 @@ namespace Oqtane.Controllers // DELETE api//5?siteid=x [HttpDelete("{id}")] + [Authorize] public void Delete(int id, string siteid) { SiteUser siteuser = SiteUsers.GetSiteUser(id, int.Parse(siteid)); @@ -175,7 +185,7 @@ namespace Oqtane.Controllers // POST api//login [HttpPost("login")] - public async Task Login([FromBody] User User) + public async Task Login([FromBody] User User, bool SetCookie, bool IsPersistent) { User user = new Models.User { Username = User.Username, IsAuthenticated = false }; @@ -202,9 +212,9 @@ namespace Oqtane.Controllers { user.IsAuthenticated = true; } - if (user.IsAuthenticated) + if (user.IsAuthenticated && SetCookie) { - await IdentitySignInManager.SignInAsync(identityuser, User.IsPersistent); + await IdentitySignInManager.SignInAsync(identityuser, IsPersistent); } } } @@ -216,28 +226,36 @@ namespace Oqtane.Controllers // POST api//logout [HttpPost("logout")] + [Authorize] public async Task Logout([FromBody] User User) { - await IdentitySignInManager.SignOutAsync(); + await HttpContext.SignOutAsync(IdentityConstants.ApplicationScheme); } // GET api//current [HttpGet("authenticate")] public User Authenticate() { - return new User { Username = User.Identity.Name, IsAuthenticated = User.Identity.IsAuthenticated }; + User user = new User(); + user.Username = User.Identity.Name; + user.IsAuthenticated = User.Identity.IsAuthenticated; + string roles = ""; + foreach (var claim in User.Claims.Where(item => item.Type == ClaimTypes.Role)) + { + roles += claim.Value + ";"; + } + if (roles != "") roles = ";" + roles; + user.Roles = roles; + return user; } private string GetUserRoles(int UserId, int SiteId) { string roles = ""; - IEnumerable userroles = UserRoles.GetUserRoles(UserId); + IEnumerable userroles = UserRoles.GetUserRoles(UserId, SiteId); foreach (UserRole userrole in userroles) { - if (userrole.Role.SiteId == SiteId) - { - roles += userrole.Role.Name + ";"; - } + roles += userrole.Role.Name + ";"; } if (roles != "") roles = ";" + roles; return roles; diff --git a/Oqtane.Server/Controllers/UserRoleController.cs b/Oqtane.Server/Controllers/UserRoleController.cs index 8865e763..36f6879d 100644 --- a/Oqtane.Server/Controllers/UserRoleController.cs +++ b/Oqtane.Server/Controllers/UserRoleController.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Authorization; using Oqtane.Repository; using Oqtane.Models; @@ -38,6 +39,7 @@ namespace Oqtane.Controllers // POST api/ [HttpPost] + [Authorize] public UserRole Post([FromBody] UserRole UserRole) { if (ModelState.IsValid) @@ -49,6 +51,7 @@ namespace Oqtane.Controllers // PUT api//5 [HttpPut("{id}")] + [Authorize] public UserRole Put(int id, [FromBody] UserRole UserRole) { if (ModelState.IsValid) @@ -60,6 +63,7 @@ namespace Oqtane.Controllers // DELETE api//5 [HttpDelete("{id}")] + [Authorize] public void Delete(int id) { UserRoles.DeleteUserRole(id); diff --git a/Oqtane.Server/Modules/HtmlText/Controllers/HtmlTextController.cs b/Oqtane.Server/Modules/HtmlText/Controllers/HtmlTextController.cs index 695e460b..6a0119d1 100644 --- a/Oqtane.Server/Modules/HtmlText/Controllers/HtmlTextController.cs +++ b/Oqtane.Server/Modules/HtmlText/Controllers/HtmlTextController.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Authorization; using Oqtane.Shared.Modules.HtmlText.Models; using Oqtane.Server.Modules.HtmlText.Repository; @@ -24,6 +25,7 @@ namespace Oqtane.Server.Modules.HtmlText.Controllers // POST api/ [HttpPost] + [Authorize] public HtmlTextInfo Post([FromBody] HtmlTextInfo HtmlText) { if (ModelState.IsValid) @@ -35,6 +37,7 @@ namespace Oqtane.Server.Modules.HtmlText.Controllers // PUT api//5 [HttpPut("{id}")] + [Authorize] public HtmlTextInfo Put(int id, [FromBody] HtmlTextInfo HtmlText) { if (ModelState.IsValid) @@ -46,6 +49,7 @@ namespace Oqtane.Server.Modules.HtmlText.Controllers // DELETE api//5 [HttpDelete("{id}")] + [Authorize] public void Delete(int id) { htmltext.DeleteHtmlText(id); diff --git a/Oqtane.Server/Pages/Login.cshtml.cs b/Oqtane.Server/Pages/Login.cshtml.cs index cc1877cc..4c08b1d5 100644 --- a/Oqtane.Server/Pages/Login.cshtml.cs +++ b/Oqtane.Server/Pages/Login.cshtml.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Security.Claims; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Authentication; +using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; @@ -14,24 +10,22 @@ namespace Oqtane.Pages public class LoginModel : PageModel { - private readonly UserManager identityUserManager; - private readonly SignInManager identitySignInManager; + private readonly UserManager IdentityUserManager; + private readonly SignInManager IdentitySignInManager; public LoginModel(UserManager IdentityUserManager, SignInManager IdentitySignInManager) { - identityUserManager = IdentityUserManager; - identitySignInManager = IdentitySignInManager; + this.IdentityUserManager = IdentityUserManager; + this.IdentitySignInManager = IdentitySignInManager; } public async Task OnPostAsync(string username, string password, bool remember, string returnurl) { - await HttpContext.SignOutAsync(IdentityConstants.ApplicationScheme); - bool validuser = false; - IdentityUser identityuser = await identityUserManager.FindByNameAsync(username); + IdentityUser identityuser = await IdentityUserManager.FindByNameAsync(username); if (identityuser != null) { - var result = await identitySignInManager.CheckPasswordSignInAsync(identityuser, password, false); + var result = await IdentitySignInManager.CheckPasswordSignInAsync(identityuser, password, false); if (result.Succeeded) { validuser = true; @@ -40,10 +34,7 @@ namespace Oqtane.Pages if (validuser) { - var claims = new List{ new Claim(ClaimTypes.Name, username) }; - var claimsIdentity = new ClaimsIdentity(claims, IdentityConstants.ApplicationScheme); - var authProperties = new AuthenticationProperties{IsPersistent = remember}; - await HttpContext.SignInAsync(IdentityConstants.ApplicationScheme, new ClaimsPrincipal(claimsIdentity), authProperties); + await IdentitySignInManager.SignInAsync(identityuser, remember); } return LocalRedirect(Url.Content("~" + returnurl)); diff --git a/Oqtane.Server/Pages/Logout.cshtml.cs b/Oqtane.Server/Pages/Logout.cshtml.cs index d976f39d..e4629619 100644 --- a/Oqtane.Server/Pages/Logout.cshtml.cs +++ b/Oqtane.Server/Pages/Logout.cshtml.cs @@ -1,14 +1,9 @@ -using System; -using System.Collections.Generic; -using System.Security.Claims; -using System.Threading.Tasks; +using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; -using Oqtane.Models; namespace Oqtane.Pages { diff --git a/Oqtane.Server/Repository/Context/DBContextBase.cs b/Oqtane.Server/Repository/Context/DBContextBase.cs index a6b07ac4..0b12b6d7 100644 --- a/Oqtane.Server/Repository/Context/DBContextBase.cs +++ b/Oqtane.Server/Repository/Context/DBContextBase.cs @@ -8,7 +8,7 @@ using System.Linq; namespace Oqtane.Repository { - public class DBContextBase : IdentityDbContext + public class DBContextBase : IdentityUserContext { private Tenant tenant; private IHttpContextAccessor accessor; diff --git a/Oqtane.Server/Repository/Interfaces/ITenantResolver.cs b/Oqtane.Server/Repository/Interfaces/ITenantResolver.cs index e665d76c..95e916f7 100644 --- a/Oqtane.Server/Repository/Interfaces/ITenantResolver.cs +++ b/Oqtane.Server/Repository/Interfaces/ITenantResolver.cs @@ -4,6 +4,7 @@ namespace Oqtane.Repository { public interface ITenantResolver { + Alias GetAlias(); Tenant GetTenant(); } } diff --git a/Oqtane.Server/Repository/Interfaces/IUserRoleRepository.cs b/Oqtane.Server/Repository/Interfaces/IUserRoleRepository.cs index fad2f850..d2141a13 100644 --- a/Oqtane.Server/Repository/Interfaces/IUserRoleRepository.cs +++ b/Oqtane.Server/Repository/Interfaces/IUserRoleRepository.cs @@ -7,6 +7,7 @@ namespace Oqtane.Repository { IEnumerable GetUserRoles(); IEnumerable GetUserRoles(int UserId); + IEnumerable GetUserRoles(int UserId, int SiteId); UserRole AddUserRole(UserRole UserRole); UserRole UpdateUserRole(UserRole UserRole); UserRole GetUserRole(int UserRoleId); diff --git a/Oqtane.Server/Repository/TenantResolver.cs b/Oqtane.Server/Repository/TenantResolver.cs index 08c553c2..35d4b0ab 100644 --- a/Oqtane.Server/Repository/TenantResolver.cs +++ b/Oqtane.Server/Repository/TenantResolver.cs @@ -32,14 +32,25 @@ namespace Oqtane.Repository } } - public Tenant GetTenant() + public Alias GetAlias() { try { IEnumerable aliases = _aliasrepository.GetAliases(); // cached - Alias alias = aliases.Where(item => item.Name == aliasname).FirstOrDefault(); + return aliases.Where(item => item.Name == aliasname).FirstOrDefault(); + } + catch + { + throw; + } + } + + public Tenant GetTenant() + { + try + { IEnumerable tenants = _tenantrepository.GetTenants(); // cached - return tenants.Where(item => item.TenantId == alias.TenantId).FirstOrDefault(); + return tenants.Where(item => item.TenantId == GetAlias().TenantId).FirstOrDefault(); } catch { diff --git a/Oqtane.Server/Repository/UserRoleRepository.cs b/Oqtane.Server/Repository/UserRoleRepository.cs index 8e095ffb..82911356 100644 --- a/Oqtane.Server/Repository/UserRoleRepository.cs +++ b/Oqtane.Server/Repository/UserRoleRepository.cs @@ -39,6 +39,21 @@ namespace Oqtane.Repository } } + public IEnumerable GetUserRoles(int UserId, int SiteId) + { + try + { + return db.UserRole.Where(item => item.UserId == UserId) + .Include(item => item.Role) // eager load roles + .Where(item => item.Role.SiteId == SiteId) + .ToList(); + } + catch + { + throw; + } + } + public UserRole AddUserRole(UserRole UserRole) { try diff --git a/Oqtane.Server/Scripts/00.00.00.sql b/Oqtane.Server/Scripts/00.00.00.sql index d047d5f8..fe02870e 100644 --- a/Oqtane.Server/Scripts/00.00.00.sql +++ b/Oqtane.Server/Scripts/00.00.00.sql @@ -251,7 +251,7 @@ INSERT [dbo].[Page] ([PageId], [SiteId], [Name], [Path], [ThemeType], [Icon], [P VALUES (1, 1, N'Page1', N'', N'Oqtane.Client.Themes.Theme1.Theme1, Oqtane.Client', N'oi-home', N'Left;Right', N'All Users', N'Administrators', NULL, 1, 1, N'', '', getdate(), '', getdate()) GO INSERT [dbo].[Page] ([PageId], [SiteId], [Name], [Path], [ThemeType], [Icon], [Panes], [ViewPermissions], [EditPermissions], [ParentId], [Order], [IsNavigation], [LayoutType], [CreatedBy], [CreatedOn], [ModifiedBy], [ModifiedOn]) -VALUES (2, 1, N'Page2', N'page2', N'Oqtane.Client.Themes.Theme2.Theme2, Oqtane.Client', N'oi-plus', N'Top;Bottom', N'Administrators;Editors;', N'Administrators', NULL, 3, 1, N'', '', getdate(), '', getdate()) +VALUES (2, 1, N'Page2', N'page2', N'Oqtane.Client.Themes.Theme2.Theme2, Oqtane.Client', N'oi-plus', N'Top;Bottom', N'Administrators', N'Administrators', NULL, 3, 1, N'', '', getdate(), '', getdate()) GO INSERT [dbo].[Page] ([PageId], [SiteId], [Name], [Path], [ThemeType], [Icon], [Panes], [ViewPermissions], [EditPermissions], [ParentId], [Order], [IsNavigation], [LayoutType], [CreatedBy], [CreatedOn], [ModifiedBy], [ModifiedOn]) VALUES (3, 1, N'Page3', N'page3', N'Oqtane.Client.Themes.Theme3.Theme3, Oqtane.Client', N'oi-list-rich', N'Left;Right', N'All Users', N'Administrators', NULL, 3, 1, N'Oqtane.Client.Themes.Theme3.HorizontalLayout, Oqtane.Client', '', getdate(), '', getdate()) @@ -319,7 +319,7 @@ INSERT [dbo].[Module] ([ModuleId], [SiteId], [ModuleDefinitionName], [ViewPermis VALUES (6, 1, N'Oqtane.Client.Modules.HtmlText, Oqtane.Client', N'All Users', N'Administrators', '', getdate(), '', getdate()) GO INSERT [dbo].[Module] ([ModuleId], [SiteId], [ModuleDefinitionName], [ViewPermissions], [EditPermissions], [CreatedBy], [CreatedOn], [ModifiedBy], [ModifiedOn]) -VALUES (7, 1, N'Oqtane.Client.Modules.HtmlText, Oqtane.Client', N'Administrators;Editors;Members;', N'Administrators;Editors;', '', getdate(), '', getdate()) +VALUES (7, 1, N'Oqtane.Client.Modules.HtmlText, Oqtane.Client', N'Administrators', N'Administrators', '', getdate(), '', getdate()) GO INSERT [dbo].[Module] ([ModuleId], [SiteId], [ModuleDefinitionName], [ViewPermissions], [EditPermissions], [CreatedBy], [CreatedOn], [ModifiedBy], [ModifiedOn]) VALUES (8, 1, N'Oqtane.Client.Modules.Admin.Pages, Oqtane.Client', N'Administrators', N'Administrators', '', getdate(), '', getdate()) diff --git a/Oqtane.Server/Scripts/00.00.01.sql b/Oqtane.Server/Scripts/00.00.01.sql index 45f9cd66..90118490 100644 --- a/Oqtane.Server/Scripts/00.00.01.sql +++ b/Oqtane.Server/Scripts/00.00.01.sql @@ -1,62 +1,8 @@ -CREATE TABLE [dbo].[AspNetRoleClaims]( - [Id] [int] IDENTITY(1,1) NOT NULL, - [RoleId] [nvarchar](450) NOT NULL, - [ClaimType] [nvarchar](max) NULL, - [ClaimValue] [nvarchar](max) NULL, - CONSTRAINT [PK_AspNetRoleClaims] PRIMARY KEY CLUSTERED -( - [Id] ASC -) ON [PRIMARY] -) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] -GO +/* -CREATE TABLE [dbo].[AspNetRoles]( - [Id] [nvarchar](450) NOT NULL, - [Name] [nvarchar](256) NULL, - [NormalizedName] [nvarchar](256) NULL, - [ConcurrencyStamp] [nvarchar](max) NULL, - CONSTRAINT [PK_AspNetRoles] PRIMARY KEY CLUSTERED -( - [Id] ASC -) ON [PRIMARY] -) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] -GO +ASP.NET Identity Minimal Schema -CREATE TABLE [dbo].[AspNetUserClaims]( - [Id] [int] IDENTITY(1,1) NOT NULL, - [UserId] [nvarchar](450) NOT NULL, - [ClaimType] [nvarchar](max) NULL, - [ClaimValue] [nvarchar](max) NULL, - CONSTRAINT [PK_AspNetUserClaims] PRIMARY KEY CLUSTERED -( - [Id] ASC -) ON [PRIMARY] -) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] -GO - -CREATE TABLE [dbo].[AspNetUserLogins]( - [LoginProvider] [nvarchar](128) NOT NULL, - [ProviderKey] [nvarchar](128) NOT NULL, - [ProviderDisplayName] [nvarchar](max) NULL, - [UserId] [nvarchar](450) NOT NULL, - CONSTRAINT [PK_AspNetUserLogins] PRIMARY KEY CLUSTERED -( - [LoginProvider] ASC, - [ProviderKey] ASC -) ON [PRIMARY] -) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] -GO - -CREATE TABLE [dbo].[AspNetUserRoles]( - [UserId] [nvarchar](450) NOT NULL, - [RoleId] [nvarchar](450) NOT NULL, - CONSTRAINT [PK_AspNetUserRoles] PRIMARY KEY CLUSTERED -( - [UserId] ASC, - [RoleId] ASC -) ON [PRIMARY] -) ON [PRIMARY] -GO +*/ CREATE TABLE [dbo].[AspNetUsers]( [Id] [nvarchar](450) NOT NULL, @@ -81,52 +27,24 @@ CREATE TABLE [dbo].[AspNetUsers]( ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] GO -CREATE TABLE [dbo].[AspNetUserTokens]( +CREATE TABLE [dbo].[AspNetUserClaims]( + [Id] [int] IDENTITY(1,1) NOT NULL, [UserId] [nvarchar](450) NOT NULL, - [LoginProvider] [nvarchar](128) NOT NULL, - [Name] [nvarchar](128) NOT NULL, - [Value] [nvarchar](max) NULL, - CONSTRAINT [PK_AspNetUserTokens] PRIMARY KEY CLUSTERED + [ClaimType] [nvarchar](max) NULL, + [ClaimValue] [nvarchar](max) NULL, + CONSTRAINT [PK_AspNetUserClaims] PRIMARY KEY CLUSTERED ( - [UserId] ASC, - [LoginProvider] ASC, - [Name] ASC + [Id] ASC ) ON [PRIMARY] ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] GO -CREATE NONCLUSTERED INDEX [IX_AspNetRoleClaims_RoleId] ON [dbo].[AspNetRoleClaims] -( - [RoleId] ASC -) ON [PRIMARY] -GO - -CREATE UNIQUE NONCLUSTERED INDEX [RoleNameIndex] ON [dbo].[AspNetRoles] -( - [NormalizedName] ASC -) -WHERE ([NormalizedName] IS NOT NULL) - ON [PRIMARY] -GO - CREATE NONCLUSTERED INDEX [IX_AspNetUserClaims_UserId] ON [dbo].[AspNetUserClaims] ( [UserId] ASC ) ON [PRIMARY] GO -CREATE NONCLUSTERED INDEX [IX_AspNetUserLogins_UserId] ON [dbo].[AspNetUserLogins] -( - [UserId] ASC -) ON [PRIMARY] -GO - -CREATE NONCLUSTERED INDEX [IX_AspNetUserRoles_RoleId] ON [dbo].[AspNetUserRoles] -( - [RoleId] ASC -) ON [PRIMARY] -GO - CREATE NONCLUSTERED INDEX [EmailIndex] ON [dbo].[AspNetUsers] ( [NormalizedEmail] ASC @@ -141,14 +59,6 @@ WHERE ([NormalizedUserName] IS NOT NULL) ON [PRIMARY] GO -ALTER TABLE [dbo].[AspNetRoleClaims] WITH CHECK ADD CONSTRAINT [FK_AspNetRoleClaims_AspNetRoles_RoleId] FOREIGN KEY([RoleId]) -REFERENCES [dbo].[AspNetRoles] ([Id]) -ON DELETE CASCADE -GO - -ALTER TABLE [dbo].[AspNetRoleClaims] CHECK CONSTRAINT [FK_AspNetRoleClaims_AspNetRoles_RoleId] -GO - ALTER TABLE [dbo].[AspNetUserClaims] WITH CHECK ADD CONSTRAINT [FK_AspNetUserClaims_AspNetUsers_UserId] FOREIGN KEY([UserId]) REFERENCES [dbo].[AspNetUsers] ([Id]) ON DELETE CASCADE @@ -156,35 +66,3 @@ GO ALTER TABLE [dbo].[AspNetUserClaims] CHECK CONSTRAINT [FK_AspNetUserClaims_AspNetUsers_UserId] GO - -ALTER TABLE [dbo].[AspNetUserLogins] WITH CHECK ADD CONSTRAINT [FK_AspNetUserLogins_AspNetUsers_UserId] FOREIGN KEY([UserId]) -REFERENCES [dbo].[AspNetUsers] ([Id]) -ON DELETE CASCADE -GO - -ALTER TABLE [dbo].[AspNetUserLogins] CHECK CONSTRAINT [FK_AspNetUserLogins_AspNetUsers_UserId] -GO - -ALTER TABLE [dbo].[AspNetUserRoles] WITH CHECK ADD CONSTRAINT [FK_AspNetUserRoles_AspNetRoles_RoleId] FOREIGN KEY([RoleId]) -REFERENCES [dbo].[AspNetRoles] ([Id]) -ON DELETE CASCADE -GO - -ALTER TABLE [dbo].[AspNetUserRoles] CHECK CONSTRAINT [FK_AspNetUserRoles_AspNetRoles_RoleId] -GO - -ALTER TABLE [dbo].[AspNetUserRoles] WITH CHECK ADD CONSTRAINT [FK_AspNetUserRoles_AspNetUsers_UserId] FOREIGN KEY([UserId]) -REFERENCES [dbo].[AspNetUsers] ([Id]) -ON DELETE CASCADE -GO - -ALTER TABLE [dbo].[AspNetUserRoles] CHECK CONSTRAINT [FK_AspNetUserRoles_AspNetUsers_UserId] -GO - -ALTER TABLE [dbo].[AspNetUserTokens] WITH CHECK ADD CONSTRAINT [FK_AspNetUserTokens_AspNetUsers_UserId] FOREIGN KEY([UserId]) -REFERENCES [dbo].[AspNetUsers] ([Id]) -ON DELETE CASCADE -GO - -ALTER TABLE [dbo].[AspNetUserTokens] CHECK CONSTRAINT [FK_AspNetUserTokens_AspNetUsers_UserId] -GO diff --git a/Oqtane.Server/Security/ClaimsPrincipalFactory.cs b/Oqtane.Server/Security/ClaimsPrincipalFactory.cs new file mode 100644 index 00000000..d62ba167 --- /dev/null +++ b/Oqtane.Server/Security/ClaimsPrincipalFactory.cs @@ -0,0 +1,50 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Options; +using System.Security.Claims; +using System.Threading.Tasks; +using Oqtane.Repository; +using Oqtane.Models; + +namespace Oqtane.Security +{ + public class ClaimsPrincipalFactory : UserClaimsPrincipalFactory where TUser : IdentityUser + { + private readonly IdentityOptions options; + private readonly ITenantResolver Tenants; + private readonly IUserRepository Users; + private readonly IUserRoleRepository UserRoles; + + public ClaimsPrincipalFactory(UserManager userManager, IOptions optionsAccessor, ITenantResolver tenants, IUserRepository users, IUserRoleRepository userroles) : base(userManager, optionsAccessor) + { + options = optionsAccessor.Value; + Tenants = tenants; + Users = users; + UserRoles = userroles; + } + + protected override async Task GenerateClaimsAsync(TUser identityuser) + { + var id = await base.GenerateClaimsAsync(identityuser); + + User user = Users.GetUser(identityuser.UserName); + if (user != null) + { + if (user.IsSuperUser) + { + id.AddClaim(new Claim(options.ClaimsIdentity.RoleClaimType, "Administrators")); + } + else + { + Alias alias = Tenants.GetAlias(); + foreach (UserRole userrole in UserRoles.GetUserRoles(user.UserId, alias.SiteId)) + { + id.AddClaim(new Claim(options.ClaimsIdentity.RoleClaimType, userrole.Role.Name)); + } + } + } + + return id; + } + } + +} diff --git a/Oqtane.Server/Startup.cs b/Oqtane.Server/Startup.cs index ebf18df8..a084f410 100644 --- a/Oqtane.Server/Startup.cs +++ b/Oqtane.Server/Startup.cs @@ -21,6 +21,10 @@ using Microsoft.AspNetCore.Identity; using System.Threading.Tasks; using System.Collections.Generic; using Microsoft.OpenApi.Models; +using Oqtane.Security; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Authentication; +using System.Net; namespace Oqtane.Server { @@ -91,10 +95,11 @@ namespace Oqtane.Server )); services.AddDbContext(options => { }); - services.AddIdentity() + services.AddIdentityCore(options => { }) .AddEntityFrameworkStores() + .AddSignInManager() .AddDefaultTokenProviders(); - + services.Configure(options => { // Password settings @@ -113,6 +118,9 @@ namespace Oqtane.Server options.User.RequireUniqueEmail = false; }); + services.AddAuthentication(IdentityConstants.ApplicationScheme) + .AddCookie(IdentityConstants.ApplicationScheme); + services.ConfigureApplicationCookie(options => { options.Cookie.HttpOnly = false; @@ -123,6 +131,9 @@ namespace Oqtane.Server }; }); + // register custom claims principal factory for role claims + services.AddTransient, ClaimsPrincipalFactory>(); + // get list of loaded assemblies Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); @@ -241,8 +252,9 @@ namespace Oqtane.Server )); services.AddDbContext(options => { }); - services.AddIdentity() + services.AddIdentityCore(options => { }) .AddEntityFrameworkStores() + .AddSignInManager() .AddDefaultTokenProviders(); services.Configure(options => @@ -263,6 +275,9 @@ namespace Oqtane.Server options.User.RequireUniqueEmail = false; }); + services.AddAuthentication(IdentityConstants.ApplicationScheme) + .AddCookie(IdentityConstants.ApplicationScheme); + services.ConfigureApplicationCookie(options => { options.Cookie.HttpOnly = false; @@ -273,7 +288,10 @@ namespace Oqtane.Server }; }); - // get list of loaded assemblies + // register custom claims principal factory for role claims + services.AddTransient, ClaimsPrincipalFactory>(); + + // get list of loaded assemblies Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); // iterate through Oqtane module assemblies in /bin ( filter is narrow to optimize loading process ) @@ -377,5 +395,6 @@ namespace Oqtane.Server }); } #endif + } } diff --git a/Oqtane.Shared/Models/User.cs b/Oqtane.Shared/Models/User.cs index e57f765f..0cd2bcc2 100644 --- a/Oqtane.Shared/Models/User.cs +++ b/Oqtane.Shared/Models/User.cs @@ -23,8 +23,5 @@ namespace Oqtane.Models public string Password { get; set; } [NotMapped] public bool IsAuthenticated { get; set; } - [NotMapped] - public bool IsPersistent { get; set; } - } }