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; isCached = pInt(data.Time) > microtime() - start;
} }
if (data && data.UpdateToken) {
if (GlobalsData.__APP__ && GlobalsData.__APP__.setClientSideToken) {
GlobalsData.__APP__.setClientSideToken(data.UpdateToken);
}
}
// backward capability // backward capability
let type = ''; let type = '';
switch (true) { switch (true) {

View file

@ -161,6 +161,12 @@ class AbstractAjaxRemote {
cached = pInt(oData.Time) > new window.Date().getTime() - start; 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 (action && this.oRequests[action]) {
if (this.oRequests[action].__aborted) { if (this.oRequests[action].__aborted) {
sType = 'abort'; sType = 'abort';

View file

@ -12,7 +12,7 @@ class SocialAdminSettings {
constructor() { constructor() {
this.googleEnable = SocialStore.google.enabled; this.googleEnable = SocialStore.google.enabled;
this.googleEnableAuth = SocialStore.google.capa.auth; 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.googleEnableDrive = SocialStore.google.capa.drive;
this.googleEnablePreview = SocialStore.google.capa.preview; this.googleEnablePreview = SocialStore.google.capa.preview;
@ -89,6 +89,7 @@ class SocialAdminSettings {
this.googleEnable.subscribe(Remote.saveAdminConfigHelper('GoogleEnable', boolToAjax)); this.googleEnable.subscribe(Remote.saveAdminConfigHelper('GoogleEnable', boolToAjax));
this.googleEnableAuth.subscribe(Remote.saveAdminConfigHelper('GoogleEnableAuth', boolToAjax)); this.googleEnableAuth.subscribe(Remote.saveAdminConfigHelper('GoogleEnableAuth', boolToAjax));
this.googleEnableAuthGmail.subscribe(Remote.saveAdminConfigHelper('GoogleEnableAuthGmail', boolToAjax));
this.googleEnableDrive.subscribe(Remote.saveAdminConfigHelper('GoogleEnableDrive', boolToAjax)); this.googleEnableDrive.subscribe(Remote.saveAdminConfigHelper('GoogleEnableDrive', boolToAjax));
this.googleEnablePreview.subscribe(Remote.saveAdminConfigHelper('GoogleEnablePreview', boolToAjax)); this.googleEnablePreview.subscribe(Remote.saveAdminConfigHelper('GoogleEnablePreview', boolToAjax));
this.googleClientID.subscribe(Remote.saveAdminConfigHelper('GoogleClientID', trim, f5)); this.googleClientID.subscribe(Remote.saveAdminConfigHelper('GoogleClientID', trim, f5));

View file

@ -8,7 +8,7 @@ class SocialUserSettings {
constructor() { constructor() {
this.googleEnable = SocialStore.google.enabled; this.googleEnable = SocialStore.google.enabled;
this.googleEnableAuth = SocialStore.google.capa.auth; 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.googleEnableDrive = SocialStore.google.capa.drive;
this.googleEnablePreview = SocialStore.google.capa.preview; this.googleEnablePreview = SocialStore.google.capa.preview;

View file

@ -24,13 +24,14 @@ class SocialStore {
this.google.capa = {}; this.google.capa = {};
this.google.capa.auth = ko.observable(false); 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.drive = ko.observable(false);
this.google.capa.preview = ko.observable(false); this.google.capa.preview = ko.observable(false);
this.google.require = {}; this.google.require = {};
this.google.require.clientSettings = ko.computed( 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()); 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.apiKey(Settings.settingsGet('GoogleApiKey'));
this.google.capa.auth(!!Settings.settingsGet('AllowGoogleSocialAuth')); 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.drive(!!Settings.settingsGet('AllowGoogleSocialDrive'));
this.google.capa.preview(!!Settings.settingsGet('AllowGoogleSocialPreview')); this.google.capa.preview(!!Settings.settingsGet('AllowGoogleSocialPreview'));

View file

@ -143,7 +143,7 @@ class LoginUserView extends AbstractViewNext {
this.facebookLoginEnabled = ko.observable(false); this.facebookLoginEnabled = ko.observable(false);
this.googleLoginEnabled = ko.observable(false); this.googleLoginEnabled = ko.observable(false);
this.googleFastLoginEnabled = ko.observable(false); this.googleGmailLoginEnabled = ko.observable(false);
this.twitterLoginEnabled = ko.observable(false); this.twitterLoginEnabled = ko.observable(false);
this.socialLoginEnabled = ko.computed(() => { this.socialLoginEnabled = ko.computed(() => {
@ -175,8 +175,8 @@ class LoginUserView extends AbstractViewNext {
return true; return true;
} }
@command((self) => !self.submitRequest() && self.googleFastLoginEnabled()) @command((self) => !self.submitRequest() && self.googleGmailLoginEnabled())
googleFastCommand() { googleGmailCommand() {
window.open(socialGoogle(true), 'Google', this.windowOpenFeatures(550)); window.open(socialGoogle(true), 'Google', this.windowOpenFeatures(550));
return true; return true;
} }
@ -344,8 +344,8 @@ class LoginUserView extends AbstractViewNext {
this.googleLoginEnabled( this.googleLoginEnabled(
!!Settings.settingsGet('AllowGoogleSocial') && !!Settings.settingsGet('AllowGoogleSocialAuth') !!Settings.settingsGet('AllowGoogleSocial') && !!Settings.settingsGet('AllowGoogleSocialAuth')
); );
this.googleFastLoginEnabled( this.googleGmailLoginEnabled(
!!Settings.settingsGet('AllowGoogleSocial') && !!Settings.settingsGet('AllowGoogleSocialAuthFast') !!Settings.settingsGet('AllowGoogleSocial') && !!Settings.settingsGet('AllowGoogleSocialAuthGmail')
); );
switch (signMe) { switch (signMe) {
@ -375,7 +375,7 @@ class LoginUserView extends AbstractViewNext {
this.email(AppStore.devEmail); this.email(AppStore.devEmail);
this.password(AppStore.devPassword); this.password(AppStore.devPassword);
if (this.googleLoginEnabled() || this.googleFastLoginEnabled()) { if (this.googleLoginEnabled() || this.googleGmailLoginEnabled()) {
window['rl_' + jsHash + '_google_login_service'] = fSocial; window['rl_' + jsHash + '_google_login_service'] = fSocial;
} }

View file

@ -126,6 +126,11 @@ class Actions
*/ */
private $sSpecAuthToken; private $sSpecAuthToken;
/**
* @var string
*/
private $sUpdateAuthToken;
/** /**
* @access private * @access private
*/ */
@ -155,6 +160,8 @@ class Actions
$this->oPremProvider = null; $this->oPremProvider = null;
$this->sSpecAuthToken = ''; $this->sSpecAuthToken = '';
$this->sUpdateAuthToken = '';
$this->bIsAjax = false;
$oConfig = $this->Config(); $oConfig = $this->Config();
$this->Plugins()->RunHook('filter.application-config', array(&$oConfig)); $this->Plugins()->RunHook('filter.application-config', array(&$oConfig));
@ -182,6 +189,30 @@ class Actions
return $this; 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 * @return string
*/ */
@ -190,6 +221,22 @@ class Actions
return $this->sSpecAuthToken; return $this->sSpecAuthToken;
} }
/**
* @return string
*/
public function GetUpdateAuthToken()
{
return $this->sUpdateAuthToken;
}
/**
* @return boolean
*/
public function GetIsAjax()
{
return $this->bIsAjax;
}
/** /**
* @return string * @return string
*/ */
@ -1647,7 +1694,7 @@ NewThemeLink IncludeCss LoadingDescriptionEsc TemplatesLink LangLink IncludeBack
$aResult['AllowGoogleSocial'] = (bool) $oConfig->Get('social', 'google_enable', false); $aResult['AllowGoogleSocial'] = (bool) $oConfig->Get('social', 'google_enable', false);
$aResult['AllowGoogleSocialAuth'] = (bool) $oConfig->Get('social', 'google_enable_auth', true); $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['AllowGoogleSocialDrive'] = (bool) $oConfig->Get('social', 'google_enable_drive', true);
$aResult['AllowGoogleSocialPreview'] = (bool) $oConfig->Get('social', 'google_enable_preview', 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', ''))))) '' === \trim($oConfig->Get('social', 'google_client_id', '')) || '' === \trim($oConfig->Get('social', 'google_client_secret', '')))))
{ {
$aResult['AllowGoogleSocialAuth'] = false; $aResult['AllowGoogleSocialAuth'] = false;
$aResult['AllowGoogleSocialAuthFast'] = false; $aResult['AllowGoogleSocialAuthGmail'] = false;
$aResult['AllowGoogleSocialDrive'] = false; $aResult['AllowGoogleSocialDrive'] = false;
$aResult['GoogleClientID'] = ''; $aResult['GoogleClientID'] = '';
$aResult['GoogleApiKey'] = ''; $aResult['GoogleApiKey'] = '';
@ -1670,7 +1717,7 @@ NewThemeLink IncludeCss LoadingDescriptionEsc TemplatesLink LangLink IncludeBack
} }
if ($aResult['AllowGoogleSocial'] && if ($aResult['AllowGoogleSocial'] &&
!$aResult['AllowGoogleSocialAuth'] && !$aResult['AllowGoogleSocialAuthFast'] && !$aResult['AllowGoogleSocialAuth'] && !$aResult['AllowGoogleSocialAuthGmail'] &&
!$aResult['AllowGoogleSocialDrive'] && !$aResult['AllowGoogleSocialPreview']) !$aResult['AllowGoogleSocialDrive'] && !$aResult['AllowGoogleSocialPreview'])
{ {
$aResult['AllowGoogleSocial'] = false; $aResult['AllowGoogleSocial'] = false;
@ -1746,7 +1793,7 @@ NewThemeLink IncludeCss LoadingDescriptionEsc TemplatesLink LangLink IncludeBack
$aResult['AllowGoogleSocial'] = (bool) $oConfig->Get('social', 'google_enable', false); $aResult['AllowGoogleSocial'] = (bool) $oConfig->Get('social', 'google_enable', false);
$aResult['AllowGoogleSocialAuth'] = (bool) $oConfig->Get('social', 'google_enable_auth', true); $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['AllowGoogleSocialDrive'] = (bool) $oConfig->Get('social', 'google_enable_drive', true);
$aResult['AllowGoogleSocialPreview'] = (bool) $oConfig->Get('social', 'google_enable_preview', true); $aResult['AllowGoogleSocialPreview'] = (bool) $oConfig->Get('social', 'google_enable_preview', true);
@ -1986,6 +2033,51 @@ NewThemeLink IncludeCss LoadingDescriptionEsc TemplatesLink LangLink IncludeBack
return $aResult; 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 * @return string
*/ */
@ -2059,7 +2151,7 @@ NewThemeLink IncludeCss LoadingDescriptionEsc TemplatesLink LangLink IncludeBack
{ {
try 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) catch (\RainLoop\Exceptions\ClientException $oException)
{ {
@ -3366,7 +3458,7 @@ NewThemeLink IncludeCss LoadingDescriptionEsc TemplatesLink LangLink IncludeBack
* *
* @throws \MailSo\Base\Exceptions\Exception * @throws \MailSo\Base\Exceptions\Exception
*/ */
public function getAccountUnredCountFromHash($sHash) public function getAccountUnreadCountFromHash($sHash)
{ {
$iResult = 0; $iResult = 0;
@ -3378,7 +3470,7 @@ NewThemeLink IncludeCss LoadingDescriptionEsc TemplatesLink LangLink IncludeBack
$oMailClient = \MailSo\Mail\MailClient::NewInstance(); $oMailClient = \MailSo\Mail\MailClient::NewInstance();
$oMailClient->SetLogger($this->Logger()); $oMailClient->SetLogger($this->Logger());
$oAccount->IncConnectAndLoginHelper($this->Plugins(),$oMailClient, $this->Config()); $oAccount->IncConnectAndLoginHelper($this->Plugins(), $oMailClient, $this->Config(), array($this, 'googleRefreshTokenCallback'));
$iResult = $oMailClient->InboxUnreadCount(); $iResult = $oMailClient->InboxUnreadCount();
@ -3425,7 +3517,7 @@ NewThemeLink IncludeCss LoadingDescriptionEsc TemplatesLink LangLink IncludeBack
foreach ($mAccounts as $sEmail => $sHash) foreach ($mAccounts as $sEmail => $sHash)
{ {
$aCounts[] = array(\MailSo\Base\Utils::IdnToUtf8($sEmail), $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, 'GoogleEnable', 'social', 'google_enable', 'bool');
$this->setConfigFromParams($oConfig, 'GoogleEnableAuth', 'social', 'google_enable_auth', '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, 'GoogleEnableDrive', 'social', 'google_enable_drive', 'bool');
$this->setConfigFromParams($oConfig, 'GoogleEnablePreview', 'social', 'google_enable_preview', 'bool'); $this->setConfigFromParams($oConfig, 'GoogleEnablePreview', 'social', 'google_enable_preview', 'bool');
$this->setConfigFromParams($oConfig, 'GoogleClientID', 'social', 'google_client_id', 'string'); $this->setConfigFromParams($oConfig, 'GoogleClientID', 'social', 'google_client_id', 'string');
@ -4293,7 +4385,7 @@ NewThemeLink IncludeCss LoadingDescriptionEsc TemplatesLink LangLink IncludeBack
$iRepTime = $this->Cacher()->GetTimer($sCacheKey); $iRepTime = $this->Cacher()->GetTimer($sCacheKey);
} }
if ('' === $sRep || 0 === $iRepTime || time() - 3600 > $iRepTime) if ('' === $sRep || 0 === $iRepTime || \time() - 3600 > $iRepTime)
{ {
$iCode = 0; $iCode = 0;
$sContentType = ''; $sContentType = '';
@ -4393,7 +4485,7 @@ NewThemeLink IncludeCss LoadingDescriptionEsc TemplatesLink LangLink IncludeBack
$iRepTime = $this->Cacher()->GetTimer($sCacheKey); $iRepTime = $this->Cacher()->GetTimer($sCacheKey);
} }
if ('' === $sRep || 0 === $iRepTime || time() - 3600 > $iRepTime) if ('' === $sRep || 0 === $iRepTime || \time() - 3600 > $iRepTime)
{ {
$iCode = 0; $iCode = 0;
$sContentType = ''; $sContentType = '';
@ -6172,7 +6264,9 @@ NewThemeLink IncludeCss LoadingDescriptionEsc TemplatesLink LangLink IncludeBack
$oSmtpClient = \MailSo\Smtp\SmtpClient::NewInstance()->SetLogger($this->Logger()); $oSmtpClient = \MailSo\Smtp\SmtpClient::NewInstance()->SetLogger($this->Logger());
$oSmtpClient->SetTimeOuts(10, (int) \RainLoop\Api::Config()->Get('labs', 'smtp_timeout', 60)); $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) if ($bUsePhpMail)
{ {
@ -8315,7 +8409,7 @@ NewThemeLink IncludeCss LoadingDescriptionEsc TemplatesLink LangLink IncludeBack
$iExpires = $this->Config()->Get('cache', 'http_expires', 3600); $iExpires = $this->Config()->Get('cache', 'http_expires', 3600);
if (0 < $iExpires) if (0 < $iExpires)
{ {
$this->oHttp->ServerUseCache($this->etag($sKey), 1382478804, time() + $iExpires); $this->oHttp->ServerUseCache($this->etag($sKey), 1382478804, \time() + $iExpires);
$bResult = true; $bResult = true;
} }
} }
@ -8964,7 +9058,7 @@ NewThemeLink IncludeCss LoadingDescriptionEsc TemplatesLink LangLink IncludeBack
try 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) catch (\MailSo\Net\Exceptions\ConnectionException $oException)
{ {

View file

@ -341,7 +341,7 @@ Examples:
'social' => array( 'social' => array(
'google_enable' => array(false, 'Google'), 'google_enable' => array(false, 'Google'),
'google_enable_auth' => array(false), 'google_enable_auth' => array(false),
'google_enable_auth_fast' => array(false), 'google_enable_auth_gmail' => array(false),
'google_enable_drive' => array(false), 'google_enable_drive' => array(false),
'google_enable_preview' => array(false), 'google_enable_preview' => array(false),
'google_client_id' => array(''), '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 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 * @return string
*/ */
@ -366,18 +395,18 @@ class Account extends \RainLoop\Account // for backward compatibility
public function GetAuthToken() public function GetAuthToken()
{ {
return \RainLoop\Utils::EncodeKeyValues(array( return \RainLoop\Utils::EncodeKeyValues(array(
'token', // 0 'token', // 0
$this->sEmail, // 1 $this->sEmail, // 1
$this->sLogin, // 2 $this->sLogin, // 2
$this->sPassword, // 3 $this->sPassword, // 3
\RainLoop\Utils::Fingerprint(), // 4 \RainLoop\Utils::Fingerprint(), // 4
$this->sSignMeToken, // 5 $this->sSignMeToken, // 5
$this->sParentEmail, // 6 $this->sParentEmail, // 6
\RainLoop\Utils::GetShortToken(), // 7 \RainLoop\Utils::GetShortToken(), // 7
$this->sProxyAuthUser, // 8 $this->sProxyAuthUser, // 8
$this->sProxyAuthPassword, // 9 $this->sProxyAuthPassword, // 9
0, // 10 // timelife 0, // 10 // lifetime
$this->sClientCert // 11 $this->sClientCert // 11
)); ));
} }
@ -387,18 +416,18 @@ class Account extends \RainLoop\Account // for backward compatibility
public function GetAuthTokenQ() public function GetAuthTokenQ()
{ {
return \RainLoop\Utils::EncodeKeyValuesQ(array( return \RainLoop\Utils::EncodeKeyValuesQ(array(
'token', // 0 'token', // 0
$this->sEmail, // 1 $this->sEmail, // 1
$this->sLogin, // 2 $this->sLogin, // 2
$this->sPassword, // 3 $this->sPassword, // 3
\RainLoop\Utils::Fingerprint(), // 4 \RainLoop\Utils::Fingerprint(), // 4
$this->sSignMeToken, // 5 $this->sSignMeToken, // 5
$this->sParentEmail, // 6 $this->sParentEmail, // 6
\RainLoop\Utils::GetShortToken(), // 7 \RainLoop\Utils::GetShortToken(), // 7
$this->sProxyAuthUser, // 8 $this->sProxyAuthUser, // 8
$this->sProxyAuthPassword, // 9 $this->sProxyAuthPassword, // 9
0, // 10 // timelife 0, // 10 // lifetime
$this->sClientCert // 11 $this->sClientCert // 11
)); ));
} }
@ -406,10 +435,11 @@ class Account extends \RainLoop\Account // for backward compatibility
* @param \RainLoop\Plugins\Manager $oPlugins * @param \RainLoop\Plugins\Manager $oPlugins
* @param \MailSo\Mail\MailClient $oMailClient * @param \MailSo\Mail\MailClient $oMailClient
* @param \RainLoop\Application $oConfig * @param \RainLoop\Application $oConfig
* @param callback|null $refreshTokenCallback = null
* *
* @return bool * @return bool
*/ */
public function IncConnectAndLoginHelper($oPlugins, $oMailClient, $oConfig) public function IncConnectAndLoginHelper($oPlugins, $oMailClient, $oConfig, $refreshTokenCallback = null)
{ {
$bLogin = false; $bLogin = false;
@ -456,12 +486,21 @@ class Account extends \RainLoop\Account // for backward compatibility
} }
else else
{ {
$iGatLen = \strlen(APP_GOOGLE_ACCESS_TOKEN_PREFIX);
$sPassword = $aImapCredentials['Password']; $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( $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 else
{ {
@ -482,11 +521,12 @@ class Account extends \RainLoop\Account // for backward compatibility
* @param \RainLoop\Plugins\Manager $oPlugins * @param \RainLoop\Plugins\Manager $oPlugins
* @param \MailSo\Smtp\SmtpClient|null $oSmtpClient * @param \MailSo\Smtp\SmtpClient|null $oSmtpClient
* @param \RainLoop\Application $oConfig * @param \RainLoop\Application $oConfig
* @param callback|null $refreshTokenCallback = null
* @param bool $bUsePhpMail = false * @param bool $bUsePhpMail = false
* *
* @return bool * @return bool
*/ */
public function OutConnectAndLoginHelper($oPlugins, $oSmtpClient, $oConfig, &$bUsePhpMail = false) public function OutConnectAndLoginHelper($oPlugins, $oSmtpClient, $oConfig, $refreshTokenCallback = null, &$bUsePhpMail = false)
{ {
$bLogin = false; $bLogin = false;
@ -526,12 +566,21 @@ class Account extends \RainLoop\Account // for backward compatibility
if ($aSmtpCredentials['UseAuth'] && !$aSmtpCredentials['UsePhpMail'] && $oSmtpClient) if ($aSmtpCredentials['UseAuth'] && !$aSmtpCredentials['UsePhpMail'] && $oSmtpClient)
{ {
$iGatLen = \strlen(APP_GOOGLE_ACCESS_TOKEN_PREFIX);
$sPassword = $aSmtpCredentials['Password']; $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( $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 else
{ {

View file

@ -124,6 +124,8 @@ class ServiceActions
$sAction = $this->aPaths[2]; $sAction = $this->aPaths[2];
} }
$this->oActions->SetIsAjax(true);
try try
{ {
if ($this->oHttp->IsPost() && if ($this->oHttp->IsPost() &&
@ -211,6 +213,12 @@ class ServiceActions
if (\is_array($aResponseItem)) if (\is_array($aResponseItem))
{ {
$aResponseItem['Time'] = (int) ((\microtime(true) - APP_START) * 1000); $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)); $this->Plugins()->RunHook('filter.ajax-response', array($sAction, &$aResponseItem));
@ -498,7 +506,7 @@ class ServiceActions
if (\method_exists($this->oActions, $sMethodName)) if (\method_exists($this->oActions, $sMethodName))
{ {
@\header('X-Raw-Action: '.$sMethodName, true); @\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 = ''; $sRawError = '';
$this->oActions->SetActionParams(array( $this->oActions->SetActionParams(array(

View file

@ -168,6 +168,24 @@ class Social
return $sResult; 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 * @return string
*/ */
@ -221,11 +239,11 @@ class Social
'response_type' => 'code' 'response_type' => 'code'
); );
// if ($bGmail) if ($bGmail)
// { {
// $aParams['access_type'] = 'offline'; $aParams['access_type'] = 'offline';
// $aParams['approval_prompt'] = 'force'; // $aParams['prompt'] = 'consent';
// } }
$sLoginUrl = $oGoogle->getAuthenticationUrl('https://accounts.google.com/o/oauth2/auth', $sRedirectUrl, $aParams); $sLoginUrl = $oGoogle->getAuthenticationUrl('https://accounts.google.com/o/oauth2/auth', $sRedirectUrl, $aParams);
} }
@ -238,12 +256,17 @@ class Social
$bLogin = !$oAccount; $bLogin = !$oAccount;
} }
$aParams = array('code' => $this->oHttp->GetQuery('code'), 'redirect_uri' => $sRedirectUrl); $aAuthorizationCodeResponse = $oGoogle->getAccessToken('https://accounts.google.com/o/oauth2/token', 'authorization_code', array(
$aAuthorizationResponse = $oGoogle->getAccessToken('https://accounts.google.com/o/oauth2/token', 'authorization_code', $aParams); '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'); $aUserInfoResponse = $oGoogle->fetch('https://www.googleapis.com/oauth2/v2/userinfo');
if (!empty($aUserInfoResponse['result']['id'])) if (!empty($aUserInfoResponse['result']['id']))
@ -257,7 +280,7 @@ class Social
{ {
$aUserData = array( $aUserData = array(
'Email' => $aUserInfoResponse['result']['email'], '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" LEGEND_GOOGLE: "Google"
LABEL_ENABLE_GOOGLE: "Enable Google Integration" LABEL_ENABLE_GOOGLE: "Enable Google Integration"
LABEL_GOOGLE_AUTH: "Authorization" LABEL_GOOGLE_AUTH: "Authorization"
LABEL_GOOGLE_AUTH_GMAIL: "Authorization (Gmail)"
LABEL_GOOGLE_DRIVE: "Google Drive Integration (Compose view)" LABEL_GOOGLE_DRIVE: "Google Drive Integration (Compose view)"
LABEL_GOOGLE_PREVIEW: "Google Viewer Integration (Preview for Microsoft Word, Excel and PowerPoint files)" LABEL_GOOGLE_PREVIEW: "Google Viewer Integration (Preview for Microsoft Word, Excel and PowerPoint files)"
LABEL_GOOGLE_CLIENT_ID: "Client ID" 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" LABEL_DONT_ASK_VERIFICATION_CODE: "Don't ask for the code for 2 weeks"
BUTTON_SIGN_IN: "Sign In" BUTTON_SIGN_IN: "Sign In"
TITLE_SIGN_IN_GOOGLE: "Sign In using Google" 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_FACEBOOK: "Sign In using Facebook"
TITLE_SIGN_IN_TWITTER: "Sign In using Twitter" TITLE_SIGN_IN_TWITTER: "Sign In using Twitter"
LABEL_FORGOT_PASSWORD: "Forgot password" LABEL_FORGOT_PASSWORD: "Forgot password"
@ -155,11 +156,11 @@ en:
LINK_SAVE_TO_DROPBOX: "Save to Dropbox" LINK_SAVE_TO_DROPBOX: "Save to Dropbox"
READ_RECEIPT: READ_RECEIPT:
SUBJECT: "Return Receipt (displayed) - %SUBJECT%" SUBJECT: "Return Receipt (displayed) - %SUBJECT%"
BODY: | BODY: |
This is a Return Receipt for the mail that you sent to %READ-RECEIPT%. 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." 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. There is no guarantee that the recipient has read or understood the message contents.
SUGGESTIONS: SUGGESTIONS:
SEARCHING_DESC: "Searching..." SEARCHING_DESC: "Searching..."
CONTACTS: CONTACTS:
@ -365,16 +366,16 @@ en:
LABEL_ARCHIVE: "Archive" LABEL_ARCHIVE: "Archive"
BUTTON_CANCEL: "Cancel" BUTTON_CANCEL: "Cancel"
BUTTON_CLOSE: "Close" BUTTON_CLOSE: "Close"
NOTIFICATION_SENT: | NOTIFICATION_SENT: |
You haven't selected "Sent" system folder messages are put to after sending. 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. 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_DRAFTS: "You haven't selected \"Drafts\" system folder messages are saved to while composing."
NOTIFICATION_SPAM: | NOTIFICATION_SPAM: |
You haven't selected "Spam" system folder spamed messages are placed to. 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. If you wish to remove messages permanently, please select "Do not use" option.
NOTIFICATION_TRASH: | NOTIFICATION_TRASH: |
You haven't selected "Trash" system folder deleted messages are placed to. 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. 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." NOTIFICATION_ARCHIVE: "You haven't selected \"Archive\" system folder achived messages are placed to."
POPUPS_TWO_FACTOR_CFG: POPUPS_TWO_FACTOR_CFG:
LEGEND_TWO_FACTOR_AUTH: "2-Step Verification (TOTP)" 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_REQUIRE_DESC: "Your account requires 2-Step verification configuration."
TWO_FACTOR_SECRET_CONFIGURED_DESC: "Configured" TWO_FACTOR_SECRET_CONFIGURED_DESC: "Configured"
TWO_FACTOR_SECRET_NOT_CONFIGURED_DESC: "Not configured" TWO_FACTOR_SECRET_NOT_CONFIGURED_DESC: "Not configured"
TWO_FACTOR_SECRET_DESC: > TWO_FACTOR_SECRET_DESC: >
Import this info into your Google Authenticator client (or other TOTP client) Import this info into your Google Authenticator client (or other TOTP client)
using the provided QR code below or by entering the code manually. using the provided QR code below or by entering the code manually.
TWO_FACTOR_BACKUP_CODES_DESC: > TWO_FACTOR_BACKUP_CODES_DESC: >
If you can't receive codes via Google Authenticator (or other TOTP client), 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 you can use backup codes to sign in. After youve used a backup code to sign
in, it will become inactive. in, it will become inactive.
TWO_FACTOR_SECRET_TEST_BEFORE_DESC: "You can't change this setting before test." TWO_FACTOR_SECRET_TEST_BEFORE_DESC: "You can't change this setting before test."
TITLES: TITLES:
LOADING: "Loading" LOADING: "Loading"
@ -642,15 +643,15 @@ en:
ACCESS_ERROR: "Access error" ACCESS_ERROR: "Access error"
CONNECTION_ERROR: "Can't connect to server" CONNECTION_ERROR: "Can't connect to server"
CAPTCHA_ERROR: "Incorrect CAPTCHA." CAPTCHA_ERROR: "Incorrect CAPTCHA."
SOCIAL_FACEBOOK_LOGIN_ACCESS_DISABLE: > SOCIAL_FACEBOOK_LOGIN_ACCESS_DISABLE: >
This social ID is not assigned for any email account yet. Log in using email This social ID is not assigned for any email account yet. Log in using email
credentials and enable this feature in account settings. credentials and enable this feature in account settings.
SOCIAL_TWITTER_LOGIN_ACCESS_DISABLE: > SOCIAL_TWITTER_LOGIN_ACCESS_DISABLE: >
This social ID is not assigned for any email account yet. Log in using email This social ID is not assigned for any email account yet. Log in using email
credentials and enable this feature in account settings. credentials and enable this feature in account settings.
SOCIAL_GOOGLE_LOGIN_ACCESS_DISABLE: > SOCIAL_GOOGLE_LOGIN_ACCESS_DISABLE: >
This social ID is not assigned for any email account yet. Log in using email This social ID is not assigned for any email account yet. Log in using email
credentials and enable this feature in account settings. credentials and enable this feature in account settings.
DOMAIN_NOT_ALLOWED: "Domain is not allowed" DOMAIN_NOT_ALLOWED: "Domain is not allowed"
ACCOUNT_NOT_ALLOWED: "Account is not allowed" ACCOUNT_NOT_ALLOWED: "Account is not allowed"
ACCOUNT_TWO_FACTOR_AUTH_REQUIRED: "Two factor verification required" 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_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!" 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_TITLE: "JavaScript is required for this application."
NO_SCRIPT_DESC: | NO_SCRIPT_DESC: |
JavaScript support is not available in your browser. JavaScript support is not available in your browser.
Please enable JavaScript support in your browser settings and retry. Please enable JavaScript support in your browser settings and retry.
NO_COOKIE_TITLE: "Cookies support is required for this application." NO_COOKIE_TITLE: "Cookies support is required for this application."
NO_COOKIE_DESC: | NO_COOKIE_DESC: |
Cookies support is not available in your browser. Cookies support is not available in your browser.
Please enable Cookie support in your browser settings and retry. Please enable Cookie support in your browser settings and retry.
BAD_BROWSER_TITLE: "Your browser is outdated." BAD_BROWSER_TITLE: "Your browser is outdated."
BAD_BROWSER_DESC: | BAD_BROWSER_DESC: |
To use all the features of the application, To use all the features of the application,
download and install one of these browsers: download and install one of these browsers:

View file

@ -24,6 +24,13 @@
label: 'TAB_INTEGRATIONS/LABEL_GOOGLE_AUTH' label: 'TAB_INTEGRATIONS/LABEL_GOOGLE_AUTH'
} }
}"></div> }"></div>
<div data-bind="component: {
name: 'Checkbox',
params: {
value: googleEnableAuthGmail,
label: 'TAB_INTEGRATIONS/LABEL_GOOGLE_AUTH_GMAIL'
}
}"></div>
<div data-bind="component: { <div data-bind="component: {
name: 'Checkbox', name: 'Checkbox',
params: { params: {
@ -90,7 +97,8 @@
</div> </div>
<div class="legend"> <div class="legend">
<span data-i18n="TAB_INTEGRATIONS/LEGEND_FACEBOOK"></span> <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>
<div data-bind="if: facebookSupported"> <div data-bind="if: facebookSupported">
<div class="control-group"> <div class="control-group">

View file

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

View file

@ -1,5 +1,6 @@
<div class="b-login--welcome-content"> <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> <i class="icon-mail iconsize50" style="cursor: pointer" data-bind="click: displayMainForm"></i>
<br /><br /> <br /><br />
</div> </div>