520 lines
		
	
	
	
		
			17 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			520 lines
		
	
	
	
		
			17 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
# Copyright 2018 Google Inc.
 | 
						|
#
 | 
						|
# Licensed under the Apache License, Version 2.0 (the "License");
 | 
						|
# you may not use this file except in compliance with the License.
 | 
						|
# You may obtain a copy of the License at
 | 
						|
#
 | 
						|
#     http://www.apache.org/licenses/LICENSE-2.0
 | 
						|
#
 | 
						|
# Unless required by applicable law or agreed to in writing, software
 | 
						|
# distributed under the License is distributed on an "AS IS" BASIS,
 | 
						|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
						|
# See the License for the specific language governing permissions and
 | 
						|
# limitations under the License.
 | 
						|
 | 
						|
"""Firebase user import sub module."""
 | 
						|
 | 
						|
import base64
 | 
						|
import json
 | 
						|
 | 
						|
from firebase_admin import _auth_utils
 | 
						|
 | 
						|
 | 
						|
def b64_encode(bytes_value):
 | 
						|
    return base64.urlsafe_b64encode(bytes_value).decode()
 | 
						|
 | 
						|
 | 
						|
class UserProvider:
 | 
						|
    """Represents a user identity provider that can be associated with a Firebase user.
 | 
						|
 | 
						|
    One or more providers can be specified in an ``ImportUserRecord`` when importing users via
 | 
						|
    ``auth.import_users()``.
 | 
						|
 | 
						|
    Args:
 | 
						|
        uid: User's unique ID assigned by the identity provider.
 | 
						|
        provider_id: ID of the identity provider. This can be a short domain name or the identifier
 | 
						|
            of an OpenID identity provider.
 | 
						|
        email: User's email address (optional).
 | 
						|
        display_name: User's display name (optional).
 | 
						|
        photo_url: User's photo URL (optional).
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, uid, provider_id, email=None, display_name=None, photo_url=None):
 | 
						|
        self.uid = uid
 | 
						|
        self.provider_id = provider_id
 | 
						|
        self.email = email
 | 
						|
        self.display_name = display_name
 | 
						|
        self.photo_url = photo_url
 | 
						|
 | 
						|
    @property
 | 
						|
    def uid(self):
 | 
						|
        return self._uid
 | 
						|
 | 
						|
    @uid.setter
 | 
						|
    def uid(self, uid):
 | 
						|
        self._uid = _auth_utils.validate_uid(uid, required=True)
 | 
						|
 | 
						|
    @property
 | 
						|
    def provider_id(self):
 | 
						|
        return self._provider_id
 | 
						|
 | 
						|
    @provider_id.setter
 | 
						|
    def provider_id(self, provider_id):
 | 
						|
        self._provider_id = _auth_utils.validate_provider_id(provider_id, required=True)
 | 
						|
 | 
						|
    @property
 | 
						|
    def email(self):
 | 
						|
        return self._email
 | 
						|
 | 
						|
    @email.setter
 | 
						|
    def email(self, email):
 | 
						|
        self._email = _auth_utils.validate_email(email)
 | 
						|
 | 
						|
    @property
 | 
						|
    def display_name(self):
 | 
						|
        return self._display_name
 | 
						|
 | 
						|
    @display_name.setter
 | 
						|
    def display_name(self, display_name):
 | 
						|
        self._display_name = _auth_utils.validate_display_name(display_name)
 | 
						|
 | 
						|
    @property
 | 
						|
    def photo_url(self):
 | 
						|
        return self._photo_url
 | 
						|
 | 
						|
    @photo_url.setter
 | 
						|
    def photo_url(self, photo_url):
 | 
						|
        self._photo_url = _auth_utils.validate_photo_url(photo_url)
 | 
						|
 | 
						|
    def to_dict(self):
 | 
						|
        payload = {
 | 
						|
            'rawId': self.uid,
 | 
						|
            'providerId': self.provider_id,
 | 
						|
            'displayName': self.display_name,
 | 
						|
            'email': self.email,
 | 
						|
            'photoUrl': self.photo_url,
 | 
						|
        }
 | 
						|
        return {k: v for k, v in payload.items() if v is not None}
 | 
						|
 | 
						|
 | 
						|
class ImportUserRecord:
 | 
						|
    """Represents a user account to be imported to Firebase Auth.
 | 
						|
 | 
						|
    Must specify the ``uid`` field at a minimum. A sequence of ``ImportUserRecord`` objects can be
 | 
						|
    passed to the ``auth.import_users()`` function, in order to import those users into Firebase
 | 
						|
    Auth in bulk. If the ``password_hash`` is set on a user, a hash configuration must be
 | 
						|
    specified when calling ``import_users()``.
 | 
						|
 | 
						|
    Args:
 | 
						|
        uid: User's unique ID. Must be a non-empty string not longer than 128 characters.
 | 
						|
        email: User's email address (optional).
 | 
						|
        email_verified: A boolean indicating whether the user's email has been verified (optional).
 | 
						|
        display_name: User's display name (optional).
 | 
						|
        phone_number: User's phone number (optional).
 | 
						|
        photo_url: User's photo URL (optional).
 | 
						|
        disabled: A boolean indicating whether this user account has been disabled (optional).
 | 
						|
        user_metadata: An ``auth.UserMetadata`` instance with additional user metadata (optional).
 | 
						|
        provider_data: A list of ``auth.UserProvider`` instances (optional).
 | 
						|
        custom_claims: A ``dict`` of custom claims to be set on the user account (optional).
 | 
						|
        password_hash: User's password hash as a ``bytes`` sequence (optional).
 | 
						|
        password_salt: User's password salt as a ``bytes`` sequence (optional).
 | 
						|
 | 
						|
    Raises:
 | 
						|
        ValueError: If provided arguments are invalid.
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, uid, email=None, email_verified=None, display_name=None, phone_number=None,
 | 
						|
                 photo_url=None, disabled=None, user_metadata=None, provider_data=None,
 | 
						|
                 custom_claims=None, password_hash=None, password_salt=None):
 | 
						|
        self.uid = uid
 | 
						|
        self.email = email
 | 
						|
        self.display_name = display_name
 | 
						|
        self.phone_number = phone_number
 | 
						|
        self.photo_url = photo_url
 | 
						|
        self.password_hash = password_hash
 | 
						|
        self.password_salt = password_salt
 | 
						|
        self.email_verified = email_verified
 | 
						|
        self.disabled = disabled
 | 
						|
        self.user_metadata = user_metadata
 | 
						|
        self.provider_data = provider_data
 | 
						|
        self.custom_claims = custom_claims
 | 
						|
 | 
						|
    @property
 | 
						|
    def uid(self):
 | 
						|
        return self._uid
 | 
						|
 | 
						|
    @uid.setter
 | 
						|
    def uid(self, uid):
 | 
						|
        self._uid = _auth_utils.validate_uid(uid, required=True)
 | 
						|
 | 
						|
    @property
 | 
						|
    def email(self):
 | 
						|
        return self._email
 | 
						|
 | 
						|
    @email.setter
 | 
						|
    def email(self, email):
 | 
						|
        self._email = _auth_utils.validate_email(email)
 | 
						|
 | 
						|
    @property
 | 
						|
    def display_name(self):
 | 
						|
        return self._display_name
 | 
						|
 | 
						|
    @display_name.setter
 | 
						|
    def display_name(self, display_name):
 | 
						|
        self._display_name = _auth_utils.validate_display_name(display_name)
 | 
						|
 | 
						|
    @property
 | 
						|
    def phone_number(self):
 | 
						|
        return self._phone_number
 | 
						|
 | 
						|
    @phone_number.setter
 | 
						|
    def phone_number(self, phone_number):
 | 
						|
        self._phone_number = _auth_utils.validate_phone(phone_number)
 | 
						|
 | 
						|
    @property
 | 
						|
    def photo_url(self):
 | 
						|
        return self._photo_url
 | 
						|
 | 
						|
    @photo_url.setter
 | 
						|
    def photo_url(self, photo_url):
 | 
						|
        self._photo_url = _auth_utils.validate_photo_url(photo_url)
 | 
						|
 | 
						|
    @property
 | 
						|
    def password_hash(self):
 | 
						|
        return self._password_hash
 | 
						|
 | 
						|
    @password_hash.setter
 | 
						|
    def password_hash(self, password_hash):
 | 
						|
        self._password_hash = _auth_utils.validate_bytes(password_hash, 'password_hash')
 | 
						|
 | 
						|
    @property
 | 
						|
    def password_salt(self):
 | 
						|
        return self._password_salt
 | 
						|
 | 
						|
    @password_salt.setter
 | 
						|
    def password_salt(self, password_salt):
 | 
						|
        self._password_salt = _auth_utils.validate_bytes(password_salt, 'password_salt')
 | 
						|
 | 
						|
    @property
 | 
						|
    def user_metadata(self):
 | 
						|
        return self._user_metadata
 | 
						|
 | 
						|
    @user_metadata.setter
 | 
						|
    def user_metadata(self, user_metadata):
 | 
						|
        created_at = user_metadata.creation_timestamp if user_metadata is not None else None
 | 
						|
        last_login_at = user_metadata.last_sign_in_timestamp if user_metadata is not None else None
 | 
						|
        self._created_at = _auth_utils.validate_timestamp(created_at, 'creation_timestamp')
 | 
						|
        self._last_login_at = _auth_utils.validate_timestamp(
 | 
						|
            last_login_at, 'last_sign_in_timestamp')
 | 
						|
        self._user_metadata = user_metadata
 | 
						|
 | 
						|
    @property
 | 
						|
    def provider_data(self):
 | 
						|
        return self._provider_data
 | 
						|
 | 
						|
    @provider_data.setter
 | 
						|
    def provider_data(self, provider_data):
 | 
						|
        if provider_data is not None:
 | 
						|
            try:
 | 
						|
                if any([not isinstance(p, UserProvider) for p in provider_data]):
 | 
						|
                    raise ValueError('One or more provider data instances are invalid.')
 | 
						|
            except TypeError:
 | 
						|
                raise ValueError('provider_data must be iterable.')
 | 
						|
        self._provider_data = provider_data
 | 
						|
 | 
						|
    @property
 | 
						|
    def custom_claims(self):
 | 
						|
        return self._custom_claims
 | 
						|
 | 
						|
    @custom_claims.setter
 | 
						|
    def custom_claims(self, custom_claims):
 | 
						|
        json_claims = json.dumps(custom_claims) if isinstance(
 | 
						|
            custom_claims, dict) else custom_claims
 | 
						|
        self._custom_claims_str = _auth_utils.validate_custom_claims(json_claims)
 | 
						|
        self._custom_claims = custom_claims
 | 
						|
 | 
						|
    def to_dict(self):
 | 
						|
        """Returns a dict representation of the user. For internal use only."""
 | 
						|
        payload = {
 | 
						|
            'localId': self.uid,
 | 
						|
            'email': self.email,
 | 
						|
            'displayName': self.display_name,
 | 
						|
            'phoneNumber': self.phone_number,
 | 
						|
            'photoUrl': self.photo_url,
 | 
						|
            'emailVerified': (bool(self.email_verified)
 | 
						|
                              if self.email_verified is not None else None),
 | 
						|
            'disabled': bool(self.disabled) if self.disabled is not None else None,
 | 
						|
            'customAttributes': self._custom_claims_str,
 | 
						|
            'createdAt': self._created_at,
 | 
						|
            'lastLoginAt': self._last_login_at,
 | 
						|
            'passwordHash': b64_encode(self.password_hash) if self.password_hash else None,
 | 
						|
            'salt': b64_encode(self.password_salt) if self.password_salt else None,
 | 
						|
        }
 | 
						|
        if self.provider_data:
 | 
						|
            payload['providerUserInfo'] = [p.to_dict() for p in self.provider_data]
 | 
						|
        return {k: v for k, v in payload.items() if v is not None}
 | 
						|
 | 
						|
 | 
						|
