Twitter Media Upload OAuth1.0a auth error - google-apps-script

I am trying to implement twitter media upload on google apps script via OAuth1.0a... as there has been no oauth2 for media uploads since 2 years. Following is the code. Still facing 401, 402, 403 , 400... all such return codes since last one week. Is this end point not working? anyone has any info? any ideas why its failing again and again.
using OAuth1 (https://github.com/googleworkspace/apps-script-oauth1/tree/3f3a6697d95a3ed9a91d09c65ffc34941136f587)
var url = 'https://upload.twitter.com/1.1/media/upload.json?media_category=tweet_image';
var baseUrl = 'https://upload.twitter.com/1.1/media/upload.json';
var params = {
'payload': {'media': imageBlob},
'method': 'POST',
'muteHttpExceptions' : true
};
var token = JSON.parse(PropertiesService.getUserProperties().getProperty("oauth1."+ account));
var oauth_token = token.oauth_token
var oauth_token_secret = token.oauth_token_secret
var oauth_consumer_key = PropertiesService.getUserProperties().getProperty("TWITTER_CONSUMER_KEY");
var oauth_consumer_secret = PropertiesService.getUserProperties().getProperty("TWITTER_CONSUMER_SECRET");
const method = params['method'] || 'post';
params['method'] = method;
const oauthParameters = {
oauth_version: "1.0",
oauth_token: oauth_token,
oauth_consumer_key: oauth_consumer_key,
oauth_signature_method: "HMAC-SHA1",
oauth_timestamp: (Math.floor((new Date()).getTime() / 1000)).toString(),
};
oauthParameters.oauth_nonce = oauthParameters.oauth_timestamp + Math.floor(Math.random() * 100000000);
const payload = params['payload'] || {};
const q = {"media_category": "tweet_image"} //parms from url
const queryKeys = Object.keys(oauthParameters).concat(Object.keys(payload)).concat(Object.keys(q)).sort();
const baseString = queryKeys.reduce(function(acc, key, idx) {
if (idx) acc += encodeURIComponent("&");
if (oauthParameters.hasOwnProperty(key))
acc += _encode(key + "=" + oauthParameters[key]);
else if (payload.hasOwnProperty(key))
acc += _encode(key + "=" + _encode(payload[key]));
return acc;
}, method.toUpperCase() + '&' + _encode(baseUrl) + '&');
oauthParameters.oauth_signature = Utilities.base64Encode(
Utilities.computeHmacSignature(
Utilities.MacAlgorithm.HMAC_SHA_1,
baseString, oauth_consumer_secret + '&' + oauth_token_secret
)
);
if (!params['headers']) params['headers'] = {};
params['headers']['authorization'] = "OAuth " + Object.keys(oauthParameters)
.sort().reduce(function(acc, key) {
acc.push(key + '="' + _encode(oauthParameters[key]) + '"');
return acc;
}, []).join(', ');
params['payload'] = Object.keys(payload).reduce(function(acc, key) {
acc.push(key + '=' + _encode(payload[key]));
return acc;
}, []).join('&');
console.log(params)
response = UrlFetchApp.fetch(url, params);
for info...other than this, I also tried this repo - https://github.com/airhadoken/twitter-lib
still facing similar issues.
EDIT: on postman it works.. somethings wrong with the code then.. :(

Related

How to use correctly google drive API?

I have developed the following code based on other code I found on the internet, but it does not work.
Can someone help me to understand my issue?
I get an error saying I don't have the right.
What is the problem?
Thank you very much for your support, that will be well appreciated.
var tokenService_ = function(){return ScriptApp.getOAuthToken()};
function exe() {
var listObj = reportTeamDrivePermissions('id of the folder');
console.log(listObj.toString());
}
function reportTeamDrivePermissions(driveId) {
// Reading the Drive permissions
var options = {"supportsAllDrives": true, "fields": 'permissions,nextPageToken', "pageSize":100};
var paramString = Object.keys(options).map(function(key) {
return encodeURIComponent(key) + '=' + encodeURIComponent(options[key]);
}).join('&');
var url = "files/"+driveId+"/permissions";
url = url + (url.indexOf('?') >= 0 ? '&' : '?') + paramString;
url = "https://www.googleapis.com/drive/v3/" + url;
var fetchOptions = {method:"GET",muteHttpExceptions:true, contentType:"application/json", headers:{Authorization:"Bearer "+tokenService_()}};
var response = UrlFetchApp.fetch(url, fetchOptions);
if(response.getResponseCode() != 200){
throw new Error(response.getContentText());
} else {
var PermissionListResource = JSON.parse(response.getContentText());
}
var myPermisionList = PermissionListResource.permissions;
var nextPageToken = PermissionListResource.nextPageToken;
while (nextPageToken != null) {
// Reading the Drive permissions
options = {"supportsAllDrives": true, "fields": 'permissions,nextPageToken', "pageSize":100, 'pageToken': nextPageToken};
paramString = Object.keys(options).map(function(key) {
return encodeURIComponent(key) + '=' + encodeURIComponent(options[key]);
}).join('&');
url = "files/"+driveId+"/permissions";
url = url + (url.indexOf('?') >= 0 ? '&' : '?') + paramString;
url = "https://www.googleapis.com/drive/v3/" + url;
fetchOptions = {method:"GET",muteHttpExceptions:true, contentType:"application/json", headers: {Authorization:"Bearer "+tokenService_()}}
response = UrlFetchApp.fetch(url, fetchOptions)
if(response.getResponseCode() != 200){
throw new Error(response.getContentText());
} else {
var PermissionListResource2 = JSON.parse(response.getContentText());
}
myPermisionList = myPermisionList.concat(PermissionListResource2.permissions);
nextPageToken = myPermisionObject2.nextPageToken;
}
return myPermisionList;
}

Google Service Account Delegation 404 error

I am attempting to authenticate with a service account to work on behalf of a user account on the domain. I have delegated admin access and added to the GSuite console. I can get an access token with the below but the making batch requests to copy drive files returns "code: 404, message: 'File not found:". The below code is writted in Google Apps Script. Am I missing something form the process to creating and authenticating the service account?
var CREDENTIALS = {
private_key: "-----BEGIN PRIVATE KEY----- XXXXXXX \n-----END PRIVATE KEY-----\n",
client_email: "XXXXXX#fXXXXXX.iam.gserviceaccount.com",
client_id: "1XXXXXXXXXXXXXXXX",
user_email: "XXXXX#XXXX.XXX.XXX",
scopes: ["https://www.googleapis.com/auth/drive","https://www.googleapis.com/auth/spreadsheets","https://www.googleapis.com/auth/userinfo.email","https://www.googleapis.com/auth/script.external_request"]
};
function oAuthToken(){
var url = "https://www.googleapis.com/oauth2/v3/token";
var header = {
alg: "RS256",
typ: "JWT",
};
var now = Math.floor(Date.now() / 1000);
var claim = {
iss: CREDENTIALS.client_id,
sub: CREDENTIALS.user_email,
scope: CREDENTIALS.scopes.join(" "),
aud: url,
exp: (now + 3600).toString(),
iat: now.toString(),
};
var signature = Utilities.base64Encode(JSON.stringify(header)) + "." + Utilities.base64Encode(JSON.stringify(claim));
var jwt = signature + "." + Utilities.base64Encode(Utilities.computeRsaSha256Signature(signature, CREDENTIALS.private_key));
var params = {
method: "post",
payload: {
assertion: jwt,
grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
},
};
var res = UrlFetchApp.fetch(url, params).getContentText();
return JSON.parse(res)
}
The batch process is a bit rough but this is the gist of it.
var request={
batchPath:
requests:[]
}
var backoff =0
function batch(request) {
var oAuth=oAuthToken().access_token
var url ='https://www.googleapis.com/'+request.batchPath
var body =request.requests
if(body.length<1){
return []
}
var boundary = 'xxxxxxxxxx';
var contentId = 0;
var data = '--' + boundary + '\r\n';
for (var i in body) {
if(typeof body[i]=='object'){
data += 'Content-Type: application/http\r\n';
data += 'Content-ID: ' + ++contentId + '\r\n\r\n';
data += body[i].method + ' ' + body[i].endpoint + '\r\n';
data += body[i].requestBody ? 'Content-Type: application/json; charset=utf-8\r\n\r\n' : '\r\n';
data += body[i].requestBody ? JSON.stringify(body[i].requestBody) + '\r\n' : '';
data += "--" + boundary + '\r\n';
}
}
var parseBatchRes = function(res) {
var splittedRes = res.split('--batch');
return splittedRes.slice(1, splittedRes.length - 1).map(function(e) {
return {
contentId: Number(e.match(/Content-ID: response-(\d+)/)[1]),
status: Number(e.match(/HTTP\/\d+.\d+ (\d+)/)[1]),
object: JSON.parse(e.match(/{[\S\s]+}/)[0]),
};
});
};
var payload = Utilities.newBlob(data).getBytes();
var head = {Authorization: 'Bearer ' + oAuth}
var options = {
method: 'POST',
contentType: 'multipart/mixed; boundary=' + boundary,
payload: payload,
headers: head,
muteHttpExceptions: false
};
var complete=false;
var finalResponse=[];
for (var n=0; n<=backoff; n++) {
if(complete){
break;
}
var complete = true
console.log('backoff',n);
var response =UrlFetchApp.fetch(url, options).getContentText();
for(var j=0;j<response.length;j++){
if(response[r].status!=200){
var complete = false
}
}
}
}
Add the supportsAllDrives = true query parameter to the request.
The parameters indicates whether the requesting application supports both My Drives and shared drives and the default value for this is false.
Reference
Drive API Parameters

Extract data from JSON within Lambda so it's not undefined

Looking for advice / second opinion. I'm trying to pass JSON via HTTP API (api gateway) > Lambda. I'm receiving the data (pic of Cloudwatch), getting undefined when trying to extract values. The file is being written to S3, but undefined.
I included Lambda code, picture of Cloudwatch logs. I'm about there :) . Newbie here...
Logs
Lambda Code
var AWS = require('aws-sdk');
var s3 = new AWS.S3();
exports.handler = async (event, context, callback) => {
var bucketName = process.env.bucketName;
var folder = process.env.folder;
var filename = getFileName();
console.log("Filename:" + filename);
var raw = JSON.stringify(event.body);
console.log("raw after stringify:" + raw);
var results = JSON.parse(raw);
console.log("results:" + results);
let firstname = results.firstName;
console.log("firstName:" + firstname);
let lastname = results.lastName;
console.log("lastName:" + lastname);
let message = results.Message;
console.log("Message:" + message);
var content = message + "," + firstname + "," + lastname;
console.log("content:" + content);
var keyName = getKeyName(folder, filename);
var params = { Bucket: bucketName, Key: keyName, Body: content };
s3.putObject(params, function (err, data) {
if (err)
console.log(err)
else
console.log("Successfully saved object to: " + bucketName + "/" + keyName);
});
function getKeyName(folder, filename) {
return folder + '/' + filename;
}
function getFileName() {
var _uuid = uuidv4();
var _date = Date.now();
return _uuid + "-" + _date;
}
function uuidv4() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
var html = '<html><head><title>Prayer received result</title></head>' +
'<body><h1>Your Prayer has been received!</h1></body></html>';
//callback(null, res); - use this when using proxy model
callback(null, html);
};
Made the following changes.
//var raw = JSON.stringify(event.body);
//console.log("raw after stringify:" + raw);
var results = JSON.parse(event.body);
console.log("results:" + results);
Hope this helps others. I'm newer as of this post to Lambda, JSON.

Xero API signature invalid using GAS

I am accessing API using Google Apps Script. I am looking for a https://developer.xero.com/documentation/api/reports#TrialBalance
I have tried with the API code as but I get signature invaid as reposnse result [19-09-03 19:45:46:402 IST] oauth_problem=signature_invalid&oauth_problem_advice=Failed%20to%20validate%20signature
function doGet(e) {
getTrialBalances();
}
function getTrialBalances() {
var oauth_nonce = createGuid();
var oauth_timestamp = (new Date().valueOf() / 1000).toFixed(0);
var CONSUMER_KEY = 'B7D5YA8D1HWHUZIGXL1AZS44N'
var PEM_KEY = '-----BEGIN RSA PRIVATE KEY-----' +
'ANIICXAIBAAKBgQC2WiSrkljVAZIgNUe/nBZ+PGJzauBJ6szlzPow1XoySkVikswui1IX4wUzgLmvnCmnQkRPgA43oiZqmK1H68MvirYzQkMa3sETViQAOiRPOrDEUTkemKiDXpaIKedD8T6/P9qzgtgU5hlP/R45POanIuNFvYPdpkm2yybOmI+1TwIjAQABAoGADt/3kc9UU7vXEa2G9shixVVjqoqTVTREFpLL7ePcHfIVCt9yrHFM9wnbyMG9uRZRIyDmbpumClROJImuADxc6reamXdTMX0OwEPogAREnY2diadjVjicoMYYEcdbb6pgDSOWcYtamNmzD5tkPI0bPFU+fTdpzGCOCECQQDvZTha0SRcCZPZipCs7PtAOWtMP1FBe140+cvsWiq2eHMmYDtIi7Mx210i3wzz4+Izl4jXeICKprppaBlJxSFZAkEAwwALfSnpqWeop86nnUICOPmksbK2rTtNVd+WGiAK4reUDJArOOXdDm7fYqppQNA35hxcRmvxeKK7jSYLQYHO5wJAeLFubRL+IszNVqLud9Buh52rQ+C0RbA9+bVqozl+SUqGu3VOzi9oY5114kvUCu38MAiY/BELtVuDpfrOrQuO2QJAHrZZGOOLC8VpyNRBjgEhfHvFNr+hCfO3IHlQmNjHHiIvzTK/u/xoLqfDwzR30194DmQVHHpP0+I9i+OcDjs1rQJBAJMY6h4QdYSFpTPxUOPA/s1lKVvJUIzgzX6oMfvc4TDb0RCz4nCvjJ1NEqPjveB6ze5TzC8BzfRW/aUh49vmgRA=' +
'-----END RSA PRIVATE KEY-----';
var payload = '';
var URL = 'https://api.xero.com/api.xro/2.0/Reports/TrialBalance';
var signatureBase = "GET" + "&" +
encodeURIComponent(URL) + "&" +
encodeURIComponent('date=2019-02-01') + "&" +
encodeURIComponent("oauth_consumer_key=" + CONSUMER_KEY +
"&oauth_nonce=" + oauth_nonce + "&oauth_signature_method=RSA-SHA1&oauth_timestamp=" +
oauth_timestamp + "&oauth_token=" + CONSUMER_KEY + "&oauth_version=1.0");
var rsa = new RSAKey();
rsa.readPrivateKeyFromPEMString(PEM_KEY);
var hashAlg = "sha1";
var hSig = rsa.signString(signatureBase, hashAlg);
var oauth_signature = encodeURIComponent(hextob64(hSig));
var authHeader = "OAuth oauth_token=\"" + CONSUMER_KEY + "\",oauth_nonce=\"" + oauth_nonce +
"\",oauth_consumer_key=\"" + CONSUMER_KEY + "\",oauth_signature_method=\"RSA-SHA1\",oauth_timestamp=\"" +
oauth_timestamp + "\",oauth_version=\"1.0\",oauth_signature=\"" + oauth_signature + "\"";
var headers = {
"Authorization": authHeader,
"Accept": "application/json"
};
var options = {
"headers": headers,
'method': 'GET',
'payload': payload,
'muteHttpExceptions': true,
};
var requestURL = URL + '?date=2019-02-01';
var response = UrlFetchApp.fetch(requestURL, options);
var responseXml = response.getContentText();
Logger.log(responseXml);
}
function createGuid() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random() * 16 | 0,
v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16)
});
}
For RSA signing I have used https://github.com/csi-lk/google-app-script-xero-api/blob/master/jsrsasign.gs
UPDATE 2:
I code this, but still not able to get the result
var signatureBase = encodeURIComponent("GET" + "&" + URL + "&" + 'date=2019-02-01' + "&" + "oauth_consumer_key=" + CONSUMER_KEY +
"&oauth_nonce=" + oauth_nonce + "&oauth_signature_method=RSA-SHA1&oauth_timestamp=" +
oauth_timestamp + "&oauth_token=" + CONSUMER_KEY + "&oauth_version=1.0");
Before reading the rest of my answer can you please urgently reset your applications Consumer Key/Secret, as well as create and upload a new public certificate to the developer portal as you've provided both in your question.
At least one issue you're running into that I can spot is how you're building up the signature base string.
Only the initial & should be left unencoded, however the rest of them in the signature base string should be encoded. It looks like the & after the encoded URL and encoded date query param are being left unencoded.
Edit:
The following two lines are leaving the &s out ouf encoding, but they need to be included in the uri encoding
encodeURIComponent(URL) + "&" +
encodeURIComponent('date=2019-02-01') + "&" +

