Multi-tenant role authorization

This commit is contained in:
Shaun Walker 2019-08-25 14:52:25 -04:00
parent ad2d865d7c
commit f037898c6e
34 changed files with 312 additions and 252 deletions

View File

@ -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 = "<div class=\"alert alert-danger\" role=\"alert\">Login Failed. Please Remember That Passwords Are Case Sensitive.</div>";
}
}
else
{
Message = "<div class=\"alert alert-danger\" role=\"alert\">Login Failed. Please Remember That Passwords Are Case Sensitive.</div>";
// 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 = "<div class=\"alert alert-danger\" role=\"alert\">Login Failed. Please Remember That Passwords Are Case Sensitive.</div>";
}
}
}

View File

@ -8,7 +8,7 @@
@inject IPageService PageService
@inject IThemeService ThemeService
@message
@((MarkupString)message)
<table class="form-group">
<tr>
@ -144,7 +144,7 @@
}
catch (Exception ex)
{
message = ex.Message;
message = "<div class=\"alert alert-danger\" role=\"alert\">" + ex.Message + "</div><br /><br />";
}
}
@ -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 = "<div class=\"alert alert-danger\" role=\"alert\">" + ex.Message + "</div><br /><br />";
}
}
}

View File

@ -9,7 +9,7 @@
@inject IPageService PageService
@inject IThemeService ThemeService
@message
@((MarkupString)message)
<table class="form-group">
<tr>
@ -116,7 +116,8 @@
</table>
<button type="button" class="btn btn-danger" @onclick="@DeletePage">Delete</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">Cancel</NavLink>
<br /><br />
<br />
<br />
<AuditInfo CreatedBy="@createdby" CreatedOn="@createdon" ModifiedBy="@modifiedby" ModifiedOn="@modifiedon"></AuditInfo>
@code {
@ -172,7 +173,7 @@
}
catch (Exception ex)
{
message = ex.Message;
message = "<div class=\"alert alert-danger\" role=\"alert\">" + ex.Message + "</div><br /><br />";
}
}
@ -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 = "<div class=\"alert alert-danger\" role=\"alert\">" + ex.Message + "</div><br /><br />";
}
}
}

View File

@ -9,7 +9,7 @@
@inject IPageService PageService
@inject IThemeService ThemeService
@message
@((MarkupString)message)
<table class="form-group">
<tr>
@ -116,7 +116,8 @@
</table>
<button type="button" class="btn btn-success" @onclick="@SavePage">Save</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">Cancel</NavLink>
<br /><br />
<br />
<br />
<AuditInfo CreatedBy="@createdby" CreatedOn="@createdon" ModifiedBy="@modifiedby" ModifiedOn="@modifiedon"></AuditInfo>
@code {
@ -179,7 +180,7 @@
}
catch (Exception ex)
{
message = ex.Message;
message = "<div class=\"alert alert-danger\" role=\"alert\">" + ex.Message + "</div><br /><br />";
}
}
@ -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 = "<div class=\"alert alert-danger\" role=\"alert\">" + ex.Message + "</div><br /><br />";
}
}
}

View File

@ -20,7 +20,6 @@
public string Style { get; set; }
string text = "";
string style = "";
protected override void OnInitialized()
{

View File

@ -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<AuthenticationState> 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<User>(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));
}

View File

@ -18,7 +18,7 @@ namespace Oqtane.Services
Task DeleteUserAsync(int UserId);
Task<User> LoginUserAsync(User User);
Task<User> LoginUserAsync(User User, bool SetCookie, bool IsPersistent);
Task LogoutUserAsync();

View File

@ -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)

View File

@ -57,9 +57,9 @@ namespace Oqtane.Services
await http.DeleteAsync(apiurl + "/" + UserId.ToString());
}
public async Task<User> LoginUserAsync(User User)
public async Task<User> LoginUserAsync(User User, bool SetCookie, bool IsPersistent)
{
return await http.PostJsonAsync<User>(apiurl + "/login", User);
return await http.PostJsonAsync<User>(apiurl + "/login?setcookie=" + SetCookie.ToString() + "&persistent =" + IsPersistent.ToString(), User);
}
public async Task LogoutUserAsync()

View File

@ -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();

View File

@ -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"));
}
}
}

View File

