Creating email drafts using Gmail API on Ruby (google-api-ruby-client 0.9) - ruby

I am currently trying out Ruby and the Google API for Ruby and I am having difficulties accessing my Gmail account and creating drafts with it (via create_user_draft) using a Service Account. I have successfully authenticated my Service Account with the API (Access Tokens are being generated).
I can use it with the Google::Apis::DriveV2::DriveService::list_files but not on GmailV1 methods.
I use this code to authorise the service account and the scope https://www.googleapis.com/auth/gmail.compose
Authorisation
def authorise
#jsonKeyIo = self.loadCredentialsFile
gAuthDefaultCreds = ##gAuthDefaultCreds
serviceAccountCredentials = gAuthDefaultCreds.make_creds(
{json_key_io: #jsonKeyIo, scope: #scope})
#service.authorization = serviceAccountCredentials
#service.authorization.fetch_access_token!
end
It generates an access token with this format:
{"access_token"=>"ya29.access_token_codes_here", "token_type"=>"Bearer", "expires_in"=>3600}
Draft creator snippet
##gmail = Google::Apis::GmailV1
##service = ##gmail::GmailService.new
def createDraft(draftTitle, draftMessage)
draft = ##gmail::Draft.new
draft.message = draftMessage
#service.create_user_draft('my.email#gmail.com', draft)
end
It throws a failedPrecondition: Bad Request (Google::Apis::ClientError) with the above code but when I added options: {authorization: #accessToken } as a third parameter of create_user_draft, the exception becomes Unauthorized (Google::Apis::AuthorizationError).
Can you help me go to the right path? I find the API documentation, on the Google API sites and on the source code itself, lackluster.
UPDATE
I have read here that in order for Service Accounts to work on the Gmail API, a paid Google Apps account is required (normal #gmail.com accounts won't work) since on the Admin Console is where we should have to enable the scopes for our Service Accounts.
Currently trying out JWT Credentials login.

Related

Domain Shared Contacts API to create external contacts using python + service account (client library)

I want to use Domain Shared Contacts API as part of python client library.
The flow that I use in other cases:
Create credentials:
credentials = service_account.Credentials.from_service_account_info(....)
'Create' a service object which I execute later
service = googleapiclient.discovery.build(service_name, api_version,credentials=credentials)
In the case of Domain Shared Contacts API I dont know what service_name or api_version to use Google API Discovery Service if any.
Is it possible to create/update/remove external contacts for a domain using the contact OR people APIs?
If not, the process of utilising this API is to create requests in your codebase Using OAuth 2.0 for Web Server Applications to REST endpoints like:
https://www.google.com/m8/feeds/contacts/example.com/full
I managed to solve my problem only by using the Domain Shared Contacts API
I created a service account.
Prepared an authorized (HTTP/REST) API call using the example here. Special attention to Additional claims sub.
Since I am using Python, I installed pyjwt and used it to create and sign my JWT. As a secret, I used the service_account.private_key:
jwt.encode(jwt_claim_set, secret, algorithm="RS256")
Then depending on the use case (get contacts, create one..). I assigned the Google token (signed JWT) to my request.
Example:
endpoint = 'https://www.google.com/m8/feeds/contacts/{}/{}'.format(your_own_domain, projection_value)
headers = {"Authorization": "Bearer " + token}
gsuite_get_response = requests.get(endpoint, headers=headers)

Google adwords api how to authorize with service account impersonation

I'm trying to develop a ruby script to query the Google Adwords API using service account credentials. I know that the json credentials file works in another script which isn't ruby but I can't get past authorization in this ruby script. I can't find any examples using this pattern. I don't want to use OAuth2. I know this script is not well developed but I'm just trying to get the basics. What am I doing wrong here?
My company has a G Suite account and I have full administrator permissions.
Here is my code:
#!/usr/bin/ruby
require 'googleauth'
require 'fileutils'
require 'adwords_api'
API_VERSION = :v201809
scopes = ["https://www.googleapis.com/auth/adwords"]
credential_file = File.open('credentials/adwords-production.json')
prn = "adwords#mycompany.com"
authorizer = Google::Auth::ServiceAccountCredentials.make_creds(json_key_io: credential_file, scope: scopes, prn: prn)
def get_report_fields(report_type)
adwords = AdwordsApi::Api.new
report_def_srv = adwords.service(:ReportDefinitionService, API_VERSION)
# Get report fields.
fields = report_def_srv.get_report_fields(report_type)
if fields
puts "Report type '%s' contains the following fields:" % report_type
fields.each do |field|
puts ' - %s (%s)' % [field[:field_name], field[:field_type]]
puts ' := [%s]' % field[:enum_values].join(', ') if field[:enum_values]
end
end
end
begin
report_type = 'ACCOUNT_PERFORMANCE_REPORT'
get_report_fields(report_type)
end
From Google's docs here:
All AdWords API calls must be authorized through OAuth2. OAuth2 enables your AdWords API client app to access a user's Google Ads account without having to handle or store the user's login info.
It appears you have to use OAuth2 Authentication.
Further reading here confirms this:
Your app will need to access user data and contact other Google services on your behalf. Authentication via OAuth2 allows your app to operate on behalf of your account.
To enable your app to access the API, you need an OAuth2 client ID and client secret.
You say you have done this somewhere else where you were able to use a JSON file, so I looked into the source a little.
The source for the Ruby API is here and here. It appears there really is no other way to manage credentials, at least in the Ruby API. Looking here:
# Retrieve correct soap_header_handler.
#
# Args:
# - auth_handler: instance of an AdsCommon::Auth::BaseHandler subclass to
# handle authentication
# - version: intended API version
# - header_ns: header namespace
# - default_ns: default namespace
#
# Returns:
# - SOAP header handler
#
def soap_header_handler(auth_handler, version, header_ns, default_ns)
auth_method = #config.read('authentication.method', :OAUTH2)
handler_class = case auth_method
when :OAUTH2, :OAUTH2_SERVICE_ACCOUNT
AdsCommon::SavonHeaders::OAuthHeaderHandler
else
raise AdsCommon::Errors::AuthError,
"Unknown auth method: %s" % auth_method
end
return handler_class.new(#credential_handler, auth_handler, header_ns,
default_ns, version)
end
Notice that unless OAuth is used, it throws an error. There really is no other way to authenticate. Even if you managed to authenticate another way and try passing it to the credential manager, it will reject it.
Short answer: This is not possible in the Ruby API without hacking it some way.

Gmail API with service account

Under mygmailaccount#gmail.com in the API console I created a project and I am successfully using a service account for the Analytics API and Search Console API services. I've now enabled the Gmail V1 API and ran this code:
gmail_scopes = [
"https://mail.google.com/",
"https://www.googleapis.com/auth/gmail.compose",
"https://www.googleapis.com/auth/gmail.readonly",
"https://www.googleapis.com/auth/gmail.send",
"https://www.googleapis.com/auth/gmail.readonly"
]
gmail = Google::Apis::GmailV1::GmailService.new
gmail.authorization = Google::Auth.get_application_default(gmail_scopes)
This is the same code (with different scopes of course) that I use to authorize the Analytics and Search Console API.
However I get BAD REQUEST Failed Precondition errors when I run this:
gmail.get_user_profile("mygmailaccount#gmail.com")
Some googling tells me that is because the API is trying to access the non-existent inbox for the serviceaccount email address and that in the initiatlization process I need to include mygmailaccount#gmail.com so that the API service will use that inbox.
How do I do that?
I've tried the following '...' always being the email address
gmail.authorization = Google::Auth.get_application_default(gmail_scopes, username: '...')
gmail.authorization = Google::Auth.get_application_default(gmail_scopes, {username: '...'})
gmail.authorization.username = '...'
Service accounts cannot access #gmail.com mailboxes. You need to use an OAuth2 UserAuthorizer. See the Gmail API Ruby Quickstart guide for an example.

Possible to use Google service account with Sites APIs

I am using a service account to impersonate an admin user successfully with the Admin Directory and Google+ Domain APIs, but I am unable to figure out if Sites can make use of service accounts. Is it even possible? I am referring to the API that allows you to create and delete sites, not the Webmaster Tools API for managing the content and so on.
You can use Service Accounts with Sites API. All you need to do is use SignedJwtAssertionCredentials, impersonate the user from background and access the GData APIs.
Here is the code snippet:
credentials = SignedJwtAssertionCredentials(
SERVICE_ACCOUNT_EMAIL,
file(SERVICE_ACCOUNT_PEM_FILE_PATH, "rb").read(),
scope=["https://sites.google.com/feeds/"],
prn=userEmailYouWantToImpersonate
)
http = httplib2.Http()
http = credentials.authorize(http)
sitesClient = gdata.sites.client.SitesClient(source='mycompany', site='mySite', domain='mydomain')
sitesClient.auth_token = TokenFromOAuth2Creds(credentials)
feed = sitesClient.GetContentFeed()
class TokenFromOAuth2Creds:
def __init__(self, creds):
self.creds = creds
def modify_request(self, req):
if self.creds.access_token_expired or not self.creds.access_token:
self.creds.refresh(httplib2.Http())
self.creds.apply(req.headers)
Yes it is possible. Here's a code example :
SitesService sitesService = new SitesService("MyApplication");
final GoogleCredential credential = new GoogleCredential.Builder()
.setTransport(new NetHttpTransport()).setJsonFactory(new JacksonFactory())
.setServiceAccountId("XXXX.apps.googleusercontent.com")
.setServiceAccountScopes(Collections.singleton(SERVICE_SCOPES))
.setServiceAccountPrivateKeyFromP12File(new File("key.p12"))
.setServiceAccountUser("mysiteowner#domain.com").build();
sitesService.setOAuth2Credentials(credential);
The only thing you have to be cautious with, is that some SitesService methods might not be able to refresh the token properly, in which case you will have to catch the SessionExpiredException and refresh the Credential yourself.
Google Sites API pages says [1] that you can "Create new Sites or copy existing Sites" with it.
[1] https://developers.google.com/google-apps/sites

Google Group Settings API enabled for service accounts?

Most of the Google Management APIs seem to have been enabled for Service Accounts. For example, I can retrieve calendars like so:
string scope = Google.Apis.Calendar.v3.CalendarService.Scopes.Calendar.ToString().ToLower();
string scope_url = "https://www.googleapis.com/auth/" + scope;
string client_id = "999...#developer.gserviceaccount.com";
string key_file = #"\path\to\my-privatekey.p12";
string key_pass = "notasecret";
AuthorizationServerDescription desc = GoogleAuthenticationServer.Description;
X509Certificate2 key = new X509Certificate2(key_file, key_pass, X509KeyStorageFlags.Exportable);
AssertionFlowClient client = new AssertionFlowClient(desc, key) { ServiceAccountId = client_id, Scope = scope_url };
OAuth2Authenticator<AssertionFlowClient> auth = new OAuth2Authenticator<AssertionFlowClient>(client, AssertionFlowClient.GetState);
CalendarService service = new CalendarService(auth);
var x = service.Calendars.Get("calendarID#mydomain.com").Fetch();
However, identical code on the GroupssettingsService returns a 503 - Server Not Available. Does that mean service accounts can't be used with that API?
In a possibly related issue, the scope of the Groups Settings Service seems to be apps.groups.settings but if you call
GroupssettingsService.Scopes.AppsGroupsSettings.ToString().ToLower();
...you get appsgroupssettings instead, without the embedded periods.
Is there another method to use service accounts for the GroupssettingsService? Or any information on the correct scope string?
Many thanks.
I found this thread, and the most important part of the docs after some time. Posting so others don't waste their time in the future.
Your application must use OAuth 2.0 to authorize requests. No other authorization protocols are supported. If your application uses Google Sign-In, some aspects of authorization are handled for you.
See the "About authorization protocols" section of the docs
Why do you need to use a service account for this? You can use regular OAuth 2.0 authorization flows to get an authorization token from a Google Apps super admin user and use that:
https://developers.google.com/accounts/docs/OAuth2InstalledApp

Resources