login as another user

This commit is contained in:
Yuri Kuznetsov
2022-09-23 14:02:31 +03:00
parent d5058d9399
commit 8e62ed2fb8
26 changed files with 428 additions and 99 deletions
@@ -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);
}
+82
View File
@@ -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.",
+6
View File
@@ -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
View File
@@ -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,
+2
View File
@@ -136,6 +136,8 @@ define('controller', [], function () {
*/
setRouter: function (router) {
this._router = router;
this.trigger('router-set', router);
},
/**
+13 -3
View File
@@ -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,
+54
View File
@@ -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);
})
});
},
});
});
+2 -2
View File
@@ -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,
+23 -6
View File
@@ -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 => {
+35 -9
View File
@@ -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 => {
+1 -1
View File
@@ -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) {
+1 -1
View File
@@ -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();
+1 -1
View File
@@ -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;
}
+56
View File
@@ -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}`;
},
});
});
+19
View File
@@ -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());
},
});
});