<commit_before><?php

declare(strict_types=1);

namespace App\Service;

use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\JsonResponse;
use App\Entity\Manager\AuthenticationManager;
use App\Traits\AuthenticationService;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Generator\UrlGenerator;
use Symfony\Component\Routing\RouterInterface;
use Exception;

/**
 * Authenticate user using CAS Protocol and return a JWT
 */
class CasAuthentication implements AuthenticationInterface
{
    use AuthenticationService;

    protected const REDIRECT_COOKIE = 'ilios-cas-redirect';
    protected const JWT_COOKIE = 'ilios-cas-jwt';
    protected const NO_ACCOUNT_EXISTS_COOKIE = 'ilios-cas-no-account-exists';

    /**
     * @var AuthenticationManager
     */
    protected $authManager;

    /**
     * @var JsonWebTokenManager
     */
    protected $jwtManager;

    /**
     * @var LoggerInterface
     */
    protected $logger;

    /**
     * @var RouterInterface
     */
    protected $router;

    /**
     * @var CasManager
     */
    protected $casManager;

    /**
     * @var SessionUserProvider
     */
    protected $sessionUserProvider;

    /**
     * Constructor
     * @param AuthenticationManager $authManager
     * @param JsonWebTokenManager $jwtManager
     * @param LoggerInterface $logger
     * @param RouterInterface $router
     * @param CasManager $casManager
     * @param SessionUserProvider $sessionUserProvider
     */
    public function __construct(
        AuthenticationManager $authManager,
        JsonWebTokenManager $jwtManager,
        LoggerInterface $logger,
        RouterInterface $router,
        CasManager $casManager,
        SessionUserProvider $sessionUserProvider
    ) {
        $this->authManager = $authManager;
        $this->jwtManager = $jwtManager;
        $this->logger = $logger;
        $this->router = $router;
        $this->casManager = $casManager;
        $this->sessionUserProvider = $sessionUserProvider;
    }

    /**
     * Authenticate a user from CAS
     *
     * If a JWT cookie exists user it to create a JSON response
     * If the user account doesn't exist send a JSON response with that error
     * If the user is not yet logged in send a redirect Request
     * If the user is logged in, but no account exists set a cookie and redirect them back to the frontend
     * If the user is authenticated set a cookie and redirect back to the frontend
     */
    public function login(Request $request)
    {
        if ($request->cookies->has(self::JWT_COOKIE)) {
            $response = $this->createSuccessResponseFromJWT($request->cookies->get(self::JWT_COOKIE));
            $response->headers->clearCookie(self::JWT_COOKIE);

            return $response;
        }
        if ($request->cookies->has(self::NO_ACCOUNT_EXISTS_COOKIE)) {
            $response = $this->createNoAccountExistsResponse($request->cookies->get(self::NO_ACCOUNT_EXISTS_COOKIE));
            $response->headers->clearCookie(self::NO_ACCOUNT_EXISTS_COOKIE);

            return $response;
        }
        $ticket = $request->query->get('ticket');

        if (!$ticket) {
            return new JsonResponse([
                'status' => 'redirect',
                'errors' => [],
                'jwt' => null,
            ], JsonResponse::HTTP_OK);
        }

        $username = $this->casManager->getUsername($this->getServiceUrl(), $ticket);
        if (!$username) {
            $msg =  "No user found for authenticated user.";
            $this->logger->error($msg, ['server vars' => var_export($_SERVER, true)]);
            throw new Exception($msg);
        }
        /* @var \App\Entity\AuthenticationInterface $authEntity */
        $authEntity = $this->authManager->findOneBy(['username' => $username]);
        if ($authEntity) {
            $sessionUser = $this->sessionUserProvider->createSessionUserFromUser($authEntity->getUser());
            if ($sessionUser->isEnabled()) {
                $jwt = $this->jwtManager->createJwtFromSessionUser($sessionUser);
                if ($request->cookies->has(self::REDIRECT_COOKIE)) {
                    $response = RedirectResponse::create($request->cookies->get(self::REDIRECT_COOKIE));
                    $response->headers->clearCookie(self::REDIRECT_COOKIE);
                } else {
                    $response = $this->createSuccessResponseFromJWT($jwt);
                }
                $response->headers->setCookie(Cookie::create(
                    self::JWT_COOKIE,
                    $jwt,
                    strtotime('now + 45 seconds')
                ));

                return $response;
            }
        }

        if ($request->cookies->has(self::REDIRECT_COOKIE)) {
            $url = $request->cookies->get(self::REDIRECT_COOKIE);
        } else {
            $url = $this->router->generate(
                'ilios_web_assets',
                [],
                UrlGenerator::ABSOLUTE_URL
            );
        }

        $response = RedirectResponse::create($url);
        $response->headers->setCookie(Cookie::create(
            self::NO_ACCOUNT_EXISTS_COOKIE,
            $username,
            strtotime('now + 45 seconds')
        ));

        return $response;
    }

    /**
     * @inheritDoc
     */
    public function logout(Request $request)
    {
        $logoutUrl = $this->casManager->getLogoutUrl();
        return new JsonResponse([
            'status' => 'redirect',
            'logoutUrl' => $logoutUrl
        ], JsonResponse::HTTP_OK);
    }

