Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot make POST request #9

Open
philiphacks opened this issue May 9, 2012 · 7 comments
Open

Cannot make POST request #9

philiphacks opened this issue May 9, 2012 · 7 comments

Comments

@philiphacks
Copy link

When making a POST request (i.e. sharing something or trying to send a message), I get a 401 error:

error: [Messaging] error: { statusCode: 401,
data: '\n\n 401\n 1336572233851\n Q9YREZSZ2Z\n 0\n [unauthorized]. OAU:blablabla (token etc)=\n\n' }

When trying to do a GET request, everything goes fine (i.e. getting my connections). Here is the code for the POST request:

this.userManager.getLITokens(this.email, function (err, doc) {
            if (err) {
                log.error(util.inspect(err));
            } else if (doc != null) {
                var token = crypto.decrypt(doc.token);
                var secret = crypto.decrypt(doc.secret);
                log.debug('sending message: ' + message);
                linkedin.apiCall('POST', '/people/~/shares',
                    {
                        'token': {
                            'oauth_token': token,
                            'oauth_token_secret': secret
                        },
                        // 'mailbox-item': {
                        //     'recipients' : {
                        //         'values' :
                        //             [{
                        //                 'person' : {
                        //                     '_path' : '/people/~',
                        //                 }
                        //             }]
                        //     },
                        //     'subject' : subject,
                        //     'body' : message
                        // }
                        'share' : {
                            'comment' : message,
                            'visibility' : {code: 'anyone'}
                        }
                    }, 
                    function (error, result) {
                        if (error) {
                            log.error('[Messaging] error: ' + util.inspect(error));
                            callback(error, null);
                        } else {
                            callback(null, result);
                        }
                    }
                );
            } else {
                log.error('Something has gone wrong.');
            }
        });

I don't know if this is a real issue (i.e. bug) with the library or something else is wrong. The token and secret definitely work (get the same error when I hard-code them unencrypted). Someone who knows what's wrong?

@philiphacks
Copy link
Author

