From c0e182f6cb0c0aa16f14ea9346261bc6779d65dc Mon Sep 17 00:00:00 2001 From: Narges Simjour Date: Mon, 13 Jan 2020 10:47:53 -0500 Subject: [PATCH] Add API to link/unlink provider info to/from user account. --- firebase_admin/_auth_utils.py | 10 ++++++++ firebase_admin/_user_mgt.py | 10 ++++++-- firebase_admin/auth.py | 2 ++ integration/test_auth.py | 30 +++++++++++++++++++++++ tests/test_user_mgt.py | 46 +++++++++++++++++++++++++++++++++++ 5 files changed, 96 insertions(+), 2 deletions(-) diff --git a/firebase_admin/_auth_utils.py b/firebase_admin/_auth_utils.py index df3e0acfc..1ff0f13ca 100644 --- a/firebase_admin/_auth_utils.py +++ b/firebase_admin/_auth_utils.py @@ -102,6 +102,16 @@ def validate_provider_id(provider_id, required=True): 'string.'.format(provider_id)) return provider_id +def validate_provider_ids(provider_ids, required=False): + if provider_ids is None: + if required: + raise ValueError('Invalid provider IDs. The list must be non-empty.') + else: + return None + for provider_id in provider_ids: + validate_provider_id(provider_id, True) + return provider_ids + def validate_photo_url(photo_url, required=False): if photo_url is None and not required: return None diff --git a/firebase_admin/_user_mgt.py b/firebase_admin/_user_mgt.py index 2e10fac1b..bf5e2826a 100644 --- a/firebase_admin/_user_mgt.py +++ b/firebase_admin/_user_mgt.py @@ -532,7 +532,8 @@ def create_user(self, uid=None, display_name=None, email=None, phone_number=None def update_user(self, uid, display_name=None, email=None, phone_number=None, photo_url=None, password=None, disabled=None, email_verified=None, - valid_since=None, custom_claims=None): + valid_since=None, custom_claims=None, link_provider=None, + delete_provider_ids=None): """Updates an existing user account with the specified properties""" payload = { '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, 'validSince': _auth_utils.validate_timestamp(valid_since, 'valid_since'), 'emailVerified': bool(email_verified) if email_verified is not None else None, 'disableUser': bool(disabled) if disabled is not None else None, + 'linkProviderUserInfo': link_provider.to_dict() if link_provider is not None else None, + 'deleteProvider': _auth_utils.validate_provider_ids(delete_provider_ids, required=False), } remove = [] @@ -559,7 +562,10 @@ def update_user(self, uid, display_name=None, email=None, phone_number=None, if phone_number is not None: if phone_number is DELETE_ATTRIBUTE: - payload['deleteProvider'] = ['phone'] + if payload['deleteProvider'] is None: + payload['deleteProvider'] = ['phone'] + else: + payload['deleteProvider'].append('phone') else: payload['phoneNumber'] = _auth_utils.validate_phone(phone_number) diff --git a/firebase_admin/auth.py b/firebase_admin/auth.py index a5110c211..159b3a13b 100644 --- a/firebase_admin/auth.py +++ b/firebase_admin/auth.py @@ -388,6 +388,8 @@ def update_user(uid, **kwargs): user account (optional). To remove all custom claims, pass ``auth.DELETE_ATTRIBUTE``. valid_since: An integer signifying the seconds since the epoch. This field is set by ``revoke_refresh_tokens`` and it is discouraged to set this field directly. + link_provider: User's provider info to be linked to the user account. + delete_provider_ids: A list of IDs of providers to be unlinked from the user account. Returns: UserRecord: An updated UserRecord instance for the user. diff --git a/integration/test_auth.py b/integration/test_auth.py index 9d5d0dfe3..c28531909 100644 --- a/integration/test_auth.py +++ b/integration/test_auth.py @@ -288,6 +288,36 @@ def test_update_user(new_user): assert user.custom_claims is None assert len(user.provider_data) == 2 + user = auth.update_user( + new_user.uid, + link_provider=auth.UserProvider( + uid='test', provider_id='google.com', email='test@example.com', + display_name='Test Name', photo_url='https://test.com/user.png')) + assert user.uid == new_user.uid + assert len(user.provider_data) == 3 + + user = auth.update_user( + new_user.uid, + phone_number=auth.DELETE_ATTRIBUTE, + delete_provider_ids=['google.com']) + assert user.uid == new_user.uid + assert user.phone_number is None + assert len(user.provider_data) == 1 + + user = auth.update_user( + new_user.uid, + phone_number=phone) + assert user.uid == new_user.uid + assert user.phone_number == phone + assert len(user.provider_data) == 2 + + user = auth.update_user( + new_user.uid, + delete_provider_ids=['phone', 'google.com']) + assert user.uid == new_user.uid + assert user.phone_number is None + assert len(user.provider_data) == 1 + def test_set_custom_user_claims(new_user, api_key): claims = {'admin' : True, 'package' : 'gold'} auth.set_custom_user_claims(new_user.uid, claims) diff --git a/tests/test_user_mgt.py b/tests/test_user_mgt.py index f4e03cc3f..29c2574b8 100644 --- a/tests/test_user_mgt.py +++ b/tests/test_user_mgt.py @@ -488,6 +488,52 @@ def test_update_user_delete_fields(self, user_mgt_app): 'deleteProvider' : ['phone'], } + def test_update_user_delete_providers(self, user_mgt_app): + user_mgt, recorder = _instrument_user_manager(user_mgt_app, 200, '{"localId":"testuser"}') + user_mgt.update_user( + 'testuser', + delete_provider_ids=['google.com', 'facebook.com']) + request = json.loads(recorder[0].body.decode()) + assert request == { + 'localId' : 'testuser', + 'deleteProvider' : ['google.com', 'facebook.com'], + } + + def test_update_user_delete_fields_and_providers(self, user_mgt_app): + user_mgt, recorder = _instrument_user_manager(user_mgt_app, 200, '{"localId":"testuser"}') + user_mgt.update_user( + 'testuser', + display_name=auth.DELETE_ATTRIBUTE, + photo_url=auth.DELETE_ATTRIBUTE, + phone_number=auth.DELETE_ATTRIBUTE, + delete_provider_ids=['google.com', 'facebook.com']) + request = json.loads(recorder[0].body.decode()) + print request + assert request == { + 'localId' : 'testuser', + 'deleteAttribute' : ['DISPLAY_NAME', 'PHOTO_URL'], + 'deleteProvider' : ['google.com', 'facebook.com', 'phone'], + } + + def test_update_user_link_provider(self, user_mgt_app): + user_mgt, recorder = _instrument_user_manager(user_mgt_app, 200, '{"localId":"testuser"}') + user_mgt.update_user( + 'testuser', + link_provider=auth.UserProvider( + uid='test', provider_id='google.com', email='test@example.com', + display_name='Test Name', photo_url='https://test.com/user.png')) + request = json.loads(recorder[0].body.decode()) + assert request == { + 'localId' : 'testuser', + 'linkProviderUserInfo' : { + 'rawId': 'test', + 'providerId': 'google.com', + 'email': 'test@example.com', + 'displayName': 'Test Name', + 'photoUrl': 'https://test.com/user.png' + } + } + def test_update_user_error(self, user_mgt_app): _instrument_user_manager(user_mgt_app, 500, '{"error": {"message": "UNEXPECTED_CODE"}}') with pytest.raises(exceptions.InternalError) as excinfo: