Skip to content
test_auth.py 103 KiB
Newer Older

        self.flush_mailbox()

        resp = self.post_sign_up({})
        self.assertEqual(resp.status_code, 400)
        self.assertNoNewUserCreated()
        self.assertEqual(len(mail.outbox), 1)
        self.assertIn("self-registration is not enabled", mail.outbox[0].body)

    def get_sign_up_user_dict(self):
        return self.sign_up_user_dict.copy()

    def test_sign_up_form_invalid(self):
        with self.temporarily_switch_to_user(None):
            data = self.get_sign_up_user_dict()
            data["email"] = "not a email"
            resp = self.post_sign_up(data=data,
                                     follow=False)
            self.assertEqual(resp.status_code, 200)
            self.assertFormErrorLoose(resp, 'Enter a valid email address.')
            self.assertNoNewUserCreated()
            self.assertEqual(len(mail.outbox), 0)

Dong Zhuang's avatar
Dong Zhuang committed
    def test_signup_existing_user_name(self):
        resp = self.get_sign_up()
        self.assertEqual(resp.status_code, 200)
        self.assertNoNewUserCreated()

        expected_msg = "A user with that username already exists."

        data = self.get_sign_up_user_dict()
        data["username"] = self.test_user.username
        resp = self.post_sign_up(data=data, follow=False)

        self.assertEqual(resp.status_code, 200)
        self.assertNoNewUserCreated()
        self.assertEqual(len(mail.outbox), 0)
Dong Zhuang's avatar
Dong Zhuang committed
        self.assertAddMessageCalledWith(expected_msg)
Dong Zhuang's avatar
Dong Zhuang committed
    def test_signup_existing_email(self):
        expected_msg = (
                "That email address is already in use. "
                "Would you like to "
                "<a href='%s'>reset your password</a> instead?"
                % reverse("relate-reset_password"))

        data = self.get_sign_up_user_dict()
        data["email"] = self.test_user.email
        resp = self.post_sign_up(data=data, follow=False)

        self.assertEqual(resp.status_code, 200)
        self.assertNoNewUserCreated()
        self.assertEqual(len(mail.outbox), 0)
Dong Zhuang's avatar
Dong Zhuang committed
        self.assertAddMessageCalledWith(expected_msg)
Dong Zhuang's avatar
Dong Zhuang committed
    def test_signup_success(self):
        expected_msg = (
            "Email sent. Please check your email and click "
            "the link.")

        data = self.get_sign_up_user_dict()
        resp = self.post_sign_up(data=data, follow=False)
        self.assertRedirects(resp, reverse("relate-home"),
                             fetch_redirect_response=False)

        sent_request = resp.wsgi_request

        self.assertEqual(resp.status_code, 302)
        self.assertNewUserCreated()
        self.assertEqual(len(mail.outbox), 1)

        new_user = get_user_model().objects.last()
        sign_in_url = sent_request.build_absolute_uri(
            reverse(
                "relate-reset_password_stage2",
                args=(new_user.id, new_user.sign_in_key,))
            + "?to_profile=1")
        self.assertIn(sign_in_url, mail.outbox[0].body)
Dong Zhuang's avatar
Dong Zhuang committed
        self.assertAddMessageCalledWith(expected_msg)
Dong Zhuang's avatar
Dong Zhuang committed
class SignOutTest(CoursesTestMixinBase, AuthTestMixin,
                  MockAddMessageMixing, TestCase):
Dong Zhuang's avatar
Dong Zhuang committed
    def test_sign_out_anonymous(self):
        with self.temporarily_switch_to_user(None):
            expected_msg = "You've already signed out."
            resp = self.get_sign_out(follow=False)
            self.assertEqual(resp.status_code, 302)
            self.assertRedirects(resp, reverse("relate-home"),
                                 fetch_redirect_response=False)
            self.assertSessionHasNoUserLoggedIn()
Dong Zhuang's avatar
Dong Zhuang committed
            self.assertAddMessageCalledWith(expected_msg)

    @override_settings(RELATE_SIGN_IN_BY_SAML2_ENABLED=False)
    def test_sign_out_by_get(self):
        with mock.patch("djangosaml2.views._get_subject_id") \
                as mock_get_subject_id, \
                mock.patch("djangosaml2.views.LogoutInitView.get") \
                as mock_saml2_logout:
            mock_get_subject_id.return_value = "some_id"
            with self.temporarily_switch_to_user(self.test_user):
                resp = self.get_sign_out(follow=True)
                self.assertRedirects(resp, reverse("relate-home"),
                                     target_status_code=200,
                                     fetch_redirect_response=False)
                self.assertSessionHasNoUserLoggedIn()
            self.assertEqual(mock_saml2_logout.call_count, 0)

    @override_settings(RELATE_SIGN_IN_BY_SAML2_ENABLED=False)
    def test_sign_out_by_post(self):
        with mock.patch("djangosaml2.views._get_subject_id") \
                as mock_get_subject_id, \
                mock.patch("djangosaml2.views.LogoutInitView.get") \
                as mock_saml2_logout:
            mock_get_subject_id.return_value = "some_id"
            with self.temporarily_switch_to_user(self.test_user):
                resp = self.post_sign_out({})
                self.assertRedirects(resp, reverse("relate-home"),
                                     target_status_code=200,
                                     fetch_redirect_response=False)
                self.assertSessionHasNoUserLoggedIn()
            self.assertEqual(mock_saml2_logout.call_count, 0)

    @override_settings(RELATE_SIGN_IN_BY_SAML2_ENABLED=False)
    def test_sign_out_with_redirect_to(self):
        with self.temporarily_switch_to_user(self.test_user):
            resp = self.get_sign_out(redirect_to="/some_where/")
            self.assertRedirects(resp, "/some_where/",
                                 target_status_code=200,
                                 fetch_redirect_response=False)
            self.assertSessionHasNoUserLoggedIn()

    @override_settings(RELATE_SIGN_IN_BY_SAML2_ENABLED=True)
    def test_sign_out_with_saml2_enabled_no_subject_id(self):
        with mock.patch("djangosaml2.views._get_subject_id") \
                as mock_get_subject_id, \
                mock.patch("djangosaml2.views.LogoutInitView.get") \
                as mock_saml2_logout:
            mock_get_subject_id.return_value = None
            with self.temporarily_switch_to_user(self.test_user):
                resp = self.get_sign_out(follow=True)
                self.assertEqual(resp.status_code, 200)
                self.assertSessionHasNoUserLoggedIn()
            self.assertEqual(mock_saml2_logout.call_count, 0)

    @override_settings(RELATE_SIGN_IN_BY_SAML2_ENABLED=True)
    def test_sign_out_with_saml2_enabled_with_subject_id(self):
        self.c.force_login(self.test_user)
        with mock.patch("djangosaml2.views._get_subject_id") \
                as mock_get_subject_id, \
                mock.patch("djangosaml2.views.LogoutInitView.get") \
                as mock_saml2_logout:
            mock_get_subject_id.return_value = "some_id"
            mock_saml2_logout.return_value = HttpResponse()

            resp = self.get_sign_out(follow=True)

            self.assertEqual(resp.status_code, 200)
            self.assertEqual(mock_saml2_logout.call_count, 1)

