Commit 796294f33faf87ac31f14b320210600cabb89bc3

Authored by Igor Kulikov
1 parent 8efb250f

Login As User feature.

... ... @@ -15,7 +15,12 @@
15 15 */
16 16 package org.thingsboard.server.controller;
17 17
  18 +import com.fasterxml.jackson.databind.JsonNode;
  19 +import com.fasterxml.jackson.databind.ObjectMapper;
  20 +import com.fasterxml.jackson.databind.node.ObjectNode;
  21 +import lombok.Getter;
18 22 import org.springframework.beans.factory.annotation.Autowired;
  23 +import org.springframework.beans.factory.annotation.Value;
19 24 import org.springframework.http.HttpStatus;
20 25 import org.springframework.security.access.prepost.PreAuthorize;
21 26 import org.springframework.web.bind.annotation.PathVariable;
... ... @@ -39,7 +44,11 @@ import org.thingsboard.server.common.data.page.TextPageData;
39 44 import org.thingsboard.server.common.data.page.TextPageLink;
40 45 import org.thingsboard.server.common.data.security.Authority;
41 46 import org.thingsboard.server.common.data.security.UserCredentials;
  47 +import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository;
42 48 import org.thingsboard.server.service.security.model.SecurityUser;
  49 +import org.thingsboard.server.service.security.model.UserPrincipal;
  50 +import org.thingsboard.server.service.security.model.token.JwtToken;
  51 +import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
43 52
44 53 import javax.servlet.http.HttpServletRequest;
45 54
... ... @@ -50,9 +59,21 @@ public class UserController extends BaseController {
50 59 public static final String USER_ID = "userId";
51 60 public static final String YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION = "You don't have permission to perform this operation!";
52 61 public static final String ACTIVATE_URL_PATTERN = "%s/api/noauth/activate?activateToken=%s";
  62 +
  63 + @Value("${security.user_token_access_enabled}")
  64 + @Getter
  65 + private boolean userTokenAccessEnabled;
  66 +
53 67 @Autowired
54 68 private MailService mailService;
55 69
  70 + @Autowired
  71 + private JwtTokenFactory tokenFactory;
  72 +
  73 + @Autowired
  74 + private RefreshTokenRepository refreshTokenRepository;
  75 +
  76 +
56 77 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
57 78 @RequestMapping(value = "/user/{userId}", method = RequestMethod.GET)
58 79 @ResponseBody
... ... @@ -71,6 +92,42 @@ public class UserController extends BaseController {
71 92 }
72 93 }
73 94
  95 + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
  96 + @RequestMapping(value = "/user/tokenAccessEnabled", method = RequestMethod.GET)
  97 + @ResponseBody
  98 + public boolean isUserTokenAccessEnabled() {
  99 + return userTokenAccessEnabled;
  100 + }
  101 +
  102 + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
  103 + @RequestMapping(value = "/user/{userId}/token", method = RequestMethod.GET)
  104 + @ResponseBody
  105 + public JsonNode getUserToken(@PathVariable(USER_ID) String strUserId) throws ThingsboardException {
  106 + checkParameter(USER_ID, strUserId);
  107 + try {
  108 + UserId userId = new UserId(toUUID(strUserId));
  109 + SecurityUser authUser = getCurrentUser();
  110 + User user = userService.findUserById(userId);
  111 + if (!userTokenAccessEnabled || (authUser.getAuthority() == Authority.SYS_ADMIN && user.getAuthority() != Authority.TENANT_ADMIN)
  112 + || (authUser.getAuthority() == Authority.TENANT_ADMIN && !authUser.getTenantId().equals(user.getTenantId()))) {
  113 + throw new ThingsboardException(YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION,
  114 + ThingsboardErrorCode.PERMISSION_DENIED);
  115 + }
  116 + UserPrincipal principal = new UserPrincipal(UserPrincipal.Type.USER_NAME, user.getEmail());
  117 + UserCredentials credentials = userService.findUserCredentialsByUserId(userId);
  118 + SecurityUser securityUser = new SecurityUser(user, credentials.isEnabled(), principal);
  119 + JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser);
  120 + JwtToken refreshToken = refreshTokenRepository.requestRefreshToken(securityUser);
  121 + ObjectMapper objectMapper = new ObjectMapper();
  122 + ObjectNode tokenObject = objectMapper.createObjectNode();
  123 + tokenObject.put("token", accessToken.getToken());
  124 + tokenObject.put("refreshToken", refreshToken.getToken());
  125 + return tokenObject;
  126 + } catch (Exception e) {
  127 + throw handleException(e);
  128 + }
  129 + }
  130 +