class UserImportHash:
 | 
						|
    """Represents a hash algorithm used to hash user passwords.
 | 
						|
 | 
						|
    An instance of this class must be specified when importing users with passwords via the
 | 
						|
    ``auth.import_users()`` API. Use one of the provided class methods to obtain new
 | 
						|
    instances when required. Refer to `documentation`_ for more details.
 | 
						|
 | 
						|
    .. _documentation: https://firebase.google.com/docs/auth/admin/import-users
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, name, data=None):
 | 
						|
        self._name = name
 | 
						|
        self._data = data
 | 
						|
 | 
						|
    def to_dict(self):
 | 
						|
        payload = {'hashAlgorithm': self._name}
 | 
						|
        if self._data:
 | 
						|
            payload.update(self._data)
 | 
						|
        return payload
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def _hmac(cls, name, key):
 | 
						|
        data = {
 | 
						|
            'signerKey': b64_encode(_auth_utils.validate_bytes(key, 'key', required=True))
 | 
						|
        }
 | 
						|
        return UserImportHash(name, data)
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def hmac_sha512(cls, key):
 | 
						|
        """Creates a new HMAC SHA512 algorithm instance.
 | 
						|
 | 
						|
        Args:
 | 
						|
            key: Signer key as a byte sequence.
 | 
						|
 | 
						|
        Returns:
 | 
						|
            UserImportHash: A new ``UserImportHash``.
 | 
						|
        """
 | 
						|
        return cls._hmac('HMAC_SHA512', key)
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def hmac_sha256(cls, key):
 | 
						|
        """Creates a new HMAC SHA256 algorithm instance.
 | 
						|
 | 
						|
        Args:
 | 
						|
            key: Signer key as a byte sequence.
 | 
						|
 | 
						|
        Returns:
 | 
						|
            UserImportHash: A new ``UserImportHash``.
 | 
						|
        """
 | 
						|
        return cls._hmac('HMAC_SHA256', key)
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def hmac_sha1(cls, key):
 | 
						|
        """Creates a new HMAC SHA1 algorithm instance.
 | 
						|
 | 
						|
        Args:
 | 
						|
            key: Signer key as a byte sequence.
 | 
						|
 | 
						|
        Returns:
 | 
						|
            UserImportHash: A new ``UserImportHash``.
 | 
						|
        """
 | 
						|
        return cls._hmac('HMAC_SHA1', key)
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def hmac_md5(cls, key):
 | 
						|
        """Creates a new HMAC MD5 algorithm instance.
 | 
						|
 | 
						|
        Args:
 | 
						|
            key: Signer key as a byte sequence.
 | 
						|
 | 
						|
        Returns:
 | 
						|
            UserImportHash: A new ``UserImportHash``.
 | 
						|
        """
 | 
						|
        return cls._hmac('HMAC_MD5', key)
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def md5(cls, rounds):
 | 
						|
        """Creates a new MD5 algorithm instance.
 | 
						|
 | 
						|
        Args:
 | 
						|
            rounds: Number of rounds. Must be an integer between 0 and 8192.
 | 
						|
 | 
						|
        Returns:
 | 
						|
            UserImportHash: A new ``UserImportHash``.
 | 
						|
        """
 | 
						|
        return UserImportHash(
 | 
						|
            'MD5',
 | 
						|
            {'rounds': _auth_utils.validate_int(rounds, 'rounds', 0, 8192)})
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def sha1(cls, rounds):
 | 
						|
        """Creates a new SHA1 algorithm instance.
 | 
						|
 | 
						|
        Args:
 | 
						|
            rounds: Number of rounds. Must be an integer between 1 and 8192.
 | 
						|
 | 
						|
        Returns:
 | 
						|
            UserImportHash: A new ``UserImportHash``.
 | 
						|
        """
 | 
						|
        return UserImportHash(
 | 
						|
            'SHA1',
 | 
						|
            {'rounds': _auth_utils.validate_int(rounds, 'rounds', 1, 8192)})
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def sha256(cls, rounds):
 | 
						|
        """Creates a new SHA256 algorithm instance.
 | 
						|
 | 
						|
        Args:
 | 
						|
            rounds: Number of rounds. Must be an integer between 1 and 8192.
 | 
						|
 | 
						|
        Returns:
 | 
						|
            UserImportHash: A new ``UserImportHash``.
 | 
						|
        """
 | 
						|
        return UserImportHash(
 | 
						|
            'SHA256',
 | 
						|
            {'rounds': _auth_utils.validate_int(rounds, 'rounds', 1, 8192)})
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def sha512(cls, rounds):
 | 
						|
        """Creates a new SHA512 algorithm instance.
 | 
						|
 | 
						|
        Args:
 | 
						|
            rounds: Number of rounds. Must be an integer between 1 and 8192.
 | 
						|
 | 
						|
        Returns:
 | 
						|
            UserImportHash: A new ``UserImportHash``.
 | 
						|
        """
 | 
						|
        return UserImportHash(
 | 
						|
            'SHA512',
 | 
						|
            {'rounds': _auth_utils.validate_int(rounds, 'rounds', 1, 8192)})
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def pbkdf_sha1(cls, rounds):
 | 
						|
        """Creates a new PBKDF SHA1 algorithm instance.
 | 
						|
 | 
						|
        Args:
 | 
						|
            rounds: Number of rounds. Must be an integer between 0 and 120000.
 | 
						|
 | 
						|
        Returns:
 | 
						|
            UserImportHash: A new ``UserImportHash``.
 | 
						|
        """
 | 
						|
        return UserImportHash(
 | 
						|
            'PBKDF_SHA1',
 | 
						|
            {'rounds': _auth_utils.validate_int(rounds, 'rounds', 0, 120000)})
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def pbkdf2_sha256(cls, rounds):
 | 
						|
        """Creates a new PBKDF2 SHA256 algorithm instance.
 | 
						|
 | 
						|
        Args:
 | 
						|
            rounds: Number of rounds. Must be an integer between 0 and 120000.
 | 
						|
 | 
						|
        Returns:
 | 
						|
            UserImportHash: A new ``UserImportHash``.
 | 
						|
        """
 | 
						|
        return UserImportHash(
 | 
						|
            'PBKDF2_SHA256',
 | 
						|
            {'rounds': _auth_utils.validate_int(rounds, 'rounds', 0, 120000)})
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def scrypt(cls, key, rounds, memory_cost, salt_separator=None):
 | 
						|
        """Creates a new Scrypt algorithm instance.
 | 
						|
 | 
						|
        This is the modified Scrypt algorithm used by Firebase Auth. See ``standard_scrypt()``
 | 
						|
        function for the standard Scrypt algorith,
 | 
						|
 | 
						|
        Args:
 | 
						|
            key: Signer key as a byte sequence.
 | 
						|
            rounds: Number of rounds. Must be an integer between 1 and 8.
 | 
						|
            memory_cost: Memory cost as an integer between 1 and 14.
 | 
						|
            salt_separator: Salt separator as a byte sequence (optional).
 | 
						|
 | 
						|
        Returns:
 | 
						|
            UserImportHash: A new ``UserImportHash``.
 | 
						|
        """
 | 
						|
        data = {
 | 
						|
            'signerKey': b64_encode(_auth_utils.validate_bytes(key, 'key', required=True)),
 | 
						|
            'rounds': _auth_utils.validate_int(rounds, 'rounds', 1, 8),
 | 
						|
            'memoryCost': _auth_utils.validate_int(memory_cost, 'memory_cost', 1, 14),
 | 
						|
        }
 | 
						|
        if salt_separator:
 | 
						|
            data['saltSeparator'] = b64_encode(_auth_utils.validate_bytes(
 | 
						|
                salt_separator, 'salt_separator'))
 | 
						|
        return UserImportHash('SCRYPT', data)
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def bcrypt(cls):
 | 
						|
        """Creates a new Bcrypt algorithm instance.
 | 
						|
 | 
						|
        Returns:
 | 
						|
            UserImportHash: A new ``UserImportHash``.
 | 
						|
        """
 | 
						|
        return UserImportHash('BCRYPT')
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def standard_scrypt(cls, memory_cost, parallelization, block_size, derived_key_length):
 | 
						|
        """Creates a new standard Scrypt algorithm instance.
 | 
						|
 | 
						|
        Args:
 | 
						|
            memory_cost: Memory cost as a non-negaive integer.
 | 
						|
            parallelization: Parallelization as a non-negative integer.
 | 
						|
            block_size: Block size as a non-negative integer.
 | 
						|
            derived_key_length: Derived key length as a non-negative integer.
 | 
						|
 | 
						|
        Returns:
 | 
						|
            UserImportHash: A new ``UserImportHash``.
 | 
						|
        """
 | 
						|
        data = {
 | 
						|
            'memoryCost': _auth_utils.validate_int(memory_cost, 'memory_cost', low=0),
 | 
						|
            'parallelization': _auth_utils.validate_int(parallelization, 'parallelization', low=0),
 | 
						|
            'blockSize': _auth_utils.validate_int(block_size, 'block_size', low=0),
 | 
						|
            'dkLen': _auth_utils.validate_int(derived_key_length, 'derived_key_length', low=0),
 | 
						|
        }
 | 
						|
        return UserImportHash('STANDARD_SCRYPT', data)
 | 
						|
 | 
						|
 | 
						|
class ErrorInfo:
 | 
						|
    """Represents an error encountered while performing a batch operation such
 | 
						|
    as importing users or deleting multiple user accounts.
 | 
						|
    """
 | 
						|
    # TODO(rsgowman): This class used to be specific to importing users (hence
 | 
						|
    # it's home in _user_import.py). It's now also used by bulk deletion of
 | 
						|
    # users. Move this to a more common location.
 | 
						|
 | 
						|
    def __init__(self, error):
 | 
						|
        self._index = error['index']
 | 
						|
        self._reason = error['message']
 | 
						|
 | 
						|
    @property
 | 
						|
    def index(self):
 | 
						|
        return self._index
 | 
						|
 | 
						|
    @property
 | 
						|
    def reason(self):
 | 
						|
        return self._reason
 | 
						|
 | 
						|
 | 
						|
class UserImportResult:
 | 
						|
    """Represents the result of a bulk user import operation.
 | 
						|
 | 
						|
    See ``auth.import_users()`` API for more details.
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, result, total):
 | 
						|
        errors = result.get('error', [])
 | 
						|
        self._success_count = total - len(errors)
 | 
						|
        self._failure_count = len(errors)
 | 
						|
        self._errors = [ErrorInfo(err) for err in errors]
 | 
						|
 | 
						|
    @property
 | 
						|
    def success_count(self):
 | 
						|
        """Returns the number of users successfully imported."""
 | 
						|
        return self._success_count
 | 
						|
 | 
						|
    @property
 | 
						|
    def failure_count(self):
 | 
						|
        """Returns the number of users that failed to be imported."""
 | 
						|
        return self._failure_count
 | 
						|
 | 
						|
    @property
 | 
						|
    def errors(self):
 | 
						|
        """Returns a list of ``auth.ErrorInfo`` instances describing the errors encountered."""
 | 
						|
        return self._errors
 |