Custom AuthorizeAttribute on WebApi OData EntitySetController - asp.net-web-api

I created custom authorize attribute to handle my custom permissions on WebAPI odata controller inherited from EntitySetController, here is the code for my attribute
[AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)]
public class RequirePermissionsAttribute : System.Web.Http.AuthorizeAttribute
{
public Permissions[] Permissions { get; set; }
public RequirePermissionsAttribute()
{ }
public RequirePermissionsAttribute(params Permissions[] permissions)
{
this.Permissions = permissions;
}
public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext)
{
// Custom authorization logic
}
Now I try to add this attribute on Get() method, it get invoked
public class ItemsController : EntitySetController<Item, Guid>
{
[EnableQuery(MaxExpansionDepth = 5)]
[RequirePermissionsAttribute(Permissions.ViewAll)]
public override IQueryable<Item> Get()
{
//Code go here
}
}
But when I add the same attribute on CreateEntity() it never get invoked
[RequirePermissionsAttribute(Permissions.Add)]
protected override Item CreateEntity(Item item)
{
// Create item
}
Any help appreciated

You should use your attribute as RequirePermissions without the tailed "Attribute" word, So change your code to be like this
[RequirePermissions(Permissions.Add)]
protected override Item CreateEntity(Item item)
{
// Create item
}

Islam
From the WebAPI source codes, the internal virtual function CreateEntity() is called in POST request. Here's the source codes in EntitySetController:
public virtual HttpResponseMessage Post([FromBody] TEntity entity)
{
TEntity createdEntity = CreateEntity(entity);
TKey entityKey = GetKey(entity);
return EntitySetControllerHelpers.PostResponse(this, createdEntity, entityKey);
}
I use your sample codes and send a POST request, the CreateEntity() can be invoked as:
POST ~/odata/Items
Content-type: application/json
{"Id":"9daf653f-212c-42e3-80a4-4778e445c092"}
However, if you want to get the correct response, you should override GetKey() because GetKey() is called after CreateEntity() in the Post() method. The same information is also mentioned in the remarks of CreateEntity() as below:
Sample Test
I create the following two functions in ItemsController:
protected override Guid GetKey(Item entity)
{
return entity.Id;
}
[RequirePermissionsAttribute(Permissions.Add)]
protected override Item CreateEntity(Item item)
{
// Create item
return item;
}
And send the same POST request mentioned above, I can get the following response:
HTTP/1.1 201 Created
Cache-Control: no-cache
.....
Content-Length: 124
{
"odata.metadata":"http://localhost:47794/odata/$metadata#Items/#Element","Id":"9daf653f-212c-42e3-80a4-4778e445c092"
}
Hope it can help you. Thanks.

Related

How to get Response Body in Zuul?

