8000 fixes for review comments · firebase/firebase-admin-python@fc10c14 · GitHub
[go: up one dir, main page]

Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit fc10c14

Browse files
committed
fixes for review comments
1 parent 8e7df34 commit fc10c14

File tree

6 files changed

+179
-127
lines changed

6 files changed

+179
-127
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Unreleased
22

33
- [added] Added `generate_password_reset_link()`,
4-
`generate_email_verification_link()` and `generate_email_sign_in_link()`
4+
`generate_email_verification_link()` and `generate_sign_in_with_email_link()`
55
methods to the `auth` API.
66
- [added] Migrated the `auth` user management API to the
77
new Identity Toolkit endpoint.

firebase_admin/_auth_utils.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
'acr', 'amr', 'at_hash', 'aud', 'auth_time', 'azp', 'cnf', 'c_hash', 'exp', 'iat',
2727
'iss', 'jti', 'nbf', 'nonce', 'sub', 'firebase',
2828
])
29-
VALID_ACTION_TYPE = set(['VERIFY_EMAIL', 'EMAIL_SIGNIN', 'PASSWORD_RESET'])
29+
VALID_EMAIL_ACTION_TYPES = set(['VERIFY_EMAIL', 'EMAIL_SIGNIN', 'PASSWORD_RESET'])
3030

3131

3232
def validate_uid(uid, required=False):
@@ -184,7 +184,7 @@ def validate_custom_claims(custom_claims, required=False):
184184
return claims_str
185185

