Browse Source

Login As User feature.

pull/1025/head
Igor Kulikov 8 years ago
parent
commit
796294f33f
  1. 57
      application/src/main/java/org/thingsboard/server/controller/UserController.java
  2. 16
      application/src/main/resources/thingsboard.yml
  3. 84
      ui/src/app/api/user.service.js
  4. 4
      ui/src/app/locale/locale.constant-en_US.json
  5. 3
      ui/src/app/user/user-fieldset.tpl.html
  6. 16
      ui/src/app/user/user.controller.js
  7. 9
      ui/src/app/user/user.directive.js
  8. 1
      ui/src/app/user/users.tpl.html

57
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

16
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:

84
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() {
});
}
}

4
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",

3
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>

16
ui/src/app/user/user.controller.js

@ -30,6 +30,17 @@ export default function UserController(userService, toast, $scope, $mdDialog, $d
var usersType = $state.$current.data.usersType;
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);
@ -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);
}
}

9
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: '&'
}
};

1
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 }}">

Loading…
Cancel
Save