';
$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 img_picto('', $supportedoauth2array[$keyforsupportedoauth2array]['picto'], 'class="pictofixedwidth"');
print $label;
+ if ($keyforprovider) {
+ print ' ('.$keyforprovider.')';
+ } else {
+ print ' ('.$langs->trans("NoName").')';
+ }
print ' | ';
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 '';
@@ -140,13 +196,13 @@ foreach ($list as $key) {
// Api Id
print '';
- print ' | ';
+ print ' | ';
print '';
print ' |
';
// Api Secret
print '';
- 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 img_picto('', $supportedoauth2array[$keyforsupportedoauth2array]['picto'], 'class="pictofixedwidth"');
print $langs->trans($keyforsupportedoauth2array);
+ if ($keyforprovider) {
+ print ' ('.$keyforprovider.')';
+ } else {
+ print ' ('.$langs->trans("NoName").')';
+ }
print ' | ';
print ' | ';
print ' | ';
@@ -299,9 +319,11 @@ if ($mode == 'setup' && $user->admin) {
//var_dump($key);
print $langs->trans("Token").'';
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 '| ';
//var_dump($key);
- print $langs->trans("TOKEN_REFRESH").' | ';
+ print $langs->trans("TOKEN_REFRESH");
+ print '';
print '';
- print yn($refreshtoken);
+ print ''.showValueWithClipboardCPButton($refreshtoken, 1, dol_trunc($refreshtoken, 32)).'';
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 '';
print yn($expire);
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 '';
print $expiredat;
print ' | ';
@@ -399,17 +424,18 @@ if ($mode == 'userconf' && $user->admin) {
print ''.$langs->trans("NumberOfCopy").' | ';
print ''.$langs->trans("Delete").' | ';
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 '| '.$row['login'].' | ';
- print ''.$row['module'].' | ';
- print ''.$row['driver'].' | ';
- print ''.$row['printer_name'].' | ';
- print ''.$row['printer_location'].' | ';
- print ''.$row['printer_id'].' | ';
- print ''.$row['copy'].' | ';
+ print ''.$obj->login.' | ';
+ print ''.$obj->module.' | ';
+ print ''.$obj->driver.' | ';
+ print ''.$obj->printer_name.' | ';
+ print ''.$obj->printer_location.' | ';
+ print ''.$obj->printer_id.' | ';
+ print ''.$obj->copy.' | ';
print ''.img_picto($langs->trans("Delete"), 'delete').' | ';
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;
|