Skip to content

Commit c0e182f

Browse files
committed
Add API to link/unlink provider info to/from user account.
1 parent 31d91d6 commit c0e182f

File tree

5 files changed

+96
-2
lines changed

5 files changed

+96
-2
lines changed

firebase_admin/_auth_utils.py

+10
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,16 @@ def validate_provider_id(provider_id, required=True):
102102
'string.'.format(provider_id))
103103
return provider_id
104104

105+
def validate_provider_ids(provider_ids, required=False):
106+
if provider_ids is None:
107+
if required:
108+
raise ValueError('Invalid provider IDs. The list must be non-empty.')
109+
else:
110+
return None
111+
for provider_id in provider_ids:
112+
validate_provider_id(provider_id, True)
113+
return provider_ids
114+
105115
def validate_photo_url(photo_url, required=False):
106116
if photo_url is None and not required:
107117
return None

firebase_admin/_user_mgt.py

+8-2
Original file line numberDiff line numberDiff line change
@@ -532,7 +532,8 @@ def create_user(self, uid=None, display_name=None, email=None, phone_number=None
532532

533533
def update_user(self, uid, display_name=None, email=None, phone_number=None,
534534
photo_url=None, password=None, disabled=None, email_verified=None,
535-
valid_since=None, custom_claims=None):
535+
valid_since=None, custom_claims=None, link_provider=None,
536+
delete_provider_ids=None):
536537
"""Updates an existing user account with the specified properties"""
537538
payload = {
538539
'localId': _auth_utils.validate_uid(uid, required=True),
@@ -541,6 +542,8 @@ def update_user(self, uid, display_name=None, email=None, phone_number=None,
541542
'validSince': _auth_utils.validate_timestamp(valid_since, 'valid_since'),
542543
'emailVerified': bool(email_verified) if email_verified is not None else None,
543544
'disableUser': bool(disabled) if disabled is not None else None,
545+
'linkProviderUserInfo': link_provider.to_dict() if link_provider is not None else None,
546+
'deleteProvider': _auth_utils.validate_provider_ids(delete_provider_ids, required=False),
544547
}
545548

546549
remove = []
@@ -559,7 +562,10 @@ def update_user(self, uid, display_name=None, email=None, phone_number=None,
559562

560563
if phone_number is not None:
561564
if phone_number is DELETE_ATTRIBUTE:
562-
payload['deleteProvider'] = ['phone']
565+
if payload['deleteProvider'] is None:
566+
payload['deleteProvider'] = ['phone']
567+
else:
568+
payload['deleteProvider'].append('phone')
563569
else:
564570
payload['phoneNumber'] = _auth_utils.validate_phone(phone_number)
565571

firebase_admin/auth.py

+2
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,8 @@ def update_user(uid, **kwargs):
388388
user account (optional). To remove all custom claims, pass ``auth.DELETE_ATTRIBUTE``.
389389
valid_since: An integer signifying the seconds since the epoch. This field is set by
390390
``revoke_refresh_tokens`` and it is discouraged to set this field directly.
391+
link_provider: User's provider info to be linked to the user account.
392+
delete_provider_ids: A list of IDs of providers to be unlinked from the user account.
391393
392394
Returns:
393395
UserRecord: An updated UserRecord instance for the user.

integration/test_auth.py

+30
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,36 @@ def test_update_user(new_user):
288288
assert user.custom_claims is None
289289
assert len(user.provider_data) == 2
290290

291+
user = auth.update_user(
292+
new_user.uid,
293+
link_provider=auth.UserProvider(
294+
uid='test', provider_id='google.com', email='[email protected]',
295+
display_name='Test Name', photo_url='https://test.com/user.png'))
296+
assert user.uid == new_user.uid
297+
assert len(user.provider_data) == 3
298+
299+
user = auth.update_user(
300+
new_user.uid,
301+
phone_number=auth.DELETE_ATTRIBUTE,
302+
delete_provider_ids=['google.com'])
303+
assert user.uid == new_user.uid
304+
assert user.phone_number is None
305+
assert len(user.provider_data) == 1
306+
307+
user = auth.update_user(
308+
new_user.uid,
309+
phone_number=phone)
310+
assert user.uid == new_user.uid
311+
assert user.phone_number == phone
312+
assert len(user.provider_data) == 2
313+
314+
user = auth.update_user(
315+
new_user.uid,
316+
delete_provider_ids=['phone', 'google.com'])
317+
assert user.uid == new_user.uid
318+
assert user.phone_number is None
319+
assert len(user.provider_data) == 1
320+
291321
def test_set_custom_user_claims(new_user, api_key):
292322
claims = {'admin' : True, 'package' : 'gold'}
293323
auth.set_custom_user_claims(new_user.uid, claims)

tests/test_user_mgt.py

+46
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,52 @@ def test_update_user_delete_fields(self, user_mgt_app):
488488
'deleteProvider' : ['phone'],
489489
}
490490

491+
def test_update_user_delete_providers(self, user_mgt_app):
492+
user_mgt, recorder = _instrument_user_manager(user_mgt_app, 200, '{"localId":"testuser"}')
493+
user_mgt.update_user(
494+
'testuser',
495+
delete_provider_ids=['google.com', 'facebook.com'])
496+
request = json.loads(recorder[0].body.decode())
497+
assert request == {
498+
'localId' : 'testuser',
499+
'deleteProvider' : ['google.com', 'facebook.com'],
500+
}
501+
502+
def test_update_user_delete_fields_and_providers(self, user_mgt_app):
503+
user_mgt, recorder = _instrument_user_manager(user_mgt_app, 200, '{"localId":"testuser"}')
504+
user_mgt.update_user(
505+
'testuser',
506+
display_name=auth.DELETE_ATTRIBUTE,
507+
photo_url=auth.DELETE_ATTRIBUTE,
508+
phone_number=auth.DELETE_ATTRIBUTE,
509+
delete_provider_ids=['google.com', 'facebook.com'])
510+
request = json.loads(recorder[0].body.decode())
511+
print request
512+
assert request == {
513+
'localId' : 'testuser',
514+
'deleteAttribute' : ['DISPLAY_NAME', 'PHOTO_URL'],
515+
'deleteProvider' : ['google.com', 'facebook.com', 'phone'],
516+
}
517+
518+
def test_update_user_link_provider(self, user_mgt_app):
519+
user_mgt, recorder = _instrument_user_manager(user_mgt_app, 200, '{"localId":"testuser"}')
520+
user_mgt.update_user(
521+
'testuser',
522+
link_provider=auth.UserProvider(
523+
uid='test', provider_id='google.com', email='[email protected]',
524+
display_name='Test Name', photo_url='https://test.com/user.png'))
525+
request = json.loads(recorder[0].body.decode())
526+
assert request == {
527+
'localId' : 'testuser',
528+
'linkProviderUserInfo' : {
529+
'rawId': 'test',
530+
'providerId': 'google.com',
531+
'email': '[email protected]',
532+
'displayName': 'Test Name',
533+
'photoUrl': 'https://test.com/user.png'
534+
}
535+
}
536+
491537
def test_update_user_error(self, user_mgt_app):
492538
_instrument_user_manager(user_mgt_app, 500, '{"error": {"message": "UNEXPECTED_CODE"}}')
493539
with pytest.raises(exceptions.InternalError) as excinfo:

0 commit comments

Comments
 (0)