Here's the situation:
Legacy ASP.NET product. A lot of old ASMX services (among other types of endpoints - ASPX, ASHX, etc).
We're enhancing some security logic. Part of the changes dictate defining the application module to which each ASMX service belongs. We plan to use the custom attribute shown below for this purpose.
[AttributeUsage(AttributeTargets.Class)]
public class ModuleAssignmentAttribute : Attribute
{
public Module[] Modules { get; set; }
public ModuleAssignmentAttribute(params Module[] modules)
{
Modules = modules;
}
}
Below is a sample of how the module will be applied to an ASMX service.
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[ModuleAssignment(Module.ApplicationModuleA)]
public class SomeService : System.Web.Services.WebService
{
[WebMethod(true)]
public string GetValue()
{
return "Some Service Value";
}
}
The HTTP module below will be used to authorize access to the service.
public class MyAuthorizationModule : IHttpModule { public void Dispose() { //clean-up code here. }
public void Init(HttpApplication context)
{
context.PreRequestHandlerExecute += new EventHandler(OnAuthorizeRequest);
}
public void OnAuthorizeRequest(object sender, EventArgs e)
{
if (HttpContext.Current.Handler == null) return;
Attribute att = Attribute.GetCustomAttribute(HttpContext.Current.Handler.GetType(), typeof(ModuleAssignmentAttribute));
if (att != null)
{
Module[] modules = ((ModuleAssignmentAttribute)att).Modules;
// Simulate getting the user's active role ID
int roleId = 1;
// Simulate performing an authz check
AuthorizationAgent agent = new AuthorizationAgent();
bool authorized = agent.AuthorizeRequest(roleId, modules);
if (!authorized)
{
HttpContext.Current.Response.Clear();
HttpContext.Current.Response.StatusCode = 401;
HttpContext.Current.ApplicationInstance.CompleteRequest();
}
}
}
The problem is that, for ASMX web services, the following line of code from the HTTP module returns null (note that this works for ASPX pages).
Attribute att = Attribute.GetCustomAttribute(HttpContext.Current.Handler.GetType(), typeof(ModuleAssignmentAttribute));
The value of HttpContext.Current.Handler.GetType() in this situation is "System.Web.Script.Services.ScriptHandlerFactory+HandlerWrapperWithSession". That type is apparently unaware of the custom attribute defined on the ASMX service.
Any ideas on how to get the custom attribute from the ASMX service type in this scenario?
Here's a solution to the problem. Requires reflection. Ugly and fragile code - I wouldn't recommend using it if you don't have to. I'd be interested to know if I'm overlooking a more elegant way.
public void Init(HttpApplication context)
{
context.PreRequestHandlerExecute += new EventHandler(OnAuthorizeRequest);
}
public void OnAuthorizeRequest(object sender, EventArgs e)
{
if (HttpContext.Current.Handler == null) return;
Attribute att = null;
// ScriptHandlerFactory+HandlerWrapperWithSession is the type of handler for ASMX web service calls to web methods that use session.
// This class is internal, so need to do a string comparison here (is there another way?).
if (HttpContext.Current.Handler.GetType().ToString() == "System.Web.Script.Services.ScriptHandlerFactory+HandlerWrapperWithSession")
{
// HandlerWrapperWithSession has a protected field named "_originalHandler" that it inherits from HandlerWrapper.
FieldInfo originalHandlerField = HttpContext.Current.Handler.GetType().GetField("_originalHandler",BindingFlags.NonPublic | BindingFlags.Instance);
object originalHandler = originalHandlerField.GetValue(HttpContext.Current.Handler);
// The _originalHandler value is an instance of SyncSessionHandler.
// The inheritance tree for SyncSessionHandler is:
//
// WebServiceHandler
// ----> SyncSessionlessHandler
// ----> SyncSessionHandler
//
// We need to walk the tree up to the WebServiceHandler class.
bool exitLoop = false;
Type t = originalHandler.GetType();
while (t != null)
{
// WebServiceHandler is internal, so again another string comparison.
if (t.ToString() == "System.Web.Services.Protocols.WebServiceHandler")
{
// WebServiceHandler has a private field named protocol. This field has the type HttpGetServerProtocol.
FieldInfo protocolField = t.GetField("protocol", BindingFlags.NonPublic | BindingFlags.Instance);
object protocolValue = protocolField.GetValue(originalHandler);
// The inheritance tree for ServerProtocol is:
//
// HttpServerProtocol
// ----> HttpGetServerProtocol
//
// We need to walk the three up to the HttpServerProtocol class.
Type t2 = protocolValue.GetType();
while (t2 != null)
{
if (t2.ToString() == "System.Web.Services.Protocols.HttpServerProtocol")
{
// HttpServerProtocol has an internal property named MethodInfo. This property has the type LogicalMethodInfo.
PropertyInfo methodInfoProperty = t2.GetProperty("MethodInfo", BindingFlags.NonPublic | BindingFlags.Instance);
object methodInfoValue = methodInfoProperty.GetValue(protocolValue);
// The LogicalMethodInfo class has a DeclaringType property. This property stores the type of the ASMX service.
att = Attribute.GetCustomAttribute(((LogicalMethodInfo)methodInfoValue).DeclaringType, typeof(ModuleAssignmentAttribute));
exitLoop = true;
break;
}
t2 = t2.BaseType;
}
}
if (exitLoop) break;
t = t.BaseType;
}
}
else
{
att = Attribute.GetCustomAttribute(HttpContext.Current.Handler.GetType(), typeof(ModuleAssignmentAttribute));
}
if (att != null)
{
Module[] modules = ((ModuleAssignmentAttribute)att).Modules;
// Simulate getting the user's active role ID
int roleId = 1;
// Simulate performing an authz check
AuthorizationAgent agent = new AuthorizationAgent();
bool authorized = agent.AuthorizeRequest(roleId, modules);
if (!authorized)
{
HttpContext.Current.Response.Clear();
HttpContext.Current.Response.StatusCode = 401;
HttpContext.Current.ApplicationInstance.CompleteRequest();
}
}
}
I had to use Reflector + runtime debugging to find this solution.