Dong Zhuang's avatar
Dong Zhuang committed
    def test_sign_out_confirmation_anonymous(self):
        with self.temporarily_switch_to_user(None):
            expected_msg = "You've already signed out."
            resp = self.get_sign_out_confirmation(follow=False)
            self.assertEqual(resp.status_code, 302)
            self.assertRedirects(resp, reverse("relate-home"),
                                 fetch_redirect_response=False)
            self.assertSessionHasNoUserLoggedIn()
Dong Zhuang's avatar
Dong Zhuang committed
            self.assertAddMessageCalledWith(expected_msg)

    @override_settings(RELATE_SIGN_IN_BY_SAML2_ENABLED=True)
    def test_sign_out_confirmation(self):
        with self.temporarily_switch_to_user(self.test_user):
            resp = self.get_sign_out_confirmation(follow=False)
            self.assertEqual(resp.status_code, 200)

    @override_settings(RELATE_SIGN_IN_BY_SAML2_ENABLED=True)
    def test_sign_out_confirmation_with_redirect_to(self):
        with self.temporarily_switch_to_user(self.test_user):
            redirect_to = "/some_where/"
            resp = self.get_sign_out_confirmation(
                redirect_to=redirect_to, follow=False)
            self.assertEqual(resp.status_code, 200)
            self.assertIn(
                self.concatenate_redirect_url(
                    self.get_sign_out_view_url(), redirect_to
                ),
                resp.content.decode())
Dong Zhuang's avatar
Dong Zhuang committed


class UserProfileTest(CoursesTestMixinBase, AuthTestMixin,
Dong Zhuang's avatar
Dong Zhuang committed
                      MockAddMessageMixing, TestCase):
Dong Zhuang's avatar
Dong Zhuang committed

    def setUp(self):
Dong Zhuang's avatar
Dong Zhuang committed
        self.rf = RequestFactory()

    def generate_profile_data(self, **kwargs):
        profile_data = {
            "first_name": "",
            "last_name": "",
            "institutional_id": "",
            "editor_mode": "default"}
        profile_data.update(kwargs)
        return profile_data

    def post_profile_by_request_factory(self, data, query_string_dict=None):
        data.update({"submit_user": [""]})
        url = self.get_profile_view_url()
        if query_string_dict is not None:
            url = (
Dong Zhuang's avatar
Dong Zhuang committed
                    url,
                    "&".join([f"{k}={v}"
                              for k, v in query_string_dict.items()])))
