Custom authorize attribute based on application settings - c#

Context: C# ASP.NET MVC Web Application.
I added functionality which enables me to impersonate another user.
I should only see this functionality when the application is configured as such. So I added to the appSettings in web.config.
<add key="AllowImpersonation" value="true" />
(Not) Showing the controls is easy. But I also want to disable the corresponding controller method.
First thing on my mind is a custom AuthorizeAttribute in which I can just check for the configuration setting.
Even better would be a more generic attribute in which you can provide the key of the (boolean) Appsetting to check. Something like this:
namespace MILF.Security
{
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
public class ConfigSetting_Authorize : AuthorizeAttribute
{
private string _configKey;
public ConfigSetting_Authorize(string configKey)
{
if (string.IsNullOrEmpty(configKey))
throw new ArgumentException("configKey");
this._configKey = configKey;
}
//Core authentication, called before each action
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
try
{
return Convert.ToBoolean(AppSettingsHelper.Get<bool>(this._configKey))
}
catch (Exception)
{
return false;
}
}
//Called when access is denied
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
SecurityLogManager securityLogManager = new SecurityLogManager(filterContext);
var message = string.format("{0} triez to access shit that waz disabled.", filterContext.HttpContext.User.Identity.Name);
securityLogManager.Write(message);
securityLogManager = null;
}
}
}
namespace MILF.Utils
{
public static class AppSettingsHelper
{
public static T Get<T>(string key)
{
var appSetting = ConfigurationManager.AppSettings[key];
if (string.IsNullOrWhiteSpace(appSetting)) throw new Exception(key);
var converter = TypeDescriptor.GetConverter(typeof(T));
return (T)(converter.ConvertFromInvariantString(appSetting));
}
}
}
And then use it like this:
public class ImpersonateController : BaseController
{
// GET: Impersonate
[ConfigSetting_Authorize("AllowImpersonation")]
public ActionResult Index()
{
if (!LoggedInUser.IsAdministrator)
{
return RedirectToAction("Index", "Home");
}
return View(new List<KBEAccounts.Account>());
}
[ConfigSetting_Authorize("AllowImpersonation")]
public ActionResult Impersonate(string Id)
{
if (!LoggedInUser.IsAdministrator)
{
return RedirectToAction("Index", "Home");
}
// Set the account to impersonate
var service = new ActiveDirectoryUsersService();
this.LoggedInUser =
service.GetUserByLoginNameToImpersonate(
Id,
(string.IsNullOrWhiteSpace(LoggedInUser.ImpersonatorPersonnelId)) ?
LoggedInUser.EmployeeNumber :
LoggedInUser.ImpersonatorPersonnelId,
(string.IsNullOrWhiteSpace(LoggedInUser.ImpersonatorCommonName)) ?
LoggedInUser.CommonName :
LoggedInUser.ImpersonatorCommonName,
(string.IsNullOrWhiteSpace(LoggedInUser.ImpersonatorFullName)) ?
LoggedInUser.FullName :
LoggedInUser.ImpersonatorFullName);
// redirect to home
return RedirectToAction("", "");
}
}
But it feels like I am re-inventing the wheel.
Does the .NET framework not already supply this functionality?

Related

How do I create Basic Authentication in Web Api?

