Skip to content

Commit a536d43

Browse files
authored
Implemented a util for getting project ID from the credential or the environment (#69)
* Implemented a util for getting project ID from the credential or the environment * Moved project ID lookup logic to initialize_app() * Removing an unused method
1 parent 17bfa67 commit a536d43

File tree

7 files changed

+95
-49
lines changed

7 files changed

+95
-49
lines changed

firebase_admin/__init__.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
"""Firebase Admin SDK for Python."""
1616
import datetime
17+
import os
1718
import threading
1819

1920
import six
@@ -183,6 +184,15 @@ def __init__(self, name, credential, options):
183184
self._options = _AppOptions(options)
184185
self._lock = threading.RLock()
185186
self._services = {}
187+
pid = self._options.get('projectId')
188+
if not pid:
189+
try:
190+
pid = self._credential.project_id
191+
except AttributeError:
192+
pass
193+
if not pid:
194+
pid = os.environ.get('GCLOUD_PROJECT')
195+
self._project_id = pid
186196

187197
@property
188198
def name(self):
@@ -196,6 +206,10 @@ def credential(self):
196206
def options(self):
197207
return self._options
198208

209+
@property
210+
def project_id(self):
211+
return self._project_id
212+
199213
def _get_service(self, name, initializer):
200214
"""Returns the service instance identified by the given name.
201215

firebase_admin/utils.py renamed to firebase_admin/_utils.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import firebase_admin
1818

19+
1920
def _get_initialized_app(app):
2021
if app is None:
2122
return firebase_admin.get_app()

firebase_admin/auth.py

Lines changed: 7 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
creating and managing user accounts in Firebase projects.
2020
"""
2121

22-
import os
2322
import time
2423

2524
from google.auth import jwt
@@ -28,15 +27,14 @@
2827
import six
2928

3029
from firebase_admin import credentials
31-
from firebase_admin import utils
3230
from firebase_admin import _user_mgt
31+
from firebase_admin import _utils
3332

3433

3534
# Provided for overriding during tests.
3635
_request = transport.requests.Request()
3736

3837
_AUTH_ATTRIBUTE = '_auth'
39-
GCLOUD_PROJECT_ENV_VAR = 'GCLOUD_PROJECT'
4038

4139

4240
def _get_auth_service(app):
@@ -55,7 +53,7 @@ def _get_auth_service(app):
5553
Raises:
5654
ValueError: If the app argument is invalid.
5755
"""
58-
return utils.get_app_service(app, _AUTH_ATTRIBUTE, _AuthService)
56+
return _utils.get_app_service(app, _AUTH_ATTRIBUTE, _AuthService)
5957

6058

6159
def create_custom_token(uid, developer_claims=None, app=None):
@@ -250,14 +248,6 @@ def delete_user(uid, app=None):
250248
raise AuthError(error.code, str(error), error.detail)
251249

252250

253-
def _handle_http_error(code, msg, error):
254-
if error.response is not None:
255-
msg += '\nServer response: {0}'.format(error.response.content.decode())
256-
else:
257-
msg += '\nReason: {0}'.format(error)
258-
raise AuthError(code, msg, error)
259-
260-
261251
class UserInfo(object):
262252
"""A collection of standard profile information for a user.
263253
@@ -576,18 +566,13 @@ def verify_id_token(self, id_token):
576566
raise ValueError('Illegal ID token provided: {0}. ID token must be a non-empty '
577567
'string.'.format(id_token))
578568

579-
try:
580-
project_id = self._app.credential.project_id
581-
if project_id is None:
582-
project_id = os.environ.get(GCLOUD_PROJECT_ENV_VAR)
583-
except AttributeError:
584-
project_id = os.environ.get(GCLOUD_PROJECT_ENV_VAR)
585-
569+
project_id = self._app.project_id
586570
if not project_id:
587571
raise ValueError('Failed to ascertain project ID from the credential or the '
588-
'environment. Must initialize app with a credentials.Certificate or '
589-
'set your Firebase project ID as the GCLOUD_PROJECT environment '
590-
'variable to call verify_id_token().')
572+
'environment. Project ID is required to call verify_id_token(). '
573+
'Initialize the app with a credentials.Certificate or set '
574+
'your Firebase project ID as an app option. Alternatively '
575+
'set the GCLOUD_PROJECT environment variable.')
591576