74 131 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
75 132 @RequestMapping(value = "/user", method = RequestMethod.POST)
76 133 @ResponseBody
... ...
... ... @@ -66,12 +66,16 @@ plugins:
66 66 # Comma seperated package list used during classpath scanning for plugins
67 67 scan_packages: "${PLUGINS_SCAN_PACKAGES:org.thingsboard.server.extensions,org.thingsboard.rule.engine}"
68 68
69   -# JWT Token parameters
70   -security.jwt:
71   - tokenExpirationTime: "${JWT_TOKEN_EXPIRATION_TIME:900}" # Number of seconds (15 mins)
72   - refreshTokenExpTime: "${JWT_REFRESH_TOKEN_EXPIRATION_TIME:3600}" # Seconds (1 hour)
73   - tokenIssuer: "${JWT_TOKEN_ISSUER:thingsboard.io}"
74   - tokenSigningKey: "${JWT_TOKEN_SIGNING_KEY:thingsboardDefaultSigningKey}"
  69 +# Security parameters
  70 +security:
  71 + # JWT Token parameters
  72 + jwt:
  73 + tokenExpirationTime: "${JWT_TOKEN_EXPIRATION_TIME:900}" # Number of seconds (15 mins)
  74 + refreshTokenExpTime: "${JWT_REFRESH_TOKEN_EXPIRATION_TIME:3600}" # Seconds (1 hour)
  75 + tokenIssuer: "${JWT_TOKEN_ISSUER:thingsboard.io}"
  76 + tokenSigningKey: "${JWT_TOKEN_SIGNING_KEY:thingsboardDefaultSigningKey}"
  77 + # Enable/disable access to Tenant Administrators JWT token by System Administrator or Customer Users JWT token by Tenant Administrator
  78 + user_token_access_enabled: "${SECURITY_USER_TOKEN_ACCESS_ENABLED:true}"
75 79
76 80 # Device communication protocol parameters
77 81 http:
... ...
... ... @@ -27,6 +27,7 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi
27 27 currentUserDetails = null,
28 28 lastPublicDashboardId = null,
29 29 allowedDashboardIds = [],
  30 + userTokenAccessEnabled = false,
30 31 userLoaded = false;
31 32
32 33 var refreshTokenQueue = [];
... ... @@ -59,7 +60,9 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi
59 60 forceDefaultPlace: forceDefaultPlace,
60 61 updateLastPublicDashboardId: updateLastPublicDashboardId,
61 62 logout: logout,
62   - reloadUser: reloadUser
  63 + reloadUser: reloadUser,
  64 + isUserTokenAccessEnabled: isUserTokenAccessEnabled,
  65 + loginAsUser: loginAsUser
63 66 }
64 67
65 68 reloadUser();
... ... @@ -105,6 +108,7 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi
105 108 currentUser = null;
106 109 currentUserDetails = null;
107 110 lastPublicDashboardId = null;
  111 + userTokenAccessEnabled = false;
108 112 allowedDashboardIds = [];
109 113 if (!jwtToken) {
110 114 clearTokenData();
... ... @@ -299,24 +303,36 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi
299 303 } else if (currentUser) {
300 304 currentUser.authority = "ANONYMOUS";
301 305 }
  306 + var sysParamsPromise = loadSystemParams();
