diff --git a/.tx/config b/.tx/config index 03301ded378..06607e62894 100644 --- a/.tx/config +++ b/.tx/config @@ -218,6 +218,12 @@ source_file = htdocs/langs/en_US/members.lang source_lang = en_US type = MOZILLAPROPERTIES +[dolibarr.oauth] +file_filter = htdocs/langs//oauth.lang +source_file = htdocs/langs/en_US/oauth.lang +source_lang = en_US +type = MOZILLAPROPERTIES + [dolibarr.opensurvey] file_filter = htdocs/langs//opensurvey.lang source_file = htdocs/langs/en_US/opensurvey.lang diff --git a/htdocs/admin/oauth.php b/htdocs/admin/oauth.php new file mode 100644 index 00000000000..517b4eeb8e9 --- /dev/null +++ b/htdocs/admin/oauth.php @@ -0,0 +1,335 @@ + + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * 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, see . + * + */ + +/** + * \file htdocs/admin/oauth.php + * \ingroup oauth + * \brief Setup page to configure oauth access api + */ + +require '../main.inc.php'; + +// required Class +require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php'; + +$langs->load("admin"); +$langs->load("oauth"); + +// Security check +if (!$user->admin) + accessforbidden(); + +$action = GETPOST('action', 'alpha'); + +// API access parameters OAUTH +$list = array ( + array( + 'OAUTH_AMAZON_NAME', + 'OAUTH_AMAZON_ID', + 'OAUTH_AMAZON_SECRET', + ), + array( + 'OAUTH_BITBUCKET_NAME', + 'OAUTH_BITBUCKET_ID', + 'OAUTH_BITBUCKET_SECRET', + ), + array( + 'OAUTH_BITLY_NAME', + 'OAUTH_BITLY_ID', + 'OAUTH_BITLY_SECRET', + ), + array( + 'OAUTH_BITRIX24_NAME', + 'OAUTH_BITRIX24_ID', + 'OAUTH_BITRIX24_SECRET', + ), + array( + 'OAUTH_BOX_NAME', + 'OAUTH_BOX_ID', + 'OAUTH_BOX_SECRET', + ), + array( + 'OAUTH_BUFFER_NAME', + 'OAUTH_BUFFER_ID', + 'OAUTH_BUFFER_SECRET', + ), + array( + 'OAUTH_DAILYMOTION_NAME', + 'OAUTH_DAILYMOTION_ID', + 'OAUTH_DAILYMOTION_SECRET', + ), + array( + 'OAUTH_DEVIANTART_NAME', + 'OAUTH_DEVIANTART_ID', + 'OAUTH_DEVIANTART_SECRET', + ), + array( + 'OAUTH_DROPBOX_NAME', + 'OAUTH_DROPBOX_ID', + 'OAUTH_DROPBOX_SECRET', + ), + array( + 'OAUTH_ETSY_NAME', + 'OAUTH_ETSY_ID', + 'OAUTH_ETSY_SECRET', + ), + array( + 'OAUTH_EVEONLINE_NAME', + 'OAUTH_EVEONLINE_ID', + 'OAUTH_EVEONLINE_SECRET', + ), + array( + 'OAUTH_FACEBOOK_NAME', + 'OAUTH_FACEBOOK_ID', + 'OAUTH_FACEBOOK_SECRET', + ), + array( + 'OAUTH_FITBIT_NAME', + 'OAUTH_FITBIT_ID', + 'OAUTH_FITBIT_SECRET', + ), + array( + 'OAUTH_FIVEHUNDREDPX_NAME', + 'OAUTH_FIVEHUNDREDPX_ID', + 'OAUTH_FIVEHUNDREDPX_SECRET', + ), + array( + 'OAUTH_FLICKR_NAME', + 'OAUTH_FLICKR_ID', + 'OAUTH_FLICKR_SECRET', + ), + array( + 'OAUTH_FOURSQUARE_NAME', + 'OAUTH_FOURSQUARE_ID', + 'OAUTH_FOURSQUARE_SECRET', + ), + array( + 'OAUTH_GITHUB_NAME', + 'OAUTH_GITHUB_ID', + 'OAUTH_GITHUB_SECRET', + ), + array( + 'OAUTH_GOOGLE_NAME', + 'OAUTH_GOOGLE_ID', + 'OAUTH_GOOGLE_SECRET', + ), + array( + 'OAUTH_HUBIC_NAME', + 'OAUTH_HUBIC_ID', + 'OAUTH_HUBIC_SECRET', + ), + array( + 'OAUTH_INSTAGRAM_NAME', + 'OAUTH_INSTAGRAM_ID', + 'OAUTH_INSTAGRAM_SECRET', + ), + array( + 'OAUTH_LINKEDIN_NAME', + 'OAUTH_LINKEDIN_ID', + 'OAUTH_LINKEDIN_SECRET', + ), + array( + 'OAUTH_MAILCHIMP_NAME', + 'OAUTH_MAILCHIMP_ID', + 'OAUTH_MAILCHIMP_SECRET', + ), + array( + 'OAUTH_MICROSOFT_NAME', + 'OAUTH_MICROSOFT_ID', + 'OAUTH_MICROSOFT_SECRET', + ), + array( + 'OAUTH_NEST_NAME', + 'OAUTH_NEST_ID', + 'OAUTH_NEST_SECRET', + ), + array( + 'OAUTH_NETATMO_NAME', + 'OAUTH_NETATMO_ID', + 'OAUTH_NETATMO_SECRET', + ), + array( + 'OAUTH_PARROTFLOWERPOWER_NAME', + 'OAUTH_PARROTFLOWERPOWER_ID', + 'OAUTH_PARROTFLOWERPOWER_SECRET', + ), + array( + 'OAUTH_PAYPAL_NAME', + 'OAUTH_PAYPAL_ID', + 'OAUTH_PAYPAL_SECRET', + ), + array( + 'OAUTH_POCKET_NAME', + 'OAUTH_POCKET_ID', + 'OAUTH_POCKET_SECRET', + ), + array( + 'OAUTH_QUICKBOOKS_NAME', + 'OAUTH_QUICKBOOKS_ID', + 'OAUTH_QUICKBOOKS_SECRET', + ), + array( + 'OAUTH_REDDIT_NAME', + 'OAUTH_REDDIT_ID', + 'OAUTH_REDDIT_SECRET', + ), + array( + 'OAUTH_REDMINE_NAME', + 'OAUTH_REDMINE_ID', + 'OAUTH_REDMINE_SECRET', + ), + array( + 'OAUTH_RUNKEEPER_NAME', + 'OAUTH_RUNKEEPER_ID', + 'OAUTH_RUNKEEPER_SECRET', + ), + array( + 'OAUTH_SCOOPIT_NAME', + 'OAUTH_SCOOPIT_ID', + 'OAUTH_SCOOPIT_SECRET', + ), + array( + 'OAUTH_SOUNDCLOUD_NAME', + 'OAUTH_SOUNDCLOUD_ID', + 'OAUTH_SOUNDCLOUD_SECRET', + ), + array( + 'OAUTH_SPOTIFY_NAME', + 'OAUTH_SPOTIFY_ID', + 'OAUTH_SPOTIFY_SECRET', + ), + array( + 'OAUTH_STRAVA_NAME', + 'OAUTH_STRAVA_ID', + 'OAUTH_STRAVA_SECRET', + ), + array( + 'OAUTH_TUMBLR_NAME', + 'OAUTH_TUMBLR_ID', + 'OAUTH_TUMBLR_SECRET', + ), + array( + 'OAUTH_TWITTER_NAME', + 'OAUTH_TWITTER_ID', + 'OAUTH_TWITTER_SECRET', + ), + array( + 'OAUTH_USTREAM_NAME', + 'OAUTH_USTREAM_ID', + 'OAUTH_USTREAM_SECRET', + ), + array( + 'OAUTH_VIMEO_NAME', + 'OAUTH_VIMEO_ID', + 'OAUTH_VIMEO_SECRET', + ), + array( + 'OAUTH_YAHOO_NAME', + 'OAUTH_YAHOO_ID', + 'OAUTH_YAHOO_SECRET', + ), + array( + 'OAUTH_YAMMER_NAME', + 'OAUTH_YAMMER_ID', + 'OAUTH_YAMMER_SECRET', + ), +); + + +/* + * Actions + */ + +if ($action == 'update') +{ + $error = 0; + + foreach ($list as $constname) { + $constvalue = GETPOST($constname[1], 'alpha'); + if (!dolibarr_set_const($db, $constname[1], $constvalue, 'chaine', 0, '', $conf->entity)) + $error++; + $constvalue = GETPOST($constname[2], 'alpha'); + if (!dolibarr_set_const($db, $constname[2], $constvalue, 'chaine', 0, '', $conf->entity)) + $error++; + } + + if (! $error) + { + setEventMessage($langs->trans("SetupSaved")); + } else { + setEventMessage($langs->trans("Error"),'errors'); + } +} + +/* + * View + */ + +llxHeader(); + +$form = new Form($db); + +$linkback=''.$langs->trans("BackToModuleList").''; +print load_fiche_titre($langs->trans('ConfigOAuth'),$linkback,'title_setup'); + +print '
'; +print ''; +print ''; + +/* + * Parameters + */ +dol_fiche_head($head, $mode, $langs->trans("ModuleSetup"), 0, 'technic'); + +print ''; + +$var = true; + +foreach ($list as $key) +{ + print ''; + // Api Name + $label = $langs->trans($key[0]); + print ''; + + // Api Id + $var = !$var; + print ''; + print ''; + print ''; + + // Api Secret + $var = !$var; + print ''; + print ''; + print ''; + +} + +print ''; + +print ''; +print '
'.$label.'
'; + print '
'; + print '
'."\n"; + +print '
'; +dol_fiche_end(); + +llxFooter(); +$db->close(); \ No newline at end of file diff --git a/htdocs/core/actions_printing.inc.php b/htdocs/core/actions_printing.inc.php index 431ac89a94e..e447722fc67 100644 --- a/htdocs/core/actions_printing.inc.php +++ b/htdocs/core/actions_printing.inc.php @@ -46,11 +46,12 @@ if ($action == 'print_file' and $user->rights->printing->read) if (! empty($conf->global->{$printer->active})) { $subdir=(GETPOST('printer', 'alpha')=='expedition'?'sending':''); - $errorprint = $printer->print_file(GETPOST('file', 'alpha'), GETPOST('printer', 'alpha'), $subdir); - //if ($errorprint < 0) { - // setEventMessage($interface->errors, 'errors'); - //} - if ($errorprint=='') { + $ret = $printer->print_file(GETPOST('file', 'alpha'), GETPOST('printer', 'alpha'), $subdir); + if ($ret < 0) { + setEventMessage($printer->error, 'errors'); + } + if ($ret==0) { + setEventMessage($printer->error, 'warning'); setEventMessage($langs->trans("FileWasSentToPrinter", basename(GETPOST('file'))).' '.$langs->trans("ViaModule").' '.$printer->name); $printed++; } diff --git a/htdocs/core/modules/modOauth.class.php b/htdocs/core/modules/modOauth.class.php new file mode 100644 index 00000000000..9320e45b0b9 --- /dev/null +++ b/htdocs/core/modules/modOauth.class.php @@ -0,0 +1,123 @@ + + * Copyright (C) 2015 Frederic France + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * 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, see . + */ + +/** \defgroup oauth Module oauth + * \brief Module for activation of Oauth for several Api Access + */ + +/** + * \file htdocs/core/modules/modOauth.class.php + * \ingroup oauth + * \brief File of class to describe and activate module Oauth + */ +include_once DOL_DOCUMENT_ROOT .'/core/modules/DolibarrModules.class.php'; + + + +/** + * Class to describe and activate module Direct Printing + */ +class modOauth extends DolibarrModules +{ + + /** + * Constructor + * + * @param DoliDB $db Database handler + */ + function __construct($db) + { + $this->db = $db ; + $this->numero = 66000; + // Family can be 'crm','financial','hr','projects','products','ecm','technic','other' + // It is used to group modules in module setup page + $this->family = "technic"; + // Module label (no space allowed), used if translation string 'ModuleXXXName' not found (where XXX is value of numeric property 'numero' of module) + $this->name = preg_replace('/^mod/i','',get_class($this)); + // Module description, used if translation string 'ModuleXXXDesc' not found (where XXX is value of numeric property 'numero' of module) + $this->description = "Enable Oauth."; + $this->version = 'dolibarr'; // 'development' or 'experimental' or 'dolibarr' or version + $this->const_name = 'MAIN_MODULE_'.strtoupper($this->name); + // Where to store the module in setup page (0=common,1=interface,2=others,3=very specific) + $this->special = 1; + // Name of image file used for this module. + // If file is in theme/yourtheme/img directory under name object_pictovalue.png, use this->picto='pictovalue' + // If file is in module/img directory under name object_pictovalue.png, use this->picto='pictovalue@module' + $this->picto = 'technic'; + + // Data directories to create when module is enabled. + $this->dirs = array(); + + // Config pages + $this->config_page_url = array("oauth.php"); + + // Dependencies + $this->depends = array(); + $this->requiredby = array(); + $this->phpmin = array(5,1); // Minimum version of PHP required by module + $this->need_dolibarr_version = array(3,7,-2); // Minimum version of Dolibarr required by module + $this->conflictwith = array(); + $this->langfiles = array("oauth"); + + // Constants + $this->const = array(); + + // Boxes + $this->boxes = array(); + + // Permissions + $this->rights = array(); + $this->rights_class = 'oauth'; + + $r=0; + // $this->rights[$r][0] Id permission (unique tous modules confondus) + // $this->rights[$r][1] Libelle par defaut si traduction de cle "PermissionXXX" non trouvee (XXX = Id permission) + // $this->rights[$r][2] Non utilise + // $this->rights[$r][3] 1=Permis par defaut, 0=Non permis par defaut + // $this->rights[$r][4] Niveau 1 pour nommer permission dans code + // $this->rights[$r][5] Niveau 2 pour nommer permission dans code + + $r++; + $this->rights[$r][0] = 66001; + $this->rights[$r][1] = 'OauthAccess'; + $this->rights[$r][2] = 'r'; + $this->rights[$r][3] = 1; + $this->rights[$r][4] = 'read'; + + // Main menu entries + $this->menus = array(); // List of menus to add + $r=0; + + // This is to declare the Top Menu entry: + //$this->menu[$r]=array( 'fk_menu'=>'fk_mainmenu=home,fk_leftmenu=modulesadmintools', // Put 0 if this is a top menu + // 'type'=>'left', // This is a Top menu entry + // 'titre'=>'MenuOauth', + // 'mainmenu'=>'oauth', + // 'url'=>'/oauth/index.php', + // 'langs'=>'oauth', // Lang file to use (without .lang) by module. File must be in langs/code_CODE/ directory. + // 'position'=>300, + // 'enabled'=>'$conf->oauth->enabled && $leftmenu==\'modulesadmintools\'', + // 'perms'=>'$user->rights->oauth->read', // Use 'perms'=>'1' if you want your menu with no permission rules + // 'target'=>'', + // 'user'=>0); // 0=Menu for internal users, 1=external users, 2=both + + //$r++; + + + } +} diff --git a/htdocs/core/modules/oauth/getgoogleoauthcallback.php b/htdocs/core/modules/oauth/getgoogleoauthcallback.php new file mode 100644 index 00000000000..82d83aa1e44 --- /dev/null +++ b/htdocs/core/modules/oauth/getgoogleoauthcallback.php @@ -0,0 +1,110 @@ + + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * 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, see . + */ + +/** + * \file htdocs/core/modules/oauth/getoauthcallback.php + * \ingroup oauth + * \brief Page to get oauth callback + */ + +require '../../../main.inc.php'; +require_once DOL_DOCUMENT_ROOT.'/includes/OAuth/bootstrap.php'; +use OAuth\Common\Storage\Session; +use OAuth\Common\Storage\DoliStorage; +use OAuth\Common\Consumer\Credentials; +use OAuth\Common\Token\TokenInterface; +use OAuth\OAuth2\Service\Google; + +/** + * Create a new instance of the URI class with the current URI, stripping the query string + */ +$uriFactory = new \OAuth\Common\Http\Uri\UriFactory(); +$currentUri = $uriFactory->createFromSuperGlobalArray($_SERVER); +$currentUri->setQuery(''); + +/** + * Load the credential for the service + */ + +/** @var $serviceFactory \OAuth\ServiceFactory An OAuth service factory. */ +$serviceFactory = new \OAuth\ServiceFactory(); +// Dolibarr storage +$storage = new DoliStorage($db, $conf); +// Setup the credentials for the requests +$credentials = new Credentials( + $conf->global->OAUTH_GOOGLE_ID, + $conf->global->OAUTH_GOOGLE_SECRET, + $currentUri->getAbsoluteUri() +); + +// Instantiate the Api service using the credentials, http client and storage mechanism for the token +/** @var $apiService Service */ +// TODO remove hardcoded array +$apiService = $serviceFactory->createService('Google', $credentials, $storage, array('userinfo_email', 'userinfo_profile', 'cloud_print')); + +// access type needed for google refresh token +$apiService->setAccessType('offline'); +//print '
'.print_r($apiService,true).'
'; +//print 'Has access Token: '.($storage->hasAccessToken('Google')?'Yes':'No').''; +//print 'Has Author State: '.($storage->hasAuthorizationState('Google')?'Yes':'No').''; +//print 'Authorization State: '.$storage->retrieveAuthorizationState('Google').''; +//print '
'.print_r($token,true).'
'; +if (! empty($_GET['code'])) { + llxHeader('',$langs->trans("OAuthSetup")); + + $linkback=''.$langs->trans("BackToModuleList").''; + print load_fiche_titre($langs->trans("OAuthSetup"),$linkback,'title_setup'); + // retrieve the CSRF state parameter + $state = isset($_GET['state']) ? $_GET['state'] : null; + try { + //$token = $storage->retrieveAccessToken('Google'); + } catch (Exception $e) { + print $e->getMessage(); + } + //print '
'.print_r($token->getRefreshToken(),true).'
'; + //$refreshtoken = $token->getRefreshToken(); + // This was a callback request from service, get the token + $apiService->requestAccessToken($_GET['code'], $state); + //print '
'.print_r($apiService,true).'
'; + + try { + $token = $storage->retrieveAccessToken('Google'); + } catch (Exception $e) { + print $e->getMessage(); + } + $newrefreshtoken = $token->getRefreshToken(); + if (empty($newrefreshtoken) && ! empty($refreshtoken)) { + $token->setRefreshToken($refreshtoken); + $storage->storeAccessToken('Google', $token); + } + print '
'.print_r($token,true).'
'; + //$apiService->refreshAccessToken($token); + //print '
'.print_r($apiService,true).'
'; + //$token = $storage->retrieveAccessToken('Google'); + //print '
'.print_r($token,true).'
'; + +} else { + $url = $apiService->getAuthorizationUri(); + // we go on google authorization page + header('Location: ' . $url); + exit(); +} + +llxFooter(); + +$db->close(); diff --git a/htdocs/core/modules/oauth/index.html b/htdocs/core/modules/oauth/index.html new file mode 100644 index 00000000000..e69de29bb2d diff --git a/htdocs/core/modules/printing/modules_printing.php b/htdocs/core/modules/printing/modules_printing.php index 7bb9ac98f58..4a6560b47fa 100644 --- a/htdocs/core/modules/printing/modules_printing.php +++ b/htdocs/core/modules/printing/modules_printing.php @@ -1,6 +1,6 @@ + * Copyright (C) 2014-2015 Frederic France * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -56,8 +56,8 @@ class PrintingDriver { global $conf; - $type='printing'; - $liste=array(); + $type = 'printing'; + $list = array(); $moduledir=DOL_DOCUMENT_ROOT."/core/modules/printing/"; $tmpfiles=dol_dir_list($moduledir,'all',0,'\modules.php','','name',SORT_ASC,0); diff --git a/htdocs/core/modules/printing/printgcp.modules.php b/htdocs/core/modules/printing/printgcp.modules.php index b32eece2e1a..d7f44ae243f 100644 --- a/htdocs/core/modules/printing/printgcp.modules.php +++ b/htdocs/core/modules/printing/printgcp.modules.php @@ -1,6 +1,6 @@ + * Copyright (C) 2014-2015 Frederic France * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,6 +24,11 @@ */ include_once DOL_DOCUMENT_ROOT.'/core/modules/printing/modules_printing.php'; +require_once DOL_DOCUMENT_ROOT.'/includes/OAuth/bootstrap.php'; +use OAuth\Common\Storage\Session; +use OAuth\Common\Storage\DoliStorage; +use OAuth\Common\Consumer\Credentials; +use OAuth\OAuth2\Service\Google; /** * Class to provide printing with Google Cloud Print @@ -35,14 +40,13 @@ class printing_printgcp extends PrintingDriver var $picto = 'printer'; var $active = 'PRINTING_PRINTGCP'; var $conf = array(); - var $login = ''; - var $password = ''; - var $authtoken = ''; + var $google_id = ''; + var $google_secret = ''; var $db; - const LOGIN_URL = 'https://www.google.com/accounts/ClientLogin'; - const PRINTERS_SEARCH_URL = 'https://www.google.com/cloudprint/interface/search'; - const PRINT_URL = 'https://www.google.com/cloudprint/interface/submit'; + const LOGIN_URL = 'https://accounts.google.com/o/oauth2/token'; + const PRINTERS_SEARCH_URL = 'https://www.google.com/cloudprint/search'; + const PRINT_URL = 'https://www.google.com/cloudprint/submit'; /** * Constructor @@ -54,11 +58,66 @@ class printing_printgcp extends PrintingDriver global $conf; $this->db = $db; - $this->login = $conf->global->PRINTGCP_LOGIN; - $this->password = $conf->global->PRINTGCP_PASSWORD; - $this->authtoken = $conf->global->PRINTGCP_AUTHTOKEN; - $this->conf[] = array('varname'=>'PRINTGCP_LOGIN', 'required'=>1, 'example'=>'user@gmail.com', 'type'=>'text'); - $this->conf[] = array('varname'=>'PRINTGCP_PASSWORD', 'required'=>1, 'example'=>'', 'type'=>'password'); + $this->google_id = $conf->global->OAUTH_GOOGLE_ID; + $this->google_secret = $conf->global->OAUTH_GOOGLE_SECRET; + // Token storage + $storage = new DoliStorage($this->db, $this->conf); + //$storage->clearToken('Google'); + // Setup the credentials for the requests + $credentials = new Credentials( + $this->google_id, + $this->google_secret, + DOL_MAIN_URL_ROOT.'/core/modules/oauth/getgoogleoauthcallback.php' + ); + $access = ($storage->hasAccessToken('Google')?'HasAccessToken':'NoAccessToken'); + $serviceFactory = new \OAuth\ServiceFactory(); + $apiService = $serviceFactory->createService('Google', $credentials, $storage, array()); + $token_ok=true; + try { + $token = $storage->retrieveAccessToken('Google'); + } catch (Exception $e) { + $this->errors[] = $e->getMessage(); + $token_ok = false; + } + $expire = false; + // Is token expired or will token expire in the next 30 seconds + if ($token_ok) { + $expire = ($token->getEndOfLife() !== -9002 && $token->getEndOfLife() !== -9001 && time() > ($token->getEndOfLife() - 30)); + } + + // Token expired so we refresh it + if ($token_ok && $expire) { + try { + // il faut sauvegarder le refresh token car google ne le donne qu'une seule fois + $refreshtoken = $token->getRefreshToken(); + $token = $apiService->refreshAccessToken($token); + $token->setRefreshToken($refreshtoken); + $storage->storeAccessToken('Google', $token); + } catch (Exception $e) { + $this->errors[] = $e->getMessage(); + print '
MissingRefreshToken: '.$e->getMessage().'
'; + //print '
'.print_r($e,true).'
'; + } + } + if (!$conf->oauth->enabled) { + $this->conf[] = array('varname'=>'PRINTGCP_INFO', 'info'=>'ModuleAuthNotActive', 'type'=>'info'); + } else { + if ($this->google_id != '' && $this->google_secret != '') { + $this->conf[] = array('varname'=>'PRINTGCP_INFO', 'info'=>'GoogleAuthConfigured', 'type'=>'info'); + $this->conf[] = array('varname'=>'PRINTGCP_TOKEN_ACCESS', 'info'=>$access, 'type'=>'info'); + if ($token_ok) { + $refreshtoken = $token->getRefreshToken(); + $this->conf[] = array('varname'=>'PRINTGCP_TOKEN_REFRESH', 'info'=>((! empty($refreshtoken))?'Yes':'No'), 'type'=>'info'); + $this->conf[] = array('varname'=>'PRINTGCP_TOKEN_EXPIRED', 'info'=>($expire?'Yes':'No'), 'type'=>'info'); + $this->conf[] = array('varname'=>'PRINTGCP_TOKEN_EXPIRE_AT', 'info'=>(date("Y-m-d H:i:s", $token->getEndOfLife())), 'type'=>'info'); + } + $this->conf[] = array('varname'=>'PRINTGCP_AUTHLINK', 'link'=>DOL_MAIN_URL_ROOT.'/core/modules/oauth/getgoogleoauthcallback.php', 'type'=>'authlink'); + } else { + $this->conf[] = array('varname'=>'PRINTGCP_INFO', 'info'=>'GoogleAuthNotConfigured', 'type'=>'info'); + } + } + // do not display submit button + $this->conf[] = array('enabled'=>0, 'type'=>'submit'); } /** @@ -111,48 +170,71 @@ class printing_printgcp extends PrintingDriver return $html; } + /** * Return list of available printers * - * @return array list of printers + * @return array list of printers */ function getlist_available_printers() { - global $conf,$db; - if ($this->authtoken=='') { - $this->GoogleLogin(); - } - $ret['available'] = $this->get_printer_detail(); - return $ret; - } - - /** - * List of printers - * - * @return array list of printers - */ - function get_printer_detail() - { + // Token storage + $storage = new DoliStorage($this->db, $this->conf); + // Setup the credentials for the requests + $credentials = new Credentials( + $this->google_id, + $this->google_secret, + DOL_MAIN_URL_ROOT.'/core/modules/oauth/getgoogleoauthcallback.php' + ); + $serviceFactory = new \OAuth\ServiceFactory(); + $apiService = $serviceFactory->createService('Google', $credentials, $storage, array()); // Check if we have auth token - if(empty($this->authtoken)) { - // We don't have auth token so throw exception - throw new Exception("Please first login to Google by calling loginToGoogle function"); + $token_ok=true; + try { + $token = $storage->retrieveAccessToken('Google'); + } catch (Exception $e) { + $this->errors[] = $e->getMessage(); + $token_ok = false; } - // Prepare auth headers with auth token - $authheaders = array("Authorization: GoogleLogin auth=".$this->authtoken, - "GData-Version: 3.0", - ); - // Make Http call to get printers added by user to Google Cloud Print - $responsedata = $this->makeCurl(self::PRINTERS_SEARCH_URL,array(),$authheaders); - $printers = json_decode($responsedata); + $expire = false; + // Is token expired or will token expire in the next 30 seconds + if ($token_ok) { + $expire = ($token->getEndOfLife() !== -9002 && $token->getEndOfLife() !== -9001 && time() > ($token->getEndOfLife() - 30)); + } + + // Token expired so we refresh it + if ($token_ok && $expire) { + try { + // il faut sauvegarder le refresh token car google ne le donne qu'une seule fois + $refreshtoken = $token->getRefreshToken(); + $token = $apiService->refreshAccessToken($token); + $token->setRefreshToken($refreshtoken); + $storage->storeAccessToken('Google', $token); + } catch (Exception $e) { + $this->errors[] = $e->getMessage(); + print '
MissingRefreshToken: '.$e->getMessage().'
'; + //print '
'.print_r($e,true).'
'; + } + } + // Send a request with api + try { + $response = $apiService->request(self::PRINTERS_SEARCH_URL); + } catch (Exception $e) { + $this->errors[] = $e->getMessage(); + print '
'.print_r($e->getMessage(),true).'
'; + } + //print '
'.print_r($response, true).'
'; + $responsedata = json_decode($response, true); + $printers = $responsedata['printers']; // Check if we have printers? - if(is_null($printers)) { + if(count($printers)==0) { // We dont have printers so return blank array - return array(); + $ret['available'] = array(); } else { // We have printers so returns printers as array - return $this->parsePrinters($printers); + $ret['available'] = $printers; } + return $ret; } /** @@ -161,14 +243,11 @@ class printing_printgcp extends PrintingDriver * @param string $file file * @param string $module module * @param string $subdir subdir for file - * @return string '' if OK, Error message if KO + * @return int 0 if OK, <0 if KO */ function print_file($file, $module, $subdir='') { global $conf, $user, $db; - if ($this->authtoken=='') { - $this->GoogleLogin(); - } // si $module=commande_fournisseur alors $conf->fournisseur->commande->dir_output $fileprint=$conf->{$module}->dir_output; if ($subdir!='') $fileprint.='/'.$subdir; @@ -181,7 +260,7 @@ class printing_printgcp extends PrintingDriver $obj = $this->db->fetch_object($result); if ($obj) { - $printer_id=$obj->printer_id; + $printer_id = $obj->printer_id; } else { @@ -197,7 +276,12 @@ class printing_printgcp extends PrintingDriver } else dol_print_error($db); - $this->sendPrintToPrinter($printer_id, $file, $fileprint, 'application/pdf'); + $ret = $this->sendPrintToPrinter($printer_id, $file, $fileprint, 'application/pdf'); + $this->error = 'PRINTGCP: '.$ret['errormessage']; + if ($ret['status']==1) + return 0; + else + return -1; } /** @@ -209,24 +293,16 @@ class printing_printgcp extends PrintingDriver * @param string $contenttype File content type by example application/pdf, image/png * @return array status array */ - public function sendPrintToPrinter($printerid,$printjobtitle,$filepath,$contenttype) + public function sendPrintToPrinter($printerid, $printjobtitle, $filepath, $contenttype) { - $errors=0; - // Check auth token - if(empty($this->authtoken)) { - $errors++; - setEventMessage('Please first login to Google', 'warning'); - } // Check if printer id if(empty($printerid)) { - $errors++; - setEventMessage('No provided printer ID', 'warning'); + return array('status' =>0, 'errorcode' =>'','errormessage'=>'No provided printer ID'); } // Open the file which needs to be print $handle = fopen($filepath, "rb"); if(!$handle) { - $errors++; - setEventMessage('Could not read the file.'); + return array('status' =>0, 'errorcode' =>'','errormessage'=>'Could not read the file.'); } // Read file content $contents = fread($handle, filesize($filepath)); @@ -238,118 +314,41 @@ class printing_printgcp extends PrintingDriver 'content' => base64_encode($contents), // encode file content as base64 'contentType' => $contenttype ); - // Prepare authorization headers - $authheaders = array("Authorization: GoogleLogin auth=" . $this->authtoken); - // Make http call for sending print Job - $response = json_decode($this->makeCurl(self::PRINT_URL,$post_fields,$authheaders)); - // Has document been successfully sent? - if($response->success=="1") { - return array('status' =>true,'errorcode' =>'','errormessage'=>""); - } else { - return array('status' =>false,'errorcode' =>$response->errorCode,'errormessage'=>$response->message); + // Token storage + $storage = new DoliStorage($this->db, $this->conf); + // Setup the credentials for the requests + $credentials = new Credentials( + $this->google_id, + $this->google_secret, + DOL_MAIN_URL_ROOT.'/core/modules/oauth/getoauthcallback.php?service=google' + ); + $serviceFactory = new \OAuth\ServiceFactory(); + $apiService = $serviceFactory->createService('Google', $credentials, $storage, array()); + + // Check if we have auth token and refresh it + $token_ok=true; + try { + $token = $storage->retrieveAccessToken('Google'); + } catch (Exception $e) { + $this->errors[] = $e->getMessage(); + $token_ok = false; } - } - - - /** - * Login into Google Account - * - * @return boolean true or false - */ - function GoogleLogin() - { - global $db, $conf; - // Prepare post fields required for the login - $loginpostfields = array("accountType" => "HOSTED_OR_GOOGLE", - "Email" => $this->login, - "Passwd" => $this->password, - "service" => "cloudprint", - "source" => "GCP" - ); - // Get the Auth token - $loginresponse = $this->makeCurl(self::LOGIN_URL,$loginpostfields); - $token = $this->getAuthToken($loginresponse); - if(! empty($token)&&!is_null($token)) { - $this->authtoken = $token; - $result=dolibarr_set_const($db, 'PRINTGCP_AUTHTOKEN', $token, 'chaine', 0, '', $conf->entity); - return true; - } else { - return false; - } - - } - - /** - * Parse json response and return printers array - * - * @param string $jsonobj Json response object - * @return array return array of printers - */ - private function parsePrinters($jsonobj) - { - $printers = array(); - if (isset($jsonobj->printers)) { - foreach ($jsonobj->printers as $gcpprinter) { - $printers[] = array('id' =>$gcpprinter->id, - 'name' =>$gcpprinter->name, - 'defaultDisplayName' =>$gcpprinter->defaultDisplayName, - 'displayName' =>$gcpprinter->displayName, - 'ownerId' =>$gcpprinter->ownerId, - 'ownerName' =>$gcpprinter->ownerName, - 'connectionStatus' =>$gcpprinter->connectionStatus, - 'status' =>$gcpprinter->status, - 'type' =>$gcpprinter->type - ); + if ($token_ok) { + try { + // il faut sauvegarder le refresh token car google ne le donne qu'une seule fois + $refreshtoken = $token->getRefreshToken(); + $token = $apiService->refreshAccessToken($token); + $token->setRefreshToken($refreshtoken); + $storage->storeAccessToken('Google', $token); + } catch (Exception $e) { + $this->errors[] = $e->getMessage(); } } - return $printers; - } - /** - * Parse data to get auth token - * - * @param string $response response from curl - * @return string token - */ - private function getAuthToken($response) - { - // Search Auth tag - preg_match("/Auth=([a-z0-9_-]+)/i", $response, $matches); - $authtoken = @$matches[1]; - return $authtoken; + // Send a request with api + $response = json_decode($apiService->request(self::PRINT_URL, 'POST', $post_fields), true); + //print '
'.print_r($response, true).'
'; + return array('status' =>$response['success'],'errorcode' =>$response['errorCode'],'errormessage'=>$response['message']); } - /** - * Make a curl request - * - * @param string $url url to hit - * @param array $postfields array of post fields - * @param string[] $headers array of http headers - * @return string response from curl - */ - private function makeCurl($url, $postfields=array(), $headers=array()) - { - // Curl Init - $curl = curl_init($url); - // Curl post request - if(! empty($postfields)) { - // As is HTTP post curl request so set post fields - curl_setopt($curl, CURLOPT_POST, true); - curl_setopt($curl, CURLOPT_POSTFIELDS, $postfields); - } - // Curl request headers - if(! empty($headers)) { - // As curl requires header so set headers here - curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); - } - curl_setopt($curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY); - curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); - curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); - // Execute the curl and return response - $response = curl_exec($curl); - curl_close($curl); - return $response; - } - - } diff --git a/htdocs/core/modules/printing/printipp.modules.php b/htdocs/core/modules/printing/printipp.modules.php index 3fbb66fb088..56cd4c3b2b6 100644 --- a/htdocs/core/modules/printing/printipp.modules.php +++ b/htdocs/core/modules/printing/printipp.modules.php @@ -1,6 +1,6 @@ + * Copyright (C) 2014-2015 Frederic France * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -62,6 +62,7 @@ class printing_printipp extends PrintingDriver $this->conf[] = array('varname'=>'PRINTIPP_PORT', 'required'=>1, 'example'=>'631', 'type'=>'text'); $this->conf[] = array('varname'=>'PRINTIPP_USER', 'required'=>0, 'example'=>'', 'type'=>'text', 'moreattributes'=>'autocomplete="off"'); $this->conf[] = array('varname'=>'PRINTIPP_PASSWORD', 'required'=>0, 'example'=>'', 'type'=>'password', 'moreattributes'=>'autocomplete="off"'); + $this->conf[] = array('enabled'=>1, 'type'=>'submit'); } /** @@ -71,7 +72,7 @@ class printing_printipp extends PrintingDriver * @param string $module module * @param string $subdir subdirectory of document like for expedition subdir is sendings * - * @return string '' if OK, Error message if KO + * @return int 0 if OK, <0 if KO */ function print_file($file, $module, $subdir='') { @@ -107,7 +108,8 @@ class printing_printipp extends PrintingDriver } else { - return 'NoDefaultPrinterDefined'; + $this->error = 'NoDefaultPrinterDefined'; + return -1; } } } @@ -121,7 +123,7 @@ class printing_printipp extends PrintingDriver $ipp->setData($fileprint); $ipp->printJob(); - return ''; + return 0; } /** diff --git a/htdocs/includes/OAuth/Common/AutoLoader.php b/htdocs/includes/OAuth/Common/AutoLoader.php new file mode 100644 index 00000000000..9fe7951cbd3 --- /dev/null +++ b/htdocs/includes/OAuth/Common/AutoLoader.php @@ -0,0 +1,81 @@ + + */ +class AutoLoader +{ + /** + * @var string The namespace prefix for this instance. + */ + protected $namespace = ''; + + /** + * @var string The filesystem prefix to use for this instance + */ + protected $path = ''; + + /** + * Build the instance of the autoloader + * + * @param string $namespace The prefixed namespace this instance will load + * @param string $path The filesystem path to the root of the namespace + */ + public function __construct($namespace, $path) + { + $this->namespace = ltrim($namespace, '\\'); + $this->path = rtrim($path, '/\\') . DIRECTORY_SEPARATOR; + } + + /** + * Try to load a class + * + * @param string $class The class name to load + * + * @return boolean If the loading was successful + */ + public function load($class) + { + $class = ltrim($class, '\\'); + + if (strpos($class, $this->namespace) === 0) { + $nsparts = explode('\\', $class); + $class = array_pop($nsparts); + $nsparts[] = ''; + $path = $this->path . implode(DIRECTORY_SEPARATOR, $nsparts); + $path .= str_replace('_', DIRECTORY_SEPARATOR, $class) . '.php'; + + if (file_exists($path)) { + require $path; + + return true; + } + } + + return false; + } + + /** + * Register the autoloader to PHP + * + * @return boolean The status of the registration + */ + public function register() + { + return spl_autoload_register(array($this, 'load')); + } + + /** + * Unregister the autoloader to PHP + * + * @return boolean The status of the unregistration + */ + public function unregister() + { + return spl_autoload_unregister(array($this, 'load')); + } +} diff --git a/htdocs/includes/OAuth/Common/Consumer/Credentials.php b/htdocs/includes/OAuth/Common/Consumer/Credentials.php new file mode 100644 index 00000000000..8e98e9fa9a6 --- /dev/null +++ b/htdocs/includes/OAuth/Common/Consumer/Credentials.php @@ -0,0 +1,60 @@ +consumerId = $consumerId; + $this->consumerSecret = $consumerSecret; + $this->callbackUrl = $callbackUrl; + } + + /** + * @return string + */ + public function getCallbackUrl() + { + return $this->callbackUrl; + } + + /** + * @return string + */ + public function getConsumerId() + { + return $this->consumerId; + } + + /** + * @return string + */ + public function getConsumerSecret() + { + return $this->consumerSecret; + } +} diff --git a/htdocs/includes/OAuth/Common/Consumer/CredentialsInterface.php b/htdocs/includes/OAuth/Common/Consumer/CredentialsInterface.php new file mode 100644 index 00000000000..a33e54e9ee2 --- /dev/null +++ b/htdocs/includes/OAuth/Common/Consumer/CredentialsInterface.php @@ -0,0 +1,24 @@ +userAgent = $userAgent; + } + + /** + * @param int $redirects Maximum redirects for client + * + * @return ClientInterface + */ + public function setMaxRedirects($redirects) + { + $this->maxRedirects = $redirects; + + return $this; + } + + /** + * @param int $timeout Request timeout time for client in seconds + * + * @return ClientInterface + */ + public function setTimeout($timeout) + { + $this->timeout = $timeout; + + return $this; + } + + /** + * @param array $headers + */ + public function normalizeHeaders(&$headers) + { + // Normalize headers + array_walk( + $headers, + function (&$val, &$key) { + $key = ucfirst(strtolower($key)); + $val = ucfirst(strtolower($key)) . ': ' . $val; + } + ); + } +} diff --git a/htdocs/includes/OAuth/Common/Http/Client/ClientInterface.php b/htdocs/includes/OAuth/Common/Http/Client/ClientInterface.php new file mode 100644 index 00000000000..f9c20226a6d --- /dev/null +++ b/htdocs/includes/OAuth/Common/Http/Client/ClientInterface.php @@ -0,0 +1,32 @@ + value` pairs) to be passed to `curl_setopt` + * + * @var array + */ + private $parameters = array(); + + /** + * Additional `curl_setopt` parameters + * + * @param array $parameters + */ + public function setCurlParameters(array $parameters) + { + $this->parameters = $parameters; + } + + /** + * @param bool $force + * + * @return CurlClient + */ + public function setForceSSL3($force) + { + $this->forceSSL3 = $force; + + return $this; + } + + /** + * Any implementing HTTP providers should send a request to the provided endpoint with the parameters. + * They should return, in string form, the response body and throw an exception on error. + * + * @param UriInterface $endpoint + * @param mixed $requestBody + * @param array $extraHeaders + * @param string $method + * + * @return string + * + * @throws TokenResponseException + * @throws \InvalidArgumentException + */ + public function retrieveResponse( + UriInterface $endpoint, + $requestBody, + array $extraHeaders = array(), + $method = 'POST' + ) { + // Normalize method name + $method = strtoupper($method); + + $this->normalizeHeaders($extraHeaders); + + if ($method === 'GET' && !empty($requestBody)) { + throw new \InvalidArgumentException('No body expected for "GET" request.'); + } + + if (!isset($extraHeaders['Content-Type']) && $method === 'POST' && is_array($requestBody)) { + $extraHeaders['Content-Type'] = 'Content-Type: application/x-www-form-urlencoded'; + } + + $extraHeaders['Host'] = 'Host: '.$endpoint->getHost(); + $extraHeaders['Connection'] = 'Connection: close'; + + $ch = curl_init(); + + curl_setopt($ch, CURLOPT_URL, $endpoint->getAbsoluteUri()); + + if ($method === 'POST' || $method === 'PUT') { + if ($requestBody && is_array($requestBody)) { + $requestBody = http_build_query($requestBody, '', '&'); + } + + if ($method === 'PUT') { + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT'); + } else { + curl_setopt($ch, CURLOPT_POST, true); + } + + curl_setopt($ch, CURLOPT_POSTFIELDS, $requestBody); + } else { + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); + } + + if ($this->maxRedirects > 0) { + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($ch, CURLOPT_MAXREDIRS, $this->maxRedirects); + } + + curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HEADER, false); + curl_setopt($ch, CURLOPT_HTTPHEADER, $extraHeaders); + curl_setopt($ch, CURLOPT_USERAGENT, $this->userAgent); + + foreach ($this->parameters as $key => $value) { + curl_setopt($ch, $key, $value); + } + + if ($this->forceSSL3) { + curl_setopt($ch, CURLOPT_SSLVERSION, 3); + } + + $response = curl_exec($ch); + $responseCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + + if (false === $response) { + $errNo = curl_errno($ch); + $errStr = curl_error($ch); + curl_close($ch); + if (empty($errStr)) { + throw new TokenResponseException('Failed to request resource.', $responseCode); + } + throw new TokenResponseException('cURL Error # '.$errNo.': '.$errStr, $responseCode); + } + + curl_close($ch); + + return $response; + } +} diff --git a/htdocs/includes/OAuth/Common/Http/Client/StreamClient.php b/htdocs/includes/OAuth/Common/Http/Client/StreamClient.php new file mode 100644 index 00000000000..d81fee88202 --- /dev/null +++ b/htdocs/includes/OAuth/Common/Http/Client/StreamClient.php @@ -0,0 +1,95 @@ +normalizeHeaders($extraHeaders); + + if ($method === 'GET' && !empty($requestBody)) { + throw new \InvalidArgumentException('No body expected for "GET" request.'); + } + + if (!isset($extraHeaders['Content-Type']) && $method === 'POST' && is_array($requestBody)) { + $extraHeaders['Content-Type'] = 'Content-Type: application/x-www-form-urlencoded'; + } + + $host = 'Host: '.$endpoint->getHost(); + // Append port to Host if it has been specified + if ($endpoint->hasExplicitPortSpecified()) { + $host .= ':'.$endpoint->getPort(); + } + + $extraHeaders['Host'] = $host; + $extraHeaders['Connection'] = 'Connection: close'; + + if (is_array($requestBody)) { + $requestBody = http_build_query($requestBody, '', '&'); + } + $extraHeaders['Content-length'] = 'Content-length: '.strlen($requestBody); + + $context = $this->generateStreamContext($requestBody, $extraHeaders, $method); + + $level = error_reporting(0); + $response = file_get_contents($endpoint->getAbsoluteUri(), false, $context); + error_reporting($level); + if (false === $response) { + $lastError = error_get_last(); + if (is_null($lastError)) { + throw new TokenResponseException( + 'Failed to request resource. HTTP Code: ' . + ((isset($http_response_header[0]))?$http_response_header[0]:'No response') + ); + } + throw new TokenResponseException($lastError['message']); + } + + return $response; + } + + private function generateStreamContext($body, $headers, $method) + { + return stream_context_create( + array( + 'http' => array( + 'method' => $method, + 'header' => implode("\r\n", array_values($headers)), + 'content' => $body, + 'protocol_version' => '1.1', + 'user_agent' => $this->userAgent, + 'max_redirects' => $this->maxRedirects, + 'timeout' => $this->timeout + ), + ) + ); + } +} diff --git a/htdocs/includes/OAuth/Common/Http/Exception/TokenResponseException.php b/htdocs/includes/OAuth/Common/Http/Exception/TokenResponseException.php new file mode 100644 index 00000000000..c519a223b3d --- /dev/null +++ b/htdocs/includes/OAuth/Common/Http/Exception/TokenResponseException.php @@ -0,0 +1,12 @@ +parseUri($uri); + } + } + + /** + * @param string $uri + * + * @throws \InvalidArgumentException + */ + protected function parseUri($uri) + { + if (false === ($uriParts = parse_url($uri))) { + // congratulations if you've managed to get parse_url to fail, + // it seems to always return some semblance of a parsed url no matter what + throw new InvalidArgumentException("Invalid URI: $uri"); + } + + if (!isset($uriParts['scheme'])) { + throw new InvalidArgumentException('Invalid URI: http|https scheme required'); + } + + $this->scheme = $uriParts['scheme']; + $this->host = $uriParts['host']; + + if (isset($uriParts['port'])) { + $this->port = $uriParts['port']; + $this->explicitPortSpecified = true; + } else { + $this->port = strcmp('https', $uriParts['scheme']) ? 80 : 443; + $this->explicitPortSpecified = false; + } + + if (isset($uriParts['path'])) { + $this->path = $uriParts['path']; + if ('/' === $uriParts['path']) { + $this->explicitTrailingHostSlash = true; + } + } else { + $this->path = '/'; + } + + $this->query = isset($uriParts['query']) ? $uriParts['query'] : ''; + $this->fragment = isset($uriParts['fragment']) ? $uriParts['fragment'] : ''; + + $userInfo = ''; + if (!empty($uriParts['user'])) { + $userInfo .= $uriParts['user']; + } + if ($userInfo && !empty($uriParts['pass'])) { + $userInfo .= ':' . $uriParts['pass']; + } + + $this->setUserInfo($userInfo); + } + + /** + * @param string $rawUserInfo + * + * @return string + */ + protected function protectUserInfo($rawUserInfo) + { + $colonPos = strpos($rawUserInfo, ':'); + + // rfc3986-3.2.1 | http://tools.ietf.org/html/rfc3986#section-3.2 + // "Applications should not render as clear text any data + // after the first colon (":") character found within a userinfo + // subcomponent unless the data after the colon is the empty string + // (indicating no password)" + if ($colonPos !== false && strlen($rawUserInfo)-1 > $colonPos) { + return substr($rawUserInfo, 0, $colonPos) . ':********'; + } else { + return $rawUserInfo; + } + } + + /** + * @return string + */ + public function getScheme() + { + return $this->scheme; + } + + /** + * @return string + */ + public function getUserInfo() + { + return $this->userInfo; + } + + /** + * @return string + */ + public function getRawUserInfo() + { + return $this->rawUserInfo; + } + + /** + * @return string + */ + public function getHost() + { + return $this->host; + } + + /** + * @return int + */ + public function getPort() + { + return $this->port; + } + + /** + * @return string + */ + public function getPath() + { + return $this->path; + } + + /** + * @return string + */ + public function getQuery() + { + return $this->query; + } + + /** + * @return string + */ + public function getFragment() + { + return $this->fragment; + } + + /** + * Uses protected user info by default as per rfc3986-3.2.1 + * Uri::getRawAuthority() is available if plain-text password information is desirable. + * + * @return string + */ + public function getAuthority() + { + $authority = $this->userInfo ? $this->userInfo.'@' : ''; + $authority .= $this->host; + + if ($this->explicitPortSpecified) { + $authority .= ":{$this->port}"; + } + + return $authority; + } + + /** + * @return string + */ + public function getRawAuthority() + { + $authority = $this->rawUserInfo ? $this->rawUserInfo.'@' : ''; + $authority .= $this->host; + + if ($this->explicitPortSpecified) { + $authority .= ":{$this->port}"; + } + + return $authority; + } + + /** + * @return string + */ + public function getAbsoluteUri() + { + $uri = $this->scheme . '://' . $this->getRawAuthority(); + + if ('/' === $this->path) { + $uri .= $this->explicitTrailingHostSlash ? '/' : ''; + } else { + $uri .= $this->path; + } + + if (!empty($this->query)) { + $uri .= "?{$this->query}"; + } + + if (!empty($this->fragment)) { + $uri .= "#{$this->fragment}"; + } + + return $uri; + } + + /** + * @return string + */ + public function getRelativeUri() + { + $uri = ''; + + if ('/' === $this->path) { + $uri .= $this->explicitTrailingHostSlash ? '/' : ''; + } else { + $uri .= $this->path; + } + + return $uri; + } + + /** + * Uses protected user info by default as per rfc3986-3.2.1 + * Uri::getAbsoluteUri() is available if plain-text password information is desirable. + * + * @return string + */ + public function __toString() + { + $uri = $this->scheme . '://' . $this->getAuthority(); + + if ('/' === $this->path) { + $uri .= $this->explicitTrailingHostSlash ? '/' : ''; + } else { + $uri .= $this->path; + } + + if (!empty($this->query)) { + $uri .= "?{$this->query}"; + } + + if (!empty($this->fragment)) { + $uri .= "#{$this->fragment}"; + } + + return $uri; + } + + /** + * @param $path + */ + public function setPath($path) + { + if (empty($path)) { + $this->path = '/'; + $this->explicitTrailingHostSlash = false; + } else { + $this->path = $path; + if ('/' === $this->path) { + $this->explicitTrailingHostSlash = true; + } + } + } + + /** + * @param string $query + */ + public function setQuery($query) + { + $this->query = $query; + } + + /** + * @param string $var + * @param string $val + */ + public function addToQuery($var, $val) + { + if (strlen($this->query) > 0) { + $this->query .= '&'; + } + $this->query .= http_build_query(array($var => $val), '', '&'); + } + + /** + * @param string $fragment + */ + public function setFragment($fragment) + { + $this->fragment = $fragment; + } + + /** + * @param string $scheme + */ + public function setScheme($scheme) + { + $this->scheme = $scheme; + } + + + /** + * @param string $userInfo + */ + public function setUserInfo($userInfo) + { + $this->userInfo = $userInfo ? $this->protectUserInfo($userInfo) : ''; + $this->rawUserInfo = $userInfo; + } + + + /** + * @param int $port + */ + public function setPort($port) + { + $this->port = intval($port); + + if (('https' === $this->scheme && $this->port === 443) || ('http' === $this->scheme && $this->port === 80)) { + $this->explicitPortSpecified = false; + } else { + $this->explicitPortSpecified = true; + } + } + + /** + * @param string $host + */ + public function setHost($host) + { + $this->host = $host; + } + + /** + * @return bool + */ + public function hasExplicitTrailingHostSlash() + { + return $this->explicitTrailingHostSlash; + } + + /** + * @return bool + */ + public function hasExplicitPortSpecified() + { + return $this->explicitPortSpecified; + } +} diff --git a/htdocs/includes/OAuth/Common/Http/Uri/UriFactory.php b/htdocs/includes/OAuth/Common/Http/Uri/UriFactory.php new file mode 100644 index 00000000000..127aa203bec --- /dev/null +++ b/htdocs/includes/OAuth/Common/Http/Uri/UriFactory.php @@ -0,0 +1,168 @@ +attemptProxyStyleParse($_server)) { + return $uri; + } + + $scheme = $this->detectScheme($_server); + $host = $this->detectHost($_server); + $port = $this->detectPort($_server); + $path = $this->detectPath($_server); + $query = $this->detectQuery($_server); + + return $this->createFromParts($scheme, '', $host, $port, $path, $query); + } + + /** + * @param string $absoluteUri + * + * @return UriInterface + */ + public function createFromAbsolute($absoluteUri) + { + return new Uri($absoluteUri); + } + + /** + * Factory method to build a URI from parts + * + * @param string $scheme + * @param string $userInfo + * @param string $host + * @param string $port + * @param string $path + * @param string $query + * @param string $fragment + * + * @return UriInterface + */ + public function createFromParts($scheme, $userInfo, $host, $port, $path = '', $query = '', $fragment = '') + { + $uri = new Uri(); + $uri->setScheme($scheme); + $uri->setUserInfo($userInfo); + $uri->setHost($host); + $uri->setPort($port); + $uri->setPath($path); + $uri->setQuery($query); + $uri->setFragment($fragment); + + return $uri; + } + + /** + * @param array $_server + * + * @return UriInterface|null + */ + private function attemptProxyStyleParse($_server) + { + // If the raw HTTP request message arrives with a proxy-style absolute URI in the + // initial request line, the absolute URI is stored in $_SERVER['REQUEST_URI'] and + // we only need to parse that. + if (isset($_server['REQUEST_URI']) && parse_url($_server['REQUEST_URI'], PHP_URL_SCHEME)) { + return new Uri($_server['REQUEST_URI']); + } + + return null; + } + + /** + * @param array $_server + * + * @return string + * + * @throws RuntimeException + */ + private function detectPath($_server) + { + if (isset($_server['REQUEST_URI'])) { + $uri = $_server['REQUEST_URI']; + } elseif (isset($_server['REDIRECT_URL'])) { + $uri = $_server['REDIRECT_URL']; + } else { + throw new RuntimeException('Could not detect URI path from superglobal'); + } + + $queryStr = strpos($uri, '?'); + if ($queryStr !== false) { + $uri = substr($uri, 0, $queryStr); + } + + return $uri; + } + + /** + * @param array $_server + * + * @return string + */ + private function detectHost(array $_server) + { + $host = isset($_server['HTTP_HOST']) ? $_server['HTTP_HOST'] : ''; + + if (strstr($host, ':')) { + $host = parse_url($host, PHP_URL_HOST); + } + + return $host; + } + + /** + * @param array $_server + * + * @return string + */ + private function detectPort(array $_server) + { + return isset($_server['SERVER_PORT']) ? $_server['SERVER_PORT'] : 80; + } + + /** + * @param array $_server + * + * @return string + */ + private function detectQuery(array $_server) + { + return isset($_server['QUERY_STRING']) ? $_server['QUERY_STRING'] : ''; + } + + /** + * Determine URI scheme component from superglobal array + * + * When using ISAPI with IIS, the value will be "off" if the request was + * not made through the HTTPS protocol. As a result, we filter the + * value to a bool. + * + * @param array $_server A super-global $_SERVER array + * + * @return string Returns http or https depending on the URI scheme + */ + private function detectScheme(array $_server) + { + if (isset($_server['HTTPS']) && filter_var($_server['HTTPS'], FILTER_VALIDATE_BOOLEAN)) { + return 'https'; + } else { + return 'http'; + } + } +} diff --git a/htdocs/includes/OAuth/Common/Http/Uri/UriFactoryInterface.php b/htdocs/includes/OAuth/Common/Http/Uri/UriFactoryInterface.php new file mode 100644 index 00000000000..2b157d841aa --- /dev/null +++ b/htdocs/includes/OAuth/Common/Http/Uri/UriFactoryInterface.php @@ -0,0 +1,42 @@ +credentials = $credentials; + $this->httpClient = $httpClient; + $this->storage = $storage; + } + + /** + * @param UriInterface|string $path + * @param UriInterface $baseApiUri + * + * @return UriInterface + * + * @throws Exception + */ + protected function determineRequestUriFromPath($path, UriInterface $baseApiUri = null) + { + if ($path instanceof UriInterface) { + $uri = $path; + } elseif (stripos($path, 'http://') === 0 || stripos($path, 'https://') === 0) { + $uri = new Uri($path); + } else { + if (null === $baseApiUri) { + throw new Exception( + 'An absolute URI must be passed to ServiceInterface::request as no baseApiUri is set.' + ); + } + + $uri = clone $baseApiUri; + if (false !== strpos($path, '?')) { + $parts = explode('?', $path, 2); + $path = $parts[0]; + $query = $parts[1]; + $uri->setQuery($query); + } + + if ($path[0] === '/') { + $path = substr($path, 1); + } + + $uri->setPath($uri->getPath() . $path); + } + + return $uri; + } + + /** + * Accessor to the storage adapter to be able to retrieve tokens + * + * @return TokenStorageInterface + */ + public function getStorage() + { + return $this->storage; + } + + /** + * @return string + */ + public function service() + { + // get class name without backslashes + $classname = get_class($this); + + return preg_replace('/^.*\\\\/', '', $classname); + } +} diff --git a/htdocs/includes/OAuth/Common/Service/ServiceInterface.php b/htdocs/includes/OAuth/Common/Service/ServiceInterface.php new file mode 100644 index 00000000000..5856a03947c --- /dev/null +++ b/htdocs/includes/OAuth/Common/Service/ServiceInterface.php @@ -0,0 +1,49 @@ + + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * 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, see . + */ + +/** + * \file htdocs/includes/OAuth/Common/Storage/DoliStorage.php + * \ingroup oauth + * \brief Dolibarr token storage class + */ + +namespace OAuth\Common\Storage; + +use OAuth\Common\Token\TokenInterface; +use OAuth\Common\Storage\Exception\TokenNotFoundException; +use OAuth\Common\Storage\Exception\AuthorizationStateNotFoundException; +use DoliDB; + +class DoliStorage implements TokenStorageInterface +{ + /** + * @var DoliDB Database handler + */ + protected $db; + + /** + * @var object|TokenInterface + */ + protected $tokens; + + /** + * @var string Error code (or message) + */ + public $error; + /** + * @var string[] Several error codes (or messages) + */ + public $errors = array(); + + private $conf; + private $key; + private $stateKey; + + /** + * @param Conf $conf + * @param string $key + * @param string $stateKey + */ + public function __construct(DoliDB $db, $conf) + { + $this->db = $db; + $this->conf = $conf; + $this->tokens = array(); + $this->states = array(); + //$this->key = $key; + //$this->stateKey = $stateKey; + } + + /** + * {@inheritDoc} + */ + public function retrieveAccessToken($service) + { + if ($this->hasAccessToken($service)) { + return $this->tokens[$service]; + } + + throw new TokenNotFoundException('Token not found in db, are you sure you stored it?'); + } + + /** + * {@inheritDoc} + */ + public function storeAccessToken($service, TokenInterface $token) + { + $serializedToken = serialize($token); + $this->tokens[$service] = $token; + + if (!is_array($this->tokens)) { + $this->tokens = array(); + } + $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."oauth_token"; + $sql.= " WHERE service='".$service."' AND entity=1"; + $resql = $this->db->query($sql); + $obj = $this->db->fetch_array($resql); + if ($obj) { + // update + $sql = "UPDATE ".MAIN_DB_PREFIX."oauth_token"; + $sql.= " SET token='".$this->db->escape($serializedToken)."'"; + $sql.= " WHERE rowid='".$obj['rowid']."'"; + $resql = $this->db->query($sql); + } else { + // save + $sql = "INSERT INTO ".MAIN_DB_PREFIX."oauth_token (service, token, entity)"; + $sql.= " VALUES ('".$service."', '".$this->db->escape($serializedToken)."', 1)"; + $resql = $this->db->query($sql); + } + + // allow chaining + return $this; + } + + /** + * {@inheritDoc} + */ + public function hasAccessToken($service) + { + // get from db + $sql = "SELECT token FROM ".MAIN_DB_PREFIX."oauth_token"; + $sql.= " WHERE service='".$service."'"; + $resql = $this->db->query($sql); + $result = $this->db->fetch_array($resql); + $token = unserialize($result[token]); + $this->tokens[$service] = $token; + + return is_array($this->tokens) + && isset($this->tokens[$service]) + && $this->tokens[$service] instanceof TokenInterface; + } + + /** + * {@inheritDoc} + */ + public function clearToken($service) + { + // TODO + // get previously saved tokens + $tokens = $this->session->get($this->key); + + if (is_array($tokens) && array_key_exists($service, $tokens)) { + unset($tokens[$service]); + + // Replace the stored tokens array + $this->conf->set($this->key, $tokens); + } + + // allow chaining + return $this; + } + + /** + * {@inheritDoc} + */ + public function clearAllTokens() + { + // TODO + $this->conf->remove($this->key); + + // allow chaining + return $this; + } + + /** + * {@inheritDoc} + */ + public function retrieveAuthorizationState($service) + { + if ($this->hasAuthorizationState($service)) { + return $this->states[$service]; + + } + + throw new AuthorizationStateNotFoundException('State not found in conf, are you sure you stored it?'); + } + + /** + * {@inheritDoc} + */ + public function storeAuthorizationState($service, $state) + { + // TODO save or update + // get previously saved tokens + //$states = $this->conf->get($this->stateKey); + + if (!is_array($states)) { + $states = array(); + } + + $states[$service] = $state; + $this->states[$service] = $state; + + // save + $sql = "INSERT INTO ".MAIN_DB_PREFIX."oauth_state (service, state, entity)"; + $sql.= " VALUES ('".$service."', '".$state."', 1)"; + $resql = $this->db->query($sql); + + // allow chaining + return $this; + } + + /** + * {@inheritDoc} + */ + public function hasAuthorizationState($service) + { + // get from db + $sql = "SELECT state FROM ".MAIN_DB_PREFIX."oauth_state"; + $sql.= " WHERE service='".$service."'"; + $resql = $this->db->query($sql); + $result = $this->db->fetch_array($resql); + $states[$service] = $result[state]; + $this->states[$service] = $states[$service]; + + return is_array($states) + && isset($states[$service]) + && null !== $states[$service]; + } + + /** + * {@inheritDoc} + */ + public function clearAuthorizationState($service) + { + // TODO + // get previously saved tokens + //$states = $this->conf->get($this->stateKey); + + if (is_array($states) && array_key_exists($service, $states)) { + unset($states[$service]); + + // Replace the stored tokens array + //$this->conf->set($this->stateKey, $states); + } + + // allow chaining + return $this; + } + + /** + * {@inheritDoc} + */ + public function clearAllAuthorizationStates() + { + // TODO + //$this->conf->remove($this->stateKey); + + // allow chaining + return $this; + } + +} diff --git a/htdocs/includes/OAuth/Common/Storage/Exception/AuthorizationStateNotFoundException.php b/htdocs/includes/OAuth/Common/Storage/Exception/AuthorizationStateNotFoundException.php new file mode 100644 index 00000000000..b3daeabb72e --- /dev/null +++ b/htdocs/includes/OAuth/Common/Storage/Exception/AuthorizationStateNotFoundException.php @@ -0,0 +1,10 @@ +tokens = array(); + $this->states = array(); + } + + /** + * {@inheritDoc} + */ + public function retrieveAccessToken($service) + { + if ($this->hasAccessToken($service)) { + return $this->tokens[$service]; + } + + throw new TokenNotFoundException('Token not stored'); + } + + /** + * {@inheritDoc} + */ + public function storeAccessToken($service, TokenInterface $token) + { + $this->tokens[$service] = $token; + + // allow chaining + return $this; + } + + /** + * {@inheritDoc} + */ + public function hasAccessToken($service) + { + return isset($this->tokens[$service]) && $this->tokens[$service] instanceof TokenInterface; + } + + /** + * {@inheritDoc} + */ + public function clearToken($service) + { + if (array_key_exists($service, $this->tokens)) { + unset($this->tokens[$service]); + } + + // allow chaining + return $this; + } + + /** + * {@inheritDoc} + */ + public function clearAllTokens() + { + $this->tokens = array(); + + // allow chaining + return $this; + } + + /** + * {@inheritDoc} + */ + public function retrieveAuthorizationState($service) + { + if ($this->hasAuthorizationState($service)) { + return $this->states[$service]; + } + + throw new AuthorizationStateNotFoundException('State not stored'); + } + + /** + * {@inheritDoc} + */ + public function storeAuthorizationState($service, $state) + { + $this->states[$service] = $state; + + // allow chaining + return $this; + } + + /** + * {@inheritDoc} + */ + public function hasAuthorizationState($service) + { + return isset($this->states[$service]) && null !== $this->states[$service]; + } + + /** + * {@inheritDoc} + */ + public function clearAuthorizationState($service) + { + if (array_key_exists($service, $this->states)) { + unset($this->states[$service]); + } + + // allow chaining + return $this; + } + + /** + * {@inheritDoc} + */ + public function clearAllAuthorizationStates() + { + $this->states = array(); + + // allow chaining + return $this; + } +} diff --git a/htdocs/includes/OAuth/Common/Storage/Redis.php b/htdocs/includes/OAuth/Common/Storage/Redis.php new file mode 100644 index 00000000000..5d3d9aea006 --- /dev/null +++ b/htdocs/includes/OAuth/Common/Storage/Redis.php @@ -0,0 +1,230 @@ +redis = $redis; + $this->key = $key; + $this->stateKey = $stateKey; + $this->cachedTokens = array(); + $this->cachedStates = array(); + } + + /** + * {@inheritDoc} + */ + public function retrieveAccessToken($service) + { + if (!$this->hasAccessToken($service)) { + throw new TokenNotFoundException('Token not found in redis'); + } + + if (isset($this->cachedTokens[$service])) { + return $this->cachedTokens[$service]; + } + + $val = $this->redis->hget($this->key, $service); + + return $this->cachedTokens[$service] = unserialize($val); + } + + /** + * {@inheritDoc} + */ + public function storeAccessToken($service, TokenInterface $token) + { + // (over)write the token + $this->redis->hset($this->key, $service, serialize($token)); + $this->cachedTokens[$service] = $token; + + // allow chaining + return $this; + } + + /** + * {@inheritDoc} + */ + public function hasAccessToken($service) + { + if (isset($this->cachedTokens[$service]) + && $this->cachedTokens[$service] instanceof TokenInterface + ) { + return true; + } + + return $this->redis->hexists($this->key, $service); + } + + /** + * {@inheritDoc} + */ + public function clearToken($service) + { + $this->redis->hdel($this->key, $service); + unset($this->cachedTokens[$service]); + + // allow chaining + return $this; + } + + /** + * {@inheritDoc} + */ + public function clearAllTokens() + { + // memory + $this->cachedTokens = array(); + + // redis + $keys = $this->redis->hkeys($this->key); + $me = $this; // 5.3 compat + + // pipeline for performance + $this->redis->pipeline( + function ($pipe) use ($keys, $me) { + foreach ($keys as $k) { + $pipe->hdel($me->getKey(), $k); + } + } + ); + + // allow chaining + return $this; + } + + /** + * {@inheritDoc} + */ + public function retrieveAuthorizationState($service) + { + if (!$this->hasAuthorizationState($service)) { + throw new AuthorizationStateNotFoundException('State not found in redis'); + } + + if (isset($this->cachedStates[$service])) { + return $this->cachedStates[$service]; + } + + $val = $this->redis->hget($this->stateKey, $service); + + return $this->cachedStates[$service] = $val; + } + + /** + * {@inheritDoc} + */ + public function storeAuthorizationState($service, $state) + { + // (over)write the token + $this->redis->hset($this->stateKey, $service, $state); + $this->cachedStates[$service] = $state; + + // allow chaining + return $this; + } + + /** + * {@inheritDoc} + */ + public function hasAuthorizationState($service) + { + if (isset($this->cachedStates[$service]) + && null !== $this->cachedStates[$service] + ) { + return true; + } + + return $this->redis->hexists($this->stateKey, $service); + } + + /** + * {@inheritDoc} + */ + public function clearAuthorizationState($service) + { + $this->redis->hdel($this->stateKey, $service); + unset($this->cachedStates[$service]); + + // allow chaining + return $this; + } + + /** + * {@inheritDoc} + */ + public function clearAllAuthorizationStates() + { + // memory + $this->cachedStates = array(); + + // redis + $keys = $this->redis->hkeys($this->stateKey); + $me = $this; // 5.3 compat + + // pipeline for performance + $this->redis->pipeline( + function ($pipe) use ($keys, $me) { + foreach ($keys as $k) { + $pipe->hdel($me->getKey(), $k); + } + } + ); + + // allow chaining + return $this; + } + + /** + * @return Predis $redis + */ + public function getRedis() + { + return $this->redis; + } + + /** + * @return string $key + */ + public function getKey() + { + return $this->key; + } +} diff --git a/htdocs/includes/OAuth/Common/Storage/Session.php b/htdocs/includes/OAuth/Common/Storage/Session.php new file mode 100644 index 00000000000..b011d927503 --- /dev/null +++ b/htdocs/includes/OAuth/Common/Storage/Session.php @@ -0,0 +1,188 @@ +startSession = $startSession; + $this->sessionVariableName = $sessionVariableName; + $this->stateVariableName = $stateVariableName; + if (!isset($_SESSION[$sessionVariableName])) { + $_SESSION[$sessionVariableName] = array(); + } + if (!isset($_SESSION[$stateVariableName])) { + $_SESSION[$stateVariableName] = array(); + } + } + + /** + * {@inheritDoc} + */ + public function retrieveAccessToken($service) + { + if ($this->hasAccessToken($service)) { + return unserialize($_SESSION[$this->sessionVariableName][$service]); + } + + throw new TokenNotFoundException('Token not found in session, are you sure you stored it?'); + } + + /** + * {@inheritDoc} + */ + public function storeAccessToken($service, TokenInterface $token) + { + $serializedToken = serialize($token); + + if (isset($_SESSION[$this->sessionVariableName]) + && is_array($_SESSION[$this->sessionVariableName]) + ) { + $_SESSION[$this->sessionVariableName][$service] = $serializedToken; + } else { + $_SESSION[$this->sessionVariableName] = array( + $service => $serializedToken, + ); + } + + // allow chaining + return $this; + } + + /** + * {@inheritDoc} + */ + public function hasAccessToken($service) + { + return isset($_SESSION[$this->sessionVariableName], $_SESSION[$this->sessionVariableName][$service]); + } + + /** + * {@inheritDoc} + */ + public function clearToken($service) + { + if (array_key_exists($service, $_SESSION[$this->sessionVariableName])) { + unset($_SESSION[$this->sessionVariableName][$service]); + } + + // allow chaining + return $this; + } + + /** + * {@inheritDoc} + */ + public function clearAllTokens() + { + unset($_SESSION[$this->sessionVariableName]); + + // allow chaining + return $this; + } + + /** + * {@inheritDoc} + */ + public function storeAuthorizationState($service, $state) + { + if (isset($_SESSION[$this->stateVariableName]) + && is_array($_SESSION[$this->stateVariableName]) + ) { + $_SESSION[$this->stateVariableName][$service] = $state; + } else { + $_SESSION[$this->stateVariableName] = array( + $service => $state, + ); + } + + // allow chaining + return $this; + } + + /** + * {@inheritDoc} + */ + public function hasAuthorizationState($service) + { + return isset($_SESSION[$this->stateVariableName], $_SESSION[$this->stateVariableName][$service]); + } + + /** + * {@inheritDoc} + */ + public function retrieveAuthorizationState($service) + { + if ($this->hasAuthorizationState($service)) { + return $_SESSION[$this->stateVariableName][$service]; + } + + throw new AuthorizationStateNotFoundException('State not found in session, are you sure you stored it?'); + } + + /** + * {@inheritDoc} + */ + public function clearAuthorizationState($service) + { + if (array_key_exists($service, $_SESSION[$this->stateVariableName])) { + unset($_SESSION[$this->stateVariableName][$service]); + } + + // allow chaining + return $this; + } + + /** + * {@inheritDoc} + */ + public function clearAllAuthorizationStates() + { + unset($_SESSION[$this->stateVariableName]); + + // allow chaining + return $this; + } + + public function __destruct() + { + if ($this->startSession) { + session_write_close(); + } + } +} diff --git a/htdocs/includes/OAuth/Common/Storage/SymfonySession.php b/htdocs/includes/OAuth/Common/Storage/SymfonySession.php new file mode 100644 index 00000000000..6c5fbf60b00 --- /dev/null +++ b/htdocs/includes/OAuth/Common/Storage/SymfonySession.php @@ -0,0 +1,200 @@ +session = $session; + $this->sessionVariableName = $sessionVariableName; + $this->stateVariableName = $stateVariableName; + } + + /** + * {@inheritDoc} + */ + public function retrieveAccessToken($service) + { + if ($this->hasAccessToken($service)) { + // get from session + $tokens = $this->session->get($this->sessionVariableName); + + // one item + return $tokens[$service]; + } + + throw new TokenNotFoundException('Token not found in session, are you sure you stored it?'); + } + + /** + * {@inheritDoc} + */ + public function storeAccessToken($service, TokenInterface $token) + { + // get previously saved tokens + $tokens = $this->session->get($this->sessionVariableName); + + if (!is_array($tokens)) { + $tokens = array(); + } + + $tokens[$service] = $token; + + // save + $this->session->set($this->sessionVariableName, $tokens); + + // allow chaining + return $this; + } + + /** + * {@inheritDoc} + */ + public function hasAccessToken($service) + { + // get from session + $tokens = $this->session->get($this->sessionVariableName); + + return is_array($tokens) + && isset($tokens[$service]) + && $tokens[$service] instanceof TokenInterface; + } + + /** + * {@inheritDoc} + */ + public function clearToken($service) + { + // get previously saved tokens + $tokens = $this->session->get($this->sessionVariableName); + + if (is_array($tokens) && array_key_exists($service, $tokens)) { + unset($tokens[$service]); + + // Replace the stored tokens array + $this->session->set($this->sessionVariableName, $tokens); + } + + // allow chaining + return $this; + } + + /** + * {@inheritDoc} + */ + public function clearAllTokens() + { + $this->session->remove($this->sessionVariableName); + + // allow chaining + return $this; + } + + /** + * {@inheritDoc} + */ + public function retrieveAuthorizationState($service) + { + if ($this->hasAuthorizationState($service)) { + // get from session + $states = $this->session->get($this->stateVariableName); + + // one item + return $states[$service]; + } + + throw new AuthorizationStateNotFoundException('State not found in session, are you sure you stored it?'); + } + + /** + * {@inheritDoc} + */ + public function storeAuthorizationState($service, $state) + { + // get previously saved tokens + $states = $this->session->get($this->stateVariableName); + + if (!is_array($states)) { + $states = array(); + } + + $states[$service] = $state; + + // save + $this->session->set($this->stateVariableName, $states); + + // allow chaining + return $this; + } + + /** + * {@inheritDoc} + */ + public function hasAuthorizationState($service) + { + // get from session + $states = $this->session->get($this->stateVariableName); + + return is_array($states) + && isset($states[$service]) + && null !== $states[$service]; + } + + /** + * {@inheritDoc} + */ + public function clearAuthorizationState($service) + { + // get previously saved tokens + $states = $this->session->get($this->stateVariableName); + + if (is_array($states) && array_key_exists($service, $states)) { + unset($states[$service]); + + // Replace the stored tokens array + $this->session->set($this->stateVariableName, $states); + } + + // allow chaining + return $this; + } + + /** + * {@inheritDoc} + */ + public function clearAllAuthorizationStates() + { + $this->session->remove($this->stateVariableName); + + // allow chaining + return $this; + } + + /** + * @return Session + */ + public function getSession() + { + return $this->session; + } +} diff --git a/htdocs/includes/OAuth/Common/Storage/TokenStorageInterface.php b/htdocs/includes/OAuth/Common/Storage/TokenStorageInterface.php new file mode 100644 index 00000000000..46552cee936 --- /dev/null +++ b/htdocs/includes/OAuth/Common/Storage/TokenStorageInterface.php @@ -0,0 +1,98 @@ +accessToken = $accessToken; + $this->refreshToken = $refreshToken; + $this->setLifetime($lifetime); + $this->extraParams = $extraParams; + } + + /** + * @return string + */ + public function getAccessToken() + { + return $this->accessToken; + } + + /** + * @return string + */ + public function getRefreshToken() + { + return $this->refreshToken; + } + + /** + * @return int + */ + public function getEndOfLife() + { + return $this->endOfLife; + } + + /** + * @param array $extraParams + */ + public function setExtraParams(array $extraParams) + { + $this->extraParams = $extraParams; + } + + /** + * @return array + */ + public function getExtraParams() + { + return $this->extraParams; + } + + /** + * @param string $accessToken + */ + public function setAccessToken($accessToken) + { + $this->accessToken = $accessToken; + } + + /** + * @param int $endOfLife + */ + public function setEndOfLife($endOfLife) + { + $this->endOfLife = $endOfLife; + } + + /** + * @param int $lifetime + */ + public function setLifetime($lifetime) + { + if (0 === $lifetime || static::EOL_NEVER_EXPIRES === $lifetime) { + $this->endOfLife = static::EOL_NEVER_EXPIRES; + } elseif (null !== $lifetime) { + $this->endOfLife = intval($lifetime) + time(); + } else { + $this->endOfLife = static::EOL_UNKNOWN; + } + } + + /** + * @param string $refreshToken + */ + public function setRefreshToken($refreshToken) + { + $this->refreshToken = $refreshToken; + } + + public function isExpired() + { + return ($this->getEndOfLife() !== TokenInterface::EOL_NEVER_EXPIRES + && $this->getEndOfLife() !== TokenInterface::EOL_UNKNOWN + && time() > $this->getEndOfLife()); + } +} diff --git a/htdocs/includes/OAuth/Common/Token/Exception/ExpiredTokenException.php b/htdocs/includes/OAuth/Common/Token/Exception/ExpiredTokenException.php new file mode 100644 index 00000000000..26ad6cc5cb1 --- /dev/null +++ b/htdocs/includes/OAuth/Common/Token/Exception/ExpiredTokenException.php @@ -0,0 +1,12 @@ +signature = $signature; + $this->baseApiUri = $baseApiUri; + + $this->signature->setHashingAlgorithm($this->getSignatureMethod()); + } + + /** + * {@inheritDoc} + */ + public function requestRequestToken() + { + $authorizationHeader = array('Authorization' => $this->buildAuthorizationHeaderForTokenRequest()); + $headers = array_merge($authorizationHeader, $this->getExtraOAuthHeaders()); + + $responseBody = $this->httpClient->retrieveResponse($this->getRequestTokenEndpoint(), array(), $headers); + + $token = $this->parseRequestTokenResponse($responseBody); + $this->storage->storeAccessToken($this->service(), $token); + + return $token; + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationUri(array $additionalParameters = array()) + { + // Build the url + $url = clone $this->getAuthorizationEndpoint(); + foreach ($additionalParameters as $key => $val) { + $url->addToQuery($key, $val); + } + + return $url; + } + + /** + * {@inheritDoc} + */ + public function requestAccessToken($token, $verifier, $tokenSecret = null) + { + if (is_null($tokenSecret)) { + $storedRequestToken = $this->storage->retrieveAccessToken($this->service()); + $tokenSecret = $storedRequestToken->getRequestTokenSecret(); + } + $this->signature->setTokenSecret($tokenSecret); + + $bodyParams = array( + 'oauth_verifier' => $verifier, + ); + + $authorizationHeader = array( + 'Authorization' => $this->buildAuthorizationHeaderForAPIRequest( + 'POST', + $this->getAccessTokenEndpoint(), + $this->storage->retrieveAccessToken($this->service()), + $bodyParams + ) + ); + + $headers = array_merge($authorizationHeader, $this->getExtraOAuthHeaders()); + + $responseBody = $this->httpClient->retrieveResponse($this->getAccessTokenEndpoint(), $bodyParams, $headers); + + $token = $this->parseAccessTokenResponse($responseBody); + $this->storage->storeAccessToken($this->service(), $token); + + return $token; + } + + /** + * Refreshes an OAuth1 access token + * @param TokenInterface $token + * @return TokenInterface $token + */ + public function refreshAccessToken(TokenInterface $token) + { + } + + /** + * Sends an authenticated API request to the path provided. + * If the path provided is not an absolute URI, the base API Uri (must be passed into constructor) will be used. + * + * @param string|UriInterface $path + * @param string $method HTTP method + * @param array $body Request body if applicable (key/value pairs) + * @param array $extraHeaders Extra headers if applicable. + * These will override service-specific any defaults. + * + * @return string + */ + public function request($path, $method = 'GET', $body = null, array $extraHeaders = array()) + { + $uri = $this->determineRequestUriFromPath($path, $this->baseApiUri); + + /** @var $token StdOAuth1Token */ + $token = $this->storage->retrieveAccessToken($this->service()); + $extraHeaders = array_merge($this->getExtraApiHeaders(), $extraHeaders); + $authorizationHeader = array( + 'Authorization' => $this->buildAuthorizationHeaderForAPIRequest($method, $uri, $token, $body) + ); + $headers = array_merge($authorizationHeader, $extraHeaders); + + return $this->httpClient->retrieveResponse($uri, $body, $headers, $method); + } + + /** + * Return any additional headers always needed for this service implementation's OAuth calls. + * + * @return array + */ + protected function getExtraOAuthHeaders() + { + return array(); + } + + /** + * Return any additional headers always needed for this service implementation's API calls. + * + * @return array + */ + protected function getExtraApiHeaders() + { + return array(); + } + + /** + * Builds the authorization header for getting an access or request token. + * + * @param array $extraParameters + * + * @return string + */ + protected function buildAuthorizationHeaderForTokenRequest(array $extraParameters = array()) + { + $parameters = $this->getBasicAuthorizationHeaderInfo(); + $parameters = array_merge($parameters, $extraParameters); + $parameters['oauth_signature'] = $this->signature->getSignature( + $this->getRequestTokenEndpoint(), + $parameters, + 'POST' + ); + + $authorizationHeader = 'OAuth '; + $delimiter = ''; + foreach ($parameters as $key => $value) { + $authorizationHeader .= $delimiter . rawurlencode($key) . '="' . rawurlencode($value) . '"'; + + $delimiter = ', '; + } + + return $authorizationHeader; + } + + /** + * Builds the authorization header for an authenticated API request + * + * @param string $method + * @param UriInterface $uri The uri the request is headed + * @param TokenInterface $token + * @param array $bodyParams Request body if applicable (key/value pairs) + * + * @return string + */ + protected function buildAuthorizationHeaderForAPIRequest( + $method, + UriInterface $uri, + TokenInterface $token, + $bodyParams = null + ) { + $this->signature->setTokenSecret($token->getAccessTokenSecret()); + $authParameters = $this->getBasicAuthorizationHeaderInfo(); + if (isset($authParameters['oauth_callback'])) { + unset($authParameters['oauth_callback']); + } + + $authParameters = array_merge($authParameters, array('oauth_token' => $token->getAccessToken())); + + $authParameters = (is_array($bodyParams)) ? array_merge($authParameters, $bodyParams) : $authParameters; + $authParameters['oauth_signature'] = $this->signature->getSignature($uri, $authParameters, $method); + + if (isset($bodyParams['oauth_session_handle'])) { + $authParameters['oauth_session_handle'] = $bodyParams['oauth_session_handle']; + unset($bodyParams['oauth_session_handle']); + } + + $authorizationHeader = 'OAuth '; + $delimiter = ''; + + foreach ($authParameters as $key => $value) { + $authorizationHeader .= $delimiter . rawurlencode($key) . '="' . rawurlencode($value) . '"'; + $delimiter = ', '; + } + + return $authorizationHeader; + } + + /** + * Builds the authorization header array. + * + * @return array + */ + protected function getBasicAuthorizationHeaderInfo() + { + $dateTime = new \DateTime(); + $headerParameters = array( + 'oauth_callback' => $this->credentials->getCallbackUrl(), + 'oauth_consumer_key' => $this->credentials->getConsumerId(), + 'oauth_nonce' => $this->generateNonce(), + 'oauth_signature_method' => $this->getSignatureMethod(), + 'oauth_timestamp' => $dateTime->format('U'), + 'oauth_version' => $this->getVersion(), + ); + + return $headerParameters; + } + + /** + * Pseudo random string generator used to build a unique string to sign each request + * + * @param int $length + * + * @return string + */ + protected function generateNonce($length = 32) + { + $characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'; + + $nonce = ''; + $maxRand = strlen($characters)-1; + for ($i = 0; $i < $length; $i++) { + $nonce.= $characters[rand(0, $maxRand)]; + } + + return $nonce; + } + + /** + * @return string + */ + protected function getSignatureMethod() + { + return 'HMAC-SHA1'; + } + + /** + * This returns the version used in the authorization header of the requests + * + * @return string + */ + protected function getVersion() + { + return '1.0'; + } + + /** + * Parses the request token response and returns a TokenInterface. + * This is only needed to verify the `oauth_callback_confirmed` parameter. The actual + * parsing logic is contained in the access token parser. + * + * @abstract + * + * @param string $responseBody + * + * @return TokenInterface + * + * @throws TokenResponseException + */ + abstract protected function parseRequestTokenResponse($responseBody); + + /** + * Parses the access token response and returns a TokenInterface. + * + * @abstract + * + * @param string $responseBody + * + * @return TokenInterface + * + * @throws TokenResponseException + */ + abstract protected function parseAccessTokenResponse($responseBody); +} diff --git a/htdocs/includes/OAuth/OAuth1/Service/BitBucket.php b/htdocs/includes/OAuth/OAuth1/Service/BitBucket.php new file mode 100644 index 00000000000..f6d8edfe17d --- /dev/null +++ b/htdocs/includes/OAuth/OAuth1/Service/BitBucket.php @@ -0,0 +1,96 @@ +baseApiUri = new Uri('https://bitbucket.org/api/1.0/'); + } + } + + /** + * {@inheritDoc} + */ + public function getRequestTokenEndpoint() + { + return new Uri('https://bitbucket.org/!api/1.0/oauth/request_token'); + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://bitbucket.org/!api/1.0/oauth/authenticate'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://bitbucket.org/!api/1.0/oauth/access_token'); + } + + /** + * {@inheritdoc} + */ + protected function parseRequestTokenResponse($responseBody) + { + parse_str($responseBody, $data); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (!isset($data['oauth_callback_confirmed']) || $data['oauth_callback_confirmed'] !== 'true') { + throw new TokenResponseException('Error in retrieving token.'); + } + + return $this->parseAccessTokenResponse($responseBody); + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + parse_str($responseBody, $data); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth1Token(); + + $token->setRequestToken($data['oauth_token']); + $token->setRequestTokenSecret($data['oauth_token_secret']); + $token->setAccessToken($data['oauth_token']); + $token->setAccessTokenSecret($data['oauth_token_secret']); + + $token->setEndOfLife(StdOAuth1Token::EOL_NEVER_EXPIRES); + unset($data['oauth_token'], $data['oauth_token_secret']); + $token->setExtraParams($data); + + return $token; + } +} diff --git a/htdocs/includes/OAuth/OAuth1/Service/Etsy.php b/htdocs/includes/OAuth/OAuth1/Service/Etsy.php new file mode 100644 index 00000000000..30dc331cba7 --- /dev/null +++ b/htdocs/includes/OAuth/OAuth1/Service/Etsy.php @@ -0,0 +1,132 @@ +baseApiUri = new Uri('https://openapi.etsy.com/v2/'); + } + } + + /** + * {@inheritdoc} + */ + public function getRequestTokenEndpoint() + { + $uri = new Uri($this->baseApiUri . 'oauth/request_token'); + $scopes = $this->getScopes(); + + if (count($scopes)) { + $uri->setQuery('scope=' . implode('%20', $scopes)); + } + + return $uri; + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri($this->baseApiUri); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri($this->baseApiUri . 'oauth/access_token'); + } + + /** + * {@inheritdoc} + */ + protected function parseRequestTokenResponse($responseBody) + { + parse_str($responseBody, $data); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (!isset($data['oauth_callback_confirmed']) || $data['oauth_callback_confirmed'] !== 'true') { + throw new TokenResponseException('Error in retrieving token.'); + } + + return $this->parseAccessTokenResponse($responseBody); + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + parse_str($responseBody, $data); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth1Token(); + + $token->setRequestToken($data['oauth_token']); + $token->setRequestTokenSecret($data['oauth_token_secret']); + $token->setAccessToken($data['oauth_token']); + $token->setAccessTokenSecret($data['oauth_token_secret']); + + $token->setEndOfLife(StdOAuth1Token::EOL_NEVER_EXPIRES); + unset($data['oauth_token'], $data['oauth_token_secret']); + $token->setExtraParams($data); + + return $token; + } + + /** + * Set the scopes for permissions + * @see https://www.etsy.com/developers/documentation/getting_started/oauth#section_permission_scopes + * @param array $scopes + * + * @return $this + */ + public function setScopes(array $scopes) + { + if (!is_array($scopes)) { + $scopes = array(); + } + + $this->scopes = $scopes; + return $this; + } + + /** + * Return the defined scopes + * @return array + */ + public function getScopes() + { + return $this->scopes; + } +} diff --git a/htdocs/includes/OAuth/OAuth1/Service/FitBit.php b/htdocs/includes/OAuth/OAuth1/Service/FitBit.php new file mode 100644 index 00000000000..78032d75a77 --- /dev/null +++ b/htdocs/includes/OAuth/OAuth1/Service/FitBit.php @@ -0,0 +1,96 @@ +baseApiUri = new Uri('https://api.fitbit.com/1/'); + } + } + + /** + * {@inheritdoc} + */ + public function getRequestTokenEndpoint() + { + return new Uri('https://api.fitbit.com/oauth/request_token'); + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://www.fitbit.com/oauth/authorize'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://api.fitbit.com/oauth/access_token'); + } + + /** + * {@inheritdoc} + */ + protected function parseRequestTokenResponse($responseBody) + { + parse_str($responseBody, $data); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (!isset($data['oauth_callback_confirmed']) || $data['oauth_callback_confirmed'] !== 'true') { + throw new TokenResponseException('Error in retrieving token.'); + } + + return $this->parseAccessTokenResponse($responseBody); + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + parse_str($responseBody, $data); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth1Token(); + + $token->setRequestToken($data['oauth_token']); + $token->setRequestTokenSecret($data['oauth_token_secret']); + $token->setAccessToken($data['oauth_token']); + $token->setAccessTokenSecret($data['oauth_token_secret']); + + $token->setEndOfLife(StdOAuth1Token::EOL_NEVER_EXPIRES); + unset($data['oauth_token'], $data['oauth_token_secret']); + $token->setExtraParams($data); + + return $token; + } +} diff --git a/htdocs/includes/OAuth/OAuth1/Service/FiveHundredPx.php b/htdocs/includes/OAuth/OAuth1/Service/FiveHundredPx.php new file mode 100644 index 00000000000..ea7f9b34d62 --- /dev/null +++ b/htdocs/includes/OAuth/OAuth1/Service/FiveHundredPx.php @@ -0,0 +1,120 @@ + + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link https://developers.500px.com/ + */ + +namespace OAuth\OAuth1\Service; + +use OAuth\OAuth1\Signature\SignatureInterface; +use OAuth\OAuth1\Token\StdOAuth1Token; +use OAuth\Common\Http\Exception\TokenResponseException; +use OAuth\Common\Http\Uri\Uri; +use OAuth\Common\Consumer\CredentialsInterface; +use OAuth\Common\Http\Uri\UriInterface; +use OAuth\Common\Storage\TokenStorageInterface; +use OAuth\Common\Http\Client\ClientInterface; + +/** + * 500px service. + * + * @author Pedro Amorim + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link https://developers.500px.com/ + */ +class FiveHundredPx extends AbstractService +{ + public function __construct( + CredentialsInterface $credentials, + ClientInterface $httpClient, + TokenStorageInterface $storage, + SignatureInterface $signature, + UriInterface $baseApiUri = null + ) { + parent::__construct( + $credentials, + $httpClient, + $storage, + $signature, + $baseApiUri + ); + + if (null === $baseApiUri) { + $this->baseApiUri = new Uri('https://api.500px.com/v1/'); + } + } + + /** + * {@inheritDoc} + */ + public function getRequestTokenEndpoint() + { + return new Uri('https://api.500px.com/v1/oauth/request_token'); + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://api.500px.com/v1/oauth/authorize'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://api.500px.com/v1/oauth/access_token'); + } + + /** + * {@inheritdoc} + */ + protected function parseRequestTokenResponse($responseBody) + { + parse_str($responseBody, $data); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (!isset($data['oauth_callback_confirmed']) + || $data['oauth_callback_confirmed'] !== 'true' + ) { + throw new TokenResponseException('Error in retrieving token.'); + } + + return $this->parseAccessTokenResponse($responseBody); + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + parse_str($responseBody, $data); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException( + 'Error in retrieving token: "' . $data['error'] . '"' + ); + } + + $token = new StdOAuth1Token(); + + $token->setRequestToken($data['oauth_token']); + $token->setRequestTokenSecret($data['oauth_token_secret']); + $token->setAccessToken($data['oauth_token']); + $token->setAccessTokenSecret($data['oauth_token_secret']); + + $token->setEndOfLife(StdOAuth1Token::EOL_NEVER_EXPIRES); + unset($data['oauth_token'], $data['oauth_token_secret']); + $token->setExtraParams($data); + + return $token; + } +} diff --git a/htdocs/includes/OAuth/OAuth1/Service/Flickr.php b/htdocs/includes/OAuth/OAuth1/Service/Flickr.php new file mode 100644 index 00000000000..7ceee7dccaa --- /dev/null +++ b/htdocs/includes/OAuth/OAuth1/Service/Flickr.php @@ -0,0 +1,133 @@ +baseApiUri = new Uri('https://api.flickr.com/services/rest/'); + } + } + + public function getRequestTokenEndpoint() + { + return new Uri('https://www.flickr.com/services/oauth/request_token'); + } + + public function getAuthorizationEndpoint() + { + return new Uri('https://www.flickr.com/services/oauth/authorize'); + } + + public function getAccessTokenEndpoint() + { + return new Uri('https://www.flickr.com/services/oauth/access_token'); + } + + protected function parseRequestTokenResponse($responseBody) + { + parse_str($responseBody, $data); + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (!isset($data['oauth_callback_confirmed']) || $data['oauth_callback_confirmed'] != 'true') { + throw new TokenResponseException('Error in retrieving token.'); + } + return $this->parseAccessTokenResponse($responseBody); + } + + protected function parseAccessTokenResponse($responseBody) + { + parse_str($responseBody, $data); + if ($data === null || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth1Token(); + $token->setRequestToken($data['oauth_token']); + $token->setRequestTokenSecret($data['oauth_token_secret']); + $token->setAccessToken($data['oauth_token']); + $token->setAccessTokenSecret($data['oauth_token_secret']); + $token->setEndOfLife(StdOAuth1Token::EOL_NEVER_EXPIRES); + unset($data['oauth_token'], $data['oauth_token_secret']); + $token->setExtraParams($data); + + return $token; + } + + public function request($path, $method = 'GET', $body = null, array $extraHeaders = array()) + { + $uri = $this->determineRequestUriFromPath('/', $this->baseApiUri); + $uri->addToQuery('method', $path); + + if (!empty($this->format)) { + $uri->addToQuery('format', $this->format); + + if ($this->format === 'json') { + $uri->addToQuery('nojsoncallback', 1); + } + } + + $token = $this->storage->retrieveAccessToken($this->service()); + $extraHeaders = array_merge($this->getExtraApiHeaders(), $extraHeaders); + $authorizationHeader = array( + 'Authorization' => $this->buildAuthorizationHeaderForAPIRequest($method, $uri, $token, $body) + ); + $headers = array_merge($authorizationHeader, $extraHeaders); + + return $this->httpClient->retrieveResponse($uri, $body, $headers, $method); + } + + public function requestRest($path, $method = 'GET', $body = null, array $extraHeaders = array()) + { + return $this->request($path, $method, $body, $extraHeaders); + } + + public function requestXmlrpc($path, $method = 'GET', $body = null, array $extraHeaders = array()) + { + $this->format = 'xmlrpc'; + + return $this->request($path, $method, $body, $extraHeaders); + } + + public function requestSoap($path, $method = 'GET', $body = null, array $extraHeaders = array()) + { + $this->format = 'soap'; + + return $this->request($path, $method, $body, $extraHeaders); + } + + public function requestJson($path, $method = 'GET', $body = null, array $extraHeaders = array()) + { + $this->format = 'json'; + + return $this->request($path, $method, $body, $extraHeaders); + } + + public function requestPhp($path, $method = 'GET', $body = null, array $extraHeaders = array()) + { + $this->format = 'php_serial'; + + return $this->request($path, $method, $body, $extraHeaders); + } +} diff --git a/htdocs/includes/OAuth/OAuth1/Service/QuickBooks.php b/htdocs/includes/OAuth/OAuth1/Service/QuickBooks.php new file mode 100644 index 00000000000..0014ca817d5 --- /dev/null +++ b/htdocs/includes/OAuth/OAuth1/Service/QuickBooks.php @@ -0,0 +1,120 @@ +baseApiUri = new Uri('https://quickbooks.api.intuit.com/'); + } + } + + /** + * {@inheritdoc} + */ + public function getRequestTokenEndpoint() + { + return new Uri('https://oauth.intuit.com/oauth/v1/get_request_token'); + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://appcenter.intuit.com/Connect/Begin'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://oauth.intuit.com/oauth/v1/get_access_token'); + } + + /** + * {@inheritdoc} + */ + protected function parseRequestTokenResponse($responseBody) + { + parse_str($responseBody, $data); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (!isset($data['oauth_callback_confirmed']) + || $data['oauth_callback_confirmed'] !== 'true') { + throw new TokenResponseException('Error in retrieving token.'); + } + + return $this->parseAccessTokenResponse($responseBody); + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + parse_str($responseBody, $data); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + $message = 'Error in retrieving token: "' . $data['error'] . '"'; + throw new TokenResponseException($message); + } + + $token = new StdOAuth1Token(); + + $token->setRequestToken($data['oauth_token']); + $token->setRequestTokenSecret($data['oauth_token_secret']); + $token->setAccessToken($data['oauth_token']); + $token->setAccessTokenSecret($data['oauth_token_secret']); + + $token->setEndOfLife(StdOAuth1Token::EOL_NEVER_EXPIRES); + unset($data['oauth_token'], $data['oauth_token_secret']); + $token->setExtraParams($data); + + return $token; + } + + /** + * {@inheritDoc} + */ + public function request( + $path, + $method = 'GET', + $body = null, + array $extraHeaders = array() + ) { + $extraHeaders['Accept'] = 'application/json'; + return parent::request($path, $method, $body, $extraHeaders); + } +} diff --git a/htdocs/includes/OAuth/OAuth1/Service/Redmine.php b/htdocs/includes/OAuth/OAuth1/Service/Redmine.php new file mode 100644 index 00000000000..55f89a27594 --- /dev/null +++ b/htdocs/includes/OAuth/OAuth1/Service/Redmine.php @@ -0,0 +1,96 @@ +baseApiUri->getAbsoluteUri() . '/request_token'); + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri($this->baseApiUri->getAbsoluteUri() . '/authorize'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri($this->baseApiUri->getAbsoluteUri() . '/access_token'); + } + + /** + * {@inheritdoc} + */ + protected function parseRequestTokenResponse($responseBody) + { + parse_str($responseBody, $data); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (!isset($data['oauth_callback_confirmed']) || $data['oauth_callback_confirmed'] !== 'true') { + throw new TokenResponseException('Error in retrieving token.'); + } + + return $this->parseAccessTokenResponse($responseBody); + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + parse_str($responseBody, $data); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth1Token(); + + $token->setRequestToken($data['oauth_token']); + $token->setRequestTokenSecret($data['oauth_token_secret']); + $token->setAccessToken($data['oauth_token']); + $token->setAccessTokenSecret($data['oauth_token_secret']); + + $token->setEndOfLife(StdOAuth1Token::EOL_NEVER_EXPIRES); + unset($data['oauth_token'], $data['oauth_token_secret']); + $token->setExtraParams($data); + + return $token; + } +} diff --git a/htdocs/includes/OAuth/OAuth1/Service/ScoopIt.php b/htdocs/includes/OAuth/OAuth1/Service/ScoopIt.php new file mode 100644 index 00000000000..28bd250b79f --- /dev/null +++ b/htdocs/includes/OAuth/OAuth1/Service/ScoopIt.php @@ -0,0 +1,96 @@ +baseApiUri = new Uri('https://www.scoop.it/api/1/'); + } + } + + /** + * {@inheritDoc} + */ + public function getRequestTokenEndpoint() + { + return new Uri('https://www.scoop.it/oauth/request'); + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://www.scoop.it/oauth/authorize'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://www.scoop.it/oauth/access'); + } + + /** + * {@inheritdoc} + */ + protected function parseRequestTokenResponse($responseBody) + { + parse_str($responseBody, $data); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (!isset($data['oauth_callback_confirmed']) || $data['oauth_callback_confirmed'] !== 'true') { + throw new TokenResponseException('Error in retrieving token.'); + } + + return $this->parseAccessTokenResponse($responseBody); + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + parse_str($responseBody, $data); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth1Token(); + + $token->setRequestToken($data['oauth_token']); + $token->setRequestTokenSecret($data['oauth_token_secret']); + $token->setAccessToken($data['oauth_token']); + $token->setAccessTokenSecret($data['oauth_token_secret']); + + $token->setEndOfLife(StdOAuth1Token::EOL_NEVER_EXPIRES); + unset($data['oauth_token'], $data['oauth_token_secret']); + $token->setExtraParams($data); + + return $token; + } +} diff --git a/htdocs/includes/OAuth/OAuth1/Service/ServiceInterface.php b/htdocs/includes/OAuth/OAuth1/Service/ServiceInterface.php new file mode 100644 index 00000000000..3f91fbf2c4c --- /dev/null +++ b/htdocs/includes/OAuth/OAuth1/Service/ServiceInterface.php @@ -0,0 +1,45 @@ +baseApiUri = new Uri('https://api.tumblr.com/v2/'); + } + } + + /** + * {@inheritdoc} + */ + public function getRequestTokenEndpoint() + { + return new Uri('https://www.tumblr.com/oauth/request_token'); + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://www.tumblr.com/oauth/authorize'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://www.tumblr.com/oauth/access_token'); + } + + /** + * {@inheritdoc} + */ + protected function parseRequestTokenResponse($responseBody) + { + parse_str($responseBody, $data); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (!isset($data['oauth_callback_confirmed']) || $data['oauth_callback_confirmed'] !== 'true') { + throw new TokenResponseException('Error in retrieving token.'); + } + + return $this->parseAccessTokenResponse($responseBody); + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + parse_str($responseBody, $data); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth1Token(); + + $token->setRequestToken($data['oauth_token']); + $token->setRequestTokenSecret($data['oauth_token_secret']); + $token->setAccessToken($data['oauth_token']); + $token->setAccessTokenSecret($data['oauth_token_secret']); + + $token->setEndOfLife(StdOAuth1Token::EOL_NEVER_EXPIRES); + unset($data['oauth_token'], $data['oauth_token_secret']); + $token->setExtraParams($data); + + return $token; + } +} diff --git a/htdocs/includes/OAuth/OAuth1/Service/Twitter.php b/htdocs/includes/OAuth/OAuth1/Service/Twitter.php new file mode 100644 index 00000000000..dea680fe44d --- /dev/null +++ b/htdocs/includes/OAuth/OAuth1/Service/Twitter.php @@ -0,0 +1,123 @@ +baseApiUri = new Uri('https://api.twitter.com/1.1/'); + } + } + + /** + * {@inheritdoc} + */ + public function getRequestTokenEndpoint() + { + return new Uri('https://api.twitter.com/oauth/request_token'); + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + if ($this->authorizationEndpoint != self::ENDPOINT_AUTHENTICATE + && $this->authorizationEndpoint != self::ENDPOINT_AUTHORIZE) { + $this->authorizationEndpoint = self::ENDPOINT_AUTHENTICATE; + } + return new Uri($this->authorizationEndpoint); + } + + /** + * @param string $authorizationEndpoint + * + * @throws Exception + */ + public function setAuthorizationEndpoint($endpoint) + { + if ($endpoint != self::ENDPOINT_AUTHENTICATE && $endpoint != self::ENDPOINT_AUTHORIZE) { + throw new Exception( + sprintf("'%s' is not a correct Twitter authorization endpoint.", $endpoint) + ); + } + $this->authorizationEndpoint = $endpoint; + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://api.twitter.com/oauth/access_token'); + } + + /** + * {@inheritdoc} + */ + protected function parseRequestTokenResponse($responseBody) + { + parse_str($responseBody, $data); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (!isset($data['oauth_callback_confirmed']) || $data['oauth_callback_confirmed'] !== 'true') { + throw new TokenResponseException('Error in retrieving token.'); + } + + return $this->parseAccessTokenResponse($responseBody); + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + parse_str($responseBody, $data); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response: ' . $responseBody); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } elseif (!isset($data["oauth_token"]) || !isset($data["oauth_token_secret"])) { + throw new TokenResponseException('Invalid response. OAuth Token data not set: ' . $responseBody); + } + + $token = new StdOAuth1Token(); + + $token->setRequestToken($data['oauth_token']); + $token->setRequestTokenSecret($data['oauth_token_secret']); + $token->setAccessToken($data['oauth_token']); + $token->setAccessTokenSecret($data['oauth_token_secret']); + + $token->setEndOfLife(StdOAuth1Token::EOL_NEVER_EXPIRES); + unset($data['oauth_token'], $data['oauth_token_secret']); + $token->setExtraParams($data); + + return $token; + } +} diff --git a/htdocs/includes/OAuth/OAuth1/Service/Xing.php b/htdocs/includes/OAuth/OAuth1/Service/Xing.php new file mode 100644 index 00000000000..e6824db3f0b --- /dev/null +++ b/htdocs/includes/OAuth/OAuth1/Service/Xing.php @@ -0,0 +1,97 @@ +baseApiUri = new Uri('https://api.xing.com/v1/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://api.xing.com/v1/authorize'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://api.xing.com/v1/access_token'); + } + + /** + * {@inheritdoc} + */ + public function getRequestTokenEndpoint() + { + return new Uri('https://api.xing.com/v1/request_token'); + } + + /** + * {@inheritdoc} + */ + protected function parseRequestTokenResponse($responseBody) + { + parse_str($responseBody, $data); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (!isset($data['oauth_callback_confirmed']) || $data['oauth_callback_confirmed'] !== 'true') { + throw new TokenResponseException('Error in retrieving token.'); + } + + return $this->parseAccessTokenResponse($responseBody); + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + parse_str($responseBody, $data); + $errors = json_decode($responseBody); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif ($errors) { + throw new TokenResponseException('Error in retrieving token: "' . $errors->error_name . '"'); + } + + $token = new StdOAuth1Token(); + + $token->setRequestToken($data['oauth_token']); + $token->setRequestTokenSecret($data['oauth_token_secret']); + $token->setAccessToken($data['oauth_token']); + $token->setAccessTokenSecret($data['oauth_token_secret']); + + $token->setEndOfLife(StdOAuth1Token::EOL_NEVER_EXPIRES); + unset($data['oauth_token'], $data['oauth_token_secret']); + $token->setExtraParams($data); + + return $token; + } +} diff --git a/htdocs/includes/OAuth/OAuth1/Service/Yahoo.php b/htdocs/includes/OAuth/OAuth1/Service/Yahoo.php new file mode 100644 index 00000000000..50a825b2546 --- /dev/null +++ b/htdocs/includes/OAuth/OAuth1/Service/Yahoo.php @@ -0,0 +1,131 @@ +baseApiUri = new Uri('https://social.yahooapis.com/v1/'); + } + } + + /** + * {@inheritDoc} + */ + public function getRequestTokenEndpoint() + { + return new Uri('https://api.login.yahoo.com/oauth/v2/get_request_token'); + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://api.login.yahoo.com/oauth/v2/request_auth'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://api.login.yahoo.com/oauth/v2/get_token'); + } + + /** + * {@inheritdoc} + */ + public function refreshAccessToken(TokenInterface $token) + { + $extraParams = $token->getExtraParams(); + $bodyParams = array('oauth_session_handle' => $extraParams['oauth_session_handle']); + + $authorizationHeader = array( + 'Authorization' => $this->buildAuthorizationHeaderForAPIRequest( + 'POST', + $this->getAccessTokenEndpoint(), + $this->storage->retrieveAccessToken($this->service()), + $bodyParams + ) + ); + + + + $headers = array_merge($authorizationHeader, $this->getExtraOAuthHeaders(), array()); + + $responseBody = $this->httpClient->retrieveResponse($this->getAccessTokenEndpoint(), $bodyParams, $headers); + + $token = $this->parseAccessTokenResponse($responseBody); + $this->storage->storeAccessToken($this->service(), $token); + + return $token; + } + + /** + * {@inheritdoc} + */ + protected function parseRequestTokenResponse($responseBody) + { + parse_str($responseBody, $data); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (!isset($data['oauth_callback_confirmed']) || $data['oauth_callback_confirmed'] !== 'true') { + throw new TokenResponseException('Error in retrieving token.'); + } + + return $this->parseAccessTokenResponse($responseBody); + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + parse_str($responseBody, $data); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth1Token(); + + $token->setRequestToken($data['oauth_token']); + $token->setRequestTokenSecret($data['oauth_token_secret']); + $token->setAccessToken($data['oauth_token']); + $token->setAccessTokenSecret($data['oauth_token_secret']); + + if (isset($data['oauth_expires_in'])) { + $token->setLifetime($data['oauth_expires_in']); + } else { + $token->setEndOfLife(StdOAuth1Token::EOL_NEVER_EXPIRES); + } + + unset($data['oauth_token'], $data['oauth_token_secret']); + $token->setExtraParams($data); + + return $token; + } +} diff --git a/htdocs/includes/OAuth/OAuth1/Signature/Exception/UnsupportedHashAlgorithmException.php b/htdocs/includes/OAuth/OAuth1/Signature/Exception/UnsupportedHashAlgorithmException.php new file mode 100644 index 00000000000..44c36ce77cc --- /dev/null +++ b/htdocs/includes/OAuth/OAuth1/Signature/Exception/UnsupportedHashAlgorithmException.php @@ -0,0 +1,12 @@ +credentials = $credentials; + } + + /** + * @param string $algorithm + */ + public function setHashingAlgorithm($algorithm) + { + $this->algorithm = $algorithm; + } + + /** + * @param string $token + */ + public function setTokenSecret($token) + { + $this->tokenSecret = $token; + } + + /** + * @param UriInterface $uri + * @param array $params + * @param string $method + * + * @return string + */ + public function getSignature(UriInterface $uri, array $params, $method = 'POST') + { + parse_str($uri->getQuery(), $queryStringData); + + foreach (array_merge($queryStringData, $params) as $key => $value) { + $signatureData[rawurlencode($key)] = rawurlencode($value); + } + + ksort($signatureData); + + // determine base uri + $baseUri = $uri->getScheme() . '://' . $uri->getRawAuthority(); + + if ('/' === $uri->getPath()) { + $baseUri .= $uri->hasExplicitTrailingHostSlash() ? '/' : ''; + } else { + $baseUri .= $uri->getPath(); + } + + $baseString = strtoupper($method) . '&'; + $baseString .= rawurlencode($baseUri) . '&'; + $baseString .= rawurlencode($this->buildSignatureDataString($signatureData)); + + return base64_encode($this->hash($baseString)); + } + + /** + * @param array $signatureData + * + * @return string + */ + protected function buildSignatureDataString(array $signatureData) + { + $signatureString = ''; + $delimiter = ''; + foreach ($signatureData as $key => $value) { + $signatureString .= $delimiter . $key . '=' . $value; + + $delimiter = '&'; + } + + return $signatureString; + } + + /** + * @return string + */ + protected function getSigningKey() + { + $signingKey = rawurlencode($this->credentials->getConsumerSecret()) . '&'; + if ($this->tokenSecret !== null) { + $signingKey .= rawurlencode($this->tokenSecret); + } + + return $signingKey; + } + + /** + * @param string $data + * + * @return string + * + * @throws UnsupportedHashAlgorithmException + */ + protected function hash($data) + { + switch (strtoupper($this->algorithm)) { + case 'HMAC-SHA1': + return hash_hmac('sha1', $data, $this->getSigningKey(), true); + default: + throw new UnsupportedHashAlgorithmException( + 'Unsupported hashing algorithm (' . $this->algorithm . ') used.' + ); + } + } +} diff --git a/htdocs/includes/OAuth/OAuth1/Signature/SignatureInterface.php b/htdocs/includes/OAuth/OAuth1/Signature/SignatureInterface.php new file mode 100644 index 00000000000..da50ddb6da6 --- /dev/null +++ b/htdocs/includes/OAuth/OAuth1/Signature/SignatureInterface.php @@ -0,0 +1,28 @@ +requestToken = $requestToken; + } + + /** + * @return string + */ + public function getRequestToken() + { + return $this->requestToken; + } + + /** + * @param string $requestTokenSecret + */ + public function setRequestTokenSecret($requestTokenSecret) + { + $this->requestTokenSecret = $requestTokenSecret; + } + + /** + * @return string + */ + public function getRequestTokenSecret() + { + return $this->requestTokenSecret; + } + + /** + * @param string $accessTokenSecret + */ + public function setAccessTokenSecret($accessTokenSecret) + { + $this->accessTokenSecret = $accessTokenSecret; + } + + /** + * @return string + */ + public function getAccessTokenSecret() + { + return $this->accessTokenSecret; + } +} diff --git a/htdocs/includes/OAuth/OAuth1/Token/TokenInterface.php b/htdocs/includes/OAuth/OAuth1/Token/TokenInterface.php new file mode 100644 index 00000000000..0bc3f7396f4 --- /dev/null +++ b/htdocs/includes/OAuth/OAuth1/Token/TokenInterface.php @@ -0,0 +1,41 @@ +stateParameterInAuthUrl = $stateParameterInAutUrl; + + foreach ($scopes as $scope) { + if (!$this->isValidScope($scope)) { + throw new InvalidScopeException('Scope ' . $scope . ' is not valid for service ' . get_class($this)); + } + } + + $this->scopes = $scopes; + + $this->baseApiUri = $baseApiUri; + + $this->apiVersion = $apiVersion; + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationUri(array $additionalParameters = array()) + { + $parameters = array_merge( + $additionalParameters, + array( + 'type' => 'web_server', + 'client_id' => $this->credentials->getConsumerId(), + 'redirect_uri' => $this->credentials->getCallbackUrl(), + 'response_type' => 'code', + ) + ); + + $parameters['scope'] = implode($this->getScopesDelimiter(), $this->scopes); + + if ($this->needsStateParameterInAuthUrl()) { + if (!isset($parameters['state'])) { + $parameters['state'] = $this->generateAuthorizationState(); + } + $this->storeAuthorizationState($parameters['state']); + } + + // Build the url + $url = clone $this->getAuthorizationEndpoint(); + foreach ($parameters as $key => $val) { + $url->addToQuery($key, $val); + } + + return $url; + } + + /** + * {@inheritdoc} + */ + public function requestAccessToken($code, $state = null) + { + if (null !== $state) { + $this->validateAuthorizationState($state); + } + + $bodyParams = array( + 'code' => $code, + 'client_id' => $this->credentials->getConsumerId(), + 'client_secret' => $this->credentials->getConsumerSecret(), + 'redirect_uri' => $this->credentials->getCallbackUrl(), + 'grant_type' => 'authorization_code', + ); + + $responseBody = $this->httpClient->retrieveResponse( + $this->getAccessTokenEndpoint(), + $bodyParams, + $this->getExtraOAuthHeaders() + ); + + $token = $this->parseAccessTokenResponse($responseBody); + $this->storage->storeAccessToken($this->service(), $token); + + return $token; + } + + /** + * Sends an authenticated API request to the path provided. + * If the path provided is not an absolute URI, the base API Uri (must be passed into constructor) will be used. + * + * @param string|UriInterface $path + * @param string $method HTTP method + * @param array $body Request body if applicable. + * @param array $extraHeaders Extra headers if applicable. These will override service-specific + * any defaults. + * + * @return string + * + * @throws ExpiredTokenException + * @throws Exception + */ + public function request($path, $method = 'GET', $body = null, array $extraHeaders = array()) + { + $uri = $this->determineRequestUriFromPath($path, $this->baseApiUri); + $token = $this->storage->retrieveAccessToken($this->service()); + + if ($token->getEndOfLife() !== TokenInterface::EOL_NEVER_EXPIRES + && $token->getEndOfLife() !== TokenInterface::EOL_UNKNOWN + && time() > $token->getEndOfLife() + ) { + throw new ExpiredTokenException( + sprintf( + 'Token expired on %s at %s', + date('m/d/Y', $token->getEndOfLife()), + date('h:i:s A', $token->getEndOfLife()) + ) + ); + } + + // add the token where it may be needed + if (static::AUTHORIZATION_METHOD_HEADER_OAUTH === $this->getAuthorizationMethod()) { + $extraHeaders = array_merge(array('Authorization' => 'OAuth ' . $token->getAccessToken()), $extraHeaders); + } elseif (static::AUTHORIZATION_METHOD_QUERY_STRING === $this->getAuthorizationMethod()) { + $uri->addToQuery('access_token', $token->getAccessToken()); + } elseif (static::AUTHORIZATION_METHOD_QUERY_STRING_V2 === $this->getAuthorizationMethod()) { + $uri->addToQuery('oauth2_access_token', $token->getAccessToken()); + } elseif (static::AUTHORIZATION_METHOD_QUERY_STRING_V3 === $this->getAuthorizationMethod()) { + $uri->addToQuery('apikey', $token->getAccessToken()); + } elseif (static::AUTHORIZATION_METHOD_QUERY_STRING_V4 === $this->getAuthorizationMethod()) { + $uri->addToQuery('auth', $token->getAccessToken()); + } elseif (static::AUTHORIZATION_METHOD_HEADER_BEARER === $this->getAuthorizationMethod()) { + $extraHeaders = array_merge(array('Authorization' => 'Bearer ' . $token->getAccessToken()), $extraHeaders); + } + + $extraHeaders = array_merge($this->getExtraApiHeaders(), $extraHeaders); + + return $this->httpClient->retrieveResponse($uri, $body, $extraHeaders, $method); + } + + /** + * Accessor to the storage adapter to be able to retrieve tokens + * + * @return TokenStorageInterface + */ + public function getStorage() + { + return $this->storage; + } + + /** + * Refreshes an OAuth2 access token. + * + * @param TokenInterface $token + * + * @return TokenInterface $token + * + * @throws MissingRefreshTokenException + */ + public function refreshAccessToken(TokenInterface $token) + { + $refreshToken = $token->getRefreshToken(); + + if (empty($refreshToken)) { + throw new MissingRefreshTokenException(); + } + + $parameters = array( + 'grant_type' => 'refresh_token', + 'type' => 'web_server', + 'client_id' => $this->credentials->getConsumerId(), + 'client_secret' => $this->credentials->getConsumerSecret(), + 'refresh_token' => $refreshToken, + ); + + $responseBody = $this->httpClient->retrieveResponse( + $this->getAccessTokenEndpoint(), + $parameters, + $this->getExtraOAuthHeaders() + ); + $token = $this->parseAccessTokenResponse($responseBody); + $this->storage->storeAccessToken($this->service(), $token); + + return $token; + } + + /** + * Return whether or not the passed scope value is valid. + * + * @param string $scope + * + * @return bool + */ + public function isValidScope($scope) + { + $reflectionClass = new \ReflectionClass(get_class($this)); + + return in_array($scope, $reflectionClass->getConstants(), true); + } + + /** + * Check if the given service need to generate a unique state token to build the authorization url + * + * @return bool + */ + public function needsStateParameterInAuthUrl() + { + return $this->stateParameterInAuthUrl; + } + + /** + * Validates the authorization state against a given one + * + * @param string $state + * @throws InvalidAuthorizationStateException + */ + protected function validateAuthorizationState($state) + { + if ($this->retrieveAuthorizationState() !== $state) { + throw new InvalidAuthorizationStateException(); + } + } + + /** + * Generates a random string to be used as state + * + * @return string + */ + protected function generateAuthorizationState() + { + return md5(rand()); + } + + /** + * Retrieves the authorization state for the current service + * + * @return string + */ + protected function retrieveAuthorizationState() + { + return $this->storage->retrieveAuthorizationState($this->service()); + } + + /** + * Stores a given authorization state into the storage + * + * @param string $state + */ + protected function storeAuthorizationState($state) + { + $this->storage->storeAuthorizationState($this->service(), $state); + } + + /** + * Return any additional headers always needed for this service implementation's OAuth calls. + * + * @return array + */ + protected function getExtraOAuthHeaders() + { + return array(); + } + + /** + * Return any additional headers always needed for this service implementation's API calls. + * + * @return array + */ + protected function getExtraApiHeaders() + { + return array(); + } + + /** + * Parses the access token response and returns a TokenInterface. + * + * @abstract + * + * @param string $responseBody + * + * @return TokenInterface + * + * @throws TokenResponseException + */ + abstract protected function parseAccessTokenResponse($responseBody); + + /** + * Returns a class constant from ServiceInterface defining the authorization method used for the API + * Header is the sane default. + * + * @return int + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_HEADER_OAUTH; + } + + /** + * Returns api version string if is set else retrun empty string + * + * @return string + */ + protected function getApiVersionString() + { + return !(empty($this->apiVersion)) ? "/".$this->apiVersion : "" ; + } + + /** + * Returns delimiter to scopes in getAuthorizationUri + * For services that do not fully respect the Oauth's RFC, + * and use scopes with commas as delimiter + * + * @return string + */ + protected function getScopesDelimiter() + { + return ' '; + } +} diff --git a/htdocs/includes/OAuth/OAuth2/Service/Amazon.php b/htdocs/includes/OAuth/OAuth2/Service/Amazon.php new file mode 100644 index 00000000000..035d1a557f8 --- /dev/null +++ b/htdocs/includes/OAuth/OAuth2/Service/Amazon.php @@ -0,0 +1,97 @@ + + * @link https://images-na.ssl-images-amazon.com/images/G/01/lwa/dev/docs/website-developer-guide._TTH_.pdf + */ +class Amazon extends AbstractService +{ + /** + * Defined scopes + * @link https://images-na.ssl-images-amazon.com/images/G/01/lwa/dev/docs/website-developer-guide._TTH_.pdf + */ + const SCOPE_PROFILE = 'profile'; + const SCOPE_POSTAL_CODE = 'postal_code'; + + public function __construct( + CredentialsInterface $credentials, + ClientInterface $httpClient, + TokenStorageInterface $storage, + $scopes = array(), + UriInterface $baseApiUri = null + ) { + parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri); + + if (null === $baseApiUri) { + $this->baseApiUri = new Uri('https://api.amazon.com/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://www.amazon.com/ap/oa'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://www.amazon.com/ap/oatoken'); + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_HEADER_BEARER; + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error_description'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error_description'] . '"'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + $token->setLifeTime($data['expires_in']); + + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + unset($data['expires_in']); + + $token->setExtraParams($data); + + return $token; + } +} diff --git a/htdocs/includes/OAuth/OAuth2/Service/Bitly.php b/htdocs/includes/OAuth/OAuth2/Service/Bitly.php new file mode 100644 index 00000000000..e01cbc4296e --- /dev/null +++ b/htdocs/includes/OAuth/OAuth2/Service/Bitly.php @@ -0,0 +1,111 @@ +baseApiUri = new Uri('https://api-ssl.bitly.com/v3/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://bitly.com/oauth/authorize'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://api-ssl.bitly.com/oauth/access_token'); + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_QUERY_STRING; + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + // I'm invincible!!! + $token->setEndOfLife(StdOAuth2Token::EOL_NEVER_EXPIRES); + unset($data['access_token']); + + $token->setExtraParams($data); + + return $token; + } + + /** + * {@inheritdoc} + */ + public function requestAccessToken($code, $state = null) + { + if (null !== $state) { + $this->validateAuthorizationState($state); + } + + $bodyParams = array( + 'code' => $code, + 'client_id' => $this->credentials->getConsumerId(), + 'client_secret' => $this->credentials->getConsumerSecret(), + 'redirect_uri' => $this->credentials->getCallbackUrl(), + 'grant_type' => 'authorization_code', + ); + + $responseBody = $this->httpClient->retrieveResponse( + $this->getAccessTokenEndpoint(), + $bodyParams, + $this->getExtraOAuthHeaders() + ); + + // we can scream what we want that we want bitly to return a json encoded string (format=json), but the + // WOAH WATCH YOUR LANGUAGE ;) service doesn't seem to like screaming, hence we need to manually + // parse the result + $parsedResult = array(); + parse_str($responseBody, $parsedResult); + + $token = $this->parseAccessTokenResponse(json_encode($parsedResult)); + $this->storage->storeAccessToken($this->service(), $token); + + return $token; + } +} diff --git a/htdocs/includes/OAuth/OAuth2/Service/Bitrix24.php b/htdocs/includes/OAuth/OAuth2/Service/Bitrix24.php new file mode 100644 index 00000000000..1630d30dce5 --- /dev/null +++ b/htdocs/includes/OAuth/OAuth2/Service/Bitrix24.php @@ -0,0 +1,118 @@ +baseApiUri)); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri(sprintf('%s/oauth/token/', $this->baseApiUri)); + } + + /** + * {@inheritdoc} + */ + public function requestAccessToken($code, $state = null) + { + if (null !== $state) { + $this->validateAuthorizationState($state); + } + + $responseBody = $this->httpClient->retrieveResponse( + $this->getAccessTokenUri($code), + array(), + $this->getExtraOAuthHeaders(), + 'GET' + ); + + $token = $this->parseAccessTokenResponse($responseBody); + $this->storage->storeAccessToken($this->service(), $token); + + return $token; + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenUri($code) + { + $parameters = array( + 'code' => $code, + 'client_id' => $this->credentials->getConsumerId(), + 'client_secret' => $this->credentials->getConsumerSecret(), + 'redirect_uri' => $this->credentials->getCallbackUrl(), + 'grant_type' => 'authorization_code', + 'scope' => $this->scopes + ); + + $parameters['scope'] = implode(' ', $this->scopes); + + // Build the url + $url = $this->getAccessTokenEndpoint(); + foreach ($parameters as $key => $val) { + $url->addToQuery($key, $val); + } + + return $url; + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + $token->setLifetime($data['expires_in']); + + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + unset($data['expires_in']); + + $token->setExtraParams($data); + + return $token; + } +} diff --git a/htdocs/includes/OAuth/OAuth2/Service/Box.php b/htdocs/includes/OAuth/OAuth2/Service/Box.php new file mode 100644 index 00000000000..14696c5936d --- /dev/null +++ b/htdocs/includes/OAuth/OAuth2/Service/Box.php @@ -0,0 +1,88 @@ + + * @link https://developers.box.com/oauth/ + */ +class Box extends AbstractService +{ + public function __construct( + CredentialsInterface $credentials, + ClientInterface $httpClient, + TokenStorageInterface $storage, + $scopes = array(), + UriInterface $baseApiUri = null + ) { + parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri, true); + + if (null === $baseApiUri) { + $this->baseApiUri = new Uri('https://api.box.com/2.0/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://www.box.com/api/oauth2/authorize'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://www.box.com/api/oauth2/token'); + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_HEADER_BEARER; + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + $token->setLifeTime($data['expires_in']); + + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + unset($data['expires_in']); + + $token->setExtraParams($data); + + return $token; + } +} diff --git a/htdocs/includes/OAuth/OAuth2/Service/Buffer.php b/htdocs/includes/OAuth/OAuth2/Service/Buffer.php new file mode 100644 index 00000000000..5905678e8c6 --- /dev/null +++ b/htdocs/includes/OAuth/OAuth2/Service/Buffer.php @@ -0,0 +1,151 @@ + + * @link https://bufferapp.com/developers/api + */ +class Buffer extends AbstractService +{ + public function __construct( + CredentialsInterface $credentials, + ClientInterface $httpClient, + TokenStorageInterface $storage, + $scopes = array(), + UriInterface $baseApiUri = null + ) { + parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri); + if ($baseApiUri === null) { + $this->baseApiUri = new Uri('https://api.bufferapp.com/1/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://bufferapp.com/oauth2/authorize'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://api.bufferapp.com/1/oauth2/token.json'); + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_QUERY_STRING; + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationUri(array $additionalParameters = array()) + { + $parameters = array_merge( + $additionalParameters, + array( + 'client_id' => $this->credentials->getConsumerId(), + 'redirect_uri' => $this->credentials->getCallbackUrl(), + 'response_type' => 'code', + ) + ); + + // Build the url + $url = clone $this->getAuthorizationEndpoint(); + foreach ($parameters as $key => $val) { + $url->addToQuery($key, $val); + } + + return $url; + } + + /** + * {@inheritdoc} + */ + public function requestRequestToken() + { + $responseBody = $this->httpClient->retrieveResponse( + $this->getRequestTokenEndpoint(), + array( + 'client_key' => $this->credentials->getConsumerId(), + 'redirect_uri' => $this->credentials->getCallbackUrl(), + 'response_type' => 'code', + ) + ); + + $code = $this->parseRequestTokenResponse($responseBody); + + return $code; + } + + protected function parseRequestTokenResponse($responseBody) + { + parse_str($responseBody, $data); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (!isset($data['code'])) { + throw new TokenResponseException('Error in retrieving code.'); + } + return $data['code']; + } + + public function requestAccessToken($code) + { + $bodyParams = array( + 'client_id' => $this->credentials->getConsumerId(), + 'client_secret' => $this->credentials->getConsumerSecret(), + 'redirect_uri' => $this->credentials->getCallbackUrl(), + 'code' => $code, + 'grant_type' => 'authorization_code', + ); + + $responseBody = $this->httpClient->retrieveResponse( + $this->getAccessTokenEndpoint(), + $bodyParams, + $this->getExtraOAuthHeaders() + ); + $token = $this->parseAccessTokenResponse($responseBody); + $this->storage->storeAccessToken($this->service(), $token); + + return $token; + } + + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if ($data === null || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + + $token->setEndOfLife(StdOAuth2Token::EOL_NEVER_EXPIRES); + unset($data['access_token']); + $token->setExtraParams($data); + + return $token; + } +} diff --git a/htdocs/includes/OAuth/OAuth2/Service/Dailymotion.php b/htdocs/includes/OAuth/OAuth2/Service/Dailymotion.php new file mode 100644 index 00000000000..095a467fd98 --- /dev/null +++ b/htdocs/includes/OAuth/OAuth2/Service/Dailymotion.php @@ -0,0 +1,129 @@ + + * @link http://www.dailymotion.com/doc/api/authentication.html + */ +class Dailymotion extends AbstractService +{ + /** + * Scopes + * + * @var string + */ + const SCOPE_EMAIL = 'email', + SCOPE_PROFILE = 'userinfo', + SCOPE_VIDEOS = 'manage_videos', + SCOPE_COMMENTS = 'manage_comments', + SCOPE_PLAYLIST = 'manage_playlists', + SCOPE_TILES = 'manage_tiles', + SCOPE_SUBSCRIPTIONS = 'manage_subscriptions', + SCOPE_FRIENDS = 'manage_friends', + SCOPE_FAVORITES = 'manage_favorites', + SCOPE_GROUPS = 'manage_groups'; + + /** + * Dialog form factors + * + * @var string + */ + const DISPLAY_PAGE = 'page', + DISPLAY_POPUP = 'popup', + DISPLAY_MOBILE = 'mobile'; + + /** + * {@inheritdoc} + */ + public function __construct( + CredentialsInterface $credentials, + ClientInterface $httpClient, + TokenStorageInterface $storage, + $scopes = array(), + UriInterface $baseApiUri = null + ) { + parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri); + + if (null === $baseApiUri) { + $this->baseApiUri = new Uri('https://api.dailymotion.com/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://api.dailymotion.com/oauth/authorize'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://api.dailymotion.com/oauth/token'); + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_HEADER_OAUTH; + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error_description']) || isset($data['error'])) { + throw new TokenResponseException( + sprintf( + 'Error in retrieving token: "%s"', + isset($data['error_description']) ? $data['error_description'] : $data['error'] + ) + ); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + $token->setLifeTime($data['expires_in']); + + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + unset($data['expires_in']); + + $token->setExtraParams($data); + + return $token; + } + + /** + * {@inheritdoc} + */ + protected function getExtraOAuthHeaders() + { + return array('Accept' => 'application/json'); + } +} diff --git a/htdocs/includes/OAuth/OAuth2/Service/Delicious.php b/htdocs/includes/OAuth/OAuth2/Service/Delicious.php new file mode 100644 index 00000000000..eba6035f39d --- /dev/null +++ b/htdocs/includes/OAuth/OAuth2/Service/Delicious.php @@ -0,0 +1,139 @@ + + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link https://github.com/SciDevs/delicious-api/blob/master/api/oauth.md + */ + +namespace OAuth\OAuth2\Service; + +use OAuth\OAuth2\Token\StdOAuth2Token; +use OAuth\Common\Http\Exception\TokenResponseException; +use OAuth\Common\Http\Uri\Uri; +use OAuth\Common\Consumer\CredentialsInterface; +use OAuth\Common\Http\Client\ClientInterface; +use OAuth\Common\Storage\TokenStorageInterface; +use OAuth\Common\Http\Uri\UriInterface; + +/** + * Delicious service. + * + * @author Pedro Amorim + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link https://github.com/SciDevs/delicious-api/blob/master/api/oauth.md + */ +class Delicious extends AbstractService +{ + public function __construct( + CredentialsInterface $credentials, + ClientInterface $httpClient, + TokenStorageInterface $storage, + $scopes = array(), + UriInterface $baseApiUri = null + ) { + parent::__construct( + $credentials, + $httpClient, + $storage, + $scopes, + $baseApiUri, + true + ); + + if (null === $baseApiUri) { + $this->baseApiUri = new Uri('https://api.del.icio.us/v1/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://delicious.com/auth/authorize'); + + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://avosapi.delicious.com/api/v1/oauth/token'); + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_HEADER_BEARER; + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException( + 'Error in retrieving token: "' . $data['error'] . '"' + ); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + + if (isset($data['expires_in'])) { + $token->setLifetime($data['expires_in']); + unset($data['expires_in']); + } + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + + $token->setExtraParams($data); + + return $token; + } + + + // Special, delicious didn't respect the oauth2 RFC and need a grant_type='code' + /** + * {@inheritdoc} + */ + public function requestAccessToken($code, $state = null) + { + if (null !== $state) { + $this->validateAuthorizationState($state); + } + + $bodyParams = array( + 'code' => $code, + 'client_id' => $this->credentials->getConsumerId(), + 'client_secret' => $this->credentials->getConsumerSecret(), + 'redirect_uri' => $this->credentials->getCallbackUrl(), + 'grant_type' => 'code', + ); + + $responseBody = $this->httpClient->retrieveResponse( + $this->getAccessTokenEndpoint(), + $bodyParams, + $this->getExtraOAuthHeaders() + ); + + $token = $this->parseAccessTokenResponse($responseBody); + $this->storage->storeAccessToken($this->service(), $token); + + return $token; + } +} diff --git a/htdocs/includes/OAuth/OAuth2/Service/DeviantArt.php b/htdocs/includes/OAuth/OAuth2/Service/DeviantArt.php new file mode 100644 index 00000000000..31e94b45dbd --- /dev/null +++ b/htdocs/includes/OAuth/OAuth2/Service/DeviantArt.php @@ -0,0 +1,99 @@ +baseApiUri = new Uri('https://www.deviantart.com/api/v1/oauth2/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://www.deviantart.com/oauth2/authorize'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://www.deviantart.com/oauth2/token'); + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + + if (isset($data['expires_in'])) { + $token->setLifeTime($data['expires_in']); + } + + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + unset($data['expires_in']); + + $token->setExtraParams($data); + + return $token; + } +} diff --git a/htdocs/includes/OAuth/OAuth2/Service/Dropbox.php b/htdocs/includes/OAuth/OAuth2/Service/Dropbox.php new file mode 100644 index 00000000000..43ec6c7f081 --- /dev/null +++ b/htdocs/includes/OAuth/OAuth2/Service/Dropbox.php @@ -0,0 +1,111 @@ + + * @link https://www.dropbox.com/developers/core/docs + */ +class Dropbox extends AbstractService +{ + public function __construct( + CredentialsInterface $credentials, + ClientInterface $httpClient, + TokenStorageInterface $storage, + $scopes = array(), + UriInterface $baseApiUri = null + ) { + parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri); + + if (null === $baseApiUri) { + $this->baseApiUri = new Uri('https://api.dropbox.com/1/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationUri(array $additionalParameters = array()) + { + $parameters = array_merge( + $additionalParameters, + array( + 'client_id' => $this->credentials->getConsumerId(), + 'redirect_uri' => $this->credentials->getCallbackUrl(), + 'response_type' => 'code', + ) + ); + + $parameters['scope'] = implode(' ', $this->scopes); + + // Build the url + $url = clone $this->getAuthorizationEndpoint(); + foreach ($parameters as $key => $val) { + $url->addToQuery($key, $val); + } + + return $url; + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://www.dropbox.com/1/oauth2/authorize'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://api.dropbox.com/1/oauth2/token'); + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_QUERY_STRING; + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + + $token->setExtraParams($data); + + return $token; + } +} diff --git a/htdocs/includes/OAuth/OAuth2/Service/EveOnline.php b/htdocs/includes/OAuth/OAuth2/Service/EveOnline.php new file mode 100644 index 00000000000..76fafa6d7ee --- /dev/null +++ b/htdocs/includes/OAuth/OAuth2/Service/EveOnline.php @@ -0,0 +1,100 @@ + + */ +namespace OAuth\OAuth2\Service; + +use OAuth\Common\Consumer\CredentialsInterface; +use OAuth\Common\Http\Client\ClientInterface; +use OAuth\Common\Http\Exception\TokenResponseException; +use OAuth\Common\Http\Uri\Uri; +use OAuth\Common\Http\Uri\UriInterface; +use OAuth\Common\Storage\TokenStorageInterface; +use OAuth\Common\Token\TokenInterface; +use OAuth\OAuth2\Token\StdOAuth2Token; + +/** + * Class EveOnline + */ +class EveOnline extends AbstractService +{ + public function __construct( + CredentialsInterface $credentials, + ClientInterface $httpClient, + TokenStorageInterface $storage, + $scopes = array(), + UriInterface $baseApiUri = null + ) { + parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri); + + if (null === $baseApiUri) { + $this->baseApiUri = new Uri('https://login.eveonline.com'); + } + } + + /** + * Returns the authorization API endpoint. + * @return UriInterface + */ + public function getAuthorizationEndpoint() + { + return new Uri($this->baseApiUri . '/oauth/authorize'); + } + + /** + * Returns the access token API endpoint. + * @return UriInterface + */ + public function getAccessTokenEndpoint() + { + return new Uri($this->baseApiUri . '/oauth/token'); + } + + /** + * Parses the access token response and returns a TokenInterface. + * + * @param string $responseBody + * + * @return TokenInterface + * @throws TokenResponseException + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error_description'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error_description'] . '"'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + $token->setLifeTime($data['expires_in']); + + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + unset($data['expires_in']); + + $token->setExtraParams($data); + + return $token; + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_HEADER_BEARER; + } +} diff --git a/htdocs/includes/OAuth/OAuth2/Service/Exception/InvalidAccessTypeException.php b/htdocs/includes/OAuth/OAuth2/Service/Exception/InvalidAccessTypeException.php new file mode 100644 index 00000000000..398df2fdcfa --- /dev/null +++ b/htdocs/includes/OAuth/OAuth2/Service/Exception/InvalidAccessTypeException.php @@ -0,0 +1,12 @@ + + * Released under the MIT license. + */ + +namespace OAuth\OAuth2\Service\Exception; + +use OAuth\Common\Exception\Exception; + +/** + * Exception thrown when a scope provided to a service is invalid. + */ +class InvalidScopeException extends Exception +{ +} diff --git a/htdocs/includes/OAuth/OAuth2/Service/Exception/MissingRefreshTokenException.php b/htdocs/includes/OAuth/OAuth2/Service/Exception/MissingRefreshTokenException.php new file mode 100644 index 00000000000..21eece6ac1b --- /dev/null +++ b/htdocs/includes/OAuth/OAuth2/Service/Exception/MissingRefreshTokenException.php @@ -0,0 +1,17 @@ + + * Released under the MIT license. + */ + +namespace OAuth\OAuth2\Service\Exception; + +use OAuth\Common\Exception\Exception; + +/** + * Exception thrown when service is requested to refresh the access token but no refresh token can be found. + */ +class MissingRefreshTokenException extends Exception +{ +} diff --git a/htdocs/includes/OAuth/OAuth2/Service/Facebook.php b/htdocs/includes/OAuth/OAuth2/Service/Facebook.php new file mode 100644 index 00000000000..308513bce94 --- /dev/null +++ b/htdocs/includes/OAuth/OAuth2/Service/Facebook.php @@ -0,0 +1,208 @@ +baseApiUri = new Uri('https://graph.facebook.com'.$this->getApiVersionString().'/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://www.facebook.com'.$this->getApiVersionString().'/dialog/oauth'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://graph.facebook.com'.$this->getApiVersionString().'/oauth/access_token'); + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + // Facebook gives us a query string ... Oh wait. JSON is too simple, understand ? + parse_str($responseBody, $data); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + + if (isset($data['expires'])) { + $token->setLifeTime($data['expires']); + } + + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + unset($data['expires']); + + $token->setExtraParams($data); + + return $token; + } + + public function getDialogUri($dialogPath, array $parameters) + { + if (!isset($parameters['redirect_uri'])) { + throw new Exception("Redirect uri is mandatory for this request"); + } + $parameters['app_id'] = $this->credentials->getConsumerId(); + $baseUrl = self::WWW_URL .$this->getApiVersionString(). '/dialog/' . $dialogPath; + $query = http_build_query($parameters); + return new Uri($baseUrl . '?' . $query); + } + + /** + * {@inheritdoc} + */ + protected function getScopesDelimiter() + { + return ','; + } +} diff --git a/htdocs/includes/OAuth/OAuth2/Service/Foursquare.php b/htdocs/includes/OAuth/OAuth2/Service/Foursquare.php new file mode 100644 index 00000000000..981ff44f691 --- /dev/null +++ b/htdocs/includes/OAuth/OAuth2/Service/Foursquare.php @@ -0,0 +1,81 @@ +baseApiUri = new Uri('https://api.foursquare.com/v2/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://foursquare.com/oauth2/authenticate'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://foursquare.com/oauth2/access_token'); + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + // Foursquare tokens evidently never expire... + $token->setEndOfLife(StdOAuth2Token::EOL_NEVER_EXPIRES); + unset($data['access_token']); + + $token->setExtraParams($data); + + return $token; + } + + /** + * {@inheritdoc} + */ + public function request($path, $method = 'GET', $body = null, array $extraHeaders = array()) + { + $uri = $this->determineRequestUriFromPath($path, $this->baseApiUri); + $uri->addToQuery('v', $this->apiVersionDate); + + return parent::request($uri, $method, $body, $extraHeaders); + } +} diff --git a/htdocs/includes/OAuth/OAuth2/Service/GitHub.php b/htdocs/includes/OAuth/OAuth2/Service/GitHub.php new file mode 100644 index 00000000000..8d4d122f1fc --- /dev/null +++ b/htdocs/includes/OAuth/OAuth2/Service/GitHub.php @@ -0,0 +1,216 @@ +baseApiUri = new Uri('https://api.github.com/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://github.com/login/oauth/authorize'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://github.com/login/oauth/access_token'); + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_QUERY_STRING; + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + // Github tokens evidently never expire... + $token->setEndOfLife(StdOAuth2Token::EOL_NEVER_EXPIRES); + unset($data['access_token']); + + $token->setExtraParams($data); + + return $token; + } + + /** + * Used to configure response type -- we want JSON from github, default is query string format + * + * @return array + */ + protected function getExtraOAuthHeaders() + { + return array('Accept' => 'application/json'); + } + + /** + * Required for GitHub API calls. + * + * @return array + */ + protected function getExtraApiHeaders() + { + return array('Accept' => 'application/vnd.github.beta+json'); + } + + /** + * {@inheritdoc} + */ + protected function getScopesDelimiter() + { + return ','; + } +} diff --git a/htdocs/includes/OAuth/OAuth2/Service/Google.php b/htdocs/includes/OAuth/OAuth2/Service/Google.php new file mode 100644 index 00000000000..a8bb44d8238 --- /dev/null +++ b/htdocs/includes/OAuth/OAuth2/Service/Google.php @@ -0,0 +1,188 @@ +baseApiUri = new Uri('https://www.googleapis.com/oauth2/v1/'); + } + } + + public function setAccessType($accessType) + { + if (!in_array($accessType, array('online', 'offline'), true)) { + throw new InvalidAccessTypeException('Invalid accessType, expected either online or offline'); + } + $this->accessType = $accessType; + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://accounts.google.com/o/oauth2/auth?access_type=' . $this->accessType); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://accounts.google.com/o/oauth2/token'); + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + $token->setLifetime($data['expires_in']); + + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + unset($data['expires_in']); + + $token->setExtraParams($data); + + return $token; + } +} diff --git a/htdocs/includes/OAuth/OAuth2/Service/Harvest.php b/htdocs/includes/OAuth/OAuth2/Service/Harvest.php new file mode 100644 index 00000000000..96fb0f2da7d --- /dev/null +++ b/htdocs/includes/OAuth/OAuth2/Service/Harvest.php @@ -0,0 +1,157 @@ +baseApiUri = new Uri('https://api.harvestapp.com/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationUri(array $additionalParameters = array()) + { + $parameters = array_merge( + $additionalParameters, + array( + 'client_id' => $this->credentials->getConsumerId(), + 'redirect_uri' => $this->credentials->getCallbackUrl(), + 'state' => 'optional-csrf-token', + 'response_type' => 'code', + ) + ); + + // Build the url + $url = clone $this->getAuthorizationEndpoint(); + foreach ($parameters as $key => $val) { + $url->addToQuery($key, $val); + } + + return $url; + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://api.harvestapp.com/oauth2/authorize'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://api.harvestapp.com/oauth2/token'); + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_QUERY_STRING; + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || ! is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + $token->setLifetime($data['expires_in']); + $token->setRefreshToken($data['refresh_token']); + + unset($data['access_token']); + + $token->setExtraParams($data); + + return $token; + } + + /** + * Refreshes an OAuth2 access token. + * + * @param TokenInterface $token + * + * @return TokenInterface $token + * + * @throws MissingRefreshTokenException + */ + public function refreshAccessToken(TokenInterface $token) + { + $refreshToken = $token->getRefreshToken(); + + if (empty($refreshToken)) { + throw new MissingRefreshTokenException(); + } + + $parameters = array( + 'grant_type' => 'refresh_token', + 'type' => 'web_server', + 'client_id' => $this->credentials->getConsumerId(), + 'client_secret' => $this->credentials->getConsumerSecret(), + 'refresh_token' => $refreshToken, + ); + + $responseBody = $this->httpClient->retrieveResponse( + $this->getAccessTokenEndpoint(), + $parameters, + $this->getExtraOAuthHeaders() + ); + $token = $this->parseAccessTokenResponse($responseBody); + $this->storage->storeAccessToken($this->service(), $token); + + return $token; + } + + /** + * @return array + */ + protected function getExtraOAuthHeaders() + { + return array('Accept' => 'application/json'); + } + + /** + * Return any additional headers always needed for this service implementation's API calls. + * + * @return array + */ + protected function getExtraApiHeaders() + { + return array('Accept' => 'application/json'); + } +} diff --git a/htdocs/includes/OAuth/OAuth2/Service/Heroku.php b/htdocs/includes/OAuth/OAuth2/Service/Heroku.php new file mode 100644 index 00000000000..470cedc3398 --- /dev/null +++ b/htdocs/includes/OAuth/OAuth2/Service/Heroku.php @@ -0,0 +1,123 @@ + + * @link https://devcenter.heroku.com/articles/oauth + */ +class Heroku extends AbstractService +{ + /** + * Defined scopes + * @link https://devcenter.heroku.com/articles/oauth#scopes + */ + const SCOPE_GLOBAL = 'global'; + const SCOPE_IDENTITY = 'identity'; + const SCOPE_READ = 'read'; + const SCOPE_WRITE = 'write'; + const SCOPE_READ_PROTECTED = 'read-protected'; + const SCOPE_WRITE_PROTECTED = 'write-protected'; + + /** + * {@inheritdoc} + */ + public function __construct( + CredentialsInterface $credentials, + ClientInterface $httpClient, + TokenStorageInterface $storage, + $scopes = array(), + UriInterface $baseApiUri = null + ) { + parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri); + + if (null === $baseApiUri) { + $this->baseApiUri = new Uri('https://api.heroku.com/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://id.heroku.com/oauth/authorize'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://id.heroku.com/oauth/token'); + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_HEADER_BEARER; + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error_description']) || isset($data['error'])) { + throw new TokenResponseException( + sprintf( + 'Error in retrieving token: "%s"', + isset($data['error_description']) ? $data['error_description'] : $data['error'] + ) + ); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + $token->setLifeTime($data['expires_in']); + + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + unset($data['expires_in']); + + $token->setExtraParams($data); + + return $token; + } + + /** + * {@inheritdoc} + */ + protected function getExtraOAuthHeaders() + { + return array('Accept' => 'application/vnd.heroku+json; version=3'); + } + + /** + * {@inheritdoc} + */ + protected function getExtraApiHeaders() + { + return array('Accept' => 'application/vnd.heroku+json; version=3', 'Content-Type' => 'application/json'); + } +} diff --git a/htdocs/includes/OAuth/OAuth2/Service/Hubic.php b/htdocs/includes/OAuth/OAuth2/Service/Hubic.php new file mode 100644 index 00000000000..b9954503f59 --- /dev/null +++ b/htdocs/includes/OAuth/OAuth2/Service/Hubic.php @@ -0,0 +1,155 @@ + + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link https://api.hubic.com/docs/ + */ + +namespace OAuth\OAuth2\Service; + +use OAuth\OAuth2\Token\StdOAuth2Token; +use OAuth\Common\Http\Exception\TokenResponseException; +use OAuth\Common\Http\Uri\Uri; +use OAuth\Common\Consumer\CredentialsInterface; +use OAuth\Common\Http\Client\ClientInterface; +use OAuth\Common\Storage\TokenStorageInterface; +use OAuth\Common\Http\Uri\UriInterface; + +/** + * Hubic service. + * + * @author Pedro Amorim + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link https://api.hubic.com/docs/ + */ +class Hubic extends AbstractService +{ + + // Scopes + const SCOPE_USAGE_GET = 'usage.r'; + const SCOPE_ACCOUNT_GET = 'account.r'; + const SCOPE_GETALLLINKS_GET = 'getAllLinks.r'; + const SCOPE_CREDENTIALS_GET = 'credentials.r'; + const SCOPE_SPONSORCODE_GET = 'sponsorCode.r'; + const SCOPE_ACTIVATE_POST = 'activate.w'; + const SCOPE_SPONSORED_GET = 'sponsored.r'; + const SCOPE_LINKS_GET = 'links.r'; + const SCOPE_LINKS_POST = 'links.rw'; + const SCOPE_LINKS_ALL = 'links.drw'; + + public function __construct( + CredentialsInterface $credentials, + ClientInterface $httpClient, + TokenStorageInterface $storage, + $scopes = array(), + UriInterface $baseApiUri = null + ) { + parent::__construct( + $credentials, + $httpClient, + $storage, + $scopes, + $baseApiUri, + true + ); + + if (null === $baseApiUri) { + $this->baseApiUri = new Uri('https://api.hubic.com/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://api.hubic.com/oauth/auth'); + + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://api.hubic.com/oauth/token'); + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_HEADER_BEARER; + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException( + 'Error in retrieving token: "' . $data['error'] . '"' + ); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + $token->setLifetime($data['expires_in']); + + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + unset($data['expires_in']); + + $token->setExtraParams($data); + + return $token; + } + + + /** + * {@inheritdoc} + */ + public function getAuthorizationUri(array $additionalParameters = array()) + { + $parameters = array_merge( + $additionalParameters, + array( + 'type' => 'web_server', + 'client_id' => $this->credentials->getConsumerId(), + 'redirect_uri' => $this->credentials->getCallbackUrl(), + 'response_type' => 'code', + ) + ); + + // special, hubic use a param scope with commas + // between scopes instead of spaces + $parameters['scope'] = implode(',', $this->scopes); + + if ($this->needsStateParameterInAuthUrl()) { + if (!isset($parameters['state'])) { + $parameters['state'] = $this->generateAuthorizationState(); + } + $this->storeAuthorizationState($parameters['state']); + } + + // Build the url + $url = clone $this->getAuthorizationEndpoint(); + foreach ($parameters as $key => $val) { + $url->addToQuery($key, $val); + } + + return $url; + } +} diff --git a/htdocs/includes/OAuth/OAuth2/Service/Instagram.php b/htdocs/includes/OAuth/OAuth2/Service/Instagram.php new file mode 100644 index 00000000000..c60c455f492 --- /dev/null +++ b/htdocs/includes/OAuth/OAuth2/Service/Instagram.php @@ -0,0 +1,85 @@ +baseApiUri = new Uri('https://api.instagram.com/v1/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://api.instagram.com/oauth/authorize/'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://api.instagram.com/oauth/access_token'); + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_QUERY_STRING; + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + // Instagram tokens evidently never expire... + $token->setEndOfLife(StdOAuth2Token::EOL_NEVER_EXPIRES); + unset($data['access_token']); + + $token->setExtraParams($data); + + return $token; + } +} diff --git a/htdocs/includes/OAuth/OAuth2/Service/JawboneUP.php b/htdocs/includes/OAuth/OAuth2/Service/JawboneUP.php new file mode 100644 index 00000000000..fad112536ce --- /dev/null +++ b/htdocs/includes/OAuth/OAuth2/Service/JawboneUP.php @@ -0,0 +1,144 @@ + + * @link https://jawbone.com/up/developer/authentication + */ +class JawboneUP extends AbstractService +{ + /** + * Defined scopes + * + * + * @link https://jawbone.com/up/developer/authentication + */ + // general information scopes + const SCOPE_BASIC_READ = 'basic_read'; + const SCOPE_EXTENDED_READ = 'extended_read'; + const SCOPE_LOCATION_READ = 'location_read'; + const SCOPE_FRIENDS_READ = 'friends_read'; + // mood scopes + const SCOPE_MOOD_READ = 'mood_read'; + const SCOPE_MOOD_WRITE = 'mood_write'; + // move scopes + const SCOPE_MOVE_READ = 'move_read'; + const SCOPE_MOVE_WRITE = 'move_write'; + // sleep scopes + const SCOPE_SLEEP_READ = 'sleep_read'; + const SCOPE_SLEEP_WRITE = 'sleep_write'; + // meal scopes + const SCOPE_MEAL_READ = 'meal_read'; + const SCOPE_MEAL_WRITE = 'meal_write'; + // weight scopes + const SCOPE_WEIGHT_READ = 'weight_read'; + const SCOPE_WEIGHT_WRITE = 'weight_write'; + // generic event scopes + const SCOPE_GENERIC_EVENT_READ = 'generic_event_read'; + const SCOPE_GENERIC_EVENT_WRITE = 'generic_event_write'; + + + public function __construct( + CredentialsInterface $credentials, + ClientInterface $httpClient, + TokenStorageInterface $storage, + $scopes = array(), + UriInterface $baseApiUri = null + ) { + parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri); + + if (null === $baseApiUri) { + $this->baseApiUri = new Uri('https://jawbone.com/nudge/api/v.1.1/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationUri(array $additionalParameters = array()) + { + $parameters = array_merge( + $additionalParameters, + array( + 'client_id' => $this->credentials->getConsumerId(), + 'redirect_uri' => $this->credentials->getCallbackUrl(), + 'response_type' => 'code', + ) + ); + + $parameters['scope'] = implode(' ', $this->scopes); + + // Build the url + $url = clone $this->getAuthorizationEndpoint(); + foreach ($parameters as $key => $val) { + $url->addToQuery($key, $val); + } + + return $url; + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://jawbone.com/auth/oauth2/auth'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://jawbone.com/auth/oauth2/token'); + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_HEADER_BEARER; + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + $token->setLifeTime($data['expires_in']); + + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + unset($data['expires_in']); + + $token->setExtraParams($data); + + return $token; + } +} diff --git a/htdocs/includes/OAuth/OAuth2/Service/Linkedin.php b/htdocs/includes/OAuth/OAuth2/Service/Linkedin.php new file mode 100644 index 00000000000..9c0674c5142 --- /dev/null +++ b/htdocs/includes/OAuth/OAuth2/Service/Linkedin.php @@ -0,0 +1,103 @@ + + * @link http://developer.linkedin.com/documents/authentication + */ +class Linkedin extends AbstractService +{ + /** + * Defined scopes + * @link http://developer.linkedin.com/documents/authentication#granting + */ + const SCOPE_R_BASICPROFILE = 'r_basicprofile'; + const SCOPE_R_FULLPROFILE = 'r_fullprofile'; + const SCOPE_R_EMAILADDRESS = 'r_emailaddress'; + const SCOPE_R_NETWORK = 'r_network'; + const SCOPE_R_CONTACTINFO = 'r_contactinfo'; + const SCOPE_RW_NUS = 'rw_nus'; + const SCOPE_RW_COMPANY_ADMIN = 'rw_company_admin'; + const SCOPE_RW_GROUPS = 'rw_groups'; + const SCOPE_W_MESSAGES = 'w_messages'; + const SCOPE_W_SHARE = 'w_share'; + + public function __construct( + CredentialsInterface $credentials, + ClientInterface $httpClient, + TokenStorageInterface $storage, + $scopes = array(), + UriInterface $baseApiUri = null + ) { + parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri, true); + + if (null === $baseApiUri) { + $this->baseApiUri = new Uri('https://api.linkedin.com/v1/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://www.linkedin.com/uas/oauth2/authorization'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://www.linkedin.com/uas/oauth2/accessToken'); + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_QUERY_STRING_V2; + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + $token->setLifeTime($data['expires_in']); + + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + unset($data['expires_in']); + + $token->setExtraParams($data); + + return $token; + } +} diff --git a/htdocs/includes/OAuth/OAuth2/Service/Mailchimp.php b/htdocs/includes/OAuth/OAuth2/Service/Mailchimp.php new file mode 100644 index 00000000000..842153de2e7 --- /dev/null +++ b/htdocs/includes/OAuth/OAuth2/Service/Mailchimp.php @@ -0,0 +1,114 @@ +baseApiUri) && $storage->hasAccessToken($this->service())) { + $this->setBaseApiUri($storage->retrieveAccessToken($this->service())); + } + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_QUERY_STRING_V3; + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://login.mailchimp.com/oauth2/authorize'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://login.mailchimp.com/oauth2/token'); + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + // Parse JSON + $data = json_decode($responseBody, true); + + // Do validation. + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + // Create token object. + $token = new StdOAuth2Token($data['access_token']); + + // Set the right API endpoint. + $this->setBaseApiUri($token); + + // Mailchimp tokens evidently never expire... + $token->setEndOfLife(StdOAuth2Token::EOL_NEVER_EXPIRES); + + return $token; + } + + /** + * {@inheritdoc} + */ + public function request($path, $method = 'GET', $body = null, array $extraHeaders = array()) + { + if (is_null($this->baseApiUri)) { + $this->setBaseApiUri($this->storage->retrieveAccessToken($this->service())); + } + + return parent::request($path, $method, $body, $extraHeaders); + } + + /** + * Set the right base endpoint. + * + * @param StdOAuth2Token $token + */ + protected function setBaseApiUri(StdOAuth2Token $token) + { + // Make request uri. + $endpoint = 'https://login.mailchimp.com/oauth2/metadata?oauth_token='. $token->getAccessToken(); + + // Grab meta data about the token. + $response = $this->httpClient->retrieveResponse(new Uri($endpoint), array(), array(), 'GET'); + + // Parse JSON. + $meta = json_decode($response, true); + + // Set base api uri. + $this->baseApiUri = new Uri('https://'. $meta['dc'] .'.api.mailchimp.com/2.0/'); + + // Allow chaining. + return $this; + } +} diff --git a/htdocs/includes/OAuth/OAuth2/Service/Microsoft.php b/htdocs/includes/OAuth/OAuth2/Service/Microsoft.php new file mode 100644 index 00000000000..c815b22bd44 --- /dev/null +++ b/htdocs/includes/OAuth/OAuth2/Service/Microsoft.php @@ -0,0 +1,120 @@ +baseApiUri = new Uri('https://apis.live.net/v5.0/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://login.live.com/oauth20_authorize.srf'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://login.live.com/oauth20_token.srf'); + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_QUERY_STRING; + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + $token->setLifetime($data['expires_in']); + + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + unset($data['expires_in']); + + $token->setExtraParams($data); + + return $token; + } +} diff --git a/htdocs/includes/OAuth/OAuth2/Service/Nest.php b/htdocs/includes/OAuth/OAuth2/Service/Nest.php new file mode 100644 index 00000000000..7659e4fb74b --- /dev/null +++ b/htdocs/includes/OAuth/OAuth2/Service/Nest.php @@ -0,0 +1,106 @@ + + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link https://developer.nest.com/documentation + */ + +namespace OAuth\OAuth2\Service; + +use OAuth\OAuth2\Token\StdOAuth2Token; +use OAuth\Common\Http\Exception\TokenResponseException; +use OAuth\Common\Http\Uri\Uri; +use OAuth\Common\Consumer\CredentialsInterface; +use OAuth\Common\Http\Client\ClientInterface; +use OAuth\Common\Storage\TokenStorageInterface; +use OAuth\Common\Http\Uri\UriInterface; + +/** + * Nest service. + * + * @author Pedro Amorim + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link https://developer.nest.com/documentation + */ +class Nest extends AbstractService +{ + + public function __construct( + CredentialsInterface $credentials, + ClientInterface $httpClient, + TokenStorageInterface $storage, + $scopes = array(), + UriInterface $baseApiUri = null + ) { + parent::__construct( + $credentials, + $httpClient, + $storage, + $scopes, + $baseApiUri, + true + ); + + if (null === $baseApiUri) { + $this->baseApiUri = new Uri('https://developer-api.nest.com/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://home.nest.com/login/oauth2'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://api.home.nest.com/oauth2/access_token'); + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_QUERY_STRING_V4; + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException( + 'Error in retrieving token: "' . $data['error'] . '"' + ); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + $token->setLifeTime($data['expires_in']); + + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + unset($data['expires_in']); + + $token->setExtraParams($data); + + return $token; + } +} diff --git a/htdocs/includes/OAuth/OAuth2/Service/Netatmo.php b/htdocs/includes/OAuth/OAuth2/Service/Netatmo.php new file mode 100644 index 00000000000..eb5c68d9eb6 --- /dev/null +++ b/htdocs/includes/OAuth/OAuth2/Service/Netatmo.php @@ -0,0 +1,117 @@ + + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link https://dev.netatmo.com/doc/ + */ + +namespace OAuth\OAuth2\Service; + +use OAuth\OAuth2\Token\StdOAuth2Token; +use OAuth\Common\Http\Exception\TokenResponseException; +use OAuth\Common\Http\Uri\Uri; +use OAuth\Common\Consumer\CredentialsInterface; +use OAuth\Common\Http\Client\ClientInterface; +use OAuth\Common\Storage\TokenStorageInterface; +use OAuth\Common\Http\Uri\UriInterface; + +/** + * Netatmo service. + * + * @author Pedro Amorim + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link https://dev.netatmo.com/doc/ + */ +class Netatmo extends AbstractService +{ + + // SCOPES + // @link https://dev.netatmo.com/doc/authentication/scopes + + // Used to read weather station's data (devicelist, getmeasure) + const SCOPE_STATION_READ = 'read_station'; + // Used to read thermostat's data (devicelist, getmeasure, getthermstate) + const SCOPE_THERMOSTAT_READ = 'read_thermostat'; + // Used to configure the thermostat (syncschedule, setthermpoint) + const SCOPE_THERMOSTAT_WRITE = 'write_thermostat'; + + public function __construct( + CredentialsInterface $credentials, + ClientInterface $httpClient, + TokenStorageInterface $storage, + $scopes = array(), + UriInterface $baseApiUri = null + ) { + parent::__construct( + $credentials, + $httpClient, + $storage, + $scopes, + $baseApiUri, + true // use parameter state + ); + + if (null === $baseApiUri) { + $this->baseApiUri = new Uri('https://api.netatmo.net/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri($this->baseApiUri.'oauth2/authorize'); + + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri($this->baseApiUri.'oauth2/token'); + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_QUERY_STRING; + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException( + 'Error in retrieving token: "' . $data['error'] . '"' + ); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + $token->setLifetime($data['expires_in']); + + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + unset($data['expires_in']); + + $token->setExtraParams($data); + + return $token; + } +} diff --git a/htdocs/includes/OAuth/OAuth2/Service/ParrotFlowerPower.php b/htdocs/includes/OAuth/OAuth2/Service/ParrotFlowerPower.php new file mode 100644 index 00000000000..78ef94257c0 --- /dev/null +++ b/htdocs/includes/OAuth/OAuth2/Service/ParrotFlowerPower.php @@ -0,0 +1,142 @@ + + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link https://flowerpowerdev.parrot.com/projects/flower-power-web-service-api/wiki + */ + +namespace OAuth\OAuth2\Service; + +use OAuth\OAuth2\Token\StdOAuth2Token; +use OAuth\Common\Http\Exception\TokenResponseException; +use OAuth\Common\Http\Uri\Uri; +use OAuth\Common\Consumer\CredentialsInterface; +use OAuth\Common\Http\Client\ClientInterface; +use OAuth\Common\Storage\TokenStorageInterface; +use OAuth\Common\Http\Uri\UriInterface; +use OAuth\OAuth2\Service\Exception\MissingRefreshTokenException; +use OAuth\Common\Token\TokenInterface; + +/** + * ParrotFlowerPower service. + * + * @author Pedro Amorim + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link https://flowerpowerdev.parrot.com/projects/flower-power-web-service-api/wiki + */ +class ParrotFlowerPower extends AbstractService +{ + + public function __construct( + CredentialsInterface $credentials, + ClientInterface $httpClient, + TokenStorageInterface $storage, + $scopes = array(), + UriInterface $baseApiUri = null + ) { + parent::__construct( + $credentials, + $httpClient, + $storage, + $scopes, + $baseApiUri, + true + ); + + if (null === $baseApiUri) { + $this->baseApiUri = new Uri('https://apiflowerpower.parrot.com/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri($this->baseApiUri.'oauth2/v1/authorize'); + + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri($this->baseApiUri.'user/v1/authenticate'); + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_HEADER_BEARER; + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException( + 'Error in retrieving token: "' . $data['error'] . '"' + ); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + $token->setLifetime($data['expires_in']); + + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + unset($data['expires_in']); + + $token->setExtraParams($data); + + return $token; + } + + + /** + * Parrot use a different endpoint for refresh a token + * + * {@inheritdoc} + */ + public function refreshAccessToken(TokenInterface $token) + { + $refreshToken = $token->getRefreshToken(); + + if (empty($refreshToken)) { + throw new MissingRefreshTokenException(); + } + + $parameters = array( + 'grant_type' => 'refresh_token', + 'type' => 'web_server', + 'client_id' => $this->credentials->getConsumerId(), + 'client_secret' => $this->credentials->getConsumerSecret(), + 'refresh_token' => $refreshToken, + ); + + $responseBody = $this->httpClient->retrieveResponse( + new Uri($this->baseApiUri.'user/v1/refresh'), + $parameters, + $this->getExtraOAuthHeaders() + ); + $token = $this->parseAccessTokenResponse($responseBody); + $this->storage->storeAccessToken($this->service(), $token); + + return $token; + } +} diff --git a/htdocs/includes/OAuth/OAuth2/Service/Paypal.php b/htdocs/includes/OAuth/OAuth2/Service/Paypal.php new file mode 100644 index 00000000000..761c09d6696 --- /dev/null +++ b/htdocs/includes/OAuth/OAuth2/Service/Paypal.php @@ -0,0 +1,103 @@ + + * @link https://developer.paypal.com/webapps/developer/docs/integration/direct/log-in-with-paypal/detailed/ + */ +class Paypal extends AbstractService +{ + /** + * Defined scopes + * @link https://developer.paypal.com/webapps/developer/docs/integration/direct/log-in-with-paypal/detailed/ + * @see #attributes + */ + const SCOPE_OPENID = 'openid'; + const SCOPE_PROFILE = 'profile'; + const SCOPE_PAYPALATTRIBUTES = 'https://uri.paypal.com/services/paypalattributes'; + const SCOPE_EMAIL = 'email'; + const SCOPE_ADDRESS = 'address'; + const SCOPE_PHONE = 'phone'; + const SCOPE_EXPRESSCHECKOUT = 'https://uri.paypal.com/services/expresscheckout'; + + public function __construct( + CredentialsInterface $credentials, + ClientInterface $httpClient, + TokenStorageInterface $storage, + $scopes = array(), + UriInterface $baseApiUri = null + ) { + parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri); + + if (null === $baseApiUri) { + $this->baseApiUri = new Uri('https://api.paypal.com/v1/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://www.paypal.com/webapps/auth/protocol/openidconnect/v1/authorize'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://api.paypal.com/v1/identity/openidconnect/tokenservice'); + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_HEADER_BEARER; + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['message'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['message'] . '"'); + } elseif (isset($data['name'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['name'] . '"'); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + $token->setLifeTime($data['expires_in']); + + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + unset($data['expires_in']); + + $token->setExtraParams($data); + + return $token; + } +} diff --git a/htdocs/includes/OAuth/OAuth2/Service/Pocket.php b/htdocs/includes/OAuth/OAuth2/Service/Pocket.php new file mode 100644 index 00000000000..8c955440e27 --- /dev/null +++ b/htdocs/includes/OAuth/OAuth2/Service/Pocket.php @@ -0,0 +1,125 @@ +baseApiUri = new Uri('https://getpocket.com/v3/'); + } + } + + public function getRequestTokenEndpoint() + { + return new Uri('https://getpocket.com/v3/oauth/request'); + } + + public function getAuthorizationEndpoint() + { + return new Uri('https://getpocket.com/auth/authorize'); + } + + public function getAccessTokenEndpoint() + { + return new Uri('https://getpocket.com/v3/oauth/authorize'); + } + + public function getAuthorizationUri(array $additionalParameters = array()) + { + $parameters = array_merge( + $additionalParameters, + array( + 'redirect_uri' => $this->credentials->getCallbackUrl(), + ) + ); + + // Build the url + $url = clone $this->getAuthorizationEndpoint(); + foreach ($parameters as $key => $val) { + $url->addToQuery($key, $val); + } + + return $url; + } + + public function requestRequestToken() + { + $responseBody = $this->httpClient->retrieveResponse( + $this->getRequestTokenEndpoint(), + array( + 'consumer_key' => $this->credentials->getConsumerId(), + 'redirect_uri' => $this->credentials->getCallbackUrl(), + ) + ); + + $code = $this->parseRequestTokenResponse($responseBody); + + return $code; + } + + protected function parseRequestTokenResponse($responseBody) + { + parse_str($responseBody, $data); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (!isset($data['code'])) { + throw new TokenResponseException('Error in retrieving code.'); + } + return $data['code']; + } + + public function requestAccessToken($code) + { + $bodyParams = array( + 'consumer_key' => $this->credentials->getConsumerId(), + 'code' => $code, + ); + + $responseBody = $this->httpClient->retrieveResponse( + $this->getAccessTokenEndpoint(), + $bodyParams, + $this->getExtraOAuthHeaders() + ); + $token = $this->parseAccessTokenResponse($responseBody); + $this->storage->storeAccessToken($this->service(), $token); + + return $token; + } + + protected function parseAccessTokenResponse($responseBody) + { + parse_str($responseBody, $data); + + if ($data === null || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth2Token(); + #$token->setRequestToken($data['access_token']); + $token->setAccessToken($data['access_token']); + $token->setEndOfLife(StdOAuth2Token::EOL_NEVER_EXPIRES); + unset($data['access_token']); + $token->setExtraParams($data); + + return $token; + } +} diff --git a/htdocs/includes/OAuth/OAuth2/Service/Reddit.php b/htdocs/includes/OAuth/OAuth2/Service/Reddit.php new file mode 100644 index 00000000000..9e524d12144 --- /dev/null +++ b/htdocs/includes/OAuth/OAuth2/Service/Reddit.php @@ -0,0 +1,114 @@ +baseApiUri = new Uri('https://oauth.reddit.com'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://ssl.reddit.com/api/v1/authorize'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://ssl.reddit.com/api/v1/access_token'); + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_HEADER_BEARER; + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + $token->setLifeTime($data['expires_in']); + + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + unset($data['expires_in']); + + $token->setExtraParams($data); + + return $token; + } + + /** + * {@inheritdoc} + */ + protected function getExtraOAuthHeaders() + { + // Reddit uses a Basic OAuth header + return array('Authorization' => 'Basic ' . + base64_encode($this->credentials->getConsumerId() . ':' . $this->credentials->getConsumerSecret())); + } +} diff --git a/htdocs/includes/OAuth/OAuth2/Service/RunKeeper.php b/htdocs/includes/OAuth/OAuth2/Service/RunKeeper.php new file mode 100644 index 00000000000..715840767fe --- /dev/null +++ b/htdocs/includes/OAuth/OAuth2/Service/RunKeeper.php @@ -0,0 +1,105 @@ +baseApiUri = new Uri('https://api.runkeeper.com/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationUri(array $additionalParameters = array()) + { + $parameters = array_merge( + $additionalParameters, + array( + 'client_id' => $this->credentials->getConsumerId(), + 'redirect_uri' => $this->credentials->getCallbackUrl(), + 'response_type' => 'code', + ) + ); + + $parameters['scope'] = implode(' ', $this->scopes); + + // Build the url + $url = clone $this->getAuthorizationEndpoint(); + foreach ($parameters as $key => $val) { + $url->addToQuery($key, $val); + } + + return $url; + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://runkeeper.com/apps/authorize'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://runkeeper.com/apps/token'); + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_HEADER_BEARER; + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + + unset($data['access_token']); + + $token->setExtraParams($data); + + return $token; + } +} diff --git a/htdocs/includes/OAuth/OAuth2/Service/Salesforce.php b/htdocs/includes/OAuth/OAuth2/Service/Salesforce.php new file mode 100644 index 00000000000..a50de1123a4 --- /dev/null +++ b/htdocs/includes/OAuth/OAuth2/Service/Salesforce.php @@ -0,0 +1,92 @@ +parseAccessTokenResponse($responseBody); + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + // Salesforce tokens evidently never expire... + $token->setEndOfLife(StdOAuth2Token::EOL_NEVER_EXPIRES); + unset($data['access_token']); + + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + $token->setExtraParams($data); + + return $token; + } + + /** + * {@inheritdoc} + */ + protected function getExtraOAuthHeaders() + { + return array('Accept' => 'application/json'); + } +} diff --git a/htdocs/includes/OAuth/OAuth2/Service/ServiceInterface.php b/htdocs/includes/OAuth/OAuth2/Service/ServiceInterface.php new file mode 100644 index 00000000000..e689a4c0d3a --- /dev/null +++ b/htdocs/includes/OAuth/OAuth2/Service/ServiceInterface.php @@ -0,0 +1,38 @@ +baseApiUri = new Uri('https://api.soundcloud.com/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://soundcloud.com/connect'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://api.soundcloud.com/oauth2/token'); + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + + if (isset($data['expires_in'])) { + $token->setLifetime($data['expires_in']); + unset($data['expires_in']); + } + + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + + $token->setExtraParams($data); + + return $token; + } +} diff --git a/htdocs/includes/OAuth/OAuth2/Service/Spotify.php b/htdocs/includes/OAuth/OAuth2/Service/Spotify.php new file mode 100644 index 00000000000..5baf7aef7ae --- /dev/null +++ b/htdocs/includes/OAuth/OAuth2/Service/Spotify.php @@ -0,0 +1,109 @@ +baseApiUri = new Uri('https://api.spotify.com/v1/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://accounts.spotify.com/authorize'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://accounts.spotify.com/api/token'); + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_HEADER_BEARER; + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + + if (isset($data['expires_in'])) { + $token->setLifetime($data['expires_in']); + unset($data['expires_in']); + } + + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + + $token->setExtraParams($data); + + return $token; + } + + /** + * {@inheritdoc} + */ + protected function getExtraOAuthHeaders() + { + return array('Authorization' => 'Basic ' . + base64_encode($this->credentials->getConsumerId() . ':' . $this->credentials->getConsumerSecret())); + } +} diff --git a/htdocs/includes/OAuth/OAuth2/Service/Strava.php b/htdocs/includes/OAuth/OAuth2/Service/Strava.php new file mode 100644 index 00000000000..208ec635913 --- /dev/null +++ b/htdocs/includes/OAuth/OAuth2/Service/Strava.php @@ -0,0 +1,147 @@ + + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://strava.github.io/ + * @link http://strava.github.io/api/v3/oauth/ + */ + +namespace OAuth\OAuth2\Service; + +use OAuth\OAuth2\Token\StdOAuth2Token; +use OAuth\Common\Http\Exception\TokenResponseException; +use OAuth\Common\Http\Uri\Uri; +use OAuth\Common\Consumer\CredentialsInterface; +use OAuth\Common\Http\Client\ClientInterface; +use OAuth\Common\Storage\TokenStorageInterface; +use OAuth\Common\Http\Uri\UriInterface; +use OAuth\OAuth2\Service\Exception\InvalidAccessTypeException; + +/** + * Strava service. + * + * @author Pedro Amorim + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://strava.github.io/ + * @link http://strava.github.io/api/v3/oauth/ + */ +class Strava extends AbstractService +{ + /** + * Scopes + */ + // default + const SCOPE_PUBLIC = 'public'; + // Modify activities, upload on the user’s behalf + const SCOPE_WRITE = 'write'; + // View private activities and data within privacy zones + const SCOPE_VIEW_PRIVATE = 'view_private'; + + protected $approvalPrompt = 'auto'; + + public function __construct( + CredentialsInterface $credentials, + ClientInterface $httpClient, + TokenStorageInterface $storage, + $scopes = array(), + UriInterface $baseApiUri = null + ) { + if (empty($scopes)) { + $scopes = array(self::SCOPE_PUBLIC); + } + + parent::__construct( + $credentials, + $httpClient, + $storage, + $scopes, + $baseApiUri, + true + ); + + if (null === $baseApiUri) { + $this->baseApiUri = new Uri('https://www.strava.com/api/v3/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://www.strava.com/oauth/authorize?approval_prompt=' . $this->approvalPrompt); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://www.strava.com/oauth/token'); + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_HEADER_BEARER; + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error_description'])) { + throw new TokenResponseException( + 'Error in retrieving token: "' . $data['error_description'] . '"' + ); + } elseif (isset($data['error'])) { + throw new TokenResponseException( + 'Error in retrieving token: "' . $data['error'] . '"' + ); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + + if (isset($data['expires_in'])) { + $token->setLifeTime($data['expires_in']); + unset($data['expires_in']); + } + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + + $token->setExtraParams($data); + + return $token; + } + + public function setApprouvalPrompt($prompt) + { + if (!in_array($prompt, array('auto', 'force'), true)) { + // @todo Maybe could we rename this exception + throw new InvalidAccessTypeException('Invalid approuvalPrompt, expected either auto or force.'); + } + $this->approvalPrompt = $prompt; + } + + /** + * {@inheritdoc} + */ + protected function getScopesDelimiter() + { + return ','; + } +} diff --git a/htdocs/includes/OAuth/OAuth2/Service/Ustream.php b/htdocs/includes/OAuth/OAuth2/Service/Ustream.php new file mode 100644 index 00000000000..7e5581532a3 --- /dev/null +++ b/htdocs/includes/OAuth/OAuth2/Service/Ustream.php @@ -0,0 +1,98 @@ +baseApiUri = new Uri('https://api.ustream.tv/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://www.ustream.tv/oauth2/authorize'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://www.ustream.tv/oauth2/token'); + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_HEADER_BEARER; + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + $token->setLifeTime($data['expires_in']); + + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + unset($data['expires_in']); + + $token->setExtraParams($data); + + return $token; + } + + /** + * {@inheritdoc} + */ + protected function getExtraOAuthHeaders() + { + return array('Authorization' => 'Basic ' . $this->credentials->getConsumerSecret()); + } +} diff --git a/htdocs/includes/OAuth/OAuth2/Service/Vimeo.php b/htdocs/includes/OAuth/OAuth2/Service/Vimeo.php new file mode 100644 index 00000000000..2efe6b254ba --- /dev/null +++ b/htdocs/includes/OAuth/OAuth2/Service/Vimeo.php @@ -0,0 +1,156 @@ + + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link https://developer.vimeo.com/ + * @link https://developer.vimeo.com/api/authentication + */ + +namespace OAuth\OAuth2\Service; + +use OAuth\OAuth2\Token\StdOAuth2Token; +use OAuth\Common\Http\Exception\TokenResponseException; +use OAuth\Common\Http\Uri\Uri; +use OAuth\Common\Consumer\CredentialsInterface; +use OAuth\Common\Http\Client\ClientInterface; +use OAuth\Common\Storage\TokenStorageInterface; +use OAuth\Common\Http\Uri\UriInterface; + +/** + * Vimeo service. + * + * @author Pedro Amorim + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link https://developer.vimeo.com/ + * @link https://developer.vimeo.com/api/authentication + */ +class Vimeo extends AbstractService +{ + // API version + const VERSION = '3.2'; + // API Header Accept + const HEADER_ACCEPT = 'application/vnd.vimeo.*+json;version=3.2'; + + /** + * Scopes + * @see https://developer.vimeo.com/api/authentication#scope + */ + // View public videos + const SCOPE_PUBLIC = 'public'; + // View private videos + const SCOPE_PRIVATE = 'private'; + // View Vimeo On Demand purchase history + const SCOPE_PURCHASED = 'purchased'; + // Create new videos, groups, albums, etc. + const SCOPE_CREATE = 'create'; + // Edit videos, groups, albums, etc. + const SCOPE_EDIT = 'edit'; + // Delete videos, groups, albums, etc. + const SCOPE_DELETE = 'delete'; + // Interact with a video on behalf of a user, such as liking + // a video or adding it to your watch later queue + const SCOPE_INTERACT = 'interact'; + // Upload a video + const SCOPE_UPLOAD = 'upload'; + + public function __construct( + CredentialsInterface $credentials, + ClientInterface $httpClient, + TokenStorageInterface $storage, + $scopes = array(), + UriInterface $baseApiUri = null + ) { + parent::__construct( + $credentials, + $httpClient, + $storage, + $scopes, + $baseApiUri, + true + ); + + if (null === $baseApiUri) { + $this->baseApiUri = new Uri('https://api.vimeo.com/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://api.vimeo.com/oauth/authorize'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://api.vimeo.com/oauth/access_token'); + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_HEADER_BEARER; + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error_description'])) { + throw new TokenResponseException( + 'Error in retrieving token: "' . $data['error_description'] . '"' + ); + } elseif (isset($data['error'])) { + throw new TokenResponseException( + 'Error in retrieving token: "' . $data['error'] . '"' + ); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + + if (isset($data['expires_in'])) { + $token->setLifeTime($data['expires_in']); + unset($data['expires_in']); + } + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + + $token->setExtraParams($data); + + return $token; + } + + /** + * {@inheritdoc} + */ + protected function getExtraOAuthHeaders() + { + return array('Accept' => self::HEADER_ACCEPT); + } + + /** + * {@inheritdoc} + */ + protected function getExtraApiHeaders() + { + return array('Accept' => self::HEADER_ACCEPT); + } +} diff --git a/htdocs/includes/OAuth/OAuth2/Service/Vkontakte.php b/htdocs/includes/OAuth/OAuth2/Service/Vkontakte.php new file mode 100644 index 00000000000..4a7744ecad7 --- /dev/null +++ b/htdocs/includes/OAuth/OAuth2/Service/Vkontakte.php @@ -0,0 +1,109 @@ +baseApiUri = new Uri('https://api.vk.com/method/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://oauth.vk.com/authorize'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://oauth.vk.com/access_token'); + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + $token->setLifeTime($data['expires_in']); + + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + unset($data['expires_in']); + + $token->setExtraParams($data); + + return $token; + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_QUERY_STRING; + } +} diff --git a/htdocs/includes/OAuth/OAuth2/Service/Yahoo.php b/htdocs/includes/OAuth/OAuth2/Service/Yahoo.php new file mode 100644 index 00000000000..270e240325e --- /dev/null +++ b/htdocs/includes/OAuth/OAuth2/Service/Yahoo.php @@ -0,0 +1,78 @@ +setAccessToken($data['access_token']); + $token->setLifetime($data['expires_in']); + + if (isset($data['refresh_token'])) + { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + unset($data['expires_in']); + + $token->setExtraParams($data); + + return $token; + } + + /** + * {@inheritdoc} + */ + protected function getExtraOAuthHeaders() + { + $encodedCredentials = base64_encode($this->credentials->getConsumerId() . ':' . $this->credentials->getConsumerSecret()); + return array('Authorization' => 'Basic ' . $encodedCredentials); + } + +} diff --git a/htdocs/includes/OAuth/OAuth2/Service/Yammer.php b/htdocs/includes/OAuth/OAuth2/Service/Yammer.php new file mode 100644 index 00000000000..994a2935432 --- /dev/null +++ b/htdocs/includes/OAuth/OAuth2/Service/Yammer.php @@ -0,0 +1,82 @@ +baseApiUri = new Uri('https://www.yammer.com/api/v1/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://www.yammer.com/dialog/oauth'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://www.yammer.com/oauth2/access_token.json'); + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_HEADER_BEARER; + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']['token']); + $token->setLifetime($data['access_token']['expires_at']); + + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + unset($data['expires_in']); + + $token->setExtraParams($data); + + return $token; + } +} diff --git a/htdocs/includes/OAuth/OAuth2/Token/StdOAuth2Token.php b/htdocs/includes/OAuth/OAuth2/Token/StdOAuth2Token.php new file mode 100644 index 00000000000..eaaacac778b --- /dev/null +++ b/htdocs/includes/OAuth/OAuth2/Token/StdOAuth2Token.php @@ -0,0 +1,13 @@ + + * @author Pieter Hordijk + * @copyright Copyright (c) 2013 The authors + * @license http://www.opensource.org/licenses/mit-license.html MIT License + */ + +namespace OAuth; + +use OAuth\Common\Service\ServiceInterface; +use OAuth\Common\Consumer\CredentialsInterface; +use OAuth\Common\Storage\TokenStorageInterface; +use OAuth\Common\Http\Client\ClientInterface; +use OAuth\Common\Http\Client\StreamClient; +use OAuth\Common\Http\Uri\UriInterface; +use OAuth\Common\Exception\Exception; +use OAuth\OAuth1\Signature\Signature; + +class ServiceFactory +{ + /** + *@var ClientInterface + */ + protected $httpClient; + + /** + * @var array + */ + protected $serviceClassMap = array( + 'OAuth1' => array(), + 'OAuth2' => array() + ); + + /** + * @var array + */ + protected $serviceBuilders = array( + 'OAuth2' => 'buildV2Service', + 'OAuth1' => 'buildV1Service', + ); + + /** + * @param ClientInterface $httpClient + * + * @return ServiceFactory + */ + public function setHttpClient(ClientInterface $httpClient) + { + $this->httpClient = $httpClient; + + return $this; + } + + /** + * Register a custom service to classname mapping. + * + * @param string $serviceName Name of the service + * @param string $className Class to instantiate + * + * @return ServiceFactory + * + * @throws Exception If the class is nonexistent or does not implement a valid ServiceInterface + */ + public function registerService($serviceName, $className) + { + if (!class_exists($className)) { + throw new Exception(sprintf('Service class %s does not exist.', $className)); + } + + $reflClass = new \ReflectionClass($className); + + foreach (array('OAuth2', 'OAuth1') as $version) { + if ($reflClass->implementsInterface('OAuth\\' . $version . '\\Service\\ServiceInterface')) { + $this->serviceClassMap[$version][ucfirst($serviceName)] = $className; + + return $this; + } + } + + throw new Exception(sprintf('Service class %s must implement ServiceInterface.', $className)); + } + + /** + * Builds and returns oauth services + * + * It will first try to build an OAuth2 service and if none found it will try to build an OAuth1 service + * + * @param string $serviceName Name of service to create + * @param CredentialsInterface $credentials + * @param TokenStorageInterface $storage + * @param array|null $scopes If creating an oauth2 service, array of scopes + * @param UriInterface|null $baseApiUri + * @param string $apiVersion version of the api call + * + * @return ServiceInterface + */ + public function createService( + $serviceName, + CredentialsInterface $credentials, + TokenStorageInterface $storage, + $scopes = array(), + UriInterface $baseApiUri = null, + $apiVersion = "" + ) { + if (!$this->httpClient) { + // for backwards compatibility. + $this->httpClient = new StreamClient(); + } + + foreach ($this->serviceBuilders as $version => $buildMethod) { + $fullyQualifiedServiceName = $this->getFullyQualifiedServiceName($serviceName, $version); + + if (class_exists($fullyQualifiedServiceName)) { + return $this->$buildMethod( + $fullyQualifiedServiceName, + $credentials, + $storage, + $scopes, + $baseApiUri, + $apiVersion + ); + } + } + + return null; + } + + /** + * Gets the fully qualified name of the service + * + * @param string $serviceName The name of the service of which to get the fully qualified name + * @param string $type The type of the service to get (either OAuth1 or OAuth2) + * + * @return string The fully qualified name of the service + */ + private function getFullyQualifiedServiceName($serviceName, $type) + { + $serviceName = ucfirst($serviceName); + + if (isset($this->serviceClassMap[$type][$serviceName])) { + return $this->serviceClassMap[$type][$serviceName]; + } + + return '\\OAuth\\' . $type . '\\Service\\' . $serviceName; + } + + /** + * Builds v2 services + * + * @param string $serviceName The fully qualified service name + * @param CredentialsInterface $credentials + * @param TokenStorageInterface $storage + * @param array|null $scopes Array of scopes for the service + * @param UriInterface|null $baseApiUri + * + * @return ServiceInterface + * + * @throws Exception + */ + private function buildV2Service( + $serviceName, + CredentialsInterface $credentials, + TokenStorageInterface $storage, + array $scopes, + UriInterface $baseApiUri = null, + $apiVersion = "" + ) { + return new $serviceName( + $credentials, + $this->httpClient, + $storage, + $this->resolveScopes($serviceName, $scopes), + $baseApiUri, + $apiVersion + ); + } + + /** + * Resolves scopes for v2 services + * + * @param string $serviceName The fully qualified service name + * @param array $scopes List of scopes for the service + * + * @return array List of resolved scopes + */ + private function resolveScopes($serviceName, array $scopes) + { + $reflClass = new \ReflectionClass($serviceName); + $constants = $reflClass->getConstants(); + + $resolvedScopes = array(); + foreach ($scopes as $scope) { + $key = strtoupper('SCOPE_' . $scope); + + if (array_key_exists($key, $constants)) { + $resolvedScopes[] = $constants[$key]; + } else { + $resolvedScopes[] = $scope; + } + } + + return $resolvedScopes; + } + + /** + * Builds v1 services + * + * @param string $serviceName The fully qualified service name + * @param CredentialsInterface $credentials + * @param TokenStorageInterface $storage + * @param array $scopes + * @param UriInterface $baseApiUri + * + * @return ServiceInterface + * + * @throws Exception + */ + private function buildV1Service( + $serviceName, + CredentialsInterface $credentials, + TokenStorageInterface $storage, + $scopes, + UriInterface $baseApiUri = null + ) { + if (!empty($scopes)) { + throw new Exception( + 'Scopes passed to ServiceFactory::createService but an OAuth1 service was requested.' + ); + } + + return new $serviceName($credentials, $this->httpClient, $storage, new Signature($credentials), $baseApiUri); + } +} diff --git a/htdocs/includes/OAuth/bootstrap.php b/htdocs/includes/OAuth/bootstrap.php new file mode 100644 index 00000000000..548678aa115 --- /dev/null +++ b/htdocs/includes/OAuth/bootstrap.php @@ -0,0 +1,13 @@ +register(); diff --git a/htdocs/langs/en_US/oauth.lang b/htdocs/langs/en_US/oauth.lang new file mode 100644 index 00000000000..a45f9920dea --- /dev/null +++ b/htdocs/langs/en_US/oauth.lang @@ -0,0 +1,128 @@ +# Dolibarr language file - Source file is en_US - oauth +ConfigOAuth=Oauth Configuration +OAUTH_AMAZON_NAME=Api Amazon +OAUTH_AMAZON_ID=Api Amazon Id +OAUTH_AMAZON_SECRET=Api Amazon Secret +OAUTH_BITBUCKET_NAME=Api Bitbucket +OAUTH_BITBUCKET_ID=Api Bitbucket Id +OAUTH_BITBUCKET_SECRET=Api Bitbucket Secret +OAUTH_BITLY_NAME=Api Bitly +OAUTH_BITLY_ID=Api Bitly Id +OAUTH_BITLY_SECRET=Api Bitly Secret +OAUTH_BITRIX24_NAME=Api Bitrix24 +OAUTH_BITRIX24_ID=Api Bitrix24 Id +OAUTH_BITRIX24_SECRET=Api Bitrix24 Secret +OAUTH_BOX_NAME=Api Box +OAUTH_BOX_ID=Api Box Id +OAUTH_BOX_SECRET=Api Box Secret +OAUTH_BUFFER_NAME=Api Buffer +OAUTH_BUFFER_ID=Api Buffer Id +OAUTH_BUFFER_SECRET=Api Buffer Secret +OAUTH_DAILYMOTION_NAME=Api Daylymotion +OAUTH_DAILYMOTION_ID=Api Daylymotion Id +OAUTH_DAILYMOTION_SECRET=Api Daylymotion Secret +OAUTH_DEVIANTART_NAME=Api DeviantArt +OAUTH_DEVIANTART_ID=Api DeviantArt Id +OAUTH_DEVIANTART_SECRET=Api DeviantArt Secret +OAUTH_DROPBOX_NAME=Api Dropbox +OAUTH_DROPBOX_ID=Api Dropbox Id +OAUTH_DROPBOX_SECRET=Api Dropbox Secret +OAUTH_ETSY_NAME=Api Etsy +OAUTH_ETSY_ID=Api Etsy Id +OAUTH_ETSY_SECRET=Api Etsy Secret +OAUTH_EVEONLINE_NAME=Api EveOnline +OAUTH_EVEONLINE_ID=Api EveOnline Id +OAUTH_EVEONLINE_SECRET=Api EveOnline Secret +OAUTH_FACEBOOK_NAME=Api Facebook +OAUTH_FACEBOOK_ID=Api Facebook Id +OAUTH_FACEBOOK_SECRET=Api Facebook Secret +OAUTH_FITBIT_NAME=Api FitBit +OAUTH_FITBIT_ID=Api FitBit Id +OAUTH_FITBIT_SECRET=Api FitBit Secret +OAUTH_FIVEHUNDREDPX_NAME=Api 500px +OAUTH_FIVEHUNDREDPX_ID=Api 500px Id +OAUTH_FIVEHUNDREDPX_SECRET=Api 500px Secret +OAUTH_FLICKR_NAME=Api Flickr +OAUTH_FLICKR_ID=Api Flickr Id +OAUTH_FLICKR_SECRET=Api Flickr Secret +OAUTH_FOURSQUARE_NAME=Api Foursquare +OAUTH_FOURSQUARE_ID=Api Foursquare Id +OAUTH_FOURSQUARE_SECRET=Api Foursquare Secret +OAUTH_GITHUB_NAME=Api Github +OAUTH_GITHUB_ID=Api Github Id +OAUTH_GITHUB_SECRET=Api Github Secret +OAUTH_GOOGLE_NAME=Api Google +OAUTH_GOOGLE_ID=Api Google Id +OAUTH_GOOGLE_SECRET=Api Google Secret +OAUTH_HUBIC_NAME=Api Hubic +OAUTH_HUBIC_ID=Api Hubic Id +OAUTH_HUBIC_SECRET=Api Hubic Secret +OAUTH_INSTAGRAM_NAME=Api Instagram +OAUTH_INSTAGRAM_ID=Api Instagram Id +OAUTH_INSTAGRAM_SECRET=Api Instagram Secret +OAUTH_LINKEDIN_NAME=Api Linkedin +OAUTH_LINKEDIN_ID=Api Linkedin Id +OAUTH_LINKEDIN_SECRET=Api Linkedin Secret +OAUTH_MAILCHIMP_NAME=Api Mailchimp +OAUTH_MAILCHIMP_ID=Api Mailchimp Id +OAUTH_MAILCHIMP_SECRET=Api Mailchimp Secret +OAUTH_MICROSOFT_NAME=Api Microsoft +OAUTH_MICROSOFT_ID=Api Microsoft Id +OAUTH_MICROSOFT_SECRET=Api Microsoft Secret +OAUTH_NEST_NAME=Api Nest +OAUTH_NEST_ID=Api Nest Id +OAUTH_NEST_SECRET=Api Nest Secret +OAUTH_NETATMO_NAME=Api Netatmo +OAUTH_NETATMO_ID=Api Netatmo Id +OAUTH_NETATMO_SECRET=Api Netatmo Secret +OAUTH_PARROTFLOWERPOWER_NAME=Api Parrot Flower Power +OAUTH_PARROTFLOWERPOWER_ID=Api Parrot Flower Power Id +OAUTH_PARROTFLOWERPOWER_SECRET=Api Parrot Flower Power Secret +OAUTH_PAYPAL_NAME=Api Paypal +OAUTH_PAYPAL_ID=Api Paypal Id +OAUTH_PAYPAL_SECRET=Api Paypal Secret +OAUTH_POCKET_NAME=Api Pocket +OAUTH_POCKET_ID=Api Pocket Id +OAUTH_POCKET_SECRET=Api Pocket Secret +OAUTH_QUICKBOOKS_NAME=Api Quickbooks +OAUTH_QUICKBOOKS_ID=Api Quickbooks Id +OAUTH_QUICKBOOKS_SECRET=Api Quickbooks Secret +OAUTH_REDDIT_NAME=Api Reddit +OAUTH_REDDIT_ID=Api Reddit Id +OAUTH_REDDIT_SECRET=Api Reddit Secret +OAUTH_REDMINE_NAME=Api Redmine +OAUTH_REDMINE_ID=Api Redmine Id +OAUTH_REDMINE_SECRET=Api Redmine Secret +OAUTH_RUNKEEPER_NAME=Api Runkeeper +OAUTH_RUNKEEPER_ID=Api Runkeeper Id +OAUTH_RUNKEEPER_SECRET=Api Runkeeper Secret +OAUTH_SCOOPIT_NAME=Api Scoopit +OAUTH_SCOOPIT_ID=Api Scoopit Id +OAUTH_SCOOPIT_SECRET=Api Scoopit Secret +OAUTH_SOUNDCLOUD_NAME=Api Soundcloud +OAUTH_SOUNDCLOUD_ID=Api Soundcloud Id +OAUTH_SOUNDCLOUD_SECRET=Api Soundcloud Secret +OAUTH_SPOTIFY_NAME=Api Spotify +OAUTH_SPOTIFY_ID=Api Spotify id +OAUTH_SPOTIFY_SECRET=Api Spotify Secret +OAUTH_STRAVA_NAME=Api Strava +OAUTH_STRAVA_ID=Api Strava Id +OAUTH_STRAVA_SECRET=Api Strava Secret +OAUTH_TUMBLR_NAME=Api Tumblr +OAUTH_TUMBLR_ID=Api Tumblr Id +OAUTH_TUMBLR_SECRET=Api Tumblr Secret +OAUTH_TWITTER_NAME=Api Twitter +OAUTH_TWITTER_ID=Api Twitter Id +OAUTH_TWITTER_SECRET=Api Twitter Secret +OAUTH_USTREAM_NAME=Api Ustream +OAUTH_USTREAM_ID=Api Ustream Id +OAUTH_USTREAM_SECRET=Api Ustream Secret +OAUTH_VIMEO_NAME=Api Vimeo +OAUTH_VIMEO_ID=Api Vimeo Id +OAUTH_VIMEO_SECRET=Api Vimeo Secret +OAUTH_YAHOO_NAME=Api Yahoo +OAUTH_YAHOO_ID=Api Yahoo Id +OAUTH_YAHOO_SECRET=Api Yahoo Secret +OAUTH_YAMMER_NAME=Api Yammer +OAUTH_YAMMER_ID=Api Yammer Id +OAUTH_YAMMER_SECRET=Api Yammer Secret diff --git a/htdocs/langs/en_US/printing.lang b/htdocs/langs/en_US/printing.lang index a81355efe38..c41a1a9a20a 100644 --- a/htdocs/langs/en_US/printing.lang +++ b/htdocs/langs/en_US/printing.lang @@ -18,6 +18,9 @@ TestDriver=Test TargetedPrinter=Targeted printer UserConf=Setup per user PRINTGCP=Google Cloud Print +PRINTGCP_INFO=Google Api State +PRINTGCP_AUTHLINK=Authentication +RequestAccess=Request Access PrintGCPDesc=This driver allow to send documents directly to a printer with Google Cloud Print. PrintingDriverDescprintgcp=Configuration variables for printing driver Google Cloud Print. PrintTestDescprintgcp=List of Printers for Google Cloud Print. diff --git a/htdocs/printing/admin/printing.php b/htdocs/printing/admin/printing.php index 872ddd9f5dd..a779582d108 100644 --- a/htdocs/printing/admin/printing.php +++ b/htdocs/printing/admin/printing.php @@ -1,6 +1,6 @@ - * Copyright (C) 2014 Frederic France +/* Copyright (C) 2013 Laurent Destailleur + * Copyright (C) 2014-2015 Frederic France * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -126,7 +126,9 @@ if ($mode == 'setup' && $user->admin) print ''; print ''.$langs->trans("Parameters").''; print ''.$langs->trans("Value").''; + print ' '; print "\n"; + $submit_enabled=0; if (! empty($driver)) { @@ -136,17 +138,39 @@ if ($mode == 'setup' && $user->admin) $printer = new $classname($db); //print '
'.print_r($printer, true).'
'; $i=0; + $submit_enabled=0; foreach ($printer->conf as $key) { $var=!$var; - print ''; - print ''.$langs->trans($key['varname']).''; - print ''; - print ''; - print ' '.($key['example']!=''?$langs->trans("Example").' : '.$key['example']:''); - print ''; + switch ($key['type']) { + case "text": + case "password": + print ''; + print ''.$langs->trans($key['varname']).''; + print ''; + print ' '.($key['example']!=''?$langs->trans("Example").' : '.$key['example']:'').''; + print ''."\n"; + break; + case "authlink": + print ''; + print ''.$langs->trans($key['varname']).''; + print ''.$langs->trans('RequestAccess').''; + print ' '; + print ''."\n"; + break; + case "info": + print ''; + print ''.$langs->trans($key['varname']).''; + print ''.$langs->trans($key['info']).''; + print ' '; + print ''."\n"; + break; + case "submit": + if ($key['enabled']) $submit_enabled=1; + break; + } $i++; } } else { @@ -156,7 +180,9 @@ if ($mode == 'setup' && $user->admin) print ''; if (! empty($driver)) { - print '
'; + if ($submit_enabled) { + print '
'; + } } print ''; dol_fiche_end(); diff --git a/htdocs/printing/index.php b/htdocs/printing/index.php index 1ff3859565c..c860ece5616 100644 --- a/htdocs/printing/index.php +++ b/htdocs/printing/index.php @@ -1,6 +1,6 @@ + * Copyright (C) 2014-2015 Frederic France * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -29,6 +29,7 @@ llxHeader("",$langs->trans("Printing")); print load_fiche_titre($langs->trans("Printing")); +// TODO use modules to only activated modules // List Jobs from printing modules $printer = new dolPrintIPP($db,$conf->global->PRINTIPP_HOST,$conf->global->PRINTIPP_PORT,$user->login,$conf->global->PRINTIPP_USER,$conf->global->PRINTIPP_PASSWORD); $printer->list_jobs('commande');