fix(release): close fixed-code mobile auth gaps

This commit is contained in:
key
2026-07-29 11:59:30 +08:00
parent 3c92edd23d
commit 70e5516ff3
21 changed files with 546 additions and 65 deletions
@@ -92,10 +92,11 @@ public class AihrMobileController {
}
LoginUser loginUser = LoginHelper.getLoginUser();
String identity = currentAppUsername();
if (loginUser == null
|| !UserType.APP_USER.getUserType().equals(loginUser.getUserType())
|| identity.isBlank()) {
return R.ok(mobileSeedService.publicHome(role));
if (loginUser == null || identity.isBlank()) {
throw new ServiceException("移动端登录身份缺失,请重新登录");
}
if (!UserType.APP_USER.getUserType().equals(loginUser.getUserType())) {
throw new ServiceException("后台管理账号不能使用移动端员工首页");
}
String resolvedIdentity = identity;
if (supervisorRoleRequested(role)) {
@@ -114,36 +115,39 @@ public class AihrMobileController {
public R<IdentityResponse> me() {
String phone = currentAppUsername();
if (phone.isBlank()) {
return R.ok(new IdentityResponse("user", "员工端", "", "一线", List.of()));
throw new ServiceException("移动端登录身份缺失,请重新登录");
}
// APP username is a phone number. Validate it before it reaches the
// organization lookup so a phone-shaped external ID cannot borrow
// another employee's role or project scope.
mobileSeedService.requireMobileIdentity(phone);
List<OrgPersonRow> people = List.of();
var row = java.util.Optional.<OrgPersonRow>empty();
List<OrgPersonRow> people;
try {
var snapshotPeople = orgSyncService.activeByMobilePhone(phone);
people = snapshotPeople;
row = snapshotPeople.stream()
.filter(person -> "主管".equals(person.positionLevel()) || "项目经理".equals(person.positionLevel()))
.findFirst()
.or(() -> snapshotPeople.stream().findFirst());
people = orgSyncService.activeByMobilePhone(phone);
} catch (RuntimeException ex) {
log.warn("mobile identity lookup failed, fallback to employee role(处理错误已隐藏)");
log.warn("mobile identity lookup failed closed: {}", ex.getClass().getSimpleName());
throw new ServiceException("组织身份加载失败,请稍后重试");
}
if (row.isEmpty()) {
return R.ok(new IdentityResponse("user", "员工端", "", "一线", List.of()));
return R.ok(identityResponse(people));
}
static IdentityResponse identityResponse(List<OrgPersonRow> people) {
if (people == null || people.isEmpty()) {
throw new ServiceException("未找到有效的在职组织身份,请联系管理员");
}
var person = row.get();
var person = people.stream()
.filter(item -> "主管".equals(item.positionLevel()) || "项目经理".equals(item.positionLevel()))
.findFirst()
.or(() -> people.stream().findFirst())
.orElseThrow(() -> new ServiceException("未找到有效的在职组织身份,请联系管理员"));
boolean supervisor = "主管".equals(person.positionLevel()) || "项目经理".equals(person.positionLevel());
return R.ok(new IdentityResponse(
return new IdentityResponse(
supervisor ? "supervisor" : "user",
supervisor ? "主管端" : "员工端",
person.positionName(),
person.positionLevel(),
projectOptions(people)
));
);
}
static List<ProjectResponse> projectOptions(List<OrgPersonRow> people) {
@@ -1,12 +1,14 @@
package org.dromara.aihr.controller;
import org.dromara.aihr.domain.AihrOrgSyncDto.OrgPersonRow;
import org.dromara.common.core.exception.ServiceException;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
@Tag("dev")
class AihrMobileControllerTest {
@@ -27,8 +29,31 @@ class AihrMobileControllerTest {
assertEquals("星河湾一期", projects.get(1).projectName());
}
@Test
void identityResponsePrefersSupervisorAndKeepsProjectOptions() {
var rows = List.of(
row(1L, "P2", "星河湾二期", "一线"),
row(2L, "P1", "星河湾一期", "主管")
);
var response = AihrMobileController.identityResponse(rows);
assertEquals("supervisor", response.role());
assertEquals("主管端", response.roleLabel());
assertEquals(List.of("P1", "P2"), response.projects().stream().map(item -> item.projectCode()).toList());
}
@Test
void identityResponseFailsClosedWithoutActiveOrganizationRows() {
assertThrows(ServiceException.class, () -> AihrMobileController.identityResponse(List.of()));
}
private static OrgPersonRow row(Long id, String projectCode, String projectName) {
return row(id, projectCode, projectName, "一线");
}
private static OrgPersonRow row(Long id, String projectCode, String projectName, String positionLevel) {
return new OrgPersonRow(id, projectCode, projectName, "客服部", "employee-1", "王敏",
"生活顾问", "一线", "active", "2026-07-21");
"生活顾问", positionLevel, "active", "2026-07-21");
}
}
@@ -1602,8 +1602,11 @@ public class AihrPracticeSeedServiceTest {
assertTrue(controllerSource.contains("mobileSeedService.practiceAlerts(supervisorScopeExtPartyId())"));
assertTrue(controllerSource.contains("mobileSeedService.createPracticeAssignment(request, supervisorScopeExtPartyId())"));
assertTrue(controllerSource.contains("orgSyncService.activeByMobilePhone(phone)"));
assertTrue(controllerSource.contains("mobile identity lookup failed, fallback to employee role"));
assertTrue(controllerSource.contains("mobile identity lookup failed closed"));
assertTrue(controllerSource.contains("catch (RuntimeException ex)"));
assertTrue(controllerSource.contains("throw new ServiceException(\"组织身份加载失败,请稍后重试\")"));
assertTrue(controllerSource.contains("throw new ServiceException(\"未找到有效的在职组织身份,请联系管理员\")"));
assertFalse(controllerSource.contains("fallback to employee role"));
assertTrue(controllerSource.contains("private String supervisorScopeExtPartyId()"));
assertTrue(controllerSource.contains("if (isSystemOperator())"));
assertTrue(controllerSource.contains("后台管理账号不能使用主管带教接口"));
@@ -1623,6 +1626,11 @@ public class AihrPracticeSeedServiceTest {
assertTrue(controllerSource.contains("if (!LoginHelper.isLogin())"));
assertTrue(controllerSource.contains("return R.ok(mobileSeedService.publicHome(role))"));
assertTrue(controllerSource.contains("!UserType.APP_USER.getUserType().equals(loginUser.getUserType())"));
assertTrue(controllerSource.contains("throw new ServiceException(\"后台管理账号不能使用移动端员工首页\")"));
assertEquals(
controllerSource.indexOf("return R.ok(mobileSeedService.publicHome(role))"),
controllerSource.lastIndexOf("return R.ok(mobileSeedService.publicHome(role))")
);
assertTrue(controllerSource.contains("return R.ok(mobileSeedService.home(role, resolvedIdentity))"));
assertTrue(controllerSource.contains("mobileSeedService.requireMobileSupervisorIdentity(identity)"));
assertTrue(controllerSource.contains("mobileSeedService.requireMobileIdentity(identity)"));