Skip to content

Commit

Permalink
feature: implement password policies to avoid weak passwords
Browse files Browse the repository at this point in the history
Signed-off-by: WillardHu <wei.hu@daocloud.io>
  • Loading branch information
WillardHu committed Sep 30, 2021
1 parent 6e2e4d8 commit a732f6c
Show file tree
Hide file tree
Showing 9 changed files with 207 additions and 7 deletions.
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 {

Expand All @@ -53,13 +53,17 @@ 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.isWeakPassword(user.getPassword())) {
throw new BadRequestException(
"Password needs a number and lowercase letter and between 8~20 characters. "
+ "And cannot be a weak password.");
}

if (userService instanceof SpringSecurityUserService) {
((SpringSecurityUserService) userService).createOrUpdate(user);
Expand Down Expand Up @@ -91,5 +95,4 @@ public UserInfo getUserByUserId(@PathVariable String userId) {
return userService.findByUserId(userId);
}


}
Original file line number Diff line number Diff line change
@@ -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<String> 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 isWeakPassword(String password) {
return !PWD_PATTERN.matcher(password).matches() || isCommonlyUsed(password);
}

/**
* @return The password contains code fragment or is blank.
*/
private 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;
}

}
1 change: 1 addition & 0 deletions apollo-portal/src/main/resources/static/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions apollo-portal/src/main/resources/static/i18n/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,7 @@
"UserMange.UserDisplayName": "用户名称",
"UserMange.UserNameTips": "输入的用户名如果不存在,则新建。若已存在,则更新。",
"UserMange.Pwd": "密码",
"UserMange.Random": "随机生成",
"UserMange.Email": "邮箱",
"UserMange.Created": "创建用户成功",
"UserMange.CreateFailed": "创建用户失败",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'));
Expand Down
3 changes: 3 additions & 0 deletions apollo-portal/src/main/resources/static/user-manage.html
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@
<div class="col-sm-5">
<input type="text" class="form-control" name="password" ng-model="user.password">
</div>
<dev class="col-sm-2">
<button type="button" class="btn btn-default" ng-click="randomPassword()">{{'UserMange.Random' | translate }}</button>
</dev>
</div>
<div class="form-group" valdr-form-group>
<label class="col-sm-2 control-label">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* 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.controller;

import com.ctrip.framework.apollo.common.exception.BadRequestException;
import com.ctrip.framework.apollo.portal.entity.po.UserPO;
import com.ctrip.framework.apollo.portal.spi.springsecurity.SpringSecurityUserService;
import java.util.Arrays;
import java.util.List;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;

@RunWith(MockitoJUnitRunner.class)
public class UserInfoControllerTest {

@Mock
private UserInfoController userInfoController;
@InjectMocks
private SpringSecurityUserService userService;

@Test
public void testCreateOrUpdateUser() {
UserPO user = new UserPO();
user.setUsername("username");
user.setPassword("af12gvi3ox");
userInfoController.createOrUpdateUser(user);
}

@Test
public void testCreateOrUpdateUserFailed() {
List<String> pwdCases = Arrays.asList("1111111", "1111qwer");
for (String p : pwdCases) {
UserPO user = new UserPO();
user.setUsername("username");
user.setPassword(p);
try {
userInfoController.createOrUpdateUser(user);
} catch (BadRequestException e) {
Assert.assertEquals(
"Password needs a number and lowercase letter and between 8~20 characters. "
+ "And cannot be a weak password.",
e.getMessage());
}
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* 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 java.util.Arrays;
import java.util.List;
import org.junit.Assert;
import org.junit.Test;

public class CommonlyUsedPwdCheckerTest {

@Test
public void testIsWeakPassword() {
List<String> weakPwdList = Arrays.asList(
"12345678", "98765432", "11111111", "22222222", "33333333", "44444444",
"55555555", "66666666", "77777777", "88888888", "99999999", "00000000",
"1q2w3e4r", "qwertyuiop", "asdfghjkl", "asdfghjkl", "abcd1234"
);
for (String p : weakPwdList) {
Assert.assertTrue(UserPasswordChecker.isWeakPassword(p));
}

Assert.assertFalse(UserPasswordChecker.isWeakPassword("1s39gvisk"));
}

}

0 comments on commit a732f6c

Please sign in to comment.