【问题标题】:Can you use Microsoft Graph API in JavaScript to get list items?您可以在 JavaScript 中使用 Microsoft Graph API 来获取列表项吗?
【发布时间】:2019-10-11 11:51:08
【问题描述】:

我正在使用 MSAL 的示例并将它们转换为使用 MS Graph 来读取 SharePoint,但是在读取列表项时,我似乎遇到了权限问题。

为了确保我的语法正确,我将图形资源管理器与我的 AD 帐户一起使用,我能够读取列表项并确认 URI 是正确的。我还能够阅读并获得一系列列表。但是,一旦我尝试获取列表的列表项,则不会返回任何内容。

基本代码在这里https://docs.microsoft.com/en-us/azure/active-directory/develop/tutorial-v2-javascript-spa

这是我从示例转换的代码。如果您更新变量并在 Azure 中注册,您应该能够针对您的 SPO 站点运行。

<!DOCTYPE html>
<html>
<head>
    <title>Quickstart for MSAL JS</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/bluebird/3.3.4/bluebird.min.js"></script>
    <script src="https://secure.aadcdn.microsoftonline-p.com/lib/1.0.0/js/msal.js"></script>
</head>
<body>
    <h2>Welcome to MSAL.js Quickstart</h2><br />
    <h4 id="WelcomeMessage"></h4>
    <button id="SignIn" onclick="signIn()">Sign In</button><br /><br />
    <button id="btnAllLists" onclick="GetWithEndPoint()">Get All Lists</button><br /><br />
    <button id="btnListItems" onclick="GetWithEndPoint()">Get List Items</button><br /><br />
    <button id="btnListItemsAllFields" onclick="GetWithEndPoint()">Get List Items All Fields</button><br /><br />
    <pre id="json"></pre>
    <script>
        var config = {
            portalname: "yourportalname",
            sitename: "yoursitename",
            listid: "guidofalist"
        }
        var msalConfig = {
            auth: {
                clientId: "azureclientguid",
                authority: "https://login.microsoftonline.com/yourportal.onmicrosoft.com"
            },
            cache: {
                cacheLocation: "localStorage",
                storeAuthStateInCookie: true
            }
        };
        var graphConfig = {
            graphMeEndpoint: "https://graph.microsoft.com/v1.0/me",
            spShowAllListsEp: "https://graph.microsoft.com/v1.0/sites/" + config.portalname + ".sharepoint.com:/sites/" + config.sitename + ":/lists",
            spShowListItemsEp: "https://graph.microsoft.com/v1.0/sites/" + config.portalname + ".sharepoint.com:/sites/" + config.sitename + ":/lists/" + config.listid + "/items",
            spShowListItemsAllFieldsEp: "https://graph.microsoft.com/v1.0/sites/" + config.portalname + ".sharepoint.com:/sites/" + config.sitename + ":/lists/" + config.listid + "/items?expand=fields",
        };
        // this can be used for login or token request, however in more complex situations this can have diverging options
        var requestObj = {
            scopes: ["user.read"]
        };
        var myMSALObj = new Msal.UserAgentApplication(msalConfig);

        // Register Callbacks for redirect flow
        myMSALObj.handleRedirectCallback(authRedirectCallBack);

        function callMSGraph(theUrl, accessToken, callback) {
            var xmlHttp = new XMLHttpRequest();
            xmlHttp.onreadystatechange = function () {
                if (this.readyState == 4 && this.status == 200)
                    callback(JSON.parse(this.responseText));
            }
            xmlHttp.open("GET", theUrl, true); // true for asynchronous
            xmlHttp.setRequestHeader('Authorization', 'Bearer ' + accessToken);
            xmlHttp.send();
        }

        function signIn() {
            myMSALObj.loginPopup(requestObj).then(function (loginResponse) {
                //Login Success
                showWelcomeMessage();
                acquireTokenPopupAndCallMSGraph();
            }).catch(function (error) {
                console.log(error);
            });
        }

        function acquireTokenPopupAndCallMSGraph() {
            //Always start with acquireTokenSilent to obtain a token in the signed in user from cache
            myMSALObj.acquireTokenSilent(requestObj).then(function (tokenResponse) {
                callMSGraph(graphConfig.graphMeEndpoint, tokenResponse.accessToken, graphAPICallback);
            }).catch(function (error) {
                console.log(error);
                // Upon acquireTokenSilent failure (due to consent or interaction or login required ONLY)
                // Call acquireTokenPopup(popup window)
                if (requiresInteraction(error.errorCode)) {
                    myMSALObj.acquireTokenPopup(requestObj).then(function (tokenResponse) {
                        callMSGraph(graphConfig.graphMeEndpoint, tokenResponse.accessToken, graphAPICallback);
                    }).catch(function (error) {
                        console.log(error);
                    });
                }
            });
        }

        function graphAPICallback(data) {
            document.getElementById("json").innerHTML = JSON.stringify(data, null, 2);
        }

        function showWelcomeMessage() {
            var divWelcome = document.getElementById('WelcomeMessage');
            divWelcome.innerHTML = 'Welcome ' + myMSALObj.getAccount().userName + "to Microsoft Graph API";
            var loginbutton = document.getElementById('SignIn');
            loginbutton.innerHTML = 'Sign Out';
            loginbutton.setAttribute('onclick', 'signOut();');
            var btn1 = document.getElementById('btnAllLists');
            btn1.setAttribute('onclick', "GetWithEndPoint('" + graphConfig.spShowAllListsEp + "');");
            var btn2 = document.getElementById('btnListItems');
            btn2.setAttribute('onclick', "GetWithEndPoint('" + graphConfig.spShowListItemsEp + "');");
            var btn3 = document.getElementById('btnListItemsAllFields');
            btn3.setAttribute('onclick', "GetWithEndPoint('" + graphConfig.spShowListItemsAllFieldsEp + "');");
        }

        //This function can be removed if you do not need to support IE
        function acquireTokenRedirectAndCallMSGraph() {
            //Always start with acquireTokenSilent to obtain a token in the signed in user from cache
            myMSALObj.acquireTokenSilent(requestObj).then(function (tokenResponse) {
                callMSGraph(graphConfig.graphMeEndpoint, tokenResponse.accessToken, graphAPICallback);
            }).catch(function (error) {
                console.log(error);
                // Upon acquireTokenSilent failure (due to consent or interaction or login required ONLY)
                // Call acquireTokenRedirect
                if (requiresInteraction(error.errorCode)) {
                    myMSALObj.acquireTokenRedirect(requestObj);
                }
            });
        }

        function authRedirectCallBack(error, response) {
            if (error) {
                console.log(error);
            }
            else {
                if (response.tokenType === "access_token") {
                    callMSGraph(graphConfig.graphEndpoint, response.accessToken, graphAPICallback);
                } else {
                    console.log("token type is:" + response.tokenType);
                }
            }
        }

        function requiresInteraction(errorCode) {
            if (!errorCode || !errorCode.length) {
                return false;
            }
            return errorCode === "consent_required" ||
                errorCode === "interaction_required" ||
                errorCode === "login_required";
        }
        function signOut() {
            myMSALObj.logout();
        }

        // Browser check variables
        var ua = window.navigator.userAgent;
        var msie = ua.indexOf('MSIE ');
        var msie11 = ua.indexOf('Trident/');
        var msedge = ua.indexOf('Edge/');
        var isIE = msie > 0 || msie11 > 0;
        var isEdge = msedge > 0;
        //If you support IE, our recommendation is that you sign-in using Redirect APIs
        //If you as a developer are testing using Edge InPrivate mode, please add "isEdge" to the if check
        // can change this to default an experience outside browser use
        var loginType = isIE ? "REDIRECT" : "POPUP";

        if (loginType === 'POPUP') {
            if (myMSALObj.getAccount()) {// avoid duplicate code execution on page load in case of iframe and popup window.
                showWelcomeMessage();
                acquireTokenPopupAndCallMSGraph();
            }
        }
        else if (loginType === 'REDIRECT') {
            document.getElementById("SignIn").onclick = function () {
                myMSALObj.loginRedirect(requestObj);
            };
            if (myMSALObj.getAccount() && !myMSALObj.isCallback(window.location.hash)) {// avoid duplicate code execution on page load in case of iframe and popup window.
                showWelcomeMessage();
                acquireTokenRedirectAndCallMSGraph();
            }
        } else {
            console.error('Please set a valid login type');
        }
    </script>

    <script>
        function GetWithEndPoint(endpointString) {
            myMSALObj.acquireTokenSilent(requestObj).then(function (tokenResponse) {
                callMSGraph(endpointString, tokenResponse.accessToken, graphAPICallback);
            }).catch(function (error) {
                console.log(error);
                if (requiresInteraction(error.errorCode)) {
                    myMSALObj.acquireTokenPopup(requestObj).then(function (tokenResponse) {
                        callMSGraph(endpointString, tokenResponse.accessToken, graphAPICallback);
                    }).catch(function (error) {
                        console.log(error);
                    });
                }
            });
        }
    </script>

