Each user on the domain initiates a simple script we run for leave entitlements but we want the welcome message to be "Hi First Name," however the script doesn't seem to be able to fetch getGivenName() from getActiveUser() for a standard user.
Is there a way?
As noted in comments, and in Documentation, the UserManager Service is only accessible by Domain Administrators.
Here's an alternative. Domain Users may have themselves in their own contacts, so how about a best-effort attempt at finding themselves there?
/**
* Get current user's name, by accessing their contacts.
*
* #returns {String} First name (GivenName) if available,
* else FullName, or login ID (userName)
* if record not found in contacts.
*/
function getOwnName(){
var email = Session.getEffectiveUser().getEmail();
var self = ContactsApp.getContact(email);
// If user has themselves in their contacts, return their name
if (self) {
// Prefer given name, if that's available
var name = self.getGivenName();
// But we will settle for the full name
if (!name) name = self.getFullName();
return name;
}
// If they don't have themselves in Contacts, return the bald userName.
else {
var userName = Session.getEffectiveUser().getUsername();
return userName;
}
}
In Apps Script, I was able to get this information using the About REST API: https://developers.google.com/drive/v2/reference/about/get
var aboutData = DriveApp.About.get();
var userEmail = aboutData["user"]["emailAddress"];
var userDisplayName = aboutData["user"]["displayName"];
You can get a user name but first you have to create a domain user using the provisioning api. You can enable the API by logging in to your admin account, and select Domain settings and the User settings tab to select the checkbox enabling the Provisioning API. Read more about it here
You can then use
user = user.getgivenName()
Since the UserManager Service is only available to a Domain Administrator, you could publish a service as the administrator, that serves user's Given Names, and invoke that from the user-run script using the UrlFetchApp.
The UserName Service
Refer to the Content Service Documentation for the background information this is based upon.
The service accepts a parameter, userName, which it uses to perform a lookup as the administrator.
Paste the following code into a script, then deploy the script as a web service. This must be done by a Domain Administrator, as the service access the UserManager Service, but the script must be made accessible by all users in the domain. (Since I'm not an admin in my domain, I cannot access the UserManager, so I've included a domain-user-invokable line for testing, calling the getOwnName() function I described in my first answer.)
Remember to invoke doGet() from the debugger to go through the authorization before accessing the published service.
/**
* When invoked as a Web Service running as Domain Administrator,
* returns the GivenName of the requested user.
*
* #param {String} userName= Should be set to Session.getEffectiveUser().getUsername().
*/
function doGet(request) {
//return ContentService.createTextOutput(getOwnName()); // for testing by non-admin user
var userName = request.parameters.userName;
var givenName = UserManager.getUser(userName).getGivenName();
return ContentService.createTextOutput(givenName);
}
Invoke service using UrlFetch
Refer to Using External APIs for an explanation of how to make use of the service written in the previous section. I'll show how to access the service from another script, but remember that you can also do this from web pages within your domain.
We will use UrlFetchApp.fetch() to get our service to return the user's first name as a String.
The service was written to accept one parameter, userName, and we append this to the url, in the form userName=<string>.
With the URL built, we fetch(), then retrieve the name from the response. While this example returns just the name, you may choose to change the service to return the complete "Hello User" string.
function testService() {
var domain = "my-google-domain.com";
var scriptId = "Script ID of service";
var url = "https://script.google.com/a/macros/"+domain+"/s/"+scriptId+"/exec?"
+ "userName="+Session.getEffectiveUser().getUsername();
var response = UrlFetchApp.fetch(url);
var myName = response.getContentText();
debugger; // pause in debugger
}
Another potential way of getting the display name on a gmail account is to find a Draft email in the GmailApp, and get the From header, which may have the full name. Some drafts might be setup with no display name in gmail, in which case the From header will only be the email address, but typically the From header is in the format:
Firstname Lastname <email#domain.com>
This code should get you the string above from the first gmail Draft: (note this will probably throw an exception if there are no drafts, so check that first.)
GmailApp.getDrafts()[0].getMessage().getHeader("From")
Ref: https://developers.google.com/apps-script/reference/gmail/gmail-message#getHeader(String)
Ref: https://www.ietf.org/rfc/rfc2822.txt
Related
Objective:
as a google workspace domain admin for a school that uses google workspace education, I want to create a google apps script that given a google workspace user's email address (the current owner), the scritp should be able to get a list of all the user's folders and files in their google drive and then it should also be able to transfer the ownership of those folders and files to domain user and add the current owner as a viewer so they can only see the folders/files but can't modify them in any way.
things I tried:
DriveApp can access files/folders and change the ownership of the file/folder but only if you are the owner, and I want to do this as the domain admin, regardless which user owns the google drive and respective files/folders.
Drive API, seems to do the same as DriveApp as far you're the owner, I couldn't figure out how to give Drive API admin permissions so I can see every domain user google drive file list, if that's even possible.
GAM advance: I found this as management tool, I set it and it migh do what I need but it's bit complex for me, plus I was really hoping to be able to build the tool myself.
What worked halfway:
I found this: https://github.com/googleworkspace/apps-script-oauth2#using-service-accounts which refers to using a service account. It took a while but I manage to get a list of items that exist on a user's google drive with the script below. but I can't figure out how to access those files/folders so I can change the ownership or set viewers on them. I think I read that the service account will only give me read-only access so I'm doubting this is even possible.
Here's what I got so far:
function main(){
// Private key and client email of the service account.
var key = getJsonKey()
var clientEmail = 'service_account_email_setup_in_google_dev_console';
// Email address of the user to impersonate.
var userEmail = 'a_regular_domain_user#my_google_workspace_domain.com';
try{
var drive = getDriveService_(key,userEmail,clientEmail);
if (drive.hasAccess()) {
// this code gets me a json response with items that list id's and urls and other
//file metadata of the
// files that belongs to the domain user, this is as far as i got.
var url = 'https://www.googleapis.com/drive/v2/files';
var response = UrlFetchApp.fetch(url, {
headers: {
Authorization: 'Bearer ' + drive.getAccessToken()
}
});
var result = JSON.parse(response.getContentText());
//the following code returns a fileid in the user's google
//drive not shared with the admin
var fileid = JSON.stringify(result.items[0].id)
Logger.log(fileid);
//but the following code returns an error indicating that the
//file is not found (in reality it's not accessible by the
//admin account)
var file = Drive.Files.get(fileid);
//access a list of items and as I traverse it I'd like to
//change the ownership and
//add the the current user as a file viewer
//??
} else {
Logger.log(drive.getLastError());
}
}catch (e){
Logger.log(e)
}
}
// Load the JSON key file with private key for service account
function getJsonKey(){
var keyFile = DriveApp.getFileById("json_fileid_in_drive_obtained_from_googledevcons");
var key = JSON.parse(keyFile.getBlob().getDataAsString()).private_key;
return key
}
function reset() {
getDriveService__().reset();
}
//get the google drive from the domain user's email address
function getDriveService_(key,userEmail,clientEmail) {
return OAuth2.createService('GoogleDrive:' + userEmail)
.setTokenUrl('https://oauth2.googleapis.com/token')
.setPrivateKey(key)
.setIssuer(clientEmail)
.setSubject(userEmail)
.setPropertyStore(PropertiesService.getUserProperties())
.setCache(CacheService.getUserCache())
.setScope('https://www.googleapis.com/auth/drive');
}
Any help is appreciated :)
You are going in the right direction, the only part you are missing currently is setting up Domain Wide Delegation this will allow you to impersonate the users in your domain so you can make the changes on behalf of them by granting the service account permissions through the above mentioned DWD.
Since you have already created the Oauth2Service you will just need to send the user to impersonate through the OauthParams:
const oauthParams = {
serviceName: 'Nameofyourservice',
serviceAccount,
scopes: ['https://www.googleapis.com/auth/appsmarketplace.license', 'https://www.googleapis.com/auth/userinfo.email', 'https://mail.google.com', 'https://www.googleapis.com/auth/iam'],
userToImpersonate: 'usertoimpersonate#test.com',
};
The scopes were from the Marketplace API as an example.
I'm trying to get a user's name from their email and we are all on the same business domain. I saw a post here detailing how to do it if you want to pull from the contacts list. The problem is when I try it myself, it knows that there is contact for me, but it returns all of the values as null. If I use another contact email, then it pulls the info just fine.
The link says that there should be another way to do it but you need admin privileges. I can get that, but all of the links to the usermanager documentation are broken. Also, searching usermanager doesn't come up with anything on Google's developer site.
var email = Session.getActiveUser().getEmail();
Browser.msgBox(email)
var self = ContactsApp.getContact(email);
var name = self.getFullName();
With admin rights you can use the directory api. Do not forget to enhable the admin api in your appscript (Ressources > Advanced Google services) and also in the Developers Console.
function getUserName(email){
var result = AdminDirectory.Users.get(email, {fields:'name'});
var fullname = result.name.fullName;
Logger.log(fullname);
return fullname;
}
If parsing from the text, you can use like below,
var email = "john.doe#email.com";
var name = email.substring(0, email.lastIndexOf("#"));
console.log( name ); // john.doe
Hopefully help
We want to get data from our API into Google Sheets. Our API requires authentication. Our tentative architecture has the user entering their credentials in a ModalDialog, then we hit our authentication endpoint which returns a session token which is saved. Subsequently the user makes customfunction calls in which we make calls to our API data end-point and include the authentication token.
My question -
How do I get the authentication token into the scope of the customfunction? It seems like PropertiesService.getUserProperties should do this but the values we set in the function that gets the credential token are not visible in the customfunction.
function saveCredentials(action){
var username = action['username'],
password = action['password'];
var response = UrFetchApp.fetch('https://www.foo.com/account/logon/, {method: 'post', payload: {email: action, password: password}});
var token = response.getAllHeaders()['Set-Cookie'];
PropertiesService.getUserProperties().setProperty('token', token);}
/**
* #param {parameter1}
* #param {parameter2}
* #customfunction
*/
function getDataFromFooAPI(parameter1, parameter2){
var token = PropertiesService.getUserProperties().getProperty('token') // expecting this to be the value set in saveCredentials but it is not.
var url = 'https://www.foo.com/api/data/' + parameter1 + '/' + parameter2;
var response = UrlFetchApp.fetch(url, {'headers' : {'token' : token}};
}
Does the context in which the customfunction executes references a different PropertiesService than the rest of the script? Are we trying to do the impossible?
There are some documented limitations with custom functions, notably that they don't have access to personal information. They can access script and document properties, but not user properties. Custom functions should behave consistently for all users who have access to the sheet and operate without authorization. Relying on user properties would make that virtually impossible.
If you can use document properties instead that should work.
I am not sure what to use as my Redirect URI. Bitbucket has a space for "Callback URL" which I am assuming is where it is entered. Anyone else have this problem/know how to use Redirect URI in this situation? I am setting up my service object below.
function getBitbucketServiceOAuth2()
{
// Create a new service with the given name. The name will be used when
// persisting the authorized token, so ensure it is unique within the
// scope of the property store.
return OAuth2.createService('bitbucket')
// Set the endpoint URLs, which are the same for all Google services.
.setAuthorizationBaseUrl('https://bitbucket.org/site/oauth2/authorize')
.setTokenUrl('https://bitbucket.org/site/oauth2/access_token')
// Set the client ID and secret, from the Google Developers Console.
.setClientId('WL6MQbGku7axd5kqun')
.setClientSecret('jTRmqm5ug9fEUMAxeCQRx95uQz8LCevW')
// Set the name of the callback function in the script referenced
// above that should be invoked to complete the OAuth flow.
.setCallbackFunction('authCallback')
// Set the property store where authorized tokens should be persisted.
.setPropertyStore(PropertiesService.getUserProperties())
// Set the scopes to request (space-separated for Google services).
// .setScope('https://www.googleapis.com/auth/drive')
// Below are Google-specific OAuth2 parameters.
// Sets the login hint, which will prevent the account chooser screen
// from being shown to users logged in with multiple accounts.
//.setParam('login_hint', Session.getActiveUser().getEmail())
// Requests offline access.
//.setParam('access_type', 'offline')
// Forces the approval prompt every time. This is useful for testing,
// but not desirable in a production application.
.setParam('approval_prompt', 'force');
}
And then getting the Authorize url below.
var authorizationUrl = service.getAuthorizationUrl();
var template = HtmlService.createTemplate(
'Authorize. ' +
'Reopen the sidebar when the authorization is complete.');
template.authorizationUrl = authorizationUrl;
var page = template.evaluate();
SpreadsheetApp.getUi().showSidebar(page);
Any help would be great!
When you get the error 403 (or equivalent), you will see "read more" link. Click on that and copy paste the expected link it gives.
Now, use this link to generate new credentials in redirect uri.
This flow worked for me. Hope it works for you too.
I've been using RSS2Email to turn Gmail into Google Reader.
I'd like to create a Google Apps Script to do this instead, to get two advantages:
The scheduling would be handled by Google Apps Script. (No need to have a dedicated computer to run the script.)
The resulting emails would be labeled per-feed, nicely organizing things.
Using Google Apps Script would be the same implementation spirit as e.g. Gmail Snooze.
I understand how to fetch the feeds. Where I'm stuck is how to create the emails using a specific label. The approaches I've investigated so far:
The Gmail Service for Google Apps Scripts doesn't allow this, at least not obviously.
GmailApp.sendEmail doesn't let you specify a label. Which makes sense since this is for generic email sending. But ...
GmailApp.sendEmail returns GmailApp, not something you could use to ID the message and change its label later.
The service also doesn't appear to let you create Gmail filters programatically, ruling out another possible way to tackle this.
The Gmail Migration API would be perfect to do this -- but it doesn't work for normal, consumer Gmail accounts. Gah.
Google's IMAP extensions might allow this, but it's unclear to me.
I suppose I could use 1, putting some UID in the Subject that I could use to look up the delivered message, to add the label (and hopefully un-uglify the Subject by removing the UID). But that seems a bit kludgy.
Using 3, IMAP extensions, seems possibly less kludgy, although probably much more work to code and test.
Any recommendations among these? Is there some other API or strategy?
The Google Apps Script GmailApp API does not give a way to add a label to a message as you send it, something you can do from the UI. This appears as Issue 1859 in the issue tracker, visit and star it for updates.
A work-around would be to take advantage of the thread apis. If the message is blind-copied (bcc'd) to the sender, it will also appear as a new message in their inbox, where we can use getInboxThreads() to find it. Add the label to this thread, and optionally mark it read then archive it.
Code also available as a gist.
/**
* An alternative to GmailApp.sendEmail(), which applies a
* label to the message thread in the sender's account.
*
* Sends an email message with optional arguments. The email can
* contain plain text or an HTML body. The size of the email
* (including headers, but excluding attachments) may not
* exceed 20KB.
*
* #param {String} recipient the addresses of the recipient
* #param {String} subject the subject line
* #param {String} body the body of the email
* #param {Object} options a JavaScript object that specifies
* advanced parameters, as documented
* for GmailApp.sendEmail()
* #param {String} label the label to be applied
*/
function sendAndLabel(recipient, subject, body, options, label) {
var sender = Session.getActiveUser().getEmail();
// Add sender to bcc list
if (options.bcc) {
options.bcc = options.bcc.split(',').concat(sender).join(',');
}
else {
options.bcc = sender;
}
GmailApp.sendEmail( recipient, subject, body, options )
// Check if label already exists, create if it doesn't
var newLabel = GmailApp.getUserLabelByName(label);
if (!newLabel) newLabel = GmailApp.createLabel(label);
// Look for our new message in inbox threads
Utilities.sleep(2000); // Wait for message to be received
var inboxThreads = GmailApp.getInboxThreads();
for (var t = 0; t < inboxThreads.length; t++) {
var foundSubject = inboxThreads[t].getFirstMessageSubject();
var numLabels = inboxThreads[t].getLabels().length; // Could add more criteria
if (foundSubject === subject && numLabels === 0) {
// Found our thread - label it
inboxThreads[t].addLabel(newLabel)
.markRead()
.moveToArchive();
break;
}
}
}
Example of use:
function test_sendAndLabel() {
var recipient = Session.getActiveUser().getEmail();
var subject = "Test Labelling";
var body = "This email is testing message labelling.";
// var options = {bcc:"someone#example.com"};
var options = {};
var label = "LabelTest";
sendAndLabel(recipient, subject, body, options, label);
}
yes, IMAP is the right way to go. You can find some third party IMAP lib to easy your job.
the command to add lable is : STORE 123 +X-GM-LABELS (yourLabel)
The official docs about Gmail label :
https://developers.google.com/google-apps/gmail/imap_extensions#access_to_gmail_labels_x-gm-labels
It would be convenient for GmailApp.sendEmail() to return a GmailMessage object so we can get its id and then assign a label. It doesn't, but you can use this workaround:
Create the message as a draft using GmailApp.createDraft()
Send the draft message using GmailDraft.send(). This method does return a GmailMessage object.
Assign the label (either assign it to the whole thread with GmailMessage.getThread().addLabel() or if you want to assign it to the individual message, you'll have to use the Advanced Gmail service as described here).