HwiOAuthBundle OAuthToken UserResponse объекта AbstractResourceOwner содержит ошибки - PullRequest
0 голосов
/ 08 ноября 2018

Я использую HWI OAuthBundle в Symfony, чтобы связать их с Google и Facebook. Я использую версию 0.6.3 пакета и версию Symfony v2.8.46.

Итак, моя проблема заключается в следующем: у меня есть пользователь (пользователь A), который уже один раз подключился к своей учетной записи с помощью кнопки Google Connect. Он подключен, на какое-то время закрывает вкладку, возвращается через несколько дней (я использую Remember_me), снова открывает страницу сайта.

OAuthToken, который он получает во время соединения, содержит правильные данные, но срок его действия истек, а атрибут "authenticated" OAuthToken имеет значение false.

Поэтому каждый раз, когда я вызываю метод is_granted('ROLE_ADMIN') в моем шаблоне, метод authenticate из OAuthProvider выдает AccountNotLinkedException.

Вот мое объяснение ниже (+ детали кода)

OAuthProvider выглядит следующим образом

/**
 * OAuthProvider.
 *
 * @author Geoffrey Bachelet <geoffrey.bachelet@gmail.com>
 * @author Alexander <iam.asm89@gmail.com>
 */
class OAuthProvider implements AuthenticationProviderInterface
{
    /**
     * @var ResourceOwnerMapInterface
     */
    private $resourceOwnerMap;

    /**
     * @var OAuthAwareUserProviderInterface
     */
    private $userProvider;

    /**
     * @var UserCheckerInterface
     */
    private $userChecker;

    /**
     * @var TokenStorageInterface
     */
    private $tokenStorage;

    /**
     * @param OAuthAwareUserProviderInterface $userProvider     User provider
     * @param ResourceOwnerMapInterface       $resourceOwnerMap Resource owner map
     * @param UserCheckerInterface            $userChecker      User checker
     * @param TokenStorageInterface           $tokenStorage
     */
    public function __construct(OAuthAwareUserProviderInterface $userProvider, ResourceOwnerMapInterface $resourceOwnerMap, UserCheckerInterface $userChecker, TokenStorageInterface $tokenStorage)
    {
        $this->userProvider = $userProvider;
        $this->resourceOwnerMap = $resourceOwnerMap;
        $this->userChecker = $userChecker;
        $this->tokenStorage = $tokenStorage;
    }

    /**
     * {@inheritdoc}
     */
    public function supports(TokenInterface $token)
    {
        return
            $token instanceof OAuthToken
            && $this->resourceOwnerMap->hasResourceOwnerByName($token->getResourceOwnerName())
        ;
    }

    /**
     * {@inheritdoc}
     */
    public function authenticate(TokenInterface $token)
    {
        if (!$this->supports($token)) {
            return;
        }

        // fix connect to external social very time
        if ($token->isAuthenticated()) {
            return $token;
        }

        /* @var OAuthToken $token */
        $resourceOwner = $this->resourceOwnerMap->getResourceOwnerByName($token->getResourceOwnerName());

        $oldToken = $token->isExpired() ? $this->refreshToken($token, $resourceOwner) : $token;
        $userResponse = $resourceOwner->getUserInformation($oldToken->getRawToken());

        try {
            $user = $this->userProvider->loadUserByOAuthUserResponse($userResponse);
        } catch (OAuthAwareExceptionInterface $e) {
            $e->setToken($oldToken);
            $e->setResourceOwnerName($oldToken->getResourceOwnerName());

            throw $e;
        }

        if (!$user instanceof UserInterface) {
            throw new AuthenticationServiceException('loadUserByOAuthUserResponse() must return a UserInterface.');
        }

        $this->userChecker->checkPreAuth($user);
        $this->userChecker->checkPostAuth($user);

        $token = new OAuthToken($oldToken->getRawToken(), $user->getRoles());
        $token->setResourceOwnerName($resourceOwner->getName());
        $token->setUser($user);
        $token->setAuthenticated(true);
        $token->setRefreshToken($oldToken->getRefreshToken());
        $token->setCreatedAt($oldToken->getCreatedAt());

        return $token;
    }

    /**
     * @param TokenInterface         $expiredToken
     * @param ResourceOwnerInterface $resourceOwner
     *
     * @return OAuthToken|TokenInterface
     */
    protected function refreshToken(TokenInterface $expiredToken, ResourceOwnerInterface $resourceOwner)
    {
        if (!$expiredToken->getRefreshToken()) {
            return $expiredToken;
        }

        $token = new OAuthToken($resourceOwner->refreshAccessToken($expiredToken->getRefreshToken()));
        $token->setRefreshToken($expiredToken->getRefreshToken());
        $this->tokenStorage->setToken($token);

        return $token;
    }
}

