Im trying to learn .net 3.1 by building a small test webapi and currently my objective is to validate dtos with fluentvalidation and in case it fails, present a custom json to the caller. The problems i have found and cant get over are two;
i cant seem to get the messages i write via fluentvalidation (they always are the - i assume .net core default ones)
i cant seem to modify the object type that is json-ified and then output to the caller.
My code is as follows:
1. The Controller
[ApiController]
[Route("[controller]")]
public class AdminController : ControllerBase
{
[HttpPost]
[ProducesResponseType(StatusCodes.Status409Conflict)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status202Accepted)]
public async Task<IActionResult> RegisterAccount(NewAccountInput dto)
{
return Ok();
}
}
2. The Dto and the custom validator
public class NewAccountInput
{
public string Username { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
public AccountType Type { get; set; }
}
public class NewAccountInputValidator : AbstractValidator<NewAccountInput>
{
public NewAccountInputValidator()
{
RuleFor(o => o.Email).NotNull().NotEmpty().WithMessage("Email vazio");
RuleFor(o => o.Username).NotNull().NotEmpty().WithMessage("Username vazio");
}
}
3. The Filter im using for validation
public class ApiValidationFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
//the only output i want are the error descriptions, nothing else
var data = context.ModelState
.Values
.SelectMany(v => v.Errors.Select(b => b.ErrorMessage))
.ToList();
context.Result = new JsonResult(data) { StatusCode = 400 };
}
//base.OnActionExecuting(context);
}
}
finally, my configureservices
public void ConfigureServices(IServiceCollection services)
{
services
//tried both lines and it doesnt seem to work either way
.AddScoped<ApiValidationFilter>()
.AddControllers(//config=>
//config.Filters.Add(new ApiValidationFilter())
)
.AddFluentValidation(fv => {
fv.RunDefaultMvcValidationAfterFluentValidationExecutes = false;//i was hoping this did the trick
fv.RegisterValidatorsFromAssemblyContaining<NewAccountInputValidator>();
});
}
Now, trying this with postman i get the result
which highlights both issues im having atm
This was done with asp.net core 3.15 and visualstudio 16.6.3
The message you are seeing is in fact coming from FluentValidation - see the source.
The reason you aren't seeing the custom message you are providing is that FluentValidation will show the validation message from the first validator that fails in the chain, in this case NotNull.
This question gives some options for specifying a single custom validation message for an entire chain of validators.
In this case the Action Filter you describe is never being hit, as the validation is failing first. To prevent this you can use:
services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
which will stop the automatic return of BadRequest for an invalid model. This question provides some alternative solutions, including configuring an InvalidModelStateResponseFactory to do what you require.
Related
I created a new .Net 5 Web API with the following setup
public class MyController : ControllerBase
{
[HttpGet("{Title}")]
public ActionResult<string> Get([FromRoute] RouteModel routeModel)
{
return Ok(routeModel);
}
}
public class RouteModel
{
[MinLength(3)]
public string Title { get; set; }
}
The request validation works fine. Since I'm using FluentValidation I installed the package
FluentValidation.AspNetCore v10.3.0
and updated the Startup.ConfigureServices method to
public void ConfigureServices(IServiceCollection services)
{
services.AddValidatorsFromAssemblies(AppDomain.CurrentDomain.GetAssemblies());
services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "WebApplication", Version = "v1" });
});
}
Instead of using the data annotations I removed the data annotations from RouteModel.Title and created this validator
public class RouteModelValidator : AbstractValidator<RouteModel>
{
public RouteModelValidator()
{
RuleFor(model => model.Title).MinimumLength(3);
}
}
I would expect a ValidationException and this would result into a 500 status code if the title has a length less than 3. But it seems there is no validation trigger anymore, the request always passes.
Does someone know what's missing?
Register FluentValidation as
services
.AddFluentValidation(x => x.RegisterValidatorsFromAssemblies(AppDomain.CurrentDomain.GetAssemblies()));
Mark your Controller with ApiController attribute
[ApiController]
public class MyController : ControllerBase
Otherwise, you should manually validate ModelState, something like:
[HttpGet("{Title}")]
public ActionResult<string> Get([FromRoute] RouteModel routeModel)
{
if (!ModelState.IsValid)
{
return BadRequest("bad");
}
return Ok(routeModel);
}
Some info:
It depends on the type of controller - if you're using the [ApiController] attribute, then ASP.NET will generate a Bad Request result automatically. If you don't use the ApiController attribute (ie for non-api controllers, such as where you return a view), then you're expected to handle the ModelState.IsValid check manually (see the asp.net doc link above). But again, this is a convention of ASP.NET, it isn't a feature of FluentValidation.
I am working on a custom middlware that's going to set the response Http Status Code based on the response itself.
I have a class:
public class Response<T>
{
public T Data { get; set; }
public IEnumerable<CustomError> Errors { get; set; }
}
that is returned by every controller in my .Net Core API.
I want to create a custom middleware that's going to access the response after it is returned from the controller and it will assign a correct Http Status Code in the Response based on the Errors field.
I can see some solutions for accessing the Response.Body field of the HttpContext, but it would provide a serialized string that I would have to deserialize again and that's running around in circles.
Is it possible in .Net Core?
Best regards,
Marcin
Instead of middleware, you could create an ActionFilter, specifically your own implementation IAsyncResultFilter. It's going to be easier to cast to Response in the MVC context rather than in the middleware because you may access there ObjectResult.
It could look like this.
public class Response
{
public IEnumerable<string> Errors { get; set; }
}
public class Response<T> : Response
{
public T Data { get; set; }
}
Note that I changed the Response<T> class to make casting easier.
public class ErrorResultFilter : IAsyncResultFilter
{
public Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{
var result = context.Result as ObjectResult;
var response = result?.Value as Response;
if (response != null)
context.HttpContext.Response.StatusCode
= response.Errors.Any(x => x.Equals("SomeError")) ? 400 : 200;
return next();
}
}
This sample sets status code based on the presence of "SomeError". There's no serialization/deserialization involved, just casting.
services.AddControllers(o =>
{
o.Filters.Add(typeof(ErrorResultFilter));
});
This way, I registered my filter in the startup.cs
I'm facing an issue while working with web api azure ad authentication
I'm having controller like below, the one which having giving proper response, But the one which having customauthorization roles throwing error as "Authentication has been for this request".
[RoutePrefix("api/hospitals")]
public class hospitals : ApiController
{
[Route("GetAll")]
[HttpGet]
[Authorize]
public async Task<IEnumerable<Hospitals>> GetAll()
{
// return ok;
}
[Route("Getbeds")]
[HttpGet]
[SmAuthorize(Constants.Roles.Admin,
Constants.Roles.HotSpitalAdmin,
Constants.Roles.QA)]
public async Task<IEnumerable<Hospitals>> Getbeds()
{
// return ok;
}
}
The Getbeds method is throwing an error as "Authorization has been request".
Please find me Custom attribute class as well
public class SmAuthorizeAttribute : AuthorizeAttribute
{
public SmAuthorizeAttribute(params string[] roles)
{
this.Roles = string.Join(",", roles.Select(s => s.Trim()).ToArray());
}
}
Can anyone help on this ?
You can refer to this SO question's answer by Derek Greer for Dot Net core, additionally I will reiterate the answer below -
The approach recommended by the ASP.Net Core team is to use the new policy design which is fully documented here. The basic idea behind the new approach is to use the new [Authorize] attribute to designate a "policy" (e.g. [Authorize( Policy = "YouNeedToBe18ToDoThis")] where the policy is registered in the application's Startup.cs to execute some block of code (i.e. ensure the user has an age claim where the age is 18 or older).
The policy design is a great addition to the framework and the ASP.Net Security Core team should be commended for its introduction. That said, it isn't well-suited for all cases. The shortcoming of this approach is that it fails to provide a convenient solution for the most common need of simply asserting that a given controller or action requires a given claim type. In the case where an application may have hundreds of discrete permissions governing CRUD operations on individual REST resources ("CanCreateOrder", "CanReadOrder", "CanUpdateOrder", "CanDeleteOrder", etc.), the new approach either requires repetitive one-to-one mappings between a policy name and a claim name (e.g. options.AddPolicy("CanUpdateOrder", policy => policy.RequireClaim(MyClaimTypes.Permission, "CanUpdateOrder));), or writing some code to perform these registrations at run time (e.g. read all claim types from a database and perform the aforementioned call in a loop). The problem with this approach for the majority of cases is that it's unnecessary overhead.
While the ASP.Net Core Security team recommends never creating your own solution, in some cases this may be the most prudent option with which to start.
The following is an implementation which uses the IAuthorizationFilter to provide a simple way to express a claim requirement for a given controller or action:
public class ClaimRequirementAttribute : TypeFilterAttribute
{
public ClaimRequirementAttribute(string claimType, string claimValue) : base(typeof(ClaimRequirementFilter))
{
Arguments = new object[] {new Claim(claimType, claimValue) };
}
}
public class ClaimRequirementFilter : IAuthorizationFilter
{
readonly Claim _claim;
public ClaimRequirementFilter(Claim claim)
{
_claim = claim;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
var hasClaim = context.HttpContext.User.Claims.Any(c => c.Type == _claim.Type && c.Value == _claim.Value);
if (!hasClaim)
{
context.Result = new ForbidResult();
}
}
}
[Route("api/resource")]
public class MyController : Controller
{
[ClaimRequirement(MyClaimTypes.Permission, "CanReadResource")]
[HttpGet]
public IActionResult GetResource()
{
return Ok();
}
}
Part of this answer for .NET Framework-
Recommended Custom Attribute class:
public class CustomAuthorize : System.Web.Http.AuthorizeAttribute
{
private readonly PermissionAction[] permissionActions;
public CustomAuthorize(PermissionItem item, params PermissionAction[] permissionActions)
{
this.permissionActions = permissionActions;
}
protected override Boolean IsAuthorized(HttpActionContext actionContext)
{
var currentIdentity = actionContext.RequestContext.Principal.Identity;
if (!currentIdentity.IsAuthenticated)
return false;
var userName = currentIdentity.Name;
using (var context = new DataContext())
{
var userStore = new UserStore<AppUser>(context);
var userManager = new UserManager<AppUser>(userStore);
var user = userManager.FindByName(userName);
if (user == null)
return false;
foreach (var role in permissionActions)
if (!userManager.IsInRole(user.Id, Convert.ToString(role)))
return false;
return true;
}
}
}
I just wasted a lot of hours with trying to get a custom ComplexTypeModelBinder to work. Whatever I did, it never worked. As it turns out, this only works when the data is POSTed as form data; when you post a JSON object (in my case from a Swagger "try out" form) the ComplexTypeModelBinder never invokes the SetProperty method.
I have a lot of models, some more complex than others, and I have annotated some of the properties with a custom attribute. Whenever that property is bound I want it to 'normalized' (apply some 'formatting' to it) so that by the time the model gets validated the validator gets to see the 'normalized' data instead of the user-entered data.
I really, really, want to keep the current modelbinding behavior because that currently works fine but with the one exception that the annotated properties are processed by some code implemented by me. All other properties and behavior should be kept as-is. That is why I hoped to inherit from ComplexTypeModelBinder, but, as it turns out, this doesn't work if data is POSTed as JSON.
My (example) model looks like:
public class MyComplexModel
{
public int Id { get; set; }
public string Name { get; set; }
[FormatNumber(NumberFormat.E164)]
public string PhoneNumber { get; set; }
}
My controller method looks like this:
[HttpPost]
public MyComplexModel Post(MyComplexModel model)
{
return model;
}
My (not working) custom ComplexTypeModelBinder looks like:
public class MyModelBinder : ComplexTypeModelBinder
{
private readonly INumberFormatter _numberformatter;
private static readonly ConcurrentDictionary<Type, Dictionary<string, FormatNumberAttribute>> _formatproperties = new ConcurrentDictionary<Type, Dictionary<string, FormatNumberAttribute>>();
public MyModelBinder(IDictionary<ModelMetadata, IModelBinder> propertyBinders, INumberFormatter numberFormatter, ILoggerFactory loggerFactory)
: base(propertyBinders, loggerFactory)
{
_numberformatter = numberFormatter;
}
protected override object CreateModel(ModelBindingContext bindingContext)
{
// Index and cache all properties having the FormatNumber Attribute
_formatproperties.GetOrAdd(bindingContext.ModelType, (t) =>
{
return t.GetProperties().Where(prop => Attribute.IsDefined(prop, typeof(FormatNumberAttribute))).ToDictionary(pi => pi.Name, pi => pi.GetCustomAttribute<FormatNumberAttribute>(), StringComparer.OrdinalIgnoreCase);
});
return base.CreateModel(bindingContext);
}
protected override Task BindProperty(ModelBindingContext bindingContext)
{
return base.BindProperty(bindingContext);
}
protected override void SetProperty(ModelBindingContext bindingContext, string modelName, ModelMetadata propertyMetadata, ModelBindingResult result)
{
if (_formatproperties.TryGetValue(bindingContext.ModelType, out var props) && props.TryGetValue(modelName, out var att))
{
// Do our formatting here
var formatted = _numberformatter.FormatNumber(result.Model as string, att.NumberFormat);
base.SetProperty(bindingContext, modelName, propertyMetadata, ModelBindingResult.Success(formatted));
} else
{
// Do nothing
base.SetProperty(bindingContext, modelName, propertyMetadata, result);
}
}
}
(The actual MyModelBinder checks for the FormatNumber attribute and handles the property accordingly, but I left it out for brevity since it doesn't really matter).
And my ModelBinderProvider:
public class MyModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
var modelType = context.Metadata.ModelType;
if (!typeof(MyComplexModel).IsAssignableFrom(modelType))
return null;
if (!context.Metadata.IsComplexType || context.Metadata.IsCollectionType)
return null;
var propertyBinders = context.Metadata.Properties
.ToDictionary(modelProperty => modelProperty, context.CreateBinder);
return new MyModelBinder(
propertyBinders,
(INumberFormatter)context.Services.GetService(typeof(INumberFormatter)),
(ILoggerFactory)context.Services.GetService(typeof(ILoggerFactory))
);
}
}
And ofcourse, I added the provider in the StartUp class:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(config =>
{
config.ModelBinderProviders.Insert(0, new MyModelBinderProvider());
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
Again, this works fine when data is posted as form-data but not when posted as JSON. What would be the correct way to implement this? I have read somewhere that I shouldn't be looking in the ModelBinding direction but in the "JSON converters" direction but I haven't found anything that actually worked (yet).
Edit: I have created a git repository to demonstrate my problem here. To see my problem, set a breakpoint here in the TestController where the model is returned in the Post method. Start the project; a simple webpage will be shown with two buttons. The left one will post the form data as, well, form-data and you will see the model being returned with a reversed phonenumber (as an example). Click the right button and the data will be posted as a JSON model. Notice the model being returned having a 0 id and null values for both properties.
I am trying to implement permission based access control with aspnet core. For dynamically managing user roles and permissions(create_product, delete_product etc.), they are stored in the database. Data Model is like http://i.stack.imgur.com/CHMPE.png
Before aspnet core (in MVC 5) i was using custom AuthorizeAttribute like below to handle the issue:
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
private readonly string _permissionName { get; set; }
[Inject]
public IAccessControlService _accessControlService { get; set; }
public CustomAuthorizeAttribute(string permissionName = "")
{
_permissionName = permissionName;
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
var user = _accessControlService.GetUser();
if (PermissionName != "" && !user.HasPermission(_permissionName))
{
// set error result
filterContext.HttpContext.Response.StatusCode = 403;
return;
}
filterContext.HttpContext.Items["CUSTOM_USER"] = user;
}
}
Then i was using it in action method like below:
[HttpGet]
[CustomAuthorize(PermissionEnum.PERSON_LIST)]
public ActionResult Index(PersonListQuery query){ }
Additionally, i was using HttpContext.Items["CUSTOM_USER"] in views to show or hide html part:
#if (CurrentUser.HasPermission("<Permission Name>"))
{
}
When i decided to switch aspnet core, all my plan was failed. Because there was no virtual OnAuthorization method in the AuthorizeAttribute. I tried some ways to solve problem. Those are below:
Using new policy based authorization(i think it is not suitable for
my scenerio)
Using custom AuthorizeAttribute and AuthorizationFilter(i read this
post https://stackoverflow.com/a/35863514/5426333 but i couldn’t change it properly)
Using custom middleware(how to get AuthorizeAttribute of current
action?)
Using ActionFilter(is it correct for security purpose?)
I couldn’t decide which way is the best for my scenerio and how to implement it.
First question: Is MVC5 implementation bad practice?
Second question: Do you have any suggest to implement aspnet core?
Based on the comments, here an example on how to use the policy based authorization:
public class PermissionRequirement : IAuthorizationRequirement
{
public PermissionRequirement(PermissionEnum permission)
{
Permission = permission;
}
public PermissionEnum Permission { get; }
}
public class PermissionHandler : AuthorizationHandler<PermissionRequirement>
{
private readonly IUserPermissionsRepository permissionRepository;
public PermissionHandler(IUserPermissionsRepository permissionRepository)
{
if(permissionRepository == null)
throw new ArgumentNullException(nameof(permissionRepository));
this.permissionRepository = permissionRepository;
}
protected override void Handle(AuthorizationContext context, PermissionRequirement requirement)
{
if(context.User == null)
{
// no user authorizedd. Alternatively call context.Fail() to ensure a failure
// as another handler for this requirement may succeed
return null;
}
bool hasPermission = permissionRepository.CheckPermissionForUser(context.User, requirement.Permission);
if (hasPermission)
{
context.Succeed(requirement);
}
}
}
And register it in your Startup class:
services.AddAuthorization(options =>
{
UserDbContext context = ...;
foreach(var permission in context.Permissions)
{
// assuming .Permission is enum
options.AddPolicy(permission.Permission.ToString(),
policy => policy.Requirements.Add(new PermissionRequirement(permission.Permission)));
}
});
// Register it as scope, because it uses Repository that probably uses dbcontext
services.AddScope<IAuthorizationHandler, PermissionHandler>();
And finally in the controller
[HttpGet]
[Authorize(Policy = PermissionEnum.PERSON_LIST.ToString())]
public ActionResult Index(PersonListQuery query)
{
...
}
The advantage of this solution is that you can also have multiple handlers for a requirement, i.e. if first one succeed the second handler can determine it's a fail and you can use it with resource based authorization with little extra effort.
The policy based approach is the preferred way to do it by the ASP.NET Core team.
From blowdart:
We don't want you writing custom authorize attributes. If you need to do that we've done something wrong. Instead you should be writing authorization requirements.
I had same requirement and i have done it as below and it works fine for me. I am using .Net Core 2.0 Webapi
[AttributeUsage(AttributeTargets.Class |
AttributeTargets.Method
, AllowMultiple = true
, Inherited = true)]
public class CheckAccessAttribute : AuthorizeAttribute, IAuthorizationFilter
{
private string[] _permission;
public CheckAccessAttribute(params string[] permission)
{
_permission = permission;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
var user = context.HttpContext.User;
if (!user.Identity.IsAuthenticated)
{
return;
}
IRepository service =
(IRepositoryWrapper)context.HttpContext.RequestServices.GetService(typeof(IRepository));
var success = service.CheckAccess(userName, _permission.ToList());
if (!success)
{
context.Result = JsonFormatter.GetErrorJsonObject(
CommonResource.error_unauthorized,
StatusCodeEnum.Forbidden);
return;
}
return;
}
}
In Controller use it like below
[HttpPost]
[CheckAccess(Permission.CreateGroup)]
public JsonResult POST([FromBody]Group group)
{
// your code api code here.
}
For a solution that doesn't require you to add a policy for each permission see my answer for another question.
It lets you decorate your Controllers and Actions with any custom attributes you wish, and access them in your AuthorizationHandler.