diff --git a/classes/UserHandler.php b/classes/UserHandler.php index f744588c663e72c52140bd92468c62f2036ff268..1963a2223a789b3ab7c9df1751a1b88acc9abbb0 100644 --- a/classes/UserHandler.php +++ b/classes/UserHandler.php @@ -95,8 +95,11 @@ class UserHandler { //show information regarding the error curl_close($conn); error_log($response); - http_response_code(500); - die('Error: GMS response code: ' . $info['http_code'] . "\n"); + $httpCode = $info['http_code']; + if ($httpCode === 0) { + throw new ServerErrorException('GMS service is unreachable'); + } + throw new ServerErrorException('Error: GMS response code: ' . $httpCode); } } diff --git a/classes/datalayer/mysql/MySQLUserDAO.php b/classes/datalayer/mysql/MySQLUserDAO.php index cd533542abdb6f8756eb1ae8574ab10c1fcf5fa5..02f8b8a86940050cc949c9fb77d33e48ffbb6f02 100644 --- a/classes/datalayer/mysql/MySQLUserDAO.php +++ b/classes/datalayer/mysql/MySQLUserDAO.php @@ -37,8 +37,8 @@ class MySQLUserDAO extends BaseMySQLDAO implements UserDAO { $dbh = $this->getDBHandler(); - $stmt = $dbh->prepare("INSERT INTO identity(`user_id`, `type`, `email`, `name`, `surname`, `institution`, `typed_id`, `eppn`)" - . " VALUES(:user_id, :type, :email, :name, :surname, :institution, :typed_id, :eppn)"); + $stmt = $dbh->prepare("INSERT INTO identity(`user_id`, `type`, `email`, `name`, `surname`, `institution`, `typed_id`, `eppn`, `last_login`)" + . " VALUES(:user_id, :type, :email, :name, :surname, :institution, :typed_id, :eppn, NOW())"); $stmt->bindParam(':user_id', $userId); $stmt->bindParam(':type', $identity->type); @@ -288,8 +288,8 @@ class MySQLUserDAO extends BaseMySQLDAO implements UserDAO { $dbh = $this->getDBHandler(); - $query = "UPDATE identity SET email = :email, name = :name, surname = :surname, institution = :institution" - . " WHERE id = :id"; + $query = "UPDATE identity SET email = :email, name = :name, surname = :surname, institution = :institution," + . " last_login = NOW() WHERE id = :id"; $stmt = $dbh->prepare($query); $stmt->bindParam(':email', $identity->email); diff --git a/classes/exceptions/ServerErrorException.php b/classes/exceptions/ServerErrorException.php new file mode 100644 index 0000000000000000000000000000000000000000..9aeb3d646e044aa48d3731931336f6e8c10d5ee6 --- /dev/null +++ b/classes/exceptions/ServerErrorException.php @@ -0,0 +1,13 @@ +<?php + +namespace RAP; + +class ServerErrorException extends \Exception { + + public $message; + + public function __construct($message) { + $this->message = $message; + } + +} diff --git a/classes/login/LoginHandler.php b/classes/login/LoginHandler.php index 8403f655ac960758083f97a03c4dd45804a6305b..5232ae0100c4a7eb7a4e92192aae6af5aacfaeb8 100644 --- a/classes/login/LoginHandler.php +++ b/classes/login/LoginHandler.php @@ -10,6 +10,7 @@ class LoginHandler { public function __construct(Locator $locator, string $identityType) { $this->locator = $locator; $this->identityType = $identityType; + $this->locator->getSession()->setLoginIdentityType($identityType); } public function onIdentityDataReceived(string $typedId, \Closure $fillIdentityData): string { @@ -22,6 +23,12 @@ class LoginHandler { $this->updateUser($user, $typedId, $fillIdentityData); } + $session = $this->locator->getSession(); + if ($session->getOAuth2RequestData() === null && $session->getAction() === 'join' && + $session->getUser() !== null && $session->getUser()->id !== $user->id) { + return $this->showConfirmJoin($user); + } + return $this->getAfterLoginRedirect($user); } @@ -30,25 +37,16 @@ class LoginHandler { $session = $this->locator->getSession(); if ($session->getUser() !== null && $session->getAction() === 'join') { - return $this->joinToPreviousUser($session->getUser(), $typedId, $fillIdentityData); + $userToJoin = $this->getNewUser($typedId, $fillIdentityData); + return $this->showConfirmJoin($userToJoin); } else { return $this->redirectToTOUCheck($typedId, $fillIdentityData); } } - private function joinToPreviousUser(User $user, string $typedId, \Closure $fillIdentityData): string { - - $identity = new Identity($this->identityType); - $identity->typedId = $typedId; - $fillIdentityData($identity); - - $user->addIdentity($identity); - - $this->locator->getUserHandler()->saveUser($user); - - $this->locator->getSession()->setUser($user); - - return $this->getAfterLoginRedirect($user); + private function showConfirmJoin(User $userToJoin): string { + $this->locator->getSession()->setUserToJoin($userToJoin); + return $this->locator->getBasePath() . '/confirm-join'; } /** @@ -57,19 +55,25 @@ class LoginHandler { private function redirectToTOUCheck(string $typedId, \Closure $fillIdentityData): string { // Create new user - $user = new \RAP\User(); + $user = $this->getNewUser($typedId, $fillIdentityData); + + $this->locator->getSession()->setUser($user); + + return $this->locator->getBasePath() . '/tou-check'; + } + private function getNewUser(string $typedId, \Closure $fillIdentityData): User { + $user = new User(); $identity = new Identity($this->identityType); $identity->typedId = $typedId; $fillIdentityData($identity); - $user->addIdentity($identity); - - $this->locator->getSession()->setUser($user); - - return $this->locator->getBasePath() . '/tou-check'; + return $user; } + /** + * Update user with fresh information received by IdP. Useful for keeping email address always updated. + */ private function updateUser(User $user, string $typedId, \Closure $fillIdentityData): void { $identity = $user->getIdentityByTypedId($typedId); $fillIdentityData($identity); @@ -93,11 +97,7 @@ class LoginHandler { $action = $session->getAction(); if ($action === 'join') { - if ($session->getUser()->id !== $user->id) { - $user = $this->locator->getUserHandler()->joinUsers($session->getUser(), $user); - } - - // the join is completed + $user = $this->joinTo($user); $action = 'account'; $session->setAction($action); } @@ -112,4 +112,28 @@ class LoginHandler { 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; + } + } diff --git a/classes/model/SessionData.php b/classes/model/SessionData.php index a560b6a03214691bcf9e194714372e5c0889f29c..54806aeb301ec26c92d413d3f7000b714285d46d 100644 --- a/classes/model/SessionData.php +++ b/classes/model/SessionData.php @@ -33,9 +33,11 @@ class SessionData { const KEY = "SessionData"; private $user; + private $userToJoin; private $x509DataToRegister; private $oauth2RequestData; private $action; + private $loginIdentityType; public function setUser(?User $user): void { $this->user = $user; @@ -46,6 +48,24 @@ class SessionData { return $this->user; } + public function setUserToJoin(?User $userToJoin): void { + $this->userToJoin = $userToJoin; + $this->save(); + } + + public function getUserToJoin(): ?User { + return $this->userToJoin; + } + + public function setLoginIdentityType(string $loginIdentityType): void { + $this->loginIdentityType = $loginIdentityType; + $this->save(); + } + + public function getLoginIdentityType(): ?string { + return $this->loginIdentityType; + } + /** * Update the user data model stored into the session after the primary * identity has changed, in order to avoid reading again the user data from diff --git a/include/front-controller.php b/include/front-controller.php index 540a16d929acb033c2ab0fb829c5e3870bc606f3..135852847a09f260b3b54d0537edcbf04f222eb5 100644 --- a/include/front-controller.php +++ b/include/front-controller.php @@ -290,6 +290,35 @@ Flight::route('GET /tou-check', function() { } }); +Flight::route('GET /confirm-join', function() { + + session_start(); + global $locator; + + if ($locator->getSession()->getUser() === null) { + die("User data not retrieved."); + } else { + Flight::render('confirm-join.php', array('title' => 'Confirm join', + 'user' => $locator->getSession()->getUser(), + 'user_to_join' => $locator->getSession()->getUserToJoin(), + 'version' => $locator->getVersion())); + } +}); + +Flight::route('POST /confirm-join', function() { + + session_start(); + global $locator; + + $user = $locator->getSession()->getUserToJoin(); + if ($user === null) { + die("Unable to find user to join"); + } else { + $loginHandler = new \RAP\LoginHandler($locator, $locator->getSession()->getLoginIdentityType()); + Flight::redirect($loginHandler->getAfterLoginRedirect($user)); + } +}); + /** * Stores the user data into the database after he/she accepted the Terms of Use. */ @@ -305,7 +334,7 @@ Flight::route('GET /register', function() { } else { $locator->getUserHandler()->saveUser($user); - $loginHandler = new \RAP\LoginHandler($locator, $user->identities[0]->type); + $loginHandler = new \RAP\LoginHandler($locator, $locator->getSession()->getLoginIdentityType()); Flight::redirect($loginHandler->getAfterLoginRedirect($user)); } }); diff --git a/include/identities.php b/include/identities.php new file mode 100644 index 0000000000000000000000000000000000000000..db5355b21eb7d14857f8cef739f8e037fe4b6a09 --- /dev/null +++ b/include/identities.php @@ -0,0 +1,40 @@ +<?php /** + * This fragment represent a panel containing information about an identity. + */ ?> +<dl class="dl-horizontal"> + <dt> + <?php if (!isset($readOnly)) { ?> + <?php if ($identity->primary) { ?> + <span class="primary-identity-icon" data-toggle="tooltip" data-placement="left" title="This is your primary identity. You will receive email messages on the address related to this identity."> + <span class="glyphicon glyphicon-star"></span> + </span> + <?php } else { ?> + <span class="primary-identity-icon" data-toggle="tooltip" data-placement="left" title="Click on this icon to set this as the primary identity"> + <a href="#" onclick="setPrimaryIdentity(<?php echo $i; ?>);"> + <span class="glyphicon glyphicon-star-empty"></span> + </a> + </span> + <?php } ?> + <?php } ?> + Type + </dt> + <dd><?php echo $identity->getUIType(); ?></dd> + <dt>E-mail</dt> + <dd><?php echo $identity->email; ?></dd> + <?php if ($identity->eppn !== null) { ?> + <dt><abbr title="EduPerson Principal Name, an unique identifier used into federations.">EPPN</abbr></dt> + <dd><?php echo $identity->eppn; ?></dd> + <?php } ?> + <?php if ($identity->name !== null) { ?> + <dt>Name</dt> + <dd><?php echo $identity->name; ?></dd> + <?php } ?> + <?php if ($identity->surname !== null) { ?> + <dt>Surname</dt> + <dd><?php echo $identity->surname; ?></dd> + <?php } ?> + <?php if ($identity->institution !== null) { ?> + <dt>Institution</dt> + <dd><?php echo $identity->institution; ?></dd> + <?php } ?> +</dl> diff --git a/include/user-data.php b/include/user-data.php index 87df4a1dff27a7b5b4bc8cad4507e2f33b80700f..53edced0d588cde82c3adae6f730a7879a77e29d 100644 --- a/include/user-data.php +++ b/include/user-data.php @@ -5,45 +5,7 @@ */ $i = 0; // identity index foreach ($user->identities as $identity) { - ?> - <dl class="dl-horizontal"> - <dt> - <?php if (!isset($readOnly)) { ?> - <?php if ($identity->primary) { ?> - <span class="primary-identity-icon" data-toggle="tooltip" data-placement="left" title="This is your primary identity. You will receive email messages on the address related to this identity."> - <span class="glyphicon glyphicon-star"></span> - </span> - <?php } else { ?> - <span class="primary-identity-icon" data-toggle="tooltip" data-placement="left" title="Click on this icon to set this as the primary identity"> - <a href="#" onclick="setPrimaryIdentity(<?php echo $i; ?>);"> - <span class="glyphicon glyphicon-star-empty"></span> - </a> - </span> - <?php } ?> - <?php } ?> - Type - </dt> - <dd><?php echo $identity->getUIType(); ?></dd> - <dt>E-mail</dt> - <dd><?php echo $identity->email; ?></dd> - <?php if ($identity->eppn !== null) { ?> - <dt><abbr title="EduPerson Principal Name, an unique identifier used into federations.">EPPN</abbr></dt> - <dd><?php echo $identity->eppn; ?></dd> - <?php } ?> - <?php if ($identity->name !== null) { ?> - <dt>Name</dt> - <dd><?php echo $identity->name; ?></dd> - <?php } ?> - <?php if ($identity->surname !== null) { ?> - <dt>Surname</dt> - <dd><?php echo $identity->surname; ?></dd> - <?php } ?> - <?php if ($identity->institution !== null) { ?> - <dt>Institution</dt> - <dd><?php echo $identity->institution; ?></dd> - <?php } ?> - </dl> - <?php + include 'identities.php'; $i++; } ?> diff --git a/index.php b/index.php index 3f0546170bc2db4d68382bc9d1e924d5caf63cf5..451735075de5c6d22d135181e422840fc085d790 100644 --- a/index.php +++ b/index.php @@ -34,26 +34,31 @@ Flight::set('flight.log_errors', true); Flight::map('error', function($ex) { if ($ex instanceof \Exception) { error_log($ex->getTraceAsString()); + } else { + http_response_code(500); + throw $ex; } + $message = "A fatal error happened"; if ($ex instanceof \RAP\BadRequestException) { http_response_code(400); - echo "Bad request: " . $ex->message; + $message = "Bad request: " . $ex->message; } else if ($ex instanceof \RAP\UnauthorizedException) { http_response_code(401); - echo "Unauthorized: " . $ex->message; + $message = "Unauthorized: " . $ex->message; } else if ($ex instanceof \Exception) { http_response_code(500); if ($ex->getMessage() !== null) { - echo $ex->getMessage(); - } else { - echo $ex->getTraceAsString(); + $message = $ex->getMessage(); } - } else { - http_response_code(500); - throw $ex; } + + global $locator; + Flight::render('error.php', array('title' => 'Error', + 'version' => $locator->getVersion(), 'error' => $message, + 'contactEmail' => isset($locator->config->contactEmail) ? $locator->config->contactEmail : null, + 'contactLabel' => isset($locator->config->contactLabel) ? $locator->config->contactLabel : null, + 'contextRoot' => $locator->config->contextRoot)); }); // Starting Flight framework Flight::start(); - diff --git a/sql/setup-database.sql b/sql/setup-database.sql index 6e8b7f6e3b5e9ddb6a6b021114f7791c52aad3d3..e87e1960abd77d730a3daba80ff7f6dd3397ad5f 100644 --- a/sql/setup-database.sql +++ b/sql/setup-database.sql @@ -51,9 +51,12 @@ CREATE TABLE `identity` ( `institution` varchar(255) DEFAULT NULL, `eppn` varchar(255) DEFAULT NULL, `tou_accepted` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `last_login` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', PRIMARY KEY (`id`), FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +ALTER TABLE identity ADD CONSTRAINT eppn_unique UNIQUE(eppn); +ALTER TABLE identity ADD CONSTRAINT typed_id_unique UNIQUE(typed_id); SET FOREIGN_KEY_CHECKS=0; ALTER TABLE `user` ADD FOREIGN KEY (`primary_identity`) REFERENCES `identity`(`id`); diff --git a/views/account-management.php b/views/account-management.php index ab8db20613d18b22f32e65ed57f20d158c57f335..39712aba8c5797b11af5818f15d1232a6d90a8d6 100644 --- a/views/account-management.php +++ b/views/account-management.php @@ -20,7 +20,7 @@ include 'include/header.php'; <div class="col-sm-2"> <div class="row"> <div class="col-sm-12"> - <a class="btn btn-success disabled" id="join-btn" href="<?php echo $contextRoot; ?>?action=join" title="Perform an additional login to join your identities" data-toggle="tooltip" data-placement="bottom"> + <a class="btn btn-success" id="join-btn" href="<?php echo $contextRoot; ?>?action=join" title="Perform an additional login to join your identities" data-toggle="tooltip" data-placement="bottom"> Join with another identity </a> </div> diff --git a/views/confirm-join.php b/views/confirm-join.php new file mode 100644 index 0000000000000000000000000000000000000000..7eb2095228ab58a6e6a6d68f2748bb4e6d66fb56 --- /dev/null +++ b/views/confirm-join.php @@ -0,0 +1,48 @@ +<?php +include 'include/header.php'; +?> + +<br/> +<div class="text-center"> + <h3>Following identities will be joined:</h3><br/> +</div> + +<div class="row"> + <div class="col-xs-12 col-md-6"> + <h4>User id: <?php echo $user->id; ?></h4> + <div class="panel"> + <div class="panel-body"> + <?php + $readOnly = true; + include 'include/user-data.php'; + ?> + </div> + </div> + </div> + <div class="col-xs-12 col-md-6"> + <h4><?php echo $user_to_join->id === null ? ' ' : ('User id: ' . $user_to_join->id); ?></h4> + <div class="panel"> + <div class="panel-body"> + <?php + $readOnly = true; + $i = 0; // identity index + foreach ($user_to_join->identities as $identity) { + include 'include/identities.php'; + $i++; + } + ?> + </div> + </div> + </div> +</div> + +<div class="text-center"> + <br/> + <form action="confirm-join" method="POST"> + <input type="submit" value="Confirm join" class="btn btn-success btn-lg" /> + </form> + <br/><br/><br/> +</div> + +<?php +include 'include/footer.php'; diff --git a/views/error.php b/views/error.php new file mode 100644 index 0000000000000000000000000000000000000000..d2dc1ead1ed22dfd65dddeefa45e8dfc33e706ea --- /dev/null +++ b/views/error.php @@ -0,0 +1,23 @@ +<?php +include 'include/header.php'; +?> + +<br/> +<h2 class="text-danger">Error</h2> + +<div class="row"> + <div class="col-xs-12"> + <p> + <strong>An error happened:</strong> + <span class="text-danger"><?php echo $error; ?></span> + </p> + <?php if (isset($contactEmail) && isset($contactLabel)) { ?> + <br/> + <p>If you need support please contact <a href="mailto:<?php echo $contactEmail; ?>"><?php echo $contactLabel; ?></a>.</p> + <?php } ?> + <br/><br/><br/><br/> + </div> +</div> + +<?php +include 'include/footer.php'; diff --git a/views/services-list.php b/views/services-list.php index 5929fd778417a487bec07c55f2e284f54e1bcc40..991237a3be08b9f9ec159ce777096f40044f857e 100644 --- a/views/services-list.php +++ b/views/services-list.php @@ -17,7 +17,7 @@ include 'include/header.php'; } ?> <li> - <a href="?action=account">RAP Account Management</a> + <a href="?action=account">RAP Account Management and Join</a> </li> </ul> <br/> diff --git a/views/tou-check.php b/views/tou-check.php index a72f88a223a5b7c1fb0e55785c7d60dbe358ffd8..5672f26aba74b0707514448a3cbcc20c535f15c1 100644 --- a/views/tou-check.php +++ b/views/tou-check.php @@ -27,7 +27,8 @@ include 'include/header.php'; </strong> <div class="checkbox"> <label> - <input type="checkbox" id="tou-ck" /> + <input type="checkbox" id="tou-ck" style="margin-top: -2px" /> + I accept IA2 services Terms of Use. </label> </div>