Skip to content

Remove (base64) 'REDACTED' passwords from user records. #352

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 2 commits into from
Sep 30, 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
14 changes: 12 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ pylint firebase_admin

However, it is recommended that you use the [`lint.sh`](lint.sh) bash script to invoke
pylint. This script will run the linter on both `firebase_admin` and the corresponding
`tests` module. It suprresses some of the noisy warnings that get generated
`tests` module. It suppresses some of the noisy warnings that get generated
when running pylint on test code. Note that by default `lint.sh` will only
validate the locally modified source files. To validate all source files,
pass `all` as an argument.
Expand Down Expand Up @@ -181,13 +181,23 @@ Then set up your Firebase/GCP project as follows:
to set up Firestore either in the locked mode or in the test mode.
2. Enable password auth: Select "Authentication" from the "Develop" menu in
Firebase Console. Select the "Sign-in method" tab, and enable the
"Email/Password" sign-in method.
"Email/Password" sign-in method, including the Email link (passwordless
sign-in) option.

3. Enable the IAM API: Go to the
[Google Cloud Platform Console](https://console.cloud.google.com) and make
sure your Firebase/GCP project is selected. Select "APIs & Services >
Dashboard" from the main menu, and click the "ENABLE APIS AND SERVICES"
button. Search for and enable the "Identity and Access Management (IAM)
API".
4. Grant your service account the 'Firebase Authentication Admin' role. This is
required to ensure that exported user records contain the password hashes of
the user accounts:
1. Go to [Google Cloud Platform Console / IAM & admin](https://console.cloud.google.com/iam-admin).
2. Find your service account in the list, and click the 'pencil' icon to edit it's permissions.
3. Click 'ADD ANOTHER ROLE' and choose 'Firebase Authentication Admin'.
4. Click 'SAVE'.


Now you can invoke the integration test suite as follows:

Expand Down
18 changes: 14 additions & 4 deletions firebase_admin/_user_mgt.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@

"""Firebase user management sub module."""

import base64
import json

import requests
import six
from six.moves import urllib
Expand All @@ -26,6 +26,7 @@

MAX_LIST_USERS_RESULTS = 1000
MAX_IMPORT_USERS_SIZE = 1000
B64_REDACTED = base64.b64encode(b'REDACTED')


class Sentinel(object):
Expand Down Expand Up @@ -257,9 +258,17 @@ def password_hash(self):
If the Firebase Auth hashing algorithm (SCRYPT) was used to create the user account, this
is the base64-encoded password hash of the user. If a different hashing algorithm was
used to create this user, as is typical when migrating from another Auth system, this
is an empty string. If no password is set, this is ``None``.
is an empty string. If no password is set, or if the service account doesn't have permission
to read the password, then this is ``None``.
"""
return self._data.get('passwordHash')
password_hash = self._data.get('passwordHash')

# If the password hash is redacted (probably due to missing permissions) then clear it out,
# similar to how the salt is returned. (Otherwise, it *looks* like a b64-encoded hash is
# present, which is confusing.)
if password_hash == B64_REDACTED:
return None
return password_hash

@property
def password_salt(self):
Expand All @@ -268,7 +277,8 @@ def password_salt(self):
If the Firebase Auth hashing algorithm (SCRYPT) was used to create the user account, this
is the base64-encoded password salt of the user. If a different hashing algorithm was
used to create this user, as is typical when migrating from another Auth system, this is
an empty string. If no password is set, this is ``None``.
an empty string. If no password is set, or if the service account doesn't have permission to
read the password, then this is ``None``.
"""
return self._data.get('salt')

Expand Down
16 changes: 12 additions & 4 deletions integration/test_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,10 @@ def test_get_user(new_user_with_params):
assert provider_ids == ['password', 'phone']

def test_list_users(new_user_list):
err_msg_template = (
'Missing {field} field. A common cause would be forgetting to add the "Firebase ' +
'Authentication Admin" permission. See instructions in CONTRIBUTING.md')

fetched = []
# Test exporting all user accounts.
page = auth.list_users()
Expand All @@ -228,8 +232,10 @@ def test_list_users(new_user_list):
assert isinstance(user, auth.ExportedUserRecord)
if user.uid in new_user_list:
fetched.append(user.uid)
assert user.password_hash is not None
assert user.password_salt is not None
assert user.password_hash is not None, (
err_msg_template.format(field='password_hash'))
assert user.password_salt is not None, (
err_msg_template.format(field='password_salt'))
page = page.get_next_page()
assert len(fetched) == len(new_user_list)

Expand All @@ -239,8 +245,10 @@ def test_list_users(new_user_list):
assert isinstance(user, auth.ExportedUserRecord)
if user.uid in new_user_list:
fetched.append(user.uid)
assert user.password_hash is not None
assert user.password_salt is not None
assert user.password_hash is not None, (
err_msg_template.format(field='password_hash'))
assert user.password_salt is not None, (
err_msg_template.format(field='password_salt'))
assert len(fetched) == len(new_user_list)

def test_create_user(new_user):
Expand Down
8 changes: 8 additions & 0 deletions tests/test_user_mgt.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

"""Test cases for the firebase_admin._user_mgt module."""

import base64
import json
import time

Expand Down Expand Up @@ -152,6 +153,13 @@ def test_exported_record_empty_password(self):
assert user.password_hash == ''
assert user.password_salt == ''

def test_redacted_passwords_cleared(self):
user = auth.ExportedUserRecord({
'localId': 'user',
'passwordHash': base64.b64encode(b'REDACTED'),
})
assert user.password_hash is None

def test_custom_claims(self):
user = auth.UserRecord({
'localId' : 'user',
Expand Down