MVC HTTP Error 403.14 - Forbidden after create new record - asp.net-mvc

1- AuthorizeUserAttribute.cs is class for costume authorize attribute
public class AuthorizeUserAttribute : AuthorizeAttribute
{
public string AccessLevel { get; set; }
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var isAuthorized = base.AuthorizeCore(httpContext);
if (!isAuthorized)
return false;
if (this.AccessLevel.Contains("Admin"))
{
return true;
}
else return false;
}
2- this is my controller
[AuthorizeUser(AccessLevel = "Admin")]
public class ProductsController : Controller
{
private DataBaseContext db = new DataBaseContext();
public ActionResult Index()
{
var product = db.Product.Include(p => p.ProductGroup);
return View(product.ToList());
}
}
[AuthorizeUser(AccessLevel = "Admin")]
public ActionResult Create([Bind(Include = "Product_Id,ProductName,Description,PicUrl,Group_Id")] Product product)
{
if (ModelState.IsValid)
{
db.Product.Add(product);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.Group_Id = new SelectList(db.ProductGroups, "Group_Id", "GreoupName", product.Group_Id);
return View(product);
}
3-FilterConfig.cs in start_up folder
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
filters.Add(new AuthorizeAttribute());
filters.Add(new AuthorizeUserAttribute());
}
}
4-Global.asax.cs
void Application_Start(object sender, EventArgs e)
{
// Code that runs on application startup
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
RouteConfig.RegisterRoutes(RouteTable.Routes);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.NameIdentifier;
}
5- Admin1Controller.cs for login and etc...
[HttpPost]
public ActionResult Login(LoginViewModel model)
{
if (!ModelState.IsValid) //Checks if input fields have the correct format
{
return View(model); //Returns the view with the input values so that the user doesn't have to retype again
}
if(model.Email == "info#psmgroups.com" & model.Password == "#1234psm")
{
var identity = new ClaimsIdentity(new[] {
new Claim(ClaimTypes.Name,"Admin" ),
new Claim(ClaimTypes.Email, "info#psmgroups.com"),
new Claim(ClaimTypes.Role,"Admin")
}, "ApplicationCookie");
var ctx = Request.GetOwinContext();
var authManager = ctx.Authentication;
authManager.SignIn(identity);
return Redirect(GetRedirectUrl(model.ReturnUrl));
}
ModelState.AddModelError("", "incorrect UserName or pass");
return View(model);
}
after create new product and return to products/ show HTTP Error 403.14 - Forbidden page. while write product/Index show correct page

First, there's no code here that actually ever sets the AccessLevel property on your custom attribute. Maybe you just didn't post it, but if this is all your code, then it's fairly obvious why this doesn't work: AccessLevel is always null, and therefore never contains the string "Admin".
That said, you don't even need a custom attribute here. AuthorizeAttribute already handles roles. It seems you're trying to implement some sort of parallel role-like functionality, but that's a waste of time. Just do:
[Authorize(Roles = "Admin")]
And call it a day.

Related

Custom Authentication for different areas in mvc

