【问题标题】:OneDrive for Business Javascript access with NodeJS no response使用 NodeJS 的 OneDrive for Business Javascript 访问无响应
【发布时间】:2014-10-22 04:18:06
【问题描述】:

我有一个 nodejs 应用服务器试图访问我的组织 onedrive 上的文件。该应用程序已在 Azure 中注册,我可以进行图形 api 调用并返回结果。当我尝试调用 onedrive api 时它挂起而没有响应,api 是:

https://-my.sharepoint.com/personal//_api/file

我使用的资源 ID 是“https://-my.sharepoint.com”。我也试过 Microsoft.Sharepoint

我正在像这样(在标题中)传递 oauth 令牌:

  'Authorization': 'Bearer ' + aToken,
  'Accept': 'application/json;odata=minimalmetadata;charset=utf-8'

我还尝试在资源 ID (http://office.microsoft.com/sharepoint/) 中为 Office 365 在线共享点添加 Azure 中的 URL,但这会返回未为帐户注册的错误资源。


在 chrome 请求标头上使用 REST 工具,如下所示:

接受:应用程序/json 授权:承载 eyJ0eXAiOiJKV1QiLCJhbGci... 连接:保持活动 内容类型:应用程序/xml 来源:chrome-extension://rest-console-id 用户代理:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.104 Safari/537.36

响应标头:

Status Code: 200
Date: Mon, 20 Oct 2014 18:53:55 GMT
Content-Encoding: gzip
Vary: Accept-Encoding
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Transfer-Encoding: chunked
P3P: CP="ALL IND DSP COR ADM CONo CUR CUSo IVAo IVDo PSA PSD TAI TELo OUR SAMo CNT COM INT NAV ONL PHY PRE PUR UNI"
X-SharePointHealthScore: 0
X-SP-SERVERSTATE: ReadOnly=0
request-id: 5611c49c-b0b2-1000-8ca2-5acda39588a3
MicrosoftSharePointTeamServices: 16.0.0.3312
X-MS-InvokeApp: 1; RequireReadOnly
Last-Modified: Mon, 20 Oct 2014 18:53:54 GMT
Server: Microsoft-IIS/7.5
SPRequestGuid: 5611c49c-b0b2-1000-8ca2-5acda39588a3
X-FRAME-OPTIONS: SAMEORIGIN
Content-Type: application/json;odata=minimalmetadata;streaming=true;charset=utf-8
Cache-Control: private, max-age=0
SPClientServiceRequestDuration: 1642
X-Content-Type-Options: nosniff
Expires: Sun, 05 Oct 2014 18:53:54 GMT

Request: 
Request Url: https://xxxx-my.sharepoint.com/personal/satish_ramjee_xxxxx_co_uk/_api/files
Request Method: GET
Status Code: 200
Params: {}
这将返回预期的 JSON 对象

相关代码如下

'use strict';

var express = require('express');
var request = require('request');
var http = require('http');
var path = require('path');
var passport = require('passport');
var AzureAdOAuth2Strategy = require('passport-azure-ad-oauth2');
var engine = require('ejs-locals');
var app = express();

var config = {
   // Enter the App ID URI of your application. To find this value in the Windows Azure Management Portal,
  // click Active Directory, click Integrated Apps, click your app, and click Configure.
  // The App ID URI is at the bottom of the page in the Single Sign-On section.
  realm: 'http://localhost:4000',

    
  // Enter the endpoint to which your app sends sign-on and sign-out requests when using WS-Federation protocol.
  // To find this value in the Windows Azure Management Portal, click Active Directory, click Integrated Apps,
  // and in the black menu bar at the bottom of the page, click View endpoints.
  // Then, copy the value of the WS-Federation Sign-On Endpoint.
  // Note: This field is ignored if you specify an identityMetadata url
  identityProviderUrl: 'https://login.windows.net/8b87af7d-8647-xxxxxxx/wsfed',

  // Enter the logout url of your application. The user will be redirected to this endpoint after
  // the auth token has been revoked by the WSFed endpoint.
  logoutUrl: 'http:/localhost:4000/',

  // Enter the URL of the federation metadata document for your app or the cert of the X.509 certificate found
  // in the X509Certificate tag of the RoleDescriptor with xsi:type="fed:SecurityTokenServiceType" in the federation metadata.
  // If you enter both fields, the metadata takes precedence
  identityMetadata: 'https://login.windows.net/8b87af7d-8647-4dc7-xxxxxxxx/federationmetadata/2007-06/federationmetadata.xml'
};


var graphConfig = {
// Enter the domain for your Active directory subscription, such as contoso.onmicrosoft.com
  tenant: '8b87af7d-8647-4dc7-xxxxxxxxxxxxxxx', 
  // Enter the Client ID GUID of your app.
  // In the Windows Azure Management Portal, click Active Directory, click your tenant,
  // click Integrated Apps, click your app, and click Configure.
  // The Client ID is on this app configuration page.
  clientid: '2462ee60-5695-xxxxxxxxxxxxxx',

  //Enter the value of the key for the app. You can create the key on the Configure page for the app.
  // The value appears only when you first save the key. Enter the saved value.
  clientsecret: 'xxxxxxxxxxxxxxxx'
};

// array to hold logged in users
var users = [];

// AAD Graph Client for AAD queries
var graphClient = null;
var aToken;

// use ejs-locals for all ejs templates:
app.engine('ejs', engine);

app.configure(function(){
  app.set('port', process.env.PORT || 4000);
  app.set('views',__dirname + '/views');
  app.set('view engine', 'ejs');
  app.use(express.favicon());
  app.use(express.logger('dev'));
  app.use(express.bodyParser());
  app.use(express.methodOverride());
  app.use(express.cookieParser('your secret here'));
  app.use(express.session({ secret: 'keyboard cat' }));
  app.use(passport.initialize());
  app.use(passport.session());
  app.use(app.router);
  app.use(express.static(path.join(__dirname, 'public')));
});

app.configure('development', function(){
  app.use(express.errorHandler());
});

var findByEmail = function (email, fn) {
  for (var i = 0, len = users.length; i < len; i++) {
    var user = users[i];
    if (user.email === email) {
      return fn(null, user);
    }
  }
  return fn(null, null);
};


// Simple route middleware to ensure user is authenticated.
//   Use this route middleware on any resource that needs to be protected.  If
//   the request is authenticated (typically via a persistent login session),
//   the request will proceed.  Otherwise, the user will be redirected to the
//   login page.
var ensureAuthenticated = function(req, res, next) {
  if (req.isAuthenticated()) {
    return next();
  }
  res.redirect('/login');
};

//var RESOURCE = "https://graph.windows.net"; 
//var REST_CALL = 'https://graph.windows.net/' + graphConfig.tenant + '/Users()';
var RESOURCE = "https://xxxx-my.sharepoint.com";
var REST_CALL = "https://xxxx-my.sharepoint.com/personal/satish_ramjee_xxxxxx_co_uk/_api/files";

//passport.use(wsfedStrategy);
passport.use(new AzureAdOAuth2Strategy ({
    clientID: graphConfig.clientid,
    clientSecret: graphConfig.clientsecret,
    tenant: graphConfig.tenant,
    resource: RESOURCE,
    callbackURL: "http://localhost:4000/callback",
  },
  function(accessToken, refreshToken, params, profile, done) {
        console.log("access token ---> " + accessToken);
        aToken = accessToken;
        console.log("done ---> " + JSON.stringify(done));
    
        var waadProfile = profile || jwt.decode(params.id_token, '', true);
        console.log("waad ---> " + JSON.stringify(waadProfile));
    
//        _res.redirect("/ok");
//        User.findOrCreate({ id: waadProfile.upn }, function (err, user) {
          return done();
//        });
}));

var sp_files = function(callback) {
  var headers = {
      'Authorization': 'Bearer ' + aToken,
      'Accept': 'application/json',
  };

 if (RESOURCE.indexOf("graph") != -1) {
     headers[ 'x-ms-dirapi-data-contract-version'] = '0.5';
 }
    
    console.log("CALL___________________ "+REST_CALL);
  request({
    url: REST_CALL,
//    qs: qs, 
    headers: headers
  }, function(err, resp, body) {
     console.log("Body " + body);
      console.log("Err " + err);
//    if (err) return callback(err, null);
        
    if (resp && resp.statusCode != 200) {
      return callback(new Error(body), null);
    }
    else if (!resp) {
        return callback(null, null);
    }

//    { results: 
//   [ { __metadata: [Object],
//       Manager: [Object],
//       DirectReports: [Object],

    var d = JSON.parse(body).d,
      users = d.results;
//      meta = buildMetadata(d);
      console.log("users" + users);
      return callback(users);
  })
 
  console.log("Sent request---->");
}

http.createServer(app).listen(app.get('port'), function(){
  console.log("Express server listening on port " + app.get('port'));
});

var graphQuery = function(res, user) {
  graphClient.getUsers(function(err, result) {
    if(err) {
      res.end('GraphClient.getUsers error:' + err + '\n');
    } else {
    
        console.log("User " + JSON.stringify(result) );
        
      //res.render('index', { user: user, data: JSON.stringify(result) });
    }
    // get user properties (user.DisplayName, user.Mail, etc.)
  });
};

var doWaad = function(res, user) {
  if(graphClient === null) {
    waad.getGraphClientWithClientCredentials2(graphConfig.tenant, graphConfig.clientid, graphConfig.clientsecret, function(err, client) {
      if(err) {
        res.end('waad.getGraphClientWithClientCredentials2 error:' + err + '\n');
      } else {
        graphClient = client;
        graphQuery(res, user);
      }
    });
  } else {
    graphQuery(res, user);
  }
};

app.get('/cb', function(req, res) {
    console.log("cb");
});

app.get('/fail', function(req, res) {
    console.log("fail");
});
app.get('/ok', function(req, res) {
    console.log("*************** ok ", req.user);
    //doWaad(res, req.user);
});




app.get('/', function(req, res){
  if (aToken) {
      console.log("T: " + aToken);
      sp_files(function(d) {

        var mail = [];
          if (d)
          for (var i=0; i< d.length; i++) {
              console.log(i + ">>>>  " + d[i]);
//              if (d[i].Mail)
//              mail.push(d[i].Mail);
              if (d[i])
              mail.push(JSON.stringify(d[i]));
          }
          res.render('result', { user: d, data: mail, oauth: aToken});
      });
      
  } else {
    res.render('index', { user: null});
  }
});

app.get('/account', ensureAuthenticated, function(req, res){
  res.render('account', { user:req.user });
});



app.get('/login',
  passport.authenticate('azure_ad_oauth2', { 
    failureRedirect: '/fail'
  })
);



app.get('/callback', 
    passport.authenticate('azure_ad_oauth2', { failureRedirect: '/', failureFlash: true }),
    function(req, res) {
      // Successful authentication, redirect home.
    console.log("================= callback");
      res.redirect('/ok');
    }
);


app.get('/logout', function(req, res){

// clear the passport session cookies
  req.logout();

// We need to redirect the user to the WSFED logout endpoint so the
// auth token will be revoked
  wsfedStrategy.logout({}, function(err, url) {
    if(err) {
      res.redirect('/');
    } else {
      res.redirect(url);
    }
  });
});

// Passport session setup.
//   To support persistent login sessions, Passport needs to be able to
//   serialize users into and deserialize users out of the session.  Typically,
//   this will be as simple as storing the user ID when serializing, and finding
//   the user by ID when deserializing.
passport.serializeUser(function(user, done) {
  done(null, user.email);
});

passport.deserializeUser(function(id, done) {
  findByEmail(id, function (err, user) {
    done(err, user);
  });
});

这是主要的 app.js 节点代码,它适用于图形 api,但不适用于 sharepoint api。一旦收到令牌,就会调用函数 sp_files 来发出 https 请求。 sharepoint 调用挂起而没有响应,虽然这是 javascript,但它仍然是服务器端,因此跨域问题在这里不应该是相关的。

【问题讨论】:

  • 顺便说一句,我使用的 RESTApi https://-my.sharepoint.com/personal/_co_uk/_api/files 在浏览器中工作正常
  • 您能否在此处复制并粘贴 Fiddler 跟踪,以确保标题肯定被推送?
  • 由于您可以从 Fiddler 调用这些 API,因此发布您用于调用 SPO 的代码也会有所帮助。

标签: javascript sharepoint azure sharepoint-2013 onedrive


【解决方案1】:

好的,解决了问题,sharepoint 在不支持 TLS 但支持 SSLv3 的 IIS 上运行。 NodeJS 默认使用 TLS,我将其更改为使用 SSLv3,现在它可以工作了。

但是,不幸的是,最近发现 SSLv3 容易受到 POODLE MITM 攻击。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-12-15
    • 2014-06-14
    • 1970-01-01
    • 2015-12-12
    • 2016-05-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多