From 5d61d3390fd7b2c8f17ea5796b84b03ee61084eb Mon Sep 17 00:00:00 2001 From: Greg Bowler Date: Thu, 12 Mar 2020 21:14:40 +0000 Subject: [PATCH 01/20] Introduce App ID, closes #3 (#5) --- src/AuthUri.php | 15 ++++++---- src/Authenticator.php | 4 +++ src/InitVectorNotSetException.php | 4 --- test/phpunit/AuthUriTest.php | 43 +++++++++++++++++++++++++---- test/phpunit/AuthenticatorTest.php | 44 +++++++++++++++++++++++------- 5 files changed, 84 insertions(+), 26 deletions(-) delete mode 100644 src/InitVectorNotSetException.php diff --git a/src/AuthUri.php b/src/AuthUri.php index 7472b60..df8e0a0 100644 --- a/src/AuthUri.php +++ b/src/AuthUri.php @@ -4,8 +4,9 @@ use Gt\Http\Uri; class AuthUri extends Uri { - const DEFAULT_BASE_URI = "login.authwave.com"; + const DEFAULT_BASE_REMOTE_URI = "login.authwave.com"; + const QUERY_STRING_ID = "id"; const QUERY_STRING_CIPHER = "cipher"; const QUERY_STRING_INIT_VECTOR = "iv"; const QUERY_STRING_CURRENT_PATH = "path"; @@ -14,20 +15,22 @@ class AuthUri extends Uri { * @param Token $token This must be the same instance of the Token when * creating Authenticator for the first time as it is when checking the * response from the Authwave provider (store in a session). + * @param string $clientId * @param string $currentPath - * @param string $baseUri The base URI of the application. This is the + * @param string $baseRemoteUri The base URI of the application. This is the * URI authority with optional scheme, as localhost allows http:// */ public function __construct( Token $token, + string $clientId, string $currentPath = "/", - string $baseUri = self::DEFAULT_BASE_URI + string $baseRemoteUri = self::DEFAULT_BASE_REMOTE_URI ) { - $baseUri = $this->normaliseBaseUri($baseUri); - - parent::__construct($baseUri); + $baseRemoteUri = $this->normaliseBaseUri($baseRemoteUri); + parent::__construct($baseRemoteUri); $this->query = http_build_query([ + self::QUERY_STRING_ID => $clientId, self::QUERY_STRING_CIPHER => (string)$token->generateRequestCipher(), self::QUERY_STRING_INIT_VECTOR => (string)$token->getIv(), self::QUERY_STRING_CURRENT_PATH => $currentPath, diff --git a/src/Authenticator.php b/src/Authenticator.php index faa39b4..fae0113 100644 --- a/src/Authenticator.php +++ b/src/Authenticator.php @@ -14,8 +14,10 @@ class Authenticator { private SessionContainer $session; private SessionData $sessionData; private RedirectHandler $redirectHandler; + private string $clientId; public function __construct( + string $clientId, string $clientKey, string $currentUriPath, string $authwaveHost = "login.authwave.com", @@ -32,6 +34,7 @@ public function __construct( $session->set(self::SESSION_KEY, new SessionData()); } + $this->clientId = $clientId; $this->clientKey = $clientKey; $this->currentUriPath = $currentUriPath; $this->authwaveHost = $authwaveHost; @@ -69,6 +72,7 @@ public function login(Token $token = null):void { $loginUri = new AuthUri( $token, + $this->clientId, $this->currentUriPath, $this->authwaveHost ); diff --git a/src/InitVectorNotSetException.php b/src/InitVectorNotSetException.php deleted file mode 100644 index ef6a6e3..0000000 --- a/src/InitVectorNotSetException.php +++ /dev/null @@ -1,4 +0,0 @@ -willReturn("https://example.com"); $token = self::createMock(Token::class); - $sut = new AuthUri($token, "", $baseUri); + $sut = new AuthUri( + $token, + "example-app-id", + "", + $baseUri + ); self::assertEquals( "https", $sut->getScheme() @@ -26,7 +31,13 @@ public function testAuthUriHttps() { // But it should still default to HTTPS on localhost. public function testGetAuthUriHostnameLocalhostHttpsByDefault() { $token = self::createMock(Token::class); - $sut = new AuthUri($token, "/", "localhost"); + $sut = new AuthUri( + $token, + "example-app-id", + "/", + "localhost" + ); + self::assertStringStartsWith( "https://localhost", $sut @@ -36,7 +47,12 @@ public function testGetAuthUriHostnameLocalhostHttpsByDefault() { // We should be able to set the scheme to HTTP for localhost hostname only. public function testGetAuthUriHostnameLocalhostHttpAllowed() { $token = self::createMock(Token::class); - $sut = new AuthUri($token, "/", "http://localhost"); + $sut = new AuthUri( + $token, + "example-app-id", + "/", + "http://localhost" + ); self::assertStringStartsWith( "http://localhost", $sut @@ -47,7 +63,12 @@ public function testGetAuthUriHostnameLocalhostHttpAllowed() { public function testGetAuthUriHostnameNotLocalhostHttpNotAllowed() { $token = self::createMock(Token::class); self::expectException(InsecureProtocolException::class); - new AuthUri($token, "/", "http://localhost.com"); + new AuthUri( + $token, + "example-app-id", + "/", + "http://localhost.com" + ); } public function testAuthUriHttpsInferred() { @@ -57,7 +78,12 @@ public function testAuthUriHttpsInferred() { // Note on the line above, no scheme is passed in - we must assume https. $token = self::createMock(Token::class); - $sut = new AuthUri($token, "/", $baseUri); + $sut = new AuthUri( + $token, + "example-app-id", + "/", + $baseUri); + self::assertEquals( "https", $sut->getScheme() @@ -79,7 +105,12 @@ public function testQueryString() { ->willReturn($iv); $returnPath = "/examplePage"; - $sut = new AuthUri($token, $returnPath, $baseUri); + $sut = new AuthUri( + $token, + "example-app-id", + $returnPath, + $baseUri + ); parse_str($sut->getQuery(), $queryParts); self::assertEquals( diff --git a/test/phpunit/AuthenticatorTest.php b/test/phpunit/AuthenticatorTest.php index 5d87e7f..c603dd1 100644 --- a/test/phpunit/AuthenticatorTest.php +++ b/test/phpunit/AuthenticatorTest.php @@ -16,12 +16,20 @@ class AuthenticatorTest extends TestCase { public function testConstructWithDefaultSessionNotStarted() { self::expectException(SessionNotStartedException::class); - new Authenticator("test-key","/"); + new Authenticator( + "example-app-id", + "test-key", + "/" + ); } public function testConstructWithDefaultSession() { $_SESSION = []; - new Authenticator("test-key", "/"); + new Authenticator( + "example-app-id", + "test-key", + "/" + ); self::assertArrayHasKey( Authenticator::SESSION_KEY, $_SESSION @@ -31,6 +39,7 @@ public function testConstructWithDefaultSession() { public function testIsLoggedInFalseByDefault() { $_SESSION = []; $sut = new Authenticator( + "example-app-id", "test-key", "/" ); @@ -49,8 +58,9 @@ public function testIsLoggedInTrueWhenSessionDataSet() { ]; $sut = new Authenticator( + "example-app-id", "test-key", - "/", + "/" ); self::assertTrue($sut->isLoggedIn()); } @@ -62,8 +72,9 @@ public function testLogoutClearsSession() { ]; $sut = new Authenticator( + "example-app-id", "test-key", - "/", + "/" ); $sut->logout(); self::assertEmpty($_SESSION); @@ -76,13 +87,14 @@ public function testLoginRedirects() { $redirectHandler->expects(self::once()) ->method("redirect") ->with(self::callback(fn(UriInterface $uri) => - $uri->getHost() === AuthUri::DEFAULT_BASE_URI + $uri->getHost() === AuthUri::DEFAULT_BASE_REMOTE_URI )); $sut = new Authenticator( + "example-app-id", "test-key", "/", - AuthUri::DEFAULT_BASE_URI, + AuthUri::DEFAULT_BASE_REMOTE_URI, null, $redirectHandler ); @@ -102,6 +114,7 @@ public function testLoginRedirectsLocalhost() { )); $sut = new Authenticator( + "example-app-id", "test-key", "/", "http://localhost:8081", @@ -117,6 +130,7 @@ public function testLoginRedirectsWithCorrectQueryString() { $key = uniqid("key-"); $currentPath = uniqid("/path/"); + $id = "example-app-id"; $cipher = "example-cipher"; $ivString = "example-iv"; @@ -131,6 +145,7 @@ public function testLoginRedirectsWithCorrectQueryString() { ->willReturn($iv); $expectedQueryParts = [ + AuthUri::QUERY_STRING_ID => $id, AuthUri::QUERY_STRING_CIPHER => $cipher, AuthUri::QUERY_STRING_INIT_VECTOR => $ivString, AuthUri::QUERY_STRING_CURRENT_PATH => $currentPath, @@ -145,9 +160,10 @@ public function testLoginRedirectsWithCorrectQueryString() { )); $sut = new Authenticator( + $id, $key, $currentPath, - AuthUri::DEFAULT_BASE_URI, + AuthUri::DEFAULT_BASE_REMOTE_URI, null, $redirectHandler ); @@ -165,9 +181,10 @@ public function testLoginDoesNothingWhenAlreadyLoggedIn() { ->method("redirect"); $sut = new Authenticator( + "example-app-id", "test-key", "/", - AuthUri::DEFAULT_BASE_URI, + AuthUri::DEFAULT_BASE_REMOTE_URI, null, $redirectHandler ); @@ -178,6 +195,7 @@ public function testLoginDoesNothingWhenAlreadyLoggedIn() { public function testGetUuidThrowsExceptionWhenNotLoggedIn() { $_SESSION = []; $sut = new Authenticator( + "example-app-id", "test-key", "/" ); @@ -199,6 +217,7 @@ public function testGetUuid() { Authenticator::SESSION_KEY => $sessionData, ]; $sut = new Authenticator( + "example-app-id", "test-key", "/" ); @@ -208,6 +227,7 @@ public function testGetUuid() { public function testGetEmailThrowsExceptionWhenNotLoggedIn() { $_SESSION = []; $sut = new Authenticator( + "example-app-id", "test-key", "/" ); @@ -229,6 +249,7 @@ public function testGetEmail() { Authenticator::SESSION_KEY => $sessionData, ]; $sut = new Authenticator( + "example-app-id", "test-key", "/" ); @@ -243,6 +264,7 @@ public function testCompleteAuthNotLoggedIn() { $_SESSION = []; self::expectException(NotLoggedInException::class); new Authenticator( + "example-app-id", "test-key", $currentUri ); @@ -275,9 +297,10 @@ public function testCompleteAuth() { Authenticator::SESSION_KEY => $sessionData, ]; new Authenticator( + "example-app-id", "test-key", $currentUri, - AuthUri::DEFAULT_BASE_URI, + AuthUri::DEFAULT_BASE_REMOTE_URI, null, $redirectHandler ); @@ -302,9 +325,10 @@ public function testCompleteAuthNotAffectedByQueryString() { $_SESSION = []; new Authenticator( + "example-app-id", "test-key", "/example-path?filter=something", - AuthUri::DEFAULT_BASE_URI, + AuthUri::DEFAULT_BASE_REMOTE_URI, null, $redirectHandler ); From 3a0a3cf1ea487ffe2c6a19bf5455aa05c63f09ef Mon Sep 17 00:00:00 2001 From: Greg Bowler Date: Wed, 18 Mar 2020 18:30:20 +0000 Subject: [PATCH 02/20] Add missing ID to example --- README.md | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 925246d..7683900 100644 --- a/README.md +++ b/README.md @@ -15,15 +15,17 @@ With the following PHP code below, you can display a log in button that, when cl use Authwave\Authenticator; require __DIR__ . "/vendor/autoload.php"; -// This constant can be loaded from your application's configuration -// or environment variables. They are supplied by the remote Authwave provider. +// These constants can be loaded from your application's configuration +// or environment variables. They are created in the remote Authwave provider. +define("CLIENT_ID", "my-secure-application"); define("CLIENT_KEY", "1234567890abcdef"); // Construct the Authenticator class as soon as possible, as this handles the // Authentication steps passed via the query string from the remote provider. $auth = new Authenticator( - CLIENT_KEY, // See above - $_SERVER["REQUEST_URI"] + CLIENT_ID, + CLIENT_KEY, + $_SERVER["REQUEST_URI"] ); // Handle authentication login/logout action via the querystring: @@ -32,24 +34,24 @@ if(isset($_GET["login"])) { // remote provider. The remote provider will in turn redirect the user agent // back to the return URI (set as 3rd parameter of Authenticator's constructor), // at which point the user will be considered authenticated. - $auth->login(); + $auth->login(); } elseif(isset($_GET["logout"])) { - $auth->logout(); + $auth->logout(); } // Authentication is handled by Authwave, so you can trust "isLoggedIn" // as a mechanism for protecting your sensitive information. if($auth->isLoggedIn()) { - echo <<You are logged in as {$auth->getEmail()}

