thingsboard-aplcache
Changes
ui/src/app/api/user.service.js 84(+69 -15)
ui/src/app/user/user.controller.js 16(+16 -0)
ui/src/app/user/user.directive.js 9(+6 -3)
ui/src/app/user/users.tpl.html 1(+1 -0)
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:
ui/src/app/api/user.service.js 84(+69 -15)
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",
ui/src/app/user/user.controller.js 16(+16 -0)
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);
+ }
}
ui/src/app/user/user.directive.js 9(+6 -3)
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>
ui/src/app/user/users.tpl.html 1(+1 -0)
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 }}">