    /**
     * @inheritdoc
     */
    public function getPublicConfigurationInformation(Request $request)
    {<commit_msg>Hold onto the CAS JWT cookie for continued login<commit_after><?php

declare(strict_types=1);

namespace App\Service;

use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\JsonResponse;
use App\Entity\Manager\AuthenticationManager;
use App\Traits\AuthenticationService;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Generator\UrlGenerator;
use Symfony\Component\Routing\RouterInterface;
use Exception;
use UnexpectedValueException;

/**
 * Authenticate user using CAS Protocol and return a JWT
 */
class CasAuthentication implements AuthenticationInterface
{
    use AuthenticationService;

    protected const REDIRECT_COOKIE = 'ilios-cas-redirect';
    protected const JWT_COOKIE = 'ilios-cas-jwt';
    protected const NO_ACCOUNT_EXISTS_COOKIE = 'ilios-cas-no-account-exists';

    /**
     * @var AuthenticationManager
     */
    protected $authManager;

    /**
     * @var JsonWebTokenManager
     */
    protected $jwtManager;

    /**
     * @var LoggerInterface
     */
    protected $logger;

    /**
     * @var RouterInterface
     */
    protected $router;

    /**
     * @var CasManager
     */
    protected $casManager;

    /**
     * @var SessionUserProvider
     */
    protected $sessionUserProvider;

    /**
     * Constructor
     * @param AuthenticationManager $authManager
     * @param JsonWebTokenManager $jwtManager
     * @param LoggerInterface $logger
     * @param RouterInterface $router
     * @param CasManager $casManager
     * @param SessionUserProvider $sessionUserProvider
     */
    public function __construct(
        AuthenticationManager $authManager,
        JsonWebTokenManager $jwtManager,
        LoggerInterface $logger,
        RouterInterface $router,
        CasManager $casManager,
        SessionUserProvider $sessionUserProvider
    ) {
        $this->authManager = $authManager;
        $this->jwtManager = $jwtManager;
        $this->logger = $logger;
        $this->router = $router;
        $this->casManager = $casManager;
        $this->sessionUserProvider = $sessionUserProvider;
    }

    /**
     * Authenticate a user from CAS
     *
     * If a JWT cookie exists user it to create a JSON response
     * If the user account doesn't exist send a JSON response with that error
     * If the user is not yet logged in send a redirect Request
     * If the user is logged in, but no account exists set a cookie and redirect them back to the frontend
     * If the user is authenticated set a cookie and redirect back to the frontend
     */
    public function login(Request $request)
    {
        if ($request->cookies->has(self::JWT_COOKIE)) {
            $jwt = $request->cookies->get(self::JWT_COOKIE);
            try {
                $this->jwtManager->getUserIdFromToken($jwt);
                return $this->createSuccessResponseFromJWT($jwt);
            } catch (UnexpectedValueException $e) {
                //JWT could not be validated, move on
            }
        }
        if ($request->cookies->has(self::NO_ACCOUNT_EXISTS_COOKIE)) {
            $response = $this->createNoAccountExistsResponse($request->cookies->get(self::NO_ACCOUNT_EXISTS_COOKIE));
            $response->headers->clearCookie(self::NO_ACCOUNT_EXISTS_COOKIE);

            return $response;
        }
        $ticket = $request->query->get('ticket');

        if (!$ticket) {
            return new JsonResponse([
                'status' => 'redirect',
                'errors' => [],
                'jwt' => null,
            ], JsonResponse::HTTP_OK);
        }

        $username = $this->casManager->getUsername($this->getServiceUrl(), $ticket);
        if (!$username) {
            $msg =  "No user found for authenticated user.";
            $this->logger->error($msg, ['server vars' => var_export($_SERVER, true)]);
            throw new Exception($msg);
        }
        /* @var \App\Entity\AuthenticationInterface $authEntity */
        $authEntity = $this->authManager->findOneBy(['username' => $username]);
        if ($authEntity) {
            $sessionUser = $this->sessionUserProvider->createSessionUserFromUser($authEntity->getUser());
            if ($sessionUser->isEnabled()) {
                $jwt = $this->jwtManager->createJwtFromSessionUser($sessionUser);
                if ($request->cookies->has(self::REDIRECT_COOKIE)) {
                    $response = RedirectResponse::create($request->cookies->get(self::REDIRECT_COOKIE));
                    $response->headers->clearCookie(self::REDIRECT_COOKIE);
                } else {
                    $response = $this->createSuccessResponseFromJWT($jwt);
                }
                $exp = $this->jwtManager->getExpiresAtFromToken($jwt);
                $response->headers->setCookie(Cookie::create(
                    self::JWT_COOKIE,
                    $jwt,
                    $exp
                ));

                return $response;
            }
        }

        if ($request->cookies->has(self::REDIRECT_COOKIE)) {
            $url = $request->cookies->get(self::REDIRECT_COOKIE);
        } else {
            $url = $this->router->generate(
                'ilios_web_assets',
                [],
                UrlGenerator::ABSOLUTE_URL
            );
        }

        $response = RedirectResponse::create($url);
        $response->headers->setCookie(Cookie::create(
            self::NO_ACCOUNT_EXISTS_COOKIE,
            $username,
            strtotime('now + 45 seconds')
        ));

        return $response;
    }

    /**
     * @inheritDoc
     */
    public function logout(Request $request)
    {
        $logoutUrl = $this->casManager->getLogoutUrl();
        $response =  new JsonResponse([
            'status' => 'redirect',
            'logoutUrl' => $logoutUrl
        ], JsonResponse::HTTP_OK);
        $response->headers->clearCookie(self::JWT_COOKIE);

        return $response;
    }

    /**
     * @inheritdoc
     */
    public function getPublicConfigurationInformation(Request $request)
    {
{'fraction_non_alphanumeric': 0.11576929488248042, 'fraction_numerical': 0.0006667777962993832, 'mean_word_length': 2.319225449515906, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 129, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '502530', 'n_tokens_mistral': 3346, 'n_tokens_neox': 3245, 'n_words': 662}