</body>
</html>

单击返回列表项的任一按钮都会引发此消息,我理解的意思是权限。

    {
      "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#Collection(microsoft.graph.list)('myid')/items",
      "value": []
    }

我的期望是我会从 Graph Explorer 中获得相同的结果。但这表明我没有权限。我尝试了几个不同的列表,结果总是一样的。我可以获得所有列表的列表。但尝试从列表中获取项目失败。

我们可以不使用带有 JS 的 Graph API 来获取列表项吗?

这里是来自 Azure 的 Azure 委派权限,我认为这应该是获取列表项所需的全部内容。

【问题讨论】:

    标签: javascript microsoft-graph-api sharepoint-online


    【解决方案1】:

    但这表明我没有权限

    没错,空结果通常表示Get Items endpoint 缺少以下权限之一(在您的情况下为委托权限):

    • Sites.Read.All - 读取所有网站集中的项目
    • Sites.ReadWrite.All - 编辑或删除所有网站集中的项目

    【讨论】:

    • 我 99% 确定我已阅读所有网站,但我会尝试获取屏幕打印以进行验证。我没有包括读/写认为不需要它。我想我还包括阅读认为列表项被视为文件项的文件,但我也会得到屏幕打印来验证这一点。
    • 我添加了 azure 权限的屏幕打印。我什至添加了读/写只是为了看看这是否有所作为并且仍然是空数组。我还通过浏览器窗口确认我确实可以访问。
    • @pretzelb,您能否确认访问令牌scp 声明包含Sites.Read.All 范围,例如可以验证here
    • 看起来你正在做某事。使用你给我的 URL 我看到了"scp": "User.Read profile openid email"。我认为这意味着即使我更新了 Azure 中的权限,我也不会将这些权限恢复到我的令牌中。在 Azure 门户中,当您更新 API 权限时,您会收到消息 Permissions have changed. Users and/or admins will have to consent even if they have already done so previously.,但不会要求我同意新权限。有没有办法强制检查同意?我会调查一下。
    • 帮我找到这个做得很好。我知道这一定是我忽略的一些简单的事情。在这种情况下,我开始使用的示例代码包含此配置var requestObj = {scopes: ["user.read"]};,它覆盖了我在门户中所做的一切。对我来说真是愚蠢的错误。一旦我将sites.read.all 添加到该变量中,它就起作用了。我也可以删除我打赌的那条线。我打算用最终代码发布一个单独的回复,但我只是投票给它作为解决方案。非常感谢。
    猜你喜欢
    • 2021-07-05
    • 1970-01-01
    • 1970-01-01
    • 2018-11-28
    • 2011-02-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-02-17
    相关资源
    最近更新 更多