I've found that I have a lot of repeated code in all of my actions, and want to know the best way to avoid this. Say for example that each logged on user belongs to a school and I need to access this SchoolId in almost every action.
They way I have it now almost every action will have a repeated database hit and need to reference my userService class...something like:
public ActionResult Index()
{
var schoolId = userService.GetSchoolId(User.Identity.GetUserId());
var textBooksForSchool = textBookService.GetBooks(schoolId);
...
}
public ActionResult Delete()
{
var schoolId = userService.GetSchoolId(User.Identity.GetUserId());//all over the place
var textBooksForSchool = textBookService.DeleteBooks(schoolId);
...
}
I know that I can add the SchoolId to the claims but the syntax for returning it in every method is quite verbose (as far as I understand this avoids the db hit each time the claim is accessed?):
In GenerateIdentityAsync:
var claims = new Collection<Claim>
{
new Claim("SchoolId", User.SchoolId.ToString())
};
userIdentity.AddClaims(claims);
In Action:
var SchoolId = Convert.ToInt32((User as ClaimsPrincipal).Claims.First(x => x.Type == "SchoolId").Value);
Is there some kind of best practice here? Possibly storing the claim in a global variable on logon?
This is how I am doing...
Base Controller
public class BaseController : Controller
{
public AppUser CurrentUser
{
get
{
return new AppUser(this.User as ClaimsPrincipal);
}
}
}
Claims Principal
public class AppUser : ClaimsPrincipal
{
public AppUser(ClaimsPrincipal principal)
: base(principal)
{
}
public string Name
{
get
{
return this.FindFirst(ClaimTypes.Name).Value;
}
}
public string Email
{
get
{
return this.FindFirst(ClaimTypes.Email).Value;
}
}
}
In the other controller you can access the claim type just by doing
CurrentUser.Email
What about creating your own base controller that all your controllers inherit from that has SchoolId as a property and then creating an ActionFilter that casts each controller as that base controller and sets that value on every request? Then it will be available on all your actions but you only have to write the code once.
It will fire each request, so you might consider other techniques for minimizing the number of times you have to look up the value, but this mechanism is a way to solve your code duplication issue.
I really like the extension method approach:
public static int SchoolId(this IPrincipal principal)
{
return Convert.ToInt32((principal as ClaimsPrincipal).Claims.First(x => x.Type == "SchoolId").Value);
}
Action:
var textBooksForSchool = textBookService.GetBooks(User.SchoolId());
Related
I'm trying to pull out of database objects where current user Id is in list of those objects.
My model:
public class Procedure
{
...
public IList<User> Lawyers{ get; set; }
...
}
And in controller:
[HttpGet]
public async Task<IActionResult> MyProcedures()
{
var user = await userManager.GetUserAsync(User);
var procedures = context.Procedures.Where(x => x.Lawyers.Contains(user));
return View(procedures);
}
This only selects one object.
EDIT:
Problem is in my User class, it takes only one Id from Procedure and that is why its showing my only one (last added). Thanks for help guys!
The way you wrote will work if you have icomparable or something implemented that can tell that a lawyer is “the” user if user id matches.
Without that you would need to do lawyers.Any(x=> x.UserId == user.UserId)
You are missing something, which user ??
Either pass something into your function signature or get it from HTTPContext you need to get the user you want to find
If you are using ASP core its bit different from the old ways
First this:
var userId = _httpContextAccessor.HttpContext.User.FindFirst(ClaimTypes.NameIdentifier).Value;
And now..
// make sure you can access/inject it if you want
// private.. _httpContextAccessor
// then in your action
[HttpGet]
public async Task<IActionResult> MyProcedures()
{
// again make sure you can access the context _httpContextAccessor
var userId = _httpContextAccessor.HttpContext.User.FindFirst(ClaimTypes.NameIdentifier).Value;
var procedures = context.Procedures.Where(x => x.Lawyers.Contains(userId).FirstOrDefault());
//... fill in whatever logic you want..
return View(parnice);
}
Update 2 based on question/comments:
Do this in two Steps
Step 1: Get the Current User (with claims or HTTPContext as shown below), for e.g. System.Security.Claims.ClaimsPrincipal currentUser = this.User;
Step 2: Using the user, find all the related Lawyers etc. context.Procedures.Where(x => x.Lawyers.Contains(userId)
Make sure to register HttpContextAccessor in your startup... double check this.. to register in your Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
}
In the original Core version, I have to double check if it changed now, assuming your code is inside an MVC controller:
public class YourController : Microsoft.AspNetCore.Mvc.Controller
Now, since you have the Controller base class, you can get the IClaimsPrincipal from the User property
System.Security.Claims.ClaimsPrincipal currentUser = this.User;
You can check the claims directly (without a round trip to the database):
var userId = _userManager.GetUserId(User); // Get user id:
To your second comment, you can get either the UserId or UserName
// user's userId
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier)
// user's userName
var userName = User.FindFirstValue(ClaimTypes.Name)
A nice reference for you hope it helps :)
I have an HTML form that is sent to a controller action (via POST) based on MVC.
The form contains different inputs. There are extra inputs if the user has certain claims.
For example, if the User is administrator, he/she sees an additional text area for comments.
public class MySubmit
{
public string Name { get; set; }
public string IsActive { get; set; }
// only an administrator should be able to set this field
// for all other users, this should be empty
public string Comment { get; set; }
}
public class MyController : Controller
{
public IActionResult MyActionResult(MySubmit submit)
{
}
}
What is the best and safest way to process the result on the action?
Theoretically it is possible that the a tries to submit values although he/she does not actually see the corresponding form controls, because he/she does not have the claim.
I would like to set default values used for field values instead, if the user does NOT have these claims - no matter what values he sends for these fields.
Is there anything built in?
Bryan Lewis gave the right hint: Fluent Validation.
Fluent Validation has the ability to use the HTTP context via Dependency Injection to receive the user and perform a claim comparison:
public class YourModelValidator: AbstractValidator<YourModel>
{
public YourModelValidator(IHttpContextAccessor httpContext)
{
RuleFor(x => x.YourProprty).Custom( (html, context) =>
{
var user = httpContext.User;
if (!user.HasClaim(c => c.Type.Equals(claim))
{
context.AddFailure("Claim is missing.");
}
});
}
}
You can validate the value, but you should not set the value.
Is there anything built in?
No. There's no built-in way to do that.
Design
You might want to achieve that with a custom model binder. But I believe that's not a good way. Because you'll have to process all kinds of input formatters at the same time. Think about somewhere your action expects a [FromForm]MySubmit mySubmit while another action expects a [FromBody] Submit mySubmit. The first action requires a payload of form, while the second action might expect a JSON. Even you take care of the two above scenarios, what about you want to enable XML payloads in future? In short, you can hardly write a general Model Binder for this.
Validation might help. But validation usually makes you repeat yourself if you have several models( Think about you have ten domain models, each one has several properties that requires some claims)
IMO, a better way is to use ActionFilter. Since ActionFilter takes place after the model binding, it would be possible to erase the field when the field requires a role.
To do that, create a custom attribute to mark which property requires some role:
[AttributeUsage(AttributeTargets.Property, AllowMultiple=false)]
internal class RequireRolesForBindingAttribute : Attribute
{
public string[] Roles {get;}
public RequireRolesForBindingAttribute(params string[] roles)
{
this.Roles = roles;
}
}
Now when some roles are required, simply annotate the target property like below:
public class MySubmit
{
public string Name { get; set; }
public string IsActive { get; set; }
// only an root/admin can bind this field for all other users, this should be empty
[RequireRolesForBindingAttribute("root","admin")]
public string Comment { get; set; }
public Sub Sub{get;set;} // test it with a complex child
}
public class Sub{
public int Id {get;set;}
public string Name {get;set;}
[RequireRolesForBindingAttribute("root","admin")]
public string Note {get;set;}
}
The above data annotation represents that the two properties should be erased if the user has no rights:
Comment property of MySubmit
Note property of Sub
Finally, don't forget to enable an custom action filter. For example, add it on action method:
[TypeFilter(typeof(RequireRolesForBindingFilter))]
public IActionResult Test(MySubmit mySubmit)
{
return Ok(mySubmit);
}
An Implementation of RequireRolesForBindingFilter
I create an implementation of RequireRolesForBindingFilter for your reference:
public class RequireRolesForBindingFilter : IAsyncActionFilter
{
private readonly IAuthorizationService _authSvc;
public RequireRolesForBindingFilter(IAuthorizationService authSvc)
{
this._authSvc = authSvc;
}
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
// skip early when User ==null,
// if you don't want to allow anonymous access, use `[Authorize]`
if(context.HttpContext.User !=null) {
await this._checkUserRights(context.ActionArguments, context.HttpContext.User);
}
await next();
}
private async Task _checkUserRights(IDictionary<string, object> args, ClaimsPrincipal user){
// handle each argument
foreach(var kvp in args){
if(kvp.Value==null) { return; }
var valueType = kvp.Value.GetType();
if(await _shouldSetNullForType(valueType, user)) {
args[kvp.Key] = valueType.IsValueType? Activator.CreateInstance(valueType) : null;
}else{
// handle each property of this argument
foreach(var pi in valueType.GetProperties())
{
var pv = pi.GetValue(kvp.Value);
await _checkPropertiesRecursive( instanceValue: kvp.Value, propInfo: pi, user: user);
}
}
}
async Task<bool> _shouldSetNullForType(Type type, ClaimsPrincipal user)
{
// the `RequireRolesForBindingAttribute`
var attr= type
.GetCustomAttributes(typeof(RequireRolesForBindingAttribute), false)
.OfType<RequireRolesForBindingAttribute>()
.FirstOrDefault();
return await _shouldSetNullForAttribute(attr,user);
}
async Task<bool> _shouldSetNullForPropInfo(PropertyInfo pi, ClaimsPrincipal user)
{
// the `RequireRolesForBindingAttribute`
var attr= pi
.GetCustomAttributes(typeof(RequireRolesForBindingAttribute), false)
.OfType<RequireRolesForBindingAttribute>()
.FirstOrDefault();
return await _shouldSetNullForAttribute(attr,user);
}
async Task<bool> _shouldSetNullForAttribute(RequireRolesForBindingAttribute attr, ClaimsPrincipal user)
{
if(attr!=null) {
var policy = new AuthorizationPolicyBuilder().RequireRole(attr.Roles).Build();
// does the user have the rights?
var authResult = await this._authSvc.AuthorizeAsync(user, null, policy);
if(!authResult.Succeeded){
return true;
}
}
return false;
}
// check one property (propInfo) for instance `instanceValue`
async Task _checkPropertiesRecursive(object instanceValue, PropertyInfo propInfo, ClaimsPrincipal user){
if(instanceValue == null) return;
Type propType = propInfo.PropertyType;
object propValue = propInfo.GetValue(instanceValue);
if(await _shouldSetNullForPropInfo(propInfo, user))
{
propInfo.SetValue(instanceValue, propType.IsValueType? Activator.CreateInstance(propType) : null);
}
else if( !shouldSkipCheckChildren(propType) && propValue!=null ){
// process every sub property for this propType
foreach(var spi in propType.GetProperties())
{
await _checkPropertiesRecursive(instanceValue: propValue , spi, user );
}
}
bool shouldSkipCheckChildren(Type type) => (type == typeof(string) || type == typeof(DateTime));
}
}
}
Demo:
When some user, who has no rights to submit the comment and note filed, sends a payload as below:
POST https://localhost:5001/home/test
cookie: <my-cookie>
Content-Type: application/x-www-form-urlencoded
name=a&isActive=true&comment=abc&sub.Name=s1&sub.note=magic
The response will be:
HTTP/1.1 200 OK
Connection: close
Content-Type: application/json; charset=utf-8
Server: Kestrel
Transfer-Encoding: chunked
{
"name": "a",
"isActive": "true",
"comment": null,
"sub": {
"id": 0,
"name": "s1",
"note": null
}
}
There are really two actions that you want to perform -- validation and conditional editing of the incoming model. For complex validation, you should consider using something like FluentValidation (https://fluentvalidation.net/), which is quite flexible, integrates with MVC's ModelState and will allow you to check all sorts of things based on conditions. It's not clear from your post if you referring to "claims" in the general sense or specifically to ASP.Net Identity Claims -- either way, you can pull identity information into the FluentValidation Validator and create conditional checks based on identity information. The validators (FV or otherwise) don't really handle resetting/editing the model. For your example, you can simply alter the model directly after the validation is complete.
// if Validation is successful
if (isAdmin) { // however you are checking their role
submit.Comment = null; // or whatever the default value should be
}
// Do something with the incoming model / dave to DB etc
I have a basic C# Web Api 2 controller that has a POST method to create an entity
public HttpResponseMessage Post(UserModel userModel){ ... }
And also a PUT method to update said model
public HttpResponseMessage Put(int id, UserModel userModel) { ... }
And here is the UserModel
public class UserModel
{
public virtual Name { get; set; }
public virtual Username { get; set; }
}
For my validator, I want to validate that the name is not taken on Post - easy enough. For PUT, I need to validate that the name is not taken, by another user, but of course this particular user would have the same username.
public class UserModelValidator : AbstractValidator<UserModel>
{
public UserModelValidator()
{
RuleFor(user => user.Username)
.Must(NotDuplicateName).WithMessage("The username is taken");
}
private bool NotDuplicateName(string username)
{
var isValid = false;
//Access repository and check to see if username is not in use
//If it is in use by this user, then it is ok - but user ID is
//in the route parameter and not in the model. How do I access?
return isValid;
}
}
I am using AutoFac, so maybe there is a way to inject the HttpRequest into the validator and get the route data that way.
Or possibly I could create a model binder that looks for the route data and adds it to the model?
Or is there an easy way?
I have found an other solution with inject the IActionContextAccessor into the Validator. With this I can access the ROUTE paramerter without the need of a special model binding.
Startup.cs
services.AddHttpContextAccessor();
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
UserModelValidator.cs
public class UserModelValidator : AbstractValidator<UserModel>
{
public UserModelValidator(IActionContextAccessor actionContextAccessor)
{
RuleFor(item => item.Username)
.MustAsync(async (context, username, propertyValidatorContext, cancellationToken) =>
{
var userId = (string)actionContextAccessor.ActionContext.RouteData.Values
.Where(o => o.Key == "userId")
.Select(o => o.Value)
.FirstOrDefault();
return true;
});
}
}
Update 2022 / FluentValidation 11
Starting in FluentValidation 11.0, validators that contain asynchronous rules will now throw a AsyncValidatorInvokedSynchronouslyException
https://docs.fluentvalidation.net/en/latest/upgrading-to-11.html#sync-over-async-now-throws-an-exception
UserModelValidator.cs
public class UserModelValidator : AbstractValidator<UserModel>
{
public UserModelValidator(IActionContextAccessor actionContextAccessor)
{
RuleFor(item => item.Username)
.Must((context, username, propertyValidatorContext) =>
{
var userId = (string)actionContextAccessor.ActionContext.RouteData.Values
.GetValueOrDefault("userId");
return true;
});
}
}
The easiest way of course is to add the Id to the UserModel. You'd have to add some extra checking on the Post and Put operations though. The first should ignore the Id when a client provides it. The second could check whether the Id in the path is the same as the Id in the model. If not, then return a BadRequest.
Altered model:
public class UserModel
{
public virtual Id { get; set; }
public virtual Name { get; set; }
public virtual Username { get; set; }
}
Altered methods:
public HttpResponseMessage Post(UserModel userModel)
{
// ignore provided userModel.Id
}
public HttpResponseMessage Put(int id, UserModel userModel)
{
if(id != userModel.Id)
{
// return bad request response
}
}
Update
Having an Id in the route as well as in the model does indeed allow for a discrepancy between the two as you commented. A respectful API consumer will probably not post a request with misaligned Ids. But a malicious consumer (aka hacker) most probably will. Therefore you should return BadRequest when the Ids don't match.
You certainly do not want to update the UserModel with the Id as you mentioned otherwise you might end up with user 1 (the one in the url) overwritten by the details of user 2 (the one in the UserModel).
I have model class:
public class Person {
public int Id { get; set; }
...
}
and to see details about a person user can guess its' id
http://localhost:17697/Person/Details/2
they're just consecutive integers.
How can I tell Entity Framework to shuffle those ID to make them harder to guess?
If you don't want predictable IDs then you could use a Guid instead of int. "Shuffling" would over-complicate the process and it's not going to give you any protection.
Remember that if you're trying to secure a url, write proper security using authorization and filters. Security through obscurity does not actually secure anything
Personally, I utilize slugs in my URLs, rather than ids. Something like:
http://localhost:17697/Person/Details/john-doe
You then pull the object based on the slug:
db.People.SingleOrDefault(m => m.Slug == slug);
However, "security by obscurity" is not a good game plan. Making the ids "harder to guess", doesn't solve the problem of people accessing it who shouldn't. If the details should be protected, then implement authentication and specify an authorization policy for the action.
Late to the party, but since there isn't much about using HashIds within ASP.NET MVC I'll share my solution using a custom ModelBinder and a BaseModel class. The end route looks something like /example/voQ/details.
First you need a model, that your existing models can extend from and generate a HashId;
public class abstract BaseModel
{
private static readonly Hashids __hashId = new Hashids("seed", 2);
public Id { get; set; }
[NotMapped]
public HashId
{
get { return BaseModel.__hashId.Encode(this.Id); }
}
}
The binder needs registering in Global.asax for each model:
ModelBinders.Binders.Add(typeof(ExampleModel), new ControllerModelBinder<ExampleModel>());
Then the action can use the model directly without worrying about the hash id:
public ActionResult Details(ExampleModel model)
{
return View(model);
}
Setting up a link is the same, but rather than passing the Id, you need to use the HashId property from the BaseModel.
#Url.Action("Details", new { id = item.HashId })
Finally the the model binder:
public class ControllerModelBinder<T> : DefaultModelBinder
where T : BaseModel
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
if (bindingContext.ModelType == typeof(T))
{
string hash = bindingContext.ValueProvider.GetValue("id").RawValue.ToString();
if (!string.IsNullOrWhiteSpace(hash))
{
int id = HashIdHelper.ToInt(hash);
if (id > 0)
{
using (ApplicationContext context = new ApplicationContext())
{
DbRawSqlQuery<T> query = context.Database.SqlQuery<T>(string.Format("SELECT * FROM {0} WHERE id = #Id LIMIT 1", EntityHelper.GetTableName<T>(context)), new MySqlParameter("#Id", id));
try
{
T model = query.Cast<T>().FirstOrDefault();
if (model != null)
{
return model;
}
}
catch (Exception ex)
{
if (ex is ArgumentNullException || ex is InvalidCastException)
{
return base.BindModel(controllerContext, bindingContext);
}
throw;
}
}
}
}
}
return base.BindModel(controllerContext, bindingContext);
}
}
You can use HttpServerUtility.UrlTokenEncode and HttpServerUtility.UrlTokenDecode
Encode uses base64 encoding, but replaces URL unfriendly characters.
There's a similar answer in a previous SO question. See the accepted answer.
MSDN Reference
How can I access a ServiceStack.net session in my validation code?
public class UserSettingsValidator : AbstractValidator<UserSettingsRequest>
{
public UserSettingsValidator()
{
RuleFor(x => x.UserId)
.SetValidator(new PositiveIntegerValidator())
.SetValidator(new UserAccessValidator(session.UserId)); //<-- I need to pass the UserID from the session here
}
}
In the Service Implementation I just do:
var session = base.SessionAs<UserSession>();
but this does not work for my abstract validator.
Thanks!
Edit: this is version 3.9.71.0
I assume you are just using the ValidationFeature plugin, as most do. If that's the case, then I don't think it is possible. Ultimately the ValidationFeature is a plugin which uses a RequestFilter.
I wanted to do something similar before too, then realised it wasn't possible.
The RequestFilter is run before the ServiceRunner. See the order of operations guide here.
What this means to you is your populated request DTO reaches your service, and the validation feature's request filter will try validate your request, before it has even created the ServiceRunner.
The ServiceRunner is where an instance of your service class becomes active. It is your service class instance that will be injected with your UserSession object.
So effectively you can't do any validation that relies on the session at this point.
Overcomplicated ?:
It is possible to do validation in your service method, and you could create a custom object that would allow you pass the session along with the object you want to validate. (See next section). But I would ask yourself, are you overcomplicating your validation?
For a simple check of the request UserId matching the session's UserId, presumably you are doing this so the user can only make changes to their own records; Why not check in the service's action method and throw an Exception? I am guessing people shouldn't be changing this Id, so it's not so much a validation issue, but more a security exception. But like I say, maybe your scenario is different.
public class SomeService : Service
{
public object Post(UserSettingsRequest request) // Match to your own request
{
if(request.UserId != Session.UserId)
throw new Exception("Invalid UserId");
}
}
Validation in the Service Action:
You should read up on using Fluent Validators. You can call the custom validator yourself in your service method.
// This class allows you to add pass in your session and your object
public class WithSession<T>
{
public UserSession Session { get; set; }
public T Object { get; set; }
}
public interface IUserAccessValidator
{
bool ValidUser(UserSession session);
}
public class UserAccessValidator : IUserAccessValidator
{
public bool ValidUser(UserSession session)
{
// Your validation logic here
// session.UserId
return true;
}
}
public class UserSettingsValidator : AbstractValidator<WithSession<UserSettingsRequest>>
{
public IUserAccessValidator UserAccessValidator { get; set; }
public UserSettingsValidator()
{
// Notice check now uses .Object to access the object within
RuleFor(x => x.Object.UserId)
.SetValidator(new PositiveIntegerValidator());
// Custom User Access Validator check, passing the session
RuleFor(x => x.Session).Must(x => UserAccessValidator.ValidUser(x));
}
}
Then to actually use the validator in your service:
public class SomeService : Service
{
// Validator with be injected, you need to registered it in the IoC container.
public IValidator<WithSession<UserSettingsRequest>> { get; set; }
public object Post(UserSettingsRequest request) // Match to your own request
{
// Combine the request with the current session instance
var requestWithSession = new WithSession<UserSettingsRequest> {
Session = this.Session,
Object = request
};
// Validate the request
ValidationResult result = this.Validator.Validate(requestWithSession);
if(!result.IsValid)
{
throw result.ToException();
}
// Request is valid
// ... more logic here
return result;
}
}
I hope this helps. Note: code is untested
It appears that after reading from a bunch of people experiencing similar problems, then many hours of playing with several solutions based on the SS4 Cookbook etc, this is a problem that is already solved:
https://forums.servicestack.net/t/blaz-miheljak-355-feb-3-2015/176/2
Implement the IRequiresRequest interface on your validator, and voila.