Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
f151c37
Maintain | Add support for Symfony 6
stloyd Sep 13, 2021
bed34d4
Bugfix | Rework `ConnectController` to match deprecation of `get()` i…
stloyd Sep 14, 2021
475acb0
Bugfix | Fixed usage of deprecated `framework.session.storage_id` opt…
stloyd Sep 14, 2021
50734fd
Bugfix | Fixed `ResourceOwnerTestCase::createMockResponse()` to match…
stloyd Sep 14, 2021
37a50c8
Bugfix | Rework functional tests to work with session system changes
stloyd Sep 14, 2021
a5ba2f5
Bugfix | Force redirects in tests & enable session; remove support fo…
stloyd Dec 4, 2021
93ca69d
Bugfix | `Token::setAuthenticated()` is deprecated since Symfony 5.4
stloyd Dec 4, 2021
beefc5e
Bugfix | Update test fixtures to use type & return hints
stloyd Dec 4, 2021
8e0baea
Bugfix | Added new `OAuthAuthenticator::createToken()` method
stloyd Dec 4, 2021
1e91b70
Bugfix | Fixed `OAuthUserProvider` to not use deprecated code
stloyd Dec 4, 2021
fda73cb
Bugfix | Fixed missing type hint & return types in security code
stloyd Dec 4, 2021
3f0f2a2
Bugfix | Initialize `OAuthAuthenticatorFactory` when possible as secu…
stloyd Dec 4, 2021
ca06652
Bugfix | Make integration tests more dynamic to work with wide range …
stloyd Dec 4, 2021
4b4c25a
Bugfix | Split functional security configuration to be per Symfony ve…
stloyd Dec 6, 2021
4f1675d
Bugfix | Adjust `OAuthAuthenticator` to be compatible with new Symfon…
stloyd Dec 6, 2021
d6569e3
Bugfix | Rework `ConnectController` to use DI injection only
stloyd Dec 6, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ Changelog
=========
## 2.0.0 (2021-xx-xx)
* BC Break: Dropped PHP 7.3 support,
* BC Break: Dropped support for Symfony: >=5.1 & <5.4,
* BC Break: `OAuthExtension` is now a lazy Twig extension using a Runtime,
* BC Break: removed support for `FOSUserBundle`,
* BC Break: changed `process()` argument for `Form/RegistrationFormHandlerInterface`, from `Form $form` to `FormInterface $form`,
Expand All @@ -19,6 +20,8 @@ Changelog
* BC Break: changed `__construct()` argument for `OAuth/ResourceOwner/AbstractResourceOwner`, from `HttpMethodsClient $httpClient` to `HttpClientInterface $httpClient`,
* BC Break: replaced `php-http/httplug-bundle` with `symfony/http-client`
* BC Break: removed `hwi_oauth.http` configuration
* Added support for PHP 8.1,
* Added support for Symfony 5.6,

## 1.4.2 (2021-08-09)
* Bugfix: remove `@final` declaration from `OAuthFactory` & `FOSUBUserProvider`,
Expand Down
111 changes: 68 additions & 43 deletions Controller/ConnectController.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,33 +22,47 @@
use HWI\Bundle\OAuthBundle\Security\Core\Exception\AccountNotLinkedException;
use HWI\Bundle\OAuthBundle\Security\Http\ResourceOwnerMapLocator;
use HWI\Bundle\OAuthBundle\Security\OAuthUtils;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\EventDispatcher\Event as DeprecatedEvent;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Core\Exception\AccountStatusException;
use Symfony\Component\Security\Core\User\UserCheckerInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use Symfony\Component\Security\Http\SecurityEvents;
use Symfony\Contracts\EventDispatcher\Event;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
use Twig\Environment;