I have the following .cs in order to create some basic authentication in my api. This works fine,but it appears only one time, when i run it for the first time.How do I make it appear again (in every run)?
namespace CMob
{
public class BasicAuthenticationAttribute : AuthorizationFilterAttribute
{
public override void OnAuthorization(HttpActionContext actionContext)
{
var authHeader = actionContext.Request.Headers.Authorization;
if (authHeader != null)
{
var authenticationToken = actionContext.Request.Headers.Authorization.Parameter;
var decodedAuthenticationToken = Encoding.UTF8.GetString(Convert.FromBase64String(authenticationToken));
var usernamePasswordArray = decodedAuthenticationToken.Split(':');
var userName = usernamePasswordArray[0];
var password = usernamePasswordArray[1];
var isValid = userName == "chrysa" && password == "1234";
if (isValid)
{
var principal = new GenericPrincipal(new GenericIdentity(userName), null);
Thread.CurrentPrincipal = principal;
return;
}
}
HandleUnathorized(actionContext);
}
private static void HandleUnathorized(HttpActionContext actionContext)
{
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
actionContext.Response.Headers.Add("WWW-Authenticate", "Basic Scheme='Data' location = 'http://localhost:");
}
}
}ยจ
My controller
public class DController : ApiController
{
[BasicAuthentication]
[Route("api/D")]
public IEnumerable<D> Get()
{
using (CM_DataEntities entities = new CM_DataEntities())
{
return entities.Ds.ToList();
}
}
}
Thanks!
"To unauthenticated requests, the server should return a response whose header contains a HTTP 401 Unauthorized status[4] and a WWW-Authenticate field.[5]"
You should refer to https://en.wikipedia.org/wiki/Basic_access_authentication.
I am quite sure you can find the answer you're looking for over there.
Basically, The browser provides authentication, and you have absolutely no control over it.
You have to declare the attribute in WebApiConfig.cs :
config.Filters.Add(new BasicAuthenticationAttribute());
And you have to decorate your Controllers and or Actions :
public class MyController : ApiController
{
[BasicAuthentication]
public string Get()
{
return "Hello";
}
}
It actually depends on what behavior you want to define.
If you wish to use your authentication filter for your whole API, you can add it to the global filter list this way (in WebApiConfig.cs) :
public static void Register(HttpConfiguration config)
{
config.Filters.Add(new BasicAuthenticationAttribute());
}
If you desire to restrict all methods of a controller, decorate it this way :
[BasicAuthentication]
public class RestrictedController : ApiController
{
//Your controller definition
}
Of course you can use it on a single method, this way :
[BasicAuthentication]
public JsonResult GetJsonDataAsAuthenticatedUser()
{
//your method definition
}
You can specify a method which require no authentication with AllowAnonymous decoration :
[BasicAuthentication]
public class RestrictedController : ApiController
{
[AllowAnonymous]
public IActionResult Authenticate()
{
//Your authentication entry point
}
}
You can refer to this link

Getting values from Custom AuthorizeAttribute being default

