thingsboard-aplcache

Login As User feature.

8/17/2018 9:59:14 AM

Details

diff --git a/application/src/main/java/org/thingsboard/server/controller/UserController.java b/application/src/main/java/org/thingsboard/server/controller/UserController.java
index 5f6c1ce..a050fa3 100644
--- a/application/src/main/java/org/thingsboard/server/controller/UserController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/UserController.java
@@ -15,7 +15,12 @@
  */
 package org.thingsboard.server.controller;
 
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import lombok.Getter;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.http.HttpStatus;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.PathVariable;
@@ -39,7 +44,11 @@ import org.thingsboard.server.common.data.page.TextPageData;
 import org.thingsboard.server.common.data.page.TextPageLink;
 import org.thingsboard.server.common.data.security.Authority;
 import org.thingsboard.server.common.data.security.UserCredentials;
+import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository;
 import org.thingsboard.server.service.security.model.SecurityUser;
+import org.thingsboard.server.service.security.model.UserPrincipal;
+import org.thingsboard.server.service.security.model.token.JwtToken;
+import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
 
 import javax.servlet.http.HttpServletRequest;
 
@@ -50,9 +59,21 @@ public class UserController extends BaseController {
     public static final String USER_ID = "userId";
     public static final String YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION = "You don't have permission to perform this operation!";
     public static final String ACTIVATE_URL_PATTERN = "%s/api/noauth/activate?activateToken=%s";
+
+    @Value("${security.user_token_access_enabled}")
+    @Getter
+    private boolean userTokenAccessEnabled;
+
     @Autowired
     private MailService mailService;
 
+    @Autowired
+    private JwtTokenFactory tokenFactory;
+
+    @Autowired
+    private RefreshTokenRepository refreshTokenRepository;
+
+
     @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
     @RequestMapping(value = "/user/{userId}", method = RequestMethod.GET)
     @ResponseBody
@@ -71,6 +92,42 @@ public class UserController extends BaseController {
         }
     }
 
