web-dev-qa-db-fra.com

Comment obtenir la valeur de résumé de demande à partir de l'application hébergée par le fournisseur?

Je développe une application hébergée du fournisseur SharePoint 2013 à l'aide de javascript REST Api. Pour effectuer des opérations de création (POST) ou de mise à jour (MERGE) sur des éléments de point de partage, je dois définir le "X-RequestDigest" en-tête avec la demande.

Lorsque dans les applications hébergées par SharePoint, j'ai pu utiliser le service http://contoso.sharepoint.com/SharePointHostedApp/_api/contextinfo pour récupérer la valeur de résumé de la demande; cependant, j'ai du mal à obtenir cette valeur dans une application hébergée par un fournisseur.

La première différence de l'application hébergée par le fournisseur est que nous devons maintenant faire une demande interdomaine car nous ne fonctionnons pas sur un site sharepoint, mais dans un domaine différent hébergé sur un serveur différent. Pour être clair: au lieu de

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

J'ai supposé que nous devons utiliser le SP.RequestExecutor Pour exécuter une demande interdomaine. Lorsque je construis la demande, elle ressemble à ceci (j'ai changé les URL réelles en quelque chose de faux, mais en gros, nous disons au proxy d'utiliser le site Web hôte a la cible et d'obtenir le point de terminaison /_api/contextinfo):

https://contoso-6f921c6addc19f.sharepoint.com/ProviderHostedApp/_api/SP.AppContextSite(@target)/contextinfo?@target=%27https://contoso.sharepoint.com%27

Cependant, je reçois cette erreur: Cannot find resource for the request contextinfo. Signifiant que le point de terminaison n'existe pas.

Je me suis assuré d'utiliser la méthode POST avec les en-têtes application/json;odata=verbose Corrects avec un corps vide.

Comment puis-je obtenir la valeur de résumé de demande du service /_api/contextinfo À l'application hébergée par le fournisseur?

Sur la base de mes recherches:

  • Nous ne pouvons pas utiliser $('#__REQUESTDIGEST').val(); car cela n'est pas disponible pour une application hébergée par un fournisseur.
  • Nous devons utiliser une partie de la demande interdomaine car je cours en dehors de sharepoint.
  • J'ai essayé de définir la cible de la demande interdomaine à la fois hostWebUrl et appWebUrl et les deux donnent la même erreur.

Il doit y avoir un moyen d'obtenir cette valeur, sinon nous ne serions limités qu'aux opérations de lecture lors de l'utilisation de JavaScript. Quelqu'un d'autre a-t-il résolu cela en utilisant javascript?

Techniquement, je pourrais essayer d'implémenter les services nécessaires en utilisant le CSOM sur le serveur et de les exposer en utilisant WebAPI ou WCF, mais il semble déraisonnable de devoir implémenter cela.

MISE À JOUR:

Je suis allé de l'avant et j'ai essayé d'ajouter un contrôleur WebAPI qui expose un service qui récupère la valeur de résumé de la demande. Cela récupère en fait une valeur de résumé de demande; cependant, lorsque j'essaie de l'utiliser dans l'en-tête des appels futurs, je reçois l'erreur: "The security validation for this page is invalid and might be corrupted. Please use your web browser's Back button to try your operation again." Je suppose que la valeur de résumé de la demande contient des informations d'en-tête de référent qui indiquent qu'elle a été demandée par le serveur; cependant, les futures demandes qui en découlent proviennent du navigateur, et cette incompatibilité peut être une raison acceptable pour qu'elle ne soit pas valide.

Quelques notes supplémentaires sur la tentative d'ajout du contrôleur webAPI. J'ai basé mon code sur cet exemple: http://code.msdn.Microsoft.com/SharePoint-2013-Perform-335d925b mais je l'ai converti pour utiliser le nouveau HttpClient. J'ai surchargé la méthode▶Load, stocké le contextTokenString dans une variable accessible par le contrôleur WebAPI, puis analysé/utilisé lors de la demande de contextinfo.

Est-ce que quelqu'un sait si c'est un diagnostic correct de cette erreur? Y a-t-il quelque chose de codé dans la valeur de résumé de la demande qui l'empêcherait d'être récupéré comme je l'ai suggéré?

