Skip to content

Commit c6c3c8f

Browse files
authored
Supporting initialization of credentials with parsed dict arguments (#48)
1 parent 9f32b8a commit c6c3c8f

File tree

3 files changed

+89
-39
lines changed

3 files changed

+89
-39
lines changed

firebase_admin/auth.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ def create_custom_token(self, uid, developer_claims=None):
133133
developer_claims: A dictionary of claims to be included in the token.
134134
135135
Returns:
136-
string: A token string minted from the input parameters.
136+
string: A token minted from the input parameters as a byte string.
137137
138138
Raises:
139139
ValueError: If input parameters are invalid.

firebase_admin/credentials.py

+47-23
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"""Firebase credentials module."""
1616
import collections
1717
import json
18+
import six
1819

1920
import google.auth
2021
from google.auth.transport import requests
@@ -55,32 +56,41 @@ class Certificate(Base):
5556

5657
_CREDENTIAL_TYPE = 'service_account'
5758

58-
def __init__(self, file_path):
59-
"""Initializes a credential from a certificate file.
59+
def __init__(self, cert):
60+
"""Initializes a credential from a Google service account certificate.
6061
61-
Parses the specified certificate file (service account file), and
62-
creates a credential instance from it.
62+
Service account certificates can be downloaded as JSON files from the Firebase console.
63+
To instantiate a credential from a certificate file, either specify the file path or a
64+
dict representing the parsed contents of the file.
6365
6466
Args:
65-
file_path: Path to a service account certificate file.
67+
cert: Path to a certificate file or a dict representing the contents of a certificate.
6668
6769
Raises:
68-
IOError: If the specified file doesn't exist or cannot be read.
69-
ValueError: If the certificate file is invalid.
70+
IOError: If the specified certificate file doesn't exist or cannot be read.
71+
ValueError: If the specified certificate is invalid.
7072
"""
7173
super(Certificate, self).__init__()
72-
with open(file_path) as json_keyfile:
73-
json_data = json.load(json_keyfile)
74+
if isinstance(cert, six.string_types):
75+
with open(cert) as json_file:
76+
json_data = json.load(json_file)
77+
elif isinstance(cert, dict):
78+
json_data = cert
79+
else:
80+
raise ValueError(
81+
'Invalid certificate argument: "{0}". Certificate argument must be a file path, '
82+
'or a dict containing the parsed file contents.'.format(cert))
83+
7484
if json_data.get('type') != self._CREDENTIAL_TYPE:
75-
raise ValueError('Invalid certificate file: "{0}". File must contain a '
76-
'"type" field set to "{1}".'.format(file_path, self._CREDENTIAL_TYPE))
85+
raise ValueError('Invalid service account certificate. Certificate must contain a '
86+
'"type" field set to "{0}".'.format(self._CREDENTIAL_TYPE))
7787
self._project_id = json_data.get('project_id')
7888
try:
7989
self._g_credential = service_account.Credentials.from_service_account_info(
8090
json_data, scopes=_scopes)
8191
except ValueError as error:
82-
raise ValueError('Failed to initialize a certificate credential from file "{0}". '
83-
'Caused by: "{1}"'.format(file_path, error))
92+
raise ValueError('Failed to initialize a certificate credential. '
93+
'Caused by: "{0}"'.format(error))
8494

8595
@property
8696
def project_id(self):
@@ -132,29 +142,43 @@ class RefreshToken(Base):
132142

133143
_CREDENTIAL_TYPE = 'authorized_user'
134144

135-
def __init__(self, file_path):
136-
"""Initializes a refresh token credential from the specified JSON file.
145+
def __init__(self, refresh_token):
146+
"""Initializes a credential from a refresh token JSON file.
147+
148+
The JSON must consist of client_id, client_secert and refresh_token fields. Refresh
149+
token files are typically created and managed by the gcloud SDK. To instantiate
150+
a credential from a refresh token file, either specify the file path or a dict
151+
representing the parsed contents of the file.
137152
138153
Args:
139-
file_path: File path to a refresh token JSON file.
154+
refresh_token: Path to a refresh token file or a dict representing the contents of a
155+
refresh token file.
140156
141157
Raises:
142158
IOError: If the specified file doesn't exist or cannot be read.
143-
ValueError: If the refresh token file is invalid.
159+
ValueError: If the refresh token configuration is invalid.
144160
"""
145161
super(RefreshToken, self).__init__()
146-
with open(file_path) as json_keyfile:
147-
json_data = json.load(json_keyfile)
162+
if isinstance(refresh_token, six.string_types):
163+
with open(refresh_token) as json_file:
164+
json_data = json.load(json_file)
165+
elif isinstance(refresh_token, dict):
166+
json_data = refresh_token
167+
else:
168+
raise ValueError(
169+
'Invalid refresh token argument: "{0}". Refresh token argument must be a file '
170+
'path, or a dict containing the parsed file contents.'.format(refresh_token))
171+
148172
if json_data.get('type') != self._CREDENTIAL_TYPE:
149-
raise ValueError('Invalid refresh token file: "{0}". File must contain a '
150-
'"type" field set to "{1}".'.format(file_path, self._CREDENTIAL_TYPE))
173+
raise ValueError('Invalid refresh token configuration. JSON must contain a '
174+
'"type" field set to "{0}".'.format(self._CREDENTIAL_TYPE))
151175
try:
152176
client_id = json_data['client_id']
153177
client_secret = json_data['client_secret']
154178
refresh_token = json_data['refresh_token']
155179
except KeyError as error:
156-
raise ValueError('Failed to initialize a refresh token credential from file "{0}". '
157-
'Caused by: "{1}"'.format(file_path, error))
180+
raise ValueError('Failed to initialize a refresh token credential. '
181+
'Caused by: "{0}"'.format(error))
158182
self._g_credential = credentials.Credentials(
159183
token=None, refresh_token=refresh_token,
160184
token_uri='https://accounts.google.com/o/oauth2/token',

tests/test_credentials.py

+41-15
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,24 @@ class TestCertificate(object):
4444
def test_init_from_file(self):
4545
credential = credentials.Certificate(
4646
testutils.resource_filename('service_account.json'))
47+
self._verify_credential(credential)
48+
49+
def test_init_from_dict(self):
50+
parsed_json = json.loads(testutils.resource('service_account.json'))
51+
credential = credentials.Certificate(parsed_json)
52+
self._verify_credential(credential)
53+
54+
@pytest.mark.parametrize('file_name,error', invalid_certs.values(), ids=list(invalid_certs))
55+
def test_init_from_invalid_certificate(self, file_name, error):
56+
with pytest.raises(error):
57+
credentials.Certificate(testutils.resource_filename(file_name))
58+
59+
@pytest.mark.parametrize('arg', [None, 0, 1, True, False, list(), tuple(), dict()])
60+
def test_invalid_args(self, arg):
61+
with pytest.raises(ValueError):
62+
credentials.Certificate(arg)
63+
64+
def _verify_credential(self, credential):
4765
assert credential.project_id == 'mock-project-id'
4866
assert credential.service_account_email == '[email protected]'
4967
assert isinstance(credential.signer, crypt.Signer)
@@ -59,11 +77,6 @@ def test_init_from_file(self):
5977
assert access_token.access_token == 'mock_access_token'
6078
assert isinstance(access_token.expiry, datetime.datetime)
6179

62-
@pytest.mark.parametrize('file_name,error', invalid_certs.values(), ids=list(invalid_certs))
63-
def test_init_from_invalid_certificate(self, file_name, error):
64-
with pytest.raises(error):
65-
credentials.Certificate(testutils.resource_filename(file_name))
66-
6780

6881
@pytest.fixture
6982
def app_default(request):
@@ -110,6 +123,29 @@ class TestRefreshToken(object):
110123
def test_init_from_file(self):
111124
credential = credentials.RefreshToken(
112125
testutils.resource_filename('refresh_token.json'))
126+
self._verify_credential(credential)
127+
128+
def test_init_from_dict(self):
129+
parsed_json = json.loads(testutils.resource('refresh_token.json'))
130+
credential = credentials.RefreshToken(parsed_json)
131+
self._verify_credential(credential)
132+
133+
def test_init_from_nonexisting_file(self):
134+
with pytest.raises(IOError):
135+
credentials.RefreshToken(
136+
testutils.resource_filename('non_existing.json'))
137+
138+
def test_init_from_invalid_file(self):
139+
with pytest.raises(ValueError):
140+
credentials.RefreshToken(
141+
testutils.resource_filename('service_account.json'))
142+
143+
@pytest.mark.parametrize('arg', [None, 0, 1, True, False, list(), tuple(), dict()])
144+
def test_invalid_args(self, arg):
145+
with pytest.raises(ValueError):
146+
credentials.RefreshToken(arg)
147+
148+
def _verify_credential(self, credential):
113149
assert credential.client_id == 'mock.apps.googleusercontent.com'
114150
assert credential.client_secret == 'mock-secret'
115151
assert credential.refresh_token == 'mock-refresh-token'
@@ -127,13 +163,3 @@ def test_init_from_file(self):
127163
access_token = credential.get_access_token()
128164
assert access_token.access_token == 'mock_access_token'
129165
assert isinstance(access_token.expiry, datetime.datetime)
130-
131-
def test_init_from_nonexisting_file(self):
132-
with pytest.raises(IOError):
133-
credentials.RefreshToken(
134-
testutils.resource_filename('non_existing.json'))
135-
136-
def test_init_from_invalid_file(self):
137-
with pytest.raises(ValueError):
138-
credentials.RefreshToken(
139-
testutils.resource_filename('service_account.json'))

0 commit comments

Comments
 (0)