Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* #%L
* Smart ID sample Java client
* %%
* Copyright (C) 2018 - 2025 SK ID Solutions AS
* Copyright (C) 2018 - 2026 SK ID Solutions AS
* %%
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
Expand Down Expand Up @@ -220,14 +220,14 @@ private static void validateSignatureAlgorithmParameters(SessionSignature sessio
}
Optional<HashAlgorithm> maskGenHashAlgorithm = HashAlgorithm.fromString(maskGenAlgorithm.getParameters().getHashAlgorithm());
if (maskGenHashAlgorithm.isEmpty()) {
logger.error("Authentication session status field 'signature.signatureAlgorithmParameters.maskGenAlgorithm.hashAlgorithm' has invalid value: {}", maskGenAlgorithm.getParameters().getHashAlgorithm());
logger.error("Authentication session status field 'signature.signatureAlgorithmParameters.maskGenAlgorithm.parameters.hashAlgorithm' has invalid value: {}", maskGenAlgorithm.getParameters().getHashAlgorithm());
throw new UnprocessableSmartIdResponseException("Authentication session status field 'signature.signatureAlgorithmParameters.maskGenAlgorithm.parameters.hashAlgorithm' has unsupported value");
}
if (hashAlgorithm.get() != maskGenHashAlgorithm.get()) {
logger.error("Authentication session status field 'signature.signatureAlgorithmParameters.maskGenAlgorithm.hashAlgorithm' and 'signature.signatureAlgorithmParameters.hashAlgorithm' do not match. Expected: {}, actual: {}",
logger.error("Authentication session status field 'signature.signatureAlgorithmParameters.maskGenAlgorithm.parameters.hashAlgorithm' and 'signature.signatureAlgorithmParameters.hashAlgorithm' do not match. Expected: {}, actual: {}",
hashAlgorithm.get().getAlgorithmName(),
maskGenHashAlgorithm.get().getAlgorithmName());
throw new UnprocessableSmartIdResponseException("Authentication session status field 'signature.signatureAlgorithmParameters.maskGenAlgorithm.hashAlgorithm' value does not match 'signature.signatureAlgorithmParameters.hashAlgorithm' value");
throw new UnprocessableSmartIdResponseException("Authentication session status field 'signature.signatureAlgorithmParameters.maskGenAlgorithm.parameters.hashAlgorithm' value does not match 'signature.signatureAlgorithmParameters.hashAlgorithm' value");
}

if (signatureAlgorithmParameters.getSaltLength() == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* #%L
* Smart ID sample Java client
* %%
* Copyright (C) 2018 - 2025 SK ID Solutions AS
* Copyright (C) 2018 - 2026 SK ID Solutions AS
* %%
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
Expand All @@ -30,9 +30,11 @@
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

import ch.qos.logback.classic.Level;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ArgumentsSource;
import org.junit.jupiter.params.provider.EnumSource;
Expand All @@ -49,11 +51,18 @@
import ee.sk.smartid.rest.dao.SessionSignature;
import ee.sk.smartid.rest.dao.SessionSignatureAlgorithmParameters;
import ee.sk.smartid.rest.dao.SessionStatus;
import ee.sk.smartid.testhelper.log.Logs;
import ee.sk.smartid.testhelper.log.LogsSpy;
import ee.sk.smartid.testhelper.log.LogsSpyExtension;

@ExtendWith(LogsSpyExtension.class)
class AuthenticationResponseMapperImplTest {

private static final String AUTH_CERT = FileUtil.readFileToString("test-certs/auth-cert-40504040001.pem.crt");

@Logs
private LogsSpy logs;

private AuthenticationResponseMapper authenticationResponseMapper;

@BeforeEach
Expand Down Expand Up @@ -439,6 +448,9 @@ void from_hashAlgorithmIsInvalid_throwException(String invalidHashAlgorithm) {
var sessionStatus = toSessionStatus(sessionResult, sessionSignature);

var exception = assertThrows(UnprocessableSmartIdResponseException.class, () -> authenticationResponseMapper.from(sessionStatus));

logs.shouldHave(Level.ERROR, "Authentication session status field 'signature.signatureAlgorithmParameters.hashAlgorithm' has invalid value: " + invalidHashAlgorithm);

assertEquals("Authentication session status field 'signature.signatureAlgorithmParameters.hashAlgorithm' has unsupported value", exception.getMessage());
}

Expand Down Expand Up @@ -487,6 +499,9 @@ void from_algorithmValueInMaskGenAlgorithmIsInvalid_throwException() {
var sessionStatus = toSessionStatus(sessionResult, sessionSignature);

var exception = assertThrows(UnprocessableSmartIdResponseException.class, () -> authenticationResponseMapper.from(sessionStatus));

logs.shouldHave(Level.ERROR, "Authentication session status field 'signature.signatureAlgorithmParameters.maskGenAlgorithm' has invalid value: " + maskGenAlgorithm.getAlgorithm());

assertEquals("Authentication session status field 'signature.signatureAlgorithmParameters.maskGenAlgorithm' has unsupported value", exception.getMessage());
}

Expand Down Expand Up @@ -549,6 +564,9 @@ void from_hashAlgorithmInMaskGenAlgorithmParametersInvalid_throwException(String
var sessionStatus = toSessionStatus(sessionResult, sessionSignature);

var exception = assertThrows(UnprocessableSmartIdResponseException.class, () -> authenticationResponseMapper.from(sessionStatus));

logs.shouldHave(Level.ERROR, "Authentication session status field 'signature.signatureAlgorithmParameters.maskGenAlgorithm.parameters.hashAlgorithm' has invalid value: " + maskGenAlgorithmParameters.getHashAlgorithm());

assertEquals("Authentication session status field 'signature.signatureAlgorithmParameters.maskGenAlgorithm.parameters.hashAlgorithm' has unsupported value", exception.getMessage());
}

Expand All @@ -570,7 +588,10 @@ void from_hashAlgorithmInMaskGenAlgorithmDoesNotMatchSignaturesHashAlgorithm_thr
var sessionStatus = toSessionStatus(sessionResult, sessionSignature);

var exception = assertThrows(UnprocessableSmartIdResponseException.class, () -> authenticationResponseMapper.from(sessionStatus));
assertEquals("Authentication session status field 'signature.signatureAlgorithmParameters.maskGenAlgorithm.hashAlgorithm' value does not match 'signature.signatureAlgorithmParameters.hashAlgorithm' value", exception.getMessage());

logs.shouldHave(Level.ERROR, "Authentication session status field 'signature.signatureAlgorithmParameters.maskGenAlgorithm.parameters.hashAlgorithm' and 'signature.signatureAlgorithmParameters.hashAlgorithm' do not match. Expected: SHA3-512, actual: SHA-512");

assertEquals("Authentication session status field 'signature.signatureAlgorithmParameters.maskGenAlgorithm.parameters.hashAlgorithm' value does not match 'signature.signatureAlgorithmParameters.hashAlgorithm' value", exception.getMessage());
}

@Test
Expand Down Expand Up @@ -608,6 +629,9 @@ void from_saltLengthDoesNotMatchHashAlgorithmOctetLength_throwException() {
var sessionStatus = toSessionStatus(sessionResult, sessionSignature);

var exception = assertThrows(UnprocessableSmartIdResponseException.class, () -> authenticationResponseMapper.from(sessionStatus));

logs.shouldHave(Level.ERROR, "Authentication session status field 'signature.signatureAlgorithmParameters.saltLength' has invalid value. Expected: 64, actual: 20");

assertEquals("Authentication session status field 'signature.signatureAlgorithmParameters.saltLength' has invalid value", exception.getMessage());
}

Expand Down Expand Up @@ -649,6 +673,9 @@ void from_trailerFieldValueIsInvalid_throwException() {
var sessionStatus = toSessionStatus(sessionResult, sessionSignature);

var exception = assertThrows(UnprocessableSmartIdResponseException.class, () -> authenticationResponseMapper.from(sessionStatus));

logs.shouldHave(Level.ERROR, "Authentication session status field 'signature.signatureAlgorithmParameters.trailerField' has invalid value: invalid");

assertEquals("Authentication session status field 'signature.signatureAlgorithmParameters.trailerField' has unsupported value", exception.getMessage());
}

Expand Down
39 changes: 39 additions & 0 deletions src/test/java/ee/sk/smartid/testhelper/log/Logs.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package ee.sk.smartid.testhelper.log;

/*-
* #%L
* Smart ID sample Java client
* %%
* Copyright (C) 2018 - 2026 SK ID Solutions AS
* %%
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
* #L%
*/

import org.junit.jupiter.api.extension.ExtendWith;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ ElementType.FIELD, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith(LogsSpyExtension.class)
public @interface Logs {}
94 changes: 94 additions & 0 deletions src/test/java/ee/sk/smartid/testhelper/log/LogsSpy.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package ee.sk.smartid.testhelper.log;

/*-
* #%L
* Smart ID sample Java client
* %%
* Copyright (C) 2018 - 2026 SK ID Solutions AS
* %%
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
* #L%
*/

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.read.ListAppender;
import org.slf4j.LoggerFactory;

import java.util.List;
import java.util.function.Predicate;

public final class LogsSpy {

private Logger logger;
private ListAppender<ILoggingEvent> appender;
private Level initialLevel;

void prepare() {
appender = new ListAppender<>();
logger = (Logger) LoggerFactory.getLogger("ee.sk.smartid");
logger.addAppender(appender);
initialLevel = logger.getLevel();
logger.setLevel(Level.TRACE);
appender.start();
}

void reset() {
logger.setLevel(initialLevel);
logger.detachAppender(appender);
}

public LogsSpy shouldHave(Level level, String content) {
boolean found = logEvents().stream().anyMatch(withLog(level, content));
assertTrue(found, "Expected at least one log entry with level " + level + " and content: " + content);

return this;
}

public LogsSpy shouldHave(Level level, String content, int count) {
long actualCount = logEvents().stream()
.filter(withLog(level, content))
.count();

assertEquals(count, actualCount, "Expected " + count + " log entries with level " + level + " and content: " + content);

return this;
}

public LogsSpy shouldNotHave(Level level, String content) {
boolean found = logEvents().stream().anyMatch(withLog(level, content));
assertFalse(found, "Expected no log entries with level " + level + " and content: " + content);

return this;
}

private List<ILoggingEvent> logEvents() {
// Copy the list to avoid concurrent modification exceptions
return List.copyOf(appender.list);
}

private Predicate<ILoggingEvent> withLog(Level level, String content) {
return event -> level.equals(event.getLevel()) && event.toString().contains(content);
}
}
94 changes: 94 additions & 0 deletions src/test/java/ee/sk/smartid/testhelper/log/LogsSpyExtension.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package ee.sk.smartid.testhelper.log;

/*-
* #%L
* Smart ID sample Java client
* %%
* Copyright (C) 2018 - 2026 SK ID Solutions AS
* %%
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
* #L%
*/

import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolutionException;
import org.junit.jupiter.api.extension.ParameterResolver;
import org.junit.jupiter.api.extension.TestInstancePostProcessor;
import org.junit.platform.commons.support.ModifierSupport;

import java.lang.reflect.Field;
import java.util.function.Predicate;

import static org.junit.platform.commons.util.AnnotationUtils.findAnnotatedFields;

public final class LogsSpyExtension
implements BeforeAllCallback, BeforeEachCallback, AfterEachCallback, ParameterResolver, TestInstancePostProcessor {

private final LogsSpy logsSpy = new LogsSpy();

@Override
public void beforeEach(ExtensionContext context) {
logsSpy.prepare();
}

@Override
public void afterEach(ExtensionContext context) {
logsSpy.reset();
}

@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
throws ParameterResolutionException {
return parameterContext.getParameter().getType().equals(LogsSpy.class);
}

@Override
public LogsSpy resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
throws ParameterResolutionException {
return logsSpy;
}

@Override
public void beforeAll(ExtensionContext context) {
Class<?> testClass = context.getRequiredTestClass();
injectFields(testClass, null, ModifierSupport::isStatic);
}

@Override
public void postProcessTestInstance(Object testInstance, ExtensionContext context) {
Class<?> testClass = context.getRequiredTestClass();
injectFields(testClass, testInstance, ModifierSupport::isNotStatic);
}

private void injectFields(Class<?> testClass, Object testInstance, Predicate<Field> predicate) {
predicate = predicate.and(field -> LogsSpy.class.isAssignableFrom(field.getType()));
findAnnotatedFields(testClass, Logs.class, predicate).forEach(field -> {
try {
field.setAccessible(true);
field.set(testInstance, logsSpy);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
});
}
}
Loading