Моя проблема в том, что OAuthToken, переданный в качестве аргумента методу authenticate, имеет атрибут $authenticated, равный false, хотя он имеет правильные пользовательские данные. Как вы можете видеть ниже

enter image description here

Тогда когда метод $userResponse = $resourceOwner->getUserInformation($oldToken->getRawToken());, мой userResponse выглядит так

enter image description here

Как вы можете видеть в атрибуте $data UserResponse, есть некоторые ошибки. Из-за этих ошибок, когда я звоню $username = $response->getUsername();, я получаю нулевое значение для $username. Так что каждый раз, FOSUBUserProvider бросает и AccountNotLinkedException из-за этого кода ниже

/**
 * {@inheritdoc}
 */
public function loadUserByOAuthUserResponse(UserResponseInterface $response)
{
    $username = $response->getUsername();

    $user = $this->userManager->findUserBy(array($this->getProperty($response) => $username));
    if (null === $user || null === $username) {
        throw new AccountNotLinkedException(sprintf("User '%s' not found.", $username));
    }

    return $user;
}

И это исключение появляется, только если я вызываю is_granted('ROLE_ADMIN') в моем шаблоне. Если я не использую его, все работает нормально, и я могу получить данные «пользователя A» непосредственно из текущего OAuthToken. Но как только я использую is_granted, я получаю это AccountNotLinkedExpetion.

Чтобы помочь вам, пожалуйста, найдите ниже метод getUserInformation из GenericOAuth2ResourceOwner, который используется OAuthProvider

/**
     * {@inheritdoc}
     */
    public function getUserInformation(array $accessToken, array $extraParameters = array())
    {
        if ($this->options['use_bearer_authorization']) {
            $content = $this->httpRequest(
                $this->normalizeUrl($this->options['infos_url'], $extraParameters),
                null,
                array('Authorization' => 'Bearer '.$accessToken['access_token'])
            );
        } else {
            $content = $this->doGetUserInformationRequest(
                $this->normalizeUrl(
                    $this->options['infos_url'],
                    array_merge(array($this->options['attr_name'] => $accessToken['access_token']), $extraParameters)
                )
            );
        }

        $response = $this->getUserResponse();
        $response->setData($content instanceof ResponseInterface ? (string) $content->getBody() : $content);
        $response->setResourceOwner($this);
        $response->setOAuthToken(new OAuthToken($accessToken));

        return $response;
    }

А также данные отрисованного UserResponse

{\n
  "error": {\n
    "code": 401,\n
    "message": "Request is missing required authentication credential. Expected OAuth 2 access token, login cookie or other valid authentication credential. See ▶
    "status": "UNAUTHENTICATED"\n
  }\n
}\n
"""

Как получился ответ пользователя с ошибками? Это потому, что OAuthToken не аутентифицирован? В таком случае как это предотвратить?

Когда "пользователь A" возвращается на страницу, если OAuthToken не аутентифицирован, я бы предпочел, чтобы пользователь просто отключился, а не имел исключение (= 500 ошибка сервера).

Кто-нибудь может мне помочь, пожалуйста? Спасибо?

ОБНОВЛЕНИЕ

Так что у меня есть дополнительная информация. Я частично знаю, откуда и откуда возникла ошибка. Так в GenericOAuth2ResourceOwner есть такой метод

public function getUserInformation(array $accessToken, array $extraParameters = array())
    {
        if ($this->options['use_bearer_authorization']) {
            $content = $this->httpRequest(
                $this->normalizeUrl($this->options['infos_url'], $extraParameters),
                null,
                array('Authorization' => 'Bearer '.$accessToken['access_token'])
            );
        } else {
            $content = $this->doGetUserInformationRequest(
                $this->normalizeUrl(
                    $this->options['infos_url'],
                    array_merge(array($this->options['attr_name'] => $accessToken['access_token']), $extraParameters)
                )
            );
        }

        $response = $this->getUserResponse();
        $response->setData($content instanceof ResponseInterface ? (string) $content->getBody() : $content);
        $response->setResourceOwner($this);
        $response->setOAuthToken(new OAuthToken($accessToken));

        return $response;
    }

Похоже, что для возвращающихся пользователей $accessToken['access_token'] не обновляется и, следовательно, существует проблема с извлеченными $content.

На странице, где у меня была ошибка, $content выглядело так

enter image description here

Но когда я открыл новое окно в режиме инкогнито и попытался подключиться к Google с тем же пользователем, $content выглядел так

enter image description here

Единственная разница между этими двумя значениями $accessToken['access_token']. Моя гипотеза состоит в том, что для пользователей Remember_me старое значение сохраняется для $accessToken['access_token'].

Как вы думаете, это так? Есть ли способ автоматически обновить значение access_token? Если нет, как я могу вместо этого отключить пользователя?

...