From d5402cf45895dd3ffa77dd2bb85b285527c196af Mon Sep 17 00:00:00 2001 From: Mart Somermaa Date: Mon, 15 Feb 2021 14:09:18 +0200 Subject: [PATCH 1/2] test: get rid of JMockit and use raw reflection to make tests stable (#3) Signed-off-by: Mart Somermaa --- ...unctionalSubjectCertificateValidators.java | 15 ++++++- .../testutil/AbstractTestWithMockedDate.java | 6 ++- .../org/webeid/security/testutil/Dates.java | 39 ++++++++++++++----- .../security/validator/ClockSkewTest.java | 5 ++- .../webeid/security/validator/NonceTest.java | 9 +++-- .../webeid/security/validator/OcspTest.java | 14 ++++--- .../security/validator/ValidateTest.java | 19 +++++++-- .../webeid/security/validator/X5cTest.java | 10 +++-- 8 files changed, 88 insertions(+), 29 deletions(-) diff --git a/src/main/java/org/webeid/security/validator/validators/FunctionalSubjectCertificateValidators.java b/src/main/java/org/webeid/security/validator/validators/FunctionalSubjectCertificateValidators.java index 189b64f0..03e5a81e 100644 --- a/src/main/java/org/webeid/security/validator/validators/FunctionalSubjectCertificateValidators.java +++ b/src/main/java/org/webeid/security/validator/validators/FunctionalSubjectCertificateValidators.java @@ -22,6 +22,7 @@ package org.webeid.security.validator.validators; +import io.jsonwebtoken.Clock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.webeid.security.exceptions.*; @@ -30,6 +31,7 @@ import java.security.cert.CertificateExpiredException; import java.security.cert.CertificateNotYetValidException; import java.security.cert.CertificateParsingException; +import java.util.Date; import java.util.List; public final class FunctionalSubjectCertificateValidators { @@ -45,7 +47,8 @@ public final class FunctionalSubjectCertificateValidators { */ public static void validateCertificateExpiry(AuthTokenValidatorData actualTokenData) throws TokenValidationException { try { - actualTokenData.getSubjectCertificate().checkValidity(); + // Use JJWT Clock interface so that the date can be mocked in tests. + actualTokenData.getSubjectCertificate().checkValidity(DefaultClock.INSTANCE.now()); LOG.debug("User certificate is valid."); } catch (CertificateNotYetValidException e) { throw new UserCertificateNotYetValidException(e); @@ -78,4 +81,14 @@ public static void validateCertificatePurpose(AuthTokenValidatorData actualToken private FunctionalSubjectCertificateValidators() { throw new IllegalStateException("Functional class"); } + + public static class DefaultClock implements Clock { + + public static final Clock INSTANCE = new DefaultClock(); + + public Date now() { + return new Date(); + } + + } } diff --git a/src/test/java/org/webeid/security/testutil/AbstractTestWithMockedDate.java b/src/test/java/org/webeid/security/testutil/AbstractTestWithMockedDate.java index 0de25a46..532662f4 100644 --- a/src/test/java/org/webeid/security/testutil/AbstractTestWithMockedDate.java +++ b/src/test/java/org/webeid/security/testutil/AbstractTestWithMockedDate.java @@ -26,6 +26,8 @@ import java.text.ParseException; +import static org.webeid.security.testutil.Dates.setMockedDefaultJwtParserDate; + public abstract class AbstractTestWithMockedDate extends AbstractTestWithCache { @Override @@ -34,8 +36,8 @@ protected void setup() { super.setup(); try { // Authentication token is valid until 2020-04-14 - Dates.setMockedDate(Dates.create("2020-04-11")); - } catch (ParseException e) { + setMockedDefaultJwtParserDate(Dates.create("2020-04-11")); + } catch (ParseException | NoSuchFieldException | IllegalAccessException e) { throw new RuntimeException(e); } } diff --git a/src/test/java/org/webeid/security/testutil/Dates.java b/src/test/java/org/webeid/security/testutil/Dates.java index 2b6f3a00..3dc5a378 100644 --- a/src/test/java/org/webeid/security/testutil/Dates.java +++ b/src/test/java/org/webeid/security/testutil/Dates.java @@ -23,9 +23,12 @@ package org.webeid.security.testutil; import com.fasterxml.jackson.databind.util.StdDateFormat; -import mockit.Mock; -import mockit.MockUp; +import io.jsonwebtoken.Clock; +import io.jsonwebtoken.impl.DefaultClock; +import org.webeid.security.validator.validators.FunctionalSubjectCertificateValidators; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; import java.text.ParseException; import java.util.Date; @@ -36,13 +39,31 @@ public static Date create(String iso8601Date) throws ParseException { return STD_DATE_FORMAT.parse(iso8601Date); } - public static void setMockedDate(Date mockedDate) { - new MockUp() { - @Mock - public long currentTimeMillis() { - return mockedDate.getTime(); - } - }; + public static void setMockedDefaultJwtParserDate(Date mockedDate) throws NoSuchFieldException, IllegalAccessException { + setClockField(DefaultClock.class, mockedDate); + } + + public static void setMockedFunctionalSubjectCertificateValidatorsDate(Date mockedDate) throws NoSuchFieldException, IllegalAccessException { + setClockField(FunctionalSubjectCertificateValidators.DefaultClock.class, mockedDate); + } + + public static void resetMockedFunctionalSubjectCertificateValidatorsDate() throws NoSuchFieldException, IllegalAccessException { + setClockField(FunctionalSubjectCertificateValidators.DefaultClock.class, new Date()); + } + + private static void setClockField(Class cls, Date date) throws NoSuchFieldException, IllegalAccessException { + final Field clockField = cls.getDeclaredField("INSTANCE"); + setFinalStaticField(clockField, (Clock) () -> date); + } + + private static void setFinalStaticField(Field field, Object newValue) throws NoSuchFieldException, IllegalAccessException { + field.setAccessible(true); + + final Field modifiersField = Field.class.getDeclaredField("modifiers"); + modifiersField.setAccessible(true); + modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); + + field.set(null, newValue); } } diff --git a/src/test/java/org/webeid/security/validator/ClockSkewTest.java b/src/test/java/org/webeid/security/validator/ClockSkewTest.java index 21c751cd..11f119cc 100644 --- a/src/test/java/org/webeid/security/validator/ClockSkewTest.java +++ b/src/test/java/org/webeid/security/validator/ClockSkewTest.java @@ -30,13 +30,14 @@ import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.webeid.security.testutil.Dates.setMockedDefaultJwtParserDate; class ClockSkewTest extends AbstractTestWithValidatorAndCorrectNonce { @Test void blockLargeClockSkew4min() throws Exception { // Authentication token expires at 2020-04-14 13:32:49 - Dates.setMockedDate(Dates.create("2020-04-14T13:36:49Z")); + setMockedDefaultJwtParserDate(Dates.create("2020-04-14T13:36:49Z")); assertThatThrownBy(() -> validator.validate(Tokens.SIGNED)) .isInstanceOf(TokenExpiredException.class); } @@ -44,7 +45,7 @@ void blockLargeClockSkew4min() throws Exception { @Test void allowSmallClockSkew2min() throws Exception { // Authentication token expires at 2020-04-14 13:32:49 - Dates.setMockedDate(Dates.create("2020-04-14T13:30:49Z")); + setMockedDefaultJwtParserDate(Dates.create("2020-04-14T13:30:49Z")); assertThatCode(() -> validator.validate(Tokens.SIGNED)) .doesNotThrowAnyException(); } diff --git a/src/test/java/org/webeid/security/validator/NonceTest.java b/src/test/java/org/webeid/security/validator/NonceTest.java index 0e2602c7..6d4574b7 100644 --- a/src/test/java/org/webeid/security/validator/NonceTest.java +++ b/src/test/java/org/webeid/security/validator/NonceTest.java @@ -31,6 +31,7 @@ import org.webeid.security.testutil.Tokens; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.webeid.security.testutil.Dates.setMockedDefaultJwtParserDate; class NonceTest extends AbstractTestWithValidator { @@ -50,7 +51,7 @@ void validateExpiredNonce() { @Test void testNonceMissing() throws Exception { - Dates.setMockedDate(Dates.create("2020-04-14T13:00:00Z")); + setMockedDefaultJwtParserDate(Dates.create("2020-04-14T13:00:00Z")); assertThatThrownBy(() -> validator.validate(Tokens.NONCE_MISSING)) .isInstanceOf(TokenParseException.class) .hasMessageStartingWith("nonce field must be present and not empty in authentication token body"); @@ -58,7 +59,7 @@ void testNonceMissing() throws Exception { @Test void testNonceEmpty() throws Exception { - Dates.setMockedDate(Dates.create("2020-04-14T13:00:00Z")); + setMockedDefaultJwtParserDate(Dates.create("2020-04-14T13:00:00Z")); assertThatThrownBy(() -> validator.validate(Tokens.NONCE_EMPTY)) .isInstanceOf(TokenParseException.class) .hasMessageStartingWith("nonce field must be present and not empty in authentication token body"); @@ -66,14 +67,14 @@ void testNonceEmpty() throws Exception { @Test void testTokenNonceNotString() throws Exception { - Dates.setMockedDate(Dates.create("2020-04-14T13:00:00Z")); + setMockedDefaultJwtParserDate(Dates.create("2020-04-14T13:00:00Z")); assertThatThrownBy(() -> validator.validate(Tokens.NONCE_NOT_STRING)) .isInstanceOf(TokenParseException.class); } @Test void testNonceTooShort() throws Exception { - Dates.setMockedDate(Dates.create("2020-04-14T13:00:00Z")); + setMockedDefaultJwtParserDate(Dates.create("2020-04-14T13:00:00Z")); putTooShortNonceToCache(); assertThatThrownBy(() -> validator.validate(Tokens.NONCE_TOO_SHORT)) .isInstanceOf(TokenParseException.class); diff --git a/src/test/java/org/webeid/security/validator/OcspTest.java b/src/test/java/org/webeid/security/validator/OcspTest.java index 30ad8264..d1481c2d 100644 --- a/src/test/java/org/webeid/security/validator/OcspTest.java +++ b/src/test/java/org/webeid/security/validator/OcspTest.java @@ -24,6 +24,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.webeid.security.exceptions.TokenValidationException; import org.webeid.security.exceptions.UserCertificateRevocationCheckFailedException; import org.webeid.security.exceptions.UserCertificateRevokedException; import org.webeid.security.testutil.AbstractTestWithMockedDateAndCorrectNonce; @@ -31,6 +32,7 @@ import java.security.cert.CertificateException; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.webeid.security.testutil.AuthTokenValidators.getAuthTokenValidatorWithOcspCheck; @@ -51,14 +53,16 @@ protected void setup() { @Test void detectRevokedUserCertificate() { - // This test used to have flaky results, due to occasionally failing - // OCSP requests. Therefore, the failed revocation check is now handled - // as passing. - assertThatThrownBy(() -> validator.validate(Tokens.SIGNED)) - .isInstanceOfAny( + // This test had flaky results as OCSP requests sometimes failed, sometimes passed. + // Hence the catch which may or may not execute instead of assertThatThrownBy(). + try { + validator.validate(Tokens.SIGNED); + } catch (TokenValidationException e) { + assertThat(e).isInstanceOfAny( UserCertificateRevokedException.class, UserCertificateRevocationCheckFailedException.class ); + } } } diff --git a/src/test/java/org/webeid/security/validator/ValidateTest.java b/src/test/java/org/webeid/security/validator/ValidateTest.java index 6316d9c0..b2743704 100644 --- a/src/test/java/org/webeid/security/validator/ValidateTest.java +++ b/src/test/java/org/webeid/security/validator/ValidateTest.java @@ -22,7 +22,7 @@ package org.webeid.security.validator; - +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.webeid.security.exceptions.TokenParseException; import org.webeid.security.exceptions.UserCertificateExpiredException; @@ -34,13 +34,26 @@ import java.util.Date; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.webeid.security.testutil.Dates.resetMockedFunctionalSubjectCertificateValidatorsDate; +import static org.webeid.security.testutil.Dates.setMockedFunctionalSubjectCertificateValidatorsDate; class ValidateTest extends AbstractTestWithMockedDateValidatorAndCorrectNonce { + @Override + @AfterEach + public void tearDown() { + super.tearDown(); + try { + resetMockedFunctionalSubjectCertificateValidatorsDate(); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new RuntimeException(e); + } + } + @Test void certificateIsNotValidYet() throws Exception { final Date certValidFrom = Dates.create("2018-10-17"); - Dates.setMockedDate(certValidFrom); + setMockedFunctionalSubjectCertificateValidatorsDate(certValidFrom); assertThatThrownBy(() -> validator.validate(Tokens.SIGNED)) .isInstanceOf(UserCertificateNotYetValidException.class); @@ -49,7 +62,7 @@ void certificateIsNotValidYet() throws Exception { @Test void certificateIsNoLongerValid() throws Exception { final Date certValidFrom = Dates.create("2023-10-19"); - Dates.setMockedDate(certValidFrom); + setMockedFunctionalSubjectCertificateValidatorsDate(certValidFrom); assertThatThrownBy(() -> validator.validate(Tokens.SIGNED)) .isInstanceOf(UserCertificateExpiredException.class); diff --git a/src/test/java/org/webeid/security/validator/X5cTest.java b/src/test/java/org/webeid/security/validator/X5cTest.java index a1a2f929..ed65cce9 100644 --- a/src/test/java/org/webeid/security/validator/X5cTest.java +++ b/src/test/java/org/webeid/security/validator/X5cTest.java @@ -24,7 +24,10 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.webeid.security.exceptions.*; +import org.webeid.security.exceptions.TokenParseException; +import org.webeid.security.exceptions.UserCertificateDisallowedPolicyException; +import org.webeid.security.exceptions.UserCertificateMissingPurposeException; +import org.webeid.security.exceptions.UserCertificateWrongPurposeException; import org.webeid.security.testutil.AbstractTestWithValidatorAndCorrectNonce; import org.webeid.security.testutil.Dates; import org.webeid.security.testutil.Tokens; @@ -32,6 +35,7 @@ import java.text.ParseException; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.webeid.security.testutil.Dates.setMockedDefaultJwtParserDate; class X5cTest extends AbstractTestWithValidatorAndCorrectNonce { @@ -41,8 +45,8 @@ protected void setup() { super.setup(); try { // Ensure that certificate is valid - Dates.setMockedDate(Dates.create("2020-09-25")); - } catch (ParseException e) { + setMockedDefaultJwtParserDate(Dates.create("2020-09-25")); + } catch (ParseException | NoSuchFieldException | IllegalAccessException e) { throw new RuntimeException(e); } } From 1ca4cc955ba1b4896d950522f9b44aa574278b73 Mon Sep 17 00:00:00 2001 From: Mart Somermaa Date: Mon, 15 Feb 2021 14:04:33 +0200 Subject: [PATCH 2/2] ci: setup publishing to GitHub Packages, bump version to 1.0.0 (#3) Signed-off-by: Mart Somermaa --- .github/workflows/maven-build.yml | 36 ++++++++++++++++-------------- .github/workflows/maven-deploy.yml | 28 +++++++++++++++++++++++ .gitlab-ci-mvn-settings.xml | 16 ------------- pom.xml | 35 ++++++++--------------------- 4 files changed, 56 insertions(+), 59 deletions(-) create mode 100644 .github/workflows/maven-deploy.yml delete mode 100644 .gitlab-ci-mvn-settings.xml diff --git a/.github/workflows/maven-build.yml b/.github/workflows/maven-build.yml index 4860d135..825d29cd 100644 --- a/.github/workflows/maven-build.yml +++ b/.github/workflows/maven-build.yml @@ -1,25 +1,27 @@ name: Maven build -on: [push, pull_request] +on: [ push, pull_request ] jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-java@v1 - with: - java-version: 1.8 - - name: Cache Maven packages - uses: actions/cache@v1 - with: - path: ~/.m2 - key: ${{ runner.os }}-m2-v8-${{ hashFiles('**/pom.xml') }} - restore-keys: ${{ runner.os }}-m2-v8 - - name: Build - run: mvn --batch-mode compile - - name: Test - run: | - mvn -version - mvn --batch-mode verify + - uses: actions/checkout@v2 + + - uses: actions/setup-java@v1 + with: + java-version: 1.8 + + - name: Cache Maven packages + uses: actions/cache@v1 + with: + path: ~/.m2 + key: ${{ runner.os }}-m2-v8-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-m2-v8 + + - name: Build + run: mvn --batch-mode compile + + - name: Test + run: mvn --batch-mode verify diff --git a/.github/workflows/maven-deploy.yml b/.github/workflows/maven-deploy.yml new file mode 100644 index 00000000..352a8ac1 --- /dev/null +++ b/.github/workflows/maven-deploy.yml @@ -0,0 +1,28 @@ +name: Deploy to Github Packages + +on: + release: + types: [ created ] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - uses: actions/setup-java@v1 + with: + java-version: 1.8 + + - name: Cache Maven packages + uses: actions/cache@v1 + with: + path: ~/.m2 + key: ${{ runner.os }}-m2-v8-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-m2-v8 + + - name: Deploy to GitHub Packages + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: mvn --batch-mode deploy diff --git a/.gitlab-ci-mvn-settings.xml b/.gitlab-ci-mvn-settings.xml deleted file mode 100644 index 59c8c271..00000000 --- a/.gitlab-ci-mvn-settings.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - gitlab-maven - - - - Job-Token - ${env.CI_JOB_TOKEN} - - - - - - diff --git a/pom.xml b/pom.xml index 02b09450..a4d8012f 100644 --- a/pom.xml +++ b/pom.xml @@ -5,22 +5,20 @@ 4.0.0 authtoken-validation org.webeid.security - 1.0.0-SNAPSHOT + 1.0.0 jar authtoken-validation Web eID authentication token validation library for Java - 3.3.9 - 2.22.1 + 2.22.2 1.8 0.11.2 1.7.30 2.8.5 5.6.2 3.17.2 - 1.44 0.8.5 ${project.basedir}/../jacoco-coverage-report/target/site/jacoco-aggregate/jacoco.xml @@ -92,12 +90,6 @@ ${assertj.version} test - - org.jmockit - jmockit - ${jmockit.version} - test - org.slf4j slf4j-simple @@ -121,13 +113,10 @@ + + org.apache.maven.plugins maven-surefire-plugin ${maven-surefire-plugin.version} - - - @{argLine} -javaagent:${settings.localRepository}/org/jmockit/jmockit/${jmockit.version}/jmockit-${jmockit.version}.jar - - @@ -152,21 +141,15 @@ - - - - gitlab-maven - https://gitlab.com/api/v4/projects/${env.CI_PROJECT_ID}/packages/maven - - + - gitlab-maven - https://gitlab.com/api/v4/projects/${env.CI_PROJECT_ID}/packages/maven + github + https://maven.pkg.github.com/web-eid/web-eid-authtoken-validation-java - gitlab-maven - https://gitlab.com/api/v4/projects/${env.CI_PROJECT_ID}/packages/maven + github + https://maven.pkg.github.com/web-eid/web-eid-authtoken-validation-java