our business has a number of sites that we manage and each of these sites have sites they are responsible for and so on. So everything is hierarchical as far as permissions go for our software too. If a person at site-X wants to edit stuff for site-X and any sub-site-X they should be allowed to. We also have applications roles, mainly admin, that will allow a person to edit everything as well as maintain the application.
I'm currently working on dealing with permissions for this application and I've got everything working, but I really hate it. Its clunky, not very testable and doesn't seem like its in the right place for my MVC application. I was hoping someone would have some thoughts on how I can refactor this code and make it most importantly more testable and perhaps make it a bit more usable.
Thank you in advance.
public class OuController : BaseController {
private readonly IOrganizationUnitRepository repo;
public OUController(IOrganizationUnitRepository repo) {
this.repo = repo;
}
public ActionResult Details(string site) {
//Get the site we are viewing
var ou = repo.GetOuByName(site);
//make sure the site really exists
if (ou != null) {
//Get all the roles for the current user via the role provider
//will return the sites they are able to manage along with
//any application roles they have
var roles = ((RolePrincipal)User).GetRoles().ToList();
//Get all the parents of the current ou, this will include itself
var parents = repo.GetParents(ou, new List<OU>());
//create a new viewmodel object
//ou is used for details obviously
//parents are used for a breadcrumb
var model = new OrganizationalViewModel(ou, parents);
//if a user has no roles, there is no way he can possibly edit
if (roles.Any()) {
if(roles.Contains(InfoRoles.Administrator.ToString())) {
model.CanEdit = true;
} else if(parents == null) {
//If there are no parents, check if this ou is in users list of roles
model.CanEdit = roles.Contains(ou.DisplayName);
} else {
//check to see if any of the roles i have are parents of the current ou
model.CanEdit = parents.Any(c => roles.Contains(c.DisplayName));
}
}
return View("Details", model);
}
return View("NotFound");
}
}
}
Anything that looks like this:
((RolePrincipal)User).GetRoles().ToList()
... belongs in a class of its own (with an interface method like "GetCurrentRoles"), so it can be easily mocked.
Furthermore, this:
//if a user has no roles, there is no way he can possibly edit
if (roles.Any()) {
if(roles.Contains(InfoRoles.Administrator.ToString())) {
return true;
} else if(parents == null) {
//If there are no parents, check if this ou is in users list of roles
return roles.Contains(ou.DisplayName);
} else {
//check to see if any of the roles i have are parents of the current ou
return parents.Any(c => roles.Contains(c.DisplayName));
}
... belongs in a utility class in a method called something like CanRolesEditOrganizationalView(IEnumerable<RolePrinciple> roles, ...). That way your controller can just say:
var roles = _sessionManager.GetCurrentRoles();
...
model.Edit = _orgViewRightsUtil.CanRolesEditOrganizationalView(roles, ...);
Related
I have setup a standard ASP.NET MVC site with normal authentication. I have added roles, so new users get a specific role.
Now, I want to be able to impersonate a user.
Impersonating advice
Impersonating, when I search around, comes the following way:
FormsAuthentication.SetAuthCookie(user.UserName, false);
This doesn't work by default, as you have to do two things:
1:
Enable forms authentication:
<system.web>
<authentication mode="Forms" />
</system.web>
2:
Disable the module:
<system.webServer>
<modules>
<!--<remove name="FormsAuthentication" />-->
</modules>
<staticContent>
The challenge
However, doing this leaves a couple of challenges.
When you impersonate, you cannot log out. This is easily fixed by
adding the following in LogOut: FormsAuthentication.SignOut();
The User.IsInRole(Constants.Roles.Creditor); stops working, so
we cannot check if user in a role
What to do?
This COULD boil down to me - apparently - not fully understanding the membership framework despite trying. However, how do you get impersonate to work here?
I have no explicit reason to use "Forms" authentication, and the only reason I started on this path is Impersonating. So I see I have two obvious directions:
A) Implement impersonation in a different way, so I don't touch my
web.config to use forms
B) Fix the role problem in forms
Any help here? :-)
There are quite a few ways to accomplish this all you really need to do is get both the Id's to your controller and decide how you want it persisted (Cookie, Cache, Db , etc.).
An easy way to do this is to create a claim for the impersonation and add a policy for those kind of claims. Here is a link for adding claim based policies.
Here is some code to get you started :
In your controllers you will want an end point that does something like this
var claims = await UserManager.GetClaimsAsync(CurrentUserId);
var claim = claims.FirstOrDefault(c => c.Type == "Impersonate");
if (claim!=null)
{
//You may forget to remove it or the user could end there session before you are able to
var r = await UserManager.RemoveClaimAsync(CurrentUserId, claim);
}
var result = await UserManager.AddClaimAsync(CurrentUserId, new Claim("Impersonate", userId));
if (!result.Succeeded)
{
return GetErrorResult(result);
}
Now with the code above we wanted the users ID, but we could have just as easily gotten there role and saved that with the claim. From here you just need to decide how you want to use this claim. The link below will show you how you can do that.
https://learn.microsoft.com/en-us/aspnet/core/security/authorization/claims
Just remember to remove the claim after you are done.
This one works nice when you may have to impersonate a user for sometime. One project I worked on the clients the needed to be able to impersonate users for weeks at a time to complete work for other clients.
If we need to implement the IsInRole() method in basic forms principal, we need to customize the principal.
User is a principal object, which contains list of Identities. Default identity does not contain role property, so we need to create custom identity object.
For e.g:
public class CustomPricipal : IPrincipal
{
public CustomPricipal(string username)
{
this.Identity = new CustomIdentity(username);
}
public IIdentity Identity
{
get;
private set;
}
public bool IsInRole(string role)
{
return this.Identity != null && ((CustomIdentity)this.Identity).Roles.Any(x => x.ToLower() == role.ToLower());
}
}
public class CustomIdentity : IIdentity
{
public CustomIdentity(string name)
{
// We can fetch the user information from database and create custom properties
this.Name = name;
this.IsAuthenticated = true;
this.AuthenticationType = "Forms";
this.Roles = new List<string>() { "Admin", "SuperAdmin" };
}
public string AuthenticationType
{
get;
private set;
}
public bool IsAuthenticated
{
get;
private set;
}
public string Name
{
get;
private set;
}
public List<string> Roles
{
get;
private set;
}
}
In global.asax.cs
public override void Init()
{
this.PostAuthenticateRequest += MvcApplication_PostAuthenticateRequest;
base.Init();
}
void MvcApplication_PostAuthenticateRequest(object sender, EventArgs e)
{
if (Request.IsAuthenticated)
{
HttpCookie authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
Context.User = Thread.CurrentPrincipal = new CustomPricipal(authTicket.Name);
}
}
}
Now we can use the User.IsInRole("")
I am making an asp.NET MVC5 intranet website for my company and as I have just finished implementing authorization and a dynamic menu according to user roles in Active Directory, I need to apply restrictions for access. I have read on MSDN / Technet that you can apply authorizations by using [Authorize(Role="<YourRole>")] and I make it work perfectly. Now my problem is that I have a 20 differents roles, and each Role is linked with Documents and Categories in my MSSQL Database. This means that if you want to access a document or a ressource, you must first find the corresponding entry in the DB (If you need I can explain this further). So If I implement Authorizations with the [Authorize] attribute, I will have to check my DB to see if the row exists, and if does I add it. I have started to do that with a static class :
public static class CustomRoles
{
public const string Role1 = "Role1";
public const string Role2 = "Role2";
//And so on ...
}
Then in my controller action methods:
[Authorize(Roles=CustomRoles.Role1+","+CustomRoles.Role2)]
public ActionResult Index(){}
You can imagine doing this for each role will be long and tedious.
So my question : do you know of any better / simpler way to do this? Because I have to manually check each document (thousands!) and look then in another table what profiles are associated, and then apply the corresponding authorization. And technically, my dynamic menu is supposed to take care of this since you cannot see what is not available to you, but then again, using the URL you can access anything in this way if authorizations aren't implemented
And also : Not all roles are registered in my DB, most users have around 140 roles, but there are likely just 1 or 2 that are registered in the database. Is this going to create some performance issues? I know I can handle this when I create my Claims and filter out the ones not belonging to the DB but I'd prefer not to.
One workaround you could do is by using overidden OnActionExecuting method in the ActionFilter instead of decorators AuthorizeAttribute to check if a user is authorized to do certain action or not.
Yep, you still need to carefully check of your role authorization, but not sparsely, as you only need to check all of them in one place, namely your action filter (or, to be more precise, your single switch). Also, putting everything in one place enable you to avoid redundancy and make the logic more compact whenever possible.
Example:
The ActionFilter:
public class AuthorizeActionFilterAttribute : ActionFilterAttribute {
public override void OnActionExecuting(ActionExecutingContext filterContext) {
IPrincipal user = HttpContext.Current.User; //get the current user
//Get controller name and action
string controllerName = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName;
string actionName = filterContext.ActionDescriptor.ActionName;
//All roles will be defined here!
List<string> rolesAccepted = new List<string>();
switch (controllerName){
case "Controller1": //suppose these three happen to have the same rules
case "Controller2":
case "Controller3":
//Describe roles accepted for certain controller and action
rolesAccepted = new List<string> { "Role1", "Role2" };
break;
case "Controller4": //suppose these have specific restrictions only for some actions
if (actionName == "action1") {//can also use switch
rolesAccepted = new List<string> { "Role3", "Role4" };
} else {
rolesAccepted = new List<string> { "Role5", "Role6", "Role7" };
}
break;
....
}
//Redirect to login if non of the user role is authorized
if (!rolesAccepted.Any(x => user.IsInRole(x)){
filterContext.Result = redirectToLogin();
return;
}
}
private ActionResult redirectToLogin() {
return new RedirectToRouteResult(
new RouteValueDictionary(new { controller = "Account", action = "Login" })
);
}
}
Then you only need to put the AuthorizeActionFilterAttribute in every controller (and not roles in every single action) as you have handled all authorization in the single place:
[AuthorizeActionFilter]
public class Controller1 : Controller {
...
}
[AuthorizeActionFilter]
public class Controller2 : Controller {
...
}
... and so on
When a user signs in to my website, I want cache some data like email, confirmation status, mobile confirmation status, etc. Because I don't want fetch this data in each page request. The requirement is that the user must confirm email and mobile before do anything.
I am using code like this:
public static class CachedData
{
public static bool IsEmailConfirmed
{
get
{
if (HttpContext.Current.Session["IsEmailConfirmed"] == null)
Initialize();
return Convert.ToBoolean(HttpContext.Current.Session["IsEmailConfirmed"]);
}
set
{
HttpContext.Current.Session["IsEmailConfirmed"] = value;
}
}
public static bool IsMobileConfirmed
{
get
{
if (HttpContext.Current.Session["IsMobileConfirmed"] == null)
Initialize();
return Convert.ToBoolean(HttpContext.Current.Session["IsMobileConfirmed"]);
}
set
{
HttpContext.Current.Session["IsMobileConfirmed"] = value;
}
}
public static void Initialize()
{
UserAccount currentUser = UserAccount.GetUser();
if (currentUser == null)
return;
IsEmailConfirmed = currentUser.EmailConfirmed;
IsMobileConfirmed = currentUser.MobileConfirmed;
}
}
I have PageBase class that all page classes drive from it. I am using class CachedData in PageBase class:
public class PageBase : Page
{
protected override void OnInit(EventArgs e)
{
if (authentication.Required && User.Identity.IsAuthenticated && !IsPostBack)
{
if (CachedData.HasProfile && (!CachedData.IsEmailConfirmed || !CachedData.IsMobileConfirmed) && !Request.Url.AbsolutePath.ToLower().EndsWith("settings.aspx"))
Response.Redirect("/settings-page", true);
}
}
}
May be it is strange, but this code, sometimes work wrong and redirect to setting page for user confirmed email and mobile.
Is there any better solution.
I think, if this is your logic, you should create an object UserInfo. Something like this:
public class UserInfo
{
public string Name {get; set; }
public bool IsEmailConfirmed {get; set; }
public bool IsMobileConfirmed {get; set; }
....
}
Then set this object into session. Now! when any operation on user record are performed in your BLL, you should re-populate new Instance of UserInfo and replace old one in the session. This way your user info will be up to day and will always work.
But your problem may coming from the fact that you use a web farm and your sessions are not synchronized. You need to use a sticky session so each request from the unique user is processed on the same server. Right now there is thing called App Fabric. It is caching on steroids. It can find an item in cache on another server.
You should not store different fields of your object in the different session names.
If you do need to use sessions, you can store the whole user object in your session.
The choice where to store the data depands on the requirements (including how critical are the data and the requirements for such the things as IIS reset) and what and why you really have to store.
Depending on the answers you could store your data either in a session or in a viewstate or in cache or in application.
You can also look at the cache because it provides some nice features like automatic update, triggering, etc.
I'm looking into how to implement authorization in MVC 4 (.NET 4.5), and have specifically been reading about SimpleMembership. Is there any typical or accepted way in MVC to have roles that have additional properties aside from a name?
For example, suppose you were designing a CMS, and wanted to be able to have a role called something like Writer that let a user make modifications. However, you also want the role to be restrictive to a single page. The only way that I know of to do that would be to have a separate role for each page, where each role might be named something like Writer_<PageID>. Is there any pattern that's nicer than this, or is that pretty much all we can do?
Ideally, I'm wondering if there'd be some way to be able to have something remotely like:
public ActionResult EditPage(Page page) {
WriterRole role = new WriterRole(page);
if (!User.IsInRole(role)) {
return NotAuthorized();
}
// Edit...
}
Instead of:
public ActionResult EditPage(Page page) {
string role = "Writer_" + page.Id;
if (!User.IsInRole(role)) {
return NotAuthorized();
}
// Edit...
}
What I would do is have one Writer role then check the UserId to see if the person owns the editable resource.
[Authorize(Roles = "Writer")]
public ActionResult EditPage(Page page) {
if (User.UserId == page.UserId) { ... }
}
I recently starded developing for MVC 3 but have experience in both C# and ASP.NET since earlier. So i'll start with what i'm trying to accomplish. I've developed a small site for hosting articles. I've implemented SQLServer based membership managament to the site. Now i want to create a credentials system that restricts and allows the right users to create, delete and update articles. There is one simple solution to this and that is to do it like this:
[Authorize(Roles="Admin")]
public ActionResult UpdateArticle(ArticleModel model, int articleid)
{
return View();
}
Now this is really simple. I simply say that only members that are in the role "Admin" are allowed to update an article. But that's just to static. So i created a credentials table in my database that in the end tells me that "Article 5 can be edited by roles 1,2,3 & 4 and by users A, b & C". So far so good. But how would i implement that with the Authorize solution?
I would like to do something like this:
[Authorize(getAuthorizedusers("update",this.articleid))]
where getAuthorizedusers returns which users and roles are authorized to update the article with the articleid that was passed to it.
So I have (at least) two problems here:
-Getting the Authorize method to accept multiple users and roles.
-Passing the supplied articleid, that was sent to the UpdateArticle method, to the getAuthorizedusers method.
You can create your own custom attribute that inherits from AuthorizeAttribute and override the OnAuthorize method to do what you need.
This should get you started:
public class ArticleAuthorizeAttribute : AuthorizeAttribute
{
public enum ArticleAction
{
Read,
Create,
Update,
Delete
}
public ArticleAction Action { get; set; }
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
//do custom authorizization using Action and getting ArticleID
//from filterContext.HttpContext.Request.QueryString or
//filterContext.HttpContext.Request.Form
}
}
The usage would look like this:
[ArticleAuthorize(Action=ArticleAuthorizeAttribute.ArticleAction.Update)]
Edit: After looking into this a bit more, it looks like you can't pass this.articleID in to the attribute. However, you do have access to the parameters from filterContext.HttpContext.Request through the QueryString property or the Form property, depending on how you are passing the values. I have updated the code sample appropriately.
A more complete example can be found here
To check for authorization using user role and user list you would do something like this:
var allowedUsers = new List<string>();
//populate allowedUsers from DB
If (User.IsInRole("Update") || allowedUsers.Contains(User.Identity.Name))
{
//authorized
}
Alternatively, you can do both checks against the DB directly in a single method to keep from making two calls.
Here's a much easier way to accomplish the same thing:
[Authorize]
public ActionResult UpdateArticle(ArticleModel model, int articleid)
{
// if current user is an article editor
return View();
// else
return View("Error");
}
I got it working as I wanted when I overrode the AuthorizeCore method and authorizes the way I want to.
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext == null)
{
throw new ArgumentNullException("httpContext");
}
IPrincipal user = httpContext.User;
if (!user.Identity.IsAuthenticated)
{
return false;
}
if ((_usersSplit.Length > 0 && !_usersSplit.Contains(user.Identity.Name, StringComparer.OrdinalIgnoreCase)) && (_rolesSplit.Length > 0 && !_rolesSplit.Any(user.IsInRole)))
{
return false;
}
return true;
}