Showing
8 changed files
with
165 additions
and
25 deletions
@@ -15,7 +15,12 @@ | @@ -15,7 +15,12 @@ | ||
15 | */ | 15 | */ |
16 | package org.thingsboard.server.controller; | 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 | import org.springframework.beans.factory.annotation.Autowired; | 22 | import org.springframework.beans.factory.annotation.Autowired; |
23 | +import org.springframework.beans.factory.annotation.Value; | ||
19 | import org.springframework.http.HttpStatus; | 24 | import org.springframework.http.HttpStatus; |
20 | import org.springframework.security.access.prepost.PreAuthorize; | 25 | import org.springframework.security.access.prepost.PreAuthorize; |
21 | import org.springframework.web.bind.annotation.PathVariable; | 26 | import org.springframework.web.bind.annotation.PathVariable; |
@@ -39,7 +44,11 @@ import org.thingsboard.server.common.data.page.TextPageData; | @@ -39,7 +44,11 @@ import org.thingsboard.server.common.data.page.TextPageData; | ||
39 | import org.thingsboard.server.common.data.page.TextPageLink; | 44 | import org.thingsboard.server.common.data.page.TextPageLink; |
40 | import org.thingsboard.server.common.data.security.Authority; | 45 | import org.thingsboard.server.common.data.security.Authority; |
41 | import org.thingsboard.server.common.data.security.UserCredentials; | 46 | import org.thingsboard.server.common.data.security.UserCredentials; |
47 | +import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository; | ||
42 | import org.thingsboard.server.service.security.model.SecurityUser; | 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 | import javax.servlet.http.HttpServletRequest; | 53 | import javax.servlet.http.HttpServletRequest; |
45 | 54 | ||
@@ -50,9 +59,21 @@ public class UserController extends BaseController { | @@ -50,9 +59,21 @@ public class UserController extends BaseController { | ||
50 | public static final String USER_ID = "userId"; | 59 | public static final String USER_ID = "userId"; |
51 | public static final String YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION = "You don't have permission to perform this operation!"; | 60 | public static final String YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION = "You don't have permission to perform this operation!"; |
52 | public static final String ACTIVATE_URL_PATTERN = "%s/api/noauth/activate?activateToken=%s"; | 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 | @Autowired | 67 | @Autowired |
54 | private MailService mailService; | 68 | private MailService mailService; |
55 | 69 | ||
70 | + @Autowired | ||
71 | + private JwtTokenFactory tokenFactory; | ||
72 | + | ||
73 | + @Autowired | ||
74 | + private RefreshTokenRepository refreshTokenRepository; | ||
75 | + | ||
76 | + | ||
56 | @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") | 77 | @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") |
57 | @RequestMapping(value = "/user/{userId}", method = RequestMethod.GET) | 78 | @RequestMapping(value = "/user/{userId}", method = RequestMethod.GET) |
58 | @ResponseBody | 79 | @ResponseBody |
@@ -71,6 +92,42 @@ public class UserController extends BaseController { | @@ -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 | @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") | 131 | @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") |
75 | @RequestMapping(value = "/user", method = RequestMethod.POST) | 132 | @RequestMapping(value = "/user", method = RequestMethod.POST) |
76 | @ResponseBody | 133 | @ResponseBody |
@@ -66,12 +66,16 @@ plugins: | @@ -66,12 +66,16 @@ plugins: | ||
66 | # Comma seperated package list used during classpath scanning for plugins | 66 | # Comma seperated package list used during classpath scanning for plugins |
67 | scan_packages: "${PLUGINS_SCAN_PACKAGES:org.thingsboard.server.extensions,org.thingsboard.rule.engine}" | 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 | # Device communication protocol parameters | 80 | # Device communication protocol parameters |
77 | http: | 81 | http: |
@@ -27,6 +27,7 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi | @@ -27,6 +27,7 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi | ||
27 | currentUserDetails = null, | 27 | currentUserDetails = null, |
28 | lastPublicDashboardId = null, | 28 | lastPublicDashboardId = null, |
29 | allowedDashboardIds = [], | 29 | allowedDashboardIds = [], |
30 | + userTokenAccessEnabled = false, | ||
30 | userLoaded = false; | 31 | userLoaded = false; |
31 | 32 | ||
32 | var refreshTokenQueue = []; | 33 | var refreshTokenQueue = []; |
@@ -59,7 +60,9 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi | @@ -59,7 +60,9 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi | ||
59 | forceDefaultPlace: forceDefaultPlace, | 60 | forceDefaultPlace: forceDefaultPlace, |
60 | updateLastPublicDashboardId: updateLastPublicDashboardId, | 61 | updateLastPublicDashboardId: updateLastPublicDashboardId, |
61 | logout: logout, | 62 | logout: logout, |
62 | - reloadUser: reloadUser | 63 | + reloadUser: reloadUser, |
64 | + isUserTokenAccessEnabled: isUserTokenAccessEnabled, | ||
65 | + loginAsUser: loginAsUser | ||
63 | } | 66 | } |
64 | 67 | ||
65 | reloadUser(); | 68 | reloadUser(); |
@@ -105,6 +108,7 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi | @@ -105,6 +108,7 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi | ||
105 | currentUser = null; | 108 | currentUser = null; |
106 | currentUserDetails = null; | 109 | currentUserDetails = null; |
107 | lastPublicDashboardId = null; | 110 | lastPublicDashboardId = null; |
111 | + userTokenAccessEnabled = false; | ||
108 | allowedDashboardIds = []; | 112 | allowedDashboardIds = []; |
109 | if (!jwtToken) { | 113 | if (!jwtToken) { |
110 | clearTokenData(); | 114 | clearTokenData(); |
@@ -299,24 +303,36 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi | @@ -299,24 +303,36 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi | ||
299 | } else if (currentUser) { | 303 | } else if (currentUser) { |
300 | currentUser.authority = "ANONYMOUS"; | 304 | currentUser.authority = "ANONYMOUS"; |
301 | } | 305 | } |
306 | + var sysParamsPromise = loadSystemParams(); | ||
302 | if (currentUser.isPublic) { | 307 | if (currentUser.isPublic) { |
303 | $rootScope.forceFullscreen = true; | 308 | $rootScope.forceFullscreen = true; |
304 | - fetchAllowedDashboardIds(); | 309 | + sysParamsPromise.then( |
310 | + () => { fetchAllowedDashboardIds(); }, | ||
311 | + () => { deferred.reject(); } | ||
312 | + ); | ||
305 | } else if (currentUser.userId) { | 313 | } else if (currentUser.userId) { |
306 | getUser(currentUser.userId, true).then( | 314 | getUser(currentUser.userId, true).then( |
307 | function success(user) { | 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 | function fail() { | 337 | function fail() { |
322 | deferred.reject(); | 338 | deferred.reject(); |
@@ -353,6 +369,30 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi | @@ -353,6 +369,30 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi | ||
353 | return deferred.promise; | 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 | function notifyUserLoaded() { | 396 | function notifyUserLoaded() { |
357 | if (!userLoaded) { | 397 | if (!userLoaded) { |
358 | userLoaded = true; | 398 | userLoaded = true; |
@@ -520,7 +560,7 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi | @@ -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 | } else { | 564 | } else { |
525 | $state.go('login', params); | 565 | $state.go('login', params); |
526 | } | 566 | } |
@@ -549,4 +589,18 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi | @@ -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,7 +1270,9 @@ | ||
1270 | "activation-link-text": "In order to activate user use the following <a href='{{activationLink}}' target='_blank'>activation link</a> :", | 1270 | "activation-link-text": "In order to activate user use the following <a href='{{activationLink}}' target='_blank'>activation link</a> :", |
1271 | "copy-activation-link": "Copy activation link", | 1271 | "copy-activation-link": "Copy activation link", |
1272 | "activation-link-copied-message": "User activation link has been copied to clipboard", | 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 | "value": { | 1277 | "value": { |
1276 | "type": "Value type", | 1278 | "type": "Value type", |
@@ -21,6 +21,9 @@ | @@ -21,6 +21,9 @@ | ||
21 | <md-button ng-click="onResendActivation({event: $event})" ng-show="!isEdit" class="md-raised md-primary">{{ | 21 | <md-button ng-click="onResendActivation({event: $event})" ng-show="!isEdit" class="md-raised md-primary">{{ |
22 | 'user.resend-activation' | translate }} | 22 | 'user.resend-activation' | translate }} |
23 | </md-button> | 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 | <md-button ng-click="onDeleteUser({event: $event})" ng-show="!isEdit" class="md-raised md-primary">{{ 'user.delete' | | 27 | <md-button ng-click="onDeleteUser({event: $event})" ng-show="!isEdit" class="md-raised md-primary">{{ 'user.delete' | |
25 | translate }} | 28 | translate }} |
26 | </md-button> | 29 | </md-button> |
@@ -32,6 +32,17 @@ export default function UserController(userService, toast, $scope, $mdDialog, $d | @@ -32,6 +32,17 @@ export default function UserController(userService, toast, $scope, $mdDialog, $d | ||
32 | var userActionsList = [ | 32 | var userActionsList = [ |
33 | { | 33 | { |
34 | onAction: function ($event, item) { | 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 | vm.grid.deleteItem($event, item); | 46 | vm.grid.deleteItem($event, item); |
36 | }, | 47 | }, |
37 | name: function() { return $translate.instant('action.delete') }, | 48 | name: function() { return $translate.instant('action.delete') }, |
@@ -78,6 +89,7 @@ export default function UserController(userService, toast, $scope, $mdDialog, $d | @@ -78,6 +89,7 @@ export default function UserController(userService, toast, $scope, $mdDialog, $d | ||
78 | 89 | ||
79 | vm.displayActivationLink = displayActivationLink; | 90 | vm.displayActivationLink = displayActivationLink; |
80 | vm.resendActivation = resendActivation; | 91 | vm.resendActivation = resendActivation; |
92 | + vm.loginAsUser = loginAsUser; | ||
81 | 93 | ||
82 | initController(); | 94 | initController(); |
83 | 95 | ||
@@ -184,4 +196,8 @@ export default function UserController(userService, toast, $scope, $mdDialog, $d | @@ -184,4 +196,8 @@ export default function UserController(userService, toast, $scope, $mdDialog, $d | ||
184 | toast.showSuccess($translate.instant('user.activation-email-sent-message')); | 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,18 +22,20 @@ import userFieldsetTemplate from './user-fieldset.tpl.html'; | ||
22 | /* eslint-enable import/no-unresolved, import/default */ | 22 | /* eslint-enable import/no-unresolved, import/default */ |
23 | 23 | ||
24 | /*@ngInject*/ | 24 | /*@ngInject*/ |
25 | -export default function UserDirective($compile, $templateCache/*, dashboardService*/) { | 25 | +export default function UserDirective($compile, $templateCache, userService) { |
26 | var linker = function (scope, element) { | 26 | var linker = function (scope, element) { |
27 | var template = $templateCache.get(userFieldsetTemplate); | 27 | var template = $templateCache.get(userFieldsetTemplate); |
28 | element.html(template); | 28 | element.html(template); |
29 | 29 | ||
30 | scope.isTenantAdmin = function() { | 30 | scope.isTenantAdmin = function() { |
31 | return scope.user && scope.user.authority === 'TENANT_ADMIN'; | 31 | return scope.user && scope.user.authority === 'TENANT_ADMIN'; |
32 | - } | 32 | + }; |
33 | 33 | ||
34 | scope.isCustomerUser = function() { | 34 | scope.isCustomerUser = function() { |
35 | return scope.user && scope.user.authority === 'CUSTOMER_USER'; | 35 | return scope.user && scope.user.authority === 'CUSTOMER_USER'; |
36 | - } | 36 | + }; |
37 | + | ||
38 | + scope.loginAsUserEnabled = userService.isUserTokenAccessEnabled(); | ||
37 | 39 | ||
38 | $compile(element.contents())(scope); | 40 | $compile(element.contents())(scope); |
39 | } | 41 | } |
@@ -46,6 +48,7 @@ export default function UserDirective($compile, $templateCache/*, dashboardServi | @@ -46,6 +48,7 @@ export default function UserDirective($compile, $templateCache/*, dashboardServi | ||
46 | theForm: '=', | 48 | theForm: '=', |
47 | onDisplayActivationLink: '&', | 49 | onDisplayActivationLink: '&', |
48 | onResendActivation: '&', | 50 | onResendActivation: '&', |
51 | + onLoginAsUser: '&', | ||
49 | onDeleteUser: '&' | 52 | onDeleteUser: '&' |
50 | } | 53 | } |
51 | }; | 54 | }; |
@@ -27,6 +27,7 @@ | @@ -27,6 +27,7 @@ | ||
27 | the-form="vm.grid.detailsForm" | 27 | the-form="vm.grid.detailsForm" |
28 | on-display-activation-link="vm.displayActivationLink(event, vm.grid.detailsConfig.currentItem)" | 28 | on-display-activation-link="vm.displayActivationLink(event, vm.grid.detailsConfig.currentItem)" |
29 | on-resend-activation="vm.resendActivation(vm.grid.detailsConfig.currentItem)" | 29 | on-resend-activation="vm.resendActivation(vm.grid.detailsConfig.currentItem)" |
30 | + on-login-as-user="vm.loginAsUser(vm.grid.detailsConfig.currentItem)" | ||
30 | on-delete-user="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-user> | 31 | on-delete-user="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-user> |
31 | </md-tab> | 32 | </md-tab> |
32 | <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.grid.isTenantAdmin()" md-on-select="vm.grid.triggerResize()" label="{{ 'audit-log.audit-logs' | translate }}"> | 33 | <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.grid.isTenantAdmin()" md-on-select="vm.grid.triggerResize()" label="{{ 'audit-log.audit-logs' | translate }}"> |