Building drive app from apps script - Whats wrong in the below code

Below is the code taken from Arun Nagarajan's Example: I am tried the same code to check.. But Its not installing properly. (I removed my redirect url, client id and secret in the below). Please tell me what wrong in the below code.
var AUTHORIZE_URL = 'https://accounts.google.com/o/oauth2/auth';
var TOKEN_URL = 'https://accounts.google.com/o/oauth2/token';
var REDIRECT_URL = 'exec';
var tokenPropertyName = 'GOOGLE_OAUTH_TOKEN';
var CLIENT_ID = '';
var CLIENT_SECRET = '';
function doGet(e) {
var HTMLToOutput;
if(e.parameters.state){
var state = JSON.parse(e.parameters.state);
if(state.action === 'çreate'){
var meetingURL = createMeetingNotes();
HTMLToOutput = '<html><h1>Meeting notes document created!</h1> <click here to open</html>';
}
else if (state.ids){
var doc = DocsList.getFileById(state.ids[0]);
var url = doc.getContentAsString();
HTMLToOutput = '"<html><a href="' +url+'"</a></html>"';
}
else {
zipAndSend(state.ecportIds.Session.getEffectUser().getEmail());
HTMLToOutput = '"<html><h1>Email sent. Check your Inbox.</h1></html>"';
}
}
else if(e.parameters.code){
getAndStoreAccessToken(e.parameters.code);
HTMLToOutput = '<html><h1>App is installed. You can close this window now or navigate to your </h1>Google Drive</html>';
}
else {
HTMLToOutput = '<html><h1>Install this App into your google drive </h1>Click here to start install</html>';
}
return HtmlService.createHtmlOutput(HTMLToOutput);
}
function getURLForAuthorization() {
return AUTHORIZE_URL + '?response_type=code&client_id=' + CLIENT_ID + '&redirect_uri=' + REDIRECT_URL + '&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive.install+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email';
}
function getAndStoreAccessToken(code) {
var parameters = { method : 'post',
payload : 'client_id='+ CLIENT_ID + '&client_secret=' + CLIENT_SECRET + '&grant_type=authorization.code&redirect_uri=' + REDIRECT_URL};
var response = UrlFetchApp.fetch(TOKEN_URL.parameters).getContentText();
var tokenResponse = JSON.parse(response);
UserProperties.getProperty(tokenPropertyName, tokenResponse.access_token);
}
function getUrlFetchOptions() {
return {'contentType' : 'application/json',
'headers' : {'Authorization': 'Bearer ' + UserProperties.getProperty(tokenPropertyName),
'Accept' : 'application/json'}};
}
function IsTokenValid() {
return UserProperties.getProperty(tokenPropertyName);
}
The error showing is: Bad request:undefined
I think the error is inside the function called : getAndStoreAccessToken.
var parameters = { method : 'post',
payload : 'client_id='+ CLIENT_ID + '&client_secret=' + CLIENT_SECRET + '&grant_type=authorization.code&redirect_uri=' + REDIRECT_URL};
Please tell me the correct url format for payload.
The error seems in this line -
var response = UrlFetchApp.fetch(TOKEN_URL.parameters).getContentText();
I think you want TOKEN_URL , parameters (note the comma)
First, if you are trying to access Google Drive from within google apps script, what is the purpose of the authorization? Google drive is available w/o authorization. Are you trying to make your application utilize the gDrive of other users (or on behalf of other users)?
Second, instead of manually performing the authorization, which is very hard to troubleshoot, you can take advantage of Class OAuthConfig which simplifies the authorization/request process. The only disadvantage is that OAuthConfig currently uses OAuth1.0 (which is currently deprecated). Although it's particular use is Fusion Tables, and not drive, this library makes great use of OAuthConfig and .fetch and I have used it to model my own OAuth functions. My example below works great. The googleAuth() function sets up the authorization and then the rest of the application can make authorized requests using UrlFetchApp.fetch(url,options) while google does all the authorization stuff in the background.
function googleAuth(oAuthFields) {
var oAuthConfig = UrlFetchApp.addOAuthService(oAuthFields.service);
oAuthConfig.setRequestTokenUrl("https://www.google.com/accounts/"+
"OAuthGetRequestToken?scope=" + oAuthFields.scope);
oAuthConfig.setAuthorizationUrl("https://www.google.com/accounts/OAuthAuthorizeToken");
oAuthConfig.setAccessTokenUrl("https://www.google.com/accounts/OAuthGetAccessToken");
oAuthConfig.setConsumerKey(oAuthFields.clientId);
oAuthConfig.setConsumerSecret(oAuthFields.clientSecret);
return {oAuthServiceName:oAuthFields.service, oAuthUseToken:"always"};
}
function fusionRequest(methodType, sql, oAuthFields, contentType) {
var fetchArgs = OAL.googleAuth(oAuthFields);
var fetchUrl = oAuthFields.queryUrl;
fetchArgs.method = methodType;
if( methodType == 'GET' ) {
fetchUrl += '?sql=' + sql;
fetchArgs.payload = null;
} else{
fetchArgs.payload = 'sql='+sql;
}
if(contentType != null) fetchArgs.contentType = contentType;
Logger.log(UrlFetchApp.getRequest(oAuthFields.queryUrl, fetchArgs));
var fetchResult = UrlFetchApp.fetch(oAuthFields.queryUrl, fetchArgs);
if( methodType == 'GET' ) return JSON.parse(fetchResult.getContentText());
else return fetchResult.getContentText();
}