Skip to content

Introducing auth.DELETE_ATTRIBUTE sentinel value #285

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 20, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# Unreleased

-
- [added] Added a new `auth.DELETE_ATTRIBUTE` sentinel value, which can be
used to delete `phone_number`, `display_name`, `photo_url` and `custom_claims`
attributes from a user account. It is now recommended to use this sentinel
value over passing `None` for deleting attributes.

# v2.16.0

Expand Down
21 changes: 14 additions & 7 deletions firebase_admin/_user_mgt.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,18 @@
MAX_LIST_USERS_RESULTS = 1000
MAX_IMPORT_USERS_SIZE = 1000

class _Unspecified(object):
pass

class Sentinel(object):

def __init__(self, description):
self.description = description


# Use this internally, until sentinels are available in the public API.
_UNSPECIFIED = _Unspecified()
_UNSPECIFIED = Sentinel('No value specified')


DELETE_ATTRIBUTE = Sentinel('Value used to delete an attribute from a user profile')


class ApiCallError(Exception):
Expand Down Expand Up @@ -546,26 +553,26 @@ def update_user(self, uid, display_name=_UNSPECIFIED, email=None, phone_number=_

remove = []
if display_name is not _UNSPECIFIED:
if display_name is None:
if display_name is None or display_name is DELETE_ATTRIBUTE:
remove.append('DISPLAY_NAME')
else:
payload['displayName'] = _auth_utils.validate_display_name(display_name)
if photo_url is not _UNSPECIFIED:
if photo_url is None:
if photo_url is None or photo_url is DELETE_ATTRIBUTE:
remove.append('PHOTO_URL')
else:
payload['photoUrl'] = _auth_utils.validate_photo_url(photo_url)
if remove:
payload['deleteAttribute'] = remove

if phone_number is not _UNSPECIFIED:
if phone_number is None:
if phone_number is None or phone_number is DELETE_ATTRIBUTE:
payload['deleteProvider'] = ['phone']
else:
payload['phoneNumber'] = _auth_utils.validate_phone(phone_number)

if custom_claims is not _UNSPECIFIED:
if custom_claims is None:
if custom_claims is None or custom_claims is DELETE_ATTRIBUTE:
custom_claims = {}
json_claims = json.dumps(custom_claims) if isinstance(
custom_claims, dict) else custom_claims
Expand Down
11 changes: 7 additions & 4 deletions firebase_admin/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
__all__ = [
'ActionCodeSettings',
'AuthError',
'DELETE_ATTRIBUTE',
'ErrorInfo',
'ExportedUserRecord',
'ImportUserRecord',
Expand Down Expand Up @@ -68,6 +69,7 @@
]

ActionCodeSettings = _user_mgt.ActionCodeSettings
DELETE_ATTRIBUTE = _user_mgt.DELETE_ATTRIBUTE
ErrorInfo = _user_import.ErrorInfo
ExportedUserRecord = _user_mgt.ExportedUserRecord
ListUsersPage = _user_mgt.ListUsersPage
Expand Down Expand Up @@ -359,17 +361,18 @@ def update_user(uid, **kwargs):

Keyword Args:
display_name: The user's display name (optional). Can be removed by explicitly passing
None.
``auth.DELETE_ATTRIBUTE``.
email: The user's primary email (optional).
email_verified: A boolean indicating whether or not the user's primary email is
verified (optional).
phone_number: The user's primary phone number (optional). Can be removed by explicitly
passing None.
photo_url: The user's photo URL (optional). Can be removed by explicitly passing None.
passing ``auth.DELETE_ATTRIBUTE``.
photo_url: The user's photo URL (optional). Can be removed by explicitly passing
``auth.DELETE_ATTRIBUTE``.
password: The user's raw, unhashed password. (optional).
disabled: A boolean indicating whether or not the user account is disabled (optional).
custom_claims: A dictionary or a JSON string contining the custom claims to be set on the
user account (optional).
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.

Expand Down
22 changes: 21 additions & 1 deletion tests/test_user_mgt.py
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,13 @@ def test_update_user_custom_claims(self, user_mgt_app):
request = json.loads(recorder[0].body.decode())
assert request == {'localId' : 'testuser', 'customAttributes' : json.dumps(claims)}

def test_update_user_delete_fields(self, user_mgt_app):
def test_delete_user_custom_claims(self, user_mgt_app):
user_mgt, recorder = _instrument_user_manager(user_mgt_app, 200, '{"localId":"testuser"}')
user_mgt.update_user('testuser', custom_claims=auth.DELETE_ATTRIBUTE)
request = json.loads(recorder[0].body.decode())
assert request == {'localId' : 'testuser', 'customAttributes' : json.dumps({})}

def test_update_user_delete_fields_with_none(self, user_mgt_app):
user_mgt, recorder = _instrument_user_manager(user_mgt_app, 200, '{"localId":"testuser"}')
user_mgt.update_user('testuser', display_name=None, photo_url=None, phone_number=None)
request = json.loads(recorder[0].body.decode())
Expand All @@ -391,6 +397,20 @@ def test_update_user_delete_fields(self, user_mgt_app):
'deleteProvider' : ['phone'],
}

def test_update_user_delete_fields(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)
request = json.loads(recorder[0].body.decode())
assert request == {
'localId' : 'testuser',
'deleteAttribute' : ['DISPLAY_NAME', 'PHOTO_URL'],
'deleteProvider' : ['phone'],
}

def test_update_user_error(self, user_mgt_app):
_instrument_user_manager(user_mgt_app, 500, '{"error":"test"}')
with pytest.raises(auth.AuthError) as excinfo:
Expand Down