In my web application registered users can add new content and edit it later. I want only the content's author to be able to edit it. Is there any smart way of doing this other than manually writing code in all the action methods that checks if the logged user is the same as the author? Any attribute that I could use for the whole controller?
Any attribute that I could use for the whole controller?
Yes, you could extend the Authorize attribute with a custom one:
public class AuthorizeAuthorAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var isAuthorized = base.AuthorizeCore(httpContext);
if (!isAuthorized)
{
// the user is either not authenticated or
// not in roles => no need to continue any further
return false;
}
// get the currently logged on user
var username = httpContext.User.Identity.Name;
// get the id of the article that he is trying to manipulate
// from the route data (this assumes that the id is passed as a route
// data parameter: /foo/edit/123). If this is not the case and you
// are using query string parameters you could fetch the id using the Request
var id = httpContext.Request.RequestContext.RouteData.Values["id"] as string;
// Now that we have the current user and the id of the article he
// is trying to manipualte all that's left is go ahead and look in
// our database to see if this user is the owner of the article
return IsUserOwnerOfArticle(username, id);
}
private bool IsUserOwnerOfArticle(string username, string articleId)
{
throw new NotImplementedException();
}
}
and then:
[HttpPost]
[AuthorizeAuthor]
public ActionResult Edit(int id)
{
... perform the edit
}
I would:
Save the db.aspnet_Users columm UserId (Guid) against the content record
Write an extension method for your content model which verifies the current users Guid against the saved contents User Guid
I would write some code that overrides this functionality for your Admin logins (I would create an Admin Role).
Related
When i call my admin controller- Index Action method will get all the user details
when i want select particular user again i dont want to hit the DB.
both action method same controller and i'm using model popup for display details.
My Question
I dont want to use entity framework.
- when admin form load i will get all the user details this is Index Action Method
-based on user id i need to display particular user so again i dont want hit to the DB already i'm having all the user details. that details how to get another action method?
i can remember asp.net i used session to share the data globally. like that asp.net mvc is possible? please help me.
Thanks
It looks you're looking for a cache mechanism. For simple scenarios, I use a simple static variable, but I keep it in a separated class. Let's suppose you have a User class like this:
public class User
{
public string Id { get; set; }
public string Name { get; set; }
}
You could create a class like this:
public static class UserCacheService
{
private static IEnumerable<User> _users;
private static readonly object lockObj = new object();
public static IEnumerable<User> GetUsers()
{
lock (lockObj)
{
if (_users == null)
{
using (var db = new MyNiceDbContext())
{
_users = db.Users.ToList();
}
}
return _users;
}
}
public static void InvalidateCache()
{
lock (lockObj)
{
_users = null;
}
}
}
Then you can get your shared users in any action, of any controller like this:
public class AdminController : Controller
{
public ActionResult Index()
{
// the first time, it'll need to get users from DB (e.g with Entity Framework)
var users = UserCacheService.GetUsers();
return View();
}
}
The first time, the _users in your UserCacheService will be null, and as expected, it'll need to load users from database. However, the next time it won't, no matter if you are using another controller:
public class AnotherController : Controller
{
public ActionResult Index(string userId)
{
// now, it won't load from DB anymore, because _users is already populated...
var users = UserCacheService.GetUsers();
var currentUser = users.Where(u => u.Id == userId).FirstOrDefault();
if (currentUser != null)
{
// do something with the user...
}
return View();
}
}
There are times when unfortunately your _users will become null again, for example when you restart your ApplicationPool in IIS, but UserCacheService is already prepared for fetching database once if that's the case.
Be careful about three things:
Whenever you keep data in memory (like _users), you are consuming
your server's memory, which might be limited. Don't start trying to
keep everything in memory, only data you know you'll need everytime.
Whenever you update something in your users, like a name, an address or something else, since the _users will not get from database everytime, you need to call the UserCacheService.InvalidateCache() method, in order to force the next call to load again from database, thus making sure you have _users up to date.
This only works for simplistic scenarios. If you have your application distributed in two or more servers, this won't work, as each server has it's own memory and they can't share it out of the box. That's when you would look forward for something like Redis. Though, I don't think it's your case here.
Designing an ASP.Net MVC application authorization using claim based model. Lets say that we have an object called - Product. Typically, there are 4 different actions - Create, Edit, Delete and View. Authorization is done using ClaimsAuthorize attribute.
[Authorize]
public class ProductController : Controller
{
[ClaimsAuthorize("Product", "VIEW")]
public List<Product> GetProducts()
{
// ....
}
[ClaimsAuthorize("Product", "CREATE")]
public Product CreateNewProduct(Product product)
{
//....
}
}
But in my case, I have to support different types of EDIT permissions:
Some Users can Edit the product if the same user has created the Product originally
Some users can Edit the product if the Product belongs to a specific category and the user also has access to the same category
Some users can Edit all the products (this is the normal Product Edit operation)
How do you elegantly authorize all these Edit operations (preferably attribute driven as shown above) and at the same time I want to keep the authorization code separate from the normal MVC controller code and business logic.
[Above code sample is not syntactically correct, I just made it up for the purpose of explaining this question]
Let me know your thoughts.
For first part of your question, Claim based authorization, I have already answered it in this similar question. And I am not going to repeat here.
But for your another rules like products editable only by owner. You could write separate AuthorizeAttribute for each rule and apply them on your Actions consider this as an simple example:
using Microsoft.AspNet.Identity;
public class OwnerAuthorizeAttribute : AuthorizeAttribute
{
private string _keyName;
public bool IsPost { get; set; }
public OwnerAuthorizeAttribute(string keyName)
{
_keyName = keyName;
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
// imagine you have a service which could check owner of
// product based on userID and ProductID
return httpContext.User.Identity.IsAuthenticated
&& this.ContainsKey
&& _productService.IsOwner(httpContext.User.Identity.GetUserId(),
int.Parse(this.KeyValue.ToString()));
}
private bool ContainsKey
{
get
{
return IsPost
? HttpContext.Current.Request.Form.AllKeys.Contains(_keyName)
// for simplicity I just check route data
// in real world you might need to check query string too
: ((MvcHandler)HttpContext.Current.Handler).RequestContext
.RouteData.Values.ContainsKey(_keyName);
}
}
private object KeyValue
{
get
{
return IsPost
? HttpContext.Current.Request.Form[_keyName]
// for simplicity I just check route data
// in real world you might need to check query string too
: ((MvcHandler)HttpContext.Current.Handler)
.RequestContext.RouteData.Values[_keyName];
}
}
}
You could repeat same pattern to your other rules too.
And you could simply apply your custom attributes to your actions:
[OwnerAuthorize("id")]
public ActionResult Edit(int id)
{
// your code
}
[HttpPost]
// double checking in post back too
[OwnerAuthorize("id", IsPost = true)]
public ActionResult Edit(Product product)
{
// your code
}
It is obvious you could apply more then one AuthorizeAttribute to your actions. In this case all of them must return true.
[ClaimsAuthorize("Product", "EDIT")]
[OwnerAuthorize("id")]
[YetOtherAuthorize]
public ActionResult MyFancyAction(int id)
{
}
I'm looking for recommendations on how to have multiple authorize attributes on an action.
eg:
[AuthorizePermission(PermissionName.SectionOne, PermissionLevel.Two)]
[AuthorizePermission(PermissionName.SectionTwo, PermissionLevel.Three)]
public ActionResult Index(int userId = 0){
}
If the user has access to SectionOne OR SectionTwo with the required PermissionLevel then they should be allowed in.
The problem i'm facing is how do I check both attributes before deciding they aren't allowed in (as they are separate attributes)? If the first one fails then it will never get to the second one.
I can not pass both permission sets to one attribute as they need to be paired together.
Does anyone have any suggestions?
I can not pass both permission sets to one attribute as they need to be paired together.
Yes, you can.
There is no reason why you can't include all the permissions in a single attribute. Something like this:
[AuthorizePermission(new Permission[]{
new Permission(PermissionName.SectionOne, PermissionLevel.Two),
new Permission(PermissionName.SectionTwo, PermissionLevel.Three)}]
This would pass an array of Permission objects, which you can then evaluate in your method with OR logic.
public class AuthorizePermissionAttribute : AuthorizeAttribute
{
private Permission[] _permissions = null;
public AuthorizePermissionAttribute(Permission[] permissions)
{
_permissions = permissions;
}
}
You could even get fancy and add a parameter that tells whether to AND or OR them...
The only way that I know is something like this
public class CustomRolesAttribute : AuthorizeAttribute
{
public CustomRolesAttribute(params string[] roles)
{
Roles = String.Join(",", roles);
}
}
Usage:
[CustomRoles("members", "admin")]
Is it possible to bypass the authorization role check on a controller, but enforce the role check on an action? I've spent a bit of time researching this and everything I find shows how to implement an AllowAnonymousAttribute. I'm currently using the AllowAnonymousAttribute and it works great for completely bypassing authorization for an action. That isn't what I want. I have a controller that requires certain roles. When a particular action is requested I want to skip the roles at the controller level and just verify user has the roles designated on the action.
Here's some code:
[Authorize(Roles="Administrator")]
public class MembersController : ViewApiController<MemberView>
{
// a list of actions....
[Authorize(Roles="ApiUser")]
[HttpPost]
public void AutoPayPost([FromBody] List<AutoPayModel> autoPayList)
{
//....
}
}
The problem is I want users with just the 'ApiUser' role to have access to the 'AutoPayPost' action. I realize I can remove the class level authorize attribute, then add it to every action method on my controller, minus the 'AutoPayPost' action. I would like to avoid this because several of my controllers inherit from a base class that provides a long list of actions that require the 'Administrative' role. Because of that I would have to override every base action, add the Authorize attribute to the overridden method, then delegate the call back to the base class. This WILL work but if I later decide to add functionality to the base class I'll have to remember to go back to the MembersController and override the new methods, add the attribute etc...
It would be great if the end result looked like this:
[Authorize(Roles="Administrator")]
public class MembersController : ViewApiController<MemberView>
{
// a list of actions....
[Authorize(Roles="ApiUser", IgnoreControllerRoles=true)]
[HttpPost]
public void AutoPayPost([FromBody] List<AutoPayModel> autoPayList)
{
//....
}
}
Do something like this, where you will check if the roles/users are in the roles and then deny any of them.
public class ByPassAuthorizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
string[] roles = this.Roles.Split(',');
string[] users = this.Users.Split(',');
foreach (var r in roles)
{
if (httpContext.User.IsInRole(r.Trim()))
return false;
}
foreach (var u in users)
{
if (httpContext.User.Identity.Name.Equals(u))
return false;
}
return base.AuthorizeCore(httpContext);
}
}
And then decore your controller/action like this:
[ByPassAuthorize(Roles = "Admin,test,testint", Users = "Tester")]
public ActionResult Edit(int id = 0)
{
FooModel foomodel = db.FooModels.Find(id);
if (foomodel == null)
{
return HttpNotFound();
}
return View(foomodel);
}
Hope its help you!
If I understand you correctly, you could implement a custom ByPassControllerChecksAttribute (it is for decorating methods that you want to allow "passthrough" access to), then in your LogonAuthorizeAttribute retrieve the action method being called by this request and check if its custom attribute collection has an instance of ByPassControllerChecksAttribute. If it does, run the code that checks if the user is allowed access to the method, otherwise run the code that checks if the user is allowed access to the controller. Of course if you have just one method and the name is known not to change, you can bypass the extra attribute and just check for the name, but of course the first method is much better.
EDIT
If your LogonAuthorizeAttribute inherits from AuthorizeAttribute then you can override the AuthorizeCore method which returns a boolean (true meaning the user is authorized, false otherwise). In this method you can have something along the following pseudocode:
if(CheckIfMethodHasByPassAttribute()){
return CheckIfUserIsAllowedToRunThisMethod();
}
return CheckIfUserIsAllowedToRunThisController();
The method CheckIfUserIsAllowedToRunThisMethod would have whatever checks you need to do to determine if a user is allowed to run this method, while the CheckIfUserIsAllowedToRunThisController would have the code to check if a user is allowed access to the controller in general (which I assume is already in you LogonAuthorizeAttribute)
Lets stay I store content for a single user of my application at:
C:\Files{GUID}\
Those files may be something like:
C:\Files{GUID}\bunny.jpg
C:\Files{GUID}\unicorn.html
The end user should be presented with a "friendly" url.
http:// domain.com/files/{GUID}/bunny.jpg
That url somehow must pass through a controller or httpmodule or thingIdontknow to be authorized to view that file. These permissions may change day to day so files need to be checked for permissions often.
From what I've been reading this is entirely possible but I'm not sure what to code up next or if anybody has any insight here. HttpModule or Controller? I'm confused as to what needs to happen.
That url somehow must pass through a controller or httpmodule or thingIdontknow to be authorized to view that file. These permissions may change day to day so files need to be checked for permissions often.
The thing you don't know has a name. It's called an authorization action filter.
First let's suppose that you have registered a custom route for serving those files:
routes.MapRoute(
"MyImagesRoute",
"files/{id}/{name}",
new { controller = "Files", action = "Index" }
// TODO: you could constrain the id parameter to be a GUID.
// Just Google for a Regex that will match a GUID pattern and put here
// as route constraint
);
and then of course a corresponding controller to serve them:
public class FilesController: Controller
{
public ActionResult Index(Guid guid, string name)
{
var path = #"C:\files";
var file = Path.Combine(path, guid.ToString(), name);
file = Path.GetFullPath(file);
if (!file.StartsWith(path))
{
// someone tried to be smart and send
// files/{Guid}/..\..\creditcard.pdf as parameter
throw new HttpException(403, "Forbidden");
}
// TODO: adjust the mime type based on the extension
return File(file, "image/png");
}
}
Unfortunately at this stage there's nothing preventing a user ALPHA from requesting the file of user BETA, right? That's the scenario you would like to handle, aren't you?
So let's write a custom Authorize attribute to protect this controller action:
public class MyAuthorizeAttribute: AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var authorized = base.AuthorizeCore(httpContext);
if (!authorized)
{
// The user is not authenticated or doesn't have
// permissions to access this controller action
return false;
}
// at this stage we know that there's some user authenticated
// Let's get the Guid now from our route:
var routeData = httpContext.Request.RequestContext.RouteData;
var id = routeData.Values["id"] as string;
Guid guid;
if (!Guid.TryParse(id, out guid))
{
// invalid Guid => no need to continue any further, just deny access
return false;
}
// Now we've got the GUID that this user is requesting
// Let's see who this user is:
string username = httpContext.User.Identity.Name;
// and finally ensure that this user
// is actually the owner of the folder
return IsAuthorized(username, guid);
}
private bool IsAuthorized(string username, Guid guid)
{
// You know what to do here: hit your data store to verify
// that the currently authenticated username is actually
// the owner of this GUID
throw new NotImplementedException();
}
}
and then let's decorate our controller action with this authorization attribute:
public class FilesController: Controller
{
[MyAuthorize]
public ActionResult Index(Guid guid, string name)
{
// at this stage we know that the currently authenticated user
// is authorized to access the file.
var path = #"C:\files";
var file = Path.Combine(path, guid.ToString(), name);
file = Path.GetFullPath(file);
if (!file.StartsWith(path))
{
// someone tried to be smart and send
// files/{Guid}/..\..\creditcard.pdf as parameter
throw new HttpException(403, "Forbidden");
}
var file = Path.Combine(#"c:\files", guid.ToString(), name);
// TODO: adjust the mime type based on the extension
return File(file, "image/png");
}
}