From cc1cb83f6aa9afb9cca004b6e9bc7faf19fff048 Mon Sep 17 00:00:00 2001 From: WillardHu Date: Thu, 30 Sep 2021 10:21:43 +0800 Subject: [PATCH] feature: implement password policies to avoid weak passwords Signed-off-by: WillardHu --- CHANGES.md | 1 + .../portal/controller/UserInfoController.java | 20 +++--- .../portal/util/UserPasswordChecker.java | 57 +++++++++++++++++ .../src/main/resources/static/i18n/en.json | 1 + .../src/main/resources/static/i18n/zh-CN.json | 1 + .../scripts/controller/UserController.js | 29 +++++++++ .../main/resources/static/user-manage.html | 3 + .../controller/UserInfoControllerTest.java | 61 +++++++++++++++++++ .../util/CommonlyUsedPwdCheckerTest.java | 50 +++++++++++++++ 9 files changed, 216 insertions(+), 7 deletions(-) create mode 100644 apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/util/UserPasswordChecker.java create mode 100644 apollo-portal/src/test/java/com/ctrip/framework/apollo/portal/controller/UserInfoControllerTest.java create mode 100644 apollo-portal/src/test/java/com/ctrip/framework/apollo/portal/util/CommonlyUsedPwdCheckerTest.java diff --git a/CHANGES.md b/CHANGES.md index 02de03547e6..bf95b084771 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -16,6 +16,7 @@ Apollo 1.10.0 * [refactor: let open api more easier to use and development](https://github.com/apolloconfig/apollo/pull/3943) * [feat(scripts): use bash to call openapi](https://github.com/apolloconfig/apollo/pull/3980) * [Support search by item](https://github.com/apolloconfig/apollo/pull/3977) +* [Implement password policies to avoid weak passwords](https://github.com/apolloconfig/apollo/pull/4008) ------------------ All issues and pull requests are [here](https://github.com/ctripcorp/apollo/milestone/8?closed=1) \ No newline at end of file diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/UserInfoController.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/UserInfoController.java index 3d5ac10d43a..17b83bab62b 100644 --- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/UserInfoController.java +++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/UserInfoController.java @@ -24,6 +24,11 @@ import com.ctrip.framework.apollo.portal.spi.UserInfoHolder; import com.ctrip.framework.apollo.portal.spi.UserService; import com.ctrip.framework.apollo.portal.spi.springsecurity.SpringSecurityUserService; +import com.ctrip.framework.apollo.portal.util.UserPasswordChecker; +import java.io.IOException; +import java.util.List; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -32,11 +37,6 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.util.List; - @RestController public class UserInfoController { @@ -53,13 +53,20 @@ public UserInfoController( this.userService = userService; } - @PreAuthorize(value = "@permissionValidator.isSuperAdmin()") @PostMapping("/users") public void createOrUpdateUser(@RequestBody UserPO user) { if (StringUtils.isContainEmpty(user.getUsername(), user.getPassword())) { throw new BadRequestException("Username and password can not be empty."); } + if (UserPasswordChecker.notMatchRegex(user.getPassword())) { + throw new BadRequestException( + "Password needs a number and lowercase letter and between 8~20 characters."); + } + if (UserPasswordChecker.isCommonlyUsed(user.getPassword())) { + throw new BadRequestException( + "Password is in a list of passwords commonly used on other websites."); + } if (userService instanceof SpringSecurityUserService) { ((SpringSecurityUserService) userService).createOrUpdate(user); @@ -91,5 +98,4 @@ public UserInfo getUserByUserId(@PathVariable String userId) { return userService.findByUserId(userId); } - } diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/util/UserPasswordChecker.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/util/UserPasswordChecker.java new file mode 100644 index 00000000000..03eeca8fddf --- /dev/null +++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/util/UserPasswordChecker.java @@ -0,0 +1,57 @@ +/* + * Copyright 2021 Apollo 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. + * + */ +package com.ctrip.framework.apollo.portal.util; + +import com.google.common.base.Strings; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Pattern; + +public class UserPasswordChecker { + + private static final Pattern PWD_PATTERN = Pattern + .compile("^(?=.*[0-9].*)(?=.*[a-z].*).{8,20}$"); + + private static final List LIST_OF_CODE_FRAGMENT = Arrays.asList( + "111", "222", "333", "444", "555", "666", "777", "888", "999", "000", + "001122", "112233", "223344", "334455", "445566", "556677", "667788", "778899", "889900", + "009988", "998877", "887766", "776655", "665544", "554433", "443322", "332211", "221100", + "0123", "1234", "2345", "3456", "4567", "5678", "6789", "7890", + "0987", "9876", "8765", "7654", "6543", "5432", "4321", "3210", + "1q2w", "2w3e", "3e4r", "5t6y", "abcd", "qwer", "asdf", "zxcv" + ); + + public static boolean notMatchRegex(String password) { + return !PWD_PATTERN.matcher(password).matches(); + } + + /** + * @return The passwrod contains code fragment or is blank. + */ + public static boolean isCommonlyUsed(String password) { + if (Strings.isNullOrEmpty(password)) { + return true; + } + for (String s : LIST_OF_CODE_FRAGMENT) { + if (password.toLowerCase().contains(s)) { + return true; + } + } + return false; + } + +} diff --git a/apollo-portal/src/main/resources/static/i18n/en.json b/apollo-portal/src/main/resources/static/i18n/en.json index f3b9ea60aaf..895ae3aa7c7 100644 --- a/apollo-portal/src/main/resources/static/i18n/en.json +++ b/apollo-portal/src/main/resources/static/i18n/en.json @@ -553,6 +553,7 @@ "UserMange.UserDisplayName": "User Display Name", "UserMange.UserNameTips": "If the user name entered does not exist, will create a new one. If it already exists, then it will be updated.", "UserMange.Pwd": "Password", + "UserMange.Random": "Random", "UserMange.Email": "Email", "UserMange.Created": "Create user successfully", "UserMange.CreateFailed": "Failed to create user", diff --git a/apollo-portal/src/main/resources/static/i18n/zh-CN.json b/apollo-portal/src/main/resources/static/i18n/zh-CN.json index 9fdb8573737..bd3dbb0312d 100644 --- a/apollo-portal/src/main/resources/static/i18n/zh-CN.json +++ b/apollo-portal/src/main/resources/static/i18n/zh-CN.json @@ -553,6 +553,7 @@ "UserMange.UserDisplayName": "用户名称", "UserMange.UserNameTips": "输入的用户名如果不存在,则新建。若已存在,则更新。", "UserMange.Pwd": "密码", + "UserMange.Random": "随机生成", "UserMange.Email": "邮箱", "UserMange.Created": "创建用户成功", "UserMange.CreateFailed": "创建用户失败", diff --git a/apollo-portal/src/main/resources/static/scripts/controller/UserController.js b/apollo-portal/src/main/resources/static/scripts/controller/UserController.js index 39738f3a692..93ea4457ea0 100644 --- a/apollo-portal/src/main/resources/static/scripts/controller/UserController.js +++ b/apollo-portal/src/main/resources/static/scripts/controller/UserController.js @@ -31,6 +31,35 @@ function UserController($scope, $window, $translate, toastr, AppUtil, UserServic }) } + var num_chars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; + var alpha_chars =[ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', + 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']; + + function randomAlphanumeric(len) { + var str = ""; + var hasNum = false, hasAlpha = false; + for (var i = 0; i < len; i++) { + var choose = Math.round(Math.random() * 9 + 1); // random 1~10 + var pos; + if (choose % 2 == 0 && i < len - 1 + // In extreme cases, add alpha to last index + || i == len - 1 && !hasNum && hasAlpha) { + pos = Math.round(Math.random() * (num_chars.length - 1)); + str += num_chars[pos]; + hasNum = true; + } else { + pos = Math.round(Math.random() * (alpha_chars.length - 1)); + str += alpha_chars[pos]; + hasAlpha = true; + } + } + return str; + } + + $scope.randomPassword = function() { + $scope.user.password = randomAlphanumeric(8); + } + $scope.createOrUpdateUser = function () { UserService.createOrUpdateUser($scope.user).then(function (result) { toastr.success($translate.instant('UserMange.Created')); diff --git a/apollo-portal/src/main/resources/static/user-manage.html b/apollo-portal/src/main/resources/static/user-manage.html index 2b56bc276cc..36ee7ae9533 100644 --- a/apollo-portal/src/main/resources/static/user-manage.html +++ b/apollo-portal/src/main/resources/static/user-manage.html @@ -72,6 +72,9 @@
+ + +