+    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
+    @RequestMapping(value = "/user/tokenAccessEnabled", method = RequestMethod.GET)
+    @ResponseBody
+    public boolean isUserTokenAccessEnabled() {
+        return userTokenAccessEnabled;
+    }
+
+    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
+    @RequestMapping(value = "/user/{userId}/token", method = RequestMethod.GET)
+    @ResponseBody
+    public JsonNode getUserToken(@PathVariable(USER_ID) String strUserId) throws ThingsboardException {
+        checkParameter(USER_ID, strUserId);
+        try {
+            UserId userId = new UserId(toUUID(strUserId));
+            SecurityUser authUser = getCurrentUser();
+            User user = userService.findUserById(userId);
+            if (!userTokenAccessEnabled || (authUser.getAuthority() == Authority.SYS_ADMIN && user.getAuthority() != Authority.TENANT_ADMIN)
+                    || (authUser.getAuthority() == Authority.TENANT_ADMIN && !authUser.getTenantId().equals(user.getTenantId()))) {
+                throw new ThingsboardException(YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION,
+                        ThingsboardErrorCode.PERMISSION_DENIED);
+            }
+            UserPrincipal principal = new UserPrincipal(UserPrincipal.Type.USER_NAME, user.getEmail());
+            UserCredentials credentials = userService.findUserCredentialsByUserId(userId);
+            SecurityUser securityUser = new SecurityUser(user, credentials.isEnabled(), principal);
+            JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser);
+            JwtToken refreshToken = refreshTokenRepository.requestRefreshToken(securityUser);
+            ObjectMapper objectMapper = new ObjectMapper();
+            ObjectNode tokenObject = objectMapper.createObjectNode();
+            tokenObject.put("token", accessToken.getToken());
+            tokenObject.put("refreshToken", refreshToken.getToken());
+            return tokenObject;
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
     @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
     @RequestMapping(value = "/user", method = RequestMethod.POST)
     @ResponseBody 
diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml
index f510ec4..3e62453 100644
--- a/application/src/main/resources/thingsboard.yml
+++ b/application/src/main/resources/thingsboard.yml
@@ -66,12 +66,16 @@ plugins:
   # Comma seperated package list used during classpath scanning for plugins
   scan_packages: "${PLUGINS_SCAN_PACKAGES:org.thingsboard.server.extensions,org.thingsboard.rule.engine}"
 
-# JWT Token parameters
-security.jwt:
-  tokenExpirationTime: "${JWT_TOKEN_EXPIRATION_TIME:900}" # Number of seconds (15 mins)
-  refreshTokenExpTime: "${JWT_REFRESH_TOKEN_EXPIRATION_TIME:3600}" # Seconds (1 hour)
-  tokenIssuer: "${JWT_TOKEN_ISSUER:thingsboard.io}"
-  tokenSigningKey: "${JWT_TOKEN_SIGNING_KEY:thingsboardDefaultSigningKey}"
+# Security parameters
+security:
+  # JWT Token parameters
+  jwt:
+    tokenExpirationTime: "${JWT_TOKEN_EXPIRATION_TIME:900}" # Number of seconds (15 mins)
+    refreshTokenExpTime: "${JWT_REFRESH_TOKEN_EXPIRATION_TIME:3600}" # Seconds (1 hour)
+    tokenIssuer: "${JWT_TOKEN_ISSUER:thingsboard.io}"
+    tokenSigningKey: "${JWT_TOKEN_SIGNING_KEY:thingsboardDefaultSigningKey}"
+  # Enable/disable access to Tenant Administrators JWT token by System Administrator or Customer Users JWT token by Tenant Administrator
+  user_token_access_enabled: "${SECURITY_USER_TOKEN_ACCESS_ENABLED:true}"
 
 # Device communication protocol parameters
 http:
diff --git a/ui/src/app/api/user.service.js b/ui/src/app/api/user.service.js
index 48c811b..fa4d63c 100644
--- a/ui/src/app/api/user.service.js
+++ b/ui/src/app/api/user.service.js
@@ -27,6 +27,7 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi
         currentUserDetails = null,
         lastPublicDashboardId = null,
         allowedDashboardIds = [],
+        userTokenAccessEnabled = false,
         userLoaded = false;
 
     var refreshTokenQueue = [];
@@ -59,7 +60,9 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi
         forceDefaultPlace: forceDefaultPlace,
         updateLastPublicDashboardId: updateLastPublicDashboardId,
         logout: logout,
-        reloadUser: reloadUser
+        reloadUser: reloadUser,
+        isUserTokenAccessEnabled: isUserTokenAccessEnabled,
+        loginAsUser: loginAsUser
     }
 
     reloadUser();
@@ -105,6 +108,7 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi
         currentUser = null;
         currentUserDetails = null;
         lastPublicDashboardId = null;
+        userTokenAccessEnabled = false;
         allowedDashboardIds = [];
         if (!jwtToken) {
             clearTokenData();
@@ -299,24 +303,36 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi
                 } else if (currentUser) {
                     currentUser.authority = "ANONYMOUS";
                 }