I have the follow Custom AuthorizeAttribute:
public class SystemAuthorizeAttribute : AuthorizeAttribute
{
public Form PermissionForm { get; set; } //Enum
public PermissionValue Permissions { get; set; }//Enum
public override void OnAuthorization(AuthorizationContext filterContext)
{
//Request is an Authenticated Method?
if (filterContext.HttpContext.Request.IsAuthenticated)
{
Debug.WriteLine("Test 1 " + PermissionForm);
if (!CurrentUserHasPermissionForm(PermissionForm))
{
//Deny access code
}
}
}
//...
}
After Login method it redirects to Index page from HomeController. The problem is when use SystemAuthorize Attribute in my HomeController the Form value always come as 0 when it should be 4 (Content).
HomeController method:
[SystemAuthorize(PermissionForm = Form.CONTENT, Permissions = PermissionValue.VIEW)]
public ActionResult Index()
{
return this.View();
}
Login method:
[AllowAnonymous]
[Route("Account/Login")]
public ActionResult Login(LoginViewModel model, string url = "")
{
var user= GetUserAccount(model);
if (user == null)
{
ModelState.AddModelError("", "User not found!");
return View(model);
}
else
{
FormsAuthentication.SetAuthCookie(user.Sign, false);
var authTicket = new FormsAuthenticationTicket(1, user.Sign, DateTime.Now, DateTime.Now.AddMinutes(20), false, JsonConvert.SerializeObject(user));
var authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(authTicket));
HttpContext.Response.Cookies.Add(authCookie);
return RedirectToAction("Index", "Home");
}
}
Form enum:
public enum Form : short
{
PATIENT = 1,
USERS = 2,
MEDICE = 3,
CONTENT = 4,
}
What I'm doing wrong or missing?
Unfortunately Microsoft made this a bit confusing by combining IAuthorizationFilter with Attribute in the same class. The fact of the matter is that attributes cannot do anything except store meta-data.
The part of MVC that reads the attribute is the IAuthorizationFilter which through some MVC magic is registered with MVC automatically when you place AuthorizeAttribute (or a subclass) on a controller or action.
But the only way to actually read the meta-data from the attribute is to use Reflection. The meta-data is in the same class, but not the same instance of the class. The meta-data is in the Attribute, but the code that executes when the filter runs is in the IAuthorizationFilter, which is a separate instance of the same class.
public class SystemAuthorizeAttribute : AuthorizeAttribute
{
public Form PermissionForm { get; set; } //Enum
public PermissionValue Permissions { get; set; }//Enum
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var actionDescriptor = httpContext.Items["ActionDescriptor"] as ActionDescriptor;
if (actionDescriptor != null)
{
var authorizeAttribute = this.GetSystemAuthorizeAttribute(actionDescriptor);
// If the authorization attribute exists
if (authorizeAttribute != null)
{
// Run the authorization based on the attribute
var form = authorizeAttribute.PermissionForm;
var permissions = authorizeAttribute.Permissions;
// Return true if access is allowed, false if not...
if (!CurrentUserHasPermissionForm(form))
{
//Deny access code
}
}
}
return true;
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
// Pass the current action descriptor to the AuthorizeCore
// method on the same thread by using HttpContext.Items
filterContext.HttpContext.Items["ActionDescriptor"] = filterContext.ActionDescriptor;
base.OnAuthorization(filterContext);
}
private SystemAuthorizeAttribute GetSystemAuthorizeAttribute(ActionDescriptor actionDescriptor)
{
SystemAuthorizeAttribute result = null;
// Check if the attribute exists on the action method
result = (SystemAuthorizeAttribute)actionDescriptor
.GetCustomAttributes(attributeType: typeof(SystemAuthorizeAttribute), inherit: true)
.SingleOrDefault();
if (result != null)
{
return result;
}
// Check if the attribute exists on the controller
result = (SystemAuthorizeAttribute)actionDescriptor
.ControllerDescriptor
.GetCustomAttributes(attributeType: typeof(SystemAuthorizeAttribute), inherit: true)
.SingleOrDefault();
return result;
}
}
Note that OnAuthorization has some logic in it that you will need to support output caching and the part of the code that checks for [AllowAnonymous], so you should not put your authorization check there, but in AuthorizeCore. But unfortunately, AuthorizeCore isn't passed the ActionDescriptor you need to check whether the attribute exists, so you need the above httpContext.Items hack to ensure it is passed into that method on the same thread.
The Reflection part becomes much more clear if you separate your Attribute into a different class from the IAuthorizationFilter, as in this example.

Creating a custom Authorize attribute with specific rules

I'm trying to create a custom Authorize attribute to do the following:
If the user has a role of "Regular user" - he is redirected to /index/subscribe
All other users(Administrator,Subscriber) gets access to /Search/Index
This is when the user tries to open up the Search controller. I made the custom Authorize attribute like this:
public class DenyRegularUser : System.Web.Mvc.AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
filterContext.Result = new RedirectResult("~/User/Logon");
return;
}
if (filterContext.HttpContext.User.IsInRole("Regular user"))
{
filterContext.Result = new RedirectResult("~/Index/Subscribe");
}
}
}
And this is my Search controller:
namespace WebApplication2.Controllers
{
[DenyRegularUser(Roles ="Regular user")]
public class SearchController : Controller
{
// GET: Search
public ActionResult Index()
{
return View();
}
}
}
But for some reason, even when I update the user's role from Regular user to Administrator or Subscriber, I get redirected to login page: /user/login...
This shouldn't happen as the login functionality works perfectly and I get the role of the user...
What am I missing out here??
This may help.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
public class DenyRegularUser : AuthorizeAttribute
{
public DenyRegularUser() :
base()
{
}
protected override bool IsAuthorized (System.Web.Http.Controllers.HttpActionContext actionContext)
{
if (AuthorizeRequest(actionContext))
{
return true;
}
return false;
}
protected override void HandleUnauthorizedRequest(System.Web.Http.Controllers.HttpActionContext actionContext)
{
//Code to handle unauthorized request
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.TemporaryRedirect);
actionContext.Response.Headers.Add("Location", "~/Index/Subscribe");
}
private bool AuthorizeRequest(System.Web.Http.Controllers.HttpActionContext actionContext)
{
//Write your code here to perform authorization
}
}
I believe the IsAuthorized method is the correct way to override the AuthorizeAttribute.

