# Copyright 2015 Google Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Tests for the auth views."""

from unittest import mock
from flask import current_app
from flask_login import current_user

from timesketch.lib.definitions import HTTP_STATUS_CODE_REDIRECT
from timesketch.lib.definitions import HTTP_STATUS_CODE_OK
from timesketch.lib.testlib import BaseTest


class AuthViewTest(BaseTest):
    """Test the auth view."""

    def test_login_view_unauthenticated(self):
        """Test the login view handler with an unauthenticated session."""
        response = self.client.get("/login/")
        self.assert200(response)

    def test_login_view_form_authenticated(self):
        """Test the login view handler with an authenticated session."""
        self.login()
        response = self.client.get("/login/")
        self.assertEqual(response.status_code, HTTP_STATUS_CODE_REDIRECT)

    def test_login_view_sso_authenticated(self):
        """Test the login view handler with an SSO authenticated session."""
        current_app.config["SSO_ENABLED"] = True
        current_app.config["SSO_GROUP_ENV_VARIABLE"] = "SSO_GROUP"
        current_app.config["SSO_GROUP_SEPARATOR"] = ";"
        current_app.config["SSO_GROUP_NOT_MEMBER_SIGN"] = "-"
        with self.client:
            response = self.client.get(
                "/login/",
                environ_base={
                    "REMOTE_USER": "test1",
                    "SSO_GROUP": "test_group1;-test_group2",
                },
            )
            self.assertEqual(current_user.username, "test1")
            self.assertIn(self.group1, current_user.groups)
            self.assertNotIn(self.group2, current_user.groups)
            self.assertEqual(response.status_code, HTTP_STATUS_CODE_REDIRECT)

    def test_logout_view(self):
        """Test the logout view handler."""
        self.login()
        response = self.client.get("/logout/")
        self.assertEqual(response.status_code, HTTP_STATUS_CODE_REDIRECT)

    @mock.patch("flask.current_app.logger")
    def test_login_invalid_next_url(self, mock_logger):
        """Test the login view handler with an invalid next_url."""
        invalid_next_urls = [
            "//example.com",
            "/\\example.com",
            "http://example.com",
        ]
        for url in invalid_next_urls:
            response = self.client.get(f"/login/?next={url}")
            self.assertEqual(response.status_code, HTTP_STATUS_CODE_OK)
            mock_logger.warning.assert_called()
            mock_logger.warning.reset_mock()


class AuthApiViewTest(BaseTest):
    """Test the auth API view."""

    @mock.patch("timesketch.views.auth.requests.post")
    @mock.patch("timesketch.views.auth.get_oauth2_discovery_document")
    @mock.patch("timesketch.views.auth.validate_jwt")
    def test_validate_api_token_scope_superset(self, _, mock_discovery, mock_post):
        """Test validate_api_token with superset of scopes."""
        # Setup config
        self.app.config["GOOGLE_OIDC_CLIENT_ID"] = "test_client_id"
        self.app.config["GOOGLE_OIDC_API_CLIENT_ID"] = "test_api_client_id"

        # Mock responses
        mock_discovery.return_value = {"issuer": "https://accounts.google.com"}

        # Mock requests.post
        def side_effect(*_, **kwargs):
            data = kwargs.get("data", {})
            if "access_token" in data:
                return mock.Mock(
                    status_code=200,
                    json=lambda: {
                        # This includes extra scopes (short forms) that caused
                        # the failure previously.
                        "scope": (
                            "email profile openid "
                            "https://www.googleapis.com/auth/userinfo.profile "
                            "https://www.googleapis.com/auth/userinfo.email"
                        ),
                        "azp": "test_client_id",
                        "email": "test@example.com",
                    },
                )
            if "id_token" in data:
                return mock.Mock(
                    status_code=200,
                    json=lambda: {
                        "email_verified": True,
                        "azp": "test_client_id",
                        "email": "test@example.com",
                        "aud": "test_client_id",
                    },
                )
            return mock.Mock(status_code=400)

        mock_post.side_effect = side_effect

        # Headers and Args
        headers = {"Authorization": "Bearer test_access_token"}

        with mock.patch.object(self.app.logger, "warning") as mock_warning:
            response = self.client.get(
                "/login/api_callback/?id_token=test_id_token", headers=headers
            )
            mock_warning.assert_called()
        # We expect 200 because scope mismatch is now relaxed
        self.assertEqual(response.status_code, HTTP_STATUS_CODE_OK)
        self.assertIn(b"Authenticated", response.data)