I have a MVC project with 2 areas: Admin and Client. I also have a login page in the main controller. What I want to do is to Authenticate a user based on its roles. If the user is for client they can't login to admin and the other way around.
For example if you try Localhost/admin, the code checks if the user is authorised. If not it redirects you to Localhost/admin/AccountLogin. The same for Localhost/client to Localhost/client/account/login.
I want to use a customAuthorize rather than [Authorize(Roles="Admin")].
everything works fine if I don't use roles, but the problem is if you login as client you can simply change the url and go to admin. So I tried to use roles.
In admin area:
An account Controller:
public class AccountController : MainProject.Controllers.AccountController
{ }
A home controller:
[CustomAuthorize("Admin")]
public class HomeController : Controller
{
public ActionResult HomePage()
{
return View();
}
}
The custom Authorise:
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
private string _loginPage { get; set; }
private string _customRole { get; set; }
public CustomAuthorizeAttribute(string userProfilesRequired)
{
_customRole = userProfilesRequired;
_loginPage = "/" + _customRole + "/Account/Login";
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
var formsIdentity = filterContext.HttpContext.User.Identity as System.Web.Security.FormsIdentity;
// I want to check if the role of current user is the same as the controller If not redirect to the /account/login page.
var validRole = this.Roles == _customRole;//filterContext.HttpContext.User.IsInRole(_customRole);
if (filterContext.HttpContext.User.Identity.IsAuthenticated)
{
if (!validRole)
{
filterContext.HttpContext.Response.Redirect(_loginPage);
}
}
else
{
filterContext.HttpContext.Response.Redirect(_loginPage);
}
base.OnAuthorization(filterContext);
}
}
The Account Controller in Main Controller:
public class AccountController : Controller
{
[AllowAnonymous]
public ActionResult Login()
{
return View();
}
//
// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginModel model, string ReturnUrl)
{
try
{
if (ModelState.IsValid)
{
if (model.UserName == "Arash" && model.Password == "123")
{
FormsAuthentication.SetAuthCookie(model.UserName, false);
//I need to set the roles here but not sure how
return RedirectToAction("homePage", "Home", new { area = GetArea() });
}
}
ModelState.AddModelError("", "The user name or password provided is incorrect.");
return View(model);
}
catch (Exception ex)
{
ModelState.AddModelError("", "Error: " + ex.Message);
return View(model);
}
}
}
and it the web config:
<forms loginUrl="~/Account/Login" timeout="200" />
</authentication>
<authorization>
<allow roles="Admin,Client" />
</authorization>
I searched a lot in the web but couldn't find a proper answer. I appreciate if you Could help me out to correctly implement this authorisation in MVC.
I just want to know how can I set a role to a user when login. At the moment if I set a user in login, it can't remember when it gets to CustomAuthorize class.
Any help?
Cheers,
There are a lot of ways to this but I will tell you what I used in this case.
You don't actually need to create a custom Authorization Attribute but instead make use of PostAuthenticateRequest event Handler in Global.asax given that you have a "table" roles in your database.
Add the code below in Global.asax
public override void Init()
{
this.PostAuthenticateRequest += new EventHandler(MvcApplication_PostAuthenticateRequest);
base.Init();
}
void MvcApplication_PostAuthenticateRequest(object sender, EventArgs e)
{
if (User.Identity.IsAuthenticated && User.Identity.AuthenticationType == "Forms")
{
string[] roles = GetRoleOfUser(Context.User.Identity.Name);
var newUser = new GenericPrincipal(Context.User.Identity, roles);
Context.User = Thread.CurrentPrincipal = newUser;
}
}
public string[] GetRoleOfUser(string username)
{
string[] usersInRole;
// Get the Role of User from the Database
// Should be of String Array
// Example Query to Database: 'Select UserRole FROM User WHERE Username = "arash"'
// It doesnt matter if user has One or more Role.
return usersInRole;
}
Then your account controller should be this.
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginModel model, string ReturnUrl)
{
try
{
if (ModelState.IsValid)
{
if (model.UserName == "Arash" && model.Password == "123")
{
FormsAuthentication.SetAuthCookie(model.UserName, false);
HttpCookie authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
return RedirectToAction("HomePage", "Home");
}
}
ModelState.AddModelError("", "The user name or password provided is incorrect.");
return View(model);
}
catch (Exception ex)
{
ModelState.AddModelError("", "Error: " + ex.Message);
return View(model);
}
}
Now for example there is an Action in your HomeController that can only be access by Admin. You can just decorate the action with Authorize attribute like this below.
HomeController.cs
[Authorize(Roles = "Admin")]
public ActionResult AdminHomepage()
{
//For Admin Only
return View();
}
[Authorize(Roles = "Client")]
public ActionResult ClientHomepage()
{
//Client only Homepage, User with Role "Admin" cant go here.
return View();
}
[AllowAnonymous]
public ActionResult HomePageForAll()
{
//For Everyone
return View();
}
[Authorize(Roles = "Client,Admin")]
public ActionResult HomePageForClientAndAdmin()
{
return View();
}
public ActionResult HomePage()
{
return View();
}
The user will be redirected to Login URL if they are not authorized given that it is specified in Web.config (Which you already have set).
I have an action method and that can be accessed by Admin only
// Action Methods
[AuthorizationService] // My custom filter ,you can apply at controller level
public ActionResult ProjectList(Employee emp)
{
// do some work
}
//Employee class
public class Employee
{
string Name{get;set;}
string Role{get;set;}
}
// My custom filter
class AuthorizationService : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
Employee = filterContext.ActionParameters["emp"] as Employee;
if (Employee.Role!="Admin")
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary(
new { action = "Login", Controller ="Home"}));
}
}
}

Trigger authorization validation manually

I've a custom AuthorizeAttribute in my website. It has some logic about the Result created for unathorized requests.
In some cases, I want to trigger its validation manually*. I don't know if its possible. As I haven't found how to do that, I thought that I could extract the logic to get the Result to a diferrent method, and call it when I want. But then I don't know how to execute the ActionResult (outside de controllers).
How can I do to manually execute authorize validation? If not possible, how can I do to execute an ActionResult outside a controller?
*I need to trigger it manually because some request may pass the validation (because the session is created) and then, when accessing my services, found that the session was closed by someone else. I wouldn't like to add a call to the services in OnAuthorization to reduce services calls.
I'm not sure if its the best, but I've found a way to get it working (still listening for better answers).
When I call the services and notice that the work session has expired, all I do is removing the active user in the web session.
My custom authorize attribute also implements IResultFilter and IExceptionFilter.
In both OnResultExecuted and OnException I validate the active user once more. If the session was removed, then apply the same ActionResult that I would apply in OnAuthorization.
Here is the final class:
public class CustomAuthorizeAttribute : AuthorizeAttribute, IResultFilter, IExceptionFilter
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
ActionResult result = Validate(filterContext.HttpContext);
if (result != null)
filterContext.Result = result;
}
public void OnResultExecuted(ResultExecutedContext filterContext)
{
ActionResult result = Validate(filterContext.HttpContext);
if (result != null)
filterContext.Result = result;
}
public void OnResultExecuting(ResultExecutingContext filterContext)
{
}
public void OnException(ExceptionContext filterContext)
{
ActionResult result = Validate(filterContext.HttpContext);
if (result != null)
{
filterContext.Result = result;
filterContext.ExceptionHandled = true;
}
}
public static ActionResult Validate(HttpContextBase httpContext)
{
if (UserActiveInSession)
return null;
// Different rules to build an ActionResult for this specific case.
}
}
I did not get Diego answer's, But Just simply answering the title, I got it to work like that, You can use it as attribute on controllers actions and also trigger it manually at any place in C# or in Razor views.
namespace SomeNameSpace
{
public class CustomAuthorizeAttributeMVC : AuthorizeAttribute
{
private readonly string[] rolesParams;
public CustomAuthorizeAttributeMVC(params string[] roles)
{
this.rolesParams = roles;
}
public bool IsAuthorized { get {
//Do your authorization logic here and return true if the current user has permission/role for the passed "rolesParams"
string[] allowedRoles = new string[] {"role 1", "role 2", "role 3"};
return allowedRoles.Intersect(rolesParams).Any(); //for the example
}
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
return this.IsAuthorized;
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
//...
}
}
public class AuthorizeHelper
{
public static bool HasPermission(params string[] roles)
{
return new CustomAuthorizeAttributeMVC(roles).IsAuthorized;
}
}
}
Usage example:
[CustomAuthorizeAttributeMVC("role 2")]
public ActionResult SomeAction()
{
return Content("Authorized !");
}
public ActionResult SomeOtherAction()
{
if(AuthorizeHelper.HasPermission("role 2"))
{
return Content("Authorized !");
}
return Content("401 Not Authorized !");
}
And as said, it can be used in Razor views by calling it normally
#if(AuthorizeHelper.HasPermission("role 2")) {
//...
}
Thanks

Inconsistent accessibility: parameter type is less accessible

public class UserController : Controller
{
//
// GET: /User/
public ActionResult Register()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Register(User U)
{
if (ModelState.IsValid)
{
using (MyDatabaseEntities dc = new MyDatabaseEntities())
{
dc.Users.Add(U);
dc.SaveChanges();
ModelState.Clear();
U = null;
ViewBag.Message = "Successfully register Done";
}
}
return View(U);
}
}
I suspect, but without the full error message giving us the type and location in the code it is something of a guess, that type User is protected or internal.

Trying to register a user from another controller other than AccountController gives an error

