feat(aihr): harden M0-M2 mobile training flows
This commit is contained in:
+82
@@ -0,0 +1,82 @@
|
||||
package org.dromara.web.controller;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.dromara.common.core.constant.Constants;
|
||||
import org.dromara.common.core.exception.ServiceException;
|
||||
import org.dromara.common.core.utils.SmsCodeUtils;
|
||||
import org.dromara.web.domain.vo.LoginVo;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.redisson.api.RBucket;
|
||||
import org.redisson.api.RLock;
|
||||
import org.redisson.api.RedissonClient;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@Tag("dev")
|
||||
class MobileSmsLoginTenantBoundaryTest {
|
||||
|
||||
@Test
|
||||
void mobileLoginIgnoresClientTenantSelection() throws Exception {
|
||||
AuthController.MobileSmsLoginBody body = new ObjectMapper().readValue("""
|
||||
{"phonenumber":"13900000000","smsCode":"123456","tenantId":"tenant-b"}
|
||||
""", AuthController.MobileSmsLoginBody.class);
|
||||
|
||||
assertEquals("13900000000", body.phonenumber());
|
||||
assertEquals("123456", body.smsCode());
|
||||
assertEquals("000000", AuthController.mobileTenantId());
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void resendingSmsUsesTheSameLockAsVerification() throws Exception {
|
||||
RedissonClient redisClient = mock(RedissonClient.class);
|
||||
RLock lock = mock(RLock.class);
|
||||
RBucket<String> bucket = mock(RBucket.class);
|
||||
String tenantId = "000000";
|
||||
String phonenumber = "13900000000";
|
||||
when(redisClient.getLock(SmsCodeUtils.verificationLockKey(tenantId, phonenumber))).thenReturn(lock);
|
||||
when(redisClient.<String>getBucket(anyString())).thenReturn(bucket);
|
||||
when(lock.tryLock(2, 5, TimeUnit.SECONDS)).thenReturn(true);
|
||||
when(lock.isHeldByCurrentThread()).thenReturn(true);
|
||||
|
||||
CaptchaController.storeSmsCode(redisClient, tenantId, phonenumber, "123456");
|
||||
|
||||
verify(bucket).set("123456", Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
|
||||
verify(lock).unlock();
|
||||
}
|
||||
|
||||
@Test
|
||||
void mobileLoginResponseUsesTheH5AuthContractOnly() throws Exception {
|
||||
LoginVo loginVo = new LoginVo();
|
||||
loginVo.setAccessToken("test-access-token");
|
||||
loginVo.setClientId("test-mobile-client");
|
||||
loginVo.setRefreshToken("must-not-be-returned");
|
||||
|
||||
JsonNode json = new ObjectMapper().readTree(new ObjectMapper().writeValueAsString(
|
||||
AuthController.MobileSmsLoginResponse.from(loginVo)
|
||||
));
|
||||
|
||||
assertEquals("test-access-token", json.path("access_token").asText());
|
||||
assertEquals("test-mobile-client", json.path("client_id").asText());
|
||||
assertFalse(json.has("refresh_token"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void mobileLoginResponseRejectsAnIncompleteStrategyResult() {
|
||||
LoginVo loginVo = new LoginVo();
|
||||
loginVo.setClientId("test-mobile-client");
|
||||
|
||||
assertThrows(ServiceException.class, () -> AuthController.MobileSmsLoginResponse.from(loginVo));
|
||||
}
|
||||
}
|
||||
+86
-12
@@ -1,25 +1,99 @@
|
||||
package org.dromara.web.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.MybatisConfiguration;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
||||
import org.dromara.common.core.enums.UserType;
|
||||
import org.dromara.common.core.utils.SmsCodeUtils;
|
||||
import org.apache.ibatis.builder.MapperBuilderAssistant;
|
||||
import org.dromara.system.domain.SysUser;
|
||||
import org.dromara.system.domain.vo.SysClientVo;
|
||||
import org.dromara.system.domain.vo.SysUserVo;
|
||||
import org.dromara.system.mapper.SysUserMapper;
|
||||
import org.dromara.web.service.SysLoginService;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.redisson.api.RBucket;
|
||||
import org.redisson.api.RLock;
|
||||
import org.redisson.api.RedissonClient;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@Tag("dev")
|
||||
class SmsAuthStrategyTenantTest {
|
||||
|
||||
@Test
|
||||
void mobileSmsUserLookupKeepsTenantBoundary() throws Exception {
|
||||
Path source = Path.of("src/main/java/org/dromara/web/service/impl/SmsAuthStrategy.java");
|
||||
if (!Files.exists(source)) {
|
||||
source = Path.of("ruoyi-admin/src/main/java/org/dromara/web/service/impl/SmsAuthStrategy.java");
|
||||
}
|
||||
String code = Files.readString(source);
|
||||
private static final String TEST_MOBILE = "13900000000";
|
||||
|
||||
assertTrue(code.contains(".eq(SysUser::getTenantId, tenantId)"));
|
||||
assertTrue(code.contains(".eq(SysUser::getPhonenumber, phonenumber)"));
|
||||
@BeforeAll
|
||||
static void initializeSysUserLambdaMetadata() {
|
||||
if (TableInfoHelper.getTableInfo(SysUser.class) == null) {
|
||||
TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new MybatisConfiguration(), ""), SysUser.class);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void mobileSmsLoginUsesAppUsersAndRejectsAConflictingSystemUser() {
|
||||
SysLoginService loginService = mock(SysLoginService.class);
|
||||
SysUserMapper userMapper = mock(SysUserMapper.class);
|
||||
SmsAuthStrategy strategy = new SmsAuthStrategy(loginService, userMapper);
|
||||
when(userMapper.selectVoOne(any())).thenReturn(null);
|
||||
when(userMapper.exists(any())).thenReturn(true);
|
||||
|
||||
assertThrows(RuntimeException.class,
|
||||
() -> strategy.loadOrRegisterUserByPhonenumber("000000", TEST_MOBILE, true));
|
||||
verify(userMapper, never()).insert(any(SysUser.class));
|
||||
|
||||
SysClientVo appClient = new SysClientVo();
|
||||
appClient.setClientId(SmsCodeUtils.MOBILE_CLIENT_ID);
|
||||
assertTrue(SmsAuthStrategy.isAppClient(appClient));
|
||||
}
|
||||
|
||||
@Test
|
||||
void existingAppUserIsRetainedAndWrongOtpDoesNotConsumeTheCode() throws Exception {
|
||||
SysLoginService loginService = mock(SysLoginService.class);
|
||||
SysUserMapper userMapper = mock(SysUserMapper.class);
|
||||
SmsAuthStrategy strategy = new SmsAuthStrategy(loginService, userMapper);
|
||||
SysUserVo appUser = new SysUserVo();
|
||||
appUser.setUserType(UserType.APP_USER.getUserType());
|
||||
appUser.setStatus("0");
|
||||
when(userMapper.selectVoOne(any())).thenReturn(appUser);
|
||||
assertSame(appUser, strategy.loadOrRegisterUserByPhonenumber("000000", TEST_MOBILE, true));
|
||||
|
||||
RedissonClient redisClient = mock(RedissonClient.class);
|
||||
RLock lock = mock(RLock.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
RBucket<String> bucket = mock(RBucket.class);
|
||||
when(redisClient.getLock(SmsCodeUtils.verificationLockKey("000000", TEST_MOBILE))).thenReturn(lock);
|
||||
when(redisClient.<String>getBucket(anyString())).thenReturn(bucket);
|
||||
when(lock.tryLock(eq(2L), eq(5L), eq(TimeUnit.SECONDS))).thenReturn(true);
|
||||
when(lock.isHeldByCurrentThread()).thenReturn(true);
|
||||
when(bucket.get()).thenReturn("123456");
|
||||
|
||||
assertFalse(strategy.validateSmsCode(redisClient, "000000", TEST_MOBILE, "000000"));
|
||||
verify(bucket, never()).delete();
|
||||
verify(lock).unlock();
|
||||
|
||||
assertTrue(strategy.validateSmsCode(redisClient, "000000", TEST_MOBILE, "123456"));
|
||||
verify(bucket).delete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void mobileSmsLoginRejectsAConflictingSystemUserEvenWhenAnAppUserExists() {
|
||||
SysLoginService loginService = mock(SysLoginService.class);
|
||||
SysUserMapper userMapper = mock(SysUserMapper.class);
|
||||
SmsAuthStrategy strategy = new SmsAuthStrategy(loginService, userMapper);
|
||||
SysUserVo appUser = new SysUserVo();
|
||||
appUser.setUserType(UserType.APP_USER.getUserType());
|
||||
when(userMapper.exists(any())).thenReturn(true);
|
||||
when(userMapper.selectVoOne(any())).thenReturn(appUser);
|
||||
|
||||
assertThrows(RuntimeException.class,
|
||||
() -> strategy.loadOrRegisterUserByPhonenumber("000000", TEST_MOBILE, true));
|
||||
verify(userMapper, never()).selectVoOne(any());
|
||||
verify(userMapper, never()).insert(any(SysUser.class));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user