How to get Response Body in Zuul?
So I have the ff. code snippet to get the response body in a request.
RequestContext ctx = RequestContext.getCurrentContext();
getResponseDataStream
getResponseBody()
ctx.getResponse()
But all of this are returning null. I already tried those InputStreams, but I can't get it to retrive the data as all of it just returns null..
Try something from this answer. It should be access via getResponseDataStream() or getResponseBody(). If you do it in ZuulFilter in 'post' keep the higher value for filterOrder. Maybe it was not written yet. For example:
public class PostFilter extends ZuulFilter {
#Override
public String filterType() {
return POST_TYPE;
}
#Override
public int filterOrder() {
return SEND_RESPONSE_FILTER_ORDER - 1;
}

Swagger-Net supporting API Key authentication

We are using token authentication in our WebAPI application. Every call (other then method which obtains key) uses same pattern.
Authorization: our-token v01544b7dce-95c1-4406-ad4d-b29202d0776c
We implemented authentication using Attribute and IActionFilter
Controllers look like so:
[RoutePrefix("api/tms/auth")]
public class AuthController : BaseController
{
public ISecurityService SecurityService { get; set; }
[TokenAuth]
[Route("logout")]
[HttpPost]
public HttpResponseMessage Logout()
{
try
{
this.SecurityService.InvalidateAccessToken(this.StaticContextWrapperService.AccountId, token, HttpContext.Current.Request.UserHostAddress);
// Return OK status
return new HttpResponseMessage(HttpStatusCode.OK);
}
catch (LoginException le)
{
return this.LogoutFailureResponse(le.Message);
}
}
private HttpResponseMessage LogoutFailureResponse(string message)
{
return new HttpResponseMessage(HttpStatusCode.BadRequest)
{
Content = new StringContent(message, Encoding.UTF8, "text/plain")
};
}
}
Swagger config has following:
c.ApiKey("our-token", "header", "Our Token Authentication");
Swagger UI showing "Authorize" button and I can paste token into field on popup.
However, no headers passed in any tests. And no methods have "lock" icon on them.
EDIT:
I also tried:
c.ApiKey("our-token", "header", "Our Token Authentication", typeof(TokenAuthAttribute));
Where attribute is just attribute:
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public class TokenAuthAttribute : Attribute
{
}
Then we use IActionFilter to check if attribute applied to method and thats where we check for permission. This is done to use service via DI.
EDIT2:
I made change to how Attribute declared:
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public class TokenAuthAttribute : AuthorizeAttribute
{
}
After than Swagger UI started to show all methods as secured, so it does analyze that it's in fact AuthorizeAttribute, not just Attribute
After that it started to put header like so:
our-token: ZGV2OnYwMTA2YjZmYjdhLWRlNTUtNDZlNC1hN2Q4LTYxMjgwNTg2M2FiZQ==
Where it should be:
Authorization: our-token GV2OnYwMTA2YjZmYjdhLWRlNTUtNDZlNC1hN2Q4LTYxMjgwNTg2M2FiZQ==
If I'm not mistaken you should have:
c.ApiKey("our-token", "header", "Our Token Authentication", typeof(TokenAuthAttribute));
With that in place, all the actions tagged with TokenAuth should show a lock icon
You can see it in action in one of mine:
https://turoapi.azurewebsites.net/swagger/ui/index
And the code behind that is here:
https://github.com/heldersepu/TuroApi/blob/master/TuroApi/App_Start/SwaggerConfig.cs#L67

How to return xml or json with ASP.NET web mvc 6 depending on Accept Header

I have implemented a web api controller using ASP.NET mvc 6 and I would like to return the result of the controller as json or xml, depending on the client's Accept header. For example, if the client sends a GET request with "Accept: application/xml", then the returned response should be xml. If the header is "Accept: application/json", then it should be json. At the moment the controller always returns json. Is there a way of configuring this? Note: this question is indeed a duplicate of How to return XML from an ASP.NET 5 MVC 6 controller action. However the solution provided there did not solve my problem. The accepted answer below worked for me.
The controller is given below and is the one provided by the ASP.NET 5 web api template:
[Route("api/[controller]")]
public class ValuesController : Controller
{
// GET: api/values
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/values/5
[HttpGet("{id:int}")]
public string Get(int id)
{
return "value";
}
// POST api/values
[HttpPost]
public void Post([FromBody]string value)
{
}
// PUT api/values/5
[HttpPut("{id}")]
public void Put(int id, [FromBody]string value)
{
}
// DELETE api/values/5
[HttpDelete("{id}")]
public void Delete(int id)
{
}
}
Thanks for your help!
I did the research for you, you may continue alone:
needed:
"Microsoft.AspNet.Mvc": "6.0.0-rc1-final",
"Microsoft.AspNet.Mvc.Core": "6.0.0-rc1-final",
"Microsoft.AspNet.Mvc.Formatters.Xml": "6.0.0-rc1-final"
startup:
services.Configure<MvcOptions>(options =>
{
options.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter());
});
go on from here
another
and antoher

Appropriate pattern for setting request object properties from POST request with MVC3?

