Skip to content

Migrating the Python Admin SDK to google-auth Library #19

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
May 5, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 18 additions & 18 deletions firebase_admin/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,18 @@
import threading
import time

from oauth2client import crypt
from google.auth import jwt
from google.auth.transport import requests
import google.oauth2.id_token
import six

import firebase_admin
from firebase_admin import credentials
from firebase_admin import jwt

_auth_lock = threading.Lock()

"""Provided for overriding during tests. (OAuth2 client uses a caching-enabled
HTTP client internally if none provided)
"""
_http = None
"""Provided for overriding during tests."""
_request = requests.Request()

_AUTH_ATTRIBUTE = '_auth'
GCLOUD_PROJECT_ENV_VAR = 'GCLOUD_PROJECT'
Expand Down Expand Up @@ -196,7 +195,7 @@ def create_custom_token(self, uid, developer_claims=None):
if developer_claims is not None:
payload['claims'] = developer_claims

return jwt.encode(payload, self._app.credential.signer)
return jwt.encode(self._app.credential.signer, payload)

def verify_id_token(self, id_token):
"""Verifies the signature and data for the provided JWT.
Expand Down Expand Up @@ -226,16 +225,19 @@ def verify_id_token(self, id_token):

try:
project_id = self._app.credential.project_id
if project_id is None:
project_id = os.environ.get(GCLOUD_PROJECT_ENV_VAR)
except AttributeError:
project_id = os.environ.get(GCLOUD_PROJECT_ENV_VAR)

if not project_id:
raise ValueError('Must initialize app with a credentials.Certificate '
'or set your Firebase project ID as the '
'GCLOUD_PROJECT environment variable to call '
'verify_id_token().')
raise ValueError('Failed to ascertain project ID from the credential or the '
'environment. Must initialize app with a credentials.Certificate or '
'set your Firebase project ID as the GCLOUD_PROJECT environment '
'variable to call verify_id_token().')

header, payload = jwt.decode(id_token)
header = jwt.decode_header(id_token)
payload = jwt.decode(id_token, verify=False)
issuer = payload.get('iss')
audience = payload.get('aud')
subject = payload.get('sub')
Expand Down Expand Up @@ -286,13 +288,11 @@ def verify_id_token(self, id_token):
'characters. ') + verify_id_token_msg

if error_message:
raise crypt.AppIdentityError(error_message)
raise ValueError(error_message)

verified_claims = jwt.verify_id_token(
verified_claims = google.oauth2.id_token.verify_firebase_token(
id_token,
self.FIREBASE_CERT_URI,
audience=project_id,
kid=header.get('kid'),
http=_http)
request=_request,
audience=project_id)
verified_claims['uid'] = verified_claims['sub']
return verified_claims
95 changes: 53 additions & 42 deletions firebase_admin/credentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,20 @@
# limitations under the License.

"""Firebase credentials module."""
import collections
import json

import httplib2
import google.auth
from google.auth.transport import requests
from google.oauth2 import credentials
from google.oauth2 import service_account

from oauth2client import client
from oauth2client import crypt

_request = requests.Request()

_http = httplib2.Http()

AccessTokenInfo = collections.namedtuple(
'AccessTokenInfo', ['access_token', 'expiry'])


class Base(object):
Expand All @@ -39,6 +44,8 @@ def get_credential(self):
class Certificate(Base):
"""A credential initialized from a JSON certificate keyfile."""

_CREDENTIAL_TYPE = 'service_account'

def __init__(self, file_path):
"""Initializes a credential from a certificate file.

Expand All @@ -53,23 +60,15 @@ def __init__(self, file_path):
ValueError: If the certificate file is invalid.
"""
super(Certificate, self).__init__()
# TODO(hkj): Clean this up once we are able to take a dependency
# TODO(hkj): on latest oauth2client.
with open(file_path) as json_keyfile:
json_data = json.load(json_keyfile)
if json_data.get('type') != client.SERVICE_ACCOUNT:
raise ValueError('Invalid certificate file. File must contain a '
'"type" field set to "{0}".'.format(client.SERVICE_ACCOUNT))
if json_data.get('type') != self._CREDENTIAL_TYPE:
raise ValueError('Invalid certificate file: "{0}". File must contain a '
'"type" field set to "{1}".'.format(file_path, self._CREDENTIAL_TYPE))
self._project_id = json_data.get('project_id')
self._service_account_email = json_data.get('client_email')
try:
self._signer = crypt.Signer.from_string(json_data.get('private_key'))
except Exception as error:
raise ValueError('Failed to parse the private key string or initialize an '
'RSA signer. Caused by: "{0}".'.format(error))
try:
self._g_credential = client.GoogleCredentials.from_stream(file_path)
except client.ApplicationDefaultCredentialsError as error:
self._g_credential = service_account.Credentials.from_service_account_info(json_data)
except ValueError as error:
raise ValueError('Failed to initialize a certificate credential from file "{0}". '
'Caused by: "{1}"'.format(file_path, error))

Expand All @@ -79,25 +78,26 @@ def project_id(self):

@property
def signer(self):
return self._signer
return self._g_credential.signer

@property
def service_account_email(self):
return self._service_account_email
return self._g_credential.service_account_email

def get_access_token(self):
"""Fetches a Google OAuth2 access token using this certificate credential.

