Skip to content

Commit 3214b8e

Browse files
OXDEV-9889 Add provider getter
1 parent 3c5914b commit 3214b8e

6 files changed

Lines changed: 137 additions & 9 deletions

File tree

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
<?php
2+
3+
namespace OxidEsales\SecurityModule\Authentication\OAuth2\Controller;
4+
5+
use OxidEsales\Eshop\Application\Controller\FrontendController;
6+
use OxidEsales\Eshop\Core\Registry;
7+
use League\OAuth2\Client\Token\AccessTokenInterface;
8+
use OxidEsales\SecurityModule\Authentication\OAuth2\Service\ProviderCollectorInterface;
9+
10+
class OAuthController extends FrontendController
11+
{
12+
public function redirect()
13+
{
14+
$providerName = Registry::getRequest()->getRequestEscapedParameter('provider');
15+
$collector = $this->getService(ProviderCollectorInterface::class);
16+
$provider = $collector->getProvider($providerName);
17+
18+
if (!$provider) {
19+
Registry::getUtilsView()->addErrorToDisplay("Unknown provider: $providerName");
20+
return;
21+
}
22+
23+
$state = bin2hex(random_bytes(16));
24+
$_SESSION['oauth_state'] = $state;
25+
26+
$authUrl = $provider->getAuthorizationUrl($state);
27+
Registry::getUtils()->redirect($authUrl);
28+
}
29+
30+
public function callback()
31+
{
32+
$providerName = Registry::getRequest()->getRequestEscapedParameter('provider');
33+
$collector = $this->getService(ProviderCollectorInterface::class);
34+
$provider = $collector->getProvider($providerName);
35+
36+
if (!$provider) {
37+
Registry::getUtilsView()->addErrorToDisplay("Unknown provider: $providerName");
38+
return;
39+
}
40+
41+
$state = Registry::getRequest()->getRequestEscapedParameter('state');
42+
if ($state !== ($_SESSION['oauth_state'] ?? null)) {
43+
Registry::getUtilsView()->addErrorToDisplay('Invalid OAuth state');
44+
return;
45+
}
46+
47+
try {
48+
$code = Registry::getRequest()->getRequestEscapedParameter('code');
49+
$token = $provider->getAccessToken($code);
50+
51+
if (!$provider->validateToken($token)) {
52+
throw new \RuntimeException('Token invalid or expired');
53+
}
54+
55+
$userInfo = $provider->getUserInfo($token);
56+
57+
// Now handle login or registration (customize this part)
58+
$this->handleUserLogin($providerName, $userInfo);
59+
60+
Registry::getUtils()->redirect('index.php?cl=account');
61+
62+
} catch (\Throwable $e) {
63+
Registry::getLogger()->error('OAuth callback error: ' . $e->getMessage());
64+
Registry::getUtilsView()->addErrorToDisplay('OAuth login failed. Please try again.');
65+
Registry::getUtils()->redirect('index.php?cl=login');
66+
}
67+
}
68+
69+
protected function handleUserLogin(string $providerName, array $userInfo): void
70+
{
71+
// TODO: check if user exists by provider ID or email, then log in or create
72+
// Example: $userService->loginOrRegisterViaOAuth($providerName, $userInfo);
73+
}
74+
}

src/Authentication/OAuth2/Service/ProviderCollector.php

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,30 @@ public function __construct(
1616
) {
1717
}
1818

19-
public function getProviders(): iterable
19+
/**
20+
* @return array<ProviderInterface>
21+
*/
22+
public function getProviders(): array
2023
{
21-
return $this->providers;
24+
$result = [];
25+
26+
foreach ($this->providers as $provider) {
27+
$result[$provider->getName()] = $provider;
28+
}
29+
30+
ksort($result);
31+
32+
return $result;
33+
}
34+
35+
public function getProvider(string $name): ProviderInterface
36+
{
37+
$providers = $this->getProviders();
38+
39+
if (!isset($providers[$name])) {
40+
throw new ProviderNotFound();
41+
}
42+
43+
return $providers[$name];
2244
}
2345
}

src/Authentication/OAuth2/Service/ProviderCollectorInterface.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,7 @@
99

1010
interface ProviderCollectorInterface
1111
{
12-
public function getProviders(): iterable;
12+
public function getProviders(): array;
13+
14+
public function getProvider(string $name): ProviderInterface;
1315
}

src/Authentication/OAuth2/Service/Provider/ProviderInterface.php renamed to src/Authentication/OAuth2/Service/ProviderInterface.php

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?php
22

3-
namespace OxidEsales\SecurityModule\Authentication\OAuth2\Service\Provider;
3+
namespace OxidEsales\SecurityModule\Authentication\OAuth2\Service;
44

55
use League\OAuth2\Client\Provider\AbstractProvider;
66
use League\OAuth2\Client\Token\AccessTokenInterface;
@@ -20,10 +20,8 @@ public function getClient(): AbstractProvider;
2020

2121
/**
2222
* Get the authorization URL to redirect the user for login/consent.
23-
*
24-
* @param string $state Random CSRF prevention token.
2523
*/
26-
public function getAuthorizationUrl(string $state): string;
24+
public function getAuthorizationUrl(): string;
2725

2826
/**
2927
* Exchange the authorization code for an access token.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
/**
4+
* Copyright © OXID eSales AG. All rights reserved.
5+
* See LICENSE file for license details.
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
namespace OxidEsales\SecurityModule\Authentication\OAuth2\Service;
11+
12+
class ProviderNotFound extends \Exception
13+
{
14+
}

tests/Unit/Authentication/OAuth2/Service/ProviderCollectorTest.php

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99

1010
namespace Authentication\OAuth2\Service;
1111

12-
use OxidEsales\SecurityModule\Authentication\OAuth2\Service\Provider\ProviderInterface;
12+
use OxidEsales\SecurityModule\Authentication\OAuth2\Service\ProviderInterface;
13+
use OxidEsales\SecurityModule\Authentication\OAuth2\Service\ProviderNotFound;
1314
use OxidEsales\SecurityModule\Authentication\OAuth2\Service\ProviderCollector;
1415
use PHPUnit\Framework\TestCase;
1516

@@ -26,12 +27,29 @@ public function testGetProviders(): void
2627
'provider 2',
2728
'provider 3'
2829
],
29-
array_map(fn($provider) => $provider->getName(), $result)
30+
array_keys($result)
3031
);
3132

3233
$this->assertContainsOnlyInstancesOf(ProviderInterface::class, $result);
3334
}
3435

36+
public function testGetExistingProvider()
37+
{
38+
$sut = new ProviderCollector($this->getProviders());
39+
$result = $sut->getProvider('provider 2');
40+
41+
$this->assertInstanceOf(ProviderInterface::class, $result);
42+
$this->assertSame('provider 2', $result->getName());
43+
}
44+
45+
public function testGetNotExistingProvider()
46+
{
47+
$this->expectException(ProviderNotFound::class);
48+
49+
$sut = new ProviderCollector($this->getProviders());
50+
$sut->getProvider('provider 9');
51+
}
52+
3553
private function getProviders(): array
3654
{
3755
$provider1 = $this->createConfiguredMock(ProviderInterface::class, [

0 commit comments

Comments
 (0)