diff --git a/htdocs/admin/oauth.php b/htdocs/admin/oauth.php index a0d82d1d6bd..2fd6aff1a3b 100644 --- a/htdocs/admin/oauth.php +++ b/htdocs/admin/oauth.php @@ -43,6 +43,8 @@ if (!$user->admin) { } $action = GETPOST('action', 'aZ09'); +$provider = GETPOST('provider', 'aZ09'); +$label = GETPOST('label', 'aZ09'); /* @@ -52,21 +54,28 @@ $action = GETPOST('action', 'aZ09'); 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) { - setEventMessages($langs->trans("SetupSaved"), null); + if (GETPOST('add') && $provider && $provider != '-1') { // $provider is OAUTH_XXX + $constname = strtoupper($provider).($label ? '-'.$label : '').'_ID'; + dolibarr_set_const($db, $constname, 'ToComplete', 'chaine', 0, '', $conf->entity); } else { - setEventMessages($langs->trans("Error"), null, 'errors'); + foreach ($conf->global as $key => $val) { + if (!empty($val) && preg_match('/^OAUTH_.+_ID$/', $key)) { + $constvalue = str_replace('_ID', '', $key); + if (!dolibarr_set_const($db, $constvalue.'_ID', GETPOST($constvalue.'_ID'), 'chaine', 0, '', $conf->entity)) { + $error++; + } + // If we reset this provider, we also remove the secret + if (!dolibarr_set_const($db, $constvalue.'_SECRET', GETPOST($constvalue.'_ID') ? GETPOST($constvalue.'_SECRET') : '', 'chaine', 0, '', $conf->entity)) { + $error++; + } + } + } + + if (!$error) { + setEventMessages($langs->trans("SetupSaved"), null); + } else { + setEventMessages($langs->trans("Error"), null, 'errors'); + } } } @@ -92,15 +101,57 @@ print dol_get_fiche_head($head, 'services', '', -1, 'technic'); print ''.$langs->trans("ListOfSupportedOauthProviders").'

'; + +print ''; +print ajax_combobox('provider'); +print ' '; +print ' '; +print '
'; +print '
'; + + print '
'; print ''; $i = 0; +//var_dump($list); +foreach ($conf->global as $key => $val) { + if (!empty($val) && preg_match('/^OAUTH_.*_ID$/', $key)) { + $provider = preg_replace('/_ID$/', '', $key); + $listinsetup[] = array($provider.'_NAME', $provider.'_ID', $provider.'_SECRET', 'OAUTH Provider '.str_replace('OAUTH_', '', $provider)); + } +} + // $list is defined into oauth.lib.php to the list of supporter OAuth providers. -foreach ($list as $key) { +foreach ($listinsetup as $key) { $supported = 0; - $keyforsupportedoauth2array = $key[0]; + $keyforsupportedoauth2array = $key[0]; // May be OAUTH_GOOGLE_NAME or OAUTH_GOOGLE_xxx_NAME + $keyforsupportedoauth2array = preg_replace('/^OAUTH_/', '', $keyforsupportedoauth2array); + $keyforsupportedoauth2array = preg_replace('/_NAME$/', '', $keyforsupportedoauth2array); + if (preg_match('/^.*-/', $keyforsupportedoauth2array)) { + $keyforprovider = preg_replace('/^.*-/', '', $keyforsupportedoauth2array); + } else { + $keyforprovider = ''; + } + $keyforsupportedoauth2array = preg_replace('/-.*$/', '', $keyforsupportedoauth2array); + $keyforsupportedoauth2array = 'OAUTH_'.$keyforsupportedoauth2array.'_NAME'; if (in_array($keyforsupportedoauth2array, array_keys($supportedoauth2array))) { $supported = 1; @@ -117,10 +168,15 @@ foreach ($list as $key) { print ''; print ''; print ''; @@ -140,13 +196,13 @@ foreach ($list as $key) { // Api Id print ''; - print ''; + print ''; print ''; // Api Secret print ''; - print ''; + print ''; print ''; } diff --git a/htdocs/admin/oauthlogintokens.php b/htdocs/admin/oauthlogintokens.php index 73a9139f856..93aac9cff15 100644 --- a/htdocs/admin/oauthlogintokens.php +++ b/htdocs/admin/oauthlogintokens.php @@ -138,19 +138,32 @@ if (GETPOST('error')) { if ($mode == 'setup' && $user->admin) { print ''.$langs->trans("OAuthSetupForLogin")."

\n"; - foreach ($list as $key) { + //var_dump($list); + foreach ($conf->global as $key => $val) { + if (!empty($val) && preg_match('/^OAUTH_.*_ID$/', $key)) { + $provider = preg_replace('/_ID$/', '', $key); + $listinsetup[] = array($provider.'_NAME', $provider.'_ID', $provider.'_SECRET', 'OAUTH Provider '.str_replace('OAUTH_', '', $provider)); + } + } + + $oauthstateanticsrf = bin2hex(random_bytes(128/8)); + + // $list is defined into oauth.lib.php to the list of supporter OAuth providers. + foreach ($listinsetup as $key) { $supported = 0; - $keyforsupportedoauth2array = $key[0]; - - if (in_array($keyforsupportedoauth2array, array_keys($supportedoauth2array))) { - $supported = 1; - } - if (!$supported) { - continue; // show only supported + $keyforsupportedoauth2array = $key[0]; // May be OAUTH_GOOGLE_NAME or OAUTH_GOOGLE_xxx_NAME + $keyforsupportedoauth2array = preg_replace('/^OAUTH_/', '', $keyforsupportedoauth2array); + $keyforsupportedoauth2array = preg_replace('/_NAME$/', '', $keyforsupportedoauth2array); + if (preg_match('/^.*-/', $keyforsupportedoauth2array)) { + $keyforprovider = preg_replace('/^.*-/', '', $keyforsupportedoauth2array); + } else { + $keyforprovider = ''; } + $keyforsupportedoauth2array = preg_replace('/-.*$/', '', $keyforsupportedoauth2array); + $keyforsupportedoauth2array = 'OAUTH_'.$keyforsupportedoauth2array.'_NAME'; - $OAUTH_SERVICENAME = empty($supportedoauth2array[$keyforsupportedoauth2array]['name']) ? 'Unknown' : $supportedoauth2array[$keyforsupportedoauth2array]['name']; + $OAUTH_SERVICENAME = (empty($supportedoauth2array[$keyforsupportedoauth2array]['name']) ? 'Unknown' : $supportedoauth2array[$keyforsupportedoauth2array]['name'].($keyforprovider ? '-'.$keyforprovider : '')); // Define $shortscope, $urltorenew, $urltodelete, $urltocheckperms // TODO Use array $supportedoauth2array @@ -158,6 +171,8 @@ if ($mode == 'setup' && $user->admin) { // List of keys that will be converted into scopes (from constants 'SCOPE_state_in_uppercase' in file of service). // We pass this param list in to 'state' because we need it before and after the redirect. $shortscope = 'user,public_repo'; + + // Note: github does not accept csrf key inside the state parameter (only know values) $urltorenew = $urlwithroot.'/core/modules/oauth/github_oauthcallback.php?shortscope='.$shortscope.'&state='.$shortscope.'&backtourl='.urlencode(DOL_URL_ROOT.'/admin/oauthlogintokens.php'); $urltodelete = $urlwithroot.'/core/modules/oauth/github_oauthcallback.php?action=delete&token='.newToken().'&backtourl='.urlencode(DOL_URL_ROOT.'/admin/oauthlogintokens.php'); $urltocheckperms = 'https://github.com/settings/applications/'; @@ -177,17 +192,18 @@ if ($mode == 'setup' && $user->admin) { $shortscope.=',gmail_full'; } - $oauthstateanticsrf = bin2hex(random_bytes(128/8)); - $_SESSION['oauthstateanticsrf'] = $shortscope.'-'.$oauthstateanticsrf; - $urltorenew = $urlwithroot.'/core/modules/oauth/google_oauthcallback.php?shortscope='.$shortscope.'&state='.$shortscope.'-'.$oauthstateanticsrf.'&backtourl='.urlencode(DOL_URL_ROOT.'/admin/oauthlogintokens.php'); $urltodelete = $urlwithroot.'/core/modules/oauth/google_oauthcallback.php?action=delete&token='.newToken().'&backtourl='.urlencode(DOL_URL_ROOT.'/admin/oauthlogintokens.php'); $urltocheckperms = 'https://security.google.com/settings/security/permissions'; } elseif ($keyforsupportedoauth2array == 'OAUTH_STRIPE_TEST_NAME') { + $shortscope = 'none'; + $urltorenew = $urlwithroot.'/core/modules/oauth/stripetest_oauthcallback.php?backtourl='.urlencode(DOL_URL_ROOT.'/admin/oauthlogintokens.php'); $urltodelete = ''; $urltocheckperms = ''; } elseif ($keyforsupportedoauth2array == 'OAUTH_STRIPE_LIVE_NAME') { + $shortscope = 'none'; + $urltorenew = $urlwithroot.'/core/modules/oauth/stripelive_oauthcallback.php?backtourl='.urlencode(DOL_URL_ROOT.'/admin/oauthlogintokens.php'); $urltodelete = ''; $urltocheckperms = ''; @@ -196,7 +212,7 @@ if ($mode == 'setup' && $user->admin) { $urltodelete = ''; $urltocheckperms = ''; } - + $urltorenew .= '&keyforprovider='.$keyforprovider; // Show value of token $tokenobj = null; @@ -220,7 +236,6 @@ if ($mode == 'setup' && $user->admin) { if (is_object($tokenobj)) { $expire = ($tokenobj->getEndOfLife() !== $tokenobj::EOL_NEVER_EXPIRES && $tokenobj->getEndOfLife() !== $tokenobj::EOL_UNKNOWN && time() > ($tokenobj->getEndOfLife() - 30)); } - if ($key[1] != '' && $key[2] != '') { if (is_object($tokenobj)) { $refreshtoken = $tokenobj->getRefreshToken(); @@ -249,6 +264,11 @@ if ($mode == 'setup' && $user->admin) { print ''; print ''; print ''; @@ -299,9 +319,11 @@ if ($mode == 'setup' && $user->admin) { //var_dump($key); print $langs->trans("Token").''; print ''; print ''; //var_dump($key); - print $langs->trans("TOKEN_REFRESH").''; + print $langs->trans("TOKEN_REFRESH"); + print ''; print ''; print ''; @@ -327,7 +350,8 @@ if ($mode == 'setup' && $user->admin) { print ''; print ''; //var_dump($key); - print $langs->trans("TOKEN_EXPIRED").''; + print $langs->trans("TOKEN_EXPIRED"); + print ''; print ''; @@ -337,7 +361,8 @@ if ($mode == 'setup' && $user->admin) { print ''; print ''; //var_dump($key); - print $langs->trans("TOKEN_EXPIRE_AT").''; + print $langs->trans("TOKEN_EXPIRE_AT"); + print ''; print ''; @@ -399,17 +424,18 @@ if ($mode == 'userconf' && $user->admin) { print ''; print ''; print "\n"; - $sql = 'SELECT p.rowid, p.printer_name, p.printer_location, p.printer_id, p.copy, p.module, p.driver, p.userid, u.login FROM '.MAIN_DB_PREFIX.'printing as p, '.MAIN_DB_PREFIX.'user as u WHERE p.userid=u.rowid'; + $sql = "SELECT p.rowid, p.printer_name, p.printer_location, p.printer_id, p.copy, p.module, p.driver, p.userid, u.login"; + $sql .= " FROM ".MAIN_DB_PREFIX."printing as p, ".MAIN_DB_PREFIX."user as u WHERE p.userid = u.rowid"; $resql = $db->query($sql); - while ($row = $db->fetch_array($resql)) { + while ($obj = $db->fetch_object($resql)) { print ''; - print ''; - print ''; - print ''; - print ''; - print ''; - print ''; - print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; print ''; print "\n"; } diff --git a/htdocs/core/lib/functions.lib.php b/htdocs/core/lib/functions.lib.php index 93e88ec44d6..59fdee364b8 100644 --- a/htdocs/core/lib/functions.lib.php +++ b/htdocs/core/lib/functions.lib.php @@ -6459,6 +6459,7 @@ function get_default_localtax($thirdparty_seller, $thirdparty_buyer, $local, $id function yn($yesno, $case = 1, $color = 0) { global $langs; + $result = 'unknown'; $classname = ''; if ($yesno == 1 || strtolower($yesno) == 'yes' || strtolower($yesno) == 'true') { // A mettre avant test sur no a cause du == 0 diff --git a/htdocs/core/lib/oauth.lib.php b/htdocs/core/lib/oauth.lib.php index ab1b5a217b8..f99266db178 100644 --- a/htdocs/core/lib/oauth.lib.php +++ b/htdocs/core/lib/oauth.lib.php @@ -25,13 +25,13 @@ // Supported OAUTH (a provider is supported when a file xxx_oauthcallback.php is available into htdocs/core/modules/oauth) $supportedoauth2array = array( - 'OAUTH_GOOGLE_NAME'=>array('callbackfile' => 'google', 'picto' => 'google', 'urlforapp' => 'OAUTH_GOOGLE_DESC', 'name'=>'Google'), + 'OAUTH_GOOGLE_NAME'=>array('callbackfile' => 'google', 'picto' => 'google', 'urlforapp' => 'OAUTH_GOOGLE_DESC', 'name'=>'Google', 'urlforcredentials'=>'https://console.developers.google.com/'), ); if (!empty($conf->stripe->enabled)) { - $supportedoauth2array['OAUTH_STRIPE_TEST_NAME'] = array('callbackfile' => 'stripetest', 'picto' => 'stripe', 'urlforapp' => '', 'name'=>'StripeTest'); - $supportedoauth2array['OAUTH_STRIPE_LIVE_NAME'] = array('callbackfile' => 'stripelive', 'picto' => 'stripe', 'urlforapp' => '', 'name'=>'StripeLive'); + $supportedoauth2array['OAUTH_STRIPE_TEST_NAME'] = array('callbackfile' => 'stripetest', 'picto' => 'stripe', 'urlforapp' => '', 'name'=>'StripeTest', 'urlforcredentials'=>''); + $supportedoauth2array['OAUTH_STRIPE_LIVE_NAME'] = array('callbackfile' => 'stripelive', 'picto' => 'stripe', 'urlforapp' => '', 'name'=>'StripeLive', 'urlforcredentials'=>''); } -$supportedoauth2array['OAUTH_GITHUB_NAME'] = array('callbackfile' => 'github', 'picto' => 'github', 'urlforapp' => 'OAUTH_GITHUB_DESC', 'name'=>'GitHub'); +$supportedoauth2array['OAUTH_GITHUB_NAME'] = array('callbackfile' => 'github', 'picto' => 'github', 'urlforapp' => 'OAUTH_GITHUB_DESC', 'name'=>'GitHub', 'urlforcredentials'=>'https://github.com/settings/developers'); diff --git a/htdocs/core/modules/oauth/github_oauthcallback.php b/htdocs/core/modules/oauth/github_oauthcallback.php index 496a2c988fa..5c24e23aafa 100644 --- a/htdocs/core/modules/oauth/github_oauthcallback.php +++ b/htdocs/core/modules/oauth/github_oauthcallback.php @@ -34,9 +34,12 @@ $urlwithroot = $urlwithouturlroot.DOL_URL_ROOT; // This is to use external domai //$urlwithroot=DOL_MAIN_URL_ROOT; // This is to use same domain name than current - $action = GETPOST('action', 'aZ09'); $backtourl = GETPOST('backtourl', 'alpha'); +$keyforprovider = GETPOST('keyforprovider', 'aZ09'); +if (empty($keyforprovider) && !empty($_SESSION["oauthkeyforproviderbeforeoauthjump"]) && (GETPOST('code') || $action == 'delete')) { + $keyforprovider = $_SESSION["oauthkeyforproviderbeforeoauthjump"]; +} /** @@ -64,9 +67,11 @@ $serviceFactory->setHttpClient($httpClient); $storage = new DoliStorage($db, $conf); // Setup the credentials for the requests +$keyforparamid = 'OAUTH_GITHUB'.($keyforprovider ? '-'.$keyforprovider : '').'_ID'; +$keyforparamsecret = 'OAUTH_GITHUB'.($keyforprovider ? '-'.$keyforprovider : '').'_SECRET'; $credentials = new Credentials( - $conf->global->OAUTH_GITHUB_ID, - $conf->global->OAUTH_GITHUB_SECRET, + getDolGlobalString($keyforparamid), + getDolGlobalString($keyforparamsecret), $currentUri->getAbsoluteUri() ); @@ -81,13 +86,20 @@ if ($action != 'delete' && empty($requestedpermissionsarray)) { //var_dump($requestedpermissionsarray);exit; // Instantiate the Api service using the credentials, http client and storage mechanism for the token -$apiService = $serviceFactory->createService('GitHub', $credentials, $storage, $requestedpermissionsarray); +$apiService = $serviceFactory->createService('GitHub'.($keyforprovider ? '-'.$keyforprovider : ''), $credentials, $storage, $requestedpermissionsarray); // access type needed to have oauth provider refreshing token //$apiService->setAccessType('offline'); $langs->load("oauth"); +if (!getDolGlobalString($keyforparamid)) { + accessforbidden('Setup of service is not complete. Customer ID is missing'); +} +if (!getDolGlobalString($keyforparamsecret)) { + accessforbidden('Setup of service is not complete. Secret key is missing'); +} + /* * Actions @@ -140,14 +152,15 @@ if (!empty($_GET['code'])) { // We are coming from oauth provider page } catch (Exception $e) { print $e->getMessage(); } -} else // If entry on page with no parameter, we arrive here -{ +} else { // If entry on page with no parameter, we arrive here $_SESSION["backtourlsavedbeforeoauthjump"] = $backtourl; + $_SESSION["oauthkeyforproviderbeforeoauthjump"] = $keyforprovider; + $_SESSION['oauthstateanticsrf'] = $state; // This may create record into oauth_state before the header redirect. // Creation of record with state in this tables depend on the Provider used (see its constructor). if (GETPOST('state')) { - $url = $apiService->getAuthorizationUri(array('state'=>GETPOST('state'))); + $url = $apiService->getAuthorizationUri(array('state' => GETPOST('state'))); } else { $url = $apiService->getAuthorizationUri(); // Parameter state will be randomly generated } diff --git a/htdocs/core/modules/oauth/google_oauthcallback.php b/htdocs/core/modules/oauth/google_oauthcallback.php index 9fbc38ef156..2812c4f7163 100644 --- a/htdocs/core/modules/oauth/google_oauthcallback.php +++ b/htdocs/core/modules/oauth/google_oauthcallback.php @@ -40,6 +40,11 @@ $urlwithroot = $urlwithouturlroot.DOL_URL_ROOT; // This is to use external domai $action = GETPOST('action', 'aZ09'); $backtourl = GETPOST('backtourl', 'alpha'); +$keyforprovider = GETPOST('keyforprovider', 'aZ09'); +if (empty($keyforprovider) && !empty($_SESSION["oauthkeyforproviderbeforeoauthjump"]) && (GETPOST('code') || $action == 'delete')) { + // If we are coming from the Oauth page + $keyforprovider = $_SESSION["oauthkeyforproviderbeforeoauthjump"]; +} /** @@ -63,23 +68,25 @@ $httpClient = new \OAuth\Common\Http\Client\CurlClient(); //$httpClient->setCurlParameters($params); $serviceFactory->setHttpClient($httpClient); -// Dolibarr storage -$storage = new DoliStorage($db, $conf); - // Setup the credentials for the requests +$keyforparamid = 'OAUTH_GOOGLE'.($keyforprovider ? '-'.$keyforprovider : '').'_ID'; +$keyforparamsecret = 'OAUTH_GOOGLE'.($keyforprovider ? '-'.$keyforprovider : '').'_SECRET'; $credentials = new Credentials( - $conf->global->OAUTH_GOOGLE_ID, - $conf->global->OAUTH_GOOGLE_SECRET, + getDolGlobalString($keyforparamid), + getDolGlobalString($keyforparamsecret), $currentUri->getAbsoluteUri() ); $state = GETPOST('state'); +$statewithscopeonly = ''; +$statewithanticsrfonly = ''; $requestedpermissionsarray = array(); if ($state) { // 'state' parameter is standard to store a hash value and can be used to retrieve some parameters back $statewithscopeonly = preg_replace('/\-.*$/', '', $state); $requestedpermissionsarray = explode(',', $statewithscopeonly); // Example: 'userinfo_email,userinfo_profile,openid,email,profile,cloud_print'. + $statewithanticsrfonly = preg_replace('/^.*\-/', '', $state); } if ($action != 'delete' && empty($requestedpermissionsarray)) { print 'Error, parameter state is not defined'; @@ -88,6 +95,8 @@ if ($action != 'delete' && empty($requestedpermissionsarray)) { //var_dump($requestedpermissionsarray);exit; +// Dolibarr storage +$storage = new DoliStorage($db, $conf, $keyforprovider); // Instantiate the Api service using the credentials, http client and storage mechanism for the token // $requestedpermissionsarray contains list of scopes. @@ -101,6 +110,13 @@ $apiService->setAccessType('offline'); $langs->load("oauth"); +if (!getDolGlobalString($keyforparamid)) { + accessforbidden('Setup of service is not complete. Customer ID is missing'); +} +if (!getDolGlobalString($keyforparamsecret)) { + accessforbidden('Setup of service is not complete. Secret key is missing'); +} + /* * Actions @@ -117,7 +133,7 @@ if ($action == 'delete') { } if (GETPOST('code')) { // We are coming from oauth provider page. - dol_syslog("We are coming from the oauth provider page"); + dol_syslog("We are coming from the oauth provider page keyforprovider=".$keyforprovider); // We must validate that the $state is the same than the one into $_SESSION['oauthstateanticsrf'], return error if not. if (isset($_SESSION['oauthstateanticsrf']) && $state != $_SESSION['oauthstateanticsrf']) { @@ -174,8 +190,10 @@ if (GETPOST('code')) { // We are coming from oauth provider page. } } else { // If we enter this page without 'code' parameter, we arrive here. this is the case when we want to get the redirect - // to the OAuth provider login page + // to the OAuth provider login page. $_SESSION["backtourlsavedbeforeoauthjump"] = $backtourl; + $_SESSION["oauthkeyforproviderbeforeoauthjump"] = $keyforprovider; + $_SESSION['oauthstateanticsrf'] = $state; if (!preg_match('/^forlogin/', $state)) { $apiService->setApprouvalPrompt('force'); diff --git a/htdocs/core/modules/oauth/stripelive_oauthcallback.php b/htdocs/core/modules/oauth/stripelive_oauthcallback.php index 77ef3ebde1a..71f447098a2 100644 --- a/htdocs/core/modules/oauth/stripelive_oauthcallback.php +++ b/htdocs/core/modules/oauth/stripelive_oauthcallback.php @@ -34,9 +34,12 @@ $urlwithroot = $urlwithouturlroot.DOL_URL_ROOT; // This is to use external domai //$urlwithroot=DOL_MAIN_URL_ROOT; // This is to use same domain name than current - $action = GETPOST('action', 'aZ09'); $backtourl = GETPOST('backtourl', 'alpha'); +$keyforprovider = GETPOST('keyforprovider', 'aZ09'); +if (empty($keyforprovider) && !empty($_SESSION["oauthkeyforproviderbeforeoauthjump"]) && (GETPOST('code') || $action == 'delete')) { + $keyforprovider = $_SESSION["oauthkeyforproviderbeforeoauthjump"]; +} /** @@ -64,9 +67,11 @@ $serviceFactory->setHttpClient($httpClient); $storage = new DoliStorage($db, $conf); // Setup the credentials for the requests +$keyforparamid = 'OAUTH_STRIPE_LIVE'.($keyforprovider ? '-'.$keyforprovider : '').'_ID'; +$keyforparamsecret = 'OAUTH_STRIPE_LIVE'.($keyforprovider ? '-'.$keyforprovider : '').'_SECRET'; $credentials = new Credentials( - $conf->global->OAUTH_STRIPE_LIVE_ID, - $conf->global->STRIPE_LIVE_SECRET_KEY, + getDolGlobalString($keyforparamid), + getDolGlobalString($keyforparamsecret), $currentUri->getAbsoluteUri() ); @@ -84,7 +89,7 @@ if (GETPOST('state')) { // Instantiate the Api service using the credentials, http client and storage mechanism for the token //$apiService = $serviceFactory->createService('StripeTest', $credentials, $storage, $requestedpermissionsarray); -$sql = "INSERT INTO ".MAIN_DB_PREFIX."oauth_token set service='StripeLive', entity=".$conf->entity; +$sql = "INSERT INTO ".MAIN_DB_PREFIX."oauth_token SET service = 'StripeLive".($keyforprovider ? '-'.$keyforprovider : '')."', entity=".$conf->entity; $db->query($sql); // access type needed to have oauth provider refreshing token @@ -92,6 +97,13 @@ $db->query($sql); $langs->load("oauth"); +if (!getDolGlobalString($keyforparamid)) { + accessforbidden('Setup of service is not complete. Customer ID is missing'); +} +if (!getDolGlobalString($keyforparamsecret)) { + accessforbidden('Setup of service is not complete. Secret key is missing'); +} + /* * Actions @@ -148,6 +160,8 @@ if (!empty($_GET['code'])) { // We are coming from oauth provider page } else // If entry on page with no parameter, we arrive here { $_SESSION["backtourlsavedbeforeoauthjump"] = $backtourl; + $_SESSION["oauthkeyforproviderbeforeoauthjump"] = $keyforprovider; + $_SESSION['oauthstateanticsrf'] = $state; // This may create record into oauth_state before the header redirect. // Creation of record with state in this tables depend on the Provider used (see its constructor). @@ -156,7 +170,7 @@ if (!empty($_GET['code'])) { // We are coming from oauth provider page } else { //$url = $apiService->getAuthorizationUri(); // Parameter state will be randomly generated //https://connect.stripe.com/oauth/authorize?response_type=code&client_id=ca_AX27ut70tJ1j6eyFCV3ObEXhNOo2jY6V&scope=read_write - $url = 'https://connect.stripe.com/oauth/authorize?response_type=code&client_id='.$conf->global->OAUTH_STRIPE_LIVE_ID.'&scope=read_write'; + $url = 'https://connect.stripe.com/oauth/authorize?response_type=code&client_id='.$conf->global->$keyforparamid.'&scope=read_write'; } // we go on oauth provider authorization page diff --git a/htdocs/core/modules/oauth/stripetest_oauthcallback.php b/htdocs/core/modules/oauth/stripetest_oauthcallback.php index a31f0f43db4..e1e2434ea29 100644 --- a/htdocs/core/modules/oauth/stripetest_oauthcallback.php +++ b/htdocs/core/modules/oauth/stripetest_oauthcallback.php @@ -34,9 +34,12 @@ $urlwithroot = $urlwithouturlroot.DOL_URL_ROOT; // This is to use external domai //$urlwithroot=DOL_MAIN_URL_ROOT; // This is to use same domain name than current - $action = GETPOST('action', 'aZ09'); $backtourl = GETPOST('backtourl', 'alpha'); +$keyforprovider = GETPOST('keyforprovider', 'aZ09'); +if (empty($keyforprovider) && !empty($_SESSION["oauthkeyforproviderbeforeoauthjump"]) && (GETPOST('code') || $action == 'delete')) { + $keyforprovider = $_SESSION["oauthkeyforproviderbeforeoauthjump"]; +} /** @@ -64,9 +67,11 @@ $serviceFactory->setHttpClient($httpClient); $storage = new DoliStorage($db, $conf); // Setup the credentials for the requests +$keyforparamid = 'OAUTH_STRIPE_TEST'.($keyforprovider ? '-'.$keyforprovider : '').'_ID'; +$keyforparamsecret = 'OAUTH_STRIPE_TEST'.($keyforprovider ? '-'.$keyforprovider : '').'_SECRET'; $credentials = new Credentials( - $conf->global->OAUTH_STRIPE_TEST_ID, - $conf->global->STRIPE_TEST_SECRET_KEY, + getDolGlobalString($keyforparamid), + getDolGlobalString($keyforparamsecret), $currentUri->getAbsoluteUri() ); @@ -84,7 +89,7 @@ if (GETPOST('state')) { // Instantiate the Api service using the credentials, http client and storage mechanism for the token //$apiService = $serviceFactory->createService('StripeTest', $credentials, $storage, $requestedpermissionsarray); -$sql = "INSERT INTO ".MAIN_DB_PREFIX."oauth_token set service='StripeTest', entity=".$conf->entity; +$sql = "INSERT INTO ".MAIN_DB_PREFIX."oauth_token SET service = 'StripeTest".($keyforprovider ? '-'.$keyforprovider : '')."', entity=".$conf->entity; $db->query($sql); // access type needed to have oauth provider refreshing token @@ -92,6 +97,13 @@ $db->query($sql); $langs->load("oauth"); +if (!getDolGlobalString($keyforparamid)) { + accessforbidden('Setup of service is not complete. Customer ID is missing'); +} +if (!getDolGlobalString($keyforparamsecret)) { + accessforbidden('Setup of service is not complete. Secret key is missing'); +} + /* * Actions @@ -148,6 +160,8 @@ if (!empty($_GET['code'])) { // We are coming from oauth provider page } else // If entry on page with no parameter, we arrive here { $_SESSION["backtourlsavedbeforeoauthjump"] = $backtourl; + $_SESSION["oauthkeyforproviderbeforeoauthjump"] = $keyforprovider; + $_SESSION['oauthstateanticsrf'] = $state; // This may create record into oauth_state before the header redirect. // Creation of record with state in this tables depend on the Provider used (see its constructor). @@ -156,7 +170,7 @@ if (!empty($_GET['code'])) { // We are coming from oauth provider page } else { //$url = $apiService->getAuthorizationUri(); // Parameter state will be randomly generated //https://connect.stripe.com/oauth/authorize?response_type=code&client_id=ca_AX27ut70tJ1j6eyFCV3ObEXhNOo2jY6V&scope=read_write - $url = 'https://connect.stripe.com/oauth/authorize?response_type=code&client_id='.$conf->global->OAUTH_STRIPE_TEST_ID.'&scope=read_write'; + $url = 'https://connect.stripe.com/oauth/authorize?response_type=code&client_id='.$conf->global->$keyforparamid.'&scope=read_write'; } // we go on oauth provider authorization page diff --git a/htdocs/includes/OAuth/Common/Storage/DoliStorage.php b/htdocs/includes/OAuth/Common/Storage/DoliStorage.php index b2a79dc4751..8bd15b7602e 100644 --- a/htdocs/includes/OAuth/Common/Storage/DoliStorage.php +++ b/htdocs/includes/OAuth/Common/Storage/DoliStorage.php @@ -29,257 +29,280 @@ use OAuth\Common\Storage\Exception\TokenNotFoundException; use OAuth\Common\Storage\Exception\AuthorizationStateNotFoundException; use DoliDB; +/** + * Class to manage storage of OAUTH2 in Dolibarr + */ class DoliStorage implements TokenStorageInterface { - /** - * @var DoliDB Database handler - */ - protected $db; + /** + * @var DoliDB Database handler + */ + protected $db; - /** - * @var object|TokenInterface - */ - protected $tokens; + /** + * @var object|TokenInterface + */ + protected $tokens; - /** - * @var string Error code (or message) - */ - public $error; - /** - * @var string[] Several error codes (or messages) - */ - public $errors = array(); + /** + * @var string Error code (or message) + */ + public $error; + /** + * @var string[] Several error codes (or messages) + */ + public $errors = array(); - private $conf; - private $key; - private $stateKey; + private $conf; + private $key; + private $stateKey; + private $keyforprovider; - /** - * @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]; - } + /** + * @param DoliDB $db Database handler + * @param Conf $conf Conf object + * @param string $keyforprovider Key to manage several providers of the same type. For example 'abc' will be added to 'Google' to defined storage key. + */ + public function __construct(DoliDB $db, $conf, $keyforprovider = '') + { + $this->db = $db; + $this->conf = $conf; + $this->keyforprovider = $keyforprovider; + $this->tokens = array(); + $this->states = array(); + //$this->key = $key; + //$this->stateKey = $stateKey; + } - throw new TokenNotFoundException('Token not found in db, are you sure you stored it?'); - } + /** + * {@inheritDoc} + */ + public function retrieveAccessToken($service) + { + dol_syslog("retrieveAccessToken service=".$service); - /** - * {@inheritDoc} - */ - public function storeAccessToken($service, TokenInterface $token) - { - //var_dump("storeAccessToken"); - //var_dump($token); - dol_syslog("storeAccessToken"); - - $serializedToken = serialize($token); - $this->tokens[$service] = $token; + if ($this->hasAccessToken($service)) { + return $this->tokens[$service]; + } - if (!is_array($this->tokens)) { - $this->tokens = array(); - } - $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."oauth_token"; - $sql.= " WHERE service='".$this->db->escape($service)."' AND entity=1"; - $resql = $this->db->query($sql); - if (! $resql) - { - dol_print_error($this->db); - } - $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 ('".$this->db->escape($service)."', '".$this->db->escape($serializedToken)."', 1)"; - $resql = $this->db->query($sql); - } - //print $sql; - - // allow chaining - return $this; - } + throw new TokenNotFoundException('Token not found in db, are you sure you stored it?'); + } - /** - * {@inheritDoc} - */ - public function hasAccessToken($service) - { - // get from db - dol_syslog("hasAccessToken service=".$service); - $sql = "SELECT token FROM ".MAIN_DB_PREFIX."oauth_token"; - $sql.= " WHERE service='".$this->db->escape($service)."'"; - $resql = $this->db->query($sql); - if (! $resql) - { - dol_print_error($this->db); - } - $result = $this->db->fetch_array($resql); - $token = unserialize($result['token']); - - $this->tokens[$service] = $token; + /** + * {@inheritDoc} + */ + public function storeAccessToken($service, TokenInterface $token) + { + global $conf; - return is_array($this->tokens) - && isset($this->tokens[$service]) - && $this->tokens[$service] instanceof TokenInterface; - } + //var_dump("storeAccessToken"); + //var_dump($token); + dol_syslog("storeAccessToken service=".$service); - /** - * {@inheritDoc} - */ - public function clearToken($service) - { - // TODO - // get previously saved tokens - //$tokens = $this->retrieveAccessToken($service); + $serializedToken = serialize($token); + $this->tokens[$service] = $token; - //if (is_array($tokens) && array_key_exists($service, $tokens)) { - // unset($tokens[$service]); + if (!is_array($this->tokens)) { + $this->tokens = array(); + } + $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."oauth_token"; + $sql .= " WHERE service = '".$this->db->escape($service.($this->keyforprovider?'-'.$this->keyforprovider:''))."'"; + $sql .= " AND entity IN (".getEntity('oauth_token').")"; + $resql = $this->db->query($sql); + if (! $resql) { + dol_print_error($this->db); + } + $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 = ".((int) $obj['rowid']); + $resql = $this->db->query($sql); + } else { + // save + $sql = "INSERT INTO ".MAIN_DB_PREFIX."oauth_token (service, token, entity)"; + $sql.= " VALUES ('".$this->db->escape($service.($this->keyforprovider?'-'.$this->keyforprovider:''))."', '".$this->db->escape($serializedToken)."', ".((int) $conf->entity).")"; + $resql = $this->db->query($sql); + } + //print $sql; - $sql = "DELETE FROM ".MAIN_DB_PREFIX."oauth_token"; - $sql.= " WHERE service='".$this->db->escape($service)."'"; - $resql = $this->db->query($sql); - //} + // allow chaining + return $this; + } - // allow chaining - return $this; - } + /** + * {@inheritDoc} + */ + public function hasAccessToken($service) + { + // get from db + dol_syslog("hasAccessToken service=".$service); - /** - * {@inheritDoc} - */ - public function clearAllTokens() - { - // TODO - $this->conf->remove($this->key); + $sql = "SELECT token FROM ".MAIN_DB_PREFIX."oauth_token"; + $sql .= " WHERE service = '".$this->db->escape($service.($this->keyforprovider?'-'.$this->keyforprovider:''))."'"; + $sql .= " AND entity IN (".getEntity('oauth_token').")"; + $resql = $this->db->query($sql); + if (! $resql) { + dol_print_error($this->db); + } + $result = $this->db->fetch_array($resql); + $token = unserialize($result['token']); - // allow chaining - return $this; - } + $this->tokens[$service] = $token; - /** - * {@inheritDoc} - */ - public function retrieveAuthorizationState($service) - { - if ($this->hasAuthorizationState($service)) { - return $this->states[$service]; + return is_array($this->tokens) + && isset($this->tokens[$service]) + && $this->tokens[$service] instanceof TokenInterface; + } - } + /** + * {@inheritDoc} + */ + public function clearToken($service) + { + dol_syslog("clearToken service=".$service); - throw new AuthorizationStateNotFoundException('State not found in db, are you sure you stored it?'); - } + // TODO + // get previously saved tokens + //$tokens = $this->retrieveAccessToken($service); - /** - * {@inheritDoc} - */ - public function storeAuthorizationState($service, $state) - { - // TODO save or update + //if (is_array($tokens) && array_key_exists($service, $tokens)) { + // unset($tokens[$service]); - if (!is_array($states)) { - $states = array(); - } + $sql = "DELETE FROM ".MAIN_DB_PREFIX."oauth_token"; + $sql .= " WHERE service = '".$this->db->escape($service.($this->keyforprovider?'-'.$this->keyforprovider:''))."'"; + $sql .= " AND entity IN (".getEntity('oauth_token').")"; + $resql = $this->db->query($sql); + //} - $states[$service] = $state; - $this->states[$service] = $state; + // allow chaining + return $this; + } - $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."oauth_state"; - $sql.= " WHERE service='".$this->db->escape($service)."' AND entity=1"; - $resql = $this->db->query($sql); - if (! $resql) - { - dol_print_error($this->db); - } - $obj = $this->db->fetch_array($resql); - if ($obj) { - // update - $sql = "UPDATE ".MAIN_DB_PREFIX."oauth_state"; - $sql.= " SET state='".$this->db->escape($state)."'"; - $sql.= " WHERE rowid='".$obj['rowid']."'"; - $resql = $this->db->query($sql); - } else { - // save - $sql = "INSERT INTO ".MAIN_DB_PREFIX."oauth_state (service, state, entity)"; - $sql.= " VALUES ('".$this->db->escape($service)."', '".$this->db->escape($state)."', 1)"; - $resql = $this->db->query($sql); - } + /** + * {@inheritDoc} + */ + public function clearAllTokens() + { + // TODO + $this->conf->remove($this->key); - // allow chaining - return $this; - } + // allow chaining + return $this; + } - /** - * {@inheritDoc} - */ - public function hasAuthorizationState($service) - { - // get state from db - dol_syslog("get state from db"); - $sql = "SELECT state FROM ".MAIN_DB_PREFIX."oauth_state"; - $sql.= " WHERE service='".$this->db->escape($service)."'"; - $resql = $this->db->query($sql); - $result = $this->db->fetch_array($resql); - $states[$service] = $result['state']; - $this->states[$service] = $states[$service]; + /** + * {@inheritDoc} + */ + public function retrieveAuthorizationState($service) + { + if ($this->hasAuthorizationState($service)) { + return $this->states[$service]; + } - return is_array($states) - && isset($states[$service]) - && null !== $states[$service]; - } + dol_syslog('State not found in db, are you sure you stored it?', LOG_WARNING); + throw new AuthorizationStateNotFoundException('State not found in db, are you sure you stored it?'); + } - /** - * {@inheritDoc} - */ - public function clearAuthorizationState($service) - { - // TODO - // get previously saved tokens - //$states = $this->conf->get($this->stateKey); + /** + * {@inheritDoc} + */ + public function storeAuthorizationState($service, $state) + { + global $conf; - if (is_array($states) && array_key_exists($service, $states)) { - unset($states[$service]); + // TODO save or update - // Replace the stored tokens array - //$this->conf->set($this->stateKey, $states); - } + dol_syslog("storeAuthorizationState service=".$service); - // allow chaining - return $this; - } + if (!isset($this->states) || !is_array($this->states)) { + $this->states = array(); + } - /** - * {@inheritDoc} - */ - public function clearAllAuthorizationStates() - { - // TODO - //$this->conf->remove($this->stateKey); + //$states[$service] = $state; + $this->states[$service] = $state; - // allow chaining - return $this; - } + $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."oauth_state"; + $sql .= " WHERE service = '".$this->db->escape($service.($this->keyforprovider?'-'.$this->keyforprovider:''))."'"; + $sql .= " AND entity IN (".getEntity('oauth_token').")"; + $resql = $this->db->query($sql); + if (! $resql) { + dol_print_error($this->db); + } + $obj = $this->db->fetch_array($resql); + if ($obj) { + // update + $sql = "UPDATE ".MAIN_DB_PREFIX."oauth_state"; + $sql.= " SET state = '".$this->db->escape($state)."'"; + $sql.= " WHERE rowid = ".((int) $obj['rowid']); + $resql = $this->db->query($sql); + } else { + // save + $sql = "INSERT INTO ".MAIN_DB_PREFIX."oauth_state (service, state, entity)"; + $sql.= " VALUES ('".$this->db->escape($service.($this->keyforprovider?'-'.$this->keyforprovider:''))."', '".$this->db->escape($state)."', ".((int) $conf->entity).")"; + $resql = $this->db->query($sql); + } + // allow chaining + return $this; + } + + /** + * {@inheritDoc} + */ + public function hasAuthorizationState($service) + { + // get state from db + dol_syslog("hasAuthorizationState service=".$service); + + $sql = "SELECT state FROM ".MAIN_DB_PREFIX."oauth_state"; + $sql .= " WHERE service = '".$this->db->escape($service.($this->keyforprovider?'-'.$this->keyforprovider:''))."'"; + $sql .= " AND entity IN (".getEntity('oauth_token').")"; + + $resql = $this->db->query($sql); + + $result = $this->db->fetch_array($resql); + + $states = array(); + $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($this->states) && array_key_exists($service, $this->states)) { + unset($this->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/langs/en_US/admin.lang b/htdocs/langs/en_US/admin.lang index 95ed076f347..698ade8b8d1 100644 --- a/htdocs/langs/en_US/admin.lang +++ b/htdocs/langs/en_US/admin.lang @@ -2277,3 +2277,4 @@ INVOICE_ADD_ZATCA_QR_CODEMore=Some Arabic countries need this QR Code on their i INVOICE_ADD_SWISS_QR_CODE=Show the swiss QR-Bill code on invoices UrlSocialNetworksDesc=Url link of social network. Use {socialid} for the variable part that contains the social network ID. IfThisCategoryIsChildOfAnother=If this category is a child of another one +NoName=No name diff --git a/htdocs/langs/en_US/oauth.lang b/htdocs/langs/en_US/oauth.lang index dda6f1bdf73..50e6b5031c7 100644 --- a/htdocs/langs/en_US/oauth.lang +++ b/htdocs/langs/en_US/oauth.lang @@ -28,5 +28,8 @@ OAUTH_GITHUB_NAME=OAuth GitHub service OAUTH_GITHUB_ID=OAuth GitHub Id OAUTH_GITHUB_SECRET=OAuth GitHub Secret OAUTH_GITHUB_DESC=Go to this page then "Register a new application" to create OAuth credentials +OAUTH_URL_FOR_CREDENTIAL=Go to this page to create or get your OAuth ID and Secret OAUTH_STRIPE_TEST_NAME=OAuth Stripe Test -OAUTH_STRIPE_LIVE_NAME=OAuth Stripe Live \ No newline at end of file +OAUTH_STRIPE_LIVE_NAME=OAuth Stripe Live +OAUTH_ID=OAuth ID +OAUTH_SECRET=OAuth secret \ No newline at end of file diff --git a/htdocs/theme/eldy/global.inc.php b/htdocs/theme/eldy/global.inc.php index 2f82eefffeb..614b3134e5c 100644 --- a/htdocs/theme/eldy/global.inc.php +++ b/htdocs/theme/eldy/global.inc.php @@ -7255,6 +7255,8 @@ span.clipboardCPValue.hidewithsize { display: inline-block; color: transparent; white-space: nowrap; + overflow-x: hidden; + vertical-align: middle; } div.clipboardCPValue.hidewithsize { width: 0 !important; diff --git a/htdocs/theme/md/style.css.php b/htdocs/theme/md/style.css.php index bcd2598740a..aaee7e13f8a 100644 --- a/htdocs/theme/md/style.css.php +++ b/htdocs/theme/md/style.css.php @@ -7024,6 +7024,8 @@ span.clipboardCPValue.hidewithsize { display: inline-block; color: transparent; white-space: nowrap; + overflow-x: hidden; + vertical-align: middle; } div.clipboardCPValue.hidewithsize { width: 0 !important;
'; print img_picto('', $supportedoauth2array[$keyforsupportedoauth2array]['picto'], 'class="pictofixedwidth"'); print $label; + if ($keyforprovider) { + print ' ('.$keyforprovider.')'; + } else { + print ' ('.$langs->trans("NoName").')'; + } print ''; - if (!empty($supportedoauth2array[$keyforsupportedoauth2array]['urlforapp'])) { - print $langs->trans($supportedoauth2array[$keyforsupportedoauth2array]['urlforapp']); + if (!empty($supportedoauth2array[$keyforsupportedoauth2array]['urlforcredentials'])) { + print $langs->trans("OAUTH_URL_FOR_CREDENTIAL", $supportedoauth2array[$keyforsupportedoauth2array]['urlforcredentials']); } print '
'; print '
'; print '
'; print img_picto('', $supportedoauth2array[$keyforsupportedoauth2array]['picto'], 'class="pictofixedwidth"'); print $langs->trans($keyforsupportedoauth2array); + if ($keyforprovider) { + print ' ('.$keyforprovider.')'; + } else { + print ' ('.$langs->trans("NoName").')'; + } print ''; + if (is_object($tokenobj)) { //var_dump($tokenobj); - print $tokenobj->getAccessToken().'
'; + $tokentoshow = $tokenobj->getAccessToken(); + print ''.showValueWithClipboardCPButton($tokentoshow, 1, dol_trunc($tokentoshow, 32)).'
'; //print 'Refresh: '.$tokenobj->getRefreshToken().'
'; //print 'EndOfLife: '.$tokenobj->getEndOfLife().'
'; //var_dump($tokenobj->getExtraParams()); @@ -317,9 +339,10 @@ if ($mode == 'setup' && $user->admin) { print '
'; - print yn($refreshtoken); + print ''.showValueWithClipboardCPButton($refreshtoken, 1, dol_trunc($refreshtoken, 32)).''; print '
'; print yn($expire); print '
'; print $expiredat; print ''.$langs->trans("NumberOfCopy").''.$langs->trans("Delete").'
'.$row['login'].''.$row['module'].''.$row['driver'].''.$row['printer_name'].''.$row['printer_location'].''.$row['printer_id'].''.$row['copy'].''.$obj->login.''.$obj->module.''.$obj->driver.''.$obj->printer_name.''.$obj->printer_location.''.$obj->printer_id.''.$obj->copy.''.img_picto($langs->trans("Delete"), 'delete').'