C# API Controller Custom Filter with HttpActionContext Redirect to controller?

Is there a way to create a custom filter with an API controller to redirect to a MVC controller?
After looking around a bit his is what i have.
public class APIHasOneOfThesePermissions : ActionFilterAttribute
{
protected UserManager<ApplicationUser> UserManager { get; set; }
private SAMPortal.DAL.SAMPortalContext db = new DAL.SAMPortalContext();
public string[] Permissions { get; set; }
public APIHasOneOfThesePermissions(string[] Permissions)
{
this.UserManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(this.db));
this.Permissions = Permissions;
}
public override void OnActionExecuting(HttpActionContext filterContext)
{
string userID = HttpContext.Current.User.Identity.GetUserId();
var CurrUser = db.Users.Include(u => u.Role.Permissions).Where(user => user.Id.Equals(userID)).FirstOrDefault();
bool hasPermission = false;
foreach (string x in Permissions)
{
if (hasPermission == false)
{
hasPermission = CurrUser.HasPermission(x);
}
}
if (hasPermission == false)
{
filterContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
}
base.OnActionExecuting(filterContext);
}
}
However when i execute the code it doesn't redirect them to the error page. Ideally i would like to redirect to a specify non-API controller is that possible?
I've created AuthorizeRedirectAttribute in one of my projects like this:
using System;
using System.Net;
using System.Web.Mvc;
namespace MyNamespace.Attributes
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AuthorizeRedirectAttribute : AuthorizeAttribute
{
public string RedirectUrl = "~/Error/Forbidden403";
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
base.HandleUnauthorizedRequest(filterContext);
var httpContext = filterContext.RequestContext.HttpContext;
var request = httpContext.Request;
var response = httpContext.Response;
// If AJAX request, just return appropriate code
if (request.IsAjaxRequest())
{
if (filterContext.HttpContext.User.Identity.IsAuthenticated)
response.StatusCode = (int)HttpStatusCode.Forbidden;
else
response.StatusCode = (int)HttpStatusCode.Unauthorized;
response.SuppressFormsAuthenticationRedirect = true;
response.End();
}
// Otherwise check if authenticated, and if not redirect to specified url
if (httpContext.User.Identity.IsAuthenticated)
{
httpContext.Response.Redirect(RedirectUrl);
}
}
}
}
Then I've used it like this
[AuthorizeRedirect(Roles = "Administrator")]
public class MyController : Controller
{
}
In this case I've decorated whole controller with this attribute. It can also be applied to single controller function, if necessary. Basically what it does is, it checks whether logged on user is in role Administrator. If it is not, user is redirected to "~/Error/Forbidden403" action (returning simple view displaying user has not enough permissions). Hope it helps.
You could also implement checking your own permissions, as you did in your code.

Roles in Authorize Attribute does not work as expected in MVC 4

