Open Journal Systems  3.3.0
OrcidHandler.inc.php
1 <?php
2 
17 import('classes.handler.Handler');
18 
19 class OrcidHandler extends Handler {
20  const TEMPLATE = 'orcidVerify.tpl';
21 
25  function authorize($request, &$args, $roleAssignments) {
26  // Authorize all requets
27  import('lib.pkp.classes.security.authorization.PKPSiteAccessPolicy');
28  $this->addPolicy(new PKPSiteAccessPolicy(
29  $request,
30  array('orcidVerify', 'orcidAuthorize', 'about'),
31  SITE_ACCESS_ALL_ROLES
32  ));
33 
34  $op = $request->getRequestedOp();
35  $targetOp = $request->getUserVar('targetOp');
36  if ($op === 'orcidAuthorize' && in_array($targetOp, ['profile', 'submit'])) {
37  // ... but user must be logged in for orcidAuthorize with profile or submit
38  import('lib.pkp.classes.security.authorization.UserRequiredPolicy');
39  $this->addPolicy(new UserRequiredPolicy($request));
40  }
41 
42  if (!Config::getVar('general', 'installed')) define('SESSION_DISABLE_INIT', true);
43 
44  $this->setEnforceRestrictedSite(false);
45  return parent::authorize($request, $args, $roleAssignments);
46  }
47 
48 
54  function orcidAuthorize($args, $request) {
55  $context = $request->getContext();
56  $op = $request->getRequestedOp();
57  $plugin = PluginRegistry::getPlugin('generic', 'orcidprofileplugin');
58  $contextId = ($context == null) ? CONTEXT_ID_NONE : $context->getId();
59 
60  // Set up common CURL request details
61  $curl = curl_init();
62  // Use proxy if configured
63  if ($httpProxyHost = Config::getVar('proxy', 'http_host')) {
64  curl_setopt($curl, CURLOPT_PROXY, $httpProxyHost);
65  curl_setopt($curl, CURLOPT_PROXYPORT, Config::getVar('proxy', 'http_port', '80'));
66  if ($username = Config::getVar('proxy', 'username')) {
67  curl_setopt($curl, CURLOPT_PROXYUSERPWD, $username . ':' . Config::getVar('proxy', 'password'));
68  }
69  }
70 
71  // API request: Get an OAuth token and ORCID.
72  curl_setopt_array($curl, array(
73  CURLOPT_URL => $plugin->getSetting($contextId, 'orcidProfileAPIPath') . OAUTH_TOKEN_URL,
74  CURLOPT_RETURNTRANSFER => true,
75  CURLOPT_HTTPHEADER => array('Accept: application/json'),
76  CURLOPT_POST => true,
77  CURLOPT_POSTFIELDS => http_build_query(array(
78  'code' => $request->getUserVar('code'),
79  'grant_type' => 'authorization_code',
80  'client_id' => $plugin->getSetting($contextId, 'orcidClientId'),
81  'client_secret' => $plugin->getSetting($contextId, 'orcidClientSecret')
82  ))
83  ));
84  if (!($result = curl_exec($curl))) {
85  error_log('ORCID CURL error: ' . curl_error($curl) . ' (' . __FILE__ . ' line ' . __LINE__ . ', URL ' . $url . ')');
86  $orcidUri = $orcid = $accessToken = null;
87  } else {
88  $response = json_decode($result, true);
89  $orcid = $response['orcid'];
90  $accessToken = $response['access_token'];
91  $orcidUri = ($plugin->getSetting($contextId, "isSandBox") == true ? ORCID_URL_SANDBOX : ORCID_URL) . $orcid;
92  }
93 
94  switch ($request->getUserVar('targetOp')) {
95  case 'register':
96  // API request: get user profile (for names; email; etc)
97  curl_setopt_array($curl, array(
98  CURLOPT_RETURNTRANSFER => 1,
99  CURLOPT_URL => $url = $plugin->getSetting($contextId, 'orcidProfileAPIPath') . ORCID_API_VERSION_URL . urlencode($orcid) . '/' . ORCID_PROFILE_URL,
100  CURLOPT_POST => false,
101  CURLOPT_HTTPHEADER => array(
102  'Accept: application/json',
103  'Authorization: Bearer ' . $accessToken,
104  ),
105  ));
106  if (!($result = curl_exec($curl))) error_log('ORCID CURL error: ' . curl_error($curl) . ' (' . __FILE__ . ' line ' . __LINE__ . ', URL ' . $url . ')');
107  $info = curl_getinfo($curl);
108  if ($info['http_code'] == 200) {
109  $profileJson = json_decode($result, true);
110  } else {
111  error_log('Unexpected ORCID API response: ' . $info['http_code'] . ' (' . __FILE__ . ' line ' . __LINE__ . ', URL ' . $url . ')');
112  $profileJson = null;
113  }
114 
115  // API request: get employments (for affiliation field)
116  curl_setopt_array($curl, array(
117  CURLOPT_RETURNTRANSFER => 1,
118  CURLOPT_URL => $url = $plugin->getSetting($contextId, 'orcidProfileAPIPath') . ORCID_API_VERSION_URL . urlencode($orcid) . '/' . ORCID_EMPLOYMENTS_URL,
119  CURLOPT_POST => false,
120  CURLOPT_HTTPHEADER => array(
121  'Accept: application/json',
122  'Authorization: Bearer ' . $accessToken,
123  ),
124  ));
125  if (!($result = curl_exec($curl))) error_log('ORCID CURL error: ' . curl_error($curl) . ' (' . __FILE__ . ' line ' . __LINE__ . ', URL ' . $url . ')');
126  $info = curl_getinfo($curl);
127  if ($info['http_code'] == 200) {
128  $employmentJson = json_decode($result, true);
129  } else {
130  error_log('Unexpected ORCID API response: ' . $info['http_code'] . ' (' . __FILE__ . ' line ' . __LINE__ . ', URL ' . $url . ')');
131  $employmentJson = null;
132  }
133 
134  // Suppress errors for nonexistent array indexes
135  echo '
136  <html><body><script type="text/javascript">
137  opener.document.getElementById("givenName").value = ' . json_encode(@$profileJson['name']['given-names']['value']) . ';
138  opener.document.getElementById("familyName").value = ' . json_encode(@$profileJson['name']['family-name']['value']) . ';
139  opener.document.getElementById("email").value = ' . json_encode(@$profileJson['emails']['email'][0]['email']) . ';
140  opener.document.getElementById("country").value = ' . json_encode(@$profileJson['addresses']['address'][0]['country']['value']) . ';
141  opener.document.getElementById("affiliation").value = ' . json_encode(@$employmentJson['employment-summary'][0]['organization']['name']) . ';
142  opener.document.getElementById("orcid").value = ' . json_encode($orcidUri). ';
143  opener.document.getElementById("connect-orcid-button").style.display = "none";
144  window.close();
145  </script></body></html>
146  ';
147  break;
148  case 'profile':
149  $user = $request->getUser();
150  // Store the access token and other data for the user
151  $this->_setOrcidData($user, $orcidUri, $response);
152  $userDao = DAORegistry::getDAO('UserDAO');
153  $userDao->updateLocaleFields($user);
154 
155  // Reload the public profile tab (incl. form)
156  echo '
157  <html><body><script type="text/javascript">
158  opener.$("#profileTabs").tabs("load", 3);
159  window.close();
160  </script></body></html>
161  ';
162  break;
163  default: assert(false);
164  }
165 
166  curl_close($curl);
167  }
168 
174  function orcidVerify($args, $request) {
175  $templateMgr = TemplateManager::getManager($request);
176  $context = $request->getContext();
177  $contextId = ($context == null) ? CONTEXT_ID_NONE : $context->getId();
178 
179  $plugin = PluginRegistry::getPlugin('generic', 'orcidprofileplugin');
180  $templatePath = $plugin->getTemplateResource(self::TEMPLATE);
181 
182 
183  $publicationId = $request->getUserVar('publicationId');
184  $authorDao = DAORegistry::getDAO('AuthorDAO');
185  $authors = $authorDao->getByPublicationId($publicationId);
186 
187  $publication = Services::get('publication')->get($publicationId);
188 
189  $authorToVerify = null;
190  // Find the author entry, for which the ORCID verification was requested
191  if ($request->getUserVar('token')) {
192  foreach ($authors as $author) {
193  if ($author->getData('orcidEmailToken') == $request->getUserVar('token')) {
194  $authorToVerify = $author;
195  }
196  }
197  }
198 
199  // Initialise template parameters
200  $templateMgr->assign(array(
201  'currentUrl' => $request->url(null, 'index'),
202  'verifySuccess' => false,
203  'authFailure' => false,
204  'notPublished' => false,
205  'sendSubmission' => false,
206  'sendSubmissionSuccess' => false,
207  'denied' => false,
208  ));
209 
210  if ($authorToVerify == null) {
211  // no Author exists in the database with the supplied orcidEmailToken
212  $plugin->logError('OrcidHandler::orcidverify - No author found with supplied token');
213  $templateMgr->assign('verifySuccess', false);
214  $templateMgr->display($templatePath);
215  return;
216  }
217 
218  if ($request->getUserVar('error') === 'access_denied') {
219  // User denied access
220  // Store the date time the author denied ORCID access to remember this
221  $authorToVerify->setData('orcidAccessDenied', Core::getCurrentDate());
222  // remove all previously stored ORCID access token
223  $authorToVerify->setData('orcidAccessToken', null);
224  $authorToVerify->setData('orcidAccessScope', null);
225  $authorToVerify->setData('orcidRefreshToken', null);
226  $authorToVerify->setData('orcidAccessExpiresOn', null);
227  $authorToVerify->setData('orcidEmailToken', null);
228  $authorDao->updateLocaleFields($authorToVerify);
229  $plugin->logError('OrcidHandler::orcidverify - ORCID access denied. Error description: ' . $request->getUserVar('error_description'));
230  $templateMgr->assign('denied', true);
231  $templateMgr->display($templatePath);
232  return;
233  }
234 
235  // fetch the access token
236  $url = $plugin->getSetting($contextId, 'orcidProfileAPIPath').OAUTH_TOKEN_URL;
237 
238  $ch = curl_init($url);
239 
240  $header = array('Accept: application/json');
241  $postData = http_build_query(array(
242  'code' => $request->getUserVar('code'),
243  'grant_type' => 'authorization_code',
244  'client_id' => $plugin->getSetting($contextId, 'orcidClientId'),
245  'client_secret' => $plugin->getSetting($contextId, 'orcidClientSecret')
246  ));
247 
248  $plugin->logInfo('POST ' . $url);
249  $plugin->logInfo('Request header: ' . var_export($header, true));
250  $plugin->logInfo('Request body: ' . $postData);
251 
252  // Use proxy if configured
253  if ($httpProxyHost = Config::getVar('proxy', 'http_host')) {
254  curl_setopt($ch, CURLOPT_PROXY, $httpProxyHost);
255  curl_setopt($ch, CURLOPT_PROXYPORT, Config::getVar('proxy', 'http_port', '80'));
256  if ($username = Config::getVar('proxy', 'username')) {
257  curl_setopt($ch, CURLOPT_PROXYUSERPWD, $username . ':' . Config::getVar('proxy', 'password'));
258  }
259  }
260 
261  curl_setopt_array($ch, array(
262  CURLOPT_RETURNTRANSFER => true,
263  CURLOPT_HTTPHEADER => $header,
264  CURLOPT_POST => true,
265  CURLOPT_POSTFIELDS => $postData
266  ));
267 
268  if (!($result = curl_exec($ch))) {
269  $plugin->logError('OrcidHandler::orcidverify - CURL error: ' . curl_error($ch));
270  $templateMgr->assign('authFailure', true);
271  $templateMgr->display($templatePath);
272  return;
273  }
274 
275  $httpstatus = curl_getinfo($ch, CURLINFO_HTTP_CODE);
276  curl_close($ch);
277 
278  $plugin->logInfo('Response body: ' . $result);
279  $response = json_decode($result, true);
280  if (isset($response['error']) && $response['error'] === 'invalid_grant') {
281  $plugin->logError("Response status: $httpstatus . Authroization code invalid, maybe already used");
282  $templateMgr->assign('authFailure', true);
283  $templateMgr->display($templatePath);
284  return;
285  } elseif (isset($response['error'])) {
286  $plugin->logError("Response status: $httpstatus . Invalid ORCID response: $result");
287  $templateMgr->assign('authFailure', true);
288  $templateMgr->display($templatePath);
289  }
290  // Set the orcid id using the full https uri
291  $orcidUri = ($plugin->getSetting($contextId, "isSandBox") == true ? ORCID_URL_SANDBOX : ORCID_URL) . $response['orcid'];
292  if (!empty($authorToVerify->getOrcid()) && $orcidUri != $authorToVerify->getOrcid()) {
293  // another ORCID id is stored for the author
294  $templateMgr->assign('duplicateOrcid', true);
295  $templateMgr->display($templatePath);
296  return;
297  }
298  $authorToVerify->setOrcid($orcidUri);
299  if ($plugin->getSetting($contextId, 'orcidProfileAPIPath') == ORCID_API_URL_MEMBER_SANDBOX ||
300  $plugin->getSetting($contextId, 'orcidProfileAPIPath') == ORCID_API_URL_PUBLIC_SANDBOX) {
301  // Set a flag to mark that the stored orcid id and access token came form the sandbox api
302  $authorToVerify->setData('orcidSandbox', true);
303  $templateMgr->assign('orcid', ORCID_URL_SANDBOX . $response['orcid']);
304  } else {
305  $templateMgr->assign('orcid', $orcidUri);
306  }
307 
308  // remove the email token
309  $authorToVerify->setData('orcidEmailToken', null);
310  $this->_setOrcidData($authorToVerify, $orcidUri, $response);
311  $authorDao->updateObject($authorToVerify);
312  if($plugin->isMemberApiEnabled($contextId) ) {
313  if ($publication->getData('status') == STATUS_PUBLISHED) {
314  $templateMgr->assign('sendSubmission', true);
315  $sendResult = $plugin->sendSubmissionToOrcid($publication, $request);
316  if ($sendResult === true || (is_array($sendResult) && $sendResult[$response['orcid']])) {
317  $templateMgr->assign('sendSubmissionSuccess', true);
318  }
319  } else {
320  $templateMgr->assign('submissionNotPublished', true);
321  }
322  }
323 
324  $templateMgr->assign(array(
325  'verifySuccess' => true,
326  'orcidIcon' => $plugin->getIcon()
327  ));
328 
329  $templateMgr->display($templatePath);
330  }
331 
332  function _setOrcidData($userOrAuthor, $orcidUri, $orcidResponse) {
333  // Save the access token
334  $orcidAccessExpiresOn = Carbon\Carbon::now();
335  // expires_in field from the response contains the lifetime in seconds of the token
336  // See https://members.orcid.org/api/get-oauthtoken
337  $orcidAccessExpiresOn->addSeconds($orcidResponse['expires_in']);
338  $userOrAuthor->setOrcid($orcidUri);
339  // remove the access denied marker, because now the access was granted
340  $userOrAuthor->setData('orcidAccessDenied', null);
341  $userOrAuthor->setData('orcidAccessToken', $orcidResponse['access_token']);
342  $userOrAuthor->setData('orcidAccessScope', $orcidResponse['scope']);
343  $userOrAuthor->setData('orcidRefreshToken', $orcidResponse['refresh_token']);
344  $userOrAuthor->setData('orcidAccessExpiresOn', $orcidAccessExpiresOn->toDateTimeString());
345  }
346 
347  /*
348  * Show explanation and information about ORCID
349  */
350 
351  function about($args, $request) {
352  $context = $request->getContext();
353  $contextId = ($context == null) ? CONTEXT_ID_NONE : $context->getId();
354  $templateMgr = TemplateManager::getManager($request);
355  $plugin = PluginRegistry::getPlugin('generic', 'orcidprofileplugin');
356  $templateMgr->assign('orcidIcon', $plugin->getIcon());
357  $templateMgr->assign('isMemberApi', $plugin->isMemberApiEnabled($contextId));
358  $templateMgr->display($plugin->getTemplateResource('orcidAbout.tpl'));
359  }
360 }
361 
UserRequiredPolicy
Policy to deny access if a context cannot be found in the request.
Definition: UserRequiredPolicy.inc.php:17
$op
$op
Definition: lib/pkp/pages/help/index.php:18
OrcidHandler
Pass off internal ORCID API requests to ORCID.
Definition: OrcidHandler.inc.php:19
OrcidHandler\orcidVerify
orcidVerify($args, $request)
Definition: OrcidHandler.inc.php:174
OrcidHandler\TEMPLATE
const TEMPLATE
Definition: OrcidHandler.inc.php:20
DAORegistry\getDAO
static & getDAO($name, $dbconn=null)
Definition: DAORegistry.inc.php:57
OrcidHandler\orcidAuthorize
orcidAuthorize($args, $request)
Definition: OrcidHandler.inc.php:54
Config\getVar
static getVar($section, $key, $default=null)
Definition: Config.inc.php:35
PKPTemplateManager\getManager
static & getManager($request=null)
Definition: PKPTemplateManager.inc.php:1239
OrcidHandler\about
about($args, $request)
Definition: OrcidHandler.inc.php:351
PluginRegistry\getPlugin
static getPlugin($category, $name)
Definition: PluginRegistry.inc.php:85
OrcidHandler\_setOrcidData
_setOrcidData($userOrAuthor, $orcidUri, $orcidResponse)
Definition: OrcidHandler.inc.php:332
Core\getCurrentDate
static getCurrentDate($ts=null)
Definition: Core.inc.php:63
OrcidHandler\authorize
authorize($request, &$args, $roleAssignments)
Definition: OrcidHandler.inc.php:25
PKPHandler\setEnforceRestrictedSite
setEnforceRestrictedSite($enforceRestrictedSite)
Definition: PKPHandler.inc.php:91
PKPHandler\addPolicy
addPolicy($authorizationPolicy, $addToTop=false)
Definition: PKPHandler.inc.php:157
Handler
Base request handler application class.
Definition: Handler.inc.php:18
PKPSiteAccessPolicy
Class to that makes sure that a user is logged in.
Definition: PKPSiteAccessPolicy.inc.php:20
PKPServices\get
static get($service)
Definition: PKPServices.inc.php:49