For SharePoint 2010 we used custom web services (NOT SOAP!) to make some 3rd party data available to JS code in the pages displayed by the browser. This was sensitive data so we used impersonation to ensure that only the right users were able to access it. Our solution doesn't work any more in SharePoint 2013. As the original solution is pretty complex I built a small and simple service in SP 2013 to investigate how a web service with impersonation can be set up. The service is deployed to a subfolder of ISAPI.
This is the basis without impersonation, which works:
TestService.svc:
<%@ ServiceHost
Language="C#"
Debug="true"
Service="Sandbox.TestService, $SharePoint.Project.AssemblyFullName$"
CodeBehind="TestService.svc.cs"
Factory="Microsoft.SharePoint.Client.Services.MultipleBaseAddressWebServiceHostFactory, Microsoft.SharePoint.Client.ServerRuntime, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
The code behind in TestService.svc.cs is:
using Microsoft.SharePoint.Client.Services;
using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel.Activation;
using System.ServiceModel;
using System.ServiceModel.Web;
namespace Sandbox
{
[ServiceContract]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
public class TestService
{
[OperationContract]
[WebGet(UriTemplate = "GetAllNumbers",
ResponseFormat = WebMessageFormat.Json)]
List<int> GetAllNumbers()
{
List<int> result = new List<int>();
result.AddRange(new[] { 1, 1, 2, 3, 5, 8, 13 });
return result;
}
}
}
When I perform a GET on http://pc00175/_vti_bin/Sandbox/TestService.svc/GetAllNumbers
I receive the expected resonse [1,1,2,3,5,8,13]
. Fine so far. Now I try to use impersonation:
using Microsoft.SharePoint.Client.Services;
using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel.Activation;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Security.Principal;
namespace Sandbox
{
[ServiceContract]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
public class TestService
{
[OperationContract]
[WebGet(UriTemplate = "GetAllNumbers",
ResponseFormat = WebMessageFormat.Json)]
List<int> GetAllNumbers()
{
List<int> result = new List<int>();
WindowsImpersonationContext ctx = ServiceSecurityContext.Current.WindowsIdentity.Impersonate();
try
{
result.AddRange(new[] { 1, 1, 2, 3, 5, 8, 13 });
}
finally
{
ctx.Undo();
}
return result;
}
}
}
Now I get an System.InvalidOperationException with the message "An anonymous identity cannot perform an impersonation." when making the call to ServiceSecurityContext.Current.WindowsIdentity.Impersonate()
. I need to tell the WCF that we need impersonation for that call. So I added an attribute [OperationBehavior(Impersonation=ImpersonationOption.Required)]
:
using Microsoft.SharePoint.Client.Services;
using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel.Activation;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Security.Principal;
namespace Sandbox
{
[ServiceContract]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
public class TestService
{
[OperationContract]
[WebGet(UriTemplate = "GetAllNumbers",
ResponseFormat = WebMessageFormat.Json)]
[OperationBehavior(Impersonation=ImpersonationOption.Required)]
List<int> GetAllNumbers()
{
List<int> result = new List<int>();
WindowsImpersonationContext ctx = ServiceSecurityContext.Current.WindowsIdentity.Impersonate();
try
{
result.AddRange(new[] { 1, 1, 2, 3, 5, 8, 13 });
}
finally
{
ctx.Undo();
}
return result;
}
}
}
Now I find the following error in the SharePoint log:
Error when open web service: System.InvalidOperationException: The contract operation 'GetAllNumbers' requires Windows identity for automatic impersonation. A Windows identity that represents the caller is not provided by binding ('WebHttpBinding','http://tempuri.org/') for contract ('TestService','http://tempuri.org/'. at System.ServiceModel.Dispatcher.SecurityValidationBehavior.WindowsIdentitySupportRule.ValidateWindowsIdentityCapability(Binding binding, ContractDescription contract, OperationDescription operation) at System.ServiceModel.Dispatcher.SecurityValidationBehavior.WindowsIdentitySupportRule.Validate(ServiceDescription description) at System.ServiceModel.Dispatcher.SecurityValidationBehavior.System.ServiceModel.Description.IServiceBehavior.Validate(ServiceDescriptio...
Then I guessed that I had to add a web.config next to TestService.svc and add TransportCredentialsOnly mode but that didn't help:
<?xml version="1.0"?>
<configuration>
<system.serviceModel>
<bindings>
<webHttpBinding>
<binding>
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Ntlm"/>
</security>
</binding>
</webHttpBinding>
</bindings>
</system.serviceModel>
</configuration>
I get the same error in the SharePoint log file.
I hope somebody has a hint for me.
Thank you for reading this far!
Peter