mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-06-27 14:22:47 +00:00
SEC-823, SEC-843: Allow setting of custom RememberMeServices and token validity periodon remember-me namespace element
This commit is contained in:
parent
3e33b8a880
commit
fbe3ca48f4
@ -23,12 +23,14 @@ import org.w3c.dom.Element;
|
|||||||
*/
|
*/
|
||||||
public class RememberMeBeanDefinitionParser implements BeanDefinitionParser {
|
public class RememberMeBeanDefinitionParser implements BeanDefinitionParser {
|
||||||
static final String ATT_KEY = "key";
|
static final String ATT_KEY = "key";
|
||||||
static final String DEF_KEY = "doesNotMatter";
|
static final String DEF_KEY = "SpringSecured";
|
||||||
|
|
||||||
static final String ATT_DATA_SOURCE = "data-source";
|
static final String ATT_DATA_SOURCE = "data-source-ref";
|
||||||
|
static final String ATT_SERVICES_REF = "services-ref";
|
||||||
static final String ATT_TOKEN_REPOSITORY = "token-repository-ref";
|
static final String ATT_TOKEN_REPOSITORY = "token-repository-ref";
|
||||||
static final String ATT_USER_SERVICE_REF = "user-service-ref";
|
static final String ATT_USER_SERVICE_REF = "user-service-ref";
|
||||||
|
static final String ATT_TOKEN_VALIDITY = "token-validity-seconds";
|
||||||
|
|
||||||
protected final Log logger = LogFactory.getLog(getClass());
|
protected final Log logger = LogFactory.getLog(getClass());
|
||||||
|
|
||||||
public BeanDefinition parse(Element element, ParserContext parserContext) {
|
public BeanDefinition parse(Element element, ParserContext parserContext) {
|
||||||
@ -37,32 +39,46 @@ public class RememberMeBeanDefinitionParser implements BeanDefinitionParser {
|
|||||||
String key = null;
|
String key = null;
|
||||||
Object source = null;
|
Object source = null;
|
||||||
String userServiceRef = null;
|
String userServiceRef = null;
|
||||||
|
String rememberMeServicesRef = null;
|
||||||
|
String tokenValiditySeconds = null;
|
||||||
|
|
||||||
if (element != null) {
|
if (element != null) {
|
||||||
tokenRepository = element.getAttribute(ATT_TOKEN_REPOSITORY);
|
tokenRepository = element.getAttribute(ATT_TOKEN_REPOSITORY);
|
||||||
dataSource = element.getAttribute(ATT_DATA_SOURCE);
|
dataSource = element.getAttribute(ATT_DATA_SOURCE);
|
||||||
key = element.getAttribute(ATT_KEY);
|
key = element.getAttribute(ATT_KEY);
|
||||||
userServiceRef = element.getAttribute(ATT_USER_SERVICE_REF);
|
userServiceRef = element.getAttribute(ATT_USER_SERVICE_REF);
|
||||||
|
rememberMeServicesRef = element.getAttribute(ATT_SERVICES_REF);
|
||||||
|
tokenValiditySeconds = element.getAttribute(ATT_TOKEN_VALIDITY);
|
||||||
source = parserContext.extractSource(element);
|
source = parserContext.extractSource(element);
|
||||||
}
|
}
|
||||||
|
|
||||||
RootBeanDefinition filter = new RootBeanDefinition(RememberMeProcessingFilter.class);
|
if (!StringUtils.hasText(key)) {
|
||||||
RootBeanDefinition services = new RootBeanDefinition(PersistentTokenBasedRememberMeServices.class);
|
key = DEF_KEY;
|
||||||
|
}
|
||||||
filter.getPropertyValues().addPropertyValue("authenticationManager",
|
|
||||||
new RuntimeBeanReference(BeanIds.AUTHENTICATION_MANAGER));
|
RootBeanDefinition services = null;
|
||||||
|
|
||||||
boolean dataSourceSet = StringUtils.hasText(dataSource);
|
boolean dataSourceSet = StringUtils.hasText(dataSource);
|
||||||
boolean tokenRepoSet = StringUtils.hasText(tokenRepository);
|
boolean tokenRepoSet = StringUtils.hasText(tokenRepository);
|
||||||
|
boolean servicesRefSet = StringUtils.hasText(rememberMeServicesRef);
|
||||||
|
boolean userServiceSet = StringUtils.hasText(userServiceRef);
|
||||||
|
boolean tokenValiditySet = StringUtils.hasText(tokenValiditySeconds);
|
||||||
|
|
||||||
|
if (servicesRefSet && (dataSourceSet || tokenRepoSet || userServiceSet || tokenValiditySet)) {
|
||||||
|
parserContext.getReaderContext().error(ATT_SERVICES_REF + " can't be used in combination with attributes "
|
||||||
|
+ ATT_TOKEN_REPOSITORY + "," + ATT_DATA_SOURCE + ", " + ATT_USER_SERVICE_REF + " or " + ATT_TOKEN_VALIDITY, source);
|
||||||
|
}
|
||||||
|
|
||||||
if (dataSourceSet && tokenRepoSet) {
|
if (dataSourceSet && tokenRepoSet) {
|
||||||
parserContext.getReaderContext().error("Specify tokenRepository or dataSource but not both", element);
|
parserContext.getReaderContext().error("Specify " + ATT_TOKEN_REPOSITORY + " or " +
|
||||||
|
ATT_DATA_SOURCE +" but not both", source);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isPersistent = dataSourceSet | tokenRepoSet;
|
boolean isPersistent = dataSourceSet | tokenRepoSet;
|
||||||
|
|
||||||
if (isPersistent) {
|
if (isPersistent) {
|
||||||
Object tokenRepo;
|
Object tokenRepo;
|
||||||
|
services = new RootBeanDefinition(PersistentTokenBasedRememberMeServices.class);
|
||||||
|
|
||||||
if (tokenRepoSet) {
|
if (tokenRepoSet) {
|
||||||
tokenRepo = new RuntimeBeanReference(tokenRepository);
|
tokenRepo = new RuntimeBeanReference(tokenRepository);
|
||||||
@ -72,39 +88,51 @@ public class RememberMeBeanDefinitionParser implements BeanDefinitionParser {
|
|||||||
new RuntimeBeanReference(dataSource));
|
new RuntimeBeanReference(dataSource));
|
||||||
}
|
}
|
||||||
services.getPropertyValues().addPropertyValue("tokenRepository", tokenRepo);
|
services.getPropertyValues().addPropertyValue("tokenRepository", tokenRepo);
|
||||||
} else {
|
} else if (!servicesRefSet) {
|
||||||
isPersistent = false;
|
|
||||||
services = new RootBeanDefinition(TokenBasedRememberMeServices.class);
|
services = new RootBeanDefinition(TokenBasedRememberMeServices.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!StringUtils.hasText(key) && !isPersistent) {
|
if (services != null) {
|
||||||
key = DEF_KEY;
|
if (userServiceSet) {
|
||||||
|
services.getPropertyValues().addPropertyValue("userDetailsService", new RuntimeBeanReference(userServiceRef));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tokenValiditySet) {
|
||||||
|
services.getPropertyValues().addPropertyValue("tokenValiditySeconds", Integer.parseInt(tokenValiditySeconds));
|
||||||
|
}
|
||||||
|
services.setSource(source);
|
||||||
|
services.getPropertyValues().addPropertyValue(ATT_KEY, key);
|
||||||
|
parserContext.getRegistry().registerBeanDefinition(BeanIds.REMEMBER_ME_SERVICES, services);
|
||||||
|
} else {
|
||||||
|
parserContext.getRegistry().registerAlias(rememberMeServicesRef, BeanIds.REMEMBER_ME_SERVICES);
|
||||||
}
|
}
|
||||||
|
|
||||||
BeanDefinition authManager = ConfigUtils.registerProviderManagerIfNecessary(parserContext);
|
registerProvider(parserContext, source, key);
|
||||||
RootBeanDefinition provider = new RootBeanDefinition(RememberMeAuthenticationProvider.class);
|
|
||||||
|
registerFilter(parserContext, source);
|
||||||
filter.setSource(source);
|
|
||||||
services.setSource(source);
|
|
||||||
provider.setSource(source);
|
|
||||||
|
|
||||||
if (StringUtils.hasText(userServiceRef)) {
|
|
||||||
services.getPropertyValues().addPropertyValue("userDetailsService", new RuntimeBeanReference(userServiceRef));
|
|
||||||
}
|
|
||||||
|
|
||||||
provider.getPropertyValues().addPropertyValue(ATT_KEY, key);
|
|
||||||
services.getPropertyValues().addPropertyValue(ATT_KEY, key);
|
|
||||||
|
|
||||||
ManagedList providers = (ManagedList) authManager.getPropertyValues().getPropertyValue("providers").getValue();
|
|
||||||
providers.add(provider);
|
|
||||||
|
|
||||||
filter.getPropertyValues().addPropertyValue("rememberMeServices",
|
|
||||||
new RuntimeBeanReference(BeanIds.REMEMBER_ME_SERVICES));
|
|
||||||
|
|
||||||
parserContext.getRegistry().registerBeanDefinition(BeanIds.REMEMBER_ME_SERVICES, services);
|
|
||||||
parserContext.getRegistry().registerBeanDefinition(BeanIds.REMEMBER_ME_FILTER, filter);
|
|
||||||
ConfigUtils.addHttpFilter(parserContext, new RuntimeBeanReference(BeanIds.REMEMBER_ME_FILTER));
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void registerProvider(ParserContext pc, Object source, String key) {
|
||||||
|
BeanDefinition authManager = ConfigUtils.registerProviderManagerIfNecessary(pc);
|
||||||
|
RootBeanDefinition provider = new RootBeanDefinition(RememberMeAuthenticationProvider.class);
|
||||||
|
provider.setSource(source);
|
||||||
|
provider.getPropertyValues().addPropertyValue(ATT_KEY, key);
|
||||||
|
ManagedList providers = (ManagedList) authManager.getPropertyValues().getPropertyValue("providers").getValue();
|
||||||
|
providers.add(provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void registerFilter(ParserContext pc, Object source) {
|
||||||
|
RootBeanDefinition filter = new RootBeanDefinition(RememberMeProcessingFilter.class);
|
||||||
|
filter.setSource(source);
|
||||||
|
filter.getPropertyValues().addPropertyValue("authenticationManager",
|
||||||
|
new RuntimeBeanReference(BeanIds.AUTHENTICATION_MANAGER));
|
||||||
|
|
||||||
|
filter.getPropertyValues().addPropertyValue("rememberMeServices",
|
||||||
|
new RuntimeBeanReference(BeanIds.REMEMBER_ME_SERVICES));
|
||||||
|
|
||||||
|
pc.getRegistry().registerBeanDefinition(BeanIds.REMEMBER_ME_FILTER, filter);
|
||||||
|
ConfigUtils.addHttpFilter(pc, new RuntimeBeanReference(BeanIds.REMEMBER_ME_FILTER));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -347,18 +347,24 @@ remember-me =
|
|||||||
## Sets up remember-me authentication. If used with the "key" attribute (or no attributes) the cookie-only implementation will be used. Specifying "token-repository-ref" or "remember-me-data-source-ref" will use the more secure, persisten token approach.
|
## Sets up remember-me authentication. If used with the "key" attribute (or no attributes) the cookie-only implementation will be used. Specifying "token-repository-ref" or "remember-me-data-source-ref" will use the more secure, persisten token approach.
|
||||||
element remember-me {remember-me.attlist}
|
element remember-me {remember-me.attlist}
|
||||||
remember-me.attlist &=
|
remember-me.attlist &=
|
||||||
(attribute key {xsd:string} | token-repository-ref | remember-me-data-source-ref | remember-me-services-ref)
|
## The "key" used to identify cookies from a specific token-based remember-me application. You should set this to a unique value for your application.
|
||||||
|
attribute key {xsd:string}?
|
||||||
|
|
||||||
|
remember-me.attlist &=
|
||||||
|
(token-repository-ref | remember-me-data-source-ref | remember-me-services-ref)
|
||||||
|
|
||||||
remember-me.attlist &=
|
remember-me.attlist &=
|
||||||
user-service-ref?
|
user-service-ref?
|
||||||
|
|
||||||
remember-me.attlist &=
|
remember-me.attlist &=
|
||||||
## The period (in seconds) for which the remember-me cookie should be valid.
|
## The period (in seconds) for which the remember-me cookie should be valid.
|
||||||
attribute token-validity-period {xsd:positiveInteger}?
|
attribute token-validity-seconds {xsd:positiveInteger}?
|
||||||
|
|
||||||
token-repository-ref =
|
token-repository-ref =
|
||||||
## Reference to a PersistentTokenRepository bean for use with the persistent token remember-me implementation.
|
## Reference to a PersistentTokenRepository bean for use with the persistent token remember-me implementation.
|
||||||
attribute token-repository-ref {xsd:string}
|
attribute token-repository-ref {xsd:string}
|
||||||
remember-me-services-ref =
|
remember-me-services-ref =
|
||||||
## Allows a custom implementation of RememberMeServices to be used.
|
## Allows a custom implementation of RememberMeServices to be used. Note that this implementation should return RememberMeAuthenticationToken instances with the same "key" value as specified in the remember-me element. Alternatively it should register its own AuthenticationProvider.
|
||||||
attribute services-ref {xsd:string}?
|
attribute services-ref {xsd:string}?
|
||||||
remember-me-data-source-ref =
|
remember-me-data-source-ref =
|
||||||
## DataSource bean for the database that contains the token
|
## DataSource bean for the database that contains the token
|
||||||
|
@ -1024,7 +1024,13 @@
|
|||||||
</xs:attribute>
|
</xs:attribute>
|
||||||
</xs:attributeGroup>
|
</xs:attributeGroup>
|
||||||
<xs:attributeGroup name="remember-me.attlist">
|
<xs:attributeGroup name="remember-me.attlist">
|
||||||
<xs:attribute name="key" type="xs:string"/>
|
<xs:attribute name="key" type="xs:string">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>The "key" used to identify cookies from a specific token-based remember-me
|
||||||
|
application. You should set this to a unique value for your
|
||||||
|
application.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
<xs:attribute name="token-repository-ref" type="xs:string">
|
<xs:attribute name="token-repository-ref" type="xs:string">
|
||||||
<xs:annotation>
|
<xs:annotation>
|
||||||
<xs:documentation>Reference to a PersistentTokenRepository bean for use with the persistent
|
<xs:documentation>Reference to a PersistentTokenRepository bean for use with the persistent
|
||||||
@ -1043,7 +1049,7 @@
|
|||||||
Id</xs:documentation>
|
Id</xs:documentation>
|
||||||
</xs:annotation>
|
</xs:annotation>
|
||||||
</xs:attribute>
|
</xs:attribute>
|
||||||
<xs:attribute name="token-validity-period" type="xs:positiveInteger">
|
<xs:attribute name="token-validity-seconds" type="xs:positiveInteger">
|
||||||
<xs:annotation>
|
<xs:annotation>
|
||||||
<xs:documentation>The period (in seconds) for which the remember-me cookie should be valid.
|
<xs:documentation>The period (in seconds) for which the remember-me cookie should be valid.
|
||||||
</xs:documentation>
|
</xs:documentation>
|
||||||
@ -1061,8 +1067,10 @@
|
|||||||
<xs:attributeGroup name="remember-me-services-ref">
|
<xs:attributeGroup name="remember-me-services-ref">
|
||||||
<xs:attribute name="services-ref" type="xs:string">
|
<xs:attribute name="services-ref" type="xs:string">
|
||||||
<xs:annotation>
|
<xs:annotation>
|
||||||
<xs:documentation>Allows a custom implementation of RememberMeServices to be
|
<xs:documentation>Allows a custom implementation of RememberMeServices to be used. Note that
|
||||||
used.</xs:documentation>
|
this implementation should return RememberMeAuthenticationToken instances with the same
|
||||||
|
"key" value as specified in the remember-me element. Alternatively it should register its
|
||||||
|
own AuthenticationProvider. </xs:documentation>
|
||||||
</xs:annotation>
|
</xs:annotation>
|
||||||
</xs:attribute>
|
</xs:attribute>
|
||||||
</xs:attributeGroup>
|
</xs:attributeGroup>
|
||||||
|
@ -8,6 +8,7 @@ import java.util.List;
|
|||||||
|
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.springframework.beans.BeansException;
|
||||||
import org.springframework.beans.factory.BeanCreationException;
|
import org.springframework.beans.factory.BeanCreationException;
|
||||||
import org.springframework.beans.factory.BeanDefinitionStoreException;
|
import org.springframework.beans.factory.BeanDefinitionStoreException;
|
||||||
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
|
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
|
||||||
@ -39,6 +40,7 @@ import org.springframework.security.ui.rememberme.NullRememberMeServices;
|
|||||||
import org.springframework.security.ui.rememberme.PersistentTokenBasedRememberMeServices;
|
import org.springframework.security.ui.rememberme.PersistentTokenBasedRememberMeServices;
|
||||||
import org.springframework.security.ui.rememberme.RememberMeProcessingFilter;
|
import org.springframework.security.ui.rememberme.RememberMeProcessingFilter;
|
||||||
import org.springframework.security.ui.rememberme.RememberMeServices;
|
import org.springframework.security.ui.rememberme.RememberMeServices;
|
||||||
|
import org.springframework.security.ui.rememberme.TokenBasedRememberMeServices;
|
||||||
import org.springframework.security.ui.webapp.AuthenticationProcessingFilter;
|
import org.springframework.security.ui.webapp.AuthenticationProcessingFilter;
|
||||||
import org.springframework.security.ui.webapp.DefaultLoginPageGeneratingFilter;
|
import org.springframework.security.ui.webapp.DefaultLoginPageGeneratingFilter;
|
||||||
import org.springframework.security.util.FieldUtils;
|
import org.springframework.security.util.FieldUtils;
|
||||||
@ -57,7 +59,7 @@ public class HttpSecurityBeanDefinitionParserTests {
|
|||||||
private AbstractXmlApplicationContext appContext;
|
private AbstractXmlApplicationContext appContext;
|
||||||
static final String AUTH_PROVIDER_XML =
|
static final String AUTH_PROVIDER_XML =
|
||||||
" <authentication-provider>" +
|
" <authentication-provider>" +
|
||||||
" <user-service>" +
|
" <user-service id='us'>" +
|
||||||
" <user name='bob' password='bobspassword' authorities='ROLE_A,ROLE_B' />" +
|
" <user name='bob' password='bobspassword' authorities='ROLE_A,ROLE_B' />" +
|
||||||
" <user name='bill' password='billspassword' authorities='ROLE_A,ROLE_B,AUTH_OTHER' />" +
|
" <user name='bill' password='billspassword' authorities='ROLE_A,ROLE_B,AUTH_OTHER' />" +
|
||||||
" </user-service>" +
|
" </user-service>" +
|
||||||
@ -340,23 +342,50 @@ public class HttpSecurityBeanDefinitionParserTests {
|
|||||||
public void rememberMeServiceWorksWithTokenRepoRef() {
|
public void rememberMeServiceWorksWithTokenRepoRef() {
|
||||||
setContext(
|
setContext(
|
||||||
"<http auto-config='true'>" +
|
"<http auto-config='true'>" +
|
||||||
" <remember-me key='doesntmatter' token-repository-ref='tokenRepo'/>" +
|
" <remember-me token-repository-ref='tokenRepo'/>" +
|
||||||
"</http>" +
|
"</http>" +
|
||||||
"<b:bean id='tokenRepo' " +
|
"<b:bean id='tokenRepo' " +
|
||||||
"class='org.springframework.security.ui.rememberme.InMemoryTokenRepositoryImpl'/> " + AUTH_PROVIDER_XML);
|
"class='org.springframework.security.ui.rememberme.InMemoryTokenRepositoryImpl'/> " + AUTH_PROVIDER_XML);
|
||||||
Object rememberMeServices = appContext.getBean(BeanIds.REMEMBER_ME_SERVICES);
|
Object rememberMeServices = appContext.getBean(BeanIds.REMEMBER_ME_SERVICES);
|
||||||
|
|
||||||
assertTrue(rememberMeServices instanceof PersistentTokenBasedRememberMeServices);
|
assertTrue(rememberMeServices instanceof PersistentTokenBasedRememberMeServices);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void rememberMeServiceWorksWithExternalServicesImpl() throws Exception {
|
||||||
|
setContext(
|
||||||
|
"<http auto-config='true'>" +
|
||||||
|
" <remember-me key='ourkey' services-ref='rms'/>" +
|
||||||
|
"</http>" +
|
||||||
|
"<b:bean id='rms' class='org.springframework.security.ui.rememberme.TokenBasedRememberMeServices'> " +
|
||||||
|
" <b:property name='userDetailsService' ref='us'/>" +
|
||||||
|
" <b:property name='key' value='ourkey'/>" +
|
||||||
|
" <b:property name='tokenValiditySeconds' value='5000'/>" +
|
||||||
|
"</b:bean>" +
|
||||||
|
AUTH_PROVIDER_XML);
|
||||||
|
|
||||||
|
assertEquals(5000, FieldUtils.getFieldValue(appContext.getBean(BeanIds.REMEMBER_ME_SERVICES),
|
||||||
|
"tokenValiditySeconds"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void rememberMeTokenValidityIsParsedCorrectly() throws Exception {
|
||||||
|
setContext(
|
||||||
|
"<http auto-config='true'>" +
|
||||||
|
" <remember-me key='ourkey' token-validity-seconds='10000' />" +
|
||||||
|
"</http>" + AUTH_PROVIDER_XML);
|
||||||
|
assertEquals(10000, FieldUtils.getFieldValue(appContext.getBean(BeanIds.REMEMBER_ME_SERVICES),
|
||||||
|
"tokenValiditySeconds"));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void rememberMeServiceConfigurationParsesWithCustomUserService() {
|
public void rememberMeServiceConfigurationParsesWithCustomUserService() {
|
||||||
setContext(
|
setContext(
|
||||||
"<http auto-config='true'>" +
|
"<http auto-config='true'>" +
|
||||||
" <remember-me key='doesntmatter' user-service-ref='userService'/>" +
|
" <remember-me key='somekey' user-service-ref='userService'/>" +
|
||||||
"</http>" +
|
"</http>" +
|
||||||
"<b:bean id='userService' " +
|
"<b:bean id='userService' class='org.springframework.security.userdetails.MockUserDetailsService'/> " +
|
||||||
"class='org.springframework.security.userdetails.MockUserDetailsService'/> " + AUTH_PROVIDER_XML);
|
AUTH_PROVIDER_XML);
|
||||||
// AbstractRememberMeServices rememberMeServices = (AbstractRememberMeServices) appContext.getBean(BeanIds.REMEMBER_ME_SERVICES);
|
// AbstractRememberMeServices rememberMeServices = (AbstractRememberMeServices) appContext.getBean(BeanIds.REMEMBER_ME_SERVICES);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -509,8 +538,7 @@ public class HttpSecurityBeanDefinitionParserTests {
|
|||||||
" <form-login login-page='/login.jsp' default-target-url='/messageList.html'/>" +
|
" <form-login login-page='/login.jsp' default-target-url='/messageList.html'/>" +
|
||||||
" </http>" + AUTH_PROVIDER_XML);
|
" </http>" + AUTH_PROVIDER_XML);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void setContext(String context) {
|
private void setContext(String context) {
|
||||||
appContext = new InMemoryXmlApplicationContext(context);
|
appContext = new InMemoryXmlApplicationContext(context);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user