302 307 if (currentUser.isPublic) {
303 308 $rootScope.forceFullscreen = true;
304   - fetchAllowedDashboardIds();
  309 + sysParamsPromise.then(
  310 + () => { fetchAllowedDashboardIds(); },
  311 + () => { deferred.reject(); }
  312 + );
305 313 } else if (currentUser.userId) {
306 314 getUser(currentUser.userId, true).then(
307 315 function success(user) {
308   - currentUserDetails = user;
309   - updateUserLang();
310   - $rootScope.forceFullscreen = false;
311   - if (userForceFullscreen()) {
312   - $rootScope.forceFullscreen = true;
313   - }
314   - if ($rootScope.forceFullscreen && (currentUser.authority === 'TENANT_ADMIN' ||
315   - currentUser.authority === 'CUSTOMER_USER')) {
316   - fetchAllowedDashboardIds();
317   - } else {
318   - deferred.resolve();
319   - }
  316 + sysParamsPromise.then(
  317 + () => {
  318 + currentUserDetails = user;
  319 + updateUserLang();
  320 + $rootScope.forceFullscreen = false;
  321 + if (userForceFullscreen()) {
  322 + $rootScope.forceFullscreen = true;
  323 + }
  324 + if ($rootScope.forceFullscreen && (currentUser.authority === 'TENANT_ADMIN' ||
  325 + currentUser.authority === 'CUSTOMER_USER')) {
  326 + fetchAllowedDashboardIds();
  327 + } else {
  328 + deferred.resolve();
  329 + }
  330 + },
  331 + () => {
  332 + deferred.reject();
  333 + logout();
  334 + }
  335 + );
320 336 },
321 337 function fail() {
322 338 deferred.reject();
... ... @@ -353,6 +369,30 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi
353 369 return deferred.promise;
354 370 }
355 371
  372 + function loadIsUserTokenAccessEnabled() {
  373 + var deferred = $q.defer();
  374 + if (currentUser.authority === 'SYS_ADMIN' || currentUser.authority === 'TENANT_ADMIN') {
  375 + var url = '/api/user/tokenAccessEnabled';
  376 + $http.get(url).then(function success(response) {
  377 + userTokenAccessEnabled = response.data;
  378 + deferred.resolve(response.data);
  379 + }, function fail() {
  380 + userTokenAccessEnabled = false;
  381 + deferred.reject();
  382 + });
  383 + } else {
  384 + userTokenAccessEnabled = false;
  385 + deferred.resolve(false);
  386 + }
  387 + return deferred.promise;
  388 + }
  389 +
  390 + function loadSystemParams() {
  391 + var promises = [];
  392 + promises.push(loadIsUserTokenAccessEnabled());
  393 + return $q.all(promises);
  394 + }
  395 +
356 396 function notifyUserLoaded() {
357 397 if (!userLoaded) {
358 398 userLoaded = true;
... ... @@ -520,7 +560,7 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi
520 560 }
521 561 );
522 562 }
523   - $state.go(place, params);
  563 + $state.go(place, params, {reload: true});
524 564 } else {
525 565 $state.go('login', params);
526 566 }
... ... @@ -549,4 +589,18 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi
549 589 }
550 590 }
551 591
  592 + function isUserTokenAccessEnabled() {
  593 + return userTokenAccessEnabled;
  594 + }
  595 +
  596 + function loginAsUser(userId) {
  597 + var url = '/api/user/' + userId + '/token';
  598 + $http.get(url).then(function success(response) {
  599 + var token = response.data.token;
  600 + var refreshToken = response.data.refreshToken;
  601 + setUserFromJwtToken(token, refreshToken, true);
  602 + }, function fail() {
  603 + });
  604 + }
  605 +
552 606 }
... ...
... ... @@ -1270,7 +1270,9 @@
1270 1270 "activation-link-text": "In order to activate user use the following <a href='{{activationLink}}' target='_blank'>activation link</a> :",
1271 1271 "copy-activation-link": "Copy activation link",
1272 1272 "activation-link-copied-message": "User activation link has been copied to clipboard",
1273   - "details": "Details"
  1273 + "details": "Details",
  1274 + "login-as-tenant-admin": "Login as Tenant Admin",
  1275 + "login-as-customer-user": "Login as Customer User"