Anyone? I used the LinkedIn OAuth verification test console (https://developer.linkedin.com/oauth-test-console) to see if the signatures match with my POST request, but they don't... :/ I am sure all my keys, access tokens etc are correct. Using Apigee, I can successfully send a message, so maybe the OAuth library is bugged? Can someone look into this issue? :)

@tangrammer
Copy link

Hi @philipdesmedt,
i am having the same issue and it's getting me mad

have you fixed that?

thanks!

@philiphacks
Copy link
Author

Yes, I did fix it, but it's a (kind of ugly) hack. Let me quickly explain. I might do a pull request if I've got some time the next few days. Basically, the included OAuth library doesn't work as it should with POST requests.

So, I wanted to keep using this API wrapper and make POST requests as well. After a while, I noticed the OAuth library just did not work with POST requests, as it generated the wrong authorization 'string' (which is a combination of a consumer key, nonce, signature method, timestamp, token, version and signature). I implemented the authorization and signature method myself and passed this to the linkedin-js wrapper. I used the following code:

messageConstructor.prototype._getAuthorization = function(token, secret) {
    var oauth_header = 'OAuth ';
    oauth_header += 'oauth_consumer_key=app.linkedin.consumer,';

    var timestamp = OAuth.timestamp(),
    nonce = getNonce();

    oauth_header += 'oauth_nonce="' + nonce + '",';
    oauth_header += 'oauth_signature_method="HMAC-SHA1",';
    oauth_header += 'oauth_timestamp="' + timestamp + '",';
    oauth_header += 'oauth_token="' + token + '",';
    oauth_header += 'oauth_version="1.0",';
    oauth_header += 'oauth_signature="' + this._getSignature(token, secret, timestamp, nonce) + '"';
    return oauth_header;
};

messageConstructor.prototype._getSignature = function(token, secret, timestamp, nonce) {
    var signature_basestring_parameters = {
        oauth_version: '1.0',
        oauth_consumer_key: app.linkedin.consumer,
        oauth_timestamp: timestamp,
        oauth_nonce: nonce,
        oauth_token: token,
        oauth_signature_method: 'HMAC-SHA1'
    },
    signature_basestring = OAuth.SignatureMethod.getBaseString({
        method: 'POST',
        action: 'http://api.linkedin.com/v1/people/~/mailbox',
        parameters: signature_basestring_parameters
    });

    var signer = OAuth.SignatureMethod.newMethod('HMAC-SHA1', {
        consumerSecret: app.linkedin.consumer,
        tokenSecret: secret
    });
    var oauth_signature = signer.getSignature(signature_basestring); //signature validated using example from http://oauth.net/core/1.0a/#sig_base_example
    oauth_signature = OAuth.percentEncode(oauth_signature); //percent encoded signature validated using example from http

    log.insecure('_getSignature: ' + 'generated signature: ' + oauth_signature +
                ' nonce: ' + nonce + ' timestamp: ' + timestamp);
    return oauth_signature;
};

function getNonce() {
    return OAuth.nonce(19);
}

I used the above methods to generate the authorization and signature strings. The code to send the message through linkedin itself looked something like this:

messageConstructor.prototype.sendMessage = function (recipient_id, subject, message, callback) {
    f = ':sendMessage:';
    var self = this;
    log.finer(__filename+f+'Getting LITokens');
    this.userManager.getLITokens(this.email, function (err, doc) {
        if (err) {
            log.error(__filename+f+'Failed getting LITokens. Error=('+util.inspect(err, false, null));
        } else if (doc !== null) {
            log.finer(__filename+f+'Successfully received LITokens');
            log.finest(__filename+f+'Doc=('+util.inspect(doc, false, null)+')');
            var token = crypto.decrypt(doc.token);
            var secret = crypto.decrypt(doc.secret);
            log.insecure('token = ' + token);
            log.insecure('secret = ' + secret);
            var authorization = self._getAuthorization(token, secret);
            linkedin.apiCall('POST', '/people/~/mailbox',
                {
                    token: {
                        oauth_token: token,
                        oauth_token_secret: secret
                    },
                    authorization: authorization,
                    'mailbox-item': {
                        'recipients' : {
                            'values' :
                                [
                                {
                                    'person' : {
                                        '_path' : '/people/'+recipient_id
                                    }
                                }
                                ]
                        },
                        'subject' : subject,
                        'body' : message
                    }
                },
                function (error, result) {
                    if (error) {
                        log.error(__filename+f+'Failed sending message. error= ' + util.inspect(error, false, null));
                        callback(error);
                    } else {
                        log.finer(__filename+f+'Successfully sent message to id ' + recipient_id);
                        log.finest(__filename+f+'Result=('+util.inspect(result, false, null)+')');
                        callback(null, result);
                    }
                }
            );
        } else {
            log.error(__filename+f+'Failed getting LITokens. Doc is undefined');
        }
    });
}; 

In the above, the linkedin object is just an instance of this linkedin library. As you can see, I pass an HTTP method, a URL, parameters and callback. The parameters include the authorization string. Ok... almost there. Next I modified the linkedin-js library itself to intercept this authorization string and - if not null - use it instead of the authorization string it generates. Still following? 👍

In linkedin_client.js, starting from line 75:

else if (method.toUpperCase() === 'POST') {
  return CLIENT.oauth.post(
    _rest_base + path
  , token.oauth_token
  , token.oauth_token_secret
  , JSON.stringify(params['mailbox-item'])
  , 'application/json; charset=UTF-8'
  , requestCallback(callback)
  , authorization
  );
}

As you can see, here I caught the authorization string and I JSON stringified the mailbox-item immediately (normally params should already be a JSON object, I guess). Another reason I didn't do a pull request... my solution only worked for mailbox items now, since I hardcoded that. Oh well. Finally, in /linkedin-js/node_modules/oauth/lib/oauth.js, I changed quite a few methods:

exports.OAuth.prototype._putOrPost= function(method, url, oauth_token, oauth_token_secret, post_body, post_content_type, callback, authorization) {
  var extra_params= null;
  if( typeof post_content_type == "function" ) {
    callback= post_content_type;
    post_content_type= null;
  }
  if( typeof post_body != "string" ) {
    post_content_type= "application/x-www-form-urlencoded"
    extra_params= post_body;
    post_body= null;
  }
  return this._performSecureRequest( oauth_token, oauth_token_secret, method, url, extra_params, post_body, post_content_type, callback, authorization );
}

exports.OAuth.prototype.put= function(url, oauth_token, oauth_token_secret, post_body, post_content_type, callback) {
  return this._putOrPost("PUT", null, url, oauth_token, oauth_token_secret, post_body, post_content_type, callback, authorization);
}

exports.OAuth.prototype.post= function(url, oauth_token, oauth_token_secret, post_body, post_content_type, callback, authorization) {
  return this._putOrPost("POST", url, oauth_token, oauth_token_secret, post_body, post_content_type, callback, authorization);
}

As you can see, I added the authorization string as a parameter to these methods and changed each method call all the way up to _performSecureRequest, where I did an (authorization === undefined) check. If it was undefined, I just calculated it using _buildAuthorizationHeaders, else I used the one that I calculated myself and generated. I probably spend hours and hours looking at this and in the end I was quite desperate, so that's why I tried calculating my own authorization string. Still a wonder that this worked. I'm not proud of the solution and I should've changed it once I noticed that this worked, but I guess other functionality was more important at that time.

A better (read: no monkey patching) solution would have been to contact @masylum and replace the OAuth library itself, so POST requests work again. AFAIK, that is the solution. I'm not sure if this project is still under maintenance though. If you've got other questions, feel free to ask and I'll try to respond in a timely manner.

@tangrammer
Copy link

Thanks a lot!
you are very kind, i didn't expect such a great explanation
i'll try again with your great colaboration
thanks again and have a great day
Juan

2013/3/19 Philip De Smedt notifications@github.com

Yes, I did fix it, but it's a (kind of ugly) hack. Let me quickly explain.
I might do a pull request if I've got some time the next few days.
Basically, the included OAuth library doesn't work as it should with POST
requests.

So, I wanted to keep using this API wrapper and make POST requests as
well. After a while, I noticed the OAuth library just did not work with
POST requests, as it generated the wrong authorization 'string' (which is a
combination of a consumer key, nonce, signature method, timestamp, token,
version and signature). I implemented the authorization and signature
method myself and passed this to the linkedin-js wrapper. I used the
following code:

messageConstructor.prototype._getAuthorization = function(token, secret) {
var oauth_header = 'OAuth ';
oauth_header += 'oauth_consumer_key=app.linkedin.consumer,';

var timestamp = OAuth.timestamp(),
nonce = getNonce();

oauth_header += 'oauth_nonce="' + nonce + '",';
oauth_header += 'oauth_signature_method="HMAC-SHA1",';
oauth_header += 'oauth_timestamp="' + timestamp + '",';
oauth_header += 'oauth_token="' + token + '",';
oauth_header += 'oauth_version="1.0",';
oauth_header += 'oauth_signature="' + this._getSignature(token, secret, timestamp, nonce) + '"';
return oauth_header;

};

messageConstructor.prototype._getSignature = function(token, secret, timestamp, nonce) {
var signature_basestring_parameters = {
oauth_version: '1.0',
oauth_consumer_key: app.linkedin.consumer,
oauth_timestamp: timestamp,
oauth_nonce: nonce,
oauth_token: token,
oauth_signature_method: 'HMAC-SHA1'
},
signature_basestring = OAuth.SignatureMethod.getBaseString({
method: 'POST',
action: 'http://api.linkedin.com/v1/people/~/mailbox',
parameters: signature_basestring_parameters
});

var signer = OAuth.SignatureMethod.newMethod('HMAC-SHA1', {
    consumerSecret: app.linkedin.consumer,
    tokenSecret: secret
});
var oauth_signature = signer.getSignature(signature_basestring); //signature validated using example from http://oauth.net/core/1.0a/#sig_base_example
oauth_signature = OAuth.percentEncode(oauth_signature); //percent encoded signature validated using example from http

log.insecure('_getSignature: ' + 'generated signature: ' + oauth_signature +
            ' nonce: ' + nonce + ' timestamp: ' + timestamp);
return oauth_signature;

};

function getNonce() {
return OAuth.nonce(19);
}

I used the above methods to generate the authorization and signature
strings. The code to send the message through linkedin itself looked
something like this:

messageConstructor.prototype.sendMessage = function (recipient_id, subject, message, callback) {
f = ':sendMessage:';
var self = this;
log.finer(__filename+f+'Getting LITokens');
this.userManager.getLITokens(this.email, function (err, doc) {
if (err) {
log.error(__filename+f+'Failed getting LITokens. Error=('+util.inspect(err, false, null));
} else if (doc !== null) {
log.finer(__filename+f+'Successfully received LITokens');
log.finest(__filename+f+'Doc=('+util.inspect(doc, false, null)+')');
var token = crypto.decrypt(doc.token);
var secret = crypto.decrypt(doc.secret);
log.insecure('token = ' + token);
log.insecure('secret = ' + secret);
var authorization = self._getAuthorization(token, secret);
linkedin.apiCall('POST', '/people/~/mailbox',
{
token: {
oauth_token: token,
oauth_token_secret: secret
},
authorization: authorization,
'mailbox-item': {
'recipients' : {
'values' :
[
{
'person' : {
'_path' : '/people/'+recipient_id
}
}
]
},
'subject' : subject,
'body' : message
}
},
function (error, result) {
if (error) {
log.error(__filename+f+'Failed sending message. error= ' + util.inspect(error, false, null));
callback(error);
} else {
log.finer(__filename+f+'Successfully sent message to id ' + recipient_id);
log.finest(__filename+f+'Result=('+util.inspect(result, false, null)+')');
callback(null, result);
}
}
);
} else {
log.error(__filename+f+'Failed getting LITokens. Doc is undefined');
}
});
};

In the above, the linkedin object is just an instance of this linkedin
library. As you can see, I pass an HTTP method, a URL, parameters and
callback. The parameters include the authorization string. Ok... almost
there. Next I modified the linkedin-js library itself to intercept this
authorization string and - if not null - use it instead of the
authorization string it generates. Still following? [image: 👍]

In linkedin_client.js, starting from line 75:

else if (method.toUpperCase() === 'POST') {
return CLIENT.oauth.post(
_rest_base + path
, token.oauth_token
, token.oauth_token_secret
, JSON.stringify(params['mailbox-item'])
, 'application/json; charset=UTF-8'
, requestCallback(callback)
, authorization
);
}

As you can see, here I caught the authorization string and I JSON
stringified the mailbox-item immediately (normally params should already be
a JSON object, I guess). Another reason I didn't do a pull request... my
solution only worked for mailbox items now, since I hardcoded that. Oh
well. Finally, in /linkedin-js/node_modules/oauth/lib/oauth.js, I changed
quite a few methods:

exports.OAuth.prototype._putOrPost= function(method, url, oauth_token, oauth_token_secret, post_body, post_content_type, callback, authorization) {
var extra_params= null;
if( typeof post_content_type == "function" ) {
callback= post_content_type;
post_content_type= null;
}
if( typeof post_body != "string" ) {
post_content_type= "application/x-www-form-urlencoded"
extra_params= post_body;
post_body= null;
}
return this._performSecureRequest( oauth_token, oauth_token_secret, method, url, extra_params, post_body, post_content_type, callback, authorization );
}

exports.OAuth.prototype.put= function(url, oauth_token, oauth_token_secret, post_body, post_content_type, callback) {
return this._putOrPost("PUT", null, url, oauth_token, oauth_token_secret, post_body, post_content_type, callback, authorization);
}

exports.OAuth.prototype.post= function(url, oauth_token, oauth_token_secret, post_body, post_content_type, callback, authorization) {
return this._putOrPost("POST", url, oauth_token, oauth_token_secret, post_body, post_content_type, callback, authorization);
}

As you can see, I added the authorization string as a parameter to these
methods and changed each method call all the way up to
_performSecureRequest, where I did an (authorization === undefined) check.
If it was undefined, I just calculated it using _buildAuthorizationHeaders,
else I used the one that I calculated myself and generated. I probably
spend hours and hours looking at this and in the end I was quite desperate,
so that's why I tried calculating my own authorization string. Still a
wonder that this worked. I'm not proud of the solution and I should've
changed it once I noticed that this worked, but I guess other functionality
was more important at that time.

A better (read: no monkey patching) solution would have been to contact
@masylum https://github.com/masylum and replace the OAuth library
itself, so POST requests work again. AFAIK, that is the solution. I'm not
sure if this project is still under maintenance though. If you've got other
questions, feel free to ask and I'll try to respond in a timely manner.


Reply to this email directly or view it on GitHubhttps://github.com//issues/9#issuecomment-15143275
.

@igorpavlov-zz
Copy link

Guys, may you help me, I am trying to make it work, so thank you very much for your posts, very self-explanatory. But I am stuck with the line, which contains OAuth.timestamp(). Node says object OAuth doesn't have this method. I thought OAuth = require('oauth').OAuth, but it doesn't seem to be true. What is that magic OAuth variable?

@tangrammer
Copy link

Hi Igor,
I've not tried this code but it seems that the method is
OAuth._getTimestamp() . I see that at
https://github.com/ciaranj/node-oauth/blob/master/lib/oauth.js#L57

i hope it fixes your problem,
good luck

Juan

2013/4/11 Igor Pavlov notifications@github.com

Guys, may you help me, I am trying to make it work, so thank you very much
for your posts, very self-explanatory. But I am stuck with the line, which
contains OAuth.timestamp(). Node says object OAuth doesn't have this
method. I thought OAuth = require('oauth').OAuth, but it doesn't seem to be
true. What is that magic OAuth variable?


Reply to this email directly or view it on GitHubhttps://github.com//issues/9#issuecomment-16231370
.

@igorpavlov-zz
Copy link

Thanks, I will try it!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants