Repository: OpenAEV-Platform/openaev (java)

Issue:
Make it possible to provide platform ID to be used at initial deployment or when cloning
## Context

OpenBAS has an internal identifier for each instance, which is automatically generated at bootstrap / initial deployment (UUIDv4).

For some usecases (cloning an existing instance, for example) we need to be able to change an existing ID, and for some others it would even be better to provide the ID as soon as bootstrapping the instance.

## Use case

[Similar to OCTI](https://github.com/OpenCTI-Platform/opencti/issues/11188), we have internal usecases.


Discussion:

Patch:
diff --git a/openbas-api/src/main/java/io/openbas/App.java b/openbas-api/src/main/java/io/openbas/App.java
--- a/openbas-api/src/main/java/io/openbas/App.java
+++ b/openbas-api/src/main/java/io/openbas/App.java
@@ -1,14 +1,73 @@
 package io.openbas;
 
+import static io.openbas.database.model.SettingKeys.PLATFORM_INSTANCE;
+import static io.openbas.database.model.SettingKeys.PLATFORM_INSTANCE_CREATION;
+
+import io.openbas.config.OpenBASConfig;
+import io.openbas.database.model.Setting;
+import io.openbas.database.repository.SettingRepository;
 import io.openbas.tools.FlywayMigrationValidator;
+import jakarta.annotation.PostConstruct;
+import java.sql.Timestamp;
+import java.time.Instant;
+import java.util.Optional;
+import java.util.UUID;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.logging.log4j.util.Strings;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 
 @SpringBootApplication
+@Slf4j
+@RequiredArgsConstructor
 public class App {
 
+  private final SettingRepository settingRepository;
+  private final OpenBASConfig openBASConfig;
+
   public static void main(String[] args) {
     FlywayMigrationValidator.validateFlywayMigrationNames();
     SpringApplication.run(App.class, args);
   }
+
+  @PostConstruct
+  public void init() {
+    log.info("Startup init");
+    // Get the platform instance id
+    Optional<Setting> instanceId = this.settingRepository.findByKey(PLATFORM_INSTANCE.key());
+    Setting instanceCreationDate =
+        this.settingRepository
+            .findByKey(PLATFORM_INSTANCE_CREATION.key())
+            .orElse(new Setting(PLATFORM_INSTANCE_CREATION.key(), ""));
+
+    String platformId;
+
+    // If we don't have a platform instance id or if it's been specified as another value than the
+    // one in the database
+    if (instanceId.isEmpty()
+        || (!Strings.isBlank(openBASConfig.getInstanceId())
+            && !instanceId.get().getValue().equals(openBASConfig.getInstanceId()))) {
+      log.info("Updating platform instance id");
+      // We update the platform instance id using a random UUID if the value does not exist in the
+      // database
+      platformId = UUID.randomUUID().toString();
+      Setting instanceIdSetting =
+          instanceId.orElse(new Setting(PLATFORM_INSTANCE.key(), platformId));
+
+      // If it's been specified as a specific id, we validate that it's a proper UUID and use it
+      if (!Strings.isBlank(openBASConfig.getInstanceId())) {
+        platformId = openBASConfig.getInstanceId();
+        instanceIdSetting.setValue(UUID.fromString(openBASConfig.getInstanceId()).toString());
+      }
+
+      // Then we save the id in database and update/set the creation date
+      settingRepository.save(instanceIdSetting);
+      instanceCreationDate.setValue(Timestamp.from(Instant.now()).toString());
+      settingRepository.save(instanceCreationDate);
+    } else {
+      platformId = instanceId.get().getValue();
+    }
+    log.info("Startup of the platform - Platform Instance ID: {}", platformId);
+  }
 }
diff --git a/openbas-framework/src/main/java/io/openbas/config/OpenBASConfig.java b/openbas-framework/src/main/java/io/openbas/config/OpenBASConfig.java
--- a/openbas-framework/src/main/java/io/openbas/config/OpenBASConfig.java
+++ b/openbas-framework/src/main/java/io/openbas/config/OpenBASConfig.java
@@ -61,6 +61,9 @@ public class OpenBASConfig {
   @JsonProperty("enabled_dev_features")
   private String enabledDevFeatures = "";
 
+  @JsonProperty("instance_id")
+  private String instanceId;
+
   @JsonIgnore private String cookieName = "openbas_token";
 
   @JsonIgnore private String cookieDuration = "P1D";


Test patch:
diff --git a/openbas-api/src/test/java/io/openbas/AppTest.java b/openbas-api/src/test/java/io/openbas/AppTest.java
new file mode 100644
--- /dev/null
+++ b/openbas-api/src/test/java/io/openbas/AppTest.java
@@ -0,0 +1,108 @@
+package io.openbas;
+
+import static io.openbas.database.model.SettingKeys.PLATFORM_INSTANCE;
+import static io.openbas.database.model.SettingKeys.PLATFORM_INSTANCE_CREATION;
+import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.Mockito.*;
+
+import io.openbas.config.OpenBASConfig;
+import io.openbas.database.model.Setting;
+import io.openbas.database.repository.SettingRepository;
+import java.util.Optional;
+import java.util.UUID;
+import org.junit.jupiter.api.*;
+import org.mockito.Mock;
+import org.springframework.beans.factory.annotation.Autowired;
+
+class AppTest extends IntegrationTest {
+
+  @Mock private SettingRepository settingRepository;
+  @Autowired private OpenBASConfig openBASConfig;
+
+  @DisplayName("Should throw an exception when having an incorrect instance id")
+  @Test
+  void shouldThrowExceptionWithInvalidInstanceId() {
+    openBASConfig.setInstanceId("pouet");
+    when(settingRepository.findByKey(PLATFORM_INSTANCE.key())).thenReturn(Optional.empty());
+
+    assertThatThrownBy(() -> new App(settingRepository, openBASConfig).init())
+        .isInstanceOf(IllegalArgumentException.class)
+        .hasMessage("Invalid UUID string: pouet");
+    verify(settingRepository).findByKey(PLATFORM_INSTANCE.key());
+    verify(settingRepository).findByKey(PLATFORM_INSTANCE_CREATION.key());
+  }
+
+  @DisplayName("Should update the instance id with the one in the properties")
+  @Test
+  void shouldUpdateInstanceIdWithProperty() {
+    String uuid = UUID.randomUUID().toString();
+    openBASConfig.setInstanceId(uuid);
+    new App(settingRepository, openBASConfig).init();
+    verify(settingRepository).findByKey(PLATFORM_INSTANCE.key());
+    verify(settingRepository).findByKey(PLATFORM_INSTANCE_CREATION.key());
+    verify(settingRepository)
+        .save(
+            argThat(
+                setting ->
+                    PLATFORM_INSTANCE.key().equals(setting.getKey())
+                        && uuid.equals(setting.getValue())));
+    verify(settingRepository)
+        .save(argThat(setting -> PLATFORM_INSTANCE_CREATION.key().equals(setting.getKey())));
+  }
+
+  @DisplayName("Should update the instance id when there is none in db")
+  @Test
+  void shouldUpdateInstanceIdWhenNoneInDb() {
+    // Mock le repository pour retourner empty
+    when(settingRepository.findByKey(PLATFORM_INSTANCE.key())).thenReturn(Optional.empty());
+    new App(settingRepository, openBASConfig).init();
+    verify(settingRepository).findByKey(PLATFORM_INSTANCE.key());
+    verify(settingRepository).findByKey(PLATFORM_INSTANCE_CREATION.key());
+    verify(settingRepository)
+        .save(argThat(setting -> PLATFORM_INSTANCE.key().equals(setting.getKey())));
+    verify(settingRepository)
+        .save(argThat(setting -> PLATFORM_INSTANCE_CREATION.key().equals(setting.getKey())));
+  }
+
+  @DisplayName(
+      "Should update the instance id when there is none in db and the specified one is blank")
+  @Test
+  void shouldUpdateInstanceIdWhenNoneInDbAndSpecifiedOneIsBlank() {
+    openBASConfig.setInstanceId("");
+    when(settingRepository.findByKey(PLATFORM_INSTANCE.key())).thenReturn(Optional.empty());
+    new App(settingRepository, openBASConfig).init();
+    verify(settingRepository).findByKey(PLATFORM_INSTANCE.key());
+    verify(settingRepository).findByKey(PLATFORM_INSTANCE_CREATION.key());
+    verify(settingRepository)
+        .save(argThat(setting -> PLATFORM_INSTANCE.key().equals(setting.getKey())));
+    verify(settingRepository)
+        .save(argThat(setting -> PLATFORM_INSTANCE_CREATION.key().equals(setting.getKey())));
+  }
+
+  @DisplayName("Shouldn't update anything if instance id in db and the specified one is blank")
+  @Test
+  void shouldNotUpdateInstanceIdWhenInDbAndSpecifiedOneIsBlank() {
+    openBASConfig.setInstanceId("");
+    when(settingRepository.findByKey(PLATFORM_INSTANCE.key()))
+        .thenReturn(
+            Optional.of(new Setting(PLATFORM_INSTANCE.key(), UUID.randomUUID().toString())));
+    new App(settingRepository, openBASConfig).init();
+    verify(settingRepository).findByKey(PLATFORM_INSTANCE.key());
+    verify(settingRepository).findByKey(PLATFORM_INSTANCE_CREATION.key());
+    verifyNoMoreInteractions(settingRepository);
+  }
+
+  @DisplayName("Shouldn't update anything if instance id in db and there is no specified one")
+  @Test
+  void shouldNotUpdateInstanceIdWhenInDbAndNoSpecifiedOne() {
+    openBASConfig.setInstanceId(null);
+    when(settingRepository.findByKey(PLATFORM_INSTANCE.key()))
+        .thenReturn(
+            Optional.of(new Setting(PLATFORM_INSTANCE.key(), UUID.randomUUID().toString())));
+    new App(settingRepository, openBASConfig).init();
+    verify(settingRepository).findByKey(PLATFORM_INSTANCE.key());
+    verify(settingRepository).findByKey(PLATFORM_INSTANCE_CREATION.key());
+    verifyNoMoreInteractions(settingRepository);
+  }
+}