Dong Zhuang's avatar
Dong Zhuang committed
        request = self.rf.post(url, data)
        request.user = self.test_user
        request.session = mock.MagicMock()

        from course.auth import user_profile
        response = user_profile(request)
        return response

    def get_profile_by_request_factory(self):
        request = self.rf.get(self.get_profile_view_url())
        request.user = self.test_user
        request.session = mock.MagicMock()

        from course.auth import user_profile
        response = user_profile(request)
        return response

    def generate_profile_form_data(self, **kwargs):
        form_data = {
            "first_name": "",
            "last_name": "",
            "institutional_id": "",
            "institutional_id_confirm": "",
            "no_institutional_id": True,
            "editor_mode": "default"}
        form_data.update(kwargs)
        return form_data

    def test_not_authenticated(self):
        with self.temporarily_switch_to_user(None):
            resp = self.get_profile()
            self.assertTrue(resp.status_code, 403)

            data = self.generate_profile_form_data()
            resp = self.post_profile(data)
            self.assertTrue(resp.status_code, 403)

    def test_get_profile(self):
        with self.temporarily_switch_to_user(self.test_user):
            resp = self.get_profile()
            self.assertTrue(resp.status_code, 200)

    def test_post_profile_without_submit_user(self):
        # Only POST with 'submit_user' works
        with self.temporarily_switch_to_user(self.test_user):
            resp = self.get_profile()
            self.assertTrue(resp.status_code, 200)
            data = self.generate_profile_form_data(first_name="foo")

            # No 'submit_user' in POST
            resp = self.c.post(self.get_profile_view_url(), data)
            self.test_user.refresh_from_db()
            self.assertEqual(self.test_user.first_name, "")

    def update_profile_by_post_form(self, user_profile_dict=None,
                                    update_profile_dict=None,
                                    query_string_dict=None):
        if user_profile_dict:
            assert isinstance(user_profile_dict, dict)
        else:
            user_profile_dict = {}

        if update_profile_dict:
            assert isinstance(update_profile_dict, dict)
        else:
            update_profile_dict = {}

        user_profile = self.generate_profile_data(**user_profile_dict)
        get_user_model().objects.filter(pk=self.test_user.pk).update(**user_profile)
        self.test_user.refresh_from_db()
        form_data = self.generate_profile_form_data(**update_profile_dict)
        return self.post_profile_by_request_factory(form_data, query_string_dict)

    def test_update_profile_with_different_settings(self):
        disabled_inst_id_html_pattern = (
            '<input type="text" name="institutional_id" value="%s" '
            'class="textinput textInput form-control" id="id_institutional_id" '
            'maxlength="100" disabled />')

        enabled_inst_id_html_pattern = (
            '<input type="text" name="institutional_id" value="%s" '
            'class="textinput textInput form-control" id="id_institutional_id" '
            'maxlength="100" />')

        expected_success_msg = "Profile data updated."
        expected_unchanged_msg = "No change was made on your profile."
        from collections import namedtuple
        Conf = namedtuple(
            'Conf', [
                'id',
                'override_settings_dict',
                'user_profile_dict',
                'update_profile_dict',
                'expected_result_dict',
                'assert_in_html_kwargs_list',
                'expected_msg',
            ])

        test_confs = (
            # {{{ basic test
            Conf("basic_1",
                 {EDITABLE_INST_ID_BEFORE_VERI: True, SHOW_INST_ID_FORM: True}, {},
                 {}, {}, [], expected_unchanged_msg),

            Conf("basic_2",
                 {EDITABLE_INST_ID_BEFORE_VERI: True, SHOW_INST_ID_FORM: False}, {},
                 {}, {}, [], expected_unchanged_msg),

            Conf("basic_3",
                 {EDITABLE_INST_ID_BEFORE_VERI: False, SHOW_INST_ID_FORM: True}, {},
                 {}, {}, [], expected_unchanged_msg),

            Conf("basic_4",
                 {EDITABLE_INST_ID_BEFORE_VERI: False, SHOW_INST_ID_FORM: False},
                 {}, {}, {}, [], expected_unchanged_msg),
            # }}}

            # {{{ update first_name
            Conf("first_name_1",
                 {},
                 {"first_name": "foo", "name_verified": False},
                 {"first_name": "bar"},
                 {"first_name": "bar"},
                 [],
                 expected_success_msg),

            Conf("first_name_2", {},
                 {"first_name": "foo", "name_verified": True},
                 {"first_name": "bar"},
                 {"first_name": "foo"},
                 [],
                 expected_unchanged_msg),

            # test strip
            Conf("first_name_3", {},
                 {"first_name": "foo", "name_verified": False},
                 {"first_name": "   bar  "},
                 {"first_name": "bar"},
                 [],
                 expected_success_msg),

            # }}}

            # {{{ update last_name
            Conf("last_name_1", {},
                 {"last_name": "foo", "name_verified": False},
                 {"last_name": "bar"},
                 {"last_name": "bar"},
                 [],
                 expected_success_msg),

            Conf("last_name_2", {},
                 {"last_name": "foo", "name_verified": True},
                 {"last_name": "bar"},
                 {"last_name": "foo"},
                 [],
                 expected_unchanged_msg),

            # test strip
            Conf("last_name_3", {},
                 {"last_name": "foo", "name_verified": False},
                 {"last_name": "  bar  "},
                 {"last_name": "bar"},
                 [],
                 expected_success_msg),

            # }}}

            # {{{ update institutional_id update
            Conf("institutional_id_update_1",
                 {EDITABLE_INST_ID_BEFORE_VERI: True, SHOW_INST_ID_FORM: True},
                 {"institutional_id": "1234", "institutional_id_verified": False},
                 {"institutional_id": "1234", "institutional_id_confirm": "1234"},
                 {"institutional_id": "1234"},
                 [{"needle": enabled_inst_id_html_pattern % "1234", "count": 1}],
                 expected_unchanged_msg),

            Conf("institutional_id_update_2",
                 {EDITABLE_INST_ID_BEFORE_VERI: True, SHOW_INST_ID_FORM: True},
                 {"institutional_id": "1234", "institutional_id_verified": False},
                 {"institutional_id": "123", "institutional_id_confirm": "123"},
                 {"institutional_id": "123"},
                 [{"needle": enabled_inst_id_html_pattern % "123", "count": 1}],
                 expected_success_msg),

            Conf("institutional_id_update_3",
                 {EDITABLE_INST_ID_BEFORE_VERI: True, SHOW_INST_ID_FORM: False},
                 {"institutional_id": "1234", "institutional_id_verified": False},
                 {"institutional_id": "1234", "institutional_id_confirm": "1234"},
                 {"institutional_id": "1234"},
                 [],
                 expected_unchanged_msg),

            Conf("institutional_id_update_4",
                 {EDITABLE_INST_ID_BEFORE_VERI: True, SHOW_INST_ID_FORM: False},
                 {"institutional_id": "1234", "institutional_id_verified": False},
                 {"institutional_id": "123", "institutional_id_confirm": "123"},
                 {"institutional_id": "123"},
                 [],
                 expected_success_msg),

            Conf("institutional_id_update_5",
                 {EDITABLE_INST_ID_BEFORE_VERI: True, SHOW_INST_ID_FORM: True},
                 {"institutional_id": "1234", "institutional_id_verified": True},
                 {"institutional_id": "1234", "institutional_id_confirm": "1234"},
                 {"institutional_id": "1234"},
                 [{"needle": disabled_inst_id_html_pattern % "1234", "count": 1}],
                 expected_unchanged_msg),

            Conf("institutional_id_update_6",
                 {EDITABLE_INST_ID_BEFORE_VERI: True, SHOW_INST_ID_FORM: True},
                 {"institutional_id": "1234", "institutional_id_verified": True},
                 {"institutional_id": "123", "institutional_id_confirm": "123"},
                 {"institutional_id": "1234"},
                 [{"needle": disabled_inst_id_html_pattern % "1234", "count": 1}],
                 expected_unchanged_msg),

            Conf("institutional_id_update_7",
                 {EDITABLE_INST_ID_BEFORE_VERI: True, SHOW_INST_ID_FORM: False},
                 {"institutional_id": "1234", "institutional_id_verified": True},
                 {"institutional_id": "1234", "institutional_id_confirm": "1234"},
                 {"institutional_id": "1234"},
                 [],
                 expected_unchanged_msg),

            Conf("institutional_id_update_8",
                 {EDITABLE_INST_ID_BEFORE_VERI: True, SHOW_INST_ID_FORM: False},
                 {"institutional_id": "1234", "institutional_id_verified": True},
                 {"institutional_id": "123", "institutional_id_confirm": "123"},
                 {"institutional_id": "1234"},
                 [{"needle": enabled_inst_id_html_pattern % "1234", "count": 0}],
                 expected_unchanged_msg),

            Conf("institutional_id_update_9",
                 {EDITABLE_INST_ID_BEFORE_VERI: False, SHOW_INST_ID_FORM: True},
                 {"institutional_id": "1234", "institutional_id_verified": False},
                 {"institutional_id": "1234", "institutional_id_confirm": "1234"},
                 {"institutional_id": "1234"},
                 [{"needle": disabled_inst_id_html_pattern % "1234", "count": 1}],
                 expected_unchanged_msg),

            Conf("institutional_id_update_10",
                 {EDITABLE_INST_ID_BEFORE_VERI: False, SHOW_INST_ID_FORM: True},
                 {"institutional_id": "1234", "institutional_id_verified": False},
                 {"institutional_id": "123", "institutional_id_confirm": "123"},
                 {"institutional_id": "1234"},
                 [{"needle": disabled_inst_id_html_pattern % "1234", "count": 1}],
                 expected_unchanged_msg),
            # }}}

            # {{{ institutional_id clean
            Conf("institutional_id_clean_1",
                 {EDITABLE_INST_ID_BEFORE_VERI: True, SHOW_INST_ID_FORM: True},
                 {"institutional_id": "1234", "institutional_id_verified": False},
                 {"institutional_id": "123", "institutional_id_confirm": "1234"},
                 {"institutional_id": "1234"},
                 [{"needle": "Inputs do not match.", "count": 1}],
                 None),

            Conf("institutional_id_clean_2",
                 {EDITABLE_INST_ID_BEFORE_VERI: True, SHOW_INST_ID_FORM: True},
                 {"institutional_id": "1234", "institutional_id_verified": False},
                 {"institutional_id": "123"},
                 {"institutional_id": "1234"},
                 [{"needle": "This field is required.", "count": 1}],
                 None),
            # }}}

            # Update with blank value
            # https://github.com/inducer/relate/pull/145
            Conf("clear_institutional_id",
                 {EDITABLE_INST_ID_BEFORE_VERI: True, SHOW_INST_ID_FORM: True},
                 {"institutional_id": "1234", "institutional_id_verified": False},
                 {"institutional_id": "", "institutional_id_confirm": ""},
                 {"institutional_id": None},
                 [],
                 expected_success_msg),

            # test post value with leading/trailing spaces (striped when saving)
            Conf("strip_institutional_id",
                 {EDITABLE_INST_ID_BEFORE_VERI: True, SHOW_INST_ID_FORM: True},
                 {},
                 {"institutional_id": "123   ",
                  "institutional_id_confirm": "   123"},
Dong Zhuang's avatar
Dong Zhuang committed
                 {"institutional_id": "123"},
                 [],
                 expected_success_msg),

        )

