<commit_before><?php

namespace Phisch90\OAuth\Server\Grant;

use Phisch90\OAuth\Server\Entity\ClientEntity;
use Phisch90\OAuth\Server\Entity\ScopeEntity;
use Phisch90\OAuth\Server\Entity\UserEntity;
use Phisch90\OAuth\Server\Repository\AccessTokenRepository;
use Phisch90\OAuth\Server\Repository\ClientRepository;
use Phisch90\OAuth\Server\Repository\RefreshTokenRepository;
use Phisch90\OAuth\Server\Repository\ScopeRepository;
use Phisch90\OAuth\Server\Repository\UserRepository;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;

class PasswordCredentialsGrant implements Grant
{

    /**
     * @var ClientRepository
     */
    private $clientRepository;

    /**
     * @var ScopeRepository
     */
    private $scopeRepository;

    /**
     * @var UserRepository
     */
    private $userRepository;

    /**
     * @var AccessTokenRepository
     */
    private $accessTokenRepository;

    /**
     * @var RefreshTokenRepository
     */
    private $refreshTokenRepository;

    /**
     * @param ClientRepository $clientRepository
     * @param ScopeRepository $scopeRepository
     * @param UserRepository $userRepository
     * @param AccessTokenRepository $accessTokenRepository
     * @param RefreshTokenRepository $refreshTokenRepository
     */
    public function __construct(
        ClientRepository $clientRepository,
        ScopeRepository $scopeRepository,
        UserRepository $userRepository,
        AccessTokenRepository $accessTokenRepository,
        RefreshTokenRepository $refreshTokenRepository
    ) {
        $this->clientRepository = $clientRepository;
        $this->scopeRepository = $scopeRepository;
        $this->userRepository = $userRepository;
        $this->accessTokenRepository = $accessTokenRepository;
        $this->refreshTokenRepository = $refreshTokenRepository;
    }

    /**
     * @param Request $request
     * @return bool
     */
    public function supports(Request $request)
    {
        return $request->get('grant_type') === $this->getIdentifier();
    }

    public function handle(Request $request)
    {
        $client = $this->validateClient($request);
        $scopes = $this->validateScopes($request);
        $user = $this->validateUser($request);

        // check if scopes fit for client


        $expiryDateTime = (new \DateTime())->add(new \DateInterval('PT1H'));
        $accessToken = $this->accessTokenRepository->createToken($client, $user, $scopes, $expiryDateTime);

        $expiryDateTime = (new \DateTime())->add(new \DateInterval('P1M'));
        $refreshToken = $this->refreshTokenRepository->createToken($accessToken, $expiryDateTime);

        // generate response

        $responseJson = [
            'access_token' => $accessToken->getIdentifier(),
            'token_type' => $this->getIdentifier(),
            'expires_in' => $accessToken->getExpiryDateTime()->getTimestamp() - (new \DateTime())->getTimestamp(),
            'refresh_token' => $refreshToken->getIdentifier(),
            'scope' => $this->generateScopeList($scopes)
        ];


        $response = new JsonResponse(
            $responseJson
        );

        return $response;
    }

    /**
     * @param ScopeEntity[] $scopes
     * @return string
     */
    private function generateScopeList(array $scopes)
    {
        $scopeNames = [];
        foreach ($scopes as $scope) {
            $scopeNames[] = $scope->getName();
        }
        return implode(' ', $scopeNames);
    }

    /**
     * @param Request $request
     * @return ClientEntity
     */
    private function validateClient(Request $request)
    {
        $clientId = $request->get('client_id');
        $clientSecret = $request->get('client_secret');

        $client = $this->clientRepository->getClient($clientId, $clientSecret, $this->getIdentifier());

        if ($client instanceof ClientEntity === false) {
            // throw invalid client exception
        }

        // check if redirect uri matches

        return $client;
    }