With incoming POST requests to my MVC3 application, I want to validate the incoming request parameters. If an invalid parameter exists, an exception is thrown.
Given the following object:
public class ActionRequest
{
public string ActionRequestPassword { get; set; }
public bool EnableNewsfeedAppPool { get; set; }
}
With incoming post requests, I want to initialize the object with the appropriate properties via:
public class NewsfeedAppPoolController : Controller
{
[ActionName("EnableAppPool"), AcceptVerbs(HttpVerbs.Post)]
[ValidateInput(false)]
[NoCache]
public ActionResult EnableAppPool(FormCollection formCollection)
{
Models.ActionRequest actionRequest = ValidatePOSTRequest(formCollection);
// do things with actionRequest
return null;
}
private Models.ActionRequest ValidatePOSTRequest(FormCollection formCollection)
{
try
{
Type actionRequestType = typeof(Models.ActionRequest);
System.Reflection.PropertyInfo propertyInfo = null;
object systemActivatorObject = Activator.CreateInstance(actionRequestType);
foreach (var key in formCollection.AllKeys)
{
propertyInfo = typeof(Models.ActionRequest).GetProperty(key);
Type t = propertyInfo.PropertyType; // t will be System.String
if (t.Name == "Int32")
{
actionRequestType.GetProperty(key).SetValue(systemActivatorObject, Convert.ToInt32(formCollection[key]), null);
}
else
{
actionRequestType.GetProperty(key).SetValue(systemActivatorObject, formCollection[key], null);
}
}
return (Models.ActionRequest)systemActivatorObject;
}
catch (Exception ex)
{
throw ex;
}
}
}
I would like to know if there can be any improvements made to this, or recommendations of how else to accomplish this in an efficient manner.
Thanks.
ASP.Net MVC already does all of this for you.
Just add a Models.ActionRequest actionRequest parameter to your action.
If you want to add additional validation logic, use System.ComponentModel.DataAnnotations.
Simply use the default model binder which will take care of instantiating and binding the ActionRequest from the request parameters:
public class NewsfeedAppPoolController : Controller
{
[ActionName("EnableAppPool"), AcceptVerbs(HttpVerbs.Post)]
[ValidateInput(false)]
[NoCache]
public ActionResult EnableAppPool(ActionRequest actionRequest)
{
// do things with actionRequest
return null;
}
}
The appropriate pattern is,
[HttpPost]
public ActionResult Save(Employee employee)
{
if(ModelState.IsValid)
{
db.Save(employee);
RedirectToAction("Index");
}
return View();
}
Notes:
The employee instance is automatically created and populated by the default model binder from the values available in the request(form, querystrings, routedata and more)
When the default model binder binds the values to the model it also does the validation and store all the errors in the ModelState dictionary, so by checking the ModelState.IsValid you can know that whether the validation is succeeded or not.
To know more about model binding refer this.
To know more about model validation refer this.

How to return HTTP status code form Custom Model Binder

I have a custom model binder which pulls an implementation of an interface from a MEF container. It is implemented as follows:
public class PetViewModelBinder : DefaultModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var petId = bindingContext.ValueProvider.GetValue("id");
var container = controllerContext.HttpContext.Application[MvcApplication.PLUGINS] as CompositionContainer;
Lazy<IPet, IPetMetadata> pet = null;
try
{
pet = container.GetExport(typeof(IPet), petId);
var petVM = new Models.PetViewModel(pet);
bindingContext.ModelMetadata.Model = petVM;
return base.BindModel(controllerContext, bindingContext);
}
catch (Exception)
{
throw;
}
finally
{
container.ReleaseExport(pet);
}
}
This works splendidly when MEF has an Export of petId... but returns http status 500 (server error) when an Export does not exist. Error message obfuscation requirements dictate http status 403 (forbidden) should be returned.
What can be done to trap the error, change the response status, and either not return content, or re-route the Action to handle this condition?
If you want to return a particular http status code you should do that from a controller or action filter.
One way to do this is to return null from your model binder and handle that in your controller. This is a bit coarse however so you won't be able to distinguish between different errors.
Another way to do it would be to throw a specific exception and handle that in your (global) error handling. A customized HandleError action filter could do this:
public class CustomHandleErrorAttribute : HandleErrorAttribute
{
public int StatusCode { get; set; }
public override void OnException( ExceptionContext filterContext )
{
base.OnException( filterContext );
if ( StatusCode > 0 )
{
filterContext.HttpContext.Response.StatusCode = StatusCode;
}
}
}
In your controller, decorate the action with this attribute:
[CustomHandleError( ExceptionType = typeof (NotAllowedException), View = "~/Views/Shared/Error.cshtml",
StatusCode = 403 )]
public ActionResult Index( FancyModel model )
{
return View( model );
}
Finally, in your model binder throw a NotAllowedException, which is a custom exception type you'll also need to define.
Note that this will only work on your development setup if you have enabled custom errors in your web.config file.

Resources