Подтвердить что ты не робот

Как получить значение дайджеста запроса от принимающего поставщика приложения?

Я разрабатываю приложение Host Provider SharePoint 2013, используя javascript REST Api. Чтобы выполнять операции создания (POST) или обновления (MERGE) для элементов sharepoint, мне нужно установить заголовок "X-RequestDigest" с запросом.

Когда в приложениях, поддерживающих SharePoint, я смог использовать службу http://contoso.sharepoint.com/SharePointHostedApp/_api/contextinfo для получения значения дайджеста запроса; тем не менее, у меня возникают проблемы с получением этого значения, когда в хост-приложении поставщика.

Первое отличие от размещенного провайдера заключается в том, что теперь нам нужно сделать кросс-доменный запрос, так как мы не работаем на сайте sharepoint, а в другом домене, размещенном на другом сервере. Чтобы быть ясным: вместо

$.ajax({
    url: appWebUrl + '/_api/contextinfo',
    method: "POST",
    headers: { "Accept": "application/json; odata=verbose" }
})

Я предположил, что нам нужно использовать SP.RequestExecutor для выполнения запроса на перекрестный домен. Когда я создаю запрос, он выглядит следующим образом (я изменил фактические URL-адреса на что-то фальшивое, но в основном мы говорим прокси, чтобы использовать хост-сеть с целью и получить конечную точку /_api/contextinfo):

https://contoso-6f921c6addc19f.sharepoint.com/ProviderHostedApp/_api/SP.AppContextSite(@target)/[email protected]=%27https://contoso.sharepoint.com%27

Однако я получаю эту ошибку: Cannot find resource for the request contextinfo. означает, что конечная точка не существует.

Я старался использовать метод POST с правильными заголовками application/json;odata=verbose с пустым телом.

Как получить значение дайджеста запроса из службы /_api/contextinfo для хостинга приложения поставщика?

Основываясь на том, что я исследовал:

  • Мы не можем использовать $('#__REQUESTDIGEST').val(); потому что это недоступно для размещенного провайдера приложения.
  • Нам нужно использовать некоторые из кросс-доменного запроса, так как я запускаю за пределами sharepoint.
  • Я попытался установить цель междоменного запроса как hostWebUrl, так и appWebUrl, и обе дают ту же ошибку.

Должен быть какой-то способ получить это значение, иначе мы будем ограничиваться только чтением операций при использовании JavaScript. Кто-нибудь еще решил это с помощью javascript?

Технически я мог бы попытаться внедрить необходимые службы, используя CSOM на сервере, и разоблачить их с помощью WebAPI или WCF, но, похоже, нецелесообразно выполнять это.

UPDATE:

Я пошел дальше и попробовал добавить контроллер WebAPI, который предоставляет службу, которая извлекает значение дайджеста запроса. Это фактически возвращает значение дайджеста запроса; однако при попытке использовать это в заголовке будущих вызовов я получаю сообщение об ошибке: "The security validation for this page is invalid and might be corrupted. Please use your web browser Back button to try your operation again." Я предполагаю, что значение дайджеста запроса содержит в нем некоторую информацию заголовка референта, которая указывает, что он был запрошен сервером; однако будущие запросы, сделанные с ним, принадлежат браузеру, и это несоответствие может быть приемлемой причиной для него недействительным.

Несколько заметок о попытке добавить контроллер webAPI. Я основывал свой код на этом примере: http://code.msdn.microsoft.com/SharePoint-2013-Perform-335d925b, но преобразовал его, чтобы использовать новый HttpClient. Я перегрузил метод Page_Load, сохранил contextTokenString в переменной, к которой мог получить доступ контроллер WebAPI, затем проанализировал/использовал его при запросе contextinfo.

Кто-нибудь знает, является ли это правильным диагнозом этой ошибки? Есть ли что-то закодированное в значении дайджеста запроса, которое предотвратило бы его получение, как я предположил?

Я также открыл связанный с этим вопрос на форумах MSDN, так как я отчаянно хочу найти ответ: http://social.msdn.microsoft.com/Forums/sharepoint/en-US/f601fddd-3747-4152-b2d1-4e89f0a771c4/question-about-limitation-of-providerhosted-apps-is-it-possible-to-make-rest-calls-with-javascript?forum=sharepointdevelopmentprevious

