Skip to content

Commit 0a11d07

Browse files
authored
feat(auth): ability to delete provider in auth (#579)
* [add] ability to delete provider in auth * [fix] pylint * [fix] tests * [fix] tests * fix comments * fix tests * fix lint * [fix] address comments
1 parent f38c5f7 commit 0a11d07

File tree

5 files changed

+42
-2
lines changed

5 files changed

+42
-2
lines changed

‎firebase_admin/_auth_client.py

+2
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,8 @@ def update_user(self, uid, **kwargs): # pylint: disable=differing-param-doc
336336
valid_since: An integer signifying the seconds since the epoch (optional). This field
337337
is set by ``revoke_refresh_tokens`` and it is discouraged to set this field
338338
directly.
339+
providers_to_delete: The list of provider IDs to unlink,
340+
eg: 'google.com', 'password', etc.
339341
340342
Returns:
341343
UserRecord: An updated UserRecord instance for the user.

‎firebase_admin/_auth_utils.py

+9
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,15 @@ def validate_action_type(action_type):
266266
Valid values are {1}'.format(action_type, ', '.join(VALID_EMAIL_ACTION_TYPES)))
267267
return action_type
268268

269+
def validate_provider_ids(provider_ids, required=False):
270+
if not provider_ids:
271+
if required:
272+
raise ValueError('Invalid provider IDs. Provider ids should be provided')
273+
return []
274+
for provider_id in provider_ids:
275+
validate_provider_id(provider_id, True)
276+
return provider_ids
277+
269278
def build_update_mask(params):
270279
"""Creates an update mask list from the given dictionary."""
271280
mask = []

‎firebase_admin/_user_mgt.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -688,7 +688,7 @@ def create_user(self, uid=None, display_name=None, email=None, phone_number=None
688688

689689
def update_user(self, uid, display_name=None, email=None, phone_number=None,
690690
photo_url=None, password=None, disabled=None, email_verified=None,
691-
valid_since=None, custom_claims=None):
691+
valid_since=None, custom_claims=None, providers_to_delete=None):
692692
"""Updates an existing user account with the specified properties"""
693693
payload = {
694694
'localId': _auth_utils.validate_uid(uid, required=True),
@@ -700,6 +700,7 @@ def update_user(self, uid, display_name=None, email=None, phone_number=None,
700700
}
701701

702702
remove = []
703+
remove_provider = _auth_utils.validate_provider_ids(providers_to_delete)
703704
if display_name is not None:
704705
if display_name is DELETE_ATTRIBUTE:
705706
remove.append('DISPLAY_NAME')
@@ -715,7 +716,7 @@ def update_user(self, uid, display_name=None, email=None, phone_number=None,
715716

716717
if phone_number is not None:
717718
if phone_number is DELETE_ATTRIBUTE:
718-
payload['deleteProvider'] = ['phone']
719+
remove_provider.append('phone')
719720
else:
720721
payload['phoneNumber'] = _auth_utils.validate_phone(phone_number)
721722

@@ -726,6 +727,9 @@ def update_user(self, uid, display_name=None, email=None, phone_number=None,
726727
custom_claims, dict) else custom_claims
727728
payload['customAttributes'] = _auth_utils.validate_custom_claims(json_claims)
728729

730+
if remove_provider:
731+
payload['deleteProvider'] = list(set(remove_provider))
732+
729733
payload = {k: v for k, v in payload.items() if v is not None}
730734
body, http_resp = self._make_request('post', '/accounts:update', json=payload)
731735
if not body or not body.get('localId'):

‎integration/test_auth.py

+8
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,14 @@ def test_disable_user(new_user_with_params):
496496
assert user.disabled is True
497497
assert len(user.provider_data) == 1
498498

499+
def test_remove_provider(new_user_with_provider):
500+
provider_ids = [provider.provider_id for provider in new_user_with_provider.provider_data]
501+
assert 'google.com' in provider_ids
502+
user = auth.update_user(new_user_with_provider, providers_to_delete=['google.com'])
503+
assert user.uid == new_user_with_params.uid
504+
new_provider_ids = [provider.provider_id for provider in user.provider_data]
505+
assert 'google.com' not in new_provider_ids
506+
499507
def test_delete_user():
500508
user = auth.create_user()
501509
auth.delete_user(user.uid)

‎tests/test_user_mgt.py

+17
Original file line numberDiff line numberDiff line change
@@ -663,6 +663,23 @@ def test_update_user_valid_since(self, user_mgt_app, arg):
663663
request = json.loads(recorder[0].body.decode())
664664
assert request == {'localId': 'testuser', 'validSince': int(arg)}
665665

666+
@pytest.mark.parametrize('arg', [['phone'], ['google.com', 'phone']])
667+
def test_update_user_delete_provider(self, user_mgt_app, arg):
668+
user_mgt, recorder = _instrument_user_manager(user_mgt_app, 200, '{"localId":"testuser"}')
669+
user_mgt.update_user('testuser', providers_to_delete=arg)
670+
request = json.loads(recorder[0].body.decode())
671+
assert set(request['deleteProvider']) == set(arg)
672+
673+
@pytest.mark.parametrize('arg', [[], ['phone'], ['google.com'], ['google.com', 'phone']])
674+
def test_update_user_delete_provider_and_phone(self, user_mgt_app, arg):
675+
user_mgt, recorder = _instrument_user_manager(user_mgt_app, 200, '{"localId":"testuser"}')
676+
user_mgt.update_user('testuser',
677+
providers_to_delete=arg,
678+
phone_number=auth.DELETE_ATTRIBUTE)
679+
request = json.loads(recorder[0].body.decode())
680+
assert 'phone' in request['deleteProvider']
681+
assert len(set(request['deleteProvider'])) == len(request['deleteProvider'])
682+
assert set(arg) - set(request['deleteProvider']) == set()
666683

667684
class TestSetCustomUserClaims:
668685

0 commit comments

Comments
 (0)