(I am new to ASP.NET MVC 4 and prior to this project, I was used to working with WebForms with <authentication mode="none">).
I have a database in which I have specific roles against a user entity like ADMIN and SUBADMIN . I have separate controllers in my project to deal with different views for the different roles for example, AdminController and SubAdminController I can successfully redirect users to the respective views according to their roles.
My issue is that when the users have logged in they can even access pages which they are not privileged to access.This implies that may be I have not set up authorization properly.
What I have tried?
I have tried the authorized attribute with ADMIN role filter so that only admins are authorized to view the respective views like:
[Authorize(Roles="ADMIN")]
public class AdminController : BaseController
{
....
}
This redirects the user even if he is successfully authenticated to the login page which is configured in web.config like this:
<authentication mode="Forms">
<forms loginUrl="~/Home/Index" timeout="2880" />
</authentication>
Note that if I just use [Authorize] instead then it works but even SubAdmin can gain access to the Admin views.
I have tried configuring the authorization in Web.config too like this:
<location path="Admin">
<system.web>
<authorization>
<allow roles="ADMIN"/>
<deny users="*"/>
</authorization>
</system.web>
</location>
This has lead to same results as using [Authorize(Roles="ADMIN")].
Note that if I console out the following
User.Identity.ToGenericUserIdentity().RoleName; in my AdminController then I get ADMIN and i can use it in my action methods to re-check whether the user is actually an admin or sub admin but that is not the correct way to write it down as checks in all my action methods.
Related classes:
[Serializable]
public class GenericUser
{
public bool IsInRole(string role) { return false; }
public string RoleName { get; set; }
public int UserID { get; set; }
public string UserName { get; set; }
}
[Serializable]
public class GenericUserIdentity : IIdentity
{
private GenericUser genericUser;
private FormsAuthenticationTicket ticket;
public GenericUserIdentity(FormsAuthenticationTicket ticket)
{
var serializer = new JsonSerializer();
var reader = new JsonTextReader(new StringReader(ticket.UserData));
genericUser = serializer.Deserialize<GenericUser>(reader);
this.ticket = ticket;
}
public string AuthenticationType
{
get { return "Custom"; }
}
public bool IsAuthenticated
{
get { return ticket != null; }
}
public string Name
{
get { return genericUser.UserName; }
}
public string RoleName
{
get { return genericUser.RoleName; }
}
public int UserID
{
get { return genericUser.UserID; }
}
}
public static class IdentityExtension
{
public static GenericUserIdentity ToGenericUserIdentity(this IIdentity identity)
{
var cookie = HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];
var ticketInfo = FormsAuthentication.Decrypt(cookie.Value);
return new GenericUserIdentity(ticketInfo);
}
}
And how do I authenticate?
In my HomeController which shows the login page on Index() action, I have set up a HttpPost Index action like this:
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(string username,string password)
{
UserInformation userInfo = _userProvider.FindUserByName(username);
OrderProvider orderProvider = new OrderProvider(new OrderRepository());
if (userInfo != null && string.Equals(userInfo.Password, password,StringComparison.Ordinal))
{
if (userInfo.isEnabled)
{
GenericUser genericUser = new GenericUser() { UserName = userInfo.Email, UserID = userInfo.UserID, RoleName = userInfo.RoleName };
Response.SetAuthCookie<GenericUser>(genericUser.UserName, genericUser);
if (userInfo.RoleName == "ADMIN")
{
return RedirectToAction("Index", "Admin");
}
else if (userInfo.RoleName == "USER")
{
return RedirectToAction("Index", "Customer");
}
else if (userInfo.RoleName == "DRIVER")
{
return RedirectToAction("Index", "Driver");
}
else if (userInfo.RoleName == "SUBADMIN")
{
return RedirectToAction("Index", "SubAdmin");
}
}
}
ViewBag.Error = true;
return View("Index");
}
Edit
Finally overriding the AuthorizeCore method of my custom attribute worked for me.
public enum Role
{
ADMIN, SUBADMIN, USER, DRIVER
}
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
public class AuthorizeUser : AuthorizeAttribute
{
public AuthorizeUser(params object[] roles)
{
if (roles.Any(r => r.GetType().BaseType != typeof(Enum)))
throw new ArgumentException("roles");
this.Roles = string.Join(",", roles.Select(r => Enum.GetName(r.GetType(), r)));
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var cookie = HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];
var ticketInfo = FormsAuthentication.Decrypt(cookie.Value);
GenericUserIdentity iden = new GenericUserIdentity(ticketInfo);
if (Roles.Contains(iden.RoleName))
return true;
else
return false;
}
}
The attribute [Authorize(Roles="ADMIN")] only works for the built-in Roles.
You have your own GenericUser.RoleName, that won't be used here.
You could write your own MyAuthorize attribute but first ask yourself very seriously why you are re-inventing the wheel here. The Identity and Membership frameworks are available and tested. When I see a string.Equals(userInfo.Password, password) I very much doubt the security of your implementation.

Categories

Resources