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 @@ -227,6 +227,11 @@ public Response receiveSms(
}

public void validateKannelAuth(String username, String password) {
if (Strings.isNullOrEmpty(username) || Strings.isNullOrEmpty(password)) {
throw new WebApplicationException(Response.status(Response.Status.UNAUTHORIZED)
.entity("Invalid credentials")
.build());
}
CredentialFileWatcher.Credential cred = credentialFileWatcher.getValidCredentials().get(username);
if (cred == null || cred.type() != CredentialFileWatcher.CredentialType.HTTP) {
logger.warn("No username found:{}", username);
Expand Down Expand Up @@ -258,4 +263,4 @@ private String decodeTextParam(String value, String charsetName) {
return URLDecoder.decode(value, StandardCharsets.UTF_8);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package gr.cytech.sendium.auth;

import gr.cytech.sendium.conf.SendiumConfigurationHandler;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

import java.io.File;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

class CredentialFileWatcherTest {

@TempDir Path tempDir;

private CredentialFileWatcher watcher;
private ArrayList<Map<String, CredentialFileWatcher.Credential>> notifications;

@BeforeEach
void setUp() {
watcher = new CredentialFileWatcher();
SendiumConfigurationHandler configHandler = new SendiumConfigurationHandler();
configHandler.memoryConfiguration = new ConcurrentHashMap<>();
configHandler.defaultsConfiguration = new ConcurrentHashMap<>();
configHandler.overriddenDefaultsConfiguration = new ConcurrentHashMap<>();
configHandler.currentStoreConfiguration = new ConcurrentHashMap<>();
configHandler.listeners = new CopyOnWriteArraySet<>();
watcher.configHandler = configHandler;
notifications = new ArrayList<>();
}

@Test
void reloadCredentialConfigurationParsesFileAndNotifiesListeners() throws Exception {
Path file = tempDir.resolve("credentials.yml");
Files.writeString(file, """
credentials:
- type: HTTP
systemId: http-user
password: secret
""");
watcher.addCredentialChangeListener(notifications::add);

reload(file.toFile());

assertThat(watcher.getValidCredentials()).containsKey("http-user");
assertThat(notifications).hasSize(1);
assertThat(notifications.getFirst()).containsKey("http-user");
}

@Test
void reloadCredentialConfigurationContinuesWhenListenerThrows() throws Exception {
Path file = tempDir.resolve("credentials.yml");
Files.writeString(file, """
credentials:
- type: HTTP
apiKey: api-key
""");
watcher.addCredentialChangeListener(credentials -> {
throw new RuntimeException("boom");
});
watcher.addCredentialChangeListener(notifications::add);

reload(file.toFile());

assertThat(notifications).hasSize(1);
assertThat(notifications.getFirst()).containsKey("api-key");
}

@Test
void reloadCredentialConfigurationRetainsPreviousCredentialsWhenReloadFails() throws Exception {
Path file = tempDir.resolve("credentials.yml");
Files.writeString(file, """
credentials:
- type: SMPP
systemId: smpp-user
password: secret
""");
reload(file.toFile());

reload(tempDir.toFile());

assertThat(watcher.getValidCredentials()).containsKey("smpp-user");
}

@Test
void getValidCredentialsReturnsEmptyBeforeLoadAndUnmodifiableAfterLoad() throws Exception {
assertThat(watcher.getValidCredentials()).isEmpty();
Path file = tempDir.resolve("credentials.yml");
Files.writeString(file, """
credentials:
- type: HTTP
apiKey: api-key
""");
reload(file.toFile());

assertThatThrownBy(() -> watcher.getValidCredentials().clear())
.isInstanceOf(UnsupportedOperationException.class);
}

@Test
void removeCredentialChangeListenerStopsNotifications() throws Exception {
Path file = tempDir.resolve("credentials.yml");
Files.writeString(file, """
credentials:
- type: HTTP
apiKey: api-key
""");
CredentialChangeListener listener = notifications::add;
watcher.addCredentialChangeListener(listener);
watcher.removeCredentialChangeListener(listener);

reload(file.toFile());

assertThat(notifications).isEmpty();
}

@Test
void credentialValidationAndLookupKeyFollowCredentialType() {
var smpp = new CredentialFileWatcher.Credential(
CredentialFileWatcher.CredentialType.SMPP, "account", null, "system", "pass", null, null);
var httpApiKey = new CredentialFileWatcher.Credential(
CredentialFileWatcher.CredentialType.HTTP, "account", null, null, null, "api", null);
var httpUserPass = new CredentialFileWatcher.Credential(
CredentialFileWatcher.CredentialType.HTTP, "account", null, "user", "pass", null, null);
var invalid = new CredentialFileWatcher.Credential(
CredentialFileWatcher.CredentialType.HTTP, "account", null, null, null, null, null);

assertThat(smpp.isValid()).isTrue();
assertThat(smpp.getLookupKey()).isEqualTo("system");
assertThat(httpApiKey.isValid()).isTrue();
assertThat(httpApiKey.getLookupKey()).isEqualTo("api");
assertThat(httpUserPass.isValid()).isTrue();
assertThat(httpUserPass.getLookupKey()).isEqualTo("user");
assertThat(invalid.isValid()).isFalse();
}

private void reload(File file) throws Exception {
Method method = CredentialFileWatcher.class.getDeclaredMethod("reloadCredentialConfiguration", File.class);
method.setAccessible(true);
method.invoke(watcher, file);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package gr.cytech.sendium.conf;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

import java.io.File;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;

import static org.assertj.core.api.Assertions.assertThat;

class ConfFileWatcherTest {

@TempDir Path tempDir;

private SendiumConfigurationHandler configHandler;
private ConfFileWatcher watcher;

@BeforeEach
void setUp() {
configHandler = new SendiumConfigurationHandler();
configHandler.memoryConfiguration = new ConcurrentHashMap<>();
configHandler.defaultsConfiguration = new ConcurrentHashMap<>();
configHandler.overriddenDefaultsConfiguration = new ConcurrentHashMap<>();
configHandler.currentStoreConfiguration = new ConcurrentHashMap<>();
configHandler.listeners = new CopyOnWriteArraySet<>();

watcher = new ConfFileWatcher();
watcher.configHandler = configHandler;
}

@Test
void reloadConfigurationAddsChangesAndRemovesProperties() throws Exception {
Path file = tempDir.resolve("smsg.properties");
var events = new ArrayList<PropertyChangeEvent>();
configHandler.addPropertyChangeListener(events::add);

Files.writeString(file, "alpha=one\npassword=secret\n");
reload(file.toFile());

Files.writeString(file, "alpha=two\nbeta=three\n");
reload(file.toFile());

assertThat(configHandler.get("alpha")).isEqualTo("two");
assertThat(configHandler.get("beta")).isEqualTo("three");
assertThat(configHandler.get("password")).isNull();
assertThat(events).extracting(PropertyChangeEvent::getKey)
.containsExactlyInAnyOrder("alpha", "password", "alpha", "beta", "password");
assertThat(events).anySatisfy(event -> {
assertThat(event.getKey()).isEqualTo("alpha");
assertThat(event.getNewValue()).isEqualTo("two");
assertThat(event.getOldValue()).isEqualTo("one");
});
assertThat(events).anySatisfy(event -> {
assertThat(event.getKey()).isEqualTo("password");
assertThat(event.getNewValue()).isNull();
assertThat(event.getOldValue()).isEqualTo("secret");
});
}

@Test
void reloadConfigurationSkipsMissingFile() throws Exception {
Path file = tempDir.resolve("missing.properties");
var events = new ArrayList<PropertyChangeEvent>();
configHandler.addPropertyChangeListener(events::add);

reload(file.toFile());

assertThat(events).isEmpty();
assertThat(configHandler.memoryConfiguration).isEmpty();
}

@Test
void loadPropertiesFromFileReturnsNullForDirectory() throws Exception {
Method method = ConfFileWatcher.class.getDeclaredMethod("loadPropertiesFromFile", File.class);
method.setAccessible(true);

Object result = method.invoke(watcher, tempDir.toFile());

assertThat(result).isNull();
}

@Test
void maskSecretMasksSensitiveKeysOnly() throws Exception {
Method method = ConfFileWatcher.class.getDeclaredMethod("maskSecret", String.class, String.class);
method.setAccessible(true);

assertThat(method.invoke(watcher, "db.password", "secret")).isEqualTo("*****");
assertThat(method.invoke(watcher, "api.token", "token")).isEqualTo("*****");
assertThat(method.invoke(watcher, "plain.key", "value")).isEqualTo("value");
assertThat(method.invoke(watcher, "plain.key", null)).isEqualTo("null");
}

private void reload(File file) throws Exception {
Method method = ConfFileWatcher.class.getDeclaredMethod("reloadConfiguration", File.class);
method.setAccessible(true);
method.invoke(watcher, file);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package gr.cytech.sendium.conf;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.util.ArrayList;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;

import static org.assertj.core.api.Assertions.assertThat;

class SendiumConfigurationHandlerTest {

private SendiumConfigurationHandler handler;

@BeforeEach
void setUp() {
handler = new SendiumConfigurationHandler();
handler.memoryConfiguration = new ConcurrentHashMap<>();
handler.defaultsConfiguration = new ConcurrentHashMap<>();
handler.overriddenDefaultsConfiguration = new ConcurrentHashMap<>();
handler.currentStoreConfiguration = new ConcurrentHashMap<>();
handler.listeners = new CopyOnWriteArraySet<>();
}

@Test
void getUsesMemoryBeforeStoreAndDefaults() {
handler.defaultsConfiguration.put("conf.key", "default");
handler.currentStoreConfiguration.put("conf.key", new Property("conf.key", "store", null));
handler.set("conf.key", "memory");

assertThat(handler.get("conf.key")).isEqualTo("memory");
}

@Test
void getFallsBackFromInstanceKeyToDefaultKey() {
handler.defaultsConfiguration.put("outSms.default.tps", "25");

assertThat(handler.get("outSms.instance.worker1.tps")).isEqualTo("25");
}

@Test
void setDefaultOverridesLoadedDefaultAndRemoveClearsOverride() {
handler.defaultsConfiguration.put("conf.key", "default");

handler.setDefault("conf.key", "override");
assertThat(handler.get("conf.key")).isEqualTo("override");

handler.remove("conf.key");
assertThat(handler.get("conf.key")).isEqualTo("default");
}

@Test
void typedGettersReturnFallbackWhenParsingFails() {
handler.set("bad.int", "not-int");
handler.set("bad.long", "not-long");

assertThat(handler.getIntPrpt("bad.int", 7)).isEqualTo(7);
assertThat(handler.getLongPrpt("bad.long", 9L)).isEqualTo(9L);
assertThat(handler.getBlnPrpt("missing.bool", true)).isTrue();
}

@Test
void setPropertyAndNotifySendsOldAndNewValues() {
var events = new ArrayList<PropertyChangeEvent>();
handler.set("conf.key", "old");
handler.addPropertyChangeListener(events::add);

String previous = handler.setPropertyAndNotify("conf.key", "new");

assertThat(previous).isEqualTo("old");
assertThat(events).hasSize(1);
assertThat(events.getFirst().getKey()).isEqualTo("conf.key");
assertThat(events.getFirst().getNewValue()).isEqualTo("new");
assertThat(events.getFirst().getOldValue()).isEqualTo("old");
}

@Test
void firePropertyChangeContinuesWhenOneListenerThrows() {
var events = new ArrayList<PropertyChangeEvent>();
handler.addPropertyChangeListener(evt -> {
throw new RuntimeException("boom");
});
handler.addPropertyChangeListener(events::add);

handler.firePropertyChangeEvent("conf.key", "new", "old");

assertThat(events).hasSize(1);
assertThat(events.getFirst().getOldValue()).isEqualTo("old");
}

@Test
void loadDefaultParamsPrefixesKeysOnlyOnce() {
String[][] params = {{"host", "localhost"}, {"outSms.instance.worker.port", "2775"}};

handler.loadDefaultParams("outSms.instance.worker", params);

assertThat(handler.defaultsConfiguration)
.containsEntry("outSms.instance.worker.host", "localhost")
.containsEntry("outSms.instance.worker.port", "2775");
}

@Test
void storePropertiesWritesValuesAndRemovesNullValues() {
handler.set("conf.old", "value");

boolean result = handler.storeProperties(Map.of(
"conf.new", new Property("conf.new", "new-value", null),
"conf.old", new Property("conf.old", null, null)), false);

assertThat(result).isFalse();
assertThat(handler.get("conf.new")).isEqualTo("new-value");
assertThat(handler.get("conf.old")).isNull();
}
}
Loading
Loading