From 9b4660ed2047d8d4a2fac96151a7039d7a0e3f9f Mon Sep 17 00:00:00 2001 From: Andrey Pleskach Date: Fri, 17 Jan 2025 20:51:51 +0100 Subject: [PATCH] Bump OpenSAML to 5.1.3 Signed-off-by: Andrey Pleskach --- build.gradle | 22 ++- .../auth/http/saml/HTTPSamlAuthenticator.java | 9 +- .../auth/http/saml/Saml2SettingsProvider.java | 4 +- .../saml/SamlFilesystemMetadataResolver.java | 9 +- .../http/saml/SamlHTTPMetadataResolver.java | 24 +-- .../util/SettingsBasedSSLConfiguratorV4.java | 9 + .../integration/SecurityX509CRLImpl.java | 2 +- .../SecurityX509CertificateImpl.java | 2 +- .../SecurityXMLObjectProviderInitializer.java | 2 +- .../auth/http/saml/MockSamlIdpServer.java | 184 ++++++++++++++---- 10 files changed, 189 insertions(+), 78 deletions(-) diff --git a/build.gradle b/build.gradle index b18101a8b1..44fba400eb 100644 --- a/build.gradle +++ b/build.gradle @@ -27,7 +27,8 @@ buildscript { common_utils_version = System.getProperty("common_utils.version", '3.0.0.0-SNAPSHOT') kafka_version = '3.7.1' - open_saml_version = '4.3.2' + open_saml_version = '5.1.3' + open_saml_shib_version = "9.1.3" one_login_java_saml = '2.9.0' jjwt_version = '0.12.6' guava_version = '33.4.0-jre' @@ -618,16 +619,21 @@ dependencies { testImplementation 'org.apache.camel:camel-xmlsecurity:3.22.3' - //OpenSAML - implementation 'net.shibboleth.utilities:java-support:8.4.2' - runtimeOnly "io.dropwizard.metrics:metrics-core:4.2.30" + //Onelogin OpenSaml implementation "com.onelogin:java-saml:${one_login_java_saml}" implementation "com.onelogin:java-saml-core:${one_login_java_saml}" - implementation "org.opensaml:opensaml-core:${open_saml_version}" - implementation "org.opensaml:opensaml-security-impl:${open_saml_version}" + //OpenSAML + runtimeOnly "io.dropwizard.metrics:metrics-core:4.2.30" + implementation "net.shibboleth:shib-support:${open_saml_shib_version}" + implementation "net.shibboleth:shib-security:${open_saml_shib_version}" + implementation "net.shibboleth:shib-networking:${open_saml_shib_version}" + implementation "org.opensaml:opensaml-core-api:${open_saml_version}" + implementation "org.opensaml:opensaml-core-impl:${open_saml_version}" implementation "org.opensaml:opensaml-security-api:${open_saml_version}" + implementation "org.opensaml:opensaml-security-impl:${open_saml_version}" implementation "org.opensaml:opensaml-xmlsec-api:${open_saml_version}" implementation "org.opensaml:opensaml-xmlsec-impl:${open_saml_version}" + implementation "org.opensaml:opensaml-saml-api:${open_saml_version}" implementation ("org.opensaml:opensaml-saml-impl:${open_saml_version}") { exclude(group: 'org.apache.velocity', module: 'velocity') @@ -638,6 +644,7 @@ dependencies { runtimeOnly "org.opensaml:opensaml-soap-impl:${open_saml_version}" implementation "org.opensaml:opensaml-storage-api:${open_saml_version}" + implementation "com.nulab-inc:zxcvbn:1.9.0" runtimeOnly 'com.google.guava:failureaccess:1.0.2' @@ -661,6 +668,7 @@ dependencies { testImplementation "org.opensaml:opensaml-messaging-impl:${open_saml_version}" + testImplementation "jakarta.servlet:jakarta.servlet-api:6.1.0" implementation "org.apache.commons:commons-lang3:${versions.commonslang}" testImplementation "org.opensearch:common-utils:${common_utils_version}" testImplementation "org.opensearch.plugin:reindex-client:${opensearch_version}" @@ -671,11 +679,9 @@ dependencies { testImplementation "org.opensearch.plugin:aggs-matrix-stats-client:${opensearch_version}" testImplementation "org.opensearch.plugin:search-pipeline-common:${opensearch_version}" testImplementation "org.apache.logging.log4j:log4j-core:${versions.log4j}" - testImplementation 'javax.servlet:servlet-api:2.5' testImplementation 'com.unboundid:unboundid-ldapsdk:4.0.14' testImplementation 'com.github.stephenc.jcip:jcip-annotations:1.0-1' testImplementation 'com.unboundid:unboundid-ldapsdk:4.0.14' - testImplementation 'javax.servlet:servlet-api:2.5' testImplementation 'org.apache.httpcomponents:fluent-hc:4.5.14' testImplementation "org.apache.httpcomponents.client5:httpclient5-fluent:${versions.httpclient5}" testImplementation "org.apache.kafka:kafka_2.13:${kafka_version}" diff --git a/src/main/java/com/amazon/dlic/auth/http/saml/HTTPSamlAuthenticator.java b/src/main/java/com/amazon/dlic/auth/http/saml/HTTPSamlAuthenticator.java index 20e0b25b5c..c0b9b5b1a9 100644 --- a/src/main/java/com/amazon/dlic/auth/http/saml/HTTPSamlAuthenticator.java +++ b/src/main/java/com/amazon/dlic/auth/http/saml/HTTPSamlAuthenticator.java @@ -55,12 +55,13 @@ import com.nimbusds.jose.jwk.JWK; import com.onelogin.saml2.authn.AuthnRequest; import com.onelogin.saml2.logout.LogoutRequest; +import com.onelogin.saml2.logout.LogoutRequestParams; import com.onelogin.saml2.settings.Saml2Settings; import com.onelogin.saml2.util.Constants; import com.onelogin.saml2.util.Util; -import net.shibboleth.utilities.java.support.component.ComponentInitializationException; -import net.shibboleth.utilities.java.support.component.DestructableComponent; -import net.shibboleth.utilities.java.support.xml.BasicParserPool; +import net.shibboleth.shared.component.ComponentInitializationException; +import net.shibboleth.shared.component.DestructableComponent; +import net.shibboleth.shared.xml.impl.BasicParserPool; import org.opensaml.core.config.InitializationException; import org.opensaml.core.config.InitializationService; import org.opensaml.core.config.Initializer; @@ -409,7 +410,7 @@ String buildLogoutUrl(AuthCredentials authCredentials) { String nameIdFormat = SamlNameIdFormat.getByShortName(authCredentials.getAttributes().get("attr.jwt.saml_nif")).getUri(); String sessionIndex = authCredentials.getAttributes().get("attr.jwt.saml_si"); - LogoutRequest logoutRequest = new LogoutRequest(saml2Settings, null, nameId, sessionIndex, nameIdFormat); + LogoutRequest logoutRequest = new LogoutRequest(saml2Settings, new LogoutRequestParams(sessionIndex, nameId, nameIdFormat)); return getSamlRequestRedirectBindingLocation(IdpEndpointType.SLO, saml2Settings, logoutRequest.getEncodedLogoutRequest(true)); diff --git a/src/main/java/com/amazon/dlic/auth/http/saml/Saml2SettingsProvider.java b/src/main/java/com/amazon/dlic/auth/http/saml/Saml2SettingsProvider.java index 0c7f56282e..39496205d4 100644 --- a/src/main/java/com/amazon/dlic/auth/http/saml/Saml2SettingsProvider.java +++ b/src/main/java/com/amazon/dlic/auth/http/saml/Saml2SettingsProvider.java @@ -32,8 +32,8 @@ import com.amazon.dlic.auth.http.jwt.keybyoidc.AuthenticatorUnavailableException; import com.onelogin.saml2.settings.Saml2Settings; import com.onelogin.saml2.settings.SettingsBuilder; -import net.shibboleth.utilities.java.support.resolver.CriteriaSet; -import net.shibboleth.utilities.java.support.resolver.ResolverException; +import net.shibboleth.shared.resolver.CriteriaSet; +import net.shibboleth.shared.resolver.ResolverException; import org.opensaml.core.criterion.EntityIdCriterion; import org.opensaml.saml.metadata.resolver.MetadataResolver; import org.opensaml.saml.metadata.resolver.RefreshableMetadataResolver; diff --git a/src/main/java/com/amazon/dlic/auth/http/saml/SamlFilesystemMetadataResolver.java b/src/main/java/com/amazon/dlic/auth/http/saml/SamlFilesystemMetadataResolver.java index 93d3b020ce..7449912034 100644 --- a/src/main/java/com/amazon/dlic/auth/http/saml/SamlFilesystemMetadataResolver.java +++ b/src/main/java/com/amazon/dlic/auth/http/saml/SamlFilesystemMetadataResolver.java @@ -20,7 +20,7 @@ import org.opensearch.common.settings.Settings; import org.opensearch.env.Environment; -import net.shibboleth.utilities.java.support.resolver.ResolverException; +import net.shibboleth.shared.resolver.ResolverException; import org.opensaml.saml.metadata.resolver.impl.FilesystemMetadataResolver; public class SamlFilesystemMetadataResolver extends FilesystemMetadataResolver { @@ -33,12 +33,7 @@ public class SamlFilesystemMetadataResolver extends FilesystemMetadataResolver { @SuppressWarnings("removal") protected byte[] fetchMetadata() throws ResolverException { try { - return AccessController.doPrivileged(new PrivilegedExceptionAction() { - @Override - public byte[] run() throws ResolverException { - return SamlFilesystemMetadataResolver.super.fetchMetadata(); - } - }); + return AccessController.doPrivileged((PrivilegedExceptionAction) SamlFilesystemMetadataResolver.super::fetchMetadata); } catch (PrivilegedActionException e) { if (e.getCause() instanceof ResolverException) { diff --git a/src/main/java/com/amazon/dlic/auth/http/saml/SamlHTTPMetadataResolver.java b/src/main/java/com/amazon/dlic/auth/http/saml/SamlHTTPMetadataResolver.java index d3e5571ece..df4d3af2bb 100644 --- a/src/main/java/com/amazon/dlic/auth/http/saml/SamlHTTPMetadataResolver.java +++ b/src/main/java/com/amazon/dlic/auth/http/saml/SamlHTTPMetadataResolver.java @@ -17,15 +17,16 @@ import java.security.PrivilegedExceptionAction; import java.time.Duration; -import org.apache.http.client.HttpClient; -import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.impl.client.HttpClients; +import org.apache.hc.client5.http.classic.HttpClient; +import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder; import org.opensearch.SpecialPermission; import org.opensearch.common.settings.Settings; import com.amazon.dlic.util.SettingsBasedSSLConfiguratorV4; -import net.shibboleth.utilities.java.support.resolver.ResolverException; +import net.shibboleth.shared.resolver.ResolverException; import org.opensaml.saml.metadata.resolver.impl.HTTPMetadataResolver; public class SamlHTTPMetadataResolver extends HTTPMetadataResolver { @@ -41,7 +42,7 @@ public class SamlHTTPMetadataResolver extends HTTPMetadataResolver { @SuppressWarnings("removal") protected byte[] fetchMetadata() throws ResolverException { try { - return AccessController.doPrivileged((PrivilegedExceptionAction) () -> SamlHTTPMetadataResolver.super.fetchMetadata()); + return AccessController.doPrivileged((PrivilegedExceptionAction) SamlHTTPMetadataResolver.super::fetchMetadata); } catch (PrivilegedActionException e) { if (e.getCause() instanceof ResolverException) { @@ -65,12 +66,7 @@ private static HttpClient createHttpClient(Settings settings, Path configPath) t sm.checkPermission(new SpecialPermission()); } - return AccessController.doPrivileged(new PrivilegedExceptionAction() { - @Override - public HttpClient run() throws Exception { - return createHttpClient0(settings, configPath); - } - }); + return AccessController.doPrivileged((PrivilegedExceptionAction) () -> createHttpClient0(settings, configPath)); } catch (PrivilegedActionException e) { if (e.getCause() instanceof Exception) { throw (Exception) e.getCause(); @@ -81,15 +77,15 @@ public HttpClient run() throws Exception { } private static HttpClient createHttpClient0(Settings settings, Path configPath) throws Exception { - HttpClientBuilder builder = HttpClients.custom(); - builder.useSystemProperties(); SettingsBasedSSLConfiguratorV4.SSLConfig sslConfig = getSSLConfig(settings, configPath); if (sslConfig != null) { - builder.setSSLSocketFactory(sslConfig.toSSLConnectionSocketFactory()); + builder.setConnectionManager( + PoolingHttpClientConnectionManagerBuilder.create().setSSLSocketFactory(sslConfig.toSSLConnectionSocketFactory5()).build() + ); } return builder.build(); diff --git a/src/main/java/com/amazon/dlic/util/SettingsBasedSSLConfiguratorV4.java b/src/main/java/com/amazon/dlic/util/SettingsBasedSSLConfiguratorV4.java index 9c273a14a4..e0aec008ec 100644 --- a/src/main/java/com/amazon/dlic/util/SettingsBasedSSLConfiguratorV4.java +++ b/src/main/java/com/amazon/dlic/util/SettingsBasedSSLConfiguratorV4.java @@ -478,6 +478,15 @@ public SSLConnectionSocketFactory toSSLConnectionSocketFactory() { return new SSLConnectionSocketFactory(sslContext, supportedProtocols, supportedCipherSuites, hostnameVerifier); } + public org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory toSSLConnectionSocketFactory5() { + return new org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory( + sslContext, + supportedProtocols, + supportedCipherSuites, + hostnameVerifier + ); + } + public boolean isStartTlsEnabled() { return startTlsEnabled; } diff --git a/src/main/java/org/opensearch/security/opensaml/integration/SecurityX509CRLImpl.java b/src/main/java/org/opensearch/security/opensaml/integration/SecurityX509CRLImpl.java index 036b777e27..2195cb93c5 100644 --- a/src/main/java/org/opensearch/security/opensaml/integration/SecurityX509CRLImpl.java +++ b/src/main/java/org/opensearch/security/opensaml/integration/SecurityX509CRLImpl.java @@ -17,7 +17,7 @@ import java.util.Objects; import javax.annotation.Nonnull; -import net.shibboleth.utilities.java.support.collection.IndexingObjectStore; +import net.shibboleth.shared.collection.IndexingObjectStore; import org.opensaml.core.xml.AbstractXMLObject; import org.opensaml.core.xml.XMLObject; import org.opensaml.xmlsec.signature.X509CRL; diff --git a/src/main/java/org/opensearch/security/opensaml/integration/SecurityX509CertificateImpl.java b/src/main/java/org/opensearch/security/opensaml/integration/SecurityX509CertificateImpl.java index 59fbc021d8..72c76fbdd2 100644 --- a/src/main/java/org/opensearch/security/opensaml/integration/SecurityX509CertificateImpl.java +++ b/src/main/java/org/opensearch/security/opensaml/integration/SecurityX509CertificateImpl.java @@ -17,7 +17,7 @@ import java.util.Objects; import javax.annotation.Nonnull; -import net.shibboleth.utilities.java.support.collection.IndexingObjectStore; +import net.shibboleth.shared.collection.IndexingObjectStore; import org.opensaml.core.xml.AbstractXMLObject; import org.opensaml.core.xml.XMLObject; import org.opensaml.xmlsec.signature.X509Certificate; diff --git a/src/main/java/org/opensearch/security/opensaml/integration/SecurityXMLObjectProviderInitializer.java b/src/main/java/org/opensearch/security/opensaml/integration/SecurityXMLObjectProviderInitializer.java index 6cf6d0e6aa..7957c84833 100644 --- a/src/main/java/org/opensearch/security/opensaml/integration/SecurityXMLObjectProviderInitializer.java +++ b/src/main/java/org/opensearch/security/opensaml/integration/SecurityXMLObjectProviderInitializer.java @@ -17,7 +17,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import net.shibboleth.utilities.java.support.primitive.StringSupport; +import net.shibboleth.shared.primitive.StringSupport; import org.opensaml.core.config.InitializationException; import org.opensaml.core.xml.config.XMLConfigurationException; import org.opensaml.core.xml.config.XMLConfigurator; diff --git a/src/test/java/com/amazon/dlic/auth/http/saml/MockSamlIdpServer.java b/src/test/java/com/amazon/dlic/auth/http/saml/MockSamlIdpServer.java index e9aee68158..fbadc7f992 100644 --- a/src/test/java/com/amazon/dlic/auth/http/saml/MockSamlIdpServer.java +++ b/src/test/java/com/amazon/dlic/auth/http/saml/MockSamlIdpServer.java @@ -36,8 +36,10 @@ import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.Enumeration; +import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; @@ -46,11 +48,6 @@ import javax.net.ssl.SSLContext; import javax.net.ssl.SSLParameters; import javax.net.ssl.TrustManagerFactory; -import javax.servlet.RequestDispatcher; -import javax.servlet.ServletInputStream; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpSession; import javax.xml.namespace.QName; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; @@ -69,7 +66,6 @@ import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; -import org.apache.hc.core5.http.NameValuePair; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.impl.bootstrap.HttpServer; import org.apache.hc.core5.http.impl.bootstrap.ServerBootstrap; @@ -86,9 +82,25 @@ import org.opensearch.security.test.helper.file.FileHelper; import org.opensearch.security.test.helper.network.SocketUtils; -import net.shibboleth.utilities.java.support.codec.Base64Support; -import net.shibboleth.utilities.java.support.codec.EncodingException; -import net.shibboleth.utilities.java.support.component.ComponentInitializationException; +import jakarta.servlet.AsyncContext; +import jakarta.servlet.DispatcherType; +import jakarta.servlet.ReadListener; +import jakarta.servlet.RequestDispatcher; +import jakarta.servlet.ServletConnection; +import jakarta.servlet.ServletContext; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletInputStream; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; +import jakarta.servlet.http.HttpUpgradeHandler; +import jakarta.servlet.http.Part; +import net.shibboleth.shared.codec.Base64Support; +import net.shibboleth.shared.codec.EncodingException; +import net.shibboleth.shared.component.ComponentInitializationException; import org.opensaml.core.xml.XMLObject; import org.opensaml.core.xml.XMLObjectBuilderFactory; import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; @@ -743,16 +755,25 @@ public SSLTestHttpServerConnection( static class FakeHttpServletRequest implements HttpServletRequest { private final HttpRequest delegate; - private final Map queryParams; + private final Map queryParams; private final URIBuilder uriBuilder; FakeHttpServletRequest(HttpRequest delegate) throws URISyntaxException { this.delegate = delegate; String uri = delegate.getRequestUri(); this.uriBuilder = new URIBuilder(uri); - this.queryParams = uriBuilder.getQueryParams() - .stream() - .collect(Collectors.toMap(NameValuePair::getName, NameValuePair::getValue)); + this.queryParams = new HashMap<>(); + uriBuilder.getQueryParams().forEach(nameValuePair -> { + final String[] params; + if (!queryParams.containsKey(nameValuePair.getName())) { + params = new String[] { nameValuePair.getValue() }; + } else { + final String[] current = queryParams.get(nameValuePair.getName()); + params = Arrays.copyOf(current, current.length + 1); + params[current.length] = nameValuePair.getValue(); + } + queryParams.put(nameValuePair.getName(), params); + }); } @Override @@ -760,9 +781,8 @@ public Object getAttribute(String arg0) { return null; } - @SuppressWarnings("rawtypes") @Override - public Enumeration getAttributeNames() { + public Enumeration getAttributeNames() { return Collections.emptyEnumeration(); } @@ -784,6 +804,11 @@ public int getContentLength() { } } + @Override + public long getContentLengthLong() { + return 0; + } + @Override public String getContentType() { if (delegate instanceof ClassicHttpRequest) { @@ -800,6 +825,21 @@ public ServletInputStream getInputStream() throws IOException { return new ServletInputStream() { + @Override + public boolean isFinished() { + return false; + } + + @Override + public boolean isReady() { + return false; + } + + @Override + public void setReadListener(ReadListener readListener) { + + } + public int read() throws IOException { return in.read(); } @@ -832,43 +872,84 @@ public int getLocalPort() { return 0; } + @Override + public ServletContext getServletContext() { + return null; + } + + @Override + public AsyncContext startAsync() throws IllegalStateException { + return null; + } + + @Override + public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException { + return null; + } + + @Override + public boolean isAsyncStarted() { + return false; + } + + @Override + public boolean isAsyncSupported() { + return false; + } + + @Override + public AsyncContext getAsyncContext() { + return null; + } + + @Override + public DispatcherType getDispatcherType() { + return null; + } + + @Override + public String getRequestId() { + return ""; + } + + @Override + public String getProtocolRequestId() { + return ""; + } + + @Override + public ServletConnection getServletConnection() { + return null; + } + @Override public Locale getLocale() { return null; } - @SuppressWarnings("rawtypes") @Override - public Enumeration getLocales() { + public Enumeration getLocales() { return null; } @Override public String getParameter(String name) { - return this.queryParams.get(name); + return this.queryParams.containsKey(name) ? this.queryParams.get(name)[0] : null; } - @SuppressWarnings("rawtypes") @Override - public Map getParameterMap() { - return Collections.unmodifiableMap(this.queryParams); + public Map getParameterMap() { + return Map.copyOf(this.queryParams); } - @SuppressWarnings("rawtypes") @Override - public Enumeration getParameterNames() { + public Enumeration getParameterNames() { return Collections.enumeration(this.queryParams.keySet()); } @Override public String[] getParameterValues(String name) { - String value = this.queryParams.get(name); - - if (value != null) { - return new String[] { value }; - } else { - return null; - } + return this.queryParams.get(name); } @Override @@ -887,11 +968,6 @@ public BufferedReader getReader() throws IOException { } } - @Override - public String getRealPath(String arg0) { - return null; - } - @Override public String getRemoteAddr() { return null; @@ -978,15 +1054,13 @@ public String getHeader(String name) { } } - @SuppressWarnings("rawtypes") @Override - public Enumeration getHeaderNames() { + public Enumeration getHeaderNames() { return Collections.enumeration(Arrays.asList(delegate.getHeaders()).stream().map(Header::getName).collect(Collectors.toSet())); } - @SuppressWarnings("rawtypes") @Override - public Enumeration getHeaders(String name) { + public Enumeration getHeaders(String name) { Header[] headers = delegate.getHeaders(name); if (headers != null) { @@ -1057,6 +1131,11 @@ public HttpSession getSession() { return null; } + @Override + public String changeSessionId() { + return ""; + } + @Override public HttpSession getSession(boolean arg0) { return null; @@ -1078,10 +1157,35 @@ public boolean isRequestedSessionIdFromURL() { } @Override - public boolean isRequestedSessionIdFromUrl() { + public boolean authenticate(HttpServletResponse httpServletResponse) throws IOException, ServletException { return false; } + @Override + public void login(String s, String s1) throws ServletException { + + } + + @Override + public void logout() throws ServletException { + + } + + @Override + public Collection getParts() throws IOException, ServletException { + return List.of(); + } + + @Override + public Part getPart(String s) throws IOException, ServletException { + return null; + } + + @Override + public T upgrade(Class aClass) throws IOException, ServletException { + return null; + } + @Override public boolean isRequestedSessionIdValid() { return false;