-

Log out

- HTML; + echo <<You are logged in as {$auth->getEmail()}

+

Log out

+ HTML; } else { - echo <<You are not logged in!

-

Log in

- HTML; + echo <<You are not logged in!

+

Log in

+ HTML; } ``` \ No newline at end of file From 5a66aa66d9028100c34eb9d79a24f9afb755aff4 Mon Sep 17 00:00:00 2001 From: Greg Bowler Date: Wed, 18 Mar 2020 18:32:20 +0000 Subject: [PATCH 03/20] Implement remote logout for #7 --- src/Authenticator.php | 6 ++++++ src/ProviderUri/LogoutUri.php | 12 ++++++++++++ test/phpunit/AuthenticatorTest.php | 7 ++++++- 3 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 src/ProviderUri/LogoutUri.php diff --git a/src/Authenticator.php b/src/Authenticator.php index ce5e71c..bfaeb69 100644 --- a/src/Authenticator.php +++ b/src/Authenticator.php @@ -3,6 +3,7 @@ use Authwave\ProviderUri\AdminUri; use Authwave\ProviderUri\AuthUri; +use Authwave\ProviderUri\LogoutUri; use Gt\Http\Uri; use Gt\Session\SessionContainer; use Psr\Http\Message\UriInterface; @@ -79,6 +80,7 @@ public function login(Token $token = null):void { public function logout():void { // TODO: Should the logout redirect the user agent to the redirectPath? $this->session->remove(self::SESSION_KEY); + $this->redirectHandler->redirect($this->getLogoutUri()); } public function getUuid():string { @@ -109,6 +111,10 @@ public function getAdminUri( ); } + public function getLogoutUri():UriInterface { + return new LogoutUri($this->authwaveHost); + } + private function completeAuth():void { $responseCipher = $this->getResponseCipher(); diff --git a/src/ProviderUri/LogoutUri.php b/src/ProviderUri/LogoutUri.php new file mode 100644 index 0000000..debff7a --- /dev/null +++ b/src/ProviderUri/LogoutUri.php @@ -0,0 +1,12 @@ +normaliseBaseUri($baseRemoteUri); + parent::__construct($baseRemoteUri); + $this->path = self::PATH_LOGOUT; + } +} \ No newline at end of file diff --git a/test/phpunit/AuthenticatorTest.php b/test/phpunit/AuthenticatorTest.php index d20ad70..d1e4a35 100644 --- a/test/phpunit/AuthenticatorTest.php +++ b/test/phpunit/AuthenticatorTest.php @@ -72,10 +72,15 @@ public function testLogoutClearsSession() { Authenticator::SESSION_KEY => $sessionData ]; + $redirectHandler = self::createMock(RedirectHandler::class); + $sut = new Authenticator( "example-app-id", "test-key", - "/" + "/", + AuthUri::DEFAULT_BASE_REMOTE_URI, + null, + $redirectHandler ); $sut->logout(); self::assertEmpty($_SESSION); From 5f537a4914f48a225c0f93c6049375a0759a3e2d Mon Sep 17 00:00:00 2001 From: Greg Bowler Date: Wed, 18 Mar 2020 18:35:55 +0000 Subject: [PATCH 04/20] Add assertion that correct logout URI is redirected Closes #7 --- test/phpunit/AuthenticatorTest.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/phpunit/AuthenticatorTest.php b/test/phpunit/AuthenticatorTest.php index d1e4a35..e0a6f19 100644 --- a/test/phpunit/AuthenticatorTest.php +++ b/test/phpunit/AuthenticatorTest.php @@ -6,6 +6,7 @@ use Authwave\NotLoggedInException; use Authwave\ProviderUri\AdminUri; use Authwave\ProviderUri\AuthUri; +use Authwave\ProviderUri\LogoutUri; use Authwave\RedirectHandler; use Authwave\SessionData; use Authwave\SessionNotStartedException; @@ -73,6 +74,12 @@ public function testLogoutClearsSession() { ]; $redirectHandler = self::createMock(RedirectHandler::class); + $redirectHandler->expects(self::once()) + ->method("redirect") + ->with(self::callback(fn(UriInterface $uri) => + $uri->getHost() === AuthUri::DEFAULT_BASE_REMOTE_URI + && $uri->getPath() === LogoutUri::PATH_LOGOUT + )); $sut = new Authenticator( "example-app-id", From ca024155f696d6ed760d34e6df6504aecfd9f130 Mon Sep 17 00:00:00 2001 From: Greg Bowler Date: Wed, 18 Mar 2020 18:42:26 +0000 Subject: [PATCH 05/20] Log out via remote provider (#9) * Implement remote logout for #7 * Add assertion that correct logout URI is redirected Closes #7 --- src/Authenticator.php | 6 ++++++ src/ProviderUri/LogoutUri.php | 12 ++++++++++++ test/phpunit/AuthenticatorTest.php | 14 +++++++++++++- 3 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 src/ProviderUri/LogoutUri.php diff --git a/src/Authenticator.php b/src/Authenticator.php index ce5e71c..bfaeb69 100644 --- a/src/Authenticator.php +++ b/src/Authenticator.php @@ -3,6 +3,7 @@ use Authwave\ProviderUri\AdminUri; use Authwave\ProviderUri\AuthUri; +use Authwave\ProviderUri\LogoutUri; use Gt\Http\Uri; use Gt\Session\SessionContainer; use Psr\Http\Message\UriInterface; @@ -79,6 +80,7 @@ public function login(Token $token = null):void { public function logout():void { // TODO: Should the logout redirect the user agent to the redirectPath? $this->session->remove(self::SESSION_KEY); + $this->redirectHandler->redirect($this->getLogoutUri()); } public function getUuid():string { @@ -109,6 +111,10 @@ public function getAdminUri( ); } + public function getLogoutUri():UriInterface { + return new LogoutUri($this->authwaveHost); + } + private function completeAuth():void { $responseCipher = $this->getResponseCipher(); diff --git a/src/ProviderUri/LogoutUri.php b/src/ProviderUri/LogoutUri.php new file mode 100644 index 0000000..debff7a --- /dev/null +++ b/src/ProviderUri/LogoutUri.php @@ -0,0 +1,12 @@ +normaliseBaseUri($baseRemoteUri); + parent::__construct($baseRemoteUri); + $this->path = self::PATH_LOGOUT; + } +} \ No newline at end of file diff --git a/test/phpunit/AuthenticatorTest.php b/test/phpunit/AuthenticatorTest.php index d20ad70..e0a6f19 100644 --- a/test/phpunit/AuthenticatorTest.php +++ b/test/phpunit/AuthenticatorTest.php @@ -6,6 +6,7 @@ use Authwave\NotLoggedInException; use Authwave\ProviderUri\AdminUri; use Authwave\ProviderUri\AuthUri; +use Authwave\ProviderUri\LogoutUri; use Authwave\RedirectHandler; use Authwave\SessionData; use Authwave\SessionNotStartedException; @@ -72,10 +73,21 @@ public function testLogoutClearsSession() { Authenticator::SESSION_KEY => $sessionData ]; + $redirectHandler = self::createMock(RedirectHandler::class); + $redirectHandler->expects(self::once()) + ->method("redirect") + ->with(self::callback(fn(UriInterface $uri) => + $uri->getHost() === AuthUri::DEFAULT_BASE_REMOTE_URI + && $uri->getPath() === LogoutUri::PATH_LOGOUT + )); + $sut = new Authenticator( "example-app-id", "test-key", - "/" + "/", + AuthUri::DEFAULT_BASE_REMOTE_URI, + null, + $redirectHandler ); $sut->logout(); self::assertEmpty($_SESSION); From 4405e16501a2b666de774d69564c62edc323ae42 Mon Sep 17 00:00:00 2001 From: Greg Bowler Date: Wed, 18 Mar 2020 19:08:37 +0000 Subject: [PATCH 06/20] Test that returnTo parameter is passed --- src/ProviderUri/LogoutUri.php | 8 +++++++- test/phpunit/AuthenticatorTest.php | 3 ++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/ProviderUri/LogoutUri.php b/src/ProviderUri/LogoutUri.php index debff7a..806dd52 100644 --- a/src/ProviderUri/LogoutUri.php +++ b/src/ProviderUri/LogoutUri.php @@ -4,9 +4,15 @@ class LogoutUri extends AbstractProviderUri { const PATH_LOGOUT = "/logout"; - public function __construct(string $baseRemoteUri) { + public function __construct( + string $baseRemoteUri, + string $returnToUri = "/" + ) { $baseRemoteUri = $this->normaliseBaseUri($baseRemoteUri); parent::__construct($baseRemoteUri); $this->path = self::PATH_LOGOUT; + $this->query = http_build_query([ + "returnTo" => $returnToUri, + ]); } } \ No newline at end of file diff --git a/test/phpunit/AuthenticatorTest.php b/test/phpunit/AuthenticatorTest.php index e0a6f19..90fbcd1 100644 --- a/test/phpunit/AuthenticatorTest.php +++ b/test/phpunit/AuthenticatorTest.php @@ -67,7 +67,7 @@ public function testIsLoggedInTrueWhenSessionDataSet() { self::assertTrue($sut->isLoggedIn()); } - public function testLogoutClearsSession() { + public function testLogoutClearsSessionAndRedirects() { $sessionData = self::createMock(SessionData::class); $_SESSION = [ Authenticator::SESSION_KEY => $sessionData @@ -79,6 +79,7 @@ public function testLogoutClearsSession() { ->with(self::callback(fn(UriInterface $uri) => $uri->getHost() === AuthUri::DEFAULT_BASE_REMOTE_URI && $uri->getPath() === LogoutUri::PATH_LOGOUT + && $uri->getQuery() === "returnTo=" . urlencode("/") )); $sut = new Authenticator( From 88ccea0fc1cf1cedaba45851ba5c674465442f57 Mon Sep 17 00:00:00 2001 From: Greg Bowler Date: Wed, 18 Mar 2020 19:13:00 +0000 Subject: [PATCH 07/20] Test that current path is used for logout redirection --- src/Authenticator.php | 8 ++++++-- test/phpunit/AuthenticatorTest.php | 25 +++++++++++++++++++++---- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/src/Authenticator.php b/src/Authenticator.php index bfaeb69..b6b09af 100644 --- a/src/Authenticator.php +++ b/src/Authenticator.php @@ -111,8 +111,12 @@ public function getAdminUri( ); } - public function getLogoutUri():UriInterface { - return new LogoutUri($this->authwaveHost); + public function getLogoutUri(string $returnToPath = null):UriInterface { + if(is_null($returnToPath)) { + $returnToPath = $this->currentUriPath; + } + + return new LogoutUri($this->authwaveHost, $returnToPath); } private function completeAuth():void { diff --git a/test/phpunit/AuthenticatorTest.php b/test/phpunit/AuthenticatorTest.php index 90fbcd1..572c6fb 100644 --- a/test/phpunit/AuthenticatorTest.php +++ b/test/phpunit/AuthenticatorTest.php @@ -67,31 +67,48 @@ public function testIsLoggedInTrueWhenSessionDataSet() { self::assertTrue($sut->isLoggedIn()); } - public function testLogoutClearsSessionAndRedirects() { + public function testLogoutClearsSession() { $sessionData = self::createMock(SessionData::class); $_SESSION = [ Authenticator::SESSION_KEY => $sessionData ]; + $redirectHandler = self::createMock(RedirectHandler::class); + + $sut = new Authenticator( + "example-app-id", + "test-key", + "/", + AuthUri::DEFAULT_BASE_REMOTE_URI, + null, + $redirectHandler + ); + $sut->logout(); + self::assertEmpty($_SESSION); + } + + public function testLogoutRedirectsToCurrentPath() { + $_SESSION = []; + $currentPath = "/current/example/path"; + $redirectHandler = self::createMock(RedirectHandler::class); $redirectHandler->expects(self::once()) ->method("redirect") ->with(self::callback(fn(UriInterface $uri) => $uri->getHost() === AuthUri::DEFAULT_BASE_REMOTE_URI && $uri->getPath() === LogoutUri::PATH_LOGOUT - && $uri->getQuery() === "returnTo=" . urlencode("/") + && $uri->getQuery() === "returnTo=" . urlencode($currentPath) )); $sut = new Authenticator( "example-app-id", "test-key", - "/", + $currentPath, AuthUri::DEFAULT_BASE_REMOTE_URI, null, $redirectHandler ); $sut->logout(); - self::assertEmpty($_SESSION); } public function testLoginRedirects() { From 8e51531fa50f8a153360917587704aaed838464d Mon Sep 17 00:00:00 2001 From: Greg Bowler Date: Thu, 19 Mar 2020 17:24:25 +0000 Subject: [PATCH 08/20] Remove unneeded AppID --- README.md | 6 ++---- src/Authenticator.php | 4 ---- src/ProviderUri/AuthUri.php | 3 --- test/phpunit/AuthenticatorTest.php | 21 ------------------- .../ProviderUri/AbstractProviderUriTest.php | 5 ----- test/phpunit/ProviderUri/AuthUriTest.php | 1 - 6 files changed, 2 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 7683900..539097a 100644 --- a/README.md +++ b/README.md @@ -15,15 +15,13 @@ With the following PHP code below, you can display a log in button that, when cl use Authwave\Authenticator; require __DIR__ . "/vendor/autoload.php"; -// These constants can be loaded from your application's configuration -// or environment variables. They are created in the remote Authwave provider. -define("CLIENT_ID", "my-secure-application"); +// This constant can be loaded from your application's configuration +// or environment variables. It is created in the remote Authwave provider. define("CLIENT_KEY", "1234567890abcdef"); // Construct the Authenticator class as soon as possible, as this handles the // Authentication steps passed via the query string from the remote provider. $auth = new Authenticator( - CLIENT_ID, CLIENT_KEY, $_SERVER["REQUEST_URI"] ); diff --git a/src/Authenticator.php b/src/Authenticator.php index b6b09af..94af684 100644 --- a/src/Authenticator.php +++ b/src/Authenticator.php @@ -18,10 +18,8 @@ class Authenticator { private SessionContainer $session; private SessionData $sessionData; private RedirectHandler $redirectHandler; - private string $clientId; public function __construct( - string $clientId, string $clientKey, string $currentUriPath, string $authwaveHost = "login.authwave.com", @@ -38,7 +36,6 @@ public function __construct( $session->set(self::SESSION_KEY, new SessionData()); } - $this->clientId = $clientId; $this->clientKey = $clientKey; $this->currentUriPath = $currentUriPath; $this->authwaveHost = $authwaveHost; @@ -96,7 +93,6 @@ public function getEmail():string { public function getAuthUri(Token $token):AuthUri { return new AuthUri( $token, - $this->clientId, $this->currentUriPath, $this->authwaveHost ); diff --git a/src/ProviderUri/AuthUri.php b/src/ProviderUri/AuthUri.php index e803e3e..dca18c5 100644 --- a/src/ProviderUri/AuthUri.php +++ b/src/ProviderUri/AuthUri.php @@ -5,7 +5,6 @@ use Gt\Http\Uri; class AuthUri extends AbstractProviderUri { - const QUERY_STRING_ID = "id"; const QUERY_STRING_CIPHER = "cipher"; const QUERY_STRING_INIT_VECTOR = "iv"; const QUERY_STRING_CURRENT_PATH = "path"; @@ -21,7 +20,6 @@ class AuthUri extends AbstractProviderUri { */ public function __construct( Token $token, - string $clientId, string $currentPath = "/", string $baseRemoteUri = self::DEFAULT_BASE_REMOTE_URI ) { @@ -29,7 +27,6 @@ public function __construct( parent::__construct($baseRemoteUri); $this->query = http_build_query([ - self::QUERY_STRING_ID => $clientId, self::QUERY_STRING_CIPHER => (string)$token->generateRequestCipher(), self::QUERY_STRING_INIT_VECTOR => (string)$token->getIv(), self::QUERY_STRING_CURRENT_PATH => $currentPath, diff --git a/test/phpunit/AuthenticatorTest.php b/test/phpunit/AuthenticatorTest.php index 572c6fb..7ceab98 100644 --- a/test/phpunit/AuthenticatorTest.php +++ b/test/phpunit/AuthenticatorTest.php @@ -19,7 +19,6 @@ class AuthenticatorTest extends TestCase { public function testConstructWithDefaultSessionNotStarted() { self::expectException(SessionNotStartedException::class); new Authenticator( - "example-app-id", "test-key", "/" ); @@ -28,7 +27,6 @@ public function testConstructWithDefaultSessionNotStarted() { public function testConstructWithDefaultSession() { $_SESSION = []; new Authenticator( - "example-app-id", "test-key", "/" ); @@ -41,7 +39,6 @@ public function testConstructWithDefaultSession() { public function testIsLoggedInFalseByDefault() { $_SESSION = []; $sut = new Authenticator( - "example-app-id", "test-key", "/" ); @@ -60,7 +57,6 @@ public function testIsLoggedInTrueWhenSessionDataSet() { ]; $sut = new Authenticator( - "example-app-id", "test-key", "/" ); @@ -76,7 +72,6 @@ public function testLogoutClearsSession() { $redirectHandler = self::createMock(RedirectHandler::class); $sut = new Authenticator( - "example-app-id", "test-key", "/", AuthUri::DEFAULT_BASE_REMOTE_URI, @@ -101,7 +96,6 @@ public function testLogoutRedirectsToCurrentPath() { )); $sut = new Authenticator( - "example-app-id", "test-key", $currentPath, AuthUri::DEFAULT_BASE_REMOTE_URI, @@ -122,7 +116,6 @@ public function testLoginRedirects() { )); $sut = new Authenticator( - "example-app-id", "test-key", "/", AuthUri::DEFAULT_BASE_REMOTE_URI, @@ -145,7 +138,6 @@ public function testLoginRedirectsLocalhost() { )); $sut = new Authenticator( - "example-app-id", "test-key", "/", "http://localhost:8081", @@ -161,7 +153,6 @@ public function testLoginRedirectsWithCorrectQueryString() { $key = uniqid("key-"); $currentPath = uniqid("/path/"); - $id = "example-app-id"; $cipher = "example-cipher"; $ivString = "example-iv"; @@ -176,7 +167,6 @@ public function testLoginRedirectsWithCorrectQueryString() { ->willReturn($iv); $expectedQueryParts = [ - AuthUri::QUERY_STRING_ID => $id, AuthUri::QUERY_STRING_CIPHER => $cipher, AuthUri::QUERY_STRING_INIT_VECTOR => $ivString, AuthUri::QUERY_STRING_CURRENT_PATH => $currentPath, @@ -191,7 +181,6 @@ public function testLoginRedirectsWithCorrectQueryString() { )); $sut = new Authenticator( - $id, $key, $currentPath, AuthUri::DEFAULT_BASE_REMOTE_URI, @@ -212,7 +201,6 @@ public function testLoginDoesNothingWhenAlreadyLoggedIn() { ->method("redirect"); $sut = new Authenticator( - "example-app-id", "test-key", "/", AuthUri::DEFAULT_BASE_REMOTE_URI, @@ -226,7 +214,6 @@ public function testLoginDoesNothingWhenAlreadyLoggedIn() { public function testGetUuidThrowsExceptionWhenNotLoggedIn() { $_SESSION = []; $sut = new Authenticator( - "example-app-id", "test-key", "/" ); @@ -248,7 +235,6 @@ public function testGetUuid() { Authenticator::SESSION_KEY => $sessionData, ]; $sut = new Authenticator( - "example-app-id", "test-key", "/" ); @@ -258,7 +244,6 @@ public function testGetUuid() { public function testGetEmailThrowsExceptionWhenNotLoggedIn() { $_SESSION = []; $sut = new Authenticator( - "example-app-id", "test-key", "/" ); @@ -280,7 +265,6 @@ public function testGetEmail() { Authenticator::SESSION_KEY => $sessionData, ]; $sut = new Authenticator( - "example-app-id", "test-key", "/" ); @@ -295,7 +279,6 @@ public function testCompleteAuthNotLoggedIn() { $_SESSION = []; self::expectException(NotLoggedInException::class); new Authenticator( - "example-app-id", "test-key", $currentUri ); @@ -328,7 +311,6 @@ public function testCompleteAuth() { Authenticator::SESSION_KEY => $sessionData, ]; new Authenticator( - "example-app-id", "test-key", $currentUri, AuthUri::DEFAULT_BASE_REMOTE_URI, @@ -356,7 +338,6 @@ public function testCompleteAuthNotAffectedByQueryString() { $_SESSION = []; new Authenticator( - "example-app-id", "test-key", "/example-path?filter=something", AuthUri::DEFAULT_BASE_REMOTE_URI, @@ -368,7 +349,6 @@ public function testCompleteAuthNotAffectedByQueryString() { public function testGetAdminUri() { $_SESSION = []; $auth = new Authenticator( - "example-app-id", "test-key", "/example-path", AuthUri::DEFAULT_BASE_REMOTE_URI @@ -383,7 +363,6 @@ public function testGetAdminUri() { public function testGetAdminUriCustom() { $_SESSION = []; $auth = new Authenticator( - "example-app-id", "test-key", "/example-path", AuthUri::DEFAULT_BASE_REMOTE_URI diff --git a/test/phpunit/ProviderUri/AbstractProviderUriTest.php b/test/phpunit/ProviderUri/AbstractProviderUriTest.php index 19a656c..28d2287 100644 --- a/test/phpunit/ProviderUri/AbstractProviderUriTest.php +++ b/test/phpunit/ProviderUri/AbstractProviderUriTest.php @@ -17,7 +17,6 @@ public function testAuthUriHttps() { $sut = new AuthUri( $token, - "example-app-id", "", $baseUri ); @@ -33,7 +32,6 @@ public function testGetAuthUriHostnameLocalhostHttpsByDefault() { $token = self::createMock(Token::class); $sut = new AuthUri( $token, - "example-app-id", "/", "localhost" ); @@ -49,7 +47,6 @@ public function testGetAuthUriHostnameLocalhostHttpAllowed() { $token = self::createMock(Token::class); $sut = new AuthUri( $token, - "example-app-id", "/", "http://localhost" ); @@ -65,7 +62,6 @@ public function testGetAuthUriHostnameNotLocalhostHttpNotAllowed() { self::expectException(InsecureProtocolException::class); new AuthUri( $token, - "example-app-id", "/", "http://localhost.com" ); @@ -80,7 +76,6 @@ public function testAuthUriHttpsInferred() { $sut = new AuthUri( $token, - "example-app-id", "/", $baseUri); diff --git a/test/phpunit/ProviderUri/AuthUriTest.php b/test/phpunit/ProviderUri/AuthUriTest.php index 7db14bd..deac72e 100644 --- a/test/phpunit/ProviderUri/AuthUriTest.php +++ b/test/phpunit/ProviderUri/AuthUriTest.php @@ -25,7 +25,6 @@ public function testQueryString() { $returnPath = "/examplePage"; $sut = new AuthUri( $token, - "example-app-id", $returnPath, $baseUri ); From d9a28400c66f7e7c80c974358aae57db5d574573 Mon Sep 17 00:00:00 2001 From: Greg Bowler Date: Wed, 25 Mar 2020 22:06:29 +0000 Subject: [PATCH 09/20] Use standard ports where not specified --- src/ProviderUri/AbstractProviderUri.php | 2 +- .../ProviderUri/AbstractProviderUriTest.php | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/ProviderUri/AbstractProviderUri.php b/src/ProviderUri/AbstractProviderUri.php index c7dac64..35f0dda 100644 --- a/src/ProviderUri/AbstractProviderUri.php +++ b/src/ProviderUri/AbstractProviderUri.php @@ -13,7 +13,7 @@ protected function normaliseBaseUri(string $baseUri):Uri { $host = parse_url($baseUri, PHP_URL_HOST) ?? parse_url($baseUri, PHP_URL_PATH); $port = parse_url($baseUri, PHP_URL_PORT) - ?? 80; + ?? null; $uri = (new Uri()) ->withScheme($scheme) diff --git a/test/phpunit/ProviderUri/AbstractProviderUriTest.php b/test/phpunit/ProviderUri/AbstractProviderUriTest.php index 28d2287..88ce4c0 100644 --- a/test/phpunit/ProviderUri/AbstractProviderUriTest.php +++ b/test/phpunit/ProviderUri/AbstractProviderUriTest.php @@ -24,6 +24,30 @@ public function testAuthUriHttps() { "https", $sut->getScheme() ); + self::assertNull( + $sut->getPort() + ); + } + + public function testAuthUriWithNonStandardPort() { + $baseUri = self::createMock(UriInterface::class); + $baseUri->method("__toString") + ->willReturn("http://localhost:8081"); + $token = self::createMock(Token::class); + + $sut = new AuthUri( + $token, + "", + $baseUri + ); + self::assertEquals( + "http", + $sut->getScheme() + ); + self::assertEquals( + 8081, + $sut->getPort() + ); } // All AuthUris MUST be served over HTTPS, with the one exception of localhost. From 0dcae0979e796a4f76b85fd7d62cd59b145ef586 Mon Sep 17 00:00:00 2001 From: Greg Bowler Date: Sun, 29 Mar 2020 23:50:07 +0100 Subject: [PATCH 10/20] Native unserialisation rather than relying on JSON --- composer.json | 1 - src/Token.php | 13 ++++--------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/composer.json b/composer.json index d21a9c8..b360806 100644 --- a/composer.json +++ b/composer.json @@ -5,7 +5,6 @@ "require": { "php": ">=7.4", "ext-openssl": "*", - "ext-json": "*", "phpgt/http": "1.*", "phpgt/session": ">=1.1" }, diff --git a/src/Token.php b/src/Token.php index 9fe3c62..56abd95 100644 --- a/src/Token.php +++ b/src/Token.php @@ -60,15 +60,10 @@ public function decryptResponseCipher(string $cipher):UserData { throw new ResponseCipherDecryptionException(); } - try { - $obj = json_decode( - $decrypted, - false, - 2, - JSON_THROW_ON_ERROR - ); - } - catch(JsonException $exception) { + $obj = unserialize( + $decrypted + ); + if($obj === false) { throw new InvalidUserDataSerializationException(); } From 5f56e2656f1fff8c62ae5755549fceb2b0b20de0 Mon Sep 17 00:00:00 2001 From: Greg Bowler Date: Wed, 1 Apr 2020 17:37:54 +0100 Subject: [PATCH 11/20] Simplify decryption data --- src/Authenticator.php | 2 +- src/Token.php | 15 +++++---------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/Authenticator.php b/src/Authenticator.php index 94af684..4bb5c37 100644 --- a/src/Authenticator.php +++ b/src/Authenticator.php @@ -109,7 +109,7 @@ public function getAdminUri( public function getLogoutUri(string $returnToPath = null):UriInterface { if(is_null($returnToPath)) { - $returnToPath = $this->currentUriPath; + $returnToPath = (new Uri($this->currentUriPath))->getPath(); } return new LogoutUri($this->authwaveHost, $returnToPath); diff --git a/src/Token.php b/src/Token.php index 56abd95..2c6a655 100644 --- a/src/Token.php +++ b/src/Token.php @@ -1,8 +1,6 @@ key, - $this->secretIv->getBytes(), - ]), + $this->key, 0, - $this->iv->getBytes() + $this->secretIv->getBytes() ); if(!$decrypted) { throw new ResponseCipherDecryptionException(); } - $obj = unserialize( + $data = unserialize( $decrypted ); - if($obj === false) { + if($data === false) { throw new InvalidUserDataSerializationException(); } - return new UserData($obj->uuid, $obj->email); + return new UserData($data["uuid"], $data["email"]); } } \ No newline at end of file From eb3745c53d92549b08c7135c7ee209d6a5982101 Mon Sep 17 00:00:00 2001 From: Greg Bowler Date: Mon, 13 Apr 2020 14:31:39 +0100 Subject: [PATCH 12/20] Use serialised stdClass object to represent user data --- src/Authenticator.php | 6 +++++- src/ProviderUri/AdminUri.php | 2 +- src/Token.php | 4 ++-- test/phpunit/AuthenticatorTest.php | 2 +- test/phpunit/ProviderUri/AdminUriTest.php | 4 ++-- test/phpunit/TokenTest.php | 21 ++++++++++----------- 6 files changed, 21 insertions(+), 18 deletions(-) diff --git a/src/Authenticator.php b/src/Authenticator.php index 4bb5c37..c029f99 100644 --- a/src/Authenticator.php +++ b/src/Authenticator.php @@ -80,6 +80,10 @@ public function logout():void { $this->redirectHandler->redirect($this->getLogoutUri()); } + public function adminLogin():void { +// TODO: Implement! + } + public function getUuid():string { $userData = $this->sessionData->getUserData(); return $userData->getUuid(); @@ -99,7 +103,7 @@ public function getAuthUri(Token $token):AuthUri { } public function getAdminUri( - string $path = AdminUri::PATH_ACCOUNT + string $path = AdminUri::PATH_ADMIN ):UriInterface { return new AdminUri( $this->authwaveHost, diff --git a/src/ProviderUri/AdminUri.php b/src/ProviderUri/AdminUri.php index 13a725a..31845ea 100644 --- a/src/ProviderUri/AdminUri.php +++ b/src/ProviderUri/AdminUri.php @@ -2,7 +2,7 @@ namespace Authwave\ProviderUri; class AdminUri extends AbstractProviderUri { - const PATH_ACCOUNT = "/account"; + const PATH_ADMIN = "/admin"; const PATH_SETTINGS = "/settings"; public function __construct( diff --git a/src/Token.php b/src/Token.php index 2c6a655..a45de09 100644 --- a/src/Token.php +++ b/src/Token.php @@ -55,13 +55,13 @@ public function decryptResponseCipher(string $cipher):UserData { throw new ResponseCipherDecryptionException(); } - $data = unserialize( + $data = @unserialize( $decrypted ); if($data === false) { throw new InvalidUserDataSerializationException(); } - return new UserData($data["uuid"], $data["email"]); + return new UserData($data->{"uuid"}, $data->{"email"}); } } \ No newline at end of file diff --git a/test/phpunit/AuthenticatorTest.php b/test/phpunit/AuthenticatorTest.php index 7ceab98..8c7f68f 100644 --- a/test/phpunit/AuthenticatorTest.php +++ b/test/phpunit/AuthenticatorTest.php @@ -355,7 +355,7 @@ public function testGetAdminUri() { ); $sut = $auth->getAdminUri(); self::assertEquals( - AdminUri::PATH_ACCOUNT, + AdminUri::PATH_ADMIN, $sut->getPath() ); } diff --git a/test/phpunit/ProviderUri/AdminUriTest.php b/test/phpunit/ProviderUri/AdminUriTest.php index fe9b3f1..8de8623 100644 --- a/test/phpunit/ProviderUri/AdminUriTest.php +++ b/test/phpunit/ProviderUri/AdminUriTest.php @@ -8,10 +8,10 @@ class AdminUriTest extends TestCase { public function testPathAccount() { $sut = new AdminUri( "example.com", - AdminUri::PATH_ACCOUNT + AdminUri::PATH_ADMIN ); self::assertEquals( - AdminUri::PATH_ACCOUNT, + AdminUri::PATH_ADMIN, $sut->getPath() ); } diff --git a/test/phpunit/TokenTest.php b/test/phpunit/TokenTest.php index a77a2e0..a5c0849 100644 --- a/test/phpunit/TokenTest.php +++ b/test/phpunit/TokenTest.php @@ -65,7 +65,8 @@ public function testDecryptResponseCipherBadJson() { } public function testDecryptResponseCipher() { - $key = uniqid("test-key-"); + $clientKey = uniqid("test-key-"); +// SecretIv is stored in the client application's session only. $secretIv = self::createMock(InitVector::class); $secretIv->method("getBytes") ->willReturn(str_repeat("0", 16)); @@ -75,22 +76,20 @@ public function testDecryptResponseCipher() { $uuid = "aabb-ccdd-eeff"; $email = "user@example.com"; - $json = << $uuid, + "email" => $email, + ]); $cipher = openssl_encrypt( - $json, + $serialized, Token::ENCRYPTION_METHOD, - implode("|", [$key, $secretIv->getBytes()]), + $clientKey, 0, - $iv->getBytes() + $secretIv->getBytes() ); $cipher = base64_encode($cipher); - $sut = new Token($key, $secretIv, $iv); + $sut = new Token($clientKey, $secretIv, $iv); $userData = $sut->decryptResponseCipher($cipher); self::assertInstanceOf(UserData::class, $userData); self::assertEquals($uuid, $userData->getUuid()); From a7148865dde1a6fbd19d7f32066647c9de62f95d Mon Sep 17 00:00:00 2001 From: Greg Bowler Date: Tue, 28 Apr 2020 22:29:59 +0100 Subject: [PATCH 13/20] Stub profile URI --- src/ProviderUri/LogoutUri.php | 18 ------------------ src/ProviderUri/ProfileUri.php | 6 ++++++ test/phpunit/AuthenticatorTest.php | 23 ----------------------- 3 files changed, 6 insertions(+), 41 deletions(-) delete mode 100644 src/ProviderUri/LogoutUri.php create mode 100644 src/ProviderUri/ProfileUri.php diff --git a/src/ProviderUri/LogoutUri.php b/src/ProviderUri/LogoutUri.php deleted file mode 100644 index 806dd52..0000000 --- a/src/ProviderUri/LogoutUri.php +++ /dev/null @@ -1,18 +0,0 @@ -normaliseBaseUri($baseRemoteUri); - parent::__construct($baseRemoteUri); - $this->path = self::PATH_LOGOUT; - $this->query = http_build_query([ - "returnTo" => $returnToUri, - ]); - } -} \ No newline at end of file diff --git a/src/ProviderUri/ProfileUri.php b/src/ProviderUri/ProfileUri.php new file mode 100644 index 0000000..da09f32 --- /dev/null +++ b/src/ProviderUri/ProfileUri.php @@ -0,0 +1,6 @@ +expects(self::once()) - ->method("redirect") - ->with(self::callback(fn(UriInterface $uri) => - $uri->getHost() === AuthUri::DEFAULT_BASE_REMOTE_URI - && $uri->getPath() === LogoutUri::PATH_LOGOUT - && $uri->getQuery() === "returnTo=" . urlencode($currentPath) - )); - - $sut = new Authenticator( - "test-key", - $currentPath, - AuthUri::DEFAULT_BASE_REMOTE_URI, - null, - $redirectHandler - ); - $sut->logout(); - } - public function testLoginRedirects() { $_SESSION = []; From 81ecf542c6ae1557ed498b7eba803673446f3e94 Mon Sep 17 00:00:00 2001 From: Greg Bowler Date: Tue, 28 Apr 2020 22:30:33 +0100 Subject: [PATCH 14/20] Load user's data fields --- src/Authenticator.php | 47 ++++++++++++++++++++++++++++++++----------- src/Token.php | 8 +++++++- src/UserData.php | 12 ++++++++++- 3 files changed, 53 insertions(+), 14 deletions(-) diff --git a/src/Authenticator.php b/src/Authenticator.php index c029f99..3149825 100644 --- a/src/Authenticator.php +++ b/src/Authenticator.php @@ -1,6 +1,7 @@ isLoggedIn()) { return; } @@ -71,17 +77,18 @@ public function login(Token $token = null):void { $this->sessionData = new SessionData($token); $this->session->set(self::SESSION_KEY, $this->sessionData); - $this->redirectHandler->redirect($this->getAuthUri($token)); + $this->redirectHandler->redirect( + $this->getAuthUri($token, $loginType) + ); } public function logout():void { // TODO: Should the logout redirect the user agent to the redirectPath? $this->session->remove(self::SESSION_KEY); - $this->redirectHandler->redirect($this->getLogoutUri()); } - public function adminLogin():void { -// TODO: Implement! + public function adminLogin(Token $token = null):void { + $this->login($token, self::LOGIN_TYPE_ADMIN); } public function getUuid():string { @@ -94,7 +101,22 @@ public function getEmail():string { return $userData->getEmail(); } - public function getAuthUri(Token $token):AuthUri { + public function getField(string $name):?string { + $userData = $this->sessionData->getUserData(); + return $userData->getField($name); + } + + public function getAuthUri( + Token $token, + string $loginType = self::LOGIN_TYPE_DEFAULT + ):AbstractProviderUri { + if($loginType === self::LOGIN_TYPE_ADMIN) { + return new AdminUri( + $this->currentUriPath, + $this->authwaveHost + ); + } + return new AuthUri( $token, $this->currentUriPath, @@ -111,12 +133,13 @@ public function getAdminUri( ); } - public function getLogoutUri(string $returnToPath = null):UriInterface { - if(is_null($returnToPath)) { - $returnToPath = (new Uri($this->currentUriPath))->getPath(); - } - - return new LogoutUri($this->authwaveHost, $returnToPath); + public function getProfileUri( + string $path = ProfileUri::PATH_PROFILE + ):UriInterface { + return new ProfileUri( + $this->authwaveHost, + $path + ); } private function completeAuth():void { diff --git a/src/Token.php b/src/Token.php index a45de09..047561b 100644 --- a/src/Token.php +++ b/src/Token.php @@ -1,6 +1,8 @@ {"uuid"}, $data->{"email"}); + return new UserData( + $data->{"uuid"}, + $data->{"email"}, + $data->{"fields"} ?? new StdClass() + ); } } \ No newline at end of file diff --git a/src/UserData.php b/src/UserData.php index 7f5b0a4..6d741c6 100644 --- a/src/UserData.php +++ b/src/UserData.php @@ -4,10 +4,16 @@ class UserData { private string $uuid; private string $email; + private object $fields; - public function __construct(string $uuid, string $email) { + public function __construct( + string $uuid, + string $email, + object $fields + ) { $this->uuid = $uuid; $this->email = $email; + $this->fields = $fields; } public function getUuid():string { @@ -17,4 +23,8 @@ public function getUuid():string { public function getEmail():string { return $this->email; } + + public function getField(string $name):?string { + return $this->fields->{$name} ?? null; + } } \ No newline at end of file From d3bea6178a80727cc73569dd5f59734e416c5019 Mon Sep 17 00:00:00 2001 From: Greg Bowler Date: Sun, 10 May 2020 18:01:18 +0100 Subject: [PATCH 15/20] Handle user data fields --- src/Authenticator.php | 48 ++++++++++++++++++++++-------- src/ProviderUri/LogoutUri.php | 18 ----------- src/ProviderUri/ProfileUri.php | 6 ++++ src/Token.php | 6 +++- src/UserData.php | 12 +++++++- test/phpunit/AuthenticatorTest.php | 23 -------------- test/phpunit/TokenTest.php | 3 ++ 7 files changed, 60 insertions(+), 56 deletions(-) delete mode 100644 src/ProviderUri/LogoutUri.php create mode 100644 src/ProviderUri/ProfileUri.php diff --git a/src/Authenticator.php b/src/Authenticator.php index c029f99..1f178d5 100644 --- a/src/Authenticator.php +++ b/src/Authenticator.php @@ -1,9 +1,9 @@ isLoggedIn()) { return; } @@ -71,17 +76,18 @@ public function login(Token $token = null):void { $this->sessionData = new SessionData($token); $this->session->set(self::SESSION_KEY, $this->sessionData); - $this->redirectHandler->redirect($this->getAuthUri($token)); + $this->redirectHandler->redirect( + $this->getAuthUri($token, $loginType) + ); } public function logout():void { // TODO: Should the logout redirect the user agent to the redirectPath? $this->session->remove(self::SESSION_KEY); - $this->redirectHandler->redirect($this->getLogoutUri()); } - public function adminLogin():void { -// TODO: Implement! + public function adminLogin(Token $token = null):void { + $this->login($token, self::LOGIN_TYPE_ADMIN); } public function getUuid():string { @@ -94,7 +100,22 @@ public function getEmail():string { return $userData->getEmail(); } - public function getAuthUri(Token $token):AuthUri { + public function getField(string $name):?string { + $userData = $this->sessionData->getUserData(); + return $userData->getField($name); + } + + public function getAuthUri( + Token $token, + string $loginType = self::LOGIN_TYPE_DEFAULT + ):AbstractProviderUri { + if($loginType === self::LOGIN_TYPE_ADMIN) { + return new AdminUri( + $this->currentUriPath, + $this->authwaveHost + ); + } + return new AuthUri( $token, $this->currentUriPath, @@ -111,12 +132,13 @@ public function getAdminUri( ); } - public function getLogoutUri(string $returnToPath = null):UriInterface { - if(is_null($returnToPath)) { - $returnToPath = (new Uri($this->currentUriPath))->getPath(); - } - - return new LogoutUri($this->authwaveHost, $returnToPath); + public function getProfileUri( + string $path = ProfileUri::PATH_PROFILE + ):UriInterface { + return new ProfileUri( + $this->authwaveHost, + $path + ); } private function completeAuth():void { diff --git a/src/ProviderUri/LogoutUri.php b/src/ProviderUri/LogoutUri.php deleted file mode 100644 index 806dd52..0000000 --- a/src/ProviderUri/LogoutUri.php +++ /dev/null @@ -1,18 +0,0 @@ -normaliseBaseUri($baseRemoteUri); - parent::__construct($baseRemoteUri); - $this->path = self::PATH_LOGOUT; - $this->query = http_build_query([ - "returnTo" => $returnToUri, - ]); - } -} \ No newline at end of file diff --git a/src/ProviderUri/ProfileUri.php b/src/ProviderUri/ProfileUri.php new file mode 100644 index 0000000..da09f32 --- /dev/null +++ b/src/ProviderUri/ProfileUri.php @@ -0,0 +1,6 @@ +{"uuid"}, $data->{"email"}); + return new UserData( + $data->{"uuid"}, + $data->{"email"}, + $data->{"fields"} + ); } } \ No newline at end of file diff --git a/src/UserData.php b/src/UserData.php index 7f5b0a4..6d741c6 100644 --- a/src/UserData.php +++ b/src/UserData.php @@ -4,10 +4,16 @@ class UserData { private string $uuid; private string $email; + private object $fields; - public function __construct(string $uuid, string $email) { + public function __construct( + string $uuid, + string $email, + object $fields + ) { $this->uuid = $uuid; $this->email = $email; + $this->fields = $fields; } public function getUuid():string { @@ -17,4 +23,8 @@ public function getUuid():string { public function getEmail():string { return $this->email; } + + public function getField(string $name):?string { + return $this->fields->{$name} ?? null; + } } \ No newline at end of file diff --git a/test/phpunit/AuthenticatorTest.php b/test/phpunit/AuthenticatorTest.php index 8c7f68f..5db819d 100644 --- a/test/phpunit/AuthenticatorTest.php +++ b/test/phpunit/AuthenticatorTest.php @@ -82,29 +82,6 @@ public function testLogoutClearsSession() { self::assertEmpty($_SESSION); } - public function testLogoutRedirectsToCurrentPath() { - $_SESSION = []; - $currentPath = "/current/example/path"; - - $redirectHandler = self::createMock(RedirectHandler::class); - $redirectHandler->expects(self::once()) - ->method("redirect") - ->with(self::callback(fn(UriInterface $uri) => - $uri->getHost() === AuthUri::DEFAULT_BASE_REMOTE_URI - && $uri->getPath() === LogoutUri::PATH_LOGOUT - && $uri->getQuery() === "returnTo=" . urlencode($currentPath) - )); - - $sut = new Authenticator( - "test-key", - $currentPath, - AuthUri::DEFAULT_BASE_REMOTE_URI, - null, - $redirectHandler - ); - $sut->logout(); - } - public function testLoginRedirects() { $_SESSION = []; diff --git a/test/phpunit/TokenTest.php b/test/phpunit/TokenTest.php index a5c0849..b81e46a 100644 --- a/test/phpunit/TokenTest.php +++ b/test/phpunit/TokenTest.php @@ -79,6 +79,9 @@ public function testDecryptResponseCipher() { $serialized = serialize((object)[ "uuid" => $uuid, "email" => $email, + "fields" => (object)[ + "example1" => "value1", + ] ]); $cipher = openssl_encrypt( From 28676c45c049f7439961a359c107028777dbe9a7 Mon Sep 17 00:00:00 2001 From: Greg Bowler Date: Sun, 10 May 2020 18:31:37 +0100 Subject: [PATCH 16/20] WIP work with URI classes --- src/Authenticator.php | 19 +++++-------------- src/ProviderUri/AdminUri.php | 8 ++------ src/ProviderUri/ProfileUri.php | 8 +++++++- 3 files changed, 14 insertions(+), 21 deletions(-) diff --git a/src/Authenticator.php b/src/Authenticator.php index 1f178d5..3e199c1 100644 --- a/src/Authenticator.php +++ b/src/Authenticator.php @@ -4,6 +4,7 @@ use Authwave\ProviderUri\AbstractProviderUri; use Authwave\ProviderUri\AdminUri; use Authwave\ProviderUri\AuthUri; +use Authwave\ProviderUri\ProfileUri; use Gt\Http\Uri; use Gt\Session\SessionContainer; use Psr\Http\Message\UriInterface; @@ -123,22 +124,12 @@ public function getAuthUri( ); } - public function getAdminUri( - string $path = AdminUri::PATH_ADMIN - ):UriInterface { - return new AdminUri( - $this->authwaveHost, - $path - ); + public function getAdminUri():UriInterface { + return new AdminUri($this->authwaveHost); } - public function getProfileUri( - string $path = ProfileUri::PATH_PROFILE - ):UriInterface { - return new ProfileUri( - $this->authwaveHost, - $path - ); + public function getProfileUri():UriInterface { + return new ProfileUri($this->authwaveHost); } private function completeAuth():void { diff --git a/src/ProviderUri/AdminUri.php b/src/ProviderUri/AdminUri.php index 31845ea..f8b1ae3 100644 --- a/src/ProviderUri/AdminUri.php +++ b/src/ProviderUri/AdminUri.php @@ -2,15 +2,11 @@ namespace Authwave\ProviderUri; class AdminUri extends AbstractProviderUri { - const PATH_ADMIN = "/admin"; - const PATH_SETTINGS = "/settings"; - public function __construct( - string $baseRemoteUri, - string $path + string $baseRemoteUri ) { $baseRemoteUri = $this->normaliseBaseUri($baseRemoteUri); parent::__construct($baseRemoteUri); - $this->path = $path; + $this->path = "/admin"; } } \ No newline at end of file diff --git a/src/ProviderUri/ProfileUri.php b/src/ProviderUri/ProfileUri.php index da09f32..0be2dde 100644 --- a/src/ProviderUri/ProfileUri.php +++ b/src/ProviderUri/ProfileUri.php @@ -2,5 +2,11 @@ namespace Authwave\ProviderUri; class ProfileUri extends AbstractProviderUri { - + public function __construct( + string $baseRemoteUri + ) { + $baseRemoteUri = $this->normaliseBaseUri($baseRemoteUri); + parent::__construct($baseRemoteUri); + $this->path = "/profile"; + } } \ No newline at end of file From cec4846e69ca4f5ee4721db022a44d81f16cb17c Mon Sep 17 00:00:00 2001 From: Greg Bowler Date: Sun, 10 May 2020 21:35:07 +0100 Subject: [PATCH 17/20] Remove custom admin uri --- src/Authenticator.php | 34 +++++++---------------- test/phpunit/AuthenticatorTest.php | 17 +----------- test/phpunit/ProviderUri/AdminUriTest.php | 27 ++---------------- 3 files changed, 13 insertions(+), 65 deletions(-) diff --git a/src/Authenticator.php b/src/Authenticator.php index 3e199c1..8e227d2 100644 --- a/src/Authenticator.php +++ b/src/Authenticator.php @@ -38,12 +38,14 @@ public function __construct( // need to store it to the current session at all? $session->set(self::SESSION_KEY, new SessionData()); } + /** @var SessionData $sessionData*/ + $sessionData = $session->get(self::SESSION_KEY); $this->clientKey = $clientKey; $this->currentUriPath = $currentUriPath; $this->authwaveHost = $authwaveHost; $this->session = $session; - $this->sessionData = $session->get(self::SESSION_KEY); + $this->sessionData = $sessionData; $this->redirectHandler = $redirectHandler ?? new RedirectHandler(); $this->completeAuth(); @@ -62,10 +64,7 @@ public function isLoggedIn():bool { return isset($userData); } - public function login( - Token $token = null, - string $loginType = self::LOGIN_TYPE_DEFAULT - ):void { + public function login(Token $token = null):void { if($this->isLoggedIn()) { return; } @@ -77,18 +76,15 @@ public function login( $this->sessionData = new SessionData($token); $this->session->set(self::SESSION_KEY, $this->sessionData); - $this->redirectHandler->redirect( - $this->getAuthUri($token, $loginType) - ); + $this->redirectHandler->redirect($this->getAuthUri($token)); } - public function logout():void { -// TODO: Should the logout redirect the user agent to the redirectPath? + public function logout(string $redirectToPath = "/"):void { $this->session->remove(self::SESSION_KEY); - } - public function adminLogin(Token $token = null):void { - $this->login($token, self::LOGIN_TYPE_ADMIN); + $uri = (new Uri()) + ->withPath($redirectToPath); + $this->redirectHandler->redirect($uri); } public function getUuid():string { @@ -106,17 +102,7 @@ public function getField(string $name):?string { return $userData->getField($name); } - public function getAuthUri( - Token $token, - string $loginType = self::LOGIN_TYPE_DEFAULT - ):AbstractProviderUri { - if($loginType === self::LOGIN_TYPE_ADMIN) { - return new AdminUri( - $this->currentUriPath, - $this->authwaveHost - ); - } - + public function getAuthUri(Token $token):AbstractProviderUri { return new AuthUri( $token, $this->currentUriPath, diff --git a/test/phpunit/AuthenticatorTest.php b/test/phpunit/AuthenticatorTest.php index 5db819d..8114978 100644 --- a/test/phpunit/AuthenticatorTest.php +++ b/test/phpunit/AuthenticatorTest.php @@ -332,22 +332,7 @@ public function testGetAdminUri() { ); $sut = $auth->getAdminUri(); self::assertEquals( - AdminUri::PATH_ADMIN, - $sut->getPath() - ); - } - - public function testGetAdminUriCustom() { - $_SESSION = []; - $auth = new Authenticator( - "test-key", - "/example-path", - AuthUri::DEFAULT_BASE_REMOTE_URI - ); - $path = "/custom-path"; - $sut = $auth->getAdminUri($path); - self::assertEquals( - $path, + "/admin", $sut->getPath() ); } diff --git a/test/phpunit/ProviderUri/AdminUriTest.php b/test/phpunit/ProviderUri/AdminUriTest.php index 8de8623..e2e5db7 100644 --- a/test/phpunit/ProviderUri/AdminUriTest.php +++ b/test/phpunit/ProviderUri/AdminUriTest.php @@ -6,33 +6,10 @@ class AdminUriTest extends TestCase { public function testPathAccount() { - $sut = new AdminUri( - "example.com", - AdminUri::PATH_ADMIN - ); - self::assertEquals( - AdminUri::PATH_ADMIN, - $sut->getPath() - ); - } - - public function testPathSettings() { - $sut = new AdminUri( - "example.com", - AdminUri::PATH_SETTINGS - ); + $sut = new AdminUri("example.com"); self::assertEquals( - AdminUri::PATH_SETTINGS, + "/admin", $sut->getPath() ); } - - public function testPathCustom() { - $path = "/custom/path"; - $sut = new AdminUri( - "example.com", - $path - ); - self::assertEquals($path, $sut->getPath()); - } } \ No newline at end of file From 817f1f6da59eb4830d67ce656193c73be7518a19 Mon Sep 17 00:00:00 2001 From: Greg Bowler Date: Sat, 16 May 2020 19:01:31 +0100 Subject: [PATCH 18/20] Update provider uri and cipher to allow user session to be maintained on provider --- src/Authenticator.php | 44 ++++++++++++++----- src/ProviderUri/AbstractProviderUri.php | 16 +++++++ src/ProviderUri/{AuthUri.php => LoginUri.php} | 19 ++++---- src/ProviderUri/ProfileUri.php | 6 +++ src/Token.php | 21 ++++++--- test/phpunit/AuthenticatorTest.php | 24 +++++----- .../ProviderUri/AbstractProviderUriTest.php | 14 +++--- .../{AuthUriTest.php => LoginUriTest.php} | 12 ++--- 8 files changed, 103 insertions(+), 53 deletions(-) rename src/ProviderUri/{AuthUri.php => LoginUri.php} (63%) rename test/phpunit/ProviderUri/{AuthUriTest.php => LoginUriTest.php} (77%) diff --git a/src/Authenticator.php b/src/Authenticator.php index 8e227d2..7eb6390 100644 --- a/src/Authenticator.php +++ b/src/Authenticator.php @@ -3,7 +3,7 @@ use Authwave\ProviderUri\AbstractProviderUri; use Authwave\ProviderUri\AdminUri; -use Authwave\ProviderUri\AuthUri; +use Authwave\ProviderUri\LoginUri; use Authwave\ProviderUri\ProfileUri; use Gt\Http\Uri; use Gt\Session\SessionContainer; @@ -76,15 +76,18 @@ public function login(Token $token = null):void { $this->sessionData = new SessionData($token); $this->session->set(self::SESSION_KEY, $this->sessionData); - $this->redirectHandler->redirect($this->getAuthUri($token)); + $this->redirectHandler->redirect($this->getLoginUri($token)); } - public function logout(string $redirectToPath = "/"):void { - $this->session->remove(self::SESSION_KEY); + public function logout(Token $token = null):void { + if(is_null($token)) { + $token = new Token($this->clientKey); + } + + $this->sessionData = new SessionData($token); + $this->session->set(self::SESSION_KEY, $this->sessionData); - $uri = (new Uri()) - ->withPath($redirectToPath); - $this->redirectHandler->redirect($uri); + $this->redirectHandler->redirect($this->getLogoutUri($token)); } public function getUuid():string { @@ -102,8 +105,16 @@ public function getField(string $name):?string { return $userData->getField($name); } - public function getAuthUri(Token $token):AbstractProviderUri { - return new AuthUri( + public function getLoginUri(Token $token):AbstractProviderUri { + return new LoginUri( + $token, + $this->currentUriPath, + $this->authwaveHost + ); + } + + private function getLogoutUri(Token $token):AbstractProviderUri { + return new LogoutUri( $token, $this->currentUriPath, $this->authwaveHost @@ -114,8 +125,17 @@ public function getAdminUri():UriInterface { return new AdminUri($this->authwaveHost); } - public function getProfileUri():UriInterface { - return new ProfileUri($this->authwaveHost); + public function getProfileUri(Token $token = null):UriInterface { + if(is_null($token)) { + $token = new Token($this->clientKey); + } + + return new ProfileUri( + $token, + $this->getUuid(), + $this->currentUriPath, + $this->authwaveHost + ); } private function completeAuth():void { @@ -132,6 +152,8 @@ private function completeAuth():void { new SessionData($token, $userData) ); + setcookie("authwave-trackshift", "test", 0, "/", "localhost"); + $this->redirectHandler->redirect( (new Uri($this->currentUriPath)) ->withoutQueryValue(self::RESPONSE_QUERY_PARAMETER) diff --git a/src/ProviderUri/AbstractProviderUri.php b/src/ProviderUri/AbstractProviderUri.php index 35f0dda..747e795 100644 --- a/src/ProviderUri/AbstractProviderUri.php +++ b/src/ProviderUri/AbstractProviderUri.php @@ -2,10 +2,14 @@ namespace Authwave\ProviderUri; use Authwave\InsecureProtocolException; +use Authwave\Token; use Gt\Http\Uri; abstract class AbstractProviderUri extends Uri { const DEFAULT_BASE_REMOTE_URI = "login.authwave.com"; + const QUERY_STRING_CIPHER = "c"; + const QUERY_STRING_INIT_VECTOR = "i"; + const QUERY_STRING_CURRENT_PATH = "p"; protected function normaliseBaseUri(string $baseUri):Uri { $scheme = parse_url($baseUri, PHP_URL_SCHEME) @@ -27,4 +31,16 @@ protected function normaliseBaseUri(string $baseUri):Uri { return $uri; } + + protected function buildQuery( + Token $token, + string $currentPath, + string $data = null + ):string { + return http_build_query([ + self::QUERY_STRING_CIPHER => (string)$token->generateRequestCipher($data), + self::QUERY_STRING_INIT_VECTOR => (string)$token->getIv(), + self::QUERY_STRING_CURRENT_PATH => bin2hex($currentPath), + ]); + } } \ No newline at end of file diff --git a/src/ProviderUri/AuthUri.php b/src/ProviderUri/LoginUri.php similarity index 63% rename from src/ProviderUri/AuthUri.php rename to src/ProviderUri/LoginUri.php index dca18c5..a24a6ba 100644 --- a/src/ProviderUri/AuthUri.php +++ b/src/ProviderUri/LoginUri.php @@ -4,11 +4,13 @@ use Authwave\Token; use Gt\Http\Uri; -class AuthUri extends AbstractProviderUri { - const QUERY_STRING_CIPHER = "cipher"; - const QUERY_STRING_INIT_VECTOR = "iv"; - const QUERY_STRING_CURRENT_PATH = "path"; - +/** + * The AuthUri class represents the Uri used to redirect the user agent to the + * Authwave provider in order to initiate authentication. A Token is used to + * pass the secret IV to the provider, encrypted with the API key. The secret + * IV is only ever stored in the client's session, and is unique to the session. + */ +class LoginUri extends AbstractProviderUri { /** * @param Token $token This must be the same instance of the Token when * creating Authenticator for the first time as it is when checking the @@ -25,11 +27,6 @@ public function __construct( ) { $baseRemoteUri = $this->normaliseBaseUri($baseRemoteUri); parent::__construct($baseRemoteUri); - - $this->query = http_build_query([ - self::QUERY_STRING_CIPHER => (string)$token->generateRequestCipher(), - self::QUERY_STRING_INIT_VECTOR => (string)$token->getIv(), - self::QUERY_STRING_CURRENT_PATH => $currentPath, - ]); + $this->query = $this->buildQuery($token, $currentPath); } } \ No newline at end of file diff --git a/src/ProviderUri/ProfileUri.php b/src/ProviderUri/ProfileUri.php index 0be2dde..6ca73f7 100644 --- a/src/ProviderUri/ProfileUri.php +++ b/src/ProviderUri/ProfileUri.php @@ -1,12 +1,18 @@ normaliseBaseUri($baseRemoteUri); parent::__construct($baseRemoteUri); $this->path = "/profile"; + $this->query = $this->buildQuery($token, $uuid); } } \ No newline at end of file diff --git a/src/Token.php b/src/Token.php index 047561b..1a166f0 100644 --- a/src/Token.php +++ b/src/Token.php @@ -24,13 +24,22 @@ public function getIv():InitVector { return $this->iv; } -// The request cipher is sent to the remote provider in the querystring. It -// consists of the secret IV, encrypted with the client key. The remote provider -// will decrypt the secret and use it as the key when encrypting the response -// cipher, which will be sent back to the client application in the querystring. - public function generateRequestCipher():string { +/** + * The request cipher is sent to the remote provider in the querystring. It + * consists of the token's secret IV, encrypted with the client key, along with + * an optional message. The secret IV is required for two-way encryption. The + * remote provider will decrypt the secret and use it as the key if encrypting a + * response cipher, which will be sent back to the client application in the + * querystring. + */ + public function generateRequestCipher(string $message = null):string { + $data = $this->secretIv; + if($message) { + $data .= "|" . $message; + } + $rawCipher = openssl_encrypt( - $this->secretIv, + $data, self::ENCRYPTION_METHOD, $this->key, 0, diff --git a/test/phpunit/AuthenticatorTest.php b/test/phpunit/AuthenticatorTest.php index 8114978..9c0e329 100644 --- a/test/phpunit/AuthenticatorTest.php +++ b/test/phpunit/AuthenticatorTest.php @@ -5,7 +5,7 @@ use Authwave\InitVector; use Authwave\NotLoggedInException; use Authwave\ProviderUri\AdminUri; -use Authwave\ProviderUri\AuthUri; +use Authwave\ProviderUri\LoginUri; use Authwave\ProviderUri\LogoutUri; use Authwave\RedirectHandler; use Authwave\SessionData; @@ -74,7 +74,7 @@ public function testLogoutClearsSession() { $sut = new Authenticator( "test-key", "/", - AuthUri::DEFAULT_BASE_REMOTE_URI, + LoginUri::DEFAULT_BASE_REMOTE_URI, null, $redirectHandler ); @@ -89,13 +89,13 @@ public function testLoginRedirects() { $redirectHandler->expects(self::once()) ->method("redirect") ->with(self::callback(fn(UriInterface $uri) => - $uri->getHost() === AuthUri::DEFAULT_BASE_REMOTE_URI + $uri->getHost() === LoginUri::DEFAULT_BASE_REMOTE_URI )); $sut = new Authenticator( "test-key", "/", - AuthUri::DEFAULT_BASE_REMOTE_URI, + LoginUri::DEFAULT_BASE_REMOTE_URI, null, $redirectHandler ); @@ -144,9 +144,9 @@ public function testLoginRedirectsWithCorrectQueryString() { ->willReturn($iv); $expectedQueryParts = [ - AuthUri::QUERY_STRING_CIPHER => $cipher, - AuthUri::QUERY_STRING_INIT_VECTOR => $ivString, - AuthUri::QUERY_STRING_CURRENT_PATH => $currentPath, + LoginUri::QUERY_STRING_CIPHER => $cipher, + LoginUri::QUERY_STRING_INIT_VECTOR => $ivString, + LoginUri::QUERY_STRING_CURRENT_PATH => $currentPath, ]; $expectedQuery = http_build_query($expectedQueryParts); @@ -160,7 +160,7 @@ public function testLoginRedirectsWithCorrectQueryString() { $sut = new Authenticator( $key, $currentPath, - AuthUri::DEFAULT_BASE_REMOTE_URI, + LoginUri::DEFAULT_BASE_REMOTE_URI, null, $redirectHandler ); @@ -180,7 +180,7 @@ public function testLoginDoesNothingWhenAlreadyLoggedIn() { $sut = new Authenticator( "test-key", "/", - AuthUri::DEFAULT_BASE_REMOTE_URI, + LoginUri::DEFAULT_BASE_REMOTE_URI, null, $redirectHandler ); @@ -290,7 +290,7 @@ public function testCompleteAuth() { new Authenticator( "test-key", $currentUri, - AuthUri::DEFAULT_BASE_REMOTE_URI, + LoginUri::DEFAULT_BASE_REMOTE_URI, null, $redirectHandler ); @@ -317,7 +317,7 @@ public function testCompleteAuthNotAffectedByQueryString() { new Authenticator( "test-key", "/example-path?filter=something", - AuthUri::DEFAULT_BASE_REMOTE_URI, + LoginUri::DEFAULT_BASE_REMOTE_URI, null, $redirectHandler ); @@ -328,7 +328,7 @@ public function testGetAdminUri() { $auth = new Authenticator( "test-key", "/example-path", - AuthUri::DEFAULT_BASE_REMOTE_URI + LoginUri::DEFAULT_BASE_REMOTE_URI ); $sut = $auth->getAdminUri(); self::assertEquals( diff --git a/test/phpunit/ProviderUri/AbstractProviderUriTest.php b/test/phpunit/ProviderUri/AbstractProviderUriTest.php index 88ce4c0..e7deb34 100644 --- a/test/phpunit/ProviderUri/AbstractProviderUriTest.php +++ b/test/phpunit/ProviderUri/AbstractProviderUriTest.php @@ -3,7 +3,7 @@ use Authwave\InitVector; use Authwave\InsecureProtocolException; -use Authwave\ProviderUri\AuthUri; +use Authwave\ProviderUri\LoginUri; use Authwave\Token; use PHPUnit\Framework\TestCase; use Psr\Http\Message\UriInterface; @@ -15,7 +15,7 @@ public function testAuthUriHttps() { ->willReturn("https://example.com"); $token = self::createMock(Token::class); - $sut = new AuthUri( + $sut = new LoginUri( $token, "", $baseUri @@ -35,7 +35,7 @@ public function testAuthUriWithNonStandardPort() { ->willReturn("http://localhost:8081"); $token = self::createMock(Token::class); - $sut = new AuthUri( + $sut = new LoginUri( $token, "", $baseUri @@ -54,7 +54,7 @@ public function testAuthUriWithNonStandardPort() { // But it should still default to HTTPS on localhost. public function testGetAuthUriHostnameLocalhostHttpsByDefault() { $token = self::createMock(Token::class); - $sut = new AuthUri( + $sut = new LoginUri( $token, "/", "localhost" @@ -69,7 +69,7 @@ public function testGetAuthUriHostnameLocalhostHttpsByDefault() { // We should be able to set the scheme to HTTP for localhost hostname only. public function testGetAuthUriHostnameLocalhostHttpAllowed() { $token = self::createMock(Token::class); - $sut = new AuthUri( + $sut = new LoginUri( $token, "/", "http://localhost" @@ -84,7 +84,7 @@ public function testGetAuthUriHostnameLocalhostHttpAllowed() { public function testGetAuthUriHostnameNotLocalhostHttpNotAllowed() { $token = self::createMock(Token::class); self::expectException(InsecureProtocolException::class); - new AuthUri( + new LoginUri( $token, "/", "http://localhost.com" @@ -98,7 +98,7 @@ public function testAuthUriHttpsInferred() { // Note on the line above, no scheme is passed in - we must assume https. $token = self::createMock(Token::class); - $sut = new AuthUri( + $sut = new LoginUri( $token, "/", $baseUri); diff --git a/test/phpunit/ProviderUri/AuthUriTest.php b/test/phpunit/ProviderUri/LoginUriTest.php similarity index 77% rename from test/phpunit/ProviderUri/AuthUriTest.php rename to test/phpunit/ProviderUri/LoginUriTest.php index deac72e..4b59ba2 100644 --- a/test/phpunit/ProviderUri/AuthUriTest.php +++ b/test/phpunit/ProviderUri/LoginUriTest.php @@ -2,12 +2,12 @@ namespace Authwave\Test\ProviderUri; use Authwave\InitVector; -use Authwave\ProviderUri\AuthUri; +use Authwave\ProviderUri\LoginUri; use Authwave\Token; use PHPUnit\Framework\TestCase; use Psr\Http\Message\UriInterface; -class AuthUriTest extends TestCase { +class LoginUriTest extends TestCase { public function testQueryString() { $mockCipherValue = str_repeat("f", 16); $mockIvValue = str_repeat("0", 16); @@ -23,7 +23,7 @@ public function testQueryString() { ->willReturn($iv); $returnPath = "/examplePage"; - $sut = new AuthUri( + $sut = new LoginUri( $token, $returnPath, $baseUri @@ -32,17 +32,17 @@ public function testQueryString() { self::assertEquals( $mockCipherValue, - $queryParts[AuthUri::QUERY_STRING_CIPHER], + $queryParts[LoginUri::QUERY_STRING_CIPHER], ); self::assertEquals( $mockIvValue, - $queryParts[AuthUri::QUERY_STRING_INIT_VECTOR] + $queryParts[LoginUri::QUERY_STRING_INIT_VECTOR] ); self::assertEquals( $returnPath, - $queryParts[AuthUri::QUERY_STRING_CURRENT_PATH] + $queryParts[LoginUri::QUERY_STRING_CURRENT_PATH] ); } } \ No newline at end of file From be5d06a3768253effc35885ce2d54b3818584bc3 Mon Sep 17 00:00:00 2001 From: Greg Bowler Date: Sat, 16 May 2020 22:58:04 +0100 Subject: [PATCH 19/20] Refactor UserData to extend AbstractResponseData --- src/Authenticator.php | 11 +++++------ src/ProviderUri/LogoutUri.php | 18 ++++++++++++++++++ src/ResponseData/AbstractResponseData.php | 14 ++++++++++++++ src/{ => ResponseData}/UserData.php | 9 ++++++--- src/SessionData.php | 15 +++++++++------ src/Token.php | 6 ++++-- test/phpunit/AuthenticatorTest.php | 20 ++++++++++++-------- test/phpunit/ProviderUri/LoginUriTest.php | 2 +- test/phpunit/SessionDataTest.php | 6 +++--- test/phpunit/TokenTest.php | 2 +- 10 files changed, 73 insertions(+), 30 deletions(-) create mode 100644 src/ProviderUri/LogoutUri.php create mode 100644 src/ResponseData/AbstractResponseData.php rename src/{ => ResponseData}/UserData.php (74%) diff --git a/src/Authenticator.php b/src/Authenticator.php index 7eb6390..ab1f471 100644 --- a/src/Authenticator.php +++ b/src/Authenticator.php @@ -4,6 +4,7 @@ use Authwave\ProviderUri\AbstractProviderUri; use Authwave\ProviderUri\AdminUri; use Authwave\ProviderUri\LoginUri; +use Authwave\ProviderUri\LogoutUri; use Authwave\ProviderUri\ProfileUri; use Gt\Http\Uri; use Gt\Session\SessionContainer; @@ -55,7 +56,7 @@ public function isLoggedIn():bool { $userData = null; try { - $userData = $this->sessionData->getUserData(); + $userData = $this->sessionData->getData(); } catch(NotLoggedInException $exception) { return false; @@ -91,17 +92,17 @@ public function logout(Token $token = null):void { } public function getUuid():string { - $userData = $this->sessionData->getUserData(); + $userData = $this->sessionData->getData(); return $userData->getUuid(); } public function getEmail():string { - $userData = $this->sessionData->getUserData(); + $userData = $this->sessionData->getData(); return $userData->getEmail(); } public function getField(string $name):?string { - $userData = $this->sessionData->getUserData(); + $userData = $this->sessionData->getData(); return $userData->getField($name); } @@ -152,8 +153,6 @@ private function completeAuth():void { new SessionData($token, $userData) ); - setcookie("authwave-trackshift", "test", 0, "/", "localhost"); - $this->redirectHandler->redirect( (new Uri($this->currentUriPath)) ->withoutQueryValue(self::RESPONSE_QUERY_PARAMETER) diff --git a/src/ProviderUri/LogoutUri.php b/src/ProviderUri/LogoutUri.php new file mode 100644 index 0000000..a64f1cd --- /dev/null +++ b/src/ProviderUri/LogoutUri.php @@ -0,0 +1,18 @@ +normaliseBaseUri($baseRemoteUri); + $baseRemoteUri = $baseRemoteUri->withPath("/logout"); + + parent::__construct($baseRemoteUri); + $this->query = $this->buildQuery($token, $currentPath); + } +} \ No newline at end of file diff --git a/src/ResponseData/AbstractResponseData.php b/src/ResponseData/AbstractResponseData.php new file mode 100644 index 0000000..a3e4df6 --- /dev/null +++ b/src/ResponseData/AbstractResponseData.php @@ -0,0 +1,14 @@ +message = $message; + } + + public function getMessage():?string { + return $this->message; + } +} \ No newline at end of file diff --git a/src/UserData.php b/src/ResponseData/UserData.php similarity index 74% rename from src/UserData.php rename to src/ResponseData/UserData.php index 6d741c6..01ebd49 100644 --- a/src/UserData.php +++ b/src/ResponseData/UserData.php @@ -1,7 +1,7 @@ uuid = $uuid; $this->email = $email; $this->fields = $fields; + + parent::__construct($message); } public function getUuid():string { diff --git a/src/SessionData.php b/src/SessionData.php index 89ae707..dee8c41 100644 --- a/src/SessionData.php +++ b/src/SessionData.php @@ -1,16 +1,19 @@ token = $token; - $this->userData = $userData; + $this->data = $data; } public function getToken():Token { @@ -21,11 +24,11 @@ public function getToken():Token { return $this->token; } - public function getUserData():UserData { - if(!isset($this->userData)) { + public function getData():AbstractResponseData { + if(!isset($this->data)) { throw new NotLoggedInException(); } - return $this->userData; + return $this->data; } } \ No newline at end of file diff --git a/src/Token.php b/src/Token.php index 1a166f0..223008a 100644 --- a/src/Token.php +++ b/src/Token.php @@ -1,7 +1,9 @@ expects(self::once()) - ->method("getUserData") + ->method("getData") ->willReturn($userData); $_SESSION = [ @@ -63,7 +64,10 @@ public function testIsLoggedInTrueWhenSessionDataSet() { self::assertTrue($sut->isLoggedIn()); } - public function testLogoutClearsSession() { + // TODO: Session shouldn't be cleared on call to logout - instead it should + // redirect to the provider, and a new test should asset the response data + // contains a logout confirmation. + public function TODO_UPDATE_testLogoutClearsSession() { $sessionData = self::createMock(SessionData::class); $_SESSION = [ Authenticator::SESSION_KEY => $sessionData @@ -146,7 +150,7 @@ public function testLoginRedirectsWithCorrectQueryString() { $expectedQueryParts = [ LoginUri::QUERY_STRING_CIPHER => $cipher, LoginUri::QUERY_STRING_INIT_VECTOR => $ivString, - LoginUri::QUERY_STRING_CURRENT_PATH => $currentPath, + LoginUri::QUERY_STRING_CURRENT_PATH => bin2hex($currentPath), ]; $expectedQuery = http_build_query($expectedQueryParts); @@ -205,7 +209,7 @@ public function testGetUuid() { $userData->method("getUuid") ->willReturn($expectedUuid); $sessionData = self::createMock(SessionData::class); - $sessionData->method("getUserData") + $sessionData->method("getData") ->willReturn($userData); $_SESSION = [ @@ -235,7 +239,7 @@ public function testGetEmail() { $userData->method("getEmail") ->willReturn($expectedEmail); $sessionData = self::createMock(SessionData::class); - $sessionData->method("getUserData") + $sessionData->method("getData") ->willReturn($userData); $_SESSION = [ @@ -303,8 +307,8 @@ public function testCompleteAuth() { $newSessionData ); self::assertInstanceOf( - UserData::class, - $newSessionData->getUserData() + AbstractResponseData::class, + $newSessionData->getData() ); } diff --git a/test/phpunit/ProviderUri/LoginUriTest.php b/test/phpunit/ProviderUri/LoginUriTest.php index 4b59ba2..794c754 100644 --- a/test/phpunit/ProviderUri/LoginUriTest.php +++ b/test/phpunit/ProviderUri/LoginUriTest.php @@ -41,7 +41,7 @@ public function testQueryString() { ); self::assertEquals( - $returnPath, + bin2hex($returnPath), $queryParts[LoginUri::QUERY_STRING_CURRENT_PATH] ); } diff --git a/test/phpunit/SessionDataTest.php b/test/phpunit/SessionDataTest.php index 5e54c88..8b8d07b 100644 --- a/test/phpunit/SessionDataTest.php +++ b/test/phpunit/SessionDataTest.php @@ -4,7 +4,7 @@ use Authwave\NotLoggedInException; use Authwave\SessionData; use Authwave\Token; -use Authwave\UserData; +use Authwave\ResponseData\UserData; use PHPUnit\Framework\TestCase; class SessionDataTest extends TestCase { @@ -23,13 +23,13 @@ public function testGetToken() { public function testGetUserDataNull() { $sut = new SessionData(); self::expectException(NotLoggedInException::class); - $sut->getUserData(); + $sut->getData(); } public function testGetUserData() { $token = self::createMock(Token::class); $userData = self::createMock(UserData::class); $sut = new SessionData($token, $userData); - self::assertSame($userData, $sut->getUserData()); + self::assertSame($userData, $sut->getData()); } } \ No newline at end of file diff --git a/test/phpunit/TokenTest.php b/test/phpunit/TokenTest.php index b81e46a..d001a7b 100644 --- a/test/phpunit/TokenTest.php +++ b/test/phpunit/TokenTest.php @@ -5,7 +5,7 @@ use Authwave\InvalidUserDataSerializationException; use Authwave\ResponseCipherDecryptionException; use Authwave\Token; -use Authwave\UserData; +use Authwave\ResponseData\UserData; use PHPUnit\Framework\TestCase; class TokenTest extends TestCase { From 77b7d2c6bc515e562cc17a452da0cf4161cc42e5 Mon Sep 17 00:00:00 2001 From: Greg Bowler Date: Mon, 18 May 2020 23:12:11 +0100 Subject: [PATCH 20/20] Send actions as encrypted messages --- src/ProviderUri/AbstractProviderUri.php | 4 +- src/ProviderUri/LogoutUri.php | 7 +++- test/phpunit/AuthenticatorTest.php | 49 +++++++++++++++++++++++-- 3 files changed, 52 insertions(+), 8 deletions(-) diff --git a/src/ProviderUri/AbstractProviderUri.php b/src/ProviderUri/AbstractProviderUri.php index 747e795..bdd63f4 100644 --- a/src/ProviderUri/AbstractProviderUri.php +++ b/src/ProviderUri/AbstractProviderUri.php @@ -35,10 +35,10 @@ protected function normaliseBaseUri(string $baseUri):Uri { protected function buildQuery( Token $token, string $currentPath, - string $data = null + string $message = null ):string { return http_build_query([ - self::QUERY_STRING_CIPHER => (string)$token->generateRequestCipher($data), + self::QUERY_STRING_CIPHER => (string)$token->generateRequestCipher($message), self::QUERY_STRING_INIT_VECTOR => (string)$token->getIv(), self::QUERY_STRING_CURRENT_PATH => bin2hex($currentPath), ]); diff --git a/src/ProviderUri/LogoutUri.php b/src/ProviderUri/LogoutUri.php index a64f1cd..c2ed192 100644 --- a/src/ProviderUri/LogoutUri.php +++ b/src/ProviderUri/LogoutUri.php @@ -10,9 +10,12 @@ public function __construct( string $baseRemoteUri = self::DEFAULT_BASE_REMOTE_URI ) { $baseRemoteUri = $this->normaliseBaseUri($baseRemoteUri); - $baseRemoteUri = $baseRemoteUri->withPath("/logout"); parent::__construct($baseRemoteUri); - $this->query = $this->buildQuery($token, $currentPath); + $this->query = $this->buildQuery( + $token, + $currentPath, + "action=logout" + ); } } \ No newline at end of file diff --git a/test/phpunit/AuthenticatorTest.php b/test/phpunit/AuthenticatorTest.php index 444b3ba..5fba507 100644 --- a/test/phpunit/AuthenticatorTest.php +++ b/test/phpunit/AuthenticatorTest.php @@ -64,16 +64,19 @@ public function testIsLoggedInTrueWhenSessionDataSet() { self::assertTrue($sut->isLoggedIn()); } - // TODO: Session shouldn't be cleared on call to logout - instead it should - // redirect to the provider, and a new test should asset the response data - // contains a logout confirmation. - public function TODO_UPDATE_testLogoutClearsSession() { + public function testLogoutCallsLogoutUri() { $sessionData = self::createMock(SessionData::class); $_SESSION = [ Authenticator::SESSION_KEY => $sessionData ]; $redirectHandler = self::createMock(RedirectHandler::class); + $redirectHandler->expects(self::once()) + ->method("redirect") + ->with(self::callback(fn(UriInterface $uri) => + $uri->getHost() === "login.authwave.com" + && $uri->getPath() === "/logout" + )); $sut = new Authenticator( "test-key", @@ -83,6 +86,44 @@ public function TODO_UPDATE_testLogoutClearsSession() { $redirectHandler ); $sut->logout(); + self::assertNotEmpty($_SESSION); + } + + public function testCompleteAuthFromLogoutClearsSession() { + $token = self::createMock(Token::class); + + $sessionData = self::createMock(SessionData::class); + $sessionData->method("getToken") + ->willReturn($token); + + $_SESSION = [ + Authenticator::SESSION_KEY => $sessionData, + ]; + + $responseCipher = "abcdef"; + + $currentUri = "/example-page-" . uniqid(); + $currentUri .= "?"; + $currentUri .= http_build_query([ + Authenticator::RESPONSE_QUERY_PARAMETER => $responseCipher, + ]); + + $redirectHandler = self::createMock(RedirectHandler::class); + $redirectHandler->expects(self::once()) + ->method("redirect") + ->with(self::callback(fn(UriInterface $uri) => + $uri->getHost() == "" + && $uri->getPath() == $currentUri + )); + + new Authenticator( + "test-key", + "/", + LoginUri::DEFAULT_BASE_REMOTE_URI, + null, + $redirectHandler + ); + self::assertEmpty($_SESSION); }