/**
* @author Alexander <iam.asm89@gmail.com>
*
* @internal
*/
final class ConnectController extends AbstractController
final class ConnectController
{
private OAuthUtils $oauthUtils;
private ResourceOwnerMapLocator $resourceOwnerMapLocator;
private RequestStack $requestStack;
private EventDispatcherInterface $dispatcher;
private TokenStorageInterface $tokenStorage;
private AccountConnectorInterface $accountConnector;
private UserCheckerInterface $userChecker;
private RegistrationFormHandlerInterface $formHandler;
private AuthorizationCheckerInterface $authorizationChecker;
private FormFactoryInterface $formFactory;
private Environment $twig;
private RouterInterface $router;
private bool $enableConnect;
private string $grantRule;
private bool $failedUseReferer;
Expand All @@ -64,6 +78,15 @@ public function __construct(
OAuthUtils $oauthUtils,
ResourceOwnerMapLocator $resourceOwnerMapLocator,
RequestStack $requestStack,
EventDispatcherInterface $dispatcher,
TokenStorageInterface $tokenStorage,
AccountConnectorInterface $accountConnector,
UserCheckerInterface $userChecker,
RegistrationFormHandlerInterface $formHandler,
AuthorizationCheckerInterface $authorizationChecker,
FormFactoryInterface $formFactory,
Environment $twig,
RouterInterface $router,
bool $enableConnect,
string $grantRule,
bool $failedUseReferer,
Expand All @@ -82,6 +105,15 @@ public function __construct(
$this->enableConnectConfirmation = $enableConnectConfirmation;
$this->firewallNames = $firewallNames;
$this->registrationForm = $registrationForm;
$this->dispatcher = $dispatcher;
$this->accountConnector = $accountConnector;
$this->tokenStorage = $tokenStorage;
$this->userChecker = $userChecker;
$this->formHandler = $formHandler;
$this->authorizationChecker = $authorizationChecker;
$this->formFactory = $formFactory;
$this->twig = $twig;
$this->router = $router;
}

/**
Expand All @@ -100,7 +132,7 @@ public function registrationAction(Request $request, string $key): Response
throw new NotFoundHttpException();
}

$hasUser = $this->isGranted($this->grantRule);
$hasUser = $this->authorizationChecker->isGranted($this->grantRule);
if ($hasUser) {
throw new AccessDeniedException('Cannot connect already registered account.');
}
Expand All @@ -124,28 +156,24 @@ public function registrationAction(Request $request, string $key): Response
->getUserInformation($error->getRawToken())
;

$form = $this->createForm($this->registrationForm);
$form = $this->formFactory->create($this->registrationForm);

/** @var RegistrationFormHandlerInterface $formHandler */
$formHandler = $this->get('hwi_oauth.registration.form.handler');
if ($formHandler->process($request, $form, $userInformation)) {
if ($this->formHandler->process($request, $form, $userInformation)) {
$event = new FormEvent($form, $request);
$this->dispatch($event, HWIOAuthEvents::REGISTRATION_SUCCESS);

/** @var AccountConnectorInterface $connector */
$connector = $this->get('hwi_oauth.account.connector');
$connector->connect($form->getData(), $userInformation);
$this->accountConnector->connect($form->getData(), $userInformation);

// Authenticate the user
$this->authenticateUser($request, $form->getData(), $error->getResourceOwnerName(), $error->getAccessToken());

if (null === $response = $event->getResponse()) {
if ($targetPath = $this->getTargetPath($session)) {
$response = $this->redirect($targetPath);
$response = new RedirectResponse($targetPath);
} else {
$response = $this->render('@HWIOAuth/Connect/registration_success.html.twig', [
$response = new Response($this->twig->render('@HWIOAuth/Connect/registration_success.html.twig', [
'userInformation' => $userInformation,
]);
]));
}
}

Expand All @@ -167,11 +195,11 @@ public function registrationAction(Request $request, string $key): Response
return $response;
}

return $this->render('@HWIOAuth/Connect/registration.html.twig', [
return new Response($this->twig->render('@HWIOAuth/Connect/registration.html.twig', [
'key' => $key,
'form' => $form->createView(),
'userInformation' => $userInformation,
]);
]));
}

/**
Expand All @@ -189,7 +217,7 @@ public function connectServiceAction(Request $request, string $service): Respons
throw new NotFoundHttpException();
}

$hasUser = $this->isGranted($this->grantRule);
$hasUser = $this->authorizationChecker->isGranted($this->grantRule);
if (!$hasUser) {
throw new AccessDeniedException('Cannot connect an account.');
}
Expand Down Expand Up @@ -222,38 +250,41 @@ public function connectServiceAction(Request $request, string $service): Respons
// Redirect to the login path if the token is empty (Eg. User cancelled auth)
if (null === $accessToken) {
if ($this->failedUseReferer && $targetPath = $this->getTargetPath($session)) {
return $this->redirect($targetPath);
return new RedirectResponse($targetPath);
}

return $this->redirectToRoute($this->failedAuthPath);
return new RedirectResponse($this->router->generate($this->failedAuthPath));
}

// Show confirmation page?
if (!$this->enableConnectConfirmation) {
return $this->getConfirmationResponse($request, $accessToken, $service);
}

$form = $this->createForm(FormType::class);
$form = $this->formFactory->create();
$form->handleRequest($request);

if ($form->isSubmitted() && $form->isValid()) {
return $this->getConfirmationResponse($request, $accessToken, $service);
}

$event = new GetResponseUserEvent($this->getUser(), $request);
/** @var TokenInterface $token */
$token = $this->tokenStorage->getToken();

$event = new GetResponseUserEvent($token->getUser(), $request);

$this->dispatch($event, HWIOAuthEvents::CONNECT_INITIALIZE);

if ($response = $event->getResponse()) {
return $response;
}

return $this->render('@HWIOAuth/Connect/connect_confirm.html.twig', [
return new Response($this->twig->render('@HWIOAuth/Connect/connect_confirm.html.twig', [
'key' => $key,
'service' => $service,
'form' => $form->createView(),
'userInformation' => $resourceOwner->getUserInformation($accessToken),
]);
]));
}

/**
Expand Down Expand Up @@ -285,10 +316,8 @@ private function getResourceOwnerByName(string $name): ResourceOwnerInterface
private function authenticateUser(Request $request, UserInterface $user, string $resourceOwnerName, $accessToken, bool $fakeLogin = true): void
{
try {
/** @var UserCheckerInterface $userChecker */
$userChecker = $this->get('hwi_oauth.user_checker');
$userChecker->checkPreAuth($user);
$userChecker->checkPostAuth($user);
$this->userChecker->checkPreAuth($user);
$this->userChecker->checkPostAuth($user);
} catch (AccountStatusException $e) {
// Don't authenticate locked, disabled or expired users
return;
Expand All @@ -297,11 +326,13 @@ private function authenticateUser(Request $request, UserInterface $user, string
$token = new OAuthToken($accessToken, $user->getRoles());
$token->setResourceOwnerName($resourceOwnerName);
$token->setUser($user);
$token->setAuthenticated(true);

/** @var TokenStorageInterface $tokenStorage */
$tokenStorage = $this->get('security.token_storage');
$tokenStorage->setToken($token);
// required for compatibility with Symfony 5.4
if (method_exists($token, 'setAuthenticated')) {
$token->setAuthenticated(true, false);
}

$this->tokenStorage->setToken($token);

if ($fakeLogin) {
// Since we're "faking" normal login, we need to throw our INTERACTIVE_LOGIN event manually
Expand Down Expand Up @@ -335,11 +366,8 @@ private function getTargetPath(?SessionInterface $session): ?string
*/
private function getConfirmationResponse(Request $request, array $accessToken, string $service): Response
{
/** @var TokenStorageInterface $tokenStorage */
$tokenStorage = $this->get('security.token_storage');

/** @var OAuthToken $currentToken */
$currentToken = $tokenStorage->getToken();
$currentToken = $this->tokenStorage->getToken();
/** @var UserInterface $currentUser */
$currentUser = $currentToken->getUser();

Expand All @@ -349,14 +377,11 @@ private function getConfirmationResponse(Request $request, array $accessToken, s
$event = new GetResponseUserEvent($currentUser, $request);
$this->dispatch($event, HWIOAuthEvents::CONNECT_CONFIRMED);

/** @var AccountConnectorInterface $connector */
$connector = $this->get('hwi_oauth.account.connector');
$connector->connect($currentUser, $userInformation);
$this->accountConnector->connect($currentUser, $userInformation);

if ($currentToken instanceof OAuthToken) {
// Update user token with new details
$newToken =
\is_array($accessToken) &&
(isset($accessToken['access_token']) || isset($accessToken['oauth_token'])) ?
$accessToken : $currentToken->getRawToken();

Expand All @@ -365,12 +390,12 @@ private function getConfirmationResponse(Request $request, array $accessToken, s

if (null === $response = $event->getResponse()) {
if ($targetPath = $this->getTargetPath($request->getSession())) {
$response = $this->redirect($targetPath);
$response = new RedirectResponse($targetPath);
} else {
$response = $this->render('@HWIOAuth/Connect/connect_success.html.twig', [
$response = new Response($this->twig->render('@HWIOAuth/Connect/connect_success.html.twig', [
'userInformation' => $userInformation,
'service' => $service,
]);
]));
}
}

Expand All @@ -385,7 +410,7 @@ private function getConfirmationResponse(Request $request, array $accessToken, s
*/
private function dispatch($event, string $eventName = null): void
{
$this->get('event_dispatcher')->dispatch($event, $eventName);
$this->dispatcher->dispatch($event, $eventName);
}

private function getSession(): ?SessionInterface
Expand Down
Loading