Skip to content

Commit d256436

Browse files
committed
Add API to look up users by provider user ID.
1 parent d3dda24 commit d256436

File tree

5 files changed

+126
-12
lines changed

5 files changed

+126
-12
lines changed

firebase_admin/_auth_utils.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,15 @@ def validate_provider_id(provider_id, required=True):
102102
'string.'.format(provider_id))
103103
return provider_id
104104

105+
def validate_provider_uid(provider_uid, required=True):
106+
if provider_uid is None and not required:
107+
return None
108+
if not isinstance(provider_uid, six.string_types) or not provider_uid:
109+
raise ValueError(
110+
'Invalid provider uid: "{0}". Provider uid must be a non-empty '
111+
'string.'.format(provider_uid))
112+
return provider_uid
113+
105114
def validate_photo_url(photo_url, required=False):
106115
"""Parses and validates the given URL string."""
107116
if photo_url is None and not required:

firebase_admin/_user_mgt.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,24 @@ def get_user(self, **kwargs):
468468
elif 'phone_number' in kwargs:
469469
key, key_type = kwargs.pop('phone_number'), 'phone number'
470470
payload = {'phoneNumber' : [_auth_utils.validate_phone(key, required=True)]}
471+
elif 'provider_id' in kwargs and 'provider_uid' in kwargs:
472+
provider_id = kwargs.pop('provider_id')
473+
provider_uid = kwargs.pop('provider_uid')
474+
if provider_id == 'phone':
475+
key, key_type = provider_uid, 'phone number'
476+
payload = {'phoneNumber' : [_auth_utils.validate_phone(key, required=True)]}
477+
elif provider_id == 'password':
478+
key, key_type = provider_uid, 'email'
479+
payload = {'email' : [_auth_utils.validate_email(key, required=True)]}
480+
else:
481+
key, key_type = {
482+
'provider_id': provider_id, 'provider_uid': provider_uid
483+
}, 'provider_user_id'
484+
payload = {
485+
'federated_user_id' : [{
486+
'provider_id': _auth_utils.validate_provider_id(key['provider_id']),
487+
'raw_id': _auth_utils.validate_provider_id(key['provider_uid'])}]
488+
}
471489
else:
472490
raise TypeError('Unsupported keyword arguments: {0}.'.format(kwargs))
473491

firebase_admin/auth.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
'get_user',
7373
'get_user_by_email',
7474
'get_user_by_phone_number',
75+
'get_user_by_provider_user_id',
7576
'import_users',
7677
'list_users',
7778
'revoke_refresh_tokens',
@@ -308,6 +309,28 @@ def get_user_by_phone_number(phone_number, app=None):
308309
response = user_manager.get_user(phone_number=phone_number)
309310
return UserRecord(response)
310311

312+
def get_user_by_provider_user_id(provider_id, provider_uid, app=None):
313+
"""Gets the user data corresponding to the specified provider identifier.
314+
315+
Args:
316+
provider_id: Identifier for the given provider, for example,
317+
"google.com" for the Google provider.
318+
provider_uid: The user identifier with the given provider.
319+
app: An App instance (optional).
320+
321+
Returns:
322+
UserRecord: A UserRecord instance.
323+
324+
Raises:
325+
ValueError: If the given provider identifier is None or empty, or if
326+
the given provider user identifier is None or empty.
327+
UserNotFoundError: If no user exists by the specified identifiers.
328+
FirebaseError: If an error occurs while retrieving the user.
329+
"""
330+
user_manager = _get_auth_service(app).user_manager
331+
response = user_manager.get_user(
332+
provider_id=provider_id, provider_uid=provider_uid)
333+
return UserRecord(response)
311334

