I have security in my MVC application set up with an authorize attribute...
public class UserLoggedInAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext.HttpContext.Session["UserId"] == null)
{
var values = new { controller = "Home", action = "Index" };
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(values));
}
}
}
Then I also have a .ashx HttpHandler which is called by jQuery upload control....
public class UploadFile : IHttpHandler, IReadOnlySessionState
{
...
private bool EnsureSecureTransaction(HttpContext context)
{
if (context.Session["UserId"] == null)
{
return false;
}
else
{
return true;
}
return true;
}
}
When EnsureSecureTransaction() gets called the session is coming back null. But session that is read in my MVC action its not. I notice that I'm taking session from the filterContext though.
I have tried to change all the code to try and reference HttpContext.Current.Session like this
[HttpPost]
public ActionResult Logon(AdminModel model)
{
if (model.UserName == "x" && model.Password == "x")
{
HttpContext.Session["UserId"] = "true";
return RedirectToAction("CreateBlog", "Blog");
}
return View;
}
private bool EnsureSecureTransaction(HttpContext context)
{
if (context.Session["UserId"] == null)
{
return false;
}
else
{
return true;
}
return true;
}
But basically when I hit the EnsureSecureTransaction() block its still saying my Session["UserId"] is null and therefor not autehenticating the call to the .ashx file correctly.
Anyone know why this is? Whats the actual difference between AuthorizationContext and HttpContext with regards to the session they carry and how do I get round this problem?
Related
I have a IAuthenticationFilter will check the user group in SharePoint:
public class BasicAuthFilter : ActionFilterAttribute, IAuthenticationFilter
{
public void OnAuthentication(AuthenticationContext filterContext)
{
string userLoginName = filterContext.RequestContext.HttpContext.User.Identity.Name;
if (SecurityManager.Auth(userLoginName))
return;
else
filterContext.Result = new RedirectResult(new UrlHelper(filterContext.RequestContext).Action("AccessDenied", "Error"));
}
...
}
}
It will run on every request but except ErrorController
[AllowAnonymous]
public class ErrorController : Controller
...
// Display view and link for "Logout"
public ActionResult AccessDenied()
{
return View();
}
// GET: Logout
[OutputCache(VaryByParam = "*", Duration = 0, NoStore = true)] // disable caching
public ActionResult Logout()
{
string currentUser = User.Identity.Name;
int AuthenticationAttempts = 0;
if (Session["AuthenticationAttempts"] == null || !int.TryParse(Convert.ToString(Session["AuthenticationAttempts"]), out AuthenticationAttempts))
AuthenticationAttempts = 0;
AuthenticationAttempts += 1;
if (AuthenticationAttempts == 1)
{
Session["PrevUser"] = User.Identity.Name;
Session["AuthenticationAttempts"] = AuthenticationAttempts;
return new HttpUnauthorizedResult();
}
else if (string.Compare(Convert.ToString(Session["PrevUser"]), currentUser, true) == 0) // Somehow it will have echo back, ignore it
{
return new HttpUnauthorizedResult();
}
else
{
Session.Abandon();
Session.Clear();
return RedirectToAction("Index", "Home");
}
}
}
When the Error Controller return HttpUnauthorizedResult, the browser will prompt a login. And I can get the new user name from User.Identity.Name in ErrorController.
However, when it redirect to HomeController, the user was reset to original one, I tried following but still the same
filterContext.RequestContext.HttpContext.User.Identity.Name
filterContext.HttpContext.User.Identity.Name
filterContext.Principal.Identity.Name
Do I miss something or I should assign the principal after user input?
For anybody encounter the same issue, please make sure you have test it with IIS.
This method work but cannot work in IISExpress.
I have an MVC project that appears to work great, unless you look at the error log. After every page is returned successfully, there is an attempt to load site.example.com/null and I have no idea why. Here's a snippet from Fiddler:
It has no effect to the user, but it is annoying. Here's a sample of the code that's being called:
public class GuidanceController : Controller
{
public ActionResult Index()
{
return View();
}
}
I have nothing unusual in the view and I haven't had to change the RouteConfig. I do have a custom authorization class, like so:
namespace MyProj.Filters {
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true)]
public class MyProjAuthorizeAttribute : AuthorizeAttribute
{
public AccessLvl[] AccessLvls;
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
AuthorizationClient authClient = new AuthorizationClient();
var authorized = base.AuthorizeCore(httpContext);
if (!authorized)
return false;
int intId = 0;
string requestId = httpContext.Request.Path.Split('/').Last(); //get ID sent with new page
string referrerId = httpContext.Request.UrlReferrer?.AbsolutePath.Split('/').Last(); //get ID sent with old page
if (int.TryParse(requestId, out intId) != true) //prefer new ID, if available
int.TryParse(referrerId, out intId); //else just use old id
List<AccessLvl> userAccessLevels = authClient.GetAccessLevels((intId == 0) ? null : intId.ToString());
foreach (AccessLvl level in AccessLvls)
{
if (userAccessLevels.Contains(level))
{
return true;
}
}
return false;
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary(
new
{
controller = "Error",
action = "Unauthorized",
urlReferrer = filterContext.RequestContext.HttpContext.Request.Url
})
);
}
}
}
What am I overlooking? Any ideas are greatly appreciated.
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"*/
};
Ok following up with this thread, here's what I came up with...
public class SharweAuthorizeAttribute : AuthorizeAttribute
{
private bool isAuthenticated = false;
private bool isAuthorized = false;
public new string[] Roles { get; set; }
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (SessionManager.CheckSession(SessionKeys.User) == true)
{
isAuthenticated = true;
foreach (string role in Roles)
{
if (RolesService.HasRole((string)role))
isAuthorized = true;
}
}
return (isAuthenticated && isAuthorized);
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (!isAuthenticated)
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary
{
{ "action", "User" },
{ "controller", "Login" }
});
} else if(!isAuthorized) {
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary
{
{ "action", "Home" },
{ "controller", "Error" }
});
}
}
}
How/why I came up with this? Because I believe the AuthorizeAttribute workflow goes as follows:
First, AuthorizeCore is triggered. If it returns true, the user is authorized. If it returns false, HandleUnauthorizedRequest is triggered. Is that right?
I read somewhere that I need to use the new keyword to override a property. Therefore, this is how I overrode the Roles property. But what if the overriding property was of a different type of the initial one (the one in the base class), does that also hide it or creates a totally different property?
So what do you think? Should that actually work? I cannot test it now because I haven't set up the UI (waiting for the designer to get done with the design)... In fact, this is the first time I appreciate the benefits of TDD, I used to think it's utterly stupid and useless, but I was wrong :)
P.S: On this thread, #tvanfosson is setting the CachePolicy of the context (I think), could someone explain that and why I might need to do that please?
Thanks in advance.
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
private readonly bool _authorize;
private readonly string[] _roles;
public CustomAuthorizeAttribute(string roles)
{
_authorize = true;
_roles = roles.Split(',');
}
public CustomAuthorizeAttribute(string roles, bool isAdminPath)
{
_authorize = true;
_roles = roles.Split(',');
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
//if controller have role auth and user is not loged
if(_authorize && !httpContext.User.Identity.IsAuthenticated)
return false;
// if controller have role auth and user is loged
if(_roles != null)
{
//grab user roles from DB
var UserRole = RoleRepository.GetUserRole(new Guid(httpContext.User.Identity.Name));
if (_roles.Contains(UserRole))
return true;
}
return false;
}
}
In Controller
[CustomAuthorize("Administrator,Company,OtherRole")]
public ActionResult Test(){
return View();
}
I'm modifying a system written in c# MVC at the moment.
I've just built in an extra bit of functionality in the Administrator area that allows the administrator create a user account that has limited administrator functionality. I've put the following over each of the controllers for the new functionality:
[Authorize(Roles = "Administrator")]
However, if I log in using limited administrator account, and navigate to this page, it lets me through.
I'm stumped because I appear to be doing this the right way but I'm also fairly new to MVC, is there anything else I can check? I haven't changed anything in the web.config file so that should be ok.
I know there's limited information above, not looking for a ready-made solution, more advice on what I can check to correct the issue.
thanks
EDIT:
This is how the new role/account was created. Go easy too, this is a first ditch attempt, there's not much validation.
[Authorize(Roles = "Administrator")]
[HttpPost]
public ActionResult AddSalesManager(App.Web.Areas.Administrator.Models.SalesManager model, FormCollection formValues)
{
if (formValues["Cancel"] != null)
{
return RedirectToAction("Index");
}
if (!string.Equals(model.password, model.confirmpassword))
{
ModelState.AddModelError("password", "Password and Confirmation must match");
}
if (ModelState.IsValid)
{
using (ModelContainer ctn = new ModelContainer())
{
// First, create the user account inside the ASP.Net membership system.
//
Membership.ApplicationName = "App";
Roles.ApplicationName = "App";
if (!Roles.RoleExists("LimitedAdmin"))
Roles.CreateRole("LimitedAdmin");
// MembershipCreateStatus createStatus = MembershipService.CreateUser(model.email, model.password, model.email);
if (Membership.GetUser(model.email) == null)
{
Membership.CreateUser(model.email, model.password);
Roles.AddUserToRole(model.email, "LimitedAdmin");
}
}
}
return RedirectToAction("Index");
}
Role attribute
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public class PermissionsAttribute : ActionFilterAttribute
{
private readonly PermissionsType required;
public PermissionsAttribute()
{
}
public PermissionsAttribute(PermissionsType required)
{
this.required = required;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// Log("OnActionExecuting", filterContext.RouteData);
HttpSessionStateBase session = filterContext.HttpContext.Session;
Controller controller = filterContext.Controller as Controller;
//This is uesd to redirect to same controller but differnect action
// controller.HttpContext.Response.Redirect("./Login");
var rjasthan = filterContext;
var URK = filterContext.HttpContext.Request.RawUrl;
if (session["UserPermissions"] != null)
{
if (!CheckPermissions((UserPermission)session["UserPermissions"]))
{
// this is used to signout from sesssion
// filterContext.HttpContext.GetOwinContext().Authentication.SignOut();
filterContext.Controller.TempData["AuthenticationMessages"] = "You are not authorized to access";
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary{
{ "controller", "Home" },{ "action", "UnAuthorizeAccess" }});
}
}
base.OnActionExecuting(filterContext);
}
protected bool CheckPermissions(UserPermission model)
{
bool result = false;
if (this.required == (PermissionsType.Add))
{
if (model.AddRight)
result = true;
}
else if (this.required == (PermissionsType.View))
{
if (model.ViewRight)
result = true;
}
else if (this.required == (PermissionsType.Edit))
{
if (model.EditRight)
result = true;
}
else if (this.required == (PermissionsType.Delete))
{
if (model.DeleteRight)
result = true;
}
else if (this.required == (PermissionsType.View | PermissionsType.Edit))
{
if (model.ViewRight && model.EditRight)
{
result = true;
}
}
else if (this.required == (PermissionsType.Add | PermissionsType.Edit))
{
if (model.AddRight && model.EditRight)
{
result = true;
}
}
return result;
}
private void Log(string methodName, RouteData routeData)
{
var controllerName = routeData.Values["controller"];
var actionName = routeData.Values["action"];
var message = String.Format("{0} controller:{1} action:{2}", methodName, controllerName, actionName);
Debug.WriteLine(message, "Action Filter Log");
}
}
[Flags]
public enum PermissionsType
{
View = (1 << 0),
Add = (1 << 1),
Edit = (1 << 2),
Delete = (1 << 3),
Admin = (View | Add | Edit | Delete)
}
[Permissions(PermissionsType.Add)]
public ActionResult Register()
{
return this.AjaxableView();
}
What do you expect from this code?
With this attribute you gain all users in the administrator role the right to execute this controller action no matter how limited the account is.