Can not have [Authorize()] with dynamic code inside - c#

I am trying to make authorize accept roles either as enum or smart enum
so that I don't have to debug magic strings and their typos
but I keep hitting a dead end with these two errors:
Attribute constructor parameter 'roles' has type 'Role[]', which is not a valid attribute parameter type
An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type
here is my code:
AuthorizeRoles.cs
public class AuthorizeRoles : AuthorizeAttribute
{
public AuthorizeRoles(params Role[] roles)
{
string allowed = string.Join(", ", roles.ToList().Select(x => x.Name));
Roles = allowed;
}
}
Role.cs
public class Role
{
public readonly string Name;
public enum MyEnum // added
{
Admin,
Manager
}
public static readonly Role Admin = new Role("Admin");
public static readonly Role Manager = new Role("Manager");
public Role(string name)
{
Name = name;
}
public override string ToString()
{
return Name;
}
and inside my controller I did this
[AuthorizeRoles(Role.Admin, Role.Manager)]
[AuthorizeRoles(Role.MyEnum.Admin)] // added
public IActionResult Index()
{
return Content("hello world");
}
I have looked at these answers but it doesn't work
answer 1
answer 2
answer 3

Because of the CLR constraints (how attributes stored in the metadata), atribute paramters can be only primitive types or arrays of those (and Types). You can't pass a Role (a custom object) to an attribute.
Enums are valid, but the compiler cannot convert your enum (Role.MyEnum) to Role, which is the type that the constructor of AuthorizeRoles requires. So this is a compiler error.
As you can guess, the solution is to create a constructor that take array of Role.MyEnum, as the following:
public class AuthorizeRoles : Attribute
{
public string Roles { get; private set; }
public AuthorizeRoles(params Role.MyEnum[] roles)
{
string allowed = string.Join(", ", roles);
Roles = allowed;
}
}
public class Role
{
public readonly string Name;
public enum MyEnum
{
Admin,
Manager
}
public Role(string name)
{
Name = name;
}
public override string ToString()
{
return Name;
}
}
// ...
[AuthorizeRoles(Role.MyEnum.Admin)]
public IActionResult Index()
{
// ...
}

It admittedly sucks, but the closest you can really get to this here is doing something like:
public static class Roles
{
public const string Admin = "Admin";
public const string Manager = "Manager";
}
And then:
[Authorize(Roles = Roles.Admin + "," + Roles.Manager)]
Between the combo of constant strings and in place string concatenation, it's all still a "constant expression". What you cannot do is basically anything that requires a method to be run such as string.Join. That's the breaks of the game when using attributes.

In constructor AuthorizeRoles class you use array of Role class, but in attribute [AuthorizeRoles(Role.MyEnum.Admin)] you use parameter of type MyEnum. if you want use enum, you must create AuthorizeRoles class constructor with parameter of MyEnum type.

Use constants for policy names and use authorization policies
// startup.cs
services.AddAuthorization(options =>
{
options.AddPolicy(PolicyConstants.Admin, policy =>
{
// Allowed to access the resource if role admin or manager
policy.RequireClaim(JwtClaimTypes.Role, new[] { PolicyConstants.Admin, PolicyConstants.Manager });
// Or use LINQ here
policy.RequireAssertion(c =>
{
// c.User.Claims
});
}
In the controller use policy name
[Authorize(PolicyConstants.Admin)]
public class TestController
{
// here also you can use specific policy and for controller, you can use other policy. It will match Action Level policy first and then match controller policy.
public IActionResult Index()
{
}
}

Related

Custom Attribute For Class Library Classes and Functions in C#

I'm developing 3rd party API connector bridge in class library NOT in ASP.NET.
User Levels
API has 3 user levels, lets say:
UserGoer
UserDoer
UserMaker
Service Restriction
Each API operation can work with one or multiple user level roles. For example, lets assume operations and reachable user levels as follows;
JokerService (reachable by UserGoer, UserMaker)
PokerService (reachable by UserGoer, UserDoer)
MokerService (reachable by UserGoer, UserDoer, UserMaker)
If UserDoer requests for JokerService, API returns bad request. JokerService is only reachable for UserGoer and UserMaker. So, I want to restrict and throw an exception.
User Token Structure
public interface IToken
{
string AccessToken { get; set; }
string RefreshToken { get; set; }
}
public class AuthenticationToken : IToken
{
[JsonProperty("access_token")]
public string AccessToken { get; set; }
[JsonProperty("refresh_token")]
public string RefreshToken { get; set; }
}
public class UserGoerAuthenticationToken : AuthenticationToken
{
}
public class UserDoerAuthenticationToken : AuthenticationToken
{
}
public class UserMakerAuthenticationToken : AuthenticationToken
{
}
Enum
public enum TokenType
{
Undefined = 0,
UserGoer = 1,
UserDoer = 2,
UserMaker = 3
}
Customized Authentication Attribute
public class AuthenticationFilter : Attribute
{
public TokenType[] TokenTypes { get; private set; }
public AuthenticationFilter(params TokenType[] TokenTypes)
{
this.TokenTypes = TokenTypes;
}
}
Example Service
[AuthenticationFilter(TokenType.UserGoer, TokenType.UserMaker)]
internal class JokerService : BaseService<JokerEntity>
{
public JokerService(IToken AuthenticationToken) : base(AuthenticationToken)
{
var tokenTypes =
(typeof(JokerService).GetCustomAttributes(true)[0] as AuthenticationFilter)
.TokenTypes;
bool throwExceptionFlag = true;
foreach (var item in tokenTypes)
{
// Check AuthenticationToken is UserGoer or UserMaker by StartsWith function
if (AuthenticationToken.GetType().Name.StartsWith(item.ToString()))
{
throwExceptionFlag = false;
break;
}
}
if (throwExceptionFlag)
throw new Exception("Invalid Authentication Token");
}
public JokerEntity Create(RequestModel<JokerEntity> model) => base.Create(model);
public JokerEntity Update(RequestModel<JokerEntity> model) => base.Update(model);
public JokerEntity Get(RequestModel<JokerEntity> model) => base.Get(model);
public List<JokerEntity> List(RequestModel<JokerEntity> model) => base.List(model);
}
In summary, JokerService can be executable by UserGoer and UserMaker. UserDoer has no permission for this service.
As you see the the usage of AuthenticationFilter attribute, I'm getting custom attributes in the constructor, because i want to know what IToken is. If there is an irrelevant "User Authentication Token" type that is passed as parameter (IToken), program should be throw an exception.
This is my solution, do you think is there any best practice for my problem?
Thank you for your help.
Interesting question. My initial thought at constructive critique would be that the tokens accepted by a particular class via the attribute is something decided at compile time and is unable to change. But, the checking for permissions is happening on the construction of each object.
You can prevent this with a static constructor that sets the tokenTypes variable. Static constructors always run before instance constructors. This is also a good place to ensure that tokenTypes is never null (in the absence of your custom attribute).
Likewise, the looping through tokenTypes can probably be a function that takes in an IToken and the tokenTypes, and more importantly, could probably live in the BaseService.cs. Writing that logic once will make it easier to maintain when some future requirement necessitates its change. :)
See also: https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/static-constructors
Hope this helps.

C# each attribute should execute the TestMethod

I have created a custom xUnit theory test DataAttribute named RoleAttribute:
public class RoleAttribute : DataAttribute
{
public Role Role { get; set; }
public RoleAttribute(Role role, Action<Role> method)
{
Role = role;
AuthRepository.Login(role);
method(role);
}
public override IEnumerable<object[]> GetData(MethodInfo testMethod)
{
return new[] { new object[] { Role } };
}
}
And I have the test method OpenProfilePageTest:
public class ProfileTest : AuthTest
{
[Theory, Priority(0)]
[Role(Enums.Role.SuperUser, OpenProfilePageTest)]
[Role(Enums.Role.Editor, OpenProfilePageTest)]
public void OpenProfilePageTest(Enums.Role role)
{
var profile = GetPage<ProfilePage>();
profile.GoTo();
profile.IsAt();
}
}
What I want is that for each role (attribute) it executes first:
AuthRepository.Login(role); (constructor of RoleAttribute)
and then resumes with the code inside OpenProfilePageTest() method. Before it repeats the same but for the second attribute.
How can I accomplish this, right now I'm trying to accomplish this by passing the OpenProfilePageTest() method inside the attribute and execute it in its constructor. There must be a better way to accomplish this than passing around the method I believe?
You can achieve this without passing the method, you need to modify your attribute slightly. I changed the attribute to take all the roles you want to test and return them in the data. Here is an example
public class RolesAttribute : DataAttribute
{
private Role[] _roles;
public RolesAttribute(params Role[] roles)
{
_roles = roles;
}
public override IEnumerable<object[]> GetData(MethodInfo testMethod)
{
var data = new List<object[]>();
//We need to add each role param to the list of object[] params
//This will call the method for each role
foreach(var role in _roles)
data.Add(new object[]{role});
return data;
}
}
Then in your test, you just pass all the roles you want to test in a single attribute like so
public class ProfileTest : AuthTest
{
[Theory, Priority(0)]
[Roles(Enums.Role.SuperUser, Enums.Role.Editor)]
public void OpenProfilePageTest(Enums.Role role)
{
AuthRepository.Login(role);
var profile = GetPage<ProfilePage>();
profile.GoTo();
profile.IsAt();
}
}
Having an Attribute performing functions other than providing meta data about its adorned member is mixing concerns that cause unnecessary complications and not what it was designed for.
The entire custom attribute can be done away with and the built-in data attributes used instead
For example
public class ProfileTest : AuthTest {
[Theory, Priority(0)]
[InlineData(Enums.Role.SuperUser)]
[InlineData(Enums.Role.Editor)]
public void OpenProfilePageTest(Enums.Role role) {
//Arrange
AuthRepository.Login(role);
var profile = GetPage<ProfilePage>();
//Act
profile.GoTo();
//Assert
profile.IsAt();
}
}
AuthRepository.Login in this case is part of the setup/arrangement for exercising the desired use case.

How can I enhance ModelBinder in ASP.NET Core (update property values for a model class)

I would like to enhance final result that ModelBinder returns.
For example:
public class MyModel
{
public int Order {get;set;}
[MyUpperCaseAttribute]
public string Title {get;set;}
}
In API method I expect that all string properties in MyModel which has MyUpperCaseAttribute is in upper case.
For example:
[HttpPost("AddRecord")]
public async Task<ActionResult<int>> AddRecord(MyModel model)
{
model.Title should be upper case, even if send from client in lower case.
}
My idea was to override default ModelBinder and enumerate through all properties and check if property is string and has MyUpperCaseAttribute and correct property value to upper case. I check documentation, but doesn't examples doesn't fill right, since they completely redesign what is returned. I would like to just modify result properties.
What would be the best option to achieve desired behaviour?
Important: (edited):
It would be nice if directive attributes could be stacked:
public class MyModel
{
public int Order {get;set;}
[MyUpperCaseAttribute]
[RemoveSpacesAttribute]
public string Title {get;set;}
}
Edited:
It looks similar to this, but if not other, this is ASP.NET Core, and on link is just ASP.NET. Method, properties, interfaces... are not the same.
I should say, that it would be nice if JSON case rule would work:
public class MyModel
{
public int Order {get;set;}
public string Title {get;set;}
}
should work if {order: 1, title: "test"} (notice lowercase) is send from JavaScript.
This might not be the 'best' option, but I would just use .ToUpper() extension method instead of a custom attribute filter.
public class MyModel
{
private string _title;
public int Order {get;set;}
public string Title { get => _title.ToUpper(); set => _title = value.ToUpper(); }
}
There's a big red herring here, and that's the fact that it appears that this is the sort of thing that could and should be accomplished via model binding. Unfortunately, that's not the case in ASP.Net Core Web API: because the incoming data is JSON, it is in fact handled by input formatters, not model binders. Therefore, in order to achieve the desired effect, you need to create your own custom input formatter that replaces the standard JsonInputFormatter.
First the attribute:
[AttributeUsage(AttributeTargets.Property)]
public class ToUppercaseAttribute : Attribute
{
}
Then we decorate our model class with it:
public class MyModel
{
public int Order { get; set; }
[ToUppercase]
public string Title { get; set; }
}
Now create our custom input formatter that checks for that attribute and transforms the output if necessary. In this case, it simply wraps and delegates to JsonInputFormatter to do the heavy lifting as normal, then modifies the result if it finds our ToUppercaseAttribute attribute on any string property:
public class ToUppercaseJsonInputFormatter : TextInputFormatter
{
private readonly JsonInputFormatter _jsonInputFormatter;
public ToUppercaseJsonInputFormatter(JsonInputFormatter jsonInputFormatter)
{
_jsonInputFormatter = jsonInputFormatter;
foreach (var supportedEncoding in _jsonInputFormatter.SupportedEncodings)
SupportedEncodings.Add(supportedEncoding);
foreach (var supportedMediaType in _jsonInputFormatter.SupportedMediaTypes)
SupportedMediaTypes.Add(supportedMediaType);
}
public override Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding)
{
var result = _jsonInputFormatter.ReadRequestBodyAsync(context, encoding);
foreach (var property in context.ModelType.GetProperties().Where(p => p.PropertyType.IsAssignableFrom(typeof(string))
&& p.CustomAttributes.Any(a => a.AttributeType.IsAssignableFrom(typeof(ToUppercaseAttribute)))))
{
var value = (string)property.GetValue(result.Result.Model);
property.SetValue(result.Result.Model, value.ToUpper());
}
return result;
}
}
Next we create an extension method that makes it simple to substitute the default JsonInputFormatter with our custom formatter:
public static class MvcOptionsExtensions
{
public static void UseToUppercaseJsonInputFormatter(this MvcOptions opts)
{
if (opts.InputFormatters.FirstOrDefault(f => f is JsonInputFormatter && !(f is JsonPatchInputFormatter)) is JsonInputFormatter jsonInputFormatter)
{
var jsonInputFormatterIndex = opts.InputFormatters.IndexOf(jsonInputFormatter);
opts.InputFormatters[jsonInputFormatterIndex] = new ToUppercaseJsonInputFormatter(jsonInputFormatter);
}
}
}
And finally, call that method to effect the replacement in Startup.cs:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services
.AddMvc(options => options.UseToUppercaseJsonInputFormatter());
}
}
Et voilĂ !
You can do this thing inside your MyUpperCaseAttribute as follows:
public class MyUpperCaseAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if(value != null)
{
validationContext.ObjectType
.GetProperty(validationContext.MemberName)
.SetValue(validationContext.ObjectInstance, value.ToString().ToUpper(), null);
}
return null;
}
}
Property value will be converted to UpperCase during Model Binding. I have checked it in my side and it works perfectly.

