Skip to content
Snippets Groups Projects
Commit 6f493c2f authored by Sonia Zorba's avatar Sonia Zorba
Browse files

Refactoring of login logic preparatory to autojoin

parent e2a85279
Branches
No related tags found
No related merge requests found
...@@ -98,6 +98,10 @@ class Locator { ...@@ -98,6 +98,10 @@ class Locator {
return new TokenExchanger($this); return new TokenExchanger($this);
} }
public function getGmsClient(): GmsClient {
return new GmsClient($this);
}
/** /**
* Retrieve the SessionData object from the $_SESSION PHP variable. Create a * Retrieve the SessionData object from the $_SESSION PHP variable. Create a
* new one if it is necessary. * new one if it is necessary.
......
...@@ -42,7 +42,7 @@ class UserHandler { ...@@ -42,7 +42,7 @@ class UserHandler {
* new identities to it. * new identities to it.
* @param \RAP\User $user * @param \RAP\User $user
*/ */
public function saveUser(User $user) { public function saveUser(User $user): void {
$primarySpecified = true; $primarySpecified = true;
...@@ -63,6 +63,25 @@ class UserHandler { ...@@ -63,6 +63,25 @@ class UserHandler {
} }
} }
/**
* Update user with fresh information received by IdP. Useful for keeping email address always updated.
*/
public function updateIdentity(User $user, Identity $identity): void {
$savedIdentity = $user->getIdentityByTypedId($identity->typedId);
$savedIdentity->email = $identity->email;
if ($identity->name !== null) {
$savedIdentity->name = $identity->name;
}
if ($identity->surname !== null) {
$savedIdentity->surname = $identity->surname;
}
if ($identity->institution !== null) {
$savedIdentity->institution = $identity->institution;
}
$this->userDAO->updateIdentity($savedIdentity);
}
public function joinUsers(User $user1, User $user2): User { public function joinUsers(User $user1, User $user2): User {
$userId1 = $user1->id; $userId1 = $user1->id;
...@@ -72,69 +91,47 @@ class UserHandler { ...@@ -72,69 +91,47 @@ class UserHandler {
return $user1; return $user1;
} }
// Call Grouper for moving groups and privileges from one user to the other if ($userId1 === null && $userId2 === null) {
if (isset($this->locator->config->gms)) { throw new BadRequestException("Called join on two users that are both new");
//create cURL connection
$conn = curl_init($this->locator->config->gms->joinEndpoint);
//set options
curl_setopt($conn, CURLOPT_CONNECTTIMEOUT, 30);
curl_setopt($conn, CURLOPT_RETURNTRANSFER, true);
curl_setopt($conn, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($conn, CURLOPT_FOLLOWLOCATION, 1);
// Setting an empty body, otherwise Content-Length will be negative and Spring will answer with 400
curl_setopt($conn, CURLOPT_POSTFIELDS, " ");
curl_setopt($conn, CURLOPT_HTTPHEADER, ['Authorization: Bearer '
. $this->getJoinAccessToken($userId1, $userId2)]);
//set data to be posted
curl_setopt($conn, CURLOPT_POST, 1);
//perform the request
$response = curl_exec($conn);
$info = curl_getinfo($conn);
if ($info['http_code'] === 200) {
curl_close($conn);
} else {
//show information regarding the error
curl_close($conn);
error_log($response);
$httpCode = $info['http_code'];
if ($httpCode === 0) {
throw new ServerErrorException('GMS service is unreachable');
} }
throw new ServerErrorException('Error: GMS response code: ' . $httpCode);
if ($userId1 === null) {
return $this->joinNewIdentity($user2, $user1);
} }
if ($userId2 === null) {
return $this->joinNewIdentity($user1, $user2);
} }
// Call Grouper for moving groups and privileges from one user to the other
$remainingUserId = $this->locator->getGmsClient()->joinGroups($userId1, $userId2);
$remainingUser = $userId1 === $remainingUserId ? $user1 : $user2;
$userToDelete = $userId1 === $remainingUserId ? $user2 : $user1;
// Call DAO for performing join operation into the RAP database. // Call DAO for performing join operation into the RAP database.
$this->userDAO->joinUsers($userId1, $userId2); $this->userDAO->joinUsers($remainingUserId, $userToDelete->id);
foreach ($user2->identities as $identity) { $this->moveIdentities($remainingUser, $userToDelete);
$identity->primary = false;
$user1->addIdentity($identity);
}
// merged user // merged user
return $user1; return $remainingUser;
} }
private function getJoinAccessToken(int $userId1, int $userId2): string { /**
* New identity, not yet associated with a user: simply add it to the other user.
$gmsId = $this->locator->config->gms->id; */
private function joinNewIdentity(User $user, User $newUser): User {
$accessToken = new AccessTokenData(); $identity = $newUser->identities[0];
$accessToken->clientId = $gmsId; $user->addIdentity($identity);
$accessToken->userId = $userId1; $this->saveUser($user);
// shorter expiration return $user;
$accessToken->expirationTime = $accessToken->creationTime + 100; }
$accessToken->scope = ['openid'];
return $this->locator->getTokenBuilder()->getAccessToken($accessToken, function(& $jwt) use($userId2) { private function moveIdentities(User $remainingUser, User $deletedUser) {
$jwt['alt_sub'] = strval($userId2); foreach ($deletedUser->identities as $identity) {
}); $identity->primary = false;
$remainingUser->addIdentity($identity);
}
} }
} }
...@@ -80,5 +80,7 @@ interface UserDAO { ...@@ -80,5 +80,7 @@ interface UserDAO {
function updateIdentity(Identity $identity): void; function updateIdentity(Identity $identity): void;
function findJoinableUsers($userId): array; function findJoinableUsersByEmail(string $email): array;
function findJoinableUsersByUserId(string $userId): array;
} }
...@@ -301,7 +301,30 @@ class MySQLUserDAO extends BaseMySQLDAO implements UserDAO { ...@@ -301,7 +301,30 @@ class MySQLUserDAO extends BaseMySQLDAO implements UserDAO {
$stmt->execute(); $stmt->execute();
} }
function findJoinableUsers($userId): array { function findJoinableUsersByEmail(string $email): array {
$dbh = $this->getDBHandler();
$query = "SELECT DISTINCT(i3.user_id)\n"
. "FROM user u\n"
. "JOIN identity i ON i.user_id = u.id\n"
. "JOIN identity i2 ON i.user_id = i2.user_id\n"
. "JOIN identity i3 ON i2.email = i3.email\n"
. "WHERE i.email = :email";
$stmt = $dbh->prepare($query);
$stmt->bindParam(':email', $email);
$stmt->execute();
$results = [];
foreach ($stmt->fetchAll() as $row) {
array_push($results, $row['user_id']);
}
return $results;
}
function findJoinableUsersByUserId(string $userId): array {
$dbh = $this->getDBHandler(); $dbh = $this->getDBHandler();
......
<?php
namespace RAP;
class GmsClient {
private $locator;
public function __construct(Locator $locator) {
$this->locator = $locator;
}
public function joinGroups(string $userId1, string $userId2): string {
if (!isset($this->locator->config->gms)) {
return $userId1;
}
//create cURL connection
$conn = curl_init($this->locator->config->gms->joinEndpoint);
//set options
curl_setopt($conn, CURLOPT_CONNECTTIMEOUT, 30);
curl_setopt($conn, CURLOPT_RETURNTRANSFER, true);
curl_setopt($conn, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($conn, CURLOPT_FOLLOWLOCATION, 1);
// Setting an empty body, otherwise Content-Length will be negative and Spring will answer with 400
curl_setopt($conn, CURLOPT_POSTFIELDS, " ");
curl_setopt($conn, CURLOPT_HTTPHEADER, ['Authorization: Bearer '
. $this->getJoinAccessToken($userId1, $userId2)]);
//set data to be posted
curl_setopt($conn, CURLOPT_POST, 1);
//perform the request
$response = curl_exec($conn);
$info = curl_getinfo($conn);
if ($info['http_code'] === 200) {
curl_close($conn);
} else {
//show information regarding the error
curl_close($conn);
error_log($response);
$httpCode = $info['http_code'];
if ($httpCode === 0) {
throw new ServerErrorException('GMS service is unreachable');
}
throw new ServerErrorException('Error: GMS response code: ' . $httpCode);
}
// TODO: return id extracted from GMS response
return $userId1;
}
private function getJoinAccessToken(int $userId1, int $userId2): string {
$gmsId = $this->locator->config->gms->id;
$accessToken = new AccessTokenData();
$accessToken->clientId = $gmsId;
$accessToken->userId = $userId1;
// shorter expiration
$accessToken->expirationTime = $accessToken->creationTime + 100;
$accessToken->scope = ['openid'];
return $this->locator->getTokenBuilder()->getAccessToken($accessToken, function(& $jwt) use($userId2) {
$jwt['alt_sub'] = strval($userId2);
});
}
}
...@@ -2,6 +2,9 @@ ...@@ -2,6 +2,9 @@
namespace RAP; namespace RAP;
/**
* All public methods return a redirect URL.
*/
class LoginHandler { class LoginHandler {
protected $locator; protected $locator;
...@@ -10,41 +13,116 @@ class LoginHandler { ...@@ -10,41 +13,116 @@ class LoginHandler {
$this->locator = $locator; $this->locator = $locator;
} }
/**
* Returns redirect URL.
*/
public function onIdentityDataReceived(Identity $identity): string { public function onIdentityDataReceived(Identity $identity): string {
$this->locator->getSession()->setLoginIdentityType($identity->type); $session = $this->locator->getSession();
$session->setLoginIdentityType($identity->type);
$user = $this->getUser($identity);
$joinStep = $this->checkJoinAction($session, $user);
if ($joinStep !== null) {
return $joinStep;
}
$session->setUser($user);
$autoJoinStep = $this->checkAutoJoin();
if ($autoJoinStep !== null) {
return $autoJoinStep;
}
if ($user->id === null) {
return $this->redirectToTOUCheck($user);
} else {
$this->locator->getUserHandler()->updateIdentity($user, $identity);
}
return $this->getAfterLoginRedirect();
}
private function getUser(Identity $identity): User {
$userDao = $this->locator->getUserDAO(); $userDao = $this->locator->getUserDAO();
$user = $userDao->findUserByIdentity($identity->type, $identity->typedId); $user = $userDao->findUserByIdentity($identity->type, $identity->typedId);
if ($user === null) { if ($user === null) {
return $this->handleNewIdentity($identity); // create new user with null id
$user = new User();
$user->addIdentity($identity);
}
return $user;
}
private function checkJoinAction(SessionData $session, User $user): ?string {
if ($session->getOAuth2RequestData() === null && $session->getAction() === 'join' && $session->getUser() !== null) {
if ($session->getUser()->id === $user->id) {
// attempting to join an already joined user: go to account page without joining
$session->setAction('account');
} else { } else {
$this->updateUser($user, $identity); return $this->showConfirmJoin($user);
}
}
return null;
} }
public function confirmJoin(): string {
$session = $this->locator->getSession(); $session = $this->locator->getSession();
if ($session->getOAuth2RequestData() === null && $session->getAction() === 'join' &&
$session->getUser() !== null && $session->getUser()->id !== $user->id) { if ($session->getUserToJoin() === null) {
return $this->showConfirmJoin($user); throw new BadRequestException("Unable to find user to join");
} }
return $this->getAfterLoginRedirect($user); $this->joinUsers();
$autoJoinStep = $this->checkAutoJoin();
if ($autoJoinStep !== null) {
return $autoJoinStep;
}
return $this->getAfterLoginRedirect();
} }
protected function handleNewIdentity(Identity $identity): string { private function checkAutoJoin(): ?string {
$userDao = $this->locator->getUserDAO();
$session = $this->locator->getSession(); $session = $this->locator->getSession();
$user = $session->getUser();
if ($user->id === null) {
$joinableUsers = $userDao->findJoinableUsersByEmail($user->identities[0]->email);
} else {
$joinableUsers = $userDao->findJoinableUsersByUserId($user->id);
}
if ($session->getUser() !== null && $session->getAction() === 'join') { if (count($joinableUsers) > 0) {
$userToJoin = $this->getNewUser($identity); // select first user
$userToJoin = $userDao->findUserById($joinableUsers[0]);
$session->setAutojoin(true);
return $this->showConfirmJoin($userToJoin); return $this->showConfirmJoin($userToJoin);
}
return null;
}
public function rejectJoin(): string {
$session = $this->locator->getSession();
$user = $session->getUser();
if ($user === null) {
throw new \RAP\BadRequestException("Unable to find user");
}
$session->setJoinRejected(true);
if ($session->getUser()->id === null) {
return $this->redirectToTOUCheck($session->getUser()->identities[0]);
} else { } else {
return $this->redirectToTOUCheck($identity); return $this->getAfterLoginRedirect();
} }
} }
...@@ -56,94 +134,63 @@ class LoginHandler { ...@@ -56,94 +134,63 @@ class LoginHandler {
/** /**
* Stores the data into session and Redirect to Term of Use acceptance page. * Stores the data into session and Redirect to Term of Use acceptance page.
*/ */
private function redirectToTOUCheck(Identity $identity): string { private function redirectToTOUCheck(): string {
// Create new user
$user = $this->getNewUser($identity);
$this->locator->getSession()->setUser($user);
return $this->locator->getBasePath() . '/tou-check'; return $this->locator->getBasePath() . '/tou-check';
} }
private function getNewUser(Identity $identity): User {
$user = new User();
$user->addIdentity($identity);
return $user;
}
/** /**
* Update user with fresh information received by IdP. Useful for keeping email address always updated. * Stores the user data into the database after he/she accepted the Terms of Use.
*/ */
private function updateUser(User $user, Identity $identity): void { public function register(): string {
$savedIdentity = $user->getIdentityByTypedId($identity->typedId); $user = $this->locator->getSession()->getUser();
$savedIdentity->email = $identity->email;
if($identity->name !== null) { if ($user === null) {
$savedIdentity->name = $identity->name; throw new BadRequestException("User data not retrieved.");
} else {
$this->locator->getUserHandler()->saveUser($user);
return $this->getAfterLoginRedirect();
}
} }
if($identity->surname !== null) {
$savedIdentity->surname = $identity->surname; private function joinUsers(): void {
$session = $this->locator->getSession();
$user = $session->getUser();
$userToJoin = $session->getUserToJoin();
if ($user === null) {
$session->setUser($userToJoin);
} else {
$joinedUser = $this->locator->getUserHandler()->joinUsers($userToJoin, $user);
$session->setUser($joinedUser);
} }
if($identity->institution !== null) {
$savedIdentity->institution = $identity->institution; if ($session->getAction() === 'join') {
$session->setAction('account');
} }
$this->locator->getUserDAO()->updateIdentity($savedIdentity);
$session->setUserToJoin(null);
} }
public function getAfterLoginRedirect(User $user): string { private function getAfterLoginRedirect(): string {
$session = $this->locator->getSession(); $session = $this->locator->getSession();
$this->locator->getAuditLogger()->info("LOGIN," . $session->getLoginIdentityType() . "," . $user->id); $this->locator->getAuditLogger()->info("LOGIN," . $session->getLoginIdentityType() . "," . $session->getUser()->id);
if ($session->getOAuth2RequestData() !== null) { if ($session->getOAuth2RequestData() !== null) {
$session->setUser($user); // Redirect to OAuth2 client callback URL
$redirectUrl = $this->locator->getOAuth2RequestHandler()->getRedirectResponseUrl(); $redirectUrl = $this->locator->getOAuth2RequestHandler()->getRedirectResponseUrl();
session_destroy(); session_destroy();
return $redirectUrl; return $redirectUrl;
} }
if ($session->getAction() !== null) {
$action = $session->getAction(); $action = $session->getAction();
if ($action === 'join') {
$user = $this->joinTo($user);
$action = 'account';
$session->setAction($action);
}
$session->setUser($user);
if ($action === 'account' || $action === 'admin') { if ($action === 'account' || $action === 'admin') {
return $this->locator->getBasePath() . '/' . $action; return $this->locator->getBasePath() . '/' . $action;
} }
}
throw new \Exception("Unable to find a proper redirect"); throw new \Exception("Unable to find a proper redirect");
} }
private function joinTo(User $userToJoin): User {
$session = $this->locator->getSession();
$user = $session->getUser();
if ($user === null) {
return $userToJoin;
}
if ($userToJoin->id === null) {
// New identity, not yet associated with an user: simply add it to
// previously logged in user.
$identity = $userToJoin->identities[0];
$user->addIdentity($identity);
$this->locator->getUserHandler()->saveUser($user);
} else if ($user->id !== $userToJoin->id) {
$user = $this->locator->getUserHandler()->joinUsers($user, $userToJoin);
}
$session->setUserToJoin(null);
return $user;
}
} }
...@@ -39,6 +39,7 @@ class SessionData { ...@@ -39,6 +39,7 @@ class SessionData {
private $action; private $action;
private $loginIdentityType; private $loginIdentityType;
private $autojoin = false; private $autojoin = false;
private $joinRejected = false;
public function setUser(?User $user): void { public function setUser(?User $user): void {
$this->user = $user; $this->user = $user;
...@@ -67,6 +68,15 @@ class SessionData { ...@@ -67,6 +68,15 @@ class SessionData {
return $this->autojoin; return $this->autojoin;
} }
public function setJoinRejected(bool $joinRejected): void {
$this->joinRejected = $joinRejected;
$this->save();
}
public function isJoinRejected(): bool {
return $this->joinRejected;
}
/** /**
* Used for logging the identity type chosen for the login at the end of the login process * Used for logging the identity type chosen for the login at the end of the login process
*/ */
......
...@@ -10,7 +10,8 @@ ...@@ -10,7 +10,8 @@
"phpmailer/phpmailer": "^6.0" "phpmailer/phpmailer": "^6.0"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "^8.2" "phpunit/phpunit": "^8.2",
"phpmd/phpmd": "@stable"
}, },
"autoload": { "autoload": {
"classmap": [ "classmap": [
......
...@@ -221,3 +221,7 @@ body { ...@@ -221,3 +221,7 @@ body {
left: 27px; left: 27px;
top: 0px; top: 0px;
} }
.inline {
display: inline;
}
...@@ -242,7 +242,7 @@ Flight::route('GET /x509-name-surname', function() { ...@@ -242,7 +242,7 @@ Flight::route('GET /x509-name-surname', function() {
} else { } else {
// Redirect to index // Redirect to index
header("Location: " . $locator->getBasePath()); header("Location: " . $locator->getBasePath());
die(); throw new \RAP\BadRequestException();
} }
}); });
...@@ -265,7 +265,7 @@ Flight::route('POST /submit-x509-name', function() { ...@@ -265,7 +265,7 @@ Flight::route('POST /submit-x509-name', function() {
$redirect = $loginHandler->afterNameSurnameSelection($session->getX509DataToRegister()); $redirect = $loginHandler->afterNameSurnameSelection($session->getX509DataToRegister());
Flight::redirect($redirect); Flight::redirect($redirect);
} else { } else {
die('X.509 data not returned'); throw new \RAP\BadRequestException('X.509 data not returned');
} }
}); });
...@@ -278,7 +278,7 @@ Flight::route('GET /tou-check', function() { ...@@ -278,7 +278,7 @@ Flight::route('GET /tou-check', function() {
global $locator; global $locator;
if ($locator->getSession()->getUser() === null) { if ($locator->getSession()->getUser() === null) {
die("User data not retrieved."); throw new \RAP\BadRequestException("User data not retrieved.");
} else { } else {
Flight::render('tou-check.php', array('title' => 'Terms of Use acceptance', Flight::render('tou-check.php', array('title' => 'Terms of Use acceptance',
'user' => $locator->getSession()->getUser(), 'user' => $locator->getSession()->getUser(),
...@@ -294,7 +294,7 @@ Flight::route('GET /confirm-join', function() { ...@@ -294,7 +294,7 @@ Flight::route('GET /confirm-join', function() {
global $locator; global $locator;
if ($locator->getSession()->getUser() === null) { if ($locator->getSession()->getUser() === null) {
die("User data not retrieved."); throw new \RAP\BadRequestException("User data not retrieved.");
} else { } else {
Flight::render('confirm-join.php', array('title' => 'Confirm join', Flight::render('confirm-join.php', array('title' => 'Confirm join',
'autojoin' => $locator->getSession()->isAutojoin(), 'autojoin' => $locator->getSession()->isAutojoin(),
...@@ -309,14 +309,16 @@ Flight::route('POST /confirm-join', function() { ...@@ -309,14 +309,16 @@ Flight::route('POST /confirm-join', function() {
session_start(); session_start();
global $locator; global $locator;
$loginHandler = new \RAP\LoginHandler($locator);
Flight::redirect($loginHandler->confirmJoin());
});
$user = $locator->getSession()->getUserToJoin(); Flight::route('POST /reject-join', function() {
if ($user === null) {
die("Unable to find user to join"); session_start();
} else { global $locator;
$loginHandler = new \RAP\LoginHandler($locator); $loginHandler = new \RAP\LoginHandler($locator);
Flight::redirect($loginHandler->getAfterLoginRedirect($user)); Flight::redirect($loginHandler->rejectJoin());
}
}); });
/** /**
...@@ -327,16 +329,8 @@ Flight::route('GET /register', function() { ...@@ -327,16 +329,8 @@ Flight::route('GET /register', function() {
session_start(); session_start();
global $locator; global $locator;
$user = $locator->getSession()->getUser();
if ($user === null) {
die("User data not retrieved.");
} else {
$locator->getUserHandler()->saveUser($user);
$loginHandler = new \RAP\LoginHandler($locator); $loginHandler = new \RAP\LoginHandler($locator);
Flight::redirect($loginHandler->getAfterLoginRedirect($user)); $loginHandler->register();
}
}); });
/** /**
......
<?php
use PHPUnit\Framework\TestCase;
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
/**
* This test has a setup similar to the LoginHandlerTest one, however SessionData and
* UserHandler are not mocked, so interaction between session and login handler can be
* tested and complete login and join flows can be simulated step by step.
*/
final class LoginFlowTest extends TestCase {
private $locatorStub;
private $userDaoStub;
private $oAuth2RequestHandler;
private $auditLogger;
private $gmsClientStub;
private $sessionData;
private $userHandler;
private $loginHandler;
public function setUp(): void {
$this->locatorStub = $this->createMock(\RAP\Locator::class);
$this->locatorStub->method('getBasePath')->willReturn('http://rap-ia2');
$this->userDaoStub = $this->createMock(\RAP\UserDAO::class);
$this->locatorStub->method('getUserDAO')->willReturn($this->userDaoStub);
$this->oAuth2RequestHandler = $this->createMock(\RAP\OAuth2RequestHandler::class);
$this->locatorStub->method('getOAuth2RequestHandler')->willReturn($this->oAuth2RequestHandler);
$this->auditLogger = $this->createMock(\Monolog\Logger::class);
$this->locatorStub->method('getAuditLogger')->willReturn($this->auditLogger);
$this->gmsClientStub = $this->createMock(\RAP\GmsClient::class);
$this->locatorStub->method('getGmsClient')->willReturn($this->gmsClientStub);
$this->sessionData = new \RAP\SessionData();
$this->locatorStub->method('getSession')->willReturn($this->sessionData);
$this->userHandler = new \RAP\UserHandler($this->locatorStub);
$this->locatorStub->method('getUserHandler')->willReturn($this->userHandler);
$this->loginHandler = new \RAP\LoginHandler($this->locatorStub);
}
public function testNewIdentityLogin(): void {
$this->sessionData->setAction('account');
$identity = $this->getFakeIdentity1();
// verify DAO is called for checking user existence
$this->userDaoStub->expects($this->once())
->method('findUserByIdentity')->with($identity->type, $identity->typedId);
$redirect1 = $this->loginHandler->onIdentityDataReceived($identity);
// verify ids are not set
$this->assertNull($this->sessionData->getUser()->id);
$this->assertNull($this->sessionData->getUser()->identities[0]->id);
$this->assertEquals("http://rap-ia2/tou-check", $redirect1);
$this->userDaoStub->method('createUser')->willReturn('2');
$this->userDaoStub->method('insertIdentity')->willReturn('3');
$redirect2 = $this->loginHandler->register();
$this->assertEquals('2', $this->sessionData->getUser()->id);
$this->assertEquals('3', $this->sessionData->getUser()->identities[0]->id);
$this->assertEquals("http://rap-ia2/account", $redirect2);
}
public function testExistingUserLogin(): void {
$this->sessionData->setAction('account');
$user = $this->getFakeUser1();
$this->assertEquals('test@example.com', $user->identities[0]->email);
$this->userDaoStub->method('findUserByIdentity')->willReturn($user);
$this->userDaoStub->expects($this->once())
->method('updateIdentity')->with($this->anything());
$redirect = $this->loginHandler->onIdentityDataReceived($this->getFakeIdentity1NewEmail());
// verify email update
$this->assertEquals('test2@example.com', $user->identities[0]->email);
$this->assertEquals('http://rap-ia2/account', $redirect);
}
public function testNewIdentityAutojoin(): void {
$this->oAuth2RequestHandler->method('getRedirectResponseUrl')->willReturn('http://redirect-url');
$this->userDaoStub->method('findJoinableUsersByEmail')->willReturn(['1', '2']);
$this->userDaoStub->method('findUserById')->willReturn($this->getFakeUser1());
$redirect1 = $this->loginHandler->onIdentityDataReceived($this->getFakeIdentity3());
$this->assertTrue($this->sessionData->isAutojoin());
$this->assertEquals('http://rap-ia2/confirm-join', $redirect1);
$this->assertNotNull($this->sessionData->getUserToJoin());
$this->assertNull($this->sessionData->getUser()->id);
$this->userDaoStub->method('findJoinableUsersByUserId')->willReturn(['2']);
$redirect2 = $this->loginHandler->confirmJoin();
$this->assertEquals('1', $this->sessionData->getUser()->id);
$this->assertEquals('http://rap-ia2/confirm-join', $redirect2);
$this->gmsClientStub->method('joinGroups')->willReturn('2');
$this->gmsClientStub->expects($this->once())
->method('joinGroups')->with($this->anything());
$redirect3 = $this->loginHandler->confirmJoin();
$this->assertEquals('2', $this->sessionData->getUser()->id);
$this->assertEquals('http://redirect-url', $redirect3);
}
public function testJoinAlreadyJoinedUser(): void { // go to account page without joining
$user = $this->getFakeUser1();
$this->sessionData->setAction('join');
$this->sessionData->setUser($user);
$this->userDaoStub->method('findUserByIdentity')->willReturn($user);
$redirect = $this->loginHandler->onIdentityDataReceived($this->getFakeIdentity1());
$this->assertEquals('http://rap-ia2/account', $redirect);
}
private function getFakeUser1(): \RAP\User {
$user = new \RAP\User();
$user->id = '1';
$identity = new \RAP\Identity('eduGAIN');
$identity->email = 'test@example.com';
$identity->id = '4';
$identity->typedId = '123';
$user->addIdentity($identity);
return $user;
}
private function getFakeUser2(): \RAP\User {
$user = new \RAP\User();
$user->id = '2';
$identity = new \RAP\Identity('eduGAIN');
$identity->email = 'test@example.com';
$identity->id = '6';
$identity->typedId = '999';
$user->addIdentity($identity);
return $user;
}
private function getFakeIdentity1(): \RAP\Identity {
$identity = new \RAP\Identity(\RAP\Identity::EDU_GAIN);
$identity->typedId = '123';
$identity->email = 'test@example.com';
return $identity;
}
private function getFakeIdentity1NewEmail(): \RAP\Identity {
$identity = new \RAP\Identity(\RAP\Identity::EDU_GAIN);
$identity->typedId = '123';
$identity->email = 'test2@example.com';
return $identity;
}
private function getFakeIdentity2(): \RAP\Identity {
$identity = new \RAP\Identity(\RAP\Identity::EDU_GAIN);
$identity->typedId = '456';
$identity->email = 'test3@example.com';
return $identity;
}
private function getFakeIdentity3(): \RAP\Identity {
$identity = new \RAP\Identity(\RAP\Identity::GOOGLE);
$identity->typedId = '789';
$identity->email = 'test@example.com';
return $identity;
}
}
...@@ -2,13 +2,16 @@ ...@@ -2,13 +2,16 @@
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
if (session_status() === PHP_SESSION_NONE) {
session_start(); session_start();
}
final class LoginHandlerTest extends TestCase { final class LoginHandlerTest extends TestCase {
private $locatorStub; private $locatorStub;
private $userDaoStub; private $userDaoStub;
private $sessionStub; private $sessionStub;
private $userHandlerStub;
private $oAuth2RequestHandler; private $oAuth2RequestHandler;
private $auditLogger; private $auditLogger;
private $loginHandler; private $loginHandler;
...@@ -23,39 +26,30 @@ final class LoginHandlerTest extends TestCase { ...@@ -23,39 +26,30 @@ final class LoginHandlerTest extends TestCase {
$this->sessionStub = $this->createMock(\RAP\SessionData::class); $this->sessionStub = $this->createMock(\RAP\SessionData::class);
$this->locatorStub->method('getSession')->willReturn($this->sessionStub); $this->locatorStub->method('getSession')->willReturn($this->sessionStub);
$this->userHandlerStub = $this->createMock(\RAP\UserHandler::class);
$this->locatorStub->method('getUserHandler')->willReturn($this->userHandlerStub);
$this->oAuth2RequestHandler = $this->createMock(\RAP\OAuth2RequestHandler::class); $this->oAuth2RequestHandler = $this->createMock(\RAP\OAuth2RequestHandler::class);
$this->locatorStub->method('getOAuth2RequestHandler')->willReturn($this->oAuth2RequestHandler); $this->locatorStub->method('getOAuth2RequestHandler')->willReturn($this->oAuth2RequestHandler);
$this->auditLogger = $this->createMock(\Monolog\Logger::class); $this->auditLogger = $this->createMock(\Monolog\Logger::class);
$this->locatorStub->method('getAuditLogger')->willReturn($this->auditLogger); $this->locatorStub->method('getAuditLogger')->willReturn($this->auditLogger);
$this->loginHandler = new \RAP\LoginHandler($this->locatorStub, 'eduGAIN'); $this->loginHandler = new \RAP\LoginHandler($this->locatorStub);
}
public function testTOUCheck(): void {
$redirect = $this->loginHandler->onIdentityDataReceived($this->getFakeIdentity1());
$this->assertEquals('http://rap-ia2/tou-check', $redirect);
} }
public function testExistingUserLogin(): void { public function testExistingUserLogin(): void {
$user = $this->getFakeUser(); $user = $this->getFakeUser();
$this->assertEquals('test@example.com', $user->identities[0]->email);
$this->userDaoStub->method('findUserByIdentity')->willReturn($user); $this->userDaoStub->method('findUserByIdentity')->willReturn($user);
$this->oAuth2RequestHandler->method('getRedirectResponseUrl')->willReturn('http://redirect-url'); $this->oAuth2RequestHandler->method('getRedirectResponseUrl')->willReturn('http://redirect-url');
$this->sessionStub->method('getOAuth2RequestData')->willReturn(new \RAP\OAuth2RequestData()); $this->sessionStub->method('getOAuth2RequestData')->willReturn(new \RAP\OAuth2RequestData());
$this->sessionStub->method('getUser')->willReturn($user);
$this->userDaoStub->expects($this->once()) $redirect = $this->loginHandler->onIdentityDataReceived($this->getFakeIdentity1());
->method('updateIdentity')->with($this->anything());
$redirect = $this->loginHandler->onIdentityDataReceived($this->getFakeIdentity1NewEmail());
// verify update
$this->assertEquals('test2@example.com', $user->identities[0]->email);
$this->assertEquals('http://redirect-url', $redirect); $this->assertEquals('http://redirect-url', $redirect);
} }
...@@ -91,47 +85,6 @@ final class LoginHandlerTest extends TestCase { ...@@ -91,47 +85,6 @@ final class LoginHandlerTest extends TestCase {
$this->assertEquals('http://rap-ia2/confirm-join', $redirect); $this->assertEquals('http://rap-ia2/confirm-join', $redirect);
} }
public function testJoinExistingUser(): void {
$user = $this->getFakeUser();
$this->sessionStub->method('getUser')->willReturn($user);
$this->sessionStub->method('getAction')->willReturn('join');
$userToJoin = $this->getFakeUser();
$userToJoin->id = '456';
$redirect = $this->loginHandler->getAfterLoginRedirect($userToJoin);
$this->assertEquals('http://rap-ia2/account', $redirect);
}
public function testJoinNewIdentity(): void {
$user = $this->getFakeUser();
$this->sessionStub->method('getUser')->willReturn($user);
$this->sessionStub->method('getAction')->willReturn('join');
$userToJoin = $this->getFakeUser();
// new identity
$userToJoin->id = null;
$redirect = $this->loginHandler->getAfterLoginRedirect($userToJoin);
$this->assertEquals('http://rap-ia2/account', $redirect);
}
public function testJoinAlreadyJoinedUser(): void { // go to account page without joining
$user = $this->getFakeUser();
$this->sessionStub->method('getAction')->willReturn('join');
$this->userDaoStub->method('findUserByIdentity')->willReturn($user);
$redirect = $this->loginHandler->onIdentityDataReceived($this->getFakeIdentity1());
$this->assertEquals('http://rap-ia2/account', $redirect);
}
private function getFakeUser(): \RAP\User { private function getFakeUser(): \RAP\User {
$user = new \RAP\User(); $user = new \RAP\User();
...@@ -152,14 +105,6 @@ final class LoginHandlerTest extends TestCase { ...@@ -152,14 +105,6 @@ final class LoginHandlerTest extends TestCase {
return $identity; return $identity;
} }
private function getFakeIdentity1NewEmail(): \RAP\Identity {
$identity = new \RAP\Identity(\RAP\Identity::EDU_GAIN);
$identity->typedId = '123';
$identity->email = 'test2@example.com';
return $identity;
}
private function getFakeIdentity2(): \RAP\Identity { private function getFakeIdentity2(): \RAP\Identity {
$identity = new \RAP\Identity(\RAP\Identity::EDU_GAIN); $identity = new \RAP\Identity(\RAP\Identity::EDU_GAIN);
......
2.0.2 2.0.3
...@@ -6,9 +6,16 @@ include 'include/header.php'; ...@@ -6,9 +6,16 @@ include 'include/header.php';
<div class="text-center"> <div class="text-center">
<?php if ($autojoin) { ?> <?php if ($autojoin) { ?>
<h3>Multiple accounts detected</h3><br/> <h3>Multiple accounts detected</h3><br/>
<p>The system found multiple accounts associated with the same e-mail address. We suggest you to join them, so that <div class="alert alert-info">
they will be seen as a single user. If you prefer to keep these accounts separated you can click on "Reject join" button. <p>
<span class="glyphicon glyphicon-info-sign"></span>
The system found multiple accounts associated with the same e-mail address. We <strong>suggest</strong> you to join them, so that
they will be seen as a single user.<br/><br/>
If you prefer to keep these accounts separated you can click on "Reject join" button.
If you reject the join we will not show this message again for the same account pair, however you can still join the accounts manually
(see the <a href="https://sso.ia2.inaf.it/home/index.php?lang=en" target="blank_">Help Page</a> for more information).
</p> </p>
</div>
<?php } else { ?> <?php } else { ?>
<h3>Following identities will be joined:</h3><br/> <h3>Following identities will be joined:</h3><br/>
<?php } ?> <?php } ?>
...@@ -45,9 +52,15 @@ include 'include/header.php'; ...@@ -45,9 +52,15 @@ include 'include/header.php';
<div class="text-center"> <div class="text-center">
<br/> <br/>
<form action="confirm-join" method="POST"> <form action="confirm-join" method="POST" class="inline">
<input type="submit" value="Confirm join" class="btn btn-success btn-lg" /> <input type="submit" value="Confirm join" class="btn btn-success btn-lg" />
</form> </form>
<?php if ($autojoin) { ?>
&nbsp;
<form action="reject-join" method="POST" class="inline">
<input type="submit" value="Reject join" class="btn btn-danger btn-lg" />
</form></form>
<?php } ?>
<br/><br/><br/> <br/><br/><br/>
</div> </div>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment