Page 1 of 2

OAuth 2, gSuite.gmail, Mail Plugin

PostPosted: Wed Dec 25, 2019 10:05 pm
by doctorface
I have been using the mail plugin in servoy to send outgoing email via gSuite/gmail. Currently I set the gSuite/gmail account to allow less secure apps and then authenticate with a gSuite/gmail username and password. This has worked great for many years.

gSuite will be turning off access to less secure apps (LSA) in two steps as follows
June 15, 2020 - Users who try to connect to an LSA for the first time will no longer be able to do so. This includes third-party apps that allow password-only access to Google calendars, contacts, and email via protocols such as CalDAV, CardDAV and IMAP. Users who have connected to LSAs prior to this date will be able to continue using them until usage of all LSAs is turned off.
February 15, 2021 - Access to LSAs will be turned off for all G Suite accounts.

Going forward authentication will have to be done with OAuth 2.0. I am wondering if the mail plug in will be modified in the near future to allow OAuth authentication. Alternatively if someone has a working example of how the current email plugin can be used with OAuth 2.0 (perhaps combining features of the OAuth plugin nd the Mail Plugin) to send email via gSuite or gmail that wouid be most appreciated.

Sincerely,

John McCann

Re: OAuth 2, gSuite.gmail, Mail Plugin

PostPosted: Thu Dec 26, 2019 11:34 am
by patrick
From what I understand it comes down to this: you will need an oauth refresh token that is used as password and slightly different connection properties for this. It should work with these changes out of the box. Obviously, you will have to go through all the steps necessary for any oauth flow (setup your application for oauth, get a refresh token etc). I will have a look at that in January and see whether the plugin needs changes (the Java Mail libraries may need to be updated) and keep you updated.

Re: OAuth 2, gSuite.gmail, Mail Plugin

PostPosted: Sat Dec 28, 2019 9:13 pm
by Harjo
👍🏻+1

Re: OAuth 2, gSuite.gmail, Mail Plugin

PostPosted: Mon Dec 30, 2019 12:23 am
by doctorface
Thanks so much Patrick. I look forward to you follow up post in early in 2020.

John McCann

Re: OAuth 2, gSuite.gmail, Mail Plugin

PostPosted: Fri Jan 03, 2020 2:56 pm
by Harjo
The same is the case for Office 365: https://developer.microsoft.com/en-us/o ... customers/

Today, we are announcing that on October 13th, 2020 we will stop supporting and retire Basic Authentication for Exchange Active Sync (EAS), Post Office Protocol (POP), Internet Message Access Protocol (IMAP), and Remote PowerShell (RPS) in Exchange Online. This means that new or existing applications using one or more of these API’s/protocols will not be able to use Basic Authentication when connecting to Office 365 mailboxes or endpoints and will need to update how they authenticate.


So for the mail and mailPro plugin AND the Exchange plugin, will stop working after 13 october.
They all can handle mail ONLY with username & password, which will not be supported later this year. :shock:

here is some info on how to implement oAuth for javamail: https://javaee.github.io/javamail/OAuth2

Re: OAuth 2, gSuite.gmail, Mail Plugin

PostPosted: Tue Jan 07, 2020 1:59 pm
by patrick
A short update on this. I have tested it with the mail plugin and the mail pro plugin.

The first part is OAuth standard procedure:

1. you need to register your app with google via https://console.cloud.google.com/
2. specifically, you need an OAuth 2.0 client ID, which can be created in "API & Services" / "Credentials"
3. That will give you a client ID and secret

Once that is done, you need your user to authorize your app to use GMail, which can be done via the oauth plugin. The steps are roughly these:

Code: Select all
plugins.oauth.serviceBuilder(CLIENT_ID)
      .clientSecret(CLIENT_SECRET)
      .callback(getAccessTokenCallback, 30)
      .scope('https://mail.google.com/')
      .build(plugins.oauth.OAuthProviders.GOOGLE)


CLIENT_ID and CLIENT_SECRET in the code above are the ones you received when registering your app.

