Google Calendar API – Impersonating Users

devops, Google API, ruby

WIU is in the process of moving from Zimbra to Google Apps for Education. I’ve been working on the code to migrate mail and calendar data and had a really hard time getting calendar impersonation working so I could create calendars and import events for people.

Much of my confusion was because most of the published information and samples you can find on the Internet reference the previous Google API (v0.8). The newer API (v0.9) works completely differently. If I had simply followed the published instructions about using the “Default Credentials”, and put that together with the instructions about making an authorized API call, this would have gone much easier.

To hopefully help someone else out there who may be struggling with this, here is what worked for me.

Create a Service Account

  1. Create a service account in the Google Developer Console and save the JSON file. You are going to want to deploy the JSON file securely with your application.
  2. Make sure your service account has access to the correct APIs. It should probably have Domain-wide-Delegation set and you must enable the scope of access for your client ID. Check out this page about delegating domain-wide authority.
  3. Create a .env file in the root of your application and store the GOOGLE_APPLICATION_CREDENTIALS path there. This is where the JSON file from step one will reside in your application.
# ~/.env
GOOGLE_APPLICATION_CREDENTIAL=config/Google_for_Org-1234567890.json

You can use the dotenv gem, or some other method, to retrieve the GOOGLE_APPLICATION_CREDENTIALS in your app. See below…

Create a Google::ServiceAccount Class in Your Application

Something like this…

# ~/lib/google.rb
require 'googleauth'

class Google::ServiceAccount

  def initialize(mail: nil, scopes: )
    @mail = mail
    @scopes = scopes
  end

  def user_auth
    auth = Google::Auth.get_application_default(@scopes)
    user = auth.dup
    user.sub = @mail
    user.fetch_access_token!
    user
  end

private
  attr_reader :mail

  def scopes
    Array(@scopes).join(" ")
  end
end

Main Program

In your main program or another class you can do something like this. In my example here I am just getting a list of existing calendars in the individual’s Google account.

# ~/main.rb
require 'dotenv'
require 'google/apis/calendar_v3'

require_relative 'lib/google'

APPLICATION_NAME = 'Zimbra Migration'.freeze

def gcal_list(email)
  service = Google::Apis::CalendarV3::CalendarService.new
  service.client_options.application_name = APPLICATION_NAME
  service.authorization = user_client(email)

  gcals = {}
  page_token = nil
  begin
    result = service.list_calendar_lists(page_token: page_token)
    result.items.each do |e|
      gcals[e.summary] = e.id
    end
    if result.next_page_token != page_token
      page_token = result.next_page_token
    else
      page_token = nil
    end
  end while !page_token.nil?
  gcals
end

def user_client(email)
  Google::ServiceAccount.new(
    mail: email,
    scopes: [Google::Apis::CalendarV3::AUTH_CALENDAR]
  ).user_auth
end

Dotenv.load

puts gcal_list('someuser@mygoogledomain.com')

Congratulations! You’ve just impersonated an account using the Google Calendar API.

Leave a Reply

Your email address will not be published. Required fields are marked *