| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264 |
- package com.ruoyi.app.user;
- import com.auth0.jwt.JWT;
- import com.ruoyi.app.utils.oauth.LineOAuthProperties;
- import com.ruoyi.app.user.service.DeviceTrustService;
- import com.ruoyi.app.utils.oauth.OAuthVerifyService;
- import com.ruoyi.app.utils.oauth.LineOAuthStateService;
- import com.ruoyi.common.constant.CacheConstants;
- import com.ruoyi.common.core.redis.RedisCache;
- import com.ruoyi.common.exception.ServiceException;
- import com.ruoyi.common.utils.spring.SpringUtils;
- import com.ruoyi.system.domain.InfoUser;
- import com.ruoyi.system.domain.InfoUserOauth;
- import com.ruoyi.system.mapper.InfoUserDeviceMapper;
- import com.ruoyi.system.mapper.InfoUserOauthMapper;
- import com.ruoyi.system.service.IInfoUserService;
- import com.ruoyi.system.service.MerchantStoreAccessService;
- import org.junit.jupiter.api.AfterAll;
- import org.junit.jupiter.api.AfterEach;
- import org.junit.jupiter.api.BeforeAll;
- import org.junit.jupiter.api.Test;
- import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
- import org.springframework.beans.factory.support.DefaultListableBeanFactory;
- import org.springframework.mock.web.MockHttpServletRequest;
- import org.springframework.mock.web.MockHttpServletResponse;
- import org.springframework.test.util.ReflectionTestUtils;
- import org.springframework.web.context.request.RequestContextHolder;
- import org.springframework.web.context.request.ServletRequestAttributes;
- import java.net.URLDecoder;
- import java.nio.charset.StandardCharsets;
- import java.util.concurrent.TimeUnit;
- import static org.junit.jupiter.api.Assertions.assertEquals;
- import static org.junit.jupiter.api.Assertions.assertTrue;
- import static org.mockito.ArgumentMatchers.any;
- import static org.mockito.Mockito.mock;
- import static org.mockito.Mockito.never;
- import static org.mockito.Mockito.doThrow;
- import static org.mockito.Mockito.verify;
- import static org.mockito.Mockito.when;
- class LineCallbackControllerTest {
- private static ConfigurableListableBeanFactory originalBeanFactory;
- @BeforeAll
- static void installJwtRedisBean() {
- originalBeanFactory = (ConfigurableListableBeanFactory)
- ReflectionTestUtils.getField(SpringUtils.class, "beanFactory");
- }
- @AfterAll
- static void restoreBeanFactory() {
- new SpringUtils().postProcessBeanFactory(originalBeanFactory);
- }
- @AfterEach
- void clearRequestContext() {
- RequestContextHolder.resetRequestAttributes();
- }
- @Test
- void sharedCallbackUsesProviderSpecificChannelAndKeepsProviderInTempBinding() throws Exception {
- OAuthVerifyService verifyService = mock(OAuthVerifyService.class);
- IInfoUserService infoUserService = mock(IInfoUserService.class);
- InfoUserOauthMapper oauthMapper = mock(InfoUserOauthMapper.class);
- RedisCache redisCache = mock(RedisCache.class);
- LineOAuthProperties properties = new LineOAuthProperties();
- LineOAuthProperties.Channel rider = new LineOAuthProperties.Channel();
- rider.setClientId("2011397520");
- rider.setClientSecret("secret");
- rider.setRedirectUri("https://api.test/auth/line/callback?provider=line_rider");
- rider.setAppRedirect("com.twanmsdqs.app://pages/UserCenter/oauthLogin");
- properties.setRider(rider);
- when(verifyService.verify("line_rider", "code")).thenReturn("line-uid");
- when(oauthMapper.selectOne(any())).thenReturn(null);
- LineCallbackController controller = new LineCallbackController();
- ReflectionTestUtils.setField(controller, "lineOAuthStateService", mock(LineOAuthStateService.class));
- ReflectionTestUtils.setField(controller, "oauthVerifyService", verifyService);
- ReflectionTestUtils.setField(controller, "lineOAuthProperties", properties);
- ReflectionTestUtils.setField(controller, "infoUserService", infoUserService);
- ReflectionTestUtils.setField(controller, "infoUserOauthMapper", oauthMapper);
- ReflectionTestUtils.setField(controller, "redisCache", redisCache);
- MockHttpServletResponse response = new MockHttpServletResponse();
- controller.callback("line_rider", "code", "state", response);
- assertTrue(response.getRedirectedUrl().startsWith(
- "com.twanmsdqs.app://pages/UserCenter/oauthLogin?needPhone=1&tempKey="));
- verify(redisCache).setCacheObject(any(String.class),
- org.mockito.ArgumentMatchers.eq("line_rider@line-uid"),
- org.mockito.ArgumentMatchers.eq(5), org.mockito.ArgumentMatchers.eq(TimeUnit.MINUTES));
- }
- @Test
- void boundRiderReceivesRiderSessionToken() throws Exception {
- CallbackFixture fixture = boundFixture("2");
- fixture.controller.callback("line_rider", "code", "state", fixture.response);
- String redirected = fixture.response.getRedirectedUrl();
- String token = URLDecoder.decode(redirected.substring(redirected.indexOf("?token=") + 7).split("&", 2)[0],
- StandardCharsets.UTF_8);
- assertTrue(JWT.decode(token).getId().startsWith(CacheConstants.QS_TOKEN_KEY + "42:"));
- assertEquals("line_rider", JWT.decode(token).getClaim("provider").asString());
- verify(fixture.redisCache).deleteKeys(CacheConstants.QS_TOKEN_KEY + "42:*");
- }
- @Test
- void riderChannelRejectsBindingToMerchantAccount() throws Exception {
- CallbackFixture fixture = boundFixture("1");
- fixture.controller.callback("line_rider", "code", "state", fixture.response);
- assertEquals("com.twanmsdqs.app://pages/UserCenter/oauthLogin?error=user_stopped&state=state",
- fixture.response.getRedirectedUrl());
- verify(fixture.redisCache, never()).deleteKeys(any(String.class));
- }
- @Test
- void merchantCallbackRejectsSubaccountWithUnavailableOwner() throws Exception {
- OAuthVerifyService verifyService = mock(OAuthVerifyService.class);
- IInfoUserService infoUserService = mock(IInfoUserService.class);
- InfoUserOauthMapper oauthMapper = mock(InfoUserOauthMapper.class);
- RedisCache redisCache = mock(RedisCache.class);
- MerchantStoreAccessService accessService = mock(MerchantStoreAccessService.class);
- DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
- beanFactory.registerSingleton("redisCache", redisCache);
- new SpringUtils().postProcessBeanFactory(beanFactory);
- MockHttpServletRequest request = new MockHttpServletRequest();
- request.setRemoteAddr("127.0.0.1");
- request.addHeader("User-Agent", "JUnit");
- RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));
- LineOAuthProperties properties = new LineOAuthProperties();
- LineOAuthProperties.Channel merchant = new LineOAuthProperties.Channel();
- merchant.setClientId("merchant-client");
- merchant.setClientSecret("secret");
- merchant.setRedirectUri("https://api.test/auth/line/callback?provider=line_merchant");
- merchant.setAppRedirect("com.twanmsdsj.app://pages/UserCenter/oauthLogin");
- properties.setMerchant(merchant);
- when(verifyService.verify("line_merchant", "code")).thenReturn("line-uid");
- InfoUserOauth binding = new InfoUserOauth();
- binding.setUserId(55L);
- when(oauthMapper.selectOne(any())).thenReturn(binding);
- InfoUser subaccount = new InfoUser();
- subaccount.setUserId(55L);
- subaccount.setUserName("subaccount");
- subaccount.setUserType("5");
- subaccount.setStatus("0");
- subaccount.setDelFlag("0");
- subaccount.setSubaccountStatus("0");
- when(infoUserService.getOne(any())).thenReturn(subaccount);
- doThrow(new ServiceException("owner unavailable"))
- .when(accessService).resolve(55L);
- LineCallbackController controller = new LineCallbackController();
- ReflectionTestUtils.setField(controller, "lineOAuthStateService", mock(LineOAuthStateService.class));
- ReflectionTestUtils.setField(controller, "oauthVerifyService", verifyService);
- ReflectionTestUtils.setField(controller, "lineOAuthProperties", properties);
- ReflectionTestUtils.setField(controller, "infoUserService", infoUserService);
- ReflectionTestUtils.setField(controller, "infoUserOauthMapper", oauthMapper);
- ReflectionTestUtils.setField(controller, "redisCache", redisCache);
- ReflectionTestUtils.setField(controller, "merchantStoreAccessService", accessService);
- MockHttpServletResponse response = new MockHttpServletResponse();
- controller.callback("line_merchant", "code", "state", response);
- assertEquals("com.twanmsdsj.app://pages/UserCenter/oauthLogin?error=user_stopped&state=state",
- response.getRedirectedUrl());
- }
- @Test
- void oldUserCallbackWithoutProviderKeepsOriginalResponse() throws Exception {
- LineOAuthProperties properties = new LineOAuthProperties();
- LineOAuthProperties.Channel user = new LineOAuthProperties.Channel();
- user.setClientId("user-client");
- user.setClientSecret("secret");
- user.setRedirectUri("https://api.test/auth/line/callback?provider=line_user");
- user.setAppRedirect("com.twanmsdyh.app://pages/UserCenter/oauthLogin");
- properties.setUser(user);
- OAuthVerifyService verifier = mock(OAuthVerifyService.class);
- when(verifier.verify("line", "old-code")).thenReturn("old-user-id");
- RedisCache redis = mock(RedisCache.class);
- LineCallbackController controller = new LineCallbackController();
- ReflectionTestUtils.setField(controller, "lineOAuthProperties", properties);
- ReflectionTestUtils.setField(controller, "oauthVerifyService", verifier);
- ReflectionTestUtils.setField(controller, "infoUserOauthMapper", mock(InfoUserOauthMapper.class));
- ReflectionTestUtils.setField(controller, "redisCache", redis);
- ReflectionTestUtils.setField(controller, "deviceTrustService",
- new DeviceTrustService(mock(InfoUserDeviceMapper.class)));
- MockHttpServletResponse response = new MockHttpServletResponse();
- controller.callback(null, "old-code", "line_login", response);
- assertTrue(response.getRedirectedUrl().startsWith("com.twanmsdyh.app://pages/UserCenter/oauthLogin?needPhone=1&tempKey="));
- assertTrue(!response.getRedirectedUrl().contains("&state="));
- verify(verifier).verify("line", "old-code");
- verify(redis).setCacheObject(any(String.class), org.mockito.ArgumentMatchers.eq("line@old-user-id"),
- org.mockito.ArgumentMatchers.eq(5), org.mockito.ArgumentMatchers.eq(TimeUnit.MINUTES));
- }
- @Test
- void businessCallbackRejectsInvalidStateBeforeIssuingSession() throws Exception {
- CallbackFixture fixture = boundFixture("2");
- LineOAuthStateService stateService = mock(LineOAuthStateService.class);
- doThrow(new ServiceException("invalid state")).when(stateService).consume("line_rider", "state");
- ReflectionTestUtils.setField(fixture.controller, "lineOAuthStateService", stateService);
- OAuthVerifyService verifier = (OAuthVerifyService) ReflectionTestUtils.getField(fixture.controller, "oauthVerifyService");
- fixture.controller.callback("line_rider", "code", "state", fixture.response);
- assertEquals("com.twanmsdqs.app://pages/UserCenter/oauthLogin?error=fail&state=state", fixture.response.getRedirectedUrl());
- verify(verifier, never()).verify(any(), any());
- verify(fixture.redisCache, never()).deleteKeys(any(String.class));
- }
- private CallbackFixture boundFixture(String userType) {
- OAuthVerifyService verifyService = mock(OAuthVerifyService.class);
- IInfoUserService infoUserService = mock(IInfoUserService.class);
- InfoUserOauthMapper oauthMapper = mock(InfoUserOauthMapper.class);
- RedisCache redisCache = mock(RedisCache.class);
- DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
- beanFactory.registerSingleton("redisCache", redisCache);
- new SpringUtils().postProcessBeanFactory(beanFactory);
- MockHttpServletRequest request = new MockHttpServletRequest();
- request.setRemoteAddr("127.0.0.1");
- request.addHeader("User-Agent", "JUnit");
- RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));
- LineOAuthProperties properties = new LineOAuthProperties();
- LineOAuthProperties.Channel rider = new LineOAuthProperties.Channel();
- rider.setClientId("2011397520");
- rider.setClientSecret("secret");
- rider.setRedirectUri("https://api.test/auth/line/callback?provider=line_rider");
- rider.setAppRedirect("com.twanmsdqs.app://pages/UserCenter/oauthLogin");
- properties.setRider(rider);
- InfoUserOauth binding = new InfoUserOauth();
- binding.setUserId(42L);
- InfoUser user = new InfoUser();
- user.setUserId(42L);
- user.setUserName("rider");
- user.setUserType(userType);
- user.setStatus("0");
- user.setDelFlag("0");
- when(verifyService.verify("line_rider", "code")).thenReturn("line-uid");
- when(oauthMapper.selectOne(any())).thenReturn(binding);
- when(infoUserService.getOne(any())).thenReturn(user);
- LineCallbackController controller = new LineCallbackController();
- ReflectionTestUtils.setField(controller, "lineOAuthStateService", mock(LineOAuthStateService.class));
- ReflectionTestUtils.setField(controller, "oauthVerifyService", verifyService);
- ReflectionTestUtils.setField(controller, "lineOAuthProperties", properties);
- ReflectionTestUtils.setField(controller, "infoUserService", infoUserService);
- ReflectionTestUtils.setField(controller, "infoUserOauthMapper", oauthMapper);
- ReflectionTestUtils.setField(controller, "redisCache", redisCache);
- return new CallbackFixture(controller, redisCache, new MockHttpServletResponse());
- }
- private record CallbackFixture(LineCallbackController controller, RedisCache redisCache,
- MockHttpServletResponse response) {
- }
- }
|