In the callback method (getAccessTokenCallback) you will then get a plugins.oauth.OAuthService object when everything went OK and the user granted the rights you asked for. That object has a getAccessToken() method that returns the access token used as the password for sending emails. This one you will have to store for that user.

Once you climbed over that hurdle, you can do this with the MailPro plugin:

Code: Select all
var smtpAcc = plugins.MailPro.SMTPAccount('smtp.gmail.com', 'from@gmail.com', accessToken);
smtpAcc.requiresAuthentication = true;
smtpAcc.useTLS = true;
smtpAcc.addSmtpProperty('mail.smtp.auth.mechanisms', 'XOAUTH2');
var msg = smtpAcc.createMessage('to@recipient.com', 'from@gmail.com', 'A test', 'My message')
var success = smtpAcc.sendMessage(msg);


For the standard mail plugin, something like this should work:

Code: Select all
var props = [];
props.push("mail.smtp.ssl.enable=true");
props.push("mail.smtp.auth.mechanisms=XOAUTH2");
props.push("mail.smtp.host=smtp.gmail.com");
props.push("mail.smtp.auth=true");
props.push("mail.smtp.user=from@gmail.com");
props.push("mail.smtp.password=" + accessToken);
plugins.mail.sendMail('to@recipient.com', 'from@gmail.com', 'A test', 'My message', null, null, null, props);


I hope this helps. Let me know your findings.

Re: OAuth 2, gSuite.gmail, Mail Plugin

PostPosted: Tue Jan 07, 2020 2:08 pm
by patrick
One more remark: when testing this, you usually run off localhost:8080, which does not match any URL you registered with Google. To overcome this, you can edit the hosts file on windows and add something like

127.0.0.1 test.ruhsert.de

when test.ruhsert.de is the URL you entered when creating your client credentials. Then you need to run your NG Client from test.ruhsert.de:8080 instead of localhost:8080 and it should work (although showing a safety warning from google).

Re: OAuth 2, gSuite.gmail, Mail Plugin

PostPosted: Mon May 11, 2020 1:21 pm
by swingman
Hi all,

I'm following Patricks sample and I'm struggling to get it to work...

I'm getting my refresh token back from Google and trying to use it instead of a password.

Or do I need to go back to Google to exchange it for an access token?
Or does the mail plugin do this for me?

Has anyone successfully sent email via google using oauth2?
There may be something really simple that I'm missing...

Re: OAuth 2, gSuite.gmail, Mail Plugin

PostPosted: Mon May 11, 2020 4:08 pm
by patrick
Has anyone successfully sent email via google using oauth2?
Yes, I have when I tested that and posted the sample code above.

What goes wrong in your case? What error do you get? What plugin are you trying it with? Are you setting this mail.smtp.auth.mechanisms=XOAUTH2?

Re: OAuth 2, gSuite.gmail, Mail Plugin

PostPosted: Mon May 11, 2020 4:41 pm
by swingman
Hi Patrick,

I'm using Servoy's mail plugin.

Code: Select all
   var properties = [];
   properties.push("mail.smtp.ssl.enable=true");
   properties.push("mail.smtp.auth=true");
   properties.push("mail.smtp.auth.mechanisms=XOAUTH2");
   properties.push("mail.smtp.host=smtp.gmail.com");
   properties.push("mail.smtp.username=" + globals.myEmailAddress);
   properties.push("mail.smtp.password=" + globals.myGmailPassword); //actually an Oauth2 token
    //properties.push('mail.smtp.port=587');
    //properties.push('mail.smtp.starttls.enable=true');
   //
   
   var success = plugins.mail.sendMail(to, from, subject, msg,null,null,null,properties);


with this I get an error

Code: Select all
Failed to send mail to<<recipient> 334 eyJzdGF0dXMiOiI0MDAiLCJzY2hlbWVzIjoiQmVhcmVyIiwic2NvcGUiOiJodHRwczovL21haWwuZ29vZ2xlLmNvbS8ifQ==


334 is an odd SMTP error.

