8000 Migrating Auth API to the new Identity Toolkit endpoint (#256) · ClarkDing/firebase-admin-python@32d7dcd · GitHub
[go: up one dir, main page]

Skip to content

Commit 32d7dcd

Browse files
hardiknshiranya911
authored andcommitted
Migrating Auth API to the new Identity Toolkit endpoint (firebase#256)
* Migrating Auth API to the new Identity Toolkit endpoint * fix lint errors * fixed integration tests * fix to support py3 test verification * fix review comments
1 parent 7888599 commit 32d7dcd

File tree

7 files changed

+43
-31
lines changed

7 files changed

+43
-31
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Unreleased
22

3+
- [added] Migrated the `auth` user management API to the
4+
new Identity Toolkit endpoint.
35
- [fixed] Extending HTTP retries to more HTTP methods like POST and PATCH.
46

57
# v2.15.1

firebase_admin/_token_gen.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ def create_session_cookie(self, id_token, expires_in):
206206
'validDuration': expires_in,
207207
}
208208
try:
209-
response = self.client.body('post', 'createSessionCookie', json=payload)
209+
response = self.client.body('post', ':createSessionCookie', json=payload)
210210
except requests.exceptions.RequestException as error:
211211
self._handle_http_error(COOKIE_CREATE_ERROR, 'Failed to create session cookie', error)
212212
else:

firebase_admin/_user_mgt.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,7 @@ def get_user(self, **kwargs):
394394
raise TypeError('Unsupported keyword arguments: {0}.'.format(kwargs))
395395

396396
try:
397-
response = self._client.body('post', 'getAccountInfo', json=payload)
397+
response = self._client.body('post', '/accounts:lookup', json=payload)
398398
except requests.exceptions.RequestException as error:
399399
msg = 'Failed to get user by {0}: {1}.'.format(key_type, key)
400400
self._handle_http_error(INTERNAL_ERROR, msg, error)
@@ -421,7 +421,7 @@ def list_users(self, page_token=None, max_results=MAX_LIST_USERS_RESULTS):
421421
if page_token:
422422
payload['nextPageToken'] = page_token
423423
try:
424-
return self._client.body('post', 'downloadAccount', json=payload)
424+
return self._client.body('get', '/accounts:batchGet', params=payload)
425425
except requests.exceptions.RequestException as error:
426426
self._handle_http_error(USER_DOWNLOAD_ERROR, 'Failed to download user accounts.', error)
427427

@@ -440,7 +440,7 @@ def create_user(self, uid=None, display_name=None, email=None, phone_number=None
440440
}
441441
payload = {k: v for k, v in payload.items() if v is not None}
442442
try:
443-
response = self._client.body('post', 'signupNewUser', json=payload)
443+
response = self._client.body('post', '/accounts', json=payload)
444444
except requests.exceptions.RequestException as error:
445445
self._handle_http_error(USER_CREATE_ERROR, 'Failed to create new user.', error)
446446
else:
@@ -490,7 +490,7 @@ def update_user(self, uid, display_name=_UNSPECIFIED, email=None, phone_number=_
490490

491491
payload = {k: v for k, v in payload.items() if v is not None}
492492
try:
493-
response = self._client.body('post', 'setAccountInfo', json=payload)
493+
response = self._client.body('post', '/accounts:update', json=payload)
494494
except requests.exceptions.RequestException as error:
495495
self._handle_http_error(
496496
USER_UPDATE_ERROR, 'Failed to update user: {0}.'.format(uid), error)
@@ -503,7 +503,7 @@ def delete_user(self, uid):
503503
"""Deletes the user identified by the specified user ID."""
504504
_auth_utils.validate_uid(uid, required=True)
505505
try:
506-
response = self._client.body('post', 'deleteAccount', json={'localId' : uid})
506+
response = self._client.body('post', '/accounts:delete', json={'localId' : uid})
507507
except requests.exceptions.RequestException as error:
508508
self._handle_http_error(
509509
USER_DELETE_ERROR, 'Failed to delete user: {0}.'.format(uid), error)
@@ -529,7 +529,7 @@ def import_users(self, users, hash_alg=None):
529529
raise ValueError('A UserImportHash is required to import users with passwords.')
530530
payload.update(hash_alg.to_dict())
531531
try:
532-
response = self._client.body('post', 'uploadAccount', json=payload)
532+
response = self._client.body('post', '/accounts:batchCreate', json=payload)
533533
except requests.exceptions.RequestException as error:
534534
self._handle_http_error(USER_IMPORT_ERROR, 'Failed to import users.', error)
535535
else:

firebase_admin/auth.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -466,13 +466,20 @@ def __init__(self, code, message, error=None):
466466
class _AuthService(object):
467467
"""Firebase Authentication service."""
468468

469-
ID_TOOLKIT_URL = 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/'
469+
ID_TOOLKIT_URL = 'https://identitytoolkit.googleapis.com/v1/projects/'
470470

471471
def __init__(self, app):
472472
credential = app.credential.get_credential()
473473
version_header = 'Python/Admin/{0}'.format(firebase_admin.__version__)
474+
475+
if not app.project_id:
476+
raise ValueError("""Project ID is required to access the auth service.
477+
1. Use a service account credential, or
478+
2. set the project ID explicitly via Firebase App options, or
< F438 /code>
479+
3. set the project ID via the GOOGLE_CLOUD_PROJECT environment variable.""")
480+
474481
client = _http_client.JsonHttpClient(
475-
credential=credential, base_url=self.ID_TOOLKIT_URL,
482+
credential=credential, base_url=self.ID_TOOLKIT_URL + app.project_id,
476483
headers={'X-Client-Version': version_header})
477484
self._token_generator = _token_gen.TokenGenerator(app, client)
478485
self._token_verifier = _token_gen.TokenVerifier(app)

integration/test_auth.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ def test_custom_token_without_service_account(api_key):
6565
cred = CredentialWrapper.from_existing_credential(google_cred)
6666
custom_app = firebase_admin.initialize_app(cred, {
6767
'serviceAccountId': google_cred.service_account_email,
68+
'projectId': firebase_admin.get_app().project_id
6869
}, 'temp-app')
6970
try:
7071
custom_token = auth.create_custom_token('user1', app=custom_app)

tests/test_token_gen.py

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ def test_noncert_credential(self, user_mgt_app):
199199
auth.create_custom_token(MOCK_UID, app=user_mgt_app)
200200

201201
def test_sign_with_iam(self):
202-
options = {'serviceAccountId': 'test-service-account'}
202+
options = {'serviceAccountId': 'test-service-account', 'projectId': 'mock-project-id'}
203203
app = firebase_admin.initialize_app(
204204
testutils.MockCredential(), name='iam-signer-app', options=options)
205205
try:
@@ -213,7 +213,7 @@ def test_sign_with_iam(self):
213213
firebase_admin.delete_app(app)
214214

215215
def test_sign_with_iam_error(self):
216-
options = {'serviceAccountId': 'test-service-account'}
216+
options = {'serviceAccountId': 'test-service-account', 'projectId': 'mock-project-id'}
217217
app = firebase_admin.initialize_app(
218218
testutils.MockCredential(), name='iam-signer-app', options=options)
219219
try:
@@ -228,7 +228,9 @@ def test_sign_with_iam_error(self):
228228

229229
def test_sign_with_discovered_service_account(self):
230230
request = testutils.MockRequest(200, 'discovered-service-account')
231-
app = firebase_admin.initialize_app(testutils.MockCredential(), name='iam-signer-app')
231+
options = {'projectId': 'mock-project-id'}
232+
app = firebase_admin.initialize_app(testutils.MockCredential(), name='iam-signer-app',
233+
options=options)
232234
try:
233235
_overwrite_iam_request(app, request)
234236
# Force initialization of the signing provider. This will invoke the Metadata service.
@@ -248,7 +250,9 @@ def test_sign_with_discovered_service_account(self):
248250

249251
def test_sign_with_discovery_failure(self):
250252
request = testutils.MockFailedRequest(Exception('test error'))
251-
app = firebase_admin.initialize_app(testutils.MockCredential(), name='iam-signer-app')
253+
options = {'projectId': 'mock-project-id'}
254+
app = firebase_admin.initialize_app(testutils.MockCredential(), name='iam-signer-app',
255+
options=options)
252256
try:
253257
_overwrite_iam_request(app, request)
254258
with pytest.raises(ValueError) as excinfo:
@@ -409,12 +413,6 @@ def test_project_id_env_var(self, env_var_app):
409413
claims = auth.verify_id_token(TEST_ID_TOKEN, env_var_app)
410414
assert claims['admin'] is True
411415

412-
@pytest.mark.parametrize('env_var_app', [{}], indirect=True)
413-
def test_no_project_id(self, env_var_app):
414-
_overwrite_cert_request(env_var_app, MOCK_REQUEST)
415-
with pytest.raises(ValueError):
416-
auth.verify_id_token(TEST_ID_TOKEN, env_var_app)
417-
418416
def test_custom_token(self, auth_app):
419417
id_token = auth.create_custom_token(MOCK_UID, app=auth_app)
420418
_overwrite_cert_request(auth_app, MOCK_REQUEST)
@@ -515,12 +513,6 @@ def test_project_id_env_var(self, env_var_app):
515513
claims = auth.verify_session_cookie(TEST_SESSION_COOKIE, app=env_var_app)
516514
assert claims['admin'] is True
517515

518-
@pytest.mark.parametrize('env_var_app', [{}], indirect=True)
519-
def test_no_project_id(self, env_var_app):
520-
_overwrite_cert_request(env_var_app, MOCK_REQUEST)
521-
with pytest.raises(ValueError):
522-
auth.verify_session_cookie(TEST_SESSION_COOKIE, app=env_var_app)
523-
524516
def test_custom_token(self, auth_app):
525517
custom_token = auth.create_custom_token(MOCK_UID, app=auth_app)
526518
_overwrite_cert_request(auth_app, MOCK_REQUEST)

tests/test_user_mgt.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
from firebase_admin import _user_mgt
2727
from tests import testutils
2828

29+
from six.moves import urllib
30+
2931

3032
INVALID_STRINGS = [None, '', 0, 1, True, False, list(), tuple(), dict()]
3133
INVALID_DICTS = [None, 'foo', 0, 1, True, False, list(), tuple()]
@@ -87,6 +89,14 @@ def _check_user_record(user, expected_uid='testuser'):
8789
assert provider.provider_id == 'phone'
8890

8991

92+
class TestAuthServiceInitialization(object):
93+
94+
def test_fail_on_no_project_id(self):
95+
app = firebase_admin.initialize_app(testutils.MockCredential(), name='userMgt2')
96+
with pytest.raises(ValueError):
97+
auth._get_auth_service(app)
98+
firebase_admin.delete_app(app)
99+
90100
class TestUserRecord(object):
91101

92102
# Input dict must be non-empty, and must not contain unsupported keys.
@@ -511,7 +521,7 @@ def test_list_multiple_pages(self, user_mgt_app):
511521
assert page.next_page_token == ''
512522
assert page.has_next_page is False
513523
assert page.get_next_page() is None
514-
self._check_rpc_calls(recorder, {'maxResults': 1000, 'nextPageToken': 'token'})
524+
self._check_rpc_calls(recorder, {'maxResults': '1000', 'nextPageToken': 'token'})
515525

516526
def test_list_users_paged_iteration(self, user_mgt_app):
517527
# Page 1
@@ -537,7 +547,7 @@ def test_list_users_paged_iteration(self, user_mgt_app):
537547
assert user.uid == 'user4'
538548
with pytest.raises(StopIteration):
539549
next(iterator)
540-
self._check_rpc_calls(recorder, {'maxResults': 1000, 'nextPageToken': 'token'})
550+
self._check_rpc_calls(recorder, {'maxResults': '1000', 'nextPageToken': 'token'})
541551

542552
def test_list_users_iterator_state(self, user_mgt_app):
543553
response = {
@@ -590,13 +600,13 @@ def test_list_users_with_max_results(self, user_mgt_app):
590600
_, recorder = _instrument_user_manager(user_mgt_app, 200, MOCK_LIST_USERS_RESPONSE)
591601
page = auth.list_users(max_results=500, app=user_mgt_app)
592602
self._check_page(page)
593-
self._check_rpc_calls(recorder, {'maxResults' : 500})
603+
self._check_rpc_calls(recorder, {'maxResults' : '500'})
594604

595605
def test_list_users_with_all_args(self, user_mgt_app):
596606
_, recorder = _instrument_user_manager(user_mgt_app, 200, MOCK_LIST_USERS_RESPONSE)
597607
page = auth.list_users(page_token='foo', max_results=500, app=user_mgt_app)
598608
self._check_page(page)
599-
self._check_rpc_calls(recorder, {'nextPageToken' : 'foo', 'maxResults' : 500})
609+
self._check_rpc_calls(recorder, {'nextPageToken' : 'foo', 'maxResults' : '500'})
600610

601611
def test_list_users_error(self, user_mgt_app):
602612
_instrument_user_manager(user_mgt_app, 500, '{"error":"test"}')
@@ -618,9 +628,9 @@ def _check_page(self, page):
618628

619629
def _check_rpc_calls(self, recorder, expected=None):
620630
if expected is None:
621-
expected = {'maxResults' : 1000}
631+
expected = {'maxResults' : '1000'}
622632
assert len(recorder) == 1
623-
request = json.loads(recorder[0].body.decode())
633+
request = dict(urllib.parse.parse_qsl(urllib.parse.urlsplit(recorder[0].url).query))
624634
assert request == expected
625635

626636

0 commit comments

Comments
 (0)
0