I have some entities which have data that must only be accessible for some users.
public class Foo
{
public virtual Bar { get; set; }
...
}
public class Bar
{
public string Secret { get; set; }
...
}
For example Bar.Secret must only be accessible by UserA but not by UserB.
I could so something like this:
public class BarsController : ODataController
{
[EnableQuery]
public IHttpActionResult Get()
{
if (User.Identity.Name != "UserA")
return Unauthorized();
return _db.Bars();
}
}
Besides that being a bad implementation. It doesn't cover this controller:
public class FoosController : ODataController
{
[EnableQuery]
public IHttpActionResult Get()
{
return _db.Foos();
}
}
Which could be called with /odata/Foos?$expand=Bars and then I could view Bar.Secret. I can't just disable $expand on Foo because that query is totally legit for UserA and also needed.
Is there a way to make OData validate the queries against some predicate that involves the requested entities.
Something like
public class SecureEnableQueryAttribute : EnableQueryAttribute
{
public bool ValidateResult(IEnumerable<T> entities)
{
return entities.All(x => x.Secret == UserA.Secret);
}
}
You can validate the query options before the query is executed and fail if the user is not authorized to retrieve the requested data. To do this, derive from EnableQueryAttribute and override ValidateQuery.
public class SecureEnableQueryAttribute : EnableQueryAttribute
{
public virtual void ValidateQuery(HttpRequestMessage request, ODataQueryOptions queryOptions)
{
base.ValidateQuery(request, queryOptions);
// Insert custom logic, possibly looking at queryOptions.SelectExpand
// or queryOptions.RawValues.
}
}
Related
I've set up a UML class diagram of my ASP.NET Core Web API that uses Entity Framework,
with Models and Controllers
I would appreciate any feedback
Things i'm concerned about:
Multiplicity
Relations
Here's an example of the Bookmark.cs (model):
public class Bookmark
{
public int Id { get; set; }
public int PostID { get; set; }
public int UserID { get; set; }
public virtual Post Post { get; set; }
public virtual User User { get; set; }
}
And it's controller (BookmarksController.cs):
[Route("api/[controller]")]
[ApiController]
public class BookmarksController : ControllerBase
{
private readonly WebContext _context;
public BookmarksController(WebContext context)
{
_context = context;
}
// GET: api/Bookmarks
[HttpGet]
public async Task<ActionResult<IEnumerable<Bookmark>>> GetBookmarks()
{
return await _context.Bookmarks.ToListAsync();
}
// GET: api/Bookmarks/5
[HttpGet("{id}")]
public async Task<ActionResult<Bookmark>> GetBookmark(int id)
{
var bookmark = await _context.Bookmarks.FindAsync(id);
if (bookmark == null)
{
return NotFound();
}
return bookmark;
}
// POST: api/Bookmarks
[HttpPost]
public async Task<ActionResult<Bookmark>> PostBookmark([FromForm]Bookmark bookmark)
{
_context.Bookmarks.Add(bookmark);
await _context.SaveChangesAsync();
return CreatedAtAction("GetBookmark", new { id = bookmark.Id }, bookmark);
}
// DELETE: api/Bookmarks/5
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteBookmark(int id)
{
var bookmark = await _context.Bookmarks.FindAsync(id);
if (bookmark == null)
{
return NotFound();
}
_context.Bookmarks.Remove(bookmark);
await _context.SaveChangesAsync();
return NoContent();
}
}
I don't know the domain, so IMO just some feedbacks about UML syntax:
Aggregation arrow should have diamond on container (not on contained class).
Higher abstractions should be on higher part of the diagram so inheritance arrows should be from bottom to top. It helps to understand the most important part of the diagram.
If you can, avoid crossing arrows. You should try to divide your domain not to intertwine.
Out of diagram scope classes should be grayed or diagram should indicate that they come from a different package (see ControllerBase and DbContext). It's usefull to understand dependencies.
Class diagram should state extension points of the architecture. For instance, in the source code of Bookmark class, I saw two virtual properties. If your intent is to extend this class, you should indicate it in the diagram. At least with a dummy concrete class example. Usually I use color for it (even if it's out of the standard UML).
I have this query object:
public class GetRecipeQuery: IRequest<RecipeResponse>
{
[BindRequired]
[FromRoute]
public int Id {get; set;}
}
And controller:
[ApiController]
[Route("[controller]")]
public class RecipeController
{
private AppDbContext _context;
private readonly IMediator _mediator;
public RecipeController(AppDbContext context, IMediator mediator)
{
_context = context;
_mediator = mediator;
}
[HttpGet("{Id}")]
// http://localhost:5555/Recipe/555
public RecipeResponse Get([FromRoute]GetRecipeQuery query)
{
if (query.Id == 0)
{
throw new ArgumentException("No Id!", nameof(query.Id));
}
var result = _mediator.Send(query).Result;
return result;
}
}
So I see this as a result:
Status: 400
"The value '{Id}' is not valid for Id."
Need help: How to bind Id from route to GetRecipeQuery.Id ?
Otherwise i need to construct query objects manually in every controller method.
It seems like you confuse a bit route parameters and query parameters. If you want to use URL parameters your endpoint in controller should be a value type:
[FromRoute]int id
Then URL you are calling would approximately look like this:
http://localhost:8080/foo/10
If you want to use query parameters this is how your controller endpoint argument would look like:
[FromQuery]Foo query
With Foo looking like this:
public class Foo
{
public int Id {get; set;}
}
And the address you need to call:
http://localhost:8080/foo?id=10
#tontonsevilla, answered my question. Thanks.
[HttpGet("{Id:int}")] returns 404 error, but [HttpGet("{id:int}")] works fine! Need lowercase and type for Id parameter.
Full solution.
1). Add query class
public class GetRecipeQuery : IRequest<RecipeResponse>
{
[FromRoute]
public int Id { get; set; }
}
2). Use this query class in Controller and add [HttpGet("{id:int}")]:
[HttpGet("{id:int}")]
public RecipeResponse Get(GetRecipeQuery query)
{
// your code
}
I need it because I started using Mediatr
I want to implement a certain functionality, but I do not know where to start. I will describe what I have.
Backend
public enum SourceType { Database, Folder }
public class DatabaseSource
{
public string ServerName { get; set; }
public string DatabaseName { get; set; }
}
public class FolderSource
{
public string FolderName { get; set; }
}
public class TestController : ApiController
{
[HttpPost]
[Route("source")]
public void Post([FromBody]DatabaseSource source) //method one
{
}
[HttpPost]
[Route("source")]
public void Post([FromBody]FolderSource source) //method two
{
}
}
Frontend
export enum SourceType {
Database,
Folder
}
export class DatabaseSource {
public ServerName: string;
public DatabaseName: string;
}
export class FolderSource {
public FolderName: string;
}
var source = new DatabaseSource();
source.ServerName = "serverName";
source.DatabaseName = "dbName";
var obj = {
sourceType: SourceType.Database,
source: source
};
Now imagine that I will send obj to the server. I want that specific controller method to be called depending on the enum. How can I do this?
P.S. The example is greatly simplified.
Your implementation is inconsistent for what you've specified in code.
On the front-end you are describing an object which has a sourceType field and a source object property, while on the backend you're overloading the ApiController method and mapping different REST object resources to a single HTTP method and endpoint (which I believe will not work).
There is no magic way for the ApiController to use your enum property to differentiate between the object types automatically.
A simpler (and better) implementation would be to have separate ApiController classes for your Database and Folder source object POST calls. This follows the principle of REST API design where you are essentially mapping basic CRUD operations to the HTTP methods with object types.
If your intention is to perform an operation based on these parameter objects, then clarify the intention via the API routing for the endpoint as below:
public class TestController : ApiController
{
[HttpPost]
[Route("ETLLoad/Database/source")]
public void Post([FromBody]DatabaseSource source) //method one
{
}
[HttpPost]
[Route("ETLLoad/Folder/source")]
public void Post([FromBody]FolderSource source) //method two
{
}
}
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 am building a REST API using .net WEB API.
Here is a sample code
public class ValuesController : ApiController
{
// GET api/values
public Values Get(int ID, int userID)
{
return new Values(){};
}
}
Now what I want to do is return a different class if userID is not in allowed userID list. I was thinking of throwing an exception, but after I considered it I don't think that would be a good idea. Reason for this is that process was handled with OK status.
Basically I would like to return a custom ErrorMessage object instead of Values object. Is it possible to do it?
IMO throwing an exception is valid when the flow of your code encounters an abnormal situation.
If you still dont want to throw, you can create a wrapper class that describes your result:
public class ValueResponse
{
public HttpStatusCode HttpStatus { get; set; }
public string ErrorMessage { get; set; }
public Values Values { get; set; }
}
and return that object
public class ValuesController : ApiController
{
// GET api/values
public ValueResponse Get(int ID, int userID)
{
// Send a valid response or an invalid with the proper status code
}
}