Comment obtenir la valeur request digest de l'application hébergée par le fournisseur?

je développe SharePoint 2013 Provider hosted app en utilisant l'Api REST javascript. Pour effectuer des opérations de création (POST) ou de mise à jour (MERGE) sur des éléments sharepoint, je dois définir l'en-tête 'X-RequestDigest' avec la requête.

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

la première différence de l'application hébergée par le fournisseur est que nous devons maintenant faire une requête inter-domaines car nous ne sommes 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" }
})

je suppose que nous avons besoin d'utiliser le SP.RequestExecutor pour exécuter une requête de domaine croisée. Quand je construis la requête il ressemble à ce qui suit (j'ai changé les urls actuelles à quelque chose de faux, mais fondamentalement nous disons le mandataire pour utiliser le web hôte a la cible et obtient le /_api/contextinfo point de terminaison):

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. ce qui signifie que le paramètre n'existe pas.

je me suis assuré d'utiliser la méthode POST avec la bonne application/json;odata=verbose headers avec un corps vide.

Comment puis-je obtenir la valeur de digest de la requête à partir de /_api/contextinfo service à l'application hébergée par le fournisseur?

sur la Base de ce que j'ai recherché:

  • nous ne pouvons pas utiliser $('#__REQUESTDIGEST').val(); parce que ce n'est pas disponible pour une application hébergée par un fournisseur.
  • nous avons besoin d'utiliser une partie de la requête cross-domain puisque je suis en cours d'exécution en dehors de sharepoint.
  • j'ai essayé de définir la cible de la requête cross-domain à la fois à l'hostWebUrl et à l'appWebUrl et les deux donnent la même erreur.

il doit y avoir un moyen d'obtenir cette valeur, sinon nous serions limités à lire les opérations lors de L'utilisation de JavaScript. quelqu'un d'autre a-t-il résolu le problème en utilisant javascript?

techniquement je pourrais essayer d'implémenter les services nécessaires en utilisant le CSOM sur le serveur et les exposer en utilisant WebAPI ou WCF mais il semble déraisonnable d'avoir à 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 digest de la requête. Cela permet effectivement de récupérer un condensé de requête valeur; cependant, en essayant d'utiliser ceci 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 digest de la requête contient des informations d'en-tête de referer qui indiquent qu'elle a été demandée par le serveur; cependant, les futures requêtes faites avec elle proviennent du navigateur, et cette inadéquation pourrait être une raison acceptable pour qu'elle soit invalide.

quelques notes supplémentaires sur la tentative d'ajouter le contrôleur webAPI. J'ai basé mon code de cet exemple: http://code.msdn.microsoft.com/SharePoint-2013-Perform-335d925b mais converti pour utiliser le nouveau HttpClient. J'ai surchargé la méthode Page_Load, stocké le contextTokenString dans une variable qui pouvait être consultée par le contrôleur WebAPI, puis analysé/utilisé en demandant le contextinfo.

quelqu'un sait-il s'il s'agit d'un diagnostic correct de cette erreur? Y a - t-il quelque chose encodé dans la valeur request digest qui l'empêcherait de pouvoir être récupéré comme je l'ai suggéré?

j'ai également ouvert une question connexe sur les forums MSDN puisque je suis désespéré pour trouver une réponse: 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

je trouve très difficile de croire que cela pourrait être une limitation des applications hébergées par les fournisseurs, mais étant donné tous les tests que j'ai faits, je commence à douter de la viabilité des applications hébergées par les fournisseurs quand vous voulez écrire en javascript.

Mendier de l'Aide!

15
demandé sur Matt Mazzola 2014-03-04 03:16:58

4 réponses

je me rends compte 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 ont besoin d'accéder à L'API REST à partir d'un langage non basé dans le cadre .NET, (et qui ne peuvent 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 faire de la rétro-ingénierie comme suit:

Étape 1 - L'authentification

ne va Pas réellement ce qu'il y a beaucoup de exemples en ligne qui démontrent le plus d' méthodes communes. Microsoft.SharePoint.Client la plupart des bibliothèques semblent utiliser l'authentification basée sur les revendications lorsqu'elles travaillent avec SharePoint Online, Le token étant demandé par le biais de l'endpoint trouvé à: https://login.microsoftonline.com/RST2.srf

Étape 2-acquisition du Digest de demande (muet Approche)

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 gratter le HTML de la réponse. À partir de là, il avait juste être une question de l'extraction de l' value attribut pour votre digest.

Étape 2-acquisition de L'Abrégé de demande (approche SOAP)

