diff --git a/http/oidc/src/test/java/org/wildfly/security/http/oidc/MockOidcClientConfiguration.java b/http/oidc/src/test/java/org/wildfly/security/http/oidc/MockOidcClientConfiguration.java
new file mode 100644
index 00000000000..b59d75a192f
--- /dev/null
+++ b/http/oidc/src/test/java/org/wildfly/security/http/oidc/MockOidcClientConfiguration.java
@@ -0,0 +1,167 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2024 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.wildfly.security.http.oidc;
+
+import com.gargoylesoftware.htmlunit.TextPage;
+import io.restassured.RestAssured;
+import mockit.Mock;
+import mockit.MockUp;
+import mockit.integration.junit4.JMockit;
+import okhttp3.mockwebserver.MockWebServer;
+import org.apache.http.HttpStatus;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.wildfly.security.http.HttpServerAuthenticationMechanism;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.jose4j.jws.AlgorithmIdentifiers.HMAC_SHA256;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+import static org.wildfly.security.http.oidc.KeycloakConfiguration.ALICE;
+import static org.wildfly.security.http.oidc.KeycloakConfiguration.ALICE_PASSWORD;
+import static org.wildfly.security.http.oidc.Oidc.AuthenticationRequestFormat.REQUEST;
+import static org.wildfly.security.http.oidc.Oidc.AuthenticationRequestFormat.REQUEST_URI;
+import static org.wildfly.security.http.oidc.Oidc.OIDC_NAME;
+import static org.wildfly.security.http.oidc.Oidc.OIDC_SCOPE;
+
+/**
+ * Tests for cases where the OpenID provider does not support
+ * request parameters when sending the request object as a JWT.
+ * The OidcClientConfiguration class is mocked to return values
+ * indicating a lack of support for request parameters.
+ *
+ * @author Prarthona Paul
+ */
+@RunWith(JMockit.class)
+public class MockOidcClientConfiguration extends OidcBaseTest {
+
+ @BeforeClass
+ public static void startTestContainers() throws Exception {
+ assumeTrue("Docker isn't available, OIDC tests will be skipped", isDockerAvailable());
+ KEYCLOAK_CONTAINER = new KeycloakContainer();
+ KEYCLOAK_CONTAINER.start();
+ sendRealmCreationRequest(KeycloakConfiguration.getRealmRepresentation(TEST_REALM, CLIENT_ID, CLIENT_SECRET, CLIENT_HOST_NAME, CLIENT_PORT, CLIENT_APP, false));
+ client = new MockWebServer();
+ client.start(CLIENT_PORT);
+ }
+
+ @AfterClass
+ public static void generalCleanup() throws Exception {
+ if (KEYCLOAK_CONTAINER != null) {
+ RestAssured
+ .given()
+ .auth().oauth2(KeycloakConfiguration.getAdminAccessToken(KEYCLOAK_CONTAINER.getAuthServerUrl()))
+ .when()
+ .delete(KEYCLOAK_CONTAINER.getAuthServerUrl() + "/admin/realms/" + TEST_REALM).then().statusCode(204);
+ KEYCLOAK_CONTAINER.stop();
+ }
+ if (client != null) {
+ client.shutdown();
+ }
+ }
+
+ @BeforeClass
+ public static void beforeClass() {
+ System.setProperty("oidc.provider.url", KEYCLOAK_CONTAINER.getAuthServerUrl() + "/realms/" + TEST_REALM);
+ }
+
+ @AfterClass
+ public static void afterClass() {
+ System.clearProperty("oidc.provider.url");
+ }
+
+ @Test
+ public void testOidcWithRequestParameterUnsupported() throws Exception {
+ mockOidcClientConfig();
+ performAuthentication(getOidcConfigurationInputStreamWithRequestParameter(REQUEST.getValue()), REQUEST.getValue());
+ }
+
+ @Test
+ public void testOidcWithRequestUriParameterUnsupported() throws Exception {
+ mockOidcClientConfig();
+ performAuthentication(getOidcConfigurationInputStreamWithRequestParameter(REQUEST_URI.getValue()), REQUEST_URI.getValue());
+ }
+
+ public void performAuthentication(InputStream oidcConfig, String requestFormat) throws Exception {
+ Map props = new HashMap<>();
+ OidcClientConfiguration oidcClientConfiguration = OidcClientConfigurationBuilder.build(oidcConfig);
+ assertEquals(OidcClientConfiguration.RelativeUrlsUsed.NEVER, oidcClientConfiguration.getRelativeUrls());
+ OidcClientContext oidcClientContext = new OidcClientContext(oidcClientConfiguration);
+ oidcFactory = new OidcMechanismFactory(oidcClientContext);
+ HttpServerAuthenticationMechanism mechanism = oidcFactory.createAuthenticationMechanism(OIDC_NAME, props, getCallbackHandler());
+
+ URI requestUri = new URI(getClientUrl());
+ TestingHttpServerRequest request = new TestingHttpServerRequest(null, requestUri);
+ mechanism.evaluateRequest(request);
+ TestingHttpServerResponse response = request.getResponse();
+ assertEquals(HttpStatus.SC_MOVED_TEMPORARILY, response.getStatusCode());
+ assertEquals(Status.NO_AUTH, request.getResult());
+ assertFalse(response.getFirstResponseHeaderValue("Location").contains(requestFormat + "="));
+ assertTrue(response.getFirstResponseHeaderValue("Location").contains("scope=" + OIDC_SCOPE + "+phone+profile+email")); //ALL scopes should be added to the URL directly
+
+ client.setDispatcher(createAppResponse(mechanism, HttpStatus.SC_MOVED_TEMPORARILY, getClientUrl(), CLIENT_PAGE_TEXT));
+
+ TextPage page = loginToKeycloak(ALICE, ALICE_PASSWORD, requestUri, response.getLocation(),
+ response.getCookies()).click();
+ assertTrue(page.getContent().contains(CLIENT_PAGE_TEXT));
+ }
+
+
+ private void mockOidcClientConfig(){
+ new MockUp(){
+ // Used to indicate that the OpenID provider does not support request_uri parameter
+ @Mock
+ boolean getRequestUriParameterSupported(){
+ return false;
+ }
+
+ // Used to indicate that the OpenID provider does not support request parameter
+ @Mock
+ boolean getRequestParameterSupported(){
+ return false;
+ }
+ };
+ }
+
+ private InputStream getOidcConfigurationInputStreamWithRequestParameter(String requestParameter){
+ String oidcConfig = "{\n" +
+ " \"client-id\" : \"" + CLIENT_ID + "\",\n" +
+ " \"provider-url\" : \"" + KEYCLOAK_CONTAINER.getAuthServerUrl() + "/realms/" + TEST_REALM + "/" + "\",\n" +
+ " \"public-client\" : \"false\",\n" +
+ " \"ssl-required\" : \"EXTERNAL\",\n" +
+ " \"authentication-request-format\" : \"" + requestParameter + "\",\n" +
+ " \"request-object-signing-algorithm\" : \"" + HMAC_SHA256 + "\",\n" +
+ " \"scope\" : \"profile email phone\",\n" +
+ " \"credentials\" : {\n" +
+ " \"secret\" : \"" + CLIENT_SECRET + "\"\n" +
+ " }\n" +
+ "}";
+ return new ByteArrayInputStream(oidcConfig.getBytes(StandardCharsets.UTF_8));
+ }
+}
diff --git a/tests/base/src/test/java/org/wildfly/security/auth/client/MaskedPasswordSSLAuthenticationTest.java b/tests/base/src/test/java/org/wildfly/security/auth/client/MaskedPasswordSSLAuthenticationTest.java
index 7855dc22a65..0ba8e2713a1 100644
--- a/tests/base/src/test/java/org/wildfly/security/auth/client/MaskedPasswordSSLAuthenticationTest.java
+++ b/tests/base/src/test/java/org/wildfly/security/auth/client/MaskedPasswordSSLAuthenticationTest.java
@@ -137,11 +137,11 @@ private SecurityIdentity performConnectionTest(SSLContext serverContext, SSLCont
SSLSocket sslSocket = (SSLSocket) clientContext.getSocketFactory().createSocket(InetAddress.getLoopbackAddress(), 1111);
sslSocket.getSession();
+ System.out.println("Client connected");
return sslSocket;
} catch (Exception e) {
+ System.out.println("Client Connection Failed");
throw new RuntimeException(e);
- } finally {
- System.out.println("Client connected");
}
});
diff --git a/tests/base/src/test/java/org/wildfly/security/ssl/SSLAuthenticationTest.java b/tests/base/src/test/java/org/wildfly/security/ssl/SSLAuthenticationTest.java
index 2ec81161b92..9d8cbe551ea 100644
--- a/tests/base/src/test/java/org/wildfly/security/ssl/SSLAuthenticationTest.java
+++ b/tests/base/src/test/java/org/wildfly/security/ssl/SSLAuthenticationTest.java
@@ -104,7 +104,7 @@ public class SSLAuthenticationTest {
private final int TESTING_PORT = 18201;
private static final char[] PASSWORD = "Elytron".toCharArray();
- private static final String JKS_LOCATION = "./target/test-classes/jks";
+ private static final String JKS_LOCATION = "./target/test-classes/pkcs12";
private static final String CA_CRL_LOCATION = "./target/test-classes/ca/crl";
private static final String ICA_CRL_LOCATION = "./target/test-classes/ica/crl";
private static final File WORKING_DIR_CACRL = new File(CA_CRL_LOCATION);
@@ -129,7 +129,7 @@ private static TrustManagerFactory getTrustManagerFactory() throws Exception {
}
private static KeyStore createKeyStore() throws Exception {
- KeyStore ks = KeyStore.getInstance("JKS");
+ KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(null, null);
return ks;
}
diff --git a/tests/base/src/test/java/org/wildfly/security/ssl/SSLv2HelloAuthenticationTest.java b/tests/base/src/test/java/org/wildfly/security/ssl/SSLv2HelloAuthenticationTest.java
index 6fb2a57b055..392b291de8d 100644
--- a/tests/base/src/test/java/org/wildfly/security/ssl/SSLv2HelloAuthenticationTest.java
+++ b/tests/base/src/test/java/org/wildfly/security/ssl/SSLv2HelloAuthenticationTest.java
@@ -90,7 +90,7 @@ public class SSLv2HelloAuthenticationTest {
private static final String CLIENT_CONFIG = "sslv2-hello-authentication-config.xml";
private static final char[] PASSWORD = "Elytron".toCharArray();
- private static final String CA_JKS_LOCATION = "./target/test-classes/ca/jks";
+ private static final String CA_JKS_LOCATION = "./target/test-classes/ca/pkcs12";
private static File ladybirdFile = null;
private static File scarabFile = null;
private static File beetlesFile = null;
@@ -120,7 +120,7 @@ public static void setUp() throws Exception{
createKeyStores(ladybirdFile, scarabFile, beetlesFile, trustFile);
- securityRealm = new KeyStoreBackedSecurityRealm(loadKeyStore("/ca/jks/beetles.keystore"));
+ securityRealm = new KeyStoreBackedSecurityRealm(loadKeyStore("/ca/pkcs12/beetles.keystore"));
securityDomain = SecurityDomain.builder()
.addRealm("KeystoreRealm", securityRealm)
@@ -163,7 +163,7 @@ public void testOneWaySSLv2HelloProtocolMatch() throws Exception {
SSLContext serverContext = new SSLContextBuilder()
.setSecurityDomain(securityDomain)
- .setKeyManager(getKeyManager("/ca/jks/scarab.keystore"))
+ .setKeyManager(getKeyManager("/ca/pkcs12/scarab.keystore"))
.setProtocolSelector(ProtocolSelector.empty().add(EnumSet.copyOf(list)))
.build().create();
@@ -188,7 +188,7 @@ public void testTwoWaySSLv2HelloProtocolMatch() throws Exception {
SSLContext serverContext = new SSLContextBuilder()
.setSecurityDomain(securityDomain)
- .setKeyManager(getKeyManager("/ca/jks/scarab.keystore"))
+ .setKeyManager(getKeyManager("/ca/pkcs12/scarab.keystore"))
.setTrustManager(getCATrustManager())
.setNeedClientAuth(true)
.setProtocolSelector(ProtocolSelector.empty().add(EnumSet.copyOf(list)))
@@ -215,7 +215,7 @@ public void testTwoWaySSLv2HelloProtocolMatch() throws Exception {
public void testTwoWaySSLv2HelloNotEnabled() throws Exception {
SSLContext serverContext = new SSLContextBuilder()
.setSecurityDomain(securityDomain)
- .setKeyManager(getKeyManager("/ca/jks/scarab.keystore"))
+ .setKeyManager(getKeyManager("/ca/pkcs12/scarab.keystore"))
.setTrustManager(getCATrustManager())
.setNeedClientAuth(true)
.build().create();
@@ -244,7 +244,7 @@ public void testTwoWaySSLv2HelloNoClientSupport() throws Exception {
SSLContext serverContext = new SSLContextBuilder()
.setSecurityDomain(securityDomain)
- .setKeyManager(getKeyManager("/ca/jks/scarab.keystore"))
+ .setKeyManager(getKeyManager("/ca/pkcs12/scarab.keystore"))
.setTrustManager(getCATrustManager())
.setNeedClientAuth(true)
.setProtocolSelector(ProtocolSelector.empty().add(EnumSet.copyOf(list)))
@@ -274,7 +274,7 @@ public void testTwoWaySSlv2HelloNoServerSupport() throws Exception {
SSLContext serverContext = new SSLContextBuilder()
.setSecurityDomain(securityDomain)
- .setKeyManager(getKeyManager("/ca/jks/scarab.keystore"))
+ .setKeyManager(getKeyManager("/ca/pkcs12/scarab.keystore"))
.setTrustManager(getCATrustManager())
.setNeedClientAuth(true)
.setProtocolSelector(ProtocolSelector.empty().add(EnumSet.copyOf(list)))
@@ -377,7 +377,7 @@ private static X509ExtendedKeyManager getKeyManager(final String keystorePath) t
*/
private static X509TrustManager getCATrustManager() throws Exception {
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("SunX509");
- trustManagerFactory.init(loadKeyStore("/ca/jks/ca.truststore"));
+ trustManagerFactory.init(loadKeyStore("/ca/pkcs12/ca.truststore"));
for (TrustManager current : trustManagerFactory.getTrustManagers()) {
if (current instanceof X509TrustManager) {
@@ -389,13 +389,13 @@ private static X509TrustManager getCATrustManager() throws Exception {
}
private static KeyStore loadKeyStore() throws Exception{
- KeyStore ks = KeyStore.getInstance("JKS");
+ KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(null,null);
return ks;
}
private static KeyStore loadKeyStore(final String path) throws Exception {
- KeyStore keyStore = KeyStore.getInstance("jks");
+ KeyStore keyStore = KeyStore.getInstance("PKCS12");
try (InputStream caTrustStoreFile = SSLAuthenticationTest.class.getResourceAsStream(path)) {
keyStore.load(caTrustStoreFile, PASSWORD);
}
diff --git a/tests/base/src/test/java/org/wildfly/security/ssl/TLS13AuthenticationTest.java b/tests/base/src/test/java/org/wildfly/security/ssl/TLS13AuthenticationTest.java
index 0952a781560..826916e29ca 100644
--- a/tests/base/src/test/java/org/wildfly/security/ssl/TLS13AuthenticationTest.java
+++ b/tests/base/src/test/java/org/wildfly/security/ssl/TLS13AuthenticationTest.java
@@ -65,7 +65,7 @@ public class TLS13AuthenticationTest {
private static final String CLIENT_CONFIG = "tls13-authentication-config.xml";
private static final char[] PASSWORD = "Elytron".toCharArray();
- private static final String CA_JKS_LOCATION = "./target/test-classes/jks";
+ private static final String CA_JKS_LOCATION = "./target/test-classes/pkcs12";
private static CAGenerationTool caGenerationTool = null;
private static SecurityDomain securityDomain = null;
diff --git a/tests/base/src/test/resources/org/wildfly/security/auth/client/wildfly-masked-password-ssl-config-v1_4.xml b/tests/base/src/test/resources/org/wildfly/security/auth/client/wildfly-masked-password-ssl-config-v1_4.xml
index d458d71f173..133acd78f2d 100644
--- a/tests/base/src/test/resources/org/wildfly/security/auth/client/wildfly-masked-password-ssl-config-v1_4.xml
+++ b/tests/base/src/test/resources/org/wildfly/security/auth/client/wildfly-masked-password-ssl-config-v1_4.xml
@@ -21,10 +21,11 @@
-
+
+
-
+
diff --git a/tests/base/src/test/resources/org/wildfly/security/ssl/ocsp-responder.xml b/tests/base/src/test/resources/org/wildfly/security/ssl/ocsp-responder.xml
index 50b99e567d7..8b0d7755811 100644
--- a/tests/base/src/test/resources/org/wildfly/security/ssl/ocsp-responder.xml
+++ b/tests/base/src/test/resources/org/wildfly/security/ssl/ocsp-responder.xml
@@ -19,7 +19,7 @@
JKS
- password=Elytron,keystore=file:target/test-classes/jks/ocsp-responder.keystore
+ password=Elytron,keystore=file:target/test-classes/pkcs12/ocsp-responder.keystore
SHA256withRSA
diff --git a/tests/base/src/test/resources/org/wildfly/security/ssl/ssl-authentication-config.xml b/tests/base/src/test/resources/org/wildfly/security/ssl/ssl-authentication-config.xml
index 5577e556fe2..2f5ab506492 100644
--- a/tests/base/src/test/resources/org/wildfly/security/ssl/ssl-authentication-config.xml
+++ b/tests/base/src/test/resources/org/wildfly/security/ssl/ssl-authentication-config.xml
@@ -22,41 +22,44 @@
-
-
+
+
+
-
-
+
+
+
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
diff --git a/tests/base/src/test/resources/org/wildfly/security/ssl/sslv2-hello-authentication-config.xml b/tests/base/src/test/resources/org/wildfly/security/ssl/sslv2-hello-authentication-config.xml
index 6269aefcace..c3ca9fc93d3 100644
--- a/tests/base/src/test/resources/org/wildfly/security/ssl/sslv2-hello-authentication-config.xml
+++ b/tests/base/src/test/resources/org/wildfly/security/ssl/sslv2-hello-authentication-config.xml
@@ -22,11 +22,12 @@
-
-
+
+
+
-
-
+
+
diff --git a/tests/base/src/test/resources/org/wildfly/security/ssl/tls13-authentication-config.xml b/tests/base/src/test/resources/org/wildfly/security/ssl/tls13-authentication-config.xml
index 84c57691b1c..e9146013afc 100644
--- a/tests/base/src/test/resources/org/wildfly/security/ssl/tls13-authentication-config.xml
+++ b/tests/base/src/test/resources/org/wildfly/security/ssl/tls13-authentication-config.xml
@@ -22,11 +22,12 @@
-
-
+
+
+
-
-
+
+
diff --git a/tests/common/src/test/java/org/wildfly/security/ssl/test/util/CAGenerationTool.java b/tests/common/src/test/java/org/wildfly/security/ssl/test/util/CAGenerationTool.java
index 4481352b8f8..e28f884d999 100644
--- a/tests/common/src/test/java/org/wildfly/security/ssl/test/util/CAGenerationTool.java
+++ b/tests/common/src/test/java/org/wildfly/security/ssl/test/util/CAGenerationTool.java
@@ -66,7 +66,7 @@ public class CAGenerationTool implements Closeable {
private static final String BEETLES_STORE = "beetles.keystore";
private static final String KEY_ALGORITHM = "RSA";
- private static final String KEYSTORE_TYPE = "JKS"; // TODO Switch to PKCS#12
+ private static final String KEYSTORE_TYPE = "PKCS12";
private static final int OCSP_PORT = 4854;
static final char[] PASSWORD = "Elytron".toCharArray();
@@ -144,6 +144,10 @@ public KeyStore getBeetlesKeyStore() {
return loadKeyStore(new File(workingDir, BEETLES_STORE));
}
+ public String getKeyStoreType() {
+ return KEYSTORE_TYPE;
+ }
+
/**
* @deprecated Use {@link CommonIdentity#getCertificate()} instead.
*/
@@ -364,8 +368,12 @@ private static KeyStore createEmptyKeyStore() {
}
}
+ File getKeyStoreFile(Identity identity) {
+ return new File(workingDir, identity.getKeyStoreName());
+ }
+
KeyStore loadKeyStore(final Identity identity) {
- return loadKeyStore(new File(workingDir, identity.getKeyStoreName()));
+ return loadKeyStore(getKeyStoreFile(identity));
}
static KeyStore loadKeyStore(final File location) {
diff --git a/tests/common/src/test/java/org/wildfly/security/ssl/test/util/CommonIdentity.java b/tests/common/src/test/java/org/wildfly/security/ssl/test/util/CommonIdentity.java
index 2d8f3664015..ffd89fbdf0a 100644
--- a/tests/common/src/test/java/org/wildfly/security/ssl/test/util/CommonIdentity.java
+++ b/tests/common/src/test/java/org/wildfly/security/ssl/test/util/CommonIdentity.java
@@ -16,6 +16,7 @@
package org.wildfly.security.ssl.test.util;
+import java.io.File;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
@@ -42,8 +43,14 @@ public X509Certificate getCertificate() {
return certificate;
}
+ public String getKeyStoreType() {
+ return caGenerationTool.getKeyStoreType();
+ }
+
public abstract KeyStore loadKeyStore();
+ public abstract File getKeyStoreFile();
+
public X509ExtendedKeyManager createKeyManager() {
caGenerationTool.assertNotClosed();
diff --git a/tests/common/src/test/java/org/wildfly/security/ssl/test/util/CustomIdentity.java b/tests/common/src/test/java/org/wildfly/security/ssl/test/util/CustomIdentity.java
index a1ce69a2b5e..f7f2100c53f 100644
--- a/tests/common/src/test/java/org/wildfly/security/ssl/test/util/CustomIdentity.java
+++ b/tests/common/src/test/java/org/wildfly/security/ssl/test/util/CustomIdentity.java
@@ -34,4 +34,9 @@ public KeyStore loadKeyStore() {
return CAGenerationTool.loadKeyStore(keyStoreFile);
}
+ @Override
+ public File getKeyStoreFile() {
+ return keyStoreFile;
+ }
+
}
diff --git a/tests/common/src/test/java/org/wildfly/security/ssl/test/util/DefinedIdentity.java b/tests/common/src/test/java/org/wildfly/security/ssl/test/util/DefinedIdentity.java
index 28595b8e112..0983b2e928d 100644
--- a/tests/common/src/test/java/org/wildfly/security/ssl/test/util/DefinedIdentity.java
+++ b/tests/common/src/test/java/org/wildfly/security/ssl/test/util/DefinedIdentity.java
@@ -16,6 +16,7 @@
package org.wildfly.security.ssl.test.util;
+import java.io.File;
import java.security.KeyStore;
import java.security.cert.X509Certificate;
@@ -38,4 +39,9 @@ public KeyStore loadKeyStore() {
return caGenerationTool.loadKeyStore(identity);
}
+ @Override
+ public File getKeyStoreFile() {
+ return caGenerationTool.getKeyStoreFile(identity);
+ }
+
}