Мне очень сложно поверить, что это может быть ограничением поставщиков размещенных приложений, но, учитывая все проведенные тесты, я начинаю сомневаться в жизнеспособности приложений, размещенных в хостинге, когда вы хотите писать в javascript.

Прошу помощи!

4b9b3361

Ответ 1

Я понимаю, что вы уже ответили на свой вопрос в контексте приложения, поддерживающего провайдер, но для таких разработчиков, как я, которым необходимо получить доступ к REST API с языка, не основанного на .NET Framework (и кто не может напишите их проект как веб-приложение). Я хотел бы расширить эту тему немного больше. Мне было поручено написать приложение iPad недавно, требующее этой функции, и закончил обратное проектирование следующим образом:

Шаг 1 - Аутентификация

Не собираюсь на самом деле покрывать это, так как есть много примеры онлайн, которые демонстрируют более общие методы. Обычно библиотеки Microsoft.SharePoint.Client используют аутентификацию на основе утверждений при работе с SharePoint Online, при этом маркер запрашивается через конечную точку, найденную по адресу: https://login.microsoftonline.com/RST2.srf

Шаг 2 - Приобретение дайджест-запроса (немой подход)

Если вы чувствуете себя ленивым, вы всегда можете взять свои файлы cookie, прошедшие проверку подлинности, сделать запрос GET на домашнюю страницу целевой сети и использовать регулярное выражение, например:

/(<input (?:[^>]*?)name="?__REQUESTDIGEST"?(?:[^>]*?)\/>)/i

чтобы очистить HTML от ответа. Оттуда просто нужно извлечь атрибут value для вашего дайджеста.

Шаг 2 - Получение дайджеста запроса (подход SOAP)

Библиотеки CSOM в настоящее время используют конечную точку SOAP при приобретении дайджеста запроса, который он использует для своих вызовов API. Вы можете сделать то же самое, сделав запрос SOAP к веб-службе $(SPWebUrl)/_vti_bin/sites.asmx, как показано ниже:

POST $(SPWebUrl)/_vti_bin/sites.asmx HTTP/1.1
Content-Type: text/xml
SOAPAction: http://schemas.microsoft.com/sharepoint/soap/GetUpdatedFormDigestInformation
X-RequestForceAuthentication: true
Host: $(SPSiteHostname)
Expect: 100-continue
Accept-Encoding: gzip, deflate
Cookie: $(Authenticated Cookies - Either "FedAuth=...; rtFa=..." or "SPOIDCRL=...")
Content-Length: $(Whatever)

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
    <soap:Body>
        <GetUpdatedFormDigestInformation xmlns="http://schemas.microsoft.com/sharepoint/soap/" />
    </soap:Body>
</soap:Envelope>

При успешном выполнении тело ответа будет выглядеть примерно так:

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <soap:Body>
        <GetUpdatedFormDigestInformationResponse xmlns="http://schemas.microsoft.com/sharepoint/soap/">
            <GetUpdatedFormDigestInformationResult>
                <DigestValue>0x1122334455 ... FF,27 Jul 2015 03:06:54 -0000</DigestValue>
                <TimeoutSeconds>1800</TimeoutSeconds>
                <WebFullUrl>$(SPWebUrl)</WebFullUrl>
                <LibraryVersion>16.0.3208.1222</LibraryVersion>
                <SupportedSchemaVersions>14.0.0.0,15.0.0.0</SupportedSchemaVersions>
            </GetUpdatedFormDigestInformationResult>
        </GetUpdatedFormDigestInformationResponse>
    </soap:Body>
</soap:Envelope>

В этот момент вы можете просто извлечь свой дайджест запроса из блока DigestValue.

Шаг 2 - Приобретение дайджеста запроса (подход REST) ​​

Последний подход, о котором я знаю, использует запрос OData, сделанный в конечной точке $(SPWebUrl)/_api/contextinfo:

POST $(SPWebUrl)/_api/contextinfo HTTP/1.1
Host: $(SPSiteHostname)
DataServiceVersion: 3.0
Accept: application/json; odata=nometadata
Content-Type: application/json; odata=verbose
Cookie: $(Authenticated Cookies)
Content-Length: 2

{}

При успешном выполнении, тело ответа будет выглядеть следующим образом:

