login as another user
This commit is contained in:
@@ -259,7 +259,17 @@ class Authentication
|
||||
|
||||
$user->set('ipAddress', $request->getServerParam('REMOTE_ADDR') ?? null);
|
||||
|
||||
$this->applicationUser->setUser($user);
|
||||
[$loggedUser, $anotherUserFailReason] = $this->getLoggedUser($request, $user);
|
||||
|
||||
if (!$loggedUser) {
|
||||
return $this->processFail(
|
||||
Result::fail($anotherUserFailReason ?? FailReason::ANOTHER_USER_NOT_FOUND),
|
||||
$data,
|
||||
$request
|
||||
);
|
||||
}
|
||||
|
||||
$this->applicationUser->setUser($loggedUser);
|
||||
|
||||
if (
|
||||
!$result->isSecondStepRequired() &&
|
||||
@@ -291,8 +301,8 @@ class Authentication
|
||||
$authTokenId = $authToken->id ?? null;
|
||||
}
|
||||
|
||||
$user->set('token', $authToken->getToken());
|
||||
$user->set('authTokenId', $authTokenId);
|
||||
$loggedUser->set('token', $authToken->getToken());
|
||||
$loggedUser->set('authTokenId', $authTokenId);
|
||||
|
||||
if ($authLogRecord) {
|
||||
$authLogRecord->set('authTokenId', $authTokenId);
|
||||
@@ -315,7 +325,7 @@ class Authentication
|
||||
}
|
||||
|
||||
if ($authLogRecord) {
|
||||
$user->set('authLogRecordId', $authLogRecord->getId());
|
||||
$loggedUser->set('authLogRecordId', $authLogRecord->getId());
|
||||
}
|
||||
|
||||
if ($result->isSuccess()) {
|
||||
@@ -436,13 +446,13 @@ class Authentication
|
||||
|
||||
private function processTwoFactor(Result $result, Request $request): Result
|
||||
{
|
||||
$loggedUser = $result->getLoggedUser();
|
||||
$user = $result->getUser();
|
||||
|
||||
if (!$loggedUser) {
|
||||
throw new RuntimeException("No logged-user.");
|
||||
if (!$user) {
|
||||
throw new RuntimeException("No user.");
|
||||
}
|
||||
|
||||
$method = $this->getUser2FAMethod($loggedUser);
|
||||
$method = $this->getUser2FAMethod($user);
|
||||
|
||||
if (!$method) {
|
||||
return $result;
|
||||
@@ -709,4 +719,43 @@ class Authentication
|
||||
|
||||
return $user->getUserName();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{?User, (FailReason::*)|null}
|
||||
*/
|
||||
private function getLoggedUser(Request $request, User $user): array
|
||||
{
|
||||
$username = $request->getHeader('X-Another-User');
|
||||
|
||||
if (!$username) {
|
||||
return [$user, null];
|
||||
}
|
||||
|
||||
if ($this->configDataProvider->isAnotherUserDisabled()) {
|
||||
return [null, FailReason::ANOTHER_USER_NOT_ALLOWED];
|
||||
}
|
||||
|
||||
// Important check.
|
||||
if (!$user->isAdmin()) {
|
||||
return [null, FailReason::ANOTHER_USER_NOT_ALLOWED];
|
||||
}
|
||||
|
||||
/** @var ?User $loggedUser */
|
||||
$loggedUser = $this->entityManager
|
||||
->getRDBRepository(User::ENTITY_TYPE)
|
||||
->where([
|
||||
'userName' => $username,
|
||||
])
|
||||
->findOne();
|
||||
|
||||
if (!$loggedUser) {
|
||||
return [null, FailReason::ANOTHER_USER_NOT_FOUND];
|
||||
}
|
||||
|
||||
if (!$loggedUser->isRegular()) {
|
||||
return [null, FailReason::ANOTHER_USER_NOT_ALLOWED];
|
||||
}
|
||||
|
||||
return [$loggedUser, null];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,4 +119,9 @@ class ConfigDataProvider
|
||||
{
|
||||
return (bool) $this->metadata->get(['authenticationMethods', $authenticationMethod, 'api']);
|
||||
}
|
||||
|
||||
public function isAnotherUserDisabled(): bool
|
||||
{
|
||||
return (bool) $this->config->get('authAnotherUserDisabled');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,6 @@ class Result
|
||||
private ?string $message = null;
|
||||
private ?string $token = null;
|
||||
private ?string $view = null;
|
||||
private ?User $loggedUser = null;
|
||||
private ?string $failReason = null;
|
||||
private ?Data $data = null;
|
||||
|
||||
@@ -64,7 +63,6 @@ class Result
|
||||
$this->message = $data->getMessage();
|
||||
$this->token = $data->getToken();
|
||||
$this->view = $data->getView();
|
||||
$this->loggedUser = $data->getLoggedUser();
|
||||
$this->failReason = $data->getFailReason();
|
||||
}
|
||||
}
|
||||
@@ -130,12 +128,11 @@ class Result
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a logged user. Considered that an admin user can log in as another user.
|
||||
* The logged user will be an admin user.
|
||||
* @deprecated Use `getUser`.
|
||||
*/
|
||||
public function getLoggedUser(): ?User
|
||||
{
|
||||
return $this->loggedUser ?? $this->user;
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -38,7 +38,6 @@ class Data
|
||||
private ?string $message = null;
|
||||
private ?string $token = null;
|
||||
private ?string $view = null;
|
||||
private ?User $loggedUser = null;
|
||||
private ?string $failReason = null;
|
||||
|
||||
/**
|
||||
@@ -50,14 +49,12 @@ class Data
|
||||
?string $message = null,
|
||||
?string $failReason = null,
|
||||
?string $token = null,
|
||||
?string $view = null,
|
||||
?User $loggedUser = null
|
||||
?string $view = null
|
||||
) {
|
||||
$this->message = $message;
|
||||
$this->failReason = $failReason;
|
||||
$this->token = $token;
|
||||
$this->view = $view;
|
||||
$this->loggedUser = $loggedUser;
|
||||
}
|
||||
|
||||
public static function create(): self
|
||||
@@ -75,11 +72,6 @@ class Data
|
||||
return new self($message);
|
||||
}
|
||||
|
||||
public function getLoggedUser(): ?User
|
||||
{
|
||||
return $this->loggedUser;
|
||||
}
|
||||
|
||||
public function getView(): ?string
|
||||
{
|
||||
return $this->view;
|
||||
@@ -137,14 +129,6 @@ class Data
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function withLoggedUser(?User $loggedUser): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->loggedUser = $loggedUser;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
*/
|
||||
|
||||
@@ -42,4 +42,6 @@ class FailReason
|
||||
public const HASH_NOT_MATCHED = 'Hash not matched';
|
||||
public const METHOD_NOT_ALLOWED = 'Not allowed authentication method';
|
||||
public const DISCREPANT_DATA = 'Discrepant authentication data';
|
||||
public const ANOTHER_USER_NOT_FOUND = 'Another user not found';
|
||||
public const ANOTHER_USER_NOT_ALLOWED = 'Another user not allowed';
|
||||
}
|
||||
|
||||
@@ -64,25 +64,19 @@ class EmailLogin implements Login
|
||||
{
|
||||
$code = $request->getHeader('Espo-Authorization-Code');
|
||||
|
||||
$loggedUser = $result->getLoggedUser();
|
||||
$user = $result->getUser();
|
||||
|
||||
if (!$loggedUser) {
|
||||
throw new RuntimeException("No logged-user.");
|
||||
if (!$user) {
|
||||
throw new RuntimeException("No user.");
|
||||
}
|
||||
|
||||
if (!$code) {
|
||||
$user = $result->getUser();
|
||||
|
||||
if (!$user) {
|
||||
throw new RuntimeException("No user.");
|
||||
}
|
||||
|
||||
$this->util->sendCode($loggedUser);
|
||||
$this->util->sendCode($user);
|
||||
|
||||
return Result::secondStepRequired($user, $this->getResultData());
|
||||
}
|
||||
|
||||
if ($this->verifyCode($loggedUser, $code)) {
|
||||
if ($this->verifyCode($user, $code)) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
@@ -63,10 +63,10 @@ class SmsLogin implements Login
|
||||
{
|
||||
$code = $request->getHeader('Espo-Authorization-Code');
|
||||
|
||||
$loggedUser = $result->getLoggedUser();
|
||||
$user = $result->getUser();
|
||||
|
||||
if (!$loggedUser) {
|
||||
throw new RuntimeException("No logged-user.");
|
||||
if (!$user) {
|
||||
throw new RuntimeException("No user.");
|
||||
}
|
||||
|
||||
if (!$code) {
|
||||
@@ -76,12 +76,12 @@ class SmsLogin implements Login
|
||||
throw new RuntimeException("No user.");
|
||||
}
|
||||
|
||||
$this->util->sendCode($loggedUser);
|
||||
$this->util->sendCode($user);
|
||||
|
||||
return Result::secondStepRequired($user, $this->getResultData());
|
||||
}
|
||||
|
||||
if ($this->verifyCode($loggedUser, $code)) {
|
||||
if ($this->verifyCode($user, $code)) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
@@ -64,23 +64,17 @@ class TotpLogin implements Login
|
||||
{
|
||||
$code = $request->getHeader('Espo-Authorization-Code');
|
||||
|
||||
$user = $result->getUser();
|
||||
|
||||
if (!$user) {
|
||||
throw new RuntimeException("No user.");
|
||||
}
|
||||
|
||||
if (!$code) {
|
||||
$user = $result->getUser();
|
||||
|
||||
if (!$user) {
|
||||
throw new RuntimeException("No user.");
|
||||
}
|
||||
|
||||
return Result::secondStepRequired($user, $this->getResultData());
|
||||
}
|
||||
|
||||
$loggedUser = $result->getLoggedUser();
|
||||
|
||||
if (!$loggedUser) {
|
||||
throw new RuntimeException("No logged-user.");
|
||||
}
|
||||
|
||||
if ($this->verifyCode($loggedUser, $code)) {
|
||||
if ($this->verifyCode($user, $code)) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
@@ -65,13 +65,14 @@ class ActionRenderer
|
||||
{
|
||||
$encodedData = Json::encode($data);
|
||||
|
||||
$script = "
|
||||
app.doAction({
|
||||
controllerClassName: '{$controller}',
|
||||
action: '{$action}',
|
||||
options: {$encodedData},
|
||||
});
|
||||
";
|
||||
$script =
|
||||
"
|
||||
app.doAction({
|
||||
controllerClassName: '{$controller}',
|
||||
action: '{$action}',
|
||||
options: {$encodedData},
|
||||
});
|
||||
";
|
||||
|
||||
return $this->clientManager->render($script);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2022 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\EntryPoints;
|
||||
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Core\EntryPoint\EntryPoint;
|
||||
use Espo\Core\EntryPoint\Traits\NoAuth;
|
||||
use Espo\Core\Api\Request;
|
||||
use Espo\Core\Api\Response;
|
||||
use Espo\Core\Utils\ClientManager;
|
||||
use Espo\Core\Utils\Json;
|
||||
|
||||
class LoginAs implements EntryPoint
|
||||
{
|
||||
use NoAuth;
|
||||
|
||||
private ClientManager $clientManager;
|
||||
|
||||
public function __construct(ClientManager $clientManager)
|
||||
{
|
||||
$this->clientManager = $clientManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws BadRequest
|
||||
*/
|
||||
public function run(Request $request, Response $response): void
|
||||
{
|
||||
$anotherUser = $request->getQueryParam('anotherUser');
|
||||
|
||||
if (!$anotherUser) {
|
||||
throw new BadRequest("No anotherUser.");
|
||||
}
|
||||
|
||||
$data = [
|
||||
'anotherUser' => $anotherUser,
|
||||
'username' => $request->getQueryParam('username'),
|
||||
];
|
||||
|
||||
$encodedData = Json::encode($data);
|
||||
|
||||
$script =
|
||||
"
|
||||
app.initAuth();
|
||||
app.doAction({
|
||||
controllerClassName: 'controllers/login-as',
|
||||
action: 'login',
|
||||
options: {$encodedData},
|
||||
});
|
||||
";
|
||||
|
||||
$this->clientManager->writeHeaders($response);
|
||||
$response->writeBody($this->clientManager->render($script)); ;
|
||||
}
|
||||
}
|
||||
@@ -101,6 +101,7 @@ return [
|
||||
'clientSecurityHeadersDisabled',
|
||||
'clientCspDisabled',
|
||||
'clientCspScriptSourceList',
|
||||
'authAnotherUserDisabled',
|
||||
],
|
||||
'adminItems' => [
|
||||
'devMode',
|
||||
|
||||
@@ -176,6 +176,8 @@
|
||||
"Password": "Password",
|
||||
"Login": "Login",
|
||||
"Log Out": "Log Out",
|
||||
"Log in": "Log in",
|
||||
"Log in as": "Log in as",
|
||||
"Preferences": "Preferences",
|
||||
"State": "State",
|
||||
"Street": "Street",
|
||||
|
||||
@@ -82,7 +82,8 @@
|
||||
"Reset 2FA": "Reset 2FA",
|
||||
"Code": "Code",
|
||||
"Secret": "Secret",
|
||||
"Send Code": "Send Code"
|
||||
"Send Code": "Send Code",
|
||||
"Login Link": "Login Link"
|
||||
},
|
||||
"tooltips": {
|
||||
"defaultTeam": "All records created by this user will be related to this team by default.",
|
||||
@@ -95,6 +96,7 @@
|
||||
"portals": "Portals which this user has access to."
|
||||
},
|
||||
"messages": {
|
||||
"loginAs": "Open the login link in an incognito window to preserve your current session. Use your admin credentials to log in.",
|
||||
"sendPasswordChangeLinkConfirmation": "An email with a unique link will be sent to the user allowing them to change their password. The link will expire after a specific amount of time.",
|
||||
"passwordRecoverySentIfMatched": "Assuming the entered data matched any user account.",
|
||||
"passwordStrengthLength": "Must be at least {length} characters long.",
|
||||
|
||||
@@ -35,6 +35,12 @@
|
||||
maxlength="255"
|
||||
>
|
||||
</div>
|
||||
{{#if anotherUser}}
|
||||
<div class="form-group">
|
||||
<label>{{translate 'Log in as'}}</label>
|
||||
<div>{{anotherUser}}</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
<div class="margin-top-2x">
|
||||
{{#if showForgotPassword}}
|
||||
<a
|
||||
|
||||
+35
-9
@@ -252,6 +252,14 @@ function (
|
||||
*/
|
||||
auth: null,
|
||||
|
||||
/**
|
||||
* Another user to login as.
|
||||
*
|
||||
* @private
|
||||
* @type {?string}
|
||||
*/
|
||||
anotherUser: null,
|
||||
|
||||
/**
|
||||
* A base controller.
|
||||
*
|
||||
@@ -335,6 +343,12 @@ function (
|
||||
*/
|
||||
numberUtil: null,
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {module:web-socket-manager.Class|null}
|
||||
*/
|
||||
webSocketManager: null,
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {?int}
|
||||
@@ -470,14 +484,11 @@ function (
|
||||
|
||||
if (!this.auth) {
|
||||
this.baseController.login();
|
||||
}
|
||||
else {
|
||||
this.initUserData(null, () => {
|
||||
this.onAuth.call(this);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.on('auth', this.onAuth, this);
|
||||
this.initUserData(null, () => this.onAuth.call(this));
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -497,6 +508,11 @@ function (
|
||||
this.loadStylesheet();
|
||||
}
|
||||
|
||||
if (this.anotherUser) {
|
||||
this.viewHelper.webSocketManager = null;
|
||||
this.webSocketManager = null;
|
||||
}
|
||||
|
||||
if (this.webSocketManager) {
|
||||
this.webSocketManager.connect(this.auth, this.user.id);
|
||||
}
|
||||
@@ -668,6 +684,7 @@ function (
|
||||
metadata: this.metadata,
|
||||
dateTime: this.dateTime,
|
||||
broadcastChannel: this.broadcastChannel,
|
||||
baseController: this.baseController,
|
||||
};
|
||||
},
|
||||
|
||||
@@ -716,8 +733,6 @@ function (
|
||||
controllerClass => {
|
||||
let injections = this.getControllerInjection();
|
||||
|
||||
injections.baseController = this.baseController;
|
||||
|
||||
let controller = new controllerClass(this.baseController.params, injections);
|
||||
|
||||
controller.name = name;
|
||||
@@ -871,17 +886,22 @@ function (
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @public
|
||||
*/
|
||||
initAuth: function () {
|
||||
this.on('auth', this.onAuth, this);
|
||||
|
||||
this.auth = this.storage.get('user', 'auth') || null;
|
||||
this.anotherUser = this.storage.get('user', 'anotherUser') || null;
|
||||
|
||||
this.baseController.on('login', data => {
|
||||
let userId = data.user.id;
|
||||
let userName = data.auth.userName;
|
||||
let token = data.auth.token;
|
||||
let anotherUser = data.auth.anotherUser || null;
|
||||
|
||||
this.auth = Base64.encode(userName + ':' + token);
|
||||
this.anotherUser = anotherUser;
|
||||
|
||||
let lastUserId = this.storage.get('user', 'lastUserId');
|
||||
|
||||
@@ -892,6 +912,7 @@ function (
|
||||
|
||||
this.storage.set('user', 'auth', this.auth);
|
||||
this.storage.set('user', 'lastUserId', userId);
|
||||
this.storage.set('user', 'anotherUser', this.anotherUser);
|
||||
|
||||
this.setCookieAuth(userName, token);
|
||||
|
||||
@@ -920,6 +941,7 @@ function (
|
||||
}
|
||||
|
||||
this.auth = null;
|
||||
this.anotherUser = null;
|
||||
|
||||
this.user.clear();
|
||||
this.preferences.clear();
|
||||
@@ -927,6 +949,7 @@ function (
|
||||
this.acl.clear();
|
||||
|
||||
this.storage.clear('user', 'auth');
|
||||
this.storage.clear('user', 'anotherUser');
|
||||
|
||||
this.doAction({action: 'login'});
|
||||
|
||||
@@ -1084,6 +1107,9 @@ function (
|
||||
xhr.setRequestHeader('Espo-Authorization-By-Token', 'true');
|
||||
}
|
||||
|
||||
if (this.anotherUser !== null) {
|
||||
xhr.setRequestHeader('X-Another-User', this.anotherUser);
|
||||
}
|
||||
},
|
||||
dataType: 'json',
|
||||
timeout: this.ajaxTimeout,
|
||||
|
||||
@@ -136,6 +136,8 @@ define('controller', [], function () {
|
||||
*/
|
||||
setRouter: function (router) {
|
||||
this._router = router;
|
||||
|
||||
this.trigger('router-set', router);
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -41,10 +41,18 @@ define('controllers/base', ['controller'], function (Dep) {
|
||||
/**
|
||||
* Log in.
|
||||
*/
|
||||
login: function () {
|
||||
var viewName = this.getConfig().get('loginView') || 'views/login';
|
||||
login: function (options) {
|
||||
let viewName = this.getConfig().get('loginView') || 'views/login';
|
||||
|
||||
this.entire(viewName, {}, loginView => {
|
||||
let anotherUser = (options || {}).anotherUser;
|
||||
let prefilledUsername = (options || {}).username;
|
||||
|
||||
let viewOptions = {
|
||||
anotherUser: anotherUser,
|
||||
prefilledUsername: prefilledUsername,
|
||||
};
|
||||
|
||||
this.entire(viewName, viewOptions, loginView => {
|
||||
loginView.render();
|
||||
|
||||
loginView.on('login', (userName, data) => {
|
||||
@@ -58,6 +66,7 @@ define('controllers/base', ['controller'], function (Dep) {
|
||||
loginData: data,
|
||||
userName: userName,
|
||||
password: password,
|
||||
anotherUser: anotherUser,
|
||||
}, secondStepView => {
|
||||
secondStepView.render();
|
||||
|
||||
@@ -83,6 +92,7 @@ define('controllers/base', ['controller'], function (Dep) {
|
||||
auth: {
|
||||
userName: userName,
|
||||
token: data.token,
|
||||
anotherUser: data.anotherUser,
|
||||
},
|
||||
user: data.user,
|
||||
preferences: data.preferences,
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2022 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
define('controllers/login-as', ['controller'], function (Dep) {
|
||||
|
||||
return Dep.extend({
|
||||
|
||||
actionLogin: function (options) {
|
||||
let anotherUser = options.anotherUser;
|
||||
let username = options.username;
|
||||
|
||||
if (!anotherUser) {
|
||||
throw new Error("No anotherUser.");
|
||||
}
|
||||
|
||||
this.baseController.login({
|
||||
anotherUser: anotherUser,
|
||||
username: username,
|
||||
});
|
||||
|
||||
this.listenToOnce(this.baseController, 'login', () => {
|
||||
this.baseController.once('router-set', () => {
|
||||
let url = window.location.href.split('?')[0];
|
||||
window.location.replace(url);
|
||||
})
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -202,9 +202,9 @@ function (marked, DOMPurify, /** typeof Handlebars */Handlebars) {
|
||||
themeManager: null,
|
||||
|
||||
/**
|
||||
* A web-socket manager
|
||||
* A web-socket manager. Null if not enabled.
|
||||
*
|
||||
* @type {module:web-socket-manager.Class}
|
||||
* @type {?module:web-socket-manager.Class}
|
||||
*/
|
||||
webSocketManager: null,
|
||||
|
||||
|
||||
@@ -39,6 +39,11 @@ define('views/login-second-step', ['view'], function (Dep) {
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* @type {?string}
|
||||
*/
|
||||
anotherUser: null,
|
||||
|
||||
events: {
|
||||
'submit #login-form': function (e) {
|
||||
e.preventDefault();
|
||||
@@ -65,6 +70,7 @@ define('views/login-second-step', ['view'], function (Dep) {
|
||||
|
||||
setup: function () {
|
||||
this.message = this.translate(this.options.loginData.message, 'messages', 'User');
|
||||
this.anotherUser = this.options.anotherUser || null;
|
||||
},
|
||||
|
||||
send: function () {
|
||||
@@ -117,18 +123,29 @@ define('views/login-second-step', ['view'], function (Dep) {
|
||||
|
||||
let authString = Base64.encode(userName + ':' + password);
|
||||
|
||||
let headers = {
|
||||
'Authorization': 'Basic ' + authString,
|
||||
'Espo-Authorization': authString,
|
||||
'Espo-Authorization-Code': code,
|
||||
'Espo-Authorization-Create-Token-Secret': true,
|
||||
};
|
||||
|
||||
if (this.anotherUser !== null) {
|
||||
headers['X-Another-User'] = this.anotherUser;
|
||||
}
|
||||
|
||||
Espo.Ajax
|
||||
.getRequest('App/user', {code: code}, {
|
||||
login: true,
|
||||
headers: {
|
||||
'Authorization': 'Basic ' + authString,
|
||||
'Espo-Authorization': authString,
|
||||
'Espo-Authorization-Code': code,
|
||||
'Espo-Authorization-Create-Token-Secret': true,
|
||||
},
|
||||
headers: headers,
|
||||
})
|
||||
.then(data => {
|
||||
this.notify(false);
|
||||
|
||||
if (this.anotherUser) {
|
||||
data.anotherUser = this.anotherUser;
|
||||
}
|
||||
|
||||
this.trigger('login', userName, data);
|
||||
})
|
||||
.catch(xhr => {
|
||||
|
||||
@@ -39,6 +39,11 @@ define('views/login', ['view'], function (Dep) {
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* @type {?string}
|
||||
*/
|
||||
anotherUser: null,
|
||||
|
||||
events: {
|
||||
'submit #login-form': function (e) {
|
||||
e.preventDefault();
|
||||
@@ -61,9 +66,14 @@ define('views/login', ['view'], function (Dep) {
|
||||
return {
|
||||
logoSrc: this.getLogoSrc(),
|
||||
showForgotPassword: this.getConfig().get('passwordRecoveryEnabled'),
|
||||
anotherUser: this.anotherUser,
|
||||
};
|
||||
},
|
||||
|
||||
setup: function () {
|
||||
this.anotherUser = this.options.anotherUser || null;
|
||||
},
|
||||
|
||||
getLogoSrc: function () {
|
||||
var companyLogoId = this.getConfig().get('companyLogoId');
|
||||
|
||||
@@ -76,19 +86,25 @@ define('views/login', ['view'], function (Dep) {
|
||||
|
||||
afterRender: function () {
|
||||
this.$submit = this.$el.find('#btn-login');
|
||||
this.$username = this.$el.find('#field-userName');
|
||||
this.$password = this.$el.find('#field-password');
|
||||
|
||||
if (this.options.prefilledUsername) {
|
||||
this.$username.val(this.options.prefilledUsername);
|
||||
}
|
||||
},
|
||||
|
||||
login: function () {
|
||||
let userName = $('#field-userName').val();
|
||||
let userName = this.$username.val();
|
||||
let trimmedUserName = userName.trim();
|
||||
|
||||
if (trimmedUserName !== userName) {
|
||||
$('#field-userName').val(trimmedUserName);
|
||||
this.$username.val(trimmedUserName);
|
||||
|
||||
userName = trimmedUserName;
|
||||
}
|
||||
|
||||
let password = $('#field-password').val();
|
||||
let password = this.$password.val();
|
||||
|
||||
if (userName === '') {
|
||||
this.isPopoverDestroyed = false;
|
||||
@@ -140,19 +156,29 @@ define('views/login', ['view'], function (Dep) {
|
||||
throw e;
|
||||
}
|
||||
|
||||
let headers = {
|
||||
'Authorization': 'Basic ' + authString,
|
||||
'Espo-Authorization': authString,
|
||||
'Espo-Authorization-By-Token': false,
|
||||
'Espo-Authorization-Create-Token-Secret': true,
|
||||
};
|
||||
|
||||
if (this.anotherUser !== null) {
|
||||
headers['X-Another-User'] = this.anotherUser;
|
||||
}
|
||||
|
||||
Espo.Ajax
|
||||
.getRequest('App/user', null, {
|
||||
login: true,
|
||||
headers: {
|
||||
'Authorization': 'Basic ' + authString,
|
||||
'Espo-Authorization': authString,
|
||||
'Espo-Authorization-By-Token': false,
|
||||
'Espo-Authorization-Create-Token-Secret': true,
|
||||
},
|
||||
headers: headers,
|
||||
})
|
||||
.then(data => {
|
||||
this.notify(false);
|
||||
|
||||
if (this.anotherUser) {
|
||||
data.anotherUser = this.anotherUser;
|
||||
}
|
||||
|
||||
this.trigger('login', userName, data);
|
||||
})
|
||||
.catch(xhr => {
|
||||
|
||||
@@ -51,7 +51,7 @@ define('views/notification/badge', ['view'], function (Dep) {
|
||||
|
||||
this.notificationSoundsDisabled = true;
|
||||
|
||||
this.useWebSocket = this.getConfig().get('useWebSocket');
|
||||
this.useWebSocket = !!this.getHelper().webSocketManager;
|
||||
|
||||
this.once('remove', () => {
|
||||
if (this.timeout) {
|
||||
|
||||
@@ -1856,7 +1856,7 @@ function (Dep, ViewRecordHelper, ActionItemSetup) {
|
||||
|
||||
if (
|
||||
!this.isNew &&
|
||||
this.getConfig().get('useWebSocket') &&
|
||||
!!this.getHelper().webSocketManager &&
|
||||
this.getMetadata().get(['scopes', this.entityType, 'object'])
|
||||
) {
|
||||
this.subscribeToWebSocket();
|
||||
|
||||
@@ -280,7 +280,7 @@ define('views/stream/panel', ['views/record/panels/relationship', 'lib!Textcompl
|
||||
},
|
||||
|
||||
subscribeToWebSocket: function () {
|
||||
if (!this.getConfig().get('useWebSocket')) {
|
||||
if (!this.getHelper().webSocketManager) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2022 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
define('views/user/modals/login-as', ['views/modal'], function (Dep) {
|
||||
|
||||
return Dep.extend({
|
||||
|
||||
backdrop: true,
|
||||
|
||||
templateContent: `
|
||||
<div class="well">
|
||||
{{translate 'loginAs' category='messages' scope='User'}}
|
||||
</div>
|
||||
<a href="{{viewObject.url}}" class="text-large">{{translate 'Login Link' scope='User'}}</a>
|
||||
`,
|
||||
|
||||
setup: function () {
|
||||
this.$header = $('<span>')
|
||||
.append(
|
||||
$('<span>').text(this.model.get('name')),
|
||||
' ',
|
||||
$('<span>').addClass('chevron-right'),
|
||||
' ',
|
||||
$('<span>').text(this.translate('Login')),
|
||||
);
|
||||
|
||||
this.url = `?entryPoint=loginAs` +
|
||||
`&anotherUser=${this.options.anotherUser}&username=${this.options.username}`;
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -122,6 +122,14 @@ define('views/user/record/detail', 'views/record/detail', function (Dep) {
|
||||
});
|
||||
}
|
||||
|
||||
if (this.getUser().isAdmin() && this.model.isRegular()) {
|
||||
this.addDropdownItem({
|
||||
label: 'Log in',
|
||||
name: 'login',
|
||||
action: 'login',
|
||||
});
|
||||
}
|
||||
|
||||
this.setupFieldAppearance();
|
||||
},
|
||||
|
||||
@@ -416,5 +424,16 @@ define('views/user/record/detail', 'views/record/detail', function (Dep) {
|
||||
});
|
||||
},
|
||||
|
||||
actionLogin: function () {
|
||||
let anotherUser = this.model.get('userName');
|
||||
let username = this.getUser().get('userName');
|
||||
|
||||
this.createView('dialog', 'views/user/modals/login-as', {
|
||||
model: this.model,
|
||||
anotherUser: anotherUser,
|
||||
username: username,
|
||||
})
|
||||
.then(view => view.render());
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user