Custom attributes C# not working

I have AuthActivityAttribute class. the purpose of this class is to authorize that the user have permission to perform specific activity.
Attribute Class :
[AttributeUsage(AttributeTargets.All)]
public class AuthActivityAttribute : Attribute
{
#region Properties
public string ActivityName { get; set; }
#endregion
#region Constructor
public AuthActivityAttribute()
{
}
#endregion
#region MemberFunctions
private List<aspnetactivities> GetUserActivities(ApplicationUser currentUser)
{
IList<string> roles = DALAccessObjectObj.UserDALObj.GetUserRoles(currentUser);
List<aspnetactivities> lstAspnetActivites = new List<aspnetactivities>();
foreach (string role in roles)
{
List<aspnetactivities> activities = DALAccessObjectObj.UserDALObj.GetRoleActivity(role);
lstAspnetActivites.AddRange(activities);
}
return lstAspnetActivites;
}
public void ValidateUserActivity()
{
DALAccessObjectObj.UserDALObj = new UserDAL();
ApplicationUser currentUser = DALAccessObjectObj.UserDALObj.GetUserById(HttpContext.Current.User.Identity.GetUserId());
if (GetUserActivities(currentUser).Where(r => r.ActivityName.Equals(ActivityName, StringComparison.InvariantCultureIgnoreCase)
).Select(r => r).Count() > 0)
{
throw new Exception(string.Format("User is not allowed to perform activity named : {0}", ActivityName));
}
}
#endregion
}
I have a Account controller class. All I need is user can only be registered if he is allowed to perform registration activity. However when i send the request the attribute does not validate any thing . Please let me know am i missing something or what ?
Class decorated With Attribute
public class AccountController : BaseApiController
{
[AuthActivityAttribute(ActivityName = "Register")]
public async Task<IHttpActionResult> Register(RegisterBindingModel model)
{
// do something ...
}
}
for example : we put validation on property like [MaxLength(10)] so it validates that the property must have length less than 10. or Authorize attribute in C#. like only admin can access the specific method. So this is something i need to achieve
[Authorize("Administrator")]
public void DeleteUser()
{
// do something
}
What i want ?
[AuthActivity("DeleteUser")]
public void DeleteUser()
{
// do something
}
If your goal is to let or not the user to perform a task, you don't need to create a custom attribute, you can use Authorize attribute, for each action and specify the Roles which are allowed to execute that action.
Any way, if you want to perform some custom task using a custom attribute, you must use reflection to get the actions which has that attribute and to get the properties of that attribute, something like:
public static class CustomAttrr
{
public static IEnumerable<ActionsWithAuthActivityAttribute> GetItems(Assembly types)
{
var model = from type in types.GetTypes()
from methodInfo in type.GetMethods().Where(x => x.GetCustomAttributes<AuthActivityAttribute>().Any())
from attribute in methodInfo.GetCustomAttributes()
where attribute is AuthActivityAttribute
let a = attribute as AuthActivityAttribute
select new ActionsWithAuthActivityAttribute
{
ActionName = methodInfo.Name,
ActivityName = a.ActivityName,
};
return model.ToList();
}
}
public class AuthActivityAttribute:Attribute
{
public string ActivityName { get; set; }
}
public class ActionsWithAuthActivityAttribute
{
public string ActionName { get; set; }
public string ActivityName { get; set; }
}
Now, you have a list of all actions decorated with your attribute, and you can do what ever you want.
var listAction = CustomAttrr.GetItems(Assembly.GetExecutingAssembly());
var listActionsRegister = listAction.Where(x => x.ActivityName.Equals("Register"));
Now you can check user role versus this list, but like I said, you do not need this custom attribute.
I posted this code only for you to see how to access the custom attribute.

