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.
Related
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.
I'm trying to find the "best" way to check the coherence between data in the request URL and data in the body of the request.
For example, let's say we have this PUT endpoint:
https://host:port/foo/bar/{carId}
where I can pass a Car object as body:
class Car {
string CarId { get; set; }
string Brand { get; set; }
int MaxSpeed { get; set; }
...
}
Now, in my controller I have something like this:
[HttpPut("foo/bar/{carId}")]
public async Task updateCar([FromRoute] string carId, [FromBody] Car car) {
...
}
And I want to be sure that carId in the route matches the CarId property in the body.
What is the "best" way to achieve this? Of course I could simply check with an if in the body controller, but since this is a validation task (or at least I think so), I'd like to have this logic in my validation layer.
Personal ideas so far
Ok, the question is over, here I'll just put some ideas I've tried or I want to try.
I have a custom action filter to check the validation, and I'm trying to play with it in order to see if I can do something there, or add another custom action filter only where I want to check the coherence, but this doesn't look promising.
At the moment I've seen that in an action filter I have access to the controller method parameters through context.ActionArguments property, but I don't know how to check if these arguments were "bound arguments" (namely had some [FromXXX] attribute). If I could do this maybe I could check if there are arguments with the same name (or with a property with the same name), and then compare their values. But this seems very cumbersome and inconsistent.
I've read about custom binders, but I'm still scratching the surface (I hope to learn something more in the next few hours): can they be a possible solution?
You should do it like this:
[AttributeUsage(AttributeTargets.Property)]
public class VerifyWithRouteParams : Attribute
{
public string ParamName
{
get
{
return paramName;
}
}
private readonly string paramName;
public VerifyWithRouteParams(string paramName)
{
this.paramName = paramName;
}
}
Consider you have a car model that is being recieved through body:
public class Car
{
[VerifyWithRouteParams("carId")]
public string CarId { set; get; }
public string AnotherParam {set; get;}
}
You should have a default filter:
public class RouteBodyVerificationActionFilter : IActionFilter
{
public void OnActionExecuted(ActionExecutedContext context)
{
}
public void OnActionExecuting(ActionExecutingContext context)
{
// do something before the action executes
if (context.ActionArguments != null && context.ActionArguments.Count > 0)
{
foreach (var arg in context.ActionArguments)
{
if (arg.Value == null) continue;
bool isThereAnyObjectInArgumentsWithVerificationAttribute = arg.Value
.GetType()
.GetProperties()
.Any(
x => x.GetCustomAttributes(typeof(VerifyWithRouteParams), false).Any()
);
if (isThereAnyObjectInArgumentsWithVerificationAttribute)
{
foreach (var prop in arg.Value.GetType().GetProperties())
{
var verificationAttr = prop.GetCustomAttributes(typeof(VerifyWithRouteParams), false).FirstOrDefault() as VerifyWithRouteParams;
if (null == verificationAttr) continue;
string routeArgumentName = verificationAttr.ParamName;
context.ActionArguments.TryGetValue(routeArgumentName, out var routeArgumentValue);
if (null == routeArgumentValue)
{
context.ModelState.AddModelError("invalid argument value", routeArgumentName);
}
if (routeArgumentValue?.Equals(prop.GetValue(arg.Value)) != true)
{
context.ModelState.AddModelError("invalid argument value", routeArgumentName);
}
}
}
}
}
}
}
Then you should add a default filter in startup:
services.AddControllers(cfg =>
{
cfg.Filters.Add<RouteBodyVerificationActionFilter>();
});
Then you can check it by model validation errors in controller:
if (!ModelState.IsValid)
{
return Content("invalid model");
}
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'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.