ucard login
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
package com.newyetai.security.config;
|
||||
|
||||
import com.newyetai.security.ucard.UcardAuthenticationProvider;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurationPackage;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
@@ -84,5 +85,11 @@ public class SecurityConfiguration {
|
||||
SsbAuthenticationProvider ssbAuthenticationProvider(UserLoginService userLoginService) {
|
||||
return new SsbAuthenticationProvider(userLoginService);
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
@ConditionalOnBean(UserLoginService.class)
|
||||
UcardAuthenticationProvider ucardAuthenticationProvider(UserLoginService userLoginService) {
|
||||
return new UcardAuthenticationProvider(userLoginService);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.newyetai.security.config;
|
||||
|
||||
import com.newyetai.security.ucard.UcardAuthenticationFilter;
|
||||
import com.newyetai.security.ucard.UcardAuthenticationProvider;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
@@ -43,7 +45,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* security配置
|
||||
*
|
||||
*
|
||||
* @author on 2019-11-05
|
||||
*/
|
||||
@Slf4j
|
||||
@@ -79,6 +81,9 @@ public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
|
||||
@Autowired
|
||||
private SsbAuthenticationProvider ssbAuthenticationProvider;
|
||||
|
||||
@Autowired(required = false)
|
||||
private UcardAuthenticationProvider ucardAuthenticationProvider;
|
||||
|
||||
@Autowired
|
||||
private SwaggerConfig swaggerConfig;
|
||||
|
||||
@@ -92,9 +97,10 @@ public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
|
||||
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
|
||||
// 添加cas认证提供者
|
||||
auth.authenticationProvider(upasswordAuthenticationProvider)
|
||||
.authenticationProvider(mobileAuthenticationProvider)
|
||||
.authenticationProvider(tokenAuthenticationProvider)
|
||||
.authenticationProvider(ssbAuthenticationProvider)
|
||||
.authenticationProvider(mobileAuthenticationProvider)
|
||||
.authenticationProvider(tokenAuthenticationProvider)
|
||||
.authenticationProvider(ssbAuthenticationProvider)
|
||||
.authenticationProvider(ucardAuthenticationProvider);
|
||||
;
|
||||
}
|
||||
|
||||
@@ -146,6 +152,8 @@ public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
|
||||
http.addFilterAfter(mobileAuthenticationFilter(), UpasswordAuthenticationFilter.class);
|
||||
http.addFilterAfter(tokenAuthenticationFilter(), UpasswordAuthenticationFilter.class);
|
||||
http.addFilterAfter(ssbAuthenticationFilter(), UpasswordAuthenticationFilter.class);
|
||||
http.addFilterAfter(ucardAuthenticationFilter(), UpasswordAuthenticationFilter.class);
|
||||
|
||||
|
||||
// 禁用CSRF
|
||||
http.csrf().disable();
|
||||
@@ -171,14 +179,14 @@ public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private JwtAuthenticationTokenFilter createJwtAuthenticationTokenFilter() {
|
||||
return new JwtAuthenticationTokenFilter(securityProperties);
|
||||
}
|
||||
|
||||
/**
|
||||
* 拦截器
|
||||
*
|
||||
*
|
||||
* @return
|
||||
* @throws Exception
|
||||
* @author sr on 2019-11-28
|
||||
@@ -196,11 +204,11 @@ public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
|
||||
|
||||
/**
|
||||
* 手机号登录请求过滤器
|
||||
*
|
||||
*
|
||||
* @param authenticationManager
|
||||
* @return
|
||||
* @author sr on 2020-06-04
|
||||
* @throws Exception
|
||||
* @author sr on 2020-06-04
|
||||
*/
|
||||
private MobileAuthenticationFilter mobileAuthenticationFilter() throws Exception {
|
||||
MobileAuthenticationFilter filter = new MobileAuthenticationFilter();
|
||||
@@ -216,9 +224,10 @@ public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
|
||||
|
||||
/**
|
||||
* 账号密码
|
||||
*
|
||||
* @return
|
||||
* @throws Exception
|
||||
* @author sr on 2021-06-22
|
||||
* @author sr on 2021-06-22
|
||||
*/
|
||||
private UpasswordAuthenticationFilter upasswordAuthenticationFilter() throws Exception {
|
||||
UpasswordAuthenticationFilter filter = new UpasswordAuthenticationFilter();
|
||||
@@ -228,9 +237,10 @@ public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
|
||||
|
||||
/**
|
||||
* 临时token
|
||||
*
|
||||
* @return
|
||||
* @throws Exception
|
||||
* @author sr on 2021-06-22
|
||||
* @author sr on 2021-06-22
|
||||
*/
|
||||
private TokenAuthenticationFilter tokenAuthenticationFilter() throws Exception {
|
||||
TokenAuthenticationFilter filter = new TokenAuthenticationFilter();
|
||||
@@ -244,4 +254,17 @@ public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
|
||||
return filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* 身份证登录
|
||||
* @return
|
||||
* @throws Exception
|
||||
* @author sr on 2024-09-23
|
||||
*/
|
||||
private UcardAuthenticationFilter ucardAuthenticationFilter() throws Exception {
|
||||
UcardAuthenticationFilter filter = new UcardAuthenticationFilter();
|
||||
addHandler(filter);
|
||||
return filter;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.newyetai.security.ucard;
|
||||
|
||||
import com.newyetai.usercenter.user.login.dto.LoginUCardDTO;
|
||||
import java.io.IOException;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.security.authentication.AuthenticationServiceException;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
|
||||
/**
|
||||
* 账号密码认证过滤器,指定请求组装token,调起认证
|
||||
*
|
||||
* @author sr on 2021-06-21
|
||||
*/
|
||||
public class UcardAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
|
||||
|
||||
private boolean postOnly = true;
|
||||
|
||||
public static final String REQUEST_METHOD = "POST";
|
||||
public static final AntPathRequestMatcher requiresAuthenticationRequestMatcher =
|
||||
new AntPathRequestMatcher("/login_ucard", REQUEST_METHOD);
|
||||
public static final String LOGIN_REAL_NAME_KEY = "realName";
|
||||
public static final String LOGIN_CARD_NO_KEY = "cardNo";
|
||||
|
||||
public UcardAuthenticationFilter() {
|
||||
super(requiresAuthenticationRequestMatcher);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authentication attemptAuthentication(HttpServletRequest request,
|
||||
HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
|
||||
|
||||
if (postOnly && !REQUEST_METHOD.equals(request.getMethod())) {
|
||||
throw new AuthenticationServiceException(
|
||||
"Authentication method not supported: " + request.getMethod());
|
||||
}
|
||||
|
||||
String realName = request.getParameter(LOGIN_REAL_NAME_KEY);
|
||||
String cardNo = request.getParameter(LOGIN_CARD_NO_KEY);
|
||||
realName = realName == null ? "" : realName.trim();
|
||||
cardNo = cardNo == null ? "" : cardNo.trim();
|
||||
|
||||
AbstractAuthenticationToken authRequest = new UcardAuthenticationToken(
|
||||
LoginUCardDTO.builder().realName(realName).cardNo(cardNo).build(), cardNo);
|
||||
|
||||
// Allow subclasses to set the "details" property
|
||||
setDetails(request, authRequest);
|
||||
|
||||
return this.getAuthenticationManager().authenticate(authRequest);
|
||||
}
|
||||
|
||||
protected void setDetails(HttpServletRequest request, AbstractAuthenticationToken authRequest) {
|
||||
authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.newyetai.security.ucard;
|
||||
|
||||
import com.newyetai.common.exception.util.Assert;
|
||||
import com.newyetai.common.validation.util.ValidateUtils;
|
||||
import com.newyetai.security.dto.SecurityUserDTO;
|
||||
import com.newyetai.security.expection.BadLoginException;
|
||||
import com.newyetai.security.mapstruct.UserMapMapper;
|
||||
import com.newyetai.usercenter.user.login.dto.LoginUCardDTO;
|
||||
import com.newyetai.usercenter.user.login.dto.UserDetailDTO;
|
||||
import com.newyetai.usercenter.user.login.service.UserLoginService;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
/**
|
||||
* 账号密码认证provider,比对信息是否匹配
|
||||
*
|
||||
* @author sr on 2021-06-21
|
||||
*/
|
||||
public class UcardAuthenticationProvider implements AuthenticationProvider {
|
||||
|
||||
private UserLoginService userLoginService;
|
||||
|
||||
public UcardAuthenticationProvider(UserLoginService userLoginService) {
|
||||
this.userLoginService = userLoginService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||
LoginUCardDTO loginUCardDTO =
|
||||
(LoginUCardDTO) authentication.getPrincipal();
|
||||
String errMsg = ValidateUtils.validate(LoginUCardDTO.class);
|
||||
Assert.isTrue(errMsg == null, new BadLoginException(errMsg));
|
||||
SecurityUserDTO loadedUser = null;
|
||||
try {
|
||||
UserDetailDTO userDetailDTO = this.userLoginService.loadUser(loginUCardDTO);
|
||||
loadedUser = UserMapMapper.INSTANCE.dtoToDto(userDetailDTO);
|
||||
} catch (BadLoginException be) {
|
||||
throw be;
|
||||
} catch (Exception e) {
|
||||
throw new BadLoginException(e.getMessage(), e);
|
||||
}
|
||||
|
||||
UcardAuthenticationToken result =
|
||||
new UcardAuthenticationToken(loadedUser, listUserGrantedAuthorities(loadedUser));
|
||||
result.setDetails(authentication.getDetails());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(Class<?> authentication) {
|
||||
return (UcardAuthenticationToken.class.isAssignableFrom(authentication));
|
||||
}
|
||||
|
||||
private Set<GrantedAuthority> listUserGrantedAuthorities(UserDetails userDetails) {
|
||||
// 什么都不用做,访问资源鉴权的时会拿userDetail去缓存获取对应权限
|
||||
return new HashSet<GrantedAuthority>();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.newyetai.security.ucard;
|
||||
|
||||
import java.util.Collection;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
|
||||
|
||||
/**
|
||||
* 账号密码认证信息
|
||||
* @author sr on 2021-06-21
|
||||
*/
|
||||
public class UcardAuthenticationToken extends UsernamePasswordAuthenticationToken {
|
||||
|
||||
public UcardAuthenticationToken(Object principal, Object credentials) {
|
||||
super(principal, credentials);
|
||||
}
|
||||
|
||||
public UcardAuthenticationToken(Object principal, Object credentials,
|
||||
Collection<? extends GrantedAuthority> authorities) {
|
||||
super(principal, credentials, authorities);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -35,5 +35,8 @@ public class UserDetailQueryDTO implements Serializable {
|
||||
private String phone;
|
||||
|
||||
private Long orgId;
|
||||
|
||||
|
||||
/** 身份证号 **/
|
||||
private String cardNo;
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.newyetai.usercenter.user.login.dto;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import java.io.Serializable;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Data
|
||||
@Builder
|
||||
@ApiModel(description = "身份证登录")
|
||||
public class LoginUCardDTO implements Serializable{
|
||||
|
||||
/** **/
|
||||
private static final long serialVersionUID = -5406549736684905311L;
|
||||
|
||||
@ApiModelProperty("姓名")
|
||||
@NotBlank(message = "姓名不能为空")
|
||||
private String realName;
|
||||
@ApiModelProperty("身份证号")
|
||||
@NotBlank(message = "身份证号不能为空")
|
||||
private String cardNo;
|
||||
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import java.util.List;
|
||||
|
||||
/**
|
||||
* 用户
|
||||
*
|
||||
*
|
||||
* @author sr on 2019-11-05
|
||||
*/
|
||||
@Builder
|
||||
@@ -24,13 +24,13 @@ public class UserDetailDTO implements Serializable{
|
||||
|
||||
@ApiModelProperty("用户id")
|
||||
private Long id;
|
||||
|
||||
|
||||
@ApiModelProperty("是否超管")
|
||||
private Boolean supeiror ;
|
||||
|
||||
@ApiModelProperty("类型")
|
||||
private Integer type;
|
||||
|
||||
|
||||
/** ----------------仅限机构用户--------------- **/
|
||||
@ApiModelProperty("当前操作机构")
|
||||
private Long orgId;
|
||||
@@ -40,7 +40,7 @@ public class UserDetailDTO implements Serializable{
|
||||
|
||||
@ApiModelProperty("当前操作机构类型")
|
||||
private String orgType;
|
||||
|
||||
|
||||
@ApiModelProperty("是否机构管理员")
|
||||
private Boolean administrator;
|
||||
/** ----------------仅限机构用户--------------- **/
|
||||
@@ -55,7 +55,7 @@ public class UserDetailDTO implements Serializable{
|
||||
|
||||
@ApiModelProperty("是否启用")
|
||||
private Boolean enabled;
|
||||
|
||||
|
||||
@ApiModelProperty("是否新注册")
|
||||
private Boolean register;
|
||||
|
||||
@@ -70,7 +70,7 @@ public class UserDetailDTO implements Serializable{
|
||||
|
||||
@ApiModelProperty("凭据是否未过期")
|
||||
private Boolean credentialsNonExpired;
|
||||
|
||||
|
||||
@JsonIgnore
|
||||
@ApiModelProperty("真实姓名")
|
||||
private String realName;
|
||||
@@ -78,10 +78,13 @@ public class UserDetailDTO implements Serializable{
|
||||
@JsonIgnore
|
||||
@ApiModelProperty("身份证号")
|
||||
private String cardNo;
|
||||
|
||||
|
||||
@ApiModelProperty("手机号绑定状态")
|
||||
private Boolean phoneBinding;
|
||||
|
||||
@ApiModelProperty("是否实名")
|
||||
private Boolean authenticated ;
|
||||
|
||||
@ApiModelProperty("机构列表")
|
||||
@JsonIgnore
|
||||
private List<OrgDetailDTO> orgs;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.newyetai.usercenter.user.login.service;
|
||||
|
||||
import com.newyetai.usercenter.user.login.dto.LoginUCardDTO;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import com.newyetai.security.service.IUserSecurityService;
|
||||
@@ -16,28 +17,30 @@ import com.newyetai.usercenter.user.login.dto.UserDetailDTO;
|
||||
|
||||
/**
|
||||
* 用户登录
|
||||
* @author sr on 2021-06-17
|
||||
* @author sr on 2021-06-17
|
||||
*/
|
||||
public interface UserLoginService extends IUserSecurityService {
|
||||
|
||||
|
||||
UserDetailDTO loadUser(LoginUsernamePasswordDTO loginUsernamePasswordDTO);
|
||||
|
||||
|
||||
UserDetailDTO loadUser(LoginUCardDTO loginUCardDTO);
|
||||
|
||||
UserDetailDTO loadUser(LoginPhoneDTO loginPhoneDTO);
|
||||
|
||||
|
||||
UserDetailDTO loadUser(LoginPhoneToken loginPhoneToken);
|
||||
|
||||
|
||||
UserDetailDTO loadUser(LoginUserToken loginUserToken);
|
||||
|
||||
|
||||
UserDetailDTO findUserDetailDTO(UserDetailQueryDTO userDetailQueryDto);
|
||||
|
||||
|
||||
AuthClientDTO findAuthClientDTO(AuthClientQueryDTO authClientQueryDTO);
|
||||
|
||||
UserDetailDTO findUserDetailDTO(AuthDetailLoginDTO authDetailDTO);
|
||||
|
||||
|
||||
/**
|
||||
* 如果需激活请求,判断上下文用户激活状态,如果待激活,查询数据库用户状态,直接拦截
|
||||
* @return
|
||||
* @author sr on 2021-06-30
|
||||
* @author sr on 2021-06-30
|
||||
*/
|
||||
void checkActive(HttpServletRequest request);
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package com.newyetai.usercenter.user.login.service.impl;
|
||||
|
||||
import com.newyetai.usercenter.user.dto.SuperRealNameTwoAuthDTO;
|
||||
import com.newyetai.usercenter.user.login.dto.LoginUCardDTO;
|
||||
import com.newyetai.usercenter.user.service.UserRealNameTwoVerificationService;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -97,6 +100,9 @@ public class UserLoginServiceImpl implements UserLoginService {
|
||||
@Autowired
|
||||
private SsbInvoker ssbInvoker;
|
||||
|
||||
@Autowired
|
||||
private UserRealNameTwoVerificationService userRealNameTwoVerificationService;
|
||||
|
||||
@Override
|
||||
public UserDetailDTO loadUser(LoginUsernamePasswordDTO loginUsernamePasswordDTO) {
|
||||
// 校验参数
|
||||
@@ -448,4 +454,35 @@ public class UserLoginServiceImpl implements UserLoginService {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserDetailDTO loadUser(LoginUCardDTO loginUCardDTO) {
|
||||
String cardNo = loginUCardDTO.getCardNo();
|
||||
String realName = loginUCardDTO.getRealName();
|
||||
// 查询用户
|
||||
List<UserDetailDTO> userDetailDTOs = userMapper.listUserDetailDTO(UserDetailQueryDTO.builder()
|
||||
.cardNo(cardNo).userType(UserTypeEnum.PERSON.getType()).build());
|
||||
Assert.isTrue(userDetailDTOs.size() < 2, new BadLoginException("用户数据异常,请联系管理员!"));
|
||||
if (userDetailDTOs.size() == 1) {
|
||||
UserDetailDTO userDetailDTO = userDetailDTOs.get(0);
|
||||
if (userDetailDTO.getAuthenticated()) {
|
||||
Assert.isTrue(realName.equals(userDetailDTO.getRealName()),
|
||||
new BadLoginException("用户实名信息错误"));
|
||||
return userDetailDTO;
|
||||
}
|
||||
}
|
||||
// 未实名,调第三方实名校验
|
||||
Long userId = null;
|
||||
try {
|
||||
userId = userRealNameTwoVerificationService
|
||||
.superAuth(SuperRealNameTwoAuthDTO.builder().realName(realName).cardNo(cardNo).build());
|
||||
} catch (Exception e) {
|
||||
throw new BadLoginException(e.getMessage(), e);
|
||||
}
|
||||
UserDetailDTO userDetailDTO = this.findUserDetailDTO(
|
||||
UserDetailQueryDTO.builder().userId(userId).userType(UserTypeEnum.PERSON.getType())
|
||||
.build());
|
||||
dealActiveStatus(userDetailDTO);
|
||||
return userDetailDTO;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@
|
||||
and deleted = 0 and status != 3
|
||||
and (password is null or password = '')
|
||||
</update>
|
||||
|
||||
|
||||
<select id="listUserDetailDTO" resultType="com.newyetai.usercenter.user.login.dto.UserDetailDTO">
|
||||
<!-- 非个人用户时,需要默认一个登录机构 -->
|
||||
SELECT
|
||||
@@ -119,20 +119,23 @@
|
||||
<if test="param.username!=null and param.username!=''">
|
||||
and u.username = #{param.username}
|
||||
</if>
|
||||
<if test="param.cardNo!=null and param.cardNo!=''">
|
||||
and u.card_no = #{param.cardNo}
|
||||
</if>
|
||||
<if test="param.phone!=null and param.phone!=''">
|
||||
and u.phone = #{param.phone}
|
||||
<if test="param.userType == 2">
|
||||
<!-- 个人用户(绑定用户|不存在绑定用户) -->
|
||||
and (
|
||||
u.phone_binding = 1
|
||||
or not exists (select 1
|
||||
from uc_user pu
|
||||
where pu.phone = #{param.phone}
|
||||
or not exists (select 1
|
||||
from uc_user pu
|
||||
where pu.phone = #{param.phone}
|
||||
and pu.phone_binding = 1
|
||||
and pu.type = 2
|
||||
and pu.deleted = 0)
|
||||
)
|
||||
|
||||
|
||||
</if>
|
||||
</if>
|
||||
<if test="param.userId!=null">
|
||||
@@ -153,8 +156,8 @@
|
||||
</if>
|
||||
<if test="param.orgId != null">
|
||||
and exists (
|
||||
select *
|
||||
from uc_org_user ou
|
||||
select *
|
||||
from uc_org_user ou
|
||||
where ou.user_id = u.id
|
||||
and ou.org_id = #{param.orgId}
|
||||
and ou.deleted = 0
|
||||
|
||||
Reference in New Issue
Block a user