{
    "FormDigestTimeoutSeconds" : 1800,
    "FormDigestValue" : "0x1122334455 ... FF,27 Jul 2015 03:06:54 -0000",
    "LibraryVersion" : "16.0.4230.1217",
    "SiteFullUrl" : "$(SPSiteUrl)",
    "SupportedSchemaVersions" : ["14.0.0.0", "15.0.0.0"],
    "WebFullUrl" : "$(SPWebUrl)"
}

Затем может быть извлечен дайджест запроса из свойства FormDigestValue.

Шаг 2 - Приобретение дайджеста запросов (подход CSOM)

Если вы используете CSOM, у вас есть функциональность для работы с этим встроенным. (возможно, JSOM тоже, если он не использует вход __REQUESTDIGEST) Microsoft.SharePoint.Client.ClientContext использует SOAP-подход для управления сводок запросов и публично раскрывает эту функцию с помощью метода GetFormDigestDirect.

ClientContext clientContext = new ClientContext(webUrl);
// ...
FormDigestInfo formDigest = clientContext.GetFormDigestDirect();

// X-RequestDigest header value
string headerValue = formDigest.DigestValue;

// Digest expiration
DateTime expirationDate = formDigest.Expiration;

Замечания по использованию. Хотя ClientContext поддерживает и повторно использует кэшированный формуляр для своих запросов, этот метод не дает вам доступ к этому кешированному значению. Вместо этого этот метод запрашивает новый рецепт формы с каждым вызовом, поэтому вы захотите настроить свой собственный механизм кеширования, чтобы повторно использовать неиспользуемые дайджесты для нескольких запросов.

Шаг 2 - Приобретение дайджеста запроса (подход JSOM)

Если вы используете JSOM API и не имеете доступа к входному значению __REQUESTDIGEST, вы можете получить доступ к кэшированному дайджесту ClientContext с помощью следующие расширения. (Благодаря bdimag для указания кеша)

Шаг 3 - Получение новых дайджетов запросов

Предполагая, что вы используете дайджест запроса до истечения TimeoutSeconds, действительный запрос REST выполнен следующим образом:

POST $(SPWebUrl)/_api/web/lists/getByTitle('MyList')/getchanges HTTP/1.1
Host: $(SPSiteHostname)
DataServiceVersion: 3.0
Accept: application/json; odata=nometadata
Content-Type: application/json; odata=verbose
X-RequestDigest: $(Request Digest)
Cookie: $(Authenticated Cookies)
Content-Length: 140

{
    "query" : {
        "__metadata" : {
            "type" : "SP.ChangeQuery"
        },
        "Add" : "True",
        "Item" : "True",
        "Update" : "True"
    }
}

должен привести к успешному ответу. Если вы проверите заголовки этого ответа, вы найдете что-то вроде:

HTTP/1.1 200 OK
Cache-Control: private, max-age=0
Content-Type: application/json;odata=fullmetadata;streaming=true;charset=utf-8
...
X-RequestDigest: 0xAABBCC...00,03 Sep 2014 18:09:34 -0000
...

Извлечение заголовка ответа X-RequestDigest позволит вам использовать его в последующем вызове. (Я предполагаю, что таймаут начинается с момента вашего нового ответа + $(TimeoutSeconds) из исходного запроса дайджеста, но я еще не подтвердил)

К сожалению, заголовок X-RequestDigest возвращается только запросами REST, которые на самом деле требуют дайджест запроса. Вы не получите заголовок для запросов, в которых необработанный дайджест запроса, например: $(SPWebUrl)/_api/web/lists/getByTitle('MyList')/items. Если вам понадобится новый дайджест после истечения срока оригинала, вам нужно будет сделать еще один запрос в веб-службу $(SPWebUrl)/_vti_bin/sites.asmx.

Шаг??? - Обработка ошибок

Несколько примеров ответов, когда наши запросы терпят неудачу:

Следующий ответ приходит из запроса REST, сделанного в конечную точку $(SPWebUrl)/_api/contextinfo. (не указаны файлы cookie для проверки подлинности)

