diff --git a/README.md b/README.md index 2c54c9818133f655b9ad03f8faeac3bd7d827420..e5664c02ad965e2825671dfc3e4f3dc4ceb9826d 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Requirements: On Ubuntu: - sudo apt install apache2 mariadb-server libapache2-mod-php mariadb-server php7.2-xml php7.2-mbstring php-mysql + sudo apt install apache2 mariadb-server libapache2-mod-php mariadb-server php7.2-xml php7.2-mbstring php-mysql php-curl ### PHP diff --git a/classes/login/FacebookLogin.php b/classes/login/FacebookLogin.php index 3b3b9e6779ed99420fcc92cd480dbd0abb614328..f8b85593812dcae799e02380ae34967ed956967d 100644 --- a/classes/login/FacebookLogin.php +++ b/classes/login/FacebookLogin.php @@ -8,7 +8,7 @@ class FacebookLogin extends LoginHandler { parent::__construct($locator, Identity::FACEBOOK); } - public function login() { + public function login(): string { // Retrieve Facebook configuration $Facebook = $this->locator->config->authenticationMethods->Facebook; @@ -25,10 +25,10 @@ class FacebookLogin extends LoginHandler { $loginUrl = $helper->getLoginUrl($Facebook->callback, $permissions); - header("Location: $loginUrl"); + return $loginUrl; } - public function retrieveToken() { + public function retrieveToken(): string { // Retrieve Facebook configuration $Facebook = $this->locator->config->authenticationMethods->Facebook; diff --git a/classes/login/LinkedInLogin.php b/classes/login/LinkedInLogin.php index 7f686ed2efc9bc040da6d8b52315d70d0dd3d3e2..93d3c559e9db969a09a47615f28c1f803bee4d31 100644 --- a/classes/login/LinkedInLogin.php +++ b/classes/login/LinkedInLogin.php @@ -8,7 +8,7 @@ class LinkedInLogin extends LoginHandler { parent::__construct($locator, Identity::FACEBOOK); } - public function login() { + public function login(): string { // Retrieve LinkedIn configuration $LinkedIn = $this->locator->config->authenticationMethods->LinkedIn; @@ -18,10 +18,10 @@ class LinkedInLogin extends LoginHandler { $url .= "&state=789654123"; $url .= "&scope=r_basicprofile r_emailaddress"; - header("Location: $url"); + return $url; } - public function retrieveToken() { + public function retrieveToken(): string { // Retrieve LinkedIn configuration $LinkedIn = $this->locator->config->authenticationMethods->LinkedIn; @@ -100,10 +100,10 @@ class LinkedInLogin extends LoginHandler { $typedId = $data['id']; return $this->onIdentityDataReceived($typedId, function($identity) use($data) { - $identity->email = $data['emailAddress']; - $identity->name = $data['firstName']; - $identity->surname = $data['lastName']; - }); + $identity->email = $data['emailAddress']; + $identity->name = $data['firstName']; + $identity->surname = $data['lastName']; + }); } else { //show information regarding the error $errorMessage = "Error: LinkedIn server response code: " . $info2['http_code'] . " - "; diff --git a/classes/login/OrcidLogin.php b/classes/login/OrcidLogin.php new file mode 100644 index 0000000000000000000000000000000000000000..8568a73e48cbb26494ab732852228270b7860e52 --- /dev/null +++ b/classes/login/OrcidLogin.php @@ -0,0 +1,127 @@ +locator->config->authenticationMethods->OrcID; + + $url = "https://orcid.org/oauth/authorize"; + $url = $url . "?client_id=" . $ORCID->id; + $url = $url . "&response_type=code"; + $url = $url . "&scope=/authenticate"; + $url = $url . "&redirect_uri=" . $this->locator->getBasePath() . $ORCID->callback; + + return $url; + } + + public function retrieveToken($code) { + + if ($code === null) { + throw new BadRequestException("Unable to get ORCID client code"); + } + + $token = $this->getAccessTokenFromCode($code); + + $access_token = $token['access_token']; + $expires_in = $token['expires_in']; + $orcid_id = $username = $token['orcid']; + + $orcid_data = $this->getOrcidDataUsingAccessToken($orcid_id, $access_token); + + if (!isset($orcid_data['person']['emails']['email'][0]['email'])) { + throw new \Exception("ORCID didn't return the email"); + } + + return $this->onIdentityDataReceived($orcid_id, function($identity) use($orcid_data) { + $identity->email = $email = $orcid_data['person']['emails']['email'][0]['email']; + $identity->name = $orcid_data['person']['name']['given-names']['value']; + $identity->surname = $orcid_data['person']['name']['family-name']['value']; + $employmentSummary = $orcid_data['activities-summary']['employments']['employment-summary']; + if (count($employmentSummary) > 0) { + $identity->institution = $employmentSummary[0]['organization']['name']; + } + }); + } + + private function getAccessTokenFromCode($code): array { + + $ORCID = $this->locator->config->authenticationMethods->OrcID; + + //create array of data to be posted to get AccessToken + $post_data = array( + 'grant_type' => "authorization_code", + 'code' => $code, + 'redirect_uri' => $this->locator->getBasePath() . $ORCID->callback, + 'client_id' => $ORCID->id, + 'client_secret' => $ORCID->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 + $conn = curl_init('https://orcid.org/oauth/token'); + + //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); + + //set data to be posted + curl_setopt($conn, CURLOPT_POSTFIELDS, $post_string); + + //perform our request + $result = curl_exec($conn); + + if ($result) { + $token = json_decode($result, TRUE); + curl_close($conn); + return $token; + } else { + //show information regarding the error + $errorMessage = curl_errno($conn) . "-"; + $errorMessage = $errorMessage . curl_error($conn); + curl_close($conn); + throw new \Exception($errorMessage); + } + } + + private function getOrcidDataUsingAccessToken(string $orcid_id, string $access_token) { + + // API call + $orcid_url = "https://pub.orcid.org/v2.1/" . $orcid_id . "/record"; + $conn = curl_init(); + curl_setopt($conn, CURLOPT_URL, $orcid_url); + curl_setopt($conn, CURLOPT_HTTPHEADER, array( + 'Authorization: Bearer ' . $access_token, + 'Accept: application/json')); + + curl_setopt($conn, CURLOPT_RETURNTRANSFER, true); + $result = curl_exec($conn); + + if ($result) { + $orcid_data = json_decode($result, TRUE); + curl_close($conn); + return $orcid_data; + } else { + $errorMessage = curl_errno($conn) . "-"; + $errorMessage = $errorMessage . curl_error($conn); + curl_close($conn); + throw new \Exception($errorMessage); + } + } + +} diff --git a/classes/model/AuthPageModel.php b/classes/model/AuthPageModel.php index 22756fa0f3b91bd844c1cfdbe5907e5b4a589099..bf90c89f2b2d5baf68f675cfc45bd827dc580b5e 100644 --- a/classes/model/AuthPageModel.php +++ b/classes/model/AuthPageModel.php @@ -41,7 +41,7 @@ class AuthPageModel { $this->eduGAIN = isset($config->authenticationMethods->eduGAIN) && in_array(AuthenticationMethods::EDU_GAIN, $client->authMethods); - $this->orcid = isset($config->authenticationMethods->Orcid) && + $this->orcid = isset($config->authenticationMethods->OrcID) && in_array(AuthenticationMethods::ORCID, $client->authMethods); $this->x509 = isset($config->authenticationMethods->X509) && diff --git a/classes/model/AuthenticationMethods.php b/classes/model/AuthenticationMethods.php index 9933d594077efc5a5d299d924b3b3bd9c58379fb..8b3f29d59053021b427c6a6fbe3a483aafd88abc 100644 --- a/classes/model/AuthenticationMethods.php +++ b/classes/model/AuthenticationMethods.php @@ -5,7 +5,7 @@ namespace RAP; abstract class AuthenticationMethods { const EDU_GAIN = "eduGAIN"; - const ORCID = "Orcid"; + const ORCID = "OrcID"; const X509 = "X.509"; const GOOGLE = "Google"; const LINKED_IN = "LinkedIn"; diff --git a/classes/model/Identity.php b/classes/model/Identity.php index 7710595fe534008d4fc00b0cdfb05c53971fb6cc..e0ba59240301dac14857cbdba6a838429cc64767 100644 --- a/classes/model/Identity.php +++ b/classes/model/Identity.php @@ -34,8 +34,9 @@ class Identity { const GOOGLE = "Google"; const FACEBOOK = "Facebook"; const LINKEDIN = "LinkedIn"; + const ORCID = "OrcID"; - private static $ALLOWED_TYPES = [Identity::EDU_GAIN, Identity::X509, Identity::GOOGLE, Identity::FACEBOOK, Identity::LINKEDIN]; + private static $ALLOWED_TYPES = [Identity::EDU_GAIN, Identity::X509, Identity::GOOGLE, Identity::FACEBOOK, Identity::LINKEDIN, Identity::ORCID]; /** * Identity id in the database. Mandatory field. diff --git a/config-example.json b/config-example.json index 92f9edd77142e67dddafcb3445562328ee1ecc80..873732f8d5560c24a468e661d508508555e460c8 100644 --- a/config-example.json +++ b/config-example.json @@ -37,6 +37,11 @@ "logo": "img/ia2-logo-60x60.png", "logo_alt": "IA2 logo", "description": "Use the IA2 Logo to Login if you have an account provided by IA2 or self registered" + }, + "OrcID": { + "id": "", + "callback": "/auth/orcid", + "secret": "" } } } \ No newline at end of file diff --git a/img/eduGain-200.png b/img/eduGain-200.png deleted file mode 100755 index 5235aa8fcd435d54d4e4bec1374f6aa151a0c3d4..0000000000000000000000000000000000000000 Binary files a/img/eduGain-200.png and /dev/null differ diff --git a/img/edugain-100.png b/img/edugain-100.png new file mode 100644 index 0000000000000000000000000000000000000000..c24fbd52e91725f7686df9f23f6358274129ffeb Binary files /dev/null and b/img/edugain-100.png differ diff --git a/img/orcid-100.png b/img/orcid-100.png new file mode 100644 index 0000000000000000000000000000000000000000..d7ba058ba9c8f4fda93cca35c585a7c00b24499d Binary files /dev/null and b/img/orcid-100.png differ diff --git a/include/front-controller.php b/include/front-controller.php index bd63fbfff59472cdd937570b35184d6b5d5efd5e..2b2ab02b67fd72df238619b47ee2b14ee15517ad 100644 --- a/include/front-controller.php +++ b/include/front-controller.php @@ -154,28 +154,43 @@ Flight::route('/auth/social/facebook', function() { session_start(); global $locator; $facebookLogin = new \RAP\FacebookLogin($locator); - $facebookLogin->login(); + Flight::redirect($facebookLogin->login()); }); Flight::route('/auth/social/facebook/token', function() { session_start(); global $locator; $facebookLogin = new \RAP\FacebookLogin($locator); - $facebookLogin->retrieveToken(); + Flight::redirect($facebookLogin->retrieveToken()); }); Flight::route('/auth/social/linkedIn', function() { session_start(); global $locator; $linkedInLogin = new \RAP\LinkedInLogin($locator); - $linkedInLogin->login(); + Flight::redirect($linkedInLogin->login()); }); Flight::route('/auth/social/linkedIn/token', function() { session_start(); global $locator; $linkedInLogin = new \RAP\LinkedInLogin($locator); - $linkedInLogin->retrieveToken(); + Flight::redirect($linkedInLogin->retrieveToken()); +}); + +Flight::route('/auth/orcid', function() { + session_start(); + global $locator; + $orcidLogin = new \RAP\OrcidLogin($locator); + Flight::redirect($orcidLogin->login()); +}); + +Flight::route('/auth/orcid/token', function() { + session_start(); + global $locator; + $code = filter_input(INPUT_GET, 'code', FILTER_SANITIZE_STRING); + $orcidLogin = new \RAP\OrcidLogin($locator); + Flight::redirect($orcidLogin->retrieveToken($code)); }); Flight::route('/auth/eduGAIN', function() { diff --git a/index.php b/index.php index 485f948484f111466c27fd32bd362fc971b1f4b2..7984b1c216c28a8f7212d1a42877aae4500ef86f 100644 --- a/index.php +++ b/index.php @@ -29,16 +29,19 @@ include './include/gui-backend.php'; include './include/rest-web-service.php'; // Error handling -Flight::map('error', function(\Exception $ex) { +Flight::map('error', function($ex) { if ($ex instanceof \RAP\BadRequestException) { http_response_code(400); echo "Bad request: " . $ex->message; - } else { + } else if ($ex instanceof \Exception) { if ($ex->getMessage() !== null) { echo $ex->getMessage(); } else { echo $ex->getTraceAsString(); } + } else { + error_log('Error'); + throw $ex; } }); diff --git a/views/confirm-join.php b/views/confirm-join.php deleted file mode 100644 index 1c22d6a683abd64fedaf0bd8a2b9b5933d8585f7..0000000000000000000000000000000000000000 --- a/views/confirm-join.php +++ /dev/null @@ -1,49 +0,0 @@ - - -

Confirm join request

- -
-
-
-
-
-
- -
-
-
-
- -
-
-
-
- -
-
-
-
-
- -
-
-

Pressing the following button the identities listed above will be joined.

-
- - -
-
-
- - - -

Success

- -
-
-

Your identities have been joined!

-
-

Return to index

-
- -
- eduGAIN) { ?> + eduGAIN || $model->orcid) { ?>
- - eduGAIN Logo - + eduGAIN) { ?> + + eduGAIN Logo + + + orcid) { ?> + + ORCID Logo + +
- Use the eduGAIN Logo to Login or Register to the RAP facility if you belong to an eduGAIN IdP. + Use the eduGAIN or OrcID Logo to Login or Register to RAP facility with your Institutional account.
google || $model->facebook || $model->linkedIn) { ?>