Reputation: 18424
Given the following code:
public class BackupsController : ApiController
{
private readonly IApiContext context;
private readonly IBackupService backupService;
public BackupsController(IApiContext context, IBackupService backupService)
{
this.context = context;
this.backupService = backupService;
}
public HttpResponseMessage Get(Guid id)
{
if (id == Guid.Empty)
{
throw new HttpResponseException(HttpStatusCode.BadRequest);
}
IBackupView backup = backupService.Get(id);
if (backup == null)
{
return Request.CreateErrorResponse(HttpStatusCode.NotFound, String.Format("BackupId '{0}' not found.", id));
}
if (!IsAuthorizedForBackup(backup))
{
throw new HttpResponseException(HttpStatusCode.Forbidden);
}
return Request.CreateResponse(HttpStatusCode.OK, backup);
}
private bool IsAuthorizedForBackup(IBackupView backup)
{
if (context.Principal.IsInRole(MembershipRole.Admin))
{
return true;
}
if (context.Principal.AllowDataSharing && backup.UserId == context.Principal.UserId)
{
return true;
}
if (backup.UserId == context.Principal.UserId && backup.Device.Uuid == context.DeviceUuid)
{
return true;
}
return false;
}
}
Does it make sense to extract almost all of the method body into an authorization filter? I don't see a way to do that without retrieving the backup twice.
How would you go about separating the authorization concerns from the controller action?
Upvotes: 2
Views: 270
Reputation: 19311
What you are asking for is technically possible. Say, you implement an action filter and have some logic in the overridden OnActionExecuted to set the status code to Forbidden. I have not done it this way and it is only a suggestion for you to explore the feasibility. OnActionExecuted runs after the action method and it can access backup.
Another better alternative is to use claims based identity and implement a sub-class of ClaimsAuthorizationManager. CheckAccess(AuthorizationContext) takes in both action claims and resource claims. Attributes related to back up can be passed along as resource claims.
Upvotes: 0
Reputation: 11549
In order to separate security logic from controller logic I prefer to use Http Headers to carry security tokens between browser and controller and check that header value in a custom AuthorizeAttribute
For example;
In beforeSend
function of JQuery's ajax function set the security token (which is previously taken from the server, see below)
beforeSend: function (xhr) {
xhr.setRequestHeader('requestToken', model.requestToken);
}
Check the token in a custom AuthorizeAttribute
public class AuthAttribute : AuthorizeAttribute
{
public override void OnAuthorization(HttpActionContext actionContext)
{
var token = HttpContext.Current.Request.Headers["requestToken"];
// Do the authorization based on token
}
}
Decorate the controller, whose actions require authorization, with the custom [Auth]
Attribute, like:
[Auth]
public class SomeController : ApiController
We can send new token back to client again using Http Headers
HttpContext.Current.Response.Headers["requestToken"] = Guid.NewGuid();
And at client-side you can store it in success function of JQuery's ajax function for sending back in request
success: function (res, status, xhr) {
model.requestToken = xhr.getResponseHeader('requestToken');
}
This may not handle your situation perfectly but main idea is carrying the (preferably encrypted) security data in Http Headers and dealing the security things in a custom AuthorizeAttribute
Upvotes: 3