Проброс пользовательского контекста в WCF MSPS

Вопросы, связанные с разработкой приложений для Microsoft Project

Проброс пользовательского контекста в WCF MSPS

Сообщение N1ght » 17 авг 2016, 08:39

Доброго времени суток, коллеги!
5й день бодаюсь с вопросом, который пока ни на йоту не подаётся.
Честно говоря, умумукался уже; вот посему я решил просить о помощи.
Задача: Требуется сформировать запрос на переназначение ресурса через WCF.
Контекст: Суммарно архитектура выглядит следующим образом. Пользователь в интерфейсе щелкает на контрол, добавленный через js, открывается модальное окно (по факту страничка aspx, в которой вся логика содержится), производится ряд операций в бекграунде, и в результате запрашивается стандартный алгоритм запроса на переназначение ресурса по задаче.
Входные данные: Имеется UID назначения, по которому необходимо сформировать запрос на переназначение, а также UID ресурса на кого это переназначение мы должны запросить у РП.
Работаем с сервисом Statusing.svc.
Последовательность вызовов методов предполагается следующая:
1. Дефайним DelegationDataSet.
2. Делаем вызов statusingClient.ReadAssignmentsForDelegation(statusingClient.DelegationFilterType.SingleAssignments, {GUID})
3. Задаем новый ресурс в поле newRes
4. Делаем вызов statusingClient.DelegateAssignments(), в который передаем отредактированный DelegationDataSet
5. Профит.
Суть проблемы: На втором шаге получаю в браузер 401 Unauthorized. Корнем которого служит ошибка 500 с описаловом MessageSecurityException: The HTTP request is unauthorized with client authentication scheme 'Ntlm'. The authentication header received from the server was 'NTLM'.
Код: Выделить всё
public partial class SomePage : LayoutsPageBase
  {
    protected static BasicHttpBinding binding;
    protected static backendStatusing.StatusingClient statusingClient;
    protected static Uri pwaUri = new Uri("http://sharepoint/PWA/");
    protected override void OnInit(EventArgs e)
    {
      Guid assnUID = Guid.Parse("11111111-1111-1111-1111-111111111111");
      base.OnInit(e);
      try
      {
        SetupStatusingWCFClient(pwaUri);
        SvcStatusing.DelegationDataSet dDS = new backendStatusing.DelegationDataSet();
        dDS = statusingClient.ReadAssignmentsForDelegation(backendStatusing.DelegationFilterType.SingleAssignment, assnUID);
      }
      catch (Exception)
      {
       
        throw;
      }
     
    }
    protected void Page_Load(object sender, EventArgs e)
    {
    }
    private static SvcStatusing.StatusingClient SetupStatusingWCFClient(Uri pwaUri)
    {
      const int MAXSIZE = 500000000;
      const string svcRouter = "_vti_bin/PSI/ProjectServer.svc";

      if (pwaUri.Scheme.Equals(Uri.UriSchemeHttps))
      {
        binding = new BasicHttpBinding(BasicHttpSecurityMode.Transport);
      }
      else
      {
        binding = new BasicHttpBinding(BasicHttpSecurityMode.TransportCredentialOnly);
      }

      binding.Name = "basicHttpConf";
      binding.SendTimeout = TimeSpan.MaxValue;
      binding.MaxReceivedMessageSize = MAXSIZE;
      binding.ReaderQuotas.MaxNameTableCharCount = MAXSIZE;
      binding.MessageEncoding = WSMessageEncoding.Text;
      binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Ntlm;

      EndpointAddress address = new EndpointAddress(pwaUri + svcRouter);

      if (statusingClient == null || statusingClient.State != CommunicationState.Opened)
      {
        statusingClient = new backendStatusing.StatusingClient(binding, address);
        statusingClient.ChannelFactory.Credentials.Windows.ClientCredential = System.Net.CredentialCache.DefaultNetworkCredentials;
        statusingClient.ChannelFactory.Credentials.Windows.AllowedImpersonationLevel = System.Security.Principal.TokenImpersonationLevel.Impersonation;
        statusingClient.ChannelFactory.Credentials.Windows.AllowNtlm = true;
      }
      return statusingClient;
    }


С параметром statusingClient.ChannelFactory.Credentials.Windows.ClientCredential игрался как мог. И указывал, и не указывал - все приводит к одному и тому же результату.
При попытке в дебагере посмотреть составляющую креденшелов CredentialCache.DefaultCredentials, CredentialCache.DefaultNetworkCredentials - объект пустой.
Изображение
Web-узел IIS с веб-сервисами настроен на аутентификации NTLM и Negotiate. Помимо этого включен Anonymous.
Выключение его ведет к потере функциональности в PWA. Пользователей бесконечно спрашивают о логине - пароле. В итоге они посмотреть данные по назначениям, проектам и утверждениям не могут.

Если я формирую объект ICredential куда хардкодом забиваю логин пароль и домен, а затем подпихиваю его в сервис - то для этой учетки все работает "like a charm".
По запросу к SPContext.Current.Web.CurrentUser.LoginName; - я получаю логин текущего пользователя, который сидит в своем браузере на своем рабочем месте. А вот имперсонировать его не получается.
Прошу помощи.

PS. Пробовал в аргументах метода statusingClient.ReadAssignmentsForDelegation() указывать как DelegationFilterType.SingleAssignment, так и DelegationFilterType.MyAssignments
N1ght
Белый пояс
 
Сообщения: 22
Зарегистрирован: 06 ноя 2014, 11:46
Откуда: Москва

Re: Проброс пользовательского контекста в WCF MSPS

Сообщение Brise » 18 авг 2016, 14:52

N1ght писал(а):Суть проблемы: На втором шаге получаю в браузер 401 Unauthorized. Корнем которого служит ошибка 500 с описаловом MessageSecurityException: The HTTP request is unauthorized with client authentication scheme 'Ntlm'. The authentication header received from the server was 'NTLM'.
Код: Выделить всё
public partial class SomePage : LayoutsPageBase
  {
    protected static BasicHttpBinding binding;
    protected static backendStatusing.StatusingClient statusingClient;
    protected static Uri pwaUri = new Uri("http://sharepoint/PWA/");
    protected override void OnInit(EventArgs e)
    {
      Guid assnUID = Guid.Parse("11111111-1111-1111-1111-111111111111");
      base.OnInit(e);
      try
      {
        SetupStatusingWCFClient(pwaUri);
        SvcStatusing.DelegationDataSet dDS = new backendStatusing.DelegationDataSet();
        dDS = statusingClient.ReadAssignmentsForDelegation(backendStatusing.DelegationFilterType.SingleAssignment, assnUID);
      }
      catch (Exception)
      {
       
        throw;
      }
     
    }
    protected void Page_Load(object sender, EventArgs e)
    {
    }
    private static SvcStatusing.StatusingClient SetupStatusingWCFClient(Uri pwaUri)
    {
      const int MAXSIZE = 500000000;
      const string svcRouter = "_vti_bin/PSI/ProjectServer.svc";

      if (pwaUri.Scheme.Equals(Uri.UriSchemeHttps))
      {
        binding = new BasicHttpBinding(BasicHttpSecurityMode.Transport);
      }
      else
      {
        binding = new BasicHttpBinding(BasicHttpSecurityMode.TransportCredentialOnly);
      }

      binding.Name = "basicHttpConf";
      binding.SendTimeout = TimeSpan.MaxValue;
      binding.MaxReceivedMessageSize = MAXSIZE;
      binding.ReaderQuotas.MaxNameTableCharCount = MAXSIZE;
      binding.MessageEncoding = WSMessageEncoding.Text;
      binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Ntlm;

      EndpointAddress address = new EndpointAddress(pwaUri + svcRouter);

      if (statusingClient == null || statusingClient.State != CommunicationState.Opened)
      {
        statusingClient = new backendStatusing.StatusingClient(binding, address);
        statusingClient.ChannelFactory.Credentials.Windows.ClientCredential = System.Net.CredentialCache.DefaultNetworkCredentials;
        statusingClient.ChannelFactory.Credentials.Windows.AllowedImpersonationLevel = System.Security.Principal.TokenImpersonationLevel.Impersonation;
        statusingClient.ChannelFactory.Credentials.Windows.AllowNtlm = true;
      }
      return statusingClient;
    }



Такой код работал бы без проблем в SharePoint 2010. Но внезапно выяснилось, что в SP 2013 поменяли аутентификацию с Windows на Claims, а PSI продолжил работать через Windows.
В 2013 году были бурные обсуждения на эту тему на форумах:
https://social.msdn.microsoft.com/Forum ... 10custprog
https://social.msdn.microsoft.com/Forum ... 10custprog

Что характерно, никто из Microsoft на вопросы не ответил, и инструкций не написал, партнеры боролись сами. Их опыт подытожили коллеги из FluentPro здесь:
http://www.projectserver2010blog.com/20 ... m-web.html

Чтобы твой код заработал, надо его дополнить в соответствии с подходом номер 3:
Код: Выделить всё
            // first convert claims login name to windows (this is of form DOMAIN\username)
            // then split DOMAIN\username by '\'
            Microsoft.SharePoint.Administration.Claims.SPClaimProviderManager mgr = Microsoft.SharePoint.Administration.Claims.SPClaimProviderManager.Local;
            string[] result = null;
            if (mgr != null) {
                string userName = mgr.DecodeClaim(SPContext.Current.Web.CurrentUser.LoginName).Value;
                result = userName.Split('\\');
            }

            // perform impersonation of your AppPool account (must has access to Claims2Windows Service)
            // give him UPN username (this is of form username@DOMAIN)
            WindowsIdentity windowsIdentity = null;
            using (WindowsIdentity.Impersonate(IntPtr.Zero)) {
                windowsIdentity = Microsoft.IdentityModel.WindowsTokenService.S4UClient.UpnLogon(result[1] + "@" + result[0]);
            }

            // perform impersonation of your user who is calling SharePoint
            // call PSI method on behalf of the calling user
            using (windowsIdentity.Impersonate()) {
                // здесь можно спокойно вызывать PSI
            }


Он использует клиент службы сервера приложений Claims2Windows Service, которая по-русски называется "Служба преобразования утверждений в маркеры безопасности Windows". Эту службу надо включить в центре администрирования.
Brise
Синий пояс
 
Сообщения: 145
Зарегистрирован: 01 апр 2013, 07:52
Откуда: Санкт-Петербург


Вернуться в Разработка и программирование для Microsoft Project

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 1