592577
header = jwt.decode_header(id_token)
593578
payload = jwt.decode(id_token, verify=False)

firebase_admin/db.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@
3030
from six.moves import urllib
3131

3232
import firebase_admin
33-
from firebase_admin import utils
3433
from firebase_admin import _http_client
34+
from firebase_admin import _utils
3535

3636

3737
_DB_ATTRIBUTE = '_database'
@@ -57,7 +57,7 @@ def reference(path='/', app=None):
5757
Raises:
5858
ValueError: If the specified path or app is invalid.
5959
"""
60-
client = utils.get_app_service(app, _DB_ATTRIBUTE, _Client.from_app)
60+
client = _utils.get_app_service(app, _DB_ATTRIBUTE, _Client.from_app)
6161
return Reference(client=client, path=path)
6262

6363
def _parse_path(path):

firebase_admin/storage.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727

2828
import six
2929

30-
from firebase_admin import utils
30+
from firebase_admin import _utils
3131

3232

3333
_STORAGE_ATTRIBUTE = '_storage'
@@ -50,7 +50,7 @@ def bucket(name=None, app=None):
5050
ValueError: If a bucket name is not specified either via options or method arguments,
5151
or if the specified bucket name is not a valid string.
5252
"""
53-
client = utils.get_app_service(app, _STORAGE_ATTRIBUTE, _StorageClient.from_app)
53+
client = _utils.get_app_service(app, _STORAGE_ATTRIBUTE, _StorageClient.from_app)
5454
return client.bucket(name)
5555

5656

tests/test_app.py

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,13 @@
1919

2020
import firebase_admin
2121
from firebase_admin import credentials
22-
from firebase_admin import utils
22+
from firebase_admin import _utils
2323
from tests import testutils
2424

2525

2626
CREDENTIAL = credentials.Certificate(
2727
testutils.resource_filename('service_account.json'))
28+
GCLOUD_PROJECT = 'GCLOUD_PROJECT'
2829

2930
class CredentialProvider(object):
3031
def init(self):
@@ -139,6 +140,38 @@ def test_app_init_with_invalid_name(self, name):
139140
with pytest.raises(ValueError):
140141
firebase_admin.initialize_app(CREDENTIAL, name=name)
141142

143+
def test_project_id_from_options(self, app_credential):
144+
app = firebase_admin.initialize_app(
145+
app_credential, options={'projectId': 'test-project'}, name='myApp')
146+
assert app.project_id == 'test-project'
147+
148+
def test_project_id_from_credentials(self):
149+
app = firebase_admin.initialize_app(CREDENTIAL, name='myApp')
150+
assert app.project_id == 'mock-project-id'
151+
152+
def test_project_id_from_environment(self):
153+
project_id = os.environ.get(GCLOUD_PROJECT)
154+
os.environ[GCLOUD_PROJECT] = 'env-project'
155+
try:
156+
app = firebase_admin.initialize_app(testutils.MockCredential(), name='myApp')
157+
assert app.project_id == 'env-project'
158+
finally:
159+
if project_id:
160+
os.environ[GCLOUD_PROJECT] = project_id
161+
else:
162+
del os.environ[GCLOUD_PROJECT]
163+
164+
def test_no_project_id(self):
165+
project_id = os.environ.get(GCLOUD_PROJECT)
166+
if project_id:
167+
del os.environ[GCLOUD_PROJECT]
168+
try:
169+
app = firebase_admin.initialize_app(testutils.MockCredential(), name='myApp')
170+
assert app.project_id is None
171+
finally:
172+
if project_id:
173+
os.environ[GCLOUD_PROJECT] = project_id
174+
142175
def test_app_get(self, init_app):
143176
assert init_app is firebase_admin.get_app(init_app.name)
144177

@@ -167,10 +200,10 @@ def test_app_delete(self, init_app):
167200
firebase_admin.delete_app(init_app)
168201