@ -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/<controller>
[HttpPost]
[Authorize]
public Alias Post([FromBody] Alias Alias)
{
if (ModelState.IsValid)
@ -42,6 +44,7 @@ namespace Oqtane.Controllers
// PUT api/<controller>/5
[HttpPut("{id}")]
[Authorize]
public Alias Put(int id, [FromBody] Alias Alias)
{
if (ModelState.IsValid)
@ -52,6 +55,7 @@ namespace Oqtane.Controllers
}
// DELETE api/<controller>/5
[Authorize]
[HttpDelete("{id}")]
public void Delete(int id)
{

View File

@ -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/<controller>
[HttpPost]
[Authorize]
public Module Post([FromBody] Module Module)
{
if (ModelState.IsValid)
@ -64,6 +66,7 @@ namespace Oqtane.Controllers
// PUT api/<controller>/5
[HttpPut("{id}")]
[Authorize]
public Module Put(int id, [FromBody] Module Module)
{
if (ModelState.IsValid)
@ -75,6 +78,7 @@ namespace Oqtane.Controllers
// DELETE api/<controller>/5
[HttpDelete("{id}")]
[Authorize]
public void Delete(int id)
{
Modules.DeleteModule(id);

View File

@ -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/<controller>
[HttpPost]
[Authorize(Roles = "Administrators")]
public Page Post([FromBody] Page Page)
{
if (ModelState.IsValid)
@ -49,6 +51,7 @@ namespace Oqtane.Controllers
// PUT api/<controller>/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/<controller>/5
[HttpDelete("{id}")]
[Authorize(Roles = "Administrators")]
public void Delete(int id)
{
Pages.DeletePage(id);

View File

@ -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/<controller>
[HttpPost]
[Authorize]
public PageModule Post([FromBody] PageModule PageModule)
{
if (ModelState.IsValid)
@ -42,6 +44,7 @@ namespace Oqtane.Controllers
// PUT api/<controller>/5
[HttpPut("{id}")]
[Authorize]
public PageModule Put(int id, [FromBody] PageModule PageModule)
{
if (ModelState.IsValid)
@ -53,6 +56,7 @@ namespace Oqtane.Controllers
// DELETE api/<controller>/5
[HttpDelete("{id}")]
[Authorize]
public void Delete(int id)
{
PageModules.DeletePageModule(id);

View File

@ -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/<controller>
[HttpPost]
[Authorize]
public Role Post([FromBody] Role Role)
{
if (ModelState.IsValid)
@ -49,6 +51,7 @@ namespace Oqtane.Controllers
// PUT api/<controller>/5
[HttpPut("{id}")]
[Authorize]
public Role Put(int id, [FromBody] Role Role)
{
if (ModelState.IsValid)
@ -60,6 +63,7 @@ namespace Oqtane.Controllers
// DELETE api/<controller>/5
[HttpDelete("{id}")]
[Authorize]
public void Delete(int id)
{
Roles.DeleteRole(id);

View File

@ -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/<controller>
[HttpPost]
[Authorize]
public Setting Post([FromBody] Setting Setting)
{
if (ModelState.IsValid)
@ -42,6 +44,7 @@ namespace Oqtane.Controllers
// PUT api/<controller>/5
[HttpPut("{id}")]
[Authorize]
public Setting Put(int id, [FromBody] Setting Setting)
{
if (ModelState.IsValid)
@ -53,6 +56,7 @@ namespace Oqtane.Controllers
// DELETE api/<controller>/5
[HttpDelete("{id}")]
[Authorize]
public void Delete(int id)
{
Settings.DeleteSetting(id);

View File

@ -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/<controller>
[HttpPost]
[Authorize]
public Site Post([FromBody] Site Site)
{
if (ModelState.IsValid)
@ -42,6 +44,7 @@ namespace Oqtane.Controllers
// PUT api/<controller>/5
[HttpPut("{id}")]
[Authorize]
public Site Put(int id, [FromBody] Site Site)
{
if (ModelState.IsValid)
@ -53,6 +56,7 @@ namespace Oqtane.Controllers
// DELETE api/<controller>/5
[HttpDelete("{id}")]
[Authorize]
public void Delete(int id)
{
Sites.DeleteSite(id);

View File

@ -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/<controller>
[HttpPost]
[Authorize]
public Tenant Post([FromBody] Tenant Tenant)
{
if (ModelState.IsValid)
@ -42,6 +44,7 @@ namespace Oqtane.Controllers
// PUT api/<controller>/5
[HttpPut("{id}")]
[Authorize]
public Tenant Put(int id, [FromBody] Tenant Tenant)
{
if (ModelState.IsValid)
@ -53,6 +56,7 @@ namespace Oqtane.Controllers
// DELETE api/<controller>/5
[HttpDelete("{id}")]
[Authorize]
public void Delete(int id)
{
Tenants.DeleteTenant(id);

View File

@ -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<Role> 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<Role> 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<Role> 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<Role> 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/<controller>/5
[HttpPut("{id}")]
[Authorize]
public User Put(int id, [FromBody] User User)
{
if (ModelState.IsValid)
@ -164,6 +173,7 @@ namespace Oqtane.Controllers
// DELETE api/<controller>/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/<controller>/login
[HttpPost("login")]
public async Task<User> Login([FromBody] User User)
public async Task<User> 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/<controller>/logout
[HttpPost("logout")]
[Authorize]
public async Task Logout([FromBody] User User)
{
await IdentitySignInManager.SignOutAsync();
await HttpContext.SignOutAsync(IdentityConstants.ApplicationScheme);
}
// GET api/<controller>/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<UserRole> userroles = UserRoles.GetUserRoles(UserId);
IEnumerable<UserRole> 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;

View File

@ -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/<controller>
[HttpPost]
[Authorize]
public UserRole Post([FromBody] UserRole UserRole)
{
if (ModelState.IsValid)
@ -49,6 +51,7 @@ namespace Oqtane.Controllers
// PUT api/<controller>/5
[HttpPut("{id}")]
[Authorize]
public UserRole Put(int id, [FromBody] UserRole UserRole)
{
if (ModelState.IsValid)
@ -60,6 +63,7 @@ namespace Oqtane.Controllers
// DELETE api/<controller>/5
[HttpDelete("{id}")]
[Authorize]
public void Delete(int id)
{
UserRoles.DeleteUserRole(id);

View File

@ -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/<controller>
[HttpPost]
[Authorize]
public HtmlTextInfo Post([FromBody] HtmlTextInfo HtmlText)
{
if (ModelState.IsValid)
@ -35,6 +37,7 @@ namespace Oqtane.Server.Modules.HtmlText.Controllers
// PUT api/<controller>/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/<controller>/5
[HttpDelete("{id}")]
[Authorize]
public void Delete(int id)
{
htmltext.DeleteHtmlText(id);

View File

@ -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<IdentityUser> identityUserManager;
private readonly SignInManager<IdentityUser> identitySignInManager;
private readonly UserManager<IdentityUser> IdentityUserManager;
private readonly SignInManager<IdentityUser> IdentitySignInManager;
public LoginModel(UserManager<IdentityUser> IdentityUserManager, SignInManager<IdentityUser> IdentitySignInManager)
{
identityUserManager = IdentityUserManager;
identitySignInManager = IdentitySignInManager;
this.IdentityUserManager = IdentityUserManager;
this.IdentitySignInManager = IdentitySignInManager;
}
public async Task<IActionResult> 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<Claim>{ 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));

View File

@ -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
{

View File

@ -8,7 +8,7 @@ using System.Linq;
namespace Oqtane.Repository
{
public class DBContextBase : IdentityDbContext<IdentityUser>
public class DBContextBase : IdentityUserContext<IdentityUser>
{
private Tenant tenant;
private IHttpContextAccessor accessor;

View File

@ -4,6 +4,7 @@ namespace Oqtane.Repository
{
public interface ITenantResolver
{
Alias GetAlias();
Tenant GetTenant();
}
}

View File

@ -7,6 +7,7 @@ namespace Oqtane.Repository
{
IEnumerable<UserRole> GetUserRoles();
IEnumerable<UserRole> GetUserRoles(int UserId);
IEnumerable<UserRole> GetUserRoles(int UserId, int SiteId);
UserRole AddUserRole(UserRole UserRole);
UserRole UpdateUserRole(UserRole UserRole);
UserRole GetUserRole(int UserRoleId);

View File

@ -32,14 +32,25 @@ namespace Oqtane.Repository
}
}
public Tenant GetTenant()
public Alias GetAlias()
{
try
{
IEnumerable<Alias> 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<Tenant> tenants = _tenantrepository.GetTenants(); // cached
return tenants.Where(item => item.TenantId == alias.TenantId).FirstOrDefault();
return tenants.Where(item => item.TenantId == GetAlias().TenantId).FirstOrDefault();
}
catch
{

View File

@ -39,6 +39,21 @@ namespace Oqtane.Repository
}
}
public IEnumerable<UserRole> 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

View File

@ -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())

View File

@ -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

View File

@ -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<TUser> : UserClaimsPrincipalFactory<TUser> where TUser : IdentityUser
{
private readonly IdentityOptions options;
private readonly ITenantResolver Tenants;
private readonly IUserRepository Users;
private readonly IUserRoleRepository UserRoles;
public ClaimsPrincipalFactory(UserManager<TUser> userManager, IOptions<IdentityOptions> optionsAccessor, ITenantResolver tenants, IUserRepository users, IUserRoleRepository userroles) : base(userManager, optionsAccessor)
{
options = optionsAccessor.Value;
Tenants = tenants;
Users = users;
UserRoles = userroles;
}
protected override async Task<ClaimsIdentity> 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;
}
}
}

View File

@ -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<TenantDBContext>(options => { });
services.AddIdentity<IdentityUser, IdentityRole>()
services.AddIdentityCore<IdentityUser>(options => { })
.AddEntityFrameworkStores<TenantDBContext>()
.AddSignInManager()
.AddDefaultTokenProviders();
services.Configure<IdentityOptions>(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<IUserClaimsPrincipalFactory<IdentityUser>, ClaimsPrincipalFactory<IdentityUser>>();
// get list of loaded assemblies
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
@ -241,8 +252,9 @@ namespace Oqtane.Server
));
services.AddDbContext<TenantDBContext>(options => { });
services.AddIdentity<IdentityUser, IdentityRole>()
services.AddIdentityCore<IdentityUser>(options => { })
.AddEntityFrameworkStores<TenantDBContext>()
.AddSignInManager()
.AddDefaultTokenProviders();
services.Configure<IdentityOptions>(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<IUserClaimsPrincipalFactory<IdentityUser>, ClaimsPrincipalFactory<IdentityUser>>();
// 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
}
}

View File

@ -23,8 +23,5 @@ namespace Oqtane.Models
public string Password { get; set; }
[NotMapped]
public bool IsAuthenticated { get; set; }
[NotMapped]
public bool IsPersistent { get; set; }
}
}