Dong Zhuang's avatar
Dong Zhuang committed
        for conf in test_confs:
            with self.subTest(conf.id):
                with override_settings(**conf.override_settings_dict):
                    resp = self.update_profile_by_post_form(
                        conf.user_profile_dict,
                        conf.update_profile_dict)

                    self.assertTrue(resp.status_code, 200)
                    self.test_user.refresh_from_db()
                    for k, v in conf.expected_result_dict.items():
Dong Zhuang's avatar
Dong Zhuang committed
                        self.assertEqual(self.test_user.__dict__[k], v)

                    if conf.assert_in_html_kwargs_list:
                        kwargs_list = conf.assert_in_html_kwargs_list
                        for kwargs in kwargs_list:
                            self.assertInHTML(haystack=resp.content.decode(),
                                              **kwargs)

                    if conf.expected_msg is not None:
                        self.assertAddMessageCalledWith(conf.expected_msg)
Dong Zhuang's avatar
Dong Zhuang committed

    def test_profile_page_hide_institutional_id_or_editor_mode(self):
Dong Zhuang's avatar
Dong Zhuang committed
        """
        Test whether the response content contains <input type="hidden"
        for specific field
        """
        field_div_with_id_pattern = (
Andreas Klöckner's avatar
Andreas Klöckner committed
            r".*(<div\s+[^\>]*id\s*=\s*['\"]div_id_%s['\"][^>]*\/?>).*")
