I am looking for a method to find the unread message count of delegated mailboxes from any of the Google API's.
I am not sure IF it is possible, but it would help me develop a helping tool for a company using this for 1000+ users. A lot of delegation is going on, and I am eager to find a way to accomplish this.
But I might need some help, maybe from people closer with knowledge of the possibilities of the Admin SDK from Google.
I want to use Google Apps Script to collect the unread message count.
The Email Settings API allows you to see which delegations are in place.
It is not possible for a user to access the mailbox of another user who has delegated them access via IMAP, thus you can't authenticate as a user and check the delegated mailbox.
You should use OAuth 2.0 Service Accounts to authenticate to the mailboxes via IMAP.
Once authenticated you can select the Gmail "All Mail" folder (or Inbox if you only want count for Inbox). and do a Gmail search of "is:unread" to determine how many unread messages the user has.
FYI, my open-source app, GYB can do just this. There is a getting started guide for GYB. You'll also need to setup the service account. The command to get unread message count for all mail would be something like:
gyb --email delegated-mailbox#yourcompany.com --service-account your-service#account.com --action count --search "is:unread"
I got my answer from the GAS community on Google Plus, so credits to the posters there.
https://plus.google.com/106333172328928589411/posts/7g3Vu7iFZfb
Sergii:
Check out this gist that shows how to do 2-legged OAuth authentication in GAS https://gist.github.com/rcknr/c5be4eb80d821158c8ef.
Using 2 Legged Oauth you can get access to the ATOM feed of other users:
A piece of working code for it:
function gmail2lo(user) {
var OAUTH_CONSUMER_SECRET = 'secret';
var domain = 'domain'; //use the domain as key in apps panel
var username = 'user';
var xuser = username+'#'+domain;
var method = "GET";
var baseUrl = "https://mail.google.com/mail/feed/atom";
var timestamp = Math.round(new Date().getTime() / 1000);
var paramsJson;
var paramsOauth = {
oauth_consumer_key : domain,
oauth_nonce : timestamp,
oauth_signature_method : "HMAC-SHA1",
oauth_timestamp : timestamp,
oauth_version : "1.0",
'xoauth_requestor_id' : xuser
};
var paramsStringArray = [];
for (var k in paramsJson)
paramsStringArray.push(k + '=' + encodeURIComponent(paramsJson[k]));
var oauthStringArray = [];
for (var k in paramsOauth)
oauthStringArray.push(k + '=' + encodeURIComponent(paramsOauth[k]));
var paramsString = paramsStringArray.concat(oauthStringArray).sort().join('&');
var signatureBaseString = method +"&"+ encodeURIComponent(baseUrl) +"&"+ encodeURIComponent(paramsString);
var signatureBytes = Utilities.computeHmacSignature(Utilities.MacAlgorithm.HMAC_SHA_1, signatureBaseString, OAUTH_CONSUMER_SECRET+'&');
var signature = encodeURIComponent(Utilities.base64Encode(signatureBytes));
var xoauthString = 'OAuth ' + oauthStringArray.sort().slice(0,oauthStringArray.length-1).join(", ") + ', oauth_signature=' + signature;
var ooptions = {
headers : {authorization: xoauthString}
}
url = baseUrl;
url += "?" + paramsStringArray.join("&") + '&xoauth_requestor_id=' + encodeURIComponent(xuser);
var response = UrlFetchApp.fetch(url, ooptions).getContentText();
}
Related
I have an add-on that lets the user download my templates from my drive to theirs provided the add-on is installed in their sheet or doc.
The function that should make a copy is as follows
function createFileCopy(id){
var file = id.split('.');
var docName = DriveApp.getFilesByName(file[0]);
while (docName.hasNext()) {
var file = docName.next();
var fileId = file.getId();
var fileName = file.getName();
}
Logger.log(fileId);
var sheet = SpreadsheetApp.openById(SHEET_ID).getSheetByName(TEMPLATES_DATA);
var data = sheet.getRange(1, 9, sheet.getLastRow()-1, 1).getValues();
var pos = data.map(function (obj) { return obj[0]; }).indexOf(id);
if(pos > -1){
// var val = sheet.getRange("J" + (pos + 1)).getValue() + 1;
var title = sheet.getRange("A" + (pos + 1)).getValue();
// sheet.getRange("J" + (pos + 1)).setValue(val);
}
var newFile = DriveApp.getFileById(fileId).makeCopy('Copy of '+ title);
return {
title: newFile.getName(),
url: newFile.getUrl()
}
The problem is that when a user tries to make a copy he/she is getting an error `No item with the given ID could be found, or you do not have permission to access it.'
I have commented 2 lines, I thought that the issue was due to posting back the download increment to the original spreadsheet, but as it turns out this was not the only issue there.
It works fine inside the origin account BTW.
I have asked Add-on Advisor for help but was readdressed here instead.
Please help
When a user installs an add-on it executes under that user's account. As such they cannot access your template spreadsheets unless they are granted permission to do so.
If you are comfortable sharing files with your end-users, you can programmatically grant/revoke permission to your templates by using an Advanced Service. These advanced services are merely wrappers for their corresponding APIs so in this case you need to leverage the Drive API V2 documentation to figure out how to add/remove permissions. The following guides and reference should help:
Manage Sharing
Drive API V2 Permissions
If sharing files is undesirable, you can opt to use a Service Account. Service Accounts are a special kind of account that you can create from the GCP API console. You can grant the service account access to your templates and then use the service account to retrieve the spreadsheet template as a Spreadsheet resource object in JSON format using the spreadsheet API. You can then use this resource object to create a user-owned copy of the spreadsheet without explicitly sharing your template. You can find more info on service accounts by checking other threads here on stackoverflow.
This is a follow-up to a previous question/answer I posted (How to use the Google Email Settings API and the OAuth2 for Apps Script Library to set email signatures for users in a Google Apps domain), but I'm creating a new question since the Email Settings API has been deprecated and the process is significantly different now.
As the administrator of a G Suite domain, how do you use the Gmail API to programmatically set the email signatures of users in your domain through Google Apps Script?
This method uses the Gmail API, the OAuth2 for Apps Script library, and "Domain-wide Delegation of Authority", which is a way for G Suite admins to make API calls on behalf of users within their domain.
Step 1: Make sure the OAuth2 For Apps Script library is added to your project.
Step 2: Set up "Domain-Wide Delegation of Authority." There's a page here explaining how to do it for the Drive API, but it's pretty much the same for any Google API, including the Gmail API. Follow the steps on that page up to, and including, the "Delegate domain-wide authority to your service account" step.
Step 3: The code below includes how to set the signature after the previous steps are complete:
function setSignatureTest() {
var email = 'test#test.com';
var signature = 'test signature';
var test = setSignature(email, signature);
Logger.log('test result: ' + test);
}
function setSignature(email, signature) {
Logger.log('starting setSignature');
var signatureSetSuccessfully = false;
var service = getDomainWideDelegationService('Gmail: ', 'https://www.googleapis.com/auth/gmail.settings.basic', email);
if (!service.hasAccess()) {
Logger.log('failed to authenticate as user ' + email);
Logger.log(service.getLastError());
signatureSetSuccessfully = service.getLastError();
return signatureSetSuccessfully;
} else Logger.log('successfully authenticated as user ' + email);
var username = email.split("#")[0];
var resource = { signature: signature };
var requestBody = {};
requestBody.headers = {'Authorization': 'Bearer ' + service.getAccessToken()};
requestBody.contentType = "application/json";
requestBody.method = "PUT";
requestBody.payload = JSON.stringify(resource);
requestBody.muteHttpExceptions = false;
var emailForUrl = encodeURIComponent(email);
var url = 'https://www.googleapis.com/gmail/v1/users/me/settings/sendAs/' + emailForUrl;
var maxSetSignatureAttempts = 20;
var currentSetSignatureAttempts = 0;
do {
try {
currentSetSignatureAttempts++;
Logger.log('currentSetSignatureAttempts: ' + currentSetSignatureAttempts);
var setSignatureResponse = UrlFetchApp.fetch(url, requestBody);
Logger.log('setSignatureResponse on successful attempt:' + setSignatureResponse);
signatureSetSuccessfully = true;
break;
} catch(e) {
Logger.log('set signature failed attempt, waiting 3 seconds and re-trying');
Utilities.sleep(3000);
}
if (currentSetSignatureAttempts >= maxSetSignatureAttempts) {
Logger.log('exceeded ' + maxSetSignatureAttempts + ' set signature attempts, deleting user and ending script');
throw new Error('Something went wrong when setting their email signature.');
}
} while (!signatureSetSuccessfully);
return signatureSetSuccessfully;
}
// these two things are included in the .JSON file that you download when creating the service account and service account key
var OAUTH2_SERVICE_ACCOUNT_PRIVATE_KEY = '-----BEGIN PRIVATE KEY-----\nxxxxxxxxxxxxxxxxxxxxx\n-----END PRIVATE KEY-----\n';
var OAUTH2_SERVICE_ACCOUNT_CLIENT_EMAIL = 'xxxxxxxxxxxxxxxxxxxxx.iam.gserviceaccount.com';
function getDomainWideDelegationService(serviceName, scope, email) {
Logger.log('starting getDomainWideDelegationService for email: ' + email);
return OAuth2.createService(serviceName + email)
// Set the endpoint URL.
.setTokenUrl('https://accounts.google.com/o/oauth2/token')
// Set the private key and issuer.
.setPrivateKey(OAUTH2_SERVICE_ACCOUNT_PRIVATE_KEY)
.setIssuer(OAUTH2_SERVICE_ACCOUNT_CLIENT_EMAIL)
// Set the name of the user to impersonate. This will only work for
// Google Apps for Work/EDU accounts whose admin has setup domain-wide
// delegation:
// https://developers.google.com/identity/protocols/OAuth2ServiceAccount#delegatingauthority
.setSubject(email)
// Set the property store where authorized tokens should be persisted.
.setPropertyStore(PropertiesService.getScriptProperties())
// Set the scope. This must match one of the scopes configured during the
// setup of domain-wide delegation.
.setScope(scope);
}
Please note: the do-while loop with the maxSetSignatureAttempts and currentSetSignatureAttempts variables is not necessary. I added it because if you're trying to set signatures immediately after creating the Google account and assigning a G Suite license, sometimes the Gmail API returns an error as if the user wasn't created yet. That do-while loop basically waits 3 seconds if it gets an error, then tries again, up to x number of times. You shouldn't have that issue if you're setting signatures for existing users. Also, originally I just had a fixed 10-second sleep, but most of the time it didn't need to take that long, but other times it would still fail. So this loop is better than a fixed sleep amount.
I wish to authorize my webapp to create a folder in the user's appfolder to hold the app's data files.
To do this, I need to request the scope https://www.googleapis.com/auth/drive.appfolder
So far I have the following code:
var CLIENT_ID = '3941...';
var CLIENT_SECRET = 'DY_P...';
var SCRIPT_ID = '1XAF...';
var appfolder_scope = 'https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive.appfolder';
var redirectURI = 'https%3A%2F%2Fscript.google.com%2Fmacros%2Fd%2F'+ SCRIPT_ID + '%2Fusercallback';
var AuthEndpoint = 'https://accounts.google.com/o/oauth2/v2/auth';
function getCallbackURL(callbackFunction) {
var url = ScriptApp.getService().getUrl(); // Ends in /exec (for a web app)
url = url.slice(0, -4) + 'usercallback?state='; // Change /exec to /usercallback
var stateToken = ScriptApp.newStateToken()
.withMethod(callbackFunction)
.withTimeout(120)
.createToken();
return url + stateToken;
}
function generateAuthRequestURL() {
var AuthRequest = AuthEndpoint;
var Query = '?'
+ 'scope=profile%20' + appfolder_scope
+ '&state=' + getCallbackURL(cb)
+ '&redirect_uri=' + redirectURI
+ '&response_type=code'
+ '&client_id=' + CLIENT_ID
//+ '&login_hint=...%40gmail.com'
;
AuthRequest += Query;
Logger.log(AuthRequest);
return AuthRequest;
}
function cb(response) {
Logger.log(response);
}
When I click on the url generated by the generateAuthRequestURL() it takes me to the consent screen where I click allow. But then every time I get 'The state token is invalid or has expired'.
The webapp is published and I have tested both the exec and dev versions with the same result. I have also tried with and without a login_hint.
I have also experimented with Apps-Script-Folder-Library as well as gdrive-appdata. I couldn't get the first one to work, and the second one I don't even know how to use.
Based from this thread, make sure that you've put the project key into the related field. This error may occur when the key changed after making a copy of the script.
You can also check on this issue which suggested to ensure that you are using the right project key.
Here are some related forums which might also help:
Issues with OAuth 2.0 Library for Google Apps Scripts
OAuth2 support through library returns The state token is invalid or has expired. Please try again
I am working in a Google Apps Script web app using Contacts API v3, to access all user's contacts within the domain.
I have no problems so far with Contacts API requests, but I have no idea yet on how to get authorization across the domain to access any user's contacts (other than me).
I have tried OAuth domain key from cpanel - advanced tools, with no results so far. I got "Request failed for returned code 403 (Forbidden)" when trying with other users within the domain
Thanks in advance, Fausto
EDIT-1 (Mar.05) I'm closer now, but need a bit of help
I have combined 2LO (2-legged OAuth) with oauth_signature and signing request, but still getting Error 401 (Client Error)!!1. This is the sample code I am working with now.
function test_xOAuth() {
// OAUTH_CONSUMER_SECRET from GApps: control panel > advanced tools > Manage OAuth domain key
var domain = Session.getEffectiveUser().getEmail().split("#")[1];
var xuser = 'fausto#thexs.ca';
var method = "GET";
var baseUrl = "https://www.google.com/m8/feeds/groups/default/full";
var timestamp = getTimestamp();
var paramsJson = {
oauth_consumer_key : domain,
oauth_nonce : getNonce(timestamp),
oauth_signature_method : "HMAC-SHA1",
oauth_timestamp : timestamp,
oauth_version : "1.0"
};
var paramsStringArray = [];
for (var k in paramsJson) paramsStringArray.push(k + '="' + paramsJson[k] + '"');
var paramsString = paramsStringArray.join("&") + '&xoauth_requestor_id=' + xuser;
var signatureBaseString = method +"&"+ encodeURIComponent(baseUrl) +"&"+ encodeURIComponent(paramsString);
var signatureBytes = Utilities.computeHmacSha256Signature(signatureBaseString, OAUTH_CONSUMER_SECRET);
var signature = Utilities.base64Encode(signatureBytes);
var xoauthString = 'OAuth ' + paramsStringArray.join(",") + ',oauth_signature="' + signature + '"';
var options = {
method : method,
headers : {"Authorization" : xoauthString}
}
var url = baseUrl + '?xoauth_requestor_id=' + 'fausto#thexs.ca';
var response = UrlFetchApp.fetch(url, options);
var responseHeader = response.getHeaders();
var responseText = response.getContentText();
return HtmlService.createHtmlOutput(responseText);
}
var getTimestamp = function(){
return (Math.floor((new Date()).getTime() / 1000)).toString()
}
var getNonce = function(timestamp){
return timestamp + Math.floor( Math.random() * 100000000)
}
Thanks in advance for any help !! Fausto
First, you should properly URL-encode all parameters before you calculate signature.
Second, strip the quotes, you don't really need them, especially in the base string.
Third, you should use signature method you're specifying i.e. HMAC-SHA1. And last but not least, despite you're not using OAuth token you still need to include '&' in the encryption key.
Here's the code that works properly:
var OAUTH_CONSUMER_SECRET = 'you secret goes here';
var domain = 'domain.tld';
var username = 'test.user';
var xuser = encodeURIComponent(username+'#'+domain);
var method = "GET";
var baseUrl = "https://www.google.com/m8/feeds/groups/default/full";
var timestamp = Math.round(new Date().getTime() / 1000);
var paramsJson = {
oauth_consumer_key : domain,
oauth_nonce : timestamp,
oauth_signature_method : "HMAC-SHA1",
oauth_timestamp : timestamp,
oauth_version : "1.0"
};
var paramsStringArray = [];
for (var k in paramsJson) paramsStringArray.push(k + '=' + encodeURIComponent(paramsJson[k]));
var paramsString = paramsStringArray.join("&") + "&xoauth_requestor_id="+xuser;
var signatureBaseString = method +"&"+ encodeURIComponent(baseUrl) +"&"+ encodeURIComponent(paramsString);
var signatureBytes = Utilities.computeHmacSignature(Utilities.MacAlgorithm.HMAC_SHA_1, signatureBaseString, OAUTH_CONSUMER_SECRET+'&');
var signature = encodeURIComponent(Utilities.base64Encode(signatureBytes));
var xoauthString = 'OAuth ' + paramsStringArray.join(", ") + ', oauth_signature="' + signature + '"';
var options = {
method : method,
headers : {authorization: xoauthString}
}
var url = baseUrl + '?xoauth_requestor_id=' + xuser;
var response = UrlFetchApp.fetch(url, options);
I have not performed the access to contacts like you mention here but I have manage to perform a similar action to act as a domain user and access the users Google drive, The one limiting factor to this was that to be able to do what you are trying to do is that you have to be a super administrator for you Google domain and the access level i had to use was trough URL Requests and REST API, You might also what to have a look at Google Domain Shared Contacts API to be able to manage your shared contacts in a Google domain and leave everyone's personal contacts to be managed by them self
I have a Google Drive herarchichal folder and subfolders structure owned by me and I want to add it to the "My Drive" section in all users in our Google Apps for Business domain automatically.
How can I do this? I heard about Google Apps Script and AddToFolder function.
Please, can you help me?
Thanks in advance.
This is very easy to do if each user could just access a link and authorize a script (that you build) to do the job for them (place a shared folder in their root folder).
But if it's a lot of users, you are the admin of the domain, and you really want to do it all automatically without anyone doing a thing, it is possible but probably very difficult to do. I mean, you need to access the Drive API directly and set oAuth 2.0 to impersonate your users, because the Apps Script built-in DocsList API does not have this impersonation feature. If you're really going for it, take a look at this other question.
First, set up a simple web app. The Google App Script editor even has a template that gets you most of the way there.
Second, implement something like the following and call it from the handler function.
function addRequiredFolders() {
var root = DocsList.getRootFolder();
var folderIds = ["somefolderid", "anotherfolderid"];
folderIds.map( function(id) { DocsList.getFolderById(id).addToFolder(root) } );
}
I've tested a variant of this up to this point. The next step is to publish the Web App for your domain, and email it out to people or otherwise distribute it. I assume they will have the unpleasant step of needing to grant the web app permission to access their documents.
Right now I've implemented this feature using "Google Documents List API". I know that this API is deprecated but for now it works.
(the code is not finished)
(...)
//var user = Session.getActiveUser().getUserLoginId() OR
var user = e.parameter.user_email
var TB_folder = e.parameter.TB_folder
var TB_sub_folder = e.parameter.TB_sub_folder
var base = 'https://docs.google.com/feeds/';
var fetchArgs = googleOAuth_('docs', base);
fetchArgs.method = 'POST';
var rawXml = "<?xml version='1.0' encoding='UTF-8'?>" + "<entry xmlns='http://www.w3.org/2005/Atom'>"
+ "<category scheme='http://schemas.google.com/g/2005#kind' "
+ "term='http://schemas.google.com/docs/2007#folder'/>"
+ "<title>" + TB_folder +"</title>"
+ "</entry>";
fetchArgs.payload = rawXml;
fetchArgs.contentType = 'application/atom+xml';
fetchArgs.contentLength = 245;
// POST a https://docs.google.com/feeds/default/private/full
var url = base + user + '/private/full/?v=3&alt=json';
var content = UrlFetchApp.fetch(url, fetchArgs).getContentText()
var json = Utilities.jsonParse(content)
var folder = json.entry.gd$resourceId.$t // -> I get "folder:folder_id"
var id_folder = folder.split(':')[1]
var folder_created_by = json.entry.gd$lastModifiedBy.email.$t
var folder_owner = json.entry.author['0'].email.$t
(...)
Now, you have the folder ID and can use it to create another subfolder or a file...
You need this function :
//Google oAuth
function googleOAuth_(name,scope) {
var oAuthConfig = UrlFetchApp.addOAuthService(name);
oAuthConfig.setRequestTokenUrl("https://www.google.com/accounts/OAuthGetRequestToken?scope="+scope);
oAuthConfig.setAuthorizationUrl("https://www.google.com/accounts/OAuthAuthorizeToken");
oAuthConfig.setAccessTokenUrl("https://www.google.com/accounts/OAuthGetAccessToken");
oAuthConfig.setConsumerKey("XXXXXXXX");
oAuthConfig.setConsumerSecret("XXXXXXXXXXXXXXXXXXX");
//oAuthConfig.setConsumerKey("anonymous");
//oAuthConfig.setConsumerSecret("anonymous");
return {oAuthServiceName:name, oAuthUseToken:"always"};
}
You can create a file or add a user to a file (add a wriker).
Now I want to implement this functionality with " Drive API " . If someone has done it would be nice to get some help.
Sergi