I am attempting to implement authentication for a REST service implemented in WCF and hosted on Azure. I am using HttpModule to handle the AuthenticationRequest, PostAuthenticationRequest and EndRequest events. If the Authorization header is missing or if the token contained therein is invalid, during EndRequest I am setting the StatusCode on the Response to 401. However, I have determined that EndRequest is called twice, and on the second call the response has already had headers set, causing the code which sets the StatusCode to throw an exception.
I added locks to Init() to ensure that the handler wasn't being registered twice; still ran twice. Init() also ran twice, indicating that two instances of the HttpModule were being created. However, using Set Object ID in the VS debugger seems to indicate that the requests are actually different requests. I've verified in Fiddler that there is only one request being issued to my service from the browser.
If I switch to using global.asax routing instead of depending on the WCF service host configuration, the handler is only called once and everything works fine.
If I add configuration to the system.web configuration section as well as the system.webServer configuration section in Web.config, the handler is only called once and everything works fine.
So I have mitigations, but I really dislike behavior I don't understand. Why does the handler get called twice?
Here is a minimal repro of the problem:
Web.config:
<system.web>
<compilation debug="true" targetFramework="4.0" />
<!--<httpModules>
<add name="AuthModule" type="TestWCFRole.AuthModule, TestWCFRole"/>
</httpModules>-->
</system.web>
<system.serviceModel>
<behaviors>
<endpointBehaviors>
<behavior name="WebBehavior">
<webHttp/>
</behavior>
</endpointBehaviors>
<serviceBehaviors>
<behavior>
<!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
<serviceMetadata httpGetEnabled="true" />
<!-- To receive exception details in faults for debugging purposes, set the value below to true. Set to false before deployment to avoid disclosing exception information -->
<serviceDebug includeExceptionDetailInFaults="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" aspNetCompatibilityEnabled="true" />
<services>
<service name="TestWCFRole.Service1">
<endpoint binding="webHttpBinding" name="RestEndpoint" contract="TestWCFRole.IService1" bindingConfiguration="HttpSecurityBinding" behaviorConfiguration="WebBehavior"/>
<host>
<baseAddresses>
<add baseAddress="http://localhost/" />
</baseAddresses>
</host>
</service>
</services>
<standardEndpoints>
<webHttpEndpoint>
<standardEndpoint name="" helpEnabled="true" automaticFormatSelectionEnabled="true"/>
</webHttpEndpoint>
</standardEndpoints>
<bindings>
<webHttpBinding>
<binding name="HttpSecurityBinding" >
<security mode="None" />
</binding>
</webHttpBinding>
</bindings>
</system.serviceModel>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true">
<add name="AuthModule" type="TestWCFRole.AuthModule, TestWCFRole"/>
</modules>
<directoryBrowse enabled="true"/>
</system.webServer>
Http module:
using System;
using System.Web;
namespace TestWCFRole
{
public class AuthModule : IHttpModule
{
/// <summary>
/// You will need to configure this module in the web.config file of your
/// web and register it with IIS before being able to use it. For more information
/// see the following link: http://go.microsoft.com/?linkid=8101007
/// </summary>
#region IHttpModule Members
public void Dispose()
{
//clean-up code here.
}
public void Init(HttpApplication context)
{
// Below is an example of how you can handle LogRequest event and provide
// custom logging implementation for it
context.EndRequest += new EventHandler(OnEndRequest);
}
#endregion
public void OnEndRequest(Object source, EventArgs e)
{
HttpContext.Current.Response.StatusCode = 401;
}
}
}
When an ASP.net application starts up, to maximize performance the ASP.NET Worker process will instantiate as many HttpApplication objects as it needs. Each HttpApplication object, will also instantiate one copy of each IHttpModule that is registered and call the Init method! That's really an internal design of the ASP.NET process running under IIS (or cassini which is VS built in webserver). Might be because your ASPX page has links to other resources which your browser will try to download, an external resource, and iframe, a css file, or maybe ASP.NET Worker Process behavior.
Luckily it's not the case for Global.asax:
Here's from MSDN:
The Application_Start and Application_End methods are special methods
that do not represent HttpApplication events. ASP.NET calls them once
for the lifetime of the application domain, not for each
HttpApplication instance.
However HTTPModule's init method is called once for every instance of the HttpApplication class after all modules have been created
The first time an ASP.NET page or process is requested in an
application, a new instance of HttpApplication is created. However, to
maximize performance, HttpApplication instances might be reused for
multiple requests.
And illustrated by the following diagram:
If you want code that's guaranteed to run just once, you can either use Application_Start of the Global.asax or set a flag and lock it in the underlying module which is don't think is a good practice for the sake of Authentication!
Sorry no clue to why it could be called twice, however EndRequest can end up being called for multiple reasons. request finished, request was aborted, some error happened. So i wouldn't put my trust in assuming that if you get there, you actually have a 401, it could be for other reasons.
I'd just keep my logic in the AuthenticateRequest pipeline:
public class AuthenticationModule : IHttpModule
{
public void Dispose() { }
public void Init(HttpApplication context)
{
context.AuthenticateRequest += Authenticate;
}
public static void Authenticate(object sender, EventArgs e)
{
// authentication logic here
//.............
if (authenticated) {
HttpContext.Current.User = new System.Security.Principal.GenericPrincipal(myUser, myRoles);
}
// failure logic here
//.............
}
}
Related
I created a simple workflow service with custom instance store(to support oracle).
The custom instance store, i follow the demo from MSDN: [http://msdn.microsoft.com/en-us/library/ee829481.aspx][1]
But when i invoke the service api, raise the exception:
A value of the wrong type was retrieved from the instance store. A
value of type {/Default Web Site/}OrderService.svc was expected, but
null was encountered instead.
I try to use SqlWorkflowInstanceStore, it's OK. No problem for service.
I use custom instance store again and debug, i found LoadWorkflowCommand be executed before SaveWorkflowCommand. I think it's an issue. Need your help!
The following is my code snippet:
1. Web Config:
<extensions>
<behaviorExtensions>
<add name="databaseWorkflowInstanceStore" type="Practices.Framework.Workflow.Configuration.DatabaseInstanceStoreElement, Practices.Framework" />
</behaviorExtensions>
</extensions>
<behaviors>
<serviceBehaviors>
<behavior>
<!--<sqlWorkflowInstanceStore connectionStringName="practicesDatabase" instanceCompletionAction="DeleteAll" instanceEncodingOption="GZip" />-->
<databaseWorkflowInstanceStore database="practicesDatabase" />
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
</behaviors>
2. DatabaseInstanceStore
public class DatabaseInstanceStore : InstanceStore
{
protected override IAsyncResult BeginTryCommand(InstancePersistenceContext context, InstancePersistenceCommand command, TimeSpan timeout, AsyncCallback callback, object state)
{
if (command is CreateWorkflowOwnerCommand)
{
var instanceOwnerId = Guid.NewGuid();
context.BindInstanceOwner(instanceOwnerId, instanceOwnerId);
}
else if (command is LoadWorkflowCommand)
{
var instanceData = this.LoadInstanceData();
context.LoadedInstance(InstanceState.Initialized, instanceData, null, null, null);
}
else if (command is SaveWorkflowCommand)
{
var saveCommand = (SaveWorkflowCommand)command;
var instanceData = saveCommand.InstanceData;
this.SaveInstanceData(instanceData);
}
return new CompletedAsyncResult<bool>(true, callback, state);
}
......
}
The MSDN article only shows the bare minimum of commands you need to implement. It sounds like you need to support more command so I would check which commands are dispatched that you don't support yet. See here for a list of commands.
The BeginTryCommand is called with the following commands executed in this order:
1. CreateWorkflowOwnerCommand
2. LoadWorkflowCommand
3. SaveWorkflowCommand
4. SaveWorkflowCommand
So for LoadWorkflowCommand:
I need Create Instance before Load
http://social.msdn.microsoft.com/Forums/en/dublin/thread/e51d7b18-1e27-4335-8ad0-4ce76b9f8b91
I am trying to implement Adyen recurring payment to my web application (C# .Net 4) but being relatively new to web services I am not sure I am doing it the right way.
In short the payment provider exposes a WSDL url for that purpose (https://pal-test.adyen.com/pal/Recurring.wsdl) and I added it in Visual Studio 2010 as a Service Reference (i.e. Add Service Reference > Advanced > Add Web Reference)
I then went on and created a test page, to make sure the connection was operational (see code below) and retrieve the details of a test subscription that I created previously. However I am getting an exception when executing the 'listRecurringDetails' action with the error message is 'Object reference not set to an instance of an object." and I cannot figure where I am going wrong.
Any feedback would be welcome.
#
public partial class Store_ServiceTest : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
Recurring proxy = new Recurring();
ICredentials usrCreds = new NetworkCredential("[username]", "[password]");
proxy.Credentials = usrCreds;
try
{
RecurringDetailsRequest thisUserDetail = new RecurringDetailsRequest();
thisUserDetail.merchantAccount = "[some reference]";
thisUserDetail.shopperReference = "[some reference]";
thisUserDetail.recurring.contract = "RECURRING";
RecurringDetailsResult recContractDetails = proxy.listRecurringDetails(thisUserDetail);
string createDate = recContractDetails.creationDate.ToString();
}
catch (Exception ex)
{
string err = ex.Message;
}
finally
{
proxy.Dispose();
}
}
}
Call Stack
App_Web_4h0noljo.dll!Store_ServiceTest.Page_Load(object sender, System.EventArgs e) Line 38 C#
Output window
A first chance exception of type 'System.Threading.ThreadAbortException' occurred in mscorlib.dll
An exception of type 'System.Threading.ThreadAbortException' occurred in mscorlib.dll but was not handled in user code
A first chance exception of type 'System.NullReferenceException' occurred in App_Web_4h0noljo.dll
The thread '' (0x15d0) has exited with code 0 (0x0).
Your code looks good. The key is to add the Recurring service as a
Service Reference instead of as a Web Reference. It should work if the
app configuration file contains:
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="AdyenHttpBinding">
<security mode="Transport">
<message clientCredentialType="UserName"/>
<transport clientCredentialType="Basic" realm="Adyen PAL Service Authentication"> <!--Adyen PAL Service Authentication-->
<extendedProtectionPolicy policyEnforcement="Never"/>
</transport>
</security>
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="https://pal-test.adyen.com/pal/servlet/soap/Payment" binding="basicHttpBinding" bindingConfiguration="AdyenHttpBinding" contract="Adyen.Payment.PaymentPortType" name="PaymentHttpPort"/>
<endpoint address="https://pal-test.adyen.com/pal/servlet/soap/Recurring" binding="basicHttpBinding" bindingConfiguration="AdyenHttpBinding" contract="Adyen.Recurring.RecurringPortType" name="RecurringHttpPort"/>
</client>
</system.serviceModel>
Kind Regards,
Sander Rasker (Adyen)
I have a WCF Service defined as:
[ServiceContract]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
public class Service
{
[OperationContract]
[WebGet(ResponseFormat = WebMessageFormat.Json)]
public string HelloWorld()
{
return "Hello World";
}
}
My Web.Config file:
<configuration>
<system.web>
<compilation debug="true" targetFramework="4.0"/>
</system.web>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="">
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="false"/>
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior name="webHttpBehavior">
<webHttp />
</behavior>
</endpointBehaviors>
</behaviors>
<bindings>
<webHttpBinding>
<binding name="webHttpBindingWithJsonP" crossDomainScriptAccessEnabled="true"/>
</webHttpBinding>
</bindings>
<services>
<service name="Service">
<endpoint address="" binding="webHttpBinding" bindingConfiguration="webHttpBindingWithJsonP" contract="Service" behaviorConfiguration="webHttpBehavior"/>
</service>
</services>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" aspNetCompatibilityEnabled="true"/>
</system.serviceModel>
</configuration>
I want to be able to access ASP .Net session variables in my WCF service, and I want the WCF service to be returning JSONP data, however even with this simple service, browsing to ../Service.svc/HelloWorld I am getting a 400 Bad Request error.
Can someone point me in the right direction?
Looks like the combination of JSONP, ASP.NET Compatibility and an authenticated user is not supported per this Microsoft forum.
According to the moderator of the forum, you need to disable one of the three.
Probably not the answer you were hoping for, but the moderator's explanation is pretty good and offers a few suggestions.
Hope this helps. Good luck!
I realise this has already been answered, but it's possible (though I'm unsure if recommended from a security perspective) to 'de-authenticate' a request early enough to pass the check being made by the webHttpBinding.
The gist is to set HttpContext.Current.User to be a new GenericPrincipal built on a GenericIdentity with no name or type mimicking what you'd see if an unauthenticated user had just hit your service - by the time the webHttpBinding performs its 'no authenticated JSONP calls' check the request is taking place in the context of an unauthenticated user.
Note: I'm unsure if there are security implications of this - one off the top of my head is that if you have an authenticated user their session state will still be available to your service which may be a bad thing, depending on what you're doing.
You can do this in a couple of places
By hooking the Application.AuthenticateRequest event, filtering by request URL
With a custom WCF message inspector
Example message inspector and behavior element (same class, very much use at own risk):
using System;
using System.Security.Principal;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Configuration;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.Threading;
using System.Web;
namespace MyNamespace
{
public class ForceAnonymousEndpointBehavior : BehaviorExtensionElement, IDispatchMessageInspector, IEndpointBehavior
{
public override Type BehaviorType
{
get { return typeof(ForceAnonymousEndpointBehavior); }
}
protected override object CreateBehavior()
{
return new ForceAnonymousEndpointBehavior();
}
object IDispatchMessageInspector.AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
HttpContext.Current.User = Thread.CurrentPrincipal = new GenericPrincipal(new GenericIdentity("", ""), null);
return null;
}
void IDispatchMessageInspector.BeforeSendReply(ref Message reply, object correlationState)
{
}
void IEndpointBehavior.ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
endpointDispatcher.DispatchRuntime.MessageInspectors.Add(new ForceAnonymousEndpointBehavior());
}
void IEndpointBehavior.AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
}
void IEndpointBehavior.ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
}
void IEndpointBehavior.Validate(ServiceEndpoint endpoint)
{
}
}
}
Then in web.config register the behavior extension (in the system.serviceModel element):
<extensions>
<behaviorExtensions>
<add name="ForceAnonymous" type="MyNamespace.ForceAnonymousEndpointBehavior, MyAssembly" />
</behaviorExtensions>
</extensions>
Adding the behavior to the endpointBehavior in question (again under system.serviceModel):
<behaviors>
<endpointBehaviors>
<behavior name="jsonpBehavior">
<ForceAnonymous />
</behavior>
</endpointBehaviors>
</behaviors>
...and making sure the endpoint behavior is called out in your service's endpoint declaration by setting the behaviorConfiguration attribute to match the behavior name you used above.
I am trying to create a data model to work with my GUI extension, I have created a simple service that returns a string. I have configured the model.config and added the following entry in my web.config
<services>
<service name="CMSExtensions.Model.Services.PublicationInfo" behaviorConfiguration="Tridion.Web.UI.ContentManager.WebServices.DeveloperBehavior">
<endpoint name="PublicationInfo" address="" behaviorConfiguration="Tridion.Web.UI.ContentManager.WebServices.AspNetAjaxBehavior" binding="webHttpBinding" bindingConfiguration="Tridion.Web.UI.ContentManager.WebServices.WebHttpBindingConfig" contract="CMSExtensions.Model.Services.PublicationInfo"/>
</service>
</services>
When I try to run this service directly in the browser I get the following error:
Parser Error Message: There is no service behavior named 'Tridion.Web.UI.ContentManager.WebServices.DeveloperBehavior'.
and when i try to invoke it via the JS in GUI I get this error:
Uncaught TypeError: Cannot call method 'GetPublicationData' of undefined
CMSExtensions.Popups.PublicationInfo._onExecuteButtonClickedPublicationInfo_v6.0.0.39607.0_.aspx:433
(anonymous function)PublicationInfo_v6.0.0.39607.0_.aspx:2
EventRegister.f.executeListenerPublicationInfo_v6.0.0.39607.0_.aspx:16
aPublicationInfo_v6.0.0.39607.0_.aspx:16
Tridion.ObjectWithEvents.processHandlersPublicationInfo_v6.0.0.39607.0_.aspx:14
Tridion.ObjectWithEvents.fireEventPublicationInfo_v6.0.0.39607.0_.aspx:14
Tridion.Controls.Button.onclickPublicationInfo_v6.0.0.39607.0_.aspx:428
Tridion.Controls.Button.onmouseupPublicationInfo_v6.0.0.39607.0_.aspx:428
(anonymous function)PublicationInfo_v6.0.0.39607.0_.aspx:2
EventRegister.f.executeListenerPublicationInfo_v6.0.0.39607.0_.aspx:16
a
I am using SDL Tridion 2011 (no SP1).
Here is the Service code
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;
using CMSExtensions.Model.Progress;
using Tridion.Web.UI.Models.TCM54;
namespace CMSExtensions.Model.Services
{
[ServiceContract(Namespace = "http://CMSExtensions.Model.Services", Name = "PublicationInfo")]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
public class PublicationInfo : WCFServiceBase
{
[OperationContract]
[WebInvoke(Method = "POST", RequestFormat = WebMessageFormat.Json,
ResponseFormat = WebMessageFormat.Json)]
public string GetUserDescription()
{
return "sachin";
}
}
}
Model Config:
<cfg:groups>
<cfg:group name="CMSExtensions.Model.Services" merger="Tridion.Web.UI.Core.Configuration.Resources.DomainModelProcessor" merge="always">
<cfg:domainmodel name="CMSExtensions.Model.Services">
<cfg:fileset>
<!-- <cfg:file type="script">/Scripts/Constants.js</cfg:file> -->
</cfg:fileset>
<cfg:services>
<cfg:service type="wcf">/Services/PublicationInfo.svc</cfg:service>
</cfg:services>
</cfg:domainmodel>
</cfg:group>
</cfg:groups>
Web.config Entries:
<serviceBehaviors>
<behavior>
<!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
<serviceMetadata httpGetEnabled="true"/>
<!-- To receive exception details in faults for debugging purposes, set the value below to true. Set to false before deployment to avoid disclosing exception information -->
<serviceDebug includeExceptionDetailInFaults="false"/>
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service name="CMSExtensions.Model.Services.PublicationInfo" behaviorConfiguration="Tridion.Web.UI.ContentManager.WebServices.DeveloperBehavior">
<endpoint name="PublicationInfo" address="" behaviorConfiguration="Tridion.Web.UI.ContentManager.WebServices.AspNetAjaxBehavior" binding="webHttpBinding" bindingConfiguration="Tridion.Web.UI.ContentManager.WebServices.WebHttpBindingConfig" contract="CMSExtensions.Model.Services.PublicationInfo"/>
</service>
</services>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
Tridion.Web.UI.ContentManager.WebServices.DeveloperBehavior is defined in the web.config file of the WebRoot directory. So most likely you are trying to run this in a different application (pool).
I suggest making sure that your model and editor are just virtual directories IIS and not applications.
If you correctly include your custom web service in your model's config file will generate a JavaScript proxy for calling your service. You can find the generated JavaScript code in the Default.aspx?mode=js file that you'll find in the debugging tools of your browser.
If the JavaScript proxy isn't in there, make sure that you've increased the Update number in your System.config. If you've done that and the proxy still doesn't show up, check the generated JavaScript and the event logs for error messages.
I have created a REST server under ASP.NET and I can't figure out the url to bring up the service. I am running under VS 2010 using it's built in web server. I believe it is actually running (VS 2010 starts up fine). But every combination I can think of for a url doesn't bring it up.
Update: Please take a look at the file http://www.windward.net/temp/RestUnderAspDotNet.zip - I have two solutions in there. The one at src\update runs fine as a REST server. I have pretty much the same code at inetpub\wwwroot\update and while it runs, I can't find a url that talks to it. I tried every variation of http://localhost:56469/update/App_Code/RestServiceImpl.svc/test I could think of and get either 403 or 404.
Any idea why? (I do not want any security on this - anyone will be able to hit it once it's up.)
App_Code\IRestServiceImpl.cs:
[ServiceContract]
public interface IRestServiceImpl
{
[OperationContract]
[WebInvoke(UriTemplate = "/version", Method = "POST")]
XmlElement GetVersion(XmlElement stats);
[OperationContract]
[WebInvoke(UriTemplate = "/test", Method = "GET")]
string GetTest();
}
App_Code\RestServiceImpl.svc:
<%# ServiceHost Language="C#" Debug="true" Service="RestServiceImpl" CodeBehind="RestServiceImpl.svc.cs" %>
App_Code\RestServiceImpl.cs:
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.Xml;
[AspNetCompatibilityRequirements
(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class RestServiceImpl : IRestServiceImpl
{
public XmlElement GetVersion(XmlElement stats)
{
XmlDocument doc = new XmlDocument();
XmlElement root = doc.CreateElement("update");
root.SetAttribute("version", "11.0.13.0");
doc.AppendChild(root);
return doc.DocumentElement;
}
public string GetTest()
{
return "update server is running";
}
}
Relevant part of web.config:
<system.web>
<compilation debug="true" targetFramework="4.0" />
</system.web>
<system.serviceModel>
<services>
<service name="RestServiceImpl">
<!-- Service Endpoints -->
<!-- Unless fully qualified, address is relative to base address supplied above -->
<endpoint address ="" binding="webHttpBinding" contract="IRestServiceImpl" behaviorConfiguration="webBinding">
</endpoint>
</service>
</services>
<behaviors>
<endpointBehaviors>
<behavior name="RestServiceImpl">
<webHttp/>
</behavior>
</endpointBehaviors>
</behaviors>
<bindings>
<webHttpBinding>
<binding name="webBinding">
</binding>
</webHttpBinding>
</bindings>
</system.serviceModel>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true"/>
</system.webServer>
Any idea what the url is to bring up the /test GET?
thanks - dave
You can try something like:
http://localhost:whateverportVSgivesyou/RestServiceImpl.svc/test
If the VS webserver is running you should see a system tray icon for it, and if you hover over it you'll see the port it's running on...
I finally figured this out. I have this also on my blog at Windward Wrocks with screenshots. Here's the solution w/o screenshots:
Install the WCF REST Service Template 40(CS) (requires .NET 4.0).
Create a WCF service. This is a New “Project…” not a “Web Site…”. And it is under the general “Visual C#” templates, not “WCF”!