J'ai également ouvert une question connexe sur les forums MSDN car je cherche désespérément une réponse: http://social.msdn.Microsoft.com/Forums/sharepoint/en-US/f601fddd-3747-4152 -b2d1-4e89f0a771c4/question-sur-la-limitation-des-applications-hébergées-est-il-possible-de-faire-des-appels-avec-javascript? forum = sharepointdevelopmentprevious

Je trouve très difficile de croire que cela pourrait être une limitation des applications hébergées par le fournisseur, mais compte tenu de tous les tests que j'ai effectués, je commence à douter de la viabilité des applications hébergées par le fournisseur lorsque vous souhaitez écrire en javascript.

Demander de l'aide!

20
Matt Mazzola

Je sais que vous avez déjà répondu à votre propre question dans le contexte d'une application hébergée par un fournisseur, mais pour les développeurs comme moi qui doivent accéder à l'API REST à partir d'un langage non basé sur .NET (et qui ne peut pas écrire leur projet en tant qu'application Web) Je voudrais développer un peu plus le sujet. J'ai récemment été chargé d'écrire une application iPad qui nécessitait cette fonctionnalité, et j'ai fini par procéder à la rétro-ingénierie des éléments suivants:

Étape 1 - Authentification

Je ne vais pas vraiment couvrir cela, car il y a beaucoup de exemples en ligne qui démontrent le plus méthodes courantes . Les bibliothèques Microsoft.SharePoint.Client Semblent principalement utiliser l'authentification basée sur les revendications lorsque vous travaillez avec SharePoint Online, le jeton étant demandé via le point de terminaison trouvé à: https://login.microsoftonline.com/RST2.srf

Étape 2 - Acquisition du résumé des demandes (approche muette)

Si vous vous sentez paresseux, vous pouvez toujours prendre vos cookies authentifiés, faire une demande GET sur la page d'accueil du site Web cible et utiliser une expression régulière comme:

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

pour extraire le code HTML de la réponse. À partir de là, il suffirait d'extraire l'attribut value pour votre résumé.

Étape 2 - Acquisition du résumé des demandes (approche SOAP)

Les bibliothèques CSOM utilisent actuellement un point de terminaison SOAP lors de l'acquisition du condensé de demande qu'il utilise pour ses appels d'API. Vous pouvez faire de même en effectuant une demande SOAP au $(SPWebUrl)/_vti_bin/sites.asmx service Web similaire à ce qui suit:

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>

Une fois exécuté avec succès, le corps de la réponse ressemblera à:

<?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>

À ce stade, vous pouvez simplement extraire votre résumé de demande du bloc DigestValue.

Étape 2 - Acquisition du résumé de demande (approche REST)

La dernière approche que je connaisse utilise une requête OData adressée au point de terminaison $(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

{}

Une fois exécuté avec succès, le corps de la réponse ressemblera à ceci:

{
    "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)"
}

Le résumé de la demande peut ensuite être extrait de la propriété FormDigestValue.

Étape 2 - Acquisition du résumé des demandes (approche CSOM)

Si vous utilisez CSOM, vous disposez de fonctionnalités pour gérer cette fonction intégrée. (probablement JSOM aussi, sauf s'il utilise l'entrée __REQUESTDIGEST) Microsoft.SharePoint.Client.ClientContext utilise l'approche SOAP en interne pour gérer son résumé de demande et expose publiquement cette fonctionnalité via son GetFormDigestDirect méthode.

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

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

// Digest expiration
DateTime expirationDate = formDigest.Expiration;

Notes d'utilisation : Bien que ClientContext conserve et réutilise un résumé de formulaire mis en cache pour ses demandes, cette méthode ne vous donne pas accès à cette valeur mise en cache . Au lieu de cela, cette méthode demande un nouveau résumé de formulaire à chaque appel, vous voudrez donc configurer votre propre mécanisme de mise en cache afin de réutiliser les résumés non expirés sur plusieurs demandes.

Étape 2 - Acquisition du résumé de demande (approche JSOM)

Si vous utilisez l'API JSOM et n'avez pas accès à une valeur d'entrée __REQUESTDIGEST, Vous pouvez accéder au résumé mis en cache de ClientContext avec les extensions suivantes . ( Merci à bdimag pour avoir signalé le cache)

Étape 3 - Acquisition de nouveaux résumés de demande

En supposant que vous utilisez le résumé de la demande avant que le TimeoutSeconds ne soit écoulé, une demande valide REST effectuée comme suit:

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"
    }
}

