I'm having an issue with an internal MVC site. I may have titled this wrong as I don't know exactly where the issue lies. I have the following custom authorize attribute:
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
public CustomAuthorizeAttribute(params string[] roles)
{
using (var dataLayer = new CarrierBoundEntities())
{
string userNames = string.Empty;
foreach (var user in dataLayer.tbl_PremiumWriteOffs_Users)
{
if (roles.Contains(user.Role))
{
userNames += user.Username + ",";
}
}
if (userNames.Length > 0)
{
// Remove last comma
userNames.Remove(userNames.Length - 1);
}
Users = userNames;
}
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
}
}
The datalayer is the default DbContext created by the Entity Framework. There is a view that allows users with an admin role to add/edit users. Here is the controller that handles a post to edit a user:
[HttpPost]
[ValidateAntiForgeryToken]
[CustomAuthorize(new string[] { "Admin" })]
public ActionResult UsersEditView(UsersVM viewModel)
{
using (var dataLayer = new CarrierBoundEntities())
{
var userToEdit = dataLayer.tbl_PremiumWriteOffs_Users.SingleOrDefault(x => x.ID == viewModel.UserSubmit.ID);
if (userToEdit == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
// If the username was changed then check and display error if it's the same as another entry
if (userToEdit.Username.Equals(viewModel.UserSubmit.Username, StringComparison.OrdinalIgnoreCase) == false)
{
foreach (var user in dataLayer.tbl_PremiumWriteOffs_Users)
{
if (user.Username.Equals(viewModel.UserSubmit.Username, StringComparison.OrdinalIgnoreCase))
{
ModelState.AddModelError("UserSubmit.Username", "A user with this username already exists.");
break;
}
}
}
if (ModelState.IsValid)
{
userToEdit.Username = viewModel.UserSubmit.Username;
userToEdit.Role = viewModel.UserSubmit.Role;
userToEdit.UserLastModified = this.User.Identity.Name;
userToEdit.DateLastModified = DateTime.Now;
dataLayer.SaveChanges();
return RedirectToAction("UsersView");
}
viewModel.RoleSelect = GetRoleSelectList();
return View(viewModel);
}
}
Now when I run it locally on my machine things work fine, but when deployed on a server something doesn't get updated when a new user is added or the role of an existing user is changed. New users still don't get access to any part of the site, and a user that is changed from admin to user will still have access to the admin areas. It stays this way until the app is restarted on the server.
The odd thing is that after making a change, the change is visible on both the front end and back end, so it seems that both the database and entity context are being updated fine. So I'm thinking it might be the custom authorize attributes that aren't updating with the new list of usernames, but I really have no idea and am having trouble debugging since it works as it should locally.
Any help would be greatly appreciated.
The way you are trying to extend the AuthorizeAttribue is wrong. You need to do something like this:
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
private string[] _roles;
public CustomAuthorizeAttribute(params string[] roles)
{
_roles = roles;
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var user = httpContext.User;
var isAuthorized = false;
// check in the database to see if the user
// is in one of the Roles in _roles and therefore authorized...
if(/* some code to check if the user is authorized */)
{
isAuthorized = true;
}
return isAuthorized;
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
}
}
Related
I have an action in my controller design for users already register in my system:
[Authorize]
public ActionResult getUserData()
{
string UserId = User.Identity.GetUserId();
return getDataFromDB(UserID);
}
And is working OK. Also redirect to the LOGIN page if you arent authenticated yet.
But I also want to have a dummy function getFakeData() for anonymous users visiting the page so they can see the like a demo of the page.
Where should I put the validation to see if user is authenticated or not and change the behaviour?
On the webpage I can set the dataUrl based if user is authenticated. I can make a separated function without [Authorize] tag
#if (Request.IsAuthenticated)
{
dataUrl = '=/Project/Controller/getUserData'
} else {
dataUrl = '=/Project/Controller/getFakeData'
}
Or can I do it on the same action controller and checking if user is authenticated or not? But not sure is that is possible.
Or is a better way to do it?
There are various ways to do this but here is my recommendation
// For ASP.Net MVC 5 simply inherit from AuthorizationAttribute and override the methods.
public class AccessControlAttribute : Attribute, IAuthorizationFilter
{
private readonly Roles role;
public AccessControlAttribute(Roles role) {
this.role = role;
}
private Boolean AuthorizationCore(AuthorizationFilterContext context) {
var username = context.HttpContext.Request.Cookies["loginCookie_username"];
var password = context.HttpContext.Request.Cookies["loginCookie_password"];
if (role == Roles.FakeFullAccess) {
username = "FAKE";
goto final;
}
//In ASP.Net MVC 5 use Ninject for dependency injection and get the service using : [NinjectContext].GetKernel.Get<DbContext>();
DbContext db = (DbContext) context.HttpContext.RequestServices.GetService(typeof(DbContext));
if (username != null && password != null) {
var findUser = db.Set<Login>().Find(username);
if (findUser != null && findUser.Password.Equals(password) && findUser.RoleId == (int)role) {
goto final;
}
}
return false;
final: {
context.HttpContext.User.AddIdentity(new System.Security.Principal.GenericIdentity(username));
return true;
}
}
private void HandleUnauthorizedRequest(AuthorizationFilterContext context) {
context.Result = new RedirectToRouteResult(new {
area = "",
controller = "",
action = ""
});
}
public void OnAuthorization(AuthorizationFilterContext context)
{
if (AuthorizationCore(context))
{
// If using a combination of roles, you have to unmask it
if (role == Roles.FakeFullAccess) {
context.HttpContext.Request.Headers.Add("Render", "FakeAccess");
}
else if (role == Roles.Admin)
{
context.HttpContext.Request.Headers.Add("Render", "AdminAccess");
}
}
else {
HandleUnauthorizedRequest(context);
}
}
}
[Flags]
public enum Roles
{
FakeFullAccess = 0,
ReadOnly = 1,
Admin = 2,
Supervisor = 1 << 2,
AnotherRole = 1 << 3
}
in your view you can read the added header and customize the view (in ASP.Net Core there's no access to ControllerContext and ViewBag, if using ASP.Net MVC 5 you don't need to use the header trick)
// For ASP.Net MVC 5 use the ViewBag or ViewData
#Html.Partial(HttpContext.Request.Header["Render"])
//Assuming this renders the menu with proper functions.
Now you have fully customizable role based authentication system with fake access for testing.
Update:
To consume the attribute do the following
[AccessControl(Role.Admin)]
public TestController: Controller {
...
}
// Dedicated for testing
[AccessControl(Role.FakeAccess)]
public PreviewController: TestCoontroller{}
You can also combine roles if required like [AccessControl(Role.FakeAccess | Role.ReadOnly)] but you have to implement an unmasking method.
I have an action that is being authorized as following:
[Authorize(Roles = "Admin, Agency, Subscribed, Normal")]
public ActionResult LocationProfile()
{}
what I need is to add another filter that gets executed before the authorization filter and if the result is true is doesn't execute the authorize attribute and proceeds to execute my action directly (LocationProfile())
is there any way to accomplish this task
You will have to roll your own version of the Authorize attribute that has that functionality built in. Included from the C# corner post linked above:
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
Entities context = new Entities(); // my entity
private readonly string[] allowedroles;
public CustomAuthorizeAttribute(params string[] roles)
{
this.allowedroles = roles;
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
bool authorize = false;
foreach (var role in allowedroles)
{
var user = context.AppUser.Where(m => m.UserID == GetUser.CurrentUser/* getting user form current context */ && m.Role == role &&
m.IsActive == true); // checking active users with allowed roles.
if (user.Count() > 0)
{
authorize = true; /* return true if Entity has current user(active) with specific role */
}
}
return authorize;
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
filterContext.Result = new HttpUnauthorizedResult();
}
}
In the AuthorizeCore method, you need to add your check to account for the situation describe.
My project got pages with [Authorize] where user have to log in to visit those pages.
Upon successful login with same userid and password as in database, the current users id get stored in session. But how do I do I authenticate/allow user to visit pages with [Authorize]?
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Login(User u)
{
if (ModelState.IsValid) //this is check validity
{
using (UserEntities db = new UserEntities())
{
var v = db.Users.Where(a=>a.UserName.Equals(u.UserName) && a.Password.Equals(u.Password)).FirstOrDefault();
if (v != null)
{
Session["LoggedUserID"] = u.Id.ToString();
Session["UserFullname"] = u.Name.ToString();
return RedirectToAction("AfterLogin");
}
}
}
return View(u);
}
Any help is much appreciate. Thanks.
If you absolutely want to manage login and security yourself using Session, You can create your own action filter which checks whether session has a user id set to it.
Something like this
public class AuthorizeWithSession : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (context.HttpContext.Session == null ||
context.HttpContext.Session["LoggedUserID"]==null)
{
context.Result =
new RedirectToRouteResult(new RouteValueDictionary(
new {controller = "Account", action = "Login"}));
}
base.OnActionExecuting(context);
}
}
Now decorate this action filter on your secure actions/controllers
[AuthorizeWithSession]
public class TeamController : Controller
{
}
You should have your own role management if you want to control what the users can do.
Each user should have one or more roles, each role can have a set of permissions and you can create an action filter that inherits from AuthorizeAttribute to make sure it is executed as early as possible.
Inside the AuthorizeCore method of the AuthorizeAttribute , you will see if the user is authenticated or not, and if he is authenticated then you can read his identity, read his roles and permissions from the database and compare it to a value passed to the role.
ex:
public class RequireRoleAttribute : AuthorizeAttribute
{
public RoleEnum[] RequiredRoles { get; set; }
public RequireRoleAttribute()
{
}
public RequireRoleAttribute(params RoleEnum[] roles)
: this()
{
RequiredRoles = roles;
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var principle = httpContext.User;
if (principle == null || principle.Identity == null || !principle.Identity.IsAuthenticated)
{
return false;
}
if (RequiredRoles != null)
{
if (!HasRole(RequiredRoles))
{
httpContext.Response.Redirect("/AccessDenied");
}
}
return base.AuthorizeCore(httpContext);
}
public bool HasRole(RoleEnum[] roles)
{
foreach (var role in roles)
{
if (HasRole(role))
return true;
}
return false;
}
public bool HasRole(RoleEnum role)
{
return true if the user role has the role specified (read it from database for example)
}
}
Then in your controller, just annotate the controller or action with the attribute
[RequireRole(RoleEnum.Administator)]
public class MySecureController : Controller
{
}
Hello I have a web api controller inside a mvc web site.
I'm trying to allow access to the controller using 2 rules:
User is admin or the request came from local computer;
I'm new to AuthorizationFilterAttribute but I tried to write one that limit access
to local request only:
public class WebApiLocalRequestAuthorizationFilter : AuthorizationFilterAttribute
{
public override void OnAuthorization(HttpActionContext actionContext)
{
if (actionContext == null)
{
throw new ArgumentNullException("httpContext");
}
if (actionContext.Request.IsLocal())
{
return;
}
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
actionContext.Response.Content = new StringContent("Username and password are missings or invalid");
}
}
Then I decorated my controller with 2 attributes as
[Authorize(Roles = "Admin")]
[WebApiLocalRequestAuthorizationFilter]
public class ContactController : ApiController
{
public ContactModel Get(int id)
{
ContactsService contactsService = new ContactsService();
return contactsService.GetContactById(id).Map<ContactModel>();
}
}
But as I suspected , now, in order to access the controller I need to be admin and the request should be made from localhost. How can I do it?
Kind regards,
Tal Humy
One solution is to create a class that inherits from AuthorizeAttribute
e.g. something like this
public class MyAuthorizeAttribute: AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
bool accessAllowed = false;
bool isInGroup = false;
List<string> roleValues = Roles.Split(',').Select(rValue => rValue.Trim().ToUpper()).ToList();
foreach (string role in roleValues)
{
isInGroup = IdentityExtensions.UserHasRole(httpContext.User.Identity, role);
if (isInGroup)
{
accessAllowed = true;
break;
}
}
//add any other validation here
//if (actionContext.Request.IsLocal()) accessAllowed = true;
if (!accessAllowed)
{
//do some logging
}
return accessAllowed;
}
...
}
Then you can use it like so:
[MyAuthorizeAttribute(Roles = "Support,Admin")]
In the above code, IdentityExtensions checks for, and caches, ActiveDirectory roles which also allows us to fake the current user having roles by changing the cache.
I have an MVC4 Intranet application (using the default template). I am using Windows Authentication as my login system, however, i want to be able to capture some details from the user the first time they register with the site.
Use Cases
First time user authenticates using their AD login (currently working). They are presented with an 'Enter your Details' View.
Second time user authenticates using their AD login. They are taken
straight to the home screen.
Cheers,
Dave
Create a custom AuthorizeAttribute like this:
public class MyAuthorizeAttribute : AuthorizeAttribute
{
private UnitOfWork _unitOfWork = new UnitOfWork();
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var isAuthorized = false;
var username = httpContext.User.Identity.Name;
// Some code to find the user in the database...
var user = _unitOfWork.UserRepository.Find(username);
if(user != null)
{
// Check if there are Details for the user in the database
if(user.HasDetails)
{
isAuthorized = true;
}
}
return isAuthorized;
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
if (!AuthorizeCore(filterContext.HttpContext))
{
// If not authorized, redirect to the Details action
// of the Account controller...
var action = filterContext.RouteData.Values["action"];
if(filterContext.Controller is AccountController
&& action.Equals("Details"))
{
// Do nothing
}
else
{
filterContext.Result = new RedirectToRouteResult(
new System.Web.Routing.RouteValueDictionary {
{"controller", "Account"}, {"action", "Details"}
}
);
}
}
}
}
Then, you can use it in your Controllers like this:
[MyAuthorize]
public class HomeController : Controller
{
}
Or, you can register it as a global action filter in your Global.asax file like this:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new MyAuthorizeAttribute());
}