Dong Zhuang's avatar
Dong Zhuang committed

Dong Zhuang's avatar
Dong Zhuang committed
        def assertFieldDiv(field_name, exist=True):  # noqa
Dong Zhuang's avatar
Dong Zhuang committed
            resp = self.get_profile_by_request_factory()
            self.assertEqual(resp.status_code, 200)
Dong Zhuang's avatar
Dong Zhuang committed
            pattern = field_div_with_id_pattern % field_name
            if exist:
                self.assertRegex(resp.content.decode(), pattern,
                                 msg=("Field Div of '%s' is expected to exist."
                                      % field_name))
            else:
                self.assertNotRegex(resp.content.decode(), pattern,
                                    msg=("Field Div of '%s' is not expected "
                                         "to exist." % field_name))

        with override_settings(
                RELATE_SHOW_INST_ID_FORM=True, RELATE_SHOW_EDITOR_FORM=True):
            assertFieldDiv("institutional_id", True)
            assertFieldDiv("editor_mode", True)
Dong Zhuang's avatar
Dong Zhuang committed

        with override_settings(
                RELATE_SHOW_INST_ID_FORM=False, RELATE_SHOW_EDITOR_FORM=True):
Dong Zhuang's avatar
Dong Zhuang committed
            assertFieldDiv("institutional_id", False)
            assertFieldDiv("editor_mode", True)
Dong Zhuang's avatar
Dong Zhuang committed

        with override_settings(
                RELATE_SHOW_INST_ID_FORM=True, RELATE_SHOW_EDITOR_FORM=False):
Dong Zhuang's avatar
Dong Zhuang committed
            assertFieldDiv("institutional_id", True)
            assertFieldDiv("editor_mode", False)
Dong Zhuang's avatar
Dong Zhuang committed

        with override_settings(
                RELATE_SHOW_INST_ID_FORM=False, RELATE_SHOW_EDITOR_FORM=False):
Dong Zhuang's avatar
Dong Zhuang committed
            assertFieldDiv("institutional_id", False)
            assertFieldDiv("editor_mode", False)
Dong Zhuang's avatar
Dong Zhuang committed

    def test_update_profile_for_first_login(self):
        data = self.generate_profile_data(first_name="foo")
        expected_msg = "Profile data updated."
Dong Zhuang's avatar
Dong Zhuang committed
        resp = self.post_profile_by_request_factory(
            data, query_string_dict={"first_login": "1"})
        self.assertRedirects(resp, reverse("relate-home"),
                             fetch_redirect_response=False)
        self.assertAddMessageCalledWith(expected_msg)
Dong Zhuang's avatar
Dong Zhuang committed

    def test_update_profile_for_referer(self):
        data = self.generate_profile_data(first_name="foo")
Dong Zhuang's avatar
Dong Zhuang committed
        expected_msg = "Profile data updated."
        resp = self.post_profile_by_request_factory(
            data, query_string_dict={"referer": "/some/where/",
                                     "set_inst_id": "1"})
        self.assertRedirects(resp, "/some/where/",
                             fetch_redirect_response=False)
        self.assertAddMessageCalledWith(expected_msg)
Dong Zhuang's avatar
Dong Zhuang committed

    def test_update_profile_for_referer_wrong_spell(self):
        data = self.generate_profile_data(first_name="foo")
Dong Zhuang's avatar
Dong Zhuang committed
        expected_msg = "Profile data updated."
Dong Zhuang's avatar
Dong Zhuang committed

Dong Zhuang's avatar
Dong Zhuang committed
        # "Wrong" spell of referer, no redirect
        resp = self.post_profile_by_request_factory(
            data, query_string_dict={"referrer": "/some/where/",
                                     "set_inst_id": "1"})
        self.assertTrue(resp.status_code, 200)
        self.assertAddMessageCalledWith(expected_msg)
Dong Zhuang's avatar
Dong Zhuang committed

Dong Zhuang's avatar
Dong Zhuang committed
class ResetPasswordStageOneTest(CoursesTestMixinBase, MockAddMessageMixing,
                                LocmemBackendTestsMixin, TestCase):
    @classmethod
    def setUpTestData(cls):  # noqa
        super().setUpTestData()
        cls.user_email = "a_very_looooooong_email@somehost.com"
        cls.user_inst_id = "1234"
Dong Zhuang's avatar
Dong Zhuang committed
        cls.user = factories.UserFactory.create(email=cls.user_email,
                                      institutional_id=cls.user_inst_id)

    def setUp(self):
        self.registration_override_setting = override_settings(
            RELATE_REGISTRATION_ENABLED=True)
        self.registration_override_setting.enable()
        self.addCleanup(self.registration_override_setting.disable)
        self.user.refresh_from_db()
        self.c.logout()

    def test_reset_get(self):
        resp = self.get_reset_password()
        self.assertEqual(resp.status_code, 200)
        resp = self.get_reset_password(use_instid=True)
        self.assertEqual(resp.status_code, 200)

    @override_settings(RELATE_REGISTRATION_ENABLED=False)
    def test_reset_with_registration_disabled(self):
        resp = self.get_reset_password()
        self.assertEqual(resp.status_code, 400)

        resp = self.get_reset_password(use_instid=True)
        self.assertEqual(resp.status_code, 400)

        resp = self.post_reset_password(data={})
        self.assertEqual(resp.status_code, 400)

        resp = self.post_reset_password(use_instid=True, data={})
        self.assertEqual(resp.status_code, 400)

    def test_reset_form_invalid(self):
        resp = self.post_reset_password(
            data={"email": "some/email"})
        self.assertTrue(resp.status_code, 200)
        self.assertFormErrorLoose(resp, "Enter a valid email address.")
        self.assertEqual(len(mail.outbox), 0)

    def test_reset_by_email_non_exist(self):
