-
-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
ec5d456
commit 93a92d3
Showing
6 changed files
with
316 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
<a href="{link authenticate!}" class=="oauth2-bumake csftton">Login</a> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace Contributte\OAuth2Client\UI\Components; | ||
|
||
use Contributte\OAuth2Client\Flow\AuthCodeFlow; | ||
use League\OAuth2\Client\Provider\Exception\IdentityProviderException; | ||
use League\OAuth2\Client\Provider\ResourceOwnerInterface; | ||
use League\OAuth2\Client\Token\AccessToken; | ||
use Nette\Application\UI\Control; | ||
use Nette\Bridges\ApplicationLatte\Template; | ||
use UnexpectedValueException; | ||
|
||
class GenericAuthControl extends Control | ||
{ | ||
|
||
/** @var AuthCodeFlow */ | ||
private $authCodeFlow; | ||
|
||
/** @var string|null */ | ||
private $redirectUri = null; | ||
|
||
/** @var string|null */ | ||
private $templatePath = null; | ||
|
||
/** @var array<callable> */ | ||
public $onAuthenticated = []; | ||
|
||
/** @var array<callable> */ | ||
public $onFailed = []; | ||
|
||
public function __construct(AuthCodeFlow $authCodeFlow, ?string $redirectUri = null) | ||
{ | ||
$this->authCodeFlow = $authCodeFlow; | ||
$this->redirectUri = $redirectUri; | ||
} | ||
|
||
public function setTemplate(string $templatePath): void | ||
{ | ||
$this->templatePath = $templatePath; | ||
} | ||
|
||
public function handleAuthenticate(): void | ||
{ | ||
$this->authenticate(); | ||
} | ||
|
||
public function authenticate(): void | ||
{ | ||
$this->getPresenter()->redirectUrl( | ||
$this->authCodeFlow->getAuthorizationUrl(['redirect_uri' => $this->redirectUri]) | ||
); | ||
} | ||
|
||
public function authorize(): ?ResourceOwnerInterface | ||
{ | ||
try { | ||
$accessToken = $this->authCodeFlow->getAccessToken($this->getPresenter()->getHttpRequest()->getQuery()); | ||
if (!$accessToken instanceof AccessToken) { | ||
throw new UnexpectedValueException(); | ||
} | ||
|
||
$user = $this->authCodeFlow->getProvider()->getResourceOwner($accessToken); | ||
$this->authenticationSucceed($accessToken, $user); | ||
return $user; | ||
} catch (IdentityProviderException $e) { | ||
$this->authenticationFailed(); | ||
} | ||
|
||
return null; | ||
} | ||
|
||
protected function authenticationFailed(): void | ||
{ | ||
$this->onFailed(); | ||
} | ||
|
||
protected function authenticationSucceed(AccessToken $accessToken, ResourceOwnerInterface $user): void | ||
{ | ||
$this->onAuthenticated($accessToken, $user); | ||
} | ||
|
||
public function render(): void | ||
{ | ||
$template = $this->getTemplate(); | ||
if (!$template instanceof Template) { | ||
throw new UnexpectedValueException(); | ||
} | ||
|
||
$template->render($this->templatePath ?? __DIR__ . '/GenericAuthControl.latte'); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace Tests\Cases\Flow; | ||
|
||
use Contributte\OAuth2Client\Flow\AuthCodeFlow; | ||
use Contributte\OAuth2Client\UI\Components\GenericAuthControl; | ||
use League\OAuth2\Client\Provider\AbstractProvider; | ||
use League\OAuth2\Client\Provider\Exception\IdentityProviderException; | ||
use League\OAuth2\Client\Provider\GenericResourceOwner; | ||
use League\OAuth2\Client\Token\AccessToken; | ||
use Mockery; | ||
use Nette\Application\AbortException; | ||
use Nette\Application\Responses\RedirectResponse; | ||
use Nette\Http\Request; | ||
use Nette\Http\UrlScript; | ||
use Ninjify\Nunjuck\Toolkit; | ||
use Tester\Assert; | ||
use Tester\Environment; | ||
use Tests\Fixtures\Presenter\TestPresenter; | ||
|
||
require_once __DIR__ . '/../../../bootstrap.php'; | ||
|
||
Environment::bypassFinals(); | ||
|
||
Toolkit::test(function (): void { | ||
$flow = Mockery::mock(AuthCodeFlow::class); | ||
$flow->shouldReceive('getAUthorizationUrl') | ||
->with(['redirect_uri' => 'https://localhost/redirect']) | ||
->andReturn('https://localhost/auth'); | ||
|
||
$authControl = new GenericAuthControl($flow, 'https://localhost/redirect'); | ||
$presenter = new TestPresenter($authControl); | ||
|
||
Assert::exception(function () use ($authControl) { | ||
$authControl->authenticate(); | ||
}, AbortException::class); | ||
|
||
/** @var RedirectResponse $response */ | ||
$response = $presenter->response; | ||
Assert::type(RedirectResponse::class, $response); | ||
Assert::equal('https://localhost/auth', $response->getUrl()); | ||
}); | ||
|
||
Toolkit::test(function (): void { | ||
$token = Mockery::mock(AccessToken::class); | ||
|
||
$provider = Mockery::mock(AbstractProvider::class); | ||
$provider->shouldReceive('getResourceOwner') | ||
->andReturn(new GenericResourceOwner([], 1)); | ||
|
||
$flow = Mockery::mock(AuthCodeFlow::class); | ||
$flow->shouldReceive('getAccessToken') | ||
->with(['code' => '123']) | ||
->andReturn($token); | ||
$flow->shouldReceive('getProvider') | ||
->andReturn($provider); | ||
|
||
$request = new Request(new UrlScript('https://localhost/redirect?code=123')); | ||
|
||
$events = []; | ||
|
||
$authControl = new GenericAuthControl($flow, 'https://localhost/redirect'); | ||
$authControl->onAuthenticated[] = function ($accessToken, $user) use (&$events) { | ||
$events[] = ['onAuthenticated', $accessToken, $user]; | ||
}; | ||
$authControl->onFailed[] = function () use (&$events) { | ||
$events[] = ['onFailed']; | ||
}; | ||
new TestPresenter($authControl, $request); | ||
|
||
$user = $authControl->authorize(); | ||
|
||
Assert::type(GenericResourceOwner::class, $user); | ||
|
||
Assert::count(1, $events); | ||
Assert::equal('onAuthenticated', $events[0][0]); | ||
Assert::type(AccessToken::class, $events[0][1]); | ||
Assert::type(GenericResourceOwner::class, $events[0][2]); | ||
}); | ||
|
||
Toolkit::test(function (): void { | ||
$flow = Mockery::mock(AuthCodeFlow::class); | ||
$flow->shouldReceive('getAccessToken') | ||
->andThrow(new IdentityProviderException('error', 1, null)); | ||
|
||
$request = new Request(new UrlScript('https://localhost/redirect?code=123')); | ||
|
||
$events = []; | ||
|
||
$authControl = new GenericAuthControl($flow, 'https://localhost/redirect'); | ||
$authControl->onAuthenticated[] = function ($accessToken, $user) use (&$events) { | ||
$events[] = ['onAuthenticated', $accessToken, $user]; | ||
}; | ||
$authControl->onFailed[] = function () use (&$events) { | ||
$events[] = ['onFailed']; | ||
}; | ||
new TestPresenter($authControl, $request); | ||
|
||
$user = $authControl->authorize(); | ||
|
||
Assert::null($user); | ||
|
||
Assert::count(1, $events); | ||
Assert::equal('onFailed', $events[0][0]); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace Tests\Fixtures\Presenter; | ||
|
||
use Nette\Application\Response; | ||
use Nette\Application\UI\Component; | ||
use Nette\Application\UI\Presenter; | ||
use Nette\Http\Request as HttpRequest; | ||
use Nette\Http\Response as HttpResponse; | ||
use Nette\Http\UrlScript; | ||
|
||
final class TestPresenter extends Presenter | ||
{ | ||
|
||
/** @var Component */ | ||
private $component; | ||
|
||
/** @var HttpRequest */ | ||
public $httpRequest; | ||
|
||
/** @var HttpResponse */ | ||
public $httpResponse; | ||
|
||
/** @var Response */ | ||
public $response; | ||
|
||
public function __construct(Component $component, ?HttpRequest $request = null, ?HttpResponse $response = null) | ||
{ | ||
parent::__construct(); | ||
$this->injectPrimary( | ||
null, | ||
null, | ||
null, | ||
$request ?? new HttpRequest(new UrlScript('http://localhost/page')), | ||
$response ?? new HttpResponse() | ||
); | ||
$this->component = $component; | ||
$this->getComponent('subject'); | ||
} | ||
|
||
protected function createComponentSubject(): Component | ||
{ | ||
return $this->component; | ||
} | ||
|
||
public function sendResponse(Response $response): void | ||
{ | ||
$this->response = $response; | ||
parent::sendResponse($response); | ||
} | ||
|
||
} |