Expire session when update user password (#15219)

This commit is contained in:
Wenjun Ruan 2023-11-26 18:53:44 +08:00 committed by GitHub
parent 0016b96b85
commit 12f8138167
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 326 additions and 473 deletions

View File

@ -34,6 +34,7 @@ import org.apache.dolphinscheduler.common.constants.Constants;
import org.apache.dolphinscheduler.common.enums.UserType;
import org.apache.dolphinscheduler.common.utils.JSONUtils;
import org.apache.dolphinscheduler.common.utils.OkHttpUtils;
import org.apache.dolphinscheduler.dao.entity.Session;
import org.apache.dolphinscheduler.dao.entity.User;
import org.apache.commons.lang3.StringUtils;
@ -160,20 +161,13 @@ public class LoginController extends BaseController {
return Result.success();
}
/**
* sign out
*
* @param loginUser login user
* @param request request
* @return sign out result
*/
@Operation(summary = "signOut", description = "SIGNOUT_NOTES")
@PostMapping(value = "/signOut")
@ApiException(SIGN_OUT_ERROR)
public Result signOut(@Parameter(hidden = true) @RequestAttribute(value = Constants.SESSION_USER) User loginUser,
HttpServletRequest request) {
String ip = getClientIpAddress(request);
sessionService.signOut(ip, loginUser);
sessionService.expireSession(loginUser.getId());
// clear session
request.removeAttribute(Constants.SESSION_USER);
return success();
@ -244,13 +238,10 @@ public class LoginController extends BaseController {
if (user == null) {
user = usersService.createUser(UserType.GENERAL_USER, username, null);
}
String sessionId = sessionService.createSession(user, null);
if (sessionId == null) {
log.error("Failed to create session, userName:{}.", user.getUserName());
}
Session session = sessionService.createSessionIfAbsent(user);
response.setStatus(HttpStatus.SC_MOVED_TEMPORARILY);
response.sendRedirect(String.format("%s?sessionId=%s&authType=%s", oAuth2ClientProperties.getCallbackUrl(),
sessionId, "oauth2"));
session.getId(), "oauth2"));
} catch (Exception ex) {
log.error(ex.getMessage(), ex);
response.setStatus(HttpStatus.SC_MOVED_TEMPORARILY);

View File

@ -76,18 +76,6 @@ public class UsersController extends BaseController {
@Autowired
private UsersService usersService;
/**
* create user
*
* @param loginUser login user
* @param userName user name
* @param userPassword user password
* @param email email
* @param tenantId tenant id
* @param phone phone
* @param queue queue
* @return create result code
*/
@Operation(summary = "createUser", description = "CREATE_USER_NOTES")
@Parameters({
@Parameter(name = "userName", description = "USER_NAME", required = true, schema = @Schema(implementation = String.class)),
@ -172,19 +160,27 @@ public class UsersController extends BaseController {
@PostMapping(value = "/update")
@ResponseStatus(HttpStatus.OK)
@ApiException(UPDATE_USER_ERROR)
public Result updateUser(@Parameter(hidden = true) @RequestAttribute(value = Constants.SESSION_USER) User loginUser,
@RequestParam(value = "id") int id,
@RequestParam(value = "userName") String userName,
@RequestParam(value = "userPassword") String userPassword,
@RequestParam(value = "queue", required = false, defaultValue = "") String queue,
@RequestParam(value = "email") String email,
@RequestParam(value = "tenantId") int tenantId,
@RequestParam(value = "phone", required = false) String phone,
@RequestParam(value = "state", required = false) int state,
@RequestParam(value = "timeZone", required = false) String timeZone) throws Exception {
Map<String, Object> result = usersService.updateUser(loginUser, id, userName, userPassword, email, tenantId,
phone, queue, state, timeZone);
return returnDataList(result);
public Result<User> updateUser(@Parameter(hidden = true) @RequestAttribute(value = Constants.SESSION_USER) User loginUser,
@RequestParam(value = "id") int id,
@RequestParam(value = "userName") String userName,
@RequestParam(value = "userPassword") String userPassword,
@RequestParam(value = "queue", required = false, defaultValue = "") String queue,
@RequestParam(value = "email") String email,
@RequestParam(value = "tenantId") int tenantId,
@RequestParam(value = "phone", required = false) String phone,
@RequestParam(value = "state", required = false) int state,
@RequestParam(value = "timeZone", required = false) String timeZone) throws Exception {
User user = usersService.updateUser(loginUser,
id,
userName,
userPassword,
email,
tenantId,
phone,
queue,
state,
timeZone);
return Result.success(user);
}
/**

View File

@ -24,16 +24,19 @@ import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import lombok.NonNull;
public interface Authenticator {
/**
* Verifying legality via username and password
*
* @param username user name
* @param password user password
* @param extra extra info
* @param ip client ip
* @return result object
*/
Result<Map<String, String>> authenticate(String username, String password, String extra);
Result<Map<String, String>> authenticate(@NonNull String username, String password, @NonNull String ip);
/**
* Get authenticated user

View File

@ -29,15 +29,20 @@ import org.apache.dolphinscheduler.common.enums.Flag;
import org.apache.dolphinscheduler.dao.entity.Session;
import org.apache.dolphinscheduler.dao.entity.User;
import org.apache.commons.lang3.StringUtils;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.util.WebUtils;
@Slf4j
public abstract class AbstractAuthenticator implements Authenticator {
@ -56,15 +61,14 @@ public abstract class AbstractAuthenticator implements Authenticator {
*
* @param userId user identity field
* @param password user login password
* @param extra extra user login field
* @return user object in databse
*/
public abstract User login(String userId, String password, String extra);
public abstract User login(@NonNull String userId, String password);
@Override
public Result<Map<String, String>> authenticate(String userId, String password, String extra) {
public Result<Map<String, String>> authenticate(@NonNull String userId, String password, @NonNull String ip) {
Result<Map<String, String>> result = new Result<>();
User user = login(userId, password, extra);
User user = login(userId, password);
if (user == null) {
if (Objects.equals(securityConfig.getType(), AuthenticationType.CASDOOR_SSO.name())) {
log.error("State or code entered incorrectly.");
@ -87,9 +91,8 @@ public abstract class AbstractAuthenticator implements Authenticator {
}
// create session
String sessionId = sessionService.createSession(user, extra);
if (sessionId == null) {
log.error("Failed to create session, userName:{}.", user.getUserName());
Session session = sessionService.createSessionIfAbsent(user);
if (session == null) {
result.setCode(Status.LOGIN_SESSION_FAILED.getCode());
result.setMsg(Status.LOGIN_SESSION_FAILED.getMsg());
return result;
@ -98,7 +101,7 @@ public abstract class AbstractAuthenticator implements Authenticator {
log.info("Session is created, userName:{}.", user.getUserName());
Map<String, String> data = new HashMap<>();
data.put(Constants.SESSION_ID, sessionId);
data.put(Constants.SESSION_ID, session.getId());
data.put(Constants.SECURITY_CONFIG_TYPE, securityConfig.getType());
result.setData(data);
@ -109,9 +112,15 @@ public abstract class AbstractAuthenticator implements Authenticator {
@Override
public User getAuthUser(HttpServletRequest request) {
Session session = sessionService.getSession(request);
String sessionId = request.getHeader(Constants.SESSION_ID);
if (StringUtils.isBlank(sessionId)) {
Cookie cookie = WebUtils.getCookie(request, Constants.SESSION_ID);
if (cookie != null) {
sessionId = cookie.getValue();
}
}
Session session = sessionService.getSession(sessionId);
if (session == null) {
log.info("session info is null ");
return null;
}
// get user object from session

View File

@ -20,6 +20,8 @@ package org.apache.dolphinscheduler.api.security.impl.ldap;
import org.apache.dolphinscheduler.api.security.impl.AbstractAuthenticator;
import org.apache.dolphinscheduler.dao.entity.User;
import lombok.NonNull;
import org.springframework.beans.factory.annotation.Autowired;
public class LdapAuthenticator extends AbstractAuthenticator {
@ -28,7 +30,7 @@ public class LdapAuthenticator extends AbstractAuthenticator {
LdapService ldapService;
@Override
public User login(String userId, String password, String extra) {
public User login(@NonNull String userId, String password) {
User user = null;
String ldapEmail = ldapService.ldapLogin(userId, password);
if (ldapEmail != null) {

View File

@ -23,7 +23,7 @@ import org.apache.dolphinscheduler.dao.entity.User;
public class PasswordAuthenticator extends AbstractAuthenticator {
@Override
public User login(String userId, String password, String extra) {
public User login(String userId, String password) {
return userService.queryUser(userId, password);
}
}

View File

@ -27,6 +27,8 @@ import java.security.MessageDigest;
import javax.servlet.http.HttpServletRequest;
import lombok.NonNull;
import org.casbin.casdoor.entity.CasdoorUser;
import org.casbin.casdoor.service.CasdoorAuthService;
import org.springframework.beans.factory.annotation.Autowired;
@ -46,7 +48,7 @@ public class CasdoorAuthenticator extends AbstractSsoAuthenticator {
private String adminUserName;
@Override
public User login(String state, String code, String extra) {
public User login(@NonNull String state, String code) {
ServletRequestAttributes servletRequestAttributes =
(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (servletRequestAttributes == null) {

View File

@ -20,36 +20,13 @@ package org.apache.dolphinscheduler.api.service;
import org.apache.dolphinscheduler.dao.entity.Session;
import org.apache.dolphinscheduler.dao.entity.User;
import javax.servlet.http.HttpServletRequest;
/**
* session service
*/
public interface SessionService {
/**
* get user session from request
*
* @param request request
* @return session
*/
Session getSession(HttpServletRequest request);
Session getSession(String sessionId);
/**
* create session
*
* @param user user
* @param ip ip
* @return session string
*/
String createSession(User user, String ip);
Session createSessionIfAbsent(User user);
/**
* sign out
* remove ip restrictions
*
* @param ip no use
* @param loginUser login user
*/
void signOut(String ip, User loginUser);
void expireSession(Integer userId);
boolean isSessionExpire(Session session);
}

View File

@ -114,24 +114,16 @@ public interface UsersService {
*/
Result queryUserList(User loginUser, String searchVal, Integer pageNo, Integer pageSize);
/**
* updateProcessInstance user
*
*
* @param loginUser
* @param userId user id
* @param userName user name
* @param userPassword user password
* @param email email
* @param tenantId tennat id
* @param phone phone
* @param queue queue
* @return update result code
* @throws Exception exception
*/
Map<String, Object> updateUser(User loginUser, int userId, String userName, String userPassword, String email,
int tenantId, String phone, String queue, int state,
String timeZone) throws IOException;
User updateUser(User loginUser,
Integer userId,
String userName,
String userPassword,
String email,
Integer tenantId,
String phone,
String queue,
int state,
String timeZone) throws IOException;
/**
* delete user

View File

@ -17,12 +17,11 @@
package org.apache.dolphinscheduler.api.service.impl;
import org.apache.dolphinscheduler.api.controller.BaseController;
import org.apache.dolphinscheduler.api.service.SessionService;
import org.apache.dolphinscheduler.common.constants.Constants;
import org.apache.dolphinscheduler.dao.entity.Session;
import org.apache.dolphinscheduler.dao.entity.User;
import org.apache.dolphinscheduler.dao.mapper.SessionMapper;
import org.apache.dolphinscheduler.dao.repository.SessionDao;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
@ -31,15 +30,11 @@ import java.util.Date;
import java.util.List;
import java.util.UUID;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.util.WebUtils;
/**
* session service implement
@ -49,114 +44,57 @@ import org.springframework.web.util.WebUtils;
public class SessionServiceImpl extends BaseServiceImpl implements SessionService {
@Autowired
private SessionMapper sessionMapper;
private SessionDao sessionDao;
/**
* get user session from request
*
* @param request request
* @return session
*/
@Override
public Session getSession(HttpServletRequest request) {
String sessionId = request.getHeader(Constants.SESSION_ID);
if (StringUtils.isBlank(sessionId)) {
Cookie cookie = WebUtils.getCookie(request, Constants.SESSION_ID);
if (cookie != null) {
sessionId = cookie.getValue();
}
}
public Session getSession(String sessionId) {
if (StringUtils.isBlank(sessionId)) {
return null;
}
String ip = BaseController.getClientIpAddress(request);
log.debug("Get session: {}, ip: {}.", sessionId, ip);
return sessionMapper.selectById(sessionId);
return sessionDao.queryById(sessionId);
}
/**
* create session
*
* @param user user
* @param ip ip
* @return session string
*/
@Override
@Transactional
public String createSession(User user, String ip) {
Session session = null;
// logined
List<Session> sessionList = sessionMapper.queryByUserId(user.getId());
Date now = new Date();
/**
* if you have logged in and are still valid, return directly
*/
public Session createSessionIfAbsent(User user) {
Session session;
List<Session> sessionList = sessionDao.queryByUserId(user.getId());
// todo: this can be remove after the old session data is cleared
if (CollectionUtils.isNotEmpty(sessionList)) {
// is session list greater 1 delete other get one
if (sessionList.size() > 1) {
for (int i = 1; i < sessionList.size(); i++) {
sessionMapper.deleteById(sessionList.get(i).getId());
sessionDao.deleteById(sessionList.get(i).getId());
}
}
session = sessionList.get(0);
if (now.getTime() - session.getLastLoginTime().getTime() <= Constants.SESSION_TIME_OUT * 1000) {
/**
* updateProcessInstance the latest login time
*/
session.setLastLoginTime(now);
sessionMapper.updateById(session);
return session.getId();
if (isSessionExpire(session)) {
session.setLastLoginTime(new Date());
sessionDao.updateById(session);
return session;
} else {
/**
* session expired, then delete this session first
*/
sessionMapper.deleteById(session.getId());
sessionDao.deleteById(session.getId());
}
}
// assign new session
session = new Session();
session.setId(UUID.randomUUID().toString());
session.setIp(ip);
session.setUserId(user.getId());
session.setLastLoginTime(now);
sessionMapper.insert(session);
return session.getId();
Session newSession = Session.builder()
.id(UUID.randomUUID().toString())
.userId(user.getId())
.lastLoginTime(new Date())
.build();
sessionDao.insert(newSession);
return newSession;
}
/**
* sign out
* remove ip restrictions
*
* @param ip no use
* @param loginUser login user
*/
@Override
public void signOut(String ip, User loginUser) {
try {
/**
* query session by user id and ip
*/
Session session = sessionMapper.queryByUserIdAndIp(loginUser.getId(), ip);
public void expireSession(Integer userId) {
sessionDao.deleteByUserId(userId);
}
// delete session
sessionMapper.deleteById(session.getId());
} catch (Exception e) {
log.warn("userId : {} , ip : {} , find more one session", loginUser.getId(), ip, e);
}
@Override
public boolean isSessionExpire(Session session) {
return System.currentTimeMillis() - session.getLastLoginTime().getTime() <= Constants.SESSION_TIME_OUT * 1000;
}
}

View File

@ -23,6 +23,7 @@ import org.apache.dolphinscheduler.api.dto.resources.ResourceComponent;
import org.apache.dolphinscheduler.api.enums.Status;
import org.apache.dolphinscheduler.api.exceptions.ServiceException;
import org.apache.dolphinscheduler.api.service.MetricsCleanUpService;
import org.apache.dolphinscheduler.api.service.SessionService;
import org.apache.dolphinscheduler.api.service.UsersService;
import org.apache.dolphinscheduler.api.utils.CheckUtils;
import org.apache.dolphinscheduler.api.utils.PageInfo;
@ -132,6 +133,9 @@ public class UsersServiceImpl extends BaseServiceImpl implements UsersService {
@Autowired
private MetricsCleanUpService metricsCleanUpService;
@Autowired
private SessionService sessionService;
/**
* create user, only system admin have permission
*
@ -359,102 +363,68 @@ public class UsersServiceImpl extends BaseServiceImpl implements UsersService {
return result;
}
/**
* updateProcessInstance user
*
* @param userId user id
* @param userName user name
* @param userPassword user password
* @param email email
* @param tenantId tenant id
* @param phone phone
* @param queue queue
* @param state state
* @param timeZone timeZone
* @return update result code
* @throws Exception exception
*/
@Override
public Map<String, Object> updateUser(User loginUser, int userId,
String userName,
String userPassword,
String email,
int tenantId,
String phone,
String queue,
int state,
String timeZone) throws IOException {
Map<String, Object> result = new HashMap<>();
result.put(Constants.STATUS, false);
@Transactional
public User updateUser(User loginUser,
Integer userId,
String userName,
String userPassword,
String email,
Integer tenantId,
String phone,
String queue,
int state,
String timeZone) {
if (resourcePermissionCheckService.functionDisabled()) {
putMsg(result, Status.FUNCTION_DISABLED);
return result;
throw new ServiceException(Status.FUNCTION_DISABLED);
}
if (check(result, !canOperator(loginUser, userId), Status.USER_NO_OPERATION_PERM)) {
log.warn("User does not have permission for this feature, userId:{}, userName:{}.", loginUser.getId(),
loginUser.getUserName());
return result;
if (!canOperator(loginUser, userId)) {
throw new ServiceException(Status.USER_NO_OPERATION_PERM);
}
User user = userMapper.selectById(userId);
if (user == null) {
log.error("User does not exist, userId:{}.", userId);
putMsg(result, Status.USER_NOT_EXIST, userId);
return result;
throw new ServiceException(Status.USER_NOT_EXIST, userId);
}
if (StringUtils.isNotEmpty(userName)) {
if (!CheckUtils.checkUserName(userName)) {
log.warn("Parameter userName check failed.");
putMsg(result, Status.REQUEST_PARAMS_NOT_VALID_ERROR, userName);
return result;
throw new ServiceException(Status.REQUEST_PARAMS_NOT_VALID_ERROR, userName);
}
// todo: use the db unique index
User tempUser = userMapper.queryByUserNameAccurately(userName);
if (tempUser != null && tempUser.getId() != userId) {
log.warn("User name already exists, userName:{}.", tempUser.getUserName());
putMsg(result, Status.USER_NAME_EXIST);
return result;
if (tempUser != null && !userId.equals(tempUser.getId())) {
throw new ServiceException(Status.USER_NAME_EXIST);
}
user.setUserName(userName);
}
if (StringUtils.isNotEmpty(userPassword)) {
if (!CheckUtils.checkPasswordLength(userPassword)) {
log.warn("Parameter userPassword check failed.");
putMsg(result, Status.USER_PASSWORD_LENGTH_ERROR);
return result;
throw new ServiceException(Status.USER_PASSWORD_LENGTH_ERROR);
}
user.setUserPassword(EncryptionUtils.getMd5(userPassword));
sessionService.expireSession(user.getId());
}
if (StringUtils.isNotEmpty(email)) {
if (!CheckUtils.checkEmail(email)) {
log.warn("Parameter email check failed.");
putMsg(result, Status.REQUEST_PARAMS_NOT_VALID_ERROR, email);
return result;
throw new ServiceException(Status.REQUEST_PARAMS_NOT_VALID_ERROR, email);
}
user.setEmail(email);
}
if (StringUtils.isNotEmpty(phone) && !CheckUtils.checkPhone(phone)) {
log.warn("Parameter phone check failed.");
putMsg(result, Status.REQUEST_PARAMS_NOT_VALID_ERROR, phone);
return result;
throw new ServiceException(Status.REQUEST_PARAMS_NOT_VALID_ERROR, phone);
}
if (state == 0 && user.getState() != state && Objects.equals(loginUser.getId(), user.getId())) {
log.warn("Not allow to disable your own account, userId:{}, userName:{}.", user.getId(),
user.getUserName());
putMsg(result, Status.NOT_ALLOW_TO_DISABLE_OWN_ACCOUNT);
return result;
throw new ServiceException(Status.NOT_ALLOW_TO_DISABLE_OWN_ACCOUNT);
}
if (StringUtils.isNotEmpty(timeZone)) {
if (!CheckUtils.checkTimeZone(timeZone)) {
log.warn("Parameter time zone is illegal.");
putMsg(result, Status.TIME_ZONE_ILLEGAL, timeZone);
return result;
throw new ServiceException(Status.TIME_ZONE_ILLEGAL, timeZone);
}
user.setTimeZone(timeZone);
}
@ -462,20 +432,13 @@ public class UsersServiceImpl extends BaseServiceImpl implements UsersService {
user.setPhone(phone);
user.setQueue(queue);
user.setState(state);
Date now = new Date();
user.setUpdateTime(now);
user.setUpdateTime(new Date());
user.setTenantId(tenantId);
// updateProcessInstance user
int update = userMapper.updateById(user);
if (update > 0) {
log.info("User is updated and id is :{}.", userId);
putMsg(result, Status.SUCCESS);
} else {
log.error("User update error, userId:{}.", userId);
putMsg(result, Status.UPDATE_USER_ERROR);
if (userMapper.updateById(user) <= 0) {
throw new ServiceException(Status.UPDATE_USER_ERROR);
}
return result;
return user;
}
/**
@ -521,6 +484,7 @@ public class UsersServiceImpl extends BaseServiceImpl implements UsersService {
userMapper.queryTenantCodeByUserId(id);
accessTokenMapper.deleteAccessTokenByUserId(id);
sessionService.expireSession(id);
if (userMapper.deleteById(id) > 0) {
metricsCleanUpService.cleanUpApiResponseTimeMetricsByUserId(id);
@ -1459,10 +1423,13 @@ public class UsersServiceImpl extends BaseServiceImpl implements UsersService {
*/
@Override
@Transactional
public User createUserIfNotExists(String userName, String userPassword, String email, String phone,
public User createUserIfNotExists(String userName,
String userPassword,
String email,
String phone,
String tenantCode,
String queue,
int state) throws IOException {
int state) {
User user = userMapper.queryByUserNameAccurately(userName);
if (Objects.isNull(user)) {
Tenant tenant = tenantMapper.queryByTenantCode(tenantCode);

View File

@ -25,9 +25,9 @@ import org.apache.dolphinscheduler.api.service.UsersService;
import org.apache.dolphinscheduler.api.utils.Result;
import org.apache.dolphinscheduler.common.constants.Constants;
import org.apache.dolphinscheduler.dao.DaoConfiguration;
import org.apache.dolphinscheduler.dao.entity.Session;
import org.apache.dolphinscheduler.dao.entity.User;
import org.apache.commons.lang3.StringUtils;
import org.apache.curator.test.TestingServer;
import java.text.MessageFormat;
@ -77,17 +77,20 @@ public abstract class AbstractControllerTest {
@AfterEach
public void after() throws Exception {
sessionService.signOut("127.0.0.1", user);
if (user != null) {
sessionService.expireSession(user.getId());
}
}
private void createSession(User loginUser) {
user = loginUser;
String session = sessionService.createSession(loginUser, "127.0.0.1");
sessionId = session;
Session session = sessionService.createSessionIfAbsent(loginUser);
Assertions.assertNotNull(session);
Assertions.assertFalse(StringUtils.isEmpty(session));
sessionId = session.getId();
Assertions.assertNotNull(sessionId);
}
public Map<String, Object> success() {

View File

@ -17,6 +17,7 @@
package org.apache.dolphinscheduler.api.security.impl.ldap;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import org.apache.dolphinscheduler.api.controller.AbstractControllerTest;
@ -109,7 +110,7 @@ public class LdapAuthenticatorTest extends AbstractControllerTest {
@Test
public void testAuthenticate() {
when(ldapService.ldapLogin(ldapUid, ldapUserPwd)).thenReturn(ldapEmail);
when(sessionService.createSession(Mockito.any(User.class), Mockito.eq(ip))).thenReturn(mockSession.getId());
when(sessionService.createSessionIfAbsent(Mockito.any(User.class))).thenReturn(mockSession);
// test username pwd correct and user not exist, config user not exist action deny, so login denied
when(ldapService.getLdapUserNotExistAction()).thenReturn(LdapUserNotExistActionType.DENY);
@ -125,7 +126,7 @@ public class LdapAuthenticatorTest extends AbstractControllerTest {
logger.info(result.toString());
// test username pwd correct and user not exist, config action create but can't create session, so login failed
when(sessionService.createSession(Mockito.any(User.class), Mockito.eq(ip))).thenReturn(null);
when(sessionService.createSessionIfAbsent(Mockito.any(User.class))).thenReturn(null);
result = ldapAuthenticator.authenticate(ldapUid, ldapUserPwd, ip);
Assertions.assertEquals(Status.LOGIN_SESSION_FAILED.getCode(), (int) result.getCode());
@ -139,12 +140,12 @@ public class LdapAuthenticatorTest extends AbstractControllerTest {
public void testGetAuthUser() {
HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
when(usersService.queryUser(mockUser.getId())).thenReturn(mockUser);
when(sessionService.getSession(request)).thenReturn(mockSession);
when(sessionService.getSession(any())).thenReturn(mockSession);
User user = ldapAuthenticator.getAuthUser(request);
Assertions.assertNotNull(user);
when(sessionService.getSession(request)).thenReturn(null);
when(sessionService.getSession(any())).thenReturn(null);
user = ldapAuthenticator.getAuthUser(request);
Assertions.assertNull(user);
}

View File

@ -17,6 +17,7 @@
package org.apache.dolphinscheduler.api.security.impl.pwd;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import org.apache.dolphinscheduler.api.controller.AbstractControllerTest;
@ -81,21 +82,21 @@ public class PasswordAuthenticatorTest extends AbstractControllerTest {
@Test
public void testLogin() {
when(usersService.queryUser("test", "test")).thenReturn(mockUser);
User login = authenticator.login("test", "test", "127.0.0.1");
User login = authenticator.login("test", "test");
Assertions.assertNotNull(login);
}
@Test
public void testAuthenticate() {
when(usersService.queryUser("test", "test")).thenReturn(mockUser);
when(sessionService.createSession(mockUser, "127.0.0.1")).thenReturn(mockSession.getId());
when(sessionService.createSessionIfAbsent(mockUser)).thenReturn(mockSession);
Result result = authenticator.authenticate("test", "test", "127.0.0.1");
Assertions.assertEquals(Status.SUCCESS.getCode(), (int) result.getCode());
logger.info(result.toString());
mockUser.setState(0);
when(usersService.queryUser("test", "test")).thenReturn(mockUser);
when(sessionService.createSession(mockUser, "127.0.0.1")).thenReturn(mockSession.getId());
when(sessionService.createSessionIfAbsent(mockUser)).thenReturn(mockSession);
Result result1 = authenticator.authenticate("test", "test", "127.0.0.1");
Assertions.assertEquals(Status.USER_DISABLED.getCode(), (int) result1.getCode());
logger.info(result1.toString());
@ -105,7 +106,7 @@ public class PasswordAuthenticatorTest extends AbstractControllerTest {
public void testGetAuthUser() {
HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
when(usersService.queryUser(mockUser.getId())).thenReturn(mockUser);
when(sessionService.getSession(request)).thenReturn(mockSession);
when(sessionService.getSession(any())).thenReturn(mockSession);
User user = authenticator.getAuthUser(request);
Assertions.assertNotNull(user);

View File

@ -112,7 +112,7 @@ public class CasdoorAuthenticatorTest extends AbstractControllerTest {
@Test
public void testAuthenticate() {
when(usersService.getUserByUserName(casdoorUsername)).thenReturn(mockUser);
when(sessionService.createSession(mockUser, ip)).thenReturn(mockSession.getId());
when(sessionService.createSessionIfAbsent(mockUser)).thenReturn(mockSession);
when(casdoorAuthService.getOAuthToken(code, state)).thenReturn(token);
when(casdoorAuthService.parseJwtToken(token)).thenReturn(mockCasdoorUser);
@ -131,13 +131,13 @@ public class CasdoorAuthenticatorTest extends AbstractControllerTest {
Objects.requireNonNull(request.getSession()).setAttribute(Constants.SSO_LOGIN_USER_STATE, state);
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));
when(sessionService.createSession(mockUser, ip)).thenReturn(null);
when(sessionService.createSessionIfAbsent(mockUser)).thenReturn(null);
result = casdoorAuthenticator.authenticate(state, code, ip);
Assertions.assertEquals(Status.LOGIN_SESSION_FAILED.getCode(), (int) result.getCode());
Objects.requireNonNull(request.getSession()).setAttribute(Constants.SSO_LOGIN_USER_STATE, state);
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));
when(sessionService.createSession(mockUser, ip)).thenReturn(mockSession.getId());
when(sessionService.createSessionIfAbsent(mockUser)).thenReturn(mockSession);
when(usersService.getUserByUserName(casdoorUsername)).thenReturn(null);
when(usersService.createUser(userType, casdoorUsername, casdoorEmail)).thenReturn(null);
result = casdoorAuthenticator.authenticate(state, code, ip);

View File

@ -18,14 +18,11 @@
package org.apache.dolphinscheduler.api.service;
import org.apache.dolphinscheduler.api.service.impl.SessionServiceImpl;
import org.apache.dolphinscheduler.common.constants.Constants;
import org.apache.dolphinscheduler.common.enums.UserType;
import org.apache.dolphinscheduler.common.utils.DateUtils;
import org.apache.dolphinscheduler.dao.entity.Session;
import org.apache.dolphinscheduler.dao.entity.User;
import org.apache.dolphinscheduler.dao.mapper.SessionMapper;
import org.apache.commons.lang3.StringUtils;
import org.apache.dolphinscheduler.dao.repository.SessionDao;
import java.util.ArrayList;
import java.util.Calendar;
@ -43,8 +40,6 @@ import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.mock.web.MockCookie;
import org.springframework.mock.web.MockHttpServletRequest;
/**
* session service test
@ -58,7 +53,7 @@ public class SessionServiceTest {
private SessionServiceImpl sessionService;
@Mock
private SessionMapper sessionMapper;
private SessionDao sessionDao;
private String sessionId = "aaaaaaaaaaaaaaaaaa";
@ -70,32 +65,14 @@ public class SessionServiceTest {
public void after() {
}
/**
* create session
*/
@Test
public void testGetSession() {
Mockito.when(sessionMapper.selectById(sessionId)).thenReturn(getSession());
// get sessionId from header
MockHttpServletRequest mockHttpServletRequest = new MockHttpServletRequest();
mockHttpServletRequest.addHeader(Constants.SESSION_ID, sessionId);
mockHttpServletRequest.addHeader("HTTP_X_FORWARDED_FOR", "127.0.0.1");
Mockito.when(sessionDao.queryById(sessionId)).thenReturn(getSession());
// query
Session session = sessionService.getSession(mockHttpServletRequest);
Session session = sessionService.getSession(sessionId);
Assertions.assertNotNull(session);
logger.info("session ip {}", session.getIp());
// get sessionId from cookie
mockHttpServletRequest = new MockHttpServletRequest();
mockHttpServletRequest.addHeader("HTTP_X_FORWARDED_FOR", "127.0.0.1");
MockCookie mockCookie = new MockCookie(Constants.SESSION_ID, sessionId);
mockHttpServletRequest.setCookies(mockCookie);
// query
session = sessionService.getSession(mockHttpServletRequest);
Assertions.assertNotNull(session);
logger.info("session ip {}", session.getIp());
Assertions.assertEquals(session.getIp(), "127.0.0.1");
}
/**
@ -107,10 +84,10 @@ public class SessionServiceTest {
User user = new User();
user.setUserType(UserType.GENERAL_USER);
user.setId(1);
Mockito.when(sessionMapper.queryByUserId(1)).thenReturn(getSessions());
String sessionId = sessionService.createSession(user, ip);
logger.info("createSessionId is " + sessionId);
Assertions.assertTrue(!StringUtils.isEmpty(sessionId));
Mockito.when(sessionDao.queryByUserId(1)).thenReturn(getSessions());
Session session = sessionService.createSessionIfAbsent(user);
Assertions.assertNotNull(session);
Assertions.assertNotNull(session.getId());
}
/**
@ -118,15 +95,15 @@ public class SessionServiceTest {
* remove ip restrictions
*/
@Test
public void testSignOut() {
public void testExpireSession() {
int userId = 88888888;
String ip = "127.0.0.1";
User user = new User();
user.setId(userId);
Mockito.when(sessionMapper.queryByUserIdAndIp(userId, ip)).thenReturn(getSession());
Mockito.doNothing().when(sessionDao).deleteByUserId(userId);
sessionService.signOut(ip, user);
sessionService.expireSession(userId);
}

View File

@ -17,6 +17,8 @@
package org.apache.dolphinscheduler.api.service;
import static org.apache.dolphinscheduler.api.AssertionsHelper.assertDoesNotThrow;
import static org.apache.dolphinscheduler.api.AssertionsHelper.assertThrowsServiceException;
import static org.apache.dolphinscheduler.api.constants.ApiFuncIdentificationConstant.USER_MANAGER;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
@ -54,7 +56,6 @@ import org.apache.dolphinscheduler.spi.enums.ResourceType;
import org.apache.commons.collections4.CollectionUtils;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@ -131,6 +132,9 @@ public class UsersServiceTest {
@Mock
private ResourcePermissionCheckService resourcePermissionCheckService;
@Mock
private SessionService sessionService;
private String queueName = "UsersServiceTestQueue";
private static final Logger serviceLogger = LoggerFactory.getLogger(BaseServiceImpl.class);
@ -301,20 +305,36 @@ public class UsersServiceTest {
}
@Test
public void testUpdateUser() throws IOException {
public void testUpdateUser() {
String userName = "userTest0001";
String userPassword = "userTest0001";
// user not exist
Map<String, Object> result = usersService.updateUser(getLoginUser(), 0, userName, userPassword,
"3443@qq.com", 1, "13457864543", "queue", 1, "Asia/Shanghai");
Assertions.assertEquals(Status.USER_NOT_EXIST, result.get(Constants.STATUS));
assertThrowsServiceException(
Status.USER_NOT_EXIST,
() -> usersService.updateUser(getLoginUser(),
0,
userName,
userPassword,
"3443@qq.com",
1,
"13457864543",
"queue",
1,
"Asia/Shanghai"));
// success
when(userMapper.selectById(1)).thenReturn(getUser());
when(userMapper.selectById(any())).thenReturn(getUser());
when(userMapper.updateById(any())).thenReturn(1);
result = usersService.updateUser(getLoginUser(), 1, userName, userPassword, "32222s@qq.com", 1,
"13457864543", "queue", 1, "Asia/Shanghai");
Assertions.assertEquals(Status.SUCCESS, result.get(Constants.STATUS));
assertDoesNotThrow(() -> usersService.updateUser(getLoginUser(),
1,
userName,
userPassword,
"32222s@qq.com",
1,
"13457864543",
"queue",
1,
"Asia/Shanghai"));
}
@Test
@ -784,7 +804,7 @@ public class UsersServiceTest {
}
@Test
public void testCreateUserIfNotExists() throws IOException {
public void testCreateUserIfNotExists() {
User user;
String userName = "userTest0001";
String userPassword = "userTest";
@ -794,11 +814,12 @@ public class UsersServiceTest {
int stat = 1;
// User exists
Mockito.when(userMapper.existUser(userName)).thenReturn(true);
Mockito.when(userMapper.selectById(getUser().getId())).thenReturn(getUser());
Mockito.when(userMapper.queryDetailsById(getUser().getId())).thenReturn(getUser());
Mockito.when(userMapper.queryByUserNameAccurately(userName)).thenReturn(getUser());
Mockito.when(tenantMapper.queryByTenantCode(tenantCode)).thenReturn(getTenant());
when(userMapper.existUser(userName)).thenReturn(true);
when(userMapper.selectById(getUser().getId())).thenReturn(getUser());
when(userMapper.queryDetailsById(getUser().getId())).thenReturn(getUser());
when(userMapper.queryByUserNameAccurately(userName)).thenReturn(getUser());
when(userMapper.updateById(any())).thenReturn(1);
when(tenantMapper.queryByTenantCode(tenantCode)).thenReturn(getTenant());
user = usersService.createUserIfNotExists(userName, userPassword, email, phone, tenantCode, queueName, stat);
Assertions.assertEquals(getUser(), user);

View File

@ -18,108 +18,31 @@ package org.apache.dolphinscheduler.dao.entity;
import java.util.Date;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
/**
* session
*/
@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("t_ds_session")
public class Session {
/**
* id
*/
@TableId(value = "id", type = IdType.INPUT)
private String id;
/**
* user id
*/
private int userId;
/**
* last login time
*/
private Date lastLoginTime;
/**
* user login ip
*/
// We will not bind session with ip
@Deprecated
private String ip;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public Date getLastLoginTime() {
return lastLoginTime;
}
public void setLastLoginTime(Date lastLoginTime) {
this.lastLoginTime = lastLoginTime;
}
@Override
public String toString() {
return "Session{" +
"id=" + id +
", userId=" + userId +
", ip='" + ip + '\'' +
", lastLoginTime=" + lastLoginTime +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Session session = (Session) o;
if (userId != session.userId) {
return false;
}
if (!id.equals(session.id)) {
return false;
}
if (!lastLoginTime.equals(session.lastLoginTime)) {
return false;
}
return ip.equals(session.ip);
}
@Override
public int hashCode() {
int result = id.hashCode();
result = 31 * result + userId;
result = 31 * result + lastLoginTime.hashCode();
result = 31 * result + ip.hashCode();
return result;
}
}

View File

@ -18,30 +18,8 @@ package org.apache.dolphinscheduler.dao.mapper;
import org.apache.dolphinscheduler.dao.entity.Session;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* session mapper interface
*/
public interface SessionMapper extends BaseMapper<Session> {
/**
* query session list by userId
* @param userId userId
* @return session list
*/
List<Session> queryByUserId(@Param("userId") int userId);
/**
* query session by userId and Ip
* @param userId userId
* @param ip ip
* @return session
*/
Session queryByUserIdAndIp(@Param("userId") int userId, @Param("ip") String ip);
}

View File

@ -27,6 +27,7 @@ import java.util.Optional;
import lombok.NonNull;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
public abstract class BaseDao<ENTITY, MYBATIS_MAPPER extends BaseMapper<ENTITY>> implements IDao<ENTITY> {
@ -55,6 +56,14 @@ public abstract class BaseDao<ENTITY, MYBATIS_MAPPER extends BaseMapper<ENTITY>>
return mybatisMapper.selectBatchIds(ids);
}
@Override
public List<ENTITY> queryByCondition(ENTITY queryCondition) {
if (queryCondition == null) {
throw new IllegalArgumentException("queryCondition can not be null");
}
return mybatisMapper.selectList(new QueryWrapper<>(queryCondition));
}
@Override
public int insert(@NonNull ENTITY model) {
return mybatisMapper.insert(model);
@ -88,4 +97,12 @@ public abstract class BaseDao<ENTITY, MYBATIS_MAPPER extends BaseMapper<ENTITY>>
return mybatisMapper.deleteBatchIds(ids) > 0;
}
@Override
public boolean deleteByCondition(ENTITY queryCondition) {
if (queryCondition == null) {
throw new IllegalArgumentException("queryCondition can not be null");
}
return mybatisMapper.delete(new QueryWrapper<>(queryCondition)) > 0;
}
}

View File

@ -41,6 +41,11 @@ public interface IDao<Entity> {
*/
List<Entity> queryByIds(Collection<? extends Serializable> ids);
/**
* Query the entity by condition.
*/
List<Entity> queryByCondition(Entity queryCondition);
/**
* Insert the entity.
*/
@ -66,4 +71,9 @@ public interface IDao<Entity> {
*/
boolean deleteByIds(Collection<? extends Serializable> ids);
/**
* Delete the entities by condition.
*/
boolean deleteByCondition(Entity queryCondition);
}

View File

@ -0,0 +1,30 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dolphinscheduler.dao.repository;
import org.apache.dolphinscheduler.dao.entity.Session;
import java.util.List;
public interface SessionDao extends IDao<Session> {
void deleteByUserId(Integer userId);
List<Session> queryByUserId(Integer userId);
}

View File

@ -0,0 +1,49 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dolphinscheduler.dao.repository.impl;
import org.apache.dolphinscheduler.dao.entity.Session;
import org.apache.dolphinscheduler.dao.mapper.SessionMapper;
import org.apache.dolphinscheduler.dao.repository.BaseDao;
import org.apache.dolphinscheduler.dao.repository.SessionDao;
import java.util.List;
import lombok.NonNull;
import org.springframework.stereotype.Repository;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
@Repository
public class SessionDaoImpl extends BaseDao<Session, SessionMapper> implements SessionDao {
public SessionDaoImpl(@NonNull SessionMapper sessionMapper) {
super(sessionMapper);
}
public void deleteByUserId(Integer userId) {
mybatisMapper.delete(new QueryWrapper<>(Session.builder().userId(userId).build()));
}
@Override
public List<Session> queryByUserId(Integer userId) {
return mybatisMapper.selectList(new QueryWrapper<>(Session.builder().userId(userId).build()));
}
}

View File

@ -1,37 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
~ Licensed to the Apache Software Foundation (ASF) under one or more
~ contributor license agreements. See the NOTICE file distributed with
~ this work for additional information regarding copyright ownership.
~ The ASF licenses this file to You under the Apache License, Version 2.0
~ (the "License"); you may not use this file except in compliance with
~ the License. You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="org.apache.dolphinscheduler.dao.mapper.SessionMapper">
<sql id="baseSql">
id, user_id, ip, last_login_time
</sql>
<select id="queryByUserId" resultType="org.apache.dolphinscheduler.dao.entity.Session">
select
<include refid="baseSql"/>
from t_ds_session
where user_id = #{userId}
</select>
<select id="queryByUserIdAndIp" resultType="org.apache.dolphinscheduler.dao.entity.Session">
select
<include refid="baseSql"/>
from t_ds_session
where user_id = #{userId} AND ip = #{ip}
</select>
</mapper>

View File

@ -27,6 +27,8 @@ import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
public class SessionMapperTest extends BaseDaoTest {
@Autowired
@ -87,7 +89,8 @@ public class SessionMapperTest extends BaseDaoTest {
@Test
public void testQueryByUserId() {
Session session = insertOne();
List<Session> sessions = sessionMapper.queryByUserId(session.getUserId());
List<Session> sessions =
sessionMapper.selectList(new QueryWrapper<>(Session.builder().userId(session.getUserId()).build()));
Assertions.assertNotEquals(0, sessions.size());
}