Browse Source

Maxi debug of OAuth module. Solve tons of problems.

Laurent Destailleur 3 years ago
parent
commit
34464ac92b

+ 75 - 19
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 (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 {
+		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');
+		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 '<span class="opacitymedium">'.$langs->trans("ListOfSupportedOauthProviders").'</span><br><br>';
 
+
+print '<select name="provider" id="provider">';
+print '<option name="-1" value="-1">'.$langs->trans("Type").'</option>';
+foreach ($list as $key) {
+	$supported = 0;
+	$keyforsupportedoauth2array = $key[0];
+
+	if (in_array($keyforsupportedoauth2array, array_keys($supportedoauth2array))) {
+		$supported = 1;
+	}
+	if (!$supported) {
+		continue; // show only supported
+	}
+
+	$i++;
+	print '<option name="'.$keyforsupportedoauth2array.'" value="'.str_replace('_NAME', '', $keyforsupportedoauth2array).'">'.$supportedoauth2array[$keyforsupportedoauth2array]['name'].'</option>'."\n";
+}
+print '</select>';
+print ajax_combobox('provider');
+print ' <input type="text" name="label" value="" placeholder="'.$langs->trans("Name").'">';
+print ' <input type="submit" class="button small" name="add" value="'.$langs->trans("Add").'">';
+print '<br>';
+print '<br>';
+
+
 print '<div class="div-table-responsive">';
 print '<table class="noborder centpercent">';
 
 $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 '<td>';
 	print img_picto('', $supportedoauth2array[$keyforsupportedoauth2array]['picto'], 'class="pictofixedwidth"');
 	print $label;
+	if ($keyforprovider) {
+		print ' (<b>'.$keyforprovider.'</b>)';
+	} else {
+		print ' (<b>'.$langs->trans("NoName").'</b>)';
+	}
 	print '</td>';
 	print '<td>';
-	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 '</td>';
 	print '</tr>';
@@ -140,13 +196,13 @@ foreach ($list as $key) {
 
 	// Api Id
 	print '<tr class="oddeven value">';
-	print '<td><label for="'.$key[1].'">'.$langs->trans($key[1]).'</label></td>';
+	print '<td><label for="'.$key[1].'">'.$langs->trans("OAUTH_ID").'</label></td>';
 	print '<td><input type="text" size="100" id="'.$key[1].'" name="'.$key[1].'" value="'.$conf->global->{$key[1]}.'">';
 	print '</td></tr>';
 
 	// Api Secret
 	print '<tr class="oddeven value">';
-	print '<td><label for="'.$key[2].'">'.$langs->trans($key[2]).'</label></td>';
+	print '<td><label for="'.$key[2].'">'.$langs->trans("OAUTH_SECRET").'</label></td>';
 	print '<td><input type="password" size="100" id="'.$key[2].'" name="'.$key[2].'" value="'.$conf->global->{$key[2]}.'">';
 	print '</td></tr>';
 }

+ 54 - 28
htdocs/admin/oauthlogintokens.php

@@ -138,19 +138,32 @@ if (GETPOST('error')) {
 if ($mode == 'setup' && $user->admin) {
 	print '<span class="opacitymedium">'.$langs->trans("OAuthSetupForLogin")."</span><br><br>\n";
 
-	foreach ($list as $key) {
-		$supported = 0;
-		$keyforsupportedoauth2array = $key[0];
-
-		if (in_array($keyforsupportedoauth2array, array_keys($supportedoauth2array))) {
-			$supported = 1;
+	//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));
 		}
-		if (!$supported) {
-			continue; // show only supported
+	}
+
+	$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];						// 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 '<th class="titlefieldcreate">';
 		print img_picto('', $supportedoauth2array[$keyforsupportedoauth2array]['picto'], 'class="pictofixedwidth"');
 		print $langs->trans($keyforsupportedoauth2array);
+		if ($keyforprovider) {
+			print ' (<b>'.$keyforprovider.'</b>)';
+		} else {
+			print ' (<b>'.$langs->trans("NoName").'</b>)';
+		}
 		print '</th>';
 		print '<th></th>';
 		print '<th></th>';
@@ -299,9 +319,11 @@ if ($mode == 'setup' && $user->admin) {
 		//var_dump($key);
 		print $langs->trans("Token").'</td>';
 		print '<td colspan="2">';
+
 		if (is_object($tokenobj)) {
 			//var_dump($tokenobj);
-			print $tokenobj->getAccessToken().'<br>';
+			$tokentoshow = $tokenobj->getAccessToken();
+			print '<span class="" title="'.dol_escape_htmltag($tokentoshow).'">'.showValueWithClipboardCPButton($tokentoshow, 1, dol_trunc($tokentoshow, 32)).'<br>';
 			//print 'Refresh: '.$tokenobj->getRefreshToken().'<br>';
 			//print 'EndOfLife: '.$tokenobj->getEndOfLife().'<br>';
 			//var_dump($tokenobj->getExtraParams());
@@ -317,9 +339,10 @@ if ($mode == 'setup' && $user->admin) {
 			print '<tr class="oddeven">';
 			print '<td'.($key['required'] ? ' class="required"' : '').'>';
 			//var_dump($key);
-			print $langs->trans("TOKEN_REFRESH").'</td>';
+			print $langs->trans("TOKEN_REFRESH");
+			print '</td>';
 			print '<td colspan="2">';
-			print yn($refreshtoken);
+			print '<span class="" title="'.dol_escape_htmltag($refreshtoken).'">'.showValueWithClipboardCPButton($refreshtoken, 1, dol_trunc($refreshtoken, 32)).'</span>';
 			print '</td>';
 			print '</tr>';
 
@@ -327,7 +350,8 @@ if ($mode == 'setup' && $user->admin) {
 			print '<tr class="oddeven">';
 			print '<td'.($key['required'] ? ' class="required"' : '').'>';
 			//var_dump($key);
-			print $langs->trans("TOKEN_EXPIRED").'</td>';
+			print $langs->trans("TOKEN_EXPIRED");
+			print '</td>';
 			print '<td colspan="2">';
 			print yn($expire);
 			print '</td>';
@@ -337,7 +361,8 @@ if ($mode == 'setup' && $user->admin) {
 			print '<tr class="oddeven">';
 			print '<td'.($key['required'] ? ' class="required"' : '').'>';
 			//var_dump($key);
-			print $langs->trans("TOKEN_EXPIRE_AT").'</td>';
+			print $langs->trans("TOKEN_EXPIRE_AT");
+			print '</td>';
 			print '<td colspan="2">';
 			print $expiredat;
 			print '</td>';
@@ -399,17 +424,18 @@ if ($mode == 'userconf' && $user->admin) {
 	print '<th>'.$langs->trans("NumberOfCopy").'</th>';
 	print '<th class="center">'.$langs->trans("Delete").'</th>';
 	print "</tr>\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 '<tr class="oddeven">';
-		print '<td>'.$row['login'].'</td>';
-		print '<td>'.$row['module'].'</td>';
-		print '<td>'.$row['driver'].'</td>';
-		print '<td>'.$row['printer_name'].'</td>';
-		print '<td>'.$row['printer_location'].'</td>';
-		print '<td>'.$row['printer_id'].'</td>';
-		print '<td>'.$row['copy'].'</td>';
+		print '<td>'.$obj->login.'</td>';
+		print '<td>'.$obj->module.'</td>';
+		print '<td>'.$obj->driver.'</td>';
+		print '<td>'.$obj->printer_name.'</td>';
+		print '<td>'.$obj->printer_location.'</td>';
+		print '<td>'.$obj->printer_id.'</td>';
+		print '<td>'.$obj->copy.'</td>';
 		print '<td class="center">'.img_picto($langs->trans("Delete"), 'delete').'</td>';
 		print "</tr>\n";
 	}

+ 1 - 0
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

+ 4 - 4
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');
 
 
 

+ 20 - 7
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
 	}

+ 25 - 7
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');

+ 19 - 5
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

+ 19 - 5
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

+ 274 - 251
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 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)
-    {
-        //var_dump("storeAccessToken");
-        //var_dump($token);
-        dol_syslog("storeAccessToken");
-        
-        $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='".$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;
-    }
-
-    /**
-     * {@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;
-
-        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->retrieveAccessToken($service);
-
-        //if (is_array($tokens) && array_key_exists($service, $tokens)) {
-        //    unset($tokens[$service]);
-
-            $sql = "DELETE FROM ".MAIN_DB_PREFIX."oauth_token";
-            $sql.= " WHERE service='".$this->db->escape($service)."'";
-            $resql = $this->db->query($sql);
-        //}
-
-        // 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 db, are you sure you stored it?');
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public function storeAuthorizationState($service, $state)
-    {
-        // TODO save or update
-
-        if (!is_array($states)) {
-            $states = array();
-        }
-
-        $states[$service] = $state;
-        $this->states[$service] = $state;
-
-        $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);
-        }
-
-        // 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];
-
-        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;
-    }
-
+	/**
+	 * @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;
+	private $keyforprovider;
+
+
+	/**
+	 * @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;
+	}
+
+	/**
+	 * {@inheritDoc}
+	 */
+	public function retrieveAccessToken($service)
+	{
+		dol_syslog("retrieveAccessToken service=".$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)
+	{
+		global $conf;
+
+		//var_dump("storeAccessToken");
+		//var_dump($token);
+		dol_syslog("storeAccessToken service=".$service);
+
+		$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 = '".$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;
+
+		// allow chaining
+		return $this;
+	}
+
+	/**
+	 * {@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.($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']);
+
+		$this->tokens[$service] = $token;
+
+		return is_array($this->tokens)
+		&& isset($this->tokens[$service])
+		&& $this->tokens[$service] instanceof TokenInterface;
+	}
+
+	/**
+	 * {@inheritDoc}
+	 */
+	public function clearToken($service)
+	{
+		dol_syslog("clearToken service=".$service);
+
+		// TODO
+		// get previously saved tokens
+		//$tokens = $this->retrieveAccessToken($service);
+
+		//if (is_array($tokens) && array_key_exists($service, $tokens)) {
+		//    unset($tokens[$service]);
+
+		$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);
+		//}
+
+		// 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];
+		}
+
+		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 storeAuthorizationState($service, $state)
+	{
+		global $conf;
+
+		// TODO save or update
+
+		dol_syslog("storeAuthorizationState service=".$service);
+
+		if (!isset($this->states) || !is_array($this->states)) {
+			$this->states = array();
+		}
+
+		//$states[$service] = $state;
+		$this->states[$service] = $state;
+
+		$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;
+	}
 }

+ 1 - 0
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

+ 4 - 1
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 <a class="notasortlink" href="https://github.com/settings/developers" target="_blank" rel="noopener noreferrer external">this page</a> then "Register a new application" to create OAuth credentials
+OAUTH_URL_FOR_CREDENTIAL=Go to <a class="notasortlink" href="%s" target="_blank" rel="noopener noreferrer external">this page</a><span class="fas fa-external-link-alt paddingleft"></span> to create or get your OAuth ID and Secret
 OAUTH_STRIPE_TEST_NAME=OAuth Stripe Test
-OAUTH_STRIPE_LIVE_NAME=OAuth Stripe Live
+OAUTH_STRIPE_LIVE_NAME=OAuth Stripe Live
+OAUTH_ID=OAuth ID
+OAUTH_SECRET=OAuth secret

+ 2 - 0
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;

+ 2 - 0
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;