Skip to content

Commit

Permalink
NEW: authentication mode: Add experimental support for Google OAuth2
Browse files Browse the repository at this point in the history
connexion
  • Loading branch information
eldy committed May 14, 2023
1 parent 738f58f commit bc63d70
Show file tree
Hide file tree
Showing 6 changed files with 158 additions and 54 deletions.
3 changes: 2 additions & 1 deletion ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@ For uses:
---------

NEW: PHP 8.2 compatibility (test not yet completed).
NEW: #22740 Add OpenID Connect impl
NEW: #23436 Group social networks fields
NEW: Accountancy - Add specific page to export accounting data rather than the journals page
NEW: Accountancy - Add sub-account balance FPC22
NEW: Accountancy - Manage customer retained warranty FPC21+
NEW: Accountancy - Manage intra-community VAT on supplier invoices - FPC22
NEW: Can upload a file with drag and drop on purchase invoice, vats, salaries and social contributions
NEW: authentication mode: #22740 Add OpenID Connect impl
NEW: authentication mode: Add experimental support for Google OAuth2 connexion
NEW: adapt category and product pictures sizes on TakePOS
NEW: Supplier Invoices: add ability of ODT support to supplier invoices
NEW: Add a public virtual card page for each user
Expand Down
2 changes: 1 addition & 1 deletion htdocs/conf/conf.php.example
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ $dolibarr_main_instance_unique_id='84b5bc91f83b56e458db71e0adac2b62';
// $dolibarr_main_authentication='forceuser'; // This need to add also $dolibarr_auto_user='loginforuser';
// $dolibarr_main_authentication='twofactor'; // To use Google Authenticator. This need the non official external module "Two Factor" available on www.dolistore.com
// $dolibarr_main_authentication='openid_connect'; // See https://wiki.dolibarr.org/index.php?title=Authentication,_SSO_and_SSL
// $dolibarr_main_authentication='googleoauth'; // Still in development
// $dolibarr_main_authentication='googleoauth'; // See https://wiki.dolibarr.org/index.php?title=Authentication,_SSO_and_SSL
//
$dolibarr_main_authentication='dolibarr';

