johnhamelink
12/1/2015 - 4:41 PM

access_token.ex

defmodule GoogleContactsToGrandstream.Google.AccessToken do
  @moduledoc """
  Retrieves an Authentication Access Token
  for Google from the Google apps account
  supplied.
  """
  require Logger
  alias GoogleContactsToGrandstream.Google.Jwt

  @url "https://www.googleapis.com/oauth2/v3/token"
  @grant_type "urn:ietf:params:oauth:grant-type:jwt-bearer"

  def request(token: token, account_email: account_email) do
    Logger.info "Requesting Access Token for #{account_email}"
    HTTPoison.start
    Logger.debug "Built token:"
    Logger.debug token
    params = [
      grant_type: @grant_type,
      assertion: token
    ]

    {:ok, response} = HTTPoison.post(
      @url,
      {:form, params},
      %{"Content-type" => "application/x-www-form-urlencoded"}
    )

    Logger.debug "Received: #{inspect response}"
    res = JSX.decode!(response.body)
    {:ok, res["access_token"]}
  end

end
defmodule GoogleContactsToGrandstream.Google.Account do
  @moduledoc """
  Provides a generic module to define Google Apps
  accounts with.
  """
  alias GoogleContactsToGrandstream.Google.AccessToken
  alias GoogleContactsToGrandstream.Google.Jwt

  defstruct [
    :name, :client_id, :client_email,
    :scope, :account_emails,
    :rsa_key_path
  ]

  def start_link do
    cache = Enum.into([], HashDict.new)
    Agent.start_link(fn -> cache end)
  end

  def request_access_token(pid: pid, account: account) do
    Agent.get_and_update(
      pid,
      &do_request_access_token_with_cache(&1, account)
    )
  end

  def do_request_access_token_with_cache(cache, account) do
    if cached = cache[account.name] do
      {cached, cache}
    else
      result = do_request_access_token(account)
      {result, Dict.put(cache, account.name, result)}
    end
  end

  def do_request_access_token(account) do
    # For each account, retrieve an access code
    # along with the email address.
    accounts = Enum.map(account.account_emails, fn(acc_email) ->

      # Build a JWT Token
      {:ok, token} = Jwt.get_token(
        client_email:  account.client_email,
        scope:         account.scope,
        account_email: acc_email,
        rsa_key_path:  account.rsa_key_path
      )

      # Retrive the Access Token using it
      {:ok, access_token} = AccessToken.request(
                              token: token,
                              account_email: acc_email
                            )
      %{
        name: account.name,
        token: access_token,
        client_id: account.client_id,
        email: acc_email
      }
    end)

    {:ok, accounts}
  end

end
defmodule GoogleContactsToGrandstream.Google.Jwt do
  @moduledoc """
  Builds a Google Apps compliant JWT signature
  from account details for use with retrieving
  and auth token from Google.
  """
  require Logger
  import Joken

  @token_url "https://www.googleapis.com/oauth2/v3/token"

  # Produces a JWT containing all
  # attributes required for us to
  # be able to request an OAuth2
  # token.
  def get_token(client_email: client_email, scope: scope,
                account_email: account_email, rsa_key_path: rsa_key_path) do
    timestamp = unix_timestamp
    expires = timestamp + 3599

    jwt = %{
      iss: client_email,
      scope: scope,
      aud: @token_url,
      iat: timestamp,
      exp: expires,
      sub: account_email
    }

    Logger.debug inspect(jwt)

    payload = jwt
                |> token
                |> use_pem_to_sign(rsa_key_path)
                |> sign
    {:ok, payload.token}
  end

  def use_pem_to_sign(token, rsa_key_path) do
    pem_key = JOSE.JWK.from_pem_file(rsa_key_path)
    token |> with_signer(pem_key |> rs256)
  end

  def unix_timestamp do
    time = :calendar.datetime_to_gregorian_seconds(:calendar.universal_time())
    time-719528*24*3600
  end

end