Dong Zhuang's avatar
Dong Zhuang committed
        expected_msg = (
                "That %s doesn't have an "
                "associated user account. Are you "
                "sure you've registered?" % "email address")
        resp = self.post_reset_password(
            data={"email": "some_email@example.com"})
        self.assertTrue(resp.status_code, 200)
        self.assertAddMessageCalledWith(expected_msg)
        self.assertEqual(len(mail.outbox), 0)

    def test_reset_by_instid_non_exist(self):
Dong Zhuang's avatar
Dong Zhuang committed
        expected_msg = (
                "That %s doesn't have an "
                "associated user account. Are you "
                "sure you've registered?" % "institutional ID")
        resp = self.post_reset_password(
            data={"instid": "2345"}, use_instid=True)
        self.assertTrue(resp.status_code, 200)
        self.assertAddMessageCalledWith(expected_msg)
        self.assertEqual(len(mail.outbox), 0)
        self.assertEqual(resp.status_code, 200)

    def test_reset_user_has_no_email(self):
Dong Zhuang's avatar
Dong Zhuang committed
        self.user.email = ""
        self.user.save()
        expected_msg = (
            "The account with that institution ID "
            "doesn't have an associated email.")
        resp = self.post_reset_password(data={"instid": self.user_inst_id},
                                        use_instid=True)
        self.assertTrue(resp.status_code, 200)
        self.assertAddMessageCalledWith(expected_msg)
        self.assertEqual(len(mail.outbox), 0)
        self.assertEqual(resp.status_code, 200)

    def test_reset_by_email_have_multiple_user_with_same_email(self):
        with mock.patch("accounts.models.User.objects.get") as mock_get_user:
            from django.core.exceptions import MultipleObjectsReturned
            mock_get_user.side_effect = MultipleObjectsReturned()
Dong Zhuang's avatar
Dong Zhuang committed
            expected_msg = (
                "Failed to send an email: multiple users were "
                "unexpectedly using that same "
                "email address. Please "
                "contact site staff.")
            resp = self.post_reset_password(
                data={"email": "some_email@example.com"})
            self.assertTrue(resp.status_code, 200)
            self.assertAddMessageCalledWith(expected_msg)
            self.assertEqual(len(mail.outbox), 0)

    def test_reset_by_email_post_success(self):
Dong Zhuang's avatar
Dong Zhuang committed
        expected_msg = (
            "Email sent. Please check your email and "
            "click the link."
        )
        resp = self.post_reset_password(data={"email": self.user_email})
        self.assertTrue(resp.status_code, 200)
        self.assertAddMessageCallCount(1)
        self.assertAddMessageCalledWith(expected_msg)
        self.assertEqual(len(mail.outbox), 1)
        self.assertTemplateUsed("course/sign-in-email.txt", count=1)

    def test_reset_by_istid_post_success(self):
        from course.auth import masked_email
        masked = masked_email(self.user_email)
        self.assertNotEqual(masked, self.user_email)

Dong Zhuang's avatar
Dong Zhuang committed
        with mock.patch("course.auth.masked_email") as mock_mask_email:
            expected_msg = (
                "Email sent. Please check your email and "
                "click the link."
            )
            resp = self.post_reset_password(data={"instid": self.user_inst_id},
                                            use_instid=True)
            self.assertTrue(resp.status_code, 200)
Dong Zhuang's avatar
Dong Zhuang committed
            self.assertAddMessageCallCount(2)
            self.assertAddMessageCalledWith(
                "'The email address associated with that account is",
                reset=False)
            self.assertAddMessageCalledWith(expected_msg)
            self.assertEqual(mock_mask_email.call_count, 1)
        self.assertEqual(len(mail.outbox), 1)
        self.assertTemplateUsed("course/sign-in-email.txt", count=1)


Dong Zhuang's avatar
Dong Zhuang committed
class ResetPasswordStageTwoTest(CoursesTestMixinBase, MockAddMessageMixing,
                                LocmemBackendTestsMixin, TestCase):
Dong Zhuang's avatar
Dong Zhuang committed

    @classmethod
    def setUpTestData(cls):  # noqa
        super().setUpTestData()
Dong Zhuang's avatar
Dong Zhuang committed
        user = factories.UserFactory()
Dong Zhuang's avatar
Dong Zhuang committed
        cls.c.logout()

        with override_settings(RELATE_REGISTRATION_ENABLED=True):
            cls.post_reset_password(data={"email": user.email})

        user.refresh_from_db()
        assert user.sign_in_key is not None
        cls.user = user

    def setUp(self):
Dong Zhuang's avatar
Dong Zhuang committed
        self.registration_override_setting = override_settings(
            RELATE_REGISTRATION_ENABLED=True)
        self.registration_override_setting.enable()
        self.addCleanup(self.registration_override_setting.disable)
        self.c.logout()
        self.user.refresh_from_db()

    def assertHasUserLoggedIn(self, user):  # noqa
        self.assertEqual(self.get_logged_in_user(), user)

    def assertHasNoUserLoggedIn(self):  # noqa
        self.assertIsNone(self.get_logged_in_user())

    @override_settings(RELATE_REGISTRATION_ENABLED=False)
    def test_reset_stage2_with_registration_disabled(self):
        resp = self.get_reset_password_stage2(self.user.pk, self.user.sign_in_key)
        self.assertEqual(resp.status_code, 400)

        resp = self.post_reset_password_stage2(
            self.user.pk, self.user.sign_in_key, data={})
        self.assertEqual(resp.status_code, 400)
        self.assertHasNoUserLoggedIn()

    def test_reset_stage2_invalid_user(self):
