From 86515896dd4328acf415d1d4e691145dcb66bb2a Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 21 Jul 2017 17:31:59 +0300 Subject: [PATCH] TB-71: Ability to activate users without sending email. --- .../server/controller/AuthController.java | 7 +- .../server/controller/UserController.java | 32 ++++- ui/src/app/api/user.service.js | 17 ++- ui/src/app/app.run.js | 11 +- ui/src/app/components/grid.directive.js | 8 +- ui/src/app/locale/locale.constant.js | 10 +- ui/src/app/user/activation-link.controller.js | 35 ++++++ .../app/user/activation-link.dialog.tpl.html | 55 +++++++++ ui/src/app/user/add-user.controller.js | 112 ++++++++++++++++++ ui/src/app/user/add-user.tpl.html | 13 +- ui/src/app/user/index.js | 4 + ui/src/app/user/user-fieldset.tpl.html | 10 +- ui/src/app/user/user.controller.js | 28 ++++- ui/src/app/user/user.directive.js | 1 + ui/src/app/user/users.tpl.html | 1 + 15 files changed, 331 insertions(+), 13 deletions(-) create mode 100644 ui/src/app/user/activation-link.controller.js create mode 100644 ui/src/app/user/activation-link.dialog.tpl.html create mode 100644 ui/src/app/user/add-user.controller.js diff --git a/application/src/main/java/org/thingsboard/server/controller/AuthController.java b/application/src/main/java/org/thingsboard/server/controller/AuthController.java index fe29b083d6..204c670ef5 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AuthController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AuthController.java @@ -173,7 +173,12 @@ public class AuthController extends BaseController { String baseUrl = constructBaseUrl(request); String loginUrl = String.format("%s/login", baseUrl); String email = user.getEmail(); - mailService.sendAccountActivatedEmail(loginUrl, email); + + try { + mailService.sendAccountActivatedEmail(loginUrl, email); + } catch (Exception e) { + log.info("Unable to send account activation email [{}]", e.getMessage()); + } JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser); JwtToken refreshToken = refreshTokenRepository.requestRefreshToken(securityUser); 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 124c8e03b5..f80c7c6a31 100644 --- a/application/src/main/java/org/thingsboard/server/controller/UserController.java +++ b/application/src/main/java/org/thingsboard/server/controller/UserController.java @@ -63,6 +63,7 @@ public class UserController extends BaseController { @RequestMapping(value = "/user", method = RequestMethod.POST) @ResponseBody public User saveUser(@RequestBody User user, + @RequestParam(required = false, defaultValue = "true") boolean sendActivationMail, HttpServletRequest request) throws ThingsboardException { try { SecurityUser authUser = getCurrentUser(); @@ -70,7 +71,7 @@ public class UserController extends BaseController { throw new ThingsboardException("You don't have permission to perform this operation!", ThingsboardErrorCode.PERMISSION_DENIED); } - boolean sendEmail = user.getId() == null; + boolean sendEmail = user.getId() == null && sendActivationMail; if (getCurrentUser().getAuthority() == Authority.TENANT_ADMIN) { user.setTenantId(getCurrentUser().getTenantId()); } @@ -116,6 +117,35 @@ public class UserController extends BaseController { } } + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + @RequestMapping(value = "/user/{userId}/activationLink", method = RequestMethod.GET, produces = "text/plain") + @ResponseBody + public String getActivationLink( + @PathVariable("userId") String strUserId, + HttpServletRequest request) throws ThingsboardException { + checkParameter("userId", strUserId); + try { + UserId userId = new UserId(toUUID(strUserId)); + SecurityUser authUser = getCurrentUser(); + if (authUser.getAuthority() == Authority.CUSTOMER_USER && !authUser.getId().equals(userId)) { + throw new ThingsboardException("You don't have permission to perform this operation!", + ThingsboardErrorCode.PERMISSION_DENIED); + } + User user = checkUserId(userId); + UserCredentials userCredentials = userService.findUserCredentialsByUserId(user.getId()); + if (!userCredentials.isEnabled()) { + String baseUrl = constructBaseUrl(request); + String activateUrl = String.format("%s/api/noauth/activate?activateToken=%s", baseUrl, + userCredentials.getActivateToken()); + return activateUrl; + } else { + throw new ThingsboardException("User is already active!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); + } + } catch (Exception e) { + throw handleException(e); + } + } + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") @RequestMapping(value = "/user/{userId}", method = RequestMethod.DELETE) @ResponseStatus(value = HttpStatus.OK) diff --git a/ui/src/app/api/user.service.js b/ui/src/app/api/user.service.js index a5bb36c4ca..3e552dc196 100644 --- a/ui/src/app/api/user.service.js +++ b/ui/src/app/api/user.service.js @@ -45,6 +45,7 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi isUserLoaded: isUserLoaded, saveUser: saveUser, sendActivationEmail: sendActivationEmail, + getActivationLink: getActivationLink, setUserFromJwtToken: setUserFromJwtToken, getJwtToken: getJwtToken, clearJwtToken: clearJwtToken, @@ -397,9 +398,12 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi return deferred.promise; } - function saveUser(user) { + function saveUser(user, sendActivationMail) { var deferred = $q.defer(); var url = '/api/user'; + if (angular.isDefined(sendActivationMail)) { + url += '?sendActivationMail=' + sendActivationMail; + } $http.post(url, user).then(function success(response) { deferred.resolve(response.data); }, function fail(response) { @@ -441,6 +445,17 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi return deferred.promise; } + function getActivationLink(userId) { + var deferred = $q.defer(); + var url = `/api/user/${userId}/activationLink` + $http.get(url).then(function success(response) { + deferred.resolve(response.data); + }, function fail() { + deferred.reject(); + }); + return deferred.promise; + } + function forceDefaultPlace(to, params) { if (currentUser && isAuthenticated()) { if (currentUser.authority === 'TENANT_ADMIN' || currentUser.authority === 'CUSTOMER_USER') { diff --git a/ui/src/app/app.run.js b/ui/src/app/app.run.js index 1e36600427..2ab5fe2bd5 100644 --- a/ui/src/app/app.run.js +++ b/ui/src/app/app.run.js @@ -74,6 +74,11 @@ export default function AppRun($rootScope, $window, $injector, $location, $log, var locationSearch = $location.search(); var publicId = locationSearch.publicId; + var activateToken = locationSearch.activateToken; + + if (to.url === '/createPassword?activateToken' && activateToken && activateToken.length) { + userService.setUserFromJwtToken(null, null, false); + } if (userService.isUserLoaded() === true) { if (userService.isAuthenticated()) { @@ -124,7 +129,7 @@ export default function AppRun($rootScope, $window, $injector, $location, $log, } }) - $rootScope.pageTitle = 'Thingsboard'; + $rootScope.pageTitle = 'ThingsBoard'; $rootScope.stateChangeSuccessHandle = $rootScope.$on('$stateChangeSuccess', function (evt, to, params) { if (userService.isPublic() && to.name === 'home.dashboards.dashboard') { @@ -133,9 +138,9 @@ export default function AppRun($rootScope, $window, $injector, $location, $log, } if (angular.isDefined(to.data.pageTitle)) { $translate(to.data.pageTitle).then(function (translation) { - $rootScope.pageTitle = 'Thingsboard | ' + translation; + $rootScope.pageTitle = 'ThingsBoard | ' + translation; }, function (translationId) { - $rootScope.pageTitle = 'Thingsboard | ' + translationId; + $rootScope.pageTitle = 'ThingsBoard | ' + translationId; }); } }) diff --git a/ui/src/app/components/grid.directive.js b/ui/src/app/components/grid.directive.js index 422501bb0f..69f8e7ede3 100644 --- a/ui/src/app/components/grid.directive.js +++ b/ui/src/app/components/grid.directive.js @@ -26,6 +26,7 @@ import gridTemplate from './grid.tpl.html'; export default angular.module('thingsboard.directives.grid', [thingsboardScopeElement, thingsboardDetailsSidenav]) .directive('tbGrid', Grid) + .controller('AddItemController', AddItemController) .controller('ItemCardController', ItemCardController) .directive('tbGridCardContent', GridCardContent) .filter('range', RangeFilter) @@ -342,6 +343,11 @@ function GridController($scope, $state, $mdDialog, $document, $q, $timeout, $tra } else { vm.itemCardController = 'ItemCardController'; } + if (vm.config.addItemController) { + vm.addItemController = vm.config.addItemController; + } else { + vm.addItemController = 'AddItemController'; + } vm.parentCtl = vm.config.parentCtl || vm; @@ -468,7 +474,7 @@ function GridController($scope, $state, $mdDialog, $document, $q, $timeout, $tra function addItem($event) { $mdDialog.show({ - controller: AddItemController, + controller: vm.addItemController, controllerAs: 'vm', templateUrl: vm.addItemTemplateUrl, parent: angular.element($document[0].body), diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js index 2a33d96b2c..d5f8dd2ff5 100644 --- a/ui/src/app/locale/locale.constant.js +++ b/ui/src/app/locale/locale.constant.js @@ -1037,6 +1037,7 @@ export default angular.module('thingsboard.locale', []) "resend-activation": "Resend activation", "email": "Email", "email-required": "Email is required.", + "invalid-email-format": "Invalid email format.", "first-name": "First Name", "last-name": "Last Name", "description": "Description", @@ -1044,7 +1045,14 @@ export default angular.module('thingsboard.locale', []) "always-fullscreen": "Always fullscreen", "select-user": "Select user", "no-users-matching": "No users matching '{{entity}}' were found.", - "user-required": "User is required" + "user-required": "User is required", + "activation-method": "Activation method", + "display-activation-link": "Display activation link", + "send-activation-mail": "Send activation mail", + "activation-link": "User activation link", + "activation-link-text": "In order to activate user use the following activation link :", + "copy-activation-link": "Copy activation link", + "activation-link-copied-message": "User activation link has been copied to clipboard" }, "value": { "type": "Value type", diff --git a/ui/src/app/user/activation-link.controller.js b/ui/src/app/user/activation-link.controller.js new file mode 100644 index 0000000000..9fb76c54f4 --- /dev/null +++ b/ui/src/app/user/activation-link.controller.js @@ -0,0 +1,35 @@ +/* + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/*@ngInject*/ +export default function ActivationLinkDialogController($mdDialog, $translate, toast, activationLink) { + + var vm = this; + + vm.activationLink = activationLink; + + vm.onActivationLinkCopied = onActivationLinkCopied; + vm.close = close; + + function onActivationLinkCopied(){ + toast.showSuccess($translate.instant('user.activation-link-copied-message'), 750, angular.element('#activation-link-dialog-content'), 'bottom left'); + } + + function close() { + $mdDialog.hide(); + } + +} \ No newline at end of file diff --git a/ui/src/app/user/activation-link.dialog.tpl.html b/ui/src/app/user/activation-link.dialog.tpl.html new file mode 100644 index 0000000000..3f55d1d446 --- /dev/null +++ b/ui/src/app/user/activation-link.dialog.tpl.html @@ -0,0 +1,55 @@ + + +
+ +
+

+ + + + +
+
+ + + + + + {{ 'action.ok' | + translate }} + + +
+
diff --git a/ui/src/app/user/add-user.controller.js b/ui/src/app/user/add-user.controller.js new file mode 100644 index 0000000000..07d4751cbf --- /dev/null +++ b/ui/src/app/user/add-user.controller.js @@ -0,0 +1,112 @@ +/* + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* eslint-disable import/no-unresolved, import/default */ + +import activationLinkDialogTemplate from './activation-link.dialog.tpl.html'; + +/* eslint-enable import/no-unresolved, import/default */ + + +/*@ngInject*/ +export default function AddUserController($scope, $mdDialog, $state, $stateParams, $document, $q, types, userService, saveItemFunction, helpLinks) { + + var vm = this; + + var tenantId = $stateParams.tenantId; + var customerId = $stateParams.customerId; + var usersType = $state.$current.data.usersType; + + vm.helpLinks = helpLinks; + vm.item = {}; + + vm.activationMethods = [ + { + value: 'displayActivationLink', + name: 'user.display-activation-link' + }, + { + value: 'sendActivationMail', + name: 'user.send-activation-mail' + } + ]; + + vm.userActivationMethod = 'displayActivationLink'; + + vm.add = add; + vm.cancel = cancel; + + function cancel() { + $mdDialog.cancel(); + } + + function add($event) { + var sendActivationMail = false; + if (vm.userActivationMethod == 'sendActivationMail') { + sendActivationMail = true; + } + if (usersType === 'tenant') { + vm.item.authority = "TENANT_ADMIN"; + vm.item.tenantId = { + entityType: types.entityType.tenant, + id: tenantId + }; + } else if (usersType === 'customer') { + vm.item.authority = "CUSTOMER_USER"; + vm.item.customerId = { + entityType: types.entityType.customer, + id: customerId + }; + } + userService.saveUser(vm.item, sendActivationMail).then(function success(item) { + vm.item = item; + $scope.theForm.$setPristine(); + if (vm.userActivationMethod == 'displayActivationLink') { + userService.getActivationLink(vm.item.id.id).then( + function success(activationLink) { + displayActivationLink($event, activationLink).then( + function() { + $mdDialog.hide(); + } + ); + } + ); + } else { + $mdDialog.hide(); + } + }); + } + + function displayActivationLink($event, activationLink) { + var deferred = $q.defer(); + $mdDialog.show({ + controller: 'ActivationLinkDialogController', + controllerAs: 'vm', + templateUrl: activationLinkDialogTemplate, + locals: { + activationLink: activationLink + }, + parent: angular.element($document[0].body), + fullscreen: true, + skipHide: true, + targetEvent: $event + }).then(function () { + deferred.resolve(); + }); + return deferred.promise; + } + +} \ No newline at end of file diff --git a/ui/src/app/user/add-user.tpl.html b/ui/src/app/user/add-user.tpl.html index 8499368a49..4883394c7e 100644 --- a/ui/src/app/user/add-user.tpl.html +++ b/ui/src/app/user/add-user.tpl.html @@ -15,8 +15,8 @@ limitations under the License. --> - -
+ +

user.add

@@ -32,6 +32,15 @@
+ + + + + {{activationMethod.name | translate}} + + +
diff --git a/ui/src/app/user/index.js b/ui/src/app/user/index.js index e3b40b6c02..ae7c13f479 100644 --- a/ui/src/app/user/index.js +++ b/ui/src/app/user/index.js @@ -20,6 +20,8 @@ import thingsboardToast from '../services/toast'; import UserRoutes from './user.routes'; import UserController from './user.controller'; +import AddUserController from './add-user.controller'; +import ActivationLinkDialogController from './activation-link.controller'; import UserDirective from './user.directive'; export default angular.module('thingsboard.user', [ @@ -30,5 +32,7 @@ export default angular.module('thingsboard.user', [ ]) .config(UserRoutes) .controller('UserController', UserController) + .controller('AddUserController', AddUserController) + .controller('ActivationLinkDialogController', ActivationLinkDialogController) .directive('tbUser', UserDirective) .name; diff --git a/ui/src/app/user/user-fieldset.tpl.html b/ui/src/app/user/user-fieldset.tpl.html index a2559ff98a..9ae097fc6f 100644 --- a/ui/src/app/user/user-fieldset.tpl.html +++ b/ui/src/app/user/user-fieldset.tpl.html @@ -15,6 +15,9 @@ limitations under the License. --> +{{ + 'user.display-activation-link' | translate }} + {{ 'user.resend-activation' | translate }} @@ -26,9 +29,12 @@
- +
user.email-required
+
user.invalid-email-format
@@ -43,7 +49,7 @@ -
+
user.default-dashboard