devrait aboutir à une réponse réussie. Si vous inspectez les en-têtes de cette réponse, vous trouverez quelque chose comme:

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
...

Extraire l'en-tête de réponse X-RequestDigest Vous permettra de l'utiliser dans un appel ultérieur. (Je suppose que le délai recommence à partir de l'heure de votre nouvelle réponse + $(TimeoutSeconds) de la demande de résumé d'origine, mais je n'ai pas encore confirmé)

Malheureusement, l'en-tête X-RequestDigest Est uniquement renvoyé par REST demandes qui nécessitent réellement un résumé de demande. Vous ne recevrez pas l'en-tête des demandes pour lesquelles un résumé de demande n'est pas requis, par exemple: $(SPWebUrl)/_api/web/lists/getByTitle('MyList')/items. Si vous avez besoin d'un nouveau résumé après expiration du délai d'origine, vous devrez faire une autre demande au service Web $(SPWebUrl)/_vti_bin/sites.asmx.

Étape ??? - Gestion des erreurs

Quelques exemples de réponses lorsque nos demandes échouent:

La réponse suivante provient d'une demande REST faite au point de terminaison $(SPWebUrl)/_api/contextinfo. (Aucun cookie d'authentification spécifié)

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."
        }
    }
}

Ensuite, une réponse provenant d'une demande REST faite avec un résumé de demande expiré (Notez l'en-tête X-RequestDigest Spécifié dans la réponse .. Je ne sais pas si c'est utilisable, mais ça vaut le coup) coup):

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's Back button to try your operation again."
        }
    }
}
28
Charles Grunwald

Vous devez vous rappeler qu'au niveau des autorisations existe une vérification qui désactive tous les services sous _api

_api/web/lists _api/search/query? querytext = ’SharePoint’ _api/SP.UserProfiles.PeopleManager

Vous activez cette garantie

paramètres du site-> autorisations du site-> niveau des autorisations-> lecture->

Fonctionnalités du client d'intégration Utiliser l'interface distante

J'ai trouvé la solution dans https://letrasandnumeros.com/2017/02/28/unauthorizedaccessexception-sharepoint-_api/

1
Lucaseto

Le RequestExecutor prend en charge le RequestDigest pour vous. Vous n'avez pas besoin de l'obtenir.

Si pour une raison quelconque, vous souhaitez toujours obtenir la valeur RequestDigest, essayez de faire l'appel sans modifier le site de contexte.

0
Gab Royer

Ok, j'ai créé une nouvelle application hébergée par un fournisseur pour tester à nouveau le problème.

Vous pouvez consulter le référentiel ici:

https://github.com/mattmazzola/providerhosted_01

Après avoir comparé cette nouvelle application et l'ancienne, j'ai réalisé que j'avais une mauvaise compréhension de la façon dont le SP.RequestExecutor s'attendait à ce que les URL soient construites. Je pensais qu'il était nécessaire d'utiliser le point de terminaison SP.AppContextSite ().

Je construisais incorrectement une demande à l'appWeb avec une URL similaire à la suivante:

https://contoso-6f921c6addc19f.sharepoint.com/ProviderHostedApp/_api/SP.AppContextSite (@target) /contextinfo?@target=%27https%3A%2F%2Fcontoso-6f921c6addc19f.sharepoint.com%2FProv 27

Comme vous pouvez le voir, @target a été défini sur l'URL appWeb, mais en fait, lorsque vous faites une demande à l'appWeb à l'aide de RequestExecutor, vous n'avez pas besoin de le faire. C'est simplement appweburl + "/ _api/contextinfo". Ce n'est que lorsque vous effectuez des demandes de ressources existantes sur l'hôte Web que vous devez utiliser AppContextSite et définir le @target.

Vous pouvez voir le code complet dans la solution liée pour plus de détails. J'ai ajouté une capture d'écran de la solution. enter image description here

0
Matt Mazzola