I'm trying to fetch data from Google search console (GSC) through http request.
I'm using google app Maker with javascript.
For my purpose I'm using a service account, all the scopes are already set up for the account.
I've copied the code provided by #Morfinismo.
/*********** SERVICE ACCOUNT CONFIGURATION USING THE OAUTH LIBRARY ***********
** All of the values are obtained from the .json file that is downloaded at
** the time of the service account creation
** Ref: https://developers.google.com/identity/protocols/OAuth2ServiceAccount
** Ref: https://github.com/googlesamples/apps-script-oauth2
*/
var accessData = {
"private_key" : "-----BEGIN PRIVATE KEY-----THE KEY-----END PRIVATE KEY-----\n",
"client_email" : "searchconsolebot#project-id-xxxxxxxxxxxxxxx.iam.gserviceaccount.com",
"user_email" : "user#domain.com" // Why do we need a user mail ?
};
var scopes = ["https://www.googleapis.com/auth/webmasters", "https://www.googleapis.com/auth/webmasters.readonly"]; //GSC api scope
scopes = scopes.join(" "); //join all scopes into a space separated string
function getOAuthService(user) {
console.log("je passe par getOAuthService");
user = user || accessData.user_email;
console.log("user: " + user);
return OAuth2.createService("GSC_Service_Account")
.setTokenUrl('https://accounts.google.com/o/oauth2/token')
.setPrivateKey(accessData.private_key)
.setIssuer(accessData.client_email)
.setSubject(user)
.setPropertyStore(PropertiesService.getScriptProperties())
.setCache(CacheService.getUserCache())
.setParam('access_type', 'offline')
.setScope(scopes);
}
function reset(user) {
var service = getOAuthService(user);
console.log("service: " + service);
service.reset();
return service;
}
function getToken(userEmail){
var totoken = reset(userEmail).getAccessToken();
console.log(totoken);
return reset(userEmail).getAccessToken();
}
function getGCSUrlData(urlGiven){
var token = getToken();
if(token){
var reqBody = {
startDate: "2019-01-01",
endDate: "2020-01-23"
};
var options = {
method : 'POST',
headers : {
Authorization : 'Bearer ' + token,
},
contentType: 'application/json',
payload: JSON.stringify(reqBody),
muteHttpExceptions: true,
};
var url = "https://www.googleapis.com/webmasters/v3/sites/" + encodeURIComponent(urlGiven) + "/searchAnalytics/query";
var response = UrlFetchApp.fetch(url, options);
console.log(response);
}
}
Using the OAuth library seems really great but it does return me an error
Error: Access not granted or expired. at getToken (Service_Account_Config:46)
Also I noticed that getToken() method requires a param but when calling it we don't give any param is it normal ?
And why do we need a user_email since we are using a service account ?
Which email should I enter for the user_email then ?
I would really appreciate some help about this issue and any advice to understand this kind of issue.
Thanks a lot,
Jacky
Integration with a service account is very easy when using the OAuth2 Library for AppsScript. Here are the steps:
1.) In a server side script add the following:
/*********** SERVICE ACCOUNT CONFIGURATION USING THE OAUTH LIBRARY ***********
** All of the values are obtained from the .json file that is downloaded at
** the time of the service account creation
** Ref: https://developers.google.com/identity/protocols/OAuth2ServiceAccount
** Ref: https://github.com/googlesamples/apps-script-oauth2
*/
var accessData= {
"private_key": "-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----\n",
"client_email": "service-account825#08012018.iam.gserviceaccount.com",
"user_email": "user#domain.com"
};
var scopes = ["https://www.googleapis.com/auth/webmasters", "https://www.googleapis.com/auth/webmasters.readonly"]; //drive api scope
scopes = scopes.join(" "); //join all scopes into a space separated string
function getOAuthService(user) {
user = user || accessData.user_email;
return OAuth2.createService("Service Account")
.setTokenUrl('https://accounts.google.com/o/oauth2/token')
.setPrivateKey(accessData.private_key)
.setIssuer(accessData.client_email)
.setSubject(user)
.setPropertyStore(PropertiesService.getScriptProperties())
.setCache(CacheService.getUserCache())
.setParam('access_type', 'offline')
.setScope(scopes);
}
function reset(user) {
var service = getOAuthService(user);
service.reset();
return service;
}
function getToken(userEmail){
return reset(userEmail).getAccessToken();
}
Then you can simplye call the service you need by doing the following:
function getGCSUrlData(urlGiven){
var token = getToken();
if(token){
var reqBody = {
startDate: "2020-01-01",
endDate: "2020-01-23"
};
var options = {
method : 'POST',
headers : {
Authorization : 'Bearer ' + token,
},
contentType: 'application/json',
payload: JSON.stringify(reqBody),
muteHttpExceptions: true,
};
var url = "https://www.googleapis.com/webmasters/v3/sites/" + encodeURIComponent(urlGiven) + "/searchAnalytics/query";
var response = UrlFetchApp.fetch(url, options);
console.log(response);
}
}
Related
I am querying data from the data base from salesforce and it is working find with jsforce -l http://test.salesforce.com I then do a .register and a .authorize and then I can pull data from my salesforce database with a query language, and It works great. My .jsforce/config.json looks like
{
"connections": {
"<my_username>": {
"accessToken": "<my_accessToken>",
"instanceUrl": "<https://my.partial.instance.com>",
"client": "default"
}
},
"clients": {
"default": {
"clientId": "<myclientid>",
"clientSecret": "<longNumber>",
"redirectUri": "https://localhost:1111",
"loginUrl": "https://test.salesforce.com"
}
}
}
However I want to do this automatically using jsforce in javascript. Now i tried something like on the documentation for jsforce.
var jsforce = require('jsforce');
var conn = new jsforce.Connection({
oauth2 : {
// you can change loginUrl to connect to sandbox or prerelease env.
loginUrl : 'https://test.salesforce.com',
clientId : '<myclientid>',
clientSecret : '<longNumber>',
redirectUri : '<https://my.partial.instance.com>'
}
});
conn.login(username, 'password'+'<clientSecret>', function(err, userInfo) {
if (err) { return console.error(err); }
// Now you can get the access token and instance URL information.
// Save them to establish connection next time.
console.log(conn.accessToken);
console.log(conn.instanceUrl);
// logged in user property
console.log("User ID: " + userInfo.id);
console.log("Org ID: " + userInfo.organizationId);
// ...
});
ive also tried some thing like
var conn = new jsforce.Connection({
oauth2 : {
clientId : '<clientId>',
clientSecret : '<clientSecret',
redirectUri : 'https://localhost:1111'
},
instanceUrl : '<https://my.partial.instance.com>',
accessToken : '<accessToken>'
});
and similar things from the website here they are all giving me { invalid_grant: authentication failure } which I belive mean that my authorization is invalid. What am I doing wrong my stuff is correct because it is working on the command line interface. thanks
Edit: I did all the slandered things like white list my ip and let users to self authorize bc again its working on the command line.
We have experienced the following problem:
We had implemented Azure AD authenticated web API and we have been successfully had been making calls to it with ADAL 1.0.12 (we had our own wrapper around it for processing iframe for silent login).
Now we got rid all together of our wrapper and upgraded to the latest version and started to get the problem described here: token renewal operation timed out.
The Azure AD architecture behind the API is very simple: a registered web app with Read directory data and Sign in and read user profile permissions. We have another native app registered with permissions to the web app (Access to the Web APP NAME HERE). Both applications have oauth2AllowImplicitFlow set to true in their manifests.
Both apps have registered a wildcard redirect URIs for https://ourtenant.sharepoint.com/* (we are making the calls from SharePoint to the web API). In addition, the native client app has registered redirect URI to the unique URI (App ID URI) of the web API app.
Both apps have been granted admin consent before - they were working fine with the old version of ADAL.js
So, here is some code. Please, note that we had tried both without displayCall callback (with page refresh directly on the current page) and with page refresh (check the commented code in initLogin method). Also we had a switch for generating a popup or an iframe with a callback on successful login.
The problem is with authContext.acquireToken. Note that if we call OurNamespace.authContext.getCachedToken(OurNamespace.clientId) we get a stored token for the resource.
Note that also, we are calling properly handleWindowCallback after each page refresh / iframe / popup.
Happens regardless of the browser.
OurNamespace = {
serviceMainUrl: "https://localhost:44339",
webApiAppIdUri: "WEB-API-URI",
tenant: "TENANT",
clientId: "NATIVE-APP-GUID", // Native APP
adalEndPoints: {
get: null
},
adal: null,
authContext: null,
dummyAuthPage: null,
usePopup: true,
init: function () {
this.dummyAuthPage = "DummmyLogin.aspx";
OurNamespace.adalEndPoints.get = OurNamespace.serviceMainUrl + "/api/values";
OurNamespace.authContext = new AuthenticationContext({
tenant: OurNamespace.tenant + '.onmicrosoft.com',
clientId: OurNamespace.clientId,
webApiAppIdUri: OurNamespace.webApiAppIdUri,
endpoints: OurNamespace.adalEndPoints,
popUp: false,
postLogoutRedirectUri: window.location.origin,
cacheLocation: "localStorage",
displayCall: OurNamespace.displayCall,
redirectUri: _spPageContextInfo.siteAbsoluteUrl + "/Pages/" + OurNamespace.dummyAuthPage
});
var user = OurNamespace.authContext.getCachedUser(); // OurNamespace.authContext.getCachedToken(OurNamespace.clientId)
if (user) {
OurNamespace.azureAdAcquireToken();
} else {
OurNamespace.initLogin();
}
},
initLogin: function () {
//OurNamespace.authContext.config.displayCall = null;
//var isCallback = OurNamespace.authContext.isCallback(window.location.hash);
//OurNamespace.authContext.handleWindowCallback();
//if (isCallback && !OurNamespace.authContext.getLoginError()) {
// window.location.href = OurNamespace.authContext._getItem(OurNamespace.authContext.CONSTANTS.STORAGE.LOGIN_REQUEST);
//}
OurNamespace.authContext.login();
},
displayCall: function (url) {
var iframeId = "azure-ad-tenant-login",
popup = null;
if (OurNamespace.usePopup) {
popup = window.open(url, 'auth-popup', 'width=800,height=500');
} else {
var iframe = document.getElementById(iframeId);
if (!iframe) {
iframe = document.createElement("iframe");
iframe.setAttribute('id', iframeId);
document.body.appendChild(iframe);
}
iframe.style.visibility = 'hidden';
iframe.style.position = 'absolute';
iframe.src = url;
popup = iframe.contentDocument;
}
var intervalId = window.setInterval(function () {
try {
// refresh the contnetDocument for iframe
if (!OurNamespace.usePopup)
popup = iframe.contentDocument;
var isCallback = OurNamespace.authContext.isCallback(popup.location.hash);
OurNamespace.authContext.handleWindowCallback(popup.location.hash);
if (isCallback && !OurNamespace.authContext.getLoginError()) {
popup.location.href = OurNamespace.authContext._getItem(OurNamespace.authContext.CONSTANTS.STORAGE.LOGIN_REQUEST);
window.clearInterval(intervalId);
if (OurNamespace.usePopup) {
popup.close();
}
var user = OurNamespace.authContext.getCachedUser();
if (user) {
console.log(user);
} else {
console.error(OurNamespace.authContext.getLoginError());
}
}
} catch (e) { }
}, 400);
},
azureAdAcquireToken: function () {
OurNamespace.authContext.acquireToken(OurNamespace.adalEndPoints.get, function (error, token) {
if (error || !token) {
SP.UI.Status.addStatus("ERROR", ('acquireToken error occured: ' + error), true);
return;
} else {
OurNamespace.processWebApiRequest(token);
}
});
},
processWebApiRequest: function (token) {
// Alternatively, in MVC you can retrieve the logged in user in the web api with HttpContext.Current.User.Identity.Name
$.ajax({
type: "GET",
url: OurNamespace.adalEndPoints.get,
contentType: "application/json; charset=utf-8",
dataType: "json",
data: {},
headers: {
'Authorization': 'Bearer ' + token
},
success: function (results) {
var dataObject = JSON.parse(results);
SP.UI.Status.addStatus("Info", "ADAL GET data success: " + dataObject.webApiUser);
$(".grid-info").html("<h1 class=\"h1\">Current Web API authenticated user: " + dataObject.webApiUser + "</h1>");
},
error: function (xhr, errorThrown, textStatus) {
console.error(xhr);
SP.UI.Status.addStatus("ERROR", ('Service error occured: ' + textStatus), true);
}
});
}
}
I am testing using the 1.0.15 adal.js and get the access token successfully by using the authContext.acquireToken which actually call the this._renewToken(resource, callback) to acquire the access token by the hide iframe. Here is the full test sample code for your reference:
<html>
<head>
<script src="/js/1.0.15/adal.js"></script>
<script>
var config = {
tenant: 'adfei.onmicrosoft.com',
clientId: '7e3b0f81-cf5c-4346-b3df-82638848104f',
redirectUri: 'http://localhost/spa.html',
navigateToLoginRequestUrl:false,
};
var authContext=new AuthenticationContext(config);
var hash = window.location.hash;
if(hash.length!=0){
authContext.handleWindowCallback(hash);
var user=authContext.getCachedUser();
}
function login(){
authContext.login();
}
function acqureToken(){
authContext.acquireToken("https://graph.microsoft.com", function(error, token, message){
console.log(token)
})
}
</script>
</head>
<body>
<button onclick="login()">Login</button>
<button onclick="acqureToken()">AcquireToken</button>
</body>
</html>
Is it helpful? Or would you mind sharing a run-able code sample for this issue?
I'm not sure if what version of adal.js you are using. But there is a loadFrameTimeout setting for the config object that you can set in milliseconds. Check the top of your adal.js file and it should be there.
I have HTML/JS client trying to access APIController on Azure Mobile App service.
Following is my code
var _client = new WindowsAzure.MobileServiceClient("https://myapp.azurewebsites.net/");
var pp = _client.invokeApi("/Lookup/GetTransactionType", {
body: null,
method: "get",
parameters: { TenantID: 1 },
headers: {
"ZUMO-API-VERSION": "2.0.0",
"Content-Type":"application/json",
"Cache-Control":"false",
"x-zumo-auth": "tada"
}
}).done(function (results) {
var message = results.results.count;
}, function (error) {
alert(error.message)
});
The issue here is that my api's are published as such :
https://myapp.azurewebsites.net/Lookup/GetTransactionType?TenantID={{TenantID}}
But I get NOT FOUND error in client since its looking for following URL :
(XHR)GET - https://myapp.azurewebsites.net/api/Lookup/GetTransactionType?TenantID=1
How can I eliminate the /api in the URI?
As #rolspace mentioned, you need to call the .invokeApi function with the absolute URL (must start with http:// or https://) to eliminate the /api in the URI.
So you can change the line of code to:
var pp = _client.invokeApi(_client.applicationUrl + "/Lookup/GetTransactionType", { //...
I'm trying to make the following tutorial work which simulates a login from here : http://jasonwatmore.com/post/2014/05/26/AngularJS-Basic-HTTP-Authentication-Example.aspx
Here is the AngularJS code:
/* Dummy authentication for testing, uses $timeout to simulate api call
----------------------------------------------*/
$timeout(function(){
var response = { success: username === 'test' && password === 'test' };
if(!response.success) {
response.message = 'Username or password is incorrect';
}
callback(response);
}, 1000);
/* Use this for real authentication
----------------------------------------------*/
//$http.post('/api/authenticate', { username: username, password: password })
// .success(function (response) {
// callback(response);
// });
};
I now want to change it from dummy to the real authentification.
So far I have in the backend:
#CrossOrigin
#RequestMapping(value = "/login", method = RequestMethod.POST)
public String login(#RequestBody Login login) {
if (login.username.equals("test") && login.password.equals("test")) {
//return what?
} else {
//return what?
}
}
You return a normal Java object. The object should automatically be transferred in json format and you can read json format in javascript.
Example:
// java
String name;
int age;
// json format
{"name": "theName", "age": 32}t
// javascript
var myObj = {
name: "theName",
age: 32
};
So in your java you return you simply return your java object. If you look in your dev console of your browser in the network tab you should see the response in json format. Otherwise just $log.log("response", response); to check what object you get from the server.
I have some simple javascript functions to interact with an API like this one to login:
login: function(username, password) {
var calledUrl = baseapi + "user/login/" + credentials;
calledUrl.post(
function (content) {
/*console.log("success" + JSON.stringify(content, null, 4));*/
},
function (e) {
console.log("it failed! -> " + e);
},
{
"username": username,
"password": password
},
{"Accept" : "application/json"}
);
},
The problem is, in the URL I must pass some credentials and they look like that:
var credentials = "?api_username=" + api_username + "&api_key=" + api_key;
Right now this variable is hardcoded to make some tests but of course it should change for each person using the function. I don't want to ask for it with each request, in this case I only want to ask for username and password. I would like to ask for it once and for all during an initializing process or whatever it is called and then remember it when executing the various functions.
If .login() is the first method that typically needs the credentials, then you can just make it a required argument for that method and then store the credentials in the object:
login: function(username, password, credentials) {
// save credentials for use in other methods
this.credentials = credentials;
var calledUrl = baseapi + "user/login/" + credentials;
calledUrl.post(
function (content) {
/*console.log("success" + JSON.stringify(content, null, 4));*/
},
function (e) {
console.log("it failed! -> " + e);
},
{
"username": username,
"password": password
},
{"Accept" : "application/json"}
);
},
Then, in other methods, you can access the credentials for this user with this.credentials.
If there are other methods that could also be called first and need the credentials for them, then you can either make the credentials an argument for those also or you can create a .init() method that just establishes the credentials or you can make it an argument in the constructor for this object.
You will probably also have to fix this line:
calledUrl.post(...)
because calledUrl is a string and strings don't have a .post() method unless you're using some sort of 3rd party library that adds one.
I recommend that you read about scope in JavaScript. Without more explanation of what you are trying to do, I would try something like this pattern...
var app = {
baseapi: 'http://some.url.com'
/* assuming the api user/pass are different form the account trying to log in */
,api_username: ''
,api_key: ''
,username: ''
,userpass: ''
,get_creditialString: function() {
return '?api_username=' + this.api_username + '&api_key=' + this.api_key;
}
,init: function(){
// do something to prompt for username and password
this.username = 'myUserName';
this.userpass = 'supersecretpassword';
this.login();
}
,login: function() {
var calledUrl = this.baseapi + "user/login/" + this.get_credentialString();
calledUrl.post(
function (content) {
/*console.log("success" + JSON.stringify(content, null, 4));*/
},
function (e) {
console.log("it failed! -> " + e);
},
{
"username": this.username,
"password": this.userpass
},
{"Accept" : "application/json"}
);
}
}
app.init();