from rest_framework import status from rest_framework.test import APITestCase from django.test.utils import override_settings from django.conf import settings from django.utils import timezone from django.test import RequestFactory from pingcap_sso.models import SecurityCode, User from pingcap_sso.lib.sso import sync_cookie_url from pingcap_sso.forms.login import PasswordLoginForm, PhoneLoginForm @override_settings( SMS_BACKEND='sms.backends.dummy.SmsBackend', TRUSTED_HOSTS=['tidb.io'], ALLOWED_HOSTS=['pingcap.com'], # Just for disable django-rest-framework's throttle CACHES={ 'default': { 'BACKEND': 'django.core.cache.backends.dummy.DummyCache', } } ) class LoginTest(APITestCase): LOGIN_PHONE_CHECK_PATH = '/api/login/phone/check' LOGIN_PHONE_SEND_CODE_PATH = '/api/login/phone/send-code' LOGIN_BY_PHONE_PATH = '/api/login/phone' LOGIN_BY_PASSWORD_PATH = '/api/login/password' def setUp(self): self.user1_without_password = User( username=User.generate_random_username(), company_name='company_name', phone='13666666666', email='user1@mail.com', ) self.user1_without_password.set_password(None) self.user1_without_password.save() self.user2_login_password = 'pingcapLogin123' self.user2_with_password = User( username=User.generate_random_username(), company_name='company_name', phone='13666666667', email='user2@mail.com' ) self.user2_with_password.set_password(self.user2_login_password) self.user2_with_password.save() self.hk_user_login_password = 'hongKongLogin123' self.hk_user = User( username=User.generate_random_username(), company_name='company_name', phone='+85251234567', email='hongkong@mail.com', ) self.hk_user.set_password(self.hk_user_login_password) self.hk_user.save() def test_normal_login_by_phone_code(self): # Send code response = self.client.post( self.LOGIN_PHONE_SEND_CODE_PATH, data={ 'phone': self.user1_without_password.phone, 're_token_v3': 'dummy' }, SERVER_NAME='pingcap.com' ) self.assertEquals(response.status_code, status.HTTP_200_OK) code = SecurityCode.objects.get( deliver_address=self.user1_without_password.phone, deliver_channel=SecurityCode.DELIVER_BY_PHONE, confirmed=False).code # Login redirect_to = 'https://tidb.io/redirect_to' response = self.client.post( self.LOGIN_BY_PHONE_PATH, data={ 'phone': self.user1_without_password.phone, 'code': code, 're_token_v3': 'dummy', 'redirect_to': redirect_to }, SERVER_NAME='pingcap.com' ) self.assertEquals(response.status_code, status.HTTP_200_OK) self.assertEquals(response.data['data']['redirect_to'], sync_cookie_url(redirect_to)) self.assertIn(settings.SESSION_COOKIE_NAME, response.client.cookies) self.assertIn(settings.CSRF_COOKIE_NAME, response.client.cookies) self.assertIn('uid', response.client.cookies) self.user1_without_password.refresh_from_db() self.assertEquals(self.user1_without_password.last_login.date(), timezone.now().date()) def test_normal_login_by_phone_password(self): self._normal_login_by_password( self.user2_with_password.phone, self.user2_login_password) def test_login_hong_kong_phone_by_code_and_password(self): response = self.client.post( self.LOGIN_PHONE_SEND_CODE_PATH, data={'phone': self.hk_user.phone, 're_token_v3': 'dummy'}, SERVER_NAME='pingcap.com', ) self.assertEqual(response.status_code, status.HTTP_200_OK) code = SecurityCode.objects.get( deliver_address=self.hk_user.phone, deliver_channel=SecurityCode.DELIVER_BY_PHONE, confirmed=False, ).code form = PhoneLoginForm( RequestFactory().post('/', SERVER_NAME='pingcap.com'), data={ 'phone': self.hk_user.phone, 'code': code, 're_token_v3': 'dummy', 'redirect_to': 'https://tidb.io/redirect_to', }, ) self.assertTrue(form.is_valid(), form.errors) self.assertEqual(form.get_user(), self.hk_user) password_form = PasswordLoginForm( RequestFactory().post('/', SERVER_NAME='pingcap.com'), data={ 'identifier': self.hk_user.phone, 'password': self.hk_user_login_password, 're_token_v3': 'dummy', 'redirect_to': 'https://tidb.io/redirect_to', }, ) self.assertTrue(password_form.is_valid(), password_form.errors) self.assertEqual(password_form.get_user(), self.hk_user) def test_normal_login_by_email_password(self): self._normal_login_by_password( self.user2_with_password.email, self.user2_login_password) def test_normal_login_by_username_password(self): self._normal_login_by_password( self.user2_with_password.username, self.user2_login_password) def _normal_login_by_password(self, identifier, password): redirect_to = 'https://tidb.io/redirect_to' response = self.client.post( self.LOGIN_BY_PASSWORD_PATH, data={ 'identifier': identifier, 'password': password, 're_token_v3': 'dummy', 'redirect_to': redirect_to }, SERVER_NAME='pingcap.com' ) self.assertEquals(response.status_code, status.HTTP_200_OK) self.assertEquals(response.data['data']['redirect_to'], sync_cookie_url(redirect_to)) self.assertIn(settings.SESSION_COOKIE_NAME, response.client.cookies) self.assertIn(settings.CSRF_COOKIE_NAME, response.client.cookies) self.assertIn('uid', response.client.cookies) def test_check_phone_registered(self): response = self.client.post( self.LOGIN_PHONE_CHECK_PATH, data={ 'phone': self.user1_without_password.phone }, SERVER_NAME='pingcap.com' ) self.assertEquals(response.status_code, status.HTTP_200_OK) def test_check_phone_unregistered(self): response = self.client.post( self.LOGIN_PHONE_CHECK_PATH, data={ 'phone': '13611088888' }, SERVER_NAME='pingcap.com' ) self.assertEquals(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertIn('phone', response.data['errors']) def teset_check_phone_invalid_input(self): response = self.client.post( self.LOGIN_PHONE_CHECK_PATH, data={ 'phone': 'invalid' }, SERVER_NAME='pingcap.com' ) self.assertEquals(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertIn('phone', response.data['errors']) def test_login_by_phone_with_wrong_code(self): redirect_to = 'https://tidb.io/redirect_to' response = self.client.post( self.LOGIN_BY_PHONE_PATH, data={ 'phone': self.user1_without_password.phone, 'code': 'invalid', 're_token_v3': 'dummy', 'redirect_to': redirect_to }, SERVER_NAME='pingcap.com' ) self.assertEquals(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertIn('code', response.data['errors']) self.assertIn(settings.CSRF_COOKIE_NAME, response.client.cookies) self.assertNotIn(settings.SESSION_COOKIE_NAME, response.client.cookies) self.assertNotIn('uid', response.client.cookies) def test_login_with_not_exists_email(self): response = self.client.post( self.LOGIN_BY_PASSWORD_PATH, data={ 'identifier': 'not_exists', 'password': 'pwd', 're_token_v3': 'dummy', 'redirect_to': '' }, SERVER_NAME='pingcap.com' ) self.assertEquals(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertIn('identifier', response.data['errors']) self.assertIn(settings.CSRF_COOKIE_NAME, response.client.cookies) self.assertNotIn(settings.SESSION_COOKIE_NAME, response.client.cookies) self.assertNotIn('uid', response.client.cookies) def test_login_with_invalid_redirect_to(self): redirect_to = 'https://invalid' response = self.client.post( self.LOGIN_BY_PASSWORD_PATH, data={ 'identifier': self.user2_with_password.email, 'password': self.user2_login_password, 're_token_v3': 'dummy', 'redirect_to': redirect_to }, SERVER_NAME='pingcap.com' ) self.assertEquals(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertIn('redirect_to', response.data['errors']) self.assertIn(settings.CSRF_COOKIE_NAME, response.client.cookies) self.assertNotIn(settings.SESSION_COOKIE_NAME, response.client.cookies) self.assertNotIn('uid', response.client.cookies) def test_login_with_untrusted_redirect_to(self): redirect_to = 'https://untrusted.com/redirect_to' response = self.client.post( self.LOGIN_BY_PASSWORD_PATH, data={ 'identifier': self.user2_with_password.email, 'password': self.user2_login_password, 're_token_v3': 'dummy', 'redirect_to': redirect_to }, SERVER_NAME='pingcap.com' ) self.assertEquals(response.status_code, status.HTTP_200_OK) self.assertEquals( response.data['data']['redirect_to'], sync_cookie_url('http://pingcap.com')) self.assertIn(settings.SESSION_COOKIE_NAME, response.client.cookies) self.assertIn(settings.CSRF_COOKIE_NAME, response.client.cookies) self.assertIn('uid', response.client.cookies) def test_phone_send_code_invalid_phone(self): response = self.client.post( self.LOGIN_PHONE_SEND_CODE_PATH, data={ 'phone': 'invalid', 're_token_v3': 'dummy' }, SERVER_NAME='pingcap.com' ) self.assertEquals(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertIn('phone', response.data['errors']) def test_phone_send_code_rate_limit(self): response = self.client.post( self.LOGIN_PHONE_SEND_CODE_PATH, data={ 'phone': self.user1_without_password.phone, 're_token_v3': 'dummy' }, SERVER_NAME='pingcap.com' ) self.assertEquals(response.status_code, status.HTTP_200_OK) response = self.client.post( self.LOGIN_PHONE_SEND_CODE_PATH, data={ 'phone': self.user1_without_password.phone, 're_token_v3': 'dummy' }, SERVER_NAME='pingcap.com' ) self.assertEquals(response.status_code, status.HTTP_429_TOO_MANY_REQUESTS)