JAVA-16250 Update spring-activiti module to use parent-boot-2 module as parent (#13120)
This commit is contained in:
parent
7b9ab06c14
commit
3810a37078
|
@ -9,12 +9,10 @@
|
|||
<description>Demo project for Spring Boot</description>
|
||||
|
||||
<parent>
|
||||
<!-- This module uses Activiti version 6.0.0 with support for Spring Boot 1. -->
|
||||
<!-- Upgrade to Boot 2 should be clubbed with upgrade to Activiti 7 -->
|
||||
<groupId>com.baeldung</groupId>
|
||||
<artifactId>parent-boot-1</artifactId>
|
||||
<artifactId>parent-boot-2</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<relativePath>../parent-boot-1</relativePath>
|
||||
<relativePath>../parent-boot-2</relativePath>
|
||||
</parent>
|
||||
|
||||
<dependencyManagement>
|
||||
|
@ -32,18 +30,21 @@
|
|||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.activiti</groupId>
|
||||
<artifactId>activiti-spring-boot-starter-basic</artifactId>
|
||||
<version>${activiti.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.activiti</groupId>
|
||||
<artifactId>activiti-spring-boot-starter-security</artifactId>
|
||||
<artifactId>activiti-spring-boot-starter</artifactId>
|
||||
<version>${activiti.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
|
@ -70,7 +71,7 @@
|
|||
</build>
|
||||
|
||||
<properties>
|
||||
<activiti.version>6.0.0</activiti.version>
|
||||
<activiti.version>7.1.0.M6</activiti.version>
|
||||
<log4j2.version>2.17.1</log4j2.version>
|
||||
</properties>
|
||||
|
||||
|
|
|
@ -2,14 +2,11 @@ package com.baeldung.activiti.security.config;
|
|||
|
||||
import java.util.List;
|
||||
|
||||
import org.activiti.engine.IdentityService;
|
||||
import org.activiti.engine.RuntimeService;
|
||||
import org.activiti.engine.TaskService;
|
||||
import org.activiti.engine.runtime.ProcessInstance;
|
||||
import org.activiti.engine.task.Task;
|
||||
import org.activiti.spring.SpringProcessEngineConfiguration;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
|
@ -22,21 +19,9 @@ public class ProcessController {
|
|||
@Autowired
|
||||
private TaskService taskService;
|
||||
|
||||
@Autowired
|
||||
private IdentityService identityService;
|
||||
|
||||
@Autowired
|
||||
SpringProcessEngineConfiguration config;
|
||||
|
||||
@GetMapping("/protected-process")
|
||||
public String startProcess() {
|
||||
|
||||
String userId = SecurityContextHolder.getContext()
|
||||
.getAuthentication()
|
||||
.getName();
|
||||
|
||||
identityService.setAuthenticatedUserId(userId);
|
||||
|
||||
ProcessInstance pi = runtimeService.startProcessInstanceByKey("protected-process");
|
||||
|
||||
List<Task> usertasks = taskService.createTaskQuery()
|
||||
|
|
|
@ -1,86 +0,0 @@
|
|||
package com.baeldung.activiti.security.config;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.activiti.engine.identity.Group;
|
||||
import org.activiti.engine.identity.GroupQuery;
|
||||
import org.activiti.engine.impl.GroupQueryImpl;
|
||||
import org.activiti.engine.impl.Page;
|
||||
import org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl;
|
||||
import org.activiti.engine.impl.persistence.entity.GroupEntityImpl;
|
||||
import org.activiti.engine.impl.persistence.entity.GroupEntityManagerImpl;
|
||||
import org.activiti.engine.impl.persistence.entity.data.GroupDataManager;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.provisioning.JdbcUserDetailsManager;
|
||||
|
||||
public class SpringSecurityGroupManager extends GroupEntityManagerImpl {
|
||||
|
||||
private JdbcUserDetailsManager userManager;
|
||||
|
||||
public SpringSecurityGroupManager(ProcessEngineConfigurationImpl processEngineConfiguration, GroupDataManager groupDataManager) {
|
||||
super(processEngineConfiguration, groupDataManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Group> findGroupByQueryCriteria(GroupQueryImpl query, Page page) {
|
||||
|
||||
if (query.getUserId() != null) {
|
||||
return findGroupsByUser(query.getUserId());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long findGroupCountByQueryCriteria(GroupQueryImpl query) {
|
||||
return findGroupByQueryCriteria(query, null).size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Group> findGroupsByUser(String userId) {
|
||||
UserDetails userDetails = userManager.loadUserByUsername(userId);
|
||||
System.out.println("group manager");
|
||||
if (userDetails != null) {
|
||||
List<Group> groups = userDetails.getAuthorities()
|
||||
.stream()
|
||||
.map(a -> a.getAuthority())
|
||||
.map(a -> {
|
||||
Group g = new GroupEntityImpl();
|
||||
g.setId(a);
|
||||
return g;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
return groups;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setUserManager(JdbcUserDetailsManager userManager) {
|
||||
this.userManager = userManager;
|
||||
}
|
||||
|
||||
public Group createNewGroup(String groupId) {
|
||||
throw new UnsupportedOperationException("This operation is not supported!");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(String groupId) {
|
||||
throw new UnsupportedOperationException("This operation is not supported!");
|
||||
|
||||
}
|
||||
|
||||
public GroupQuery createNewGroupQuery() {
|
||||
throw new UnsupportedOperationException("This operation is not supported!");
|
||||
}
|
||||
|
||||
public List<Group> findGroupsByNativeQuery(Map<String, Object> parameterMap, int firstResult, int maxResults) {
|
||||
throw new UnsupportedOperationException("This operation is not supported!");
|
||||
}
|
||||
|
||||
public long findGroupCountByNativeQuery(Map<String, Object> parameterMap) {
|
||||
throw new UnsupportedOperationException("This operation is not supported!");
|
||||
}
|
||||
|
||||
}
|
|
@ -1,144 +0,0 @@
|
|||
package com.baeldung.activiti.security.config;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.activiti.engine.identity.Group;
|
||||
import org.activiti.engine.identity.User;
|
||||
import org.activiti.engine.identity.UserQuery;
|
||||
import org.activiti.engine.impl.Page;
|
||||
import org.activiti.engine.impl.UserQueryImpl;
|
||||
import org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl;
|
||||
import org.activiti.engine.impl.persistence.entity.GroupEntityImpl;
|
||||
import org.activiti.engine.impl.persistence.entity.UserEntity;
|
||||
import org.activiti.engine.impl.persistence.entity.UserEntityImpl;
|
||||
import org.activiti.engine.impl.persistence.entity.UserEntityManagerImpl;
|
||||
import org.activiti.engine.impl.persistence.entity.data.UserDataManager;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.provisioning.JdbcUserDetailsManager;
|
||||
|
||||
public class SpringSecurityUserManager extends UserEntityManagerImpl {
|
||||
|
||||
private JdbcUserDetailsManager userManager;
|
||||
|
||||
public SpringSecurityUserManager(ProcessEngineConfigurationImpl processEngineConfiguration, UserDataManager userDataManager, JdbcUserDetailsManager userManager) {
|
||||
super(processEngineConfiguration, userDataManager);
|
||||
this.userManager = userManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserEntity findById(String userId) {
|
||||
UserDetails userDetails = userManager.loadUserByUsername(userId);
|
||||
if (userDetails != null) {
|
||||
UserEntityImpl user = new UserEntityImpl();
|
||||
user.setId(userId);
|
||||
return user;
|
||||
}
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<User> findUserByQueryCriteria(UserQueryImpl query, Page page) {
|
||||
List<User> users = null;
|
||||
if (query.getGroupId() != null) {
|
||||
users = userManager.findUsersInGroup(query.getGroupId())
|
||||
.stream()
|
||||
.map(username -> {
|
||||
User user = new UserEntityImpl();
|
||||
user.setId(username);
|
||||
return user;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
if (page != null) {
|
||||
return users.subList(page.getFirstResult(), page.getFirstResult() + page.getMaxResults());
|
||||
|
||||
}
|
||||
return users;
|
||||
}
|
||||
|
||||
if (query.getId() != null) {
|
||||
UserDetails userDetails = userManager.loadUserByUsername(query.getId());
|
||||
if (userDetails != null) {
|
||||
UserEntityImpl user = new UserEntityImpl();
|
||||
user.setId(query.getId());
|
||||
return Collections.singletonList(user);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean checkPassword(String userId, String password) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void setUserManager(JdbcUserDetailsManager userManager) {
|
||||
this.userManager = userManager;
|
||||
}
|
||||
|
||||
public User createNewUser(String userId) {
|
||||
throw new UnsupportedOperationException("This operation is not supported!");
|
||||
}
|
||||
|
||||
public void updateUser(User updatedUser) {
|
||||
throw new UnsupportedOperationException("This operation is not supported!");
|
||||
|
||||
}
|
||||
|
||||
public void delete(UserEntity userEntity) {
|
||||
throw new UnsupportedOperationException("This operation is not supported!");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deletePicture(User user) {
|
||||
UserEntity userEntity = (UserEntity) user;
|
||||
if (userEntity.getPictureByteArrayRef() != null) {
|
||||
userEntity.getPictureByteArrayRef()
|
||||
.delete();
|
||||
}
|
||||
}
|
||||
|
||||
public void delete(String userId) {
|
||||
throw new UnsupportedOperationException("This operation is not supported!");
|
||||
|
||||
}
|
||||
|
||||
public long findUserCountByQueryCriteria(UserQueryImpl query) {
|
||||
return findUserByQueryCriteria(query, null).size();
|
||||
}
|
||||
|
||||
public List<Group> findGroupsByUser(String userId) {
|
||||
UserDetails userDetails = userManager.loadUserByUsername(userId);
|
||||
if (userDetails != null) {
|
||||
List<Group> groups = userDetails.getAuthorities()
|
||||
.stream()
|
||||
.map(a -> a.getAuthority())
|
||||
.map(a -> {
|
||||
Group g = new GroupEntityImpl();
|
||||
g.setId(a);
|
||||
return g;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
return groups;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public UserQuery createNewUserQuery() {
|
||||
throw new UnsupportedOperationException("This operation is not supported!");
|
||||
}
|
||||
|
||||
public List<User> findUsersByNativeQuery(Map<String, Object> parameterMap, int firstResult, int maxResults) {
|
||||
throw new UnsupportedOperationException("This operation is not supported!");
|
||||
}
|
||||
|
||||
public long findUserCountByNativeQuery(Map<String, Object> parameterMap) {
|
||||
throw new UnsupportedOperationException("This operation is not supported!");
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
package com.baeldung.activiti.security.withactiviti;
|
||||
|
||||
import org.activiti.engine.IdentityService;
|
||||
import org.activiti.spring.security.IdentityServiceUserDetailsService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
|
||||
@Configuration
|
||||
public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http.antMatcher("/**")
|
||||
.authorizeRequests()
|
||||
.antMatchers("/protected-process*")
|
||||
.authenticated()
|
||||
.anyRequest()
|
||||
.permitAll()
|
||||
.and()
|
||||
.formLogin()
|
||||
.loginPage("/login")
|
||||
.defaultSuccessUrl("/homepage")
|
||||
.failureUrl("/login?error=true")
|
||||
.and()
|
||||
.csrf()
|
||||
.disable()
|
||||
.logout()
|
||||
.logoutSuccessUrl("/login");
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private IdentityService identityService;
|
||||
|
||||
@Bean
|
||||
public IdentityServiceUserDetailsService userDetailsService() {
|
||||
return new IdentityServiceUserDetailsService(identityService);
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
|
||||
auth.userDetailsService(userDetailsService());
|
||||
}
|
||||
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
package com.baeldung.activiti.security.withactiviti;
|
||||
|
||||
import org.activiti.engine.IdentityService;
|
||||
import org.activiti.engine.identity.Group;
|
||||
import org.activiti.engine.identity.User;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
@SpringBootApplication(scanBasePackages = { "com.baeldung.activiti.security.config", "com.baeldung.activiti.security.withactiviti" })
|
||||
public class SpringSecurityActivitiApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SpringSecurityActivitiApplication.class, args);
|
||||
}
|
||||
|
||||
@Bean
|
||||
InitializingBean usersAndGroupsInitializer(IdentityService identityService) {
|
||||
return new InitializingBean() {
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
User user = identityService.newUser("activiti_user");
|
||||
user.setPassword("pass");
|
||||
identityService.saveUser(user);
|
||||
|
||||
Group group = identityService.newGroup("user");
|
||||
group.setName("ROLE_USER");
|
||||
group.setType("USER");
|
||||
identityService.saveGroup(group);
|
||||
identityService.createMembership(user.getId(), group.getId());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
package com.baeldung.activiti.security.withspring;
|
||||
|
||||
import org.activiti.engine.impl.persistence.entity.data.impl.MybatisGroupDataManager;
|
||||
import org.activiti.engine.impl.persistence.entity.data.impl.MybatisUserDataManager;
|
||||
import org.activiti.spring.SpringProcessEngineConfiguration;
|
||||
import org.activiti.spring.boot.SecurityAutoConfiguration;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.security.provisioning.JdbcUserDetailsManager;
|
||||
|
||||
import com.baeldung.activiti.security.config.SpringSecurityGroupManager;
|
||||
import com.baeldung.activiti.security.config.SpringSecurityUserManager;
|
||||
|
||||
@SpringBootApplication(exclude = SecurityAutoConfiguration.class, scanBasePackages = { "com.baeldung.activiti.security.config", "com.baeldung.activiti.security.withspring" })
|
||||
public class ActivitiSpringSecurityApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(ActivitiSpringSecurityApplication.class, args);
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private SpringProcessEngineConfiguration processEngineConfiguration;
|
||||
|
||||
@Autowired
|
||||
private JdbcUserDetailsManager userManager;
|
||||
|
||||
@Bean
|
||||
InitializingBean processEngineInitializer() {
|
||||
return new InitializingBean() {
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
processEngineConfiguration.setUserEntityManager(new SpringSecurityUserManager(processEngineConfiguration, new MybatisUserDataManager(processEngineConfiguration), userManager));
|
||||
processEngineConfiguration.setGroupEntityManager(new SpringSecurityGroupManager(processEngineConfiguration, new MybatisGroupDataManager(processEngineConfiguration)));
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,21 +1,17 @@
|
|||
package com.baeldung.activiti.security.withspring;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
import org.springframework.security.provisioning.JdbcUserDetailsManager;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
||||
|
||||
@Configuration
|
||||
public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Autowired
|
||||
private DataSource dataSource;
|
||||
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http.antMatcher("/**")
|
||||
.authorizeRequests()
|
||||
|
@ -36,15 +32,12 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
|||
}
|
||||
|
||||
@Bean
|
||||
public JdbcUserDetailsManager userDetailsManager() {
|
||||
JdbcUserDetailsManager manager = new JdbcUserDetailsManager();
|
||||
manager.setDataSource(dataSource);
|
||||
return manager;
|
||||
}
|
||||
public UserDetailsService userDetailsService() {
|
||||
UserDetails user = User.withUsername("user")
|
||||
.password("{noop}pass")
|
||||
.authorities("ROLE_ACTIVITI_USER")
|
||||
.build();
|
||||
|
||||
@Autowired
|
||||
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
|
||||
auth.userDetailsService(userDetailsManager());
|
||||
return new InMemoryUserDetailsManager(user);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
package com.baeldung.activiti.security.withspring;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication(scanBasePackages = { "com.baeldung.activiti.security.config", "com.baeldung.activiti.security.withspring" })
|
||||
public class SpringSecurityActivitiApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SpringSecurityActivitiApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
|
@ -2,9 +2,8 @@ package com.baeldung.activitiwithspring;
|
|||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration;
|
||||
|
||||
@SpringBootApplication(exclude = {SecurityAutoConfiguration.class})
|
||||
@SpringBootApplication
|
||||
public class ActivitiWithSpringApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(ActivitiWithSpringApplication.class, args);
|
||||
|
|
|
@ -1,33 +1,22 @@
|
|||
package com.baeldung.activitiwithspring;
|
||||
|
||||
import org.activiti.engine.IdentityService;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
|
||||
import com.baeldung.activiti.security.withspring.ActivitiSpringSecurityApplication;
|
||||
import com.baeldung.activiti.security.withspring.SpringSecurityActivitiApplication;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(classes = ActivitiSpringSecurityApplication.class)
|
||||
@SpringBootTest(classes = SpringSecurityActivitiApplication.class)
|
||||
@WebAppConfiguration
|
||||
@AutoConfigureTestDatabase
|
||||
public class ActivitiSpringSecurityIntegrationTest {
|
||||
@Autowired
|
||||
private IdentityService identityService;
|
||||
|
||||
@Test
|
||||
public void whenUserExists_thenOk() {
|
||||
identityService.setUserPicture("spring_user", null);
|
||||
}
|
||||
|
||||
@Test(expected = UsernameNotFoundException.class)
|
||||
public void whenUserNonExistent_thenSpringException() {
|
||||
identityService.setUserPicture("user3", null);
|
||||
public void contextLoads() {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue