diff --git a/common/djangoapps/student/forms.py b/common/djangoapps/student/forms.py index 3ad9593d96..b8bbd2e5e4 100644 --- a/common/djangoapps/student/forms.py +++ b/common/djangoapps/student/forms.py @@ -22,7 +22,7 @@ from openedx.core.djangoapps.site_configuration import helpers as configuration_ from openedx.core.djangoapps.theming.helpers import get_current_site from openedx.core.djangoapps.user_api import accounts as accounts_settings # lint-amnesty, pylint: disable=unused-import from openedx.core.djangoapps.user_api.accounts.utils import is_secondary_email_feature_enabled # lint-amnesty, pylint: disable=unused-import -from openedx.core.djangoapps.user_authn.utils import should_redirect_to_authn_microfrontend +from openedx.core.djangoapps.user_authn.toggles import should_redirect_to_authn_microfrontend from openedx.core.djangoapps.user_api.preferences.api import get_user_preference from common.djangoapps.student.message_types import AccountRecovery as AccountRecoveryMessage from common.djangoapps.student.models import CourseEnrollmentAllowed, email_exists_or_retired # lint-amnesty, pylint: disable=unused-import diff --git a/common/djangoapps/student/tests/test_activate_account.py b/common/djangoapps/student/tests/test_activate_account.py index 291cf85416..abeacb6777 100644 --- a/common/djangoapps/student/tests/test_activate_account.py +++ b/common/djangoapps/student/tests/test_activate_account.py @@ -8,15 +8,17 @@ from django.conf import settings from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user from django.test import TestCase, override_settings from django.urls import reverse +from edx_toggles.toggles.testutils import override_waffle_flag from mock import patch from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers +from openedx.core.djangoapps.user_authn.toggles import REDIRECT_TO_AUTHN_MICROFRONTEND from common.djangoapps.student.models import Registration from common.djangoapps.student.tests.factories import UserFactory -FEATURES_WITH_LOGIN_MFE_ENABLED = settings.FEATURES.copy() -FEATURES_WITH_LOGIN_MFE_ENABLED['ENABLE_AUTHN_MICROFRONTEND'] = True +FEATURES_WITH_AUTHN_MFE_ENABLED = settings.FEATURES.copy() +FEATURES_WITH_AUTHN_MFE_ENABLED['ENABLE_AUTHN_MICROFRONTEND'] = True @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms') @@ -151,7 +153,8 @@ class TestActivateAccount(TestCase): self.assertRedirects(response, login_page_url) self.assertContains(response, 'Your account could not be activated') - @override_settings(FEATURES=FEATURES_WITH_LOGIN_MFE_ENABLED) + @override_settings(FEATURES=FEATURES_WITH_AUTHN_MFE_ENABLED) + @override_waffle_flag(REDIRECT_TO_AUTHN_MICROFRONTEND, active=True) def test_unauthenticated_user_redirects_to_mfe(self): """ Verify that if Authn MFE is enabled then authenticated user redirects to diff --git a/common/djangoapps/student/views/management.py b/common/djangoapps/student/views/management.py index 923e956f2b..0a62a604de 100644 --- a/common/djangoapps/student/views/management.py +++ b/common/djangoapps/student/views/management.py @@ -50,7 +50,7 @@ from openedx.core.djangoapps.programs.models import ProgramsApiConfig from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers from openedx.core.djangoapps.theming import helpers as theming_helpers from openedx.core.djangoapps.user_api.preferences import api as preferences_api -from openedx.core.djangoapps.user_authn.utils import should_redirect_to_authn_microfrontend +from openedx.core.djangoapps.user_authn.toggles import should_redirect_to_authn_microfrontend from openedx.core.djangolib.markup import HTML, Text from common.djangoapps.student.email_helpers import generate_activation_email_context from common.djangoapps.student.helpers import DISABLE_UNENROLL_CERT_STATES, cert_info diff --git a/common/djangoapps/third_party_auth/pipeline.py b/common/djangoapps/third_party_auth/pipeline.py index 248055b5d2..a07cb6716b 100644 --- a/common/djangoapps/third_party_auth/pipeline.py +++ b/common/djangoapps/third_party_auth/pipeline.py @@ -89,7 +89,7 @@ from openedx.core.djangoapps.site_configuration import helpers as configuration_ from openedx.core.djangoapps.user_api import accounts from openedx.core.djangoapps.user_api.accounts.utils import is_multiple_sso_accounts_association_to_saml_user_enabled from openedx.core.djangoapps.user_authn import cookies as user_authn_cookies -from openedx.core.djangoapps.user_authn.utils import should_redirect_to_authn_microfrontend +from openedx.core.djangoapps.user_authn.toggles import should_redirect_to_authn_microfrontend from common.djangoapps.third_party_auth.utils import ( get_user_from_email, is_enterprise_customer_user, diff --git a/lms/templates/header/navbar-not-authenticated.html b/lms/templates/header/navbar-not-authenticated.html index 5a7daf64b3..1f99ac9fe3 100644 --- a/lms/templates/header/navbar-not-authenticated.html +++ b/lms/templates/header/navbar-not-authenticated.html @@ -11,7 +11,7 @@ from django.urls import reverse from django.utils.translation import ugettext as _ from six import text_type -from openedx.core.djangoapps.user_authn.utils import should_redirect_to_authn_microfrontend +from openedx.core.djangoapps.user_authn.toggles import should_redirect_to_authn_microfrontend %> <% diff --git a/openedx/core/djangoapps/user_authn/toggles.py b/openedx/core/djangoapps/user_authn/toggles.py index 3b1cd9c9f2..67c9f08fbc 100644 --- a/openedx/core/djangoapps/user_authn/toggles.py +++ b/openedx/core/djangoapps/user_authn/toggles.py @@ -5,6 +5,9 @@ Toggles for user_authn from django.conf import settings +from edx_toggles.toggles import WaffleFlag +from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers + # .. toggle_name: ENABLE_REQUIRE_THIRD_PARTY_AUTH # .. toggle_implementation: DjangoSetting # .. toggle_default: False @@ -21,3 +24,23 @@ from django.conf import settings def is_require_third_party_auth_enabled(): # TODO: Replace function with SettingToggle when it is available. return getattr(settings, "ENABLE_REQUIRE_THIRD_PARTY_AUTH", False) + +# .. toggle_name: user_authn.redirect_to_microfrontend +# .. toggle_implementation: WaffleFlag +# .. toggle_default: False +# .. toggle_description: Supports staged rollout of a new micro-frontend-based implementation of the login and registration pages +# .. toggle_use_cases: temporary, open_edx +# .. toggle_creation_date: 2021-02-02 +# .. toggle_target_removal_date: None +# .. toggle_warnings: Also set settings.AUTHN_MICROFRONTEND_URL and site's ENABLE_AUTHN_MICROFRONTEND +# .. toggle_tickets: VAN-308 +REDIRECT_TO_AUTHN_MICROFRONTEND = WaffleFlag('user_authn.redirect_to_microfrontend', __name__) + + +def should_redirect_to_authn_microfrontend(): + """ + Checks if login/registration should be done via MFE. + """ + return configuration_helpers.get_value( + 'ENABLE_AUTHN_MICROFRONTEND', settings.FEATURES.get('ENABLE_AUTHN_MICROFRONTEND') + ) and REDIRECT_TO_AUTHN_MICROFRONTEND.is_enabled() diff --git a/openedx/core/djangoapps/user_authn/utils.py b/openedx/core/djangoapps/user_authn/utils.py index 67be03dfa8..238f2ac770 100644 --- a/openedx/core/djangoapps/user_authn/utils.py +++ b/openedx/core/djangoapps/user_authn/utils.py @@ -68,12 +68,3 @@ def is_registration_api_v1(request): :return: Bool """ return 'v1' in request.get_full_path() and 'register' not in request.get_full_path() - - -def should_redirect_to_authn_microfrontend(): - """ - Checks if login/registration should be done via MFE. - """ - return configuration_helpers.get_value( - 'ENABLE_AUTHN_MICROFRONTEND', settings.FEATURES.get('ENABLE_AUTHN_MICROFRONTEND') - ) diff --git a/openedx/core/djangoapps/user_authn/views/login.py b/openedx/core/djangoapps/user_authn/views/login.py index 79088970d1..6fca722081 100644 --- a/openedx/core/djangoapps/user_authn/views/login.py +++ b/openedx/core/djangoapps/user_authn/views/login.py @@ -33,7 +33,7 @@ from openedx.core.djangoapps.site_configuration import helpers as configuration_ from openedx.core.djangoapps.user_authn.views.login_form import get_login_session_form from openedx.core.djangoapps.user_authn.cookies import get_response_with_refreshed_jwt_cookies, set_logged_in_cookies from openedx.core.djangoapps.user_authn.exceptions import AuthFailedError -from openedx.core.djangoapps.user_authn.utils import should_redirect_to_authn_microfrontend +from openedx.core.djangoapps.user_authn.toggles import should_redirect_to_authn_microfrontend from openedx.core.djangoapps.util.user_messages import PageLevelMessages from openedx.core.djangoapps.user_authn.views.password_reset import send_password_reset_email_for_user from openedx.core.djangoapps.user_authn.toggles import is_require_third_party_auth_enabled @@ -125,7 +125,7 @@ def _generate_locked_out_error_message(): """ locked_out_period_in_sec = settings.MAX_FAILED_LOGIN_ATTEMPTS_LOCKOUT_PERIOD_SECS - if not should_redirect_to_authn_microfrontend: # pylint: disable=no-else-raise + if not should_redirect_to_authn_microfrontend(): # pylint: disable=no-else-raise raise AuthFailedError(Text(_('To protect your account, it’s been temporarily ' 'locked. Try again in {locked_out_period} minutes.' '{li_start}To be on the safe side, you can reset your ' @@ -238,7 +238,7 @@ def _handle_failed_authentication(user, authenticated_user): if not LoginFailures.is_user_locked_out(user): max_failures_allowed = settings.MAX_FAILED_LOGIN_ATTEMPTS_ALLOWED remaining_attempts = max_failures_allowed - failure_count - if not should_redirect_to_authn_microfrontend: # pylint: disable=no-else-raise + if not should_redirect_to_authn_microfrontend(): # pylint: disable=no-else-raise raise AuthFailedError(Text(_('Email or password is incorrect.' '{li_start}You have {remaining_attempts} more sign-in ' 'attempts before your account is temporarily locked.{li_end}' diff --git a/openedx/core/djangoapps/user_authn/views/login_form.py b/openedx/core/djangoapps/user_authn/views/login_form.py index 34f9fa5bab..ff1ecb1d9d 100644 --- a/openedx/core/djangoapps/user_authn/views/login_form.py +++ b/openedx/core/djangoapps/user_authn/views/login_form.py @@ -24,7 +24,7 @@ from openedx.core.djangoapps.user_api.accounts.utils import ( ) from openedx.core.djangoapps.user_api.helpers import FormDescription from openedx.core.djangoapps.user_authn.cookies import are_logged_in_cookies_set -from openedx.core.djangoapps.user_authn.utils import should_redirect_to_authn_microfrontend +from openedx.core.djangoapps.user_authn.toggles import should_redirect_to_authn_microfrontend from openedx.core.djangoapps.user_authn.views.password_reset import get_password_reset_form from openedx.core.djangoapps.user_authn.views.registration_form import RegistrationFormFactory from openedx.core.djangoapps.user_authn.views.utils import third_party_auth_context diff --git a/openedx/core/djangoapps/user_authn/views/password_reset.py b/openedx/core/djangoapps/user_authn/views/password_reset.py index 0381c64689..ef218dca67 100644 --- a/openedx/core/djangoapps/user_authn/views/password_reset.py +++ b/openedx/core/djangoapps/user_authn/views/password_reset.py @@ -35,7 +35,7 @@ from openedx.core.djangoapps.oauth_dispatch.api import destroy_oauth_tokens from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers from openedx.core.djangoapps.theming.helpers import get_current_request, get_current_site from openedx.core.djangoapps.user_api import accounts, errors, helpers -from openedx.core.djangoapps.user_authn.utils import should_redirect_to_authn_microfrontend +from openedx.core.djangoapps.user_authn.toggles import should_redirect_to_authn_microfrontend from openedx.core.djangoapps.user_api.accounts.utils import is_secondary_email_feature_enabled from openedx.core.djangoapps.user_api.helpers import FormDescription from openedx.core.djangoapps.user_api.models import UserRetirementRequest diff --git a/openedx/core/djangoapps/user_authn/views/tests/test_login.py b/openedx/core/djangoapps/user_authn/views/tests/test_login.py index 0855f1126f..dcbcb4e50f 100644 --- a/openedx/core/djangoapps/user_authn/views/tests/test_login.py +++ b/openedx/core/djangoapps/user_authn/views/tests/test_login.py @@ -18,7 +18,7 @@ from django.http import HttpResponse from django.test.client import Client from django.test.utils import override_settings from django.urls import NoReverseMatch, reverse -from edx_toggles.toggles.testutils import override_waffle_switch +from edx_toggles.toggles.testutils import override_waffle_flag, override_waffle_switch from mock import Mock, patch from common.djangoapps.student.tests.factories import RegistrationFactory, UserFactory, UserProfileFactory @@ -29,6 +29,7 @@ from openedx.core.djangoapps.password_policy.compliance import ( from openedx.core.djangoapps.user_api.accounts import EMAIL_MIN_LENGTH, EMAIL_MAX_LENGTH from openedx.core.djangoapps.user_authn.cookies import jwt_cookies from openedx.core.djangoapps.user_authn.tests.utils import setup_login_oauth_client +from openedx.core.djangoapps.user_authn.toggles import REDIRECT_TO_AUTHN_MICROFRONTEND from openedx.core.djangoapps.user_authn.views.login import ( ENABLE_LOGIN_USING_THIRDPARTY_AUTH_ONLY, AllowedAuthUser, @@ -79,8 +80,8 @@ class LoginTest(SiteMixin, CacheIsolationTestCase): self._assert_response(response, success=True) self._assert_audit_log(mock_audit_log, 'info', [u'Login success', self.user_email]) - FEATURES_WITH_LOGIN_MFE_ENABLED = settings.FEATURES.copy() - FEATURES_WITH_LOGIN_MFE_ENABLED['ENABLE_AUTHN_MICROFRONTEND'] = True + FEATURES_WITH_AUTHN_MFE_ENABLED = settings.FEATURES.copy() + FEATURES_WITH_AUTHN_MFE_ENABLED['ENABLE_AUTHN_MICROFRONTEND'] = True @patch.dict(settings.FEATURES, { "ENABLE_THIRD_PARTY_AUTH": True @@ -157,7 +158,8 @@ class LoginTest(SiteMixin, CacheIsolationTestCase): ) @ddt.unpack @override_settings(LOGIN_REDIRECT_WHITELIST=['openedx.service']) - @override_settings(FEATURES=FEATURES_WITH_LOGIN_MFE_ENABLED) + @override_settings(FEATURES=FEATURES_WITH_AUTHN_MFE_ENABLED) + @override_waffle_flag(REDIRECT_TO_AUTHN_MICROFRONTEND, active=True) @skip_unless_lms def test_login_success_with_redirect(self, next_url, course_id, expected_redirect): post_params = {} diff --git a/openedx/core/djangoapps/user_authn/views/tests/test_logistration.py b/openedx/core/djangoapps/user_authn/views/tests/test_logistration.py index 88c169e2b4..a2b50573ce 100644 --- a/openedx/core/djangoapps/user_authn/views/tests/test_logistration.py +++ b/openedx/core/djangoapps/user_authn/views/tests/test_logistration.py @@ -19,6 +19,7 @@ from django.test.client import RequestFactory from django.test.utils import override_settings from django.urls import reverse from django.utils.translation import ugettext as _ +from edx_toggles.toggles.testutils import override_waffle_flag from freezegun import freeze_time from pytz import UTC @@ -26,6 +27,7 @@ from common.djangoapps.course_modes.models import CourseMode from lms.djangoapps.branding.api import get_privacy_url from openedx.core.djangoapps.site_configuration.tests.mixins import SiteMixin from openedx.core.djangoapps.theming.tests.test_util import with_comprehensive_theme_context +from openedx.core.djangoapps.user_authn.toggles import REDIRECT_TO_AUTHN_MICROFRONTEND from openedx.core.djangoapps.user_authn.views.login_form import login_and_registration_form from openedx.core.djangolib.js_utils import dump_js_escaped_json from openedx.core.djangolib.markup import HTML, Text @@ -64,8 +66,8 @@ class LoginAndRegistrationTest(ThirdPartyAuthTestMixin, UrlResetMixin, ModuleSto ) self.hidden_disabled_provider = self.configure_azure_ad_provider() - FEATURES_WITH_LOGIN_MFE_ENABLED = settings.FEATURES.copy() - FEATURES_WITH_LOGIN_MFE_ENABLED['ENABLE_AUTHN_MICROFRONTEND'] = True + FEATURES_WITH_AUTHN_MFE_ENABLED = settings.FEATURES.copy() + FEATURES_WITH_AUTHN_MFE_ENABLED['ENABLE_AUTHN_MICROFRONTEND'] = True @ddt.data( ("signin_user", "/login"), @@ -73,16 +75,21 @@ class LoginAndRegistrationTest(ThirdPartyAuthTestMixin, UrlResetMixin, ModuleSto ("password_assistance", "/reset"), ) @ddt.unpack - @override_settings(FEATURES=FEATURES_WITH_LOGIN_MFE_ENABLED) + @override_settings(FEATURES=FEATURES_WITH_AUTHN_MFE_ENABLED) def test_logistration_mfe_redirects(self, url_name, path): """ Test that if Logistration MFE is enabled, then we redirect to the correct URL. """ - response = self.client.get(reverse(url_name)) - self.assertEqual(response.url, settings.AUTHN_MICROFRONTEND_URL + path) - self.assertEqual(response.status_code, 302) + # Test with setting enabled and waffle flag in-active, does not redirect + response = self.client.get(reverse(url_name)) + self.assertContains(response, 'Sign in or Register') + + # Test with setting enabled and waffle flag active, redirects to microfrontend + with override_waffle_flag(REDIRECT_TO_AUTHN_MICROFRONTEND, active=True): + response = self.client.get(reverse(url_name)) + self.assertRedirects(response, settings.AUTHN_MICROFRONTEND_URL + path, fetch_redirect_response=False) @ddt.data( ( @@ -97,7 +104,8 @@ class LoginAndRegistrationTest(ThirdPartyAuthTestMixin, UrlResetMixin, ModuleSto ) ) @ddt.unpack - @override_settings(FEATURES=FEATURES_WITH_LOGIN_MFE_ENABLED) + @override_settings(FEATURES=FEATURES_WITH_AUTHN_MFE_ENABLED) + @override_waffle_flag(REDIRECT_TO_AUTHN_MICROFRONTEND, active=True) def test_logistration_redirect_params(self, url_name, path, query_params): """ Test that if request is redirected to logistration MFE, @@ -106,7 +114,7 @@ class LoginAndRegistrationTest(ThirdPartyAuthTestMixin, UrlResetMixin, ModuleSto expected_url = settings.AUTHN_MICROFRONTEND_URL + path + '?' + urlencode(query_params) response = self.client.get(reverse(url_name), query_params) - self.assertRedirects(response, expected_url, target_status_code=302) + self.assertRedirects(response, expected_url, fetch_redirect_response=False) @ddt.data( ("signin_user", "login"), diff --git a/openedx/core/djangoapps/user_authn/views/tests/test_reset_password.py b/openedx/core/djangoapps/user_authn/views/tests/test_reset_password.py index 7e72f17788..08ab304498 100644 --- a/openedx/core/djangoapps/user_authn/views/tests/test_reset_password.py +++ b/openedx/core/djangoapps/user_authn/views/tests/test_reset_password.py @@ -22,6 +22,7 @@ from django.test.client import RequestFactory from django.test.utils import override_settings from django.urls import reverse from django.utils.http import int_to_base36 +from edx_toggles.toggles.testutils import override_waffle_flag from freezegun import freeze_time from mock import Mock, patch from oauth2_provider import models as dot_models @@ -36,6 +37,7 @@ from openedx.core.djangoapps.user_api.accounts import EMAIL_MAX_LENGTH, EMAIL_MI from openedx.core.djangoapps.user_authn.views.password_reset import ( SETTING_CHANGE_INITIATED, password_reset, LogistrationPasswordResetView, PasswordResetConfirmWrapper) +from openedx.core.djangoapps.user_authn.toggles import REDIRECT_TO_AUTHN_MICROFRONTEND from openedx.core.djangolib.testing.utils import CacheIsolationTestCase from common.djangoapps.student.tests.factories import TEST_PASSWORD, UserFactory from common.djangoapps.student.tests.test_configuration_overrides import fake_get_value @@ -331,6 +333,7 @@ class ResetPasswordTests(EventTestMixin, CacheIsolationTestCase): ) @override_settings(FEATURES=ENABLE_AUTHN_MICROFRONTEND) + @override_waffle_flag(REDIRECT_TO_AUTHN_MICROFRONTEND, active=True) @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', "Test only valid in LMS") @ddt.data(('Crazy Awesome Site', 'Crazy Awesome Site'), ('edX', 'edX')) @ddt.unpack