Test to add GMail as AdditionalAccount but it fails due to missing cookies

This commit is contained in:
the-djmaze 2024-06-05 00:13:31 +02:00
parent 3e7eb46f33
commit f73456c462
2 changed files with 200 additions and 98 deletions

View file

@ -1,10 +1,10 @@
(rl => { (rl => {
const client_id = rl.pluginSettingsGet('login-gmail', 'client_id'), const client_id = rl.pluginSettingsGet('login-gmail', 'client_id'),
login = () => { login = mode => {
document.location = 'https://accounts.google.com/o/oauth2/auth?' + (new URLSearchParams({ document.location = 'https://accounts.google.com/o/oauth2/auth?' + (new URLSearchParams({
response_type: 'code', response_type: 'code',
client_id: client_id, client_id: client_id,
redirect_uri: document.location.href + '?LoginGMail', redirect_uri: document.location.href + '?' + mode,
scope: [ scope: [
// Primary Google Account email address // Primary Google Account email address
'https://www.googleapis.com/auth/userinfo.email', 'https://www.googleapis.com/auth/userinfo.email',
@ -36,10 +36,17 @@
container = e.detail.viewModelDom.querySelector('#plugin-Login-BottomControlGroup'), container = e.detail.viewModelDom.querySelector('#plugin-Login-BottomControlGroup'),
btn = Element.fromHTML('<button type="button">GMail</button>'), btn = Element.fromHTML('<button type="button">GMail</button>'),
div = Element.fromHTML('<div class="controls"></div>'); div = Element.fromHTML('<div class="controls"></div>');
btn.onclick = login; btn.onclick = ()=>login('LoginGMail');
div.append(btn); div.append(btn);
container && container.append(div); container && container.append(div);
} }
if ('PopupsAccount' === e.detail.viewModelTemplateID) {
const
container = e.detail.viewModelDom.querySelector('footer'),
btn = Element.fromHTML('<button class="btn" type="button">GMail</button>');
btn.onclick = ()=>login('AddGMail');
container && container.append(btn);
}
}); });
} }

View file

@ -34,6 +34,7 @@ class LoginGMailPlugin extends \RainLoop\Plugins\AbstractPlugin
$this->addHook('sieve.before-login', 'clientLogin'); $this->addHook('sieve.before-login', 'clientLogin');
$this->addPartHook('LoginGMail', 'ServiceLoginGMail'); $this->addPartHook('LoginGMail', 'ServiceLoginGMail');
$this->addPartHook('AddGMail', 'ServiceAddGMail');
// Prevent Disallowed Sec-Fetch Dest: document Mode: navigate Site: cross-site User: true // Prevent Disallowed Sec-Fetch Dest: document Mode: navigate Site: cross-site User: true
$this->addHook('filter.http-paths', 'httpPaths'); $this->addHook('filter.http-paths', 'httpPaths');
@ -47,81 +48,20 @@ class LoginGMailPlugin extends \RainLoop\Plugins\AbstractPlugin
\trim($oConfig->Get('security', 'secfetch_allow', '') . ';site=cross-site', ';') \trim($oConfig->Get('security', 'secfetch_allow', '') . ';site=cross-site', ';')
); );
} }
if (!empty($aPaths[0]) && 'AddGMail' === $aPaths[0]) {
$oConfig = \RainLoop\Api::Config();
$oConfig->Set('security', 'secfetch_allow',
\trim($oConfig->Get('security', 'secfetch_allow', '') . ';site=cross-site', ';')
);
}
} }
public function ServiceLoginGMail() : string public function ServiceLoginGMail() : string
{ {
$oActions = \RainLoop\Api::Actions(); $oActions = \RainLoop\Api::Actions();
$oHttp = $oActions->Http();
$oHttp->ServerNoCache();
try try
{ {
if (isset($_GET['error'])) { $aUserInfo = $this->gmailResponse('LoginGMail');
throw new \RuntimeException($_GET['error']);
}
if (!isset($_GET['code']) || empty($_GET['state']) || 'gmail' !== $_GET['state']) {
$oActions->Location(\RainLoop\Utils::WebPath());
exit;
}
$oGMail = $this->gmailConnector();
if (!$oGMail) {
$oActions->Location(\RainLoop\Utils::WebPath());
exit;
}
$iExpires = \time();
$aResponse = $oGMail->getAccessToken(
static::TOKEN_URI,
'authorization_code',
array(
'code' => $_GET['code'],
'redirect_uri' => $oHttp->GetFullUrl().'?LoginGMail'
)
);
if (200 != $aResponse['code']) {
if (isset($aResponse['result']['error'])) {
throw new \RuntimeException(
$aResponse['code']
. ': '
. $aResponse['result']['error']
. ' / '
. $aResponse['result']['error_description']
);
}
throw new \RuntimeException("HTTP: {$aResponse['code']}");
}
$aResponse = $aResponse['result'];
if (empty($aResponse['access_token'])) {
throw new \RuntimeException('access_token missing');
}
if (empty($aResponse['refresh_token'])) {
throw new \RuntimeException('refresh_token missing');
}
$sAccessToken = $aResponse['access_token'];
$iExpires += $aResponse['expires_in'];
$oGMail->setAccessToken($sAccessToken);
$aUserInfo = $oGMail->fetch('https://www.googleapis.com/oauth2/v2/userinfo');
if (200 != $aUserInfo['code']) {
throw new \RuntimeException("HTTP: {$aResponse['code']}");
}
$aUserInfo = $aUserInfo['result'];
if (empty($aUserInfo['id'])) {
throw new \RuntimeException('unknown id');
}
if (empty($aUserInfo['email'])) {
throw new \RuntimeException('unknown email address');
}
static::$auth = [
'access_token' => $sAccessToken,
'refresh_token' => $aResponse['refresh_token'],
'expires_in' => $aResponse['expires_in'],
'expires' => $iExpires
];
$oPassword = new \SnappyMail\SensitiveString($aUserInfo['id']); $oPassword = new \SnappyMail\SensitiveString($aUserInfo['id']);
$oAccount = $oActions->LoginProcess($aUserInfo['email'], $oPassword); $oAccount = $oActions->LoginProcess($aUserInfo['email'], $oPassword);
// $oAccount = MainAccount::NewInstanceFromCredentials($oActions, $aUserInfo['email'], $aUserInfo['email'], $oPassword, true); // $oAccount = MainAccount::NewInstanceFromCredentials($oActions, $aUserInfo['email'], $aUserInfo['email'], $oPassword, true);
@ -141,6 +81,50 @@ class LoginGMailPlugin extends \RainLoop\Plugins\AbstractPlugin
exit; exit;
} }
/**
* Add as additional account
*/
public function ServiceAddGMail() : string
{
$oActions = \RainLoop\Api::Actions();
try
{
if (empty($_COOKIE)) {
// Failed in Firefox
exit;
}
$oMainAccount = $oActions->getMainAccountFromToken();
$aAccounts = $oActions->GetAccounts($oMainAccount);
$aUserInfo = $this->gmailResponse('AddGMail');
$sEmail = $aUserInfo['email'];
if ($oMainAccount->Email() === $sEmail || isset($aAccounts[$sEmail])) {
throw new ClientException(Notifications::AccountAlreadyExists);
}
$oPassword = new \SnappyMail\SensitiveString('oauth2');
$oAccount = $oActions->LoginProcess($aUserInfo['email'], $oPassword, false);
if ($oAccount) {
$aAccount = $oAccount->asTokenArray($oMainAccount);
$aAccount['name'] = '';
$oActions->LocalStorageProvider()->Put($oAccount, StorageType::CONFIG, 'oauth2',
\SnappyMail\Crypt::EncryptToJSON(static::$auth, $oAccount->CryptKey())
);
$aAccounts[$sEmail] = $aAccount;
$this->SetAccounts($oMainAccount, $aAccounts);
}
}
catch (\Exception $oException)
{
print_r($oException);
exit($oException->getMessage());
$oActions->Logger()->WriteException($oException, \LOG_ERR);
}
exit;
$oActions->Location(\RainLoop\Utils::WebPath());
}
public function configMapping() : array public function configMapping() : array
{ {
return [ return [
@ -158,38 +142,73 @@ class LoginGMailPlugin extends \RainLoop\Plugins\AbstractPlugin
public function clientLogin(\RainLoop\Model\Account $oAccount, \MailSo\Net\NetClient $oClient, \MailSo\Net\ConnectSettings $oSettings) : void public function clientLogin(\RainLoop\Model\Account $oAccount, \MailSo\Net\NetClient $oClient, \MailSo\Net\ConnectSettings $oSettings) : void
{ {
if ($oAccount instanceof MainAccount && \str_ends_with($oAccount->Email(), '@gmail.com')) { if (\str_ends_with($oAccount->Email(), '@gmail.com')) {
$oActions = \RainLoop\Api::Actions(); $oActions = \RainLoop\Api::Actions();
try { if ($oAccount instanceof MainAccount) {
$aData = static::$auth ?: \SnappyMail\Crypt::DecryptFromJSON( try {
$oActions->StorageProvider()->Get($oAccount, StorageType::SESSION, \RainLoop\Utils::GetSessionToken()), $aData = static::$auth ?: \SnappyMail\Crypt::DecryptFromJSON(
$oAccount->CryptKey() $oActions->StorageProvider()->Get($oAccount, StorageType::SESSION, \RainLoop\Utils::GetSessionToken()),
); $oAccount->CryptKey()
} catch (\Throwable $oException) { );
// $oActions->Logger()->WriteException($oException, \LOG_ERR); } catch (\Throwable $oException) {
return; // $oActions->Logger()->WriteException($oException, \LOG_ERR);
} return;
if (!empty($aData['expires']) && !empty($aData['access_token']) && !empty($aData['refresh_token'])) { }
if (\time() >= $aData['expires']) { if (!empty($aData['expires']) && !empty($aData['access_token']) && !empty($aData['refresh_token'])) {
$iExpires = \time(); if (\time() >= $aData['expires']) {
$oGMail = $this->gmailConnector(); $iExpires = \time();
if ($oGMail) { $oGMail = $this->gmailConnector();
$aRefreshTokenResponse = $oGMail->getAccessToken( if ($oGMail) {
static::TOKEN_URI, $aRefreshTokenResponse = $oGMail->getAccessToken(
'refresh_token', static::TOKEN_URI,
array('refresh_token' => $aData['refresh_token']) 'refresh_token',
); array('refresh_token' => $aData['refresh_token'])
if (!empty($aRefreshTokenResponse['result']['access_token'])) {
$aData['access_token'] = $aRefreshTokenResponse['result']['access_token'];
$aResponse['expires'] = $iExpires + $aResponse['expires_in'];
$oActions->StorageProvider()->Put($oAccount, StorageType::SESSION, \RainLoop\Utils::GetSessionToken(),
\SnappyMail\Crypt::EncryptToJSON($aData, $oAccount->CryptKey())
); );
if (!empty($aRefreshTokenResponse['result']['access_token'])) {
$aData['access_token'] = $aRefreshTokenResponse['result']['access_token'];
$aResponse['expires'] = $iExpires + $aResponse['expires_in'];
$oActions->StorageProvider()->Put($oAccount, StorageType::SESSION, \RainLoop\Utils::GetSessionToken(),
\SnappyMail\Crypt::EncryptToJSON($aData, $oAccount->CryptKey())
);
}
} }
} }
$oSettings->passphrase = $aData['access_token'];
\array_unshift($oSettings->SASLMechanisms, 'OAUTHBEARER', 'XOAUTH2');
}
} else if ('oauth2' == $oSettings->passphrase) {
// \RainLoop\Model\AdditionalAccount
try {
$aData = static::$auth ?: \SnappyMail\Crypt::DecryptFromJSON(
$oActions->LocalStorageProvider()->Get($oAccount, StorageType::CONFIG, 'oauth2'),
$oAccount->CryptKey()
);
} catch (\Throwable $oException) {
// $oActions->Logger()->WriteException($oException, \LOG_ERR);
return;
}
if (!empty($aData['expires']) && !empty($aData['access_token']) && !empty($aData['refresh_token'])) {
if (\time() >= $aData['expires']) {
$iExpires = \time();
$oGMail = $this->gmailConnector();
if ($oGMail) {
$aRefreshTokenResponse = $oGMail->getAccessToken(
static::TOKEN_URI,
'refresh_token',
array('refresh_token' => $aData['refresh_token'])
);
if (!empty($aRefreshTokenResponse['result']['access_token'])) {
$aData['access_token'] = $aRefreshTokenResponse['result']['access_token'];
$aResponse['expires'] = $iExpires + $aResponse['expires_in'];
$oActions->LocalStorageProvider()->Put($oAccount, StorageType::CONFIG, 'oauth2',
\SnappyMail\Crypt::EncryptToJSON($aData, $oAccount->CryptKey())
);
}
}
}
$oSettings->passphrase = $aData['access_token'];
\array_unshift($oSettings->SASLMechanisms, 'OAUTHBEARER', 'XOAUTH2');
} }
$oSettings->passphrase = $aData['access_token'];
\array_unshift($oSettings->SASLMechanisms, 'OAUTHBEARER', 'XOAUTH2');
} }
} }
} }
@ -220,4 +239,80 @@ class LoginGMailPlugin extends \RainLoop\Plugins\AbstractPlugin
} }
return null; return null;
} }
protected function gmailResponse(string $query) : array
{
$oActions = \RainLoop\Api::Actions();
$oHttp = $oActions->Http();
$oHttp->ServerNoCache();
if (isset($_GET['error'])) {
throw new \RuntimeException($_GET['error']);
}
if (!isset($_GET['code']) || empty($_GET['state']) || 'gmail' !== $_GET['state']) {
$oActions->Location(\RainLoop\Utils::WebPath());
exit;
}
$oGMail = $this->gmailConnector();
if (!$oGMail) {
$oActions->Location(\RainLoop\Utils::WebPath());
exit;
}
$iExpires = \time();
$aResponse = $oGMail->getAccessToken(
static::TOKEN_URI,
'authorization_code',
array(
'code' => $_GET['code'],
'redirect_uri' => $oHttp->GetFullUrl().'?'.$query
)
);
if (200 != $aResponse['code']) {
if (isset($aResponse['result']['error'])) {
throw new \RuntimeException(
$aResponse['code']
. ': '
. $aResponse['result']['error']
. ' / '
. $aResponse['result']['error_description']
);
}
throw new \RuntimeException("HTTP: {$aResponse['code']}");
}
$aResponse = $aResponse['result'];
if (empty($aResponse['access_token'])) {
throw new \RuntimeException('access_token missing');
}
if (empty($aResponse['refresh_token'])) {
throw new \RuntimeException('refresh_token missing');
}
$sAccessToken = $aResponse['access_token'];
$iExpires += $aResponse['expires_in'];
$oGMail->setAccessToken($sAccessToken);
$aUserInfo = $oGMail->fetch('https://www.googleapis.com/oauth2/v2/userinfo');
if (200 != $aUserInfo['code']) {
throw new \RuntimeException("HTTP: {$aResponse['code']}");
}
$aUserInfo = $aUserInfo['result'];
if (empty($aUserInfo['id'])) {
throw new \RuntimeException('unknown id');
}
if (empty($aUserInfo['email'])) {
throw new \RuntimeException('unknown email address');
}
static::$auth = [
'access_token' => $sAccessToken,
'refresh_token' => $aResponse['refresh_token'],
'expires_in' => $aResponse['expires_in'],
'expires' => $iExpires
];
return $aUserInfo;
}
} }