Returns:
oauth2client.client.AccessTokenInfo: An access token obtained via oauth2client.
AccessTokenInfo: An access token obtained using the credential.
"""
return self._g_credential.get_access_token(_http)
self._g_credential.refresh(_request)
return AccessTokenInfo(self._g_credential.token, self._g_credential.expiry)

def get_credential(self):
"""Returns the underlying Google credential.

Returns:
oauth2client.client.GoogleCredentials: An oauth2client credential instance."""
google.auth.credentials.Credentials: A Google Auth credential instance."""
return self._g_credential


Expand All @@ -108,31 +108,38 @@ def __init__(self):
"""Initializes the Application Default credentials for the current environment.

Raises:
oauth2client.client.ApplicationDefaultCredentialsError: If Application Default
google.auth.exceptions.DefaultCredentialsError: If Application Default
credentials cannot be initialized in the current environment.
"""
super(ApplicationDefault, self).__init__()
self._g_credential = client.GoogleCredentials.get_application_default()
self._g_credential, self._project_id = google.auth.default()

def get_access_token(self):
"""Fetches a Google OAuth2 access token using this application default credential.

Returns:
oauth2client.client.AccessTokenInfo: An access token obtained via oauth2client.
AccessTokenInfo: An access token obtained using the credential.
"""
return self._g_credential.get_access_token(_http)
self._g_credential.refresh(_request)
return AccessTokenInfo(self._g_credential.token, self._g_credential.expiry)

def get_credential(self):
"""Returns the underlying Google credential.

Returns:
oauth2client.client.GoogleCredentials: An oauth2client credential instance."""
google.auth.credentials.Credentials: A Google Auth credential instance."""
return self._g_credential

@property
def project_id(self):
return self._project_id


class RefreshToken(Base):
"""A credential initialized from an existing refresh token."""

_CREDENTIAL_TYPE = 'authorized_user'

def __init__(self, file_path):
"""Initializes a refresh token credential from the specified JSON file.

Expand All @@ -146,41 +153,45 @@ def __init__(self, file_path):
super(RefreshToken, self).__init__()
with open(file_path) as json_keyfile:
json_data = json.load(json_keyfile)
if json_data.get('type') != client.AUTHORIZED_USER:
raise ValueError('Invalid refresh token file. File must contain a '
'"type" field set to "{0}".'.format(client.AUTHORIZED_USER))
self._client_id = json_data.get('client_id')
self._client_secret = json_data.get('client_secret')
self._refresh_token = json_data.get('refresh_token')
if json_data.get('type') != self._CREDENTIAL_TYPE:
raise ValueError('Invalid refresh token file: "{0}". File must contain a '
'"type" field set to "{1}".'.format(file_path, self._CREDENTIAL_TYPE))
try:
self._g_credential = client.GoogleCredentials.from_stream(file_path)
except client.ApplicationDefaultCredentialsError as error:
client_id = json_data['client_id']
client_secret = json_data['client_secret']
refresh_token = json_data['refresh_token']
except KeyError as error:
raise ValueError('Failed to initialize a refresh token credential from file "{0}". '
'Caused by: "{1}".'.format(file_path, error))
'Caused by: "{1}"'.format(file_path, error))
self._g_credential = credentials.Credentials(
token=None, refresh_token=refresh_token,
token_uri='https://accounts.google.com/o/oauth2/token',
client_id=client_id, client_secret=client_secret)

@property
def client_id(self):
return self._client_id
return self._g_credential.client_id

@property
def client_secret(self):
return self._client_secret
return self._g_credential.client_secret

@property
def refresh_token(self):
return self._refresh_token
return self._g_credential.refresh_token

def get_access_token(self):
"""Fetches a Google OAuth2 access token using this refresh token credential.

Returns:
oauth2client.client.AccessTokenInfo: An access token obtained via oauth2client.
AccessTokenInfo: An access token obtained using the credential.
"""
return self._g_credential.get_access_token(_http)
self._g_credential.refresh(_request)
return AccessTokenInfo(self._g_credential.token, self._g_credential.expiry)

def get_credential(self):
"""Returns the underlying Google credential.

Returns:
oauth2client.client.GoogleCredentials: An oauth2client credential instance."""
google.auth.credentials.Credentials: A Google Auth credential instance."""
return self._g_credential
Loading