Dong Zhuang's avatar
Dong Zhuang committed
        expected_msg = ("Account does not exist.")
        resp = self.get_reset_password_stage2(user_id=1000,  # no exist
                                              sign_in_key=self.user.sign_in_key)
        self.assertTrue(resp.status_code, 403)
        self.assertAddMessageCallCount(1)
        self.assertAddMessageCalledWith(expected_msg)
        self.assertHasNoUserLoggedIn()
Dong Zhuang's avatar
Dong Zhuang committed
        resp = self.post_reset_password_stage2(
            user_id=1000,  # no exist
            sign_in_key=self.user.sign_in_key,
            data={})
        self.assertTrue(resp.status_code, 403)
        self.assertAddMessageCallCount(1)
        self.assertAddMessageCalledWith(expected_msg)
        self.assertHasNoUserLoggedIn()
Dong Zhuang's avatar
Dong Zhuang committed

    def test_reset_stage2_invalid_token(self):
Dong Zhuang's avatar
Dong Zhuang committed
        expected_msg = ("Invalid sign-in token. Perhaps you've used an "
                        "old token email?")
        resp = self.get_reset_password_stage2(user_id=self.user.id,
                                              sign_in_key="some_invalid_token")
        self.assertTrue(resp.status_code, 403)
        self.assertAddMessageCallCount(1)
        self.assertAddMessageCalledWith(expected_msg)
        self.assertHasNoUserLoggedIn()
Dong Zhuang's avatar
Dong Zhuang committed
        resp = self.post_reset_password_stage2(
            user_id=self.user.id,
            sign_in_key="some_invalid_token", data={})
        self.assertTrue(resp.status_code, 403)
        self.assertAddMessageCallCount(1)
        self.assertAddMessageCalledWith(expected_msg)
        self.assertHasNoUserLoggedIn()
Dong Zhuang's avatar
Dong Zhuang committed

    def test_reset_stage2_get_sucess(self):
        resp = self.get_reset_password_stage2(self.user.id, self.user.sign_in_key)
        self.assertEqual(resp.status_code, 200)
        self.assertHasNoUserLoggedIn()

    def test_reset_stage2_post_form_not_valid(self):
        data = {"password": "my_pass", "password_repeat": ""}
        resp = self.post_reset_password_stage2(self.user.id,
                                               self.user.sign_in_key, data)
        self.assertEqual(resp.status_code, 200)
        self.assertFormErrorLoose(resp, "This field is required.")
        self.assertFormErrorLoose(resp, "The two password fields didn't match.")
        self.assertHasNoUserLoggedIn()

    def test_reset_stage2_post_success_redirect_profile_no_real_name(self):
        assert not (self.user.first_name or self.user.last_name)
Dong Zhuang's avatar
Dong Zhuang committed
        expected_msg = ("Successfully signed in. "
                        "Please complete your registration information below.")
        data = {"password": "my_pass", "password_repeat": "my_pass"}
        resp = self.post_reset_password_stage2(self.user.id,
                                               self.user.sign_in_key, data)
        self.assertRedirects(resp,
                             self.get_profile_view_url() + "?first_login=1",
                             fetch_redirect_response=False)
        self.assertAddMessageCallCount(1)
        self.assertAddMessageCalledWith(expected_msg)
        self.assertHasUserLoggedIn(self.user)
Dong Zhuang's avatar
Dong Zhuang committed

    def test_reset_stage2_post_success_redirect_profile_no_full_name(self):
        self.user.first_name = "testuser"
        self.user.save()
Dong Zhuang's avatar
Dong Zhuang committed

        expected_msg = ("Successfully signed in. "
                        "Please complete your registration information below.")
        data = {"password": "my_pass", "password_repeat": "my_pass"}
        resp = self.post_reset_password_stage2(self.user.id,
                                               self.user.sign_in_key, data)
        self.assertRedirects(resp,
                             self.get_profile_view_url() + "?first_login=1",
                             fetch_redirect_response=False)
        self.assertAddMessageCallCount(1)
        self.assertAddMessageCalledWith(expected_msg)
        self.assertHasUserLoggedIn(self.user)
Dong Zhuang's avatar
Dong Zhuang committed

    def test_reset_stage2_post_success_redirect_home(self):
        self.user.first_name = "test"
        self.user.last_name = "user"
        self.user.save()
Dong Zhuang's avatar
Dong Zhuang committed

        expected_msg = ("Successfully signed in.")
        data = {"password": "my_pass", "password_repeat": "my_pass"}
        resp = self.post_reset_password_stage2(self.user.id,
                                               self.user.sign_in_key, data)
        self.assertEqual(resp.status_code, 302)
        self.assertAddMessageCallCount(1)
        self.assertAddMessageCalledWith(expected_msg)
        self.assertHasUserLoggedIn(self.user)
Dong Zhuang's avatar
Dong Zhuang committed

    def test_reset_stage2_post_success_redirect_profile_requesting_profile(self):
        self.user.first_name = "testuser"
        self.user.last_name = "user"
        self.user.save()