312335
def list_users(page_token=None, max_results=_user_mgt.MAX_LIST_USERS_RESULTS, app=None):
313336
"""Retrieves a page of user accounts from a Firebase project.

integration/test_auth.py

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,27 @@ def new_user_with_params():
177177
yield user
178178
auth.delete_user(user.uid)
179179

180+
@pytest.fixture
181+
def imported_user_with_params():
182+
random_id, email = _random_id()
183+
phone = _random_phone()
184+
user = auth.ImportUserRecord(
185+
uid=random_id,
186+
email=email,
187+
phone_number=phone,
188+
display_name='Random User',
189+
photo_url='https://example.com/photo.png',
190+
user_metadata=auth.UserMetadata(100, 150),
191+
password_hash=b'password', password_salt=b'NaCl', custom_claims={'admin': True},
192+
email_verified=True,
193+
disabled=False,
194+
provider_data=[auth.UserProvider(uid='test', provider_id='google.com')])
195+
hash_alg = auth.UserImportHash.scrypt(
196+
b'key', rounds=8, memory_cost=14, salt_separator=b'sep')
197+
auth.import_users([user], hash_alg=hash_alg)
198+
yield user
199+
auth.delete_user(user.uid)
200+
180201
@pytest.fixture
181202
def new_user_list():
182203
users = [
@@ -200,24 +221,33 @@ def new_user_email_unverified():
200221
yield user
201222
auth.delete_user(user.uid)
202223

203-
def test_get_user(new_user_with_params):
204-
user = auth.get_user(new_user_with_params.uid)
205-
assert user.uid == new_user_with_params.uid
224+
def test_get_user(imported_user_with_params):
225+
user = auth.get_user(imported_user_with_params.uid)
226+
assert user.uid == imported_user_with_params.uid
206227
assert user.display_name == 'Random User'
207-
assert user.email == new_user_with_params.email
208-
assert user.phone_number == new_user_with_params.phone_number
228+
assert user.email == imported_user_with_params.email
229+
assert user.phone_number == imported_user_with_params.phone_number
209230
assert user.photo_url == 'https://example.com/photo.png'
210231
assert user.email_verified is True
211232
assert user.disabled is False
233+
assert len(user.provider_data) == 3
234+
provider_ids = sorted([provider.provider_id for provider in user.provider_data])
235+
assert provider_ids == ['google.com', 'password', 'phone']
212236

213-
user = auth.get_user_by_email(new_user_with_params.email)
214-
assert user.uid == new_user_with_params.uid
215-
user = auth.get_user_by_phone_number(new_user_with_params.phone_number)
216-
assert user.uid == new_user_with_params.uid
237+
user = auth.get_user_by_email(imported_user_with_params.email)
238+
assert user.uid == imported_user_with_params.uid
217239

218-
assert len(user.provider_data) == 2
219-
provider_ids = sorted([provider.provider_id for provider in user.provider_data])
220-
assert provider_ids == ['password', 'phone']
240+
user = auth.get_user_by_phone_number(imported_user_with_params.phone_number)
241+
assert user.uid == imported_user_with_params.uid
242+
243+
user = auth.get_user_by_provider_user_id('phone', imported_user_with_params.phone_number)
244+
assert user.uid == imported_user_with_params.uid
245+
246+
user = auth.get_user_by_provider_user_id('password', imported_user_with_params.email)
247+
assert user.uid == imported_user_with_params.uid
248+
249+
user = auth.get_user_by_provider_user_id('google.com', 'test')
250+
assert user.uid == imported_user_with_params.uid
221251

222252
def test_list_users(new_user_list):
223253
err_msg_template = (

tests/test_user_mgt.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,19 @@ def test_get_user_by_phone(self, user_mgt_app):
217217
_instrument_user_manager(user_mgt_app, 200, MOCK_GET_USER_RESPONSE)
218218
_check_user_record(auth.get_user_by_phone_number('+1234567890', user_mgt_app))
219219

220+
def test_invalid_get_user_empty_provider_id(self, user_mgt_app):
221+
with pytest.raises(ValueError):
222+
auth.get_user_by_provider_user_id("", "test_provider_uid", app=user_mgt_app)
223+
224+
def test_invalid_get_user_empty_provider_uid(self, user_mgt_app):
225+
with pytest.raises(ValueError):
226+
auth.get_user_by_provider_user_id("google.com", "", app=user_mgt_app)
227+
228+
def test_get_user_by_provider_user_id(self, user_mgt_app):
229+
_instrument_user_manager(user_mgt_app, 200, MOCK_GET_USER_RESPONSE)
230+
_check_user_record(
231+
auth.get_user_by_provider_user_id('google.com', 'test_google_id', user_mgt_app))
232+
220233
def test_get_user_non_existing(self, user_mgt_app):
221234
_instrument_user_manager(user_mgt_app, 200, '{"users":[]}')
222235
with pytest.raises(auth.UserNotFoundError) as excinfo:
@@ -247,6 +260,17 @@ def test_get_user_by_phone_non_existing(self, user_mgt_app):
247260
assert excinfo.value.http_response is not None
248261
assert excinfo.value.cause is None
249262

263+
def test_get_user_by_provider_user_id_non_existing(self, user_mgt_app):
264+
_instrument_user_manager(user_mgt_app, 200, '{"users":[]}')
265+
with pytest.raises(auth.UserNotFoundError) as excinfo:
266+
auth.get_user_by_provider_user_id('google.com', 'test_google_id', user_mgt_app)
267+
error_msg = 'No user record found for the provided provider_user_id: %s.' % str(
268+
{'provider_id': 'google.com', 'provider_uid': 'test_google_id'})
269+
assert excinfo.value.code == exceptions.NOT_FOUND
270+
assert str(excinfo.value) == error_msg
271+
assert excinfo.value.http_response is not None
272+
assert excinfo.value.cause is None
273+
250274
def test_get_user_http_error(self, user_mgt_app):
251275
_instrument_user_manager(user_mgt_app, 500, '{"error":{"message": "USER_NOT_FOUND"}}')
252276
with pytest.raises(auth.UserNotFoundError) as excinfo:
@@ -293,6 +317,16 @@ def test_get_user_by_phone_http_error(self, user_mgt_app):
293317
assert excinfo.value.http_response is not None
294318
assert excinfo.value.cause is not None
295319

320+
def test_get_user_by_provider_user_id_http_error(self, user_mgt_app):
321+
_instrument_user_manager(user_mgt_app, 500, '{"error":{"message": "USER_NOT_FOUND"}}')
322+
with pytest.raises(auth.UserNotFoundError) as excinfo:
323+
auth.get_user_by_provider_user_id('google.com', 'test_google_id', user_mgt_app)
324+
error_msg = 'No user record found for the given identifier (USER_NOT_FOUND).'
325+
assert excinfo.value.code == exceptions.NOT_FOUND
326+
assert str(excinfo.value) == error_msg
327+
assert excinfo.value.http_response is not None
328+
assert excinfo.value.cause is not None
329+
296330

297331
class TestCreateUser:
298332

0 commit comments

Comments
 (0)