+                var sysParamsPromise = loadSystemParams();
                 if (currentUser.isPublic) {
                     $rootScope.forceFullscreen = true;
-                    fetchAllowedDashboardIds();
+                    sysParamsPromise.then(
+                        () => { fetchAllowedDashboardIds(); },
+                        () => { deferred.reject(); }
+                    );
                 } else if (currentUser.userId) {
                     getUser(currentUser.userId, true).then(
                         function success(user) {
-                            currentUserDetails = user;
-                            updateUserLang();
-                            $rootScope.forceFullscreen = false;
-                            if (userForceFullscreen()) {
-                                $rootScope.forceFullscreen = true;
-                            }
-                            if ($rootScope.forceFullscreen && (currentUser.authority === 'TENANT_ADMIN' ||
-                                currentUser.authority === 'CUSTOMER_USER')) {
-                                fetchAllowedDashboardIds();
-                            } else {
-                                deferred.resolve();
-                            }
+                            sysParamsPromise.then(
+                                () => {
+                                    currentUserDetails = user;
+                                    updateUserLang();
+                                    $rootScope.forceFullscreen = false;
+                                    if (userForceFullscreen()) {
+                                        $rootScope.forceFullscreen = true;
+                                    }
+                                    if ($rootScope.forceFullscreen && (currentUser.authority === 'TENANT_ADMIN' ||
+                                        currentUser.authority === 'CUSTOMER_USER')) {
+                                        fetchAllowedDashboardIds();
+                                    } else {
+                                        deferred.resolve();
+                                    }
+                                },
+                                () => {
+                                    deferred.reject();
+                                    logout();
+                                }
+                            );
                         },
                         function fail() {
                             deferred.reject();
@@ -353,6 +369,30 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi
         return deferred.promise;
     }
 
+    function loadIsUserTokenAccessEnabled() {
+        var deferred = $q.defer();
+        if (currentUser.authority === 'SYS_ADMIN' || currentUser.authority === 'TENANT_ADMIN') {
+            var url = '/api/user/tokenAccessEnabled';
+            $http.get(url).then(function success(response) {
+                userTokenAccessEnabled = response.data;
+                deferred.resolve(response.data);
+            }, function fail() {
+                userTokenAccessEnabled = false;
+                deferred.reject();
+            });
+        } else {
+            userTokenAccessEnabled = false;
+            deferred.resolve(false);
+        }
+        return deferred.promise;
+    }
+
+    function loadSystemParams() {
+        var promises = [];
+        promises.push(loadIsUserTokenAccessEnabled());
+        return $q.all(promises);
+    }
+
     function notifyUserLoaded() {
         if (!userLoaded) {
             userLoaded = true;
@@ -520,7 +560,7 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi
                     }
                 );
             }
-            $state.go(place, params);
+            $state.go(place, params, {reload: true});
         } else {
             $state.go('login', params);
         }
@@ -549,4 +589,18 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi
         }
     }
 
+    function isUserTokenAccessEnabled() {
+        return userTokenAccessEnabled;
+    }
+
+    function loginAsUser(userId) {
+        var url = '/api/user/' + userId + '/token';
+        $http.get(url).then(function success(response) {
+            var token = response.data.token;
+            var refreshToken = response.data.refreshToken;
+            setUserFromJwtToken(token, refreshToken, true);
+        }, function fail() {
+        });
+    }
+
 }
diff --git a/ui/src/app/locale/locale.constant-en_US.json b/ui/src/app/locale/locale.constant-en_US.json
index 22aad1a..a98733c 100644
--- a/ui/src/app/locale/locale.constant-en_US.json
+++ b/ui/src/app/locale/locale.constant-en_US.json
@@ -1270,7 +1270,9 @@
         "activation-link-text": "In order to activate user use the following <a href='{{activationLink}}' target='_blank'>activation link</a> :",
         "copy-activation-link": "Copy activation link",
         "activation-link-copied-message": "User activation link has been copied to clipboard",