169202
def test_app_services(self, init_app):
170-
service = utils.get_app_service(init_app, 'test.service', AppService)
203+
service = _utils.get_app_service(init_app, 'test.service', AppService)
171204
assert isinstance(service, AppService)
172-
service2 = utils.get_app_service(init_app, 'test.service', AppService)
205+
service2 = _utils.get_app_service(init_app, 'test.service', AppService)
173206
assert service is service2
174207
firebase_admin.delete_app(init_app)
175208
with pytest.raises(ValueError):
176-
utils.get_app_service(init_app, 'test.service', AppService)
209+
_utils.get_app_service(init_app, 'test.service', AppService)

tests/test_auth.py

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333

3434
FIREBASE_AUDIENCE = ('https://identitytoolkit.googleapis.com/'
3535
'google.identity.identitytoolkit.v1.IdentityToolkit')
36+
GCLOUD_PROJECT_ENV_VAR = 'GCLOUD_PROJECT'
3637

3738
MOCK_UID = 'user1'
3839
MOCK_CREDENTIAL = credentials.Certificate(
@@ -98,6 +99,21 @@ def non_cert_app():
9899
yield app
99100
firebase_admin.delete_app(app)
100101

102+
@pytest.fixture
103+
def env_var_app(request):
104+
"""Returns an App instance initialized with the given set of environment variables.
105+
106+
The lines of code following the yield statement are guaranteed to run after each test case
107+
that depends on this fixture. This ensures that the environment is left intact after the
108+
tests.
109+
"""
110+
environ = os.environ
111+
os.environ = request.param
112+
app = firebase_admin.initialize_app(testutils.MockCredential(), name='env-var-app')
113+
yield app
114+
os.environ = environ
115+
firebase_admin.delete_app(app)
116+
101117
def verify_custom_token(custom_token, expected_claims):
102118
assert isinstance(custom_token, six.binary_type)
103119
token = google.oauth2.id_token.verify_token(
@@ -232,28 +248,25 @@ def test_invalid_token(self, authtest, id_token):
232248
with pytest.raises(ValueError):
233249
authtest.verify_id_token(id_token)
234250

235-
def test_project_id_env_var(self, non_cert_app):
236-
gcloud_project = os.environ.get(auth.GCLOUD_PROJECT_ENV_VAR)
251+
def test_project_id_option(self):
252+
app = firebase_admin.initialize_app(
253+
testutils.MockCredential(), options={'projectId': 'mock-project-id'}, name='myApp')
237254
try:
238-
os.environ[auth.GCLOUD_PROJECT_ENV_VAR] = MOCK_CREDENTIAL.project_id
239-
claims = auth.verify_id_token(TEST_ID_TOKEN, non_cert_app)
255+
claims = auth.verify_id_token(TEST_ID_TOKEN, app)
240256
assert claims['admin'] is True
257+
assert claims['uid'] == claims['sub']
241258
finally:
242-
if gcloud_project:
243-
os.environ[auth.GCLOUD_PROJECT_ENV_VAR] = gcloud_project
244-
else:
245-
del os.environ[auth.GCLOUD_PROJECT_ENV_VAR]
246-
247-
def test_no_project_id(self, non_cert_app):
248-
gcloud_project = os.environ.get(auth.GCLOUD_PROJECT_ENV_VAR)
249-
if gcloud_project:
250-
del os.environ[auth.GCLOUD_PROJECT_ENV_VAR]
251-
try:
252-
with pytest.raises(ValueError):
253-
auth.verify_id_token(TEST_ID_TOKEN, non_cert_app)
254-
finally:
255-
if gcloud_project:
256-
os.environ[auth.GCLOUD_PROJECT_ENV_VAR] = gcloud_project
259+
firebase_admin.delete_app(app)
260+
261+
@pytest.mark.parametrize('env_var_app', [{'GCLOUD_PROJECT': 'mock-project-id'}], indirect=True)
262+
def test_project_id_env_var(self, env_var_app):
263+
claims = auth.verify_id_token(TEST_ID_TOKEN, env_var_app)
264+
assert claims['admin'] is True
265+
266+
@pytest.mark.parametrize('env_var_app', [{}], indirect=True)
267+
def test_no_project_id(self, env_var_app):
268+
with pytest.raises(ValueError):
269+
auth.verify_id_token(TEST_ID_TOKEN, env_var_app)
257270

258271
def test_custom_token(self, authtest):
259272
id_token = authtest.create_custom_token(MOCK_UID)

0 commit comments

Comments
 (0)