I have an intranet application setup with windows authentication. Like in most applications, certain parts of the application are accessible to specific roles only. When a user not in desired role would try to access that area, he should be shown a friendly "You do not have permission to view this page" view.
I searched and looked at several resources that guides to extend the Authorize Attribute. I tried that approach, but it simply doesn't work. I still get the IIS error message and the breakpoint in this custom attributes never gets hit. The breakpoint in my extended attibute doen't get hit even when a user in role visits the page. So, I am wondering if I am missing anything ?
This is what I have -
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AuthorizeRedirect : AuthorizeAttribute
{
private const string IS_AUTHORIZED = "isAuthorized";
public string RedirectUrl = "~Areas/Errors/Http401";
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
bool isAuthorized = base.AuthorizeCore(httpContext);
httpContext.Items.Add(IS_AUTHORIZED, isAuthorized);
return isAuthorized;
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
var isAuthorized = filterContext.HttpContext.Items[IS_AUTHORIZED] != null ? Convert.ToBoolean(filterContext.HttpContext.Items[IS_AUTHORIZED]) : false;
if(!isAuthorized && filterContext.RequestContext.HttpContext.User.Identity.IsAuthenticated)
{
filterContext.RequestContext.HttpContext.Response.Redirect(RedirectUrl);
}
}
}
CONTROLLER -
[AuthorizeRedirect]
[HttpPost, ValidateInput(true)]
public ActionResult NewPart(PartsViewModel vmodel) {..}
Any ideas?
Thanks
I think you could use custom error pages instead. Use AuthorizeAttribute to restrict access by callers to an action method.
[Authorize (Roles="Editor, Moderator", Users="Ann, Gohn")]
public ActionResult RestrictedAction()
{
// action logic
}
Then you could use one of the ways those are proposed by #Marco. I like handle HTTP status code within Application_EndRequest. So, it is possible to solve your problem using by following:
protected void Application_EndRequest()
{
int status = Response.StatusCode;
if (Response.StatusCode == 401)
{
Response.Clear();
var rd = new RouteData();
rd.DataTokens["area"] = "Areas";
rd.Values["controller"] = "Errors";
rd.Values["action"] = "Http401";
IController c = new ErrorsController();
c.Execute(new RequestContext(new HttpContextWrapper(Context), rd));
}
}
To clearly specifiey what happens to an existing response when the HTTP status code is an error, you should use existingResponse attribute of <httpErrors> element in your configuration file. If you want to the error page appears immediately, then use Replace value, in otherwise - PassThrough (see details in my issue).
Related
Using .net core 1.1 mvc
The objective is to restrict users so that they may only edit themselves, or allow admin to edit user. I need to get the current user and compare that user's id with the user id passed in an an argument (or cookie).
If the user ids don't match, check if the current user is an admin. If so, grab the user of the user id passed in as an argument (or cookie).
We have a controller that receives ApplicationDBContext and UserManager in the constructor via Dependency Injection.
I failed with both of these attempts:
1) I tried creating an ActionFilter which accessed UserManager through context.Controller.UserManager (a property I set from the controller constructor) but UserManager had an IDisposable error.
2) I tried doing this from the Controller constructor but I need to set the response to 404 if the user is not found and didnt know how to do that from outside of the Action method.
After a day of trial and error I figured it out and wanted to share since I could not find any examples online.
Note that I had to use IAsyncActionFilter because code execution would begin inside the Action method as soon as I called an async method in the filter (Controller.UserToEditProp = await UserManager.FindByIdAsync)
public class ImpersonateAttribute : ActionFilterAttribute, IAsyncActionFilter
{
public override async Task OnActionExecutionAsync(
ActionExecutingContext context,
ActionExecutionDelegate next)
{
string ThisUserID = context.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier);
ApiController Controller = (ApiController)context.Controller;
var UserManager = Controller.UserManager;
if (context.ActionArguments.ContainsKey("UserID"))
{
string RequestedUserID = context.ActionArguments["UserID"].ToString();
if (ThisUserID != RequestedUserID)
{
if (!context.HttpContext.User.IsInRole(UserType.Admin.GetDisplayName()))
{
context.Result = new UnauthorizedResult();
}
}
Controller.UserToEditProp = await UserManager.FindByIdAsync(RequestedUserID);
}
else
{
Controller.UserToEditProp = await UserManager.FindByIdAsync(ThisUserID);
}
await next();
// do something after the action executes
}
}
I am developing an application with Play Framework 2.2 and Java
I have implemented the authentication module just like the following tutorial
http://www.playframework.com/documentation/2.1.0/JavaGuide4
In a nutshell implemented a class Secured as
public class Secured extends Security.Authenticator{
#Override
public String getUsername(Context ctx) {
return ctx.session().get("email");
}
#Override
public Result onUnauthorized(Context ctx) {
return redirect(routes.Users.login());
}
}
and then in controllers I added this line to the methods of controllers
#Security.Authenticated(Secured.class)
public static Result methodOfController(){
//some codes here
return ok( someView.render());
}
As you can see it's just authentication not authorization, for example it checks if user is logged in but never checks if this is email of admin
My question is this: How should I add access rights to these class, or namely how can I add authorization to this authentication
please provide me with a descriptive answer that shows what modifications should I make to this class, controllers and even some other parts of project ( maybe models ) to have a proper authorization
please don't provide links to websites or weblogs unless they are focused on a very similar issue
You can look at a solution like Deadbolt that provides a solution for this, or you can roll your own. The main idea in Java is to use Action composition to create custom action annotations. Thus you could check for if a user is authenticated and then if the user is authorized for the requested resource.
I have written a simple authorization action composition for our project.
Before your actions or controllers you can add a line like the following:
#Auth({"GeneralManager","Manager"})
With the line above only the the people with the role "GeneralManager" or "Manager" can access the action or controller. The implementation of "AuthAction" can be like this:
public class AuthAction extends Action<Auth> {
public F.Promise<SimpleResult> call(Http.Context context) throws Throwable
{
String[] params = configuration.value();
int c = params.length;
boolean found = false;
if(params.length == 0) {
found = true;
}
// Loop the given parameters(role names) to check that the user belongs to one of them
for (String code: params) {
// validate types
int roleCount = Role.find.where().eq("code",code).findRowCount();
if(roleCount == 0) {
throw new Exception("Auth code is not found.");
}
if(user.role.code.equals(code)) {
found = true;
}
}
// if the role is not found for the user, it means the user is not authorised
if(!found) {
// no access, redirect to home
return F.Promise.pure(redirect("/"));
}
// execute the action
return delegate.call(context);
}
}
In my .net mvc 4 app I am using the latest release of FluentSecurity (1.4) in order to secure my actions.
Here is an example that illustrates my problem:
Suppose I have a controller with 2 edit actions (get and post):
public class MyController : Controller
{
//
// GET: /My/
public ActionResult Edit(decimal id)
{
var modelToReturn = GetFromDb(id);
return View(modelToReturn);
}
[HttpPost]
public ActionResult Edit(MyModel model)
{
Service.saveToDb(model);
return View(model);
}
}
Now, I would like to have a different security policy for each action. To do that I define (using fluent security):
configuration.For<MyController>(x => x.Edit(0))
.AddPolicy(new MyPolicy("my.VIEW.permission"));
configuration.For<MyController>(x => x.Edit(null))
.AddPolicy(new MyPolicy("my.EDIT.permission"));
The first configuration refers to the get while the second to the post.
If you wonder why I'm sending dummy params you can have a look here and here.
Problem is that fluent security can't tell the difference between those 2, hence this doesn't work.
Couldn't find a way to overcome it (I'm open for ideas) and I wonder if installing the new 2.0 beta release can resolve this issue.
Any ideas?
It is currently not possible to apply different policies to each signature in FluentSecurity. This is because FluentSecurity can not know what signature will be called by ASP.NET MVC. All it knows is the name of the action. So FluentSecurity has to treat both action signatures as a single action.
However, you can apply multiple policies to the same action (you are not limited to have a single policy per action). With this, you can apply an Http verb filter for each of the policies. Below is an example of what it could look like:
1) Create a base policy you can inherit from
public abstract class HttpVerbFilteredPolicy : ISecurityPolicy
{
private readonly List<HttpVerbs> _httpVerbs;
protected HttpVerbFilteredPolicy(params HttpVerbs[] httpVerbs)
{
_httpVerbs = httpVerbs.ToList();
}
public PolicyResult Enforce(ISecurityContext securityContext)
{
HttpVerbs httpVerb;
Enum.TryParse(securityContext.Data.HttpVerb, true, out httpVerb);
return !_httpVerbs.Contains(httpVerb)
? PolicyResult.CreateSuccessResult(this)
: EnforcePolicy(securityContext);
}
protected abstract PolicyResult EnforcePolicy(ISecurityContext securityContext);
}
2) Create your custom policy
public class CustomPolicy : HttpVerbFilteredPolicy
{
private readonly string _role;
public CustomPolicy(string role, params HttpVerbs[] httpVerbs) : base(httpVerbs)
{
_role = role;
}
protected override PolicyResult EnforcePolicy(ISecurityContext securityContext)
{
var accessAllowed = //... Do your checks here;
return accessAllowed
? PolicyResult.CreateSuccessResult(this)
: PolicyResult.CreateFailureResult(this, "Access denied");
}
}
3) Add the HTTP verb of the current request to the Data property of ISecurityContext and secure your actions
SecurityConfigurator.Configure(configuration =>
{
// General setup goes here...
configuration.For<MyController>(x => x.Edit(0)).AddPolicy(new CustomPolicy("my.VIEW.permission", HttpVerbs.Get));
configuration.For<MyController>(x => x.Edit(null)).AddPolicy(new CustomPolicy("my.EDIT.permission", HttpVerbs.Post));
configuration.Advanced.ModifySecurityContext(context => context.Data.HttpVerb = HttpContext.Current.Request.HttpMethod);
});
I have a custom claim that determines language user will be using.
I have extension to ClaimsPrincipal, that checks for that claim and generates appropriate culture:
public static CultureInfo Culture(this ClaimsPrincipal principal)
{
Claim claim = principal.FindFirst(CustomClaimTypes.Language);
if (claim == null)
return CultureInfo.CreateSpecificCulture("en");
else
return CultureInfo.CreateSpecificCulture(claim.Value);
}
I've also implemented custom UserNameSecurityTokenHandler and custom ClaimsAuthorizationManager and both work fine. Actually entire solution works just fine, except for the fact that I'm unable to find appropriate point in WCF pipeline where I can set Thread.CurrentCulture based on the extension above.
I've tried ICallContextInitializer, IDispatchMessageInspector, but none of the above can actually see my ClaimsPrincipal (it gets overriden with empty principal). However, in my service operations, ClaimsPrincipal.Current points properly to identity I've created in UserNameSecurityTokenHandler.
Where should I set this Claim-dependant culture?
For WCF data service i use ProcessingRequest event:
public class ConfigurationManagementDataService : SecurityDataService<ConfigurationContext>
{
public ConfigurationManagementDataService()
{
Database.SetInitializer<ConfigurationContext>(null);
ProcessingPipeline.ProcessingRequest += OnProcessingRequest;
}
public void OnProcessingRequest(object sender, DataServiceProcessingPipelineEventArgs dataServiceProcessingPipelineEventArgs)
{
//assign IPrincipal to a Thread.Principal
//we must set both, but Thread.CurrentPrincipal is a main
if (context != null)
{
context.User = principal;
}
Thread.CurrentPrincipal = principal;
I'm working with the default template for MVC 4 and trying to add my own openID provider for example http://steamcommunity.com/dev to the list of openID logins and an openID box where the user can type in their openID information.
To add Google I just un-comment
OAuthWebSecurity.RegisterGoogleClient();
as for other custom solutions you can do something like
OAuthWebSecurity.RegisterClient(new SteamClient(),"Steam",null);
The trouble I have is creating SteamClient (or a generic one) http://blogs.msdn.com/b/webdev/archive/2012/08/23/plugging-custom-oauth-openid-providers.aspx doesn't show anywhere to change the URL.
I think the reason I could not find the answer is that most people thought it was common sense. I prefer my sense to be uncommon.
public class OidCustomClient : OpenIdClient
{
public OidCustomClient() : base("Oid", "http://localhost:5004/") { }
}
Based on #Jeff's answer I created a class to handle Stack Exchange OpenID.
Register:
OAuthWebSecurity.RegisterClient(new StackExchangeOpenID());
Class:
public class StackExchangeOpenID : OpenIdClient
{
public StackExchangeOpenID()
: base("stackexchange", "https://openid.stackexchange.com")
{
}
protected override Dictionary<string, string> GetExtraData(IAuthenticationResponse response)
{
FetchResponse fetchResponse = response.GetExtension<FetchResponse>();
if (fetchResponse != null)
{
var extraData = new Dictionary<string, string>();
extraData.Add("email", fetchResponse.GetAttributeValue(WellKnownAttributes.Contact.Email));
extraData.Add("name", fetchResponse.GetAttributeValue(WellKnownAttributes.Name.FullName));
return extraData;
}
return null;
}
protected override void OnBeforeSendingAuthenticationRequest(IAuthenticationRequest request)
{
var fetchRequest = new FetchRequest();
fetchRequest.Attributes.AddRequired(WellKnownAttributes.Contact.Email);
fetchRequest.Attributes.AddRequired(WellKnownAttributes.Name.FullName);
request.AddExtension(fetchRequest);
}
}
Retrieving extra data:
var result = OAuthWebSecurity.VerifyAuthentication();
result.ExtraData["email"];
result.ExtraData["name"];