def test_import_users_missing_required_hash(self, user_mgt_app): users = [ auth.ImportUserRecord(uid='user1', password_hash=b'password'), auth.ImportUserRecord(uid='user2'), ] with pytest.raises(ValueError): auth.import_users(users, app=user_mgt_app)
def test_import_users_with_hash(self, user_mgt_app): _, recorder = _instrument_user_manager(user_mgt_app, 200, '{}') users = [ auth.ImportUserRecord(uid='user1', password_hash=b'password'), auth.ImportUserRecord(uid='user2'), ] hash_alg = auth.UserImportHash.scrypt(b'key', rounds=8, memory_cost=14, salt_separator=b'sep') result = auth.import_users(users, hash_alg=hash_alg, app=user_mgt_app) assert result.success_count == 2 assert result.failure_count is 0 assert result.errors == [] expected = { 'users': [{ 'localId': 'user1', 'passwordHash': _user_import.b64_encode(b'password') }, { 'localId': 'user2' }], 'hashAlgorithm': 'SCRYPT', 'signerKey': _user_import.b64_encode(b'key'), 'rounds': 8, 'memoryCost': 14, 'saltSeparator': _user_import.b64_encode(b'sep'), } self._check_rpc_calls(recorder, expected)
def import_users(): # [START build_user_list] # Up to 1000 users can be imported at once. users = [ auth.ImportUserRecord(uid='uid1', email='*****@*****.**', password_hash=b'password_hash_1', password_salt=b'salt1'), auth.ImportUserRecord(uid='uid2', email='*****@*****.**', password_hash=b'password_hash_2', password_salt=b'salt2'), ] # [END build_user_list] # [START import_users] hash_alg = auth.UserImportHash.hmac_sha256(key=b'secret_key') try: result = auth.import_users(users, hash_alg=hash_alg) print('Successfully imported {0} users. Failed to import {1} users.'. format(result.success_count, result.failure_count)) for err in result.errors: print('Failed to import {0} due to {1}'.format( users[err.index].uid, err.reason)) except exceptions.FirebaseError: # Some unrecoverable error occurred that prevented the operation from running. pass
def test_import_users_error(self, user_mgt_app): _, recorder = _instrument_user_manager( user_mgt_app, 200, """{"error": [ {"index": 0, "message": "Some error occured in user1"}, {"index": 2, "message": "Another error occured in user3"} ]}""") users = [ auth.ImportUserRecord(uid='user1'), auth.ImportUserRecord(uid='user2'), auth.ImportUserRecord(uid='user3'), ] result = auth.import_users(users, app=user_mgt_app) assert result.success_count == 1 assert result.failure_count == 2 assert len(result.errors) == 2 err = result.errors[0] assert err.index == 0 assert err.reason == 'Some error occured in user1' err = result.errors[1] assert err.index == 2 assert err.reason == 'Another error occured in user3' expected = { 'users': [{ 'localId': 'user1' }, { 'localId': 'user2' }, { 'localId': 'user3' }] } self._check_rpc_calls(recorder, expected)
def test_import_users(self, user_mgt_app): _, recorder = _instrument_user_manager(user_mgt_app, 200, '{}') users = [ auth.ImportUserRecord(uid='user1'), auth.ImportUserRecord(uid='user2'), ] result = auth.import_users(users, app=user_mgt_app) assert result.success_count == 2 assert result.failure_count is 0 assert result.errors == [] expected = {'users': [{'localId': 'user1'}, {'localId': 'user2'}]} self._check_rpc_calls(recorder, expected)
def import_with_scrypt(): # [START import_with_scrypt] users = [ auth.ImportUserRecord( uid='some-uid', email='*****@*****.**', password_hash=b'password_hash', password_salt=b'salt' ), ] # All the parameters below can be obtained from the Firebase Console's "Users" # section. Base64 encoded parameters must be decoded into raw bytes. hash_alg = auth.UserImportHash.scrypt( key=base64.b64decode('base64_secret'), salt_separator=base64.b64decode('base64_salt_separator'), rounds=8, memory_cost=14 ) try: result = auth.import_users(users, hash_alg=hash_alg) for err in result.errors: print('Failed to import user:'******'Error importing users:', error)
def test_import_users_with_password(api_key): uid, email = _random_id() password_hash = base64.b64decode( 'V358E8LdWJXAO7muq0CufVpEOXaj8aFiC7T/rcaGieN04q/ZPJ08WhJEHGjj9lz/2TT+/86N5VjVoc5DdBhBiw==' ) user = auth.ImportUserRecord(uid=uid, email=email, password_hash=password_hash, password_salt=b'NaCl') scrypt_key = base64.b64decode( 'jxspr8Ki0RYycVU8zykbdLGjFQ3McFUH0uiiTvC8pVMXAn210wjLNmdZJzxUECKbm0QsEmYUSDzZvpjeJ9WmXA==' ) salt_separator = base64.b64decode('Bw==') scrypt = auth.UserImportHash.scrypt(key=scrypt_key, salt_separator=salt_separator, rounds=8, memory_cost=14) result = auth.import_users([user], hash_alg=scrypt) try: assert result.success_count == 1 assert result.failure_count == 0 saved_user = auth.get_user(uid) assert saved_user.email == email id_token = _sign_in_with_password(email, 'password', api_key) assert len(id_token) > 0 finally: auth.delete_user(uid)
def test_all_params(self): providers = [auth.UserProvider(uid='test', provider_id='google.com')] metadata = auth.UserMetadata(100, 150) user = auth.ImportUserRecord(uid='test', email='*****@*****.**', photo_url='https://test.com/user.png', phone_number='+1234567890', display_name='name', user_metadata=metadata, password_hash=b'password', password_salt=b'NaCl', custom_claims={'admin': True}, email_verified=True, disabled=False, provider_data=providers) expected = { 'localId': 'test', 'email': '*****@*****.**', 'photoUrl': 'https://test.com/user.png', 'phoneNumber': '+1234567890', 'displayName': 'name', 'createdAt': 100, 'lastLoginAt': 150, 'passwordHash': _user_import.b64_encode(b'password'), 'salt': _user_import.b64_encode(b'NaCl'), 'customAttributes': json.dumps({'admin': True}), 'emailVerified': True, 'disabled': False, 'providerUserInfo': [{ 'rawId': 'test', 'providerId': 'google.com' }], } assert user.to_dict() == expected
def import_without_password(): # [START import_without_password] users = [ auth.ImportUserRecord( uid='some-uid', display_name='John Doe', email='*****@*****.**', photo_url='http://www.example.com/12345678/photo.png', email_verified=True, phone_number='+11234567890', custom_claims={'admin': True}, # set this user as admin provider_data=[ # user with Google provider auth.UserProvider( uid='google-uid', email='*****@*****.**', display_name='John Doe', photo_url='http://www.example.com/12345678/photo.png', provider_id='google.com') ], ), ] try: result = auth.import_users(users) for err in result.errors: print('Failed to import user:'******'Error importing users:', error)
def test_custom_claims(self, claims): user = auth.ImportUserRecord(uid='test', custom_claims=claims) assert user.custom_claims == claims json_claims = json.dumps(claims) if isinstance(claims, dict) else claims expected = {'localId': 'test', 'customAttributes': json_claims} assert user.to_dict() == expected
def test_email_verified(self, email_verified): user = auth.ImportUserRecord(uid='test', email_verified=email_verified) assert user.email_verified == email_verified assert user.to_dict() == { 'localId': 'test', 'emailVerified': email_verified }
def clone_auth(src, dst): print("\nCloning users...") print( "WARNING: PLEASE MANUALLY EDIT \"Sign-in method\" AND \"Templates\" FROM FIREBASE CONSOLE" ) hash_alg = auth.UserImportHash.bcrypt() user_list = auth.list_users(max_results=1000, app=src) import_list = [] for user in user_list.users: iuser = auth.ImportUserRecord( user.uid, user.email, user.email_verified, user.display_name, user.phone_number, user.photo_url, user.disabled, user.user_metadata, None, user.custom_claims, bytes(user.password_hash.encode('ascii')), bytes(user.password_salt.encode('ascii'))) import_list.append(iuser) print("\tCloning " + str(len(user_list.users)) + " users...") try: result = auth.import_users(import_list, hash_alg=hash_alg, app=dst) for err in result.errors: print('Failed to import user:'******'Error importing users:', error) while user_list.has_next_page: user_list = user_list.get_next_page() import_list = [] for user in user_list.users: iuser = auth.ImportUserRecord( user.uid, user.email, user.email_verified, user.display_name, user.phone_number, user.photo_url, user.disabled, user.user_metadata, None, user.custom_claims, bytes(user.password_hash.encode('ascii')), bytes(user.password_salt.encode('ascii'))) import_list.append(iuser) print("\tCloning " + str(len(user_list.users)) + " users...") try: result = auth.import_users(import_list, hash_alg=hash_alg, app=dst) for err in result.errors: print('Failed to import user:'******'Error importing users:', error)
def reduce(key, values): # The reduce() function must be static, so we manually create a "cls" # variable instead of changing the function into a classmethod. cls = PopulateFirebaseAccountsOneOffJob if key == cls.POPULATED_KEY: yield (cls.AUDIT_KEY, len(values)) return elif key in (cls.SUPER_ADMIN_ACK, cls.SYSTEM_COMMITTER_ACK): yield (key, values) return # NOTE: This is only sorted to make unit testing easier. user_fields = sorted(ast.literal_eval(v) for v in values) user_records = [ firebase_auth.ImportUserRecord( uid=auth_id, email=email, email_verified=True, custom_claims=('{"role":"%s"}' % feconf.FIREBASE_ROLE_SUPER_ADMIN if user_is_super_admin else None)) for auth_id, _, email, user_is_super_admin in user_fields ] # The Firebase Admin SDK places a hard-limit on the number of users that # can be "imported" in a single call. To compensate, we break up the # users into chunks. offsets = python_utils.RANGE( 0, len(user_records), cls.MAX_USERS_FIREBASE_CAN_IMPORT_PER_CALL) results = (cls.populate_firebase( [r for r in record_group if r is not None]) for record_group in utils.grouper( user_records, cls.MAX_USERS_FIREBASE_CAN_IMPORT_PER_CALL)) assocs_to_create = [] for offset, (result, exception) in python_utils.ZIP(offsets, results): if exception is not None: yield (cls.ERROR_KEY, repr(exception)) else: successful_indices = set( python_utils.RANGE(result.success_count + result.failure_count)) for error in result.errors: successful_indices.remove(error.index) debug_info = 'Import user_id=%r failed: %s' % ( user_fields[offset + error.index][1], error.reason) yield (cls.ERROR_KEY, debug_info) assocs_to_create.extend( auth_domain.AuthIdUserIdPair(*user_fields[offset + i][:2]) for i in successful_indices) if assocs_to_create: firebase_auth_services.associate_multi_auth_ids_with_user_ids( assocs_to_create) yield (cls.SUCCESS_KEY, len(assocs_to_create))
def import_with_hmac_tenant(tenant_client): # [START import_with_hmac_tenant] users = [ auth.ImportUserRecord(uid='uid1', email='*****@*****.**', password_hash=b'password_hash_1', password_salt=b'salt1'), auth.ImportUserRecord(uid='uid2', email='*****@*****.**', password_hash=b'password_hash_2', password_salt=b'salt2'), ] hash_alg = auth.UserImportHash.hmac_sha256(key=b'secret') try: result = tenant_client.import_users(users, hash_alg=hash_alg) for err in result.errors: print('Failed to import user:'******'Error importing users:', error)
def test_import_users(sample_tenant): client = tenant_mgt.auth_for_tenant(sample_tenant.tenant_id) user = auth.ImportUserRecord(uid=_random_uid(), email=_random_email()) result = client.import_users([user]) try: assert result.success_count == 1 assert result.failure_count == 0 saved_user = client.get_user(user.uid) assert saved_user.email == user.email finally: client.delete_user(user.uid)
def test_import_users(): uid, email = _random_id() user = auth.ImportUserRecord(uid=uid, email=email) result = auth.import_users([user]) try: assert result.success_count == 1 assert result.failure_count == 0 saved_user = auth.get_user(uid) assert saved_user.email == email finally: auth.delete_user(uid)
def test_import_users(self, tenant_mgt_app): client = tenant_mgt.auth_for_tenant('tenant-id', app=tenant_mgt_app) recorder = _instrument_user_mgt(client, 200, '{}') users = [ auth.ImportUserRecord(uid='user1'), auth.ImportUserRecord(uid='user2'), ] result = client.import_users(users) assert isinstance(result, auth.UserImportResult) assert result.success_count == 2 assert result.failure_count == 0 assert result.errors == [] self._assert_request(recorder, '/accounts:batchCreate', { 'users': [{ 'localId': 'user1' }, { 'localId': 'user2' }], })
def signup(): if 'photo' in request.files: photo = request.files['photo'] else: photo = request.form['photo'] photo = photo.replace('data:image/jpeg;base64,', '') photo = BytesIO(base64.b64decode(photo)) email = request.form['email'] password = request.form['password'] uid = str(uuid.uuid4()) salt = bcrypt.gensalt() password_hash = bcrypt.hashpw(password.encode('utf8'), salt) upload_config = TransferConfig(use_threads=False) image_name = uid + ".jpg" result = s3_client.upload_fileobj(photo, s3_bucket_name, image_name, ExtraArgs={ "ACL": "public-read", "ContentType": "image/jpeg" }, Config=upload_config) photo_url = "https://s3-ap-northeast-1.amazonaws.com/awesome-transit-photo/" + \ image_name ref = db.reference().child('user').child(uid) ref.set({"photo_url": photo_url, "balance": 10000, "email": email}) result = reko_client.index_faces( CollectionId=face_collection_name, DetectionAttributes=["DEFAULT"], ExternalImageId=uid, Image=dict(S3Object=dict(Bucket=s3_bucket_name, Name=image_name))) user = auth.ImportUserRecord(uid, email=email, photo_url=photo_url, password_hash=password_hash, password_salt=salt) result = auth.import_users([user], hash_alg=auth.UserImportHash.bcrypt()) return jsonify(dict(reuslt=True))
def import_with_bcrypt(): # [START import_with_bcrypt] users = [ auth.ImportUserRecord(uid='some-uid', email='*****@*****.**', password_hash=b'password_hash', password_salt=b'salt'), ] hash_alg = auth.UserImportHash.bcrypt() try: result = auth.import_users(users, hash_alg=hash_alg) for err in result.errors: print('Failed to import user:'******'Error importing users:', error)
def import_with_pbkdf(): # [START import_with_pbkdf] users = [ auth.ImportUserRecord(uid='some-uid', email='*****@*****.**', password_hash=b'password_hash', password_salt=b'salt'), ] hash_alg = auth.UserImportHash.pbkdf2_sha256(rounds=100000) try: result = auth.import_users(users, hash_alg=hash_alg) for err in result.errors: print('Failed to import user:'******'Error importing users:', error)
def new_user_with_provider() -> auth.UserRecord: uid4, email4 = _random_id() google_uid, google_email = _random_id() import_user1 = auth.ImportUserRecord(uid=uid4, email=email4, provider_data=[ auth.UserProvider( uid=google_uid, provider_id='google.com', email=google_email, ) ]) user_import_result = auth.import_users([import_user1]) assert user_import_result.success_count == 1 assert user_import_result.failure_count == 0 user = auth.get_user(uid4) yield user auth.delete_user(user.uid)
def import_with_standard_scrypt(): # [START import_with_standard_scrypt] users = [ auth.ImportUserRecord(uid='some-uid', email='*****@*****.**', password_hash=b'password_hash', password_salt=b'salt'), ] hash_alg = auth.UserImportHash.standard_scrypt(memory_cost=1024, parallelization=16, block_size=8, derived_key_length=64) try: result = auth.import_users(users, hash_alg=hash_alg) for err in result.errors: print('Failed to import user:'******'Error importing users:', error)
def migrate_users(): try: connection = mysql.connector.connect(host='db', database='migrate_users', user='******', password='******') if connection.is_connected(): cursor = connection.cursor() query = "SELECT username, email, password, id FROM user_migrate WHERE uid IS NULL ORDER BY id ASC LIMIT 1000" cursor.execute(query) records = cursor.fetchall() users = [] for row in records: uid = secrets.token_hex(16) users.append( auth.ImportUserRecord( uid=uid, display_name=row[0], email=row[1], password_hash=bytes(row[2], encoding='utf-8'), password_salt=bytes(row[2][:28], encoding='utf-8'))) update_query = "UPDATE user_migrate SET uid = %s WHERE id = %s" cursor.execute(update_query, (uid, row[3])) connection.commit() hash_alg = auth.UserImportHash.bcrypt() try: result = auth.import_users(users, hash_alg=hash_alg) for err in result.errors: print('Failed to import user:'******'user migrated !') except exceptions.FirebaseError as error: print('Error importing users:', error) except Error as e: print("Error while connecting to MySQL", e) finally: if (connection.is_connected()): cursor.close() connection.close()
def reduce(key, values): if key == POPULATED_KEY: yield (AUDIT_KEY, len(values)) return elif key == SYSTEM_COMMITTER_ACK: yield (SYSTEM_COMMITTER_ACK, values) return try: # NOTE: "app" is the term Firebase uses for the "entry point" to the # Firebase SDK. Oppia only has one server, so it only needs to # instantiate one app. firebase_connection = firebase_admin.initialize_app() except Exception as exception: yield (WARNING_KEY, repr(exception)) return # NOTE: This is only sorted to make unit testing easier. user_fields = sorted(ast.literal_eval(v) for v in values) user_records = [ firebase_auth.ImportUserRecord(uid=auth_id, email=email, email_verified=True) for auth_id, _, email in user_fields ] # The Firebase Admin SDK places a hard-limit on the number of users that # can be "imported" in a single call. To compensate, we break up the # users into chunks. offsets = python_utils.RANGE(0, len(user_records), MAX_USERS_FIREBASE_CAN_IMPORT_PER_CALL) results = (_populate_firebase( [record for record in record_group if record]) for record_group in _grouper( user_records, MAX_USERS_FIREBASE_CAN_IMPORT_PER_CALL)) assocs_to_create = [] for offset, (result, exception) in python_utils.ZIP(offsets, results): if exception is not None: yield (FAILURE_KEY, repr(exception)) else: successful_indices = set( python_utils.RANGE(result.success_count + result.failure_count)) for error in result.errors: successful_indices.remove(error.index) debug_info = ( 'Import user_id=%r failed: %s' % (user_fields[offset + error.index][1], error.reason)) yield (FAILURE_KEY, debug_info) assocs_to_create.extend( auth_domain.AuthIdUserIdPair(*user_fields[offset + i][:2]) for i in successful_indices) if assocs_to_create: firebase_auth_services.associate_multi_auth_ids_with_user_ids( assocs_to_create) yield (SUCCESS_KEY, len(assocs_to_create)) try: # NOTE: This is not dangerous. We are just deleting the resources # used to form a connection to Firebase servers. firebase_admin.delete_app(firebase_connection) except Exception as exception: yield (WARNING_KEY, repr(exception))
def test_disabled(self, disabled): user = auth.ImportUserRecord(uid='test', disabled=disabled) assert user.disabled == disabled assert user.to_dict() == {'localId': 'test', 'disabled': disabled}
def test_too_many_users(self, user_mgt_app): users = [ auth.ImportUserRecord(uid='test{0}'.format(i)) for i in range(1001) ] with pytest.raises(ValueError): auth.import_users(users, app=user_mgt_app)
def test_invalid_args(self, args): with pytest.raises(ValueError): auth.ImportUserRecord(uid='test', **args)
def test_invalid_uid(self, arg): with pytest.raises(ValueError): auth.ImportUserRecord(uid=arg)
def test_uid(self): user = auth.ImportUserRecord(uid='test') assert user.uid == 'test' assert user.custom_claims is None assert user.user_metadata is None assert user.to_dict() == {'localId': 'test'}
def seed_firebase(): """Prepares Oppia and Firebase to run the SeedFirebaseOneOffJob. NOTE: This function is idempotent. TODO(#11462): Delete this handler once the Firebase migration logic is rollback-safe and all backup data is using post-migration data. """ seed_model = auth_models.FirebaseSeedModel.get( auth_models.ONLY_FIREBASE_SEED_MODEL_ID, strict=False) if seed_model is None: # Exactly 1 seed model must exist. auth_models.FirebaseSeedModel( id=auth_models.ONLY_FIREBASE_SEED_MODEL_ID).put() user_ids_with_admin_email = [ key.id() for key in user_models.UserSettingsModel.query( user_models.UserSettingsModel.email == feconf.ADMIN_EMAIL_ADDRESS).iter(keys_only=True) ] assoc_by_user_id_models = [ model for model in auth_models.UserAuthDetailsModel.get_multi( user_ids_with_admin_email) if model is not None and model.gae_id != feconf.SYSTEM_COMMITTER_ID ] if len(assoc_by_user_id_models) != 1: raise Exception( '%s must correspond to exactly 1 user (excluding user_id=%s), but ' 'found user_ids=[%s]' % (feconf.ADMIN_EMAIL_ADDRESS, feconf.SYSTEM_COMMITTER_ID, ', '.join( m.id for m in assoc_by_user_id_models))) else: assoc_by_user_id_model = assoc_by_user_id_models[0] user_id = assoc_by_user_id_model.id auth_id = assoc_by_user_id_model.firebase_auth_id if auth_id is None: auth_id = user_id[4:] if user_id.startswith('uid_') else user_id assoc_by_user_id_model.firebase_auth_id = auth_id assoc_by_user_id_model.update_timestamps( update_last_updated_time=False) assoc_by_user_id_model.put() assoc_by_auth_id_model = (auth_models.UserIdByFirebaseAuthIdModel.get( auth_id, strict=False)) if assoc_by_auth_id_model is None: auth_models.UserIdByFirebaseAuthIdModel(id=auth_id, user_id=user_id).put() elif assoc_by_auth_id_model.user_id != user_id: assoc_by_auth_id_model.user_id = user_id assoc_by_auth_id_model.update_timestamps( update_last_updated_time=False) assoc_by_auth_id_model.put() custom_claims = '{"role":"%s"}' % feconf.FIREBASE_ROLE_SUPER_ADMIN try: user = firebase_auth.get_user_by_email(feconf.ADMIN_EMAIL_ADDRESS) except firebase_auth.UserNotFoundError: create_new_firebase_account = True else: if user.uid != auth_id: firebase_auth.update_user(user.uid, disabled=True) firebase_auth.delete_user(user.uid) create_new_firebase_account = True else: firebase_auth.set_custom_user_claims(user.uid, custom_claims) create_new_firebase_account = False if create_new_firebase_account: firebase_auth.import_users([ firebase_auth.ImportUserRecord(auth_id, email=feconf.ADMIN_EMAIL_ADDRESS, custom_claims=custom_claims), ])