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.
Related
I have a blazor Net 5.0 spa project
I would like to use a enum like this one for a strongly typed AuthorizeAttribute and AuthorizeView for Blazor components with added functionality like checking several roles exist instead either of many exists
Enum
public enum RolesEnum
{
Access = 1,
Administrator = 2
}
How do I create an extended version of AuthorizeAttribute and AuthorizeView?
Edit
for a simple start I have tried making a class like this one without getting authorized.
I also tried adding it into services.
public class StrongRoleAuthorizeAttribute : AuthorizeAttribute
{
public StrongRoleAuthorizeAttribute(params RolesEnum[] rolesEnums) : base()
{
StrongRoles = rolesEnums;
}
private RolesEnum[] StrongRoles
{
get
{
return this.Roles.Split(",").Select(r => ClaimRoles.GetRolesTypeEnum(r)).ToArray();
}
set
{
this.Roles = string.Join(",", value.Select(r => r.RolesEnumToStringClaim()));
}
}
}
Edit2
I used the wrong method to convert to the claim value
You can simply inherit the attribute :
public class MyAuthorizeAttribute : AuthorizeAttribute
{
private readonly RolesEnum _authorizedRole;
public MyAuthorizeAttribute(RolesEnum authorizedRole)
{
_autorizedRole = authorizedRole;
}
// Override any method you want to use your enum
}
And the usage would be :
[MyAuthorize(RolesEnum.Administrator)]
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.
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()
{
}
}
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.
I am busy writing my own custom attribute for my action method called MyAuthorizeAttribute, I am still busy writing the code, here is my partial code:
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class MyAuthorizeAttribute : AuthorizeAttribute
{
public new Role Roles;
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
if (Roles != 0) // Did it this way to see what the value of Roles was
return;
// Here I am going to get a list of user roles
// I'm doing my own database calls
filterContext.Result = new HttpUnauthorizedResult();
}
}
Here is my Role enum:
public enum Role
{
Administrator = 1,
SuperAdministrator = 2
}
My action method:
[MyAuthorize(Roles = Role.Administrator|Role.SuperAdministrator)]
public ActionResult Create()
{
return View();
}
The reason why I did not use Roles = "Administrator,SuperAdministrator" was because the roles are hard-coded. I don't want to have a 100 places to change if the role name changes.
Given my method, when it gets to if (Roles != 0) then Roles total value is 3, how would I check to see if these 2 roles is in the list of user roles for a specific user?
Am I doing it correct here? If not how would I otherwise implement this? It doesn't have to be the way that I did it in.
Would it not be better if MyAuthorizeAttribute accepted an IList ( or similar)
That way it is both typesafe, but you don't have to use bit flags.
Bit flags are great if you want to save the reult, but this is the other way.
Edit (Now with examples):
Attribute:
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class MyAuthorizeAttribute : AuthorizeAttribute
{
public Role[] RoleList { get; set; }
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext == null)
{
throw new ArgumentNullException("httpContext");
}
IPrincipal user = httpContext.User;
if (!user.Identity.IsAuthenticated)
{
return false;
}
//Only role access is implemented here
/*if ((this._usersSplit.Length > 0) && !this._usersSplit.Contains<string>(user.Identity.Name, StringComparer.OrdinalIgnoreCase))
{
return false;
}*/
if ((RoleList.Length > 0) && !RoleList.Select(p=>p.ToString()).Any<string>(new Func<string, bool>(user.IsInRole)))
{
return false;
}
return true;
}
}
Controller:
[MyAuthorize(RoleList = new []{Role.Administrator , Role.SuperAdministrator} )]
public ActionResult Create()
{
return View();
}
If I understand correctly, your problem here is not with inheriting the AuthorizeAttribute, but rather with comparing enum values. You probably want an enum type that you can use as a bit flag - if so, take a look at the section about Enumeration Types in the C# Programming guide especially the second part, "Enumeration Types as Bit Flags".
To clarify a bit:
Instead of just checking Roles!=0, you could now do something like this:
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
// Here you get an enum indicating the roles this user is in. The method
// converts the db information to a Role enum before it is returned.
// If the user is not authenticated, the flag should not be set, i.e. equal 0.
Role userRole = GetUserRolesFromDatabase();
// Bitwise comparison of the two role collections.
if (Roles & userRole > 0)
{
// The user is in at least one of the roles in Roles. Return normally.
return;
}
// If we haven't returned yet, the user doesn't have the required privileges.
new HttpUnauthorizedResult();
}
To make the comparison easier, you could use the following extension method on your enum:
public static class RolesExtensions
{
public static bool HasAnyOf(this Roles r1, Roles roles)
{
return (r1 & roles) > 0;
}
}