From 8f48e6b5bcd1a4893f4636ff02df826558b59b1e Mon Sep 17 00:00:00 2001 From: Lubos Racansky Date: Thu, 4 Jan 2024 09:41:42 +0100 Subject: [PATCH 01/44] Fix #318: Remove Guava dependency --- .../PowerAuthIdentityVerificationShared.java | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/powerauth-backend-tests/src/test/java/com/wultra/security/powerauth/test/shared/PowerAuthIdentityVerificationShared.java b/powerauth-backend-tests/src/test/java/com/wultra/security/powerauth/test/shared/PowerAuthIdentityVerificationShared.java index 1bed1235..bbd2add5 100644 --- a/powerauth-backend-tests/src/test/java/com/wultra/security/powerauth/test/shared/PowerAuthIdentityVerificationShared.java +++ b/powerauth-backend-tests/src/test/java/com/wultra/security/powerauth/test/shared/PowerAuthIdentityVerificationShared.java @@ -21,7 +21,6 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; -import com.google.common.collect.ImmutableList; import com.wultra.app.enrollmentserver.api.model.onboarding.request.*; import com.wultra.app.enrollmentserver.api.model.onboarding.response.*; import com.wultra.app.enrollmentserver.model.enumeration.*; @@ -99,7 +98,7 @@ private static void processDocuments(final TestProcessContext processCtx, final approveConsent(ctx, processId); initIdentityVerification(ctx, activationId, processId); - final List idCardSubmits = ImmutableList.of( + final List idCardSubmits = List.of( FileSubmit.createFrom("images/id_card_mock_front.png", DocumentType.ID_CARD, CardSide.FRONT), FileSubmit.createFrom("images/id_card_mock_back.png", DocumentType.ID_CARD, CardSide.BACK) ); @@ -113,7 +112,7 @@ private static void processDocuments(final TestProcessContext processCtx, final assertIdentityVerificationStateWithRetries(ctx, new IdentityVerificationState(IdentityVerificationPhase.DOCUMENT_UPLOAD, IdentityVerificationStatus.IN_PROGRESS)); - final List drivingLicenseSubmits = ImmutableList.of( + final List drivingLicenseSubmits = List.of( FileSubmit.createFrom("images/driving_license_mock_front.png", DocumentType.DRIVING_LICENSE, CardSide.FRONT) ); final DocumentSubmitRequest driveLicenseSubmitRequest = createDocumentSubmitRequest(processId, drivingLicenseSubmits); @@ -176,7 +175,7 @@ public static void testSuccessfulIdentityVerificationWithRestarts(final TestCont for (int i = 0; i < 3; i++) { initIdentityVerification(ctx, activationId, processId); - List idCardSubmits = ImmutableList.of( + final List idCardSubmits = List.of( FileSubmit.createFrom("images/id_card_mock_front.png", DocumentType.ID_CARD, CardSide.FRONT), FileSubmit.createFrom("images/id_card_mock_back.png", DocumentType.ID_CARD, CardSide.BACK) ); @@ -190,7 +189,7 @@ public static void testSuccessfulIdentityVerificationWithRestarts(final TestCont assertIdentityVerificationStateWithRetries(ctx, new IdentityVerificationState(IdentityVerificationPhase.DOCUMENT_UPLOAD, IdentityVerificationStatus.IN_PROGRESS)); - final List drivingLicenseSubmits = ImmutableList.of( + final List drivingLicenseSubmits = List.of( FileSubmit.createFrom("images/driving_license_mock_front.png", DocumentType.DRIVING_LICENSE, CardSide.FRONT) ); final DocumentSubmitRequest driveLicenseSubmitRequest = createDocumentSubmitRequest(processId, drivingLicenseSubmits); @@ -227,14 +226,14 @@ public static void testSuccessfulIdentityVerificationMultipleDocSubmits(final Te approveConsent(ctx, processId); initIdentityVerification(ctx, activationId, processId); - List idCardSubmits = ImmutableList.of( + final List idCardSubmits = List.of( FileSubmit.createFrom("images/id_card_mock_front.png", DocumentType.ID_CARD, CardSide.FRONT), FileSubmit.createFrom("images/id_card_mock_back.png", DocumentType.ID_CARD, CardSide.BACK) ); DocumentSubmitRequest idCardSubmitRequest = createDocumentSubmitRequest(processId, idCardSubmits); submitDocuments(ctx, idCardSubmitRequest); - List drivingLicenseSubmits = ImmutableList.of( + final List drivingLicenseSubmits = List.of( FileSubmit.createFrom("images/driving_license_mock_front.png", DocumentType.DRIVING_LICENSE, CardSide.FRONT) ); DocumentSubmitRequest driveLicenseSubmitRequest = createDocumentSubmitRequest(processId, drivingLicenseSubmits); @@ -268,7 +267,7 @@ public static void testDocSubmitDifferentDocumentType(final TestContext ctx) thr approveConsent(ctx, processId); initIdentityVerification(ctx, activationId, processId); - final List docSubmits = ImmutableList.of( + final List docSubmits = List.of( FileSubmit.createFrom("images/id_card_mock_front.png", DocumentType.DRIVING_LICENSE, CardSide.FRONT) ); DocumentSubmitRequest idCardSubmitRequest = createDocumentSubmitRequest(processId, docSubmits); @@ -290,7 +289,7 @@ public static void testDocSubmitDifferentCardSide(final TestContext ctx) throws approveConsent(ctx, processId); initIdentityVerification(ctx, activationId, processId); - List docSubmits = ImmutableList.of( + final List docSubmits = List.of( FileSubmit.createFrom("images/id_card_mock_front.png", DocumentType.ID_CARD, CardSide.BACK) ); DocumentSubmitRequest idCardSubmitRequest = createDocumentSubmitRequest(processId, docSubmits); @@ -312,7 +311,7 @@ public static void testDocSubmitMaxAttemptsLimit(final TestContext ctx) throws E approveConsent(ctx, processId); initIdentityVerification(ctx, activationId, processId); - final List docSubmits = ImmutableList.of( + final List docSubmits = List.of( FileSubmit.createFrom("images/id_card_mock_front.png", DocumentType.DRIVING_LICENSE, CardSide.FRONT) ); @@ -340,7 +339,7 @@ public static void testIdentityVerificationNotDocumentPhotos(final TestContext c approveConsent(ctx, processId); initIdentityVerification(ctx, activationId, processId); - List invalidDocSubmits = ImmutableList.of( + final List invalidDocSubmits = List.of( FileSubmit.createFrom("images/random_photo_1.png", DocumentType.ID_CARD, CardSide.FRONT), FileSubmit.createFrom("images/random_photo_2.png", DocumentType.ID_CARD, CardSide.BACK) ); @@ -360,7 +359,7 @@ public static void testIdentityVerificationCleanup(final TestContext ctx) throws approveConsent(ctx, processId); initIdentityVerification(ctx, activationId, processId); - List idDocSubmits = ImmutableList.of( + final List idDocSubmits = List.of( FileSubmit.createFrom("images/id_card_mock_front.png", DocumentType.ID_CARD, CardSide.FRONT), FileSubmit.createFrom("images/id_card_mock_back.png", DocumentType.ID_CARD, CardSide.BACK) ); @@ -381,7 +380,7 @@ public static void testIdentityVerificationMaxAttemptLimit(final TestContext ctx for (int i = 0; i < 5; i++) { initIdentityVerification(ctx, activationId, processId); - List idDocSubmits = ImmutableList.of( + final List idDocSubmits = List.of( FileSubmit.createFrom("images/id_card_mock_front.png", DocumentType.ID_CARD, CardSide.FRONT), FileSubmit.createFrom("images/id_card_mock_back.png", DocumentType.ID_CARD, CardSide.BACK) ); From d4c774d6af29728987d84a904ad4cf93df0594ab Mon Sep 17 00:00:00 2001 From: Lubos Racansky Date: Tue, 16 Jan 2024 13:34:16 +0100 Subject: [PATCH 02/44] Fix #323: Set develop version to 1.7.0-SNAPSHOT --- pom.xml | 2 +- powerauth-backend-tests/pom.xml | 2 +- powerauth-load-tests/pom.xml | 2 +- powerauth-test-server/pom.xml | 2 +- powerauth-webflow-tests/pom.xml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 7fbb8242..9df157dd 100644 --- a/pom.xml +++ b/pom.xml @@ -14,7 +14,7 @@ com.wultra powerauth-backend-tests-parent - 1.6.0 + 1.7.0-SNAPSHOT pom Parent pom for backend tests diff --git a/powerauth-backend-tests/pom.xml b/powerauth-backend-tests/pom.xml index 94ef6826..66dc818f 100644 --- a/powerauth-backend-tests/pom.xml +++ b/powerauth-backend-tests/pom.xml @@ -8,7 +8,7 @@ com.wultra powerauth-backend-tests-parent - 1.6.0 + 1.7.0-SNAPSHOT com.wultra diff --git a/powerauth-load-tests/pom.xml b/powerauth-load-tests/pom.xml index c3b12d7b..3f4693de 100644 --- a/powerauth-load-tests/pom.xml +++ b/powerauth-load-tests/pom.xml @@ -6,7 +6,7 @@ com.wultra powerauth-backend-tests-parent - 1.6.0 + 1.7.0-SNAPSHOT com.wultra diff --git a/powerauth-test-server/pom.xml b/powerauth-test-server/pom.xml index 81b8326a..af451f13 100644 --- a/powerauth-test-server/pom.xml +++ b/powerauth-test-server/pom.xml @@ -23,7 +23,7 @@ com.wultra powerauth-backend-tests-parent - 1.6.0 + 1.7.0-SNAPSHOT powerauth-test-server diff --git a/powerauth-webflow-tests/pom.xml b/powerauth-webflow-tests/pom.xml index 912a5f48..b36d9a7c 100644 --- a/powerauth-webflow-tests/pom.xml +++ b/powerauth-webflow-tests/pom.xml @@ -8,7 +8,7 @@ com.wultra powerauth-backend-tests-parent - 1.6.0 + 1.7.0-SNAPSHOT com.wultra From 6720024c2e52739a970dc49bbf7d759a4d161c8a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 Jan 2024 12:37:49 +0000 Subject: [PATCH 03/44] Bump org.springframework.boot:spring-boot-starter-parent Bumps [org.springframework.boot:spring-boot-starter-parent](https://github.com/spring-projects/spring-boot) from 3.1.6 to 3.2.1. - [Release notes](https://github.com/spring-projects/spring-boot/releases) - [Commits](https://github.com/spring-projects/spring-boot/compare/v3.1.6...v3.2.1) --- updated-dependencies: - dependency-name: org.springframework.boot:spring-boot-starter-parent dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 7fbb8242..252a62ac 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ org.springframework.boot spring-boot-starter-parent - 3.1.6 + 3.2.1 From 539703f547da91bd3344693ef755aaed012461f3 Mon Sep 17 00:00:00 2001 From: Lubos Racansky Date: Tue, 16 Jan 2024 13:46:03 +0100 Subject: [PATCH 04/44] Revert "Fix #296: Update logback" This reverts commit 8ad30ab9d9e3519586deb21b31e125daab97cf4a. --- pom.xml | 2 -- 1 file changed, 2 deletions(-) diff --git a/pom.xml b/pom.xml index 252a62ac..1cc3e01b 100644 --- a/pom.xml +++ b/pom.xml @@ -55,8 +55,6 @@ 1.77 2.3.0 7.4 - - 1.4.14 From 8cd65cef3b0ba96441b32c3729ad38cc0f7368c7 Mon Sep 17 00:00:00 2001 From: Lubos Racansky Date: Tue, 16 Jan 2024 13:50:19 +0100 Subject: [PATCH 05/44] Fix #324: Update Wultra dependencies to SNAPSHOT --- pom.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pom.xml b/pom.xml index 9df157dd..b5d0ed94 100644 --- a/pom.xml +++ b/pom.xml @@ -45,12 +45,12 @@ - 1.6.0 - 1.6.0 - 1.6.0 - 1.6.0 - 1.6.0 - 1.8.0 + 1.7.0-SNAPSHOT + 1.7.0-SNAPSHOT + 1.7.0-SNAPSHOT + 1.7.0-SNAPSHOT + 1.7.0-SNAPSHOT + 1.9.0-SNAPSHOT 1.77 2.3.0 From 03952ca31ad711f9b108ec50c2fb16490f246623 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lubo=C5=A1=20Ra=C4=8Dansk=C3=BD?= Date: Thu, 18 Jan 2024 11:23:55 +0100 Subject: [PATCH 06/44] Add GitHub Action config maven-deploy.yml (#335) * Add GitHub Action config maven-deploy.yml --- .github/workflows/maven-deploy.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .github/workflows/maven-deploy.yml diff --git a/.github/workflows/maven-deploy.yml b/.github/workflows/maven-deploy.yml new file mode 100644 index 00000000..157f0ef5 --- /dev/null +++ b/.github/workflows/maven-deploy.yml @@ -0,0 +1,20 @@ +name: Deploy with Maven + +on: + workflow_dispatch: + branches: + - 'develop' + - 'master' + - 'releases/*' + +jobs: + maven-deploy-manual: + if: ${{ github.event_name == 'workflow_dispatch' }} + name: Manual deploy + uses: wultra/wultra-infrastructure/.github/workflows/maven-deploy.yml@develop + with: + environment: internal-publish + release_type: snapshot + secrets: + username: ${{ secrets.MAVEN_CENTRAL_USERNAME }} + password: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} From 6be064cc021311dec24bd35d1d65f898f6f7eb51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lubo=C5=A1=20Ra=C4=8Dansk=C3=BD?= Date: Thu, 18 Jan 2024 12:34:33 +0100 Subject: [PATCH 07/44] Add distribution management configuration (#336) * Add distribution management configuration --- pom.xml | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 09d1ca4f..90d3304b 100644 --- a/pom.xml +++ b/pom.xml @@ -55,6 +55,8 @@ 1.77 2.3.0 7.4 + + true @@ -181,6 +183,18 @@ true + + + jfrog-central + Wultra Artifactory-releases + https://wultra.jfrog.io/artifactory/internal-maven-repository + + + jfrog-central + Wultra Artifactory-snapshots + https://wultra.jfrog.io/artifactory/internal-maven-repository + + jfrog-central @@ -216,9 +230,6 @@ !useInternalRepo - - true - ossrh-snapshots From f65a0b49edbe348e5459ab3d51bc68810802d6aa Mon Sep 17 00:00:00 2001 From: Lubos Racansky Date: Thu, 18 Jan 2024 13:08:58 +0100 Subject: [PATCH 08/44] Specify directory_path in maven-deploy.yml (cherry picked from commit e4ae6ba62baec2916208e0c05a960fa2b98e3a99) --- .github/workflows/maven-deploy.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/maven-deploy.yml b/.github/workflows/maven-deploy.yml index 157f0ef5..32b001e5 100644 --- a/.github/workflows/maven-deploy.yml +++ b/.github/workflows/maven-deploy.yml @@ -15,6 +15,7 @@ jobs: with: environment: internal-publish release_type: snapshot + directory_path: ./powerauth-test-server secrets: username: ${{ secrets.MAVEN_CENTRAL_USERNAME }} password: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} From 9a16602213cccb46dc12710a9afa118dc229235d Mon Sep 17 00:00:00 2001 From: Jan Dusil <134381434+jandusil@users.noreply.github.com> Date: Mon, 22 Jan 2024 13:48:45 +0100 Subject: [PATCH 09/44] Fix #270: Remove com.wultra.security.powerauth.test.v3x (#328) * Fix #270: Remove com.wultra.security.powerauth.test.v3x --- .../test/shared/PowerAuthApiShared.java | 389 +----------------- .../PowerAuthApplicationRolesShared.java | 70 ---- .../test/shared/PowerAuthCallbackShared.java | 58 --- .../test/shared/PowerAuthOperationShared.java | 111 ----- .../powerauth/test/v3x/PowerAuthApiTest.java | 129 ------ .../v3x/PowerAuthApplicationRolesTest.java | 57 --- .../test/v3x/PowerAuthCallbackTest.java | 10 - .../test/v3x/PowerAuthOperationTest.java | 64 --- 8 files changed, 1 insertion(+), 887 deletions(-) delete mode 100644 powerauth-backend-tests/src/test/java/com/wultra/security/powerauth/test/shared/PowerAuthApplicationRolesShared.java delete mode 100644 powerauth-backend-tests/src/test/java/com/wultra/security/powerauth/test/shared/PowerAuthOperationShared.java delete mode 100644 powerauth-backend-tests/src/test/java/com/wultra/security/powerauth/test/v3x/PowerAuthApplicationRolesTest.java delete mode 100644 powerauth-backend-tests/src/test/java/com/wultra/security/powerauth/test/v3x/PowerAuthOperationTest.java diff --git a/powerauth-backend-tests/src/test/java/com/wultra/security/powerauth/test/shared/PowerAuthApiShared.java b/powerauth-backend-tests/src/test/java/com/wultra/security/powerauth/test/shared/PowerAuthApiShared.java index 50006570..a800bd07 100644 --- a/powerauth-backend-tests/src/test/java/com/wultra/security/powerauth/test/shared/PowerAuthApiShared.java +++ b/powerauth-backend-tests/src/test/java/com/wultra/security/powerauth/test/shared/PowerAuthApiShared.java @@ -22,7 +22,6 @@ import com.wultra.security.powerauth.client.model.entity.*; import com.wultra.security.powerauth.client.model.enumeration.*; import com.wultra.security.powerauth.client.model.error.PowerAuthClientException; -import com.wultra.security.powerauth.client.model.request.GetActivationListForUserRequest; import com.wultra.security.powerauth.client.model.request.GetEciesDecryptorRequest; import com.wultra.security.powerauth.client.model.response.*; import com.wultra.security.powerauth.configuration.PowerAuthTestConfiguration; @@ -74,7 +73,6 @@ import java.security.PrivateKey; import java.security.PublicKey; import java.security.spec.InvalidKeySpecException; -import java.time.Duration; import java.util.*; import static org.junit.jupiter.api.Assertions.*; @@ -99,190 +97,6 @@ public class PowerAuthApiShared { private static final int TIME_SYNCHRONIZATION_WINDOW_SECONDS = 60; - public static void systemStatusTest(PowerAuthClient powerAuthClient) throws PowerAuthClientException { - final GetSystemStatusResponse response = powerAuthClient.getSystemStatus(); - assertEquals("OK", response.getStatus()); - } - - public static void errorListTest(PowerAuthClient powerAuthClient) throws PowerAuthClientException { - final GetErrorCodeListResponse response = powerAuthClient.getErrorList(Locale.ENGLISH.getLanguage()); - assertTrue(response.getErrors().size() > 32); - } - - public static void initActivationTest(PowerAuthClient powerAuthClient, PowerAuthTestConfiguration config, String version) throws PowerAuthClientException { - final InitActivationResponse response = powerAuthClient.initActivation(config.getUser(version), config.getApplicationId()); - assertNotNull(response.getActivationId()); - assertNotNull(response.getActivationCode()); - assertNotNull(response.getActivationSignature()); - assertEquals(config.getUser(version), response.getUserId()); - assertEquals(config.getApplicationId(), response.getApplicationId()); - final GetActivationStatusResponse statusResponse = powerAuthClient.getActivationStatus(response.getActivationId()); - assertEquals(ActivationStatus.CREATED, statusResponse.getActivationStatus()); - } - - public static void prepareActivationTest(PowerAuthClient powerAuthClient, PowerAuthTestConfiguration config, String version) throws CryptoProviderException, EncryptorException, IOException, PowerAuthClientException { - String activationName = "test_prepare"; - InitActivationResponse response = powerAuthClient.initActivation(config.getUser(version), config.getApplicationId()); - String activationId = response.getActivationId(); - String activationCode = response.getActivationCode(); - KeyPair deviceKeyPair = CLIENT_ACTIVATION.generateDeviceKeyPair(); - byte[] devicePublicKeyBytes = KEY_CONVERTOR.convertPublicKeyToBytes(deviceKeyPair.getPublic()); - String devicePublicKeyBase64 = Base64.getEncoder().encodeToString(devicePublicKeyBytes); - ActivationLayer2Request requestL2 = new ActivationLayer2Request(); - requestL2.setActivationName(activationName); - requestL2.setDevicePublicKey(devicePublicKeyBase64); - ClientEncryptor clientEncryptorL2 = ENCRYPTOR_FACTORY.getClientEncryptor( - EncryptorId.ACTIVATION_LAYER_2, - new EncryptorParameters(version, config.getApplicationKey(), null), - new ClientEncryptorSecrets(config.getMasterPublicKey(), config.getApplicationSecret()) - ); - ByteArrayOutputStream baosL2 = new ByteArrayOutputStream(); - OBJECT_MAPPER.writeValue(baosL2, requestL2); - EncryptedRequest encryptedRequestL2 = clientEncryptorL2.encryptRequest(baosL2.toByteArray()); - PrepareActivationResponse prepareResponse = powerAuthClient.prepareActivation(activationCode, config.getApplicationKey(), true, encryptedRequestL2.getEphemeralPublicKey(), encryptedRequestL2.getEncryptedData(), encryptedRequestL2.getMac(), encryptedRequestL2.getNonce(), version, encryptedRequestL2.getTimestamp()); - assertEquals(ActivationStatus.PENDING_COMMIT, prepareResponse.getActivationStatus()); - CommitActivationResponse commitResponse = powerAuthClient.commitActivation(activationId, config.getUser(version)); - assertTrue(commitResponse.isActivated()); - } - - public static void createActivationTest(PowerAuthClient powerAuthClient, PowerAuthTestConfiguration config, String version) throws CryptoProviderException, EncryptorException, IOException, PowerAuthClientException { - String activationName = "test_create"; - KeyPair deviceKeyPair = CLIENT_ACTIVATION.generateDeviceKeyPair(); - byte[] devicePublicKeyBytes = KEY_CONVERTOR.convertPublicKeyToBytes(deviceKeyPair.getPublic()); - String devicePublicKeyBase64 = Base64.getEncoder().encodeToString(devicePublicKeyBytes); - ActivationLayer2Request requestL2 = new ActivationLayer2Request(); - requestL2.setActivationName(activationName); - requestL2.setDevicePublicKey(devicePublicKeyBase64); - ClientEncryptor clientEncryptorL2 = ENCRYPTOR_FACTORY.getClientEncryptor( - EncryptorId.ACTIVATION_LAYER_2, - new EncryptorParameters(version, config.getApplicationKey(), null), - new ClientEncryptorSecrets(config.getMasterPublicKey(), config.getApplicationSecret()) - ); - ByteArrayOutputStream baosL2 = new ByteArrayOutputStream(); - OBJECT_MAPPER.writeValue(baosL2, requestL2); - EncryptedRequest encryptedRequestL2 = clientEncryptorL2.encryptRequest(baosL2.toByteArray()); - CreateActivationResponse createResponse = powerAuthClient.createActivation(config.getUser(version), null, - null, config.getApplicationKey(), encryptedRequestL2.getEphemeralPublicKey(), encryptedRequestL2.getEncryptedData(), encryptedRequestL2.getMac(), encryptedRequestL2.getNonce(), version, encryptedRequestL2.getTimestamp()); - String activationId = createResponse.getActivationId(); - assertNotNull(activationId); - GetActivationStatusResponse statusResponse = powerAuthClient.getActivationStatus(activationId); - assertEquals(ActivationStatus.PENDING_COMMIT, statusResponse.getActivationStatus()); - CommitActivationResponse commitResponse = powerAuthClient.commitActivation(activationId, config.getUser(version)); - assertTrue(commitResponse.isActivated()); - } - - public static void updateActivationOtpAndCommitTest(PowerAuthClient powerAuthClient, PowerAuthTestConfiguration config, String version) throws CryptoProviderException, EncryptorException, IOException, PowerAuthClientException { - String activationName = "test_update_otp"; - InitActivationResponse response = powerAuthClient.initActivation(config.getUser(version), config.getApplicationId(), ActivationOtpValidation.NONE, null); - String activationId = response.getActivationId(); - String activationCode = response.getActivationCode(); - KeyPair deviceKeyPair = CLIENT_ACTIVATION.generateDeviceKeyPair(); - byte[] devicePublicKeyBytes = KEY_CONVERTOR.convertPublicKeyToBytes(deviceKeyPair.getPublic()); - String devicePublicKeyBase64 = Base64.getEncoder().encodeToString(devicePublicKeyBytes); - ActivationLayer2Request requestL2 = new ActivationLayer2Request(); - requestL2.setActivationName(activationName); - requestL2.setDevicePublicKey(devicePublicKeyBase64); - ClientEncryptor clientEncryptorL2 = ENCRYPTOR_FACTORY.getClientEncryptor( - EncryptorId.ACTIVATION_LAYER_2, - new EncryptorParameters(version, config.getApplicationKey(), null), - new ClientEncryptorSecrets(config.getMasterPublicKey(), config.getApplicationSecret()) - ); - ByteArrayOutputStream baosL2 = new ByteArrayOutputStream(); - OBJECT_MAPPER.writeValue(baosL2, requestL2); - EncryptedRequest encryptedRequestL2 = clientEncryptorL2.encryptRequest(baosL2.toByteArray()); - PrepareActivationResponse prepareResponse = powerAuthClient.prepareActivation(activationCode, config.getApplicationKey(), true, encryptedRequestL2.getEphemeralPublicKey(), encryptedRequestL2.getEncryptedData(), encryptedRequestL2.getMac(), encryptedRequestL2.getNonce(), version, encryptedRequestL2.getTimestamp()); - assertEquals(ActivationStatus.PENDING_COMMIT, prepareResponse.getActivationStatus()); - UpdateActivationOtpResponse otpResponse = powerAuthClient.updateActivationOtp(activationId, config.getUser(version), "12345678"); - assertTrue(otpResponse.isUpdated()); - CommitActivationResponse commitResponse = powerAuthClient.commitActivation(activationId, config.getUser(version), "12345678"); - assertTrue(commitResponse.isActivated()); - } - - public static void removeActivationTest(PowerAuthClient powerAuthClient, PowerAuthTestConfiguration config, String version) throws PowerAuthClientException { - InitActivationResponse response = powerAuthClient.initActivation(config.getUser(version), config.getApplicationId()); - GetActivationStatusResponse statusResponse = powerAuthClient.getActivationStatus(response.getActivationId()); - assertEquals(ActivationStatus.CREATED, statusResponse.getActivationStatus()); - RemoveActivationResponse removeResponse = powerAuthClient.removeActivation(response.getActivationId(), null); - assertTrue(removeResponse.isRemoved()); - GetActivationStatusResponse statusResponse2 = powerAuthClient.getActivationStatus(response.getActivationId()); - assertEquals(ActivationStatus.REMOVED, statusResponse2.getActivationStatus()); - } - - public static void activationListForUserTest(PowerAuthClient powerAuthClient, PowerAuthTestConfiguration config, String version) throws PowerAuthClientException { - InitActivationResponse response = powerAuthClient.initActivation(config.getUser(version), config.getApplicationId()); - GetActivationStatusResponse statusResponse = powerAuthClient.getActivationStatus(response.getActivationId()); - assertEquals(ActivationStatus.CREATED, statusResponse.getActivationStatus()); - final List listResponse = powerAuthClient.getActivationListForUser(config.getUser(version)); - assertNotEquals(0, listResponse.size()); - } - - public static void testGetActivationListForUserPagination(PowerAuthClient powerAuthClient, PowerAuthTestConfiguration config, String version) throws PowerAuthClientException { - // Prepare the base GetActivationListForUserRequest - final GetActivationListForUserRequest baseRequest = new GetActivationListForUserRequest(); - baseRequest.setUserId(config.getUser(version)); - baseRequest.setApplicationId(config.getApplicationId()); - - // Create a list to store the activation IDs - final List activationIds = new ArrayList<>(); - - // Create multiple activations for the test user - for (int i = 0; i < 10; i++) { - InitActivationResponse initResponse = powerAuthClient.initActivation(baseRequest.getUserId(), baseRequest.getApplicationId()); - activationIds.add(initResponse.getActivationId()); - } - - // Prepare the request for the first page of activations - final GetActivationListForUserRequest requestPage1 = new GetActivationListForUserRequest(); - requestPage1.setUserId(baseRequest.getUserId()); - requestPage1.setApplicationId(baseRequest.getApplicationId()); - requestPage1.setPageNumber(0); - requestPage1.setPageSize(5); - - // Fetch the first page of activations - final GetActivationListForUserResponse responsePage1 = powerAuthClient.getActivationListForUser(requestPage1); - assertEquals(5, responsePage1.getActivations().size()); - - // Prepare the request for the second page of activations - final GetActivationListForUserRequest requestPage2 = new GetActivationListForUserRequest(); - requestPage2.setUserId(baseRequest.getUserId()); - requestPage2.setApplicationId(baseRequest.getApplicationId()); - requestPage2.setPageNumber(1); - requestPage2.setPageSize(5); - - // Fetch the second page of activations - final GetActivationListForUserResponse responsePage2 = powerAuthClient.getActivationListForUser(requestPage2); - assertEquals(5, responsePage2.getActivations().size()); - - // Check that the activations on the different pages are not the same - assertNotEquals(responsePage1.getActivations(), responsePage2.getActivations()); - - // Clean up the created activations at the end - for (String id : activationIds) { - RemoveActivationResponse removeActivationResponse = powerAuthClient.removeActivation(id, config.getUser(version)); - assertTrue(removeActivationResponse.isRemoved()); - } - } - - public static void lookupActivationsTest(PowerAuthClient powerAuthClient, PowerAuthTestConfiguration config, String version) throws PowerAuthClientException { - InitActivationResponse response = powerAuthClient.initActivation(config.getUser(version), config.getApplicationId()); - GetActivationStatusResponse statusResponse = powerAuthClient.getActivationStatus(response.getActivationId()); - final Date timestampCreated = statusResponse.getTimestampCreated(); - assertEquals(ActivationStatus.CREATED, statusResponse.getActivationStatus()); - List activations = powerAuthClient.lookupActivations(Collections.singletonList(config.getUser(version)), Collections.singletonList(config.getApplicationId()), - null, timestampCreated, ActivationStatus.CREATED, null); - assertTrue(activations.size() >= 1); - } - - public static void activationStatusUpdateTest(PowerAuthClient powerAuthClient, PowerAuthTestConfiguration config, String version) throws PowerAuthClientException { - InitActivationResponse response = powerAuthClient.initActivation(config.getUser(version), config.getApplicationId()); - GetActivationStatusResponse statusResponse = powerAuthClient.getActivationStatus(response.getActivationId()); - assertEquals(ActivationStatus.CREATED, statusResponse.getActivationStatus()); - UpdateStatusForActivationsResponse updateResponse = powerAuthClient.updateStatusForActivations(Collections.singletonList(response.getActivationId()), ActivationStatus.REMOVED); - assertTrue(updateResponse.isUpdated()); - GetActivationStatusResponse statusResponse2 = powerAuthClient.getActivationStatus(response.getActivationId()); - assertEquals(ActivationStatus.REMOVED, statusResponse2.getActivationStatus()); - } - public static void verifySignatureTest(PowerAuthClient powerAuthClient, PowerAuthTestConfiguration config, String version) throws GenericCryptoException, CryptoProviderException, InvalidKeyException, PowerAuthClientException { Calendar before = new GregorianCalendar(); before.add(Calendar.SECOND, -TIME_SYNCHRONIZATION_WINDOW_SECONDS); @@ -307,7 +121,7 @@ public static void verifySignatureTest(PowerAuthClient powerAuthClient, PowerAut after.add(Calendar.SECOND, TIME_SYNCHRONIZATION_WINDOW_SECONDS); List auditItems = powerAuthClient.getSignatureAuditLog(config.getUser(version), config.getApplicationId(), before.getTime(), after.getTime()); boolean signatureFound = false; - for (SignatureAuditItem item: auditItems) { + for (SignatureAuditItem item : auditItems) { if (signatureValue.equals(item.getSignature())) { assertEquals(config.getActivationId(version), item.getActivationId()); assertEquals(normalizedDataWithSecret, new String(Base64.getDecoder().decode(item.getDataBase64()))); @@ -323,26 +137,6 @@ public static void verifySignatureTest(PowerAuthClient powerAuthClient, PowerAut assertTrue(signatureFound); } - public static void nonPersonalizedOfflineSignaturePayloadTest(PowerAuthClient powerAuthClient, PowerAuthTestConfiguration config) throws PowerAuthClientException { - // For more complete tests for createNonPersonalizedOfflineSignaturePayload see PowerAuthSignatureTest - CreateNonPersonalizedOfflineSignaturePayloadResponse response = powerAuthClient.createNonPersonalizedOfflineSignaturePayload(config.getApplicationId(), "test_data"); - assertNotNull(response.getOfflineData()); - assertNotNull(response.getNonce()); - } - - public static void personalizedOfflineSignaturePayloadTest(PowerAuthClient powerAuthClient, PowerAuthTestConfiguration config, String version) throws PowerAuthClientException { - // For more complete tests for createPersonalizedOfflineSignaturePayload see PowerAuthSignatureTest - CreatePersonalizedOfflineSignaturePayloadResponse response = powerAuthClient.createPersonalizedOfflineSignaturePayload(config.getActivationId(version), "test_data"); - assertNotNull(response.getOfflineData()); - assertNotNull(response.getNonce()); - } - - public static void verifyOfflineSignatureTest(PowerAuthClient powerAuthClient, PowerAuthTestConfiguration config, String version) throws PowerAuthClientException { - // For more complete tests for verifyOfflineSignature see PowerAuthSignatureTest - VerifyOfflineSignatureResponse response = powerAuthClient.verifyOfflineSignature(config.getActivationId(version), "test_data", "12345678", false); - assertFalse(response.isSignatureValid()); - } - public static void unlockVaultAndECDSASignatureTest(PowerAuthClient powerAuthClient, PowerAuthTestConfiguration config, String version) throws GenericCryptoException, CryptoProviderException, InvalidKeySpecException, EncryptorException, IOException, InvalidKeyException, PowerAuthClientException { byte[] transportMasterKeyBytes = Base64.getDecoder().decode(JsonUtil.stringValue(config.getResultStatusObject(version), "transportMasterKey")); byte[] serverPublicKeyBytes = Base64.getDecoder().decode(JsonUtil.stringValue(config.getResultStatusObject(version), "serverPublicKey")); @@ -401,106 +195,8 @@ public static void unlockVaultAndECDSASignatureTest(PowerAuthClient powerAuthCli assertTrue(ecdsaResponse.isSignatureValid()); } - public static void activationHistoryTest(PowerAuthClient powerAuthClient, PowerAuthTestConfiguration config, String version) throws PowerAuthClientException { - InitActivationResponse response = powerAuthClient.initActivation(config.getUser(version) + "_history_test", config.getApplicationId()); - GetActivationStatusResponse statusResponse = powerAuthClient.getActivationStatus(response.getActivationId()); - assertEquals(ActivationStatus.CREATED, statusResponse.getActivationStatus()); - final Date before = statusResponse.getTimestampCreated(); - final Date after = Date.from(before.toInstant().plus(Duration.ofSeconds(1))); - final List activationHistory = powerAuthClient.getActivationHistory(response.getActivationId(), before, after); - final ActivationHistoryItem item = activationHistory.get(0); - assertEquals(response.getActivationId(), item.getActivationId()); - assertEquals(ActivationStatus.CREATED, item.getActivationStatus()); - } - - public static void blockAndUnblockActivationTest(PowerAuthClient powerAuthClient, PowerAuthTestConfiguration config, String version) throws PowerAuthClientException { - InitActivationResponse response = powerAuthClient.initActivation(config.getUser(version), config.getApplicationId()); - GetActivationStatusResponse statusResponse = powerAuthClient.getActivationStatus(response.getActivationId()); - assertEquals(ActivationStatus.CREATED, statusResponse.getActivationStatus()); - // Fake status change to ACTIVE for block and unblock test - UpdateStatusForActivationsResponse updateResponse = powerAuthClient.updateStatusForActivations(Collections.singletonList(response.getActivationId()), ActivationStatus.ACTIVE); - assertTrue(updateResponse.isUpdated()); - BlockActivationResponse blockResponse = powerAuthClient.blockActivation(response.getActivationId(), "TEST", null); - assertEquals(ActivationStatus.BLOCKED, blockResponse.getActivationStatus()); - GetActivationStatusResponse statusResponse2 = powerAuthClient.getActivationStatus(response.getActivationId()); - assertEquals(ActivationStatus.BLOCKED, statusResponse2.getActivationStatus()); - UnblockActivationResponse unblockResponse = powerAuthClient.unblockActivation(response.getActivationId(), null); - assertEquals(ActivationStatus.ACTIVE, unblockResponse.getActivationStatus()); - GetActivationStatusResponse statusResponse3 = powerAuthClient.getActivationStatus(response.getActivationId()); - assertEquals(ActivationStatus.ACTIVE, statusResponse3.getActivationStatus()); - } - - public static void applicationListTest(PowerAuthClient powerAuthClient, PowerAuthTestConfiguration config) throws PowerAuthClientException { - final GetApplicationListResponse applications = powerAuthClient.getApplicationList(); - assertNotEquals(0, applications.getApplications().size()); - boolean testApplicationFound = false; - for (Application app: applications.getApplications()) { - if (app.getApplicationId().equals(config.getApplicationId())) { - testApplicationFound = true; - } - } - assertTrue(testApplicationFound); - } - - public static void applicationDetailTest(PowerAuthClient powerAuthClient, PowerAuthTestConfiguration config) throws PowerAuthClientException { - GetApplicationDetailResponse response = powerAuthClient.getApplicationDetail(config.getApplicationId()); - assertEquals(config.getApplicationName(), response.getApplicationId()); - boolean testAppVersionFound = false; - for (ApplicationVersion version: response.getVersions()) { - if (version.getApplicationVersionId().equals(config.getApplicationVersionId())) { - testAppVersionFound = true; - } - } - assertTrue(testAppVersionFound); - } - - public static void applicationVersionLookupTest(PowerAuthClient powerAuthClient, PowerAuthTestConfiguration config) throws PowerAuthClientException { - LookupApplicationByAppKeyResponse response = powerAuthClient.lookupApplicationByAppKey(config.getApplicationKey()); - assertEquals(config.getApplicationId(), response.getApplicationId()); - } - // createApplication and createApplication version tests are skipped to avoid creating too many applications - public static void applicationSupportTest(PowerAuthClient powerAuthClient, PowerAuthTestConfiguration config) throws PowerAuthClientException { - UnsupportApplicationVersionResponse response = powerAuthClient.unsupportApplicationVersion(config.getApplicationId(), config.getApplicationVersionId()); - assertFalse(response.isSupported()); - SupportApplicationVersionResponse response2 = powerAuthClient.supportApplicationVersion(config.getApplicationId(), config.getApplicationVersionId()); - assertTrue(response2.isSupported()); - } - - public static void applicationIntegrationTest(PowerAuthClient powerAuthClient, PowerAuthTestConfiguration config) throws PowerAuthClientException { - String integrationName = UUID.randomUUID().toString(); - CreateIntegrationResponse response = powerAuthClient.createIntegration(integrationName); - assertEquals(integrationName, response.getName()); - final GetIntegrationListResponse items = powerAuthClient.getIntegrationList(); - boolean integrationFound = false; - for (Integration integration: items.getItems()) { - if (integration.getName().equals(integrationName)) { - integrationFound = true; - } - } - assertTrue(integrationFound); - RemoveIntegrationResponse removeResponse = powerAuthClient.removeIntegration(response.getId()); - assertTrue(removeResponse.isRemoved()); - } - - public static void callbackTest(PowerAuthClient powerAuthClient, PowerAuthTestConfiguration config) throws PowerAuthClientException { - String callbackName = UUID.randomUUID().toString(); - String url = "http://test.wultra.com/"; - CreateCallbackUrlResponse response = powerAuthClient.createCallbackUrl(config.getApplicationId(), callbackName, CallbackUrlType.ACTIVATION_STATUS_CHANGE, url, Collections.emptyList(), null); - assertEquals(callbackName, response.getName()); - final GetCallbackUrlListResponse items = powerAuthClient.getCallbackUrlList(config.getApplicationId()); - boolean callbackFound = false; - for (CallbackUrl callback: items.getCallbackUrlList()) { - if (callback.getName().equals(callbackName)) { - callbackFound = true; - } - } - assertTrue(callbackFound); - RemoveCallbackUrlResponse removeResponse = powerAuthClient.removeCallbackUrl(response.getId()); - assertTrue(removeResponse.isRemoved()); - } - public static void createValidateAndRemoveTokenTestActiveActivation(PowerAuthClient powerAuthClient, PowerAuthTestConfiguration config, String version) throws InvalidKeySpecException, CryptoProviderException, GenericCryptoException, IOException, EncryptorException, PowerAuthClientException { final TokenInfo tokenInfo = createToken(powerAuthClient, config, version); @@ -518,72 +214,6 @@ public static void createValidateAndRemoveTokenTestActiveActivation(PowerAuthCli assertTrue(removeResponse.isRemoved()); } - public static void createValidateAndRemoveTokenTestBlockedActivation(PowerAuthClient powerAuthClient, PowerAuthTestConfiguration config, String version) throws InvalidKeySpecException, CryptoProviderException, GenericCryptoException, IOException, EncryptorException, PowerAuthClientException { - final TokenInfo tokenInfo = createToken(powerAuthClient, config, version); - - // Block activation - final BlockActivationResponse blockResponse = powerAuthClient.blockActivation(config.getActivationId(version), "TEST", null); - assertEquals(ActivationStatus.BLOCKED, blockResponse.getActivationStatus()); - - // Check that token validation failed and activation status and blocked reason is available - final ValidateTokenResponse validateResponse = powerAuthClient.validateToken(tokenInfo.getTokenId(), - Base64.getEncoder().encodeToString(tokenInfo.getTokenNonce()), - version, - Long.parseLong(new String(tokenInfo.getTokenTimestamp())), - Base64.getEncoder().encodeToString(tokenInfo.getTokenDigest())); - assertFalse(validateResponse.isTokenValid()); - assertEquals(ActivationStatus.BLOCKED, validateResponse.getActivationStatus()); - assertEquals("TEST", validateResponse.getBlockedReason()); - - // Unblock activation - final UnblockActivationResponse unblockResponse = powerAuthClient.unblockActivation(config.getActivationId(version), "TEST"); - assertEquals(ActivationStatus.ACTIVE, unblockResponse.getActivationStatus()); - - final RemoveTokenResponse removeResponse = powerAuthClient.removeToken(tokenInfo.getTokenId(), config.getActivationId(version)); - assertTrue(removeResponse.isRemoved()); - } - - public static void getEciesDecryptorTest(PowerAuthClient powerAuthClient, PowerAuthTestConfiguration config, String version) throws EncryptorException, PowerAuthClientException { - String requestData = "test_data"; - ClientEncryptor clientEncryptor = ENCRYPTOR_FACTORY.getClientEncryptor( - EncryptorId.APPLICATION_SCOPE_GENERIC, - new EncryptorParameters(version, config.getApplicationKey(), null), - new ClientEncryptorSecrets(config.getMasterPublicKey(), config.getApplicationSecret()) - ); - EncryptedRequest encryptedRequest = clientEncryptor.encryptRequest(requestData.getBytes(StandardCharsets.UTF_8)); - final GetEciesDecryptorRequest eciesDecryptorRequest = new GetEciesDecryptorRequest(); - eciesDecryptorRequest.setProtocolVersion(version); - eciesDecryptorRequest.setActivationId(null); - eciesDecryptorRequest.setApplicationKey(config.getApplicationKey()); - eciesDecryptorRequest.setEphemeralPublicKey(encryptedRequest.getEphemeralPublicKey()); - eciesDecryptorRequest.setNonce(encryptedRequest.getNonce()); - eciesDecryptorRequest.setTimestamp(encryptedRequest.getTimestamp()); - GetEciesDecryptorResponse decryptorResponse = powerAuthClient.getEciesDecryptor(eciesDecryptorRequest); - - final byte[] secretKey = Base64.getDecoder().decode(decryptorResponse.getSecretKey()); - final byte[] sharedInfo2Base = Base64.getDecoder().decode(decryptorResponse.getSharedInfo2()); - final byte[] ephemeralPublicKeyBytes = Base64.getDecoder().decode(encryptedRequest.getEphemeralPublicKey()); - final EciesEnvelopeKey envelopeKey = new EciesEnvelopeKey(secretKey, ephemeralPublicKeyBytes); - final ServerEncryptor serverEncryptor = ENCRYPTOR_FACTORY.getServerEncryptor( - EncryptorId.APPLICATION_SCOPE_GENERIC, - new EncryptorParameters(version, config.getApplicationKey(), null), - new ServerEncryptorSecrets(secretKey, sharedInfo2Base) - ); - byte[] decryptedData = serverEncryptor.decryptRequest(encryptedRequest); - assertArrayEquals(requestData.getBytes(StandardCharsets.UTF_8), decryptedData); - } - - public static void recoveryCodeCreateLookupRevokeTest(PowerAuthClient powerAuthClient, PowerAuthTestConfiguration config, String version) throws PowerAuthClientException { - CreateRecoveryCodeResponse createResponse = powerAuthClient.createRecoveryCode(config.getApplicationId(), config.getUser(version), 2L); - assertEquals(config.getUser(version), createResponse.getUserId()); - assertEquals(RecoveryCodeStatus.CREATED, createResponse.getStatus()); - assertEquals(2, createResponse.getPuks().size()); - LookupRecoveryCodesResponse lookupResponse = powerAuthClient.lookupRecoveryCodes(config.getUser(version), null, config.getApplicationId(), RecoveryCodeStatus.CREATED, RecoveryPukStatus.VALID); - assertNotEquals(0, lookupResponse.getRecoveryCodes().size()); - RevokeRecoveryCodesResponse revokeResponse = powerAuthClient.revokeRecoveryCodes(Collections.singletonList(createResponse.getRecoveryCodeId())); - assertTrue(revokeResponse.isRevoked()); - } - public static void recoveryCodeConfirmAndActivationTest(PowerAuthClient powerAuthClient, PowerAuthTestConfiguration config, String version) throws CryptoProviderException, GenericCryptoException, IOException, EncryptorException, InvalidKeyException, InvalidKeySpecException, PowerAuthClientException { String activationName = "test_create_recovery"; KeyPair deviceKeyPair = CLIENT_ACTIVATION.generateDeviceKeyPair(); @@ -668,23 +298,6 @@ public static void recoveryCodeConfirmAndActivationTest(PowerAuthClient powerAut assertEquals(ActivationStatus.REMOVED, statusResponseR3.getActivationStatus()); } - public static void recoveryConfigTest(PowerAuthClient powerAuthClient, PowerAuthTestConfiguration config) throws PowerAuthClientException { - GetRecoveryConfigResponse response = powerAuthClient.getRecoveryConfig(config.getApplicationId()); - String remotePostcardPublicKey = response.getRemotePostcardPublicKey(); - assertNotNull(response.getPostcardPublicKey()); - assertNotNull(remotePostcardPublicKey); - UpdateRecoveryConfigResponse configResponse = powerAuthClient.updateRecoveryConfig(config.getApplicationId(), false, false, false, "test_key"); - assertTrue(configResponse.isUpdated()); - GetRecoveryConfigResponse response2 = powerAuthClient.getRecoveryConfig(config.getApplicationId()); - assertNotNull(response2.getPostcardPublicKey()); - assertFalse(response2.isActivationRecoveryEnabled()); - assertFalse(response2.isRecoveryPostcardEnabled()); - assertFalse(response2.isAllowMultipleRecoveryCodes()); - assertEquals("test_key", response2.getRemotePostcardPublicKey()); - UpdateRecoveryConfigResponse configResponse2 = powerAuthClient.updateRecoveryConfig(config.getApplicationId(), true, true, false, remotePostcardPublicKey); - assertTrue(configResponse2.isUpdated()); - } - // Activation flags are tested using PowerAuthActivationFlagsTest // Application roles are tested using PowerAuthApplicationRolesTest diff --git a/powerauth-backend-tests/src/test/java/com/wultra/security/powerauth/test/shared/PowerAuthApplicationRolesShared.java b/powerauth-backend-tests/src/test/java/com/wultra/security/powerauth/test/shared/PowerAuthApplicationRolesShared.java deleted file mode 100644 index 61223d57..00000000 --- a/powerauth-backend-tests/src/test/java/com/wultra/security/powerauth/test/shared/PowerAuthApplicationRolesShared.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * PowerAuth test and related software components - * Copyright (C) 2023 Wultra s.r.o. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -package com.wultra.security.powerauth.test.shared; - -import com.wultra.security.powerauth.client.PowerAuthClient; -import com.wultra.security.powerauth.client.model.response.GetApplicationDetailResponse; -import com.wultra.security.powerauth.client.model.response.ListApplicationRolesResponse; -import com.wultra.security.powerauth.configuration.PowerAuthTestConfiguration; - -import java.util.Arrays; -import java.util.Collections; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -/** - * Application roles test shared logic. - * - * @author Roman Strobl, roman.strobl@wultra.com - */ -public class PowerAuthApplicationRolesShared { - - public static void applicationRolesCrudTest(PowerAuthClient powerAuthClient, PowerAuthTestConfiguration config) throws Exception { - // Test application roles CRUD - String applicationId = config.getApplicationId(); - - // Remove all existing roles - final ListApplicationRolesResponse initResponse = powerAuthClient.listApplicationRoles(applicationId); - if (!initResponse.getApplicationRoles().isEmpty()) { - powerAuthClient.removeApplicationRoles(applicationId, initResponse.getApplicationRoles()); - } - - powerAuthClient.addApplicationRoles(applicationId, Arrays.asList("ROLE1", "ROLE2")); - - final GetApplicationDetailResponse response = powerAuthClient.getApplicationDetail(applicationId); - assertEquals(Arrays.asList("ROLE1", "ROLE2"), response.getApplicationRoles()); - - ListApplicationRolesResponse listResponse = powerAuthClient.listApplicationRoles(applicationId); - assertEquals(Arrays.asList("ROLE1", "ROLE2"), listResponse.getApplicationRoles()); - - powerAuthClient.updateApplicationRoles(applicationId, Arrays.asList("ROLE3", "ROLE4")); - - ListApplicationRolesResponse listResponse2 = powerAuthClient.listApplicationRoles(applicationId); - assertEquals(Arrays.asList("ROLE3", "ROLE4"), listResponse2.getApplicationRoles()); - - powerAuthClient.removeApplicationRoles(applicationId, Collections.singletonList("ROLE4")); - - ListApplicationRolesResponse listResponse3 = powerAuthClient.listApplicationRoles(applicationId); - assertEquals(Collections.singletonList("ROLE3"), listResponse3.getApplicationRoles()); - - powerAuthClient.addApplicationRoles(applicationId, Arrays.asList("ROLE3", "ROLE4")); - - ListApplicationRolesResponse listResponse4 = powerAuthClient.listApplicationRoles(applicationId); - assertEquals(Arrays.asList("ROLE3", "ROLE4"), listResponse4.getApplicationRoles()); - } -} diff --git a/powerauth-backend-tests/src/test/java/com/wultra/security/powerauth/test/shared/PowerAuthCallbackShared.java b/powerauth-backend-tests/src/test/java/com/wultra/security/powerauth/test/shared/PowerAuthCallbackShared.java index 9107781f..38dcded9 100644 --- a/powerauth-backend-tests/src/test/java/com/wultra/security/powerauth/test/shared/PowerAuthCallbackShared.java +++ b/powerauth-backend-tests/src/test/java/com/wultra/security/powerauth/test/shared/PowerAuthCallbackShared.java @@ -39,64 +39,6 @@ */ public class PowerAuthCallbackShared { - public static void callbackCreateDeleteTest(PowerAuthClient powerAuthClient, PowerAuthTestConfiguration config) throws PowerAuthClientException { - String callbackName = UUID.randomUUID().toString(); - String callbackUrl = "http://test.test"; - powerAuthClient.createCallbackUrl(config.getApplicationId(), callbackName, CallbackUrlType.ACTIVATION_STATUS_CHANGE, callbackUrl, Collections.singletonList("activationId"), null); - final GetCallbackUrlListResponse callbacks = powerAuthClient.getCallbackUrlList(config.getApplicationId()); - boolean callbackFound = false; - for (CallbackUrl callback: callbacks.getCallbackUrlList()) { - if (callbackName.equals(callback.getName())) { - callbackFound = true; - assertEquals(callbackUrl, callback.getCallbackUrl()); - assertEquals(config.getApplicationId(), callback.getApplicationId()); - assertEquals(1, callback.getAttributes().size()); - assertEquals("activationId", callback.getAttributes().get(0)); - int callbackCountOrig = callbacks.getCallbackUrlList().size(); - powerAuthClient.removeCallbackUrl(callback.getId()); - } - } - assertTrue(callbackFound); - } - - public static void callbackUpdateTest(PowerAuthClient powerAuthClient, PowerAuthTestConfiguration config) throws PowerAuthClientException { - String callbackName = UUID.randomUUID().toString(); - String callbackUrl = "http://test.test"; - powerAuthClient.createCallbackUrl(config.getApplicationId(), callbackName, CallbackUrlType.ACTIVATION_STATUS_CHANGE, callbackUrl, Collections.singletonList("activationId"), null); - final GetCallbackUrlListResponse callbacks = powerAuthClient.getCallbackUrlList(config.getApplicationId()); - boolean callbackFound = false; - String callbackId = null; - for (CallbackUrl callback: callbacks.getCallbackUrlList()) { - if (callbackName.equals(callback.getName())) { - callbackFound = true; - callbackId = callback.getId(); - assertEquals(callbackUrl, callback.getCallbackUrl()); - assertEquals(config.getApplicationId(), callback.getApplicationId()); - assertEquals(1, callback.getAttributes().size()); - assertEquals("activationId", callback.getAttributes().get(0)); - } - } - assertTrue(callbackFound); - assertNotNull(callbackId); - String callbackName2 = UUID.randomUUID().toString(); - String callbackUrl2 = "http://test2.test2"; - powerAuthClient.updateCallbackUrl(callbackId, config.getApplicationId(), callbackName2, callbackUrl2, Arrays.asList("activationId", "userId", "deviceInfo", "platform"), null); - final GetCallbackUrlListResponse callbacks2 = powerAuthClient.getCallbackUrlList(config.getApplicationId()); - boolean callbackFound2 = false; - for (CallbackUrl callback: callbacks2.getCallbackUrlList()) { - if (callbackName2.equals(callback.getName())) { - callbackFound2 = true; - callbackId = callback.getId(); - assertEquals(callbackUrl2, callback.getCallbackUrl()); - assertEquals(config.getApplicationId(), callback.getApplicationId()); - assertEquals(4, callback.getAttributes().size()); - assertEquals(Arrays.asList("activationId", "userId", "deviceInfo", "platform"), callback.getAttributes()); - } - } - assertTrue(callbackFound2); - powerAuthClient.removeCallbackUrl(callbackId); - } - public static void callbackExecutionTest(PowerAuthClient powerAuthClient, PowerAuthTestConfiguration config, Integer port, String version) throws PowerAuthClientException, RestClientException { // Skip test when the tested PA server is not running on localhost assumeTrue(config.getPowerAuthRestUrl().contains("localhost:8080")); diff --git a/powerauth-backend-tests/src/test/java/com/wultra/security/powerauth/test/shared/PowerAuthOperationShared.java b/powerauth-backend-tests/src/test/java/com/wultra/security/powerauth/test/shared/PowerAuthOperationShared.java deleted file mode 100644 index d5310297..00000000 --- a/powerauth-backend-tests/src/test/java/com/wultra/security/powerauth/test/shared/PowerAuthOperationShared.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * PowerAuth test and related software components - * Copyright (C) 2023 Wultra s.r.o. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -package com.wultra.security.powerauth.test.shared; - -import com.wultra.security.powerauth.client.PowerAuthClient; -import com.wultra.security.powerauth.client.model.enumeration.SignatureType; -import com.wultra.security.powerauth.client.model.error.PowerAuthClientException; -import com.wultra.security.powerauth.client.model.request.OperationApproveRequest; -import com.wultra.security.powerauth.client.model.request.OperationCreateRequest; -import com.wultra.security.powerauth.client.model.request.OperationDetailRequest; -import com.wultra.security.powerauth.client.model.response.OperationDetailResponse; -import com.wultra.security.powerauth.client.model.response.OperationUserActionResponse; -import com.wultra.security.powerauth.configuration.PowerAuthTestConfiguration; - -import java.util.List; - -import static com.wultra.security.powerauth.client.model.enumeration.UserActionResult.APPROVAL_FAILED; -import static com.wultra.security.powerauth.client.model.enumeration.UserActionResult.APPROVED; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; - -/** - * PowerAuth operations test shared logic. - * - * @author Roman Strobl, roman.strobl@wultra.com - */ -public class PowerAuthOperationShared { - - public static void testOperationApprove(final PowerAuthClient powerAuthClient, final PowerAuthTestConfiguration config, final String version) throws Exception { - final OperationDetailResponse operation = createOperation(powerAuthClient, config, version); - - final OperationApproveRequest approveRequest = createOperationApproveRequest(config, operation.getId(), version); - - final OperationUserActionResponse result = powerAuthClient.operationApprove(approveRequest); - - assertEquals(APPROVED, result.getResult()); - } - - public static void testOperationApproveWithValidProximityOtp(final PowerAuthClient powerAuthClient, final PowerAuthTestConfiguration config, final String version) throws Exception { - final OperationDetailResponse operation = createOperation(powerAuthClient, config, true, version); - - final OperationDetailRequest detailRequest = new OperationDetailRequest(); - detailRequest.setOperationId(operation.getId()); - - final String totp = powerAuthClient.operationDetail(detailRequest).getProximityOtp(); - assertNotNull(totp); - - final OperationApproveRequest approveRequest = createOperationApproveRequest(config, operation.getId(), version); - approveRequest.getAdditionalData().put("proximity_otp", totp); - - final OperationUserActionResponse result = powerAuthClient.operationApprove(approveRequest); - - assertEquals(APPROVED, result.getResult()); - } - - public static void testOperationApproveWithInvalidProximityOtp(final PowerAuthClient powerAuthClient, final PowerAuthTestConfiguration config, final String version) throws Exception { - final OperationDetailResponse operation = createOperation(powerAuthClient, config, true, version); - - final OperationDetailRequest detailRequest = new OperationDetailRequest(); - detailRequest.setOperationId(operation.getId()); - - final String totp = powerAuthClient.operationDetail(detailRequest).getProximityOtp(); - assertNotNull(totp); - - final OperationApproveRequest approveRequest = createOperationApproveRequest(config, operation.getId(), version); - approveRequest.getAdditionalData().put("proximity_otp", "1111"); // invalid otp on purpose, it is too short - - final OperationUserActionResponse result = powerAuthClient.operationApprove(approveRequest); - - assertEquals(APPROVAL_FAILED, result.getResult()); - } - - private static OperationApproveRequest createOperationApproveRequest(final PowerAuthTestConfiguration config, final String operationId, final String version) { - final OperationApproveRequest approveRequest = new OperationApproveRequest(); - approveRequest.setOperationId(operationId); - approveRequest.setUserId(config.getUser(version)); - approveRequest.setApplicationId(config.getApplicationId()); - approveRequest.setData("A2"); - approveRequest.setSignatureType(SignatureType.POSSESSION_KNOWLEDGE); - return approveRequest; - } - - private static OperationDetailResponse createOperation(final PowerAuthClient powerAuthClient, final PowerAuthTestConfiguration config, String version) throws PowerAuthClientException { - return createOperation(powerAuthClient, config, null, version); - } - - private static OperationDetailResponse createOperation(final PowerAuthClient powerAuthClient, final PowerAuthTestConfiguration config, final Boolean proximityCheckEnabled, final String version) throws PowerAuthClientException { - final OperationCreateRequest createRequest = new OperationCreateRequest(); - createRequest.setApplications(List.of(config.getApplicationName())); - createRequest.setUserId(config.getUser(version)); - createRequest.setTemplateName(config.getLoginOperationTemplateName()); - createRequest.setProximityCheckEnabled(proximityCheckEnabled); - - return powerAuthClient.createOperation(createRequest); - } -} diff --git a/powerauth-backend-tests/src/test/java/com/wultra/security/powerauth/test/v3x/PowerAuthApiTest.java b/powerauth-backend-tests/src/test/java/com/wultra/security/powerauth/test/v3x/PowerAuthApiTest.java index 2ec21ce0..5e718731 100644 --- a/powerauth-backend-tests/src/test/java/com/wultra/security/powerauth/test/v3x/PowerAuthApiTest.java +++ b/powerauth-backend-tests/src/test/java/com/wultra/security/powerauth/test/v3x/PowerAuthApiTest.java @@ -60,158 +60,29 @@ public void setPowerAuthTestConfiguration(PowerAuthTestConfiguration config) { this.config = config; } - @Test - void systemStatusTest() throws PowerAuthClientException { - PowerAuthApiShared.systemStatusTest(powerAuthClient); - } - - @Test - void errorListTest() throws PowerAuthClientException { - PowerAuthApiShared.errorListTest(powerAuthClient); - } - - @Test - void initActivationTest() throws PowerAuthClientException { - PowerAuthApiShared.initActivationTest(powerAuthClient, config, VERSION); - } - - @Test - void prepareActivationTest() throws CryptoProviderException, EncryptorException, IOException, PowerAuthClientException { - PowerAuthApiShared.prepareActivationTest(powerAuthClient, config, VERSION); - } - - @Test - void createActivationTest() throws CryptoProviderException, EncryptorException, IOException, PowerAuthClientException { - PowerAuthApiShared.createActivationTest(powerAuthClient, config, VERSION); - } - - @Test - void updateActivationOtpAndCommitTest() throws CryptoProviderException, EncryptorException, IOException, PowerAuthClientException { - PowerAuthApiShared.updateActivationOtpAndCommitTest(powerAuthClient, config, VERSION); - } - - @Test - void removeActivationTest() throws PowerAuthClientException { - PowerAuthApiShared.removeActivationTest(powerAuthClient, config, VERSION); - } - - @Test - void activationListForUserTest() throws PowerAuthClientException { - PowerAuthApiShared.activationListForUserTest(powerAuthClient, config, VERSION); - } - - @Test - void testGetActivationListForUserPagination() throws PowerAuthClientException { - PowerAuthApiShared.testGetActivationListForUserPagination(powerAuthClient, config, VERSION); - } - - @Test - void lookupActivationsTest() throws PowerAuthClientException { - PowerAuthApiShared.lookupActivationsTest(powerAuthClient, config, VERSION); - } - - @Test - void activationStatusUpdateTest() throws PowerAuthClientException { - PowerAuthApiShared.activationStatusUpdateTest(powerAuthClient, config, VERSION); - } - @Test void verifySignatureTest() throws GenericCryptoException, CryptoProviderException, InvalidKeyException, PowerAuthClientException { PowerAuthApiShared.verifySignatureTest(powerAuthClient, config, VERSION); } - @Test - void nonPersonalizedOfflineSignaturePayloadTest() throws PowerAuthClientException { - PowerAuthApiShared.nonPersonalizedOfflineSignaturePayloadTest(powerAuthClient, config); - } - - @Test - void personalizedOfflineSignaturePayloadTest() throws PowerAuthClientException { - PowerAuthApiShared.personalizedOfflineSignaturePayloadTest(powerAuthClient, config, VERSION); - } - - @Test - void verifyOfflineSignatureTest() throws PowerAuthClientException { - PowerAuthApiShared.verifyOfflineSignatureTest(powerAuthClient, config, VERSION); - } - @Test void unlockVaultAndECDSASignatureTest() throws GenericCryptoException, CryptoProviderException, InvalidKeySpecException, EncryptorException, IOException, InvalidKeyException, PowerAuthClientException { PowerAuthApiShared.unlockVaultAndECDSASignatureTest(powerAuthClient, config, VERSION); } - @Test - void activationHistoryTest() throws PowerAuthClientException { - PowerAuthApiShared.activationHistoryTest(powerAuthClient, config, VERSION); - } - - @Test - void blockAndUnblockActivationTest() throws PowerAuthClientException { - PowerAuthApiShared.blockAndUnblockActivationTest(powerAuthClient, config, VERSION); - } - - @Test - void applicationListTest() throws PowerAuthClientException { - PowerAuthApiShared.applicationListTest(powerAuthClient, config); - } - - @Test - void applicationDetailTest() throws PowerAuthClientException { - PowerAuthApiShared.applicationDetailTest(powerAuthClient, config); - } - - @Test - void applicationVersionLookupTest() throws PowerAuthClientException { - PowerAuthApiShared.applicationVersionLookupTest(powerAuthClient, config); - } - // createApplication and createApplication version tests are skipped to avoid creating too many applications - @Test - void applicationSupportTest() throws PowerAuthClientException { - PowerAuthApiShared.applicationSupportTest(powerAuthClient, config); - } - - @Test - void applicationIntegrationTest() throws PowerAuthClientException { - PowerAuthApiShared.applicationIntegrationTest(powerAuthClient, config); - } - - @Test - void callbackTest() throws PowerAuthClientException { - PowerAuthApiShared.callbackTest(powerAuthClient, config); - } - @Test void createValidateAndRemoveTokenTestActiveActivation() throws InvalidKeySpecException, CryptoProviderException, GenericCryptoException, IOException, EncryptorException, PowerAuthClientException { PowerAuthApiShared.createValidateAndRemoveTokenTestActiveActivation(powerAuthClient, config, VERSION); } - @Test - void createValidateAndRemoveTokenTestBlockedActivation() throws InvalidKeySpecException, CryptoProviderException, GenericCryptoException, IOException, EncryptorException, PowerAuthClientException { - PowerAuthApiShared.createValidateAndRemoveTokenTestBlockedActivation(powerAuthClient, config, VERSION); - } - - @Test - void getEciesDecryptorTest() throws EncryptorException, PowerAuthClientException { - PowerAuthApiShared.getEciesDecryptorTest(powerAuthClient, config, VERSION); - } - - @Test - void recoveryCodeCreateLookupRevokeTest() throws PowerAuthClientException { - PowerAuthApiShared.recoveryCodeCreateLookupRevokeTest(powerAuthClient, config, VERSION); - } @Test void recoveryCodeConfirmAndActivationTest() throws CryptoProviderException, GenericCryptoException, IOException, EncryptorException, InvalidKeyException, InvalidKeySpecException, PowerAuthClientException { PowerAuthApiShared.recoveryCodeConfirmAndActivationTest(powerAuthClient, config, VERSION); } - @Test - void recoveryConfigTest() throws PowerAuthClientException { - PowerAuthApiShared.recoveryConfigTest(powerAuthClient, config); - } - // Activation flags are tested using PowerAuthActivationFlagsTest // Application roles are tested using PowerAuthApplicationRolesTest diff --git a/powerauth-backend-tests/src/test/java/com/wultra/security/powerauth/test/v3x/PowerAuthApplicationRolesTest.java b/powerauth-backend-tests/src/test/java/com/wultra/security/powerauth/test/v3x/PowerAuthApplicationRolesTest.java deleted file mode 100644 index 8f8ba2ed..00000000 --- a/powerauth-backend-tests/src/test/java/com/wultra/security/powerauth/test/v3x/PowerAuthApplicationRolesTest.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * PowerAuth test and related software components - * Copyright (C) 2020 Wultra s.r.o. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -package com.wultra.security.powerauth.test.v3x; - -import com.wultra.security.powerauth.client.PowerAuthClient; -import com.wultra.security.powerauth.configuration.PowerAuthTestConfiguration; -import com.wultra.security.powerauth.test.shared.PowerAuthApplicationRolesShared; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit.jupiter.SpringExtension; - -/** - * Application roles tests. - * - * @author Roman Strobl, roman.strobl@wultra.com - */ -@ExtendWith(SpringExtension.class) -@SpringBootTest(classes = PowerAuthTestConfiguration.class) -@EnableConfigurationProperties -class PowerAuthApplicationRolesTest { - - private PowerAuthClient powerAuthClient; - private PowerAuthTestConfiguration config; - - @Autowired - public void setPowerAuthClient(PowerAuthClient powerAuthClient) { - this.powerAuthClient = powerAuthClient; - } - - @Autowired - public void setPowerAuthTestConfiguration(PowerAuthTestConfiguration config) { - this.config = config; - } - - @Test - void applicationRolesCrudTest() throws Exception { - PowerAuthApplicationRolesShared.applicationRolesCrudTest(powerAuthClient, config); - } -} diff --git a/powerauth-backend-tests/src/test/java/com/wultra/security/powerauth/test/v3x/PowerAuthCallbackTest.java b/powerauth-backend-tests/src/test/java/com/wultra/security/powerauth/test/v3x/PowerAuthCallbackTest.java index 6995e77b..444f12cb 100644 --- a/powerauth-backend-tests/src/test/java/com/wultra/security/powerauth/test/v3x/PowerAuthCallbackTest.java +++ b/powerauth-backend-tests/src/test/java/com/wultra/security/powerauth/test/v3x/PowerAuthCallbackTest.java @@ -73,16 +73,6 @@ void tearDown() throws PowerAuthClientException { } } - @Test - void callbackCreateDeleteTest() throws PowerAuthClientException { - PowerAuthCallbackShared.callbackCreateDeleteTest(powerAuthClient, config); - } - - @Test - void callbackUpdateTest() throws PowerAuthClientException { - PowerAuthCallbackShared.callbackUpdateTest(powerAuthClient, config); - } - @Test void callbackExecutionTest() throws PowerAuthClientException, RestClientException { PowerAuthCallbackShared.callbackExecutionTest(powerAuthClient, config, port, VERSION); diff --git a/powerauth-backend-tests/src/test/java/com/wultra/security/powerauth/test/v3x/PowerAuthOperationTest.java b/powerauth-backend-tests/src/test/java/com/wultra/security/powerauth/test/v3x/PowerAuthOperationTest.java deleted file mode 100644 index 063d64fa..00000000 --- a/powerauth-backend-tests/src/test/java/com/wultra/security/powerauth/test/v3x/PowerAuthOperationTest.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * PowerAuth test and related software components - * Copyright (C) 2023 Wultra s.r.o. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -package com.wultra.security.powerauth.test.v3x; - -import com.wultra.security.powerauth.client.PowerAuthClient; -import com.wultra.security.powerauth.configuration.PowerAuthTestConfiguration; -import com.wultra.security.powerauth.test.shared.PowerAuthOperationShared; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit.jupiter.SpringExtension; - -/** - * Test of PowerAuth operation endpoints. - * - * @author Lubos Racansky, lubos.racansky@wultra.com - */ -@ExtendWith(SpringExtension.class) -@SpringBootTest(classes = PowerAuthTestConfiguration.class) -@EnableConfigurationProperties -class PowerAuthOperationTest { - - // Test only in the latestPowerAuth protocol version - private static final String VERSION = "3.2"; - - @Autowired - private PowerAuthClient powerAuthClient; - - @Autowired - private PowerAuthTestConfiguration config; - - @Test - void testOperationApprove() throws Exception { - PowerAuthOperationShared.testOperationApprove(powerAuthClient, config, VERSION); - } - - @Test - void testOperationApproveWithValidProximityOtp() throws Exception { - PowerAuthOperationShared.testOperationApproveWithValidProximityOtp(powerAuthClient, config, VERSION); - } - - @Test - void testOperationApproveWithInvalidProximityOtp() throws Exception { - PowerAuthOperationShared.testOperationApproveWithInvalidProximityOtp(powerAuthClient, config, VERSION); - } - -} From c8abef06f79586bce2817f441864428fd05d27d8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jan 2024 18:01:59 +0000 Subject: [PATCH 10/44] Bump org.springframework.boot:spring-boot-starter-parent Bumps [org.springframework.boot:spring-boot-starter-parent](https://github.com/spring-projects/spring-boot) from 3.2.1 to 3.2.2. - [Release notes](https://github.com/spring-projects/spring-boot/releases) - [Commits](https://github.com/spring-projects/spring-boot/compare/v3.2.1...v3.2.2) --- updated-dependencies: - dependency-name: org.springframework.boot:spring-boot-starter-parent dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 90d3304b..cf368e3e 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ org.springframework.boot spring-boot-starter-parent - 3.2.1 + 3.2.2 From 450729bbc64c9afde9897e112b5d0bde18e3134c Mon Sep 17 00:00:00 2001 From: Jan Dusil <134381434+jandusil@users.noreply.github.com> Date: Fri, 26 Jan 2024 10:58:11 +0100 Subject: [PATCH 11/44] Fix #278: Add request validation for Test Server endpoints (#342) * Fix ##278: Add request validation for Test Server endpoints --- .../app/testserver/controller/ActivationController.java | 5 ++++- .../app/testserver/controller/ApplicationController.java | 5 ++++- .../app/testserver/controller/OperationsController.java | 7 +++++-- .../app/testserver/controller/SignatureController.java | 7 +++++-- .../app/testserver/controller/TokenController.java | 5 +++-- .../model/request/ComputeOfflineSignatureRequest.java | 5 ++++- .../model/request/ComputeOnlineSignatureRequest.java | 7 ++++++- .../model/request/ComputeTokenDigestRequest.java | 4 ++++ .../model/request/ConfigureApplicationRequest.java | 5 ++++- .../testserver/model/request/CreateActivationRequest.java | 2 ++ .../app/testserver/model/request/CreateTokenRequest.java | 5 ++++- .../app/testserver/model/request/GetOperationsRequest.java | 5 ++++- .../model/request/OperationApproveInternalRequest.java | 6 +++++- 13 files changed, 54 insertions(+), 14 deletions(-) diff --git a/powerauth-test-server/src/main/java/com/wultra/security/powerauth/app/testserver/controller/ActivationController.java b/powerauth-test-server/src/main/java/com/wultra/security/powerauth/app/testserver/controller/ActivationController.java index f79368b7..8263a9e2 100644 --- a/powerauth-test-server/src/main/java/com/wultra/security/powerauth/app/testserver/controller/ActivationController.java +++ b/powerauth-test-server/src/main/java/com/wultra/security/powerauth/app/testserver/controller/ActivationController.java @@ -28,7 +28,9 @@ import io.getlime.core.rest.model.base.request.ObjectRequest; import io.getlime.core.rest.model.base.response.ObjectResponse; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import jakarta.validation.Valid; /** * Controller for activation actions. @@ -37,6 +39,7 @@ */ @RestController @RequestMapping("activation") +@Validated public class ActivationController { private final ActivationService activationService; @@ -60,7 +63,7 @@ public ActivationController(ActivationService activationService) { * @throws ActivationFailedException Thrown when activation fails. */ @PostMapping("create") - public ObjectResponse createActivation(@RequestBody ObjectRequest request) throws AppConfigNotFoundException, GenericCryptographyException, RemoteExecutionException, ActivationFailedException { + public ObjectResponse createActivation(@Valid @RequestBody ObjectRequest request) throws AppConfigNotFoundException, GenericCryptographyException, RemoteExecutionException, ActivationFailedException { // TODO - input validation final CreateActivationResponse response = activationService.createActivation(request.getRequestObject()); return new ObjectResponse<>(response); diff --git a/powerauth-test-server/src/main/java/com/wultra/security/powerauth/app/testserver/controller/ApplicationController.java b/powerauth-test-server/src/main/java/com/wultra/security/powerauth/app/testserver/controller/ApplicationController.java index f86c49a2..98f1b597 100644 --- a/powerauth-test-server/src/main/java/com/wultra/security/powerauth/app/testserver/controller/ApplicationController.java +++ b/powerauth-test-server/src/main/java/com/wultra/security/powerauth/app/testserver/controller/ApplicationController.java @@ -23,7 +23,9 @@ import com.wultra.security.powerauth.app.testserver.service.ApplicationService; import io.getlime.core.rest.model.base.request.ObjectRequest; import io.getlime.core.rest.model.base.response.Response; +import jakarta.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; /** @@ -32,6 +34,7 @@ * @author Roman Strobl, roman.strobl@wultra.com */ @RestController +@Validated @RequestMapping("application") public class ApplicationController { @@ -53,7 +56,7 @@ public ApplicationController(ApplicationService applicationService) { * @throws AppConfigInvalidException Thrown in case mobile SDK configuration is invalid. */ @PostMapping("config") - public Response createActivation(@RequestBody ObjectRequest request) throws AppConfigInvalidException { + public Response createActivation(@Valid @RequestBody ObjectRequest request) throws AppConfigInvalidException { return applicationService.configureApplication(request.getRequestObject()); } diff --git a/powerauth-test-server/src/main/java/com/wultra/security/powerauth/app/testserver/controller/OperationsController.java b/powerauth-test-server/src/main/java/com/wultra/security/powerauth/app/testserver/controller/OperationsController.java index 45abc814..63b09a2b 100644 --- a/powerauth-test-server/src/main/java/com/wultra/security/powerauth/app/testserver/controller/OperationsController.java +++ b/powerauth-test-server/src/main/java/com/wultra/security/powerauth/app/testserver/controller/OperationsController.java @@ -31,8 +31,10 @@ import io.getlime.core.rest.model.base.response.Response; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.enums.ParameterIn; +import jakarta.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -44,6 +46,7 @@ * @author Petr Dvorak, petr@wultra.com */ @RestController +@Validated @RequestMapping("operations") public class OperationsController { @@ -69,7 +72,7 @@ public OperationsController(OperationsService operationsService) { */ @Parameter(name = HttpHeaders.ACCEPT_LANGUAGE, in = ParameterIn.HEADER, allowEmptyValue = true, description = "Preferred language in which we want to get the operations.", example = "en") @PostMapping("pending") - public ObjectResponse fetchOperations(@RequestBody ObjectRequest request) throws RemoteExecutionException, RestClientException, SignatureVerificationException, ActivationFailedException { + public ObjectResponse fetchOperations(@Valid @RequestBody ObjectRequest request) throws RemoteExecutionException, RestClientException, SignatureVerificationException, ActivationFailedException { final OperationListResponse response = operationsService.getOperations(request.getRequestObject()); return new ObjectResponse<>(response); } @@ -84,7 +87,7 @@ public ObjectResponse fetchOperations(@RequestBody Object * @throws AppConfigNotFoundException In case app configuration is not found. */ @PostMapping("approve") - public Response approveOperations(@RequestBody ObjectRequest request) throws RemoteExecutionException, AppConfigNotFoundException, SignatureVerificationException, ActivationFailedException { + public Response approveOperations(@Valid @RequestBody ObjectRequest request) throws RemoteExecutionException, AppConfigNotFoundException, SignatureVerificationException, ActivationFailedException { return operationsService.approveOperation(request.getRequestObject()); } diff --git a/powerauth-test-server/src/main/java/com/wultra/security/powerauth/app/testserver/controller/SignatureController.java b/powerauth-test-server/src/main/java/com/wultra/security/powerauth/app/testserver/controller/SignatureController.java index 2085c3eb..4cff6b09 100644 --- a/powerauth-test-server/src/main/java/com/wultra/security/powerauth/app/testserver/controller/SignatureController.java +++ b/powerauth-test-server/src/main/java/com/wultra/security/powerauth/app/testserver/controller/SignatureController.java @@ -27,7 +27,9 @@ import com.wultra.security.powerauth.app.testserver.service.SignatureService; import io.getlime.core.rest.model.base.request.ObjectRequest; import io.getlime.core.rest.model.base.response.ObjectResponse; +import jakarta.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; /** @@ -36,6 +38,7 @@ * @author Roman Strobl, roman.strobl@wultra.com */ @RestController +@Validated @RequestMapping("signature") public class SignatureController { @@ -59,7 +62,7 @@ public SignatureController(SignatureService signatureService) { * @throws AppConfigNotFoundException In case application configuration is not found. */ @PostMapping("compute-online") - public ObjectResponse computeOnlineSignature(@RequestBody ObjectRequest request) throws RemoteExecutionException, ActivationFailedException, AppConfigNotFoundException { + public ObjectResponse computeOnlineSignature(@Valid @RequestBody ObjectRequest request) throws RemoteExecutionException, ActivationFailedException, AppConfigNotFoundException { final ComputeOnlineSignatureResponse response = signatureService.computeOnlineSignature(request.getRequestObject()); return new ObjectResponse<>(response); } @@ -72,7 +75,7 @@ public ObjectResponse computeOnlineSignature(@Re * @throws ActivationFailedException In case activation is not found. */ @PostMapping("compute-offline") - public ObjectResponse computeOfflineSignature(@RequestBody ObjectRequest request) throws RemoteExecutionException, ActivationFailedException { + public ObjectResponse computeOfflineSignature(@Valid @RequestBody ObjectRequest request) throws RemoteExecutionException, ActivationFailedException { final ComputeOfflineSignatureResponse response = signatureService.computeOfflineSignature(request.getRequestObject()); return new ObjectResponse<>(response); } diff --git a/powerauth-test-server/src/main/java/com/wultra/security/powerauth/app/testserver/controller/TokenController.java b/powerauth-test-server/src/main/java/com/wultra/security/powerauth/app/testserver/controller/TokenController.java index 2ab403e4..41b88621 100644 --- a/powerauth-test-server/src/main/java/com/wultra/security/powerauth/app/testserver/controller/TokenController.java +++ b/powerauth-test-server/src/main/java/com/wultra/security/powerauth/app/testserver/controller/TokenController.java @@ -28,6 +28,7 @@ import com.wultra.security.powerauth.app.testserver.service.TokenService; import io.getlime.core.rest.model.base.request.ObjectRequest; import io.getlime.core.rest.model.base.response.ObjectResponse; +import jakarta.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @@ -61,7 +62,7 @@ public TokenController(TokenService tokenService) { * @throws ActivationFailedException In case activation is not found. */ @PostMapping("create") - public ObjectResponse createToken(@RequestBody ObjectRequest request) throws GenericCryptographyException, RemoteExecutionException, AppConfigNotFoundException, ActivationFailedException { + public ObjectResponse createToken(@Valid @RequestBody ObjectRequest request) throws GenericCryptographyException, RemoteExecutionException, AppConfigNotFoundException, ActivationFailedException { final CreateTokenResponse response = tokenService.createToken(request.getRequestObject()); return new ObjectResponse<>(response); } @@ -74,7 +75,7 @@ public ObjectResponse createToken(@RequestBody ObjectReques * @throws ActivationFailedException In case activation is not found. */ @PostMapping("compute-digest") - public ObjectResponse computeTokenDigest(@RequestBody ObjectRequest request) throws RemoteExecutionException, ActivationFailedException { + public ObjectResponse computeTokenDigest(@Valid@RequestBody ObjectRequest request) throws RemoteExecutionException, ActivationFailedException { final ComputeTokenDigestResponse response = tokenService.computeTokenDigest(request.getRequestObject()); return new ObjectResponse<>(response); } diff --git a/powerauth-test-server/src/main/java/com/wultra/security/powerauth/app/testserver/model/request/ComputeOfflineSignatureRequest.java b/powerauth-test-server/src/main/java/com/wultra/security/powerauth/app/testserver/model/request/ComputeOfflineSignatureRequest.java index 41c4d228..cf760e3c 100644 --- a/powerauth-test-server/src/main/java/com/wultra/security/powerauth/app/testserver/model/request/ComputeOfflineSignatureRequest.java +++ b/powerauth-test-server/src/main/java/com/wultra/security/powerauth/app/testserver/model/request/ComputeOfflineSignatureRequest.java @@ -17,6 +17,7 @@ */ package com.wultra.security.powerauth.app.testserver.model.request; +import jakarta.validation.constraints.NotBlank; import lombok.Getter; import lombok.Setter; @@ -28,9 +29,11 @@ @Getter @Setter public class ComputeOfflineSignatureRequest { - + @NotBlank private String activationId; + @NotBlank private String qrCodeData; + @NotBlank private String password; } diff --git a/powerauth-test-server/src/main/java/com/wultra/security/powerauth/app/testserver/model/request/ComputeOnlineSignatureRequest.java b/powerauth-test-server/src/main/java/com/wultra/security/powerauth/app/testserver/model/request/ComputeOnlineSignatureRequest.java index e8dbcb82..f52f62db 100644 --- a/powerauth-test-server/src/main/java/com/wultra/security/powerauth/app/testserver/model/request/ComputeOnlineSignatureRequest.java +++ b/powerauth-test-server/src/main/java/com/wultra/security/powerauth/app/testserver/model/request/ComputeOnlineSignatureRequest.java @@ -17,6 +17,7 @@ */ package com.wultra.security.powerauth.app.testserver.model.request; +import jakarta.validation.constraints.NotBlank; import lombok.Getter; import lombok.Setter; @@ -28,11 +29,15 @@ @Getter @Setter public class ComputeOnlineSignatureRequest { - + @NotBlank private String activationId; + @NotBlank private String applicationId; + @NotBlank private String httpMethod; + @NotBlank private String resourceId; + @NotBlank private String signatureType; private String requestBody; private String password; diff --git a/powerauth-test-server/src/main/java/com/wultra/security/powerauth/app/testserver/model/request/ComputeTokenDigestRequest.java b/powerauth-test-server/src/main/java/com/wultra/security/powerauth/app/testserver/model/request/ComputeTokenDigestRequest.java index aac4e424..f2ae046d 100644 --- a/powerauth-test-server/src/main/java/com/wultra/security/powerauth/app/testserver/model/request/ComputeTokenDigestRequest.java +++ b/powerauth-test-server/src/main/java/com/wultra/security/powerauth/app/testserver/model/request/ComputeTokenDigestRequest.java @@ -17,6 +17,7 @@ */ package com.wultra.security.powerauth.app.testserver.model.request; +import jakarta.validation.constraints.NotBlank; import lombok.Getter; import lombok.Setter; @@ -29,8 +30,11 @@ @Setter public class ComputeTokenDigestRequest { + @NotBlank private String activationId; + @NotBlank private String tokenId; + @NotBlank private String tokenSecret; } diff --git a/powerauth-test-server/src/main/java/com/wultra/security/powerauth/app/testserver/model/request/ConfigureApplicationRequest.java b/powerauth-test-server/src/main/java/com/wultra/security/powerauth/app/testserver/model/request/ConfigureApplicationRequest.java index 39cf971b..e6618b49 100644 --- a/powerauth-test-server/src/main/java/com/wultra/security/powerauth/app/testserver/model/request/ConfigureApplicationRequest.java +++ b/powerauth-test-server/src/main/java/com/wultra/security/powerauth/app/testserver/model/request/ConfigureApplicationRequest.java @@ -18,6 +18,8 @@ package com.wultra.security.powerauth.app.testserver.model.request; +import jakarta.validation.constraints.NotBlank; + import lombok.Getter; import lombok.Setter; @@ -29,8 +31,9 @@ @Getter @Setter public class ConfigureApplicationRequest { - + @NotBlank private String applicationId; + @NotBlank private String applicationName; private String applicationKey; private String applicationSecret; diff --git a/powerauth-test-server/src/main/java/com/wultra/security/powerauth/app/testserver/model/request/CreateActivationRequest.java b/powerauth-test-server/src/main/java/com/wultra/security/powerauth/app/testserver/model/request/CreateActivationRequest.java index f2b6f86b..0ce754bb 100644 --- a/powerauth-test-server/src/main/java/com/wultra/security/powerauth/app/testserver/model/request/CreateActivationRequest.java +++ b/powerauth-test-server/src/main/java/com/wultra/security/powerauth/app/testserver/model/request/CreateActivationRequest.java @@ -18,6 +18,7 @@ package com.wultra.security.powerauth.app.testserver.model.request; +import jakarta.validation.constraints.NotBlank; import lombok.Data; /** @@ -28,6 +29,7 @@ @Data public class CreateActivationRequest { + @NotBlank private String applicationId; private String activationName; private String password; diff --git a/powerauth-test-server/src/main/java/com/wultra/security/powerauth/app/testserver/model/request/CreateTokenRequest.java b/powerauth-test-server/src/main/java/com/wultra/security/powerauth/app/testserver/model/request/CreateTokenRequest.java index 4c20a4d0..ed6e3be6 100644 --- a/powerauth-test-server/src/main/java/com/wultra/security/powerauth/app/testserver/model/request/CreateTokenRequest.java +++ b/powerauth-test-server/src/main/java/com/wultra/security/powerauth/app/testserver/model/request/CreateTokenRequest.java @@ -17,6 +17,7 @@ */ package com.wultra.security.powerauth.app.testserver.model.request; +import jakarta.validation.constraints.NotBlank; import lombok.Data; /** @@ -26,9 +27,11 @@ */ @Data public class CreateTokenRequest { - + @NotBlank private String activationId; + @NotBlank private String applicationId; private String password; + @NotBlank private String signatureType; } diff --git a/powerauth-test-server/src/main/java/com/wultra/security/powerauth/app/testserver/model/request/GetOperationsRequest.java b/powerauth-test-server/src/main/java/com/wultra/security/powerauth/app/testserver/model/request/GetOperationsRequest.java index d71c3bf0..c259199f 100644 --- a/powerauth-test-server/src/main/java/com/wultra/security/powerauth/app/testserver/model/request/GetOperationsRequest.java +++ b/powerauth-test-server/src/main/java/com/wultra/security/powerauth/app/testserver/model/request/GetOperationsRequest.java @@ -17,6 +17,7 @@ */ package com.wultra.security.powerauth.app.testserver.model.request; +import jakarta.validation.constraints.NotBlank; import lombok.Data; /** @@ -26,9 +27,11 @@ */ @Data public class GetOperationsRequest { - + @NotBlank private String activationId; + @NotBlank private String tokenId; + @NotBlank private String tokenSecret; } diff --git a/powerauth-test-server/src/main/java/com/wultra/security/powerauth/app/testserver/model/request/OperationApproveInternalRequest.java b/powerauth-test-server/src/main/java/com/wultra/security/powerauth/app/testserver/model/request/OperationApproveInternalRequest.java index f17d2928..9bbeb496 100644 --- a/powerauth-test-server/src/main/java/com/wultra/security/powerauth/app/testserver/model/request/OperationApproveInternalRequest.java +++ b/powerauth-test-server/src/main/java/com/wultra/security/powerauth/app/testserver/model/request/OperationApproveInternalRequest.java @@ -17,6 +17,7 @@ */ package com.wultra.security.powerauth.app.testserver.model.request; +import jakarta.validation.constraints.NotBlank; import lombok.Data; /** @@ -26,11 +27,14 @@ */ @Data public class OperationApproveInternalRequest { - + @NotBlank private String activationId; + @NotBlank private String applicationId; private String password; + @NotBlank private String operationId; + @NotBlank private String operationData; } From 25de61ba7a2d85f857fd2bf823451482fedcf6f1 Mon Sep 17 00:00:00 2001 From: Lubos Racansky Date: Mon, 29 Jan 2024 07:07:03 +0100 Subject: [PATCH 12/44] Fix #344: Adapt refactoring of ProximityCheck from PowerAuth server --- .../powerauth/test/shared/PowerAuthSignatureShared.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/powerauth-backend-tests/src/test/java/com/wultra/security/powerauth/test/shared/PowerAuthSignatureShared.java b/powerauth-backend-tests/src/test/java/com/wultra/security/powerauth/test/shared/PowerAuthSignatureShared.java index 8b9a8b84..721a9d07 100644 --- a/powerauth-backend-tests/src/test/java/com/wultra/security/powerauth/test/shared/PowerAuthSignatureShared.java +++ b/powerauth-backend-tests/src/test/java/com/wultra/security/powerauth/test/shared/PowerAuthSignatureShared.java @@ -749,7 +749,7 @@ private static void testSignatureOfflinePersonalizedProximityCheck(final PowerAu final CreatePersonalizedOfflineSignaturePayloadRequest request = new CreatePersonalizedOfflineSignaturePayloadRequest(); request.setActivationId(config.getActivationId(version)); request.setData(offlineData); - request.setProximityCheck(new CreatePersonalizedOfflineSignaturePayloadRequest.ProximityCheck()); + request.setProximityCheck(new CreatePersonalizedOfflineSignaturePayloadRequest.CreateProximityCheck()); request.getProximityCheck().setSeed(seed); request.getProximityCheck().setStepLength(30); @@ -808,7 +808,7 @@ private static void testSignatureOfflinePersonalizedProximityCheck(final PowerAu verifyRequest.setData(signatureBaseString); verifyRequest.setSignature(signature); verifyRequest.setAllowBiometry(true); - verifyRequest.setProximityCheck(new VerifyOfflineSignatureRequest.ProximityCheck()); + verifyRequest.setProximityCheck(new VerifyOfflineSignatureRequest.VerifyProximityCheck()); verifyRequest.getProximityCheck().setSeed(expectedResult ? seed : "bGlnaHQgd28="); verifyRequest.getProximityCheck().setStepLength(30); verifyRequest.getProximityCheck().setStepCount(2); From 3dacd3f7c8034d034c6f751590839fc89894bc0c Mon Sep 17 00:00:00 2001 From: Lubos Racansky Date: Tue, 6 Feb 2024 07:33:24 +0100 Subject: [PATCH 13/44] Fix #351: Reflect changes of ConfirmRecoveryResponsePayload --- .../test/shared/PowerAuthApiShared.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/powerauth-backend-tests/src/test/java/com/wultra/security/powerauth/test/shared/PowerAuthApiShared.java b/powerauth-backend-tests/src/test/java/com/wultra/security/powerauth/test/shared/PowerAuthApiShared.java index a800bd07..4d5d5b1c 100644 --- a/powerauth-backend-tests/src/test/java/com/wultra/security/powerauth/test/shared/PowerAuthApiShared.java +++ b/powerauth-backend-tests/src/test/java/com/wultra/security/powerauth/test/shared/PowerAuthApiShared.java @@ -19,10 +19,10 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.wultra.security.powerauth.client.PowerAuthClient; -import com.wultra.security.powerauth.client.model.entity.*; -import com.wultra.security.powerauth.client.model.enumeration.*; +import com.wultra.security.powerauth.client.model.entity.SignatureAuditItem; +import com.wultra.security.powerauth.client.model.enumeration.ActivationStatus; +import com.wultra.security.powerauth.client.model.enumeration.SignatureType; import com.wultra.security.powerauth.client.model.error.PowerAuthClientException; -import com.wultra.security.powerauth.client.model.request.GetEciesDecryptorRequest; import com.wultra.security.powerauth.client.model.response.*; import com.wultra.security.powerauth.configuration.PowerAuthTestConfiguration; import io.getlime.security.powerauth.crypto.client.activation.PowerAuthClientActivation; @@ -33,15 +33,12 @@ import io.getlime.security.powerauth.crypto.lib.config.SignatureConfiguration; import io.getlime.security.powerauth.crypto.lib.encryptor.ClientEncryptor; import io.getlime.security.powerauth.crypto.lib.encryptor.EncryptorFactory; -import io.getlime.security.powerauth.crypto.lib.encryptor.ServerEncryptor; -import io.getlime.security.powerauth.crypto.lib.encryptor.ecies.EciesEnvelopeKey; import io.getlime.security.powerauth.crypto.lib.encryptor.exception.EncryptorException; import io.getlime.security.powerauth.crypto.lib.encryptor.model.EncryptedRequest; import io.getlime.security.powerauth.crypto.lib.encryptor.model.EncryptedResponse; import io.getlime.security.powerauth.crypto.lib.encryptor.model.EncryptorId; import io.getlime.security.powerauth.crypto.lib.encryptor.model.EncryptorParameters; import io.getlime.security.powerauth.crypto.lib.encryptor.model.v3.ClientEncryptorSecrets; -import io.getlime.security.powerauth.crypto.lib.encryptor.model.v3.ServerEncryptorSecrets; import io.getlime.security.powerauth.crypto.lib.enums.PowerAuthSignatureTypes; import io.getlime.security.powerauth.crypto.lib.generator.KeyGenerator; import io.getlime.security.powerauth.crypto.lib.model.exception.CryptoProviderException; @@ -73,7 +70,10 @@ import java.security.PrivateKey; import java.security.PublicKey; import java.security.spec.InvalidKeySpecException; -import java.util.*; +import java.util.Base64; +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.util.List; import static org.junit.jupiter.api.Assertions.*; @@ -270,8 +270,8 @@ public static void recoveryCodeConfirmAndActivationTest(PowerAuthClient powerAut confirmResponse.getNonce(), confirmResponse.getTimestamp() )); - ConfirmRecoveryResponsePayload confirmResponsePayload = RestClientConfiguration.defaultMapper().readValue(confirmResponseRaw, ConfirmRecoveryResponsePayload.class); - assertTrue(confirmResponsePayload.getAlreadyConfirmed()); + final ConfirmRecoveryResponsePayload confirmResponsePayload = RestClientConfiguration.defaultMapper().readValue(confirmResponseRaw, ConfirmRecoveryResponsePayload.class); + assertTrue(confirmResponsePayload.isAlreadyConfirmed()); // Create recovery activation KeyPair deviceKeyPairR = CLIENT_ACTIVATION.generateDeviceKeyPair(); byte[] devicePublicKeyBytesR = KEY_CONVERTOR.convertPublicKeyToBytes(deviceKeyPairR.getPublic()); From 40ab8aa3a57fb142ae4fa1f6fa6c2e9d39a71872 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Feb 2024 06:56:34 +0000 Subject: [PATCH 14/44] Bump io.gatling:gatling-maven-plugin from 4.7.0 to 4.8.0 Bumps [io.gatling:gatling-maven-plugin](https://github.com/gatling/gatling-maven-plugin) from 4.7.0 to 4.8.0. - [Commits](https://github.com/gatling/gatling-maven-plugin/compare/v4.7.0...v4.8.0) --- updated-dependencies: - dependency-name: io.gatling:gatling-maven-plugin dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- powerauth-load-tests/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/powerauth-load-tests/pom.xml b/powerauth-load-tests/pom.xml index 3f4693de..b9479b84 100644 --- a/powerauth-load-tests/pom.xml +++ b/powerauth-load-tests/pom.xml @@ -41,7 +41,7 @@ 2.13.6 2.13.6 - 4.7.0 + 4.8.0 4.8.1 3.10.3 From 0a925e58154bafd06f20cda89cdd7e9b84496e35 Mon Sep 17 00:00:00 2001 From: Jan Dusil <134381434+jandusil@users.noreply.github.com> Date: Tue, 6 Feb 2024 11:12:16 +0100 Subject: [PATCH 15/44] Fix #349: Remove spring.datasource.driverClassName from app props (#350) * Fix #349: Remove spring.datasource.driverClassName from app props --- powerauth-backend-tests/README.md | 2 -- powerauth-test-server/docker/powerauth-test-server.xml | 1 - powerauth-test-server/src/main/resources/application.properties | 2 -- 3 files changed, 5 deletions(-) diff --git a/powerauth-backend-tests/README.md b/powerauth-backend-tests/README.md index b6101ad7..0ec4490a 100644 --- a/powerauth-backend-tests/README.md +++ b/powerauth-backend-tests/README.md @@ -60,7 +60,6 @@ File `powerauth-java.server.xml`: - ``` @@ -71,7 +70,6 @@ File `enrollment-server.xml`: - diff --git a/powerauth-test-server/docker/powerauth-test-server.xml b/powerauth-test-server/docker/powerauth-test-server.xml index d2381332..3c901f65 100644 --- a/powerauth-test-server/docker/powerauth-test-server.xml +++ b/powerauth-test-server/docker/powerauth-test-server.xml @@ -14,7 +14,6 @@ - diff --git a/powerauth-test-server/src/main/resources/application.properties b/powerauth-test-server/src/main/resources/application.properties index 707d8f9c..302fb7bb 100644 --- a/powerauth-test-server/src/main/resources/application.properties +++ b/powerauth-test-server/src/main/resources/application.properties @@ -13,13 +13,11 @@ resultstatus.persistenceType=memory #spring.datasource.url=jdbc:h2:file:~/powerauth-test;DB_CLOSE_ON_EXIT=FALSE;AUTO_SERVER=TRUE #spring.datasource.username=sa #spring.datasource.password= -#spring.datasource.driver-class-name=org.h2.Driver # PostgreSQL configuration spring.datasource.url=jdbc:postgresql://localhost:5432/powerauth spring.datasource.username=powerauth spring.datasource.password= -spring.datasource.driver-class-name=org.postgresql.Driver spring.jpa.properties.hibernate.connection.CharSet=UTF-8 spring.jpa.properties.hibernate.connection.characterEncoding=UTF-8 spring.jpa.properties.hibernate.connection.useUnicode=true From 54c3bfe4ff754e3bd0cbcfe8a40f6874e6f561be Mon Sep 17 00:00:00 2001 From: Jan Dusil <134381434+jandusil@users.noreply.github.com> Date: Tue, 6 Feb 2024 11:19:28 +0100 Subject: [PATCH 16/44] Fix #347: Add TraceID/SpanID to Monitoring for Enhanced Observability (#348) --- powerauth-test-server/pom.xml | 19 +++++++++++++++++++ .../src/main/resources/application.properties | 3 +++ 2 files changed, 22 insertions(+) diff --git a/powerauth-test-server/pom.xml b/powerauth-test-server/pom.xml index af451f13..4eb75242 100644 --- a/powerauth-test-server/pom.xml +++ b/powerauth-test-server/pom.xml @@ -145,6 +145,25 @@ ${springdoc-openapi-starter-webmvc-ui.version} + + + io.projectreactor + reactor-core-micrometer + + + + io.micrometer + micrometer-tracing-bridge-otel + + + + + io.netty + netty-resolver-dns-native-macos + runtime + osx-aarch_64 + + diff --git a/powerauth-test-server/src/main/resources/application.properties b/powerauth-test-server/src/main/resources/application.properties index 302fb7bb..5c2a0822 100644 --- a/powerauth-test-server/src/main/resources/application.properties +++ b/powerauth-test-server/src/main/resources/application.properties @@ -34,3 +34,6 @@ banner.application.name=${spring.application.name} banner.application.version=@project.version@ logging.config=${POWERAUTH_TEST_SERVER_LOGGING:} + +# Monitoring +management.tracing.sampling.probability=1.0 From 09e8eb31b535d9f82d5367b6aab5d8472c9082fb Mon Sep 17 00:00:00 2001 From: Lubos Racansky Date: Thu, 8 Feb 2024 07:00:09 +0100 Subject: [PATCH 17/44] Fix #355: Update docker to ibm-semeru-runtimes:open-21.0.1_12-jre --- powerauth-test-server/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/powerauth-test-server/Dockerfile b/powerauth-test-server/Dockerfile index 68b351ae..c932e1af 100644 --- a/powerauth-test-server/Dockerfile +++ b/powerauth-test-server/Dockerfile @@ -1,4 +1,4 @@ -FROM ibm-semeru-runtimes:open-17.0.9_9-jre +FROM ibm-semeru-runtimes:open-21.0.1_12-jre LABEL maintainer="roman.strobl@wultra.com" # Prepare environment variables From dc108f7d8f18fac8a1e078d890bae2584749d0a7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Feb 2024 17:57:28 +0000 Subject: [PATCH 18/44] Bump io.gatling:gatling-maven-plugin from 4.8.0 to 4.8.2 Bumps [io.gatling:gatling-maven-plugin](https://github.com/gatling/gatling-maven-plugin) from 4.8.0 to 4.8.2. - [Commits](https://github.com/gatling/gatling-maven-plugin/compare/v4.8.0...v4.8.2) --- updated-dependencies: - dependency-name: io.gatling:gatling-maven-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- powerauth-load-tests/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/powerauth-load-tests/pom.xml b/powerauth-load-tests/pom.xml index b9479b84..2db83c9b 100644 --- a/powerauth-load-tests/pom.xml +++ b/powerauth-load-tests/pom.xml @@ -41,7 +41,7 @@ 2.13.6 2.13.6 - 4.8.0 + 4.8.2 4.8.1 3.10.3 From aa5dc22fc17433b3853caaf28df22b7b32a6bdab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zden=C4=9Bk=20=C4=8Cern=C3=BD?= Date: Wed, 14 Feb 2024 13:37:22 +0100 Subject: [PATCH 19/44] Fix #358: Add Deployment of docker image to JFrog (#359) * Fix #358: Add Deployment of docker image to JFrog --- ...evelop_pa-test-internal-testserver-app.yml | 41 +++++++++++++++---- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/.github/workflows/develop_pa-test-internal-testserver-app.yml b/.github/workflows/develop_pa-test-internal-testserver-app.yml index 427b5c25..43dfa49b 100644 --- a/.github/workflows/develop_pa-test-internal-testserver-app.yml +++ b/.github/workflows/develop_pa-test-internal-testserver-app.yml @@ -1,14 +1,19 @@ # Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy # More GitHub Actions for Azure: https://github.com/Azure/actions -name: Build and deploy container app to Azure Web App - pa-test-internal-testserver-app +name: Build and deploy container app to Container Registries on: push: branches: - develop workflow_dispatch: - + inputs: + jfrog_deploy: + type: boolean + description: Check if build image should be uploaded to JFrog + default: false + required: false jobs: build: runs-on: 'ubuntu-latest' @@ -18,7 +23,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-java@v3 with: - java-version: '17' + java-version: '21' distribution: 'temurin' server-id: jfrog-central server-username: INTERNAL_USERNAME @@ -42,9 +47,9 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 - - name: Log in to registry + - name: Log in to ACR if: ${{ github.actor != 'dependabot[bot]' }} - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: https://powerauthextendedtest.azurecr.io/ username: ${{ secrets.AZUREAPPSERVICE_CONTAINERUSERNAME }} @@ -53,12 +58,32 @@ jobs: run: | cd powerauth-test-server ./copy_liquibase.sh - - name: Build and push container image to registry - uses: docker/build-push-action@v2 + - name: Build and push container image to ACR + uses: docker/build-push-action@v5 with: push: ${{ github.actor != 'dependabot[bot]' }} + platforms: linux/amd64,linux/arm64 tags: powerauthextendedtest.azurecr.io/powerauth-test-server:${{ env.REVISION }}${{ env.TIMESTAMP }}-${{ github.sha }} file: ./powerauth-test-server/Dockerfile context: ./powerauth-test-server/ - + cache-from: type=gha + cache-to: type=gha,mode=max + - name: Log in to JFrog + if: ${{ github.event_name == 'workflow_dispatch' && inputs.jfrog_deploy == true }} + uses: docker/login-action@v3 + with: + registry: https://wultra.jfrog.io/ + username: ${{ secrets.JFROG_CONTAINER_REGISTRY_USERNAME }} + password: ${{ secrets.JFROG_CONTAINER_REGISTRY_PASSWORD }} + - name: Build and push container image to JFrog + if: ${{ github.event_name == 'workflow_dispatch' && inputs.jfrog_deploy == true }} + uses: docker/build-push-action@v5 + with: + push: ${{ github.event_name == 'workflow_dispatch' && inputs.jfrog_deploy == true }} + platforms: linux/amd64,linux/arm64 + tags: wultra.jfrog.io/wultra-docker/powerauth-test-server:${{ env.REVISION }}${{ env.TIMESTAMP }}-${{ github.sha }} + file: ./powerauth-test-server/Dockerfile + context: ./powerauth-test-server/ + cache-from: type=gha + cache-to: type=gha,mode=max From 928138e40e0862378d1c58e36ee96b4eb219f711 Mon Sep 17 00:00:00 2001 From: Martin Korbel <112644829+korbelm@users.noreply.github.com> Date: Wed, 14 Feb 2024 17:21:23 +0100 Subject: [PATCH 20/44] Remove timestamp from docker build if branch != develop --- .github/workflows/develop_pa-test-internal-testserver-app.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/develop_pa-test-internal-testserver-app.yml b/.github/workflows/develop_pa-test-internal-testserver-app.yml index 43dfa49b..d267f53a 100644 --- a/.github/workflows/develop_pa-test-internal-testserver-app.yml +++ b/.github/workflows/develop_pa-test-internal-testserver-app.yml @@ -29,7 +29,8 @@ jobs: server-username: INTERNAL_USERNAME server-password: INTERNAL_PASSWORD cache: maven - - name: Set Timestamp for docker image tag + - name: Set Timestamp for docker image for development branch + if: github.ref == 'refs/heads/develop' run: echo "TIMESTAMP=-$(date +%Y.%m.%d)" >> $GITHUB_ENV - name: Get Powerauth Test Server version run: | From 2b2ab30704c0e4062152be5a58c2c87f20de8769 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zden=C4=9Bk=20=C4=8Cern=C3=BD?= Date: Wed, 14 Feb 2024 18:15:25 +0100 Subject: [PATCH 21/44] Fix #361: Simplify tag for JFrog release --- .github/workflows/develop_pa-test-internal-testserver-app.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/develop_pa-test-internal-testserver-app.yml b/.github/workflows/develop_pa-test-internal-testserver-app.yml index d267f53a..73433c51 100644 --- a/.github/workflows/develop_pa-test-internal-testserver-app.yml +++ b/.github/workflows/develop_pa-test-internal-testserver-app.yml @@ -82,7 +82,7 @@ jobs: with: push: ${{ github.event_name == 'workflow_dispatch' && inputs.jfrog_deploy == true }} platforms: linux/amd64,linux/arm64 - tags: wultra.jfrog.io/wultra-docker/powerauth-test-server:${{ env.REVISION }}${{ env.TIMESTAMP }}-${{ github.sha }} + tags: wultra.jfrog.io/wultra-docker/powerauth-test-server:${{ env.REVISION }}${{ env.TIMESTAMP }} file: ./powerauth-test-server/Dockerfile context: ./powerauth-test-server/ cache-from: type=gha From 463197008cde528ca06a2d065161f3530904a500 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Feb 2024 17:10:22 +0000 Subject: [PATCH 22/44] Bump io.gatling.highcharts:gatling-charts-highcharts Bumps [io.gatling.highcharts:gatling-charts-highcharts](https://github.com/gatling/gatling-highcharts) from 3.10.3 to 3.10.4. - [Commits](https://github.com/gatling/gatling-highcharts/compare/v3.10.3...v3.10.4) --- updated-dependencies: - dependency-name: io.gatling.highcharts:gatling-charts-highcharts dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- powerauth-load-tests/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/powerauth-load-tests/pom.xml b/powerauth-load-tests/pom.xml index 2db83c9b..0ec41053 100644 --- a/powerauth-load-tests/pom.xml +++ b/powerauth-load-tests/pom.xml @@ -44,7 +44,7 @@ 4.8.2 4.8.1 - 3.10.3 + 3.10.4 From bbd4bf8c5edc6f462e577e41ad15fd3c9f4deaf5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 23 Feb 2024 06:01:59 +0000 Subject: [PATCH 23/44] Bump org.springframework.boot:spring-boot-starter-parent Bumps [org.springframework.boot:spring-boot-starter-parent](https://github.com/spring-projects/spring-boot) from 3.2.2 to 3.2.3. - [Release notes](https://github.com/spring-projects/spring-boot/releases) - [Commits](https://github.com/spring-projects/spring-boot/compare/v3.2.2...v3.2.3) --- updated-dependencies: - dependency-name: org.springframework.boot:spring-boot-starter-parent dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index cf368e3e..b1523cea 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ org.springframework.boot spring-boot-starter-parent - 3.2.2 + 3.2.3 From ab02627cf9d0c573f7f1ff14bd3f710c692d0628 Mon Sep 17 00:00:00 2001 From: Lubos Racansky Date: Fri, 23 Feb 2024 09:14:06 +0100 Subject: [PATCH 24/44] Fix #371: Update Tomcat to 10.1.19 --- powerauth-test-server/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/powerauth-test-server/Dockerfile b/powerauth-test-server/Dockerfile index c932e1af..f22e5577 100644 --- a/powerauth-test-server/Dockerfile +++ b/powerauth-test-server/Dockerfile @@ -8,7 +8,7 @@ ENV JAVA_HOME=/opt/java/openjdk \ LB_VERSION=4.23.2 \ TOMCAT_HOME=/usr/local/tomcat \ TOMCAT_MAJOR=10 \ - TOMCAT_VERSION=10.1.17 \ + TOMCAT_VERSION=10.1.19 \ LOGBACK_CONF=/opt/logback/conf \ TZ=UTC @@ -21,7 +21,7 @@ RUN apt-get -y update \ # Install tomcat RUN curl -jkSL -o /tmp/apache-tomcat.tar.gz http://archive.apache.org/dist/tomcat/tomcat-${TOMCAT_MAJOR}/v${TOMCAT_VERSION}/bin/apache-tomcat-${TOMCAT_VERSION}.tar.gz \ - && [ "ff9670f9cd49a604e47edfbcfb5855fe59342048c3278ea8736276b51327adf2d076973f3ad1b8aa7870ef26c28cf7111527be810b445c9927f2a457795f5cb6 /tmp/apache-tomcat.tar.gz" = "$(sha512sum /tmp/apache-tomcat.tar.gz)" ] \ + && [ "7264da6196a510b0bba74469d215d61a464331302239256477f78b6bec067f7f4d90f671b96a440061ae0e20d16b1be8ca1dbd547dab9927383366dbc677f590 /tmp/apache-tomcat.tar.gz" = "$(sha512sum /tmp/apache-tomcat.tar.gz)" ] \ && gunzip /tmp/apache-tomcat.tar.gz \ && tar -C /opt -xf /tmp/apache-tomcat.tar \ && ln -s /opt/apache-tomcat-$TOMCAT_VERSION $TOMCAT_HOME From 4eccce5281c292a5388a0ea18bc3366de8a6ce7e Mon Sep 17 00:00:00 2001 From: Lubos Racansky Date: Fri, 23 Feb 2024 09:18:09 +0100 Subject: [PATCH 25/44] Fix #373: Update docker to ibm-semeru-runtimes:open-21.0.2_13-jre --- powerauth-test-server/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/powerauth-test-server/Dockerfile b/powerauth-test-server/Dockerfile index c932e1af..c5fe4a0e 100644 --- a/powerauth-test-server/Dockerfile +++ b/powerauth-test-server/Dockerfile @@ -1,4 +1,4 @@ -FROM ibm-semeru-runtimes:open-21.0.1_12-jre +FROM ibm-semeru-runtimes:open-21.0.2_13-jre LABEL maintainer="roman.strobl@wultra.com" # Prepare environment variables From 2c186535180b89c29f6c7889fddbe7a8bfb41c69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Pe=C5=A1ek?= Date: Tue, 5 Mar 2024 15:41:06 +0100 Subject: [PATCH 26/44] Fix #377: FIDO2: Demo application structure (#378) --- .run/PowerauthFido2TestApplication.run.xml | 14 +++ docs-private/Developer-How-To-Start.md | 9 ++ pom.xml | 1 + powerauth-fido2-tests/pom.xml | 85 +++++++++++++++++++ .../fido2/PowerauthFido2TestApplication.java | 36 ++++++++ .../powerauth/fido2/ServletInitializer.java | 36 ++++++++ .../fido2/controller/HomeController.java | 39 +++++++++ .../src/main/resources/application.properties | 8 ++ .../src/main/resources/banner.txt | 8 ++ .../src/main/resources/templates/home.html | 28 ++++++ 10 files changed, 264 insertions(+) create mode 100644 .run/PowerauthFido2TestApplication.run.xml create mode 100644 powerauth-fido2-tests/pom.xml create mode 100644 powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/PowerauthFido2TestApplication.java create mode 100644 powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/ServletInitializer.java create mode 100644 powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/HomeController.java create mode 100644 powerauth-fido2-tests/src/main/resources/application.properties create mode 100644 powerauth-fido2-tests/src/main/resources/banner.txt create mode 100644 powerauth-fido2-tests/src/main/resources/templates/home.html diff --git a/.run/PowerauthFido2TestApplication.run.xml b/.run/PowerauthFido2TestApplication.run.xml new file mode 100644 index 00000000..3bd0c80f --- /dev/null +++ b/.run/PowerauthFido2TestApplication.run.xml @@ -0,0 +1,14 @@ + + + + \ No newline at end of file diff --git a/docs-private/Developer-How-To-Start.md b/docs-private/Developer-How-To-Start.md index 5c5376d2..6a476e01 100644 --- a/docs-private/Developer-How-To-Start.md +++ b/docs-private/Developer-How-To-Start.md @@ -18,3 +18,12 @@ Others (like URL, username, password) depend on your environment. ```shell liquibase --changelog-file=./docs/db/changelog/changesets/powerauth-test-server/db.changelog-module.xml --url=jdbc:postgresql://localhost:5432/powerauth --username=powerauth status ``` + +## PowerAuth FIDO2 Tests + +### Standalone Run + +- Enable maven profile `standalone` +- Use IntelliJ Idea run configuration at `../.run/PowerAuthFido2TestApplication.run.xml` +- Open [http://localhost:8083/powerauth-fido2-test/actuator/health](http://localhost:8083/powerauth-fido2-test/actuator/health) and you should get `{"status":"UP"}` + diff --git a/pom.xml b/pom.xml index b1523cea..1625a4ea 100644 --- a/pom.xml +++ b/pom.xml @@ -61,6 +61,7 @@ powerauth-backend-tests + powerauth-fido2-tests powerauth-load-tests powerauth-test-server powerauth-webflow-tests diff --git a/powerauth-fido2-tests/pom.xml b/powerauth-fido2-tests/pom.xml new file mode 100644 index 00000000..eb264cee --- /dev/null +++ b/powerauth-fido2-tests/pom.xml @@ -0,0 +1,85 @@ + + + 4.0.0 + + powerauth-fido2-tests + PowerAuth FIDO2 Test Web Application + powerauth-fido2-tests + war + + + com.wultra + powerauth-backend-tests-parent + 1.7.0-SNAPSHOT + + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + + + + + + test-repository + + + !useInternalRepo + + + + + + + + org.apache.maven.plugins + maven-deploy-plugin + + true + + + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + build-info + + build-info + + + + + + + org.projectlombok + lombok + + + + + + + + diff --git a/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/PowerauthFido2TestApplication.java b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/PowerauthFido2TestApplication.java new file mode 100644 index 00000000..b2db5780 --- /dev/null +++ b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/PowerauthFido2TestApplication.java @@ -0,0 +1,36 @@ +/* + * PowerAuth Server and related software components + * Copyright (C) 2024 Wultra s.r.o. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.wultra.security.powerauth.fido2; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * Spring Boot application main class + * + * @author Jan Pesek, jan.pesek@wultra.com + */ +@SpringBootApplication +public class PowerauthFido2TestApplication { + + public static void main(String[] args) { + SpringApplication.run(PowerauthFido2TestApplication.class, args); + } + +} diff --git a/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/ServletInitializer.java b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/ServletInitializer.java new file mode 100644 index 00000000..bab1f101 --- /dev/null +++ b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/ServletInitializer.java @@ -0,0 +1,36 @@ +/* + * PowerAuth Server and related software components + * Copyright (C) 2024 Wultra s.r.o. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.wultra.security.powerauth.fido2; + +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; + +/** + * Spring Boot servlet initializer + * + * @author Jan Pesek, jan.pesek@wultra.com + */ +public class ServletInitializer extends SpringBootServletInitializer { + + @Override + protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { + return application.sources(PowerauthFido2TestApplication.class); + } + +} diff --git a/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/HomeController.java b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/HomeController.java new file mode 100644 index 00000000..4faa3df3 --- /dev/null +++ b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/HomeController.java @@ -0,0 +1,39 @@ +/* + * PowerAuth Server and related software components + * Copyright (C) 2024 Wultra s.r.o. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.wultra.security.powerauth.fido2.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; + +import java.util.Map; + +/** + * Controller to display initial web page + * + * @author Jan Pesek, jan.pesek@wultra.com + */ +@Controller +public class HomeController { + + @GetMapping + public String homePage(Map model) { + return "home"; + } + +} diff --git a/powerauth-fido2-tests/src/main/resources/application.properties b/powerauth-fido2-tests/src/main/resources/application.properties new file mode 100644 index 00000000..28a27b71 --- /dev/null +++ b/powerauth-fido2-tests/src/main/resources/application.properties @@ -0,0 +1,8 @@ + +# Application Service Configuration +powerauth.fido2.test.service.applicationName=powerauth-fido2-tests +powerauth.fido2.test.service.applicationDisplayName=PowerAuth FIDO2 Test +powerauth.fido2.test.service.applicationEnvironment= + +banner.application.name=${powerauth.fido2.test.service.applicationName} +banner.application.version=@project.version@ diff --git a/powerauth-fido2-tests/src/main/resources/banner.txt b/powerauth-fido2-tests/src/main/resources/banner.txt new file mode 100644 index 00000000..875a0a65 --- /dev/null +++ b/powerauth-fido2-tests/src/main/resources/banner.txt @@ -0,0 +1,8 @@ + ___ _ _ _ ___ ___ ___ ___ ___ _____ _ + | _ \_____ __ _____ _ _ /_\ _ _| |_| |_ | __|_ _| \ / _ \_ ) |_ _|__ __| |_ + | _/ _ \ V V / -_) '_/ _ \ || | _| ' \ | _| | || |) | (_) / / | |/ -_|_-< _| + |_| \___/\_/\_/\___|_|/_/ \_\_,_|\__|_||_| |_| |___|___/ \___/___| |_|\___/__/\__| + +${AnsiColor.GREEN} :: ${banner.application.name} (${banner.application.version}) :: ${AnsiColor.GREEN} +${AnsiColor.RED} :: Spring Boot${spring-boot.formatted-version} :: ${AnsiColor.RED} +${AnsiColor.DEFAULT} diff --git a/powerauth-fido2-tests/src/main/resources/templates/home.html b/powerauth-fido2-tests/src/main/resources/templates/home.html new file mode 100644 index 00000000..c0d1c2ac --- /dev/null +++ b/powerauth-fido2-tests/src/main/resources/templates/home.html @@ -0,0 +1,28 @@ + + + + + + + Title + + +Placeholder for a register / authenticate page. + + \ No newline at end of file From af359102091f17e93283fc33f4d3098a93d3862a Mon Sep 17 00:00:00 2001 From: Lubos Racansky Date: Fri, 23 Feb 2024 09:04:17 +0100 Subject: [PATCH 27/44] Fix #365: Set release version to 1.7.0 --- pom.xml | 2 +- powerauth-backend-tests/pom.xml | 2 +- powerauth-load-tests/pom.xml | 2 +- powerauth-test-server/pom.xml | 2 +- powerauth-webflow-tests/pom.xml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 1625a4ea..67fc79d9 100644 --- a/pom.xml +++ b/pom.xml @@ -14,7 +14,7 @@ com.wultra powerauth-backend-tests-parent - 1.7.0-SNAPSHOT + 1.7.0 pom Parent pom for backend tests diff --git a/powerauth-backend-tests/pom.xml b/powerauth-backend-tests/pom.xml index 66dc818f..cd78b524 100644 --- a/powerauth-backend-tests/pom.xml +++ b/powerauth-backend-tests/pom.xml @@ -8,7 +8,7 @@ com.wultra powerauth-backend-tests-parent - 1.7.0-SNAPSHOT + 1.7.0 com.wultra diff --git a/powerauth-load-tests/pom.xml b/powerauth-load-tests/pom.xml index 0ec41053..e2de609e 100644 --- a/powerauth-load-tests/pom.xml +++ b/powerauth-load-tests/pom.xml @@ -6,7 +6,7 @@ com.wultra powerauth-backend-tests-parent - 1.7.0-SNAPSHOT + 1.7.0 com.wultra diff --git a/powerauth-test-server/pom.xml b/powerauth-test-server/pom.xml index 4eb75242..f72896e2 100644 --- a/powerauth-test-server/pom.xml +++ b/powerauth-test-server/pom.xml @@ -23,7 +23,7 @@ com.wultra powerauth-backend-tests-parent - 1.7.0-SNAPSHOT + 1.7.0 powerauth-test-server diff --git a/powerauth-webflow-tests/pom.xml b/powerauth-webflow-tests/pom.xml index b36d9a7c..bd85cbdb 100644 --- a/powerauth-webflow-tests/pom.xml +++ b/powerauth-webflow-tests/pom.xml @@ -8,7 +8,7 @@ com.wultra powerauth-backend-tests-parent - 1.7.0-SNAPSHOT + 1.7.0 com.wultra From 9dfc5b321a3eae97953506e3d5e88eedc3068a4c Mon Sep 17 00:00:00 2001 From: Lubos Racansky Date: Wed, 6 Mar 2024 08:54:35 +0100 Subject: [PATCH 28/44] Set release version for powerauth-fido2-tests --- powerauth-fido2-tests/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/powerauth-fido2-tests/pom.xml b/powerauth-fido2-tests/pom.xml index eb264cee..93e83bce 100644 --- a/powerauth-fido2-tests/pom.xml +++ b/powerauth-fido2-tests/pom.xml @@ -11,7 +11,7 @@ com.wultra powerauth-backend-tests-parent - 1.7.0-SNAPSHOT + 1.7.0 From e2d1c916962a6a1cd8c85ed23f615180008c451e Mon Sep 17 00:00:00 2001 From: Lubos Racansky Date: Wed, 6 Mar 2024 09:15:03 +0100 Subject: [PATCH 29/44] Fix #382: Build with JDK 21 Fails --- .github/workflows/maven-deploy.yml | 1 + .github/workflows/maven-integration-test.yml | 4 ++-- .github/workflows/maven-test-compile.yml | 2 +- powerauth-load-tests/pom.xml | 5 ++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/maven-deploy.yml b/.github/workflows/maven-deploy.yml index 32b001e5..f098c09d 100644 --- a/.github/workflows/maven-deploy.yml +++ b/.github/workflows/maven-deploy.yml @@ -16,6 +16,7 @@ jobs: environment: internal-publish release_type: snapshot directory_path: ./powerauth-test-server + java_version: 21 secrets: username: ${{ secrets.MAVEN_CENTRAL_USERNAME }} password: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} diff --git a/.github/workflows/maven-integration-test.yml b/.github/workflows/maven-integration-test.yml index cdbefeac..c0161b53 100644 --- a/.github/workflows/maven-integration-test.yml +++ b/.github/workflows/maven-integration-test.yml @@ -22,10 +22,10 @@ jobs: environment: ${{ inputs.environment || 'dev' }} steps: - uses: actions/checkout@v3 - - name: Set up JDK 17 + - name: Set up JDK 21 uses: actions/setup-java@v3 with: - java-version: 17 + java-version: 21 distribution: 'temurin' server-id: jfrog-central server-username: INTERNAL_USERNAME diff --git a/.github/workflows/maven-test-compile.yml b/.github/workflows/maven-test-compile.yml index ceeaeb0a..84b057d1 100644 --- a/.github/workflows/maven-test-compile.yml +++ b/.github/workflows/maven-test-compile.yml @@ -22,7 +22,7 @@ jobs: - name: Set up JDK ${{ inputs.java_version }} uses: actions/setup-java@v3 with: - java-version: '17' + java-version: '21' distribution: 'temurin' server-id: jfrog-central server-username: INTERNAL_USERNAME diff --git a/powerauth-load-tests/pom.xml b/powerauth-load-tests/pom.xml index 0ec41053..857369de 100644 --- a/powerauth-load-tests/pom.xml +++ b/powerauth-load-tests/pom.xml @@ -38,8 +38,7 @@ - 2.13.6 - 2.13.6 + 2.13.13 4.8.2 4.8.1 @@ -83,7 +82,7 @@ -Xss100M - -target:jvm-1.8 + -release:17 -deprecation -feature -unchecked From 70b62fc928b10f7de2579e2f49640dbe3f9efe2a Mon Sep 17 00:00:00 2001 From: Lubos Racansky Date: Wed, 6 Mar 2024 09:45:12 +0100 Subject: [PATCH 30/44] Update GitHub Actions to checkout@v4, setup-java@v4, action-junit-report@v4 --- .../workflows/develop_pa-test-internal-testserver-app.yml | 4 ++-- .github/workflows/maven-integration-test.yml | 6 +++--- .github/workflows/maven-test-compile.yml | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/develop_pa-test-internal-testserver-app.yml b/.github/workflows/develop_pa-test-internal-testserver-app.yml index 73433c51..19de3f95 100644 --- a/.github/workflows/develop_pa-test-internal-testserver-app.yml +++ b/.github/workflows/develop_pa-test-internal-testserver-app.yml @@ -20,8 +20,8 @@ jobs: environment: test steps: - - uses: actions/checkout@v3 - - uses: actions/setup-java@v3 + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 with: java-version: '21' distribution: 'temurin' diff --git a/.github/workflows/maven-integration-test.yml b/.github/workflows/maven-integration-test.yml index c0161b53..78fb9c0f 100644 --- a/.github/workflows/maven-integration-test.yml +++ b/.github/workflows/maven-integration-test.yml @@ -21,9 +21,9 @@ jobs: runs-on: ubuntu-latest environment: ${{ inputs.environment || 'dev' }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up JDK 21 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: 21 distribution: 'temurin' @@ -46,7 +46,7 @@ jobs: POWERAUTH_SERVICE_SECURITY_CLIENTSECRET: ${{ secrets.POWERAUTH_SERVICE_SECURITY_CLIENTSECRET }} POWERAUTH_TEST_INCLUDECUSTOMTESTS: ${{ inputs.includeCustomTests == '' || inputs.includeCustomTests }} # default includeCustomTests=true even for 'schedule' event - name: Publish Test Report - uses: mikepenz/action-junit-report@v3 + uses: mikepenz/action-junit-report@v4 if: always() with: detailed_summary: true diff --git a/.github/workflows/maven-test-compile.yml b/.github/workflows/maven-test-compile.yml index 84b057d1..313c7283 100644 --- a/.github/workflows/maven-test-compile.yml +++ b/.github/workflows/maven-test-compile.yml @@ -18,9 +18,9 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up JDK ${{ inputs.java_version }} - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: '21' distribution: 'temurin' From 50662f8006234bfefe07b6c5b25ff6590c7dfe21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Pe=C5=A1ek?= Date: Wed, 6 Mar 2024 13:03:37 +0100 Subject: [PATCH 31/44] Issues/380 fido2 config (#381) --- lombok.config | 1 + pom.xml | 1 + powerauth-fido2-tests/pom.xml | 7 +++ .../PowerAuthConfigProperties.java | 40 ++++++++++++ .../PowerAuthWebServiceConfiguration.java | 62 +++++++++++++++++++ .../configuration/WebAuthnConfiguration.java | 43 +++++++++++++ .../main/resources/application-dev.properties | 9 +++ .../src/main/resources/application.properties | 11 ++++ powerauth-test-server/lombok.config | 1 - 9 files changed, 174 insertions(+), 1 deletion(-) create mode 100644 lombok.config create mode 100644 powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/configuration/PowerAuthConfigProperties.java create mode 100644 powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/configuration/PowerAuthWebServiceConfiguration.java create mode 100644 powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/configuration/WebAuthnConfiguration.java create mode 100644 powerauth-fido2-tests/src/main/resources/application-dev.properties delete mode 100644 powerauth-test-server/lombok.config diff --git a/lombok.config b/lombok.config new file mode 100644 index 00000000..1bb49ca0 --- /dev/null +++ b/lombok.config @@ -0,0 +1 @@ +lombok.log.fieldName=logger \ No newline at end of file diff --git a/pom.xml b/pom.xml index 1625a4ea..355b026a 100644 --- a/pom.xml +++ b/pom.xml @@ -49,6 +49,7 @@ 1.7.0-SNAPSHOT 1.7.0-SNAPSHOT 1.7.0-SNAPSHOT + 1.7.0-SNAPSHOT 1.7.0-SNAPSHOT 1.9.0-SNAPSHOT diff --git a/powerauth-fido2-tests/pom.xml b/powerauth-fido2-tests/pom.xml index eb264cee..a28fa9a7 100644 --- a/powerauth-fido2-tests/pom.xml +++ b/powerauth-fido2-tests/pom.xml @@ -30,6 +30,13 @@ spring-boot-starter-thymeleaf + + + io.getlime.security + powerauth-rest-client-spring + ${powerauth-server.version} + + diff --git a/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/configuration/PowerAuthConfigProperties.java b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/configuration/PowerAuthConfigProperties.java new file mode 100644 index 00000000..3e4d5e9a --- /dev/null +++ b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/configuration/PowerAuthConfigProperties.java @@ -0,0 +1,40 @@ +/* + * PowerAuth test and related software components + * Copyright (C) 2024 Wultra s.r.o. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.wultra.security.powerauth.fido2.configuration; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** + * PowerAuth clients configuration properties. + * + * @author Jan Pesek, jan.pesek@wultra.com + */ +@Configuration +@ConfigurationProperties(prefix = "powerauth.service") +@Data +public class PowerAuthConfigProperties { + + private String baseUrl; + private SecurityProperties security; + + public record SecurityProperties(String clientToken, String clientSecret) {} + +} diff --git a/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/configuration/PowerAuthWebServiceConfiguration.java b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/configuration/PowerAuthWebServiceConfiguration.java new file mode 100644 index 00000000..0b92e757 --- /dev/null +++ b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/configuration/PowerAuthWebServiceConfiguration.java @@ -0,0 +1,62 @@ +/* + * PowerAuth test and related software components + * Copyright (C) 2024 Wultra s.r.o. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.wultra.security.powerauth.fido2.configuration; + +import com.wultra.security.powerauth.client.PowerAuthClient; +import com.wultra.security.powerauth.client.PowerAuthFido2Client; +import com.wultra.security.powerauth.client.model.error.PowerAuthClientException; +import com.wultra.security.powerauth.rest.client.PowerAuthFido2RestClient; +import com.wultra.security.powerauth.rest.client.PowerAuthRestClient; +import com.wultra.security.powerauth.rest.client.PowerAuthRestClientConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.util.StringUtils; + +/** + * PowerAuth service configuration class. + * + * @author Jan Pesek, jan.pesek@wultra.com + */ +@Configuration +public class PowerAuthWebServiceConfiguration { + + @Bean + public PowerAuthClient powerAuthClient(final PowerAuthConfigProperties properties) throws PowerAuthClientException { + final String powerAuthServiceUrl = properties.getBaseUrl() + "/rest"; + if (StringUtils.hasText(properties.getSecurity().clientToken())) { + final PowerAuthRestClientConfiguration config = new PowerAuthRestClientConfiguration(); + config.setPowerAuthClientToken(properties.getSecurity().clientToken()); + config.setPowerAuthClientSecret(properties.getSecurity().clientSecret()); + return new PowerAuthRestClient(powerAuthServiceUrl, config); + } + return new PowerAuthRestClient(powerAuthServiceUrl); + } + + @Bean + public PowerAuthFido2Client powerAuthFido2Client(final PowerAuthConfigProperties properties) throws PowerAuthClientException { + if (StringUtils.hasText(properties.getSecurity().clientToken())) { + final PowerAuthRestClientConfiguration config = new PowerAuthRestClientConfiguration(); + config.setPowerAuthClientToken(properties.getSecurity().clientToken()); + config.setPowerAuthClientSecret(properties.getSecurity().clientSecret()); + return new PowerAuthFido2RestClient(properties.getBaseUrl(), config); + } + return new PowerAuthFido2RestClient(properties.getBaseUrl()); + } + +} diff --git a/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/configuration/WebAuthnConfiguration.java b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/configuration/WebAuthnConfiguration.java new file mode 100644 index 00000000..d8bbf579 --- /dev/null +++ b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/configuration/WebAuthnConfiguration.java @@ -0,0 +1,43 @@ +/* + * PowerAuth test and related software components + * Copyright (C) 2024 Wultra s.r.o. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.wultra.security.powerauth.fido2.configuration; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import java.time.Duration; +import java.util.List; + +/** + * WebAuthn configuration properties. + * + * @author Jan Pesek, jan.pesek@wultra.com + */ +@Configuration +@ConfigurationProperties(prefix = "powerauth.webauthn") +@Data +public class WebAuthnConfiguration { + + private String rpId; + private String rpName; + private Duration timeout; + private List allowedOrigins; + +} diff --git a/powerauth-fido2-tests/src/main/resources/application-dev.properties b/powerauth-fido2-tests/src/main/resources/application-dev.properties new file mode 100644 index 00000000..c44e0c2b --- /dev/null +++ b/powerauth-fido2-tests/src/main/resources/application-dev.properties @@ -0,0 +1,9 @@ + +# WebAuthn properties configuration +powerauth.webauthn.rpId=localhost +powerauth.webauthn.rpName=Local Development +powerauth.webauthn.allowedOrigins=http://localhost:8083 + +logging.level.com.wultra.*=DEBUG + + diff --git a/powerauth-fido2-tests/src/main/resources/application.properties b/powerauth-fido2-tests/src/main/resources/application.properties index 28a27b71..3d5cd9e1 100644 --- a/powerauth-fido2-tests/src/main/resources/application.properties +++ b/powerauth-fido2-tests/src/main/resources/application.properties @@ -1,4 +1,15 @@ +# PowerAuth service configuration +powerauth.service.baseUrl=http://localhost:8080/powerauth-java-server +powerauth.service.security.clientToken= +powerauth.service.security.clientSecret= + +# WebAuthn properties configuration +powerauth.webauthn.rpId= +powerauth.webauthn.rpName= +powerauth.webauthn.timeout=60s +powerauth.webauthn.allowedOrigins= + # Application Service Configuration powerauth.fido2.test.service.applicationName=powerauth-fido2-tests powerauth.fido2.test.service.applicationDisplayName=PowerAuth FIDO2 Test diff --git a/powerauth-test-server/lombok.config b/powerauth-test-server/lombok.config deleted file mode 100644 index 2bb794fd..00000000 --- a/powerauth-test-server/lombok.config +++ /dev/null @@ -1 +0,0 @@ -lombok.log.fieldName=logger From 425f979cb78142f971bca08d9ed734afb76d6a67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Pe=C5=A1ek?= Date: Fri, 8 Mar 2024 08:50:24 +0100 Subject: [PATCH 32/44] Fix #379: FIDO2 Test application: Backend (#385) --- pom.xml | 1 + powerauth-fido2-tests/pom.xml | 11 ++ .../fido2/controller/AssertionController.java | 60 +++++++++ .../controller/DefaultExceptionHandler.java | 51 +++++++ .../controller/RegistrationController.java | 58 ++++++++ .../request/AssertionOptionsRequest.java | 40 ++++++ .../request/RegisterCredentialRequest.java | 52 ++++++++ .../request/RegistrationOptionsRequest.java | 34 +++++ .../request/VerifyAssertionRequest.java | 51 +++++++ .../response/AssertionOptionsResponse.java | 38 ++++++ .../response/CredentialDescriptor.java | 35 +++++ .../response/RegistrationOptionsResponse.java | 41 ++++++ .../fido2/service/AssertionService.java | 124 ++++++++++++++++++ .../fido2/service/Fido2SharedService.java | 106 +++++++++++++++ .../fido2/service/RegistrationService.java | 116 ++++++++++++++++ 15 files changed, 818 insertions(+) create mode 100644 powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/AssertionController.java create mode 100644 powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/DefaultExceptionHandler.java create mode 100644 powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/RegistrationController.java create mode 100644 powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/request/AssertionOptionsRequest.java create mode 100644 powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/request/RegisterCredentialRequest.java create mode 100644 powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/request/RegistrationOptionsRequest.java create mode 100644 powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/request/VerifyAssertionRequest.java create mode 100644 powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/response/AssertionOptionsResponse.java create mode 100644 powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/response/CredentialDescriptor.java create mode 100644 powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/response/RegistrationOptionsResponse.java create mode 100644 powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/AssertionService.java create mode 100644 powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/Fido2SharedService.java create mode 100644 powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/RegistrationService.java diff --git a/pom.xml b/pom.xml index 355b026a..fb167e85 100644 --- a/pom.xml +++ b/pom.xml @@ -56,6 +56,7 @@ 1.77 2.3.0 7.4 + 0.22.2.RELEASE true diff --git a/powerauth-fido2-tests/pom.xml b/powerauth-fido2-tests/pom.xml index a28fa9a7..224a60e3 100644 --- a/powerauth-fido2-tests/pom.xml +++ b/powerauth-fido2-tests/pom.xml @@ -29,6 +29,10 @@ org.springframework.boot spring-boot-starter-thymeleaf + + org.springframework.boot + spring-boot-starter-validation + @@ -37,6 +41,13 @@ ${powerauth-server.version} + + + com.webauthn4j + webauthn4j-core + ${webauthn4j.version} + + diff --git a/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/AssertionController.java b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/AssertionController.java new file mode 100644 index 00000000..828bf55b --- /dev/null +++ b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/AssertionController.java @@ -0,0 +1,60 @@ +/* + * PowerAuth test and related software components + * Copyright (C) 2024 Wultra s.r.o. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.wultra.security.powerauth.fido2.controller; + +import com.wultra.security.powerauth.client.model.error.PowerAuthClientException; +import com.wultra.security.powerauth.client.model.response.fido2.AssertionVerificationResponse; +import com.wultra.security.powerauth.fido2.controller.request.AssertionOptionsRequest; +import com.wultra.security.powerauth.fido2.controller.request.VerifyAssertionRequest; +import com.wultra.security.powerauth.fido2.controller.response.AssertionOptionsResponse; +import com.wultra.security.powerauth.fido2.service.AssertionService; +import jakarta.validation.Valid; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * WebAuthn assertion ceremony controller. + * + * @author Jan Pesek, jan.pesek@wultra.com + */ +@Validated +@RestController +@RequestMapping("/assertion") +@AllArgsConstructor +@Slf4j +public class AssertionController { + + final AssertionService assertionService; + + @PostMapping("/options") + public AssertionOptionsResponse options(@Valid @RequestBody final AssertionOptionsRequest request) throws PowerAuthClientException { + return assertionService.assertionOptions(request); + } + + @PostMapping + public AssertionVerificationResponse verify(@Valid @RequestBody final VerifyAssertionRequest request) throws PowerAuthClientException { + return assertionService.authenticate(request); + } + +} diff --git a/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/DefaultExceptionHandler.java b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/DefaultExceptionHandler.java new file mode 100644 index 00000000..24c7f795 --- /dev/null +++ b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/DefaultExceptionHandler.java @@ -0,0 +1,51 @@ +/* + * PowerAuth test and related software components + * Copyright (C) 2024 Wultra s.r.o. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.wultra.security.powerauth.fido2.controller; + +import com.wultra.security.powerauth.client.model.error.PowerAuthError; +import io.getlime.core.rest.model.base.response.ObjectResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.ResponseStatus; + +/** + * Controller to handle exceptions. + * + * @author Jan Pesek, jan.pesek@wultra.com + */ +@ControllerAdvice +@Slf4j +public class DefaultExceptionHandler { + + @ExceptionHandler + @ResponseStatus(HttpStatus.BAD_REQUEST) + public @ResponseBody ObjectResponse handleErrors(Exception ex) { + logger.error("Error occurred while processing the request: {}", ex.getMessage()); + logger.debug("Exception details:", ex); + final PowerAuthError error = new PowerAuthError(); + error.setCode("ERROR"); + error.setMessage(ex.getMessage()); + error.setLocalizedMessage(ex.getLocalizedMessage()); + return new ObjectResponse<>("ERROR", error); + } + +} diff --git a/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/RegistrationController.java b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/RegistrationController.java new file mode 100644 index 00000000..ce2e535d --- /dev/null +++ b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/RegistrationController.java @@ -0,0 +1,58 @@ +/* + * PowerAuth test and related software components + * Copyright (C) 2024 Wultra s.r.o. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.wultra.security.powerauth.fido2.controller; + +import com.wultra.security.powerauth.client.model.error.PowerAuthClientException; +import com.wultra.security.powerauth.client.model.response.fido2.RegistrationResponse; +import com.wultra.security.powerauth.fido2.controller.request.RegisterCredentialRequest; +import com.wultra.security.powerauth.fido2.controller.request.RegistrationOptionsRequest; +import com.wultra.security.powerauth.fido2.controller.response.RegistrationOptionsResponse; +import com.wultra.security.powerauth.fido2.service.RegistrationService; +import jakarta.validation.Valid; +import lombok.AllArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * WebAuthn registration ceremony controller. + * + * @author Jan Pesek, jan.pesek@wultra.com + */ +@Validated +@RestController +@RequestMapping("/registration") +@AllArgsConstructor +public class RegistrationController { + + private final RegistrationService registrationService; + + @PostMapping("/options") + public RegistrationOptionsResponse options(@Valid @RequestBody final RegistrationOptionsRequest request) throws PowerAuthClientException { + return registrationService.registerOptions(request); + } + + @PostMapping + public RegistrationResponse register(@Valid @RequestBody final RegisterCredentialRequest request) throws PowerAuthClientException { + return registrationService.register(request); + } + +} diff --git a/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/request/AssertionOptionsRequest.java b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/request/AssertionOptionsRequest.java new file mode 100644 index 00000000..ebd79cfc --- /dev/null +++ b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/request/AssertionOptionsRequest.java @@ -0,0 +1,40 @@ +/* + * PowerAuth test and related software components + * Copyright (C) 2024 Wultra s.r.o. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.wultra.security.powerauth.fido2.controller.request; + +import jakarta.validation.constraints.NotBlank; + +import java.util.Map; + +/** + * Request for credential assertion options. + * + * @author Jan Pesek, jan.pesek@wultra.com + */ +public record AssertionOptionsRequest ( + String username, + + @NotBlank + String applicationId, + + @NotBlank + String templateName, + + Map operationParameters +) { } diff --git a/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/request/RegisterCredentialRequest.java b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/request/RegisterCredentialRequest.java new file mode 100644 index 00000000..982cd845 --- /dev/null +++ b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/request/RegisterCredentialRequest.java @@ -0,0 +1,52 @@ +/* + * PowerAuth test and related software components + * Copyright (C) 2024 Wultra s.r.o. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.wultra.security.powerauth.fido2.controller.request; + +import com.webauthn4j.data.AuthenticatorAttachment; +import com.webauthn4j.data.PublicKeyCredentialType; +import com.wultra.security.powerauth.client.model.entity.fido2.AuthenticatorAttestationResponse; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +/** + * Request for register credentials. + * + * @author Jan Pesek, jan.pesek@wultra.com + */ +public record RegisterCredentialRequest ( + @NotBlank + String applicationId, + + @NotBlank + String username, + + boolean userVerificationRequired, + + @NotBlank + String id, + + @NotNull + PublicKeyCredentialType type, + + @NotNull + AuthenticatorAttachment authenticatorAttachment, + + @NotNull + AuthenticatorAttestationResponse response +) {} diff --git a/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/request/RegistrationOptionsRequest.java b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/request/RegistrationOptionsRequest.java new file mode 100644 index 00000000..dd5726e9 --- /dev/null +++ b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/request/RegistrationOptionsRequest.java @@ -0,0 +1,34 @@ +/* + * PowerAuth test and related software components + * Copyright (C) 2024 Wultra s.r.o. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.wultra.security.powerauth.fido2.controller.request; + +import jakarta.validation.constraints.NotBlank; + +/** + * Request for credential registration options. + * + * @author Jan Pesek, jan.pesek@wultra.com + */ +public record RegistrationOptionsRequest( + @NotBlank + String username, + + @NotBlank + String applicationId +) {} diff --git a/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/request/VerifyAssertionRequest.java b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/request/VerifyAssertionRequest.java new file mode 100644 index 00000000..695960d9 --- /dev/null +++ b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/request/VerifyAssertionRequest.java @@ -0,0 +1,51 @@ +/* + * PowerAuth test and related software components + * Copyright (C) 2024 Wultra s.r.o. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.wultra.security.powerauth.fido2.controller.request; + +import com.webauthn4j.data.AuthenticatorAttachment; +import com.webauthn4j.data.PublicKeyCredentialType; +import com.wultra.security.powerauth.client.model.entity.fido2.AuthenticatorAssertionResponse; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +/** + * Request for verify credential. + * + * @author Jan Pesek, jan.pesek@wultra.com + */ +public record VerifyAssertionRequest( + @NotBlank + String applicationId, + + @NotBlank + String id, + + @NotNull + PublicKeyCredentialType type, + + @NotNull + AuthenticatorAttachment authenticatorAttachment, + + @NotNull + AuthenticatorAssertionResponse response, + + String expectedChallenge, + + boolean userVerificationRequired +) {} diff --git a/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/response/AssertionOptionsResponse.java b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/response/AssertionOptionsResponse.java new file mode 100644 index 00000000..cb6e4425 --- /dev/null +++ b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/response/AssertionOptionsResponse.java @@ -0,0 +1,38 @@ +/* + * PowerAuth test and related software components + * Copyright (C) 2024 Wultra s.r.o. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.wultra.security.powerauth.fido2.controller.response; + +import lombok.Builder; + +import java.util.List; +import java.util.Map; + +/** + * Public key credential assertion options. + * + * @author Jan Pesek, jan.pesek@wultra.com + */ +@Builder +public record AssertionOptionsResponse( + String rpId, + String challenge, + Long timeout, + List allowCredentials, + Map extensions +) {} diff --git a/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/response/CredentialDescriptor.java b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/response/CredentialDescriptor.java new file mode 100644 index 00000000..c79711e4 --- /dev/null +++ b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/response/CredentialDescriptor.java @@ -0,0 +1,35 @@ +/* + * PowerAuth test and related software components + * Copyright (C) 2024 Wultra s.r.o. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.wultra.security.powerauth.fido2.controller.response; + +import com.webauthn4j.data.AuthenticatorTransport; +import com.webauthn4j.data.PublicKeyCredentialType; + +import java.util.List; + +/** + * Structure used in allowCredentials and excludeCredentials. + * + * @author Jan Pesek, jan.pesek@wultra.com + */ +public record CredentialDescriptor( + PublicKeyCredentialType type, + String id, + List transports +) {} diff --git a/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/response/RegistrationOptionsResponse.java b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/response/RegistrationOptionsResponse.java new file mode 100644 index 00000000..227e0e66 --- /dev/null +++ b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/response/RegistrationOptionsResponse.java @@ -0,0 +1,41 @@ +/* + * PowerAuth test and related software components + * Copyright (C) 2024 Wultra s.r.o. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.wultra.security.powerauth.fido2.controller.response; + +import com.webauthn4j.data.PublicKeyCredentialParameters; +import com.webauthn4j.data.PublicKeyCredentialRpEntity; +import com.webauthn4j.data.PublicKeyCredentialUserEntity; +import lombok.Builder; + +import java.util.List; + +/** + * Public key credential creation options. + * + * @author Jan Pesek, jan.pesek@wultra.com + */ +@Builder +public record RegistrationOptionsResponse( + PublicKeyCredentialRpEntity rp, + PublicKeyCredentialUserEntity user, + String challenge, + List pubKeyCredParams, + Long timeout, + List excludeCredentials +) {} diff --git a/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/AssertionService.java b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/AssertionService.java new file mode 100644 index 00000000..5d295c04 --- /dev/null +++ b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/AssertionService.java @@ -0,0 +1,124 @@ +/* + * PowerAuth test and related software components + * Copyright (C) 2024 Wultra s.r.o. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.wultra.security.powerauth.fido2.service; + +import com.wultra.security.powerauth.client.PowerAuthFido2Client; +import com.wultra.security.powerauth.client.model.error.PowerAuthClientException; +import com.wultra.security.powerauth.client.model.request.fido2.AssertionChallengeRequest; +import com.wultra.security.powerauth.client.model.request.fido2.AssertionVerificationRequest; +import com.wultra.security.powerauth.client.model.response.fido2.AssertionChallengeResponse; +import com.wultra.security.powerauth.client.model.response.fido2.AssertionVerificationResponse; +import com.wultra.security.powerauth.fido2.configuration.WebAuthnConfiguration; +import com.wultra.security.powerauth.fido2.controller.request.AssertionOptionsRequest; +import com.wultra.security.powerauth.fido2.controller.request.VerifyAssertionRequest; +import com.wultra.security.powerauth.fido2.controller.response.AssertionOptionsResponse; +import com.wultra.security.powerauth.fido2.controller.response.CredentialDescriptor; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.util.List; +import java.util.Map; + +/** + * Service for WebAuthn authentication tasks. + * + * @author Jan Pesek, jan.pesek@wultra.com + */ +@Service +@AllArgsConstructor +@Slf4j +public class AssertionService { + + private static final String OPERATION_DATA_EXTENSION_KEY = "txAuthSimple"; + + private final PowerAuthFido2Client fido2Client; + private final Fido2SharedService fido2SharedService; + private final WebAuthnConfiguration webAuthNConfig; + + /** + * Build public key assertion options. + * @param request Request form with user input. + * @return Public key assertion options. + * @throws PowerAuthClientException in case of PowerAuth server communication error. + */ + public AssertionOptionsResponse assertionOptions(final AssertionOptionsRequest request) throws PowerAuthClientException { + final String userId = request.username(); + final String applicationId = request.applicationId(); + + logger.info("Building assertion options for userId={}, applicationId={}", userId, applicationId); + + final List existingCredentials = fido2SharedService.fetchExistingCredentials(userId, applicationId); + if (existingCredentials.isEmpty() && StringUtils.hasText(userId)) { + throw new IllegalStateException("Not registered yet."); + } + + final AssertionChallengeResponse challengeResponse = fetchChallenge(applicationId, request.templateName(), request.operationParameters()); + final String challenge = challengeResponse.getChallenge(); + final String operationData = extractOperationData(challenge); + + return AssertionOptionsResponse.builder() + .challenge(challenge) + .rpId(webAuthNConfig.getRpId()) + .timeout(webAuthNConfig.getTimeout().toMillis()) + .allowCredentials(existingCredentials) + .extensions(Map.of(OPERATION_DATA_EXTENSION_KEY, operationData)) + .build(); + } + + /** + * Verify credential at PowerAuth server. + * @param credential Received public key credential. + * @return PowerAuth authentication response. + * @throws PowerAuthClientException in case of PowerAuth server communication error. + */ + public AssertionVerificationResponse authenticate(final VerifyAssertionRequest credential) throws PowerAuthClientException { + final AssertionVerificationRequest request = new AssertionVerificationRequest(); + request.setId(credential.id()); + request.setType(credential.type().getValue()); + request.setAuthenticatorAttachment(credential.authenticatorAttachment().getValue()); + request.setResponse(credential.response()); + request.setApplicationId(credential.applicationId()); + request.setExpectedChallenge(credential.expectedChallenge()); + request.setRelyingPartyId(webAuthNConfig.getRpId()); + request.setAllowedOrigins(webAuthNConfig.getAllowedOrigins()); + request.setRequiresUserVerification(credential.userVerificationRequired()); + return fido2Client.authenticate(request); + } + + private AssertionChallengeResponse fetchChallenge(final String applicationId, final String templateName, final Map operationParameters) throws PowerAuthClientException { + final AssertionChallengeRequest request = new AssertionChallengeRequest(); + request.setApplicationIds(List.of(applicationId)); + request.setTemplateName(templateName); + if (operationParameters != null) { + request.setParameters(operationParameters); + } + return fido2Client.requestAssertionChallenge(request); + } + + private static String extractOperationData(final String challenge) { + final String[] split = challenge.split("&", 2); + if (split.length != 2) { + throw new IllegalStateException("Invalid challenge."); + } + return split[1]; + } + +} diff --git a/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/Fido2SharedService.java b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/Fido2SharedService.java new file mode 100644 index 00000000..bfab372e --- /dev/null +++ b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/Fido2SharedService.java @@ -0,0 +1,106 @@ +/* + * PowerAuth test and related software components + * Copyright (C) 2024 Wultra s.r.o. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.wultra.security.powerauth.fido2.service; + +import com.webauthn4j.data.AuthenticatorTransport; +import com.webauthn4j.data.PublicKeyCredentialType; +import com.wultra.security.powerauth.client.PowerAuthClient; +import com.wultra.security.powerauth.client.PowerAuthFido2Client; +import com.wultra.security.powerauth.client.model.entity.Application; +import com.wultra.security.powerauth.client.model.entity.fido2.AuthenticatorDetail; +import com.wultra.security.powerauth.client.model.error.PowerAuthClientException; +import com.wultra.security.powerauth.client.model.response.OperationTemplateDetailResponse; +import com.wultra.security.powerauth.fido2.controller.response.CredentialDescriptor; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.util.Collections; +import java.util.List; + +/** + * Service shared for registration and authentication. + * + * @author Jan Pesek, jan.pesek@wultra.com + */ +@Service +@AllArgsConstructor +@Slf4j +public class Fido2SharedService { + + private static final String EXTRAS_TRANSPORT_KEY = "transports"; + private static final PublicKeyCredentialType CREDENTIAL_TYPE = PublicKeyCredentialType.PUBLIC_KEY; + + private final PowerAuthFido2Client fido2Client; + private final PowerAuthClient powerAuthClient; + + /** + * Fetch all registered credentials. + * @param userId User to whom the credentials belong. + * @param applicationId Of the used application. + * @return List of credentials. + * @throws PowerAuthClientException if there is an error in PowerAuth communication. + */ + public List fetchExistingCredentials(final String userId, final String applicationId) throws PowerAuthClientException { + if (!StringUtils.hasText(userId) || !StringUtils.hasText(applicationId)) { + return Collections.emptyList(); + } + + return listAuthenticators(userId, applicationId).stream() + .map(Fido2SharedService::toCredentialDescriptor) + .toList(); + } + + /** + * Fetch list of all existing applications. + * @return List of application ids. + * @throws PowerAuthClientException if there is an error in PowerAuth communication. + */ + public List fetchApplicationNameList() throws PowerAuthClientException { + return powerAuthClient.getApplicationList().getApplications() + .stream() + .map(Application::getApplicationId) + .sorted().toList(); + } + + /** + * Fetch all existing operation templates. + * @return List of operation template names. + * @throws PowerAuthClientException if there is an error in PowerAuth communication. + */ + public List fetchTemplateNameList() throws PowerAuthClientException { + return powerAuthClient.operationTemplateList() + .stream() + .map(OperationTemplateDetailResponse::getTemplateName) + .sorted().toList(); + } + + private List listAuthenticators(final String userId, final String applicationId) throws PowerAuthClientException { + return fido2Client.getRegisteredAuthenticatorList(userId, applicationId).getAuthenticators(); + } + + @SuppressWarnings("unchecked") + private static CredentialDescriptor toCredentialDescriptor(final AuthenticatorDetail authenticatorDetail) { + final String credentialId = authenticatorDetail.getExternalId(); + final List transports = (List) authenticatorDetail.getExtras().getOrDefault(EXTRAS_TRANSPORT_KEY, Collections.emptyList()); + return new CredentialDescriptor(CREDENTIAL_TYPE, credentialId, transports); + } + +} diff --git a/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/RegistrationService.java b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/RegistrationService.java new file mode 100644 index 00000000..e8ce075e --- /dev/null +++ b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/RegistrationService.java @@ -0,0 +1,116 @@ +/* + * PowerAuth test and related software components + * Copyright (C) 2024 Wultra s.r.o. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.wultra.security.powerauth.fido2.service; + +import com.webauthn4j.data.PublicKeyCredentialParameters; +import com.webauthn4j.data.PublicKeyCredentialRpEntity; +import com.webauthn4j.data.PublicKeyCredentialType; +import com.webauthn4j.data.PublicKeyCredentialUserEntity; +import com.webauthn4j.data.attestation.statement.COSEAlgorithmIdentifier; +import com.wultra.security.powerauth.client.PowerAuthFido2Client; +import com.wultra.security.powerauth.client.model.entity.fido2.AuthenticatorParameters; +import com.wultra.security.powerauth.client.model.error.PowerAuthClientException; +import com.wultra.security.powerauth.client.model.request.fido2.RegistrationRequest; +import com.wultra.security.powerauth.client.model.response.fido2.RegistrationChallengeResponse; +import com.wultra.security.powerauth.client.model.response.fido2.RegistrationResponse; +import com.wultra.security.powerauth.fido2.configuration.WebAuthnConfiguration; +import com.wultra.security.powerauth.fido2.controller.request.RegisterCredentialRequest; +import com.wultra.security.powerauth.fido2.controller.request.RegistrationOptionsRequest; +import com.wultra.security.powerauth.fido2.controller.response.RegistrationOptionsResponse; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * Service for WebAuthn registration tasks. + * + * @author Jan Pesek, jan.pesek@wultra.com + */ +@Service +@AllArgsConstructor +@Slf4j +public class RegistrationService { + + private final PowerAuthFido2Client fido2Client; + private final Fido2SharedService fido2SharedService; + private final WebAuthnConfiguration webAuthNConfig; + + /** + * Build public key registration options. + * @param request Request form with user input. + * @return Public key registration options. + * @throws PowerAuthClientException in case of PowerAuth server communication error. + */ + public RegistrationOptionsResponse registerOptions(final RegistrationOptionsRequest request) throws PowerAuthClientException { + final String userId = request.username(); + final String applicationId = request.applicationId(); + + final RegistrationChallengeResponse challengeResponse = fetchChallenge(userId, applicationId); + + logger.info("Building registration options for userId={}, applicationId={}", userId, applicationId); + return RegistrationOptionsResponse.builder() + .rp(new PublicKeyCredentialRpEntity(webAuthNConfig.getRpId(), webAuthNConfig.getRpName())) + .user(new PublicKeyCredentialUserEntity(userId.getBytes(), userId, userId)) + .challenge(challengeResponse.getChallenge()) + .pubKeyCredParams(List.of( + new PublicKeyCredentialParameters(PublicKeyCredentialType.PUBLIC_KEY, COSEAlgorithmIdentifier.ES256) + )) + .timeout(webAuthNConfig.getTimeout().toMillis()) + .excludeCredentials(fido2SharedService.fetchExistingCredentials(userId, applicationId)) + .build(); + } + + /** + * Register credential at PowerAuth server. + * @param credential Newly created public key credential + * @return PowerAuth registration response. + * @throws PowerAuthClientException in case of PowerAuth server communication error. + */ + public RegistrationResponse register(final RegisterCredentialRequest credential) throws PowerAuthClientException { + logger.info("Registering created credential of userId={}, applicationId={}", credential.username(), credential.applicationId()); + + final RegistrationRequest request = new RegistrationRequest(); + request.setActivationName(credential.username()); + request.setApplicationId(credential.applicationId()); + request.setAuthenticatorParameters(buildAuthenticatorParameters(credential)); + return fido2Client.register(request); + } + + private RegistrationChallengeResponse fetchChallenge(final String userId, final String applicationId) throws PowerAuthClientException { + logger.info("Getting registration challenge for user {}, applicationId={}", userId, applicationId); + final RegistrationChallengeResponse response = fido2Client.requestRegistrationChallenge(userId, applicationId); + logger.debug("Got response={}", response); + return response; + } + + private AuthenticatorParameters buildAuthenticatorParameters(final RegisterCredentialRequest credential) { + final AuthenticatorParameters parameters = new AuthenticatorParameters(); + parameters.setId(credential.id()); + parameters.setAuthenticatorAttachment(credential.authenticatorAttachment().getValue()); + parameters.setType(credential.type().getValue()); + parameters.setResponse(credential.response()); + parameters.setAllowedOrigins(webAuthNConfig.getAllowedOrigins()); + parameters.setRelyingPartyId(webAuthNConfig.getRpId()); + parameters.setRequiresUserVerification(credential.userVerificationRequired()); + return parameters; + } + +} From 15fa8817c9db055d9fdcf219b13b8b29dc2538c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Pe=C5=A1ek?= Date: Wed, 13 Mar 2024 09:00:37 +0100 Subject: [PATCH 33/44] Fix #386: FIDO2 Test application: Frontend (#387) --- powerauth-fido2-tests/pom.xml | 14 + .../fido2/controller/AssertionController.java | 10 +- .../fido2/controller/HomeController.java | 61 +- .../fido2/service/AssertionService.java | 19 +- .../fido2/service/RegistrationService.java | 10 +- .../src/main/resources/templates/error.html | 26 + .../src/main/resources/templates/home.html | 28 - .../src/main/resources/templates/login.html | 101 ++ .../src/main/resources/templates/payment.html | 85 + .../webapp/resources/css/wultra-login.css | 83 + .../webapp/resources/css/wultra-theme.min.css | 1448 +++++++++++++++++ .../webapp/resources/images/background.png | Bin 0 -> 194687 bytes .../main/webapp/resources/images/favicon.png | Bin 0 -> 12429 bytes .../src/main/webapp/resources/images/logo.png | Bin 0 -> 9896 bytes .../main/webapp/resources/js/jquery.min.js | 2 + .../src/main/webapp/resources/js/login.js | 85 + .../src/main/webapp/resources/js/payment.js | 101 ++ .../src/main/webapp/resources/js/webauthn.js | 263 +++ 18 files changed, 2295 insertions(+), 41 deletions(-) create mode 100644 powerauth-fido2-tests/src/main/resources/templates/error.html delete mode 100644 powerauth-fido2-tests/src/main/resources/templates/home.html create mode 100644 powerauth-fido2-tests/src/main/resources/templates/login.html create mode 100644 powerauth-fido2-tests/src/main/resources/templates/payment.html create mode 100644 powerauth-fido2-tests/src/main/webapp/resources/css/wultra-login.css create mode 100644 powerauth-fido2-tests/src/main/webapp/resources/css/wultra-theme.min.css create mode 100644 powerauth-fido2-tests/src/main/webapp/resources/images/background.png create mode 100644 powerauth-fido2-tests/src/main/webapp/resources/images/favicon.png create mode 100644 powerauth-fido2-tests/src/main/webapp/resources/images/logo.png create mode 100644 powerauth-fido2-tests/src/main/webapp/resources/js/jquery.min.js create mode 100644 powerauth-fido2-tests/src/main/webapp/resources/js/login.js create mode 100644 powerauth-fido2-tests/src/main/webapp/resources/js/payment.js create mode 100644 powerauth-fido2-tests/src/main/webapp/resources/js/webauthn.js diff --git a/powerauth-fido2-tests/pom.xml b/powerauth-fido2-tests/pom.xml index 224a60e3..d5298ecf 100644 --- a/powerauth-fido2-tests/pom.xml +++ b/powerauth-fido2-tests/pom.xml @@ -47,6 +47,20 @@ webauthn4j-core ${webauthn4j.version} + + + + io.netty + netty-resolver-dns-native-macos + runtime + osx-aarch_64 + + + + + net.logstash.logback + logstash-logback-encoder + diff --git a/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/AssertionController.java b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/AssertionController.java index 828bf55b..6eece2be 100644 --- a/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/AssertionController.java +++ b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/AssertionController.java @@ -24,6 +24,7 @@ import com.wultra.security.powerauth.fido2.controller.request.VerifyAssertionRequest; import com.wultra.security.powerauth.fido2.controller.response.AssertionOptionsResponse; import com.wultra.security.powerauth.fido2.service.AssertionService; +import jakarta.servlet.http.HttpSession; import jakarta.validation.Valid; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -53,8 +54,13 @@ public AssertionOptionsResponse options(@Valid @RequestBody final AssertionOptio } @PostMapping - public AssertionVerificationResponse verify(@Valid @RequestBody final VerifyAssertionRequest request) throws PowerAuthClientException { - return assertionService.authenticate(request); + public AssertionVerificationResponse verify(@Valid @RequestBody final VerifyAssertionRequest request, final HttpSession session) throws PowerAuthClientException { + final AssertionVerificationResponse response = assertionService.authenticate(request); + if (response.isAssertionValid()) { + session.setAttribute("username", response.getUserId()); + session.setAttribute("applicationId", response.getApplicationId()); + } + return response; } } diff --git a/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/HomeController.java b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/HomeController.java index 4faa3df3..d4274fac 100644 --- a/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/HomeController.java +++ b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/HomeController.java @@ -18,8 +18,16 @@ package com.wultra.security.powerauth.fido2.controller; +import com.wultra.security.powerauth.client.model.error.PowerAuthClientException; +import com.wultra.security.powerauth.fido2.service.Fido2SharedService; +import jakarta.servlet.ServletContext; +import jakarta.servlet.http.HttpSession; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Controller; +import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; import java.util.Map; @@ -29,11 +37,58 @@ * @author Jan Pesek, jan.pesek@wultra.com */ @Controller +@AllArgsConstructor +@Slf4j public class HomeController { - @GetMapping - public String homePage(Map model) { - return "home"; + private static final String SESSION_KEY_USERNAME = "username"; + private static final String SESSION_KEY_APPLICATION_ID = "applicationId"; + private static final String REDIRECT_LOGIN = "redirect:login"; + private static final String REDIRECT_PAYMENT = "redirect:payment"; + private static final String LOGIN_PAGE = "login"; + private static final String PAYMENT_PAGE = "payment"; + + private final Fido2SharedService sharedService; + private final ServletContext context; + + @ModelAttribute + public void addAttributes(Map model) { + model.put("servletContextPath", context.getContextPath()); + } + + @GetMapping("/") + public String homePage(Map model, HttpSession session) { + if (StringUtils.hasText((String) session.getAttribute(SESSION_KEY_USERNAME))) { + return REDIRECT_PAYMENT; + } + return REDIRECT_LOGIN; + } + + @GetMapping("/login") + public String loginPage(Map model) throws PowerAuthClientException { + model.put("applications", sharedService.fetchApplicationNameList()); + model.put("templates", sharedService.fetchTemplateNameList()); + return LOGIN_PAGE; + } + + @GetMapping("/payment") + public String profilePage(Map model, HttpSession session) throws PowerAuthClientException { + final String username = (String) session.getAttribute(SESSION_KEY_USERNAME); + final String applicationId = (String) session.getAttribute(SESSION_KEY_APPLICATION_ID); + if (!StringUtils.hasText(username)) { + return REDIRECT_LOGIN; + } + + model.put(SESSION_KEY_USERNAME, username); + model.put(SESSION_KEY_APPLICATION_ID, applicationId); + model.put("templates", sharedService.fetchTemplateNameList()); + return PAYMENT_PAGE; + } + + @GetMapping("/logout") + public String logoutPage(Map model, HttpSession session) { + session.removeAttribute(SESSION_KEY_USERNAME); + return REDIRECT_LOGIN; } } diff --git a/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/AssertionService.java b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/AssertionService.java index 5d295c04..4bcbe43e 100644 --- a/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/AssertionService.java +++ b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/AssertionService.java @@ -67,10 +67,11 @@ public AssertionOptionsResponse assertionOptions(final AssertionOptionsRequest r final List existingCredentials = fido2SharedService.fetchExistingCredentials(userId, applicationId); if (existingCredentials.isEmpty() && StringUtils.hasText(userId)) { + logger.info("User {} is not yet registered.", userId); throw new IllegalStateException("Not registered yet."); } - final AssertionChallengeResponse challengeResponse = fetchChallenge(applicationId, request.templateName(), request.operationParameters()); + final AssertionChallengeResponse challengeResponse = fetchChallenge(userId, applicationId, request.templateName(), request.operationParameters()); final String challenge = challengeResponse.getChallenge(); final String operationData = extractOperationData(challenge); @@ -100,23 +101,31 @@ public AssertionVerificationResponse authenticate(final VerifyAssertionRequest c request.setRelyingPartyId(webAuthNConfig.getRpId()); request.setAllowedOrigins(webAuthNConfig.getAllowedOrigins()); request.setRequiresUserVerification(credential.userVerificationRequired()); - return fido2Client.authenticate(request); + + final AssertionVerificationResponse response = fido2Client.authenticate(request); + logger.debug("Credential assertion response of userId={}: {}", response.getUserId(), response); + logger.info("Activation ID {} of userId={}: valid={}", response.getActivationId(), response.getUserId(), response.isAssertionValid()); + + return response; } - private AssertionChallengeResponse fetchChallenge(final String applicationId, final String templateName, final Map operationParameters) throws PowerAuthClientException { + private AssertionChallengeResponse fetchChallenge(final String userId, final String applicationId, final String templateName, final Map operationParameters) throws PowerAuthClientException { + logger.info("Getting registration challenge for userId={}, applicationId={}, template={}, parameters={}", userId, applicationId, templateName, operationParameters); final AssertionChallengeRequest request = new AssertionChallengeRequest(); request.setApplicationIds(List.of(applicationId)); request.setTemplateName(templateName); if (operationParameters != null) { request.setParameters(operationParameters); } - return fido2Client.requestAssertionChallenge(request); + final AssertionChallengeResponse response = fido2Client.requestAssertionChallenge(request); + logger.debug("Assertion challenge response for userId={}: {}", userId, response); + return response; } private static String extractOperationData(final String challenge) { final String[] split = challenge.split("&", 2); if (split.length != 2) { - throw new IllegalStateException("Invalid challenge."); + throw new IllegalStateException("Invalid challenge format."); } return split[1]; } diff --git a/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/RegistrationService.java b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/RegistrationService.java index e8ce075e..53242040 100644 --- a/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/RegistrationService.java +++ b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/RegistrationService.java @@ -91,13 +91,17 @@ public RegistrationResponse register(final RegisterCredentialRequest credential) request.setActivationName(credential.username()); request.setApplicationId(credential.applicationId()); request.setAuthenticatorParameters(buildAuthenticatorParameters(credential)); - return fido2Client.register(request); + + final RegistrationResponse response = fido2Client.register(request); + logger.debug("Credential registration response of userId={}: {}", credential.username(), response); + logger.info("Activation ID {} of userId={}: status={}", response.getActivationId(), response.getUserId(), response.getActivationStatus()); + return response; } private RegistrationChallengeResponse fetchChallenge(final String userId, final String applicationId) throws PowerAuthClientException { - logger.info("Getting registration challenge for user {}, applicationId={}", userId, applicationId); + logger.info("Getting registration challenge for userId={}, applicationId={}", userId, applicationId); final RegistrationChallengeResponse response = fido2Client.requestRegistrationChallenge(userId, applicationId); - logger.debug("Got response={}", response); + logger.debug("Registration challenge response for userId={}: {}", userId, response); return response; } diff --git a/powerauth-fido2-tests/src/main/resources/templates/error.html b/powerauth-fido2-tests/src/main/resources/templates/error.html new file mode 100644 index 00000000..fb9cd680 --- /dev/null +++ b/powerauth-fido2-tests/src/main/resources/templates/error.html @@ -0,0 +1,26 @@ + + + + + ERROR + + + +
+ +
+

Error

+
+ +
+

Unexpected error occurred. Is the PowerAuth Server reachable?

+

+
+

+
+
+ +
+ + + \ No newline at end of file diff --git a/powerauth-fido2-tests/src/main/resources/templates/home.html b/powerauth-fido2-tests/src/main/resources/templates/home.html deleted file mode 100644 index c0d1c2ac..00000000 --- a/powerauth-fido2-tests/src/main/resources/templates/home.html +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - Title - - -Placeholder for a register / authenticate page. - - \ No newline at end of file diff --git a/powerauth-fido2-tests/src/main/resources/templates/login.html b/powerauth-fido2-tests/src/main/resources/templates/login.html new file mode 100644 index 00000000..570b33fe --- /dev/null +++ b/powerauth-fido2-tests/src/main/resources/templates/login.html @@ -0,0 +1,101 @@ + + + + + Login + + + + + + + + + + + + +
+
+
+ + +
+
+
+ + \ No newline at end of file diff --git a/powerauth-fido2-tests/src/main/resources/templates/payment.html b/powerauth-fido2-tests/src/main/resources/templates/payment.html new file mode 100644 index 00000000..881c096a --- /dev/null +++ b/powerauth-fido2-tests/src/main/resources/templates/payment.html @@ -0,0 +1,85 @@ + + + + + Payment + + + + + + + + + + + + +
+
+
+ + +
+
+
+ + + \ No newline at end of file diff --git a/powerauth-fido2-tests/src/main/webapp/resources/css/wultra-login.css b/powerauth-fido2-tests/src/main/webapp/resources/css/wultra-login.css new file mode 100644 index 00000000..5e523cf3 --- /dev/null +++ b/powerauth-fido2-tests/src/main/webapp/resources/css/wultra-login.css @@ -0,0 +1,83 @@ +.body { + background-image: url("../images/background.png"); + background-position: center center; + background-size: cover; + margin: 0; padding: 0; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; +} + +.form-wrapper { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + padding-top: 120px; + padding-bottom: 120px; + background-color: rgba(5, 55, 81, 0.8); +} + +.form-panel { + max-width: 330px; + margin: 0 auto; + border-radius: 8px; + background-color: rgba(0, 0, 0, 1); +} + +.form-panel .alert .close { + margin-top: -2px !important; +} + +.form-logo { + width: 140px; +} + +.form-logo-wrapper { + margin-top: 10px; + margin-bottom: 20px; + margin-right: 20px; +} + +.form-signin { + padding: 20px; +} + +.form-signin .form-signin-heading, .form-signin .checkbox { + margin-bottom: 10px; + color: #FFF; + text-align: center; +} + +.form-signin .checkbox { + font-weight: normal; +} + +.form-signin .form-control { + position: relative; + height: auto; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + padding: 8px; + font-size: 14px; +} + +.form-signin .form-control:focus { + z-index: 2; +} + +.form-signin input[type="text"] { + margin-bottom: -1px; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} + +.form-signin input[type="password"] { + margin-bottom: 10px; + border-top-left-radius: 0; + border-top-right-radius: 0; +} \ No newline at end of file diff --git a/powerauth-fido2-tests/src/main/webapp/resources/css/wultra-theme.min.css b/powerauth-fido2-tests/src/main/webapp/resources/css/wultra-theme.min.css new file mode 100644 index 00000000..c8457347 --- /dev/null +++ b/powerauth-fido2-tests/src/main/webapp/resources/css/wultra-theme.min.css @@ -0,0 +1,1448 @@ +/*! Generated by Live LESS Theme Customizer */ +.label,sub,sup{vertical-align:baseline} +body,figure{margin:0} +.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.pre-scrollable{max-height:340px} +html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;font-size:10px;-webkit-tap-highlight-color:transparent} +article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block} +audio,canvas,progress,video{display:inline-block;vertical-align:baseline} +audio:not([controls]){display:none;height:0} +[hidden],template{display:none} +a{background-color:transparent} +a:active,a:hover{outline:0} +b,optgroup,strong{font-weight:700} +dfn{font-style:italic} +h1{margin:.67em 0} +mark{background:#ff0;color:#000} +sub,sup{font-size:75%;line-height:0;position:relative} +sup{top:-.5em} +sub{bottom:-.25em} +img{border:0;vertical-align:middle} +svg:not(:root){overflow:hidden} +hr{box-sizing:content-box;height:0} +pre,textarea{overflow:auto} +code,kbd,pre,samp{font-size:1em} +button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0} +button{overflow:visible} +button,select{text-transform:none} +button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer} +button[disabled],html input[disabled]{cursor:default} +button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0} +input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0} +input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto} +input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none} +table{border-collapse:collapse;border-spacing:0} +td,th{padding:0} +@media print{blockquote,img,pre,tr{page-break-inside:avoid} +*,:after,:before{background:0 0!important;color:#000!important;box-shadow:none!important;text-shadow:none!important} +a,a:visited{text-decoration:underline} +a[href]:after{content:" (" attr(href) ")"} +abbr[title]:after{content:" (" attr(title) ")"} +a[href^="javascript:"]:after,a[href^="#"]:after{content:""} +blockquote,pre{border:1px solid #999} +thead{display:table-header-group} +img{max-width:100%!important} +h2,h3,p{orphans:3;widows:3} +h2,h3{page-break-after:avoid} +.navbar{display:none} +.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important} +.label{border:1px solid #000} +.table{border-collapse:collapse!important} +.table td,.table th{background-color:#fff!important} +.table-bordered td,.table-bordered th{border:1px solid #ddd!important} +} +.img-thumbnail,body{background-color:#fff} +.btn,.btn-danger.active,.btn-danger:active,.btn-default.active,.btn-default:active,.btn-info.active,.btn-info:active,.btn-primary.active,.btn-primary:active,.btn-warning.active,.btn-warning:active,.btn.active,.btn:active,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover,.form-control,.navbar-toggle,.open>.dropdown-toggle.btn-danger,.open>.dropdown-toggle.btn-default,.open>.dropdown-toggle.btn-info,.open>.dropdown-toggle.btn-primary,.open>.dropdown-toggle.btn-warning{background-image:none} +@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff2) format('woff2'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')} +.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale} +.glyphicon-asterisk:before{content:"\002a"} +.glyphicon-plus:before{content:"\002b"} +.glyphicon-eur:before,.glyphicon-euro:before{content:"\20ac"} +.glyphicon-minus:before{content:"\2212"} +.glyphicon-cloud:before{content:"\2601"} +.glyphicon-envelope:before{content:"\2709"} +.glyphicon-pencil:before{content:"\270f"} +.glyphicon-glass:before{content:"\e001"} +.glyphicon-music:before{content:"\e002"} +.glyphicon-search:before{content:"\e003"} +.glyphicon-heart:before{content:"\e005"} +.glyphicon-star:before{content:"\e006"} +.glyphicon-star-empty:before{content:"\e007"} +.glyphicon-user:before{content:"\e008"} +.glyphicon-film:before{content:"\e009"} +.glyphicon-th-large:before{content:"\e010"} +.glyphicon-th:before{content:"\e011"} +.glyphicon-th-list:before{content:"\e012"} +.glyphicon-ok:before{content:"\e013"} +.glyphicon-remove:before{content:"\e014"} +.glyphicon-zoom-in:before{content:"\e015"} +.glyphicon-zoom-out:before{content:"\e016"} +.glyphicon-off:before{content:"\e017"} +.glyphicon-signal:before{content:"\e018"} +.glyphicon-cog:before{content:"\e019"} +.glyphicon-trash:before{content:"\e020"} +.glyphicon-home:before{content:"\e021"} +.glyphicon-file:before{content:"\e022"} +.glyphicon-time:before{content:"\e023"} +.glyphicon-road:before{content:"\e024"} +.glyphicon-download-alt:before{content:"\e025"} +.glyphicon-download:before{content:"\e026"} +.glyphicon-upload:before{content:"\e027"} +.glyphicon-inbox:before{content:"\e028"} +.glyphicon-play-circle:before{content:"\e029"} +.glyphicon-repeat:before{content:"\e030"} +.glyphicon-refresh:before{content:"\e031"} +.glyphicon-list-alt:before{content:"\e032"} +.glyphicon-lock:before{content:"\e033"} +.glyphicon-flag:before{content:"\e034"} +.glyphicon-headphones:before{content:"\e035"} +.glyphicon-volume-off:before{content:"\e036"} +.glyphicon-volume-down:before{content:"\e037"} +.glyphicon-volume-up:before{content:"\e038"} +.glyphicon-qrcode:before{content:"\e039"} +.glyphicon-barcode:before{content:"\e040"} +.glyphicon-tag:before{content:"\e041"} +.glyphicon-tags:before{content:"\e042"} +.glyphicon-book:before{content:"\e043"} +.glyphicon-bookmark:before{content:"\e044"} +.glyphicon-print:before{content:"\e045"} +.glyphicon-camera:before{content:"\e046"} +.glyphicon-font:before{content:"\e047"} +.glyphicon-bold:before{content:"\e048"} +.glyphicon-italic:before{content:"\e049"} +.glyphicon-text-height:before{content:"\e050"} +.glyphicon-text-width:before{content:"\e051"} +.glyphicon-align-left:before{content:"\e052"} +.glyphicon-align-center:before{content:"\e053"} +.glyphicon-align-right:before{content:"\e054"} +.glyphicon-align-justify:before{content:"\e055"} +.glyphicon-list:before{content:"\e056"} +.glyphicon-indent-left:before{content:"\e057"} +.glyphicon-indent-right:before{content:"\e058"} +.glyphicon-facetime-video:before{content:"\e059"} +.glyphicon-picture:before{content:"\e060"} +.glyphicon-map-marker:before{content:"\e062"} +.glyphicon-adjust:before{content:"\e063"} +.glyphicon-tint:before{content:"\e064"} +.glyphicon-edit:before{content:"\e065"} +.glyphicon-share:before{content:"\e066"} +.glyphicon-check:before{content:"\e067"} +.glyphicon-move:before{content:"\e068"} +.glyphicon-step-backward:before{content:"\e069"} +.glyphicon-fast-backward:before{content:"\e070"} +.glyphicon-backward:before{content:"\e071"} +.glyphicon-play:before{content:"\e072"} +.glyphicon-pause:before{content:"\e073"} +.glyphicon-stop:before{content:"\e074"} +.glyphicon-forward:before{content:"\e075"} +.glyphicon-fast-forward:before{content:"\e076"} +.glyphicon-step-forward:before{content:"\e077"} +.glyphicon-eject:before{content:"\e078"} +.glyphicon-chevron-left:before{content:"\e079"} +.glyphicon-chevron-right:before{content:"\e080"} +.glyphicon-plus-sign:before{content:"\e081"} +.glyphicon-minus-sign:before{content:"\e082"} +.glyphicon-remove-sign:before{content:"\e083"} +.glyphicon-ok-sign:before{content:"\e084"} +.glyphicon-question-sign:before{content:"\e085"} +.glyphicon-info-sign:before{content:"\e086"} +.glyphicon-screenshot:before{content:"\e087"} +.glyphicon-remove-circle:before{content:"\e088"} +.glyphicon-ok-circle:before{content:"\e089"} +.glyphicon-ban-circle:before{content:"\e090"} +.glyphicon-arrow-left:before{content:"\e091"} +.glyphicon-arrow-right:before{content:"\e092"} +.glyphicon-arrow-up:before{content:"\e093"} +.glyphicon-arrow-down:before{content:"\e094"} +.glyphicon-share-alt:before{content:"\e095"} +.glyphicon-resize-full:before{content:"\e096"} +.glyphicon-resize-small:before{content:"\e097"} +.glyphicon-exclamation-sign:before{content:"\e101"} +.glyphicon-gift:before{content:"\e102"} +.glyphicon-leaf:before{content:"\e103"} +.glyphicon-fire:before{content:"\e104"} +.glyphicon-eye-open:before{content:"\e105"} +.glyphicon-eye-close:before{content:"\e106"} +.glyphicon-warning-sign:before{content:"\e107"} +.glyphicon-plane:before{content:"\e108"} +.glyphicon-calendar:before{content:"\e109"} +.glyphicon-random:before{content:"\e110"} +.glyphicon-comment:before{content:"\e111"} +.glyphicon-magnet:before{content:"\e112"} +.glyphicon-chevron-up:before{content:"\e113"} +.glyphicon-chevron-down:before{content:"\e114"} +.glyphicon-retweet:before{content:"\e115"} +.glyphicon-shopping-cart:before{content:"\e116"} +.glyphicon-folder-close:before{content:"\e117"} +.glyphicon-folder-open:before{content:"\e118"} +.glyphicon-resize-vertical:before{content:"\e119"} +.glyphicon-resize-horizontal:before{content:"\e120"} +.glyphicon-hdd:before{content:"\e121"} +.glyphicon-bullhorn:before{content:"\e122"} +.glyphicon-bell:before{content:"\e123"} +.glyphicon-certificate:before{content:"\e124"} +.glyphicon-thumbs-up:before{content:"\e125"} +.glyphicon-thumbs-down:before{content:"\e126"} +.glyphicon-hand-right:before{content:"\e127"} +.glyphicon-hand-left:before{content:"\e128"} +.glyphicon-hand-up:before{content:"\e129"} +.glyphicon-hand-down:before{content:"\e130"} +.glyphicon-circle-arrow-right:before{content:"\e131"} +.glyphicon-circle-arrow-left:before{content:"\e132"} +.glyphicon-circle-arrow-up:before{content:"\e133"} +.glyphicon-circle-arrow-down:before{content:"\e134"} +.glyphicon-globe:before{content:"\e135"} +.glyphicon-wrench:before{content:"\e136"} +.glyphicon-tasks:before{content:"\e137"} +.glyphicon-filter:before{content:"\e138"} +.glyphicon-briefcase:before{content:"\e139"} +.glyphicon-fullscreen:before{content:"\e140"} +.glyphicon-dashboard:before{content:"\e141"} +.glyphicon-paperclip:before{content:"\e142"} +.glyphicon-heart-empty:before{content:"\e143"} +.glyphicon-link:before{content:"\e144"} +.glyphicon-phone:before{content:"\e145"} +.glyphicon-pushpin:before{content:"\e146"} +.glyphicon-usd:before{content:"\e148"} +.glyphicon-gbp:before{content:"\e149"} +.glyphicon-sort:before{content:"\e150"} +.glyphicon-sort-by-alphabet:before{content:"\e151"} +.glyphicon-sort-by-alphabet-alt:before{content:"\e152"} +.glyphicon-sort-by-order:before{content:"\e153"} +.glyphicon-sort-by-order-alt:before{content:"\e154"} +.glyphicon-sort-by-attributes:before{content:"\e155"} +.glyphicon-sort-by-attributes-alt:before{content:"\e156"} +.glyphicon-unchecked:before{content:"\e157"} +.glyphicon-expand:before{content:"\e158"} +.glyphicon-collapse-down:before{content:"\e159"} +.glyphicon-collapse-up:before{content:"\e160"} +.glyphicon-log-in:before{content:"\e161"} +.glyphicon-flash:before{content:"\e162"} +.glyphicon-log-out:before{content:"\e163"} +.glyphicon-new-window:before{content:"\e164"} +.glyphicon-record:before{content:"\e165"} +.glyphicon-save:before{content:"\e166"} +.glyphicon-open:before{content:"\e167"} +.glyphicon-saved:before{content:"\e168"} +.glyphicon-import:before{content:"\e169"} +.glyphicon-export:before{content:"\e170"} +.glyphicon-send:before{content:"\e171"} +.glyphicon-floppy-disk:before{content:"\e172"} +.glyphicon-floppy-saved:before{content:"\e173"} +.glyphicon-floppy-remove:before{content:"\e174"} +.glyphicon-floppy-save:before{content:"\e175"} +.glyphicon-floppy-open:before{content:"\e176"} +.glyphicon-credit-card:before{content:"\e177"} +.glyphicon-transfer:before{content:"\e178"} +.glyphicon-cutlery:before{content:"\e179"} +.glyphicon-header:before{content:"\e180"} +.glyphicon-compressed:before{content:"\e181"} +.glyphicon-earphone:before{content:"\e182"} +.glyphicon-phone-alt:before{content:"\e183"} +.glyphicon-tower:before{content:"\e184"} +.glyphicon-stats:before{content:"\e185"} +.glyphicon-sd-video:before{content:"\e186"} +.glyphicon-hd-video:before{content:"\e187"} +.glyphicon-subtitles:before{content:"\e188"} +.glyphicon-sound-stereo:before{content:"\e189"} +.glyphicon-sound-dolby:before{content:"\e190"} +.glyphicon-sound-5-1:before{content:"\e191"} +.glyphicon-sound-6-1:before{content:"\e192"} +.glyphicon-sound-7-1:before{content:"\e193"} +.glyphicon-copyright-mark:before{content:"\e194"} +.glyphicon-registration-mark:before{content:"\e195"} +.glyphicon-cloud-download:before{content:"\e197"} +.glyphicon-cloud-upload:before{content:"\e198"} +.glyphicon-tree-conifer:before{content:"\e199"} +.glyphicon-tree-deciduous:before{content:"\e200"} +.glyphicon-cd:before{content:"\e201"} +.glyphicon-save-file:before{content:"\e202"} +.glyphicon-open-file:before{content:"\e203"} +.glyphicon-level-up:before{content:"\e204"} +.glyphicon-copy:before{content:"\e205"} +.glyphicon-paste:before{content:"\e206"} +.glyphicon-alert:before{content:"\e209"} +.glyphicon-equalizer:before{content:"\e210"} +.glyphicon-king:before{content:"\e211"} +.glyphicon-queen:before{content:"\e212"} +.glyphicon-pawn:before{content:"\e213"} +.glyphicon-bishop:before{content:"\e214"} +.glyphicon-knight:before{content:"\e215"} +.glyphicon-baby-formula:before{content:"\e216"} +.glyphicon-tent:before{content:"\26fa"} +.glyphicon-blackboard:before{content:"\e218"} +.glyphicon-bed:before{content:"\e219"} +.glyphicon-apple:before{content:"\f8ff"} +.glyphicon-erase:before{content:"\e221"} +.glyphicon-hourglass:before{content:"\231b"} +.glyphicon-lamp:before{content:"\e223"} +.glyphicon-duplicate:before{content:"\e224"} +.glyphicon-piggy-bank:before{content:"\e225"} +.glyphicon-scissors:before{content:"\e226"} +.glyphicon-bitcoin:before,.glyphicon-btc:before,.glyphicon-xbt:before{content:"\e227"} +.glyphicon-jpy:before,.glyphicon-yen:before{content:"\00a5"} +.glyphicon-rub:before,.glyphicon-ruble:before{content:"\20bd"} +.glyphicon-scale:before{content:"\e230"} +.glyphicon-ice-lolly:before{content:"\e231"} +.glyphicon-ice-lolly-tasted:before{content:"\e232"} +.glyphicon-education:before{content:"\e233"} +.glyphicon-option-horizontal:before{content:"\e234"} +.glyphicon-option-vertical:before{content:"\e235"} +.glyphicon-menu-hamburger:before{content:"\e236"} +.glyphicon-modal-window:before{content:"\e237"} +.glyphicon-oil:before{content:"\e238"} +.glyphicon-grain:before{content:"\e239"} +.glyphicon-sunglasses:before{content:"\e240"} +.glyphicon-text-size:before{content:"\e241"} +.glyphicon-text-color:before{content:"\e242"} +.glyphicon-text-background:before{content:"\e243"} +.glyphicon-object-align-top:before{content:"\e244"} +.glyphicon-object-align-bottom:before{content:"\e245"} +.glyphicon-object-align-horizontal:before{content:"\e246"} +.glyphicon-object-align-left:before{content:"\e247"} +.glyphicon-object-align-vertical:before{content:"\e248"} +.glyphicon-object-align-right:before{content:"\e249"} +.glyphicon-triangle-right:before{content:"\e250"} +.glyphicon-triangle-left:before{content:"\e251"} +.glyphicon-triangle-bottom:before{content:"\e252"} +.glyphicon-triangle-top:before{content:"\e253"} +.glyphicon-console:before{content:"\e254"} +.glyphicon-superscript:before{content:"\e255"} +.glyphicon-subscript:before{content:"\e256"} +.glyphicon-menu-left:before{content:"\e257"} +.glyphicon-menu-right:before{content:"\e258"} +.glyphicon-menu-down:before{content:"\e259"} +.glyphicon-menu-up:before{content:"\e260"} +*,:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box} +body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333} +button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit} +a{color:#2185d5;text-decoration:none} +a:focus,a:hover{color:#175c93;text-decoration:underline} +a:focus{outline:-webkit-focus-ring-color auto 5px;outline-offset:-2px} +.carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto} +.img-rounded{border-radius:6px} +.img-thumbnail{padding:4px;line-height:1.42857143;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out;display:inline-block;max-width:100%;height:auto} +.img-circle{border-radius:50%} +hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee} +.sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);border:0} +.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto} +[role=button]{cursor:pointer} +.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit} +.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#777} +.h1,.h2,.h3,h1,h2,h3{margin-top:20px;margin-bottom:10px} +.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{font-size:65%} +.h4,.h5,.h6,h4,h5,h6{margin-top:10px;margin-bottom:10px} +.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-size:75%} +.h1,h1{font-size:36px} +.h2,h2{font-size:30px} +.h3,h3{font-size:24px} +.h4,h4{font-size:18px} +.h5,h5{font-size:14px} +.h6,h6{font-size:12px} +p{margin:0 0 10px} +.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4} +address,blockquote .small,blockquote footer,blockquote small,dd,dt,pre{line-height:1.42857143} +dt,kbd kbd,label{font-weight:700} +@media (min-width:768px){.lead{font-size:21px} +} +.small,small{font-size:85%} +.mark,mark{background-color:#aa0;padding:.2em} +.list-inline,.list-unstyled{list-style:none;padding-left:0} +.text-left{text-align:left} +.text-right{text-align:right} +.btn,.text-center{text-align:center} +.text-justify{text-align:justify} +.text-nowrap{white-space:nowrap} +.text-lowercase{text-transform:lowercase} +.text-uppercase{text-transform:uppercase} +.text-capitalize{text-transform:capitalize} +.text-muted{color:#777} +.text-primary{color:#2185d5} +a.text-primary:focus,a.text-primary:hover{color:#1a69a9} +.text-success{color:#fff} +a.text-success:focus,a.text-success:hover{color:#e6e6e6} +.text-info{color:#fff} +a.text-info:focus,a.text-info:hover{color:#e6e6e6} +.text-warning{color:#fff} +a.text-warning:focus,a.text-warning:hover{color:#e6e6e6} +.text-danger{color:#fff} +a.text-danger:focus,a.text-danger:hover{color:#e6e6e6} +.bg-primary{color:#fff;background-color:#2185d5} +a.bg-primary:focus,a.bg-primary:hover{background-color:#1a69a9} +.bg-success{background-color:#7fc000} +a.bg-success:focus,a.bg-success:hover{background-color:#5d8d00} +.bg-info{background-color:#2185d5} +a.bg-info:focus,a.bg-info:hover{background-color:#1a69a9} +.bg-warning{background-color:#aa0} +a.bg-warning:focus,a.bg-warning:hover{background-color:#770} +.bg-danger{background-color:#c0007f} +a.bg-danger:focus,a.bg-danger:hover{background-color:#8d005d} +pre code,table{background-color:transparent} +.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee} +dl,ol,ul{margin-top:0} +ol,ul{margin-bottom:10px} +ol ol,ol ul,ul ol,ul ul{margin-bottom:0} +.list-inline{margin-left:-5px} +.list-inline>li{display:inline-block;padding-left:5px;padding-right:5px} +dl{margin-bottom:20px} +dd{margin-left:0} +@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap} +.dl-horizontal dd{margin-left:180px} +} +abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #777} +.initialism{font-size:90%;text-transform:uppercase} +blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee} +blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child{margin-bottom:0} +blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;color:#777} +code,pre{color:#333} +blockquote .small:before,blockquote footer:before,blockquote small:before{content:'\2014 \00A0'} +.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0;text-align:right} +caption,th{text-align:left} +code,kbd{padding:2px 4px;font-size:90%} +.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:''} +.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:'\00A0 \2014'} +address{margin-bottom:20px;font-style:normal} +code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace} +code{background-color:#f7f7f7;border-radius:4px} +kbd{color:#fff;background-color:#333;border-radius:3px;box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)} +kbd kbd{padding:0;font-size:100%;box-shadow:none} +pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px} +.container,.container-fluid{margin-right:auto;margin-left:auto} +pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;border-radius:0} +.container,.container-fluid{padding-left:15px;padding-right:15px} +.pre-scrollable{overflow-y:scroll} +@media (min-width:768px){.container{width:750px} +} +@media (min-width:992px){.container{width:970px} +} +@media (min-width:1200px){.container{width:1170px} +} +.row{margin-left:-15px;margin-right:-15px} +.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-left:15px;padding-right:15px} +.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{float:left} +.col-xs-12{width:100%} +.col-xs-11{width:91.66666667%} +.col-xs-10{width:83.33333333%} +.col-xs-9{width:75%} +.col-xs-8{width:66.66666667%} +.col-xs-7{width:58.33333333%} +.col-xs-6{width:50%} +.col-xs-5{width:41.66666667%} +.col-xs-4{width:33.33333333%} +.col-xs-3{width:25%} +.col-xs-2{width:16.66666667%} +.col-xs-1{width:8.33333333%} +.col-xs-pull-12{right:100%} +.col-xs-pull-11{right:91.66666667%} +.col-xs-pull-10{right:83.33333333%} +.col-xs-pull-9{right:75%} +.col-xs-pull-8{right:66.66666667%} +.col-xs-pull-7{right:58.33333333%} +.col-xs-pull-6{right:50%} +.col-xs-pull-5{right:41.66666667%} +.col-xs-pull-4{right:33.33333333%} +.col-xs-pull-3{right:25%} +.col-xs-pull-2{right:16.66666667%} +.col-xs-pull-1{right:8.33333333%} +.col-xs-pull-0{right:auto} +.col-xs-push-12{left:100%} +.col-xs-push-11{left:91.66666667%} +.col-xs-push-10{left:83.33333333%} +.col-xs-push-9{left:75%} +.col-xs-push-8{left:66.66666667%} +.col-xs-push-7{left:58.33333333%} +.col-xs-push-6{left:50%} +.col-xs-push-5{left:41.66666667%} +.col-xs-push-4{left:33.33333333%} +.col-xs-push-3{left:25%} +.col-xs-push-2{left:16.66666667%} +.col-xs-push-1{left:8.33333333%} +.col-xs-push-0{left:auto} +.col-xs-offset-12{margin-left:100%} +.col-xs-offset-11{margin-left:91.66666667%} +.col-xs-offset-10{margin-left:83.33333333%} +.col-xs-offset-9{margin-left:75%} +.col-xs-offset-8{margin-left:66.66666667%} +.col-xs-offset-7{margin-left:58.33333333%} +.col-xs-offset-6{margin-left:50%} +.col-xs-offset-5{margin-left:41.66666667%} +.col-xs-offset-4{margin-left:33.33333333%} +.col-xs-offset-3{margin-left:25%} +.col-xs-offset-2{margin-left:16.66666667%} +.col-xs-offset-1{margin-left:8.33333333%} +.col-xs-offset-0{margin-left:0} +@media (min-width:768px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left} +.col-sm-12{width:100%} +.col-sm-11{width:91.66666667%} +.col-sm-10{width:83.33333333%} +.col-sm-9{width:75%} +.col-sm-8{width:66.66666667%} +.col-sm-7{width:58.33333333%} +.col-sm-6{width:50%} +.col-sm-5{width:41.66666667%} +.col-sm-4{width:33.33333333%} +.col-sm-3{width:25%} +.col-sm-2{width:16.66666667%} +.col-sm-1{width:8.33333333%} +.col-sm-pull-12{right:100%} +.col-sm-pull-11{right:91.66666667%} +.col-sm-pull-10{right:83.33333333%} +.col-sm-pull-9{right:75%} +.col-sm-pull-8{right:66.66666667%} +.col-sm-pull-7{right:58.33333333%} +.col-sm-pull-6{right:50%} +.col-sm-pull-5{right:41.66666667%} +.col-sm-pull-4{right:33.33333333%} +.col-sm-pull-3{right:25%} +.col-sm-pull-2{right:16.66666667%} +.col-sm-pull-1{right:8.33333333%} +.col-sm-pull-0{right:auto} +.col-sm-push-12{left:100%} +.col-sm-push-11{left:91.66666667%} +.col-sm-push-10{left:83.33333333%} +.col-sm-push-9{left:75%} +.col-sm-push-8{left:66.66666667%} +.col-sm-push-7{left:58.33333333%} +.col-sm-push-6{left:50%} +.col-sm-push-5{left:41.66666667%} +.col-sm-push-4{left:33.33333333%} +.col-sm-push-3{left:25%} +.col-sm-push-2{left:16.66666667%} +.col-sm-push-1{left:8.33333333%} +.col-sm-push-0{left:auto} +.col-sm-offset-12{margin-left:100%} +.col-sm-offset-11{margin-left:91.66666667%} +.col-sm-offset-10{margin-left:83.33333333%} +.col-sm-offset-9{margin-left:75%} +.col-sm-offset-8{margin-left:66.66666667%} +.col-sm-offset-7{margin-left:58.33333333%} +.col-sm-offset-6{margin-left:50%} +.col-sm-offset-5{margin-left:41.66666667%} +.col-sm-offset-4{margin-left:33.33333333%} +.col-sm-offset-3{margin-left:25%} +.col-sm-offset-2{margin-left:16.66666667%} +.col-sm-offset-1{margin-left:8.33333333%} +.col-sm-offset-0{margin-left:0} +} +@media (min-width:992px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left} +.col-md-12{width:100%} +.col-md-11{width:91.66666667%} +.col-md-10{width:83.33333333%} +.col-md-9{width:75%} +.col-md-8{width:66.66666667%} +.col-md-7{width:58.33333333%} +.col-md-6{width:50%} +.col-md-5{width:41.66666667%} +.col-md-4{width:33.33333333%} +.col-md-3{width:25%} +.col-md-2{width:16.66666667%} +.col-md-1{width:8.33333333%} +.col-md-pull-12{right:100%} +.col-md-pull-11{right:91.66666667%} +.col-md-pull-10{right:83.33333333%} +.col-md-pull-9{right:75%} +.col-md-pull-8{right:66.66666667%} +.col-md-pull-7{right:58.33333333%} +.col-md-pull-6{right:50%} +.col-md-pull-5{right:41.66666667%} +.col-md-pull-4{right:33.33333333%} +.col-md-pull-3{right:25%} +.col-md-pull-2{right:16.66666667%} +.col-md-pull-1{right:8.33333333%} +.col-md-pull-0{right:auto} +.col-md-push-12{left:100%} +.col-md-push-11{left:91.66666667%} +.col-md-push-10{left:83.33333333%} +.col-md-push-9{left:75%} +.col-md-push-8{left:66.66666667%} +.col-md-push-7{left:58.33333333%} +.col-md-push-6{left:50%} +.col-md-push-5{left:41.66666667%} +.col-md-push-4{left:33.33333333%} +.col-md-push-3{left:25%} +.col-md-push-2{left:16.66666667%} +.col-md-push-1{left:8.33333333%} +.col-md-push-0{left:auto} +.col-md-offset-12{margin-left:100%} +.col-md-offset-11{margin-left:91.66666667%} +.col-md-offset-10{margin-left:83.33333333%} +.col-md-offset-9{margin-left:75%} +.col-md-offset-8{margin-left:66.66666667%} +.col-md-offset-7{margin-left:58.33333333%} +.col-md-offset-6{margin-left:50%} +.col-md-offset-5{margin-left:41.66666667%} +.col-md-offset-4{margin-left:33.33333333%} +.col-md-offset-3{margin-left:25%} +.col-md-offset-2{margin-left:16.66666667%} +.col-md-offset-1{margin-left:8.33333333%} +.col-md-offset-0{margin-left:0} +} +@media (min-width:1200px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left} +.col-lg-12{width:100%} +.col-lg-11{width:91.66666667%} +.col-lg-10{width:83.33333333%} +.col-lg-9{width:75%} +.col-lg-8{width:66.66666667%} +.col-lg-7{width:58.33333333%} +.col-lg-6{width:50%} +.col-lg-5{width:41.66666667%} +.col-lg-4{width:33.33333333%} +.col-lg-3{width:25%} +.col-lg-2{width:16.66666667%} +.col-lg-1{width:8.33333333%} +.col-lg-pull-12{right:100%} +.col-lg-pull-11{right:91.66666667%} +.col-lg-pull-10{right:83.33333333%} +.col-lg-pull-9{right:75%} +.col-lg-pull-8{right:66.66666667%} +.col-lg-pull-7{right:58.33333333%} +.col-lg-pull-6{right:50%} +.col-lg-pull-5{right:41.66666667%} +.col-lg-pull-4{right:33.33333333%} +.col-lg-pull-3{right:25%} +.col-lg-pull-2{right:16.66666667%} +.col-lg-pull-1{right:8.33333333%} +.col-lg-pull-0{right:auto} +.col-lg-push-12{left:100%} +.col-lg-push-11{left:91.66666667%} +.col-lg-push-10{left:83.33333333%} +.col-lg-push-9{left:75%} +.col-lg-push-8{left:66.66666667%} +.col-lg-push-7{left:58.33333333%} +.col-lg-push-6{left:50%} +.col-lg-push-5{left:41.66666667%} +.col-lg-push-4{left:33.33333333%} +.col-lg-push-3{left:25%} +.col-lg-push-2{left:16.66666667%} +.col-lg-push-1{left:8.33333333%} +.col-lg-push-0{left:auto} +.col-lg-offset-12{margin-left:100%} +.col-lg-offset-11{margin-left:91.66666667%} +.col-lg-offset-10{margin-left:83.33333333%} +.col-lg-offset-9{margin-left:75%} +.col-lg-offset-8{margin-left:66.66666667%} +.col-lg-offset-7{margin-left:58.33333333%} +.col-lg-offset-6{margin-left:50%} +.col-lg-offset-5{margin-left:41.66666667%} +.col-lg-offset-4{margin-left:33.33333333%} +.col-lg-offset-3{margin-left:25%} +.col-lg-offset-2{margin-left:16.66666667%} +.col-lg-offset-1{margin-left:8.33333333%} +.col-lg-offset-0{margin-left:0} +} +caption{padding-top:8px;padding-bottom:8px;color:#777} +.table{width:100%;max-width:100%;margin-bottom:20px} +.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd} +.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd} +.table>caption+thead>tr:first-child>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0} +.table>tbody+tbody{border-top:2px solid #ddd} +.table .table{background-color:#fff} +.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px} +.table-bordered,.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd} +.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px} +.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9} +.table-hover>tbody>tr:hover,.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5} +table col[class*=col-]{position:static;float:none;display:table-column} +table td[class*=col-],table th[class*=col-]{position:static;float:none;display:table-cell} +.btn-group>.btn-group,.btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group,.dropdown-menu{float:left} +.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8} +.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#7fc000} +.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#6ea700} +.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#2185d5} +.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#1e77bf} +.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#aa0} +.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#909100} +.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#c0007f} +.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#a7006e} +.table-responsive{overflow-x:auto;min-height:.01%} +@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd} +.table-responsive>.table{margin-bottom:0} +.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap} +.table-responsive>.table-bordered{border:0} +.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0} +.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0} +.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0} +} +fieldset,legend{padding:0;border:0} +fieldset{margin:0;min-width:0} +legend{display:block;width:100%;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border-bottom:1px solid #e5e5e5} +label{display:inline-block;max-width:100%;margin-bottom:5px} +input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-appearance:none} +input[type=checkbox],input[type=radio]{margin:4px 0 0;margin-top:1px\9;line-height:normal} +.form-control,output{display:block;font-size:14px;line-height:1.42857143;color:#555} +input[type=file]{display:block} +input[type=range]{display:block;width:100%} +select[multiple],select[size]{height:auto} +input[type=file]:focus,input[type=checkbox]:focus,input[type=radio]:focus{outline:-webkit-focus-ring-color auto 5px;outline-offset:-2px} +output{padding-top:7px} +.form-control{width:100%;height:34px;padding:6px 12px;background-color:#fff;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s} +.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)} +.form-control::-moz-placeholder{color:#999;opacity:1} +.form-control:-ms-input-placeholder{color:#999} +.form-control::-webkit-input-placeholder{color:#999} +.form-control::-ms-expand{border:0;background-color:transparent} +.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eee;opacity:1} +.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed} +textarea.form-control{height:auto} +@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date].form-control,input[type=time].form-control,input[type=datetime-local].form-control,input[type=month].form-control{line-height:34px} +.input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px} +.input-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px} +} +.form-group{margin-bottom:15px} +.checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px} +.checkbox label,.radio label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer} +.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-left:-20px;margin-top:4px\9} +.checkbox+.checkbox,.radio+.radio{margin-top:-5px} +.checkbox-inline,.radio-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;vertical-align:middle;font-weight:400;cursor:pointer} +.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px} +.checkbox-inline.disabled,.checkbox.disabled label,.radio-inline.disabled,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio label,fieldset[disabled] .radio-inline,fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed} +.form-control-static{padding-top:7px;padding-bottom:7px;margin-bottom:0;min-height:34px} +.form-control-static.input-lg,.form-control-static.input-sm{padding-left:0;padding-right:0} +.form-group-sm .form-control,.input-sm{font-size:12px;padding:5px 10px;border-radius:3px} +.input-sm{height:30px;line-height:1.5} +select.input-sm{height:30px;line-height:30px} +select[multiple].input-sm,textarea.input-sm{height:auto} +.form-group-sm .form-control{height:30px;line-height:1.5} +.form-group-sm select.form-control{height:30px;line-height:30px} +.form-group-sm select[multiple].form-control,.form-group-sm textarea.form-control{height:auto} +.form-group-sm .form-control-static{height:30px;min-height:32px;padding:6px 10px;font-size:12px;line-height:1.5} +.form-group-lg .form-control,.input-lg{font-size:18px;padding:10px 16px;border-radius:6px} +.input-lg{height:46px;line-height:1.3333333} +select.input-lg{height:46px;line-height:46px} +select[multiple].input-lg,textarea.input-lg{height:auto} +.form-group-lg .form-control{height:46px;line-height:1.3333333} +.form-group-lg select.form-control{height:46px;line-height:46px} +.form-group-lg select[multiple].form-control,.form-group-lg textarea.form-control{height:auto} +.form-group-lg .form-control-static{height:46px;min-height:38px;padding:11px 16px;font-size:18px;line-height:1.3333333} +.has-feedback{position:relative} +.has-feedback .form-control{padding-right:42.5px} +.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none} +.collapsing,.dropdown,.dropup{position:relative} +.form-group-lg .form-control+.form-control-feedback,.input-group-lg+.form-control-feedback,.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px} +.form-group-sm .form-control+.form-control-feedback,.input-group-sm+.form-control-feedback,.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px} +.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#fff} +.has-success .form-control{border-color:#fff;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)} +.has-success .form-control:focus{border-color:#e6e6e6;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #fff;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #fff} +.has-success .input-group-addon{color:#fff;border-color:#fff;background-color:#7fc000} +.has-success .form-control-feedback,.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#fff} +.has-warning .form-control{border-color:#fff;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)} +.has-warning .form-control:focus{border-color:#e6e6e6;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #fff;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #fff} +.has-warning .input-group-addon{color:#fff;border-color:#fff;background-color:#aa0} +.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label,.has-warning .form-control-feedback{color:#fff} +.has-error .form-control{border-color:#fff;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)} +.has-error .form-control:focus{border-color:#e6e6e6;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #fff;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #fff} +.has-error .input-group-addon{color:#fff;border-color:#fff;background-color:#c0007f} +.has-error .form-control-feedback{color:#fff} +.has-feedback label~.form-control-feedback{top:25px} +.has-feedback label.sr-only~.form-control-feedback{top:0} +.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373} +@media (min-width:768px){.form-inline .form-control-static,.form-inline .form-group{display:inline-block} +.form-inline .control-label,.form-inline .form-group{margin-bottom:0;vertical-align:middle} +.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle} +.form-inline .input-group{display:inline-table;vertical-align:middle} +.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto} +.form-inline .input-group>.form-control{width:100%} +.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle} +.form-inline .checkbox label,.form-inline .radio label{padding-left:0} +.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0} +.form-inline .has-feedback .form-control-feedback{top:0} +} +.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{margin-top:0;margin-bottom:0;padding-top:7px} +.form-horizontal .checkbox,.form-horizontal .radio{min-height:27px} +.form-horizontal .form-group{margin-left:-15px;margin-right:-15px} +.form-horizontal .has-feedback .form-control-feedback{right:15px} +@media (min-width:768px){.form-horizontal .control-label{text-align:right;margin-bottom:0;padding-top:7px} +.form-horizontal .form-group-lg .control-label{padding-top:11px;font-size:18px} +.form-horizontal .form-group-sm .control-label{padding-top:6px;font-size:12px} +} +.btn{display:inline-block;margin-bottom:0;font-weight:400;vertical-align:middle;touch-action:manipulation;cursor:pointer;border:1px solid transparent;white-space:nowrap;padding:6px 12px;font-size:14px;line-height:1.42857143;border-radius:4px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none} +.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:-webkit-focus-ring-color auto 5px;outline-offset:-2px} +.btn.focus,.btn:focus,.btn:hover{color:#333;text-decoration:none} +.btn.active,.btn:active{outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)} +.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none} +a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none} +.btn-default{color:#333;background-color:#fff;border-color:#ccc} +.btn-default.focus,.btn-default:focus{color:#333;background-color:#e6e6e6;border-color:#8c8c8c} +.btn-default.active,.btn-default:active,.btn-default:hover,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad} +.btn-default.active.focus,.btn-default.active:focus,.btn-default.active:hover,.btn-default:active.focus,.btn-default:active:focus,.btn-default:active:hover,.open>.dropdown-toggle.btn-default.focus,.open>.dropdown-toggle.btn-default:focus,.open>.dropdown-toggle.btn-default:hover{color:#333;background-color:#d4d4d4;border-color:#8c8c8c} +.btn-default.disabled.focus,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled].focus,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#fff;border-color:#ccc} +.btn-default .badge{color:#fff;background-color:#333} +.btn-primary{color:#fff;background-color:#2185d5;border-color:#1e77bf} +.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#1a69a9;border-color:#0c3251} +.btn-primary.active,.btn-primary:active,.btn-primary:hover,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#1a69a9;border-color:#15568a} +.btn-primary.active.focus,.btn-primary.active:focus,.btn-primary.active:hover,.btn-primary:active.focus,.btn-primary:active:focus,.btn-primary:active:hover,.open>.dropdown-toggle.btn-primary.focus,.open>.dropdown-toggle.btn-primary:focus,.open>.dropdown-toggle.btn-primary:hover{color:#fff;background-color:#15568a;border-color:#0c3251} +.btn-primary.disabled.focus,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled].focus,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#2185d5;border-color:#1e77bf} +.btn-primary .badge{color:#2185d5;background-color:#fff} +.btn-success{color:#fff;background-color:#7fc000;border-color:#6ea700} +.btn-success.focus,.btn-success:focus{color:#fff;background-color:#5d8d00;border-color:#1a2700} +.btn-success.active,.btn-success:active,.btn-success:hover,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#5d8d00;border-color:#466900} +.btn-success.active.focus,.btn-success.active:focus,.btn-success.active:hover,.btn-success:active.focus,.btn-success:active:focus,.btn-success:active:hover,.open>.dropdown-toggle.btn-success.focus,.open>.dropdown-toggle.btn-success:focus,.open>.dropdown-toggle.btn-success:hover{color:#fff;background-color:#466900;border-color:#1a2700} +.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{background-image:none} +.btn-success.disabled.focus,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled].focus,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#7fc000;border-color:#6ea700} +.btn-success .badge{color:#7fc000;background-color:#fff} +.btn-info{color:#fff;background-color:#2185d5;border-color:#1e77bf} +.btn-info.focus,.btn-info:focus{color:#fff;background-color:#1a69a9;border-color:#0c3251} +.btn-info.active,.btn-info:active,.btn-info:hover,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#1a69a9;border-color:#15568a} +.btn-info.active.focus,.btn-info.active:focus,.btn-info.active:hover,.btn-info:active.focus,.btn-info:active:focus,.btn-info:active:hover,.open>.dropdown-toggle.btn-info.focus,.open>.dropdown-toggle.btn-info:focus,.open>.dropdown-toggle.btn-info:hover{color:#fff;background-color:#15568a;border-color:#0c3251} +.btn-info.disabled.focus,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled].focus,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#2185d5;border-color:#1e77bf} +.btn-info .badge{color:#2185d5;background-color:#fff} +.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236} +.btn-warning.focus,.btn-warning:focus{color:#fff;background-color:#ec971f;border-color:#985f0d} +.btn-warning.active,.btn-warning:active,.btn-warning:hover,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512} +.btn-warning.active.focus,.btn-warning.active:focus,.btn-warning.active:hover,.btn-warning:active.focus,.btn-warning:active:focus,.btn-warning:active:hover,.open>.dropdown-toggle.btn-warning.focus,.open>.dropdown-toggle.btn-warning:focus,.open>.dropdown-toggle.btn-warning:hover{color:#fff;background-color:#d58512;border-color:#985f0d} +.btn-warning.disabled.focus,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled].focus,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#f0ad4e;border-color:#eea236} +.btn-warning .badge{color:#f0ad4e;background-color:#fff} +.btn-danger{color:#fff;background-color:#c0007f;border-color:#a7006e} +.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#8d005d;border-color:#27001a} +.btn-danger.active,.btn-danger:active,.btn-danger:hover,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#8d005d;border-color:#690046} +.btn-danger.active.focus,.btn-danger.active:focus,.btn-danger.active:hover,.btn-danger:active.focus,.btn-danger:active:focus,.btn-danger:active:hover,.open>.dropdown-toggle.btn-danger.focus,.open>.dropdown-toggle.btn-danger:focus,.open>.dropdown-toggle.btn-danger:hover{color:#fff;background-color:#690046;border-color:#27001a} +.btn-danger.disabled.focus,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled].focus,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#c0007f;border-color:#a7006e} +.btn-danger .badge{color:#c0007f;background-color:#fff} +.btn-link{color:#2185d5;font-weight:400;border-radius:0} +.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none} +.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent} +.btn-link:focus,.btn-link:hover{color:#175c93;text-decoration:underline;background-color:transparent} +.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#777;text-decoration:none} +.btn-group-lg>.btn,.btn-lg{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px} +.btn-group-sm>.btn,.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px} +.btn-group-xs>.btn,.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px} +.btn-block{display:block;width:100%} +.btn-block+.btn-block{margin-top:5px} +input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%} +.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear} +.fade.in{opacity:1} +.collapse{display:none} +.collapse.in{display:block} +tr.collapse.in{display:table-row} +tbody.collapse.in{display:table-row-group} +.collapsing{height:0;overflow:hidden;-webkit-transition-property:height,visibility;transition-property:height,visibility;-webkit-transition-duration:.35s;transition-duration:.35s;-webkit-transition-timing-function:ease;transition-timing-function:ease} +.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid\9;border-right:4px solid transparent;border-left:4px solid transparent} +.dropdown-toggle:focus{outline:0} +.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;font-size:14px;text-align:left;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175);background-clip:padding-box} +.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle,.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-top-right-radius:0} +.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child,.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0} +.btn-group-vertical>.btn:not(:first-child):not(:last-child),.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn,.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0} +.dropdown-header,.dropdown-menu>li>a{white-space:nowrap;padding:3px 20px;line-height:1.42857143} +.dropdown-menu-right,.dropdown-menu.pull-right{left:auto;right:0} +.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5} +.dropdown-menu>li>a{display:block;clear:both;font-weight:400;color:#333} +.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{text-decoration:none;color:#262626;background-color:#f5f5f5} +.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;outline:0;background-color:#2185d5} +.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#777} +.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;background-color:transparent;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);cursor:not-allowed} +.open>.dropdown-menu{display:block} +.open>a{outline:0} +.dropdown-menu-left{left:0;right:auto} +.dropdown-header{display:block;font-size:12px;color:#777} +.dropdown-backdrop{position:fixed;left:0;right:0;bottom:0;top:0;z-index:990} +.nav-justified>.dropdown .dropdown-menu,.nav-tabs.nav-justified>.dropdown .dropdown-menu{left:auto;top:auto} +.pull-right>.dropdown-menu{right:0;left:auto} +.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px dashed;border-bottom:4px solid\9;content:""} +.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px} +@media (min-width:768px){.navbar-right .dropdown-menu{left:auto;right:0} +.navbar-right .dropdown-menu-left{left:0;right:auto} +} +.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle} +.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left} +.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2} +.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px} +.btn-toolbar{margin-left:-5px} +.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px} +.btn .caret,.btn-group>.btn:first-child{margin-left:0} +.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0} +.btn-group>.btn+.dropdown-toggle{padding-left:8px;padding-right:8px} +.btn-group>.btn-lg+.dropdown-toggle{padding-left:12px;padding-right:12px} +.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)} +.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none} +.btn-lg .caret{border-width:5px 5px 0} +.dropup .btn-lg .caret{border-width:0 5px 5px} +.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%} +.media-object.img-thumbnail,.nav>li>a>img{max-width:none} +.btn-group-vertical>.btn-group>.btn{float:none} +.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0} +.btn-group-vertical>.btn:first-child:not(:last-child){border-radius:4px 4px 0 0} +.btn-group-vertical>.btn:last-child:not(:first-child){border-radius:0 0 4px 4px} +.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0} +.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0} +.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-right-radius:0;border-top-left-radius:0} +.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate} +.btn-group-justified>.btn,.btn-group-justified>.btn-group{float:none;display:table-cell;width:1%} +.btn-group-justified>.btn-group .btn{width:100%} +.btn-group-justified>.btn-group .dropdown-menu{left:auto} +[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none} +.input-group{position:relative;display:table;border-collapse:separate} +.input-group[class*=col-]{float:none;padding-left:0;padding-right:0} +.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0} +.input-group .form-control:focus{z-index:3} +.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px} +select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px} +select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto} +.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px} +select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px} +select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto} +.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell} +.nav>li,.nav>li>a{position:relative;display:block} +.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0} +.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle} +.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px} +.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px} +.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px} +.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0} +.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-top-right-radius:0} +.input-group-addon:first-child{border-right:0} +.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{border-bottom-left-radius:0;border-top-left-radius:0} +.input-group-addon:last-child{border-left:0} +.input-group-btn{position:relative;font-size:0;white-space:nowrap} +.input-group-btn>.btn{position:relative} +.input-group-btn>.btn+.btn{margin-left:-1px} +.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:2} +.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px} +.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px} +.nav{margin-bottom:0;padding-left:0;list-style:none} +.nav>li>a{padding:10px 15px} +.nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee} +.nav>li.disabled>a{color:#777} +.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#777;text-decoration:none;background-color:transparent;cursor:not-allowed} +.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#2185d5} +.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5} +.nav-tabs{border-bottom:1px solid #ddd} +.nav-tabs>li{float:left;margin-bottom:-1px} +.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0} +.nav-tabs>li>a:hover{border-color:#eee #eee #ddd} +.nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{color:#555;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent;cursor:default} +.nav-tabs.nav-justified{width:100%;border-bottom:0} +.nav-tabs.nav-justified>li{float:none} +.nav-tabs.nav-justified>li>a{text-align:center;margin-bottom:5px;margin-right:0;border-radius:4px} +.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border:1px solid #ddd} +@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%} +.nav-tabs.nav-justified>li>a{margin-bottom:0;border-bottom:1px solid #ddd;border-radius:4px 4px 0 0} +.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border-bottom-color:#fff} +} +.nav-pills>li{float:left} +.nav-justified>li,.nav-stacked>li{float:none} +.nav-pills>li>a{border-radius:4px} +.nav-pills>li+li{margin-left:2px} +.nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#2185d5} +.nav-stacked>li+li{margin-top:2px;margin-left:0} +.nav-justified{width:100%} +.nav-justified>li>a{text-align:center;margin-bottom:5px} +.nav-tabs-justified{border-bottom:0} +.nav-tabs-justified>li>a{margin-right:0;border-radius:4px} +.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border:1px solid #ddd} +@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%} +.nav-justified>li>a{margin-bottom:0} +.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0} +.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border-bottom-color:#fff} +} +.tab-content>.tab-pane{display:none} +.tab-content>.active{display:block} +.nav-tabs .dropdown-menu{margin-top:-1px;border-top-right-radius:0;border-top-left-radius:0} +.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent} +.navbar-collapse{overflow-x:visible;padding-right:15px;padding-left:15px;border-top:1px solid transparent;box-shadow:inset 0 1px 0 rgba(255,255,255,.1);-webkit-overflow-scrolling:touch} +.navbar-collapse.in{overflow-y:auto} +@media (min-width:768px){.navbar{border-radius:4px} +.navbar-header{float:left} +.navbar-collapse{width:auto;border-top:0;box-shadow:none} +.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important} +.navbar-collapse.in{overflow-y:visible} +.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{padding-left:0;padding-right:0} +} +.carousel-inner,.embed-responsive,.modal,.modal-open,.progress{overflow:hidden} +@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:200px} +} +.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px} +.navbar-static-top{z-index:1000;border-width:0 0 1px} +.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030} +.navbar-fixed-top{top:0;border-width:0 0 1px} +.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0} +.navbar-brand{float:left;padding:15px;font-size:18px;line-height:20px;height:50px} +.navbar-brand:focus,.navbar-brand:hover{text-decoration:none} +.navbar-brand>img{display:block} +@media (min-width:768px){.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:0;margin-left:0} +.navbar-fixed-bottom,.navbar-fixed-top,.navbar-static-top{border-radius:0} +.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px} +} +.navbar-toggle{position:relative;float:right;margin-right:15px;padding:9px 10px;margin-top:8px;margin-bottom:8px;background-color:transparent;border:1px solid transparent;border-radius:4px} +.navbar-toggle:focus{outline:0} +.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px} +.navbar-toggle .icon-bar+.icon-bar{margin-top:4px} +.navbar-nav{margin:7.5px -15px} +.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px} +@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;box-shadow:none} +.navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px} +.navbar-nav .open .dropdown-menu>li>a{line-height:20px} +.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{background-image:none} +} +.progress-bar-striped,.progress-striped .progress-bar,.progress-striped .progress-bar-danger,.progress-striped .progress-bar-info,.progress-striped .progress-bar-success,.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)} +@media (min-width:768px){.navbar-toggle{display:none} +.navbar-nav{float:left;margin:0} +.navbar-nav>li{float:left} +.navbar-nav>li>a{padding-top:15px;padding-bottom:15px} +} +.navbar-form{padding:10px 15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);margin:8px -15px} +@media (min-width:768px){.navbar-form .form-control-static,.navbar-form .form-group{display:inline-block} +.navbar-form .control-label,.navbar-form .form-group{margin-bottom:0;vertical-align:middle} +.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle} +.navbar-form .input-group{display:inline-table;vertical-align:middle} +.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto} +.navbar-form .input-group>.form-control{width:100%} +.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle} +.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0} +.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{position:relative;margin-left:0} +.navbar-form .has-feedback .form-control-feedback{top:0} +} +.btn .badge,.btn .label{position:relative;top:-1px} +.breadcrumb>li,.pagination{display:inline-block} +@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px} +.navbar-form .form-group:last-child{margin-bottom:0} +} +@media (min-width:768px){.navbar-form{width:auto;border:0;margin-left:0;margin-right:0;padding-top:0;padding-bottom:0;-webkit-box-shadow:none;box-shadow:none} +} +.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-right-radius:0;border-top-left-radius:0} +.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-radius:4px 4px 0 0} +.navbar-btn{margin-top:8px;margin-bottom:8px} +.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px} +.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px} +.navbar-text{margin-top:15px;margin-bottom:15px} +@media (min-width:768px){.navbar-text{float:left;margin-left:15px;margin-right:15px} +.navbar-left{float:left!important} +.navbar-right{float:right!important;margin-right:-15px} +.navbar-right~.navbar-right{margin-right:0} +} +.navbar-default{background-color:#fff;border-color:#eee} +.navbar-default .navbar-brand{color:#333} +.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{color:#1a1a1a;background-color:transparent} +.navbar-default .navbar-text{color:#333} +.navbar-default .navbar-nav>li>a{color:#2185d5} +.navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{color:#333;background-color:transparent} +.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{color:#555;background-color:#eee} +.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent} +.navbar-default .navbar-toggle{border-color:#ddd} +.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{background-color:#ddd} +.navbar-default .navbar-toggle .icon-bar{background-color:#888} +.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#eee} +.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:focus,.navbar-default .navbar-nav>.open>a:hover{background-color:#eee;color:#555} +@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#2185d5} +.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent} +.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#555;background-color:#eee} +.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#ccc;background-color:transparent} +} +.navbar-default .navbar-link{color:#2185d5} +.navbar-default .navbar-link:hover{color:#333} +.navbar-default .btn-link{color:#2185d5} +.navbar-default .btn-link:focus,.navbar-default .btn-link:hover{color:#333} +.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#ccc} +.navbar-inverse{background-color:#333;border-color:#1a1a1a} +.navbar-inverse .navbar-brand{color:#9d9d9d} +.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:#fff;background-color:transparent} +.navbar-inverse .navbar-nav>li>a,.navbar-inverse .navbar-text{color:#9d9d9d} +.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#fff;background-color:transparent} +.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#1a1a1a} +.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent} +.navbar-inverse .navbar-toggle{border-color:#333} +.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{background-color:#333} +.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff} +.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#212121} +.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{background-color:#1a1a1a;color:#fff} +@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#1a1a1a} +.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#1a1a1a} +.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#9d9d9d} +.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent} +.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#1a1a1a} +.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#444;background-color:transparent} +} +.navbar-inverse .navbar-link{color:#9d9d9d} +.navbar-inverse .navbar-link:hover{color:#fff} +.navbar-inverse .btn-link{color:#9d9d9d} +.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover{color:#fff} +.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{color:#444} +.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px} +.breadcrumb>li+li:before{content:"/\00a0";padding:0 5px;color:#ccc} +.breadcrumb>.active{color:#777} +.pagination{padding-left:0;margin:20px 0;border-radius:4px} +.pager li,.pagination>li{display:inline} +.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;line-height:1.42857143;text-decoration:none;color:#2185d5;background-color:#fff;border:1px solid #ddd;margin-left:-1px} +.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-bottom-left-radius:4px;border-top-left-radius:4px} +.pagination>li:last-child>a,.pagination>li:last-child>span{border-bottom-right-radius:4px;border-top-right-radius:4px} +.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{z-index:2;color:#175c93;background-color:#eee;border-color:#ddd} +.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:3;color:#fff;background-color:#2185d5;border-color:#2185d5;cursor:default} +.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#777;background-color:#fff;border-color:#ddd;cursor:not-allowed} +.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px;line-height:1.3333333} +.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-bottom-left-radius:6px;border-top-left-radius:6px} +.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-bottom-right-radius:6px;border-top-right-radius:6px} +.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px;line-height:1.5} +.badge,.label{text-align:center;font-weight:700;line-height:1;white-space:nowrap} +.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-bottom-left-radius:3px;border-top-left-radius:3px} +.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-bottom-right-radius:3px;border-top-right-radius:3px} +.pager{padding-left:0;margin:20px 0;list-style:none;text-align:center} +.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px} +.pager li>a:focus,.pager li>a:hover{text-decoration:none;background-color:#eee} +.pager .next>a,.pager .next>span{float:right} +.pager .previous>a,.pager .previous>span{float:left} +.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#777;background-color:#fff;cursor:not-allowed} +.label{display:inline;padding:.2em .6em .3em;font-size:75%;color:#fff;border-radius:.25em} +a.label:focus,a.label:hover{color:#fff;text-decoration:none;cursor:pointer} +.label:empty{display:none} +.label-default{background-color:#777} +.label-default[href]:focus,.label-default[href]:hover{background-color:#5e5e5e} +.label-primary{background-color:#2185d5} +.label-primary[href]:focus,.label-primary[href]:hover{background-color:#1a69a9} +.label-success{background-color:#7fc000} +.label-success[href]:focus,.label-success[href]:hover{background-color:#5d8d00} +.label-info{background-color:#2185d5} +.label-info[href]:focus,.label-info[href]:hover{background-color:#1a69a9} +.label-warning{background-color:#f0ad4e} +.label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f} +.label-danger{background-color:#c0007f} +.label-danger[href]:focus,.label-danger[href]:hover{background-color:#8d005d} +.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;color:#fff;vertical-align:middle;background-color:#777;border-radius:10px} +.badge:empty{display:none} +.media-object,.thumbnail{display:block} +.btn-group-xs>.btn .badge,.btn-xs .badge{top:0;padding:1px 5px} +a.badge:focus,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer} +.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#2185d5;background-color:#fff} +.jumbotron,.jumbotron .h1,.jumbotron h1{color:inherit} +.list-group-item>.badge{float:right} +.list-group-item>.badge+.badge{margin-right:5px} +.nav-pills>li>a>.badge{margin-left:3px} +.jumbotron{padding-top:30px;padding-bottom:30px;margin-bottom:30px;background-color:#eee} +.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200} +.alert .alert-link,.close{font-weight:700} +.alert,.thumbnail{margin-bottom:20px} +.jumbotron>hr{border-top-color:#d5d5d5} +.container .jumbotron,.container-fluid .jumbotron{border-radius:6px;padding-left:15px;padding-right:15px} +.jumbotron .container{max-width:100%} +@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px} +.container .jumbotron,.container-fluid .jumbotron{padding-left:60px;padding-right:60px} +.jumbotron .h1,.jumbotron h1{font-size:63px} +} +.thumbnail{padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out} +.thumbnail a>img,.thumbnail>img{margin-left:auto;margin-right:auto} +a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#2185d5} +.thumbnail .caption{padding:9px;color:#333} +.alert{padding:15px;border:1px solid transparent;border-radius:4px} +.alert h4{margin-top:0;color:inherit} +.alert>p,.alert>ul{margin-bottom:0} +.alert>p+p{margin-top:5px} +.alert-dismissable,.alert-dismissible{padding-right:35px} +.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit} +.modal,.modal-backdrop{right:0;bottom:0;left:0} +.alert-success{background-color:#7fc000;border-color:#8aa700;color:#fff} +.alert-success hr{border-top-color:#758d00} +.alert-success .alert-link{color:#e6e6e6} +.alert-info{background-color:#2185d5;border-color:#1c8bb6;color:#fff} +.alert-info hr{border-top-color:#197aa0} +.alert-info .alert-link{color:#e6e6e6} +.alert-warning{background-color:#aa0;border-color:#917800;color:#fff} +.alert-warning hr{border-top-color:#776300} +.alert-warning .alert-link{color:#e6e6e6} +.alert-danger{background-color:#c0007f;border-color:#a7008a;color:#fff} +.alert-danger hr{border-top-color:#8d0075} +.alert-danger .alert-link{color:#e6e6e6} +@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0} +to{background-position:0 0} +} +@keyframes progress-bar-stripes{from{background-position:40px 0} +to{background-position:0 0} +} +.progress{height:20px;margin-bottom:20px;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)} +.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#2185d5;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease} +.progress-bar-striped,.progress-striped .progress-bar{background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:40px 40px} +.progress-bar.active,.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite} +.progress-bar-success{background-color:#7fc000} +.progress-striped .progress-bar-success{background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)} +.progress-bar-info{background-color:#2185d5} +.progress-striped .progress-bar-info{background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)} +.progress-bar-warning{background-color:#f0ad4e} +.progress-striped .progress-bar-warning{background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)} +.progress-bar-danger{background-color:#c0007f} +.progress-striped .progress-bar-danger{background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)} +.media{margin-top:15px} +.media:first-child{margin-top:0} +.media,.media-body{zoom:1;overflow:hidden} +.media-body{width:10000px} +.media-right,.media>.pull-right{padding-left:10px} +.media-left,.media>.pull-left{padding-right:10px} +.media-body,.media-left,.media-right{display:table-cell;vertical-align:top} +.media-middle{vertical-align:middle} +.media-bottom{vertical-align:bottom} +.media-heading{margin-top:0;margin-bottom:5px} +.media-list{padding-left:0;list-style:none} +.list-group{margin-bottom:20px;padding-left:0} +.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd} +.list-group-item:first-child{border-top-right-radius:4px;border-top-left-radius:4px} +.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px} +a.list-group-item,button.list-group-item{color:#555} +a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333} +a.list-group-item:focus,a.list-group-item:hover,button.list-group-item:focus,button.list-group-item:hover{text-decoration:none;color:#555;background-color:#f5f5f5} +button.list-group-item{width:100%;text-align:left} +.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{background-color:#eee;color:#777;cursor:not-allowed} +.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit} +.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#777} +.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#2185d5;border-color:#2185d5} +.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit} +.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#cbe3f7} +.list-group-item-success{color:#fff;background-color:#7fc000} +a.list-group-item-success,button.list-group-item-success{color:#fff} +a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit} +a.list-group-item-success:focus,a.list-group-item-success:hover,button.list-group-item-success:focus,button.list-group-item-success:hover{color:#fff;background-color:#6ea700} +a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover,button.list-group-item-success.active,button.list-group-item-success.active:focus,button.list-group-item-success.active:hover{color:#fff;background-color:#fff;border-color:#fff} +.list-group-item-info{color:#fff;background-color:#2185d5} +a.list-group-item-info,button.list-group-item-info{color:#fff} +a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit} +a.list-group-item-info:focus,a.list-group-item-info:hover,button.list-group-item-info:focus,button.list-group-item-info:hover{color:#fff;background-color:#1e77bf} +a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover,button.list-group-item-info.active,button.list-group-item-info.active:focus,button.list-group-item-info.active:hover{color:#fff;background-color:#fff;border-color:#fff} +.list-group-item-warning{color:#fff;background-color:#aa0} +a.list-group-item-warning,button.list-group-item-warning{color:#fff} +a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit} +a.list-group-item-warning:focus,a.list-group-item-warning:hover,button.list-group-item-warning:focus,button.list-group-item-warning:hover{color:#fff;background-color:#909100} +a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover,button.list-group-item-warning.active,button.list-group-item-warning.active:focus,button.list-group-item-warning.active:hover{color:#fff;background-color:#fff;border-color:#fff} +.list-group-item-danger{color:#fff;background-color:#c0007f} +a.list-group-item-danger,button.list-group-item-danger{color:#fff} +a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit} +a.list-group-item-danger:focus,a.list-group-item-danger:hover,button.list-group-item-danger:focus,button.list-group-item-danger:hover{color:#fff;background-color:#a7006e} +a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover,button.list-group-item-danger.active,button.list-group-item-danger.active:focus,button.list-group-item-danger.active:hover{color:#fff;background-color:#fff;border-color:#fff} +.panel-heading>.dropdown .dropdown-toggle,.panel-title,.panel-title>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>a{color:inherit} +.list-group-item-heading{margin-top:0;margin-bottom:5px} +.list-group-item-text{margin-bottom:0;line-height:1.3} +.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)} +.panel-title,.panel>.list-group,.panel>.panel-collapse>.list-group,.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{margin-bottom:0} +.panel-body{padding:15px} +.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-right-radius:3px;border-top-left-radius:3px} +.panel-group .panel-heading,.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{border-bottom:0} +.panel-title{margin-top:0;font-size:16px} +.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px} +.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0} +.panel>.table-responsive:last-child>.table:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px} +.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-right-radius:3px;border-top-left-radius:3px} +.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px} +.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-right-radius:0;border-top-left-radius:0} +.panel>.table-responsive:first-child>.table:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-right-radius:3px;border-top-left-radius:3px} +.list-group+.panel-footer,.panel-heading+.list-group .list-group-item:first-child{border-top-width:0} +.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-left:15px;padding-right:15px} +.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:3px} +.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:3px} +.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px} +.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px} +.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd} +.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0} +.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0} +.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0} +.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0} +.panel>.table-responsive{border:0;margin-bottom:0} +.panel-group{margin-bottom:20px} +.panel-group .panel{margin-bottom:0;border-radius:4px} +.panel-group .panel+.panel{margin-top:5px} +.panel-group .panel-heading+.panel-collapse>.list-group,.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd} +.panel-group .panel-footer{border-top:0} +.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd} +.panel-default{border-color:#ddd} +.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd} +.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd} +.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333} +.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd} +.panel-primary{border-color:#2185d5} +.panel-primary>.panel-heading{color:#fff;background-color:#2185d5;border-color:#2185d5} +.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#2185d5} +.panel-primary>.panel-heading .badge{color:#2185d5;background-color:#fff} +.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#2185d5} +.panel-success{border-color:#8aa700} +.panel-success>.panel-heading{color:#fff;background-color:#7fc000;border-color:#8aa700} +.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#8aa700} +.panel-success>.panel-heading .badge{color:#7fc000;background-color:#fff} +.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#8aa700} +.panel-info{border-color:#1c8bb6} +.panel-info>.panel-heading{color:#fff;background-color:#2185d5;border-color:#1c8bb6} +.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#1c8bb6} +.panel-info>.panel-heading .badge{color:#2185d5;background-color:#fff} +.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#1c8bb6} +.panel-warning{border-color:#917800} +.panel-warning>.panel-heading{color:#fff;background-color:#aa0;border-color:#917800} +.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#917800} +.panel-warning>.panel-heading .badge{color:#aa0;background-color:#fff} +.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#917800} +.panel-danger{border-color:#a7008a} +.panel-danger>.panel-heading{color:#fff;background-color:#c0007f;border-color:#a7008a} +.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#a7008a} +.panel-danger>.panel-heading .badge{color:#c0007f;background-color:#fff} +.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#a7008a} +.embed-responsive{position:relative;display:block;height:0;padding:0} +.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;left:0;bottom:0;height:100%;width:100%;border:0} +.embed-responsive-16by9{padding-bottom:56.25%} +.embed-responsive-4by3{padding-bottom:75%} +.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)} +.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)} +.well-lg{padding:24px;border-radius:6px} +.well-sm{padding:9px;border-radius:3px} +.close{float:right;font-size:21px;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20)} +.popover,.tooltip{text-decoration:none;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-style:normal;font-weight:400;letter-spacing:normal;line-break:auto;line-height:1.42857143;text-shadow:none;text-transform:none;white-space:normal;word-break:normal;word-spacing:normal;word-wrap:normal} +.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;opacity:.5;filter:alpha(opacity=50)} +button.close{padding:0;cursor:pointer;background:0 0;border:0;-webkit-appearance:none} +.modal-content,.popover{background-clip:padding-box} +.modal{display:none;position:fixed;top:0;z-index:1050;-webkit-overflow-scrolling:touch;outline:0} +.modal.fade .modal-dialog{-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);-o-transform:translate(0,-25%);transform:translate(0,-25%);-webkit-transition:-webkit-transform .3s ease-out;-moz-transition:-moz-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out} +.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)} +.modal-open .modal{overflow-x:hidden;overflow-y:auto} +.modal-dialog{position:relative;width:auto;margin:10px} +.modal-content{position:relative;background-color:#fff;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5);outline:0} +.modal-backdrop{position:fixed;top:0;z-index:1040;background-color:#000} +.modal-backdrop.fade{opacity:0;filter:alpha(opacity=0)} +.modal-backdrop.in{opacity:.5;filter:alpha(opacity=50)} +.modal-header{padding:15px;border-bottom:1px solid #e5e5e5} +.tooltip.bottom .tooltip-arrow,.tooltip.bottom-left .tooltip-arrow,.tooltip.bottom-right .tooltip-arrow{top:0;border-width:0 5px 5px;border-bottom-color:#000} +.modal-header .close{margin-top:-2px} +.modal-title{margin:0;line-height:1.42857143} +.modal-body{position:relative;padding:15px} +.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5} +.modal-footer .btn+.btn{margin-left:5px;margin-bottom:0} +.modal-footer .btn-group .btn+.btn{margin-left:-1px} +.modal-footer .btn-block+.btn-block{margin-left:0} +.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll} +@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto} +.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)} +.modal-sm{width:300px} +} +.tooltip.top-left .tooltip-arrow,.tooltip.top-right .tooltip-arrow{bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000} +@media (min-width:992px){.modal-lg{width:900px} +} +.tooltip{position:absolute;z-index:1070;display:block;text-align:left;text-align:start;font-size:12px;opacity:0;filter:alpha(opacity=0)} +.tooltip.in{opacity:.9;filter:alpha(opacity=90)} +.tooltip.top{margin-top:-3px;padding:5px 0} +.tooltip.right{margin-left:3px;padding:0 5px} +.tooltip.bottom{margin-top:3px;padding:5px 0} +.tooltip.left{margin-left:-3px;padding:0 5px} +.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;background-color:#000;border-radius:4px} +.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid} +.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000} +.tooltip.top-left .tooltip-arrow{right:5px} +.tooltip.top-right .tooltip-arrow{left:5px} +.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000} +.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000} +.tooltip.bottom .tooltip-arrow{left:50%;margin-left:-5px} +.tooltip.bottom-left .tooltip-arrow{right:5px;margin-top:-5px} +.tooltip.bottom-right .tooltip-arrow{left:5px;margin-top:-5px} +.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;text-align:left;text-align:start;font-size:14px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2)} +.carousel-caption,.carousel-control{color:#fff;text-shadow:0 1px 2px rgba(0,0,0,.6)} +.popover.top{margin-top:-10px} +.popover.right{margin-left:10px} +.popover.bottom{margin-top:10px} +.popover.left{margin-left:-10px} +.popover-title{margin:0;padding:8px 14px;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0} +.popover-content{padding:9px 14px} +.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid} +.carousel,.carousel-inner{position:relative} +.popover>.arrow{border-width:11px} +.popover>.arrow:after{border-width:10px;content:""} +.popover.top>.arrow{left:50%;margin-left:-11px;border-bottom-width:0;border-top-color:#999;border-top-color:rgba(0,0,0,.25);bottom:-11px} +.popover.top>.arrow:after{content:" ";bottom:1px;margin-left:-10px;border-bottom-width:0;border-top-color:#fff} +.popover.left>.arrow:after,.popover.right>.arrow:after{content:" ";bottom:-10px} +.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-left-width:0;border-right-color:#999;border-right-color:rgba(0,0,0,.25)} +.popover.right>.arrow:after{left:1px;border-left-width:0;border-right-color:#fff} +.popover.bottom>.arrow{left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25);top:-11px} +.popover.bottom>.arrow:after{content:" ";top:1px;margin-left:-10px;border-top-width:0;border-bottom-color:#fff} +.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)} +.popover.left>.arrow:after{right:1px;border-right-width:0;border-left-color:#fff} +.carousel-inner{width:100%} +.carousel-inner>.item{display:none;position:relative;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left} +.carousel-inner>.item>a>img,.carousel-inner>.item>img{line-height:1} +@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-moz-transition:-moz-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;-moz-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;-moz-perspective:1000px;perspective:1000px} +.carousel-inner>.item.active.right,.carousel-inner>.item.next{-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0);left:0} +.carousel-inner>.item.active.left,.carousel-inner>.item.prev{-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0);left:0} +.carousel-inner>.item.active,.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);left:0} +} +.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block} +.carousel-inner>.active{left:0} +.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%} +.carousel-inner>.next{left:100%} +.carousel-inner>.prev{left:-100%} +.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0} +.carousel-inner>.active.left{left:-100%} +.carousel-inner>.active.right{left:100%} +.carousel-control{position:absolute;top:0;left:0;bottom:0;width:15%;opacity:.5;filter:alpha(opacity=50);font-size:20px;text-align:center;background-color:transparent} +.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1)} +.carousel-control.right{left:auto;right:0;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1)} +.carousel-control:focus,.carousel-control:hover{outline:0;color:#fff;text-decoration:none;opacity:.9;filter:alpha(opacity=90)} +.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;margin-top:-10px;z-index:5;display:inline-block} +.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{left:50%;margin-left:-10px} +.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{right:50%;margin-right:-10px} +.carousel-control .icon-next,.carousel-control .icon-prev{width:20px;height:20px;line-height:1;font-family:serif} +.carousel-control .icon-prev:before{content:'\2039'} +.carousel-control .icon-next:before{content:'\203a'} +.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;margin-left:-30%;padding-left:0;list-style:none;text-align:center} +.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;border:1px solid #fff;border-radius:10px;cursor:pointer;background-color:#000\9;background-color:transparent} +.carousel-indicators .active{margin:0;width:12px;height:12px;background-color:#fff} +.carousel-caption{position:absolute;left:15%;right:15%;bottom:20px;z-index:10;padding-top:20px;padding-bottom:20px;text-align:center} +.carousel-caption .btn,.text-hide{text-shadow:none} +@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{width:30px;height:30px;margin-top:-10px;font-size:30px} +.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-10px} +.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-10px} +.carousel-caption{left:20%;right:20%;padding-bottom:30px} +.carousel-indicators{bottom:20px} +} +.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.modal-footer:after,.modal-footer:before,.modal-header:after,.modal-header:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{content:" ";display:table} +.btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.modal-footer:after,.modal-header:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row:after{clear:both} +.center-block{display:block;margin-left:auto;margin-right:auto} +.pull-right{float:right!important} +.pull-left{float:left!important} +.hide{display:none!important} +.show{display:block!important} +.hidden,.visible-lg,.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important} +.invisible{visibility:hidden} +.text-hide{font:0/0 a;color:transparent;background-color:transparent;border:0} +.affix{position:fixed} +@-ms-viewport{width:device-width} +@media (max-width:767px){.visible-xs{display:block!important} +table.visible-xs{display:table!important} +tr.visible-xs{display:table-row!important} +td.visible-xs,th.visible-xs{display:table-cell!important} +.visible-xs-block{display:block!important} +.visible-xs-inline{display:inline!important} +.visible-xs-inline-block{display:inline-block!important} +} +@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important} +table.visible-sm{display:table!important} +tr.visible-sm{display:table-row!important} +td.visible-sm,th.visible-sm{display:table-cell!important} +.visible-sm-block{display:block!important} +.visible-sm-inline{display:inline!important} +.visible-sm-inline-block{display:inline-block!important} +} +@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important} +table.visible-md{display:table!important} +tr.visible-md{display:table-row!important} +td.visible-md,th.visible-md{display:table-cell!important} +.visible-md-block{display:block!important} +.visible-md-inline{display:inline!important} +.visible-md-inline-block{display:inline-block!important} +} +@media (min-width:1200px){.visible-lg{display:block!important} +table.visible-lg{display:table!important} +tr.visible-lg{display:table-row!important} +td.visible-lg,th.visible-lg{display:table-cell!important} +.visible-lg-block{display:block!important} +.visible-lg-inline{display:inline!important} +.visible-lg-inline-block{display:inline-block!important} +} +@media (max-width:767px){.hidden-xs{display:none!important} +} +@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important} +} +@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important} +} +@media (min-width:1200px){.hidden-lg{display:none!important} +} +.visible-print{display:none!important} +@media print{.visible-print{display:block!important} +table.visible-print{display:table!important} +tr.visible-print{display:table-row!important} +td.visible-print,th.visible-print{display:table-cell!important} +} +.visible-print-block{display:none!important} +@media print{.visible-print-block{display:block!important} +} +.visible-print-inline{display:none!important} +@media print{.visible-print-inline{display:inline!important} +} +.visible-print-inline-block{display:none!important} +@media print{.visible-print-inline-block{display:inline-block!important} +.hidden-print{display:none!important} +} \ No newline at end of file diff --git a/powerauth-fido2-tests/src/main/webapp/resources/images/background.png b/powerauth-fido2-tests/src/main/webapp/resources/images/background.png new file mode 100644 index 0000000000000000000000000000000000000000..ace947af214064e480e17c9ef696272460f8b31c GIT binary patch literal 194687 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%0DuF1)VZ( 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 QU(vs;l#*oGdn5n<2e`vk9RL6T literal 0 HcmV?d00001 diff --git a/powerauth-fido2-tests/src/main/webapp/resources/images/logo.png b/powerauth-fido2-tests/src/main/webapp/resources/images/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..a0d30571cfd03c82cf77e1d35d3b43115903c743 GIT binary patch literal 9896 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]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=zt.pop()||S.expando+"_"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ut.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,"$1"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument("").body).innerHTML="
",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0 o.value); + if (operationTemplateList.length < 1) { + console.log("There is no operation template to choose from."); + $("#errorMessage").html("Create a login template first."); + $("#errorDiv").show(); + $(":submit").attr("disabled", true); + } else if (!operationTemplateList.includes("login")) { + console.log("There is not operation template 'login'."); + $('#settingsBlock').show(); + } + + // Check if any application is available to select + const n_applications = $('#applicationId option').toArray().length; + if (n_applications < 2) { + console.log("There is no application to choose from."); + $("#errorMessage").html("Create an application first."); + $("#errorDiv").show(); + $(":submit").attr("disabled", true); + } + + // Set action on Register button click + $('#registerBtn').click(function(){ + $("#username").prop('required', true); + CEREMONY = REGISTRATION_CEREMONY; + }); + + // Set action on Login button click + $('#loginBtn').click(function(){ + $("#username").prop('required', false); + CEREMONY = AUTHENTICATION_CEREMONY; + }); + + // Set action on WebAuthn Settings button click + $('#settingsBtn').click(function () { + const settingsBlock = $('#settingsBlock'); + if (settingsBlock.is(":visible")) { + settingsBlock.hide(); + } else { + settingsBlock.show(); + } + }); + +}); \ No newline at end of file diff --git a/powerauth-fido2-tests/src/main/webapp/resources/js/payment.js b/powerauth-fido2-tests/src/main/webapp/resources/js/payment.js new file mode 100644 index 00000000..4fb39429 --- /dev/null +++ b/powerauth-fido2-tests/src/main/webapp/resources/js/payment.js @@ -0,0 +1,101 @@ + +const operationParamKeys = [] +const operationParamValues = [] + +/** + * Action taken on payment action. + * Shows success or errors to UI. + */ +async function handlePaymentSubmit() { + + const successDiv = $("#successDiv"); + const errorDiv = $("#errorDiv"); + errorDiv.hide(); + successDiv.hide(); + + const username = $("#username").val(); + const applicationId = $("#applicationId").val(); + const templateName = $("#operationTemplate").val(); + let operationParameters = { + "amount": $("#amount").val(), + "currency": $("#currency").val(), + "iban": $("#iban").val(), + } + + for (let i = 0; i < operationParamKeys.length; ++i) { + operationParameters[operationParamKeys[i].value] = operationParamValues[i].value + } + + try { + await requestCredential(username, applicationId, templateName, operationParameters); + successDiv.show(); + + } catch (e) { + errorDiv.show() + $("#errorMessage").html(e.message); + console.log("Error occurred during a ceremony") + console.log(e); + } +} + +/** + * Create additional Operation parameter fields. + */ +function createOperationParameter() { + const formFields = $("#divFormFields"); + const count = operationParamKeys.length; + + const key = document.createElement("input"); + key.type = "text"; + key.id = "key" + count; + key.placeholder = "Key"; + key.class = "form-control"; + key.style.width = "50%"; + + const value = document.createElement("input"); + value.type = "text"; + value.id = "value" + count; + value.placeholder = "Value"; + value.class = "form-control"; + value.style.width = "50%"; + + const div = document.createElement("div"); + div.class = "form-group input-group"; + div.style.width = "100%"; + + operationParamKeys[count] = key; + operationParamValues[count] = value; + + div.append(key); + div.append(value); + formFields.append(div); +} + +$(function() { + + const operationTemplateList = $('#operationTemplate option').toArray().map(o => o.value); + if (operationTemplateList.length < 1) { + console.log("There is no operation template to choose from."); + $("#errorMessage").html("Create a payment template first."); + $("#errorDiv").show(); + $(":submit").attr("disabled", true); + } else if (!operationTemplateList.includes("payment")) { + console.log("There is not operation template 'payment'."); + } + + // Set action on Register button click + $('#payBtn').click(function(){ + CEREMONY = AUTHENTICATION_CEREMONY; + }); + + // Set action on Logout button click + $('#logoutBtn').click(function(){ + window.location.href = SERVLET_CONTEXT_PATH + "/logout"; + }); + + // Set action on Add operation parameter button click + $('#addFieldBtn').click(function(){ + createOperationParameter(); + }); + +}); diff --git a/powerauth-fido2-tests/src/main/webapp/resources/js/webauthn.js b/powerauth-fido2-tests/src/main/webapp/resources/js/webauthn.js new file mode 100644 index 00000000..8e3060f6 --- /dev/null +++ b/powerauth-fido2-tests/src/main/webapp/resources/js/webauthn.js @@ -0,0 +1,263 @@ + +const REGISTRATION_CEREMONY = "registration"; +const AUTHENTICATION_CEREMONY = "authentication"; +let CEREMONY; + +/** + * WebAuthn ceremony to create a new credential on register request. + */ +async function createCredential(username, applicationId) { + const options = await fetchRegistrationOptions(username, applicationId); + const credential = await navigator.credentials.create({ + publicKey: options + }); + console.log("Public Key Credential") + console.log(JSON.stringify(credential, null, 2)); + + const registerResponse = await registerCredentials(username, applicationId, credential) + console.log("PowerAuth Registration Response") + console.log(JSON.stringify(registerResponse, null, 2)); + if (registerResponse.activationStatus !== "ACTIVE") { + throw Error("Registration process failed, activation is not in state 'ACTIVE'."); + } +} + +/** + * WebAuthn ceremony to request an existing credential on login request. + */ +async function requestCredential(username, applicationId, templateName, operationParameters) { + const options = await fetchAssertionOptions(username, applicationId, templateName, operationParameters); + const credential = await navigator.credentials.get({ + publicKey: options + }); + + console.log("Public Key Credential") + console.log(JSON.stringify(credential, null, 2)); + + const authenticateResponse = await verifyAssertion(applicationId, options.challenge, credential) + console.log("PowerAuth Authentication Response") + console.log(JSON.stringify(authenticateResponse, null, 2)); + if (!authenticateResponse.assertionValid) { + throw Error("Assertion is not valid."); + } +} + +/** + * Fetch and return public key credential creation options. + * @param username Username from user input. + * @param applicationId Application ID from user input. + * @returns public key credential creation options + */ +async function fetchRegistrationOptions(username, applicationId) { + + const fetchOptionsRequest = { + "username": username, + "applicationId": applicationId + }; + + let options = await post("/registration/options", fetchOptionsRequest) + + // Build selection criteria from WebAuthn settings customisable from UI + const userVerification = $("#userVerification").val(); + const residentKey = $("#residentKey").val(); + const authenticatorAttachment = $("#authenticatorAttachment").val(); + let authenticatorSelection = { + userVerification: userVerification, + residentKey: residentKey, + requireResidentKey: residentKey === "required", + } + + // If user did not choose platform or cross-platform attachment, omit the field to allow both. + if (authenticatorAttachment === "platform" || authenticatorAttachment === "cross-platform") { + authenticatorSelection = { + ...authenticatorSelection, + authenticatorAttachment: authenticatorAttachment + } + } + + // Add WebAuthn settings customisable from UI + options = { + ...options, + authenticatorSelection: authenticatorSelection, + attestation: $("#attestation").val() + } + + console.log("Public Key Credential Creation Options") + console.log(JSON.stringify(options, null, 2)); + + // Some fields have to be passed as buffer to navigator.create() + const byteEncoder = new TextEncoder(); + return { + ...options, + challenge: byteEncoder.encode(options.challenge), + user: { + ...options.user, + id: byteEncoder.encode(options.user.id) + }, + excludeCredentials: options.excludeCredentials?.map( credentialDescriptor => ({ + ...credentialDescriptor, + id: toBuffer(credentialDescriptor.id) + }) ) + } + +} + +/** + * Fetch and return public key credential request options. + * @param username Username from user input, may be empty. + * @param applicationId Application ID from user input. + * @param templateName Template name to use. + * @param operationParameters Parameters of the operation. + * @returns public key credential request options + */ +async function fetchAssertionOptions(username, applicationId, templateName, operationParameters) { + + const fetchOptionsRequest = { + "username": username, + "applicationId": applicationId, + "templateName": templateName, + "operationParameters": operationParameters + }; + + let options = await post("/assertion/options", fetchOptionsRequest) + // Add WebAuthn settings customisable from UI + options = { + ...options, + userVerification:$("#userVerification").val(), + } + + console.log("Public Key Credential Request Options") + console.log(JSON.stringify(options, null, 2)); + + // Some fields have to be passed as buffer to navigator.get() + const byteEncoder = new TextEncoder(); + return { + ...options, + challenge: byteEncoder.encode(options.challenge), + allowCredentials: options.allowCredentials?.map( credentialDescriptor => ({ + ...credentialDescriptor, + id: toBuffer(credentialDescriptor.id) + }) ) + } +} + +/** + * Send register credential request to PowerAuth Server + * @param username Username from user input. + * @param applicationId Application ID from user input. + * @param credential Newly created credential. + * @returns JSON response from PowerAuth Server. + */ +async function registerCredentials(username, applicationId, credential) { + const userVerification = $("#userVerification").val(); + + // RP entity and allowedOrigins are added on backend level + const requestBody = { + applicationId: applicationId, + username: username, + userVerificationRequired: userVerification === "required", + id: credential.id, + type: credential.type, + authenticatorAttachment: credential.authenticatorAttachment, + response: { + clientDataJSON: toBase64(credential.response.clientDataJSON), + attestationObject: toBase64(credential.response.attestationObject), + transports: credential.response.getTransports() + } + }; + + console.log("PowerAuth Registration Request") + return await post("/registration", requestBody); +} + +/** + * Send verify assertion request to PowerAuth server. + * @param applicationId Application ID from user input. + * @param challenge Challenge received from PowerAuth server. + * @param credential Retrieved credential. + * @returns JSON response from PowerAuth Server. + */ +async function verifyAssertion(applicationId, challenge, credential) { + const requestBody = { + applicationId: applicationId, + id: credential.id, + type: credential.type, + authenticatorAttachment: credential.authenticatorAttachment, + response: { + clientDataJSON: toBase64(credential.response.clientDataJSON), + authenticatorData: toBase64(credential.response.authenticatorData), + signature: toBase64(credential.response.signature), + userHandle: toBase64(credential.response.userHandle) + }, + expectedChallenge: new TextDecoder().decode(challenge), + userVerificationRequired: $("#userVerification").val() === "required" + }; + + console.log("PowerAuth Assertion Request") + return await post("/assertion", requestBody); +} + +/** + * Send a POST request to backend service. + * @param apiPath API path for the request. + * @param requestBody Body of the request. + * @returns JSON response. + */ +async function post(apiPath, requestBody) { + console.log("POST " + apiPath); + console.log(JSON.stringify(requestBody, null, 2)); + const response = await fetch(SERVLET_CONTEXT_PATH + apiPath, { + method: "POST", + body: JSON.stringify(requestBody), + headers: { + "Content-type": "application/json; charset=UTF-8" + } + }); + + const json = await response.json(); + + if (response.status !== 200) { + if (json.hasOwnProperty("responseObject")) { + throw Error(json.responseObject.message); + } else if (json.hasOwnProperty("error")) { + throw Error(json.error); + } + throw Error(JSON.stringify(json)); + } + + return json; +} + +/** + * Convert buffer to base64 string. + * @param buffer Buffer to convert. + * @returns {string} Converted string. + */ +function toBase64(buffer) { + const byteView = new Uint8Array(buffer); + let str = ""; + for (const charCode of byteView) { + str += String.fromCharCode(charCode); + } + return btoa(str); +} + +/** + * Convert base64 string to buffer. + * @param base64 String to convert. + * @returns {ArrayBufferLike} Converted array. + */ +function toBuffer(base64) { + base64 = base64 + .replace(/-/g, '+') + .replace(/_/g, '/'); + const binary_string = atob(base64); + const len = binary_string.length; + const bytes = new Uint8Array(len); + for (let i = 0; i < len; i++) { + bytes[i] = binary_string.charCodeAt(i); + } + return bytes.buffer; +} + + From 8f89d4f38d215ed409ac11de5a283364f176cd69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Pe=C5=A1ek?= Date: Thu, 14 Mar 2024 09:18:10 +0100 Subject: [PATCH 34/44] Fix #388: FIDO2 Test application: Sharing operation data (#390) --- powerauth-fido2-tests/pom.xml | 7 + .../fido2/service/AssertionService.java | 83 ++++++++++- .../src/main/webapp/resources/js/webauthn.js | 9 +- .../fido2/service/AssertionServiceTest.java | 140 ++++++++++++++++++ 4 files changed, 234 insertions(+), 5 deletions(-) create mode 100644 powerauth-fido2-tests/src/test/java/com/wultra/security/powerauth/fido2/service/AssertionServiceTest.java diff --git a/powerauth-fido2-tests/pom.xml b/powerauth-fido2-tests/pom.xml index d5298ecf..5d1715cb 100644 --- a/powerauth-fido2-tests/pom.xml +++ b/powerauth-fido2-tests/pom.xml @@ -62,6 +62,13 @@ logstash-logback-encoder + + + org.springframework.boot + spring-boot-starter-test + test + + diff --git a/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/AssertionService.java b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/AssertionService.java index 4bcbe43e..f1957d34 100644 --- a/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/AssertionService.java +++ b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/AssertionService.java @@ -34,8 +34,9 @@ import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; -import java.util.List; -import java.util.Map; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; /** * Service for WebAuthn authentication tasks. @@ -48,6 +49,8 @@ public class AssertionService { private static final String OPERATION_DATA_EXTENSION_KEY = "txAuthSimple"; + private static final String HMAC_SECRET_EXTENSION_KEY = "hmacGetSecret"; + private static final List OPERATION_DATA_FIELDS_PRIORITY = List.of("I", "Q", "A", "R", "D", "N"); private final PowerAuthFido2Client fido2Client; private final Fido2SharedService fido2SharedService; @@ -74,14 +77,17 @@ public AssertionOptionsResponse assertionOptions(final AssertionOptionsRequest r final AssertionChallengeResponse challengeResponse = fetchChallenge(userId, applicationId, request.templateName(), request.operationParameters()); final String challenge = challengeResponse.getChallenge(); final String operationData = extractOperationData(challenge); + final String shrunkOperationData = shrinkToFitByteArray(operationData); return AssertionOptionsResponse.builder() .challenge(challenge) .rpId(webAuthNConfig.getRpId()) .timeout(webAuthNConfig.getTimeout().toMillis()) .allowCredentials(existingCredentials) - .extensions(Map.of(OPERATION_DATA_EXTENSION_KEY, operationData)) - .build(); + .extensions(Map.of( + OPERATION_DATA_EXTENSION_KEY, operationData, + HMAC_SECRET_EXTENSION_KEY, convertToExtension(shrunkOperationData)) + ).build(); } /** @@ -130,4 +136,73 @@ private static String extractOperationData(final String challenge) { return split[1]; } + /** + * Function takes operation data in PowerAuth format and shrinks them, if necessary, to fit 64 bytes. + * If the passed operation data are longer than 64 bytes, they are parsed and rebuild with only subset + * of fields. The building process tries to greedy append all fields sorted by priority, until the array is full. + * @param operationData Operation data to shrink. + * @return Shrunk operation data. + */ + private static String shrinkToFitByteArray(final String operationData) { + if (fitsIntoByteArray(operationData)) { + logger.debug("Operation data fits into array as is."); + return operationData; + } + + final Map operationDataFields = parseOperationData(operationData); + if (operationDataFields.isEmpty()) { + throw new IllegalStateException("Operation data are present in unexpected format."); + } + String cropped = operationDataFields.get("header"); + + for (final String fieldKey : OPERATION_DATA_FIELDS_PRIORITY) { + cropped = appendIfFitsByteArray(cropped, operationDataFields, fieldKey); + } + + logger.debug("Operation data were shrunk to {}", cropped); + return cropped; + } + + private static Map parseOperationData(final String operationData) { + final String[] fields = operationData.split("\\*"); + if (fields.length < 1) { + return Collections.emptyMap(); + } + + final Map fieldMap = Arrays.stream(fields) + .skip(1) + .filter(StringUtils::hasText) + .collect(Collectors.toMap(field -> field.substring(0, 1), Function.identity())); + fieldMap.put("header", fields[0]); + return fieldMap; + } + + private static String appendIfFitsByteArray(String croppedData, final Map fields, final String fieldKey) { + if (fields.containsKey(fieldKey) && fitsIntoByteArray(croppedData + "*" + fields.get(fieldKey))) { + croppedData += "*" + fields.get(fieldKey); + } + return croppedData; + } + + private static boolean fitsIntoByteArray(final String operationData) { + return operationData.getBytes().length <= 64; + } + + private static HMACGetSecretInput convertToExtension(final String operationData) { + if (!fitsIntoByteArray(operationData)) { + throw new IllegalStateException("Operation data are too long."); + } + + final byte[] paddedBytes = new byte[64]; + Arrays.fill(paddedBytes, (byte) 0x2A); + final byte[] operationDataBytes = operationData.getBytes(); + System.arraycopy(operationDataBytes, 0, paddedBytes, 0, operationDataBytes.length); + + final byte[] seed1 = Arrays.copyOfRange(paddedBytes, 0, 32); + final byte[] seed2 = Arrays.copyOfRange(paddedBytes, 32, 64); + return new HMACGetSecretInput(Base64.getEncoder().encodeToString(seed1), Base64.getEncoder().encodeToString(seed2)); + } + + public record HMACGetSecretInput(String seed1, String seed2) {} + } diff --git a/powerauth-fido2-tests/src/main/webapp/resources/js/webauthn.js b/powerauth-fido2-tests/src/main/webapp/resources/js/webauthn.js index 8e3060f6..eb0d9866 100644 --- a/powerauth-fido2-tests/src/main/webapp/resources/js/webauthn.js +++ b/powerauth-fido2-tests/src/main/webapp/resources/js/webauthn.js @@ -137,7 +137,14 @@ async function fetchAssertionOptions(username, applicationId, templateName, oper allowCredentials: options.allowCredentials?.map( credentialDescriptor => ({ ...credentialDescriptor, id: toBuffer(credentialDescriptor.id) - }) ) + }) ), + extensions: { + ...options.extensions, + hmacGetSecret: { + seed1: toBuffer(options.extensions.hmacGetSecret.seed1), + seed2: toBuffer(options.extensions.hmacGetSecret.seed2) + } + } } } diff --git a/powerauth-fido2-tests/src/test/java/com/wultra/security/powerauth/fido2/service/AssertionServiceTest.java b/powerauth-fido2-tests/src/test/java/com/wultra/security/powerauth/fido2/service/AssertionServiceTest.java new file mode 100644 index 00000000..53de83df --- /dev/null +++ b/powerauth-fido2-tests/src/test/java/com/wultra/security/powerauth/fido2/service/AssertionServiceTest.java @@ -0,0 +1,140 @@ +/* + * PowerAuth test and related software components + * Copyright (C) 2024 Wultra s.r.o. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.wultra.security.powerauth.fido2.service; + +import com.wultra.security.powerauth.client.PowerAuthFido2Client; +import com.wultra.security.powerauth.client.model.response.fido2.AssertionChallengeResponse; +import com.wultra.security.powerauth.fido2.configuration.WebAuthnConfiguration; +import com.wultra.security.powerauth.fido2.controller.request.AssertionOptionsRequest; +import com.wultra.security.powerauth.fido2.controller.response.AssertionOptionsResponse; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Base64; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +/** + * Test of {@link AssertionService} + * + * @author Jan Pesek, jan.pesek@wultra.com + */ +@ExtendWith(MockitoExtension.class) +class AssertionServiceTest { + + @Mock + private PowerAuthFido2Client fido2Client; + + @Mock + private Fido2SharedService fido2SharedService; + + @Mock + private WebAuthnConfiguration webAuthNConfig; + + @InjectMocks + private AssertionService tested; + + @Test + void testAssertionOptions_paymentData() throws Exception { + final String username = null; + final String applicationId = "app"; + final String templateName = "payment"; + final Map operationParameters = Map.of( + "iban", "CZ5508000000001234567899", + "amount", "11499.99", + "currency", "CZK", + "note", "It's a gift!" + ); + + final AssertionOptionsRequest request = new AssertionOptionsRequest(username, applicationId, templateName, operationParameters); + + final AssertionChallengeResponse challengeResponse = new AssertionChallengeResponse(); + challengeResponse.setChallenge("operationId&" + buildPaymentData(operationParameters)); + when(fido2Client.requestAssertionChallenge(any())) + .thenReturn(challengeResponse); + + final AssertionOptionsResponse response = tested.assertionOptions(request); + final String rebuildPaymentData = convertHmacSecret((AssertionService.HMACGetSecretInput) response.extensions().get("hmacGetSecret")); + assertEquals("A1*ICZ5508000000001234567899*A11499.99CZK*NIt's a gift!", rebuildPaymentData); + } + + @Test + void testAssertionOptions_longPaymentData() throws Exception { + final String username = null; + final String applicationId = "app"; + final String templateName = "payment"; + final Map operationParameters = Map.of( + "iban", "CZ5508000000001234567899", + "amount", "11499.99", + "currency", "CZK", + "note", "This is a long story to tell..." + ); + + final AssertionOptionsRequest request = new AssertionOptionsRequest(username, applicationId, templateName, operationParameters); + + final AssertionChallengeResponse challengeResponse = new AssertionChallengeResponse(); + challengeResponse.setChallenge("operationId&" + buildPaymentData(operationParameters)); + when(fido2Client.requestAssertionChallenge(any())) + .thenReturn(challengeResponse); + + final AssertionOptionsResponse response = tested.assertionOptions(request); + final String rebuildPaymentData = convertHmacSecret((AssertionService.HMACGetSecretInput) response.extensions().get("hmacGetSecret")); + assertEquals("A1*ICZ5508000000001234567899*A11499.99CZK", rebuildPaymentData); + } + + private static String convertHmacSecret(final AssertionService.HMACGetSecretInput hmacSecret) { + final byte[] seed1Bytes = Base64.getDecoder().decode(hmacSecret.seed1()); + final byte[] seed2Bytes = Base64.getDecoder().decode(hmacSecret.seed2()); + + final byte[] operationDataBytes = new byte[64]; + System.arraycopy(seed1Bytes, 0, operationDataBytes, 0, seed1Bytes.length); + System.arraycopy(seed2Bytes, 0, operationDataBytes, 32, seed2Bytes.length); + + final String paddedOperationData = new String(operationDataBytes); + return String.join("*", paddedOperationData.split("\\*")); + } + + private static String buildPaymentData(final Map operationParameters) { + String paymentData = "A1"; + + if (operationParameters.containsKey("iban")) { + paymentData += "*I" + operationParameters.get("iban"); + } + + if (operationParameters.containsKey("amount")) { + paymentData += "*A" + operationParameters.get("amount"); + if (operationParameters.containsKey("currency")) { + paymentData += operationParameters.get("currency"); + } + } + + if (operationParameters.containsKey("note")) { + paymentData += "*N" + operationParameters.get("note"); + } + + return paymentData; + } + +} From d1647d88f955b562dda26cee617061dfc5e7f559 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Pe=C5=A1ek?= Date: Tue, 19 Mar 2024 12:00:41 +0100 Subject: [PATCH 35/44] Fix #391: FIDO2 Test application: Names refactoring (#392) --- .../fido2/service/AssertionService.java | 27 ++++++++++++++++--- .../fido2/service/Fido2SharedService.java | 2 +- .../fido2/service/RegistrationService.java | 3 ++- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/AssertionService.java b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/AssertionService.java index f1957d34..88d818a6 100644 --- a/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/AssertionService.java +++ b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/AssertionService.java @@ -18,7 +18,10 @@ package com.wultra.security.powerauth.fido2.service; +import com.webauthn4j.data.AuthenticatorTransport; +import com.webauthn4j.data.PublicKeyCredentialType; import com.wultra.security.powerauth.client.PowerAuthFido2Client; +import com.wultra.security.powerauth.client.model.entity.fido2.AllowCredentials; import com.wultra.security.powerauth.client.model.error.PowerAuthClientException; import com.wultra.security.powerauth.client.model.request.fido2.AssertionChallengeRequest; import com.wultra.security.powerauth.client.model.request.fido2.AssertionVerificationRequest; @@ -68,13 +71,18 @@ public AssertionOptionsResponse assertionOptions(final AssertionOptionsRequest r logger.info("Building assertion options for userId={}, applicationId={}", userId, applicationId); - final List existingCredentials = fido2SharedService.fetchExistingCredentials(userId, applicationId); - if (existingCredentials.isEmpty() && StringUtils.hasText(userId)) { + final AssertionChallengeResponse challengeResponse = fetchChallenge(userId, applicationId, request.templateName(), request.operationParameters()); + final var credentialList = Optional.ofNullable(challengeResponse.getAllowCredentials()); + if (credentialList.isEmpty() && StringUtils.hasText(userId)) { logger.info("User {} is not yet registered.", userId); throw new IllegalStateException("Not registered yet."); } - final AssertionChallengeResponse challengeResponse = fetchChallenge(userId, applicationId, request.templateName(), request.operationParameters()); + final List existingCredentials = credentialList + .orElse(Collections.emptyList()) + .stream() + .map(AssertionService::toCredentialDescriptor).toList(); + final String challenge = challengeResponse.getChallenge(); final String operationData = extractOperationData(challenge); final String shrunkOperationData = shrinkToFitByteArray(operationData); @@ -98,7 +106,7 @@ HMAC_SECRET_EXTENSION_KEY, convertToExtension(shrunkOperationData)) */ public AssertionVerificationResponse authenticate(final VerifyAssertionRequest credential) throws PowerAuthClientException { final AssertionVerificationRequest request = new AssertionVerificationRequest(); - request.setId(credential.id()); + request.setCredentialId(Base64.getEncoder().encodeToString(credential.id().getBytes())); request.setType(credential.type().getValue()); request.setAuthenticatorAttachment(credential.authenticatorAttachment().getValue()); request.setResponse(credential.response()); @@ -118,6 +126,9 @@ public AssertionVerificationResponse authenticate(final VerifyAssertionRequest c private AssertionChallengeResponse fetchChallenge(final String userId, final String applicationId, final String templateName, final Map operationParameters) throws PowerAuthClientException { logger.info("Getting registration challenge for userId={}, applicationId={}, template={}, parameters={}", userId, applicationId, templateName, operationParameters); final AssertionChallengeRequest request = new AssertionChallengeRequest(); + if (StringUtils.hasText(userId)) { + request.setUserId(userId); + } request.setApplicationIds(List.of(applicationId)); request.setTemplateName(templateName); if (operationParameters != null) { @@ -136,6 +147,14 @@ private static String extractOperationData(final String challenge) { return split[1]; } + public static CredentialDescriptor toCredentialDescriptor(final AllowCredentials allowCredentials) { + final String credentialId = new String(allowCredentials.getCredentialId()); + final List transports = allowCredentials.getTransports().stream() + .map(AuthenticatorTransport::create) + .toList(); + return new CredentialDescriptor(PublicKeyCredentialType.create(allowCredentials.getType()), credentialId, transports); + } + /** * Function takes operation data in PowerAuth format and shrinks them, if necessary, to fit 64 bytes. * If the passed operation data are longer than 64 bytes, they are parsed and rebuild with only subset diff --git a/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/Fido2SharedService.java b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/Fido2SharedService.java index bfab372e..23b328a5 100644 --- a/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/Fido2SharedService.java +++ b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/Fido2SharedService.java @@ -98,7 +98,7 @@ private List listAuthenticators(final String userId, final @SuppressWarnings("unchecked") private static CredentialDescriptor toCredentialDescriptor(final AuthenticatorDetail authenticatorDetail) { - final String credentialId = authenticatorDetail.getExternalId(); + final String credentialId = authenticatorDetail.getCredentialId(); final List transports = (List) authenticatorDetail.getExtras().getOrDefault(EXTRAS_TRANSPORT_KEY, Collections.emptyList()); return new CredentialDescriptor(CREDENTIAL_TYPE, credentialId, transports); } diff --git a/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/RegistrationService.java b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/RegistrationService.java index 53242040..90e809fd 100644 --- a/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/RegistrationService.java +++ b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/RegistrationService.java @@ -37,6 +37,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import java.util.Base64; import java.util.List; /** @@ -107,7 +108,7 @@ private RegistrationChallengeResponse fetchChallenge(final String userId, final private AuthenticatorParameters buildAuthenticatorParameters(final RegisterCredentialRequest credential) { final AuthenticatorParameters parameters = new AuthenticatorParameters(); - parameters.setId(credential.id()); + parameters.setCredentialId(Base64.getEncoder().encodeToString(credential.id().getBytes())); parameters.setAuthenticatorAttachment(credential.authenticatorAttachment().getValue()); parameters.setType(credential.type().getValue()); parameters.setResponse(credential.response()); From bbc322e2d62e230d7ee56e3ba8304c47d59a5d14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Pe=C5=A1ek?= Date: Thu, 21 Mar 2024 08:52:04 +0100 Subject: [PATCH 36/44] Fix #394: FIDO2 Test Application: Adopt new approach of sharing operation data (#395) --- .../fido2/service/AssertionService.java | 97 ++++--------------- .../src/main/webapp/resources/js/webauthn.js | 4 - .../fido2/service/AssertionServiceTest.java | 75 +++++++++----- 3 files changed, 69 insertions(+), 107 deletions(-) diff --git a/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/AssertionService.java b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/AssertionService.java index 88d818a6..b0d3bc46 100644 --- a/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/AssertionService.java +++ b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/AssertionService.java @@ -38,7 +38,6 @@ import org.springframework.util.StringUtils; import java.util.*; -import java.util.function.Function; import java.util.stream.Collectors; /** @@ -52,11 +51,8 @@ public class AssertionService { private static final String OPERATION_DATA_EXTENSION_KEY = "txAuthSimple"; - private static final String HMAC_SECRET_EXTENSION_KEY = "hmacGetSecret"; - private static final List OPERATION_DATA_FIELDS_PRIORITY = List.of("I", "Q", "A", "R", "D", "N"); private final PowerAuthFido2Client fido2Client; - private final Fido2SharedService fido2SharedService; private final WebAuthnConfiguration webAuthNConfig; /** @@ -81,11 +77,28 @@ public AssertionOptionsResponse assertionOptions(final AssertionOptionsRequest r final List existingCredentials = credentialList .orElse(Collections.emptyList()) .stream() - .map(AssertionService::toCredentialDescriptor).toList(); + .map(AssertionService::toCredentialDescriptor) + .collect(Collectors.toCollection(ArrayList::new)); final String challenge = challengeResponse.getChallenge(); final String operationData = extractOperationData(challenge); - final String shrunkOperationData = shrinkToFitByteArray(operationData); + + // Relevant to Wultra Authenticator 1 only. If there is a credential with usb transport, + // append a virtual credential holding operation data with usb transport. + final boolean containsUsbTransport = existingCredentials.stream() + .map(CredentialDescriptor::transports) + .flatMap(Collection::stream) + .anyMatch(AuthenticatorTransport.USB::equals); + + if (containsUsbTransport) { + logger.debug("Appending operation data as a virtual credential."); + existingCredentials.add( + new CredentialDescriptor( + PublicKeyCredentialType.PUBLIC_KEY, + Base64.getEncoder().encodeToString(operationData.getBytes()), + List.of(AuthenticatorTransport.USB)) + ); + } return AssertionOptionsResponse.builder() .challenge(challenge) @@ -93,8 +106,7 @@ public AssertionOptionsResponse assertionOptions(final AssertionOptionsRequest r .timeout(webAuthNConfig.getTimeout().toMillis()) .allowCredentials(existingCredentials) .extensions(Map.of( - OPERATION_DATA_EXTENSION_KEY, operationData, - HMAC_SECRET_EXTENSION_KEY, convertToExtension(shrunkOperationData)) + OPERATION_DATA_EXTENSION_KEY, operationData) ).build(); } @@ -155,73 +167,4 @@ public static CredentialDescriptor toCredentialDescriptor(final AllowCredentials return new CredentialDescriptor(PublicKeyCredentialType.create(allowCredentials.getType()), credentialId, transports); } - /** - * Function takes operation data in PowerAuth format and shrinks them, if necessary, to fit 64 bytes. - * If the passed operation data are longer than 64 bytes, they are parsed and rebuild with only subset - * of fields. The building process tries to greedy append all fields sorted by priority, until the array is full. - * @param operationData Operation data to shrink. - * @return Shrunk operation data. - */ - private static String shrinkToFitByteArray(final String operationData) { - if (fitsIntoByteArray(operationData)) { - logger.debug("Operation data fits into array as is."); - return operationData; - } - - final Map operationDataFields = parseOperationData(operationData); - if (operationDataFields.isEmpty()) { - throw new IllegalStateException("Operation data are present in unexpected format."); - } - String cropped = operationDataFields.get("header"); - - for (final String fieldKey : OPERATION_DATA_FIELDS_PRIORITY) { - cropped = appendIfFitsByteArray(cropped, operationDataFields, fieldKey); - } - - logger.debug("Operation data were shrunk to {}", cropped); - return cropped; - } - - private static Map parseOperationData(final String operationData) { - final String[] fields = operationData.split("\\*"); - if (fields.length < 1) { - return Collections.emptyMap(); - } - - final Map fieldMap = Arrays.stream(fields) - .skip(1) - .filter(StringUtils::hasText) - .collect(Collectors.toMap(field -> field.substring(0, 1), Function.identity())); - fieldMap.put("header", fields[0]); - return fieldMap; - } - - private static String appendIfFitsByteArray(String croppedData, final Map fields, final String fieldKey) { - if (fields.containsKey(fieldKey) && fitsIntoByteArray(croppedData + "*" + fields.get(fieldKey))) { - croppedData += "*" + fields.get(fieldKey); - } - return croppedData; - } - - private static boolean fitsIntoByteArray(final String operationData) { - return operationData.getBytes().length <= 64; - } - - private static HMACGetSecretInput convertToExtension(final String operationData) { - if (!fitsIntoByteArray(operationData)) { - throw new IllegalStateException("Operation data are too long."); - } - - final byte[] paddedBytes = new byte[64]; - Arrays.fill(paddedBytes, (byte) 0x2A); - final byte[] operationDataBytes = operationData.getBytes(); - System.arraycopy(operationDataBytes, 0, paddedBytes, 0, operationDataBytes.length); - - final byte[] seed1 = Arrays.copyOfRange(paddedBytes, 0, 32); - final byte[] seed2 = Arrays.copyOfRange(paddedBytes, 32, 64); - return new HMACGetSecretInput(Base64.getEncoder().encodeToString(seed1), Base64.getEncoder().encodeToString(seed2)); - } - - public record HMACGetSecretInput(String seed1, String seed2) {} - } diff --git a/powerauth-fido2-tests/src/main/webapp/resources/js/webauthn.js b/powerauth-fido2-tests/src/main/webapp/resources/js/webauthn.js index eb0d9866..c738aa7f 100644 --- a/powerauth-fido2-tests/src/main/webapp/resources/js/webauthn.js +++ b/powerauth-fido2-tests/src/main/webapp/resources/js/webauthn.js @@ -140,10 +140,6 @@ async function fetchAssertionOptions(username, applicationId, templateName, oper }) ), extensions: { ...options.extensions, - hmacGetSecret: { - seed1: toBuffer(options.extensions.hmacGetSecret.seed1), - seed2: toBuffer(options.extensions.hmacGetSecret.seed2) - } } } } diff --git a/powerauth-fido2-tests/src/test/java/com/wultra/security/powerauth/fido2/service/AssertionServiceTest.java b/powerauth-fido2-tests/src/test/java/com/wultra/security/powerauth/fido2/service/AssertionServiceTest.java index 53de83df..4610a4d0 100644 --- a/powerauth-fido2-tests/src/test/java/com/wultra/security/powerauth/fido2/service/AssertionServiceTest.java +++ b/powerauth-fido2-tests/src/test/java/com/wultra/security/powerauth/fido2/service/AssertionServiceTest.java @@ -19,6 +19,7 @@ package com.wultra.security.powerauth.fido2.service; import com.wultra.security.powerauth.client.PowerAuthFido2Client; +import com.wultra.security.powerauth.client.model.entity.fido2.AllowCredentials; import com.wultra.security.powerauth.client.model.response.fido2.AssertionChallengeResponse; import com.wultra.security.powerauth.fido2.configuration.WebAuthnConfiguration; import com.wultra.security.powerauth.fido2.controller.request.AssertionOptionsRequest; @@ -30,9 +31,11 @@ import org.mockito.junit.jupiter.MockitoExtension; import java.util.Base64; +import java.util.Collections; +import java.util.List; import java.util.Map; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; @@ -57,63 +60,83 @@ class AssertionServiceTest { private AssertionService tested; @Test - void testAssertionOptions_paymentData() throws Exception { + void testAssertionOptions_emptyAllowCredential() throws Exception { final String username = null; final String applicationId = "app"; + final String templateName = "login"; + + final AssertionOptionsRequest request = new AssertionOptionsRequest(username, applicationId, templateName, null); + + final AssertionChallengeResponse challengeResponse = new AssertionChallengeResponse(); + challengeResponse.setChallenge("operationId&A2"); + challengeResponse.setAllowCredentials(Collections.emptyList()); + when(fido2Client.requestAssertionChallenge(any())) + .thenReturn(challengeResponse); + + final AssertionOptionsResponse response = tested.assertionOptions(request); + assertTrue(response.allowCredentials().isEmpty()); + assertEquals("A2", response.extensions().get("txAuthSimple")); + } + + @Test + void testAssertionOptions_nonEmptyAllowCredential() throws Exception { + final String username = "currentUser"; + final String applicationId = "app"; final String templateName = "payment"; final Map operationParameters = Map.of( "iban", "CZ5508000000001234567899", "amount", "11499.99", - "currency", "CZK", - "note", "It's a gift!" + "currency", "CZK" ); final AssertionOptionsRequest request = new AssertionOptionsRequest(username, applicationId, templateName, operationParameters); + final AllowCredentials existingCredential = new AllowCredentials(); + existingCredential.setCredentialId("credentialID".getBytes()); + existingCredential.setTransports(List.of("internal")); + final AssertionChallengeResponse challengeResponse = new AssertionChallengeResponse(); challengeResponse.setChallenge("operationId&" + buildPaymentData(operationParameters)); + challengeResponse.setAllowCredentials(List.of(existingCredential)); when(fido2Client.requestAssertionChallenge(any())) .thenReturn(challengeResponse); - final AssertionOptionsResponse response = tested.assertionOptions(request); - final String rebuildPaymentData = convertHmacSecret((AssertionService.HMACGetSecretInput) response.extensions().get("hmacGetSecret")); - assertEquals("A1*ICZ5508000000001234567899*A11499.99CZK*NIt's a gift!", rebuildPaymentData); + final AssertionOptionsResponse assertionOptionsResponse = tested.assertionOptions(request); + assertEquals(1, assertionOptionsResponse.allowCredentials().size()); + assertEquals("credentialID", assertionOptionsResponse.allowCredentials().get(0).id()); + assertEquals("A1*ICZ5508000000001234567899*A11499.99CZK", assertionOptionsResponse.extensions().get("txAuthSimple")); } @Test - void testAssertionOptions_longPaymentData() throws Exception { - final String username = null; + void testAssertionOptions_appendOperationData() throws Exception { + final String username = "currentUser"; final String applicationId = "app"; final String templateName = "payment"; final Map operationParameters = Map.of( "iban", "CZ5508000000001234567899", "amount", "11499.99", - "currency", "CZK", - "note", "This is a long story to tell..." + "currency", "CZK" ); final AssertionOptionsRequest request = new AssertionOptionsRequest(username, applicationId, templateName, operationParameters); + final AllowCredentials existingCredential = new AllowCredentials(); + existingCredential.setCredentialId("credentialID".getBytes()); + existingCredential.setTransports(List.of("usb")); + final AssertionChallengeResponse challengeResponse = new AssertionChallengeResponse(); challengeResponse.setChallenge("operationId&" + buildPaymentData(operationParameters)); + challengeResponse.setAllowCredentials(List.of(existingCredential)); when(fido2Client.requestAssertionChallenge(any())) .thenReturn(challengeResponse); - final AssertionOptionsResponse response = tested.assertionOptions(request); - final String rebuildPaymentData = convertHmacSecret((AssertionService.HMACGetSecretInput) response.extensions().get("hmacGetSecret")); - assertEquals("A1*ICZ5508000000001234567899*A11499.99CZK", rebuildPaymentData); - } - - private static String convertHmacSecret(final AssertionService.HMACGetSecretInput hmacSecret) { - final byte[] seed1Bytes = Base64.getDecoder().decode(hmacSecret.seed1()); - final byte[] seed2Bytes = Base64.getDecoder().decode(hmacSecret.seed2()); - - final byte[] operationDataBytes = new byte[64]; - System.arraycopy(seed1Bytes, 0, operationDataBytes, 0, seed1Bytes.length); - System.arraycopy(seed2Bytes, 0, operationDataBytes, 32, seed2Bytes.length); - - final String paddedOperationData = new String(operationDataBytes); - return String.join("*", paddedOperationData.split("\\*")); + final AssertionOptionsResponse assertionOptionsResponse = tested.assertionOptions(request); + assertEquals(2, assertionOptionsResponse.allowCredentials().size()); + assertEquals("credentialID", assertionOptionsResponse.allowCredentials().get(0).id()); + assertEquals(Base64.getEncoder().encodeToString("A1*ICZ5508000000001234567899*A11499.99CZK".getBytes()), + assertionOptionsResponse.allowCredentials().get(1).id()); + assertEquals("A1*ICZ5508000000001234567899*A11499.99CZK", + assertionOptionsResponse.extensions().get("txAuthSimple")); } private static String buildPaymentData(final Map operationParameters) { From 15bcc9d6d48015c8b60942f279f63b81f1ffc20f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Pe=C5=A1ek?= Date: Fri, 22 Mar 2024 16:25:32 +0100 Subject: [PATCH 37/44] Fix #396: FIDO2 Test application: visualSignature flag --- .../fido2/service/AssertionService.java | 10 ++++++- .../fido2/service/Fido2SharedService.java | 30 +++++++++++++++++++ .../src/main/webapp/resources/js/webauthn.js | 5 ++-- 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/AssertionService.java b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/AssertionService.java index b0d3bc46..73ee461b 100644 --- a/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/AssertionService.java +++ b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/AssertionService.java @@ -51,8 +51,10 @@ public class AssertionService { private static final String OPERATION_DATA_EXTENSION_KEY = "txAuthSimple"; + private static final String WA1_AAGUID = "dca09ba7-4992-4be8-9283-ee98cd6fb529"; private final PowerAuthFido2Client fido2Client; + private final Fido2SharedService fido2SharedService; private final WebAuthnConfiguration webAuthNConfig; /** @@ -117,8 +119,10 @@ public AssertionOptionsResponse assertionOptions(final AssertionOptionsRequest r * @throws PowerAuthClientException in case of PowerAuth server communication error. */ public AssertionVerificationResponse authenticate(final VerifyAssertionRequest credential) throws PowerAuthClientException { + final String credentialId = Base64.getEncoder().encodeToString(credential.id().getBytes()); + final AssertionVerificationRequest request = new AssertionVerificationRequest(); - request.setCredentialId(Base64.getEncoder().encodeToString(credential.id().getBytes())); + request.setCredentialId(credentialId); request.setType(credential.type().getValue()); request.setAuthenticatorAttachment(credential.authenticatorAttachment().getValue()); request.setResponse(credential.response()); @@ -128,6 +132,10 @@ public AssertionVerificationResponse authenticate(final VerifyAssertionRequest c request.setAllowedOrigins(webAuthNConfig.getAllowedOrigins()); request.setRequiresUserVerification(credential.userVerificationRequired()); + final String associatedAaguid = fido2SharedService.fetchAaguid(credential.response().getUserHandle(), credential.applicationId(), credentialId); + // TODO: wait for PAS feature merge + // request.setVisualSignature(WA1_AAGUID.equals(associatedAaguid)); + final AssertionVerificationResponse response = fido2Client.authenticate(request); logger.debug("Credential assertion response of userId={}: {}", response.getUserId(), response); logger.info("Activation ID {} of userId={}: valid={}", response.getActivationId(), response.getUserId(), response.isAssertionValid()); diff --git a/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/Fido2SharedService.java b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/Fido2SharedService.java index 23b328a5..60f4f828 100644 --- a/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/Fido2SharedService.java +++ b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/Fido2SharedService.java @@ -18,13 +18,19 @@ package com.wultra.security.powerauth.fido2.service; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import com.webauthn4j.data.AuthenticatorTransport; import com.webauthn4j.data.PublicKeyCredentialType; import com.wultra.security.powerauth.client.PowerAuthClient; import com.wultra.security.powerauth.client.PowerAuthFido2Client; +import com.wultra.security.powerauth.client.model.entity.Activation; import com.wultra.security.powerauth.client.model.entity.Application; import com.wultra.security.powerauth.client.model.entity.fido2.AuthenticatorDetail; +import com.wultra.security.powerauth.client.model.enumeration.ActivationStatus; +import com.wultra.security.powerauth.client.model.enumeration.Protocols; import com.wultra.security.powerauth.client.model.error.PowerAuthClientException; +import com.wultra.security.powerauth.client.model.request.GetActivationListForUserRequest; import com.wultra.security.powerauth.client.model.response.OperationTemplateDetailResponse; import com.wultra.security.powerauth.fido2.controller.response.CredentialDescriptor; import lombok.AllArgsConstructor; @@ -32,8 +38,11 @@ import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; +import java.nio.charset.StandardCharsets; +import java.util.Base64; import java.util.Collections; import java.util.List; +import java.util.Set; /** * Service shared for registration and authentication. @@ -50,6 +59,7 @@ public class Fido2SharedService { private final PowerAuthFido2Client fido2Client; private final PowerAuthClient powerAuthClient; + private final ObjectMapper objectMapper = new ObjectMapper(); /** * Fetch all registered credentials. @@ -96,6 +106,26 @@ private List listAuthenticators(final String userId, final return fido2Client.getRegisteredAuthenticatorList(userId, applicationId).getAuthenticators(); } + public String fetchAaguid(final String userId, final String applicationId, final String credentialId) throws PowerAuthClientException { + final GetActivationListForUserRequest request = new GetActivationListForUserRequest(); + request.setUserId(new String(Base64.getDecoder().decode(userId), StandardCharsets.UTF_8)); + request.setApplicationId(applicationId); + request.setProtocols(Set.of(Protocols.FIDO2)); + request.setActivationStatuses(Set.of(ActivationStatus.ACTIVE)); + final String extras = powerAuthClient.getActivationListForUser(request).getActivations().stream() + .filter(activation -> credentialId.equals(activation.getExternalId())) + .map(Activation::getExtras) + .findFirst() + .orElseThrow(() -> new IllegalStateException("The activation is missing.")); + + try { + return objectMapper.readTree(extras).get("aaguid").asText(); + } catch (JsonProcessingException e) { + logger.info("AAGUID could not be parsed.", e); + throw new IllegalStateException("Aaguid not associated with an activation."); + } + } + @SuppressWarnings("unchecked") private static CredentialDescriptor toCredentialDescriptor(final AuthenticatorDetail authenticatorDetail) { final String credentialId = authenticatorDetail.getCredentialId(); diff --git a/powerauth-fido2-tests/src/main/webapp/resources/js/webauthn.js b/powerauth-fido2-tests/src/main/webapp/resources/js/webauthn.js index c738aa7f..1064d2b3 100644 --- a/powerauth-fido2-tests/src/main/webapp/resources/js/webauthn.js +++ b/powerauth-fido2-tests/src/main/webapp/resources/js/webauthn.js @@ -181,6 +181,7 @@ async function registerCredentials(username, applicationId, credential) { * @returns JSON response from PowerAuth Server. */ async function verifyAssertion(applicationId, challenge, credential) { + const decoder = new TextDecoder(); const requestBody = { applicationId: applicationId, id: credential.id, @@ -190,9 +191,9 @@ async function verifyAssertion(applicationId, challenge, credential) { clientDataJSON: toBase64(credential.response.clientDataJSON), authenticatorData: toBase64(credential.response.authenticatorData), signature: toBase64(credential.response.signature), - userHandle: toBase64(credential.response.userHandle) + userHandle: decoder.decode(credential.response.userHandle) }, - expectedChallenge: new TextDecoder().decode(challenge), + expectedChallenge: decoder.decode(challenge), userVerificationRequired: $("#userVerification").val() === "required" }; From 65c8e5a5220e180fb1538eb5d856e6a41921634a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Mar 2024 17:05:23 +0000 Subject: [PATCH 38/44] Bump io.gatling.highcharts:gatling-charts-highcharts Bumps [io.gatling.highcharts:gatling-charts-highcharts](https://github.com/gatling/gatling-highcharts) from 3.10.4 to 3.10.5. - [Commits](https://github.com/gatling/gatling-highcharts/compare/v3.10.4...v3.10.5) --- updated-dependencies: - dependency-name: io.gatling.highcharts:gatling-charts-highcharts dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- powerauth-load-tests/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/powerauth-load-tests/pom.xml b/powerauth-load-tests/pom.xml index 857369de..fa5864cf 100644 --- a/powerauth-load-tests/pom.xml +++ b/powerauth-load-tests/pom.xml @@ -43,7 +43,7 @@ 4.8.2 4.8.1 - 3.10.4 + 3.10.5 From c2ac75a2c4a387e6ac4f4e499c09ac552d9f39ea Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Mar 2024 17:05:42 +0000 Subject: [PATCH 39/44] Bump org.springframework.boot:spring-boot-starter-parent Bumps [org.springframework.boot:spring-boot-starter-parent](https://github.com/spring-projects/spring-boot) from 3.2.3 to 3.2.4. - [Release notes](https://github.com/spring-projects/spring-boot/releases) - [Commits](https://github.com/spring-projects/spring-boot/compare/v3.2.3...v3.2.4) --- updated-dependencies: - dependency-name: org.springframework.boot:spring-boot-starter-parent dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index fb167e85..92238308 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ org.springframework.boot spring-boot-starter-parent - 3.2.3 + 3.2.4 From fa3d94c3578beca986bc023137782eecaddda775 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Pe=C5=A1ek?= Date: Tue, 26 Mar 2024 11:35:06 +0100 Subject: [PATCH 40/44] Fix #397: FIDO2 Test Application: Drop operation data manipulation (#400) * Fix #397: FIDO2 Test Application: Drop operation data manipulation * Handle credential encoding --- .../response/CredentialDescriptor.java | 2 +- .../fido2/service/AssertionService.java | 52 +----- .../fido2/service/Fido2SharedService.java | 33 +--- .../fido2/service/RegistrationService.java | 4 +- .../src/main/webapp/resources/js/webauthn.js | 3 - .../fido2/service/AssertionServiceTest.java | 163 ------------------ 6 files changed, 11 insertions(+), 246 deletions(-) delete mode 100644 powerauth-fido2-tests/src/test/java/com/wultra/security/powerauth/fido2/service/AssertionServiceTest.java diff --git a/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/response/CredentialDescriptor.java b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/response/CredentialDescriptor.java index c79711e4..97c12798 100644 --- a/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/response/CredentialDescriptor.java +++ b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/response/CredentialDescriptor.java @@ -30,6 +30,6 @@ */ public record CredentialDescriptor( PublicKeyCredentialType type, - String id, + byte[] id, List transports ) {} diff --git a/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/AssertionService.java b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/AssertionService.java index 73ee461b..9ab5a2fc 100644 --- a/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/AssertionService.java +++ b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/AssertionService.java @@ -38,7 +38,6 @@ import org.springframework.util.StringUtils; import java.util.*; -import java.util.stream.Collectors; /** * Service for WebAuthn authentication tasks. @@ -50,11 +49,7 @@ @Slf4j public class AssertionService { - private static final String OPERATION_DATA_EXTENSION_KEY = "txAuthSimple"; - private static final String WA1_AAGUID = "dca09ba7-4992-4be8-9283-ee98cd6fb529"; - private final PowerAuthFido2Client fido2Client; - private final Fido2SharedService fido2SharedService; private final WebAuthnConfiguration webAuthNConfig; /** @@ -80,36 +75,14 @@ public AssertionOptionsResponse assertionOptions(final AssertionOptionsRequest r .orElse(Collections.emptyList()) .stream() .map(AssertionService::toCredentialDescriptor) - .collect(Collectors.toCollection(ArrayList::new)); - - final String challenge = challengeResponse.getChallenge(); - final String operationData = extractOperationData(challenge); - - // Relevant to Wultra Authenticator 1 only. If there is a credential with usb transport, - // append a virtual credential holding operation data with usb transport. - final boolean containsUsbTransport = existingCredentials.stream() - .map(CredentialDescriptor::transports) - .flatMap(Collection::stream) - .anyMatch(AuthenticatorTransport.USB::equals); - - if (containsUsbTransport) { - logger.debug("Appending operation data as a virtual credential."); - existingCredentials.add( - new CredentialDescriptor( - PublicKeyCredentialType.PUBLIC_KEY, - Base64.getEncoder().encodeToString(operationData.getBytes()), - List.of(AuthenticatorTransport.USB)) - ); - } + .toList(); return AssertionOptionsResponse.builder() - .challenge(challenge) + .challenge(challengeResponse.getChallenge()) .rpId(webAuthNConfig.getRpId()) .timeout(webAuthNConfig.getTimeout().toMillis()) .allowCredentials(existingCredentials) - .extensions(Map.of( - OPERATION_DATA_EXTENSION_KEY, operationData) - ).build(); + .extensions(Collections.emptyMap()).build(); } /** @@ -119,10 +92,10 @@ public AssertionOptionsResponse assertionOptions(final AssertionOptionsRequest r * @throws PowerAuthClientException in case of PowerAuth server communication error. */ public AssertionVerificationResponse authenticate(final VerifyAssertionRequest credential) throws PowerAuthClientException { - final String credentialId = Base64.getEncoder().encodeToString(credential.id().getBytes()); + final byte[] credentialId = Base64.getUrlDecoder().decode(credential.id()); final AssertionVerificationRequest request = new AssertionVerificationRequest(); - request.setCredentialId(credentialId); + request.setCredentialId(Base64.getEncoder().encodeToString(credentialId)); request.setType(credential.type().getValue()); request.setAuthenticatorAttachment(credential.authenticatorAttachment().getValue()); request.setResponse(credential.response()); @@ -132,10 +105,6 @@ public AssertionVerificationResponse authenticate(final VerifyAssertionRequest c request.setAllowedOrigins(webAuthNConfig.getAllowedOrigins()); request.setRequiresUserVerification(credential.userVerificationRequired()); - final String associatedAaguid = fido2SharedService.fetchAaguid(credential.response().getUserHandle(), credential.applicationId(), credentialId); - // TODO: wait for PAS feature merge - // request.setVisualSignature(WA1_AAGUID.equals(associatedAaguid)); - final AssertionVerificationResponse response = fido2Client.authenticate(request); logger.debug("Credential assertion response of userId={}: {}", response.getUserId(), response); logger.info("Activation ID {} of userId={}: valid={}", response.getActivationId(), response.getUserId(), response.isAssertionValid()); @@ -159,20 +128,11 @@ private AssertionChallengeResponse fetchChallenge(final String userId, final Str return response; } - private static String extractOperationData(final String challenge) { - final String[] split = challenge.split("&", 2); - if (split.length != 2) { - throw new IllegalStateException("Invalid challenge format."); - } - return split[1]; - } - public static CredentialDescriptor toCredentialDescriptor(final AllowCredentials allowCredentials) { - final String credentialId = new String(allowCredentials.getCredentialId()); final List transports = allowCredentials.getTransports().stream() .map(AuthenticatorTransport::create) .toList(); - return new CredentialDescriptor(PublicKeyCredentialType.create(allowCredentials.getType()), credentialId, transports); + return new CredentialDescriptor(PublicKeyCredentialType.create(allowCredentials.getType()), allowCredentials.getCredentialId(), transports); } } diff --git a/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/Fido2SharedService.java b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/Fido2SharedService.java index 60f4f828..b518408c 100644 --- a/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/Fido2SharedService.java +++ b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/Fido2SharedService.java @@ -18,19 +18,13 @@ package com.wultra.security.powerauth.fido2.service; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; import com.webauthn4j.data.AuthenticatorTransport; import com.webauthn4j.data.PublicKeyCredentialType; import com.wultra.security.powerauth.client.PowerAuthClient; import com.wultra.security.powerauth.client.PowerAuthFido2Client; -import com.wultra.security.powerauth.client.model.entity.Activation; import com.wultra.security.powerauth.client.model.entity.Application; import com.wultra.security.powerauth.client.model.entity.fido2.AuthenticatorDetail; -import com.wultra.security.powerauth.client.model.enumeration.ActivationStatus; -import com.wultra.security.powerauth.client.model.enumeration.Protocols; import com.wultra.security.powerauth.client.model.error.PowerAuthClientException; -import com.wultra.security.powerauth.client.model.request.GetActivationListForUserRequest; import com.wultra.security.powerauth.client.model.response.OperationTemplateDetailResponse; import com.wultra.security.powerauth.fido2.controller.response.CredentialDescriptor; import lombok.AllArgsConstructor; @@ -38,11 +32,8 @@ import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; -import java.nio.charset.StandardCharsets; -import java.util.Base64; import java.util.Collections; import java.util.List; -import java.util.Set; /** * Service shared for registration and authentication. @@ -59,7 +50,6 @@ public class Fido2SharedService { private final PowerAuthFido2Client fido2Client; private final PowerAuthClient powerAuthClient; - private final ObjectMapper objectMapper = new ObjectMapper(); /** * Fetch all registered credentials. @@ -106,31 +96,10 @@ private List listAuthenticators(final String userId, final return fido2Client.getRegisteredAuthenticatorList(userId, applicationId).getAuthenticators(); } - public String fetchAaguid(final String userId, final String applicationId, final String credentialId) throws PowerAuthClientException { - final GetActivationListForUserRequest request = new GetActivationListForUserRequest(); - request.setUserId(new String(Base64.getDecoder().decode(userId), StandardCharsets.UTF_8)); - request.setApplicationId(applicationId); - request.setProtocols(Set.of(Protocols.FIDO2)); - request.setActivationStatuses(Set.of(ActivationStatus.ACTIVE)); - final String extras = powerAuthClient.getActivationListForUser(request).getActivations().stream() - .filter(activation -> credentialId.equals(activation.getExternalId())) - .map(Activation::getExtras) - .findFirst() - .orElseThrow(() -> new IllegalStateException("The activation is missing.")); - - try { - return objectMapper.readTree(extras).get("aaguid").asText(); - } catch (JsonProcessingException e) { - logger.info("AAGUID could not be parsed.", e); - throw new IllegalStateException("Aaguid not associated with an activation."); - } - } - @SuppressWarnings("unchecked") private static CredentialDescriptor toCredentialDescriptor(final AuthenticatorDetail authenticatorDetail) { - final String credentialId = authenticatorDetail.getCredentialId(); final List transports = (List) authenticatorDetail.getExtras().getOrDefault(EXTRAS_TRANSPORT_KEY, Collections.emptyList()); - return new CredentialDescriptor(CREDENTIAL_TYPE, credentialId, transports); + return new CredentialDescriptor(CREDENTIAL_TYPE, authenticatorDetail.getCredentialId().getBytes(), transports); } } diff --git a/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/RegistrationService.java b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/RegistrationService.java index 90e809fd..ccf675df 100644 --- a/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/RegistrationService.java +++ b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/RegistrationService.java @@ -107,8 +107,10 @@ private RegistrationChallengeResponse fetchChallenge(final String userId, final } private AuthenticatorParameters buildAuthenticatorParameters(final RegisterCredentialRequest credential) { + final byte[] credentialId = Base64.getUrlDecoder().decode(credential.id()); + final AuthenticatorParameters parameters = new AuthenticatorParameters(); - parameters.setCredentialId(Base64.getEncoder().encodeToString(credential.id().getBytes())); + parameters.setCredentialId(Base64.getEncoder().encodeToString(credentialId)); parameters.setAuthenticatorAttachment(credential.authenticatorAttachment().getValue()); parameters.setType(credential.type().getValue()); parameters.setResponse(credential.response()); diff --git a/powerauth-fido2-tests/src/main/webapp/resources/js/webauthn.js b/powerauth-fido2-tests/src/main/webapp/resources/js/webauthn.js index 1064d2b3..1ef241a0 100644 --- a/powerauth-fido2-tests/src/main/webapp/resources/js/webauthn.js +++ b/powerauth-fido2-tests/src/main/webapp/resources/js/webauthn.js @@ -252,9 +252,6 @@ function toBase64(buffer) { * @returns {ArrayBufferLike} Converted array. */ function toBuffer(base64) { - base64 = base64 - .replace(/-/g, '+') - .replace(/_/g, '/'); const binary_string = atob(base64); const len = binary_string.length; const bytes = new Uint8Array(len); diff --git a/powerauth-fido2-tests/src/test/java/com/wultra/security/powerauth/fido2/service/AssertionServiceTest.java b/powerauth-fido2-tests/src/test/java/com/wultra/security/powerauth/fido2/service/AssertionServiceTest.java deleted file mode 100644 index 4610a4d0..00000000 --- a/powerauth-fido2-tests/src/test/java/com/wultra/security/powerauth/fido2/service/AssertionServiceTest.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * PowerAuth test and related software components - * Copyright (C) 2024 Wultra s.r.o. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package com.wultra.security.powerauth.fido2.service; - -import com.wultra.security.powerauth.client.PowerAuthFido2Client; -import com.wultra.security.powerauth.client.model.entity.fido2.AllowCredentials; -import com.wultra.security.powerauth.client.model.response.fido2.AssertionChallengeResponse; -import com.wultra.security.powerauth.fido2.configuration.WebAuthnConfiguration; -import com.wultra.security.powerauth.fido2.controller.request.AssertionOptionsRequest; -import com.wultra.security.powerauth.fido2.controller.response.AssertionOptionsResponse; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import java.util.Base64; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.when; - -/** - * Test of {@link AssertionService} - * - * @author Jan Pesek, jan.pesek@wultra.com - */ -@ExtendWith(MockitoExtension.class) -class AssertionServiceTest { - - @Mock - private PowerAuthFido2Client fido2Client; - - @Mock - private Fido2SharedService fido2SharedService; - - @Mock - private WebAuthnConfiguration webAuthNConfig; - - @InjectMocks - private AssertionService tested; - - @Test - void testAssertionOptions_emptyAllowCredential() throws Exception { - final String username = null; - final String applicationId = "app"; - final String templateName = "login"; - - final AssertionOptionsRequest request = new AssertionOptionsRequest(username, applicationId, templateName, null); - - final AssertionChallengeResponse challengeResponse = new AssertionChallengeResponse(); - challengeResponse.setChallenge("operationId&A2"); - challengeResponse.setAllowCredentials(Collections.emptyList()); - when(fido2Client.requestAssertionChallenge(any())) - .thenReturn(challengeResponse); - - final AssertionOptionsResponse response = tested.assertionOptions(request); - assertTrue(response.allowCredentials().isEmpty()); - assertEquals("A2", response.extensions().get("txAuthSimple")); - } - - @Test - void testAssertionOptions_nonEmptyAllowCredential() throws Exception { - final String username = "currentUser"; - final String applicationId = "app"; - final String templateName = "payment"; - final Map operationParameters = Map.of( - "iban", "CZ5508000000001234567899", - "amount", "11499.99", - "currency", "CZK" - ); - - final AssertionOptionsRequest request = new AssertionOptionsRequest(username, applicationId, templateName, operationParameters); - - final AllowCredentials existingCredential = new AllowCredentials(); - existingCredential.setCredentialId("credentialID".getBytes()); - existingCredential.setTransports(List.of("internal")); - - final AssertionChallengeResponse challengeResponse = new AssertionChallengeResponse(); - challengeResponse.setChallenge("operationId&" + buildPaymentData(operationParameters)); - challengeResponse.setAllowCredentials(List.of(existingCredential)); - when(fido2Client.requestAssertionChallenge(any())) - .thenReturn(challengeResponse); - - final AssertionOptionsResponse assertionOptionsResponse = tested.assertionOptions(request); - assertEquals(1, assertionOptionsResponse.allowCredentials().size()); - assertEquals("credentialID", assertionOptionsResponse.allowCredentials().get(0).id()); - assertEquals("A1*ICZ5508000000001234567899*A11499.99CZK", assertionOptionsResponse.extensions().get("txAuthSimple")); - } - - @Test - void testAssertionOptions_appendOperationData() throws Exception { - final String username = "currentUser"; - final String applicationId = "app"; - final String templateName = "payment"; - final Map operationParameters = Map.of( - "iban", "CZ5508000000001234567899", - "amount", "11499.99", - "currency", "CZK" - ); - - final AssertionOptionsRequest request = new AssertionOptionsRequest(username, applicationId, templateName, operationParameters); - - final AllowCredentials existingCredential = new AllowCredentials(); - existingCredential.setCredentialId("credentialID".getBytes()); - existingCredential.setTransports(List.of("usb")); - - final AssertionChallengeResponse challengeResponse = new AssertionChallengeResponse(); - challengeResponse.setChallenge("operationId&" + buildPaymentData(operationParameters)); - challengeResponse.setAllowCredentials(List.of(existingCredential)); - when(fido2Client.requestAssertionChallenge(any())) - .thenReturn(challengeResponse); - - final AssertionOptionsResponse assertionOptionsResponse = tested.assertionOptions(request); - assertEquals(2, assertionOptionsResponse.allowCredentials().size()); - assertEquals("credentialID", assertionOptionsResponse.allowCredentials().get(0).id()); - assertEquals(Base64.getEncoder().encodeToString("A1*ICZ5508000000001234567899*A11499.99CZK".getBytes()), - assertionOptionsResponse.allowCredentials().get(1).id()); - assertEquals("A1*ICZ5508000000001234567899*A11499.99CZK", - assertionOptionsResponse.extensions().get("txAuthSimple")); - } - - private static String buildPaymentData(final Map operationParameters) { - String paymentData = "A1"; - - if (operationParameters.containsKey("iban")) { - paymentData += "*I" + operationParameters.get("iban"); - } - - if (operationParameters.containsKey("amount")) { - paymentData += "*A" + operationParameters.get("amount"); - if (operationParameters.containsKey("currency")) { - paymentData += operationParameters.get("currency"); - } - } - - if (operationParameters.containsKey("note")) { - paymentData += "*N" + operationParameters.get("note"); - } - - return paymentData; - } - -} From 33a1e5aea8521c49705672a23a58919b1f40e2e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Pe=C5=A1ek?= Date: Thu, 28 Mar 2024 08:29:43 +0100 Subject: [PATCH 41/44] Fix #401: FIDO2 Test Application: nullable parameters (#402) --- .../fido2/controller/request/RegisterCredentialRequest.java | 1 - .../fido2/controller/request/VerifyAssertionRequest.java | 1 - .../security/powerauth/fido2/service/AssertionService.java | 4 +++- .../security/powerauth/fido2/service/RegistrationService.java | 4 +++- .../src/main/webapp/resources/js/webauthn.js | 2 +- 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/request/RegisterCredentialRequest.java b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/request/RegisterCredentialRequest.java index 982cd845..7a5f195e 100644 --- a/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/request/RegisterCredentialRequest.java +++ b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/request/RegisterCredentialRequest.java @@ -44,7 +44,6 @@ public record RegisterCredentialRequest ( @NotNull PublicKeyCredentialType type, - @NotNull AuthenticatorAttachment authenticatorAttachment, @NotNull diff --git a/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/request/VerifyAssertionRequest.java b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/request/VerifyAssertionRequest.java index 695960d9..4e323d6f 100644 --- a/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/request/VerifyAssertionRequest.java +++ b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/controller/request/VerifyAssertionRequest.java @@ -39,7 +39,6 @@ public record VerifyAssertionRequest( @NotNull PublicKeyCredentialType type, - @NotNull AuthenticatorAttachment authenticatorAttachment, @NotNull diff --git a/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/AssertionService.java b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/AssertionService.java index 9ab5a2fc..984e5f4d 100644 --- a/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/AssertionService.java +++ b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/AssertionService.java @@ -97,7 +97,9 @@ public AssertionVerificationResponse authenticate(final VerifyAssertionRequest c final AssertionVerificationRequest request = new AssertionVerificationRequest(); request.setCredentialId(Base64.getEncoder().encodeToString(credentialId)); request.setType(credential.type().getValue()); - request.setAuthenticatorAttachment(credential.authenticatorAttachment().getValue()); + if (credential.authenticatorAttachment() != null) { + request.setAuthenticatorAttachment(credential.authenticatorAttachment().getValue()); + } request.setResponse(credential.response()); request.setApplicationId(credential.applicationId()); request.setExpectedChallenge(credential.expectedChallenge()); diff --git a/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/RegistrationService.java b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/RegistrationService.java index ccf675df..0d1b06db 100644 --- a/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/RegistrationService.java +++ b/powerauth-fido2-tests/src/main/java/com/wultra/security/powerauth/fido2/service/RegistrationService.java @@ -111,7 +111,9 @@ private AuthenticatorParameters buildAuthenticatorParameters(final RegisterCrede final AuthenticatorParameters parameters = new AuthenticatorParameters(); parameters.setCredentialId(Base64.getEncoder().encodeToString(credentialId)); - parameters.setAuthenticatorAttachment(credential.authenticatorAttachment().getValue()); + if (credential.authenticatorAttachment() != null) { + parameters.setAuthenticatorAttachment(credential.authenticatorAttachment().getValue()); + } parameters.setType(credential.type().getValue()); parameters.setResponse(credential.response()); parameters.setAllowedOrigins(webAuthNConfig.getAllowedOrigins()); diff --git a/powerauth-fido2-tests/src/main/webapp/resources/js/webauthn.js b/powerauth-fido2-tests/src/main/webapp/resources/js/webauthn.js index 1ef241a0..34bf0468 100644 --- a/powerauth-fido2-tests/src/main/webapp/resources/js/webauthn.js +++ b/powerauth-fido2-tests/src/main/webapp/resources/js/webauthn.js @@ -191,7 +191,7 @@ async function verifyAssertion(applicationId, challenge, credential) { clientDataJSON: toBase64(credential.response.clientDataJSON), authenticatorData: toBase64(credential.response.authenticatorData), signature: toBase64(credential.response.signature), - userHandle: decoder.decode(credential.response.userHandle) + userHandle: credential.response.userHandle == null ? null : decoder.decode(credential.response.userHandle) }, expectedChallenge: decoder.decode(challenge), userVerificationRequired: $("#userVerification").val() === "required" From aecadd95479e7cd3e00a6fd46ad70b266b2004fa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Apr 2024 17:12:55 +0000 Subject: [PATCH 42/44] Bump com.webauthn4j:webauthn4j-core Bumps [com.webauthn4j:webauthn4j-core](https://github.com/webauthn4j/webauthn4j) from 0.22.2.RELEASE to 0.23.0.RELEASE. - [Release notes](https://github.com/webauthn4j/webauthn4j/releases) - [Changelog](https://github.com/webauthn4j/webauthn4j/blob/master/github-release-notes-generator.yml) - [Commits](https://github.com/webauthn4j/webauthn4j/compare/0.22.2.RELEASE...0.23.0.RELEASE) --- updated-dependencies: - dependency-name: com.webauthn4j:webauthn4j-core dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 92238308..e1b29b4f 100644 --- a/pom.xml +++ b/pom.xml @@ -56,7 +56,7 @@ 1.77 2.3.0 7.4 - 0.22.2.RELEASE + 0.23.0.RELEASE true From 663cac404f845dceb19b74940b56a8d7c35cfe0e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Apr 2024 17:34:07 +0000 Subject: [PATCH 43/44] Bump org.springdoc:springdoc-openapi-starter-webmvc-ui Bumps [org.springdoc:springdoc-openapi-starter-webmvc-ui](https://github.com/springdoc/springdoc-openapi) from 2.3.0 to 2.5.0. - [Release notes](https://github.com/springdoc/springdoc-openapi/releases) - [Changelog](https://github.com/springdoc/springdoc-openapi/blob/main/CHANGELOG.md) - [Commits](https://github.com/springdoc/springdoc-openapi/compare/v2.3.0...v2.5.0) --- updated-dependencies: - dependency-name: org.springdoc:springdoc-openapi-starter-webmvc-ui dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e1b29b4f..2facdfad 100644 --- a/pom.xml +++ b/pom.xml @@ -54,7 +54,7 @@ 1.9.0-SNAPSHOT 1.77 - 2.3.0 + 2.5.0 7.4 0.23.0.RELEASE From cc491acda8f516ccfad225763501ca4cc1ad15bf Mon Sep 17 00:00:00 2001 From: Lubos Racansky Date: Fri, 23 Feb 2024 09:02:34 +0100 Subject: [PATCH 44/44] Fix #367: Update Wultra dependencies --- pom.xml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pom.xml b/pom.xml index 2facdfad..a3c4be75 100644 --- a/pom.xml +++ b/pom.xml @@ -45,13 +45,13 @@ - 1.7.0-SNAPSHOT - 1.7.0-SNAPSHOT - 1.7.0-SNAPSHOT - 1.7.0-SNAPSHOT - 1.7.0-SNAPSHOT - 1.7.0-SNAPSHOT - 1.9.0-SNAPSHOT + 1.7.0 + 1.7.0 + 1.7.0 + 1.7.0 + 1.7.0 + 1.7.0 + 1.9.0 1.77 2.5.0