Custom web service in SharePoint 2013 with impersonation
Asked Answered
A

1

6

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

Adeline answered 24/10, 2014 at 10:51 Comment(0)
A
0

If you want to use REST, why don't you simply create an app for this? This is way easier than exposing a WCF service in SharePoint. You can then configure your own security settings.

This article should help you with this.

Allometry answered 25/11, 2014 at 19:5 Comment(1)
Thank you for the hint. I made a mistake describing it as REST service. It is a general web service. We do not want to redesign the entire solution as it is pretty complex and we would like to keep the SharePoint 2010 and 2013 solutions as much alike as possible.Adeline

© 2022 - 2024 — McMap. All rights reserved.