Expand Down
95 changes: 74 additions & 21 deletions htdocs/core/login/functions_googleoauth.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,34 +39,87 @@
*/
function check_user_password_googleoauth($usertotest, $passwordtotest, $entitytotest)
{
global $_POST, $db, $conf, $langs;
global $_POST, $conf;

dol_syslog("functions_googleoauth::check_user_password_googleoauth usertotest=".$usertotest);
dol_syslog("functions_googleoauth::check_user_password_googleoauth usertotest=".$usertotest." GETPOST('actionlogin')=".GETPOST('actionlogin'));

$login = '';

// Get identity from user and redirect browser to Google OAuth Server
if (GETPOSTISSET('username')) {
/*$openid = new SimpleOpenID();
$openid->SetIdentity(GETPOST('username'));
$protocol = ($conf->file->main_force_https ? 'https://' : 'http://');
$openid->SetTrustRoot($protocol . $_SERVER["HTTP_HOST"]);
$openid->SetRequiredFields(array('email','fullname'));
$_SESSION['dol_entity'] = $_POST["entity"];
//$openid->SetOptionalFields(array('dob','gender','postcode','country','language','timezone'));
if ($openid->sendDiscoveryRequestToGetXRDS())
{
$openid->SetApprovedURL($protocol . $_SERVER["HTTP_HOST"] . $_SERVER["SCRIPT_NAME"]); // Send Response from OpenID server to this script
$openid->Redirect(); // This will redirect user to OpenID Server
}
else
{
$error = $openid->GetError();
return false;
if (GETPOST('actionlogin') == 'login') {
if (!GETPOST('aftergoogleoauthreturn')) {
// We post the form on the login page by clicking on the link to login using Google.
dol_syslog("We post the form on the login page by clicking on the link to login using Google");

// We save data of form into a variable
$_SESSION['datafromloginform'] = array(
'entity'=>GETPOST('entity', 'int'),
'backtopage'=>GETPOST('backtopage'),
'tz'=>GETPOST('tz'),
'tzstring'=>GETPOST('tzstring'),
'dst_observed'=>GETPOST('dst_observed'),
'dst_first'=>GETPOST('dst_first'),
'dst_second'=>GETPOST('dst_second'),
'screenwidth'=>GETPOST('screenwidth'),
'screenheight'=>GETPOST('screenheight'),
'dol_hide_topmenu'=>GETPOST('dol_hide_topmenu'),
'dol_hide_leftmenu'=>GETPOST('dol_hide_leftmenu'),
'dol_optimize_smallscreen'=>GETPOST('dol_optimize_smallscreen'),
'dol_no_mouse_hover'=>GETPOST('dol_no_mouse_hover'),
'dol_use_jmobile'=>GETPOST('dol_use_jmobile')
);

// Make the redirect to the google_authcallback.php page to start the redirect to Google OAUTH.
/*
global $dolibarr_main_url_root;
// Define $urlwithroot
$urlwithouturlroot = preg_replace('/'.preg_quote(DOL_URL_ROOT, '/').'$/i', '', trim($dolibarr_main_url_root));
$urlwithroot = $urlwithouturlroot.DOL_URL_ROOT; // This is to use external domain name found into config file
//$urlwithroot=DOL_MAIN_URL_ROOT; // This is to use same domain name than current
//$shortscope = 'userinfo_email,userinfo_profile';
$shortscope = 'openid,email,profile'; // For openid connect
$oauthstateanticsrf = bin2hex(random_bytes(128/8));
$_SESSION['oauthstateanticsrf'] = $shortscope.'-'.$oauthstateanticsrf;
// TODO Can add param hd and login_hit
$urltorenew = $urlwithroot.'/core/modules/oauth/google_oauthcallback.php?shortscope='.$shortscope.'&state=forlogin-'.$shortscope.'-'.$oauthstateanticsrf;
// we go on oauth provider authorization page
header('Location: '.$url);
exit();
*/
} else {
// We reach this code after a call of a redirect to the targeted page from the callback url page of Google OAUTH2
dol_syslog("We reach the code after a call of a redirect to the targeted page from the callback url page of Google OAUTH2");

$tmparray = (empty($_SESSION['datafromloginform']) ? array() : $_SESSION['datafromloginform']);

if (!empty($tmparray)) {
$_POST['entity'] = $tmparray['entity'];
$_POST['backtopage'] = $tmparray['backtopage'];
$_POST['tz'] = $tmparray['tz'];
$_POST['tzstring'] = $tmparray['tzstring'];
$_POST['dst_observed'] = $tmparray['dst_observed'];
$_POST['dst_first'] = $tmparray['dst_first'];
$_POST['dst_second'] = $tmparray['dst_second'];
$_POST['screenwidth'] = $tmparray['screenwidth'];
$_POST['screenwidth'] = $tmparray['screenwidth'];
$_POST['dol_hide_topmenu'] = $tmparray['dol_hide_topmenu'];
$_POST['dol_hide_leftmenu'] = $tmparray['dol_hide_leftmenu'];
$_POST['dol_optimize_smallscreen'] = $tmparray['dol_optimize_smallscreen'];
$_POST['dol_no_mouse_hover'] = $tmparray['dol_no_mouse_hover'];
$_POST['dol_use_jmobile'] = $tmparray['dol_use_jmobile'];
}

// If googleoauth_login has been set (by google_oauthcallback after a successfull OAUTH2 request on openid scope
if (dol_verifyHash($conf->file->instance_unique_id.$usertotest, $_SESSION['googleoauth_receivedlogin'], '0')) {
unset($_SESSION['googleoauth_receivedlogin']);
$login = $usertotest;
}
}
return false;*/
}


return $login;
}
74 changes: 55 additions & 19 deletions htdocs/core/modules/oauth/google_oauthcallback.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

// This page should make the process to login and get token as described here:
// This page is used as callback for token generation of an OAUTH request.
// This page can also be used to make the process to login and get token as described here:
// https://developers.google.com/identity/protocols/oauth2/openid-connect#server-flow

/**
Expand All @@ -26,11 +27,13 @@
*/

// Force keyforprovider
$forlogin = 0;
if (preg_match('/^forlogin-/', $_GET['state'])) {
$forlogin = 1;
$_GET['keyforprovider'] = 'Login';
}