Totally messed up my last question so posting a new one.
MyTestController:
[HttpPost]
public async Task<ActionResult> Index(MyTestViewModel viewModel)
{
if (ModelState.IsValid)
{
AccountController ac = new AccountController();
var user = new ApplicationUser()
{
UserName = viewModel.Email
};
var result = await ac.UserManager.CreateAsync(user, viewModel.Password);
if (result.Succeeded)
{
await ac.SignInAsync(user, isPersistent: true);
}
else
{
ac.AddErrors(result);
}
The SignInAsync method in AccountController (changed this from private to public):
public async Task SignInAsync(ApplicationUser user, bool isPersistent)
{
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
var identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);
}
While trying to register the user it gives me the following error:
Exception Details: System.NullReferenceException: Object reference not set to an instance of an object.
Source Error:
Line 411: get
Line 412: {
Line 413: return HttpContext.GetOwinContext().Authentication;
Line 414: }
Line 415: }
Those lines in AccountController:
private IAuthenticationManager AuthenticationManager
{
get
{
return HttpContext.GetOwinContext().Authentication;
}
}
Everything in the AccountController is default MVC 5 app stuff.
It is not possible to call these methods from another controller, like in my example above?
And why am I getting a NullReferenceException on line 413?
Calling a Controller method from another Controller is difficult because of properties like the HttpContext which need to be properly initialized. This is usually done by the MVC framework which creates the controller using a ControllerFactory and at some point during this process the protected method Initialize is called on the controller which ensures that the HttpContext property is set.
This is why you get the exception on line 413, because the Initialize method hasn't been called on the controller you created using the new operator.
I think it would be easier to refactor out the functionality you want to share.
E.g. if both AccountController and your MyTestController holds a reference to something like this
public class AccountManager
{
public UserManager<ApplicationUser> UserManager { get; private set; }
public HttpContextBase HttpContext { get; private set; }
public AccountManager()
: this(new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext())))
{
}
public AccountManager(UserManager<ApplicationUser> userManager)
{
UserManager = userManager;
}
public void Initialize(HttpContextBase context)
{
HttpContext = context;
}
private IAuthenticationManager AuthenticationManager
{
get
{
return HttpContext.GetOwinContext().Authentication;
}
}
public async Task SignInAsync(ApplicationUser user, bool isPersistent)
{
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
var identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);
}
}
You would then modify the AccountController like this:
public class AccountController : Controller
{
public AccountController()
: this(new AccountManager())
{
}
public AccountController(AccountManager accountManager)
{
AccountManager = accountManager;
}
protected override void Initialize(System.Web.Routing.RequestContext requestContext)
{
base.Initialize(requestContext);
AccountManager.Initialize(this.HttpContext);
}
public UserManager<ApplicationUser> UserManager
{
get
{
return AccountManager.UserManager;
}
}
public AccountManager AccountManager { get; private set; }
And your MyTestController would be like this:
public class MyTestController : Controller
{
public MyTestController ()
: this(new AccountManager())
{
}
public MyTestController (AccountManager accountManager)
{
AccountManager = accountManager;
}
protected override void Initialize(System.Web.Routing.RequestContext requestContext)
{
base.Initialize(requestContext);
AccountManager.Initialize(this.HttpContext);
}
public AccountManager AccountManager { get; private set; }
[HttpPost]
public async Task<ActionResult> Index(MyTestViewModel viewModel)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser()
{
UserName = viewModel.Email
};
var result = await AccountManager.UserManager.CreateAsync(user, viewModel.Password);
if (result.Succeeded)
{
await AccountManager.SignInAsync(user, isPersistent: true);
}
else
{
AddErrors(result); //don't want to share this a it updates ModelState which belongs to the controller.
}
Update:
Had to make som minor changes:
I had to change the UserManager property since the Dispose method uses the setter method:
private UserManager<ApplicationUser> _userManager;
public UserManager<ApplicationUser> UserManager
{
get { return AccountManager.UserManager; }
private set { _userManager = value; }
}
protected override void Dispose(bool disposing)
{
if (disposing && UserManager != null)
{
UserManager.Dispose();
UserManager = null;
}
base.Dispose(disposing);
}
I had to add the AddErrors method to MyTestController (as you pointed out that we don't want to share that method):
private void AddErrors(IdentityResult result)
{
foreach (var error in result.Errors)
{
ModelState.AddModelError("", error);
}
}
I re-added this line to the AccountManager property in the AccountManager class (not really related to the question but I had it in my project)
UserManager.UserValidator = new UserValidator(UserManager) { AllowOnlyAlphanumericUserNames = false };
For me it working like a charm after setting the current ControllerContext to AccountControllerContext. Not sure if there are any drawbacks with this approach.
//This is employee controller class
public ActionResult Create([Bind(Include = "EmployeeId,FirstName,LastName,DOJ,DOB,Address,City,State,Mobile,Landline,ReportsTo,Salary")] Employee employee)
{
if (ModelState.IsValid)
{
AccountController accountController = new AccountController();
accountController.ControllerContext = this.ControllerContext;
//accountController.UserManager;
var userId = accountController.RegisterAccount(new RegisterViewModel { Email = "temp#temp.com", Password = "Pravallika!23" });
if (!string.IsNullOrEmpty(userId))
{
employee.UserId = userId;
employee.CreatedBy = User.Identity.GetUserId();
db.Employees.Add(employee);
db.SaveChanges();
return RedirectToAction("Index");
}
}
//customized method in AccountController
public string RegisterAccount(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser() { UserName = model.Email, Email = model.Email };
IdentityResult result = UserManager.Create(user, model.Password);
//to add roles
//UserManager.AddToRole(user.Id, "Admin");
if (result.Succeeded)
{
return user.Id;
}
else
{
AddErrors(result);
}
}
// If we got this far, something failed
return null;
}

How can I use an action filter in ASP.NET MVC to route to a different view but using the same URL?

Is it possible to make a filter that, after a controller action has been (mostly) processed, checks for a certain test condition and routes to a different view transparently to the user (i.e., no change in the URL)?
Here would be my best guess at some pseudocode:
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
// If some condition is true
// Change the resulting view resolution to XYZ
base.OnResultExecuting(filterContext);
}
filterContext.Result = new ViewResult
{
ViewName = "~/Views/SomeController/SomeView.cshtml"
};
This will short-circuit the execution of the action.
also you can return view as from your action
public ActionResult Index()
{
return View(#"~/Views/SomeView.aspx");
}
This is what I ended up doing, and wrapped up into a reusable attribute and the great thing is it retains the original URL while redirecting (or applying whatever result you wish) based on your requirements:
public class AuthoriseSiteAccessAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
// Perform your condition, or straight result assignment here.
// For me I had to test the existance of a cookie.
if (yourConditionHere)
filterContext.Result = new SiteAccessDeniedResult();
}
}
public class SiteAccessDeniedResult : ViewResult
{
public SiteAccessDeniedResult()
{
ViewName = "~/Views/SiteAccess/Login.cshtml";
}
}
Then just add the attribute [SiteAccessAuthorise] to your controllers you wish to apply the authorisation access to (in my case) or add it to a BaseController. Make sure though the action you are redirecting to's underlying controller does not have the attribute though, or you'll be caught in an endless loop!
I have extended the AuthorizeAttribute of ASP.NET MVC action filter as DCIMAuthorize, in which I perform some security checks and if user is not authenticated or authorized then action filter will take user to access denied page. My implementation is as below:
public class DCIMAuthorize : AuthorizeAttribute
{
public string BusinessComponent { get; set; }
public string Action { get; set; }
public bool ResturnJsonResponse { get; set; }
public bool Authorize { get; set; }
public DCIMAuthorize()
{
ResturnJsonResponse = true;
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
try
{
//to check whether user is authenticated
if (!httpContext.User.Identity.IsAuthenticated)
return false;
//to check site level access
if (HttpContext.Current.Session["UserSites"] != null)
{
var allSites = (VList<VSiteList>)HttpContext.Current.Session["UserSites"];
if (allSites.Count <= 0)
return false;
}
else
return false;
// use Authorize for authorization
Authorize = false;
string[] roles = null;
//get roles for currently login user
if (HttpContext.Current.Session["Roles"] != null)
{
roles = (string[])HttpContext.Current.Session["Roles"];
}
if (roles != null)
{
//for multiple roles
string[] keys = new string[roles.Length];
int index = 0;
// for each role, there is separate key
foreach (string role in roles)
{
keys[index] = role + "-" + BusinessComponent + "-" + Action;
index++;
}
//access Authorization Details and compare with keys
if (HttpContext.Current.Application["AuthorizationDetails"] != null)
{
Hashtable authorizationDetails = (Hashtable)HttpContext.Current.Application["AuthorizationDetails"];
bool hasKey = false;
foreach (var item in keys)
{
hasKey = authorizationDetails.ContainsKey(item);
if (hasKey)
{
Authorize = hasKey;
break;
}
}
}
}
return base.AuthorizeCore(httpContext);
}
catch (Exception)
{
throw;
}
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
try
{
filterContext.Controller.ViewData["ResturnJsonResponse"] = ResturnJsonResponse;
base.OnAuthorization(filterContext);
if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
// auth failed, redirect to login page
filterContext.Result = new HttpUnauthorizedResult();
return;
}
if (!Authorize)
{
//Authorization failed, redirect to Access Denied Page
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary{{ "controller", "Base" },
{ "action", "AccessDenied" }
//{ "returnUrl", filterContext.HttpContext.Request.RawUrl }
});
}
}
catch (Exception)
{
throw;
}
}
}
You Can Also Save All Route File Path in a Static And Use it Like This :
public static class ViewPath
{
public const string SomeViewName = "~/Views/SomeViewName.cshtml";
//...
}
And into Your ActionFilter :
context.Result = new ViewResult()
{
ViewName = ViewPath.SomeViewName /*"~/Views/SomeViewName.cshtml"*/
};

Resources