186186
def validate_action_type(action_type):
187-
if action_type not in VALID_ACTION_TYPE:
187+
if action_type not in VALID_EMAIL_ACTION_TYPES:
188188
raise ValueError('Invalid action type provided action_type: {0}. \
189-
Valid values are {1}'.format(action_type, VALID_ACTION_TYPE))
189+
Valid values are {1}'.format(action_type, ', '.join(VALID_EMAIL_ACTION_TYPES)))
190190
return action_type

firebase_admin/_user_mgt.py

Lines changed: 41 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
USER_DELETE_ERROR = 'USER_DELETE_ERROR'
3232
USER_IMPORT_ERROR = 'USER_IMPORT_ERROR'
3333
USER_DOWNLOAD_ERROR = 'LIST_USERS_ERROR'
34-
USER_LINK_GENERATE_ERROR = 'USER_LINK_GENERATE_ERROR'
34+
GENERATE_EMAIL_ACTION_LINK_ERROR = 'GENERATE_EMAIL_ACTION_LINK_ERROR'
3535

3636
MAX_LIST_USERS_RESULTS = 1000
3737
MAX_IMPORT_USERS_SIZE = 1000
@@ -396,56 +396,64 @@ def encode_action_code_settings(settings):
396396
settings - ``ActionCodeSettings`` object provided to be encoded
397397
returns - dict of parameters to be passed for link gereration.
398398
"""
399-
if not isinstance(settings, ActionCodeSettings):
400-
raise ValueError('Invalid data argument: {0}. Must be a dictionary.'.format(settings))
401399

402400
parameters = {}
403-
# Validate url
404-
if settings.url:
405-
try:
406-
parsed = urllib.parse.urlparse(settings.url)
407-
if not parsed.netloc:
408-
raise ValueError('Malformed dynamic action links url: "{0}".'.format(settings.url))
409-
parameters['continueUrl'] = settings.url
410-
except Exception:
411-
raise ValueError('Malformed dynamic action links url: "{0}".'.format(settings.url))
401+
# url
402+
if not settings.url:
403+
raise ValueError("Dynamic action links url is mandatory")
412404

413-
# Validate boolean types
414-
for field in ['handle_code_in_app', 'android_install_app']:
415-
value = getattr(settings, field, None)
416-
if value != None and not isinstance(value, bool):
417-
raise ValueError('Invalid value provided for {0}: {1}'.format(field, value))
418-
419-
# Validate string types
420-
for field in ['dynamic_link_domain', 'ios_bundle_id',
421-
'android_package_name', 'android_minimum_version']:
422-
value = getattr(settings, field, None)
423-
if value != None and not isinstance(value, six.string_types):
424-
raise ValueError('Invalid value provided for {0}: {1}'.format(field, value))
405+
try:
406+
parsed = urllib.parse.urlparse(settings.url)
407+
if not parsed.netloc:
408+
raise ValueError('Malformed dynamic action links url: "{0}".'.format(settings.url))
409+
parameters['continueUrl'] = settings.url
410+
except Exception:
411+
raise ValueError('Malformed dynamic action links url: "{0}".'.format(settings.url))
425412

426413
# handle_code_in_app
427-
if settings.handle_code_in_app != None:
414+
if settings.handle_code_in_app is not None:
415+
if not isinstance(settings.handle_code_in_app, bool):
416+
raise ValueError('Invalid value provided for handle_code_in_app: {0}'
417+
.format(settings.handle_code_in_app))
428418
parameters['canHandleCodeInApp'] = settings.handle_code_in_app
429419

430420
# dynamic_link_domain
431-
if settings.dynamic_link_domain != None:
421+
if settings.dynamic_link_domain is not None:
422+
if not isinstance(settings.dynamic_link_domain, six.string_types):
423+
raise ValueError('Invalid value provided for dynamic_link_domain: {0}'
424+
.format(settings.dynamic_link_domain))
432425
parameters['dynamicLinkDomain'] = settings.dynamic_link_domain
433426

434427
# ios_bundle_id
435-
if settings.ios_bundle_id:
428+
if settings.ios_bundle_id is not None:
429+
if not isinstance(settings.ios_bundle_id, six.string_types):
430+
raise ValueError('Invalid value provided for ios_bundle_id: {0}'
431+
.format(settings.ios_bundle_id))
436432
parameters['iosBundleId'] = settings.ios_bundle_id
437433

438434
# android_* attributes
439435
if (settings.android_minimum_version or settings.android_install_app) \
440436
and not settings.android_package_name:
441437
raise ValueError("Android package name is required when specifying other Android settings")
442438

443-
if settings.android_package_name:
439+
if settings.android_package_name is not None:
440+
if not isinstance(settings.android_package_name, six.string_types):
441+
raise ValueError('Invalid value provided for android_package_name: {0}'
442+
.format(settings.android_package_name))
444443
parameters['androidPackageName'] = settings.android_package_name
445-
if settings.android_minimum_version:
444+
445+
if settings.android_minimum_version is not None:
446+
if not isinstance(settings.android_minimum_version, six.string_types):
447+
raise ValueError('Invalid value provided for android_minimum_version: {0}'
448+
.format(settings.android_minimum_version))
446449
parameters['androidMinimumVersion'] = settings.android_minimum_version
447-
if settings.android_install_app:
450+
451+
if settings.android_install_app is not None:
452+
if not isinstance(settings.android_install_app, bool):
453+
raise ValueError('Invalid value provided for android_install_app: {0}'
454+
.format(settings.android_install_app))
448455
parameters['androidInstallApp'] = settings.android_install_app
456+
449457
return parameters
450458

451459
class UserManager(object):
@@ -635,18 +643,16 @@ def generate_email_action_link(self, action_type, email, action_code_settings=No
635643
}
636644

637645
if action_code_settings:
638-
if not isinstance(action_code_settings, ActionCodeSettings):
639-
raise ValueError("'action_code_settings' parameter should be " + \
640-
"of type ActionCodeSettings")
641646
payload.update(encode_action_code_settings(action_code_settings))
642647

643648
try:
644649
response = self._client.body('post', '/accounts:sendOobCode', json=payload)
645650
except requests.exceptions.RequestException as error:
646-
self._handle_http_error(USER_LINK_GENERATE_ERROR, 'Failed to generate link.', error)
651+
self._handle_http_error(GENERATE_EMAIL_ACTION_LINK_ERROR, 'Failed to generate link.',
652+
error)
647653
else:
648654
if not response or not response.get('oobLink'):
649-
raise ApiCallError(USER_LINK_GENERATE_ERROR, 'Failed to generate link.')
655+
raise ApiCallError(GENERATE_EMAIL_ACTION_LINK_ERROR, 'Failed to generate link.')
650656
return response.get('oobLink')
651657

652658
def _handle_http_error(self, code, msg, error):

firebase_admin/auth.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -461,7 +461,7 @@ def generate_password_reset_link(email, action_code_settings=None, app=None):
461461
email: The email of the user whose password is to be reset.
462462
action_code_settings: ``ActionCodeSettings`` instance (optional). Defines whether
463463
the link is to be handled by a mobile app and the additional state information to be
464-
passed in the deep link, etc.
464+
passed in the deep link.
465465
app: An App instance (optional).
466466
Returns:
467467
link: The password reset link created by API
@@ -485,7 +485,7 @@ def generate_email_verification_link(email, action_code_settings=None, app=None)
485485
email: The email of the user to be verified.
486486
action_code_settings: ``ActionCodeSettings`` instance (optional). Defines whether
487487
the link is to be handled by a mobile app and the additional state information to be
488-
passed in the deep link, etc.
488+
passed in the deep link.
489489
app: An App instance (optional).
490490
Returns:
491491
link: The email verification link created by API
@@ -509,7 +509,7 @@ def generate_sign_in_with_email_link(email, action_code_settings, app=None):
509509
email: The email of the user signing in.
510510
action_code_settings: ``ActionCodeSettings`` instance. Defines whether
511511
the link is to be handled by a mobile app and the additional state information to be
512-
passed in the deep link, etc.
512+
passed in the deep link.
513513
app: An App instance (optional).
514514
Returns:
515515
link: The email sign in link created by API

integration/test_auth.py

Lines changed: 75 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,11 @@
3131

3232
_verify_token_url = 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyCustomToken'
3333
_verify_password_url = 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword'
34+
_password_reset_url = 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/resetPassword'
35+
_verify_email_url = 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/setAccountInfo'
36+
_email_sign_in_url = 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/emailLinkSignin'
3437

38+
ACTION_LINK_CONTINUE_URL = 'http://localhost?a=1&b=5#f=1'
3539

3640
def _sign_in(custom_token, api_key):
3741
body = {'token' : custom_token.decode(), 'returnSecureToken' : True}
@@ -55,6 +59,35 @@ def _random_id():
5559
def _random_phone():
5660
return '+1' + ''.join([str(random.randint(0, 9)) for _ in range(0, 10)])
5761

62+
def _reset_password(oob_code, new_password, api_key):
63+
body = {'oobCode': oob_code, 'newPassword': new_password}
64+
params = {'key' : api_key}
65+
resp = requests.request('post', _password_reset_url, params=params, json=body)
66+
resp.raise_for_status()
67+
return resp.json().get('email')
68+
69+
def _verify_email(oob_code, api_key):
70+
body = {'oobCode': oob_code}
71+
params = {'key' : api_key}
72+
resp = requests.request('post', _verify_email_url, params=params, json=body)
73+
resp.raise_for_status()
74+
return resp.json().get('email')
75+
76+
def _sign_in_with_email_link(email, oob_code, api_key):
77+
body = {'oobCode': oob_code, 'email': email}
78+
params = {'key' : api_key}
79+
resp = requests.request('post', _email_sign_in_url, params=params, json=body)
80+
resp.raise_for_status()
81+
return resp.json().get('idToken')
82+
83+
def _validate_link_url(link, check_continue_url=True):
84+
assert isinstance(link, six.string_types)
85+
query = six.moves.urllib.parse.urlparse(link).query
86+
query_dict = dict(six.moves.urllib.parse.parse_qsl(query))
87+
if check_continue_url:
88+
assert query_dict['continueUrl'] == ACTION_LINK_CONTINUE_URL
89+
return query_dict['oobCode']
90+
5891
def test_custom_token(api_key):
5992
custom_token = auth.create_custom_token('user1')
6093
id_token = _sign_in(custom_token, api_key)
@@ -152,6 +185,18 @@ def new_user_list():
152185
for uid in users:
153186
auth.delete_user(uid)
154187

188+
@pytest.fixture
189+
def new_user_email_unverified():
190+
random_id, email = _random_id()
191+
user = auth.create_user(
192+
uid=random_id,
193+
email=email,
194+
email_verified=False,
195+
password='password'
196+
)
197+
yield user
198+
auth.delete_user(user.uid)
199+
155200
def test_get_user(new_user_with_params):
156201
user = auth.get_user(new_user_with_params.uid)
157202
assert user.uid == new_user_with_params.uid
@@ -373,37 +418,41 @@ def test_import_users_with_password(api_key):
373418
finally:
374419
auth.delete_user(uid)
375420

376-
@pytest.fixture
377-
def action_code_settings():
378-
return auth.ActionCodeSettings('http://localhost')
379-
380-
def _validate_link_url(link):
381-
assert isinstance(link, six.string_types)
382-
six.moves.urllib.parse.urlparse(link)
383-
384-
def test_password_reset(new_user_with_params):
385-
link = auth.generate_password_reset_link(new_user_with_params.email)
386-
_validate_link_url(link)
387-
388-
def test_email_verification(new_user_with_params):
389-
link = auth.generate_email_verification_link(new_user_with_params.email)
390-
_validate_link_url(link)
391-
392-
def test_password_reset_with_settings(new_user_with_params, action_code_settings):
393-
link = auth.generate_password_reset_link(new_user_with_params.email,
421+
def test_password_reset(new_user_email_unverified, api_key):
422+
link = auth.generate_password_reset_link(new_user_email_unverified.email)
423+
oob_code = _validate_link_url(link, check_continue_url=False)
424+
assert new_user_email_unverified.email == _reset_password(oob_code, "newPassword", api_key)
425+
assert auth.get_user(new_user_email_unverified.uid).email_verified
426+
427+
def test_email_verification(new_user_email_unverified, api_key):
428+
link = auth.generate_email_verification_link(new_user_email_unverified.email)
429+
oob_code = _validate_link_url(link, check_continue_url=False)
430+
assert new_user_email_unverified.email == _verify_email(oob_code, api_key)
431+
assert auth.get_user(new_user_email_unverified.uid).email_verified
432+
433+
def test_password_reset_with_settings(new_user_email_unverified, api_key):
434+
action_code_settings = auth.ActionCodeSettings(ACTION_LINK_CONTINUE_URL)
435+
link = auth.generate_password_reset_link(new_user_email_unverified.email,
394436
action_code_settings=action_code_settings)
395-
_validate_link_url(link)
437+
oob_code = _validate_link_url(link)
438+
assert new_user_email_unverified.email == _reset_password(oob_code, "newPassword", api_key)
439+
assert auth.get_user(new_user_email_unverified.uid).email_verified
396440

397-
def test_email_verification_with_settings(new_user_with_params, action_code_settings):
398-
link = auth.generate_email_verification_link(new_user_with_params.email,
441+
def test_email_verification_with_settings(new_user_email_unverified, api_key):
442+
action_code_settings = auth.ActionCodeSettings(ACTION_LINK_CONTINUE_URL)
443+
link = auth.generate_email_verification_link(new_user_email_unverified.email,
399444
action_code_settings=action_code_settings)
400-
_validate_link_url(link)
445+
oob_code = _validate_link_url(link)
446+
assert new_user_email_unverified.email == _verify_email(oob_code, api_key)
447+
assert auth.get_user(new_user_email_unverified.uid).email_verified
401448

402-
def test_email_sign_in_with_settings(new_user_with_params, action_code_settings):
403-
link = auth.generate_sign_in_with_email_link(new_user_with_params.email,
449+
def test_email_sign_in_with_settings(new_user_email_unverified, api_key):
450+
action_code_settings = auth.ActionCodeSettings(ACTION_LINK_CONTINUE_URL)
451+
link = auth.generate_sign_in_with_email_link(new_user_email_unverified.email,
404452
action_code_settings=action_code_settings)
405-
_validate_link_url(link)
406-
453+
oob_code = _validate_link_url(link)
454+
assert _sign_in_with_email_link(new_user_email_unverified.email, oob_code, api_key)
455+
assert auth.get_user(new_user_email_unverified.uid).email_verified
407456

408457
class CredentialWrapper(credentials.Base):
409458
"""A custom Firebase credential that wraps an OAuth2 token."""

0 commit comments

Comments
 (0)
0