1274 1276 },
1275 1277 "value": {
1276 1278 "type": "Value type",
... ...
... ... @@ -21,6 +21,9 @@
21 21 <md-button ng-click="onResendActivation({event: $event})" ng-show="!isEdit" class="md-raised md-primary">{{
22 22 'user.resend-activation' | translate }}
23 23 </md-button>
  24 +<md-button ng-click="onLoginAsUser({event: $event})" ng-show="!isEdit && loginAsUserEnabled" class="md-raised md-primary">{{
  25 + (isTenantAdmin() ? 'user.login-as-tenant-admin' : 'user.login-as-customer-user') | translate }}
  26 +</md-button>
24 27 <md-button ng-click="onDeleteUser({event: $event})" ng-show="!isEdit" class="md-raised md-primary">{{ 'user.delete' |
25 28 translate }}
26 29 </md-button>
... ...
... ... @@ -32,6 +32,17 @@ export default function UserController(userService, toast, $scope, $mdDialog, $d
32 32 var userActionsList = [
33 33 {
34 34 onAction: function ($event, item) {
  35 + loginAsUser(item);
  36 + },
  37 + name: function() { return $translate.instant('login.login') },
  38 + details: function() { return $translate.instant(usersType === 'tenant' ? 'user.login-as-tenant-admin' : 'user.login-as-customer-user') },
  39 + icon: "login",
  40 + isEnabled: function() {
  41 + return userService.isUserTokenAccessEnabled();
  42 + }
  43 + },
  44 + {
  45 + onAction: function ($event, item) {
35 46 vm.grid.deleteItem($event, item);
36 47 },
37 48 name: function() { return $translate.instant('action.delete') },
... ... @@ -78,6 +89,7 @@ export default function UserController(userService, toast, $scope, $mdDialog, $d
78 89
79 90 vm.displayActivationLink = displayActivationLink;
80 91 vm.resendActivation = resendActivation;
  92 + vm.loginAsUser = loginAsUser;
81 93
82 94 initController();
83 95
... ... @@ -184,4 +196,8 @@ export default function UserController(userService, toast, $scope, $mdDialog, $d
184 196 toast.showSuccess($translate.instant('user.activation-email-sent-message'));
185 197 });
186 198 }
  199 +
  200 + function loginAsUser(user) {
  201 + userService.loginAsUser(user.id.id);
  202 + }
187 203 }
... ...
... ... @@ -22,18 +22,20 @@ import userFieldsetTemplate from './user-fieldset.tpl.html';
22 22 /* eslint-enable import/no-unresolved, import/default */
23 23
24 24 /*@ngInject*/
25   -export default function UserDirective($compile, $templateCache/*, dashboardService*/) {
  25 +export default function UserDirective($compile, $templateCache, userService) {
26 26 var linker = function (scope, element) {
27 27 var template = $templateCache.get(userFieldsetTemplate);
28 28 element.html(template);
29 29
30 30 scope.isTenantAdmin = function() {
31 31 return scope.user && scope.user.authority === 'TENANT_ADMIN';
32   - }
  32 + };
33 33
34 34 scope.isCustomerUser = function() {
35 35 return scope.user && scope.user.authority === 'CUSTOMER_USER';
36   - }
  36 + };
  37 +
  38 + scope.loginAsUserEnabled = userService.isUserTokenAccessEnabled();
37 39
38 40 $compile(element.contents())(scope);
39 41 }
... ... @@ -46,6 +48,7 @@ export default function UserDirective($compile, $templateCache/*, dashboardServi
46 48 theForm: '=',
47 49 onDisplayActivationLink: '&',
48 50 onResendActivation: '&',
  51 + onLoginAsUser: '&',
49 52 onDeleteUser: '&'
50 53 }
51 54 };
... ...
... ... @@ -27,6 +27,7 @@
27 27 the-form="vm.grid.detailsForm"
28 28 on-display-activation-link="vm.displayActivationLink(event, vm.grid.detailsConfig.currentItem)"
29 29 on-resend-activation="vm.resendActivation(vm.grid.detailsConfig.currentItem)"
  30 + on-login-as-user="vm.loginAsUser(vm.grid.detailsConfig.currentItem)"
30 31 on-delete-user="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-user>
31 32 </md-tab>
32 33 <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.grid.isTenantAdmin()" md-on-select="vm.grid.triggerResize()" label="{{ 'audit-log.audit-logs' | translate }}">
... ...