I am trying to use .NET 6's policy-based authorization to check whether the user editing a record from telerik grid is an owner of the record or a business unit manager.
I can access who the owner is from the model object that is bound to the grid. I followed below doc from Microsoft Learn to implement an authorization handler: https://learn.microsoft.com/en-us/aspnet/core/security/authorization/policies?view=aspnetcore-6.0#authorization-handlers
When a user clicks on the "Edit" button from the telerik grid, a controller's action is invoked with no route values.
.Update(update => update.Action("EmployeeInfo_Update", "Employee")
Below is the implementation of EmployeeInfo_Update action method signature:
public IActionResult EmployeeInfo_Update([DataSourceRequest] DataSourceRequest request, EmpModel empModel)
{
Below is the implementation for authorization handler:
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, OwnerRequirement requirement)
{
//check if they are owner
if (context.Resource is HttpContext httpContext)
{
var routeData = httpContext.GetRouteData();
Debug.WriteLine("route data: " + routeData);
}
I was hoping that I could access the empModel parameter via RouteValues dictionary or some other mechanism from the above authorization handler. Is that possible?
I am using the .NET core's out-of-the-box StartUp configuration:
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapRazorPages();
});
What I have tried and thought of trying:
Read Rick's article on how to access RouteData: here
Injected IActionContextAccessor into my authorization handler and tried accessing the parameter with no luck.
Thought of storing the relevant piece of information in HttpContext.Items collection and accessing it from the authorization handler but do not know how to store this information from client-side/javascript (via telerik grid edit action)
I thought of researching whether telerik's grid events have this capability - perhaps it might be possible to restrict access to owner easily but determining whether current user is a business unit manager would require accessing backend service.
So, I would love to leverage ASP.NET's authorization annotation but if there are any alternative options, I am open to them.
[Authorize(Policy = "Owner")]
public IActionResult EmployeeInfo_Update([DataSourceRequest] DataSourceRequest request, EmpModel empskillModel
Related
Hope my question will be clear. ;-)
Is it possible to change Middleware settings during runtime? I explain more.
I have following code in my configure services method in an Asp.net core 2.1 webapi
services.AddMvc(options =>
if (!securitySettings)
{
options.Filters.Add(new AllowAnonymousFilter());
}
I would like to add that filter depending on a setting in the database. Is it possible to change that during runtime or do i really need to restart my application after that setting has been changed?
Unfortunately, you can't modify the filters applied to MVC after the application has started.
However, MVC has the concept of authorization requirements, which are executed on each request. That makes them a great candidate for what you're trying to achieve.
On a high-level, we will:
change the default authorization policy to include a custom requirement;
create the class that handles this requirement, that is, determines if it's satisfied or not
Let's create the requirement class. It's empty as it doesn't need any parameters, since the result will come solely from the database:
public class ConditionalAnonymousAccessRequirement : IAuthorizationRequirement
{
}
Then we create the class that handles this requirement:
public class ConditionalAnonymousAccessHandler : AuthorizationHandler<ConditionalAnonymousAccessRequirement>
{
private readonly AppDbContext _context;
public ConditionalAnonymousAccessHandler(AppDbContext context)
{
_context = context;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ConditionalAnonymousAccessRequirement requirement)
{
if (IsAnonymousAccessAllowed())
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
private bool IsAnonymousAccessAllowed()
{
// Implementation based on the value retrieved from the database
}
}
The implementation is straightforward. If we find in the database that anonymous access is allowed, we mark this requirement as succeeded. If at least one of the requirements in a policy is satisfies, then the whole policy succeeds.
The next step is to add that requirement to the authorization policy. By default, when using an [Authorize] attribute with no parameters, MVC uses the default authorization policy, which just checks that the user is authenticated. Let's modify it to add this new requirement in the ConfigureServices method of your Startup class:
services.AddAuthorization(options =>
{
options.DefaultPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.AddRequirements(new ConditionalAnonymousAccessRequirement())
.Build();
});
All looks good, but we're missing one final piece.
While we added the requirement to the policy, we haven't registered the requirement handler, which is necessary for MVC to discover it. This is again done in the ConfigureServices method.
services.AddScoped<IAuthorizationHandler, ConditionalAnonymousAccessHandler>();
While the documentation shows the handler is registered as a singleton, in this case it's better to register it per-HTTP request as it has a dependency on a DbContext which are by default registered per-HTTP request. Registering the handler as a singleton means an instance of DbContext would be kept alive during the whole application lifetime.
Please let me know how you go!
I've created both an ASP.NET MVC project which includes the Web API and I created an empty project where only Web API was selected and I'm trying to understand why they are behaving differently.
Both of them from what I can tell have the routing definition defined as follows:
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
But I'd like to understand why the functions that are defined in the controller are behaving differently.
In my ASP.NET MVC Solution, I can create multiple functions under the one controller:
public IEnumerable<Product> GetAllProducts()
{
return products;
}
public IHttpActionResult GetProduct(int id)
{
var product = products.FirstOrDefault((p) => p.Id == id);
if (product == null)
{
return NotFound();
}
return Ok(product);
}
I don't have to add any attributes, well at least for gets and I can call individual function as:
http://localhost/api/test/GetProduct/1
http://localhost/api/test/GetProducts
In the Web API only solution, I had to define it as:
public IHttpActionResult HelloWorld()
{
return Ok("Hello World");
}
but the above will not work unless I add the [HttpGet] attribute to my HelloWorld function.
Also I don't seem to be able to define multiple functions within the one controller as it is done the in ASP.NET MVC Solution. Defining the same function as above just does not work or even if I define the functions below
[HttpGet]
public IHttpActionResult HelloWorld()
{
return Ok("Hello World");
}
[HttpGet]
public IHttpActionResult GetTime()
{
return Ok(DateTime.Now.ToString());
}
I get the following error:
<Error>
<Message>An error has occurred.</Message>
<ExceptionMessage>
Multiple actions were found that match the request: HelloWorld on type
MyProject.Controllers.TestController GetTime on type
MyProject.Controllers.TestController
</ExceptionMessage>
<ExceptionType>System.InvalidOperationException</ExceptionType>
<StackTrace>
at
System.Web.Http.Controllers.ApiControllerActionSelector.
ActionSelectorCacheItem.SelectAction(HttpControllerContext
controllerContext) at System.Web.Http.ApiController.
ExecuteAsync(HttpControllerContext controllerContext, CancellationToken
cancellationToken) at System.Web.Http.Dispatcher.
HttpControllerDispatcher.<SendAsync>d__15.MoveNext()
</StackTrace>
When I define a single function, it works but contrarily to a Web API defined in my ASP.NET MVC solution, when I call the below
http://localhost/api/test/HelloWorld
It doesn't seem to care what the function name is called as it will work if I call any of the following:
http://localhost/api/test
http://localhost/api/test/GoodBye
and will only fail if I provide an invalid controller name i.e.
http://localhost/api/mytest
Can you explain the difference between the 2 and ideally what do I have to do to get a stand-alone Web API to behave the same way as it does when defined in Asp.net MVC
Thanks.
UPDATE-1
Below are 2 good examples where the behavior is clearly different but I can't see why as both are called "Web API with ASP.NET MVC".
Creating Simple Service with ASP.NET MVC Web API to be accessed by Windows Store and Windows Phone Application
Getting Started with ASP.NET Web API 2 (C#)
Edit:
I'm sry at first I misunderstood your enquiry and therefore gave you the wrong answer to your question.
In Web Api you can't define two functions that serve the exact same path. But you can overload them like you would with Functions.
public IEnumerable<Product> GetAllProducts() {}
public IHttpActionResult GetProduct(int id) {}
These two function differ in so far that they serve different paths:
localhost/api/products/
localhost/api/products/{id}/
The following functions on the other hand both serve localhost/api/test/
[HttpGet]
public IHttpActionResult HelloWorld()
{
return Ok("Hello World");
}
[HttpGet]
public IHttpActionResult GetTime()
{
return Ok(DateTime.Now.ToString());
}
Your second concern has been the naming and mostly why you can't define HelloWorld() without [HttpGet]. That is because Web Api automatically maps functions that have a keyword such as Get or Delete as part of their name to the corresponding Http Function.
Adding the Http-Attribute is only needed if the function name does not conform to these naming conventions or shall serve multiple Http-Methods. It's described here
I hope your questions are now answered.
Orig:
The Default Routing and subsequent decision on what Function is to be executed is different between MVC and Web API
Default Web Api:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Default Mvc:
config.Routes.MapHttpRoute(
name: "DefaultMvc",
routeTemplate: "/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
For Web Api the Routing is dependant on the controller name and further which Http-Method has been selected as well as the controller actually having a function the has been tagged with this method. Hence the and further Tags
Mvc on the other hand routes via the controller name and the action name to the desired functionality.
Web Api 2 adds further possibilies in letting you define AttributeRoutes. You may want to look further on this topic here and here
I am new to MVC.I am having some doubts so please clarify me.
How to store large data in MVC4 and how to pass that data across the pages.
How to maintain user details across the pages. In webforms we are having sessions but in mvc4 how we will do.
if we are having two actionresult of same name one will be fired on POSt action. How the CLR identifies which Action method to be called..means how it will identify that POSt method is called.
Define "large data" - remember that the web is stateless, persistence is done using a database or a server-side cache. I need more information about what you want to accomplish here.
ASP.NET MVC still supports Sessions. You can access the Session collection from any Controller action.
The Post action method must have a different method signature. The usual approach is to specify the view's model as a parameter, or a FormValueCollection, for example:
-
// GET
public ActionResult Foo() {
}
// POST
[HttpPost]
public ActionResult Foo(FooModel model) {
}
OR:
// POST
[HttpPost]
public ActionResult Foo(FormValueCollection postValues) {
}
I am pretty new to asp.net mvc. I want to get the parameter string in my url
http://localhost/Item/ItemSpec/3431?dep=62&cat=129&tab=2
How can I get the value=3431?
I tried to used HttpContext.Current.Request.QueryString["id"], but it's not work. 3431 is the id of the item that display in my page, that's why I used ["id"].
Thanks you so much.
The 3431 is part of the path of the request, not part of the query string. You could use HttpRequest.Path to get at the path, but MVC routing should allow you to simply write a controller method which accepts the ID as a parameter. I suggest you read up on how to configure routing. (Just searching for ASP.NET routing or MVC routing will give you lots of articles.)
Assuming the default route is configured in Global.asax ({controller}/{action}/{id}) you could have your controller action take an id parameter and the default model binder will automatically set its value:
public ActionResult Foo(string id)
{
...
}
If you want to fetch this id value from some other portion of your code that does have access to an HttpContext you need to fetch it from the RouteData:
var id = HttpContext.Request.RequestContext.RouteData["id"];
RouteData is available in all standard MVC locations. In your example you have used the static HttpContext.Current property which is something that you should never use. I suspect that you are trying to fetch this id from a portion of your code where you are not supposed to have access to the HttpContext. So you'd better fetch this id using standard techniques and then pass it as parameter to other parts of your code.
If Item is your controller, and ItemSpec is action, you can get the Id just by
public ActionResult ItemSpec(int id) { }
You routing have to be setup to:
context.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index" }
);
If you haven't changed your routing so it's still defined as it was when you created Asp.net MVC Web project then this should be one of your controllers:
public class ItemController : ControllerBase
{
...
public ActionResult ItemSpec(int id, int dep, int cat, int tab)
{
// implementation that uses all four values
}
...
}
This is of course just one of the actions in it. There may be others as well. Most likely the Index one that's generated by default and is also used by default routing...
My MVC application consists of Admin and normal users. Admin users can register as different customers using the system, and they can add their respective users to the system.
The controllers already have Authorize filters on certain controllers to prevent unauthorised access to certain pages/json.
However, say I have an admin user logged in, what prevents them from inspecting the Json posts/gets in the JavaScript and manually calling a 'get' with the Id of the specific data to be viewed? e.g. /Users/1
Testing my application, I could post an AJAX get, to retrieve the details of another user in the system which was not under the management of the current authenticated user.
I could write access check methods whenever a service method is called to see if the user can view the data. Are they any good ways of solving this problem without littering the application with access check methods all over the place?
e.g. Current Implementation
public class PeopleController : ApplicationController
{
public ActionResult GetMemberDetails(int memberId)
{
var member = _peopleService.GetMemberById(memberId);
return Json(member, JsonRequestBehavior.AllowGet);
}
}
public class PeopleService : IPeopleService
{
public MemberModel GetMemberById(int memberId)
{
// Void function which throws Unauthorised exception
MemberAccessCheck(memberId);
var member = Mapper.Map<Member, MemberModel>(_memberRepository.GetById(memberId));
return member;
}
}
The MemberAccessCheck function is called lots of times in my service.
You're going to have to write these checks inside your controller or service. I dont think you should use a custom attributefilter for this behaviour because you are basically filtering data from a service layer standpoint. Nor is Auhorize attribute suitable for this featire also.
What I suggest is that you have a service method that accepts the current userId from the controller (take the User.Identity) and send it to the service to get the list of user's or single user that they can view/modify. So its not necessarily littering with access checks, but it would be how your service operates (a business rule).
Eg of Service Method:
User GetAdminUser(int userId, int adminId);
List<User> GetAdminUsers(int adminId);
Or, just overload your previous service
User GetUser(int userId);
User GetUser(int userId, int adminId);
You should consider placing all of your Admin functions in an Area. If you do, then you can use the Route for the Area to implement a RouteConstraint, which can inspect the id submitted in the AJAX request to determine whther it is within the requesting user's scope. Your hypothetical Route in the AreaRegistration.cs file for the Area would look like the following:
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Admin_default",
"Admin/{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
new { id = new IsActionAuthorized() }
);
}
Then, your RouteConstraint would look something like this:
public class IsActionAuthorized : IRouteConstraint
{
public IsActionAuthorized()
{ }
private MyEntities _db = new MyEntities();
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
// return true if no id was submitted
if (String.IsNullOrEmpty(values[parameterName]))
return true;
// return true if action is authorized
var requestId = Convert.ToInt32(values[parameterName]);
var authorized = false;
// code to check against requester's id, to determine
// if requestedId is within requester's authority
// set authorized to true only if the request is authorized for this requester
return authorized;
}
}
This gives you a single entry point, where you can perform the necessary gatekeeping tests. If the tests fail, the request will fail routing and return a 404 error.
However, say I have an admin user logged in, what prevents them from
inspecting the Json posts/gets in the JavaScript and manually calling
a 'get' with the Id of the specific data to be viewed? e.g. /Users/1
Nothing, really.
You can use HTTPS for those requests so at least the data would be encrypted.
I could write access check methods whenever a service method is
called to see if the user can view the data.
You probably will need to do this. On a web application you cannot trust that the request that comes to you is valid.
This code tends to be very application specific so there aren't many "frameworks" that handle it for you. Your best bet would be to write this code in a way that can easily be hooked into your application everywhere you need it.