C# Attributes : Using enum constants as input parameters

I'm trying to implementing a new permission based access approach for my MVC application; We have several Permission Group and each group contains a list of Permission. for example we have Invoices permission group which contains CreateInvoice,RemoveInvoice,etc permission keys.
In this approach each mvc Action should requires a specific permission for execution. I'm trying to do this through CustomAttributes, something like this :
public class InvoiceController : Controller
{
[RequirePermission(Permissions.Invoices.CreateInvoice)]
public ActionResult Create()
{
return View();
}
}
To make it easier for developers to remember different Permission Groups and Permission Keys I'm trying to create a pre-defined list of permissions that should be a combination of permission group and permission key. but due to restrictions applied to using attributes arguments in C#
I couldn't make it work yet. (I don't want to make an extra large enumurator and put all permission keys in there)
my last try was creating an enumerator for each permission group and then define permission keys as enum constants in there :
public class PermissionEnums
{
[PermissionGroup(PermissionGroupCode.Invoice)]
public enum Invoices
{
CreateInvoice = 1,
UpdateInvoice = 2,
RemoveInvoice = 3,
ManageAttachments = 4
}
[PermissionGroup(PermissionGroupCode.UserAccounts)]
public enum UserAccounts
{
Create = 1,
ChangePassword = 2
}
}
As you can see we have a combination of codes here, the permission group key specified using a PermissionGroup attribute and permission key's code specified as numeral code on each enum constant.
the RequirePermission attribute defined as below :
public class RequirePermissionAttribute : Attribute
{
private Enum _Permission;
public RequirePermissionAttribute(Enum Permission)
: base()
{
_Permission = Permission;
}
}
but the problem is that objects of type Enum could not be used as Attribute Arguments.
Any suggestion/idea is appreciated
I've found the solution, the only thing needs to be changed is type of constructure parameter. instead of using Enum you have to use object :
public class RequirePermissionAttribute : AuthorizeAttribute
{
private object _Permission;
public RequirePermissionAttribute(object Permission)
: base()
{
_Permission = Permission;
}
}
Here is the complete code :
/***************** Permission Groups And Keys *****************/
public static class Permissions
{
[PermissionGroup(PermissionGroupCode.Invoice)]
public enum Invoices
{
CreateInvoice = 1,
UpdateInvoice = 2,
RemoveInvoice = 3,
ManageAttachments = 4
}
[PermissionGroup(PermissionGroupCode.UserAccounts)]
public enum UserAccounts
{
Create = 1,
ChangePassword = 2
}
}
public enum PermissionGroupCode
{
Invoice = 1,
UserAccounts = 2,
Members = 3
}
/***************** Attributes & ActionFilters *****************/
[AttributeUsage(AttributeTargets.Enum)]
public class PermissionGroupAttribute : Attribute
{
private PermissionGroupCode _GroupCode;
public PermissionGroupCode GroupCode
{
get
{
return _GroupCode;
}
}
public PermissionGroupAttribute(PermissionGroupCode GroupCode)
{
_GroupCode = GroupCode;
}
}
public class RequirePermissionAttribute : AuthorizeAttribute
{
private object _RequiredPermission;
public RequirePermissionAttribute(object RequiredPermission)
: base()
{
_RequiredPermission = RequiredPermission;
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var permissionGroupMetadata = (PermissionGroupAttribute)_RequiredPermission.GetType().GetCustomAttributes(typeof(PermissionGroupAttribute), false)[0];
var groupCode = permissionGroupMetadata.GroupCode;
var permissionCode = Convert.ToInt32(_RequiredPermission);
return HasPermission(currentUserId, groupCode, permissionCode);
}
}
I don't think thats possible I tried to do your thing and failed :/ sorry.
Permissions on actions should be used with Authorize and you can make your own ovveride writing something like this:
[AttributeUsage(AttributeTargets.All)]
public sealed class CustomAuthorizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext == null)
throw new ArgumentNullException("httpContext");
//Its a piece of code from my app you can modify it to suit your needs or use the base one
if (!new CustomIdentity(httpContext.User.Identity.Name).IsAuthenticated)
{
return false;
}
return true;
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
base.HandleUnauthorizedRequest(filterContext);
}
}
then on your action:
[CustomAuthorizeAttribute(Roles = "FE")]
public ActionResult Index()
{
return RedirectToAction("Index", "Documents");
}
however its still a string that you use and for it to work you need to combine it with Custom Role provider. Much hussle but worth it in my opinion.

Categories