if (!defined('NOLOGIN') && $_GET['keyforprovider'] == 'Login') {
if (!defined('NOLOGIN') && $forlogin) {
define("NOLOGIN", 1); // This means this output page does not require to be logged.
}

Expand Down Expand Up @@ -159,37 +162,41 @@
//var_dump($state);
//var_dump($apiService); // OAuth\OAuth2\Service\Google
//dol_syslog("_GET=".var_export($_GET, true));
//dol_syslog("_POST=".var_export($_POST, true));

$errorincheck = 0;

$db->begin();

// This request the token
// This requests the token from the received OAuth code (call of the https://oauth2.googleapis.com/token endpoint)
// Result is stored into object managed by class DoliStorage into includes/OAuth/Common/Storage/DoliStorage.php, so into table llx_oauth_token
// TODO Store the token with fk_user = $user->id ?
$token = $apiService->requestAccessToken(GETPOST('code'), $state);

// Note: The extraparams has the 'id_token' than contains a lot of information about the user.
$extraparams = $token->getExtraParams();
$jwt = explode('.', $extraparams['id_token']);

$useremail = '';

// Extract the middle part, base64 decode, then json_decode it
if (!empty($jwt[1])) {
$userinfo = json_decode(base64_decode($jwt[1]), true);

//dol_syslog("userinfo=".var_export($userinfo, true));
dol_syslog("userinfo=".var_export($userinfo, true));

$useremail = $userinfo['email'];
/*
$useremailuniq = $userinfo['sub'];
$useremail = $userinfo['email'];
$useremailverified = $userinfo['email_verified'];
$useremailuniq = $userinfo['sub'];
$username = $userinfo['name'];
$userfamilyname = $userinfo['family_name'];
$usergivenname = $userinfo['given_name'];
$hd = $userinfo['hd'];
*/

// We should make the 5 steps of validation of id_token
// We should make the steps of validation of id_token

// Verify that the state is the one expected
// TODO

// Verify that the ID token is properly signed by the issuer. Google-issued tokens are signed using one of the certificates found at the URI specified in the jwks_uri metadata value of the Discovery document.
// TODO
Expand All @@ -201,7 +208,6 @@
}

// Verify that the value of the aud claim in the ID token is equal to your app's client ID.
$keyforparamid = 'OAUTH_GOOGLE-'.$keyforprovider.'_ID';
if ($userinfo['aud'] != getDolGlobalString($keyforparamid)) {
setEventMessages($langs->trans('Bad value for returned userinfo[aud]'), null, 'errors');
$errorincheck++;
Expand All @@ -218,25 +224,50 @@
}

if (!$errorincheck) {
// Delete the token with fk_soc IS NULL
//$storage->clearToken('Google');
// If call back to url for a OAUTH2 login
if ($forlogin) {
dol_syslog("we received the login/email to log to, it is ".$useremail);

// Delete the token
$storage->clearToken('Google');

$tmpuser = new User($db);
$res = $tmpuser->fetch(0, '', '', 0, -1, $useremail);

// TODO Insert a token for user
//$storage->storeAccessToken
if ($res > 0) {
$username = $tmpuser->login;

$_SESSION['googleoauth_receivedlogin'] = dol_hash($conf->file->instance_unique_id.$username, '0');
dol_syslog('$_SESSION[\'googleoauth_receivedlogin\']='.$_SESSION['googleoauth_receivedlogin']);
} else {
$_SESSION["dol_loginmesg"] = "Failed to login using Google. User with this Email not found.";
$errorincheck++;
}
}
} else {
// If call back to url for a OAUTH2 login
if ($forlogin) {
$_SESSION["dol_loginmesg"] = "Failed to login using Google. OAuth callback URL retreives a token with non valid data.";
$errorincheck++;
}
}

if (!$errorincheck) {
$db->commit();
} else {
$db->rollback();
}

//setEventMessages($langs->trans('NewTokenStored'), null, 'mesgs');

$backtourl = $_SESSION["backtourlsavedbeforeoauthjump"];
unset($_SESSION["backtourlsavedbeforeoauthjump"]);

if (empty($backtourl)) {
$backtourl = DOL_URL_ROOT;
}
// If call back to url for a OAUTH2 login
if ($forlogin) {
$backtourl .= '?actionlogin=login&aftergoogleoauthreturn=1&username='.urlencode($username).'&token='.newToken();
}

dol_syslog("Redirect now on backtourl=".$backtourl);

Expand All @@ -253,7 +284,7 @@
$_SESSION["oauthkeyforproviderbeforeoauthjump"] = $keyforprovider;
$_SESSION['oauthstateanticsrf'] = $state;

if (!preg_match('/^forlogin/', $state)) {
if ($forlogin) {
$apiService->setApprouvalPrompt('force');
}

Expand All @@ -267,11 +298,16 @@

// Add more param
$url .= '&nonce='.bin2hex(random_bytes(64/8));
// TODO Add param hd and/or login_hint
if (!preg_match('/^forlogin/', $state)) {

if ($forlogin) {
// TODO Add param hd
//$url .= 'hd=xxx';
if (GETPOST('login_hint')) {
$url .= '&login_hint='.urlencode(GETPOST('login_hint'));
}
}


//var_dump($url);exit;

// we go on oauth provider authorization page
Expand Down
21 changes: 14 additions & 7 deletions htdocs/core/tpl/login.tpl.php
Original file line number Diff line number Diff line change
Expand Up @@ -207,15 +207,18 @@
</div>

<!-- Password -->
<?php if (!isset($conf->file->main_authentication) || $conf->file->main_authentication != 'googleoauth') { ?>
<div class="trinputlogin">
<div class="tagtd nowraponall center valignmiddle tdinputlogin">
<?php if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
?><label for="password" class="hidden"><?php echo $langs->trans("Password"); ?></label><?php
} ?>
<?php if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
?><label for="password" class="hidden"><?php echo $langs->trans("Password"); ?></label><?php
} ?>
<!--<span class="span-icon-password">-->
<span class="fa fa-key"></span>
<input type="password" id="password" maxlength="128" placeholder="<?php echo $langs->trans("Password"); ?>" name="password" class="flat input-icon-password minwidth150" value="<?php echo dol_escape_htmltag($password); ?>" tabindex="2" autocomplete="<?php echo empty($conf->global->MAIN_LOGIN_ENABLE_PASSWORD_AUTOCOMPLETE) ? 'off' : 'on'; ?>" />
</div></div>
<?php } ?>


<?php
if (!empty($captcha)) {
Expand Down Expand Up @@ -270,11 +273,15 @@

<div id="login_line2" style="clear: both">


<!-- Button Connection -->
<?php if (!isset($conf->file->main_authentication) || $conf->file->main_authentication != 'googleoauth') { ?>
<br>
<div id="login-submit-wrapper">
<input type="submit" class="button" value="&nbsp; <?php echo $langs->trans('Connection'); ?> &nbsp;" tabindex="5" />
</div>
<?php } ?>


<?php
if ($forgetpasslink || $helpcenterlink) {
Expand All @@ -294,7 +301,7 @@

echo '<br>';
echo '<div class="center" style="margin-top: 5px;">';
if ($forgetpasslink) {
if ($forgetpasslink && (!isset($conf->file->main_authentication) || $conf->file->main_authentication != 'googleoauth')) {
$url = DOL_URL_ROOT.'/user/passwordforgotten.php'.$moreparam;
if (!empty($conf->global->MAIN_PASSWORD_FORGOTLINK)) {
$url = $conf->global->MAIN_PASSWORD_FORGOTLINK;
Expand Down Expand Up @@ -341,16 +348,16 @@
if (isset($conf->file->main_authentication) && preg_match('/google/', $conf->file->main_authentication)) {
$langs->load("users");

echo '<br>';
echo '<div class="center" style="margin-top: 4px; margin-bottom: 10px">';

global $dolibarr_main_url_root;

// Define $urlwithroot
$urlwithouturlroot = preg_replace('/'.preg_quote(DOL_URL_ROOT, '/').'$/i', '', trim($dolibarr_main_url_root));
$urlwithroot = $urlwithouturlroot.DOL_URL_ROOT; // This is to use external domain name found into config file
//$urlwithroot=DOL_MAIN_URL_ROOT; // This is to use same domain name than current

echo '<br>';
echo '<div class="center" style="margin-top: 4px;">';

//$shortscope = 'userinfo_email,userinfo_profile';
$shortscope = 'openid,email,profile'; // For openid connect

Expand Down
Loading

0 comments on commit bc63d70

Please sign in to comment.