    /**
     * @param Request $request
     * @return ScopeEntity[]
     */
    private function validateScopes(Request $request)
    {
        $scopeParameter = $request->get('scope');
        $scopeIdentifiers = explode(' ', $scopeParameter);

        $scopes = $this->scopeRepository->getScopes($scopeIdentifiers);
<commit_msg>throw exceptions if the client is unknown, does not match the secret or does not support this grant type<commit_after><?php

namespace Phisch90\OAuth\Server\Grant;

use Phisch90\OAuth\Server\Entity\ClientEntity;
use Phisch90\OAuth\Server\Entity\ScopeEntity;
use Phisch90\OAuth\Server\Entity\UserEntity;
use Phisch90\OAuth\Server\Exception\AuthorizationServerException;
use Phisch90\OAuth\Server\Repository\AccessTokenRepository;
use Phisch90\OAuth\Server\Repository\ClientRepository;
use Phisch90\OAuth\Server\Repository\RefreshTokenRepository;
use Phisch90\OAuth\Server\Repository\ScopeRepository;
use Phisch90\OAuth\Server\Repository\UserRepository;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;

class PasswordCredentialsGrant implements Grant
{

    /**
     * @var ClientRepository
     */
    private $clientRepository;

    /**
     * @var ScopeRepository
     */
    private $scopeRepository;

    /**
     * @var UserRepository
     */
    private $userRepository;

    /**
     * @var AccessTokenRepository
     */
    private $accessTokenRepository;

    /**
     * @var RefreshTokenRepository
     */
    private $refreshTokenRepository;

    /**
     * @param ClientRepository $clientRepository
     * @param ScopeRepository $scopeRepository
     * @param UserRepository $userRepository
     * @param AccessTokenRepository $accessTokenRepository
     * @param RefreshTokenRepository $refreshTokenRepository
     */
    public function __construct(
        ClientRepository $clientRepository,
        ScopeRepository $scopeRepository,
        UserRepository $userRepository,
        AccessTokenRepository $accessTokenRepository,
        RefreshTokenRepository $refreshTokenRepository
    ) {
        $this->clientRepository = $clientRepository;
        $this->scopeRepository = $scopeRepository;
        $this->userRepository = $userRepository;
        $this->accessTokenRepository = $accessTokenRepository;
        $this->refreshTokenRepository = $refreshTokenRepository;
    }

    /**
     * @param Request $request
     * @return bool
     */
    public function supports(Request $request)
    {
        return $request->get('grant_type') === $this->getIdentifier();
    }

    public function handle(Request $request)
    {
        $client = $this->validateClient($request);
        $scopes = $this->validateScopes($request);
        $user = $this->validateUser($request);

        // check if scopes fit for client


        $expiryDateTime = (new \DateTime())->add(new \DateInterval('PT1H'));
        $accessToken = $this->accessTokenRepository->createToken($client, $user, $scopes, $expiryDateTime);

        $expiryDateTime = (new \DateTime())->add(new \DateInterval('P1M'));
        $refreshToken = $this->refreshTokenRepository->createToken($accessToken, $expiryDateTime);

        // generate response

        $responseJson = [
            'access_token' => $accessToken->getIdentifier(),
            'token_type' => $this->getIdentifier(),
            'expires_in' => $accessToken->getExpiryDateTime()->getTimestamp() - (new \DateTime())->getTimestamp(),
            'refresh_token' => $refreshToken->getIdentifier(),
            'scope' => $this->generateScopeList($scopes)
        ];


        $response = new JsonResponse(
            $responseJson
        );

        return $response;
    }

    /**
     * @param ScopeEntity[] $scopes
     * @return string
     */
    private function generateScopeList(array $scopes)
    {
        $scopeNames = [];
        foreach ($scopes as $scope) {
            $scopeNames[] = $scope->getName();
        }
        return implode(' ', $scopeNames);
    }

    /**
     * @param Request $request
     * @return ClientEntity
     * @throws AuthorizationServerException
     */
    private function validateClient(Request $request)
    {
        $clientId = $request->get('client_id');
        $clientSecret = $request->get('client_secret');

        $client = $this->clientRepository->getClient($clientId);

        if ($client instanceof ClientEntity === false) {
            throw new AuthorizationServerException('The requested client is unknown.', null, null, 'invalid_client');
        }

        if ($client->getSecret() !== $clientSecret) {
            throw new AuthorizationServerException(
                'The given client_secret does not match the client.',
                null,
                null,
                'invalid_client'
            );
        }

        if (!in_array($this->getIdentifier(), $client->getGrantTypes())) {
            throw new AuthorizationServerException(
                'The authenticated client is not authorized to use this authorization grant type.',
                null,
                null,
                'unauthorized_client'
            );
        }

        // TODO: check if validating the uri is necessary here, should be irrelevant for password credentials grant
        // TODO: might be neccessary if this will be used for multiple grants though

        return $client;
    }

    /**
     * @param Request $request
     * @return ScopeEntity[]
     */
    private function validateScopes(Request $request)
    {
        $scopeParameter = $request->get('scope');
        $scopeIdentifiers = explode(' ', $scopeParameter);

        $scopes = $this->scopeRepository->getScopes($scopeIdentifiers);

{'fraction_non_alphanumeric': 0.11620512820512821, 'fraction_numerical': 0.0043076923076923075, 'mean_word_length': 2.7838571982925884, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 79, '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': '4213994', 'n_tokens_mistral': 2588, 'n_tokens_neox': 2492, 'n_words': 524}