-        "details": "Details"
+        "details": "Details",
+        "login-as-tenant-admin": "Login as Tenant Admin",
+        "login-as-customer-user": "Login as Customer User"
     },
     "value": {
         "type": "Value type",
diff --git a/ui/src/app/user/user.controller.js b/ui/src/app/user/user.controller.js
index 4fd6b25..8d7f214 100644
--- a/ui/src/app/user/user.controller.js
+++ b/ui/src/app/user/user.controller.js
@@ -32,6 +32,17 @@ export default function UserController(userService, toast, $scope, $mdDialog, $d
     var userActionsList = [
         {
             onAction: function ($event, item) {
+                loginAsUser(item);
+            },
+            name: function() { return $translate.instant('login.login') },
+            details: function() { return $translate.instant(usersType === 'tenant' ? 'user.login-as-tenant-admin' : 'user.login-as-customer-user') },
+            icon: "login",
+            isEnabled: function() {
+                return userService.isUserTokenAccessEnabled();
+            }
+        },
+        {
+            onAction: function ($event, item) {
                 vm.grid.deleteItem($event, item);
             },
             name: function() { return $translate.instant('action.delete') },
@@ -78,6 +89,7 @@ export default function UserController(userService, toast, $scope, $mdDialog, $d
 
     vm.displayActivationLink = displayActivationLink;
     vm.resendActivation = resendActivation;
+    vm.loginAsUser = loginAsUser;
 
     initController();
 
@@ -184,4 +196,8 @@ export default function UserController(userService, toast, $scope, $mdDialog, $d
             toast.showSuccess($translate.instant('user.activation-email-sent-message'));
         });
     }
+
+    function loginAsUser(user) {
+        userService.loginAsUser(user.id.id);
+    }
 }
diff --git a/ui/src/app/user/user.directive.js b/ui/src/app/user/user.directive.js
index 1c01c8f..b32d028 100644
--- a/ui/src/app/user/user.directive.js
+++ b/ui/src/app/user/user.directive.js
@@ -22,18 +22,20 @@ import userFieldsetTemplate from './user-fieldset.tpl.html';
 /* eslint-enable import/no-unresolved, import/default */
 
 /*@ngInject*/
-export default function UserDirective($compile, $templateCache/*, dashboardService*/) {
+export default function UserDirective($compile, $templateCache, userService) {
     var linker = function (scope, element) {
         var template = $templateCache.get(userFieldsetTemplate);
         element.html(template);
 
         scope.isTenantAdmin = function() {
             return scope.user && scope.user.authority === 'TENANT_ADMIN';
-        }
+        };
 
         scope.isCustomerUser = function() {
             return scope.user && scope.user.authority === 'CUSTOMER_USER';
-        }
+        };
+
+        scope.loginAsUserEnabled = userService.isUserTokenAccessEnabled();
 
         $compile(element.contents())(scope);
     }
@@ -46,6 +48,7 @@ export default function UserDirective($compile, $templateCache/*, dashboardServi
             theForm: '=',
             onDisplayActivationLink: '&',
             onResendActivation: '&',
+            onLoginAsUser: '&',
             onDeleteUser: '&'
         }
     };
diff --git a/ui/src/app/user/user-fieldset.tpl.html b/ui/src/app/user/user-fieldset.tpl.html
index 15b32ec..d3b5111 100644
--- a/ui/src/app/user/user-fieldset.tpl.html
+++ b/ui/src/app/user/user-fieldset.tpl.html
@@ -21,6 +21,9 @@
 <md-button ng-click="onResendActivation({event: $event})" ng-show="!isEdit" class="md-raised md-primary">{{
     'user.resend-activation' | translate }}
 </md-button>
+<md-button ng-click="onLoginAsUser({event: $event})" ng-show="!isEdit && loginAsUserEnabled" class="md-raised md-primary">{{
+    (isTenantAdmin() ? 'user.login-as-tenant-admin' : 'user.login-as-customer-user') | translate }}
+</md-button>
 <md-button ng-click="onDeleteUser({event: $event})" ng-show="!isEdit" class="md-raised md-primary">{{ 'user.delete' |
     translate }}
 </md-button>
diff --git a/ui/src/app/user/users.tpl.html b/ui/src/app/user/users.tpl.html
index b73b5a6..a311d98 100644
--- a/ui/src/app/user/users.tpl.html
+++ b/ui/src/app/user/users.tpl.html
@@ -27,6 +27,7 @@
 					 the-form="vm.grid.detailsForm"
 					 on-display-activation-link="vm.displayActivationLink(event, vm.grid.detailsConfig.currentItem)"
 					 on-resend-activation="vm.resendActivation(vm.grid.detailsConfig.currentItem)"
+                     on-login-as-user="vm.loginAsUser(vm.grid.detailsConfig.currentItem)"
 					 on-delete-user="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-user>
 		</md-tab>
 		<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.grid.isTenantAdmin()" md-on-select="vm.grid.triggerResize()" label="{{ 'audit-log.audit-logs' | translate }}">