Add Gmail XAUTH support

This commit is contained in:
RainLoop Team 2019-12-31 20:44:41 +03:00
parent 08333610ce
commit 32ceafa37f
16 changed files with 359 additions and 138 deletions

View file

@ -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) {

View file

@ -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';

View file

@ -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));

View file

@ -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;

View file

@ -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'));

View file

@ -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;
}

View file

@ -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)
{

View file

@ -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(''),

View file

@ -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
{

View file

@ -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(

View file

@ -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)
);
}
}

View file

@ -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"

View file

@ -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 youve 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 youve 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:

View file

@ -24,6 +24,13 @@
label: 'TAB_INTEGRATIONS/LABEL_GOOGLE_AUTH'
}
}"></div>
<div data-bind="component: {
name: 'Checkbox',
params: {
value: googleEnableAuthGmail,
label: 'TAB_INTEGRATIONS/LABEL_GOOGLE_AUTH_GMAIL'
}
}"></div>
<div data-bind="component: {
name: 'Checkbox',
params: {
@ -90,7 +97,8 @@
</div>
<div class="legend">
<span data-i18n="TAB_INTEGRATIONS/LEGEND_FACEBOOK"></span>
<span style="color: #ccc; font-size: 14px;" data-bind="visible: !facebookSupported()">(<span data-i18n="HINTS/REQUIRES_PHP_54"></span>)</span>
<span style="color: #ccc; font-size: 14px;" data-bind="visible: !facebookSupported()">(<span
data-i18n="HINTS/REQUIRES_PHP_54"></span>)</span>
</div>
<div data-bind="if: facebookSupported">
<div class="control-group">

View file

@ -7,7 +7,8 @@
<img class="logoImg" data-bind="attr: {'src': logoImg }" />
</div>
<!-- /ko -->
<div class="descWrapper thm-login-desc plugin-mark-Login-AfterLogoDescription" data-bind="visible: '' !== loginDescription">
<div class="descWrapper thm-login-desc plugin-mark-Login-AfterLogoDescription"
data-bind="visible: '' !== loginDescription">
<span class="desc" data-bind="text: loginDescription"></span>
</div>
{{INCLUDE/AfterLogo/PLACE}}
@ -21,17 +22,20 @@
</div>
<div class="wrapper-parent">
<div class="wrapper loginWelcomeForm thm-login-text" data-bind="css: {'welcome-on': welcome}">
<!-- ko template: { name: 'LoginWelcome' } --><!-- /ko -->
<!-- ko template: { name: 'LoginWelcome' } -->
<!-- /ko -->
</div>
<form class="wrapper submitting-pane loginForm thm-login thm-login-text" action="#/" data-bind="submit: submitForm, css: {'errorAnimated': formError, 'welcome-off': welcome, 'submitting': submitRequest()}">
<form class="wrapper submitting-pane loginForm thm-login thm-login-text" action="#/"
data-bind="submit: submitForm, css: {'errorAnimated': formError, 'welcome-off': welcome, 'submitting': submitRequest()}">
{{INCLUDE/TopControlGroup/PLACE}}
<div class="controls plugin-mark-Login-TopControlGroup" data-bind="css: {'error': emailError, 'animated': emailErrorAnimation}">
<div class="controls plugin-mark-Login-TopControlGroup"
data-bind="css: {'error': emailError, 'animated': emailErrorAnimation}">
<div class="input-append">
<input type="email" class="i18n input-block-level inputEmail checkAutocomplete"
name="RainLoopEmail" id="RainLoopEmail"
style="padding-right: 35px;"
autocomplete="email" autocorrect="off" autocapitalize="off" spellcheck="false"
data-bind="textInput: email, hasFocus: emailFocus, disable: submitRequest" data-i18n="[placeholder]LOGIN/LABEL_EMAIL" />
name="RainLoopEmail" id="RainLoopEmail" style="padding-right: 35px;" autocomplete="email"
autocorrect="off" autocapitalize="off" spellcheck="false"
data-bind="textInput: email, hasFocus: emailFocus, disable: submitRequest"
data-i18n="[placeholder]LOGIN/LABEL_EMAIL" />
<span class="add-on">
<i class="icon-mail"></i>
</span>
@ -40,11 +44,12 @@
<div class="controls" data-bind="css: {'error': passwordError, 'animated': passwordErrorAnimation}">
<div class="input-append">
<input type="password" class="i18n input-block-level inputPassword checkAutocomplete"
name="RainLoopPassword" id="RainLoopPassword"
style="padding-right: 35px;"
name="RainLoopPassword" id="RainLoopPassword" style="padding-right: 35px;"
autocomplete="current-password" autocorrect="off" autocapitalize="off" spellcheck="false"
data-bind="textInput: password, hasFocus: passwordFocus, disable: submitRequest" data-i18n="[placeholder]LOGIN/LABEL_PASSWORD" />
<span class="add-on" tabindex="-1" data-tooltip-join="left" data-bind="command: submitCommand, tooltip: 'LOGIN/BUTTON_SIGN_IN'">
data-bind="textInput: password, hasFocus: passwordFocus, disable: submitRequest"
data-i18n="[placeholder]LOGIN/LABEL_PASSWORD" />
<span class="add-on" tabindex="-1" data-tooltip-join="left"
data-bind="command: submitCommand, tooltip: 'LOGIN/BUTTON_SIGN_IN'">
<i class="icon-key" data-bind="visible: '' === password()"></i>
<button type="submit" class="btn-submit-icon-wrp" data-bind="visible: '' !== password()">
<i class="icon-right-middle login-submit-icon"></i>
@ -52,18 +57,20 @@
</span>
</div>
</div>
<div class="controls" data-bind="visible: additionalCode.visibility(), css: {'error': additionalCode.error, 'animated': additionalCode.errorAnimation}">
<div class="controls"
data-bind="visible: additionalCode.visibility(), css: {'error': additionalCode.error, 'animated': additionalCode.errorAnimation}">
<div class="input-append">
<input type="text" class="i18n input-block-level inputAdditionalCode"
autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"
style="padding-right: 35px;"
data-bind="textInput: additionalCode, hasFocus: additionalCode.focused, disable: submitRequest" data-i18n="[placeholder]LOGIN/LABEL_VERIFICATION_CODE" />
<input type="text" class="i18n input-block-level inputAdditionalCode" autocomplete="off"
autocorrect="off" autocapitalize="off" spellcheck="false" style="padding-right: 35px;"
data-bind="textInput: additionalCode, hasFocus: additionalCode.focused, disable: submitRequest"
data-i18n="[placeholder]LOGIN/LABEL_VERIFICATION_CODE" />
<span class="add-on">
<i class="icon-key"></i>
</span>
</div>
</div>
<div class="controls plugin-mark-Login-BottomControlGroup" data-bind="visible: additionalCode.visibility()">
<div class="controls plugin-mark-Login-BottomControlGroup"
data-bind="visible: additionalCode.visibility()">
<div class="additionalCodeSignMeLabel" data-bind="component: {
name: 'CheckboxSimple',
params: {
@ -80,16 +87,24 @@
</div>
<div class="controls clearfix" style="margin-bottom: 10px">
<div class="pull-right social-buttons">
<a href="#" tabindex="-1" class="social-button" data-bind="visible: facebookLoginEnabled, command: facebookCommand, tooltip: 'LOGIN/TITLE_SIGN_IN_FACEBOOK'">
<i class="icon-facebook-alt"></i>
<a href="#" tabindex="-1" class="social-button"
data-bind="visible: googleGmailLoginEnabled, command: googleGmailCommand, tooltip: 'LOGIN/TITLE_SIGN_IN_GMAIL'">
<i class="icon-mail"></i>
</a>
<a href="#" tabindex="-1" class="social-button" data-bind="visible: googleLoginEnabled, command: googleCommand, tooltip: 'LOGIN/TITLE_SIGN_IN_GOOGLE'">
<a href="#" tabindex="-1" class="social-button"
data-bind="visible: googleLoginEnabled, command: googleCommand, tooltip: 'LOGIN/TITLE_SIGN_IN_GOOGLE'">
<i class="icon-google"></i>
</a>
<a href="#" tabindex="-1" class="social-button" data-bind="visible: twitterLoginEnabled, command: twitterCommand, tooltip: 'LOGIN/TITLE_SIGN_IN_TWITTER'">
<a href="#" tabindex="-1" class="social-button"
data-bind="visible: facebookLoginEnabled, command: facebookCommand, tooltip: 'LOGIN/TITLE_SIGN_IN_FACEBOOK'">
<i class="icon-facebook-alt"></i>
</a>
<a href="#" tabindex="-1" class="social-button"
data-bind="visible: twitterLoginEnabled, command: twitterCommand, tooltip: 'LOGIN/TITLE_SIGN_IN_TWITTER'">
<i class="icon-twitter"></i>
</a>
<a href="#" tabindex="-1" class="language-button" data-bind="visible: allowLanguagesOnLogin() && !socialLoginEnabled(), click: selectLanguage, tooltip: 'POPUPS_LANGUAGES/TITLE_LANGUAGES'">
<a href="#" tabindex="-1" class="language-button"
data-bind="visible: allowLanguagesOnLogin() && !socialLoginEnabled(), click: selectLanguage, tooltip: 'POPUPS_LANGUAGES/TITLE_LANGUAGES'">
<i data-bind="css: langRequest() ? 'icon-spinner animated' : 'icon-world'"></i>
</a>
</div>
@ -104,15 +119,20 @@
}"></div>
</div>
<div class="controls clearfix" data-bind="visible: '' !== forgotPasswordLinkUrl || '' !== registrationLinkUrl">
<div class="forgot-link thm-forgot pull-left" data-bind="visible: '' !== forgotPasswordLinkUrl" style="text-align: center">
<a href="#" target="_blank" class="g-ui-link" data-bind="attr: {href: forgotPasswordLinkUrl}, css: {'pull-right': '' !== registrationLinkUrl}"
><span class="i18n" data-i18n="LOGIN/LABEL_FORGOT_PASSWORD"></span></a>
<div class="controls clearfix"
data-bind="visible: '' !== forgotPasswordLinkUrl || '' !== registrationLinkUrl">
<div class="forgot-link thm-forgot pull-left" data-bind="visible: '' !== forgotPasswordLinkUrl"
style="text-align: center">
<a href="#" target="_blank" class="g-ui-link"
data-bind="attr: {href: forgotPasswordLinkUrl}, css: {'pull-right': '' !== registrationLinkUrl}"><span
class="i18n" data-i18n="LOGIN/LABEL_FORGOT_PASSWORD"></span></a>
</div>
&nbsp;
<div class="registration-link thm-registration pull-right" data-bind="visible: '' !== registrationLinkUrl" style="text-align: center">
<a href="#" target="_blank" class="g-ui-link" data-bind="attr: {href: registrationLinkUrl}, css: {'pull-left': '' !== forgotPasswordLinkUrl}"
><span class="i18n" data-i18n="LOGIN/LABEL_REGISTRATION"></span></a>
<div class="registration-link thm-registration pull-right"
data-bind="visible: '' !== registrationLinkUrl" style="text-align: center">
<a href="#" target="_blank" class="g-ui-link"
data-bind="attr: {href: registrationLinkUrl}, css: {'pull-left': '' !== forgotPasswordLinkUrl}"><span
class="i18n" data-i18n="LOGIN/LABEL_REGISTRATION"></span></a>
</div>
</div>
</form>
@ -133,11 +153,13 @@
</a>
</span>
</div>
<div class="e-languages thm-languages plugin-mark-Login-BottomFooter" data-bind="visible: allowLanguagesOnLogin() && socialLoginEnabled()">
<div class="e-languages thm-languages plugin-mark-Login-BottomFooter"
data-bind="visible: allowLanguagesOnLogin() && socialLoginEnabled()">
<label class="flag-selector">
<i data-bind="css: langRequest() ? 'icon-spinner animated' : 'icon-world'"></i>
&nbsp;&nbsp;
<span class="flag-name" tabindex="0" data-bind="text: languageFullName, click: selectLanguage, onSpace: selectLanguage, onEnter: selectLanguage, onTab: selectLanguageOnTab"></span>
<span class="flag-name" tabindex="0"
data-bind="text: languageFullName, click: selectLanguage, onSpace: selectLanguage, onEnter: selectLanguage, onTab: selectLanguageOnTab"></span>
</label>
</div>
{{INCLUDE/BottomFooter/PLACE}}

View file

@ -1,5 +1,6 @@
<div class="b-login--welcome-content">
<i class="icon-google iconsize50" style="cursor: pointer; padding-right: 20px" data-bind="command: googleFastCommand, visible: googleFastLoginEnabled"></i>
<i class="icon-google iconsize50" style="cursor: pointer; padding-right: 20px"
data-bind="command: googleGmailCommand, visible: googleGmailLoginEnabled"></i>
<i class="icon-mail iconsize50" style="cursor: pointer" data-bind="click: displayMainForm"></i>
<br /><br />
</div>