diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 000000000..867dd5775 Binary files /dev/null and b/.DS_Store differ diff --git a/firebase_admin/.DS_Store b/firebase_admin/.DS_Store new file mode 100644 index 000000000..c73e1e847 Binary files /dev/null and b/firebase_admin/.DS_Store differ diff --git a/firebase_admin/_user_mgt.py b/firebase_admin/_user_mgt.py index c77c4d40d..782e82809 100644 --- a/firebase_admin/_user_mgt.py +++ b/firebase_admin/_user_mgt.py @@ -692,7 +692,7 @@ def update_user(self, uid, display_name=None, email=None, phone_number=None, """Updates an existing user account with the specified properties""" payload = { 'localId': _auth_utils.validate_uid(uid, required=True), - 'email': _auth_utils.validate_email(email), + 'email': _auth_utils.validate_email(email) if email is not DELETE_ATTRIBUTE else None, 'password': _auth_utils.validate_password(password), 'validSince': _auth_utils.validate_timestamp(valid_since, 'valid_since'), 'emailVerified': bool(email_verified) if email_verified is not None else None, @@ -720,6 +720,12 @@ def update_user(self, uid, display_name=None, email=None, phone_number=None, else: payload['phoneNumber'] = _auth_utils.validate_phone(phone_number) + if email is not None: + if email is DELETE_ATTRIBUTE: + remove_provider.append('email') + else: + payload['email'] = _auth_utils.validate_email(email) + if custom_claims is not None: if custom_claims is DELETE_ATTRIBUTE: custom_claims = {} diff --git a/firebase_admin/tenant_mgt.py b/firebase_admin/tenant_mgt.py index 8c53e30a1..60e71f86d 100644 --- a/firebase_admin/tenant_mgt.py +++ b/firebase_admin/tenant_mgt.py @@ -91,7 +91,7 @@ def get_tenant(tenant_id, app=None): def create_tenant( - display_name, allow_password_sign_up=None, enable_email_link_sign_in=None, app=None): + display_name, allow_password_sign_up=None, enable_email_link_sign_in=None, allow_default_provider=None, app=None): """Creates a new tenant from the given options. Args: @@ -101,6 +101,8 @@ def create_tenant( provider (optional). enable_email_link_sign_in: A boolean indicating whether to enable or disable email link sign-in (optional). Disabling this makes the password required for email sign-in. + allow_default_provider: A boolean indicating whether to enable or disable default provider + sign-in (optional). app: An App instance (optional). Returns: @@ -113,12 +115,12 @@ def create_tenant( tenant_mgt_service = _get_tenant_mgt_service(app) return tenant_mgt_service.create_tenant( display_name=display_name, allow_password_sign_up=allow_password_sign_up, - enable_email_link_sign_in=enable_email_link_sign_in) + enable_email_link_sign_in=enable_email_link_sign_in, allow_default_provider=allow_default_provider) def update_tenant( tenant_id, display_name=None, allow_password_sign_up=None, enable_email_link_sign_in=None, - app=None): + allow_default_provider=None, app=None): """Updates an existing tenant with the given options. Args: @@ -128,6 +130,8 @@ def update_tenant( provider. enable_email_link_sign_in: A boolean indicating whether to enable or disable email link sign-in. Disabling this makes the password required for email sign-in. + allow_default_provider: A boolean indicating whether to enable or disable default provider + sign-in (optional). app: An App instance (optional). Returns: @@ -141,7 +145,7 @@ def update_tenant( tenant_mgt_service = _get_tenant_mgt_service(app) return tenant_mgt_service.update_tenant( tenant_id, display_name=display_name, allow_password_sign_up=allow_password_sign_up, - enable_email_link_sign_in=enable_email_link_sign_in) + enable_email_link_sign_in=enable_email_link_sign_in, allow_default_provider=allow_default_provider) def delete_tenant(tenant_id, app=None): @@ -227,6 +231,10 @@ def allow_password_sign_up(self): @property def enable_email_link_sign_in(self): return self._data.get('enableEmailLinkSignin', False) + + @property + def allow_default_provider(self): + return self._data.get('allowDefaultProvider', False) class _TenantManagementService: @@ -272,7 +280,7 @@ def get_tenant(self, tenant_id): return Tenant(body) def create_tenant( - self, display_name, allow_password_sign_up=None, enable_email_link_sign_in=None): + self, display_name, allow_password_sign_up=None, enable_email_link_sign_in=None, allow_default_provider=None): """Creates a new tenant from the given parameters.""" payload = {'displayName': _validate_display_name(display_name)} @@ -282,6 +290,9 @@ def create_tenant( if enable_email_link_sign_in is not None: payload['enableEmailLinkSignin'] = _auth_utils.validate_boolean( enable_email_link_sign_in, 'enableEmailLinkSignin') + if allow_default_provider is not None: + payload['allowDefaultProvider'] = _auth_utils.validate_boolean( + allow_default_provider, 'allowDefaultProvider') try: body = self.client.body('post', '/tenants', json=payload) @@ -292,7 +303,7 @@ def create_tenant( def update_tenant( self, tenant_id, display_name=None, allow_password_sign_up=None, - enable_email_link_sign_in=None): + enable_email_link_sign_in=None, allow_default_provider=None): """Updates the specified tenant with the given parameters.""" if not isinstance(tenant_id, str) or not tenant_id: raise ValueError('Tenant ID must be a non-empty string.') @@ -306,6 +317,9 @@ def update_tenant( if enable_email_link_sign_in is not None: payload['enableEmailLinkSignin'] = _auth_utils.validate_boolean( enable_email_link_sign_in, 'enableEmailLinkSignin') + if allow_default_provider is not None: + payload['allowDefaultProvider'] = _auth_utils.validate_boolean( + allow_default_provider, 'allowDefaultProvider') if not payload: raise ValueError('At least one parameter must be specified for update.') diff --git a/integration/.DS_Store b/integration/.DS_Store new file mode 100644 index 000000000..404a3182e Binary files /dev/null and b/integration/.DS_Store differ diff --git a/integration/test_tenant_mgt.py b/integration/test_tenant_mgt.py index c9eefd96e..60143b1d0 100644 --- a/integration/test_tenant_mgt.py +++ b/integration/test_tenant_mgt.py @@ -38,7 +38,8 @@ def sample_tenant(): tenant = tenant_mgt.create_tenant( display_name='admin-python-tenant', allow_password_sign_up=True, - enable_email_link_sign_in=True) + enable_email_link_sign_in=True, + allow_default_provider=True) yield tenant tenant_mgt.delete_tenant(tenant.tenant_id) @@ -59,6 +60,7 @@ def test_get_tenant(sample_tenant): assert tenant.display_name == 'admin-python-tenant' assert tenant.allow_password_sign_up is True assert tenant.enable_email_link_sign_in is True + assert tenant.allow_default_provider is True def test_list_tenants(sample_tenant): @@ -73,20 +75,22 @@ def test_list_tenants(sample_tenant): assert result.display_name == 'admin-python-tenant' assert result.allow_password_sign_up is True assert result.enable_email_link_sign_in is True + assert result.allow_default_provider is True def test_update_tenant(): tenant = tenant_mgt.create_tenant( - display_name='py-update-test', allow_password_sign_up=True, enable_email_link_sign_in=True) + display_name='py-update-test', allow_password_sign_up=True, enable_email_link_sign_in=True, allow_default_provider=True) try: tenant = tenant_mgt.update_tenant( tenant.tenant_id, display_name='updated-py-tenant', allow_password_sign_up=False, - enable_email_link_sign_in=False) + enable_email_link_sign_in=False, allow_default_provider=False) assert isinstance(tenant, tenant_mgt.Tenant) assert tenant.tenant_id == tenant.tenant_id assert tenant.display_name == 'updated-py-tenant' assert tenant.allow_password_sign_up is False assert tenant.enable_email_link_sign_in is False + assert tenant.allow_default_provider is False finally: tenant_mgt.delete_tenant(tenant.tenant_id) diff --git a/snippets/.DS_Store b/snippets/.DS_Store new file mode 100644 index 000000000..3bb0a1990 Binary files /dev/null and b/snippets/.DS_Store differ diff --git a/snippets/auth/.DS_Store b/snippets/auth/.DS_Store new file mode 100644 index 000000000..1c6d511cb Binary files /dev/null and b/snippets/auth/.DS_Store differ diff --git a/snippets/auth/index.py b/snippets/auth/index.py index ed324e486..2932882f0 100644 --- a/snippets/auth/index.py +++ b/snippets/auth/index.py @@ -777,7 +777,8 @@ def create_tenant(): tenant = tenant_mgt.create_tenant( display_name='myTenant1', enable_email_link_sign_in=True, - allow_password_sign_up=True) + allow_password_sign_up=True, + allow_default_provider=True) print('Created tenant:', tenant.tenant_id) # [END create_tenant] diff --git a/tests/.DS_Store b/tests/.DS_Store new file mode 100644 index 000000000..29915cdf7 Binary files /dev/null and b/tests/.DS_Store differ diff --git a/tests/test_tenant_mgt.py b/tests/test_tenant_mgt.py index 53b766239..87f445422 100644 --- a/tests/test_tenant_mgt.py +++ b/tests/test_tenant_mgt.py @@ -34,7 +34,8 @@ "name": "projects/mock-project-id/tenants/tenant-id", "displayName": "Test Tenant", "allowPasswordSignup": true, - "enableEmailLinkSignin": true + "enableEmailLinkSignin": true, + "allowDefaultProvider": true }""" TENANT_NOT_FOUND_RESPONSE = """{ @@ -49,13 +50,15 @@ "name": "projects/mock-project-id/tenants/tenant0", "displayName": "Test Tenant", "allowPasswordSignup": true, - "enableEmailLinkSignin": true + "enableEmailLinkSignin": true, + "allowDefaultProvider": true }, { "name": "projects/mock-project-id/tenants/tenant1", "displayName": "Test Tenant", "allowPasswordSignup": true, - "enableEmailLinkSignin": true + "enableEmailLinkSignin": true, + "allowDefaultProvider": true } ] }""" @@ -160,12 +163,14 @@ def test_tenant(self): 'displayName': 'Test Tenant', 'allowPasswordSignup': True, 'enableEmailLinkSignin': True, + 'allowDefaultProvider': True } tenant = tenant_mgt.Tenant(data) assert tenant.tenant_id == 'tenant-id' assert tenant.display_name == 'Test Tenant' assert tenant.allow_password_sign_up is True assert tenant.enable_email_link_sign_in is True + assert tenant.allow_default_provider is True def test_tenant_optional_params(self): data = { @@ -236,30 +241,39 @@ def test_invalid_enable_email_link_sign_in(self, enable, tenant_mgt_app): display_name='test', enable_email_link_sign_in=enable, app=tenant_mgt_app) assert str(excinfo.value).startswith('Invalid type for enableEmailLinkSignin') + @pytest.mark.parametrize('allow', INVALID_BOOLEANS) + def test_invalid_allow_default_provider(self, allow, tenant_mgt_app): + with pytest.raises(ValueError) as excinfo: + tenant_mgt.create_tenant( + display_name='test', allow_default_provider=allow, app=tenant_mgt_app) + assert str(excinfo.value).startswith('Invalid type for allowDefaultProvider') + def test_create_tenant(self, tenant_mgt_app): _, recorder = _instrument_tenant_mgt(tenant_mgt_app, 200, GET_TENANT_RESPONSE) tenant = tenant_mgt.create_tenant( display_name='My-Tenant', allow_password_sign_up=True, enable_email_link_sign_in=True, - app=tenant_mgt_app) + allow_default_provider=True, app=tenant_mgt_app) _assert_tenant(tenant) self._assert_request(recorder, { 'displayName': 'My-Tenant', 'allowPasswordSignup': True, 'enableEmailLinkSignin': True, + 'allowDefaultProvider': True, }) def test_create_tenant_false_values(self, tenant_mgt_app): _, recorder = _instrument_tenant_mgt(tenant_mgt_app, 200, GET_TENANT_RESPONSE) tenant = tenant_mgt.create_tenant( display_name='test', allow_password_sign_up=False, enable_email_link_sign_in=False, - app=tenant_mgt_app) + allow_default_provider=False, app=tenant_mgt_app) _assert_tenant(tenant) self._assert_request(recorder, { 'displayName': 'test', 'allowPasswordSignup': False, 'enableEmailLinkSignin': False, + 'allowDefaultProvider': False, }) def test_create_tenant_minimal(self, tenant_mgt_app): @@ -321,6 +335,13 @@ def test_invalid_enable_email_link_sign_in(self, enable, tenant_mgt_app): tenant_mgt.update_tenant( 'tenant-id', enable_email_link_sign_in=enable, app=tenant_mgt_app) assert str(excinfo.value).startswith('Invalid type for enableEmailLinkSignin') + + @pytest.mark.parametrize('allow', INVALID_BOOLEANS) + def test_invalid_allow_default_provider(self, allow, tenant_mgt_app): + with pytest.raises(ValueError) as excinfo: + tenant_mgt.update_tenant( + 'tenant-id', allow_default_provider=allow, app=tenant_mgt_app) + assert str(excinfo.value).startswith('Invalid type for allowDefaultProvider') def test_update_tenant_no_args(self, tenant_mgt_app): with pytest.raises(ValueError) as excinfo: @@ -331,29 +352,31 @@ def test_update_tenant(self, tenant_mgt_app): _, recorder = _instrument_tenant_mgt(tenant_mgt_app, 200, GET_TENANT_RESPONSE) tenant = tenant_mgt.update_tenant( 'tenant-id', display_name='My-Tenant', allow_password_sign_up=True, - enable_email_link_sign_in=True, app=tenant_mgt_app) + enable_email_link_sign_in=True, allow_default_provider=True, app=tenant_mgt_app) _assert_tenant(tenant) body = { 'displayName': 'My-Tenant', 'allowPasswordSignup': True, 'enableEmailLinkSignin': True, + 'allowDefaultProvider': True, } - mask = ['allowPasswordSignup', 'displayName', 'enableEmailLinkSignin'] + mask = ['allowDefaultProvider', 'allowPasswordSignup', 'displayName', 'enableEmailLinkSignin'] self._assert_request(recorder, body, mask) def test_update_tenant_false_values(self, tenant_mgt_app): _, recorder = _instrument_tenant_mgt(tenant_mgt_app, 200, GET_TENANT_RESPONSE) tenant = tenant_mgt.update_tenant( 'tenant-id', allow_password_sign_up=False, - enable_email_link_sign_in=False, app=tenant_mgt_app) + enable_email_link_sign_in=False, allow_default_provider=False, app=tenant_mgt_app) _assert_tenant(tenant) body = { 'allowPasswordSignup': False, 'enableEmailLinkSignin': False, + 'allowDefaultProvider': False, } - mask = ['allowPasswordSignup', 'enableEmailLinkSignin'] + mask = ['allowDefaultProvider', 'allowPasswordSignup', 'enableEmailLinkSignin'] self._assert_request(recorder, body, mask) def test_update_tenant_minimal(self, tenant_mgt_app): @@ -1002,3 +1025,4 @@ def _assert_tenant(tenant, tenant_id='tenant-id'): assert tenant.display_name == 'Test Tenant' assert tenant.allow_password_sign_up is True assert tenant.enable_email_link_sign_in is True + assert tenant.allow_default_provider is True diff --git a/tests/test_user_mgt.py b/tests/test_user_mgt.py index b590cca05..a6b9b6910 100644 --- a/tests/test_user_mgt.py +++ b/tests/test_user_mgt.py @@ -631,12 +631,13 @@ def test_update_user_delete_fields(self, user_mgt_app): 'testuser', display_name=auth.DELETE_ATTRIBUTE, photo_url=auth.DELETE_ATTRIBUTE, - phone_number=auth.DELETE_ATTRIBUTE) + phone_number=auth.DELETE_ATTRIBUTE, + email=auth.DELETE_ATTRIBUTE) request = json.loads(recorder[0].body.decode()) assert request == { 'localId' : 'testuser', 'deleteAttribute' : ['DISPLAY_NAME', 'PHOTO_URL'], - 'deleteProvider' : ['phone'], + 'deleteProvider' : ['email', 'phone'], } def test_update_user_error(self, user_mgt_app): @@ -681,6 +682,17 @@ def test_update_user_delete_provider_and_phone(self, user_mgt_app, arg): assert len(set(request['deleteProvider'])) == len(request['deleteProvider']) assert set(arg) - set(request['deleteProvider']) == set() + @pytest.mark.parametrize('arg', [['email', 'phone', 'google.com']]) + def test_update_user_delete_provider_and_email(self, user_mgt_app, arg): + user_mgt, recorder = _instrument_user_manager(user_mgt_app, 200, '{"localId":"testuser"}') + user_mgt.update_user('testuser', + email='yuvi@gmail.com', + providers_to_delete=arg) + request = json.loads(recorder[0].body.decode()) + assert 'email' in request['deleteProvider'] + assert len(set(request['deleteProvider'])) == len(request['deleteProvider']) + assert set(arg) - set(request['deleteProvider']) == set() + class TestSetCustomUserClaims: @pytest.mark.parametrize('arg', INVALID_STRINGS + ['a'*129])