les bibliothèques CSOM utilisent actuellement un SOAP endpoint lors de l'acquisition du request digest qu'il utilise pour ses appels API. Vous pouvez faire la même chose en faisant une demande SOAP à la $(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>

lorsqu'il est exécuté avec succès, le corps de réponse ressemblera à quelque chose comme:

<?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 moment, vous pouvez simplement extraire votre digest de la DigestValue bloc.

Étape 2-acquisition de L'Abrégé de la demande (Méthode du repos)

la dernière approche je suis connaît les utilisations D'une requête OData faite à la $(SPWebUrl)/_api/contextinfo point de terminaison:

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

{}

lorsqu'il est exécuté avec succès, le corps de réponse ressemblera à ce qui suit:

{
    "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 digest de requête peut alors être extrait du FormDigestValue propriété.

Étape 2-acquisition de L'Abrégé de demande (approche CSOM)

si vous utilisez CSOM, vous avez une fonctionnalité pour traiter avec ce intégré. (probablement JSOM, aussi, à moins qu'il n'utilise l'entrée _ _ REQUESTDIGEST) Microsoft.SharePoint.Client.ClientContext utilise l'approche SOAP en interne pour gérer son digest de requête et expose publiquement cette fonctionnalité par le biais de 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: While ClientContext maintient et réutilise un digest de formulaire en cache pour ses requêtes, cette méthode ne vous donne pas accès à cette valeur en cache. Au lieu de cela, cette méthode demande un digest de formulaire tout nouveau avec chaque appel, de sorte que vous voudrez configurer votre propre mécanisme de mise en cache afin de réutiliser les digest non expirés à travers de multiples demandes.

Étape 2-acquisition du dossier de demande (approche JSOM)

si vous utilisez L'API JSOM et que vous n'avez pas accès à un __REQUESTDIGEST valeur d'entrée, vous pouvez accéder à l' ClientContext's en cache digest les extensions suivantes. ( merci à bdimag pour souligner le cache)

Étape 3 - Acquisition De Nouvelles Requêtes

en supposant que vous utilisez le digest de requête avant le TimeoutSeconds se sont écoulés, une demande de repos valide faite 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 se traduire par 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 le X-RequestDigest en-tête de réponse vous permettra de l'utiliser dans un prochain appel. (Je suppose que le délai commence à partir du moment de votre nouvelle réponse + $(TimeoutSeconds) à partir de l'original digérer demande, mais je n'ai pas encore confirmer)

Malheureusement, le X-RequestDigest en-tête n'est retourné par le REPOS demandes qui requièrent une demande de digérer. Vous ne recevrez pas l'en-tête des requêtes pour lesquelles un récapitulatif de requête n'est pas requis, comme:$(SPWebUrl)/_api/web/lists/getByTitle('MyList')/items. Si vous avez besoin d'un nouveau digest après que l'original est terminé, vous aurez besoin de faire une autre demande à la $(SPWebUrl)/_vti_bin/sites.asmx service web.

Étape ??? - Erreurs De Manipulation

quelques exemples de réponses à partir de quand nos demandes l'échec:

la réponse suivante provient d'une demande de repos faite au $(SPWebUrl)/_api/contextinfo point de terminaison. (pas de cookies 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 de REST faite avec un digest de demande expiré (Notez le X-RequestDigest en-tête spécifié dans la réponse.. Vous ne savez pas si c'est utilisable, mais ça vaut le 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."
        }
    }
}
23
répondu Charles Grunwald 2017-05-23 12:26:20

mus Vous rappeler que, dans les autorisations de niveau existe pas de vérifier que désactiver tous les services sous _api

_api/web / listes _api/recherche/requête?querytext= ' SharePoint’ _api / SP.Userprofile.PeopleManager

vous activez qui assurent

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

intégration caractéristiques du client Utilisez l'interface à distance

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

1
répondu Lucaseto 2017-02-28 18:44:53

le Requesttexecutor prend en charge la commande Requestdig pour vous. Vous n'avez pas à obtenir.

si pour une raison quelconque, vous voulez toujours obtenir la valeur RequestDigest, essayez de faire l'appel sans changer le site de contexte.

0
répondu Gab Royer 2014-03-26 03:35:16

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

vous pouvez consulter le dépôt ici:

https://github.com/mattmazzola/providerhosted_01

après avoir comparé cette nouvelle application et l'ancienne, j'ai réalisé que j'avais un malentendu sur la façon dont le SP.Les urls attendues de RequestExecutor doivent être construites. J'ai pensé qu'il était nécessaire d'utiliser le SP.AppContextSite (point de terminaison.

je construisais incorrectement une demande à la appWeb avec une url semblable à la suivante:

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

comme vous pouvez le voir, @target a été défini à l'url appWeb mais infact lorsque vous faites une demande à l'appWeb en utilisant RequestExecutor vous n'avez pas besoin de faire cela. C'est tout simplement appweburl + "/_api / contextinfo". C'est seulement lorsque des demandes de ressources existantes sur hostWeb que vous avez besoin d'utiliser le AppContextSite et définissez @cible.

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

0
répondu Matt Mazzola 2014-03-29 23:32:11