HTTP/1.1 403 Forbidden
Cache-Control: private, max-age=0
Content-Type: application/json;odata=nometadata;charset=utf-8
...
Server: Microsoft-IIS/8.5
X-SharePointHealthScore: 0
X-Forms_Based_Auth_Required: $(SPRootSiteUrl)/_forms/default.aspx?ReturnUrl=/_layouts/15/error.aspx&Source=%2f_vti_bin%2fclient.svc%2fcontextinfo
X-Forms_Based_Auth_Return_Url: $(SPRootSiteUrl)/_layouts/15/error.aspx
X-MSDAVEXT_Error: 917656; Access+denied.+Before+opening+files+in+this+location%2c+you+must+first+browse+to+the+web+site+and+select+the+option+to+login+automatically.
DATASERVICEVERSION: 3.0
X-AspNet-Version: 4.0.30319
X-IDCRL_AUTH_PARAMS_V1: IDCRL Type="BPOSIDCRL", EndPoint="$(SiteRelativeUrl)/_vti_bin/idcrl.svc/", RootDomain="sharepoint.com", Policy="MBI"
...
Date: Wed, 12 Aug 2015 02:27:35 GMT
Content-Length: 201

{
    "odata.error" : {
        "code" : "-2147024891, System.UnauthorizedAccessException",
        "message" : {
            "lang" : "en-US",
            "value" : "Access denied. You do not have permission to perform this action or access this resource."
        }
    }
}

Далее ответ, исходящий из запроса REST, сделанного с помощью дайджеста с истекшим запросом (обратите внимание на заголовок X-RequestDigest, указанный в ответе.. Не уверен, что это можно использовать, но это стоит того):

HTTP/1.1 403 FORBIDDEN
Cache-Control: private, max-age=0
Content-Type: application/json;odata=fullmetadata;charset=utf-8
...
Server: Microsoft-IIS/8.5
Set-Cookie: rtFa=$(RtfaAuthCookie)
Set-Cookie: FedAuth=$(FedAuth)
X-SharePointHealthScore: 0
X-RequestDigest: 0x19EFFF80617AB2E48B0A9FF0ABA1440B5301E7445F3859177771BF6A39C7E4A74643108D862505A2C99350B0EDB871EF3DDE960BB68060601268818027F04956,12 Aug 2015 02:39:22 -0000
DATASERVICEVERSION: 3.0
X-AspNet-Version: 4.0.30319
...
Date: Wed, 12 Aug 2015 02:39:22 GMT
Content-Length: 253

{
    "odata.error" : {
        "code" : "-2130575251, Microsoft.SharePoint.SPException",
        "message" : {
            "lang" : "en-US",
            "value" : "The security validation for this page is invalid and might be corrupted. Please use your web browser Back button to try your operation again."
        }
    }
}

Ответ 2

Вы помните, что на уровне разрешений существует проверка, которая отключает все службы под _api

_api/веб/списки _api/поиск/запрос? querytext = SharePoint _api/SP.UserProfiles.PeopleManager

Включите, чтобы обеспечить

настройки сайта- > разрешения сайта- > уровень разрешений- > чтение →

Функции клиента интеграции Использовать удаленный интерфейс

Я нашел решение в https://letrasandnumeros.com/2017/02/28/unauthorizedaccessexception-sharepoint-_api/

Ответ 3

RequestExecutor действительно заботится о RequestDigest для вас. Вам не обязательно это делать.

Если по какой-то причине вы все равно хотите получить значение RequestDigest, попробуйте выполнить вызов без изменения контекстного сайта.

Ответ 4

Хорошо, я сделал новое приложение, поддерживающее хостинг, для повторной проверки проблемы.

Здесь вы можете просмотреть репозиторий:

https://github.com/mattmazzola/providerhosted_01

После сравнения этого нового приложения и старого, я понял, что у меня возникло непонимание того, как SP.RequestExecutor ожидает, что URL-адреса будут построены. Я думал, что необходимо использовать конечную точку SP.AppContextSite().

Я неправильно создавал запрос в appWeb с URL-адресом, подобным следующему:

https://contoso-6f921c6addc19f.sharepoint.com/ProviderHostedApp/_api/SP.AppContextSite(@target)/[email protected]=%27https%3A%2F%2Fcontoso-6f921c6addc19f.sharepoint.com%2FProviderHostedApp%27

Как вы можете видеть, @target был настроен на URL-адрес приложенияWeb, но при подаче запроса в приложениеWeb с помощью RequestExecutor вам не нужно это делать. Это просто appweburl + "/_api/contextinfo". Только при запросе ресурсов, существующих на хост-сервере, вам необходимо использовать AppContextSite и установить @target.

Подробнее см. полный код в связанном решении. Я добавил скриншот решения. enter image description here