diff --git a/auth/oauth2/linkedin_login.php b/auth/oauth2/linkedin_login.php new file mode 100644 index 0000000000000000000000000000000000000000..df940e81b57d84b22a99132e701924b203e30d74 --- /dev/null +++ b/auth/oauth2/linkedin_login.php @@ -0,0 +1,37 @@ +<?php + +/* ---------------------------------------------------------------------------- + * INAF - National Institute for Astrophysics + * IRA - Radioastronomical Institute - Bologna + * OATS - Astronomical Observatory - Trieste + * ---------------------------------------------------------------------------- + * + * Copyright (C) 2016 Istituto Nazionale di Astrofisica + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License Version 3 as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +include '../../include/init.php'; +startSession(); + +$LinkedIn = $AUTHENTICATION_METHODS['LinkedIn']; + +$url = "https://www.linkedin.com/oauth/v2/authorization?response_type=code"; +$url .= "&client_id=" . $LinkedIn['id']; +$url .= "&redirect_uri=" . $LinkedIn['callback']; +$url .= "&state=789654123"; +$url .= "&scope=r_basicprofile r_emailaddress"; + +header("Location: $url"); +?> diff --git a/auth/oauth2/linkedin_token.php b/auth/oauth2/linkedin_token.php new file mode 100644 index 0000000000000000000000000000000000000000..d4e88e4732f6b26fe3d3bc8ec850ede492fcc814 --- /dev/null +++ b/auth/oauth2/linkedin_token.php @@ -0,0 +1,124 @@ +<?php + +/* ---------------------------------------------------------------------------- + * INAF - National Institute for Astrophysics + * IRA - Radioastronomical Institute - Bologna + * OATS - Astronomical Observatory - Trieste + * ---------------------------------------------------------------------------- + * + * Copyright (C) 2016 Istituto Nazionale di Astrofisica + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License Version 3 as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +include '../../include/init.php'; +startSession(); + +$LinkedIn = $AUTHENTICATION_METHODS['LinkedIn']; + +if (!isset($_REQUEST['code'])) { + die("Unable to get LinkedIn client code"); +} + +//create array of data to be posted to get AccessToken +$post_data = array( + 'grant_type' => "authorization_code", + 'code' => $_REQUEST['code'], + 'redirect_uri' => $LinkedIn['callback'], + 'client_id' => $LinkedIn['id'], + 'client_secret' => $LinkedIn['secret']); + +//traverse array and prepare data for posting (key1=value1) +foreach ($post_data as $key => $value) { + $post_items[] = $key . '=' . $value; +} + +//create the final string to be posted +$post_string = implode('&', $post_items); + +//create cURL connection +$conn1 = curl_init('https://www.linkedin.com/oauth/v2/accessToken'); + +//set options +curl_setopt($conn1, CURLOPT_CONNECTTIMEOUT, 30); +curl_setopt($conn1, CURLOPT_RETURNTRANSFER, true); +curl_setopt($conn1, CURLOPT_SSL_VERIFYPEER, true); +curl_setopt($conn1, CURLOPT_FOLLOWLOCATION, 1); + +//set data to be posted +curl_setopt($conn1, CURLOPT_POSTFIELDS, $post_string); + +//perform our request +$result1 = curl_exec($conn1); + +if ($result1) { + $my_token = json_decode($result1, TRUE); + $access_token = $my_token['access_token']; + $expires_in = $my_token['expires_in']; + curl_close($conn1); +} else { + //show information regarding the error + $errorMessage = curl_errno($conn1) . "-"; + $errorMessage = $errorMessage . curl_error($conn1); + curl_close($conn1); + die($errorMessage); +} + +// Call to API +$conn2 = curl_init(); +curl_setopt($conn2, CURLOPT_URL, "https://api.linkedin.com/v1/people/~:(first-name,last-name,email-address,id)?format=json"); +curl_setopt($conn2, CURLOPT_HTTPHEADER, array( + 'Authorization: Bearer ' . $access_token +)); + +curl_setopt($conn2, CURLOPT_RETURNTRANSFER, true); +$result = curl_exec($conn2); + +if ($result) { + $data = json_decode($result, TRUE); + + curl_close($conn2); + + if (isset($data['errorCode'])) { + $errorMessage = $data['message']; + die($errorMessage); + } + + $typedId = $data['id']; + + $user = $userHandler->findUserByIdentity(RAP\Identity::LINKEDIN, $typedId); + + if ($user === null) { + $user = new RAP\User(); + + $identity = new RAP\Identity(RAP\Identity::LINKEDIN); + $identity->email = $data['emailAddress']; + $identity->name = $data['firstName']; + $identity->surname = $data['lastName']; + $identity->typedId = $typedId; + + $user->addIdentity($identity); + + $userHandler->saveUser($user); + } + + $callbackHandler->manageLoginRedirect($user, $session); +} else { + //show information regarding the error + $errorMessage = curl_errno($conn2) . "-"; + $errorMessage = $errorMessage . curl_error($conn2); + curl_close($conn2); + die($errorMessage); +} +?> diff --git a/classes/CallbackHandler.php b/classes/CallbackHandler.php index a569844e481c03492c11043410941c4e402b3aa9..bd48a0d8d8df9612cb8dc09c9496aa6998ae839c 100644 --- a/classes/CallbackHandler.php +++ b/classes/CallbackHandler.php @@ -39,7 +39,7 @@ class CallbackHandler { /** * returns null if the callback URL is not listed in configuration file. */ - public static function getCallbackTitle($callbackURL) { + public function getCallbackTitle($callbackURL) { if ($callbackURL === null) { return "Account Management"; @@ -58,16 +58,22 @@ class CallbackHandler { if ($session->getCallbackURL() !== null) { // External login using token - $token = Util::createNewToken(); - $this->dao->createLoginToken($token, $user->id); - header('Location: ' . $session->getCallbackURL() . '?token=' . $token); + header('Location: ' . $this->getLoginWithTokenURL($user->id, $session->getCallbackURL())); + die(); } else { // Login in session $session->user = $user; $session->save(); // Return to index header('Location: ' . $this->basePath); + die(); } } + public function getLoginWithTokenURL($userId, $callbackURL) { + $token = Util::createNewToken(); + $this->dao->createLoginToken($token, $userId); + return $callbackURL . '?token=' . $token; + } + } diff --git a/classes/MailSender.php b/classes/MailSender.php index cede88a89c79eac54a6a0460884206e0bf49d2f0..606743db11c1d66ec77371851a9558420a7ae7cf 100644 --- a/classes/MailSender.php +++ b/classes/MailSender.php @@ -26,8 +26,60 @@ namespace RAP; class MailSender { - public static function sendJoinEmail(User $recipientUser, User $applicantUser) { - + private $serverName; + private $basePath; + + public function __construct($serverName, $basePath) { + $this->serverName = $serverName; + $this->basePath = $basePath; + } + + public function sendJoinEmail(User $recipientUser, User $applicantUser, $token) { + + $subject = "IA2 RAP: Join request"; + + $header = "From: noreply@" . $this->serverName . "\r\n"; + $header .= "Content-Type: text/html; charset=UTF-8"; + + $confirmJoinURL = $this->basePath . '/confirm-join?token=' . $token; + + $body = "Dear IA2 user,<br/><br/>"; + $body .= "the following user requested to join your accounts on the " + . "<a href=\"https://sso.ia2.inaf.it/rap-ia2/\" target=\"blank_\">RAP facility</a>:<br/><br/>"; + + foreach ($applicantUser->identities as $identity) { + + $body .= "<b>Type</b>: " . $identity->type . "<br/>"; + + if ($identity->name !== null) { + $body .= "<b>Name</b>: " . $identity->name . "<br/>"; + } + + if ($identity->surname !== null) { + $body .= "<b>Surname</b>: " . $identity->surname . "<br/>"; + } + + $body .= "<b>E-mail</b>: " . $identity->email . "<br/>"; + + if ($identity->eppn !== null) { + $body .= "<b>Eppn</b>: " . $identity->eppn . "<br/>"; + } + + if ($identity->institution !== null) { + $body .= "<b>Institution</b>: " . $identity->institution . "<br/>"; + } + + $body .= "<br/>"; + } + + $body .= "<br/>If you and this user are the same person click on the following link for joining your accounts:<br/>"; + $body .= "<a href=\"$confirmJoinURL\" target=\"blank_\">$confirmJoinURL</a>"; + $body .= "<br/><br/>Otherwise you can ignore this email<br/><br/>"; + + $body .= "<b>*** This is an automatically generated email, please do not reply to this message ***</b><br/>"; + $body .= "If you need information please contact <a href=\"mailto:ia2@oats.inaf.it\">IA2 Staff</a>"; + + mail($recipientUser->getPrimaryEmail(), $subject, $body, $header); } } diff --git a/classes/MySQLDAO.php b/classes/MySQLDAO.php index 536882d47c411dcdc51e81a8953d0cfb19ecaa0f..c5c5d9f215ba9919e938061f90613925708f39bf 100644 --- a/classes/MySQLDAO.php +++ b/classes/MySQLDAO.php @@ -246,6 +246,10 @@ class MySQLDAO implements DAO { public function createJoinRequest($token, $applicantUserId, $targetUserId) { + if($applicantUserId === $targetUserId) { + throw new \Exception("Invalid target user id"); + } + $dbh = $this->getDBHandler(); $stmt = $dbh->prepare("INSERT INTO `join_request`(`token`, `applicant_user_id`, `target_user_id`)" diff --git a/config.php b/config.php index 840befa7a49d356922dc238608aef6059e698396..e13a8c36da0d8c6edeb21eddcd5b5f1a8ca7bbf2 100644 --- a/config.php +++ b/config.php @@ -22,7 +22,7 @@ * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -$CONTEXT_ROOT = "/rap-service"; +$CONTEXT_ROOT = "/rap-ia2"; $VERSION = "1.0.0"; $PROTOCOL = stripos($_SERVER['SERVER_PROTOCOL'], 'https') ? 'https://' : 'http://'; @@ -58,12 +58,15 @@ $AUTHENTICATION_METHODS = array( 'secret' => "***REMOVED***", 'version' => "v2.2", 'callback' => $BASE_PATH . "/auth/oauth2/facebook_token.php"), - 'LinkedIn' => array(), + 'LinkedIn' => array( + 'id' => '***REMOVED***', + 'secret' => '***REMOVED***', + 'callback' => $BASE_PATH . '/auth/oauth2/linkedin_token.php' + ), 'X.509' => array(), ); $GROUPER = array( - //'serviceBaseURL' => 'https://sso.ia2.inaf.it/grouper-ws/servicesRest', 'wsdlURL' => 'http://localhost:8087/grouper-ws/services/GrouperService_v2_3?wsdl', 'user' => 'GrouperSystem', 'password' => '***REMOVED***' diff --git a/include/front-controller.php b/include/front-controller.php index f1996e20b225a4733acf0a7693ede42d2ed0d450..2c94ee4e4c1fe1b3161d1109bc6344933830747e 100644 --- a/include/front-controller.php +++ b/include/front-controller.php @@ -12,14 +12,20 @@ function setCallback() { $callback = Flight::request()->data['callback']; $session->setCallbackURL($callbackHandler, isset($callback) ? $callback : null); + return $session->getCallbackURL(); } Flight::route('/', function() { startSession(); - setCallback(); - global $session, $AUTHENTICATION_METHODS; - Flight::render('index.php', array('title' => 'RAP', - 'session' => $session, 'auth' => $AUTHENTICATION_METHODS)); + $callback = setCallback(); + global $session, $callbackHandler, $AUTHENTICATION_METHODS; + if ($callback !== null && $session->user !== null) { + $redirectURL = $callbackHandler->getLoginWithTokenURL($session->user->id, $callback); + Flight::redirect($redirectURL); + } else { + Flight::render('index.php', array('title' => 'RAP', + 'session' => $session, 'auth' => $AUTHENTICATION_METHODS)); + } }); Flight::route('GET /logout', function() { @@ -38,6 +44,11 @@ Flight::route('/facebook', function() { Flight::redirect('/auth/oauth2/facebook_login.php'); }); +Flight::route('/linkedIn', function() { + startSession(); + Flight::redirect('/auth/oauth2/linkedin_login.php'); +}); + Flight::route('/eduGAIN', function() { startSession(); Flight::redirect('/auth/saml2/aai.php'); diff --git a/include/gui-backend.php b/include/gui-backend.php index 0d1b28115e83564a3292c5c89f05cd3e2d5238bc..d29b905fe532b7216657c4a4c9e8b60dbffc7eae 100644 --- a/include/gui-backend.php +++ b/include/gui-backend.php @@ -35,7 +35,7 @@ Flight::route('GET /user', function() { Flight::route('POST /join', function() { checkSession(); - global $session, $dao; + global $session, $dao, $mailSender; $selectedUserIndex = Flight::request()->data['selectedUserIndex']; $selectedSearchResult = $session->userSearchResults[$selectedUserIndex]; @@ -43,7 +43,7 @@ Flight::route('POST /join', function() { $token = RAP\Util::createNewToken(); $dao->createJoinRequest($token, $session->user->id, $targetUserId); - RAP\MailSender::sendJoinEmail($selectedSearchResult->getUser(), $session->user); + $mailSender->sendJoinEmail($selectedSearchResult->getUser(), $session->user, $token); echo $selectedSearchResult->userDisplayText; }); diff --git a/include/init.php b/include/init.php index dccbb58e6b4d7750e32089660ae98e95ba800940..28f658751c58c669460ffbc4ce90b422132fe4dc 100644 --- a/include/init.php +++ b/include/init.php @@ -55,6 +55,7 @@ switch ($DATABASE['dbtype']) { $callbackHandler = new RAP\CallbackHandler($dao, $BASE_PATH, $CALLBACKS); $userHandler = new RAP\UserHandler($dao, $GROUPER); +$mailSender = new RAP\MailSender($_SERVER['HTTP_HOST'], $BASE_PATH); function startSession() { session_start(); diff --git a/include/rest-web-service.php b/include/rest-web-service.php index 27aca4c81db10b482bead590619a21964df4c798..64d5880d2b2d67361de87fadbf968a65ce67c532 100644 --- a/include/rest-web-service.php +++ b/include/rest-web-service.php @@ -41,11 +41,47 @@ Flight::route('GET ' . $WS_PREFIX . '/user/@userId', function($userId) { Flight::route('GET ' . $WS_PREFIX . '/user', function() { global $dao; - + $searchText = Flight::request()->query['search']; $users = $dao->searchUser($searchText); echo json_encode($users); }); +/** + * Create new user from identity data. Return the new user. + */ +Flight::route('POST ' . $WS_PREFIX . '/user', function() { + + global $userHandler; + + $postData = Flight::request()->data; + + $user = new RAP\User(); + + $identity = new RAP\Identity($postData['type']); + + $identity->email = $postData['email']; + $identity->typedId = $postData['typedId']; + if (isset($postData['name'])) { + $identity->name = $postData['name']; + } + if (isset($postData['surname'])) { + $identity->surname = $postData['surname']; + } + if (isset($postData['institution'])) { + $identity->institution = $postData['institution']; + } + if (isset($postData['eppn'])) { + $identity->eppn = $postData['eppn']; + } + + $user->addIdentity($identity); + + $userHandler->saveUser($user); + + echo json_encode($user); +}); + Flight::route('GET ' . $WS_PREFIX . '/test', function() { + }); diff --git a/include/user-data.php b/include/user-data.php index 92219036c1c580bd6daa1160edd0bbce14a5a806..ca50bf0e63b38751f079e870ad25ee260b2382f6 100644 --- a/include/user-data.php +++ b/include/user-data.php @@ -1,16 +1,18 @@ <?php foreach ($user->identities as $identity) { ?> <dl class="dl-horizontal"> <dt> - <?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 $identity->id; ?>);"> - <span class="glyphicon glyphicon-star-empty"></span> - </a> - </span> + <?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 $identity->id; ?>);"> + <span class="glyphicon glyphicon-star-empty"></span> + </a> + </span> + <?php } ?> <?php } ?> Type </dt> @@ -18,7 +20,7 @@ <dt>E-mail</dt> <dd><?php echo $identity->email; ?></dd> <?php if ($identity->eppn !== null) { ?> - <dt>EduPersonPrincipalName</dt> + <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) { ?> diff --git a/js/index.js b/js/index.js index 7ca705780f22db0aa06120a4857979892095b30f..a55d573df947a3c6262da841bc5a46520f3e69fb 100644 --- a/js/index.js +++ b/js/index.js @@ -63,6 +63,9 @@ // Add click event handler to join request button $(document).on('click', '#send-join-request-btn', sendJoinRequest); + // Add event handler for closing the info alert message. + // This is used instead of data-dismiss="alert" in order to maintain + // the alert inside the DOM and be able to show it again if necessary. $(document).on('click', '#info-message-alert .close', function () { $('#info-message-alert').addClass('hide'); }); diff --git a/views/confirm-join.php b/views/confirm-join.php index aafd5fda9fdbd7f54655209097d6796ffdb2f783..15372a68b3f3a44521ad776bd127cde16db60a2a 100644 --- a/views/confirm-join.php +++ b/views/confirm-join.php @@ -12,6 +12,7 @@ include 'include/header.php'; <div class="panel-body"> <?php $user = $applicantUser; + $readOnly = true; include 'include/user-data.php'; ?> </div> @@ -25,6 +26,7 @@ include 'include/header.php'; <div class="panel-body"> <?php $user = $targetUser; + $readOnly = true; include 'include/user-data.php'; ?> </div>