0
votes

I have a nodejs app server attempting to access files on my organisational onedrive. The app is registered in Azure, I can make graph api calls and get back results. When I try to call the onedrive api it hangs without a response, the api is:

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

The resourceid I have used is "https://-my.sharepoint.com". I have also tried Microsoft.Sharepoint

I am passing the oauth token like so (in the headers):

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

I have also tried to add the URL in Azure for office 365 sharepoint online in the resoureid (http://office.microsoft.com/sharepoint/) but this comes back with the error resource not registered for the account.


Using a REST tool on chrome request headers like so:

Accept: application/json Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGci... Connection: keep-alive Content-Type: application/xml Origin: chrome-extension: //rest-console-id User-Agent: 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

Response headers:

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: {}
This returns the expected JSON object

The pertinent code is as follows

'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);
  });
});

This is the main app.js node code, which works with graph api but not with the sharepoint api. Once the token is received the function sp_files is called which makes an https request. The sharepoint call hangs with no response and although this is javascript it is nevertheless server side so cross domain issues should not be relevant here.

1
BTW the RESTApi I use https://<org>-my.sharepoint.com/personal/<user>_co_uk/_api/files works fine from the browerLightPhos
Can you copy and paste the Fiddler trace in here as well to make sure the header is definitely being pushed through?Jeremy Thake MSFT
Since you can call these APIs from Fiddler, it would also be helpful to post the code you are using to call SPO.Dorrene Brown

1 Answers

2
votes

Ok figured out the problem, sharepoint is running on IIS which does not support TLS, but supports SSLv3. NodeJS uses TLS by defualt, I changed it to use SSLv3 and it now works.

HOWEVER, unfortunately SSLv3 has recently been found to be vulnerable and susceptible to the POODLE MITM attack.