Skip to content

Commit 47baf38

Browse files
author
Greg Bowler
committed
Implement completeAuth functionality
1 parent bab50ee commit 47baf38

13 files changed

+182
-73
lines changed

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"require": {
66
"php": ">=7.4",
77
"ext-openssl": "*",
8+
"ext-json": "*",
89
"phpgt/http": "1.*",
910
"phpgt/session": ">=1.1"
1011
},

src/AuthUri.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public function __construct(
2828
parent::__construct($baseUri);
2929

3030
$this->query = http_build_query([
31-
self::QUERY_STRING_CIPHER => (string)$token->generateCipher(),
31+
self::QUERY_STRING_CIPHER => (string)$token->generateRequestCipher(),
3232
self::QUERY_STRING_INIT_VECTOR => (string)$token->getIv(),
3333
self::QUERY_STRING_CURRENT_PATH => $currentPath,
3434
]);

src/Authenticator.php

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
<?php
22
namespace Authwave;
33

4+
use Gt\Http\Uri;
45
use Gt\Session\SessionContainer;
56

67
class Authenticator {
78
const SESSION_KEY = "AUTHWAVE_SESSION";
9+
const RESPONSE_QUERY_PARAMETER = "AUTHWAVE_RESPONSE_DATA";
810

911
private string $clientKey;
10-
private string $clientSecret;
1112
private string $currentUriPath;
1213
private string $authwaveHost;
1314
private SessionContainer $session;
@@ -16,7 +17,6 @@ class Authenticator {
1617

1718
public function __construct(
1819
string $clientKey,
19-
string $clientSecret,
2020
string $currentUriPath,
2121
string $authwaveHost = "login.authwave.com",
2222
SessionContainer $session = null,
@@ -27,20 +27,19 @@ public function __construct(
2727
}
2828

2929
if(!$session->contains(self::SESSION_KEY)) {
30+
// TODO: If there is no Token or UserData in the SessionData, do we even
31+
// need to store it to the current session at all?
3032
$session->set(self::SESSION_KEY, new SessionData());
3133
}
3234

3335
$this->clientKey = $clientKey;
34-
$this->clientSecret = $clientSecret;
3536
$this->currentUriPath = $currentUriPath;
3637
$this->authwaveHost = $authwaveHost;
3738
$this->session = $session;
3839
$this->sessionData = $session->get(self::SESSION_KEY);
3940
$this->redirectHandler = $redirectHandler ?? new RedirectHandler();
4041

41-
if($this->authInProgress()) {
42-
$this->completeAuth();
43-
}
42+
$this->completeAuth();
4443
}
4544

4645
public function isLoggedIn():bool {
@@ -62,9 +61,12 @@ public function login(Token $token = null):void {
6261
}
6362

6463
if(is_null($token)) {
65-
$token = new Token($this->clientKey, $this->clientSecret);
64+
$token = new Token($this->clientKey);
6665
}
6766

67+
$this->sessionData = new SessionData($token);
68+
$this->session->set(self::SESSION_KEY, $this->sessionData);
69+
6870
$loginUri = new AuthUri(
6971
$token,
7072
$this->currentUriPath,
@@ -88,11 +90,41 @@ public function getEmail():string {
8890
return $userData->getEmail();
8991
}
9092

91-
private function authInProgress():bool {
92-
return false;
93+
private function completeAuth():void {
94+
$responseCipher = $this->getResponseCipher();
95+
96+
if(!$responseCipher) {
97+
return;
98+
}
99+
100+
$token = $this->sessionData->getToken();
101+
$userData = $token->decryptResponseCipher($responseCipher);
102+
$this->session->set(
103+
self::SESSION_KEY,
104+
new SessionData($token, $userData)
105+
);
106+
107+
$this->redirectHandler->redirect(
108+
(new Uri($this->currentUriPath))
109+
->withoutQueryValue(self::RESPONSE_QUERY_PARAMETER)
110+
);
93111
}
94112

95-
private function completeAuth():void {
113+
private function getResponseCipher():?string {
114+
$queryString = parse_url(
115+
$this->currentUriPath,
116+
PHP_URL_QUERY
117+
);
118+
if(!$queryString) {
119+
return null;
120+
}
121+
122+
$queryParts = [];
123+
parse_str($queryString, $queryParts);
124+
if(empty($queryParts[self::RESPONSE_QUERY_PARAMETER])) {
125+
return null;
126+
}
96127

128+
return $queryParts[self::RESPONSE_QUERY_PARAMETER];
97129
}
98130
}

src/InitVector.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,14 @@
44
class InitVector {
55
private string $bytes;
66

7-
public function __construct(int $length = 8) {
7+
public function __construct(int $length = 16) {
88
$this->bytes = random_bytes($length);
99
}
1010

11+
public function getBytes():string {
12+
return $this->bytes;
13+
}
14+
1115
public function __toString():string {
1216
return bin2hex($this->bytes);
1317
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?php
2+
namespace Authwave;
3+
4+
class InvalidUserDataSerializationException extends AuthwaveException {}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?php
2+
namespace Authwave;
3+
4+
class ResponseCipherDecryptionException extends AuthwaveException {}

src/SessionData.php

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,30 @@
22
namespace Authwave;
33

44
class SessionData {
5-
private InitVector $iv;
5+
private ?Token $token;
6+
private ?UserData $userData;
67

7-
public function getIv():InitVector {
8-
if(!isset($this->iv)) {
9-
throw new InitVectorNotSetException();
10-
}
11-
12-
return $this->iv;
8+
public function __construct(
9+
Token $token = null,
10+
UserData $userData = null
11+
) {
12+
$this->token = $token;
13+
$this->userData = $userData;
1314
}
1415

15-
public function setIv(InitVector $iv):void {
16-
$this->iv = $iv;
17-
}
16+
public function getToken():Token {
17+
if(!isset($this->token)) {
18+
throw new NotLoggedInException();
19+
}
1820

19-
public function removeIv():void {
20-
unset($this->iv);
21+
return $this->token;
2122
}
2223

2324
public function getUserData():UserData {
2425
if(!isset($this->userData)) {
2526
throw new NotLoggedInException();
2627
}
2728

28-
return $this->getUserData();
29+
return $this->userData;
2930
}
3031
}

src/Token.php

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,74 @@
11
<?php
22
namespace Authwave;
33

4+
use Authwave\Test\MalformedReponseDataException;
5+
use JsonException;
6+
47
class Token {
58
const ENCRYPTION_METHOD = "aes128";
69

710
private string $key;
8-
private string $secret;
11+
private string $secretIv;
912
private InitVector $iv;
1013

1114
public function __construct(
1215
string $key,
13-
string $secret,
16+
InitVector $secretIv = null,
1417
InitVector $iv = null
1518
) {
1619
$this->key = $key;
17-
$this->secret = $secret;
20+
$this->secretIv = $secretIv ?? new InitVector();
1821
$this->iv = $iv ?? new InitVector();
1922
}
2023

21-
public function generateCipher():string {
24+
// The request cipher is sent to the remote provider in the querystring. It
25+
// consists of the secret IV, encrypted with the client key. The remote provider
26+
// will decrypt the secret and use it as the key when encrypting the response
27+
// cipher, which will be sent back to the client application in the querystring.
28+
public function generateRequestCipher():string {
2229
$rawCipher = openssl_encrypt(
23-
$this->secret,
30+
$this->secretIv,
2431
self::ENCRYPTION_METHOD,
2532
$this->key,
2633
0,
27-
$this->iv
34+
$this->iv->getBytes()
2835
);
2936

3037
return base64_encode($rawCipher);
3138
}
3239

40+
// The response cipher is send from the remote provider back to the client
41+
// application after a successful authentication and includes a serialised
42+
// UserData object, encrypted using the secret IV, which was created when
43+
// encrypting the original request cipher.
44+
public function decryptResponseCipher(string $cipher):UserData {
45+
$decrypted = openssl_decrypt(
46+
base64_decode($cipher),
47+
self::ENCRYPTION_METHOD,
48+
$this->secretIv->getBytes(),
49+
0,
50+
$this->iv->getBytes()
51+
);
52+
53+
if(!$decrypted) {
54+
throw new ResponseCipherDecryptionException();
55+
}
56+
57+
try {
58+
$obj = json_decode(
59+
$decrypted,
60+
false,
61+
2,
62+
JSON_THROW_ON_ERROR
63+
);
64+
}
65+
catch(JsonException $exception) {
66+
throw new InvalidUserDataSerializationException();
67+
}
68+
69+
return new UserData($obj->uuid, $obj->email);
70+
}
71+
3372
public function getIv():InitVector {
3473
return $this->iv;
3574
}

src/UserData.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ class UserData {
55
private string $uuid;
66
private string $email;
77

8+
public function __construct(string $uuid, string $email) {
9+
$this->uuid = $uuid;
10+
$this->email = $email;
11+
}
12+
813
public function getUuid():string {
914
return $this->uuid;
1015
}

test/phpunit/AuthUriTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ public function testQueryString() {
7373

7474
$baseUri = self::createMock(UriInterface::class);
7575
$token = self::createMock(Token::class);
76-
$token->method("generateCipher")
76+
$token->method("generateRequestCipher")
7777
->willReturn($mockCipherValue);
7878
$token->method("getIv")
7979
->willReturn($iv);

0 commit comments

Comments
 (0)