fix:角色授权修改
This commit is contained in:
@@ -58,18 +58,18 @@ public class AuthAdminController {
|
||||
return ApiResponse.success();
|
||||
}
|
||||
|
||||
@PostMapping("/roles/{roleCode}/full-accounts/{uid}/change-password")
|
||||
@Operation(summary = "管理员修改 FULL 账户口令", description = "管理员为目标角色指定 UKey 席位账号设置新口令。")
|
||||
@PostMapping("/roles/{roleCode}/accounts/{uid}/change-password")
|
||||
@Operation(summary = "管理员修改角色席位口令", description = "管理员为目标角色指定席位账号设置新口令。")
|
||||
@ReplayProtected
|
||||
// @RequireInternalAuth(role = RoleCode.SUPER_ADMIN, authLevel = AuthLevel.FULL)
|
||||
@AuditedOperation(module = ModuleCode.AUTH, action = ActionType.UPDATE, summary = "管理员修改 FULL 账户口令")
|
||||
public ApiResponse<Void> changeFullAccountPassword(
|
||||
@RequireInternalAuth(role = RoleCode.SUPER_ADMIN, authLevel = AuthLevel.FULL)
|
||||
@AuditedOperation(module = ModuleCode.AUTH, action = ActionType.UPDATE, summary = "管理员修改角色席位口令")
|
||||
public ApiResponse<Void> changeAccountPassword(
|
||||
@PathVariable("roleCode") String roleCode,
|
||||
@PathVariable("uid") Integer uid,
|
||||
@Valid @RequestBody AdminChangePasswordRequest request,
|
||||
HttpServletRequest httpRequest
|
||||
) {
|
||||
authAdminService.changeFullAccountPassword(
|
||||
authAdminService.changeAccountPassword(
|
||||
(String) httpRequest.getAttribute(InternalApiAuthInterceptor.ATTR_ROLE_CODE),
|
||||
(String) httpRequest.getAttribute(InternalApiAuthInterceptor.ATTR_AUTH_LEVEL),
|
||||
roleCode,
|
||||
@@ -80,28 +80,6 @@ public class AuthAdminController {
|
||||
return ApiResponse.success();
|
||||
}
|
||||
|
||||
@PostMapping("/roles/{roleCode}/limited-accounts/{username}/change-password")
|
||||
@Operation(summary = "管理员修改 LIMITED 账户口令", description = "管理员为目标角色指定独立用户账号设置新口令。")
|
||||
@ReplayProtected
|
||||
// @RequireInternalAuth(role = RoleCode.SUPER_ADMIN, authLevel = AuthLevel.FULL)
|
||||
@AuditedOperation(module = ModuleCode.AUTH, action = ActionType.UPDATE, summary = "管理员修改 LIMITED 账户口令")
|
||||
public ApiResponse<Void> changeLimitedAccountPassword(
|
||||
@PathVariable("roleCode") String roleCode,
|
||||
@PathVariable("username") String username,
|
||||
@Valid @RequestBody AdminChangePasswordRequest request,
|
||||
HttpServletRequest httpRequest
|
||||
) {
|
||||
authAdminService.changeLimitedAccountPassword(
|
||||
(String) httpRequest.getAttribute(InternalApiAuthInterceptor.ATTR_ROLE_CODE),
|
||||
(String) httpRequest.getAttribute(InternalApiAuthInterceptor.ATTR_AUTH_LEVEL),
|
||||
roleCode,
|
||||
username,
|
||||
request.getOldPassword(),
|
||||
request.getNewPassword()
|
||||
);
|
||||
return ApiResponse.success();
|
||||
}
|
||||
|
||||
@PostMapping("/super-admin/ukeys/issue-sign")
|
||||
@Operation(summary = "签发并绑定超级管理员 UKey", description = "系统初始化后登记超级管理员 UKey,不要求已有登录态。")
|
||||
public ApiResponse<UKeySignResult> issueSuperAdminUkeyBindingSign(
|
||||
|
||||
@@ -65,16 +65,16 @@ public class AuthController {
|
||||
return ApiResponse.success();
|
||||
}
|
||||
|
||||
@PostMapping("/full-accounts/{uid}/change-password")
|
||||
@Operation(summary = "修改当前 FULL 账户口令", description = "基于当前 FULL 会话校验并更新指定席位账户口令。")
|
||||
@AuditedOperation(module = ModuleCode.AUTH, action = ActionType.UPDATE, summary = "修改当前 FULL 账户口令")
|
||||
@PostMapping("/accounts/{uid}/change-password")
|
||||
@Operation(summary = "修改当前登录席位口令", description = "基于当前会话校验并更新指定角色席位口令。")
|
||||
@AuditedOperation(module = ModuleCode.AUTH, action = ActionType.UPDATE, summary = "修改当前登录席位口令")
|
||||
@ReplayProtected
|
||||
public ApiResponse<Void> changeFullAccountPassword(
|
||||
public ApiResponse<Void> changeAccountPassword(
|
||||
@PathVariable("uid") Integer uid,
|
||||
@Valid @RequestBody ChangePasswordRequest request,
|
||||
HttpServletRequest httpRequest
|
||||
) {
|
||||
authService.changeFullAccountPassword(
|
||||
authService.changeAccountPassword(
|
||||
(String) httpRequest.getAttribute(InternalApiAuthInterceptor.ATTR_SESSION_TOKEN),
|
||||
uid,
|
||||
request.getCurrentPassword(),
|
||||
@@ -82,22 +82,4 @@ public class AuthController {
|
||||
);
|
||||
return ApiResponse.success();
|
||||
}
|
||||
|
||||
@PostMapping("/limited-accounts/{username}/change-password")
|
||||
@Operation(summary = "修改当前 LIMITED 账户口令", description = "基于当前 LIMITED 会话校验并更新指定受限账户口令。")
|
||||
@AuditedOperation(module = ModuleCode.AUTH, action = ActionType.UPDATE, summary = "修改当前 LIMITED 账户口令")
|
||||
@ReplayProtected
|
||||
public ApiResponse<Void> changeLimitedAccountPassword(
|
||||
@PathVariable("username") String username,
|
||||
@Valid @RequestBody ChangePasswordRequest request,
|
||||
HttpServletRequest httpRequest
|
||||
) {
|
||||
authService.changeLimitedAccountPassword(
|
||||
(String) httpRequest.getAttribute(InternalApiAuthInterceptor.ATTR_SESSION_TOKEN),
|
||||
username,
|
||||
request.getCurrentPassword(),
|
||||
request.getNewPassword()
|
||||
);
|
||||
return ApiResponse.success();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,9 +13,16 @@ public class AuthSessionPrincipal {
|
||||
return principal;
|
||||
}
|
||||
|
||||
public static AuthSessionPrincipal limited(String username) {
|
||||
public static AuthSessionPrincipal full(Integer uid, String username) {
|
||||
AuthSessionPrincipal principal = full(uid);
|
||||
principal.setUsername(username);
|
||||
return principal;
|
||||
}
|
||||
|
||||
public static AuthSessionPrincipal limited(Integer uid, String username) {
|
||||
AuthSessionPrincipal principal = new AuthSessionPrincipal();
|
||||
principal.setType("LIMITED");
|
||||
principal.setUid(uid);
|
||||
principal.setUsername(username);
|
||||
return principal;
|
||||
}
|
||||
|
||||
@@ -2,24 +2,25 @@ package com.cisd.tms.modules.auth.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
@Schema(description = "角色口令校验账号")
|
||||
public class PasswordLoginAccountRequest {
|
||||
|
||||
@NotBlank(message = "username is required")
|
||||
@Schema(description = "账号名", example = "super-admin-01")
|
||||
private String username;
|
||||
@NotNull(message = "uid is required")
|
||||
@Schema(description = "角色固定席位编号", example = "1")
|
||||
private Integer uid;
|
||||
|
||||
@NotBlank(message = "password is required")
|
||||
@Schema(description = "账号口令", example = "12345678")
|
||||
private String password;
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
public Integer getUid() {
|
||||
return uid;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
public void setUid(Integer uid) {
|
||||
this.uid = uid;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
package com.cisd.tms.modules.auth.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.cisd.tms.infrastructure.persistence.entity.BaseEntity;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@TableName("tms_auth_user_account")
|
||||
public class AuthUserAccountEntity extends BaseEntity {
|
||||
|
||||
private String username;
|
||||
private String displayName;
|
||||
private String roleCode;
|
||||
private String passwordHash;
|
||||
private String passwordSalt;
|
||||
private String status;
|
||||
private Boolean needChangePassword;
|
||||
private LocalDateTime passwordChangedAt;
|
||||
private Integer failedCount;
|
||||
private LocalDateTime lockedUntil;
|
||||
private LocalDateTime lastLoginAt;
|
||||
private LocalDateTime lastActiveAt;
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public void setDisplayName(String displayName) {
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
public String getRoleCode() {
|
||||
return roleCode;
|
||||
}
|
||||
|
||||
public void setRoleCode(String roleCode) {
|
||||
this.roleCode = roleCode;
|
||||
}
|
||||
|
||||
public String getPasswordHash() {
|
||||
return passwordHash;
|
||||
}
|
||||
|
||||
public void setPasswordHash(String passwordHash) {
|
||||
this.passwordHash = passwordHash;
|
||||
}
|
||||
|
||||
public String getPasswordSalt() {
|
||||
return passwordSalt;
|
||||
}
|
||||
|
||||
public void setPasswordSalt(String passwordSalt) {
|
||||
this.passwordSalt = passwordSalt;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public Boolean getNeedChangePassword() {
|
||||
return needChangePassword;
|
||||
}
|
||||
|
||||
public void setNeedChangePassword(Boolean needChangePassword) {
|
||||
this.needChangePassword = needChangePassword;
|
||||
}
|
||||
|
||||
public LocalDateTime getPasswordChangedAt() {
|
||||
return passwordChangedAt;
|
||||
}
|
||||
|
||||
public void setPasswordChangedAt(LocalDateTime passwordChangedAt) {
|
||||
this.passwordChangedAt = passwordChangedAt;
|
||||
}
|
||||
|
||||
public Integer getFailedCount() {
|
||||
return failedCount;
|
||||
}
|
||||
|
||||
public void setFailedCount(Integer failedCount) {
|
||||
this.failedCount = failedCount;
|
||||
}
|
||||
|
||||
public LocalDateTime getLockedUntil() {
|
||||
return lockedUntil;
|
||||
}
|
||||
|
||||
public void setLockedUntil(LocalDateTime lockedUntil) {
|
||||
this.lockedUntil = lockedUntil;
|
||||
}
|
||||
|
||||
public LocalDateTime getLastLoginAt() {
|
||||
return lastLoginAt;
|
||||
}
|
||||
|
||||
public void setLastLoginAt(LocalDateTime lastLoginAt) {
|
||||
this.lastLoginAt = lastLoginAt;
|
||||
}
|
||||
|
||||
public LocalDateTime getLastActiveAt() {
|
||||
return lastActiveAt;
|
||||
}
|
||||
|
||||
public void setLastActiveAt(LocalDateTime lastActiveAt) {
|
||||
this.lastActiveAt = lastActiveAt;
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package com.cisd.tms.modules.auth.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.cisd.tms.infrastructure.persistence.entity.BaseEntity;
|
||||
|
||||
@TableName("tms_auth_user")
|
||||
public class AuthUserEntity extends BaseEntity {
|
||||
|
||||
private String username;
|
||||
private String displayName;
|
||||
private String role;
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public void setDisplayName(String displayName) {
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
public String getRole() {
|
||||
return role;
|
||||
}
|
||||
|
||||
public void setRole(String role) {
|
||||
this.role = role;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.cisd.tms.modules.auth.enums;
|
||||
|
||||
public enum RoleCode {
|
||||
SUPER_ADMIN("SUPER_ADMIN", "超级管理员", 2),
|
||||
SUPER_ADMIN("SUPER_ADMIN", "系统管理员", 2),
|
||||
KEY_ADMIN("KEY_ADMIN", "密钥管理员", 1),
|
||||
AUDIT_ADMIN("AUDIT_ADMIN", "审计管理员", 1),
|
||||
OPS_ADMIN("OPS_ADMIN", "运维管理员", 1);
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
package com.cisd.tms.modules.auth.mapper;
|
||||
|
||||
import com.cisd.tms.infrastructure.persistence.mapper.BaseMapperX;
|
||||
import com.cisd.tms.modules.auth.entity.AuthUserAccountEntity;
|
||||
import java.util.List;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
@Mapper
|
||||
public interface AuthUserAccountMapper extends BaseMapperX<AuthUserAccountEntity> {
|
||||
|
||||
AuthUserAccountEntity selectByUsername(@Param("username") String username);
|
||||
|
||||
List<AuthUserAccountEntity> selectByRoleCode(@Param("roleCode") String roleCode);
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package com.cisd.tms.modules.auth.mapper;
|
||||
|
||||
import com.cisd.tms.infrastructure.persistence.mapper.BaseMapperX;
|
||||
import com.cisd.tms.modules.auth.entity.AuthUserEntity;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
@Mapper
|
||||
public interface AuthUserMapper extends BaseMapperX<AuthUserEntity> {
|
||||
|
||||
AuthUserEntity selectByUsername(@Param("username") String username);
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package com.cisd.tms.modules.auth.repository;
|
||||
|
||||
import com.cisd.tms.modules.auth.entity.AuthUserAccountEntity;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface AuthUserAccountRepository {
|
||||
|
||||
Optional<AuthUserAccountEntity> findByUsername(String username);
|
||||
|
||||
List<AuthUserAccountEntity> findByRoleCode(String roleCode);
|
||||
|
||||
void save(AuthUserAccountEntity entity);
|
||||
|
||||
void update(AuthUserAccountEntity entity);
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package com.cisd.tms.modules.auth.repository;
|
||||
|
||||
import com.cisd.tms.modules.auth.entity.AuthUserEntity;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface AuthUserRepository {
|
||||
|
||||
Optional<AuthUserEntity> findByUsername(String username);
|
||||
}
|
||||
-38
@@ -1,38 +0,0 @@
|
||||
package com.cisd.tms.modules.auth.repository.impl;
|
||||
|
||||
import com.cisd.tms.modules.auth.entity.AuthUserAccountEntity;
|
||||
import com.cisd.tms.modules.auth.mapper.AuthUserAccountMapper;
|
||||
import com.cisd.tms.modules.auth.repository.AuthUserAccountRepository;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public class AuthUserAccountRepositoryImpl implements AuthUserAccountRepository {
|
||||
|
||||
private final AuthUserAccountMapper authUserAccountMapper;
|
||||
|
||||
public AuthUserAccountRepositoryImpl(AuthUserAccountMapper authUserAccountMapper) {
|
||||
this.authUserAccountMapper = authUserAccountMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<AuthUserAccountEntity> findByUsername(String username) {
|
||||
return Optional.ofNullable(authUserAccountMapper.selectByUsername(username));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AuthUserAccountEntity> findByRoleCode(String roleCode) {
|
||||
return authUserAccountMapper.selectByRoleCode(roleCode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(AuthUserAccountEntity entity) {
|
||||
authUserAccountMapper.insert(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(AuthUserAccountEntity entity) {
|
||||
authUserAccountMapper.updateById(entity);
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package com.cisd.tms.modules.auth.repository.impl;
|
||||
|
||||
import com.cisd.tms.modules.auth.entity.AuthUserEntity;
|
||||
import com.cisd.tms.modules.auth.mapper.AuthUserMapper;
|
||||
import com.cisd.tms.modules.auth.repository.AuthUserRepository;
|
||||
import java.util.Optional;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public class AuthUserRepositoryImpl implements AuthUserRepository {
|
||||
|
||||
private final AuthUserMapper authUserMapper;
|
||||
|
||||
public AuthUserRepositoryImpl(AuthUserMapper authUserMapper) {
|
||||
this.authUserMapper = authUserMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<AuthUserEntity> findByUsername(String username) {
|
||||
return Optional.ofNullable(authUserMapper.selectByUsername(username));
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ public interface AuthAdminService {
|
||||
|
||||
void resetPassword(String operatorRoleCode, String operatorAuthLevel, String targetRoleCode);
|
||||
|
||||
void changeFullAccountPassword(
|
||||
void changeAccountPassword(
|
||||
String operatorRoleCode,
|
||||
String operatorAuthLevel,
|
||||
String targetRoleCode,
|
||||
@@ -18,15 +18,6 @@ public interface AuthAdminService {
|
||||
String newPassword
|
||||
);
|
||||
|
||||
void changeLimitedAccountPassword(
|
||||
String operatorRoleCode,
|
||||
String operatorAuthLevel,
|
||||
String targetRoleCode,
|
||||
String username,
|
||||
String oldPassword,
|
||||
String newPassword
|
||||
);
|
||||
|
||||
UKeySignResult issueUkeyBindingSign(
|
||||
String operatorRoleCode,
|
||||
String operatorAuthLevel,
|
||||
|
||||
@@ -27,7 +27,5 @@ public interface AuthService {
|
||||
|
||||
void logout(String sessionToken);
|
||||
|
||||
void changeFullAccountPassword(String sessionToken, Integer uid, String currentPassword, String newPassword);
|
||||
|
||||
void changeLimitedAccountPassword(String sessionToken, String username, String currentPassword, String newPassword);
|
||||
void changeAccountPassword(String sessionToken, Integer uid, String currentPassword, String newPassword);
|
||||
}
|
||||
|
||||
@@ -4,13 +4,11 @@ import com.cisd.tms.common.enums.ErrorCode;
|
||||
import com.cisd.tms.common.exception.BizException;
|
||||
import com.cisd.tms.common.util.TraceIdUtil;
|
||||
import com.cisd.tms.modules.auth.entity.AuthFullAccountEntity;
|
||||
import com.cisd.tms.modules.auth.entity.AuthUserAccountEntity;
|
||||
import com.cisd.tms.modules.auth.entity.RoleAccountEntity;
|
||||
import com.cisd.tms.modules.auth.entity.RoleUkeyBindingEntity;
|
||||
import com.cisd.tms.modules.auth.enums.RoleAccountStatus;
|
||||
import com.cisd.tms.modules.auth.enums.RoleCode;
|
||||
import com.cisd.tms.modules.auth.repository.AuthFullAccountRepository;
|
||||
import com.cisd.tms.modules.auth.repository.AuthUserAccountRepository;
|
||||
import com.cisd.tms.modules.auth.repository.RoleAccountRepository;
|
||||
import com.cisd.tms.modules.auth.repository.RoleUkeyBindingRepository;
|
||||
import com.cisd.tms.modules.auth.service.AuthAdminService;
|
||||
@@ -40,7 +38,6 @@ public class AuthAdminServiceImpl implements AuthAdminService {
|
||||
|
||||
private final RoleAccountRepository roleAccountRepository;
|
||||
private final AuthFullAccountRepository authFullAccountRepository;
|
||||
private final AuthUserAccountRepository authUserAccountRepository;
|
||||
private final RoleUkeyBindingRepository roleUkeyBindingRepository;
|
||||
private final PasswordHasher passwordHasher;
|
||||
private final LmkService lmkService;
|
||||
@@ -68,19 +65,6 @@ public class AuthAdminServiceImpl implements AuthAdminService {
|
||||
authFullAccountRepository.update(account);
|
||||
}
|
||||
|
||||
List<AuthUserAccountEntity> accounts = authUserAccountRepository.findByRoleCode(targetRoleCode);
|
||||
if (accounts.isEmpty()) {
|
||||
throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "target role user accounts not found");
|
||||
}
|
||||
for (AuthUserAccountEntity account : accounts) {
|
||||
account.setStatus(RoleAccountStatus.ACTIVE.name());
|
||||
account.setNeedChangePassword(Boolean.TRUE);
|
||||
account.setFailedCount(0);
|
||||
account.setLockedUntil(null);
|
||||
account.setLastActiveAt(null);
|
||||
account.setLastLoginAt(null);
|
||||
authUserAccountRepository.update(account);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -108,27 +92,10 @@ public class AuthAdminServiceImpl implements AuthAdminService {
|
||||
authFullAccountRepository.update(account);
|
||||
}
|
||||
|
||||
List<AuthUserAccountEntity> accounts = authUserAccountRepository.findByRoleCode(targetRoleCode);
|
||||
if (accounts.isEmpty()) {
|
||||
throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "target role user accounts not found");
|
||||
}
|
||||
for (AuthUserAccountEntity account : accounts) {
|
||||
String newSalt = passwordSaltGenerator.nextSalt();
|
||||
account.setPasswordSalt(newSalt);
|
||||
account.setPasswordHash(passwordHasher.hash(DEFAULT_PASSWORD, newSalt));
|
||||
account.setStatus(RoleAccountStatus.ACTIVE.name());
|
||||
account.setNeedChangePassword(Boolean.TRUE);
|
||||
account.setPasswordChangedAt(current);
|
||||
account.setFailedCount(0);
|
||||
account.setLockedUntil(null);
|
||||
account.setLastActiveAt(null);
|
||||
account.setLastLoginAt(null);
|
||||
authUserAccountRepository.update(account);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changeFullAccountPassword(
|
||||
public void changeAccountPassword(
|
||||
String operatorRoleCode,
|
||||
String operatorAuthLevel,
|
||||
String targetRoleCode,
|
||||
@@ -151,29 +118,6 @@ public class AuthAdminServiceImpl implements AuthAdminService {
|
||||
authFullAccountRepository.update(account);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changeLimitedAccountPassword(
|
||||
String operatorRoleCode,
|
||||
String operatorAuthLevel,
|
||||
String targetRoleCode,
|
||||
String username,
|
||||
String oldPassword,
|
||||
String newPassword
|
||||
) {
|
||||
PasswordComplexityValidator.validate(newPassword);
|
||||
loadRole(targetRoleCode);
|
||||
AuthUserAccountEntity account = authUserAccountRepository.findByUsername(username)
|
||||
.orElseThrow(() -> new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "target role user account not found"));
|
||||
if (!targetRoleCode.equals(account.getRoleCode())) {
|
||||
throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "target role user account not found");
|
||||
}
|
||||
if (!passwordHasher.matches(oldPassword, account.getPasswordSalt(), account.getPasswordHash())) {
|
||||
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "old password is incorrect");
|
||||
}
|
||||
applyAdminPasswordChange(account, newPassword);
|
||||
authUserAccountRepository.update(account);
|
||||
}
|
||||
|
||||
private void bindUkey(
|
||||
String targetRoleCode,
|
||||
Integer uid,
|
||||
@@ -277,19 +221,6 @@ public class AuthAdminServiceImpl implements AuthAdminService {
|
||||
}
|
||||
}
|
||||
|
||||
private void applyAdminPasswordChange(AuthUserAccountEntity account, String newPassword) {
|
||||
String newSalt = passwordSaltGenerator.nextSalt();
|
||||
account.setPasswordSalt(newSalt);
|
||||
account.setPasswordHash(passwordHasher.hash(newPassword, newSalt));
|
||||
account.setNeedChangePassword(Boolean.TRUE);
|
||||
account.setPasswordChangedAt(now());
|
||||
account.setFailedCount(0);
|
||||
account.setLockedUntil(null);
|
||||
if (RoleAccountStatus.LOCKED.name().equals(account.getStatus())) {
|
||||
account.setStatus(RoleAccountStatus.ACTIVE.name());
|
||||
}
|
||||
}
|
||||
|
||||
private RoleAccountEntity loadRole(String roleCode) {
|
||||
resolveRoleCode(roleCode);
|
||||
return roleAccountRepository.findByRoleCode(roleCode)
|
||||
|
||||
@@ -16,8 +16,6 @@ import com.cisd.tms.modules.auth.dto.UkeyLoginRandomResponse;
|
||||
import com.cisd.tms.modules.auth.dto.UkeyLoginRequest;
|
||||
import com.cisd.tms.modules.auth.entity.AuthFullAccountEntity;
|
||||
import com.cisd.tms.modules.auth.entity.AuthSessionEntity;
|
||||
import com.cisd.tms.modules.auth.entity.AuthUserAccountEntity;
|
||||
import com.cisd.tms.modules.auth.entity.AuthUserEntity;
|
||||
import com.cisd.tms.modules.auth.entity.RoleAccountEntity;
|
||||
import com.cisd.tms.modules.auth.entity.RoleUkeyBindingEntity;
|
||||
import com.cisd.tms.modules.auth.enums.AuthLevel;
|
||||
@@ -26,8 +24,6 @@ import com.cisd.tms.modules.auth.enums.RoleAccountStatus;
|
||||
import com.cisd.tms.modules.auth.enums.RoleCode;
|
||||
import com.cisd.tms.modules.auth.repository.AuthFullAccountRepository;
|
||||
import com.cisd.tms.modules.auth.repository.AuthSessionRepository;
|
||||
import com.cisd.tms.modules.auth.repository.AuthUserAccountRepository;
|
||||
import com.cisd.tms.modules.auth.repository.AuthUserRepository;
|
||||
import com.cisd.tms.modules.auth.repository.RoleAccountRepository;
|
||||
import com.cisd.tms.modules.auth.repository.RoleUkeyBindingRepository;
|
||||
import com.cisd.tms.modules.auth.service.AuthPolicyService;
|
||||
@@ -69,8 +65,6 @@ public class AuthServiceImpl implements AuthService {
|
||||
private static final int PASSWORD_EXPIRE_DAYS = 30;
|
||||
|
||||
private final RoleAccountRepository roleAccountRepository;
|
||||
private final AuthUserRepository authUserRepository;
|
||||
private final AuthUserAccountRepository authUserAccountRepository;
|
||||
private final AuthFullAccountRepository authFullAccountRepository;
|
||||
private final AuthSessionRepository authSessionRepository;
|
||||
private final RoleUkeyBindingRepository roleUkeyBindingRepository;
|
||||
@@ -92,19 +86,17 @@ public class AuthServiceImpl implements AuthService {
|
||||
|
||||
validateRoleStatus(roleAccount);
|
||||
AuthMethod authMethod = resolveAuthMethod(request.getUkeySerials());
|
||||
List<AuthUserAccountEntity> validatedAccounts = new ArrayList<>();
|
||||
List<AuthFullAccountEntity> validatedFullAccounts = new ArrayList<>();
|
||||
List<AuthFullAccountEntity> validatedAccounts;
|
||||
if (AuthMethod.PASSWORD == authMethod) {
|
||||
validatedAccounts = validateRoleAccounts(roleAccount, request.getAccounts());
|
||||
resetValidatedAccounts(validatedAccounts);
|
||||
} else {
|
||||
validatedFullAccounts = validateFullAccounts(roleAccount, request.getFullAccounts());
|
||||
resetValidatedFullAccounts(validatedFullAccounts);
|
||||
validatedAccounts = validateFullAccounts(roleAccount, request.getFullAccounts());
|
||||
}
|
||||
resetValidatedAccounts(validatedAccounts);
|
||||
AuthLevel authLevel = resolveAuthLevel(roleAccount, request.getUkeySerials());
|
||||
List<AuthSessionPrincipal> authenticatedPrincipals = AuthMethod.PASSWORD == authMethod
|
||||
? normalizeAuthenticatedUsers(validatedAccounts)
|
||||
: normalizeAuthenticatedFullAccounts(validatedFullAccounts);
|
||||
? normalizeAuthenticatedAccounts(validatedAccounts, AuthLevel.LIMITED)
|
||||
: normalizeAuthenticatedAccounts(validatedAccounts, AuthLevel.FULL);
|
||||
expireConcurrentSessions(roleAccount.getRoleCode(), authLevel, authenticatedPrincipals);
|
||||
AuthSessionEntity session = buildSession(
|
||||
roleAccount.getRoleCode(),
|
||||
@@ -120,9 +112,7 @@ public class AuthServiceImpl implements AuthService {
|
||||
response.setToken(session.getSessionToken());
|
||||
response.setExpiresAt(OffsetDateTime.of(session.getExpiresAt(), ZoneOffset.UTC).toString());
|
||||
response.setNeedChangePassword(
|
||||
AuthMethod.PASSWORD == authMethod
|
||||
? needsPasswordChangeForLimitedAccounts(validatedAccounts)
|
||||
: needsPasswordChangeForFullAccounts(validatedFullAccounts)
|
||||
needsPasswordChangeForAccounts(validatedAccounts)
|
||||
);
|
||||
return response;
|
||||
}
|
||||
@@ -189,8 +179,8 @@ public class AuthServiceImpl implements AuthService {
|
||||
@Override
|
||||
public CurrentUserResponse me(String username, String sessionToken) {
|
||||
String normalized = username == null || username.isBlank() ? "admin" : username;
|
||||
RoleAccountEntity roleAccount = roleAccountRepository.findByRoleCode(normalized).orElse(null);
|
||||
if (roleAccount != null) {
|
||||
RoleAccountEntity roleAccount = roleAccountRepository.findByRoleCode(normalized)
|
||||
.orElseThrow(() -> new BizException(ErrorCode.UNAUTHORIZED.getCode(), "role account not found"));
|
||||
CurrentUserResponse response = new CurrentUserResponse();
|
||||
response.setUsername(roleAccount.getRoleCode());
|
||||
response.setDisplayName(roleAccount.getDisplayName());
|
||||
@@ -205,23 +195,6 @@ public class AuthServiceImpl implements AuthService {
|
||||
return response;
|
||||
}
|
||||
|
||||
AuthUserEntity entity = authUserRepository.findByUsername(normalized).orElse(null);
|
||||
CurrentUserResponse response = new CurrentUserResponse();
|
||||
if (entity == null) {
|
||||
response.setUsername(normalized);
|
||||
response.setDisplayName("TMS Administrator");
|
||||
response.setRole("ADMIN");
|
||||
response.setNeedChangePassword(Boolean.FALSE);
|
||||
return response;
|
||||
}
|
||||
|
||||
response.setUsername(entity.getUsername());
|
||||
response.setDisplayName(entity.getDisplayName());
|
||||
response.setRole(entity.getRole());
|
||||
response.setNeedChangePassword(Boolean.FALSE);
|
||||
return response;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logout(String sessionToken) {
|
||||
AuthSessionEntity session = requireActiveSession(sessionToken);
|
||||
@@ -231,17 +204,14 @@ public class AuthServiceImpl implements AuthService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changeFullAccountPassword(String sessionToken, Integer uid, String currentPassword, String newPassword) {
|
||||
public void changeAccountPassword(String sessionToken, Integer uid, String currentPassword, String newPassword) {
|
||||
PasswordComplexityValidator.validate(newPassword);
|
||||
AuthSessionEntity session = requireActiveSession(sessionToken);
|
||||
if (!AuthLevel.FULL.name().equals(session.getAuthLevel())) {
|
||||
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "full session is required");
|
||||
}
|
||||
if (uid == null || !containsAuthenticatedFullUid(session, uid)) {
|
||||
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "full account is not authenticated in current session");
|
||||
if (uid == null || !containsAuthenticatedUid(session, uid)) {
|
||||
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "account is not authenticated in current session");
|
||||
}
|
||||
AuthFullAccountEntity fullAccount = authFullAccountRepository.findByRoleCodeAndUid(session.getRoleCode(), uid)
|
||||
.orElseThrow(() -> new BizException(ErrorCode.UNAUTHORIZED.getCode(), "full account not found"));
|
||||
.orElseThrow(() -> new BizException(ErrorCode.UNAUTHORIZED.getCode(), "account not found"));
|
||||
validateFullAccountStatus(fullAccount);
|
||||
if (!passwordHasher.matches(currentPassword, fullAccount.getPasswordSalt(), fullAccount.getPasswordHash())) {
|
||||
onFullPasswordFailed(fullAccount);
|
||||
@@ -265,49 +235,13 @@ public class AuthServiceImpl implements AuthService {
|
||||
authSessionRepository.update(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changeLimitedAccountPassword(String sessionToken, String username, String currentPassword, String newPassword) {
|
||||
PasswordComplexityValidator.validate(newPassword);
|
||||
AuthSessionEntity session = requireActiveSession(sessionToken);
|
||||
if (!AuthLevel.LIMITED.name().equals(session.getAuthLevel())) {
|
||||
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "limited session is required");
|
||||
}
|
||||
if (username == null || username.isBlank() || !containsAuthenticatedLimitedUsername(session, username)) {
|
||||
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "limited account is not authenticated in current session");
|
||||
}
|
||||
AuthUserAccountEntity userAccount = authUserAccountRepository.findByUsername(username)
|
||||
.orElseThrow(() -> new BizException(ErrorCode.UNAUTHORIZED.getCode(), "user account not found"));
|
||||
validateUserAccountRole(session.getRoleCode(), userAccount);
|
||||
validateUserAccountStatus(userAccount);
|
||||
if (!passwordHasher.matches(currentPassword, userAccount.getPasswordSalt(), userAccount.getPasswordHash())) {
|
||||
onPasswordFailed(userAccount);
|
||||
}
|
||||
|
||||
String salt = passwordSaltGenerator.nextSalt();
|
||||
LocalDateTime current = now();
|
||||
userAccount.setPasswordSalt(salt);
|
||||
userAccount.setPasswordHash(passwordHasher.hash(newPassword, salt));
|
||||
userAccount.setFailedCount(0);
|
||||
userAccount.setLockedUntil(null);
|
||||
userAccount.setStatus(RoleAccountStatus.ACTIVE.name());
|
||||
userAccount.setNeedChangePassword(Boolean.FALSE);
|
||||
userAccount.setPasswordChangedAt(current);
|
||||
userAccount.setLastLoginAt(current);
|
||||
userAccount.setLastActiveAt(current);
|
||||
authUserAccountRepository.update(userAccount);
|
||||
|
||||
session.setLastActiveAt(current);
|
||||
session.setExpiresAt(current.plusMinutes(IDLE_TIMEOUT_MINUTES));
|
||||
authSessionRepository.update(session);
|
||||
}
|
||||
|
||||
private void validateRoleStatus(RoleAccountEntity roleAccount) {
|
||||
if (!RoleAccountStatus.ACTIVE.name().equals(roleAccount.getStatus())) {
|
||||
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "role is not enabled");
|
||||
}
|
||||
}
|
||||
|
||||
private List<AuthUserAccountEntity> validateRoleAccounts(
|
||||
private List<AuthFullAccountEntity> validateRoleAccounts(
|
||||
RoleAccountEntity roleAccount,
|
||||
List<PasswordLoginAccountRequest> accounts
|
||||
) {
|
||||
@@ -319,54 +253,25 @@ public class AuthServiceImpl implements AuthService {
|
||||
);
|
||||
}
|
||||
|
||||
Set<String> uniqueUsernames = new LinkedHashSet<>();
|
||||
List<AuthUserAccountEntity> validatedAccounts = new ArrayList<>();
|
||||
Set<Integer> uniqueUids = new LinkedHashSet<>();
|
||||
List<AuthFullAccountEntity> validatedAccounts = new ArrayList<>();
|
||||
for (PasswordLoginAccountRequest account : accounts) {
|
||||
if (!uniqueUsernames.add(account.getUsername())) {
|
||||
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "duplicate account username submitted");
|
||||
Integer uid = account.getUid();
|
||||
if (uid == null || !uniqueUids.add(uid)) {
|
||||
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "duplicate account uid submitted");
|
||||
}
|
||||
AuthUserAccountEntity userAccount = authUserAccountRepository.findByUsername(account.getUsername())
|
||||
.orElseThrow(() -> new BizException(ErrorCode.UNAUTHORIZED.getCode(), "user account not found"));
|
||||
validateUserAccountRole(roleAccount.getRoleCode(), userAccount);
|
||||
validateUserAccountStatus(userAccount);
|
||||
if (!passwordHasher.matches(account.getPassword(), userAccount.getPasswordSalt(), userAccount.getPasswordHash())) {
|
||||
onPasswordFailed(userAccount);
|
||||
AuthFullAccountEntity roleAccountSeat = authFullAccountRepository
|
||||
.findByRoleCodeAndUid(roleAccount.getRoleCode(), uid)
|
||||
.orElseThrow(() -> new BizException(ErrorCode.UNAUTHORIZED.getCode(), "account not found"));
|
||||
validateFullAccountStatus(roleAccountSeat);
|
||||
if (!passwordHasher.matches(account.getPassword(), roleAccountSeat.getPasswordSalt(), roleAccountSeat.getPasswordHash())) {
|
||||
onFullPasswordFailed(roleAccountSeat);
|
||||
}
|
||||
validatedAccounts.add(userAccount);
|
||||
validatedAccounts.add(roleAccountSeat);
|
||||
}
|
||||
return validatedAccounts;
|
||||
}
|
||||
|
||||
private void validateUserAccountRole(String expectedRoleCode, AuthUserAccountEntity userAccount) {
|
||||
if (!expectedRoleCode.equals(userAccount.getRoleCode())) {
|
||||
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "user account does not belong to target role");
|
||||
}
|
||||
}
|
||||
|
||||
private void validateUserAccountStatus(AuthUserAccountEntity userAccount) {
|
||||
if (RoleAccountStatus.UNENABLED.name().equals(userAccount.getStatus())) {
|
||||
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "user account is not enabled");
|
||||
}
|
||||
if (RoleAccountStatus.LOCKED.name().equals(userAccount.getStatus())
|
||||
&& (userAccount.getLockedUntil() == null || userAccount.getLockedUntil().isAfter(now()))) {
|
||||
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "user account is locked");
|
||||
}
|
||||
}
|
||||
|
||||
private void onPasswordFailed(AuthUserAccountEntity userAccount) {
|
||||
int failedCount = userAccount.getFailedCount() == null ? 0 : userAccount.getFailedCount();
|
||||
failedCount++;
|
||||
userAccount.setFailedCount(failedCount);
|
||||
if (failedCount >= MAX_FAILED_ATTEMPTS) {
|
||||
userAccount.setStatus(RoleAccountStatus.LOCKED.name());
|
||||
userAccount.setLockedUntil(now().plusMinutes(IDLE_TIMEOUT_MINUTES));
|
||||
authUserAccountRepository.update(userAccount);
|
||||
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "user account is locked");
|
||||
}
|
||||
authUserAccountRepository.update(userAccount);
|
||||
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "password is incorrect");
|
||||
}
|
||||
|
||||
private void validateFullAccountStatus(AuthFullAccountEntity fullAccount) {
|
||||
if (RoleAccountStatus.UNENABLED.name().equals(fullAccount.getStatus())) {
|
||||
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "full account is not enabled");
|
||||
@@ -391,19 +296,7 @@ public class AuthServiceImpl implements AuthService {
|
||||
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "password is incorrect");
|
||||
}
|
||||
|
||||
private void resetValidatedAccounts(List<AuthUserAccountEntity> validatedAccounts) {
|
||||
LocalDateTime current = now();
|
||||
for (AuthUserAccountEntity account : validatedAccounts) {
|
||||
account.setFailedCount(0);
|
||||
account.setLockedUntil(null);
|
||||
account.setStatus(RoleAccountStatus.ACTIVE.name());
|
||||
account.setLastLoginAt(current);
|
||||
account.setLastActiveAt(current);
|
||||
authUserAccountRepository.update(account);
|
||||
}
|
||||
}
|
||||
|
||||
private void resetValidatedFullAccounts(List<AuthFullAccountEntity> validatedAccounts) {
|
||||
private void resetValidatedAccounts(List<AuthFullAccountEntity> validatedAccounts) {
|
||||
LocalDateTime current = now();
|
||||
for (AuthFullAccountEntity account : validatedAccounts) {
|
||||
account.setFailedCount(0);
|
||||
@@ -587,25 +480,18 @@ public class AuthServiceImpl implements AuthService {
|
||||
}
|
||||
}
|
||||
|
||||
private List<AuthSessionPrincipal> normalizeAuthenticatedUsers(List<AuthUserAccountEntity> validatedAccounts) {
|
||||
private List<AuthSessionPrincipal> normalizeAuthenticatedAccounts(
|
||||
List<AuthFullAccountEntity> validatedAccounts,
|
||||
AuthLevel authLevel
|
||||
) {
|
||||
if (validatedAccounts == null || validatedAccounts.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return validatedAccounts.stream()
|
||||
.map(AuthUserAccountEntity::getUsername)
|
||||
.sorted()
|
||||
.map(AuthSessionPrincipal::limited)
|
||||
.toList();
|
||||
}
|
||||
|
||||
private List<AuthSessionPrincipal> normalizeAuthenticatedFullAccounts(List<AuthFullAccountEntity> validatedAccounts) {
|
||||
if (validatedAccounts == null || validatedAccounts.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return validatedAccounts.stream()
|
||||
.map(AuthFullAccountEntity::getUid)
|
||||
.sorted()
|
||||
.map(AuthSessionPrincipal::full)
|
||||
.sorted(java.util.Comparator.comparing(AuthFullAccountEntity::getUid))
|
||||
.map(account -> AuthLevel.FULL == authLevel
|
||||
? AuthSessionPrincipal.full(account.getUid(), account.getAccountName())
|
||||
: AuthSessionPrincipal.limited(account.getUid(), account.getAccountName()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@@ -636,7 +522,11 @@ public class AuthServiceImpl implements AuthService {
|
||||
if (value.startsWith("FULL:")) {
|
||||
principals.add(AuthSessionPrincipal.full(Integer.parseInt(value.substring("FULL:".length()))));
|
||||
} else {
|
||||
principals.add(AuthSessionPrincipal.limited(value));
|
||||
AuthFullAccountEntity account = authFullAccountRepository.findByRoleCode(session.getRoleCode()).stream()
|
||||
.filter(item -> value.equals(item.getAccountName()))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
principals.add(AuthSessionPrincipal.limited(account == null ? null : account.getUid(), value));
|
||||
}
|
||||
}
|
||||
return principals;
|
||||
@@ -650,25 +540,16 @@ public class AuthServiceImpl implements AuthService {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean containsAuthenticatedFullUid(AuthSessionEntity session, Integer uid) {
|
||||
private boolean containsAuthenticatedUid(AuthSessionEntity session, Integer uid) {
|
||||
return authenticatedPrincipals(session).stream()
|
||||
.anyMatch(principal -> "FULL".equals(principal.getType()) && uid.equals(principal.getUid()));
|
||||
}
|
||||
|
||||
private boolean containsAuthenticatedLimitedUsername(AuthSessionEntity session, String username) {
|
||||
return authenticatedPrincipals(session).stream()
|
||||
.anyMatch(principal -> "LIMITED".equals(principal.getType()) && username.equals(principal.getUsername()));
|
||||
.anyMatch(principal -> uid.equals(principal.getUid()));
|
||||
}
|
||||
|
||||
private LocalDateTime now() {
|
||||
return LocalDateTime.ofInstant(clock.instant(), ZoneOffset.UTC);
|
||||
}
|
||||
|
||||
private boolean needsPasswordChangeForLimitedAccounts(List<AuthUserAccountEntity> accounts) {
|
||||
return accounts != null && accounts.stream().anyMatch(this::needsPasswordChange);
|
||||
}
|
||||
|
||||
private boolean needsPasswordChangeForFullAccounts(List<AuthFullAccountEntity> accounts) {
|
||||
private boolean needsPasswordChangeForAccounts(List<AuthFullAccountEntity> accounts) {
|
||||
return accounts != null && accounts.stream().anyMatch(this::needsPasswordChange);
|
||||
}
|
||||
|
||||
@@ -680,18 +561,10 @@ public class AuthServiceImpl implements AuthService {
|
||||
if (principals.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
if (AuthLevel.FULL.name().equals(session.getAuthLevel())) {
|
||||
List<AuthFullAccountEntity> accounts = authFullAccountRepository.findByRoleCode(session.getRoleCode());
|
||||
return accounts.stream()
|
||||
.filter(account -> principals.stream()
|
||||
.anyMatch(principal -> "FULL".equals(principal.getType()) && account.getUid().equals(principal.getUid())))
|
||||
.anyMatch(this::needsPasswordChange);
|
||||
}
|
||||
List<AuthUserAccountEntity> accounts = authUserAccountRepository.findByRoleCode(session.getRoleCode());
|
||||
return accounts.stream()
|
||||
.filter(account -> principals.stream()
|
||||
.anyMatch(principal -> "LIMITED".equals(principal.getType())
|
||||
&& account.getUsername().equals(principal.getUsername())))
|
||||
.anyMatch(principal -> account.getUid().equals(principal.getUid())))
|
||||
.anyMatch(this::needsPasswordChange);
|
||||
}
|
||||
|
||||
@@ -700,11 +573,6 @@ public class AuthServiceImpl implements AuthService {
|
||||
|| isPasswordExpired(account.getPasswordChangedAt());
|
||||
}
|
||||
|
||||
private boolean needsPasswordChange(AuthUserAccountEntity account) {
|
||||
return Boolean.TRUE.equals(account.getNeedChangePassword())
|
||||
|| isPasswordExpired(account.getPasswordChangedAt());
|
||||
}
|
||||
|
||||
private boolean isPasswordExpired(LocalDateTime passwordChangedAt) {
|
||||
return passwordChangedAt == null || !passwordChangedAt.isAfter(now().minusDays(PASSWORD_EXPIRE_DAYS));
|
||||
}
|
||||
|
||||
@@ -37,6 +37,9 @@ public class PcieCompatUkeyVerifier implements CompatUkeyVerifier {
|
||||
private static final String PROVIDER = "BC";
|
||||
private static final String SIGNATURE_ALGORITHM = "SM3withSM2";
|
||||
private static final String SM2_CURVE = "sm2p256v1";
|
||||
private static final int GM0018_ECCREF_PUBLIC_KEY_LENGTH = 132;
|
||||
private static final int GM0018_ECCREF_COMPONENT_LENGTH = 64;
|
||||
private static final int SM2_COORDINATE_LENGTH = 32;
|
||||
|
||||
static {
|
||||
if (Security.getProvider(PROVIDER) == null) {
|
||||
@@ -65,6 +68,9 @@ public class PcieCompatUkeyVerifier implements CompatUkeyVerifier {
|
||||
public void verifyLoginSignature(String pubKey, String loginSignData, String loginSign) {
|
||||
try {
|
||||
PublicKey publicKey = parsePublicKey(pubKey);
|
||||
if (loginSignData == null || loginSignData.isBlank()) {
|
||||
throw new IllegalArgumentException("login sign data is required");
|
||||
}
|
||||
byte[] signatureBytes = decodeSignature(loginSign);
|
||||
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM, PROVIDER);
|
||||
signature.initVerify(publicKey);
|
||||
@@ -96,7 +102,7 @@ public class PcieCompatUkeyVerifier implements CompatUkeyVerifier {
|
||||
try {
|
||||
return KeyFactory.getInstance("EC", PROVIDER).generatePublic(new X509EncodedKeySpec(decoded));
|
||||
} catch (Exception ignored) {
|
||||
return parseUncompressedPointPublicKey(decoded);
|
||||
return parseUncompressedPointPublicKey(toPointBytes(decoded));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,12 +116,40 @@ public class PcieCompatUkeyVerifier implements CompatUkeyVerifier {
|
||||
}
|
||||
}
|
||||
|
||||
private PublicKey parseUncompressedPointPublicKey(byte[] keyBytes) throws Exception {
|
||||
byte[] pointBytes = keyBytes;
|
||||
private byte[] toPointBytes(byte[] keyBytes) {
|
||||
if (keyBytes.length == GM0018_ECCREF_PUBLIC_KEY_LENGTH) {
|
||||
return gm0018EccRefToPointBytes(keyBytes);
|
||||
}
|
||||
return keyBytes;
|
||||
}
|
||||
|
||||
private byte[] gm0018EccRefToPointBytes(byte[] keyBytes) {
|
||||
int bitsLittleEndian = (keyBytes[0] & 0xff)
|
||||
| ((keyBytes[1] & 0xff) << 8)
|
||||
| ((keyBytes[2] & 0xff) << 16)
|
||||
| ((keyBytes[3] & 0xff) << 24);
|
||||
int bitsBigEndian = ((keyBytes[0] & 0xff) << 24)
|
||||
| ((keyBytes[1] & 0xff) << 16)
|
||||
| ((keyBytes[2] & 0xff) << 8)
|
||||
| (keyBytes[3] & 0xff);
|
||||
if (bitsLittleEndian != 256 && bitsBigEndian != 256) {
|
||||
throw new IllegalArgumentException("login public key is invalid");
|
||||
}
|
||||
byte[] pointBytes = new byte[65];
|
||||
pointBytes[0] = 0x04;
|
||||
System.arraycopy(keyBytes, 4 + GM0018_ECCREF_COMPONENT_LENGTH - SM2_COORDINATE_LENGTH,
|
||||
pointBytes, 1, SM2_COORDINATE_LENGTH);
|
||||
System.arraycopy(keyBytes, 4 + GM0018_ECCREF_COMPONENT_LENGTH * 2 - SM2_COORDINATE_LENGTH,
|
||||
pointBytes, 1 + SM2_COORDINATE_LENGTH, SM2_COORDINATE_LENGTH);
|
||||
return pointBytes;
|
||||
}
|
||||
|
||||
private PublicKey parseUncompressedPointPublicKey(byte[] pointBytes) throws Exception {
|
||||
if (pointBytes.length == 64) {
|
||||
byte[] rawPointBytes = pointBytes;
|
||||
pointBytes = new byte[65];
|
||||
pointBytes[0] = 0x04;
|
||||
System.arraycopy(keyBytes, 0, pointBytes, 1, keyBytes.length);
|
||||
System.arraycopy(rawPointBytes, 0, pointBytes, 1, rawPointBytes.length);
|
||||
}
|
||||
if (pointBytes.length != 65 || pointBytes[0] != 0x04) {
|
||||
throw new IllegalArgumentException("login public key is invalid");
|
||||
@@ -129,16 +163,31 @@ public class PcieCompatUkeyVerifier implements CompatUkeyVerifier {
|
||||
}
|
||||
|
||||
private byte[] decodeSignature(String value) {
|
||||
if (value == null || value.isBlank()) {
|
||||
throw new IllegalArgumentException("login signature is required");
|
||||
}
|
||||
byte[] decoded = decodeHexOrBase64(normalize(value), "login signature is invalid");
|
||||
if (isDerSignature(decoded)) {
|
||||
return decoded;
|
||||
}
|
||||
if (decoded.length == GM0018_ECCREF_COMPONENT_LENGTH * 2) {
|
||||
return rawRsToDer(gm0018SignatureToRawRs(decoded));
|
||||
}
|
||||
if (decoded.length == 64) {
|
||||
return rawRsToDer(decoded);
|
||||
}
|
||||
throw new IllegalArgumentException("login signature is invalid");
|
||||
}
|
||||
|
||||
private byte[] gm0018SignatureToRawRs(byte[] signatureBytes) {
|
||||
byte[] rawSignature = new byte[64];
|
||||
System.arraycopy(signatureBytes, GM0018_ECCREF_COMPONENT_LENGTH - SM2_COORDINATE_LENGTH,
|
||||
rawSignature, 0, SM2_COORDINATE_LENGTH);
|
||||
System.arraycopy(signatureBytes, GM0018_ECCREF_COMPONENT_LENGTH * 2 - SM2_COORDINATE_LENGTH,
|
||||
rawSignature, SM2_COORDINATE_LENGTH, SM2_COORDINATE_LENGTH);
|
||||
return rawSignature;
|
||||
}
|
||||
|
||||
private boolean isDerSignature(byte[] signatureBytes) {
|
||||
try (ASN1InputStream inputStream = new ASN1InputStream(signatureBytes)) {
|
||||
ASN1Primitive primitive = inputStream.readObject();
|
||||
|
||||
@@ -142,7 +142,7 @@ public class OperationAuditAspect {
|
||||
return "";
|
||||
}
|
||||
return request.getAccounts().stream()
|
||||
.map(account -> account.getUsername() == null ? "" : account.getUsername().trim())
|
||||
.map(account -> account.getUid() == null ? "" : "uid=" + account.getUid())
|
||||
.filter(value -> !value.isBlank())
|
||||
.collect(Collectors.joining(","));
|
||||
}
|
||||
|
||||
@@ -139,16 +139,6 @@ CREATE TABLE IF NOT EXISTS tms_resource_task_step (
|
||||
-- 认证与设备基础表
|
||||
-- -----------------------------------------------------------------------------
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tms_auth_user (
|
||||
id BIGINT PRIMARY KEY,
|
||||
username VARCHAR(64) NOT NULL,
|
||||
display_name VARCHAR(128) NOT NULL,
|
||||
role VARCHAR(64) NOT NULL,
|
||||
create_time DATETIME(3) NOT NULL,
|
||||
update_time DATETIME(3) NOT NULL,
|
||||
UNIQUE KEY uk_tms_auth_user_username (username)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tms_device_node (
|
||||
id BIGINT PRIMARY KEY,
|
||||
node_id VARCHAR(64) NOT NULL,
|
||||
@@ -267,26 +257,6 @@ CREATE TABLE IF NOT EXISTS tms_role_ukey_binding (
|
||||
KEY idx_tms_role_ukey_binding_role_status (role_code, status)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tms_auth_user_account (
|
||||
id BIGINT PRIMARY KEY,
|
||||
username VARCHAR(64) NOT NULL,
|
||||
display_name VARCHAR(128) NOT NULL,
|
||||
role_code VARCHAR(64) NOT NULL,
|
||||
password_hash VARCHAR(256) NOT NULL,
|
||||
password_salt VARCHAR(128) NOT NULL,
|
||||
status VARCHAR(32) NOT NULL,
|
||||
need_change_password TINYINT(1) NOT NULL DEFAULT 1,
|
||||
password_changed_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
failed_count INT NOT NULL DEFAULT 0,
|
||||
locked_until DATETIME(3) NULL,
|
||||
last_login_at DATETIME(3) NULL,
|
||||
last_active_at DATETIME(3) NULL,
|
||||
create_time DATETIME(3) NOT NULL,
|
||||
update_time DATETIME(3) NOT NULL,
|
||||
UNIQUE KEY uk_tms_auth_user_account_username (username),
|
||||
KEY idx_tms_auth_user_account_role_code (role_code)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tms_auth_full_account (
|
||||
id BIGINT PRIMARY KEY,
|
||||
role_code VARCHAR(64) NOT NULL,
|
||||
@@ -439,110 +409,6 @@ VALUES
|
||||
CURRENT_TIMESTAMP(3)
|
||||
);
|
||||
|
||||
INSERT IGNORE INTO tms_auth_user_account (
|
||||
id,
|
||||
username,
|
||||
display_name,
|
||||
role_code,
|
||||
password_hash,
|
||||
password_salt,
|
||||
status,
|
||||
need_change_password,
|
||||
password_changed_at,
|
||||
failed_count,
|
||||
locked_until,
|
||||
last_login_at,
|
||||
last_active_at,
|
||||
create_time,
|
||||
update_time
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
12001,
|
||||
'super-admin-01',
|
||||
'超级管理员账号一',
|
||||
'SUPER_ADMIN',
|
||||
'Viuew8WE+qziUZwGGU0x25cUpzsTF+QvQFU0uhb+yVA=',
|
||||
'init-super-admin-salt-20260325',
|
||||
'ACTIVE',
|
||||
1,
|
||||
CURRENT_TIMESTAMP(3),
|
||||
0,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
CURRENT_TIMESTAMP(3),
|
||||
CURRENT_TIMESTAMP(3)
|
||||
),
|
||||
(
|
||||
12002,
|
||||
'super-admin-02',
|
||||
'超级管理员账号二',
|
||||
'SUPER_ADMIN',
|
||||
'Viuew8WE+qziUZwGGU0x25cUpzsTF+QvQFU0uhb+yVA=',
|
||||
'init-super-admin-salt-20260325',
|
||||
'ACTIVE',
|
||||
1,
|
||||
CURRENT_TIMESTAMP(3),
|
||||
0,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
CURRENT_TIMESTAMP(3),
|
||||
CURRENT_TIMESTAMP(3)
|
||||
),
|
||||
(
|
||||
12003,
|
||||
'key-admin-01',
|
||||
'密钥管理员账号',
|
||||
'KEY_ADMIN',
|
||||
'/xqmDy3A/q9X5p7XWznIPSBabQ6bxTLAPgtHQ6Be33c=',
|
||||
'init-key-admin-salt-20260325',
|
||||
'ACTIVE',
|
||||
1,
|
||||
CURRENT_TIMESTAMP(3),
|
||||
0,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
CURRENT_TIMESTAMP(3),
|
||||
CURRENT_TIMESTAMP(3)
|
||||
),
|
||||
(
|
||||
12004,
|
||||
'audit-admin-01',
|
||||
'审计管理员账号',
|
||||
'AUDIT_ADMIN',
|
||||
'j/J1LKyUOzVEJfbbHe2dwKBKNdabRk502olk/LWIwhg=',
|
||||
'init-audit-admin-salt-20260325',
|
||||
'ACTIVE',
|
||||
1,
|
||||
CURRENT_TIMESTAMP(3),
|
||||
0,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
CURRENT_TIMESTAMP(3),
|
||||
CURRENT_TIMESTAMP(3)
|
||||
),
|
||||
(
|
||||
12005,
|
||||
'ops-admin-01',
|
||||
'运维管理员账号',
|
||||
'OPS_ADMIN',
|
||||
'2e9F4aCgsDb+AuD1LrBEqEg8yjy3ODo27UHtx06vj/A=',
|
||||
'init-ops-admin-salt-20260325',
|
||||
'ACTIVE',
|
||||
1,
|
||||
CURRENT_TIMESTAMP(3),
|
||||
0,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
CURRENT_TIMESTAMP(3),
|
||||
CURRENT_TIMESTAMP(3)
|
||||
);
|
||||
|
||||
INSERT IGNORE INTO tms_auth_full_account (
|
||||
id,
|
||||
role_code,
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.cisd.tms.modules.auth.mapper.AuthUserAccountMapper">
|
||||
|
||||
<resultMap id="AuthUserAccountResultMap" type="com.cisd.tms.modules.auth.entity.AuthUserAccountEntity">
|
||||
<id property="id" column="id"/>
|
||||
<result property="username" column="username"/>
|
||||
<result property="displayName" column="display_name"/>
|
||||
<result property="roleCode" column="role_code"/>
|
||||
<result property="passwordHash" column="password_hash"/>
|
||||
<result property="passwordSalt" column="password_salt"/>
|
||||
<result property="status" column="status"/>
|
||||
<result property="needChangePassword" column="need_change_password"/>
|
||||
<result property="passwordChangedAt" column="password_changed_at"/>
|
||||
<result property="failedCount" column="failed_count"/>
|
||||
<result property="lockedUntil" column="locked_until"/>
|
||||
<result property="lastLoginAt" column="last_login_at"/>
|
||||
<result property="lastActiveAt" column="last_active_at"/>
|
||||
<result property="createTime" column="create_time"/>
|
||||
<result property="updateTime" column="update_time"/>
|
||||
</resultMap>
|
||||
|
||||
<select id="selectByUsername" parameterType="string" resultMap="AuthUserAccountResultMap">
|
||||
SELECT id,
|
||||
username,
|
||||
display_name,
|
||||
role_code,
|
||||
password_hash,
|
||||
password_salt,
|
||||
status,
|
||||
need_change_password,
|
||||
password_changed_at,
|
||||
failed_count,
|
||||
locked_until,
|
||||
last_login_at,
|
||||
last_active_at,
|
||||
create_time,
|
||||
update_time
|
||||
FROM tms_auth_user_account
|
||||
WHERE username = #{username}
|
||||
LIMIT 1
|
||||
</select>
|
||||
|
||||
<select id="selectByRoleCode" parameterType="string" resultMap="AuthUserAccountResultMap">
|
||||
SELECT id,
|
||||
username,
|
||||
display_name,
|
||||
role_code,
|
||||
password_hash,
|
||||
password_salt,
|
||||
status,
|
||||
need_change_password,
|
||||
password_changed_at,
|
||||
failed_count,
|
||||
locked_until,
|
||||
last_login_at,
|
||||
last_active_at,
|
||||
create_time,
|
||||
update_time
|
||||
FROM tms_auth_user_account
|
||||
WHERE role_code = #{roleCode}
|
||||
ORDER BY username ASC
|
||||
</select>
|
||||
</mapper>
|
||||
@@ -1,23 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.cisd.tms.modules.auth.mapper.AuthUserMapper">
|
||||
|
||||
<resultMap id="AuthUserResultMap" type="com.cisd.tms.modules.auth.entity.AuthUserEntity">
|
||||
<id property="id" column="id"/>
|
||||
<result property="username" column="username"/>
|
||||
<result property="displayName" column="display_name"/>
|
||||
<result property="role" column="role"/>
|
||||
<result property="createTime" column="create_time"/>
|
||||
<result property="updateTime" column="update_time"/>
|
||||
</resultMap>
|
||||
|
||||
<select id="selectByUsername" parameterType="string" resultMap="AuthUserResultMap">
|
||||
SELECT id, username, display_name, role, create_time, update_time
|
||||
FROM tms_auth_user
|
||||
WHERE username = #{username}
|
||||
LIMIT 1
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
@@ -48,7 +48,7 @@ class AuthControllerTest {
|
||||
"roleCode": "AUDIT_ADMIN",
|
||||
"accounts": [
|
||||
{
|
||||
"username": "audit-admin-01",
|
||||
"uid": 1,
|
||||
"password": "12345678"
|
||||
}
|
||||
],
|
||||
@@ -199,7 +199,7 @@ class AuthControllerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldChangeFullAccountPasswordThroughCurrentSessionEndpoint() throws Exception {
|
||||
void shouldChangeAccountPasswordThroughCurrentSessionEndpoint() throws Exception {
|
||||
AuthService authService = Mockito.mock(AuthService.class);
|
||||
AuthAdminService authAdminService = Mockito.mock(AuthAdminService.class);
|
||||
|
||||
@@ -208,7 +208,7 @@ class AuthControllerTest {
|
||||
.setControllerAdvice(new GlobalExceptionHandler())
|
||||
.build();
|
||||
|
||||
mockMvc.perform(post("/api/v1/auth/full-accounts/1/change-password")
|
||||
mockMvc.perform(post("/api/v1/auth/accounts/1/change-password")
|
||||
.requestAttr(InternalApiAuthInterceptor.ATTR_SESSION_TOKEN, "token-change-001")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("""
|
||||
@@ -220,32 +220,7 @@ class AuthControllerTest {
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().string(containsString("\"success\":true")));
|
||||
|
||||
Mockito.verify(authService).changeFullAccountPassword("token-change-001", 1, "12345678", "Abc1234!");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldChangeLimitedAccountPasswordThroughCurrentSessionEndpoint() throws Exception {
|
||||
AuthService authService = Mockito.mock(AuthService.class);
|
||||
AuthAdminService authAdminService = Mockito.mock(AuthAdminService.class);
|
||||
|
||||
MockMvc mockMvc = MockMvcBuilders
|
||||
.standaloneSetup(new AuthController(authService), new AuthAdminController(authAdminService))
|
||||
.setControllerAdvice(new GlobalExceptionHandler())
|
||||
.build();
|
||||
|
||||
mockMvc.perform(post("/api/v1/auth/limited-accounts/audit-admin-01/change-password")
|
||||
.requestAttr(InternalApiAuthInterceptor.ATTR_SESSION_TOKEN, "token-change-limited-001")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("""
|
||||
{
|
||||
"oldPassword": "12345678",
|
||||
"newPassword": "Abc1234!"
|
||||
}
|
||||
"""))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().string(containsString("\"success\":true")));
|
||||
|
||||
Mockito.verify(authService).changeLimitedAccountPassword("token-change-limited-001", "audit-admin-01", "12345678", "Abc1234!");
|
||||
Mockito.verify(authService).changeAccountPassword("token-change-001", 1, "12345678", "Abc1234!");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -268,7 +243,7 @@ class AuthControllerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldChangeFullAccountPasswordThroughAdminEndpoint() throws Exception {
|
||||
void shouldChangeAccountPasswordThroughAdminEndpoint() throws Exception {
|
||||
AuthService authService = Mockito.mock(AuthService.class);
|
||||
AuthAdminService authAdminService = Mockito.mock(AuthAdminService.class);
|
||||
|
||||
@@ -277,7 +252,7 @@ class AuthControllerTest {
|
||||
.setControllerAdvice(new GlobalExceptionHandler())
|
||||
.build();
|
||||
|
||||
mockMvc.perform(post("/api/v1/auth/roles/KEY_ADMIN/full-accounts/1/change-password")
|
||||
mockMvc.perform(post("/api/v1/auth/roles/KEY_ADMIN/accounts/1/change-password")
|
||||
.requestAttr(InternalApiAuthInterceptor.ATTR_ROLE_CODE, "SUPER_ADMIN")
|
||||
.requestAttr(InternalApiAuthInterceptor.ATTR_AUTH_LEVEL, "FULL")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
@@ -290,33 +265,7 @@ class AuthControllerTest {
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().string(containsString("\"success\":true")));
|
||||
|
||||
Mockito.verify(authAdminService).changeFullAccountPassword("SUPER_ADMIN", "FULL", "KEY_ADMIN", 1, "12345678", "Abc1234!");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldChangeLimitedAccountPasswordThroughAdminEndpoint() throws Exception {
|
||||
AuthService authService = Mockito.mock(AuthService.class);
|
||||
AuthAdminService authAdminService = Mockito.mock(AuthAdminService.class);
|
||||
|
||||
MockMvc mockMvc = MockMvcBuilders
|
||||
.standaloneSetup(new AuthController(authService), new AuthAdminController(authAdminService))
|
||||
.setControllerAdvice(new GlobalExceptionHandler())
|
||||
.build();
|
||||
|
||||
mockMvc.perform(post("/api/v1/auth/roles/AUDIT_ADMIN/limited-accounts/audit-admin-01/change-password")
|
||||
.requestAttr(InternalApiAuthInterceptor.ATTR_ROLE_CODE, "SUPER_ADMIN")
|
||||
.requestAttr(InternalApiAuthInterceptor.ATTR_AUTH_LEVEL, "FULL")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("""
|
||||
{
|
||||
"oldPassword": "12345678",
|
||||
"newPassword": "Abc1234!"
|
||||
}
|
||||
"""))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().string(containsString("\"success\":true")));
|
||||
|
||||
Mockito.verify(authAdminService).changeLimitedAccountPassword("SUPER_ADMIN", "FULL", "AUDIT_ADMIN", "audit-admin-01", "12345678", "Abc1234!");
|
||||
Mockito.verify(authAdminService).changeAccountPassword("SUPER_ADMIN", "FULL", "KEY_ADMIN", 1, "12345678", "Abc1234!");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.cisd.tms.modules.auth.service;
|
||||
|
||||
import com.cisd.tms.modules.auth.entity.AuthUserAccountEntity;
|
||||
import com.cisd.tms.common.enums.ErrorCode;
|
||||
import com.cisd.tms.common.exception.BizException;
|
||||
import com.cisd.tms.modules.auth.entity.AuthFullAccountEntity;
|
||||
import com.cisd.tms.modules.auth.entity.RoleAccountEntity;
|
||||
import com.cisd.tms.modules.auth.entity.RoleUkeyBindingEntity;
|
||||
@@ -8,21 +9,20 @@ import com.cisd.tms.modules.auth.enums.AuthLevel;
|
||||
import com.cisd.tms.modules.auth.enums.RoleAccountStatus;
|
||||
import com.cisd.tms.modules.auth.enums.RoleCode;
|
||||
import com.cisd.tms.modules.auth.repository.AuthFullAccountRepository;
|
||||
import com.cisd.tms.modules.auth.repository.AuthUserAccountRepository;
|
||||
import com.cisd.tms.modules.auth.repository.RoleAccountRepository;
|
||||
import com.cisd.tms.modules.auth.repository.RoleUkeyBindingRepository;
|
||||
import com.cisd.tms.modules.auth.service.impl.AuthAdminServiceImpl;
|
||||
import com.cisd.tms.modules.mk.dto.MasterKeyBackupPacket;
|
||||
import com.cisd.tms.modules.mk.dto.UKeySignDTO;
|
||||
import com.cisd.tms.modules.mk.dto.UKeySignResult;
|
||||
import com.cisd.tms.modules.mk.service.LmkService;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import java.time.Clock;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.Deque;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
@@ -33,412 +33,148 @@ import org.mockito.Mockito;
|
||||
|
||||
class AuthAdminServiceTest {
|
||||
|
||||
private static final Clock FIXED_CLOCK = Clock.fixed(Instant.parse("2026-03-23T03:00:00Z"), ZoneOffset.UTC);
|
||||
private static final Clock FIXED_CLOCK = Clock.fixed(Instant.parse("2026-03-23T02:00:00Z"), ZoneOffset.UTC);
|
||||
|
||||
@Test
|
||||
void shouldEnableRoleWhenOperatorIsKeyAdminWithFullSession() {
|
||||
InMemoryRoleAccountRepository roleAccounts = new InMemoryRoleAccountRepository();
|
||||
InMemoryAuthFullAccountRepository fullAccounts = new InMemoryAuthFullAccountRepository();
|
||||
InMemoryAuthUserAccountRepository userAccounts = new InMemoryAuthUserAccountRepository();
|
||||
roleAccounts.save(role(RoleCode.AUDIT_ADMIN, RoleAccountStatus.UNENABLED));
|
||||
fullAccounts.save(fullAccount(RoleCode.AUDIT_ADMIN, 1, "audit-admin-full-01", "OLD-FULL-HASH-1", "OLD-FULL-SALT-1"));
|
||||
userAccounts.save(user("audit-admin-01", RoleCode.AUDIT_ADMIN, "OLD-HASH-1", "OLD-SALT-1"));
|
||||
fullAccounts.findByRoleCodeAndUid(RoleCode.AUDIT_ADMIN.getCode(), 1).orElseThrow().setStatus(RoleAccountStatus.UNENABLED.name());
|
||||
userAccounts.findByUsername("audit-admin-01").orElseThrow().setStatus(RoleAccountStatus.UNENABLED.name());
|
||||
void shouldEnableRoleAndUnifiedSeatAccounts() {
|
||||
InMemoryRoleAccountRepository roles = new InMemoryRoleAccountRepository();
|
||||
InMemoryAuthFullAccountRepository accounts = new InMemoryAuthFullAccountRepository();
|
||||
RoleAccountEntity role = role(RoleCode.AUDIT_ADMIN, RoleAccountStatus.UNENABLED);
|
||||
roles.save(role);
|
||||
AuthFullAccountEntity account = account(RoleCode.AUDIT_ADMIN, 1, "audit-admin-01", "HASH-OLD", "SALT-OLD");
|
||||
account.setStatus(RoleAccountStatus.UNENABLED.name());
|
||||
account.setFailedCount(3);
|
||||
account.setLockedUntil(LocalDateTime.of(2026, 3, 23, 3, 0));
|
||||
accounts.save(account);
|
||||
|
||||
AuthAdminService service = newAuthAdminService(
|
||||
roleAccounts,
|
||||
fullAccounts,
|
||||
userAccounts,
|
||||
new InMemoryRoleUkeyBindingRepository(),
|
||||
FIXED_CLOCK,
|
||||
new FixedSaltSupplier("salt-001")
|
||||
);
|
||||
service(roles, accounts, new InMemoryRoleUkeyBindingRepository(), Mockito.mock(LmkService.class))
|
||||
.enableRole(RoleCode.SUPER_ADMIN.getCode(), AuthLevel.FULL.name(), RoleCode.AUDIT_ADMIN.getCode());
|
||||
|
||||
service.enableRole(RoleCode.KEY_ADMIN.getCode(), AuthLevel.FULL.name(), RoleCode.AUDIT_ADMIN.getCode());
|
||||
|
||||
RoleAccountEntity updated = roleAccounts.findByRoleCode(RoleCode.AUDIT_ADMIN.getCode()).orElseThrow();
|
||||
AuthFullAccountEntity fullAccount = fullAccounts.findByRoleCodeAndUid(RoleCode.AUDIT_ADMIN.getCode(), 1).orElseThrow();
|
||||
AuthUserAccountEntity userAccount = userAccounts.findByUsername("audit-admin-01").orElseThrow();
|
||||
Assertions.assertEquals(RoleAccountStatus.ACTIVE.name(), updated.getStatus());
|
||||
Assertions.assertEquals(RoleAccountStatus.ACTIVE.name(), fullAccount.getStatus());
|
||||
Assertions.assertTrue(Boolean.TRUE.equals(fullAccount.getNeedChangePassword()));
|
||||
Assertions.assertEquals(RoleAccountStatus.ACTIVE.name(), userAccount.getStatus());
|
||||
Assertions.assertTrue(Boolean.TRUE.equals(userAccount.getNeedChangePassword()));
|
||||
Assertions.assertEquals(RoleAccountStatus.ACTIVE.name(), roles.findByRoleCode(RoleCode.AUDIT_ADMIN.getCode()).orElseThrow().getStatus());
|
||||
AuthFullAccountEntity changed = accounts.findByRoleCodeAndUid(RoleCode.AUDIT_ADMIN.getCode(), 1).orElseThrow();
|
||||
Assertions.assertEquals(RoleAccountStatus.ACTIVE.name(), changed.getStatus());
|
||||
Assertions.assertTrue(Boolean.TRUE.equals(changed.getNeedChangePassword()));
|
||||
Assertions.assertEquals(0, changed.getFailedCount());
|
||||
Assertions.assertNull(changed.getLockedUntil());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldResetPasswordForAllAccountsUnderRole() {
|
||||
InMemoryRoleAccountRepository roleAccounts = new InMemoryRoleAccountRepository();
|
||||
InMemoryAuthFullAccountRepository fullAccounts = new InMemoryAuthFullAccountRepository();
|
||||
InMemoryAuthUserAccountRepository userAccounts = new InMemoryAuthUserAccountRepository();
|
||||
roleAccounts.save(role(RoleCode.SUPER_ADMIN, RoleAccountStatus.ACTIVE));
|
||||
fullAccounts.save(fullAccount(RoleCode.SUPER_ADMIN, 1, "super-admin-full-01", "OLD-FULL-HASH-1", "OLD-FULL-SALT-1"));
|
||||
fullAccounts.save(fullAccount(RoleCode.SUPER_ADMIN, 2, "super-admin-full-02", "OLD-FULL-HASH-2", "OLD-FULL-SALT-2"));
|
||||
userAccounts.save(user("super-admin-01", RoleCode.SUPER_ADMIN, "OLD-HASH-1", "OLD-SALT-1"));
|
||||
userAccounts.save(user("super-admin-02", RoleCode.SUPER_ADMIN, "OLD-HASH-2", "OLD-SALT-2"));
|
||||
fullAccounts.findByRoleCodeAndUid(RoleCode.SUPER_ADMIN.getCode(), 1).orElseThrow().setFailedCount(2);
|
||||
fullAccounts.findByRoleCodeAndUid(RoleCode.SUPER_ADMIN.getCode(), 1).orElseThrow().setLockedUntil(LocalDateTime.of(2026, 3, 23, 3, 20));
|
||||
fullAccounts.findByRoleCodeAndUid(RoleCode.SUPER_ADMIN.getCode(), 2).orElseThrow().setFailedCount(5);
|
||||
fullAccounts.findByRoleCodeAndUid(RoleCode.SUPER_ADMIN.getCode(), 2).orElseThrow().setLockedUntil(LocalDateTime.of(2026, 3, 23, 3, 25));
|
||||
userAccounts.findByUsername("super-admin-01").orElseThrow().setFailedCount(3);
|
||||
userAccounts.findByUsername("super-admin-01").orElseThrow().setLockedUntil(LocalDateTime.of(2026, 3, 23, 3, 30));
|
||||
userAccounts.findByUsername("super-admin-02").orElseThrow().setFailedCount(5);
|
||||
userAccounts.findByUsername("super-admin-02").orElseThrow().setLockedUntil(LocalDateTime.of(2026, 3, 23, 3, 40));
|
||||
void shouldResetOnlyUnifiedSeatPasswords() {
|
||||
InMemoryRoleAccountRepository roles = new InMemoryRoleAccountRepository();
|
||||
InMemoryAuthFullAccountRepository accounts = new InMemoryAuthFullAccountRepository();
|
||||
roles.save(role(RoleCode.SUPER_ADMIN, RoleAccountStatus.ACTIVE));
|
||||
accounts.save(account(RoleCode.SUPER_ADMIN, 1, "super-admin-01", "OLD-HASH-1", "OLD-SALT-1"));
|
||||
accounts.save(account(RoleCode.SUPER_ADMIN, 2, "super-admin-02", "OLD-HASH-2", "OLD-SALT-2"));
|
||||
|
||||
AuthAdminService service = newAuthAdminService(
|
||||
roleAccounts,
|
||||
fullAccounts,
|
||||
userAccounts,
|
||||
new InMemoryRoleUkeyBindingRepository(),
|
||||
FIXED_CLOCK,
|
||||
new FixedSaltSupplier("salt-full-001", "salt-full-002", "salt-002", "salt-003")
|
||||
);
|
||||
service(roles, accounts, new InMemoryRoleUkeyBindingRepository(), Mockito.mock(LmkService.class))
|
||||
.resetPassword(RoleCode.SUPER_ADMIN.getCode(), AuthLevel.FULL.name(), RoleCode.SUPER_ADMIN.getCode());
|
||||
|
||||
service.resetPassword(RoleCode.KEY_ADMIN.getCode(), AuthLevel.FULL.name(), RoleCode.SUPER_ADMIN.getCode());
|
||||
|
||||
RoleAccountEntity role = roleAccounts.findByRoleCode(RoleCode.SUPER_ADMIN.getCode()).orElseThrow();
|
||||
AuthFullAccountEntity fullFirst = fullAccounts.findByRoleCodeAndUid(RoleCode.SUPER_ADMIN.getCode(), 1).orElseThrow();
|
||||
AuthFullAccountEntity fullSecond = fullAccounts.findByRoleCodeAndUid(RoleCode.SUPER_ADMIN.getCode(), 2).orElseThrow();
|
||||
AuthUserAccountEntity first = userAccounts.findByUsername("super-admin-01").orElseThrow();
|
||||
AuthUserAccountEntity second = userAccounts.findByUsername("super-admin-02").orElseThrow();
|
||||
Assertions.assertEquals(RoleAccountStatus.ACTIVE.name(), role.getStatus());
|
||||
Assertions.assertEquals(RoleAccountStatus.ACTIVE.name(), fullFirst.getStatus());
|
||||
Assertions.assertEquals(RoleAccountStatus.ACTIVE.name(), fullSecond.getStatus());
|
||||
Assertions.assertTrue(Boolean.TRUE.equals(fullFirst.getNeedChangePassword()));
|
||||
Assertions.assertTrue(Boolean.TRUE.equals(fullSecond.getNeedChangePassword()));
|
||||
Assertions.assertEquals(LocalDateTime.of(2026, 3, 23, 3, 0), fullFirst.getPasswordChangedAt());
|
||||
Assertions.assertEquals(LocalDateTime.of(2026, 3, 23, 3, 0), fullSecond.getPasswordChangedAt());
|
||||
Assertions.assertEquals(0, fullFirst.getFailedCount());
|
||||
Assertions.assertEquals(0, fullSecond.getFailedCount());
|
||||
Assertions.assertNull(fullFirst.getLockedUntil());
|
||||
Assertions.assertNull(fullSecond.getLockedUntil());
|
||||
Assertions.assertEquals("salt-full-001", fullFirst.getPasswordSalt());
|
||||
Assertions.assertEquals("HASH:12345678:salt-full-001", fullFirst.getPasswordHash());
|
||||
Assertions.assertEquals("salt-full-002", fullSecond.getPasswordSalt());
|
||||
Assertions.assertEquals("HASH:12345678:salt-full-002", fullSecond.getPasswordHash());
|
||||
Assertions.assertEquals(RoleAccountStatus.ACTIVE.name(), first.getStatus());
|
||||
Assertions.assertEquals(RoleAccountStatus.ACTIVE.name(), second.getStatus());
|
||||
AuthFullAccountEntity first = accounts.findByRoleCodeAndUid(RoleCode.SUPER_ADMIN.getCode(), 1).orElseThrow();
|
||||
AuthFullAccountEntity second = accounts.findByRoleCodeAndUid(RoleCode.SUPER_ADMIN.getCode(), 2).orElseThrow();
|
||||
Assertions.assertEquals("salt-reset-1", first.getPasswordSalt());
|
||||
Assertions.assertEquals("HASH:12345678:salt-reset-1", first.getPasswordHash());
|
||||
Assertions.assertEquals("salt-reset-2", second.getPasswordSalt());
|
||||
Assertions.assertEquals("HASH:12345678:salt-reset-2", second.getPasswordHash());
|
||||
Assertions.assertTrue(Boolean.TRUE.equals(first.getNeedChangePassword()));
|
||||
Assertions.assertTrue(Boolean.TRUE.equals(second.getNeedChangePassword()));
|
||||
Assertions.assertEquals(LocalDateTime.of(2026, 3, 23, 3, 0), first.getPasswordChangedAt());
|
||||
Assertions.assertEquals(LocalDateTime.of(2026, 3, 23, 3, 0), second.getPasswordChangedAt());
|
||||
Assertions.assertEquals(0, first.getFailedCount());
|
||||
Assertions.assertEquals(0, second.getFailedCount());
|
||||
Assertions.assertNull(first.getLockedUntil());
|
||||
Assertions.assertNull(second.getLockedUntil());
|
||||
Assertions.assertEquals("salt-002", first.getPasswordSalt());
|
||||
Assertions.assertEquals("HASH:12345678:salt-002", first.getPasswordHash());
|
||||
Assertions.assertEquals("salt-003", second.getPasswordSalt());
|
||||
Assertions.assertEquals("HASH:12345678:salt-003", second.getPasswordHash());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldChangeTargetFullAccountPasswordByAdmin() {
|
||||
InMemoryRoleAccountRepository roleAccounts = new InMemoryRoleAccountRepository();
|
||||
InMemoryAuthFullAccountRepository fullAccounts = new InMemoryAuthFullAccountRepository();
|
||||
roleAccounts.save(role(RoleCode.KEY_ADMIN, RoleAccountStatus.ACTIVE));
|
||||
fullAccounts.save(fullAccount(RoleCode.KEY_ADMIN, 1, "key-admin-full-01", "HASH:12345678:OLD-FULL-SALT", "OLD-FULL-SALT"));
|
||||
AuthFullAccountEntity account = fullAccounts.findByRoleCodeAndUid(RoleCode.KEY_ADMIN.getCode(), 1).orElseThrow();
|
||||
account.setStatus(RoleAccountStatus.LOCKED.name());
|
||||
account.setFailedCount(5);
|
||||
account.setLockedUntil(LocalDateTime.of(2026, 3, 23, 3, 50));
|
||||
void shouldChangeUnifiedSeatPassword() {
|
||||
InMemoryRoleAccountRepository roles = new InMemoryRoleAccountRepository();
|
||||
InMemoryAuthFullAccountRepository accounts = new InMemoryAuthFullAccountRepository();
|
||||
roles.save(role(RoleCode.KEY_ADMIN, RoleAccountStatus.ACTIVE));
|
||||
accounts.save(account(RoleCode.KEY_ADMIN, 1, "key-admin-01", "HASH:Old1234!:SALT-OLD", "SALT-OLD"));
|
||||
|
||||
AuthAdminService service = newAuthAdminService(
|
||||
roleAccounts,
|
||||
fullAccounts,
|
||||
new InMemoryAuthUserAccountRepository(),
|
||||
new InMemoryRoleUkeyBindingRepository(),
|
||||
FIXED_CLOCK,
|
||||
new FixedSaltSupplier("salt-full-admin")
|
||||
);
|
||||
|
||||
service.changeFullAccountPassword(
|
||||
service(roles, accounts, new InMemoryRoleUkeyBindingRepository(), Mockito.mock(LmkService.class))
|
||||
.changeAccountPassword(
|
||||
RoleCode.SUPER_ADMIN.getCode(),
|
||||
AuthLevel.FULL.name(),
|
||||
RoleCode.KEY_ADMIN.getCode(),
|
||||
1,
|
||||
"12345678",
|
||||
"Abc1234!"
|
||||
"Old1234!",
|
||||
"New1234!"
|
||||
);
|
||||
|
||||
AuthFullAccountEntity changed = fullAccounts.findByRoleCodeAndUid(RoleCode.KEY_ADMIN.getCode(), 1).orElseThrow();
|
||||
Assertions.assertEquals("salt-full-admin", changed.getPasswordSalt());
|
||||
Assertions.assertEquals("HASH:Abc1234!:salt-full-admin", changed.getPasswordHash());
|
||||
Assertions.assertEquals(RoleAccountStatus.ACTIVE.name(), changed.getStatus());
|
||||
AuthFullAccountEntity changed = accounts.findByRoleCodeAndUid(RoleCode.KEY_ADMIN.getCode(), 1).orElseThrow();
|
||||
Assertions.assertEquals("salt-reset-1", changed.getPasswordSalt());
|
||||
Assertions.assertEquals("HASH:New1234!:salt-reset-1", changed.getPasswordHash());
|
||||
Assertions.assertTrue(Boolean.TRUE.equals(changed.getNeedChangePassword()));
|
||||
Assertions.assertEquals(LocalDateTime.of(2026, 3, 23, 3, 0), changed.getPasswordChangedAt());
|
||||
Assertions.assertEquals(0, changed.getFailedCount());
|
||||
Assertions.assertNull(changed.getLockedUntil());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldChangeTargetLimitedAccountPasswordByAdmin() {
|
||||
InMemoryRoleAccountRepository roleAccounts = new InMemoryRoleAccountRepository();
|
||||
InMemoryAuthUserAccountRepository userAccounts = new InMemoryAuthUserAccountRepository();
|
||||
roleAccounts.save(role(RoleCode.AUDIT_ADMIN, RoleAccountStatus.ACTIVE));
|
||||
userAccounts.save(user("audit-admin-01", RoleCode.AUDIT_ADMIN, "HASH:12345678:OLD-SALT", "OLD-SALT"));
|
||||
AuthUserAccountEntity account = userAccounts.findByUsername("audit-admin-01").orElseThrow();
|
||||
account.setStatus(RoleAccountStatus.LOCKED.name());
|
||||
account.setFailedCount(5);
|
||||
account.setLockedUntil(LocalDateTime.of(2026, 3, 23, 3, 55));
|
||||
|
||||
AuthAdminService service = newAuthAdminService(
|
||||
roleAccounts,
|
||||
new InMemoryAuthFullAccountRepository(),
|
||||
userAccounts,
|
||||
new InMemoryRoleUkeyBindingRepository(),
|
||||
FIXED_CLOCK,
|
||||
new FixedSaltSupplier("salt-limited-admin")
|
||||
);
|
||||
|
||||
service.changeLimitedAccountPassword(
|
||||
RoleCode.SUPER_ADMIN.getCode(),
|
||||
AuthLevel.FULL.name(),
|
||||
RoleCode.AUDIT_ADMIN.getCode(),
|
||||
"audit-admin-01",
|
||||
"12345678",
|
||||
"Abc1234!"
|
||||
);
|
||||
|
||||
AuthUserAccountEntity changed = userAccounts.findByUsername("audit-admin-01").orElseThrow();
|
||||
Assertions.assertEquals("salt-limited-admin", changed.getPasswordSalt());
|
||||
Assertions.assertEquals("HASH:Abc1234!:salt-limited-admin", changed.getPasswordHash());
|
||||
Assertions.assertEquals(RoleAccountStatus.ACTIVE.name(), changed.getStatus());
|
||||
Assertions.assertTrue(Boolean.TRUE.equals(changed.getNeedChangePassword()));
|
||||
Assertions.assertEquals(LocalDateTime.of(2026, 3, 23, 3, 0), changed.getPasswordChangedAt());
|
||||
Assertions.assertEquals(0, changed.getFailedCount());
|
||||
Assertions.assertNull(changed.getLockedUntil());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectAdminPasswordChangeWhenOldPasswordIsWrong() {
|
||||
InMemoryRoleAccountRepository roleAccounts = new InMemoryRoleAccountRepository();
|
||||
InMemoryAuthUserAccountRepository userAccounts = new InMemoryAuthUserAccountRepository();
|
||||
roleAccounts.save(role(RoleCode.AUDIT_ADMIN, RoleAccountStatus.ACTIVE));
|
||||
userAccounts.save(user("audit-admin-01", RoleCode.AUDIT_ADMIN, "HASH:12345678:OLD-SALT", "OLD-SALT"));
|
||||
InMemoryRoleAccountRepository roles = new InMemoryRoleAccountRepository();
|
||||
InMemoryAuthFullAccountRepository accounts = new InMemoryAuthFullAccountRepository();
|
||||
roles.save(role(RoleCode.KEY_ADMIN, RoleAccountStatus.ACTIVE));
|
||||
accounts.save(account(RoleCode.KEY_ADMIN, 1, "key-admin-01", "HASH:Old1234!:SALT-OLD", "SALT-OLD"));
|
||||
|
||||
AuthAdminService service = newAuthAdminService(
|
||||
roleAccounts,
|
||||
new InMemoryAuthFullAccountRepository(),
|
||||
userAccounts,
|
||||
new InMemoryRoleUkeyBindingRepository(),
|
||||
FIXED_CLOCK,
|
||||
new FixedSaltSupplier("salt-unused")
|
||||
);
|
||||
|
||||
com.cisd.tms.common.exception.BizException exception = Assertions.assertThrows(
|
||||
com.cisd.tms.common.exception.BizException.class,
|
||||
() -> service.changeLimitedAccountPassword(
|
||||
RoleCode.SUPER_ADMIN.getCode(),
|
||||
AuthLevel.FULL.name(),
|
||||
RoleCode.AUDIT_ADMIN.getCode(),
|
||||
"audit-admin-01",
|
||||
"bad-password",
|
||||
"Abc1234!"
|
||||
)
|
||||
);
|
||||
|
||||
Assertions.assertEquals("old password is incorrect", exception.getMessage());
|
||||
AuthUserAccountEntity unchanged = userAccounts.findByUsername("audit-admin-01").orElseThrow();
|
||||
Assertions.assertEquals("OLD-SALT", unchanged.getPasswordSalt());
|
||||
Assertions.assertEquals("HASH:12345678:OLD-SALT", unchanged.getPasswordHash());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectAdminPasswordChangeWhenNewPasswordIsTooSimple() {
|
||||
InMemoryRoleAccountRepository roleAccounts = new InMemoryRoleAccountRepository();
|
||||
InMemoryAuthUserAccountRepository userAccounts = new InMemoryAuthUserAccountRepository();
|
||||
roleAccounts.save(role(RoleCode.AUDIT_ADMIN, RoleAccountStatus.ACTIVE));
|
||||
userAccounts.save(user("audit-admin-01", RoleCode.AUDIT_ADMIN, "HASH:12345678:OLD-SALT", "OLD-SALT"));
|
||||
|
||||
AuthAdminService service = newAuthAdminService(
|
||||
roleAccounts,
|
||||
new InMemoryAuthFullAccountRepository(),
|
||||
userAccounts,
|
||||
new InMemoryRoleUkeyBindingRepository(),
|
||||
FIXED_CLOCK,
|
||||
new FixedSaltSupplier("salt-unused")
|
||||
);
|
||||
|
||||
com.cisd.tms.common.exception.BizException exception = Assertions.assertThrows(
|
||||
com.cisd.tms.common.exception.BizException.class,
|
||||
() -> service.changeLimitedAccountPassword(
|
||||
RoleCode.SUPER_ADMIN.getCode(),
|
||||
AuthLevel.FULL.name(),
|
||||
RoleCode.AUDIT_ADMIN.getCode(),
|
||||
"audit-admin-01",
|
||||
"12345678",
|
||||
"87654321"
|
||||
)
|
||||
);
|
||||
|
||||
Assertions.assertEquals("密码复杂度不符合要求:需由8到16位大小写字母、数字、符号组成,且三类均需包含", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUpdateActiveBindingWhenIssueSignUsesSameSerial() {
|
||||
InMemoryRoleUkeyBindingRepository bindings = new InMemoryRoleUkeyBindingRepository();
|
||||
bindings.save(binding(RoleCode.SUPER_ADMIN, 1, "UK-OLD", "PUB-OLD", "SIG-OLD"));
|
||||
LmkService lmkService = Mockito.mock(LmkService.class);
|
||||
Mockito.when(lmkService.exportIkPublicKeyHex()).thenReturn("IK-PUB-001");
|
||||
Mockito.when(lmkService.signIk(org.mockito.ArgumentMatchers.anyString())).thenReturn("SIG-NEW");
|
||||
com.cisd.tms.modules.mk.dto.MasterKeyBackupPacket packet = new com.cisd.tms.modules.mk.dto.MasterKeyBackupPacket();
|
||||
packet.setPacketIndex(1);
|
||||
Mockito.when(lmkService.buildBackupPacket(1)).thenReturn(packet);
|
||||
|
||||
AuthAdminService service = new AuthAdminServiceImpl(
|
||||
new InMemoryRoleAccountRepository(),
|
||||
fullAccountRepository(binding(RoleCode.SUPER_ADMIN, 1, "UK-OLD", "PUB-OLD", "SIG-OLD")),
|
||||
new InMemoryAuthUserAccountRepository(),
|
||||
bindings,
|
||||
new FakePasswordHasher(),
|
||||
lmkService,
|
||||
new com.fasterxml.jackson.databind.ObjectMapper(),
|
||||
FIXED_CLOCK,
|
||||
new FixedSaltSupplier("salt-001")
|
||||
);
|
||||
|
||||
UKeySignDTO dto = new UKeySignDTO();
|
||||
dto.setPubKey("PUB-NEW");
|
||||
dto.setUkeySerial("UK-OLD");
|
||||
dto.setUid(1);
|
||||
|
||||
service.issueUkeyBindingSign(
|
||||
null,
|
||||
null,
|
||||
RoleCode.SUPER_ADMIN.getCode(),
|
||||
dto
|
||||
);
|
||||
|
||||
RoleUkeyBindingEntity updated = bindings.findActiveByRoleCodeAndUid(RoleCode.SUPER_ADMIN.getCode(), 1).stream()
|
||||
.filter(binding -> "UK-OLD".equals(binding.getUkeySerial()))
|
||||
.findFirst()
|
||||
.orElseThrow();
|
||||
Assertions.assertEquals("UK-OLD", updated.getUkeySerial());
|
||||
Assertions.assertEquals("PUB-NEW", updated.getUkeyPubkey());
|
||||
Assertions.assertEquals("SIG-NEW", updated.getIssuerSign());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldIssueBindingSignWithBackupPacketForSuperAdmin() {
|
||||
LmkService lmkService = Mockito.mock(LmkService.class);
|
||||
Mockito.when(lmkService.exportIkPublicKeyHex()).thenReturn("IK-PUB-001");
|
||||
Mockito.when(lmkService.signIk(org.mockito.ArgumentMatchers.anyString())).thenReturn("ISSUE-SIGN-001");
|
||||
com.cisd.tms.modules.mk.dto.MasterKeyBackupPacket packet = new com.cisd.tms.modules.mk.dto.MasterKeyBackupPacket();
|
||||
packet.setPacketIndex(1);
|
||||
Mockito.when(lmkService.buildBackupPacket(1)).thenReturn(packet);
|
||||
|
||||
AuthAdminService service = new AuthAdminServiceImpl(
|
||||
new InMemoryRoleAccountRepository(),
|
||||
fullAccountRepository(binding(RoleCode.SUPER_ADMIN, 1, "UK-001", "PUB-OLD", "SIG-OLD")),
|
||||
new InMemoryAuthUserAccountRepository(),
|
||||
new InMemoryRoleUkeyBindingRepository(),
|
||||
new FakePasswordHasher(),
|
||||
lmkService,
|
||||
new com.fasterxml.jackson.databind.ObjectMapper(),
|
||||
FIXED_CLOCK,
|
||||
new FixedSaltSupplier("salt-001")
|
||||
);
|
||||
UKeySignDTO dto = new UKeySignDTO();
|
||||
dto.setPubKey("PUB-001");
|
||||
dto.setUkeySerial("UK-001");
|
||||
dto.setUid(1);
|
||||
|
||||
UKeySignResult result = service.issueUkeyBindingSign(RoleCode.KEY_ADMIN.getCode(), AuthLevel.FULL.name(), RoleCode.SUPER_ADMIN.getCode(), dto);
|
||||
|
||||
Assertions.assertEquals("ISSUE-SIGN-001", result.getSign());
|
||||
Assertions.assertNotNull(result.getBackupPacket());
|
||||
Assertions.assertEquals(1, result.getBackupPacket().getPacketIndex());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldIssueAndBindNormalRoleUkeyInOneStep() {
|
||||
InMemoryRoleUkeyBindingRepository bindings = new InMemoryRoleUkeyBindingRepository();
|
||||
LmkService lmkService = Mockito.mock(LmkService.class);
|
||||
Mockito.when(lmkService.exportIkPublicKeyHex()).thenReturn("IK-PUB-001");
|
||||
Mockito.when(lmkService.signIk(org.mockito.ArgumentMatchers.anyString())).thenReturn("ISSUE-SIGN-KEY-001");
|
||||
|
||||
AuthAdminService service = new AuthAdminServiceImpl(
|
||||
new InMemoryRoleAccountRepository(),
|
||||
fullAccountRepository(binding(RoleCode.KEY_ADMIN, 1, "UK-KEY-001", "PUB-OLD", "SIG-OLD")),
|
||||
new InMemoryAuthUserAccountRepository(),
|
||||
bindings,
|
||||
new FakePasswordHasher(),
|
||||
lmkService,
|
||||
new com.fasterxml.jackson.databind.ObjectMapper(),
|
||||
FIXED_CLOCK,
|
||||
new FixedSaltSupplier("salt-001")
|
||||
);
|
||||
UKeySignDTO dto = new UKeySignDTO();
|
||||
dto.setPubKey("PUB-KEY-001");
|
||||
dto.setUkeySerial("UK-KEY-001");
|
||||
dto.setUid(1);
|
||||
|
||||
UKeySignResult result = service.issueUkeyBindingSign(
|
||||
BizException exception = Assertions.assertThrows(BizException.class,
|
||||
() -> service(roles, accounts, new InMemoryRoleUkeyBindingRepository(), Mockito.mock(LmkService.class))
|
||||
.changeAccountPassword(
|
||||
RoleCode.SUPER_ADMIN.getCode(),
|
||||
AuthLevel.FULL.name(),
|
||||
RoleCode.KEY_ADMIN.getCode(),
|
||||
dto
|
||||
);
|
||||
1,
|
||||
"bad-password",
|
||||
"New1234!"
|
||||
));
|
||||
|
||||
Assertions.assertEquals("ISSUE-SIGN-KEY-001", result.getSign());
|
||||
Assertions.assertNull(result.getBackupPacket());
|
||||
RoleUkeyBindingEntity binding = bindings.findActiveByRoleCodeAndUid(RoleCode.KEY_ADMIN.getCode(), 1).stream()
|
||||
.filter(item -> "UK-KEY-001".equals(item.getUkeySerial()))
|
||||
.findFirst()
|
||||
.orElseThrow();
|
||||
Assertions.assertEquals("PUB-KEY-001", binding.getUkeyPubkey());
|
||||
Assertions.assertEquals("ISSUE-SIGN-KEY-001", binding.getIssuerSign());
|
||||
Assertions.assertEquals(ErrorCode.UNAUTHORIZED.getCode(), exception.getCode());
|
||||
Assertions.assertEquals("old password is incorrect", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldIssueSuperAdminBackupUkeyWithoutReplacingExistingBindingForSameUid() {
|
||||
InMemoryRoleAccountRepository roles = new InMemoryRoleAccountRepository();
|
||||
InMemoryAuthFullAccountRepository accounts = new InMemoryAuthFullAccountRepository();
|
||||
InMemoryRoleUkeyBindingRepository bindings = new InMemoryRoleUkeyBindingRepository();
|
||||
roles.save(role(RoleCode.SUPER_ADMIN, RoleAccountStatus.ACTIVE));
|
||||
accounts.save(account(RoleCode.SUPER_ADMIN, 1, "super-admin-01", "HASH-OLD", "SALT-OLD"));
|
||||
bindings.save(binding(RoleCode.SUPER_ADMIN, 1, "UK-PRIMARY", "PUB-PRIMARY", "SIG-PRIMARY"));
|
||||
LmkService lmkService = Mockito.mock(LmkService.class);
|
||||
Mockito.when(lmkService.exportIkPublicKeyHex()).thenReturn("IK-PUB-001");
|
||||
Mockito.when(lmkService.signIk(org.mockito.ArgumentMatchers.anyString())).thenReturn("ISSUE-SIGN-BACKUP");
|
||||
com.cisd.tms.modules.mk.dto.MasterKeyBackupPacket packet = new com.cisd.tms.modules.mk.dto.MasterKeyBackupPacket();
|
||||
Mockito.when(lmkService.signIk(Mockito.anyString())).thenReturn("ISSUE-SIGN-BACKUP");
|
||||
MasterKeyBackupPacket packet = new MasterKeyBackupPacket();
|
||||
packet.setPacketIndex(1);
|
||||
Mockito.when(lmkService.buildBackupPacket(1)).thenReturn(packet);
|
||||
|
||||
AuthAdminService service = new AuthAdminServiceImpl(
|
||||
new InMemoryRoleAccountRepository(),
|
||||
fullAccountRepository(binding(RoleCode.SUPER_ADMIN, 1, "UK-PRIMARY", "PUB-PRIMARY", "SIG-PRIMARY")),
|
||||
new InMemoryAuthUserAccountRepository(),
|
||||
bindings,
|
||||
new FakePasswordHasher(),
|
||||
lmkService,
|
||||
new com.fasterxml.jackson.databind.ObjectMapper(),
|
||||
FIXED_CLOCK,
|
||||
new FixedSaltSupplier("salt-001")
|
||||
);
|
||||
UKeySignDTO dto = new UKeySignDTO();
|
||||
dto.setPubKey("PUB-BACKUP");
|
||||
dto.setUkeySerial("UK-BACKUP");
|
||||
dto.setUid(1);
|
||||
|
||||
UKeySignResult result = service.issueUkeyBindingSign(
|
||||
RoleCode.KEY_ADMIN.getCode(),
|
||||
AuthLevel.FULL.name(),
|
||||
RoleCode.SUPER_ADMIN.getCode(),
|
||||
dto
|
||||
);
|
||||
UKeySignResult result = service(roles, accounts, bindings, lmkService)
|
||||
.issueUkeyBindingSign(RoleCode.SUPER_ADMIN.getCode(), AuthLevel.FULL.name(), RoleCode.SUPER_ADMIN.getCode(), dto);
|
||||
|
||||
Assertions.assertEquals("ISSUE-SIGN-BACKUP", result.getSign());
|
||||
Assertions.assertNotNull(result.getBackupPacket());
|
||||
List<RoleUkeyBindingEntity> activeUidBindings = bindings.findActiveByRoleCodeAndUid(RoleCode.SUPER_ADMIN.getCode(), 1);
|
||||
Assertions.assertEquals(2, activeUidBindings.size());
|
||||
Assertions.assertTrue(activeUidBindings.stream().anyMatch(binding -> "UK-PRIMARY".equals(binding.getUkeySerial())
|
||||
&& "PUB-PRIMARY".equals(binding.getUkeyPubkey())));
|
||||
Assertions.assertTrue(activeUidBindings.stream().anyMatch(binding -> "UK-BACKUP".equals(binding.getUkeySerial())
|
||||
&& "PUB-BACKUP".equals(binding.getUkeyPubkey())
|
||||
&& "ISSUE-SIGN-BACKUP".equals(binding.getIssuerSign())));
|
||||
Assertions.assertTrue(activeUidBindings.stream().anyMatch(item -> "UK-PRIMARY".equals(item.getUkeySerial())));
|
||||
Assertions.assertTrue(activeUidBindings.stream().anyMatch(item -> "UK-BACKUP".equals(item.getUkeySerial())));
|
||||
}
|
||||
|
||||
private static AuthAdminService service(
|
||||
InMemoryRoleAccountRepository roles,
|
||||
InMemoryAuthFullAccountRepository accounts,
|
||||
InMemoryRoleUkeyBindingRepository bindings,
|
||||
LmkService lmkService
|
||||
) {
|
||||
return new AuthAdminServiceImpl(
|
||||
roles,
|
||||
accounts,
|
||||
bindings,
|
||||
new FakePasswordHasher(),
|
||||
lmkService,
|
||||
new ObjectMapper(),
|
||||
FIXED_CLOCK,
|
||||
new FixedSaltSupplier("salt-reset-1", "salt-reset-2", "salt-reset-3")
|
||||
);
|
||||
}
|
||||
|
||||
private static RoleAccountEntity role(RoleCode roleCode, RoleAccountStatus status) {
|
||||
RoleAccountEntity entity = new RoleAccountEntity();
|
||||
entity.setId((long) roleCode.ordinal() + 100);
|
||||
entity.setId((long) roleCode.ordinal() + 1);
|
||||
entity.setRoleCode(roleCode.getCode());
|
||||
entity.setDisplayName(roleCode.getDisplayName());
|
||||
entity.setRequiredUkeyCount(roleCode.getRequiredUkeyCount());
|
||||
@@ -446,22 +182,7 @@ class AuthAdminServiceTest {
|
||||
return entity;
|
||||
}
|
||||
|
||||
private static AuthUserAccountEntity user(String username, RoleCode roleCode, String passwordHash, String passwordSalt) {
|
||||
AuthUserAccountEntity entity = new AuthUserAccountEntity();
|
||||
entity.setId((long) (username.hashCode() & Integer.MAX_VALUE));
|
||||
entity.setUsername(username);
|
||||
entity.setDisplayName(username);
|
||||
entity.setRoleCode(roleCode.getCode());
|
||||
entity.setPasswordHash(passwordHash);
|
||||
entity.setPasswordSalt(passwordSalt);
|
||||
entity.setStatus(RoleAccountStatus.ACTIVE.name());
|
||||
entity.setNeedChangePassword(Boolean.FALSE);
|
||||
entity.setPasswordChangedAt(LocalDateTime.of(2026, 3, 23, 2, 0));
|
||||
entity.setFailedCount(0);
|
||||
return entity;
|
||||
}
|
||||
|
||||
private static AuthFullAccountEntity fullAccount(RoleCode roleCode, int uid, String accountName, String passwordHash, String passwordSalt) {
|
||||
private static AuthFullAccountEntity account(RoleCode roleCode, int uid, String accountName, String passwordHash, String passwordSalt) {
|
||||
AuthFullAccountEntity entity = new AuthFullAccountEntity();
|
||||
entity.setId((long) (accountName.hashCode() & Integer.MAX_VALUE));
|
||||
entity.setRoleCode(roleCode.getCode());
|
||||
@@ -472,32 +193,24 @@ class AuthAdminServiceTest {
|
||||
entity.setPasswordSalt(passwordSalt);
|
||||
entity.setStatus(RoleAccountStatus.ACTIVE.name());
|
||||
entity.setNeedChangePassword(Boolean.FALSE);
|
||||
entity.setPasswordChangedAt(LocalDateTime.of(2026, 3, 23, 2, 0));
|
||||
entity.setPasswordChangedAt(LocalDateTime.of(2026, 3, 23, 1, 0));
|
||||
entity.setFailedCount(0);
|
||||
return entity;
|
||||
}
|
||||
|
||||
private static RoleUkeyBindingEntity binding(
|
||||
RoleCode roleCode,
|
||||
int uid,
|
||||
String serial,
|
||||
String pubkey,
|
||||
String issuerSign
|
||||
) {
|
||||
private static RoleUkeyBindingEntity binding(RoleCode roleCode, int uid, String serial, String pubKey, String sign) {
|
||||
RoleUkeyBindingEntity entity = new RoleUkeyBindingEntity();
|
||||
entity.setId((long) uid);
|
||||
entity.setId((long) Math.abs((roleCode.getCode() + uid + serial).hashCode()));
|
||||
entity.setRoleCode(roleCode.getCode());
|
||||
entity.setUid(uid);
|
||||
entity.setUkeySerial(serial);
|
||||
entity.setUkeyPubkey(pubkey);
|
||||
entity.setIssuerSign(issuerSign);
|
||||
entity.setUkeyPubkey(pubKey);
|
||||
entity.setIssuerSign(sign);
|
||||
entity.setStatus("ACTIVE");
|
||||
entity.setBoundAt(LocalDateTime.of(2026, 3, 22, 8, 0));
|
||||
return entity;
|
||||
}
|
||||
|
||||
private static class FakePasswordHasher implements PasswordHasher {
|
||||
|
||||
@Override
|
||||
public String hash(String password, String salt) {
|
||||
return "HASH:" + password + ":" + salt;
|
||||
@@ -510,15 +223,19 @@ class AuthAdminServiceTest {
|
||||
}
|
||||
|
||||
private static class FixedSaltSupplier implements PasswordSaltGenerator {
|
||||
private final Deque<String> salts;
|
||||
private final List<String> salts;
|
||||
private int index = 0;
|
||||
|
||||
private FixedSaltSupplier(String... salts) {
|
||||
this.salts = new ArrayDeque<>(List.of(salts));
|
||||
this.salts = List.of(salts);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String nextSalt() {
|
||||
return Optional.ofNullable(salts.pollFirst()).orElse("salt-fallback");
|
||||
if (index >= salts.size()) {
|
||||
return salts.get(salts.size() - 1);
|
||||
}
|
||||
return salts.get(index++);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -541,33 +258,6 @@ class AuthAdminServiceTest {
|
||||
}
|
||||
}
|
||||
|
||||
private static class InMemoryAuthUserAccountRepository implements AuthUserAccountRepository {
|
||||
private final Map<String, AuthUserAccountEntity> store = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public Optional<AuthUserAccountEntity> findByUsername(String username) {
|
||||
return Optional.ofNullable(store.get(username));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AuthUserAccountEntity> findByRoleCode(String roleCode) {
|
||||
return store.values().stream()
|
||||
.filter(entity -> roleCode.equals(entity.getRoleCode()))
|
||||
.sorted(Comparator.comparing(AuthUserAccountEntity::getUsername))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(AuthUserAccountEntity entity) {
|
||||
store.put(entity.getUsername(), entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(AuthUserAccountEntity entity) {
|
||||
store.put(entity.getUsername(), entity);
|
||||
}
|
||||
}
|
||||
|
||||
private static class InMemoryAuthFullAccountRepository implements AuthFullAccountRepository {
|
||||
private final Map<String, AuthFullAccountEntity> store = new ConcurrentHashMap<>();
|
||||
|
||||
@@ -591,75 +281,37 @@ class AuthAdminServiceTest {
|
||||
|
||||
@Override
|
||||
public void update(AuthFullAccountEntity entity) {
|
||||
store.put(entity.getRoleCode() + "#" + entity.getUid(), entity);
|
||||
save(entity);
|
||||
}
|
||||
}
|
||||
|
||||
private static class InMemoryRoleUkeyBindingRepository implements RoleUkeyBindingRepository {
|
||||
private final Map<String, List<RoleUkeyBindingEntity>> store = new ConcurrentHashMap<>();
|
||||
private final List<RoleUkeyBindingEntity> store = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public List<RoleUkeyBindingEntity> findActiveByRoleCodeAndUid(String roleCode, Integer uid) {
|
||||
return store.getOrDefault(roleCode, List.of()).stream()
|
||||
.filter(entity -> uid.equals(entity.getUid()))
|
||||
public List<RoleUkeyBindingEntity> findActiveByRoleCode(String roleCode) {
|
||||
return store.stream()
|
||||
.filter(entity -> roleCode.equals(entity.getRoleCode()))
|
||||
.filter(entity -> "ACTIVE".equals(entity.getStatus()))
|
||||
.sorted(Comparator.comparing(RoleUkeyBindingEntity::getBoundAt).reversed())
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<RoleUkeyBindingEntity> findActiveByRoleCode(String roleCode) {
|
||||
return store.getOrDefault(roleCode, List.of()).stream()
|
||||
.filter(entity -> "ACTIVE".equals(entity.getStatus()))
|
||||
.sorted(Comparator.comparing(RoleUkeyBindingEntity::getUid))
|
||||
public List<RoleUkeyBindingEntity> findActiveByRoleCodeAndUid(String roleCode, Integer uid) {
|
||||
return findActiveByRoleCode(roleCode).stream()
|
||||
.filter(entity -> uid.equals(entity.getUid()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(RoleUkeyBindingEntity entity) {
|
||||
store.computeIfAbsent(entity.getRoleCode(), ignored -> new ArrayList<>()).add(entity);
|
||||
store.add(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(RoleUkeyBindingEntity entity) {
|
||||
List<RoleUkeyBindingEntity> list = store.computeIfAbsent(entity.getRoleCode(), ignored -> new ArrayList<>());
|
||||
list.removeIf(item -> item.getId().equals(entity.getId()));
|
||||
list.add(entity);
|
||||
store.removeIf(item -> item.getId().equals(entity.getId()));
|
||||
store.add(entity);
|
||||
}
|
||||
}
|
||||
|
||||
private static AuthAdminService newAuthAdminService(
|
||||
InMemoryRoleAccountRepository roleAccounts,
|
||||
InMemoryAuthFullAccountRepository fullAccounts,
|
||||
InMemoryAuthUserAccountRepository userAccounts,
|
||||
InMemoryRoleUkeyBindingRepository bindings,
|
||||
Clock clock,
|
||||
PasswordSaltGenerator saltGenerator
|
||||
) {
|
||||
return new AuthAdminServiceImpl(
|
||||
roleAccounts,
|
||||
fullAccounts,
|
||||
userAccounts,
|
||||
bindings,
|
||||
new FakePasswordHasher(),
|
||||
Mockito.mock(LmkService.class),
|
||||
new com.fasterxml.jackson.databind.ObjectMapper(),
|
||||
clock,
|
||||
saltGenerator
|
||||
);
|
||||
}
|
||||
|
||||
private static InMemoryAuthFullAccountRepository fullAccountRepository(RoleUkeyBindingEntity... bindings) {
|
||||
InMemoryAuthFullAccountRepository repository = new InMemoryAuthFullAccountRepository();
|
||||
for (RoleUkeyBindingEntity binding : bindings) {
|
||||
repository.save(fullAccount(
|
||||
RoleCode.valueOf(binding.getRoleCode()),
|
||||
binding.getUid(),
|
||||
binding.getRoleCode().toLowerCase() + "-full-" + binding.getUid(),
|
||||
"HASH:12345678:SALT",
|
||||
"SALT"
|
||||
));
|
||||
}
|
||||
return repository;
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -49,6 +49,55 @@ class PcieCompatUkeyVerifierTest {
|
||||
verifier().verifyLoginSignature(rawPointHex, payload, rawSignatureHex);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldVerifyLoginSignatureWithGm0018EccRefPublicKeyBase64() throws Exception {
|
||||
KeyPair keyPair = generateSm2KeyPair();
|
||||
String payload = "LOGIN-DATA-GM0018";
|
||||
String eccRefPublicKeyBase64 = Base64.getEncoder().encodeToString(gm0018EccRefPublicKey((ECPublicKey) keyPair.getPublic()));
|
||||
String signatureBase64 = Base64.getEncoder().encodeToString(gm0018Signature(signDer(payload, keyPair)));
|
||||
|
||||
verifier().verifyLoginSignature(eccRefPublicKeyBase64, payload, signatureBase64);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldParseProvidedGm0018EccRefPublicKeyBase64() {
|
||||
String pubKey = "AAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA09yf/T4UEoEnHOFslOQWh+oArfbK+RWK0jfZ5yOfPDQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADOo3fWGXgwtVeodmyfNHPZwz5SnqjMSCzVKtnc322mm";
|
||||
String rawZeroSignatureHex = "00".repeat(64);
|
||||
|
||||
BizException exception = Assertions.assertThrows(
|
||||
BizException.class,
|
||||
() -> verifier().verifyLoginSignature(pubKey, "LOGIN-DATA", rawZeroSignatureHex)
|
||||
);
|
||||
|
||||
Assertions.assertEquals("login signature verification failed", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectProvidedGm0018LoginFactorsWhenSignatureDoesNotMatchPayload() {
|
||||
String pubKey = "AAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA09yf/T4UEoEnHOFslOQWh+oArfbK+RWK0jfZ5yOfPDQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADOo3fWGXgwtVeodmyfNHPZwz5SnqjMSCzVKtnc322mm";
|
||||
String payload = "{\"ra\":\"5893784655680600\",\"rb\":\"2005fe7a2df071c378f3198de5eb2138\",\"role\":\"SUPER_ADMIN\",\"uid\":\"2\",\"rid\":\"SUPER_ADMIN\",\"block\":\"58937846556806002005fe7a2df071c378f3198de5eb2138SUPER_ADMIN\"}";
|
||||
String signature = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC19q5+THL9c3za11LSV3onnloEogld9DOkkAJodr9aUQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAnvomyeYPgfTcGnqXYZwHez6rkwNyhtXtlFpVLFg9SUk=";
|
||||
|
||||
BizException exception = Assertions.assertThrows(
|
||||
BizException.class,
|
||||
() -> verifier().verifyLoginSignature(pubKey, payload, signature)
|
||||
);
|
||||
|
||||
Assertions.assertEquals("login signature verification failed", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReportMissingLoginSignatureAfterProvidedGm0018PublicKeyIsParsed() {
|
||||
String pubKey = "AAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA09yf/T4UEoEnHOFslOQWh+oArfbK+RWK0jfZ5yOfPDQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADOo3fWGXgwtVeodmyfNHPZwz5SnqjMSCzVKtnc322mm";
|
||||
|
||||
BizException exception = Assertions.assertThrows(
|
||||
BizException.class,
|
||||
() -> verifier().verifyLoginSignature(pubKey, "LOGIN-DATA", null)
|
||||
);
|
||||
|
||||
Assertions.assertEquals("login signature is required", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectWhenLoginSignatureDoesNotMatch() throws Exception {
|
||||
KeyPair keyPair = generateSm2KeyPair();
|
||||
@@ -91,6 +140,27 @@ class PcieCompatUkeyVerifierTest {
|
||||
return result;
|
||||
}
|
||||
|
||||
private byte[] gm0018EccRefPublicKey(ECPublicKey publicKey) {
|
||||
byte[] x = toFixed32(publicKey.getW().getAffineX().toByteArray());
|
||||
byte[] y = toFixed32(publicKey.getW().getAffineY().toByteArray());
|
||||
byte[] result = new byte[132];
|
||||
result[0] = 0x00;
|
||||
result[1] = 0x01;
|
||||
result[2] = 0x00;
|
||||
result[3] = 0x00;
|
||||
System.arraycopy(x, 0, result, 4 + 32, 32);
|
||||
System.arraycopy(y, 0, result, 4 + 64 + 32, 32);
|
||||
return result;
|
||||
}
|
||||
|
||||
private byte[] gm0018Signature(byte[] derSignature) throws Exception {
|
||||
byte[] rawSignature = derToRawRs(derSignature);
|
||||
byte[] result = new byte[128];
|
||||
System.arraycopy(rawSignature, 0, result, 32, 32);
|
||||
System.arraycopy(rawSignature, 32, result, 96, 32);
|
||||
return result;
|
||||
}
|
||||
|
||||
private byte[] derToRawRs(byte[] derSignature) throws Exception {
|
||||
org.bouncycastle.asn1.ASN1Sequence sequence =
|
||||
(org.bouncycastle.asn1.ASN1Sequence) org.bouncycastle.asn1.ASN1Primitive.fromByteArray(derSignature);
|
||||
|
||||
@@ -42,7 +42,7 @@ class OperationAuditAspectLoginTest {
|
||||
PasswordLoginRequest request = new PasswordLoginRequest();
|
||||
request.setRoleCode("AUDIT_ADMIN");
|
||||
PasswordLoginAccountRequest account = new PasswordLoginAccountRequest();
|
||||
account.setUsername("audit-admin-01");
|
||||
account.setUid(1);
|
||||
request.setAccounts(List.of(account));
|
||||
LoginResponse response = new LoginResponse();
|
||||
response.setRoleCode("AUDIT_ADMIN");
|
||||
@@ -59,7 +59,7 @@ class OperationAuditAspectLoginTest {
|
||||
Assertions.assertEquals(OperatorRoleCode.AUDIT_ADMIN, command.getOperatorRoleCode());
|
||||
Assertions.assertEquals(AuthLevel.LIMITED, command.getOperatorAuthLevel());
|
||||
Assertions.assertEquals(OperationResult.SUCCESS, command.getOperationResult());
|
||||
Assertions.assertEquals("口令登录 [role=AUDIT_ADMIN, principal=audit-admin-01]", command.getSummary());
|
||||
Assertions.assertEquals("口令登录 [role=AUDIT_ADMIN, principal=uid=1]", command.getSummary());
|
||||
Assertions.assertEquals("10.0.0.8", command.getRemoteIp());
|
||||
}
|
||||
|
||||
|
||||
@@ -35,10 +35,8 @@ class ReplayProtectedEndpointsTest {
|
||||
|
||||
@Test
|
||||
void shouldProtectSensitiveControllersAndMethods() throws Exception {
|
||||
Assertions.assertNotNull(annotation(AuthController.class, "changeFullAccountPassword",
|
||||
Assertions.assertNotNull(annotation(AuthController.class, "changeAccountPassword",
|
||||
Integer.class, com.cisd.tms.modules.auth.dto.ChangePasswordRequest.class, jakarta.servlet.http.HttpServletRequest.class));
|
||||
Assertions.assertNotNull(annotation(AuthController.class, "changeLimitedAccountPassword",
|
||||
String.class, com.cisd.tms.modules.auth.dto.ChangePasswordRequest.class, jakarta.servlet.http.HttpServletRequest.class));
|
||||
Assertions.assertNotNull(annotation(DeviceController.class, "restart", jakarta.servlet.http.HttpServletRequest.class));
|
||||
|
||||
Assertions.assertNull(AnnotatedElementUtils.findMergedAnnotation(AuthAdminController.class, ReplayProtected.class));
|
||||
|
||||
Reference in New Issue
Block a user