from ...backends.oauth import BaseOAuth2
from ...utils import PARTIAL_TOKEN_SESSION_NAME
from ..models import TestUserSocialAuth, User
from .actions import BaseActionTest


class BackendThatControlsRedirect(BaseOAuth2):
    """
    A fake backend that sets the URL to redirect to after login.

    It is not always possible to set the redirect URL in the session state prior to auth and then retrieve it when
    auth is complete, because the session cookie might not be available post-auth. For example, for SAML, a POST request
    redirects the user from the IdP (Identity Provider) back to the SP (Service Provider) to complete the auth process,
    but the session cookie will not be present if the session cookie's `SameSite` attribute is not set to "None".
    To mitigate this, SAML provides a `RelayState` parameter to pass data like a redirect URL from the SP to the IdP
    and back again. In that case, the redirect URL is only known in `auth_complete`, and must be communicated back to
    the `do_complete` action via session state so that it can issue the intended redirect.
    """

    ACCESS_TOKEN_URL = "https://example.com/oauth/access_token"

    def auth_url(self):
        return "https://example.com/oauth/auth?state=foo"

    def auth_complete(self, *args, **kwargs):
        # Put the redirect URL in the session state, as this is where the `do_complete` action looks for it.
        self.strategy.session_set(kwargs["redirect_name"], "/after-login")
        return kwargs["user"]


class LoginActionTest(BaseActionTest):
    def test_login(self):
        self.do_login()

    def test_login_with_partial_pipeline(self):
        self.do_login_with_partial_pipeline()

    def test_fields_stored_in_session(self):
        self.strategy.set_settings(
            {"SOCIAL_AUTH_FIELDS_STORED_IN_SESSION": ["foo", "bar"]}
        )
        self.strategy.set_request_data({"foo": "1", "bar": "2"}, self.backend)
        self.do_login()
        self.assertEqual(self.strategy.session_get("foo"), "1")
        self.assertEqual(self.strategy.session_get("bar"), "2")

        self._logout(self.backend)
        # The _logout helper function doesn't clear the session.
        self.assertEqual(self.strategy.session_get("foo"), "1")
        self.assertEqual(self.strategy.session_get("bar"), "2")

        # Login again - without the 'bar' request param and make
        # sure its value didn't persist in the session.
        self.strategy.remove_from_request_data("bar")
        self.strategy.set_request_data({"foo": "3"}, self.backend)
        self.do_login()
        self.assertEqual(self.strategy.session_get("foo"), "3")
        self.assertEqual(self.strategy.session_get("bar"), None)

    def test_redirect_value(self):
        self.strategy.set_request_data({"next": "/after-login"}, self.backend)
        redirect = self.do_login(after_complete_checks=False)
        self.assertEqual(redirect.url, "/after-login")

    def test_redirect_value_set_by_backend(self):
        self.backend = BackendThatControlsRedirect(self.strategy)
        self.user = TestUserSocialAuth.create_user("test-user")
        redirect = self.do_login(after_complete_checks=False)
        self.assertEqual(redirect.url, "/after-login")

    def test_login_with_invalid_partial_pipeline(self):
        def before_complete():
            partial_token = self.strategy.session_get(PARTIAL_TOKEN_SESSION_NAME)
            partial = self.strategy.storage.partial.load(partial_token)
            partial.data["backend"] = "foobar"

        self.do_login_with_partial_pipeline(before_complete)

    def test_new_user(self):
        self.strategy.set_settings({"SOCIAL_AUTH_NEW_USER_REDIRECT_URL": "/new-user"})
        redirect = self.do_login(after_complete_checks=False)
        self.assertEqual(redirect.url, "/new-user")

    def test_inactive_user(self):
        self.strategy.set_settings({"SOCIAL_AUTH_INACTIVE_USER_URL": "/inactive"})
        User.set_active(False)
        redirect = self.do_login(after_complete_checks=False)
        self.assertEqual(redirect.url, "/inactive")

    def test_invalid_user(self):
        self.strategy.set_settings(
            {
                "SOCIAL_AUTH_LOGIN_ERROR_URL": "/error",
                "SOCIAL_AUTH_PIPELINE": (
                    "social_core.pipeline.social_auth.social_details",
                    "social_core.pipeline.social_auth.social_uid",
                    "social_core.pipeline.social_auth.auth_allowed",
                    "social_core.pipeline.social_auth.social_user",
                    "social_core.pipeline.user.get_username",
                    "social_core.pipeline.user.create_user",
                    "social_core.pipeline.social_auth.associate_user",
                    "social_core.pipeline.social_auth.load_extra_data",
                    "social_core.pipeline.user.user_details",
                    "social_core.tests.pipeline.remove_user",
                ),
            }
        )
        redirect = self.do_login(after_complete_checks=False)
        self.assertEqual(redirect.url, "/error")
