There are a lot of similar questions out there but this has me stumped.
If I used [Authorize] I get prompted for a username and password but if I use [InternalAuthorizeV2] I don't
I have a custom AuthorizeAttribute that for the moment does not do any anything special (I'm limiting things that could be wrong).
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public class InternalAuthorizeV2Attribute: AuthorizeAttribute
{}
and a Action in my Controller
[InternalAuthorizeV2(Roles = "MobileApps_Parkingggg")]
public ActionResult Index()
{
var model = new VmParking();
return View(model);
}
The login is handled in a different app but they have identical web config lines
<machineKey compatibilityMode="Framework20SP2" validationKey="editedOut" decryptionKey="editedOut" validation="SHA1" decryption="AES"/>
<authentication mode="Forms">
<forms name="emLogin" loginUrl="/Login/Index" timeout="540" protection="All" path="/" enableCrossAppRedirects="true" cookieless="UseCookies" />
</authentication>
<sessionState timeout="540" />
I know that if I login by going to a page with [Authorize] then back to my trouble page I can see the username but it doesn't seem to be calling my customer attribute.
New information:
My attribute is in a shared DLL as it's used by many apps. It seems that if I copy the cs file to the web project it works. No idea why, still looking for hints or tips.
From what you've said, it all behaves fine if you use [Authorize] but not [InternalAuthorizeV2].
Your shared dll shouldn't make any difference if it is set up correctly; I have the same thing working. Make sure the web project is using the latest version of the dll and you have the right assembly references in the shared dll - System.Web.Mvc, v4.0.0.0 in my project.
You say its used by many apps? Do all apps have the same problem with the shared dll or just one of them? If it's just one, check the references for the one with the problem.
If the below tests all work then the final option is that whatever you are doing in your authorize attribute in the dll isn't picking up the right context for that app, or using the right membership provider or database - you haven't included the code you are using inside your attribute so it's hard to know if that could be causing a problem.
Test dependencies
You could try adding a base authorize attribute to your shared dll, and then implementing another authorize attribute in your web project that inherits the base attribute you just created. This should show that you have your shared dll set up correctly.
// in the dll:
public class BaseAuthorizeAttribute : System.Web.Mvc.AuthorizeAttribute { ... }
// in the web project:
public class InternalAuthorizeV2Attribute : BaseAuthorizeAttribute { ... }
If simply moving it from your dll project to the web project fixes it, the most likely issue is the web project is not using the right version of the dll (try cleaning and doing a complete rebuild) or your dll is referencing the wrong dlls for the System.Web.Mvc.AuthorizeAttribute. You say you have triple checked, but trying the above debugging should help you work out if this really is the case.
Debug authorization method calls
If that doesn't work then try adding the following override methods to a very simple attribute, and seeing if you hit the breakpoint on the call to base.OnAuthorization. If you don't, then it may not be the actual attributes causing your problem.
[AttributeUsageAttribute(AttributeTargets.Class|AttributeTargets.Method,
Inherited = true, AllowMultiple = true)]
public class InternalAuthorizeV2Attribute : System.Web.Mvc.AuthorizeAttribute {
protected override bool AuthorizeCore(System.Web.HttpContextBase httpContext) {
return false; // breakpoint here, and this should force an authorization failure
}
public override void OnAuthorization(System.Web.Mvc.AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext); // breakpoint here
}
}
This should completely prevent any user access to the Action. If that doesn't work then you know the issue doesn't lie in your attribute, but that your attribute is not being applied.
You can also add the following to your controller and check that it is hit before the authorize attribute:
protected override void OnAuthorization(AuthorizationContext filterContext) {
base.OnAuthorization(filterContext);
}
Authorization chaining
Note that you have your attribute attached to the Action method, so it will only be hit if a an authorization attribute earlier in the chain (e.g. a global filter or controller attribute) hasn't already prevented the user being authorized (see my answer here), or prematurely returns an ActionResult that stops the chain reaching your Action attribute. However it's unlikely this is the problem if simply moving it from the dll to the project makes it work. Similarly it's unlikely you have an AllowAnonymous in the wrong place from what you've said.
Related
My team and I are starting up a new website project in ASP .NET 5 and I'm trying to set up the basis of our user authentication and authorization policy.
So far, I've managed to use the [Authorize] and [AllowAnonymous] attributes to selectively define an authorization policy controllers or actions. The one thing I'm still struggling to achieve is defining a default authorization policy.
Bascially, I'd like every controller and action to behave as if they had an [Authorize] attribute by default, so that only actions specifically tagged as [AllowAnonymous] can be accessed by an anonymous user. Otherwise, we expect that, at some point, someone will forget to add an [Authorize] attribute to their controller and introduce vulnerabilities into the webapp.
It is my understanding that what I'm trying to do could be achieved in previous versions of ASP .NET by adding the following statement in FilterConfig.cs:
filters.Add(new AuthorizeAttribute());
... except that FilterConfig.cs no longer exists in MVC 6. According to How to register a global filter with mvc 6, asp.net 5 I can now access the global filters list using:
services.ConfigureMvc(options =>
{
options.Filters.Add(new YouGlobalActionFilter());
}
... tried it, looks fine, but now it's the AuthorizeAttribute filter that I can't seem to find.
For experimenting purposes I've tried to handcraft an equivalent to the AuthorizeAttribute filter and came up with the following:
public class LoginFilter: AuthorizeFilter
{
public LoginFilter(): base(new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build())
{
}
public override Task OnAuthorizationAsync(Microsoft.AspNet.Mvc.AuthorizationContext context)
{
if(!context.HttpContext.User.Identity.IsAuthenticated && context.ActionDescriptor is ControllerActionDescriptor)
{
var action = context.ActionDescriptor as ControllerActionDescriptor;
if(!AcceptAnonymous(action.ControllerTypeInfo) && !AcceptAnonymous(action.MethodInfo))
{
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
}
}
return base.OnAuthorizationAsync(context);
}
private static bool AcceptAnonymous(ICustomAttributeProvider o)
{
return o.IsDefined(typeof(AllowAnonymousAttribute), true);
}
}
This kinda works... I can add it to the global filters list, and it does reject queries coming from unauthenticated users unless the query is resolved to an action tagged [AllowsAnonymous].
However...
the AuthorizationPolicyBuilder thingy is ugly and misleading: it does not serve any purpose and is apparently ignored during the whole processing. The only reason I added it is that AuthorizeFilter requires an AuthorizationPolicy in its constructor. I guess, but haven't tried yet, that directly implementing IAsyncAuthorizationFilter would solve this particular issue
nothing in this code is specific to my webapp and the functionality was apparently provided in previous versions of the framework, so I'm willing to bet that there already is (or there will soon be) a component doing exactly the same thing, and I'd rather use a standard component from the framework than handcraft my own.
So, long story short, where has the AuthorizeAttribute filter gone? Or is there any functional equivalent I can use to make rejection of anonymous users the default behavior?
You can use Microsoft.AspNet.Mvc.AuthorizeFilter.
using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Authorization;
services.ConfigureMvc(options =>
{
options.Filters.Add(new AuthorizeFilter(new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build()));
});
If you need custom authorization requirements see this answer for more information.
I have a problem with URL rewriting which works in Global.asax but not in OWIN middleware.
Global.asax code
protected void Application_BeginRequest()
{
//Perfectly working rewrite.
//By route rules, this resolves to the action Global()
//of the HomeController
HttpContext.Current.RewritePath("Home/Global");
}
OWIN middleware code (used for culture detection, code shortened for brevity)
public class GlobalizationMiddleware : OwinMiddleware
{
public GlobalizationMiddleware(OwinMiddleware next)
: base(next)
{ }
public async override Task Invoke(IOwinContext context)
{
context.Request.Path = new PathString("/Home/Global");
await Next.Invoke(context);
}
}
I expect that "Global" action of the controller "Home" gets called...but instead, the default action "Index" is called.
After the Path is changed context.Request.Uri.AbsoluteUri is http://localhost/Global/Home
But Controller's Request.Url.AbsoluteUri is still http://localhost
I even tried context.Environment["owin.RequestPath"] = "/Home/Global"; but that doesn's seem to work either.
Before anyone asks, yes, I call the IAppBuilder.Use(typeof(GlobalizationMiddleware)) in Startup.cs and the debugger enters the Invoke method.
What am I doing wrong?
EDIT
I even tried referencing System.Web and then doing this...doesn't work either :(
System.Web.Routing.RequestContext requestContext = context.Environment["System.Web.Routing.RequestContext"] as System.Web.Routing.RequestContext;
requestContext.HttpContext.RewritePath("/Home/Global");
System.Web.HttpContextBase contextBase = context.Environment["System.Web.HttpContextBase"] as System.Web.HttpContextBase;
contextBase.RewritePath("/Home/Global");
EDIT 2 - Found a working solution (see below) but I'm unsure whether it's the right solution, comments would be appreciated :)
I found a working solution.
Unfortunately, I needed to include System.Web. I'm directly altering the RouteData object in the RequestContext.
System.Web.Routing.RequestContext requestContext = context.Environment["System.Web.Routing.RequestContext"] as System.Web.Routing.RequestContext;
requestContext.HttpContext.RewritePath("Home/Global");
requestContext.RouteData.Values["action"] = "Global";
But this feels too hacky to my tastes... I'm not sure if this is the right solution so I won't accept this as the valid answer, maybe someone will come with a better solution.
I ran into this as well, and found that one way to get this working is just to use HttpContext
HttpContext.Current.RewritePath("/new/path");
If you're already including System.Web anyway this seems to be a workable solution.
OWIN is a layer on top of ASP.NET (System.Web), and some of the ASP.NET components (such as MVC) are not built on top of OWIN but actually ASP.NET directly.
Looking deeper, OwinRequest doesn't have any reference to System.Web.HttpContext and instead just deals with Environment (source), so that's why setting the OWIN request path doesn't affect ASP.NET apps.
My Question Is This
What configuration step have I missed to make Mvc Surface Controllers work in Umbraco?
My theory is that since there is a folder in the default Umbraco install called /umbraco/ which is used to connect to the CMS that the physical path is interfiering with the route /umbraco/surface/{Controller}/{Action} thus resulting in the ASP.NET YSOD (and an IIS 404
when I try to access a controller on that route that isn't defined.)
Background Information
I have added this class to my App_Code folder in a freshly downloaded copy of Umbraco 6.1.6:
public class MembersController : SurfaceController
{
public ActionResult Index()
{
return Content("Hello, Member!");
}
}
When I navigate to what I think should be the route for my Index() method, I get a YSOD that says the resource could not be found:
the code is not executed and the above error is displayed; however, if I change the Uri to garbage I get an IIS 404 error:
I started getting this in an existing site, thinking my site was screwed up I tried it in a new copy of Umbraco 6.1.6 and got the exact same results.
For the record, I have also tried MembersSurfaceController and its associated Uri, which has the exact same result as above. YSOD when I hit the valid route, and IIS 404 when I don't.
I have changed my umbracoSettings.config to MVC in the /config/ directory as well.
update
I'm using the out-of-the-box web.config file, which has this:
<system.webServer>
<validation validateIntegratedModeConfiguration="false" />
<modules runAllManagedModulesForAllRequests="true">
<remove name="UrlRewriteModule" />
<add name="UrlRewriteModule" type="UrlRewritingNet.Web.UrlRewriteModule, UrlRewritingNet.UrlRewriter" />
.
..
...
On my default Umbraco site I don't have any rewrite rules defined; but on my actual site I have several rewrite rules in place. I'm thinking that's not causing it since I'm seeing the same behavior on both sites though...
I have tried removing UrlRewrite completely I get the same results.
The following approach works for me in Umbraco 7.1, and I expect it to work in 6.1 as well:
Create folder called 'Controllers' within your App_Code folder, and put your surface controllers in there (so that they will be within the 'Controllers' namespace).
E.g. I have the following controller in the App_Code\Controllers folder (and hence, within the 'Controllers' namespace):
namespace Controllers
{
public class ServiceCentersController : SurfaceController
{
public ActionResult GetServiceCenters(string country = "", string region = "", string city = "")
{
...
}
}
}
My site runs on localhost, so I can invoke the GetServiceCenters action by navigating to:
http://localhost/umbraco/Surface/ServiceCenters/GetServiceCenters?country=aa®ion=bb&city=cc
You need a namespace for your controller - the code posted above doesn't have any namespace:
public class MembersController : SurfaceController
{
public ActionResult Index()
{
return Content("Hello, Member!");
}
}
That is why making a namespace of Controllers works ... but you could make this any logically named namespace you want.
Normally, on a usual aspx file, I can use System.Attribute at the begining of the page, like:
[AuthorizePage()]
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}
}
public class AuthorizePage : System.Attribute
{
public AuthorizePage()
{
//do some stuff to authorize
}
}
And before the page initializes the Attribute's constructor runs and do some stuff to ensure a person is currently logged in, otherwise the attribute constructor Redirects user to a login page.
I wanted to do the same on a HttpHandler (ashx file), but the attribute never initializes when on a ashx page.
[AuthorizePage()]
public class AjaxHandler : MuCustomClassBase, IHttpHandler, IReadOnlySessionState
{
//The interface implementations and some other custom private methods
}
I do an AJAX Call to this ashx page. Could this be the reason why the Attribute doesn't run? Or any other things I must know?
Eventually, I would be extremely happy to know How to run a Custom System.Attribute over an ashx file?
Assuming you are using ASP.Net authentication, you can just add the .ashx to the list of protected pages in web.config and IIS/ASP.Net will take care of the rest:
<location path="AjaxRequests.ashx">
<system.web>
<authorization>
<allow users="?" />
</authorization>
</system.web>
</location>
If you are using a self-built authentication scheme, you could override OnProcessRequest and perform the necessary authentication in that method, redirecting as needed.
Attributes don't do anything by themselves. You can pile 10 random attributes on a class and nothing will really happen. Attributes just provide metadata about the class/method/property.
There should be a piece of code that looks at the metadata and act on it. Since you seem to be using custom AuthorizePageAttribute such piece of code either don't run for handlers or does not expect class that is not derived from Page to have such attribute.
To fix an issue you need to find what handles you custom attribute and fix it. You may need to add similar code to your handlers directly.
The fact that your code in constructor of attribute does something useful for a page class on every request to that page sounds suspicious - I'd expect such attributes to be created once per type instance. Relying on non-trivial code in constructor of an attribute to run per-instance of the class seems dangerous to my.
Well first of all yes you are reinventing the ActionFilterAttribute in Asp.Net MVC. But, I have to ask if you really need to use attribute? I suggest you to use inheritance model. Let me simply explain; you might have a SecurePage : Page class that implements the security operations. You may then pass the security code that is related to the derived page.
And if you insist on using attributes, you should intersect the handler mechanism by writing a base factory handler that routes to the needed handler. This handler should behave like a mediator object.
This is driving me insane... I am reorganizing my MVC app into a Models project and a Controllers project, and then the main application as a project. So, everything is working good so far except...
Whenever I go to "Rebuild" my controllers project, I get this error:
Controllers.AccountController.Initialize(System.Web.Routing.RequestContext)': no suitable method found to override.
Keep in mind that AccountController.cs was automatically placed in my application by Visual Studio, and this was all working fine when the Controllers were within my main project. I think it might have to do with the ASPNETDB.MDF file that this AccountController.cs file references to authenticate users as they log in, since this database stayed within my main project and didn't follow the Controllers project. Thoughts on that??
Here's the Initialize method on my AccountController:
protected override void Initialize(RequestContext requestContext)
{
if (FormsService == null) { FormsService = new FormsAuthenticationService(); }
if (MembershipService == null) { MembershipService = new AccountMembershipService(); }
base.Initialize(requestContext);
}
PLEASE HELP!!! Thanks in advance!!
this error message suggests that your AccountController class isn't being derived from MVC's Controller base class.
It turns out what I was missing was a reference to System.Web.Routing. I had using System.Web.Routing, and it wasn't marked "red" (meaning, it couldn't find it), so I just assumed it was there. So this fixed the problem.