# Copyright 2014 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Utility functions to retrieve OAuth2 credentials using a service account.

A service account is used to allow multiple 'Admin' users access to resources
owned by multiple users in an apps domain.
"""

import os

import http_utils
import log_utils
from oauth2client import client
import recall_errors
import service_account

from google.appengine.api import memcache


_ACCESS_TOKEN_CACHE_S = 60 * 59  # Access tokens last about 1 hour.
_CACHE_NAMESPACE = 'messagerecall_accesstoken#ns'
_LOG = log_utils.GetLogger('messagerecall.credentials_utils')

# Load the key in PKCS 12 format that you downloaded from the Google API
# Console when you created your Service account.
_SERVICE_ACCOUNT_PEM_FILE_NAME = os.path.join(
    os.path.dirname(__file__), 'messagerecall_privatekey.pem')

with open(_SERVICE_ACCOUNT_PEM_FILE_NAME, 'rb') as f:
  _SERVICE_ACCOUNT_KEY = f.read()


def _GetSignedJwtAssertionCredentials(user_email):
  """Retrieve an OAuth2 credentials object impersonating user_email.

  This object is then used to authorize an http connection that will be
  used to connect with Google services such as the Admin SDK.
  Also includes an access_token that is used to connect to IMAP.

  The first parameter, service_account_name,is the Email address
  created for the Service account from the API Console.  The sub parameter
  is the Authenticated user to impersonate.

  Args:
    user_email: String of the user email account to impersonate.

  Returns:
    oauth2client credentials object.
  """
  return client.SignedJwtAssertionCredentials(
      service_account_name=service_account.SERVICE_ACCOUNT_NAME,
      private_key=_SERVICE_ACCOUNT_KEY,
      scope=service_account.SERVICE_SCOPES,
      sub=user_email)


def GetAuthorizedHttp(user_email):
  """Establish authorized http connection needed for API access.

  All authorizations via service account rely on an initial authorized
  connection using a domain admin. Authorized http connections are needed
  for Google Apiary API access and an authorized access_token is needed
  for IMAP access.

  Credentials objects come with an empty access_token by default. To avoid
  quota issues with the oauth server, we manage access_tokens in the memcache.
  If we didn't update the access_token, every credentials.authorize() would
  force a round-trip with the oauth server.

  Args:
    user_email: String of the authorizing user email account.

  Returns:
    Authorized http connection able to access Google API services.
  """
  credentials = _GetSignedJwtAssertionCredentials(user_email)
  credentials.access_token = GetUserAccessToken(user_email)
  return credentials.authorize(http_utils.GetHttpObject())


def GetUserAccessToken(user_email, force_refresh=False):
  """Helper to get a refreshed access_token for a user via service account.

  Args:
    user_email: User email for which access_token will be retrieved.
    force_refresh: Boolean, if True force a token refresh.

  Returns:
    Cached access_token or a new one.
  """
  access_token = memcache.get(user_email, namespace=_CACHE_NAMESPACE)
  if access_token and not force_refresh:
    return access_token

  credentials = _GetSignedJwtAssertionCredentials(user_email)
  # Have observed the following error from refresh():
  # 'Unable to fetch URL: https://accounts.google.com/o/oauth2/token'
  _LOG.debug('Refreshing access token for %s.', user_email)
  credentials.refresh(http_utils.GetHttpObject())
  access_token = credentials.access_token
  if memcache.set(user_email, access_token, time=_ACCESS_TOKEN_CACHE_S,
                  namespace=_CACHE_NAMESPACE):
    return access_token
  raise recall_errors.MessageRecallCounterError(
      'Exceeded retry limit in GetUserAccessToken: %s.' % user_email)