diff --git a/dev/Promises/AbstractAjax.js b/dev/Promises/AbstractAjax.js index 7942047a2..c659e17c6 100644 --- a/dev/Promises/AbstractAjax.js +++ b/dev/Promises/AbstractAjax.js @@ -69,6 +69,12 @@ class AbstractAjaxPromises extends AbstractBasicPromises { isCached = pInt(data.Time) > microtime() - start; } + if (data && data.UpdateToken) { + if (GlobalsData.__APP__ && GlobalsData.__APP__.setClientSideToken) { + GlobalsData.__APP__.setClientSideToken(data.UpdateToken); + } + } + // backward capability let type = ''; switch (true) { diff --git a/dev/Remote/AbstractAjax.js b/dev/Remote/AbstractAjax.js index 564de61b0..efc6cd880 100644 --- a/dev/Remote/AbstractAjax.js +++ b/dev/Remote/AbstractAjax.js @@ -161,6 +161,12 @@ class AbstractAjaxRemote { cached = pInt(oData.Time) > new window.Date().getTime() - start; } + if (oData && oData.UpdateToken) { + if (GlobalsData.__APP__ && GlobalsData.__APP__.setClientSideToken) { + GlobalsData.__APP__.setClientSideToken(oData.UpdateToken); + } + } + if (action && this.oRequests[action]) { if (this.oRequests[action].__aborted) { sType = 'abort'; diff --git a/dev/Settings/Admin/Social.js b/dev/Settings/Admin/Social.js index 9c526859d..74c7ec760 100644 --- a/dev/Settings/Admin/Social.js +++ b/dev/Settings/Admin/Social.js @@ -12,7 +12,7 @@ class SocialAdminSettings { constructor() { this.googleEnable = SocialStore.google.enabled; this.googleEnableAuth = SocialStore.google.capa.auth; - this.googleEnableAuthFast = SocialStore.google.capa.authFast; + this.googleEnableAuthGmail = SocialStore.google.capa.authGmail; this.googleEnableDrive = SocialStore.google.capa.drive; this.googleEnablePreview = SocialStore.google.capa.preview; @@ -89,6 +89,7 @@ class SocialAdminSettings { this.googleEnable.subscribe(Remote.saveAdminConfigHelper('GoogleEnable', boolToAjax)); this.googleEnableAuth.subscribe(Remote.saveAdminConfigHelper('GoogleEnableAuth', boolToAjax)); + this.googleEnableAuthGmail.subscribe(Remote.saveAdminConfigHelper('GoogleEnableAuthGmail', boolToAjax)); this.googleEnableDrive.subscribe(Remote.saveAdminConfigHelper('GoogleEnableDrive', boolToAjax)); this.googleEnablePreview.subscribe(Remote.saveAdminConfigHelper('GoogleEnablePreview', boolToAjax)); this.googleClientID.subscribe(Remote.saveAdminConfigHelper('GoogleClientID', trim, f5)); diff --git a/dev/Settings/User/Social.js b/dev/Settings/User/Social.js index 465ae8ff1..8439f752c 100644 --- a/dev/Settings/User/Social.js +++ b/dev/Settings/User/Social.js @@ -8,7 +8,7 @@ class SocialUserSettings { constructor() { this.googleEnable = SocialStore.google.enabled; this.googleEnableAuth = SocialStore.google.capa.auth; - this.googleEnableAuthFast = SocialStore.google.capa.authFast; + this.googleEnableAuthGmail = SocialStore.google.capa.authGmail; this.googleEnableDrive = SocialStore.google.capa.drive; this.googleEnablePreview = SocialStore.google.capa.preview; diff --git a/dev/Stores/Social.js b/dev/Stores/Social.js index 93291b2e0..150a767c1 100644 --- a/dev/Stores/Social.js +++ b/dev/Stores/Social.js @@ -24,13 +24,14 @@ class SocialStore { this.google.capa = {}; this.google.capa.auth = ko.observable(false); - this.google.capa.authFast = ko.observable(false); + this.google.capa.authGmail = ko.observable(false); this.google.capa.drive = ko.observable(false); this.google.capa.preview = ko.observable(false); this.google.require = {}; this.google.require.clientSettings = ko.computed( - () => this.google.enabled() && (this.google.capa.auth() || this.google.capa.drive()) + () => + this.google.enabled() && (this.google.capa.auth() || this.google.capa.authGmail() || this.google.capa.drive()) ); this.google.require.apiKeySettings = ko.computed(() => this.google.enabled() && this.google.capa.drive()); @@ -66,7 +67,7 @@ class SocialStore { this.google.apiKey(Settings.settingsGet('GoogleApiKey')); this.google.capa.auth(!!Settings.settingsGet('AllowGoogleSocialAuth')); - this.google.capa.authFast(!!Settings.settingsGet('AllowGoogleSocialAuthFast')); + this.google.capa.authGmail(!!Settings.settingsGet('AllowGoogleSocialAuthGmail')); this.google.capa.drive(!!Settings.settingsGet('AllowGoogleSocialDrive')); this.google.capa.preview(!!Settings.settingsGet('AllowGoogleSocialPreview')); diff --git a/dev/View/User/Login.js b/dev/View/User/Login.js index 2b92da9a5..d9879466a 100644 --- a/dev/View/User/Login.js +++ b/dev/View/User/Login.js @@ -143,7 +143,7 @@ class LoginUserView extends AbstractViewNext { this.facebookLoginEnabled = ko.observable(false); this.googleLoginEnabled = ko.observable(false); - this.googleFastLoginEnabled = ko.observable(false); + this.googleGmailLoginEnabled = ko.observable(false); this.twitterLoginEnabled = ko.observable(false); this.socialLoginEnabled = ko.computed(() => { @@ -175,8 +175,8 @@ class LoginUserView extends AbstractViewNext { return true; } - @command((self) => !self.submitRequest() && self.googleFastLoginEnabled()) - googleFastCommand() { + @command((self) => !self.submitRequest() && self.googleGmailLoginEnabled()) + googleGmailCommand() { window.open(socialGoogle(true), 'Google', this.windowOpenFeatures(550)); return true; } @@ -344,8 +344,8 @@ class LoginUserView extends AbstractViewNext { this.googleLoginEnabled( !!Settings.settingsGet('AllowGoogleSocial') && !!Settings.settingsGet('AllowGoogleSocialAuth') ); - this.googleFastLoginEnabled( - !!Settings.settingsGet('AllowGoogleSocial') && !!Settings.settingsGet('AllowGoogleSocialAuthFast') + this.googleGmailLoginEnabled( + !!Settings.settingsGet('AllowGoogleSocial') && !!Settings.settingsGet('AllowGoogleSocialAuthGmail') ); switch (signMe) { @@ -375,7 +375,7 @@ class LoginUserView extends AbstractViewNext { this.email(AppStore.devEmail); this.password(AppStore.devPassword); - if (this.googleLoginEnabled() || this.googleFastLoginEnabled()) { + if (this.googleLoginEnabled() || this.googleGmailLoginEnabled()) { window['rl_' + jsHash + '_google_login_service'] = fSocial; } diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php index 192ca2322..4af4cdb87 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php @@ -126,6 +126,11 @@ class Actions */ private $sSpecAuthToken; + /** + * @var string + */ + private $sUpdateAuthToken; + /** * @access private */ @@ -155,6 +160,8 @@ class Actions $this->oPremProvider = null; $this->sSpecAuthToken = ''; + $this->sUpdateAuthToken = ''; + $this->bIsAjax = false; $oConfig = $this->Config(); $this->Plugins()->RunHook('filter.application-config', array(&$oConfig)); @@ -182,6 +189,30 @@ class Actions return $this; } + /** + * @param string $sUpdateAuthToken + * + * @return \RainLoop\Application + */ + public function SetUpdateAuthToken($sUpdateAuthToken) + { + $this->sUpdateAuthToken = $sUpdateAuthToken; + + return $this; + } + + /** + * @param string $bIsAjax + * + * @return \RainLoop\Application + */ + public function SetIsAjax($bIsAjax) + { + $this->bIsAjax = $bIsAjax; + + return $this; + } + /** * @return string */ @@ -190,6 +221,22 @@ class Actions return $this->sSpecAuthToken; } + /** + * @return string + */ + public function GetUpdateAuthToken() + { + return $this->sUpdateAuthToken; + } + + /** + * @return boolean + */ + public function GetIsAjax() + { + return $this->bIsAjax; + } + /** * @return string */ @@ -1647,7 +1694,7 @@ NewThemeLink IncludeCss LoadingDescriptionEsc TemplatesLink LangLink IncludeBack $aResult['AllowGoogleSocial'] = (bool) $oConfig->Get('social', 'google_enable', false); $aResult['AllowGoogleSocialAuth'] = (bool) $oConfig->Get('social', 'google_enable_auth', true); - $aResult['AllowGoogleSocialAuthFast'] = (bool) $oConfig->Get('social', 'google_enable_auth_fast', true); + $aResult['AllowGoogleSocialAuthGmail'] = (bool) $oConfig->Get('social', 'google_enable_auth_gmail', true); $aResult['AllowGoogleSocialDrive'] = (bool) $oConfig->Get('social', 'google_enable_drive', true); $aResult['AllowGoogleSocialPreview'] = (bool) $oConfig->Get('social', 'google_enable_preview', true); @@ -1658,7 +1705,7 @@ NewThemeLink IncludeCss LoadingDescriptionEsc TemplatesLink LangLink IncludeBack '' === \trim($oConfig->Get('social', 'google_client_id', '')) || '' === \trim($oConfig->Get('social', 'google_client_secret', ''))))) { $aResult['AllowGoogleSocialAuth'] = false; - $aResult['AllowGoogleSocialAuthFast'] = false; + $aResult['AllowGoogleSocialAuthGmail'] = false; $aResult['AllowGoogleSocialDrive'] = false; $aResult['GoogleClientID'] = ''; $aResult['GoogleApiKey'] = ''; @@ -1670,7 +1717,7 @@ NewThemeLink IncludeCss LoadingDescriptionEsc TemplatesLink LangLink IncludeBack } if ($aResult['AllowGoogleSocial'] && - !$aResult['AllowGoogleSocialAuth'] && !$aResult['AllowGoogleSocialAuthFast'] && + !$aResult['AllowGoogleSocialAuth'] && !$aResult['AllowGoogleSocialAuthGmail'] && !$aResult['AllowGoogleSocialDrive'] && !$aResult['AllowGoogleSocialPreview']) { $aResult['AllowGoogleSocial'] = false; @@ -1746,7 +1793,7 @@ NewThemeLink IncludeCss LoadingDescriptionEsc TemplatesLink LangLink IncludeBack $aResult['AllowGoogleSocial'] = (bool) $oConfig->Get('social', 'google_enable', false); $aResult['AllowGoogleSocialAuth'] = (bool) $oConfig->Get('social', 'google_enable_auth', true); - $aResult['AllowGoogleSocialAuthFast'] = (bool) $oConfig->Get('social', 'google_enable_auth_fast', true); + $aResult['AllowGoogleSocialAuthGmail'] = (bool) $oConfig->Get('social', 'google_enable_auth_gmail', true); $aResult['AllowGoogleSocialDrive'] = (bool) $oConfig->Get('social', 'google_enable_drive', true); $aResult['AllowGoogleSocialPreview'] = (bool) $oConfig->Get('social', 'google_enable_preview', true); @@ -1986,6 +2033,51 @@ NewThemeLink IncludeCss LoadingDescriptionEsc TemplatesLink LangLink IncludeBack return $aResult; } + public function googleStoreTokens($oCache, $sAccessToken, $sRefreshToken) + { + $sCacheKey = 'tokens='.\md5($sRefreshToken); + $oCache->Set($sCacheKey, $sAccessToken); + $oCache->SetTimer($sCacheKey); + } + + /** + * @return string + */ + public function googleRefreshTokenCallback($sAccessToken, $sRefreshToken) + { + $oAccount = $this->getAccountFromToken(false); + if ($oAccount && $this->GetIsAjax()) + { + $oCache = $this->Cacher($oAccount); + $sCacheKey = 'tokens='.\md5($sRefreshToken); + + $sCachedAccessToken = $oCache->Get($sCacheKey); + $iTime = $oCache->GetTimer($sCacheKey); + + if ($sCachedAccessToken === '' || $iTime === 0) + { + $this->googleStoreTokens($oCache, $sAccessToken, $sRefreshToken); + } + else if (\time() - 60 * 10 * 2 > $iTime) // 20min + { + $sCachedAccessToken = $this->Social()->GoogleRefreshToken($sAccessToken, $sRefreshToken); + if ($sCachedAccessToken !== $sAccessToken) { + $this->googleStoreTokens($oCache, $sCachedAccessToken, $sRefreshToken); + + $oAccount->SetPassword(\RainLoop\Model\Account::GenerateTokensPassword($sCachedAccessToken, $sRefreshToken)); + $this->AuthToken($oAccount); + $this->SetUpdateAuthToken($this->GetSpecAuthToken()); + } + } + + if ($sCachedAccessToken) { + return $sCachedAccessToken; + } + } + + return $sAccessToken; + } + /** * @return string */ @@ -2059,7 +2151,7 @@ NewThemeLink IncludeCss LoadingDescriptionEsc TemplatesLink LangLink IncludeBack { try { - $oAccount->IncConnectAndLoginHelper($this->Plugins(), $this->MailClient(), $this->Config()); + $oAccount->IncConnectAndLoginHelper($this->Plugins(), $this->MailClient(), $this->Config(), array($this, 'googleRefreshTokenCallback')); } catch (\RainLoop\Exceptions\ClientException $oException) { @@ -3366,7 +3458,7 @@ NewThemeLink IncludeCss LoadingDescriptionEsc TemplatesLink LangLink IncludeBack * * @throws \MailSo\Base\Exceptions\Exception */ - public function getAccountUnredCountFromHash($sHash) + public function getAccountUnreadCountFromHash($sHash) { $iResult = 0; @@ -3378,7 +3470,7 @@ NewThemeLink IncludeCss LoadingDescriptionEsc TemplatesLink LangLink IncludeBack $oMailClient = \MailSo\Mail\MailClient::NewInstance(); $oMailClient->SetLogger($this->Logger()); - $oAccount->IncConnectAndLoginHelper($this->Plugins(),$oMailClient, $this->Config()); + $oAccount->IncConnectAndLoginHelper($this->Plugins(), $oMailClient, $this->Config(), array($this, 'googleRefreshTokenCallback')); $iResult = $oMailClient->InboxUnreadCount(); @@ -3425,7 +3517,7 @@ NewThemeLink IncludeCss LoadingDescriptionEsc TemplatesLink LangLink IncludeBack foreach ($mAccounts as $sEmail => $sHash) { $aCounts[] = array(\MailSo\Base\Utils::IdnToUtf8($sEmail), - $oAccount->Email() === $sEmail ? 0 : $this->getAccountUnredCountFromHash($sHash)); + $oAccount->Email() === $sEmail ? 0 : $this->getAccountUnreadCountFromHash($sHash)); } } } @@ -3775,7 +3867,7 @@ NewThemeLink IncludeCss LoadingDescriptionEsc TemplatesLink LangLink IncludeBack $this->setConfigFromParams($oConfig, 'GoogleEnable', 'social', 'google_enable', 'bool'); $this->setConfigFromParams($oConfig, 'GoogleEnableAuth', 'social', 'google_enable_auth', 'bool'); - $this->setConfigFromParams($oConfig, 'GoogleEnableAuthFast', 'social', 'google_enable_auth_fast', 'bool'); + $this->setConfigFromParams($oConfig, 'GoogleEnableAuthGmail', 'social', 'google_enable_auth_gmail', 'bool'); $this->setConfigFromParams($oConfig, 'GoogleEnableDrive', 'social', 'google_enable_drive', 'bool'); $this->setConfigFromParams($oConfig, 'GoogleEnablePreview', 'social', 'google_enable_preview', 'bool'); $this->setConfigFromParams($oConfig, 'GoogleClientID', 'social', 'google_client_id', 'string'); @@ -4293,7 +4385,7 @@ NewThemeLink IncludeCss LoadingDescriptionEsc TemplatesLink LangLink IncludeBack $iRepTime = $this->Cacher()->GetTimer($sCacheKey); } - if ('' === $sRep || 0 === $iRepTime || time() - 3600 > $iRepTime) + if ('' === $sRep || 0 === $iRepTime || \time() - 3600 > $iRepTime) { $iCode = 0; $sContentType = ''; @@ -4393,7 +4485,7 @@ NewThemeLink IncludeCss LoadingDescriptionEsc TemplatesLink LangLink IncludeBack $iRepTime = $this->Cacher()->GetTimer($sCacheKey); } - if ('' === $sRep || 0 === $iRepTime || time() - 3600 > $iRepTime) + if ('' === $sRep || 0 === $iRepTime || \time() - 3600 > $iRepTime) { $iCode = 0; $sContentType = ''; @@ -6172,7 +6264,9 @@ NewThemeLink IncludeCss LoadingDescriptionEsc TemplatesLink LangLink IncludeBack $oSmtpClient = \MailSo\Smtp\SmtpClient::NewInstance()->SetLogger($this->Logger()); $oSmtpClient->SetTimeOuts(10, (int) \RainLoop\Api::Config()->Get('labs', 'smtp_timeout', 60)); - $bLoggined = $oAccount->OutConnectAndLoginHelper($this->Plugins(), $oSmtpClient, $this->Config(), $bUsePhpMail); + $bLoggined = $oAccount->OutConnectAndLoginHelper( + $this->Plugins(), $oSmtpClient, $this->Config(), array($this, 'googleRefreshTokenCallback'), $bUsePhpMail + ); if ($bUsePhpMail) { @@ -8315,7 +8409,7 @@ NewThemeLink IncludeCss LoadingDescriptionEsc TemplatesLink LangLink IncludeBack $iExpires = $this->Config()->Get('cache', 'http_expires', 3600); if (0 < $iExpires) { - $this->oHttp->ServerUseCache($this->etag($sKey), 1382478804, time() + $iExpires); + $this->oHttp->ServerUseCache($this->etag($sKey), 1382478804, \time() + $iExpires); $bResult = true; } } @@ -8964,7 +9058,7 @@ NewThemeLink IncludeCss LoadingDescriptionEsc TemplatesLink LangLink IncludeBack try { - $oAccount->IncConnectAndLoginHelper($this->Plugins(), $this->MailClient(), $this->Config()); + $oAccount->IncConnectAndLoginHelper($this->Plugins(), $this->MailClient(), $this->Config(), array($this, 'googleRefreshTokenCallback')); } catch (\MailSo\Net\Exceptions\ConnectionException $oException) { diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Config/Application.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Config/Application.php index c4316703b..d190475e6 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Config/Application.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Config/Application.php @@ -341,7 +341,7 @@ Examples: 'social' => array( 'google_enable' => array(false, 'Google'), 'google_enable_auth' => array(false), - 'google_enable_auth_fast' => array(false), + 'google_enable_auth_gmail' => array(false), 'google_enable_drive' => array(false), 'google_enable_preview' => array(false), 'google_client_id' => array(''), diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Model/Account.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Model/Account.php index c8575d781..b0e446c4a 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Model/Account.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Model/Account.php @@ -91,6 +91,35 @@ class Account extends \RainLoop\Account // for backward compatibility return new self($sEmail, $sLogin, $sPassword, $oDomain, $sSignMeToken, $sProxyAuthUser, $sProxyAuthPassword, $sClientCert); } + /** + * @return string + */ + public static function GenerateTokensPassword($sAccessToken, $sRefreshToken) + { + return APP_GOOGLE_ACCESS_TOKEN_PREFIX.\json_encode(array($sAccessToken, $sRefreshToken)); + } + + /** + * @return array + */ + public static function ParseTokensPassword($sPassword) + { + $iGatLen = \strlen(APP_GOOGLE_ACCESS_TOKEN_PREFIX); + if ($sPassword && APP_GOOGLE_ACCESS_TOKEN_PREFIX === \substr($sPassword, 0, $iGatLen)) + { + $aTokens = @\json_decode(\substr($sPassword, $iGatLen)); + $sAccessToken = !empty($aTokens[0]) ? $aTokens[0] : ''; + $sRefreshToken = !empty($aTokens[1]) ? $aTokens[1] : ''; + + if ($sAccessToken && $sRefreshToken) + { + return array($sAccessToken, $sRefreshToken); + } + } + + return array('', ''); + } + /** * @return string */ @@ -366,18 +395,18 @@ class Account extends \RainLoop\Account // for backward compatibility public function GetAuthToken() { return \RainLoop\Utils::EncodeKeyValues(array( - 'token', // 0 - $this->sEmail, // 1 - $this->sLogin, // 2 - $this->sPassword, // 3 + 'token', // 0 + $this->sEmail, // 1 + $this->sLogin, // 2 + $this->sPassword, // 3 \RainLoop\Utils::Fingerprint(), // 4 - $this->sSignMeToken, // 5 - $this->sParentEmail, // 6 + $this->sSignMeToken, // 5 + $this->sParentEmail, // 6 \RainLoop\Utils::GetShortToken(), // 7 - $this->sProxyAuthUser, // 8 - $this->sProxyAuthPassword, // 9 - 0, // 10 // timelife - $this->sClientCert // 11 + $this->sProxyAuthUser, // 8 + $this->sProxyAuthPassword, // 9 + 0, // 10 // lifetime + $this->sClientCert // 11 )); } @@ -387,18 +416,18 @@ class Account extends \RainLoop\Account // for backward compatibility public function GetAuthTokenQ() { return \RainLoop\Utils::EncodeKeyValuesQ(array( - 'token', // 0 - $this->sEmail, // 1 - $this->sLogin, // 2 - $this->sPassword, // 3 + 'token', // 0 + $this->sEmail, // 1 + $this->sLogin, // 2 + $this->sPassword, // 3 \RainLoop\Utils::Fingerprint(), // 4 - $this->sSignMeToken, // 5 - $this->sParentEmail, // 6 + $this->sSignMeToken, // 5 + $this->sParentEmail, // 6 \RainLoop\Utils::GetShortToken(), // 7 - $this->sProxyAuthUser, // 8 - $this->sProxyAuthPassword, // 9 - 0, // 10 // timelife - $this->sClientCert // 11 + $this->sProxyAuthUser, // 8 + $this->sProxyAuthPassword, // 9 + 0, // 10 // lifetime + $this->sClientCert // 11 )); } @@ -406,10 +435,11 @@ class Account extends \RainLoop\Account // for backward compatibility * @param \RainLoop\Plugins\Manager $oPlugins * @param \MailSo\Mail\MailClient $oMailClient * @param \RainLoop\Application $oConfig + * @param callback|null $refreshTokenCallback = null * * @return bool */ - public function IncConnectAndLoginHelper($oPlugins, $oMailClient, $oConfig) + public function IncConnectAndLoginHelper($oPlugins, $oMailClient, $oConfig, $refreshTokenCallback = null) { $bLogin = false; @@ -456,12 +486,21 @@ class Account extends \RainLoop\Account // for backward compatibility } else { - $iGatLen = \strlen(APP_GOOGLE_ACCESS_TOKEN_PREFIX); $sPassword = $aImapCredentials['Password']; - if (APP_GOOGLE_ACCESS_TOKEN_PREFIX === \substr($sPassword, 0, $iGatLen)) + $aTokens = self::ParseTokensPassword($sPassword); + + $sAccessToken = $aTokens[0]; + $sRefreshToken = $aTokens[1]; + + if ($sAccessToken && $sRefreshToken) { + if ($refreshTokenCallback && \is_callable($refreshTokenCallback)) + { + $sAccessToken = \call_user_func($refreshTokenCallback, $sAccessToken, $sRefreshToken); + } + $oMailClient->LoginWithXOauth2( - \base64_encode('user='.$aImapCredentials['Login']."\1".'auth=Bearer '.\substr($sPassword, $iGatLen)."\1\1")); + \base64_encode('user='.$aImapCredentials['Login']."\1".'auth=Bearer '.$sAccessToken."\1\1")); } else { @@ -482,11 +521,12 @@ class Account extends \RainLoop\Account // for backward compatibility * @param \RainLoop\Plugins\Manager $oPlugins * @param \MailSo\Smtp\SmtpClient|null $oSmtpClient * @param \RainLoop\Application $oConfig + * @param callback|null $refreshTokenCallback = null * @param bool $bUsePhpMail = false * * @return bool */ - public function OutConnectAndLoginHelper($oPlugins, $oSmtpClient, $oConfig, &$bUsePhpMail = false) + public function OutConnectAndLoginHelper($oPlugins, $oSmtpClient, $oConfig, $refreshTokenCallback = null, &$bUsePhpMail = false) { $bLogin = false; @@ -526,12 +566,21 @@ class Account extends \RainLoop\Account // for backward compatibility if ($aSmtpCredentials['UseAuth'] && !$aSmtpCredentials['UsePhpMail'] && $oSmtpClient) { - $iGatLen = \strlen(APP_GOOGLE_ACCESS_TOKEN_PREFIX); $sPassword = $aSmtpCredentials['Password']; - if (APP_GOOGLE_ACCESS_TOKEN_PREFIX === \substr($sPassword, 0, $iGatLen)) + $aTokens = self::ParseTokensPassword($sPassword); + + $sAccessToken = $aTokens[0]; + $sRefreshToken = $aTokens[1]; + + if ($sAccessToken && $sRefreshToken) { + if ($refreshTokenCallback && \is_callable($refreshTokenCallback)) + { + $sAccessToken = \call_user_func($refreshTokenCallback, $sAccessToken, $sRefreshToken); + } + $oSmtpClient->LoginWithXOauth2( - \base64_encode('user='.$aSmtpCredentials['Login']."\1".'auth=Bearer '.\substr($sPassword, $iGatLen)."\1\1")); + \base64_encode('user='.$aSmtpCredentials['Login']."\1".'auth=Bearer '.$sAccessToken."\1\1")); } else { diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/ServiceActions.php b/rainloop/v/0.0.0/app/libraries/RainLoop/ServiceActions.php index e8a6456f1..873934e81 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/ServiceActions.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/ServiceActions.php @@ -124,6 +124,8 @@ class ServiceActions $sAction = $this->aPaths[2]; } + $this->oActions->SetIsAjax(true); + try { if ($this->oHttp->IsPost() && @@ -211,6 +213,12 @@ class ServiceActions if (\is_array($aResponseItem)) { $aResponseItem['Time'] = (int) ((\microtime(true) - APP_START) * 1000); + + $sUpdateToken = $this->oActions->GetUpdateAuthToken(); + if ($sUpdateToken) + { + $aResponseItem['UpdateToken'] = $sUpdateToken; + } } $this->Plugins()->RunHook('filter.ajax-response', array($sAction, &$aResponseItem)); @@ -498,7 +506,7 @@ class ServiceActions if (\method_exists($this->oActions, $sMethodName)) { @\header('X-Raw-Action: '.$sMethodName, true); - @\header('Content-Security-Policy: default-src \'self\'; script-src \'none\'; style-src \'none\'; frame-src \'none\'; child-src \'none\'', true); + @\header('Content-Security-Policy: script-src \'none\'; frame-src \'none\'; child-src \'none\'', true); $sRawError = ''; $this->oActions->SetActionParams(array( diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Social.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Social.php index a1363756b..42e5a9148 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Social.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Social.php @@ -168,6 +168,24 @@ class Social return $sResult; } + /** + * @return string + */ + public function GoogleRefreshToken($sAccessToken, $sRefreshToken) + { + $oGoogle = $this->GoogleConnector(); + if ($oGoogle) + { + $aRefreshTokenResponse = $oGoogle->getAccessToken('https://accounts.google.com/o/oauth2/token', 'refresh_token', array( + 'refresh_token' => $sRefreshToken + )); + + return !empty($aRefreshTokenResponse['result']['access_token']) ? $aRefreshTokenResponse['result']['access_token'] : ''; + } + + return $sAccessToken; + } + /** * @return string */ @@ -221,11 +239,11 @@ class Social 'response_type' => 'code' ); -// if ($bGmail) -// { -// $aParams['access_type'] = 'offline'; -// $aParams['approval_prompt'] = 'force'; -// } + if ($bGmail) + { + $aParams['access_type'] = 'offline'; + // $aParams['prompt'] = 'consent'; + } $sLoginUrl = $oGoogle->getAuthenticationUrl('https://accounts.google.com/o/oauth2/auth', $sRedirectUrl, $aParams); } @@ -238,12 +256,17 @@ class Social $bLogin = !$oAccount; } - $aParams = array('code' => $this->oHttp->GetQuery('code'), 'redirect_uri' => $sRedirectUrl); - $aAuthorizationResponse = $oGoogle->getAccessToken('https://accounts.google.com/o/oauth2/token', 'authorization_code', $aParams); + $aAuthorizationCodeResponse = $oGoogle->getAccessToken('https://accounts.google.com/o/oauth2/token', 'authorization_code', array( + 'code' => $this->oHttp->GetQuery('code'), + 'redirect_uri' => $sRedirectUrl + )); - if (!empty($aAuthorizationResponse['result']['access_token'])) + $sAccessToken = !empty($aAuthorizationCodeResponse['result']['access_token']) ? $aAuthorizationCodeResponse['result']['access_token'] : ''; + $sRefreshToken = !empty($aAuthorizationCodeResponse['result']['refresh_token']) ? $aAuthorizationCodeResponse['result']['refresh_token'] : ''; + + if (!empty($sAccessToken)) { - $oGoogle->setAccessToken($aAuthorizationResponse['result']['access_token']); + $oGoogle->setAccessToken($sAccessToken); $aUserInfoResponse = $oGoogle->fetch('https://www.googleapis.com/oauth2/v2/userinfo'); if (!empty($aUserInfoResponse['result']['id'])) @@ -257,7 +280,7 @@ class Social { $aUserData = array( 'Email' => $aUserInfoResponse['result']['email'], - 'Password' => APP_GOOGLE_ACCESS_TOKEN_PREFIX.$aAuthorizationResponse['result']['access_token'] + 'Password' => \RainLoop\Model\Account::GenerateTokensPassword($sAccessToken, $sRefreshToken) ); } } diff --git a/rainloop/v/0.0.0/app/localization/admin/_source.en.yml b/rainloop/v/0.0.0/app/localization/admin/_source.en.yml index ed6cbed26..7419b5d31 100644 --- a/rainloop/v/0.0.0/app/localization/admin/_source.en.yml +++ b/rainloop/v/0.0.0/app/localization/admin/_source.en.yml @@ -117,6 +117,7 @@ en: LEGEND_GOOGLE: "Google" LABEL_ENABLE_GOOGLE: "Enable Google Integration" LABEL_GOOGLE_AUTH: "Authorization" + LABEL_GOOGLE_AUTH_GMAIL: "Authorization (Gmail)" LABEL_GOOGLE_DRIVE: "Google Drive Integration (Compose view)" LABEL_GOOGLE_PREVIEW: "Google Viewer Integration (Preview for Microsoft Word, Excel and PowerPoint files)" LABEL_GOOGLE_CLIENT_ID: "Client ID" diff --git a/rainloop/v/0.0.0/app/localization/webmail/_source.en.yml b/rainloop/v/0.0.0/app/localization/webmail/_source.en.yml index f4f17c609..8f3f9a6bc 100644 --- a/rainloop/v/0.0.0/app/localization/webmail/_source.en.yml +++ b/rainloop/v/0.0.0/app/localization/webmail/_source.en.yml @@ -8,6 +8,7 @@ en: LABEL_DONT_ASK_VERIFICATION_CODE: "Don't ask for the code for 2 weeks" BUTTON_SIGN_IN: "Sign In" TITLE_SIGN_IN_GOOGLE: "Sign In using Google" + TITLE_SIGN_IN_GMAIL: "Sign In using Gmail" TITLE_SIGN_IN_FACEBOOK: "Sign In using Facebook" TITLE_SIGN_IN_TWITTER: "Sign In using Twitter" LABEL_FORGOT_PASSWORD: "Forgot password" @@ -155,11 +156,11 @@ en: LINK_SAVE_TO_DROPBOX: "Save to Dropbox" READ_RECEIPT: SUBJECT: "Return Receipt (displayed) - %SUBJECT%" - BODY: | - This is a Return Receipt for the mail that you sent to %READ-RECEIPT%. - - Note: "This Return Receipt only acknowledges that the message was displayed on the recipient's computer." - There is no guarantee that the recipient has read or understood the message contents. + BODY: | + This is a Return Receipt for the mail that you sent to %READ-RECEIPT%. + + Note: "This Return Receipt only acknowledges that the message was displayed on the recipient's computer." + There is no guarantee that the recipient has read or understood the message contents. SUGGESTIONS: SEARCHING_DESC: "Searching..." CONTACTS: @@ -365,16 +366,16 @@ en: LABEL_ARCHIVE: "Archive" BUTTON_CANCEL: "Cancel" BUTTON_CLOSE: "Close" - NOTIFICATION_SENT: | - You haven't selected "Sent" system folder messages are put to after sending. - If you don't want to save sent message, please select "Do not use" option. + NOTIFICATION_SENT: | + You haven't selected "Sent" system folder messages are put to after sending. + If you don't want to save sent message, please select "Do not use" option. NOTIFICATION_DRAFTS: "You haven't selected \"Drafts\" system folder messages are saved to while composing." - NOTIFICATION_SPAM: | - You haven't selected "Spam" system folder spamed messages are placed to. - If you wish to remove messages permanently, please select "Do not use" option. - NOTIFICATION_TRASH: | - You haven't selected "Trash" system folder deleted messages are placed to. - If you wish to remove messages permanently, please select "Do not use" option. + NOTIFICATION_SPAM: | + You haven't selected "Spam" system folder spamed messages are placed to. + If you wish to remove messages permanently, please select "Do not use" option. + NOTIFICATION_TRASH: | + You haven't selected "Trash" system folder deleted messages are placed to. + If you wish to remove messages permanently, please select "Do not use" option. NOTIFICATION_ARCHIVE: "You haven't selected \"Archive\" system folder achived messages are placed to." POPUPS_TWO_FACTOR_CFG: LEGEND_TWO_FACTOR_AUTH: "2-Step Verification (TOTP)" @@ -395,13 +396,13 @@ en: TWO_FACTOR_REQUIRE_DESC: "Your account requires 2-Step verification configuration." TWO_FACTOR_SECRET_CONFIGURED_DESC: "Configured" TWO_FACTOR_SECRET_NOT_CONFIGURED_DESC: "Not configured" - TWO_FACTOR_SECRET_DESC: > - Import this info into your Google Authenticator client (or other TOTP client) - using the provided QR code below or by entering the code manually. - TWO_FACTOR_BACKUP_CODES_DESC: > - If you can't receive codes via Google Authenticator (or other TOTP client), - you can use backup codes to sign in. After you’ve used a backup code to sign - in, it will become inactive. + TWO_FACTOR_SECRET_DESC: > + Import this info into your Google Authenticator client (or other TOTP client) + using the provided QR code below or by entering the code manually. + TWO_FACTOR_BACKUP_CODES_DESC: > + If you can't receive codes via Google Authenticator (or other TOTP client), + you can use backup codes to sign in. After you’ve used a backup code to sign + in, it will become inactive. TWO_FACTOR_SECRET_TEST_BEFORE_DESC: "You can't change this setting before test." TITLES: LOADING: "Loading" @@ -642,15 +643,15 @@ en: ACCESS_ERROR: "Access error" CONNECTION_ERROR: "Can't connect to server" CAPTCHA_ERROR: "Incorrect CAPTCHA." - SOCIAL_FACEBOOK_LOGIN_ACCESS_DISABLE: > - This social ID is not assigned for any email account yet. Log in using email - credentials and enable this feature in account settings. - SOCIAL_TWITTER_LOGIN_ACCESS_DISABLE: > - This social ID is not assigned for any email account yet. Log in using email - credentials and enable this feature in account settings. - SOCIAL_GOOGLE_LOGIN_ACCESS_DISABLE: > - This social ID is not assigned for any email account yet. Log in using email - credentials and enable this feature in account settings. + SOCIAL_FACEBOOK_LOGIN_ACCESS_DISABLE: > + This social ID is not assigned for any email account yet. Log in using email + credentials and enable this feature in account settings. + SOCIAL_TWITTER_LOGIN_ACCESS_DISABLE: > + This social ID is not assigned for any email account yet. Log in using email + credentials and enable this feature in account settings. + SOCIAL_GOOGLE_LOGIN_ACCESS_DISABLE: > + This social ID is not assigned for any email account yet. Log in using email + credentials and enable this feature in account settings. DOMAIN_NOT_ALLOWED: "Domain is not allowed" ACCOUNT_NOT_ALLOWED: "Account is not allowed" ACCOUNT_TWO_FACTOR_AUTH_REQUIRED: "Two factor verification required" @@ -700,14 +701,14 @@ en: PHP_EXSTENSIONS_ERROR_DESC: "Required PHP extension are not available in your PHP configuration!" PHP_VERSION_ERROR_DESC: "Your PHP version (%VERSION%) is lower than the minimal required 5.3.0!" NO_SCRIPT_TITLE: "JavaScript is required for this application." - NO_SCRIPT_DESC: | - JavaScript support is not available in your browser. - Please enable JavaScript support in your browser settings and retry. + NO_SCRIPT_DESC: | + JavaScript support is not available in your browser. + Please enable JavaScript support in your browser settings and retry. NO_COOKIE_TITLE: "Cookies support is required for this application." - NO_COOKIE_DESC: | - Cookies support is not available in your browser. - Please enable Cookie support in your browser settings and retry. + NO_COOKIE_DESC: | + Cookies support is not available in your browser. + Please enable Cookie support in your browser settings and retry. BAD_BROWSER_TITLE: "Your browser is outdated." - BAD_BROWSER_DESC: | - To use all the features of the application, - download and install one of these browsers: + BAD_BROWSER_DESC: | + To use all the features of the application, + download and install one of these browsers: diff --git a/rainloop/v/0.0.0/app/templates/Views/Admin/AdminSettingsSocial.html b/rainloop/v/0.0.0/app/templates/Views/Admin/AdminSettingsSocial.html index 96bcb6532..7fece3d3a 100644 --- a/rainloop/v/0.0.0/app/templates/Views/Admin/AdminSettingsSocial.html +++ b/rainloop/v/0.0.0/app/templates/Views/Admin/AdminSettingsSocial.html @@ -24,6 +24,13 @@ label: 'TAB_INTEGRATIONS/LABEL_GOOGLE_AUTH' } }"> +
- () + ()
diff --git a/rainloop/v/0.0.0/app/templates/Views/User/Login.html b/rainloop/v/0.0.0/app/templates/Views/User/Login.html index 7aa4f8693..daaeb1e89 100644 --- a/rainloop/v/0.0.0/app/templates/Views/User/Login.html +++ b/rainloop/v/0.0.0/app/templates/Views/User/Login.html @@ -7,7 +7,8 @@
-
+ {{INCLUDE/AfterLogo/PLACE}} @@ -21,17 +22,20 @@
-