I got the below compilation error when I added "System.Configuration.ConfigurationManager.AppSettings["ADGroupReader"].ToString()" to the authorize role section header.
In the web.config I have:
add key="ADGroupReader" value="Readers DEV"
Compilation error: An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type
[AuthorizedRedirect]
[Authorize(Roles = System.Configuration.ConfigurationManager.AppSettings["ADGroupReader"].ToString())]
public class HomeController : Controller
{
.....
}
I do not want to hard code the role (Roles="Readers DEV"); I would like to read it from the web.config. How can I do that?
This attributes tutorial explains attribute parameter restrictions:
Attribute parameters are restricted to constant values of the
following types:
Simple types (bool, byte, char, short, int, long, float, and double)
string
System.Type
enums
object (The argument to an attribute parameter of type object must be a constant value of one of the above types.)
One-dimensional arrays of any of the above types
From description above, this assignment is invalid due to existence of ToString method:
[Authorize(Roles = System.Configuration.ConfigurationManager.AppSettings["ADGroupReader"].ToString())]
As a workaround, you can create a custom AuthorizeAttribute with predefined Roles parameter which contains default assignment to Roles with your AppSettings:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
public CustomAuthorizeAttribute()
{
this.Roles = ConfigurationManager.AppSettings["ADGroupReader"].ToString();
}
// other stuff
}
Usage in controller class:
[AuthorizedRedirect]
[CustomAuthorize]
public class HomeController : Controller
{
.....
}
I solved it this way
Created a derived class ReaderAuthorizeAttribute
public class ReaderAuthorizeAttribute : AuthorizeAttribute
{
public ReaderAuthorizeAttribute()
{
this.Roles = System.Configuration.ConfigurationManager.AppSettings["ADGroupReader"];
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
return base.AuthorizeCore(httpContext);
}
}
Then added [LoteReaderAuthorizeAttribute]
[AuthorizedRedirect]
[ReaderAuthorizeAttribute]
public class HomeController : Controller
{
....
}
I solved this the following way, allowing you to define the setting name the roles value should come from:
public class AuthorizeBySettingAttribute : AuthorizeAttribute
{
public AuthorizeBySettingAttribute(string setting) : base()
{
if (setting != null && Settings.Default[setting] != null)
{
this.Roles = Settings.Default[setting].ToString();
}
else
{
throw new InvalidOperationException("AuthorizeBySetting initialized with invalid setting");
}
}
}
In my case I'm getting the value from a generated Settings class, but you can just change Settings.Default to ConfigurationManager.AppSettings.
Related
My route is defined as http:/.../home/index/1 which is (controller/action/id).
ViewModel is as below..
public class TestVM
{
[CustomValidation]
public string name{get; set;}
}
public class CustomValidation : ValidationAttribute
{
protected override ValidationResult? IsValid (object? value, validationContext)
{
var vm = (TestVM)validationContext.ObjectInstance;
//how to get the route value here?
}
}
To validate Name property, I need to have value of Id. To access route value in the IsValid method, I defined one more property Id in the TestVM.
Is there a way to access the Id in the IsValid without defining in the TestVM?
Is there a way to access the Id in the IsValid without defining in the
TestVM?
Seems you are trying to access parameter which has been passed to controller route so that, you can get those values inside your CustomValidation class
Well, using IHttpContextAccessor we can get all route value from which are available within HttpContext.Request. Thus, you can access property name of member of TestVM class.
Access Route Value Inside ValidationAttribute:
You can implement in following way:
public class CustomValidator : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext context)
{
var vm = context.ObjectInstance as TestVM;
var httpContextAccessor = context.GetRequiredService<IHttpContextAccessor>()
var getIdFromRouteValue = httpContextAccessor.HttpContext.Request.Query["Id"];
var getNameFromRouteValue = httpContextAccessor.HttpContext.Request.Query["name"];
vm.Id = getIdFromRouteValue;
vm.name = getNameFromRouteValue;
return ValidationResult.Success;
}
}
Program.cs:
IHttpContextAccessor requires to register AddHttpContextAccessor() service on your program.cs file. You can do as following:
builder.Services.AddHttpContextAccessor();
Note: Please make sure you have followed the correct order. You can get more details on our official document here.
Output:
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 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 am writing an app using .NET Web API (4.6 Framework)
I have an attribute that I use: [ApiExplorerSettings(IgnoreApi = true)] to hide certain controllers from my Swagger.
This attribute is a part of : System.Web.Http.Description
Basically I want to create an AppSetting in my web.config file so when I publish to Development, the controllers show up (IgnoreApi = false) and when I publish to Production, the controllers are hidden (IgnoreApi = true).
I've tried accessing ConfigurationManager.AppSettings directly in the attribute, but that seems to not work as expected.
Perhaps I need to find a way to override that attribute so that on the getter/setter of IgnoreApi, it can pull the correct value from my web.config?
Extending the "ApiExplorerSettingsAttribute" class seems straightforward but its sealed. So ended up with the following workaround;
A custom attribute which inherits from the base calss "Attribute".
IncludeInApiExplorerAttribute.cs class
public class IncludeInApiExplorerAttribute : Attribute
{
private readonly bool value;
public IncludeInApiExplorerAttribute(string IsInAPI=null)
{
if (!string.IsNullOrEmpty(IsInAPI))
{
value = Convert.ToBoolean(ConfigurationManager.AppSettings[IsInAPI]); //Reads the app config value
}
else
{
value = true;
}
}
public bool Value { get { return value; } }
}
Then we can implement a custom ApiExplorer as below.
OptApiExplorer.cs Class
public class OptApiExplorer : ApiExplorer
{
public OptApiExplorer(HttpConfiguration configuration)
: base(configuration)
{
}
//Overrides the method from the base class
public override bool ShouldExploreAction(string actionVariableValue, HttpActionDescriptor actionDescriptor, IHttpRoute route)
{
var includeAttribute = actionDescriptor.GetCustomAttributes<IncludeInApiExplorerAttribute>().FirstOrDefault(); //Get the given custom attribute from the action
if (includeAttribute != null)
{
return includeAttribute.Value && MatchRegexConstraint(route, "action", actionVariableValue); //If it is not null read the includeAttribute.Value which is set in app.config and return true or false based on the includeAttribute.Value and MatchRegexConstraint return value
}
var includeControlAttribute = actionDescriptor.ControllerDescriptor.GetCustomAttributes<IncludeInApiExplorerAttribute>().FirstOrDefault(); //If the action does not have any given type of custom attribute then chekc it in the controller level
if (includeControlAttribute != null)
{
return includeControlAttribute.Value && MatchRegexConstraint(route, "action", actionVariableValue);//Similar to action level
}
return true && MatchRegexConstraint(route, "action", actionVariableValue);
}
//This method is as it is in the base class
private static bool MatchRegexConstraint(IHttpRoute route, string parameterName, string parameterValue)
{
IDictionary<string, object> constraints = route.Constraints;
if (constraints != null)
{
object constraint;
if (constraints.TryGetValue(parameterName, out constraint))
{
string constraintsRule = constraint as string;
if (constraintsRule != null)
{
string constraintsRegEx = "^(" + constraintsRule + ")$";
return parameterValue != null && Regex.IsMatch(parameterValue, constraintsRegEx, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
}
}
}
return true;
}
}
Web config settings
This is the value read by our custom attribute. Add this to web.config file
<appSettings>
<add key="IsInAPI" value="false"/>
</appSettings>
In WebAPI.config.cs file add the following
There we have replaced the IApiExplorer with the custom class.
config.Services.Replace(typeof(IApiExplorer), new OptApiExplorer(config));
Then in your Controller or in the Action you can add the custom
attribute as below.
[IncludeInApiExplorer("IsInAPI")]
IsInApi is the web.config value which we can set to true or false. If it is not set then it will default set to true as we have implemented in IncludeInApiExplorerAttribute class.
Refer this post for more insight on this.
Say I have an enumeration like:
public enum Permissions
{
One,
Two,
Three
}
How can I create a custom filter that I can use on a controller or action that looks like:
[PermissionCheck(Permissions.One | Permissions.Two)]
public class MyController : Controller
{
...
}
Is this possible?
public class PermissionCheckAttribute: ActionFilterAttribute
{
public Permissions Permissions {get;set;}
public PermissionCheck(Permissions permissions)
{
Permissions = permissions;
}
}
You can also try using named parameters:
[PermissionsCheck(Permissions = Permissions.None)]
If the problem you're having is not being able to use multiple enum values then you're not using the [Flags] attribute on your enum. Flags