Skip to content

Commit 70f1d0c

Browse files
committed
feature: implement password policies to avoid weak passwords
Signed-off-by: WillardHu <[email protected]>
1 parent e26294f commit 70f1d0c

File tree

10 files changed

+1452
-7
lines changed

10 files changed

+1452
-7
lines changed

CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Apollo 1.10.0
1616
* [refactor: let open api more easier to use and development](https://github.com/apolloconfig/apollo/pull/3943)
1717
* [feat(scripts): use bash to call openapi](https://github.com/apolloconfig/apollo/pull/3980)
1818
* [Support search by item](https://github.com/apolloconfig/apollo/pull/3977)
19+
* [Implement password policies to avoid weak passwords](https://github.com/apolloconfig/apollo/pull/4008)
1920

2021
------------------
2122
All issues and pull requests are [here](https://github.com/ctripcorp/apollo/milestone/8?closed=1)

apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/UserInfoController.java

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@
2424
import com.ctrip.framework.apollo.portal.spi.UserInfoHolder;
2525
import com.ctrip.framework.apollo.portal.spi.UserService;
2626
import com.ctrip.framework.apollo.portal.spi.springsecurity.SpringSecurityUserService;
27+
import com.ctrip.framework.apollo.portal.util.UserPasswordChecker;
28+
import java.io.IOException;
29+
import java.util.List;
30+
import javax.servlet.http.HttpServletRequest;
31+
import javax.servlet.http.HttpServletResponse;
2732
import org.springframework.security.access.prepost.PreAuthorize;
2833
import org.springframework.web.bind.annotation.GetMapping;
2934
import org.springframework.web.bind.annotation.PathVariable;
@@ -32,11 +37,6 @@
3237
import org.springframework.web.bind.annotation.RequestParam;
3338
import org.springframework.web.bind.annotation.RestController;
3439

35-
import javax.servlet.http.HttpServletRequest;
36-
import javax.servlet.http.HttpServletResponse;
37-
import java.io.IOException;
38-
import java.util.List;
39-
4040
@RestController
4141
public class UserInfoController {
4242

@@ -53,13 +53,20 @@ public UserInfoController(
5353
this.userService = userService;
5454
}
5555

56-
5756
@PreAuthorize(value = "@permissionValidator.isSuperAdmin()")
5857
@PostMapping("/users")
5958
public void createOrUpdateUser(@RequestBody UserPO user) {
6059
if (StringUtils.isContainEmpty(user.getUsername(), user.getPassword())) {
6160
throw new BadRequestException("Username and password can not be empty.");
6261
}
62+
if (UserPasswordChecker.notMatchRegex(user.getPassword())) {
63+
throw new BadRequestException(
64+
"Password needs a number and lowercase letter and between 8~20 characters.");
65+
}
66+
if (UserPasswordChecker.isCommonlyUsed(user.getPassword())) {
67+
throw new BadRequestException(
68+
"Password is in a list of passwords commonly used on other websites.");
69+
}
6370

6471
if (userService instanceof SpringSecurityUserService) {
6572
((SpringSecurityUserService) userService).createOrUpdate(user);
@@ -91,5 +98,4 @@ public UserInfo getUserByUserId(@PathVariable String userId) {
9198
return userService.findByUserId(userId);
9299
}
93100

94-
95101
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright 2021 Apollo Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
*/
17+
package com.ctrip.framework.apollo.portal.util;
18+
19+
import com.google.common.base.Strings;
20+
import java.util.Arrays;
21+
import java.util.List;
22+
import java.util.regex.Pattern;
23+
24+
public class UserPasswordChecker {
25+
26+
private static final Pattern PWD_PATTERN = Pattern
27+
.compile("^(?=.*[0-9].*)(?=.*[a-z].*).{8,20}$");
28+
29+
private static final List<String> LIST_OF_CODE_FRAGMENT = Arrays.asList(
30+
"111", "222", "333", "444", "555", "666", "777", "888", "999", "000",
31+
"001122", "112233", "223344", "334455", "445566", "556677", "667788", "778899", "889900",
32+
"009988", "998877", "887766", "776655", "665544", "554433", "443322", "332211", "221100",
33+
"0123", "1234", "2345", "3456", "4567", "5678", "6789", "7890",
34+
"0987", "9876", "8765", "7654", "6543", "5432", "4321", "3210",
35+
"1q2w", "2w3e", "3e4r", "5t6y", "abcd", "qwer", "asdf", "zxcv"
36+
);
37+
38+
public static boolean notMatchRegex(String password) {
39+
return !PWD_PATTERN.matcher(password).matches();
40+
}
41+
42+
/**
43+
* @return The passwrod contains code fragment or is blank.
44+
*/
45+
public static boolean isCommonlyUsed(String password) {
46+
if (Strings.isNullOrEmpty(password)) {
47+
return true;
48+
}
49+
for (String s : LIST_OF_CODE_FRAGMENT) {
50+
if (password.toLowerCase().contains(s)) {
51+
return true;
52+
}
53+
}
54+
return false;
55+
}
56+
57+
}

apollo-portal/src/main/resources/static/i18n/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -553,6 +553,7 @@
553553
"UserMange.UserDisplayName": "User Display Name",
554554
"UserMange.UserNameTips": "If the user name entered does not exist, will create a new one. If it already exists, then it will be updated.",
555555
"UserMange.Pwd": "Password",
556+
"UserMange.Random": "Random",
556557
"UserMange.Email": "Email",
557558
"UserMange.Created": "Create user successfully",
558559
"UserMange.CreateFailed": "Failed to create user",

apollo-portal/src/main/resources/static/i18n/zh-CN.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -553,6 +553,7 @@
553553
"UserMange.UserDisplayName": "用户名称",
554554
"UserMange.UserNameTips": "输入的用户名如果不存在,则新建。若已存在,则更新。",
555555
"UserMange.Pwd": "密码",
556+
"UserMange.Random": "随机生成",
556557
"UserMange.Email": "邮箱",
557558
"UserMange.Created": "创建用户成功",
558559
"UserMange.CreateFailed": "创建用户失败",

apollo-portal/src/main/resources/static/scripts/controller/UserController.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,35 @@ function UserController($scope, $window, $translate, toastr, AppUtil, UserServic
3131
})
3232
}
3333

34+
var num_chars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
35+
var alpha_chars =[ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
36+
'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'];
37+
38+
function randomAlphanumeric(len) {
39+
var str = "";
40+
var hasNum = false, hasAlpha = false;
41+
for (var i = 0; i < len; i++) {
42+
var choose = Math.round(Math.random() * 9 + 1); // random 1~10
43+
var pos;
44+
if (choose % 2 == 0 && i < len - 1
45+
// In extreme cases, add alpha to last index
46+
|| i == len - 1 && !hasNum && hasAlpha) {
47+
pos = Math.round(Math.random() * (num_chars.length - 1));
48+
str += num_chars[pos];
49+
hasNum = true;
50+
} else {
51+
pos = Math.round(Math.random() * (alpha_chars.length - 1));
52+
str += alpha_chars[pos];
53+
hasAlpha = true;
54+
}
55+
}
56+
return str;
57+
}
58+
59+
$scope.randomPassword = function() {
60+
$scope.user.password = randomAlphanumeric(8);
61+
}
62+
3463
$scope.createOrUpdateUser = function () {
3564
UserService.createOrUpdateUser($scope.user).then(function (result) {
3665
toastr.success($translate.instant('UserMange.Created'));

apollo-portal/src/main/resources/static/user-manage.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@
7272
<div class="col-sm-5">
7373
<input type="text" class="form-control" name="password" ng-model="user.password">
7474
</div>
75+
<dev class="col-sm-2">
76+
<button type="button" class="btn btn-default" ng-click="randomPassword()">{{'UserMange.Random' | translate }}</button>
77+
</dev>
7578
</div>
7679
<div class="form-group" valdr-form-group>
7780
<label class="col-sm-2 control-label">
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Copyright 2021 Apollo Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
*/
17+
package com.ctrip.framework.apollo.portal.controller;
18+
19+
import com.ctrip.framework.apollo.common.exception.BadRequestException;
20+
import com.ctrip.framework.apollo.portal.entity.po.UserPO;
21+
import org.junit.Assert;
22+
import org.junit.Test;
23+
import org.junit.runner.RunWith;
24+
import org.mockito.Mock;
25+
import org.mockito.junit.MockitoJUnitRunner;
26+
27+
@RunWith(MockitoJUnitRunner.class)
28+
public class UserInfoControllerTest {
29+
30+
@Mock
31+
private UserInfoController userInfoController;
32+
33+
@Test
34+
public void testCreateOrUpdateUserFailed() {
35+
UserPO user = new UserPO();
36+
user.setUsername("username");
37+
user.setPassword("1111111");
38+
try {
39+
userInfoController.createOrUpdateUser(user);
40+
} catch (BadRequestException e) {
41+
Assert.assertEquals(
42+
"Password needs a number and lowercase letter and between 8~20 characters.",
43+
e.getMessage());
44+
}
45+
}
46+
47+
@Test
48+
public void testCreateOrUpdateUserFailed2() {
49+
UserPO user = new UserPO();
50+
user.setUsername("username");
51+
user.setPassword("1111qwer");
52+
try {
53+
userInfoController.createOrUpdateUser(user);
54+
} catch (BadRequestException e) {
55+
Assert.assertEquals(
56+
"Password is in a list of passwords commonly used on other websites.",
57+
e.getMessage());
58+
}
59+
}
60+
61+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright 2021 Apollo Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
*/
17+
package com.ctrip.framework.apollo.portal.util;
18+
19+
import java.util.Arrays;
20+
import java.util.List;
21+
import org.junit.Assert;
22+
import org.junit.Test;
23+
24+
public class CommonlyUsedPwdCheckerTest {
25+
26+
@Test
27+
public void testNotMatchRegex() {
28+
List<String> notMatchRegexPwdList = Arrays.asList("11111111", "1q2w3e");
29+
for (String p : notMatchRegexPwdList) {
30+
Assert.assertTrue(UserPasswordChecker.notMatchRegex(p));
31+
}
32+
33+
Assert.assertFalse(UserPasswordChecker.notMatchRegex("1s39gvisk"));
34+
}
35+
36+
@Test
37+
public void testIsCommonlyUsed() {
38+
List<String> commonlyUsedPwdList = Arrays.asList(
39+
"12345678", "98765432", "11111111", "22222222", "33333333", "44444444",
40+
"55555555", "66666666", "77777777", "88888888", "99999999", "00000000",
41+
"1q2w3e4r", "qwertyuiop", "asdfghjkl", "asdfghjkl", "abcd1234"
42+
);
43+
44+
for (String pwd : commonlyUsedPwdList) {
45+
Assert.assertTrue(UserPasswordChecker.isCommonlyUsed(pwd));
46+
}
47+
48+
Assert.assertFalse(UserPasswordChecker.isCommonlyUsed("29f8bhja"));
49+
}
50+
}

0 commit comments

Comments
 (0)