Compare commits
5 Commits
ebe8e5d574
...
query-prov
| Author | SHA1 | Date | |
|---|---|---|---|
| 0e5de99f55 | |||
| 0dae53017d | |||
| 934937feb0 | |||
| dcb6850306 | |||
| f264f90f3b |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -33,3 +33,4 @@
|
||||
/cygnus-installer/src/test
|
||||
/cygnus-cloud-service/target
|
||||
/cygnus-installer/src/target
|
||||
/cygnus-installer/target
|
||||
|
||||
5
.vscode/launch.json
vendored
5
.vscode/launch.json
vendored
@@ -59,14 +59,15 @@
|
||||
"REDIS_HOST": "103.125.129.116",
|
||||
"REDIS_PORT": "7901",
|
||||
"REDIS_PASSWORD": "M@triXR3d1s@6202",
|
||||
"REDIS_DATABASE": "1",
|
||||
"REDIS_SSL": "false",
|
||||
"CYGNUS_CLOUD_BASE_URL": "http://localhost:8090",
|
||||
"CYGNUS_TOKEN_URL": "http://localhost:8090/oauth2/token",
|
||||
"CYGNUS_CLIENT_ID": "matrix",
|
||||
"CYGNUS_INSTALLATION_ID": "matrix-delhi-cygnus-01",
|
||||
"CYGNUS_CLIENT_ASSERTION": "file:${workspaceFolder}/matrix-installation/config/machine-assertion.jwt",
|
||||
"CYGNUS_CLIENT_ASSERTION": "file:${workspaceFolder}/config/clients/matrix/matrix-matrix-delhi-cygnus-01-assertion.jwt",
|
||||
"CYGNUS_LOGIN_KEY_ID": "cygnus-login-2026-01",
|
||||
"CYGNUS_LOGIN_PUBLIC_KEY": "file:${workspaceFolder}/matrix-installation/config/keys/login-public.pem",
|
||||
"CYGNUS_LOGIN_PUBLIC_KEY": "file:${workspaceFolder}/config/keys/login-public.pem",
|
||||
"CYGNUS_CLOUD_REQUEST_TIMEOUT": "PT10S",
|
||||
"CYGNUS_TOKEN_REFRESH_SKEW": "PT30S"
|
||||
},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.cygnus.client;
|
||||
|
||||
import com.cygnus.client.model.CloudIdentitySession;
|
||||
import com.cygnus.client.model.CloudQueryResponse;
|
||||
import com.cygnus.client.model.LoginPayload;
|
||||
import com.cygnus.client.security.LoginEnvelopeEncryptor;
|
||||
import com.cygnus.client.security.MachineTokenProvider;
|
||||
@@ -50,4 +51,15 @@ public class CloudIdentityClient {
|
||||
.bodyToMono(CloudIdentitySession.class))
|
||||
.timeout(properties.requestTimeout());
|
||||
}
|
||||
|
||||
public Mono<CloudQueryResponse> fetchQuery(int queryId) {
|
||||
return tokenProvider.accessToken()
|
||||
.flatMap(token -> webClient.get()
|
||||
.uri(properties.baseUri().resolve("/api/v1/queries/" + queryId))
|
||||
.header(HttpHeaders.AUTHORIZATION, "Bearer " + token)
|
||||
.accept(MediaType.APPLICATION_JSON)
|
||||
.retrieve()
|
||||
.bodyToMono(CloudQueryResponse.class))
|
||||
.timeout(properties.requestTimeout());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
package com.cygnus.client.model;
|
||||
|
||||
public record CloudQueryResponse(int queryId, String query) {
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package com.cygnus.cloud.query;
|
||||
|
||||
public record CloudQuery(int queryId, String query) {
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.cygnus.cloud.query;
|
||||
|
||||
import jakarta.validation.constraints.Min;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@Validated
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/queries")
|
||||
public class CloudQueryController {
|
||||
|
||||
private final QueryCatalogRepository repository;
|
||||
|
||||
public CloudQueryController(QueryCatalogRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
@GetMapping("/{queryId}")
|
||||
public Mono<CloudQuery> query(
|
||||
@PathVariable @Min(1) int queryId) {
|
||||
return repository.findEnabled(queryId)
|
||||
.switchIfEmpty(Mono.error(new QueryNotFoundException(queryId)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.cygnus.cloud.query;
|
||||
|
||||
import com.cygnus.cloud.database.ReactiveDatabaseClient;
|
||||
import io.vertx.sqlclient.Tuple;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@Repository
|
||||
public class QueryCatalogRepository {
|
||||
|
||||
private static final String INITIALIZE = """
|
||||
CREATE SCHEMA IF NOT EXISTS platform;
|
||||
CREATE TABLE IF NOT EXISTS platform.application_query (
|
||||
query_id integer PRIMARY KEY,
|
||||
query_text text NOT NULL,
|
||||
enabled boolean NOT NULL DEFAULT true,
|
||||
created_at timestamptz NOT NULL DEFAULT current_timestamp,
|
||||
updated_at timestamptz NOT NULL DEFAULT current_timestamp,
|
||||
CONSTRAINT ck_platform_application_query_id
|
||||
CHECK (query_id > 0),
|
||||
CONSTRAINT ck_platform_application_query_text
|
||||
CHECK (length(btrim(query_text)) > 0)
|
||||
)
|
||||
""";
|
||||
private static final String FIND = """
|
||||
SELECT query_id, query_text
|
||||
FROM platform.application_query
|
||||
WHERE query_id = $1
|
||||
AND enabled = true
|
||||
""";
|
||||
private final ReactiveDatabaseClient database;
|
||||
|
||||
public QueryCatalogRepository(ReactiveDatabaseClient database) {
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
public Mono<Void> initialize() {
|
||||
return database.query(INITIALIZE).then();
|
||||
}
|
||||
|
||||
public Mono<CloudQuery> findEnabled(int queryId) {
|
||||
return database.preparedQuery(FIND, Tuple.of(queryId))
|
||||
.flatMapMany(rows -> reactor.core.publisher.Flux.fromIterable(rows))
|
||||
.next()
|
||||
.map(row -> new CloudQuery(
|
||||
row.getInteger("query_id"), row.getString("query_text")));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.cygnus.cloud.query;
|
||||
|
||||
import java.time.Duration;
|
||||
import org.springframework.boot.ApplicationArguments;
|
||||
import org.springframework.boot.ApplicationRunner;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class QueryCatalogSchemaInitializer implements ApplicationRunner {
|
||||
|
||||
private final QueryCatalogRepository repository;
|
||||
|
||||
public QueryCatalogSchemaInitializer(QueryCatalogRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(ApplicationArguments arguments) {
|
||||
repository.initialize()
|
||||
.block(Duration.ofMinutes(2));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.cygnus.cloud.query;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
|
||||
@ResponseStatus(HttpStatus.NOT_FOUND)
|
||||
public class QueryNotFoundException extends RuntimeException {
|
||||
|
||||
public QueryNotFoundException(int queryId) {
|
||||
super("Query was not found: " + queryId);
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,8 @@ public class CloudSecurityConfiguration {
|
||||
.permitAll()
|
||||
.pathMatchers("/api/v1/identity/login")
|
||||
.hasAuthority("SCOPE_identity.login")
|
||||
.pathMatchers("/api/v1/queries/**")
|
||||
.hasAuthority("SCOPE_identity.login")
|
||||
.pathMatchers("/api/v1/admin/**")
|
||||
.hasAuthority("SCOPE_cygnus.admin")
|
||||
.anyExchange().authenticated())
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
CREATE SCHEMA IF NOT EXISTS platform;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS platform.application_query (
|
||||
query_id integer PRIMARY KEY,
|
||||
query_text text NOT NULL,
|
||||
enabled boolean NOT NULL DEFAULT true,
|
||||
created_at timestamptz NOT NULL DEFAULT current_timestamp,
|
||||
updated_at timestamptz NOT NULL DEFAULT current_timestamp,
|
||||
CONSTRAINT ck_platform_application_query_id
|
||||
CHECK (query_id > 0),
|
||||
CONSTRAINT ck_platform_application_query_text
|
||||
CHECK (length(btrim(query_text)) > 0)
|
||||
);
|
||||
@@ -0,0 +1,25 @@
|
||||
DO $$
|
||||
DECLARE
|
||||
current_type text;
|
||||
BEGIN
|
||||
SELECT data_type
|
||||
INTO current_type
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'platform'
|
||||
AND table_name = 'application_query'
|
||||
AND column_name = 'query_id';
|
||||
|
||||
IF current_type IN ('character varying', 'text') THEN
|
||||
ALTER TABLE platform.application_query
|
||||
DROP CONSTRAINT IF EXISTS ck_platform_application_query_id;
|
||||
|
||||
ALTER TABLE platform.application_query
|
||||
ALTER COLUMN query_id TYPE integer
|
||||
USING regexp_replace(query_id, '^Query', '')::integer;
|
||||
|
||||
ALTER TABLE platform.application_query
|
||||
ADD CONSTRAINT ck_platform_application_query_id
|
||||
CHECK (query_id > 0);
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
@@ -30,6 +30,7 @@ public class DeploymentWriter {
|
||||
try {
|
||||
Path normalizedOutput = output.toAbsolutePath().normalize();
|
||||
Path config = normalizedOutput.resolve("config");
|
||||
protectCloudConfiguration(config, profile);
|
||||
Path keyDirectory = config.resolve("keys");
|
||||
Files.createDirectories(keyDirectory);
|
||||
|
||||
@@ -82,6 +83,23 @@ public class DeploymentWriter {
|
||||
}
|
||||
}
|
||||
|
||||
private void protectCloudConfiguration(Path deploymentConfig, ProductProfile profile) {
|
||||
if (isInside(deploymentConfig, profile.assertionEncryptionPublicKey())
|
||||
|| isInside(deploymentConfig, profile.loginEncryptionPublicKey())) {
|
||||
throw new IllegalArgumentException(
|
||||
"Refusing to write deployment files over the cloud-service "
|
||||
+ "configuration directory");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isInside(Path directory, Path file) {
|
||||
if (file == null) {
|
||||
return false;
|
||||
}
|
||||
return file.toAbsolutePath().normalize().startsWith(
|
||||
directory.toAbsolutePath().normalize());
|
||||
}
|
||||
|
||||
private String installationConfiguration(
|
||||
UUID installationUuid,
|
||||
String installationCode,
|
||||
@@ -174,6 +192,7 @@ public class DeploymentWriter {
|
||||
appendEnvironment(env, "REDIS_HOST", runtime.redisHost());
|
||||
appendEnvironment(env, "REDIS_PORT", runtime.redisPort());
|
||||
appendEnvironment(env, "REDIS_PASSWORD", runtime.redisPassword());
|
||||
appendEnvironment(env, "REDIS_DATABASE", "1");
|
||||
appendEnvironment(env, "REDIS_SSL", Boolean.toString(runtime.redisSsl()));
|
||||
appendEnvironment(
|
||||
env, "CYGNUS_CLOUD_BASE_URL", normalizedCloudServiceUrl(runtime));
|
||||
|
||||
@@ -156,6 +156,7 @@ public class InstallationService {
|
||||
if (request.outputDirectory() == null) {
|
||||
throw new IllegalArgumentException("Output directory is required");
|
||||
}
|
||||
validateOutputIsolation(request.outputDirectory());
|
||||
RuntimeConfiguration runtime = request.runtimeConfiguration();
|
||||
if (runtime == null) {
|
||||
throw new IllegalArgumentException("Runtime configuration is required");
|
||||
@@ -204,6 +205,27 @@ public class InstallationService {
|
||||
}
|
||||
}
|
||||
|
||||
private void validateOutputIsolation(Path outputDirectory) {
|
||||
Path deploymentConfig = outputDirectory
|
||||
.toAbsolutePath()
|
||||
.normalize()
|
||||
.resolve("config");
|
||||
if (isInside(deploymentConfig, profile.assertionEncryptionPublicKey())
|
||||
|| isInside(deploymentConfig, profile.loginEncryptionPublicKey())) {
|
||||
throw new IllegalArgumentException(
|
||||
"Output directory would overwrite the cloud-service config folder. "
|
||||
+ "Select a dedicated deployment directory, such as "
|
||||
+ "'matrix-installation'.");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isInside(Path directory, Path file) {
|
||||
if (file == null) {
|
||||
return false;
|
||||
}
|
||||
return file.toAbsolutePath().normalize().startsWith(directory);
|
||||
}
|
||||
|
||||
private String normalizedCloudServiceUrl(String value) {
|
||||
String normalized = value.trim();
|
||||
return normalized.endsWith("/")
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.cygnus.installer;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
@@ -108,6 +109,7 @@ class DeploymentWriterTest {
|
||||
.contains("MATRIX_IMAGE=\"registry.example.com/matrix:1.0.0\"")
|
||||
.contains("MATRIX_DB_URL=\"jdbc:postgresql://db:5432/matrix\"")
|
||||
.contains("REDIS_HOST=\"redis\"")
|
||||
.contains("REDIS_DATABASE=\"1\"")
|
||||
.contains("CYGNUS_CLOUD_BASE_URL=\"http://host.docker.internal:8090\"")
|
||||
.contains("CYGNUS_TOKEN_URL=\"http://host.docker.internal:8090/oauth2/token\"")
|
||||
.contains("CYGNUS_CLIENT_ID=\"matrix-client\"")
|
||||
@@ -121,4 +123,84 @@ class DeploymentWriterTest {
|
||||
assertThat(output.resolve("config/keys/client-signing-private.pem"))
|
||||
.isRegularFile();
|
||||
}
|
||||
|
||||
@Test
|
||||
void refusesToOverwriteCloudServiceConfiguration() throws Exception {
|
||||
Path cloudConfig = temporaryDirectory.resolve("config");
|
||||
Path cloudKeys = cloudConfig.resolve("keys");
|
||||
Files.createDirectories(cloudKeys);
|
||||
Path assertionPublic = cloudKeys.resolve("assertion-decryption-public.pem");
|
||||
Path loginPublic = cloudKeys.resolve("login-public.pem");
|
||||
Files.writeString(assertionPublic, "assertion-public-key");
|
||||
Files.writeString(loginPublic, "login-public-key");
|
||||
|
||||
var profile = new ProductProfile(
|
||||
"matrix",
|
||||
"Matrix",
|
||||
"matrix-onprem",
|
||||
"MATRIX_IMAGE",
|
||||
"matrix",
|
||||
"installation.yml",
|
||||
"MATRIX_INSTALLATION_CONFIG",
|
||||
"/srv/matrix/config/installation.yml",
|
||||
"8080:8080",
|
||||
"/matrix/",
|
||||
"/oauth2/token",
|
||||
assertionPublic,
|
||||
loginPublic,
|
||||
"cygnus-login-2026-01",
|
||||
List.of());
|
||||
|
||||
assertThatThrownBy(() -> new DeploymentWriter().write(
|
||||
temporaryDirectory,
|
||||
UUID.randomUUID(),
|
||||
"primary",
|
||||
"matrix-client",
|
||||
new ActivationDtos.ValidationResponse(
|
||||
"activation-token",
|
||||
OffsetDateTime.now().plusMinutes(5),
|
||||
UUID.randomUUID(),
|
||||
"matrix-client",
|
||||
"FULL",
|
||||
2),
|
||||
new ActivationDtos.RegistrationResponse(
|
||||
UUID.randomUUID(),
|
||||
UUID.randomUUID(),
|
||||
UUID.randomUUID(),
|
||||
null,
|
||||
"primary",
|
||||
1,
|
||||
"ACTIVE"),
|
||||
new InstallationKeyService().generate(),
|
||||
"assertion",
|
||||
new InstallerSettings(
|
||||
"matrix",
|
||||
URI.create("https://cloud.example.com"),
|
||||
URI.create("https://cloud.example.com"),
|
||||
"/api/v1/installations",
|
||||
"production",
|
||||
temporaryDirectory,
|
||||
temporaryDirectory,
|
||||
"1",
|
||||
new IniDocument(Map.of())),
|
||||
profile,
|
||||
"registry.example.com/matrix:1.0.0",
|
||||
Map.of(),
|
||||
new RuntimeConfiguration(
|
||||
"https://cloud.example.com",
|
||||
"jdbc:postgresql://db/matrix",
|
||||
"postgres",
|
||||
"secret",
|
||||
"redis",
|
||||
"7901",
|
||||
"secret",
|
||||
false,
|
||||
"PT10S",
|
||||
"PT30S")))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessageContaining("cloud-service configuration");
|
||||
|
||||
assertThat(assertionPublic).hasContent("assertion-public-key");
|
||||
assertThat(loginPublic).hasContent("login-public-key");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
spring:
|
||||
main:
|
||||
banner-mode: "off"
|
||||
application:
|
||||
name: technobee-service-installer
|
||||
technobee:
|
||||
installer:
|
||||
config: ${TECHNOBEE_INSTALLER_CONFIG:./installer.ini}
|
||||
version: ${TECHNOBEE_INSTALLER_VERSION:1.0.0}
|
||||
logging:
|
||||
level:
|
||||
root: WARN
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,3 +0,0 @@
|
||||
artifactId=technobee-service-installer
|
||||
groupId=com.cygnus
|
||||
version=1.0.0-SNAPSHOT
|
||||
@@ -1,36 +0,0 @@
|
||||
com/cygnus/installer/MachineAssertionService.class
|
||||
com/cygnus/installer/ActivationDtos$ValidationResponse.class
|
||||
com/cygnus/installer/SwingInstallationWizard$2.class
|
||||
com/cygnus/installer/CommandResult.class
|
||||
com/cygnus/installer/InstallationProgressListener.class
|
||||
com/cygnus/installer/ProductProfileRegistry.class
|
||||
com/cygnus/installer/InstallationRequest.class
|
||||
com/cygnus/installer/InstallerConfiguration.class
|
||||
com/cygnus/installer/ProductProfile.class
|
||||
com/cygnus/installer/InstallationWizard.class
|
||||
com/cygnus/installer/TechnobeeServiceInstallerApplication.class
|
||||
com/cygnus/installer/DeploymentWriter.class
|
||||
com/cygnus/installer/InstallationKeyService.class
|
||||
com/cygnus/installer/DockerComposeDeploymentService.class
|
||||
com/cygnus/installer/ActivationClient.class
|
||||
com/cygnus/installer/ActivationDtos$RegistrationResponse.class
|
||||
com/cygnus/installer/IniDocument.class
|
||||
com/cygnus/installer/SwingInstallationWizard$3.class
|
||||
com/cygnus/installer/InstallerConfigurationException.class
|
||||
com/cygnus/installer/DockerRegistryLoginService.class
|
||||
com/cygnus/installer/SwingInstallationWizard.class
|
||||
com/cygnus/installer/ActivationDtos.class
|
||||
com/cygnus/installer/DockerPrerequisiteChecker.class
|
||||
com/cygnus/installer/InstallerBootstrapProperties.class
|
||||
com/cygnus/installer/SwingInstallationWizard$1.class
|
||||
com/cygnus/installer/CommandExecutor.class
|
||||
com/cygnus/installer/IniConfigurationLoader.class
|
||||
com/cygnus/installer/InstallationService.class
|
||||
com/cygnus/installer/InstallationResult.class
|
||||
com/cygnus/installer/RuntimeConfiguration.class
|
||||
com/cygnus/installer/ActivationDtos$RegistrationRequest.class
|
||||
com/cygnus/installer/ActivationDtos$ValidationRequest.class
|
||||
com/cygnus/installer/PrerequisiteException.class
|
||||
com/cygnus/installer/InstallerSettings.class
|
||||
com/cygnus/installer/InstallationKeyPair.class
|
||||
com/cygnus/installer/SwingInstallationWizard$ProgressUpdate.class
|
||||
@@ -1,28 +0,0 @@
|
||||
/Users/maddy/Projects/cygnus/matrix/cygnus-installer/src/main/java/com/cygnus/installer/ActivationClient.java
|
||||
/Users/maddy/Projects/cygnus/matrix/cygnus-installer/src/main/java/com/cygnus/installer/ActivationDtos.java
|
||||
/Users/maddy/Projects/cygnus/matrix/cygnus-installer/src/main/java/com/cygnus/installer/CommandExecutor.java
|
||||
/Users/maddy/Projects/cygnus/matrix/cygnus-installer/src/main/java/com/cygnus/installer/CommandResult.java
|
||||
/Users/maddy/Projects/cygnus/matrix/cygnus-installer/src/main/java/com/cygnus/installer/DeploymentWriter.java
|
||||
/Users/maddy/Projects/cygnus/matrix/cygnus-installer/src/main/java/com/cygnus/installer/DockerComposeDeploymentService.java
|
||||
/Users/maddy/Projects/cygnus/matrix/cygnus-installer/src/main/java/com/cygnus/installer/DockerPrerequisiteChecker.java
|
||||
/Users/maddy/Projects/cygnus/matrix/cygnus-installer/src/main/java/com/cygnus/installer/DockerRegistryLoginService.java
|
||||
/Users/maddy/Projects/cygnus/matrix/cygnus-installer/src/main/java/com/cygnus/installer/IniConfigurationLoader.java
|
||||
/Users/maddy/Projects/cygnus/matrix/cygnus-installer/src/main/java/com/cygnus/installer/IniDocument.java
|
||||
/Users/maddy/Projects/cygnus/matrix/cygnus-installer/src/main/java/com/cygnus/installer/InstallationKeyPair.java
|
||||
/Users/maddy/Projects/cygnus/matrix/cygnus-installer/src/main/java/com/cygnus/installer/InstallationKeyService.java
|
||||
/Users/maddy/Projects/cygnus/matrix/cygnus-installer/src/main/java/com/cygnus/installer/InstallationProgressListener.java
|
||||
/Users/maddy/Projects/cygnus/matrix/cygnus-installer/src/main/java/com/cygnus/installer/InstallationRequest.java
|
||||
/Users/maddy/Projects/cygnus/matrix/cygnus-installer/src/main/java/com/cygnus/installer/InstallationResult.java
|
||||
/Users/maddy/Projects/cygnus/matrix/cygnus-installer/src/main/java/com/cygnus/installer/InstallationService.java
|
||||
/Users/maddy/Projects/cygnus/matrix/cygnus-installer/src/main/java/com/cygnus/installer/InstallationWizard.java
|
||||
/Users/maddy/Projects/cygnus/matrix/cygnus-installer/src/main/java/com/cygnus/installer/InstallerBootstrapProperties.java
|
||||
/Users/maddy/Projects/cygnus/matrix/cygnus-installer/src/main/java/com/cygnus/installer/InstallerConfiguration.java
|
||||
/Users/maddy/Projects/cygnus/matrix/cygnus-installer/src/main/java/com/cygnus/installer/InstallerConfigurationException.java
|
||||
/Users/maddy/Projects/cygnus/matrix/cygnus-installer/src/main/java/com/cygnus/installer/InstallerSettings.java
|
||||
/Users/maddy/Projects/cygnus/matrix/cygnus-installer/src/main/java/com/cygnus/installer/MachineAssertionService.java
|
||||
/Users/maddy/Projects/cygnus/matrix/cygnus-installer/src/main/java/com/cygnus/installer/PrerequisiteException.java
|
||||
/Users/maddy/Projects/cygnus/matrix/cygnus-installer/src/main/java/com/cygnus/installer/ProductProfile.java
|
||||
/Users/maddy/Projects/cygnus/matrix/cygnus-installer/src/main/java/com/cygnus/installer/ProductProfileRegistry.java
|
||||
/Users/maddy/Projects/cygnus/matrix/cygnus-installer/src/main/java/com/cygnus/installer/RuntimeConfiguration.java
|
||||
/Users/maddy/Projects/cygnus/matrix/cygnus-installer/src/main/java/com/cygnus/installer/SwingInstallationWizard.java
|
||||
/Users/maddy/Projects/cygnus/matrix/cygnus-installer/src/main/java/com/cygnus/installer/TechnobeeServiceInstallerApplication.java
|
||||
@@ -1,11 +0,0 @@
|
||||
com/cygnus/installer/MachineAssertionServiceTest.class
|
||||
com/cygnus/installer/DockerRegistryLoginServiceTest$CapturingExecutor.class
|
||||
com/cygnus/installer/DockerRegistryLoginServiceTest.class
|
||||
com/cygnus/installer/DockerComposeDeploymentServiceTest$CapturingExecutor.class
|
||||
com/cygnus/installer/InstallationServiceTest.class
|
||||
com/cygnus/installer/DockerPrerequisiteCheckerTest$1.class
|
||||
com/cygnus/installer/ProductProfileRegistryTest.class
|
||||
com/cygnus/installer/IniConfigurationLoaderTest.class
|
||||
com/cygnus/installer/DockerComposeDeploymentServiceTest.class
|
||||
com/cygnus/installer/DeploymentWriterTest.class
|
||||
com/cygnus/installer/DockerPrerequisiteCheckerTest.class
|
||||
@@ -1,8 +0,0 @@
|
||||
/Users/maddy/Projects/cygnus/matrix/cygnus-installer/src/test/java/com/cygnus/installer/DeploymentWriterTest.java
|
||||
/Users/maddy/Projects/cygnus/matrix/cygnus-installer/src/test/java/com/cygnus/installer/DockerComposeDeploymentServiceTest.java
|
||||
/Users/maddy/Projects/cygnus/matrix/cygnus-installer/src/test/java/com/cygnus/installer/DockerPrerequisiteCheckerTest.java
|
||||
/Users/maddy/Projects/cygnus/matrix/cygnus-installer/src/test/java/com/cygnus/installer/DockerRegistryLoginServiceTest.java
|
||||
/Users/maddy/Projects/cygnus/matrix/cygnus-installer/src/test/java/com/cygnus/installer/IniConfigurationLoaderTest.java
|
||||
/Users/maddy/Projects/cygnus/matrix/cygnus-installer/src/test/java/com/cygnus/installer/InstallationServiceTest.java
|
||||
/Users/maddy/Projects/cygnus/matrix/cygnus-installer/src/test/java/com/cygnus/installer/MachineAssertionServiceTest.java
|
||||
/Users/maddy/Projects/cygnus/matrix/cygnus-installer/src/test/java/com/cygnus/installer/ProductProfileRegistryTest.java
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,4 +0,0 @@
|
||||
-------------------------------------------------------------------------------
|
||||
Test set: com.cygnus.installer.DeploymentWriterTest
|
||||
-------------------------------------------------------------------------------
|
||||
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.174 s -- in com.cygnus.installer.DeploymentWriterTest
|
||||
@@ -1,4 +0,0 @@
|
||||
-------------------------------------------------------------------------------
|
||||
Test set: com.cygnus.installer.DockerComposeDeploymentServiceTest
|
||||
-------------------------------------------------------------------------------
|
||||
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.002 s -- in com.cygnus.installer.DockerComposeDeploymentServiceTest
|
||||
@@ -1,4 +0,0 @@
|
||||
-------------------------------------------------------------------------------
|
||||
Test set: com.cygnus.installer.DockerPrerequisiteCheckerTest
|
||||
-------------------------------------------------------------------------------
|
||||
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.004 s -- in com.cygnus.installer.DockerPrerequisiteCheckerTest
|
||||
@@ -1,4 +0,0 @@
|
||||
-------------------------------------------------------------------------------
|
||||
Test set: com.cygnus.installer.DockerRegistryLoginServiceTest
|
||||
-------------------------------------------------------------------------------
|
||||
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.010 s -- in com.cygnus.installer.DockerRegistryLoginServiceTest
|
||||
@@ -1,4 +0,0 @@
|
||||
-------------------------------------------------------------------------------
|
||||
Test set: com.cygnus.installer.IniConfigurationLoaderTest
|
||||
-------------------------------------------------------------------------------
|
||||
Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.008 s -- in com.cygnus.installer.IniConfigurationLoaderTest
|
||||
@@ -1,4 +0,0 @@
|
||||
-------------------------------------------------------------------------------
|
||||
Test set: com.cygnus.installer.InstallationServiceTest
|
||||
-------------------------------------------------------------------------------
|
||||
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.002 s -- in com.cygnus.installer.InstallationServiceTest
|
||||
@@ -1,4 +0,0 @@
|
||||
-------------------------------------------------------------------------------
|
||||
Test set: com.cygnus.installer.MachineAssertionServiceTest
|
||||
-------------------------------------------------------------------------------
|
||||
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.570 s -- in com.cygnus.installer.MachineAssertionServiceTest
|
||||
@@ -1,4 +0,0 @@
|
||||
-------------------------------------------------------------------------------
|
||||
Test set: com.cygnus.installer.ProductProfileRegistryTest
|
||||
-------------------------------------------------------------------------------
|
||||
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.005 s -- in com.cygnus.installer.ProductProfileRegistryTest
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -15,6 +15,7 @@ import io.lettuce.core.api.sync.RedisCommands;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import matrix.nimble.query.EncryptedQueryCache;
|
||||
|
||||
/**
|
||||
* Best-effort shared cache for the on-premises MVC application. Redis failures
|
||||
@@ -22,7 +23,7 @@ import org.springframework.stereotype.Service;
|
||||
* application.
|
||||
*/
|
||||
@Service
|
||||
public class OnPremRedisCacheService implements DisposableBean {
|
||||
public class OnPremRedisCacheService implements DisposableBean, EncryptedQueryCache {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(OnPremRedisCacheService.class.getName());
|
||||
|
||||
@@ -53,6 +54,21 @@ public class OnPremRedisCacheService implements DisposableBean {
|
||||
}
|
||||
}
|
||||
|
||||
public Optional<String> getRaw(String key) {
|
||||
try {
|
||||
return Optional.ofNullable(commands().get(key));
|
||||
} catch (RedisException exception) {
|
||||
logUnavailable(exception);
|
||||
resetConnection();
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> get(String queryId) {
|
||||
return getRaw(queryId);
|
||||
}
|
||||
|
||||
public <T> Optional<T> get(String namespace, String key, Class<T> type) {
|
||||
return get(namespace, key).flatMap(json -> deserialize(json, objectMapper.constructType(type)));
|
||||
}
|
||||
@@ -79,6 +95,37 @@ public class OnPremRedisCacheService implements DisposableBean {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean putRaw(String key, String value, Duration ttl) {
|
||||
try {
|
||||
commands().setex(key, ttl.toSeconds(), value);
|
||||
return true;
|
||||
} catch (RedisException exception) {
|
||||
logUnavailable(exception);
|
||||
resetConnection();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean put(String queryId, String encryptedQuery, Duration ttl) {
|
||||
return putRaw(queryId, encryptedQuery, ttl);
|
||||
}
|
||||
|
||||
public boolean evictRaw(String key) {
|
||||
try {
|
||||
return commands().del(key) > 0;
|
||||
} catch (RedisException exception) {
|
||||
logUnavailable(exception);
|
||||
resetConnection();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean evict(String queryId) {
|
||||
return evictRaw(queryId);
|
||||
}
|
||||
|
||||
public boolean evict(String namespace, String key) {
|
||||
try {
|
||||
return commands().del(cacheKey(namespace, key)) > 0;
|
||||
|
||||
@@ -16,11 +16,16 @@ public class OnPremRedisConfiguration {
|
||||
@Value("${REDIS_HOST:192.168.0.111}") String host,
|
||||
@Value("${REDIS_PORT:7901}") int port,
|
||||
@Value("${REDIS_PASSWORD:}") String password,
|
||||
@Value("${REDIS_DATABASE:1}") int database,
|
||||
@Value("${REDIS_SSL:false}") boolean ssl,
|
||||
@Value("${REDIS_CONNECT_TIMEOUT_SECONDS:3}") long connectTimeoutSeconds) {
|
||||
if (database < 0) {
|
||||
throw new IllegalArgumentException("REDIS_DATABASE must be zero or greater");
|
||||
}
|
||||
RedisURI.Builder uri = RedisURI.builder()
|
||||
.withHost(host)
|
||||
.withPort(port)
|
||||
.withDatabase(database)
|
||||
.withSsl(ssl)
|
||||
.withTimeout(Duration.ofSeconds(connectTimeoutSeconds));
|
||||
if (password != null && !password.isBlank()) {
|
||||
|
||||
@@ -19,66 +19,60 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.SessionAttributes;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.multipart.MultipartHttpServletRequest;
|
||||
|
||||
@Controller
|
||||
@SessionAttributes({ "Sessvals" })
|
||||
public class CaseUpload {
|
||||
@RequestMapping(value = "caseupload", method = RequestMethod.POST)
|
||||
public String UploadCases(ModelMap model,HttpServletRequest request,@ModelAttribute(value="Sessvals") Session Sessvals)
|
||||
{
|
||||
public String UploadCases(ModelMap model, HttpServletRequest request,
|
||||
@ModelAttribute(value = "Sessvals") Session Sessvals) {
|
||||
model.addAttribute("portlist", FillPortList(Sessvals.getBranchID(), "download"));
|
||||
DownloadUploadSettings us = new DownloadUploadSettings();
|
||||
us.setPortfolioid("-1");
|
||||
model.addAttribute("uploadsettings", us);
|
||||
return "general/onlinedownload";
|
||||
}
|
||||
|
||||
@RequestMapping(value = "downloadsettings", method = RequestMethod.POST)
|
||||
public String FetchDownloadSettings(ModelMap model,HttpServletRequest request,@ModelAttribute(value="Sessvals") Session Sessvals,@ModelAttribute(value="uploadsettings") DownloadUploadSettings us)
|
||||
{
|
||||
public String FetchDownloadSettings(ModelMap model, HttpServletRequest request,
|
||||
@ModelAttribute(value = "Sessvals") Session Sessvals,
|
||||
@ModelAttribute(value = "uploadsettings") DownloadUploadSettings us) {
|
||||
UploadHandler UH = new UploadHandler();
|
||||
UH.setErrCode("1111");
|
||||
UH.setProcessFlag(true);
|
||||
model.addAttribute("portlist", FillPortList(Sessvals.getBranchID(), "download"));
|
||||
UH.FetchSettings(us, 158);
|
||||
model.addAttribute("uploadsettings", us);
|
||||
if(us.getDomainname().equals("localhost"))
|
||||
{
|
||||
if (us.getDomainname().equals("localhost")) {
|
||||
return "general/offlinedownload";
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
return "general/onlinedownload";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/startexcelupload", method = RequestMethod.POST)
|
||||
public String StartExcelUpload(ModelMap model,HttpServletRequest request,@ModelAttribute(value="Sessvals") Session Sessvals,@ModelAttribute(value="uploadsettings") DownloadUploadSettings us,@RequestParam MultipartFile file)
|
||||
{
|
||||
public String StartExcelUpload(ModelMap model, HttpServletRequest request,
|
||||
@ModelAttribute(value = "Sessvals") Session Sessvals,
|
||||
@ModelAttribute(value = "uploadsettings") DownloadUploadSettings us, @RequestParam MultipartFile file) {
|
||||
HSSFWorkbook excel = null;
|
||||
XSSFWorkbook excelx = null;
|
||||
UploadHandler UH = new UploadHandler();
|
||||
UH.setErrCode("1111");
|
||||
UH.setProcessFlag(true);
|
||||
try
|
||||
{
|
||||
if(file.getOriginalFilename().endsWith("xlsx"))
|
||||
{
|
||||
try {
|
||||
if (file.getOriginalFilename().endsWith("xlsx")) {
|
||||
excelx = new XSSFWorkbook(file.getInputStream());
|
||||
UH.StartXLXUpload(us, Sessvals.getUserID(), Sessvals.getCompanyID(), Sessvals.getBranchID(), excelx);
|
||||
}
|
||||
else if(file.getOriginalFilename().endsWith("xls"))
|
||||
{
|
||||
} else if (file.getOriginalFilename().endsWith("xls")) {
|
||||
excel = new HSSFWorkbook(file.getInputStream());
|
||||
UH.StartXLUpload(us, Sessvals.getUserID(), Sessvals.getCompanyID(), Sessvals.getBranchID(), excel);
|
||||
}
|
||||
else
|
||||
{
|
||||
UH.setErrMsg(UH.getErrCode()+"INFIL:error:Invlaid file format. Please check the format of file you are uploading.");
|
||||
} else {
|
||||
UH.setErrMsg(UH.getErrCode()
|
||||
+ "INFIL:error:Invlaid file format. Please check the format of file you are uploading.");
|
||||
UH.setProcessFlag(false);
|
||||
}
|
||||
}catch(Exception exce)
|
||||
{
|
||||
} catch (Exception exce) {
|
||||
UH.setErrMsg(UH.getErrCode() + "INFIL:error:" + exce.getMessage());
|
||||
UH.setProcessFlag(false);
|
||||
}
|
||||
@@ -87,9 +81,11 @@ public class CaseUpload {
|
||||
model.addAttribute("msg", UH.getErrMsg());
|
||||
return "general/offlinedownload";
|
||||
}
|
||||
|
||||
@RequestMapping(value = "startupload", method = RequestMethod.POST)
|
||||
public String StartUpload(ModelMap model,HttpServletRequest request,@ModelAttribute(value="Sessvals") Session Sessvals,@ModelAttribute(value="uploadsettings") DownloadUploadSettings us)
|
||||
{
|
||||
public String StartUpload(ModelMap model, HttpServletRequest request,
|
||||
@ModelAttribute(value = "Sessvals") Session Sessvals,
|
||||
@ModelAttribute(value = "uploadsettings") DownloadUploadSettings us) {
|
||||
UploadHandler UH = new UploadHandler();
|
||||
UH.setErrCode("1111");
|
||||
UH.setProcessFlag(true);
|
||||
@@ -99,18 +95,21 @@ public class CaseUpload {
|
||||
model.addAttribute("msg", UH.getErrMsg());
|
||||
return "general/onlinedownload";
|
||||
}
|
||||
|
||||
@RequestMapping(value = "reportupload", method = RequestMethod.POST)
|
||||
public String UploadReports(ModelMap model,HttpServletRequest request,@ModelAttribute(value="Sessvals") Session Sessvals)
|
||||
{
|
||||
public String UploadReports(ModelMap model, HttpServletRequest request,
|
||||
@ModelAttribute(value = "Sessvals") Session Sessvals) {
|
||||
model.addAttribute("portlist", FillPortList(Sessvals.getBranchID(), "upload"));
|
||||
DownloadUploadSettings us = new DownloadUploadSettings();
|
||||
us.setPortfolioid("-1");
|
||||
model.addAttribute("uploadsettings", us);
|
||||
return "general/uploadreports";
|
||||
}
|
||||
|
||||
@RequestMapping(value = "fileuploadsettings", method = RequestMethod.POST)
|
||||
public String FetchUploadSettings(ModelMap model,HttpServletRequest request,@ModelAttribute(value="Sessvals") Session Sessvals,@ModelAttribute(value="uploadsettings") DownloadUploadSettings us)
|
||||
{
|
||||
public String FetchUploadSettings(ModelMap model, HttpServletRequest request,
|
||||
@ModelAttribute(value = "Sessvals") Session Sessvals,
|
||||
@ModelAttribute(value = "uploadsettings") DownloadUploadSettings us) {
|
||||
UploadHandler UH = new UploadHandler();
|
||||
UH.setErrCode("1112");
|
||||
UH.setProcessFlag(true);
|
||||
@@ -120,8 +119,9 @@ public class CaseUpload {
|
||||
model.addAttribute("uploadsettings", us);
|
||||
return "general/uploadreports";
|
||||
}
|
||||
public String [][] FillPortList(String BranchId,String Action)
|
||||
{
|
||||
return new ModuleFunctions("1001").GetResultArray(157,(BranchId+GlobalClass.ColDelim+Action+GlobalClass.ColDelim).split(GlobalClass.ColDelim));
|
||||
|
||||
public String[][] FillPortList(String BranchId, String Action) {
|
||||
return new ModuleFunctions("1001").GetResultArray(157,
|
||||
(BranchId + GlobalClass.ColDelim + Action + GlobalClass.ColDelim).split(GlobalClass.ColDelim));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package matrix.nimble.controller;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import matrix.nimble.cloud.identity.CloudAuthenticationException;
|
||||
import matrix.nimble.cloud.identity.CloudAuthenticationGateway;
|
||||
import matrix.nimble.model.Login;
|
||||
@@ -15,7 +13,6 @@ import jakarta.servlet.http.HttpSession;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.ModelMap;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.SessionAttributes;
|
||||
@@ -28,16 +25,10 @@ public class SessionController {
|
||||
public SessionController(CloudAuthenticationGateway cloudAuthenticationGateway) {
|
||||
this.cloudAuthenticationGateway = cloudAuthenticationGateway;
|
||||
}
|
||||
@RequestMapping(value="login",method=RequestMethod.POST )
|
||||
public String LoginPage(ModelMap model,@RequestHeader Map<String, String> headers)
|
||||
@RequestMapping(value="login", method={RequestMethod.GET, RequestMethod.POST})
|
||||
public String LoginPage(ModelMap model)
|
||||
{
|
||||
model.addAttribute("login", new Login());
|
||||
String host = headers.get("host").toString();
|
||||
if(!host.contains("192.168.10.205:8585") &&
|
||||
!host.contains("192.168.10.250:8585") &&
|
||||
!host.startsWith("localhost:") && !host.startsWith("127.0.0.1:")) {
|
||||
return "error";
|
||||
}
|
||||
return "login";
|
||||
}
|
||||
@RequestMapping(value="logout",method=RequestMethod.POST )
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
package matrix.nimble.query;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Base64;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.GCMParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
public final class AesGcmQueryCipher implements QueryCipher {
|
||||
|
||||
private static final String VERSION = "v1";
|
||||
private static final int IV_LENGTH = 12;
|
||||
private static final int TAG_BITS = 128;
|
||||
|
||||
private final SecretKeySpec key;
|
||||
private final SecureRandom random;
|
||||
|
||||
public AesGcmQueryCipher(byte[] keyBytes) {
|
||||
this(keyBytes, new SecureRandom());
|
||||
}
|
||||
|
||||
AesGcmQueryCipher(byte[] keyBytes, SecureRandom random) {
|
||||
if (keyBytes == null || keyBytes.length != 32) {
|
||||
throw new IllegalArgumentException("Query cache AES key must contain 32 bytes");
|
||||
}
|
||||
this.key = new SecretKeySpec(keyBytes.clone(), "AES");
|
||||
this.random = random;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String encrypt(String queryId, String query) {
|
||||
try {
|
||||
byte[] iv = new byte[IV_LENGTH];
|
||||
random.nextBytes(iv);
|
||||
Cipher cipher = cipher(Cipher.ENCRYPT_MODE, queryId, iv);
|
||||
byte[] encrypted = cipher.doFinal(query.getBytes(StandardCharsets.UTF_8));
|
||||
Base64.Encoder encoder = Base64.getUrlEncoder().withoutPadding();
|
||||
return VERSION + '.' + encoder.encodeToString(iv) + '.'
|
||||
+ encoder.encodeToString(encrypted);
|
||||
} catch (GeneralSecurityException exception) {
|
||||
throw new IllegalStateException("Unable to encrypt cached query", exception);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String decrypt(String queryId, String encryptedQuery) {
|
||||
try {
|
||||
String[] parts = encryptedQuery.split("\\.", -1);
|
||||
if (parts.length != 3 || !VERSION.equals(parts[0])) {
|
||||
throw new IllegalArgumentException("Unsupported encrypted query format");
|
||||
}
|
||||
Base64.Decoder decoder = Base64.getUrlDecoder();
|
||||
byte[] iv = decoder.decode(parts[1]);
|
||||
if (iv.length != IV_LENGTH) {
|
||||
throw new IllegalArgumentException("Invalid encrypted query IV");
|
||||
}
|
||||
Cipher cipher = cipher(Cipher.DECRYPT_MODE, queryId, iv);
|
||||
return new String(cipher.doFinal(decoder.decode(parts[2])), StandardCharsets.UTF_8);
|
||||
} catch (GeneralSecurityException | IllegalArgumentException exception) {
|
||||
throw new IllegalStateException("Unable to decrypt cached query", exception);
|
||||
}
|
||||
}
|
||||
|
||||
private Cipher cipher(int mode, String queryId, byte[] iv)
|
||||
throws GeneralSecurityException {
|
||||
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
cipher.init(mode, key, new GCMParameterSpec(TAG_BITS, iv));
|
||||
cipher.updateAAD(queryId.getBytes(StandardCharsets.UTF_8));
|
||||
return cipher;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package matrix.nimble.query;
|
||||
|
||||
import com.cygnus.client.CloudIdentityClient;
|
||||
|
||||
public final class CloudQuerySource implements QuerySource {
|
||||
|
||||
private final CloudIdentityClient client;
|
||||
|
||||
public CloudQuerySource(CloudIdentityClient client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String fetch(int queryId) {
|
||||
return client.fetchQuery(queryId)
|
||||
.map(response -> response.query())
|
||||
.blockOptional()
|
||||
.filter(query -> !query.isBlank())
|
||||
.orElseThrow(() -> new QueryProviderException(
|
||||
"Cloud query was empty: " + queryId));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package matrix.nimble.query;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface EncryptedQueryCache {
|
||||
|
||||
Optional<String> get(String queryId);
|
||||
|
||||
boolean put(String queryId, String encryptedQuery, Duration ttl);
|
||||
|
||||
boolean evict(String queryId);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package matrix.nimble.query;
|
||||
|
||||
public interface QueryCipher {
|
||||
|
||||
String encrypt(String queryId, String query);
|
||||
|
||||
String decrypt(String queryId, String encryptedQuery);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package matrix.nimble.query;
|
||||
|
||||
public interface QueryProvider {
|
||||
|
||||
String getQuery(int queryId);
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package matrix.nimble.query;
|
||||
|
||||
import com.cygnus.client.CloudClientProperties;
|
||||
import com.cygnus.client.CloudIdentityClient;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Base64;
|
||||
import matrix.nimble.cloud.cache.OnPremRedisCacheService;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
|
||||
@Configuration
|
||||
public class QueryProviderConfiguration implements DisposableBean {
|
||||
|
||||
private QueryProvider installed;
|
||||
|
||||
@Bean
|
||||
QueryProvider queryProvider(
|
||||
OnPremRedisCacheService cache,
|
||||
CloudIdentityClient cloudClient,
|
||||
CloudClientProperties cloudProperties,
|
||||
@Value("${CYGNUS_QUERY_CACHE_AES_KEY:}") String configuredKey) {
|
||||
byte[] key = queryCacheKey(configuredKey, cloudProperties.clientAssertion());
|
||||
installed = new RedisCachingQueryProvider(
|
||||
cache,
|
||||
new CloudQuerySource(cloudClient),
|
||||
new AesGcmQueryCipher(key));
|
||||
QueryProviders.install(installed);
|
||||
return installed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
if (installed != null) {
|
||||
QueryProviders.clear(installed);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] queryCacheKey(String configuredKey, String assertionLocation) {
|
||||
try {
|
||||
if (configuredKey != null && !configuredKey.isBlank()) {
|
||||
byte[] decoded = Base64.getDecoder().decode(configuredKey.trim());
|
||||
if (decoded.length != 32) {
|
||||
throw new IllegalStateException(
|
||||
"CYGNUS_QUERY_CACHE_AES_KEY must be a Base64-encoded 256-bit key");
|
||||
}
|
||||
return decoded;
|
||||
}
|
||||
String assertion = assertionLocation.startsWith("file:")
|
||||
? Files.readString(Path.of(assertionLocation.substring(5)),
|
||||
StandardCharsets.US_ASCII).trim()
|
||||
: assertionLocation;
|
||||
return MessageDigest.getInstance("SHA-256")
|
||||
.digest(assertion.getBytes(StandardCharsets.UTF_8));
|
||||
} catch (IllegalStateException exception) {
|
||||
throw exception;
|
||||
} catch (Exception exception) {
|
||||
throw new IllegalStateException("Unable to initialize query cache encryption", exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package matrix.nimble.query;
|
||||
|
||||
public class QueryProviderException extends RuntimeException {
|
||||
|
||||
public QueryProviderException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public QueryProviderException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package matrix.nimble.query;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
public final class QueryProviders {
|
||||
|
||||
private static final AtomicReference<QueryProvider> CURRENT = new AtomicReference<>();
|
||||
|
||||
private QueryProviders() {
|
||||
}
|
||||
|
||||
public static QueryProvider current() {
|
||||
QueryProvider provider = CURRENT.get();
|
||||
if (provider == null) {
|
||||
throw new QueryProviderException("QueryProvider has not been initialized");
|
||||
}
|
||||
return provider;
|
||||
}
|
||||
|
||||
public static void install(QueryProvider provider) {
|
||||
CURRENT.set(java.util.Objects.requireNonNull(provider));
|
||||
}
|
||||
|
||||
static void clear(QueryProvider provider) {
|
||||
CURRENT.compareAndSet(provider, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package matrix.nimble.query;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface QuerySource {
|
||||
|
||||
String fetch(int queryId);
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package matrix.nimble.query;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Optional;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public final class RedisCachingQueryProvider implements QueryProvider {
|
||||
|
||||
public static final Duration QUERY_TTL = Duration.ofHours(12);
|
||||
private static final Logger LOGGER = Logger.getLogger(RedisCachingQueryProvider.class.getName());
|
||||
private static final int LOCK_COUNT = 64;
|
||||
|
||||
private final EncryptedQueryCache cache;
|
||||
private final QuerySource cloudSource;
|
||||
private final QueryCipher cipher;
|
||||
private final Object[] locks = new Object[LOCK_COUNT];
|
||||
|
||||
public RedisCachingQueryProvider(
|
||||
EncryptedQueryCache cache,
|
||||
QuerySource cloudSource,
|
||||
QueryCipher cipher) {
|
||||
this.cache = cache;
|
||||
this.cloudSource = cloudSource;
|
||||
this.cipher = cipher;
|
||||
for (int index = 0; index < locks.length; index++) {
|
||||
locks[index] = new Object();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getQuery(int queryId) {
|
||||
String cacheKey = cacheKey(queryId);
|
||||
Optional<String> cached = cached(cacheKey);
|
||||
if (cached.isPresent()) {
|
||||
return cached.get();
|
||||
}
|
||||
|
||||
synchronized (lock(cacheKey)) {
|
||||
cached = cached(cacheKey);
|
||||
if (cached.isPresent()) {
|
||||
return cached.get();
|
||||
}
|
||||
try {
|
||||
String query = cloudSource.fetch(queryId);
|
||||
cache.put(cacheKey, cipher.encrypt(cacheKey, query), QUERY_TTL);
|
||||
return query;
|
||||
} catch (QueryProviderException exception) {
|
||||
throw exception;
|
||||
} catch (RuntimeException exception) {
|
||||
throw new QueryProviderException(
|
||||
"Unable to retrieve query: " + queryId, exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<String> cached(String queryId) {
|
||||
return cache.get(queryId).flatMap(encrypted -> {
|
||||
try {
|
||||
return Optional.of(cipher.decrypt(queryId, encrypted));
|
||||
} catch (RuntimeException exception) {
|
||||
LOGGER.log(Level.WARNING,
|
||||
"Discarding an invalid encrypted query cache entry: {0}", queryId);
|
||||
cache.evict(queryId);
|
||||
return Optional.empty();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Object lock(String queryId) {
|
||||
return locks[(queryId.hashCode() & Integer.MAX_VALUE) % locks.length];
|
||||
}
|
||||
|
||||
private String cacheKey(int queryId) {
|
||||
if (queryId <= 0) {
|
||||
throw new IllegalArgumentException("Invalid query ID");
|
||||
}
|
||||
return Integer.toString(queryId);
|
||||
}
|
||||
}
|
||||
@@ -4,15 +4,9 @@ import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import matrix.nimble.query.QueryProviders;
|
||||
|
||||
public class FileFunctions {
|
||||
private static final Object QUERY_CACHE_LOCK = new Object();
|
||||
private static volatile Map<Integer, String> queryCache = Collections.emptyMap();
|
||||
private static volatile long queryCacheLastModified = Long.MIN_VALUE;
|
||||
private static volatile String queryCachePath = "";
|
||||
private String ConnString;
|
||||
private String DBDriver;
|
||||
private String DBUser;
|
||||
@@ -207,53 +201,6 @@ public class FileFunctions {
|
||||
}
|
||||
}
|
||||
public String GetQuery(int QueryIndex) throws IOException {
|
||||
String realPath = (FileFunctions.class
|
||||
.getResource("FileFunctions.class")).toString()
|
||||
.replace("utilities/FileFunctions.class", "conf/")
|
||||
.replace("file:/", "");
|
||||
if(!isWindows())
|
||||
{
|
||||
realPath="/"+realPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
realPath=realPath.replace("%20", " ");
|
||||
}
|
||||
File queryFile = new File(realPath + "nimble.qry");
|
||||
refreshQueryCacheIfRequired(queryFile);
|
||||
return queryCache.get(QueryIndex);
|
||||
}
|
||||
|
||||
private static void refreshQueryCacheIfRequired(File queryFile) throws IOException {
|
||||
String absolutePath = queryFile.getAbsolutePath();
|
||||
long lastModified = queryFile.lastModified();
|
||||
if (absolutePath.equals(queryCachePath) && lastModified == queryCacheLastModified) {
|
||||
return;
|
||||
}
|
||||
|
||||
synchronized (QUERY_CACHE_LOCK) {
|
||||
if (absolutePath.equals(queryCachePath) && lastModified == queryCacheLastModified) {
|
||||
return;
|
||||
}
|
||||
Map<Integer, String> loadedQueries = new HashMap<>();
|
||||
try (BufferedReader reader = new BufferedReader(new FileReader(queryFile))) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
int delimiterIndex = line.indexOf(GlobalClass.ColDelim);
|
||||
if (delimiterIndex <= 5 || !line.startsWith("Query")) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
int queryIndex = Integer.parseInt(line.substring(5, delimiterIndex));
|
||||
loadedQueries.put(queryIndex, line.substring(delimiterIndex + GlobalClass.ColDelim.length()));
|
||||
} catch (NumberFormatException ignored) {
|
||||
// Ignore malformed/non-query lines, matching the legacy lookup behavior.
|
||||
}
|
||||
}
|
||||
}
|
||||
queryCache = Collections.unmodifiableMap(loadedQueries);
|
||||
queryCachePath = absolutePath;
|
||||
queryCacheLastModified = lastModified;
|
||||
}
|
||||
return QueryProviders.current().getQuery(QueryIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,17 @@
|
||||
package matrix.nimble;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import matrix.nimble.utilities.FileFunctions;
|
||||
import matrix.nimble.query.QueryProviders;
|
||||
|
||||
class FileFunctionsQueryCacheTest {
|
||||
@Test
|
||||
void loadsQueriesFromNimbleQueryFileAndHandlesMissingCodes() throws Exception {
|
||||
void delegatesLegacyQueryLookupToQueryProvider() throws Exception {
|
||||
QueryProviders.install(queryId -> "provided:" + queryId);
|
||||
FileFunctions files = new FileFunctions("TEST");
|
||||
|
||||
String query = files.GetQuery(3);
|
||||
|
||||
assertTrue(query.startsWith("select!C0L!select portfolio_id"));
|
||||
assertNull(files.GetQuery(1));
|
||||
assertNull(files.GetQuery(Integer.MAX_VALUE));
|
||||
assertEquals("provided:3", files.GetQuery(3));
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user