I am creating my own custom authorize attribute, overriding the AuthorizeCore method and wanted to know if it is possible to access the Roles which have been passed into the authorize attribute tag.
So for instance if I have this:
[CustomAuthorize(Roles = "Administrator, Sales, Entry")]
Is it possible to access these from inside here:
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
}
I could then split the string and create an array.
You can this this.Roles which is a string that you need to split.
The source code is freely available.
The default AuthorizeCore implementation:
protected virtual bool AuthorizeCore(HttpContextBase httpContext) {
if (httpContext == null) {
throw new ArgumentNullException("httpContext");
}
IPrincipal user = httpContext.User;
if (!user.Identity.IsAuthenticated) {
return false;
}
if (_usersSplit.Length > 0 && !_usersSplit.Contains(user.Identity.Name, StringComparer.OrdinalIgnoreCase)) {
return false;
}
if (_rolesSplit.Length > 0 && !_rolesSplit.Any(user.IsInRole)) {
return false;
}
return true;
}
And they have an internal split function which looks like this:
internal static string[] SplitString(string original) {
if (String.IsNullOrEmpty(original)) {
return new string[0];
}
var split = from piece in original.Split(',')
let trimmed = piece.Trim()
where !String.IsNullOrEmpty(trimmed)
select trimmed;
return split.ToArray();
}
Related
i implemented custom media-type formatter with per request logic
public class JsonPermissionBasedFormatter : PartialJsonMediaTypeFormatter
{
public override MediaTypeFormatter GetPerRequestFormatterInstance(Type type, HttpRequestMessage request, MediaTypeHeaderValue mediaType)
{
User user = request.GetOwinContext()?.Request.Get<User>("AuthorizationFilter:CurrentUser");
var formatter = (PartialJsonMediaTypeFormatter)base.GetPerRequestFormatterInstance(type, request, mediaType);
formatter.SerializerSettings = SerializerSettings;
formatter.SerializerSettings.ContractResolver = new PermissionBasedContractResolver(user);
return formatter;
}
}
public class PermissionBasedContractResolver : DefaultContractResolver
{
private readonly User _user;
public PermissionBasedContractResolver(User user)
{
_user = user;
NamingStrategy = new CamelCaseNamingStrategy
{
ProcessDictionaryKeys = true,
OverrideSpecifiedNames = true
};
}
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
if (member == null)
{
throw new ArgumentNullException(nameof(member));
}
JsonProperty property = base.CreateProperty(member, memberSerialization);
var propertyInfo = member as PropertyInfo;
if (propertyInfo != null)
{
if (!PermissionsHelper.IsPropertyVisibleForUser(propertyInfo, _user))
{
property.ShouldSerialize = DoNotSerialize;
}
}
return property;
}
static bool DoNotSerialize(object o)
{
return false;
}
}
public static bool IsPropertyVisibleForUser(PropertyInfo info, User user)
{
if (info.GetCustomAttribute<IgnoreDataMemberAttribute>() != null)
return false;
if (user == null) return true;
var permissionForExportAttribute =
info.GetCustomAttribute<VisibleForAttribute>();
if (permissionForExportAttribute != null)
{
return
user.HasPermission(permissionForExportAttribute.Permission);
}
return true;
}
PermissionBasedContractResolver add custom logic for serialization which depend on user rights. For example user with admin rights receives additional properties in json, which ordinary user do not.
But if run requests in the following manner:
Parallel.For(1, 10000, _ =>
{
Get(ordinaryUser, isAdmin: false);
Get(adminUser, isAdmin: true);
});
occasionally got for ordinaryUser json properties availiable only for admins. i dont understand how it can happens.
Problem can be reproduce only under some load, if run requests manually via postman - all is ok. Can you tell what it can be or give advices how to investigate such problem.
I have the following WebApi action that deletes an order from the back-end database, only for users that are in the Admin and Order roles. However, if the user is also in the Readonly role the action returns a HTTP 403 Forbidden response.
[Authorize(Roles = "Admin,Order")]
public async Task<IHttpActionResult> Delete(int orderid) {
if(User.IsInRole("Readonly")) { return Forbidden(); }
var order = await _repository.Get(orderid);
if(order != null) {
await _repository.Delete(orderid);
return NoContent();
}
else {
return NotFound();
}
}
What I'd like to know is it possible to prevent actions from being executed if users are in specific roles so that I do not have to put if(User.IsInRole("Readonly")) { return Forbidden(); } at the start of all database update-able action methods, e.g.
[Authorize(Roles = "Admin,Order")]
[NotAuthorized(Roles = "Readonly")]
public async Task<IHttpActionResult> Delete(int orderid) {
var order = await _repository.Get(orderid);
if(order != null) {
await _repository.Delete(orderid);
return NoContent();
}
else {
return NotFound();
}
}
The NotAuthorized action filter will return a HTTP 403 Forbidden response if the user is in the Readonly role.
Is this possible?
This is the code to implement a reverse of the [Authorize()] attribute and forbid users from executing MVC WebApi actions if they are a member of one or more roles.
using System;
using System.Net;
using System.Net.Http;
using System.Security.Principal;
using System.Web.Http;
using System.Web.Http.Controllers;
namespace MyAPI {
[AttributeUsage(AttributeTargets.Method,AllowMultiple = false)]
public class NotAuthorizedAttribute : AuthorizeAttribute {
public override void OnAuthorization(HttpActionContext actionContext) {
IPrincipal user = actionContext.RequestContext.Principal;
if(!user.Identity.IsAuthenticated) {
actionContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
}
else {
bool userInRole = false;
foreach(var role in Roles.Split(',')) {
if(user.IsInRole(role)) {
userInRole = true;
break;
}
}
if(userInRole) {
actionContext.Response = new HttpResponseMessage(HttpStatusCode.Forbidden);
}
}
}
}
}
To use this filter attribute simply decorate any actions that you don't want users to execute if they're a member of a restricted role, e.g. if the user is part of a read-only role they not permitted to update the database:
[Authorize(Roles = "Admin,Order")]
[NotAuthorized(Roles = "Readonly")]
public async Task<IHttpActionResult> Delete(int orderid) {
var order = await _repository.Get(orderid);
if(order != null) {
await _repository.Delete(orderid);
return NoContent();
}
else {
return NotFound();
}
}
When the user is authenticated I want to prevent that he updates/deletes/reads data created from other accounts... by telling him you do not have the permission 403!
What is the best way to get an instance of the ISchoolyearService to invoke its HasUserPermission() method?
I know I could new up the SchoolyearService here but that would defeat the reason using an IoContainer at all in my app.
public class UserActionsSchoolyearAuthorizationFilter : AuthorizationFilterAttribute
{
public override void OnAuthorization(HttpActionContext actionContext)
{
if (actionContext != null)
{
bool canUserExecuteAction = false;
if (actionContext.Request.Method == HttpMethod.Put)
{
int schoolyearId = Convert.ToInt32(actionContext.Request.GetRouteData().Values["Id"]);
int userId = actionContext.Request.Content.ReadAsAsync<SchoolyearEditRequest>().Result.Schoolyear.UserId;
//var schoolyearService = actionContext.ControllerContext.Controller.GetContstructorParameterServiceInstance();
//canUserExecuteAction = schoolyearService.HasUserPermission(userId, schoolyearId);
if (canUserExecuteAction)
{
base.OnAuthorization(actionContext);
}
else
{
actionContext.Response = new HttpResponseMessage(HttpStatusCode.Forbidden);
}
}
// Removed for brevity
private readonly ISchoolyearService _service;
public SchoolyearController(ISchoolyearService service)
{
_service = service;
}
If you made the _service parameter public on your SchoolyearController you could try something like this in the OnAuthorization method:
var schoolyearController = actionContext.ControllerContext.Controller as SchoolyearController;
canUserExecuteAction = schoolyearController._service.HasUserPermission(userId, schoolyearId);
Ok finally I found it out how to get the ISchoolyearService from the current request:
Grab the registered service from the DependencyScope!
Now this Attribute should be put on the controller directly. Its not needed to put it on the action due to the if/else on the http verbs which I do.
bool canUserExecuteAction = false;
if (actionContext.Request.Method == HttpMethod.Put)
{
int targetId = Convert.ToInt32(actionContext.Request.GetRouteData().Values["Id"]);
int userId = actionContext.Request.Content.ReadAsAsync<SchoolyearEditRequest>().Result.Schoolyear.UserId;
var requstScope = actionContext.ControllerContext.Request.GetDependencyScope();
var service = requstScope.GetService(typeof(ISchoolyearService)) as ISchoolyearService;
canUserExecuteAction = service.HasUserPermission(userId, targetId);
if (canUserExecuteAction)
{
base.OnAuthorization(actionContext);
}
else
{
actionContext.Response = new HttpResponseMessage(HttpStatusCode.Forbidden);
}
}
Trying to save selections from a CheckBoxList as a comma-separated list (string) in DB (one or more choices selected). I am using a proxy in order to save as a string because otherwise I'd have to create separate tables in the DB for a relation - the work is not worth it for this simple scenario and I was hoping that I could just convert it to a string and avoid that.
The CheckBoxList uses an enum for it's choices:
public enum Selection
{
Selection1,
Selection2,
Selection3
}
Not to be convoluted, but I use [Display(Name="Choice 1")] and an extension class to display something friendly on the UI. Not sure if I can save that string instead of just the enum, although I think if I save as enum it's not a big deal for me to "display" the friendly string on UI on some confirmation page.
This is the "Record" class that saves a string in the DB:
public virtual string MyCheckBox { get; set; }
This is the "Proxy", which is some sample I found but not directly dealing with enum, and which uses IEnumerable<string> (or should it be IEnumerable<Selection>?):
public IEnumerable<string> MyCheckBox
{
get
{
if (String.IsNullOrWhiteSpace(Record.MyCheckBox)) return new string[] { };
return Record
.MyCheckBox
.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.Select(r => r.Trim())
.Where(r => !String.IsNullOrEmpty(r));
}
set
{
Record.MyCheckBox = value == null ? null : String.Join(",", value);
}
}
To save in the DB, I am trying to do this in a create class:
proxy.MyCheckBox = record.MyCheckBox; //getting error here
but am getting the error:
Cannot implicitly convert 'string' to System.Collections.Generic.IEnumerable'
I don't know, if it's possible or better, to use Parse or ToString from the API for enum values.
I know that doing something like this will store whatever I put in the ("") into the DB, so it's just a matter of figuring out how to overcome the error (or, if there is an alternative):
proxy.MyCheckBox = new[] {"foo", "bar"};
I am not good with this stuff and have just been digging and digging to come up with a solution. Any help is much appreciated.
You can accomplish this using a custom user type. The example below uses an ISet<string> on the class and stores the values as a delimited string.
[Serializable]
public class CommaDelimitedSet : IUserType
{
const string delimiter = ",";
#region IUserType Members
public new bool Equals(object x, object y)
{
if (ReferenceEquals(x, y))
{
return true;
}
var xSet = x as ISet<string>;
var ySet = y as ISet<string>;
if (xSet == null || ySet == null)
{
return false;
}
// compare set contents
return xSet.Except(ySet).Count() == 0 && ySet.Except(xSet).Count() == 0;
}
public int GetHashCode(object x)
{
return x.GetHashCode();
}
public object NullSafeGet(IDataReader rs, string[] names, object owner)
{
var outValue = NHibernateUtil.String.NullSafeGet(rs, names[0]) as string;
if (string.IsNullOrEmpty(outValue))
{
return new HashSet<string>();
}
else
{
var splitArray = outValue.Split(new[] {Delimiter}, StringSplitOptions.RemoveEmptyEntries);
return new HashSet<string>(splitArray);
}
}
public void NullSafeSet(IDbCommand cmd, object value, int index)
{
var inValue = value as ISet<string>;
object setValue = inValue == null ? null : string.Join(Delimiter, inValue);
NHibernateUtil.String.NullSafeSet(cmd, setValue, index);
}
public object DeepCopy(object value)
{
// return new ISet so that Equals can work
// see http://www.mail-archive.com/nhusers#googlegroups.com/msg11054.html
var set = value as ISet<string>;
if (set == null)
{
return null;
}
return new HashSet<string>(set);
}
public object Replace(object original, object target, object owner)
{
return original;
}
public object Assemble(object cached, object owner)
{
return DeepCopy(cached);
}
public object Disassemble(object value)
{
return DeepCopy(value);
}
public SqlType[] SqlTypes
{
get { return new[] {new SqlType(DbType.String)}; }
}
public Type ReturnedType
{
get { return typeof(ISet<string>); }
}
public bool IsMutable
{
get { return false; }
}
#endregion
}
Usage in mapping file:
Map(x => x.CheckboxValues.CustomType<CommaDelimitedSet>();
This allows "frankl" to access but blocks the admins. What have I done wrong?
[Authorize(Order=1,Roles = "Admin",Users="frankl")]
public class AuthorizeBaseController_Admins_frank : Controller
{
}
It is probably simple but I don't see any examples that combine the two and the "Allowmultiple" property generates an error when I try to add it.
Thanks,
Chris
Roles and Users should be used exclusively. If you want to combine them you could write a custom authorize attribute:
public class MyAuthoirizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext == null)
{
throw new ArgumentNullException("httpContext");
}
var user = httpContext.User;
if (!user.Identity.IsAuthenticated)
{
return false;
}
var usersSplit = SplitString(Users);
var rolesSplit = SplitString(Roles);
return
(usersSplit.Length > 0 && usersSplit.Contains(user.Identity.Name, StringComparer.OrdinalIgnoreCase)) ||
(rolesSplit.Length > 0 && rolesSplit.Any(user.IsInRole));
}
private string[] SplitString(string original)
{
if (string.IsNullOrEmpty(original))
{
return new string[0];
}
return (from piece in original.Split(',')
let trimmed = piece.Trim()
where !string.IsNullOrEmpty(trimmed)
select trimmed).ToArray();
}
}
and then:
[MyAuthorize(Order = 1, Roles = "Admin", Users="frankl")]
public class AuthorizeBaseController_Admins_frank : Controller
{
...
}
Unfortunately the AuthorizeAttribrute will let you either specify valid users, or valid roles - not both. Here is the relevant bit of code from the MVC 3 source.
protected virtual bool AuthorizeCore(HttpContextBase httpContext) {
if (httpContext == null) {
throw new ArgumentNullException("httpContext");
}
IPrincipal user = httpContext.User;
if (!user.Identity.IsAuthenticated) {
return false;
}
if (_usersSplit.Length > 0 && !_usersSplit.Contains(user.Identity.Name, StringComparer.OrdinalIgnoreCase)) {
return false;
}
if (_rolesSplit.Length > 0 && !_rolesSplit.Any(user.IsInRole)) {
return false;
}
return true;
}
You will either need to make 'frankl' an Admin, or create a custom authorization attribrute