I'm wondering whether the token I get back from Google is bad.

This my code for obtaining the token (may this is where I'm going wrong:)

Code: Select all
/**
* @properties={typeid:24,uuid:"F90B3954-3412-42F5-8B80-DBB1D5128E55"}
*/
function authorise_sending_email() {
   plugins.oauth.serviceBuilder(CLIENT_ID).clientSecret(CLIENT_SECRET).deeplink('google_oauth_callback').callback(google_oauth_callback, 30).scope('https://mail.google.com/').build(plugins.oauth.OAuthProviders.GOOGLE);
}

/**
*
* @properties={typeid:24,uuid:"7836A708-1111-4E6A-8B88-015BC5E18F80"}
*/
function google_oauth_callback(result, auth_outcome) {
   //plugins.dialogs.showInfoDialog('google_oauth_callback ' + result,'OK');

   //plugins.dialogs.showInfoDialog(auth_outcome,'OK');
   //plugins.dialogs.showInfoDialog(auth_outcome['code'],'OK');
   //plugins.dialogs.showInfoDialog(auth_outcome['scope'],'OK');

   var token = auth_outcome['code'];
   if (token) {
      var query = 'SELECT user_id FROM users WHERE user_name ILIKE ?';
      var args = [security.getUserName()];
      /** @type {JSFoundSet<db:/dbname/users>} */
      var fs = databaseManager.getFoundSet('dbname', 'users');
      if (fs.loadRecords(query, args) && fs.getSize() == 1) {
         var user = fs.getRecord(1);
         user.google_api_token = token;
         databaseManager.saveData();
         plugins.dialogs.showInfoDialog('Gmail','Successfully saved token for sending emails.', 'OK');
         forms.main.controller.show();
      }
      refreshUserEmailProperties();
   }
}


I use auth_outcome['code'] to get the token. If I use auth_outcome.getCode() I get null.
Also Google seems to call back twice. My deeplink comes back right away, while the google_oauth_callback fails after 30 seconds.

Re: OAuth 2, gSuite.gmail, Mail Plugin

PostPosted: Mon May 11, 2020 4:51 pm
by patrick
I can't test this right now, but in my post above I say "getAccessToken()" should be used. If you type your auth_outcome parameter to plugins.oauth.OAuthService, you should see that method.

Re: OAuth 2, gSuite.gmail, Mail Plugin

PostPosted: Tue May 12, 2020 8:39 am
by swingman
Hi Patrick,

I have taken a look at the Google OAuth Playground

https://developers.google.com/oauthplayground/

and what I get back when I check auth_outcome['code'] is in the exact same format as an authorisation code. And I'm trying to use it as a password which may be why I can't send emails.
getAccessToken() does not return anything, even when I typecast the auth_outcome.

Is there a Servoy-way to fetch the access_token from Google, as in step 2 of the playground: 'Exchange authorisation code for tokens' or does Servoy do this for me?

It would be very good to see some working sample code for this.

Re: OAuth 2, gSuite.gmail, Mail Plugin

PostPosted: Thu May 14, 2020 5:14 pm
by swingman
Hi Patrick,

I appear to have solved the problem for now by using the way described in the webinar March 2019, using

Code: Select all
   service = plugins.oauth.getOAuthService(plugins.oauth.OAuthProviders.GOOGLE,CLIENT_ID,CLIENT_SECRET,'https://mail.google.com/',null,'on_authorize');
   application.showURL(service.getAuthorizationURL());


I did not try this a first because of the depression notice:
Creates an OAuth service that can be used to obtain an access token and access protected data. This method will be deprecated in the following versions, the preferred way is plugins.oauth.serviceBuilder with a callback function.

Re: OAuth 2, gSuite.gmail, Mail Plugin

PostPosted: Fri May 15, 2020 9:18 am
by emera
patrick wrote:A short update on this. I have tested it with the mail plugin and the mail pro plugin.

The first part is OAuth standard procedure:

1. you need to register your app with google via https://console.cloud.google.com/
2. specifically, you need an OAuth 2.0 client ID, which can be created in "API & Services" / "Credentials"
3. That will give you a client ID and secret

Once that is done, you need your user to authorize your app to use GMail, which can be done via the oauth plugin. The steps are roughly these:

Code: Select all
plugins.oauth.serviceBuilder(CLIENT_ID)
      .clientSecret(CLIENT_SECRET)
      .callback(getAccessTokenCallback, 30)
      .scope('https://mail.google.com/')
      .build(plugins.oauth.OAuthProviders.GOOGLE)


CLIENT_ID and CLIENT_SECRET in the code above are the ones you received when registering your app.

In the callback method (getAccessTokenCallback) you will then get a plugins.oauth.OAuthService object when everything went OK and the user granted the rights you asked for. That object has a getAccessToken() method that returns the access token used as the password for sending emails. This one you will have to store for that user.

Once you climbed over that hurdle, you can do this with the MailPro plugin:

Code: Select all
var smtpAcc = plugins.MailPro.SMTPAccount('smtp.gmail.com', 'from@gmail.com', accessToken);
smtpAcc.requiresAuthentication = true;
smtpAcc.useTLS = true;
smtpAcc.addSmtpProperty('mail.smtp.auth.mechanisms', 'XOAUTH2');
var msg = smtpAcc.createMessage('to@recipient.com', 'from@gmail.com', 'A test', 'My message')
var success = smtpAcc.sendMessage(msg);


For the standard mail plugin, something like this should work:

Code: Select all
var props = [];
props.push("mail.smtp.ssl.enable=true");
props.push("mail.smtp.auth.mechanisms=XOAUTH2");
props.push("mail.smtp.host=smtp.gmail.com");
props.push("mail.smtp.auth=true");
props.push("mail.smtp.user=from@gmail.com");
props.push("mail.smtp.password=" + accessToken);
plugins.mail.sendMail('to@recipient.com', 'from@gmail.com', 'A test', 'My message', null, null, null, props);


I hope this helps. Let me know your findings.


If you are using the serviceBuilder then the auth_outcome in your callback method is a fully configured service in case of success (so if result is true).
The service object doesn't have a getCode method, but a getAccessToken() which returns the token you need to sign the requests.
However, I suggest you to use the createRequest or create(Get/Post/...)Request methods on the service because those also take care of the signing or refreshing the token in case it's expired.
You can see more samples on how to use those methods on the OAuthService in the developer.

Code: Select all
function someFunction() {
          plugins.oauth.serviceBuilder("0lqd1s0aw...")      //client/application ID
         .clientSecret("bIk6163KHi...")      //client secret
         .defaultScope("email")            //ask permission to get the user email
          .state("secret123337")            //anti forgery session state, required by the Facebook api
         .deeplink("deeplink_method")      //OPTIONAL deeplink method name or last part of your redirect URL, see docs
                               //if missing, a global method with the name 'deeplink_svy_oauth' will be generated
          .callback(callbackFunc, 30)          //see function below, timeout is 30 seconds
          .build(plugins.oauth.OAuthProviders.FACEBOOK);
}
   
function callbackFunc(result, auth_outcome) {
     if (result)
     {
           //SUCCESS
           var service = auth_outcome;
          application.output(service.getAccessToken()); // this is not really needed, but maybe you want to see what the access token is

                       //ACCESS SOME PROTECTED RESOURCE
          var response = service.executeGetRequest("https://graph.facebook.com/v2.11/me");
          if (response.getCode() == 200) {
              application.output(response.getBody());
             var json = response.getAsJSON();
             application.output("Name is "+json.name);
          }
         else {
          application.output('ERROR http status '+response.getCode());
         }
       else {
         //ERROR
         application.output("ERROR "+auth_outcome);//could not get access token, request timed out, etc..
      }
     }
}

Re: OAuth 2, gSuite.gmail, Mail Plugin

PostPosted: Fri May 15, 2020 9:21 am
by emera
Google also provides a REST service to access a users's emails with oauth:
https://developers.google.com/gmail/api ... ssages/get