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