Dong Zhuang's avatar
Dong Zhuang committed
        expected_msg = ("Successfully signed in. "
                        "Please complete your registration information below.")
        data = {"password": "my_pass", "password_repeat": "my_pass"}
        resp = self.post_reset_password_stage2(self.user.id,
                                               self.user.sign_in_key, data,
                                               querystring={"to_profile": "-1"})
        self.assertRedirects(resp,
                             self.get_profile_view_url() + "?first_login=1",
                             fetch_redirect_response=False)
        self.assertAddMessageCallCount(1)
        self.assertAddMessageCalledWith(expected_msg)
        self.assertHasUserLoggedIn(self.user)
Dong Zhuang's avatar
Dong Zhuang committed

    def test_reset_stage2_post_user_not_active(self):
        self.user.is_active = False
        self.user.save()
Dong Zhuang's avatar
Dong Zhuang committed
        expected_msg = ("Account disabled.")
        data = {"password": "my_pass", "password_repeat": "my_pass"}
        resp = self.post_reset_password_stage2(self.user.id,
                                               self.user.sign_in_key, data)
        self.assertEqual(resp.status_code, 403)
        self.assertAddMessageCallCount(1)
        self.assertAddMessageCalledWith(expected_msg)
        self.assertHasNoUserLoggedIn()
Dong Zhuang's avatar
Dong Zhuang committed

    def test_reset_stage2_invalid_token_when_post_form(self):
Dong Zhuang's avatar
Dong Zhuang committed
        with mock.patch("django.contrib.auth.authenticate") as mock_auth:
Dong Zhuang's avatar
Dong Zhuang committed
            mock_auth.return_value = None
            data = {"password": "my_pass", "password_repeat": "my_pass"}
            expected_msg = ("Invalid sign-in token. Perhaps you've used an "
                            "old token email?")

            resp = self.post_reset_password_stage2(self.user.id,
                                                   self.user.sign_in_key, data)
            self.assertTrue(resp.status_code, 403)
Dong Zhuang's avatar
Dong Zhuang committed
            self.assertAddMessageCallCount(1)
            self.assertAddMessageCalledWith(expected_msg)
Dong Zhuang's avatar
Dong Zhuang committed
            self.assertHasNoUserLoggedIn()

class EmailedTokenBackendTest(CoursesTestMixinBase, TestCase):
    def test_authenticate(self):
Dong Zhuang's avatar
Dong Zhuang committed
        user = factories.UserFactory()
        self.c.logout()

        with override_settings(RELATE_REGISTRATION_ENABLED=True):
            self.post_reset_password(data={"email": user.email})

        user.refresh_from_db()
        assert user.sign_in_key is not None

        backend = EmailedTokenBackend()
        self.assertEqual(
            backend.authenticate(None, user.pk, token=user.sign_in_key), user)

        self.assertIsNone(
            backend.authenticate(None, user.pk, token="non_exist_sign_in_key"))

    def test_get_user(self):
Dong Zhuang's avatar
Dong Zhuang committed
        user = factories.UserFactory()
        self.c.logout()

        backend = EmailedTokenBackend()
        self.assertEqual(backend.get_user(user.pk), user)
        self.assertIsNone(backend.get_user(10000))


Josh Asplund's avatar
Josh Asplund committed
@pytest.mark.django_db
class LogoutConfirmationRequiredDecoratorTest(unittest.TestCase):
    def setUp(self):
Dong Zhuang's avatar
Dong Zhuang committed
        self.user = factories.UserFactory()

    def test_logout_confirmation_required_as_callable(self):
        from course.auth import logout_confirmation_required
        self.assertTrue(callable(logout_confirmation_required()))
        self.assertTrue(logout_confirmation_required()(self.user))

        from django.contrib.auth.models import AnonymousUser
        self.assertTrue(logout_confirmation_required()(AnonymousUser))


class TestSaml2AttributeMapping(TestCase):
Dong Zhuang's avatar
Dong Zhuang committed
    def test_update_user(self):
Dong Zhuang's avatar
Dong Zhuang committed
        user = factories.UserFactory(first_name="", last_name="",
                                     institutional_id="",
                                     institutional_id_verified=False,
                                     name_verified=False,
                                     status=constants.user_status.unconfirmed)
Dong Zhuang's avatar
Dong Zhuang committed

        from course.auth import RelateSaml2Backend
        backend = RelateSaml2Backend()
Dong Zhuang's avatar
Dong Zhuang committed

        saml_attribute_mapping = {
            'PrincipalName': ('username',),
            'iTrustUIN': ('institutional_id',),
            'mail': ('email',),
            'givenName': ('first_name',),
            'sn': ('last_name',),
        }

        with override_settings(SAML_ATTRIBUTE_MAPPING=saml_attribute_mapping):
            user_attribute = {
                'PrincipalName': (user.username,),
            }

            with mock.patch("accounts.models.User.save") as mock_save:
                # no changes
                user = backend._rl_update_user(user, user_attribute,
                        saml_attribute_mapping)
                self.assertEqual(mock_save.call_count, 0)

            # not set as part of _rl_update_user
            # self.assertEqual(user.first_name, "")
            # self.assertEqual(user.last_name, "")
            self.assertFalse(user.name_verified)
            self.assertEqual(user.status, constants.user_status.unconfirmed)
            self.assertFalse(user.institutional_id_verified)

            expected_first = "my_first"
            expected_last = "my_last"
            expected_inst_id = "123321"
            expected_email = "yoink@illinois.edu"

            user_attribute = {
                'PrincipalName': (user.username,),
                'iTrustUIN': (expected_inst_id,),
                'givenName': (expected_first,),
                'sn': (expected_last,),
            }