Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
K
kwell-mes
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
gavelinfo
kwell-mes
Commits
9e9bb8cd
Commit
9e9bb8cd
authored
Apr 06, 2022
by
zhoumaotao
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
config调整
parent
c30b95e5
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
228 additions
and
128 deletions
+228
-128
FilterConfig.java
gavel/src/main/java/com/gavel/kwell/config/FilterConfig.java
+26
-26
ShiroConfig.java
gavel/src/main/java/com/gavel/kwell/config/ShiroConfig.java
+202
-102
No files found.
gavel/src/main/java/com/gavel/kwell/config/FilterConfig.java
View file @
9e9bb8cd
package
com
.
gavel
.
kwell
.
config
;
package
com
.
gavel
.
kwell
.
config
;
import
java.util.Arrays
;
import
com.gavel.framework.filter.RequestWrapperFilter
;
import
com.gavel.framework.filter.ThreadContextFilter
;
import
org.springframework.boot.web.servlet.FilterRegistrationBean
;
import
org.springframework.boot.web.servlet.FilterRegistrationBean
;
import
org.springframework.context.annotation.Bean
;
import
org.springframework.context.annotation.Bean
;
import
org.springframework.context.annotation.Configuration
;
import
org.springframework.context.annotation.Configuration
;
import
org.springframework.web.filter.CharacterEncodingFilter
;
import
org.springframework.web.filter.CharacterEncodingFilter
;
import
com.gavel.framework.filter.AuthenticationFilter
;
import
java.util.Arrays
;
import
com.gavel.framework.filter.RequestWrapperFilter
;
import
com.gavel.framework.filter.ThreadContextFilter
;
@Configuration
@Configuration
public
class
FilterConfig
{
public
class
FilterConfig
{
@Bean
@Bean
public
CharacterEncodingFilter
characterEncodingFilter
()
{
public
CharacterEncodingFilter
characterEncodingFilter
()
{
CharacterEncodingFilter
filter
=
new
CharacterEncodingFilter
();
CharacterEncodingFilter
filter
=
new
CharacterEncodingFilter
();
filter
.
setEncoding
(
"UTF-8"
);
filter
.
setEncoding
(
"UTF-8"
);
filter
.
setForceEncoding
(
true
);
filter
.
setForceEncoding
(
true
);
return
filter
;
return
filter
;
}
@SuppressWarnings
({
"rawtypes"
,
"unchecked"
})
@Bean
public
FilterRegistrationBean
authenticationFilter
()
{
// 用户认证
AuthenticationFilter
authenticationFilter
=
new
AuthenticationFilter
();
FilterRegistrationBean
registrationBean
=
new
FilterRegistrationBean
();
registrationBean
.
setName
(
"authenticationFilter"
);
// 过滤器名称
registrationBean
.
setFilter
(
authenticationFilter
);
// 注入过滤器
registrationBean
.
setOrder
(
10
);
registrationBean
.
addInitParameter
(
"prefix"
,
"/css,/js,/images,/lib,/fonts,/mock"
);
registrationBean
.
setUrlPatterns
(
Arrays
.
asList
(
"/*"
));
//拦截规则
return
registrationBean
;
}
}
@SuppressWarnings
({
"rawtypes"
,
"unchecked"
})
@SuppressWarnings
({
"rawtypes"
,
"unchecked"
})
@Bean
// @Bean
// public FilterRegistrationBean authenticationFilter() {
// // 用户认证
// AuthenticationFilter authenticationFilter = new AuthenticationFilter();
// FilterRegistrationBean registrationBean = new FilterRegistrationBean();
// registrationBean.setName("authenticationFilter"); // 过滤器名称
// registrationBean.setFilter(authenticationFilter); // 注入过滤器
// registrationBean.setOrder(10);
// registrationBean.addInitParameter("prefix", "/css,/js,/images,/lib,/fonts,/mock");
// registrationBean.setUrlPatterns(Arrays.asList("/*")); //拦截规则
// return registrationBean;
// }
@Bean
public
FilterRegistrationBean
requestWrapperFilter
()
{
public
FilterRegistrationBean
requestWrapperFilter
()
{
RequestWrapperFilter
requestWrapperFilter
=
new
RequestWrapperFilter
();
RequestWrapperFilter
requestWrapperFilter
=
new
RequestWrapperFilter
();
FilterRegistrationBean
registrationBean
=
new
FilterRegistrationBean
();
FilterRegistrationBean
registrationBean
=
new
FilterRegistrationBean
();
...
...
gavel/src/main/java/com/gavel/kwell/config/ShiroConfig.java
View file @
9e9bb8cd
package
com
.
gavel
.
kwell
.
config
;
package
com
.
gavel
.
kwell
.
config
;
import
com.gavel.common.Constants
;
import
com.gavel.common.utils.StringUtils
;
import
com.gavel.framework.filter.GavelCommonLogoutFilter
;
import
com.gavel.framework.filter.ShiroAuthFilter
;
import
com.gavel.kzzx.auth.cas.GavelAuthenticationFilter
;
import
com.gavel.kzzx.auth.cas.GavelCasFilter
;
import
com.gavel.kzzx.auth.cas.GavelCasRealm
;
import
com.gavel.kzzx.auth.cas.GavelLogoutFilter
;
import
com.gavel.kzzx.auth.shiro.*
;
import
org.apache.shiro.cache.CacheManager
;
import
org.apache.shiro.cache.CacheManager
;
import
org.apache.shiro.cache.MemoryConstrainedCacheManager
;
import
org.apache.shiro.cache.MemoryConstrainedCacheManager
;
import
org.apache.shiro.cas.CasSubjectFactory
;
import
org.apache.shiro.mgt.SecurityManager
;
import
org.apache.shiro.mgt.SecurityManager
;
import
org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator
;
import
org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator
;
import
org.apache.shiro.session.mgt.eis.SessionIdGenerator
;
import
org.apache.shiro.session.mgt.eis.SessionIdGenerator
;
import
org.apache.shiro.spring.web.ShiroFilterFactoryBean
;
import
org.apache.shiro.spring.web.ShiroFilterFactoryBean
;
import
org.apache.shiro.web.mgt.DefaultWebSecurityManager
;
import
org.apache.shiro.web.mgt.DefaultWebSecurityManager
;
import
org.apache.shiro.web.session.mgt.DefaultWebSessionManager
;
import
org.crazycake.shiro.RedisCacheManager
;
import
org.crazycake.shiro.RedisCacheManager
;
import
org.crazycake.shiro.RedisManager
;
import
org.crazycake.shiro.RedisManager
;
import
org.crazycake.shiro.RedisSessionDAO
;
import
org.crazycake.shiro.RedisSessionDAO
;
import
org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator
;
import
org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.beans.factory.annotation.Qualifier
;
import
org.springframework.beans.factory.annotation.Value
;
import
org.springframework.beans.factory.annotation.Value
;
import
org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
;
import
org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
;
import
org.springframework.boot.web.servlet.FilterRegistrationBean
;
import
org.springframework.context.annotation.Bean
;
import
org.springframework.context.annotation.Bean
;
import
org.springframework.context.annotation.Configuration
;
import
org.springframework.context.annotation.Configuration
;
import
org.springframework.context.annotation.PropertySource
;
import
org.springframework.web.filter.DelegatingFilterProxy
;
import
javax.servlet.Filter
;
import
java.util.LinkedHashMap
;
import
java.util.Map
;
import
com.gavel.common.utils.StringUtils
;
import
com.gavel.kzzx.auth.shiro.GavelAuthResource
;
import
com.gavel.kzzx.auth.shiro.GavelAuthorizationAttributeSourceAdvisor
;
import
com.gavel.kzzx.auth.shiro.GavelAuthorizingRealm
;
import
com.gavel.kzzx.auth.shiro.GavelHashedCredentialsMatcher
;
@Configuration
@Configuration
@PropertySource
(
value
=
{
"classpath:config.properties"
})
public
class
ShiroConfig
{
public
class
ShiroConfig
{
@Value
(
"${shiro.cache:}"
)
private
static
final
String
CAS_FILTER_URL
=
"/shiro-cas"
;
private
String
cacheType
;
@Value
(
"${sso.enable:false}"
)
@Autowired
private
boolean
ssoEnable
;
private
RedisConfig
redisConfig
;
@Value
(
"${sso.server:}"
)
@Bean
private
String
casServerUrl
;
public
ShiroFilterFactoryBean
shirFilter
(
SecurityManager
securityManager
)
{
ShiroFilterFactoryBean
shiroFilterFactoryBean
=
new
ShiroFilterFactoryBean
();
@Value
(
"${shiro.cache:}"
)
// 必须设置 SecurityManager
private
String
cacheType
;
shiroFilterFactoryBean
.
setSecurityManager
(
securityManager
);
// 设置login URL
@Autowired
shiroFilterFactoryBean
.
setLoginUrl
(
"/login_view"
);
private
RedisConfig
redisConfig
;
// 登录成功后要跳转的链接
shiroFilterFactoryBean
.
setSuccessUrl
(
"/index"
);
@SuppressWarnings
({
"rawtypes"
,
"unchecked"
})
shiroFilterFactoryBean
.
setFilterChainDefinitionMap
(
GavelAuthResource
.
init
());
@Bean
return
shiroFilterFactoryBean
;
public
FilterRegistrationBean
filterRegistrationBean
()
{
}
FilterRegistrationBean
filterRegistration
=
new
FilterRegistrationBean
();
filterRegistration
.
setFilter
(
new
DelegatingFilterProxy
(
"shiroFilter"
));
/*
filterRegistration
.
addInitParameter
(
"targetFilterLifecycle"
,
"true"
);
* 凭证匹配器 (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了
filterRegistration
.
setEnabled
(
true
);
* 所以我们需要修改下doGetAuthenticationInfo中的代码; )
filterRegistration
.
setOrder
(
1
);
*/
filterRegistration
.
addUrlPatterns
(
"/*"
);
@Bean
return
filterRegistration
;
public
GavelHashedCredentialsMatcher
hashedCredentialsMatcher
()
{
}
GavelHashedCredentialsMatcher
hashedCredentialsMatcher
=
new
GavelHashedCredentialsMatcher
();
hashedCredentialsMatcher
.
setHashAlgorithmName
(
"md5"
);
// 散列算法:这里使用MD5算法;
hashedCredentialsMatcher
.
setHashIterations
(
1
);
// 散列的次数,比如散列两次,相当于md5(md5(""));
@Bean
(
name
=
"shiroFilter"
)
return
hashedCredentialsMatcher
;
public
ShiroFilterFactoryBean
shirFilter
(
SecurityManager
securityManager
)
{
}
ShiroFilterFactoryBean
shiroFilterFactoryBean
=
new
ShiroFilterFactoryBean
();
@Bean
// 必须设置 SecurityManager
public
GavelAuthorizingRealm
shiroRealm
()
{
shiroFilterFactoryBean
.
setSecurityManager
(
securityManager
);
GavelAuthorizingRealm
shiroRealm
=
new
GavelAuthorizingRealm
();
// 登录成功后要跳转的链接
shiroRealm
.
setCredentialsMatcher
(
hashedCredentialsMatcher
());
shiroFilterFactoryBean
.
setSuccessUrl
(
"/index"
);
return
shiroRealm
;
}
if
(
ssoEnable
)
{
Map
<
String
,
Filter
>
filters
=
new
LinkedHashMap
<>();
@Bean
shiroFilterFactoryBean
.
setFilters
(
filters
);
public
SecurityManager
securityManager
()
{
filters
.
put
(
"casFilter"
,
new
GavelCasFilter
(
casServerUrl
));
DefaultWebSecurityManager
securityManager
=
new
DefaultWebSecurityManager
();
filters
.
put
(
"logout"
,
new
GavelLogoutFilter
(
casServerUrl
,
ssoEnable
));
// 注入自定义的realm;
filters
.
put
(
"authFilter"
,
new
GavelAuthenticationFilter
(
casServerUrl
,
ssoEnable
));
securityManager
.
setRealm
(
shiroRealm
());
Map
<
String
,
String
>
filterChainDefinitionMap
=
new
LinkedHashMap
<>();
// 注入缓存管理器;
filterChainDefinitionMap
.
put
(
CAS_FILTER_URL
,
"casFilter"
);
if
(
StringUtils
.
equals
(
cacheType
,
"redis"
))
filterChainDefinitionMap
.
put
(
"/logout"
,
"logout"
);
securityManager
.
setCacheManager
(
redisCacheManager
());
filterChainDefinitionMap
.
put
(
"/file/**"
,
"anon"
);
else
filterChainDefinitionMap
.
put
(
"/api/**"
,
"anon"
);
securityManager
.
setCacheManager
(
cacheManager
());
filterChainDefinitionMap
.
put
(
"/video.html"
,
"anon"
);
return
securityManager
;
filterChainDefinitionMap
.
put
(
"/static/**"
,
"anon"
);
}
filterChainDefinitionMap
.
put
(
"/api/**"
,
"anon"
);
filterChainDefinitionMap
.
put
(
"/css/**"
,
"anon"
);
/*
filterChainDefinitionMap
.
put
(
"/js/**"
,
"anon"
);
* 开启shiro aop注解支持 使用代理方式;所以需要开启代码支持;
filterChainDefinitionMap
.
put
(
"/images/**"
,
"anon"
);
*/
filterChainDefinitionMap
.
put
(
"/lib/**"
,
"anon"
);
@Bean
filterChainDefinitionMap
.
put
(
"/fonts/**"
,
"anon"
);
public
GavelAuthorizationAttributeSourceAdvisor
authorizationAttributeSourceAdvisor
(
filterChainDefinitionMap
.
put
(
"/mock/**"
,
"anon"
);
SecurityManager
securityManager
)
{
filterChainDefinitionMap
.
put
(
"/**"
,
"authFilter"
);
GavelAuthorizationAttributeSourceAdvisor
authorizationAttributeSourceAdvisor
=
new
GavelAuthorizationAttributeSourceAdvisor
();
shiroFilterFactoryBean
.
setFilterChainDefinitionMap
(
filterChainDefinitionMap
);
authorizationAttributeSourceAdvisor
.
setSecurityManager
(
securityManager
);
}
return
authorizationAttributeSourceAdvisor
;
else
{
}
// 设置login URL
/**
shiroFilterFactoryBean
.
setLoginUrl
(
"/login_view"
);
* DefaultAdvisorAutoProxyCreator,Spring的一个bean,由Advisor决定对哪些类的方法进行AOP代理。
shiroFilterFactoryBean
.
setUnauthorizedUrl
(
"/403"
);
*/
shiroFilterFactoryBean
.
setSuccessUrl
(
"/index"
);
@Bean
public
DefaultAdvisorAutoProxyCreator
defaultAdvisorAutoProxyCreator
()
{
Map
<
String
,
Filter
>
filters
=
new
LinkedHashMap
<>();
DefaultAdvisorAutoProxyCreator
defaultAAP
=
new
DefaultAdvisorAutoProxyCreator
();
filters
.
put
(
"permFilter"
,
new
ShiroAuthFilter
());
defaultAAP
.
setProxyTargetClass
(
true
);
filters
.
put
(
"logout"
,
new
GavelCommonLogoutFilter
());
return
defaultAAP
;
shiroFilterFactoryBean
.
setFilters
(
filters
);
}
Map
<
String
,
String
>
filterChainDefinitionMap
=
GavelAuthResource
.
init
();
filterChainDefinitionMap
.
put
(
"/logout"
,
"logout"
);
/*
filterChainDefinitionMap
.
put
(
"/**"
,
"permFilter"
);
* shiro缓存管理器;
shiroFilterFactoryBean
.
setFilterChainDefinitionMap
(
filterChainDefinitionMap
);
* 需要注入对应的其它的实体类中-->安全管理器:securityManager可见securityManager是整个shiro的核心;
*/
}
@Bean
return
shiroFilterFactoryBean
;
public
CacheManager
cacheManager
()
{
}
return
new
MemoryConstrainedCacheManager
();
}
/*
* 凭证匹配器 (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了
/**
* 所以我们需要修改下doGetAuthenticationInfo中的代码; )
*/
@Bean
public
GavelHashedCredentialsMatcher
hashedCredentialsMatcher
()
{
GavelHashedCredentialsMatcher
hashedCredentialsMatcher
=
new
GavelHashedCredentialsMatcher
();
hashedCredentialsMatcher
.
setHashAlgorithmName
(
"md5"
);
// 散列算法:这里使用MD5算法;
hashedCredentialsMatcher
.
setHashIterations
(
1
);
// 散列的次数,比如散列两次,相当于md5(md5(""));
return
hashedCredentialsMatcher
;
}
@Bean
(
name
=
"shiroRealm"
)
public
GavelAuthorizingRealm
shiroRealm
()
{
GavelAuthorizingRealm
shiroRealm
=
new
GavelAuthorizingRealm
();
shiroRealm
.
setCredentialsMatcher
(
hashedCredentialsMatcher
());
return
shiroRealm
;
}
@Bean
(
name
=
"casRealm"
)
public
GavelCasRealm
casRealm
()
{
GavelCasRealm
casRealm
=
new
GavelCasRealm
();
// 认证通过后的默认角色
casRealm
.
setDefaultRoles
(
"ROLE_USER"
);
// cas 服务端地址前缀
casRealm
.
setCasServerUrlPrefix
(
casServerUrl
);
// 应用服务地址,用来接收cas服务端票证
// casRealm.setCasService(appServerUrl + CAS_FILTER_URL);
return
casRealm
;
}
@Bean
(
"securityManager"
)
public
SecurityManager
securityManager
(
GavelCasRealm
casRealm
)
{
DefaultWebSecurityManager
securityManager
=
new
DefaultWebSecurityManager
();
// 注入自定义的realm;
if
(
ssoEnable
)
{
// 设置授权策略,此步骤必须在设置realm的前面,不然会报错realm未配置
//securityManager.setAuthenticator(authenticator);
securityManager
.
setSubjectFactory
(
new
CasSubjectFactory
());
// 设置自定义验证策略
securityManager
.
setRealm
(
casRealm
);
}
else
{
securityManager
.
setRealm
(
shiroRealm
());
}
// 注入缓存管理器;
if
(
StringUtils
.
equals
(
cacheType
,
"redis"
))
securityManager
.
setCacheManager
(
redisCacheManager
());
else
securityManager
.
setCacheManager
(
cacheManager
());
securityManager
.
setSessionManager
(
sessionManager
());
return
securityManager
;
}
/*
* 开启shiro aop注解支持 使用代理方式;所以需要开启代码支持;
*/
@Bean
public
GavelAuthorizationAttributeSourceAdvisor
authorizationAttributeSourceAdvisor
(
@Qualifier
(
"securityManager"
)
SecurityManager
securityManager
)
{
GavelAuthorizationAttributeSourceAdvisor
authorizationAttributeSourceAdvisor
=
new
GavelAuthorizationAttributeSourceAdvisor
();
authorizationAttributeSourceAdvisor
.
setSecurityManager
(
securityManager
);
return
authorizationAttributeSourceAdvisor
;
}
/**
* DefaultAdvisorAutoProxyCreator,Spring的一个bean,由Advisor决定对哪些类的方法进行AOP代理。
*/
@Bean
public
DefaultAdvisorAutoProxyCreator
defaultAdvisorAutoProxyCreator
()
{
DefaultAdvisorAutoProxyCreator
defaultAAP
=
new
DefaultAdvisorAutoProxyCreator
();
defaultAAP
.
setProxyTargetClass
(
true
);
return
defaultAAP
;
}
/*
* shiro缓存管理器;
* 需要注入对应的其它的实体类中-->安全管理器:securityManager可见securityManager是整个shiro的核心;
*/
@Bean
(
"ShiroCacheManager"
)
public
CacheManager
cacheManager
()
{
return
new
MemoryConstrainedCacheManager
();
}
/**
* 配置会话ID生成器
* 配置会话ID生成器
* @return
* @return
*/
*/
...
@@ -118,14 +214,14 @@ public class ShiroConfig {
...
@@ -118,14 +214,14 @@ public class ShiroConfig {
public
SessionIdGenerator
sessionIdGenerator
()
{
public
SessionIdGenerator
sessionIdGenerator
()
{
return
new
JavaUuidSessionIdGenerator
();
return
new
JavaUuidSessionIdGenerator
();
}
}
/**
/**
* redisManager
* redisManager
*
*
* @return
* @return
*/
*/
@Bean
@Bean
(
"redisManager"
)
@ConditionalOnProperty
(
value
=
"shiro.cache"
,
havingValue
=
"redis"
,
matchIfMissing
=
false
)
//
@ConditionalOnProperty(value="shiro.cache", havingValue="redis", matchIfMissing=false)
public
RedisManager
redisManager
()
{
public
RedisManager
redisManager
()
{
RedisManager
redisManager
=
new
RedisManager
();
RedisManager
redisManager
=
new
RedisManager
();
redisManager
.
setHost
(
redisConfig
.
getHost
()+
":"
+
redisConfig
.
getPort
());
redisManager
.
setHost
(
redisConfig
.
getHost
()+
":"
+
redisConfig
.
getPort
());
...
@@ -133,7 +229,7 @@ public class ShiroConfig {
...
@@ -133,7 +229,7 @@ public class ShiroConfig {
}
}
/**
/**
* cacheManager
* cacheManager
*
*
* @return
* @return
*/
*/
...
@@ -148,23 +244,27 @@ public class ShiroConfig {
...
@@ -148,23 +244,27 @@ public class ShiroConfig {
/**
/**
* redisSessionDAO
* redisSessionDAO
*/
*/
@ConditionalOnProperty
(
value
=
"shiro.cache"
,
havingValue
=
"redis"
,
matchIfMissing
=
false
)
//
@ConditionalOnProperty(value="shiro.cache", havingValue="redis", matchIfMissing=false)
@Bean
@Bean
(
"redisSessionDAO"
)
public
RedisSessionDAO
redisSessionDAO
()
{
public
RedisSessionDAO
redisSessionDAO
()
{
RedisSessionDAO
redisSessionDAO
=
new
RedisSessionDAO
();
RedisSessionDAO
redisSessionDAO
=
new
RedisSessionDAO
();
redisSessionDAO
.
setRedisManager
(
redisManager
());
redisSessionDAO
.
setRedisManager
(
redisManager
());
// redisSessionDAO.setSessionIdGenerator(new GavelSessionGenerator());
return
redisSessionDAO
;
return
redisSessionDAO
;
}
}
/**
/**
* sessionManager
* sessionManager
*/
*/
@ConditionalOnProperty
(
value
=
"shiro.cache"
,
havingValue
=
"redis"
,
matchIfMissing
=
false
)
// @ConditionalOnProperty(value="shiro.cache", havingValue="redis", matchIfMissing=false)
@Bean
@Bean
(
"sessionManager"
)
public
DefaultWebSessionManager
SessionManager
()
{
public
GavelSessionManager
sessionManager
()
{
DefaultWebSessionManager
sessionManager
=
new
DefaultWebSessionManager
();
// DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
GavelSessionManager
sessionManager
=
new
GavelSessionManager
();
sessionManager
.
setGlobalSessionTimeout
(
Constants
.
liveMills
);
sessionManager
.
setSessionDAO
(
redisSessionDAO
());
sessionManager
.
setSessionDAO
(
redisSessionDAO
());
return
sessionManager
;
return
sessionManager
;
}
}
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment