diff --git a/cygnus-cloud-client/target/cygnus-cloud-client-1.0.0-SNAPSHOT.jar b/cygnus-cloud-client/target/cygnus-cloud-client-1.0.0-SNAPSHOT.jar index 78e52c2..93a6c8c 100644 Binary files a/cygnus-cloud-client/target/cygnus-cloud-client-1.0.0-SNAPSHOT.jar and b/cygnus-cloud-client/target/cygnus-cloud-client-1.0.0-SNAPSHOT.jar differ diff --git a/cygnus-cloud-client/target/surefire-reports/TEST-com.cygnus.client.CloudClientPropertiesTest.xml b/cygnus-cloud-client/target/surefire-reports/TEST-com.cygnus.client.CloudClientPropertiesTest.xml index 42cfb3d..9dc0f3b 100644 --- a/cygnus-cloud-client/target/surefire-reports/TEST-com.cygnus.client.CloudClientPropertiesTest.xml +++ b/cygnus-cloud-client/target/surefire-reports/TEST-com.cygnus.client.CloudClientPropertiesTest.xml @@ -12,10 +12,9 @@ - + - @@ -30,7 +29,7 @@ - + @@ -59,6 +58,6 @@ - + \ No newline at end of file diff --git a/cygnus-cloud-service/pom.xml b/cygnus-cloud-service/pom.xml index 862adb2..8d7cace 100644 --- a/cygnus-cloud-service/pom.xml +++ b/cygnus-cloud-service/pom.xml @@ -41,6 +41,10 @@ org.springframework.boot spring-boot-starter-validation + + org.springframework.boot + spring-boot-starter-mail + org.springframework.boot spring-boot-starter-security diff --git a/cygnus-cloud-service/src/main/java/com/cygnus/cloud/cache/ReactiveCacheService.java b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/cache/ReactiveCacheService.java index 8a779bd..7ac67a0 100644 --- a/cygnus-cloud-service/src/main/java/com/cygnus/cloud/cache/ReactiveCacheService.java +++ b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/cache/ReactiveCacheService.java @@ -38,6 +38,15 @@ public class ReactiveCacheService { return redis.delete(cacheKey(namespace, key)).map(deleted -> deleted > 0); } + public Mono increment(String namespace, String key, Duration ttl) { + String fullKey = cacheKey(namespace, key); + return redis.opsForValue() + .increment(fullKey) + .flatMap(count -> count == 1 + ? redis.expire(fullKey, ttl).thenReturn(count) + : Mono.just(count)); + } + private String cacheKey(String namespace, String key) { return properties.keyPrefix() + ':' + namespace + ':' + key; } diff --git a/cygnus-cloud-service/src/main/java/com/cygnus/cloud/database/ReactiveDatabaseClient.java b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/database/ReactiveDatabaseClient.java index 48a6823..40e7a4e 100644 --- a/cygnus-cloud-service/src/main/java/com/cygnus/cloud/database/ReactiveDatabaseClient.java +++ b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/database/ReactiveDatabaseClient.java @@ -3,7 +3,9 @@ package com.cygnus.cloud.database; import io.vertx.sqlclient.Pool; import io.vertx.sqlclient.Row; import io.vertx.sqlclient.RowSet; +import io.vertx.sqlclient.SqlConnection; import io.vertx.sqlclient.Tuple; +import java.util.function.Function; import org.springframework.stereotype.Service; import reactor.core.publisher.Mono; @@ -29,4 +31,37 @@ public class ReactiveDatabaseClient { return Mono.fromCompletionStage( () -> pool.preparedQuery(sql).execute(parameters).toCompletionStage()); } + + public Mono preparedUpdate(String sql, Tuple parameters) { + return preparedQuery(sql, parameters).map(RowSet::rowCount); + } + + public Mono inTransaction(Function> work) { + return Mono.usingWhen( + Mono.fromCompletionStage(() -> pool.getConnection().toCompletionStage()), + connection -> Mono.fromCompletionStage( + () -> connection.begin().toCompletionStage()) + .flatMap(transaction -> work.apply(connection) + .flatMap(result -> Mono.fromCompletionStage( + () -> transaction.commit().toCompletionStage()) + .thenReturn(result)) + .onErrorResume(error -> Mono.fromCompletionStage( + () -> transaction.rollback().toCompletionStage()) + .onErrorResume(ignored -> Mono.empty()) + .then(Mono.error(error)))), + connection -> Mono.fromCompletionStage( + () -> connection.close().toCompletionStage()), + (connection, error) -> Mono.fromCompletionStage( + () -> connection.close().toCompletionStage()), + connection -> Mono.fromCompletionStage( + () -> connection.close().toCompletionStage())); + } + + public Mono> preparedQuery( + SqlConnection connection, String sql, Tuple parameters) { + return Mono.fromCompletionStage( + () -> connection.preparedQuery(sql) + .execute(parameters) + .toCompletionStage()); + } } diff --git a/cygnus-cloud-service/src/main/java/com/cygnus/cloud/security/ClientAssertionValidator.java b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/security/ClientAssertionValidator.java index 5f0b9ff..89d2e66 100644 --- a/cygnus-cloud-service/src/main/java/com/cygnus/cloud/security/ClientAssertionValidator.java +++ b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/security/ClientAssertionValidator.java @@ -57,7 +57,11 @@ class ClientAssertionValidator { .switchIfEmpty(Mono.error(new MachineAuthenticationException( "Client license is not active"))) .map(license -> verify( - clientId, signedJwt, claims, installation, license))) + clientId, signedJwt, claims, installation, license)) + .flatMap(principal -> registrations + .touchInstallation( + installation.installationId(), clock.instant()) + .thenReturn(principal))) .onErrorMap( exception -> !(exception instanceof MachineAuthenticationException), exception -> new MachineAuthenticationException( diff --git a/cygnus-cloud-service/src/main/java/com/cygnus/cloud/security/CloudSecurityConfiguration.java b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/security/CloudSecurityConfiguration.java index 45d2fc0..382e350 100644 --- a/cygnus-cloud-service/src/main/java/com/cygnus/cloud/security/CloudSecurityConfiguration.java +++ b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/security/CloudSecurityConfiguration.java @@ -30,8 +30,14 @@ public class CloudSecurityConfiguration { .authorizeExchange(exchange -> exchange .pathMatchers("/actuator/health", "/actuator/info").permitAll() .pathMatchers("/oauth2/token").permitAll() + .pathMatchers( + "/api/v1/installations/activation/validate", + "/api/v1/installations/register") + .permitAll() .pathMatchers("/api/v1/identity/login") .hasAuthority("SCOPE_identity.login") + .pathMatchers("/api/v1/admin/**") + .hasAuthority("SCOPE_cygnus.admin") .anyExchange().authenticated()) .oauth2ResourceServer(resourceServer -> resourceServer.jwt(withDefaults())) .build(); diff --git a/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/api/ActivationValidationRequest.java b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/api/ActivationValidationRequest.java new file mode 100644 index 0000000..8b6735c --- /dev/null +++ b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/api/ActivationValidationRequest.java @@ -0,0 +1,16 @@ +package com.cygnus.cloud.tenant.api; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; +import java.util.UUID; + +public record ActivationValidationRequest( + @NotBlank @Size(max = 40) String clientCode, + @NotBlank @Size(max = 80) String licenseKey, + @NotNull UUID installationUuid, + @NotBlank @Size(max = 40) + @Pattern(regexp = "^[A-Za-z0-9._-]+$") + String installerVersion) { +} diff --git a/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/api/ActivationValidationResponse.java b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/api/ActivationValidationResponse.java new file mode 100644 index 0000000..b28d2fd --- /dev/null +++ b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/api/ActivationValidationResponse.java @@ -0,0 +1,13 @@ +package com.cygnus.cloud.tenant.api; + +import java.time.OffsetDateTime; +import java.util.UUID; + +public record ActivationValidationResponse( + String activationToken, + OffsetDateTime expiresAt, + UUID tenantId, + String tenantSlug, + String packageCode, + int maximumInstallations) { +} diff --git a/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/api/ClientAdministrationController.java b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/api/ClientAdministrationController.java new file mode 100644 index 0000000..2ffc977 --- /dev/null +++ b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/api/ClientAdministrationController.java @@ -0,0 +1,174 @@ +package com.cygnus.cloud.tenant.api; + +import com.cygnus.cloud.tenant.repository.ClientAdministrationRepository; +import com.cygnus.cloud.tenant.service.LicenseKeyService; +import com.cygnus.cloud.tenant.service.InstallationLifecycleService; +import com.cygnus.cloud.tenant.service.RegistrationEmailService; +import io.vertx.sqlclient.Tuple; +import jakarta.validation.Valid; +import java.util.Map; +import java.util.UUID; +import org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import reactor.core.publisher.Mono; + +@RestController +@RequestMapping("/api/v1/admin") +public class ClientAdministrationController { + + private final ClientAdministrationRepository repository; + private final LicenseKeyService licenseKeyService; + private final RegistrationEmailService emailService; + private final InstallationLifecycleService installationLifecycleService; + + public ClientAdministrationController( + ClientAdministrationRepository repository, + LicenseKeyService licenseKeyService, + RegistrationEmailService emailService, + InstallationLifecycleService installationLifecycleService) { + this.repository = repository; + this.licenseKeyService = licenseKeyService; + this.emailService = emailService; + this.installationLifecycleService = installationLifecycleService; + } + + @PostMapping("/tenants/{tenantId}/installations/{installationId}/decommission") + public Mono decommission( + @PathVariable UUID tenantId, + @PathVariable UUID installationId, + @Valid @RequestBody RetireInstallationRequest request, + Authentication authentication) { + return installationLifecycleService.retire( + tenantId, + installationId, + "DECOMMISSIONED", + actor(authentication), + request.reason()); + } + + @PostMapping("/tenants/{tenantId}/installations/{installationId}/revoke") + public Mono revoke( + @PathVariable UUID tenantId, + @PathVariable UUID installationId, + @Valid @RequestBody RetireInstallationRequest request, + Authentication authentication) { + return installationLifecycleService.retire( + tenantId, + installationId, + "REVOKED", + actor(authentication), + request.reason()); + } + + @PostMapping("/client-registrations") + public Mono> registration( + @Valid @RequestBody CreateClientRegistrationRequest request, + Authentication authentication) { + UUID id = UUID.randomUUID(); + Tuple values = Tuple.tuple() + .addUUID(id) + .addString(request.clientCode()) + .addString(request.legalCompanyName()) + .addString(request.tradeName()) + .addString(request.pan()) + .addString(request.cin()) + .addString(request.gstNumber()) + .addString(request.billingAddressLine1()) + .addString(request.billingAddressLine2()) + .addString(request.billingCity()) + .addString(request.billingState()) + .addString(request.billingPostalCode()) + .addString(request.billingCountry()) + .addString(request.billingEmail()) + .addString(request.primaryContactName()) + .addString(request.primaryContactEmail()) + .addString(request.primaryContactNumber()) + .addString(request.alternateContactName()) + .addString(request.alternateContactEmail()) + .addString(request.alternateContactNumber()) + .addLocalDate(request.contractStartDate()) + .addLocalDate(request.contractEndDate()) + .addString("ACTIVE") + .addString(actor(authentication)); + return repository.createRegistration(values) + .flatMap(created -> created + ? Mono.just(Map.of("registrationId", id)) + : Mono.error(new IllegalStateException( + "Registration was not created"))); + } + + @PostMapping("/client-registrations/{registrationId}/tenants") + public Mono> tenant( + @PathVariable UUID registrationId, + @Valid @RequestBody CreateTenantRequest request) { + UUID id = UUID.randomUUID(); + return repository.createTenant( + id, registrationId, request.clientSlug(), request.clientName()) + .flatMap(created -> created + ? Mono.just(Map.of("tenantId", id)) + : Mono.error(new IllegalStateException("Tenant was not created"))); + } + + @PostMapping("/tenants/{tenantId}/licenses") + public Mono> license( + @PathVariable UUID tenantId, + @Valid @RequestBody CreateLicenseRequest request) { + if (!request.validUntil().isAfter(request.validFrom())) { + return Mono.error(new IllegalArgumentException( + "License end must be after start")); + } + UUID id = UUID.randomUUID(); + return repository.createLicense( + id, + tenantId, + request.licenseType(), + request.packageCode(), + request.validFrom(), + request.validUntil(), + request.maximumUsers(), + request.maximumInstallations()) + .flatMap(created -> created + ? Mono.just(Map.of("licenseId", id)) + : Mono.error(new IllegalStateException("License was not created"))); + } + + @PostMapping("/tenants/{tenantId}/licenses/{licenseId}/activation-key") + public Mono> activationKey( + @PathVariable UUID tenantId, + @PathVariable UUID licenseId, + @Valid @RequestBody IssueLicenseKeyRequest request, + Authentication authentication) { + return licenseKeyService.issue( + tenantId, licenseId, request.expiresAt(), actor(authentication)) + .flatMap(issued -> repository + .findLicenseDeliveryDetails(tenantId, licenseId) + .switchIfEmpty(Mono.error( + new IllegalArgumentException("Tenant or license not found"))) + .flatMap(details -> emailService.sendLicense( + request.recipientEmail(), + details.clientCode(), + details.tenantSlug(), + details.packageCode(), + details.maximumInstallations(), + request.expiresAt(), + issued) + .onErrorResume(error -> licenseKeyService + .revoke(issued.activationKeyId()) + .then(Mono.error(error))) + .thenReturn(Map.of( + "activationKeyId", + issued.activationKeyId().toString(), + "keyHint", + issued.keyHint(), + "delivery", + "EMAIL_SENT")))); + } + + private String actor(Authentication authentication) { + return authentication == null ? "system" : authentication.getName(); + } +} diff --git a/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/api/CreateClientRegistrationRequest.java b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/api/CreateClientRegistrationRequest.java new file mode 100644 index 0000000..3c8acbe --- /dev/null +++ b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/api/CreateClientRegistrationRequest.java @@ -0,0 +1,33 @@ +package com.cygnus.cloud.tenant.api; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; +import java.time.LocalDate; + +public record CreateClientRegistrationRequest( + @NotBlank @Pattern(regexp = "^[A-Z0-9]+(?:-[A-Z0-9]+)*$") + @Size(max = 40) String clientCode, + @NotBlank @Size(max = 240) String legalCompanyName, + @Size(max = 240) String tradeName, + @Size(max = 20) String pan, + @Size(max = 30) String cin, + @Size(max = 30) String gstNumber, + @Size(max = 300) String billingAddressLine1, + @Size(max = 300) String billingAddressLine2, + @Size(max = 120) String billingCity, + @Size(max = 120) String billingState, + @Size(max = 20) String billingPostalCode, + @NotBlank @Pattern(regexp = "^[A-Z]{2}$") String billingCountry, + @Email @Size(max = 254) String billingEmail, + @Size(max = 160) String primaryContactName, + @Email @Size(max = 254) String primaryContactEmail, + @Size(max = 30) String primaryContactNumber, + @Size(max = 160) String alternateContactName, + @Email @Size(max = 254) String alternateContactEmail, + @Size(max = 30) String alternateContactNumber, + @NotNull LocalDate contractStartDate, + LocalDate contractEndDate) { +} diff --git a/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/api/CreateLicenseRequest.java b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/api/CreateLicenseRequest.java new file mode 100644 index 0000000..5a4d95d --- /dev/null +++ b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/api/CreateLicenseRequest.java @@ -0,0 +1,17 @@ +package com.cygnus.cloud.tenant.api; + +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import java.time.OffsetDateTime; + +public record CreateLicenseRequest( + @NotBlank @Size(max = 30) String licenseType, + @NotBlank @Size(max = 50) String packageCode, + @NotNull OffsetDateTime validFrom, + @NotNull OffsetDateTime validUntil, + @Min(1) Integer maximumUsers, + @Min(1) @Max(100) int maximumInstallations) { +} diff --git a/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/api/CreateTenantRequest.java b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/api/CreateTenantRequest.java new file mode 100644 index 0000000..e84a794 --- /dev/null +++ b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/api/CreateTenantRequest.java @@ -0,0 +1,11 @@ +package com.cygnus.cloud.tenant.api; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; + +public record CreateTenantRequest( + @NotBlank @Pattern(regexp = "^[a-z0-9]+(?:-[a-z0-9]+)*$") + @Size(max = 80) String clientSlug, + @NotBlank @Size(max = 200) String clientName) { +} diff --git a/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/api/InstallationActivationController.java b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/api/InstallationActivationController.java new file mode 100644 index 0000000..487af09 --- /dev/null +++ b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/api/InstallationActivationController.java @@ -0,0 +1,66 @@ +package com.cygnus.cloud.tenant.api; + +import com.cygnus.cloud.tenant.model.RegisteredInstallation; +import com.cygnus.cloud.tenant.service.InstallationActivationService; +import com.cygnus.cloud.tenant.service.ActivationRateLimiter; +import jakarta.validation.Valid; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import reactor.core.publisher.Mono; + +@RestController +@RequestMapping("/api/v1/installations") +public class InstallationActivationController { + + private final InstallationActivationService activationService; + private final ActivationRateLimiter rateLimiter; + + public InstallationActivationController( + InstallationActivationService activationService, + ActivationRateLimiter rateLimiter) { + this.activationService = activationService; + this.rateLimiter = rateLimiter; + } + + @PostMapping("/activation/validate") + public Mono validate( + @Valid @RequestBody ActivationValidationRequest request, + ServerHttpRequest serverRequest) { + String sourceIp = remoteAddress(serverRequest); + return rateLimiter.check(sourceIp, request.clientCode()) + .then(activationService.validateAndCreateSession( + request.clientCode(), + request.licenseKey(), + request.installationUuid(), + sourceIp, + request.installerVersion())) + .map(session -> new ActivationValidationResponse( + session.token(), + session.expiresAt(), + session.tenantId(), + session.clientSlug(), + session.packageCode(), + session.maxInstallations())); + } + + @PostMapping("/register") + public Mono register( + @Valid @RequestBody InstallationRegistrationRequest request) { + return activationService.register( + request.activationToken(), + request.installationCode(), + request.installationName(), + request.assertionPublicKey(), + request.softwareVersion(), + request.environment()); + } + + private String remoteAddress(ServerHttpRequest request) { + return request.getRemoteAddress() == null + ? null + : request.getRemoteAddress().getAddress().getHostAddress(); + } +} diff --git a/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/api/InstallationActivationErrorHandler.java b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/api/InstallationActivationErrorHandler.java new file mode 100644 index 0000000..86c448e --- /dev/null +++ b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/api/InstallationActivationErrorHandler.java @@ -0,0 +1,30 @@ +package com.cygnus.cloud.tenant.api; + +import com.cygnus.cloud.tenant.service.InstallationActivationException; +import com.cygnus.cloud.tenant.service.LicenseKeyException; +import com.cygnus.cloud.tenant.service.ActivationRateLimitException; +import java.util.Map; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@RestControllerAdvice +public class InstallationActivationErrorHandler { + + @ExceptionHandler({LicenseKeyException.class, InstallationActivationException.class}) + @ResponseStatus(HttpStatus.BAD_REQUEST) + Map activationFailure() { + return Map.of( + "code", "INSTALLATION_ACTIVATION_FAILED", + "message", "Installation activation could not be completed"); + } + + @ExceptionHandler(ActivationRateLimitException.class) + @ResponseStatus(HttpStatus.TOO_MANY_REQUESTS) + Map rateLimited() { + return Map.of( + "code", "INSTALLATION_ACTIVATION_RATE_LIMITED", + "message", "Too many activation attempts; retry later"); + } +} diff --git a/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/api/InstallationRegistrationRequest.java b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/api/InstallationRegistrationRequest.java new file mode 100644 index 0000000..8a0cf33 --- /dev/null +++ b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/api/InstallationRegistrationRequest.java @@ -0,0 +1,20 @@ +package com.cygnus.cloud.tenant.api; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; + +public record InstallationRegistrationRequest( + @NotBlank @Size(max = 100) String activationToken, + @NotBlank @Size(max = 100) + @Pattern(regexp = "^[A-Za-z0-9][A-Za-z0-9_-]*$") + String installationCode, + @NotBlank @Size(max = 160) String installationName, + @NotBlank @Size(max = 8192) String assertionPublicKey, + @NotBlank @Size(max = 40) + @Pattern(regexp = "^[A-Za-z0-9._-]+$") + String softwareVersion, + @NotBlank @Size(max = 30) + @Pattern(regexp = "^[A-Za-z0-9_-]+$") + String environment) { +} diff --git a/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/api/IssueLicenseKeyRequest.java b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/api/IssueLicenseKeyRequest.java new file mode 100644 index 0000000..2c02573 --- /dev/null +++ b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/api/IssueLicenseKeyRequest.java @@ -0,0 +1,11 @@ +package com.cygnus.cloud.tenant.api; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import java.time.OffsetDateTime; + +public record IssueLicenseKeyRequest( + @NotNull OffsetDateTime expiresAt, + @NotBlank @Email String recipientEmail) { +} diff --git a/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/api/RetireInstallationRequest.java b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/api/RetireInstallationRequest.java new file mode 100644 index 0000000..d32fd4b --- /dev/null +++ b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/api/RetireInstallationRequest.java @@ -0,0 +1,7 @@ +package com.cygnus.cloud.tenant.api; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +public record RetireInstallationRequest( + @NotBlank @Size(max = 500) String reason) {} diff --git a/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/model/ActivationKeyStatus.java b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/model/ActivationKeyStatus.java new file mode 100644 index 0000000..7e9702e --- /dev/null +++ b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/model/ActivationKeyStatus.java @@ -0,0 +1,8 @@ +package com.cygnus.cloud.tenant.model; + +public enum ActivationKeyStatus { + ACTIVE, + LOCKED, + REVOKED, + EXPIRED +} diff --git a/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/model/ActivationSession.java b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/model/ActivationSession.java new file mode 100644 index 0000000..49e5e68 --- /dev/null +++ b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/model/ActivationSession.java @@ -0,0 +1,18 @@ +package com.cygnus.cloud.tenant.model; + +import java.time.OffsetDateTime; +import java.util.UUID; + +public record ActivationSession( + UUID activationSessionId, + UUID registrationId, + UUID tenantId, + UUID licenseId, + UUID installationUuid, + String token, + OffsetDateTime expiresAt, + String clientCode, + String clientSlug, + String packageCode, + int maxInstallations) { +} diff --git a/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/model/ClientRegistration.java b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/model/ClientRegistration.java new file mode 100644 index 0000000..57bedb8 --- /dev/null +++ b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/model/ClientRegistration.java @@ -0,0 +1,19 @@ +package com.cygnus.cloud.tenant.model; + +import java.time.LocalDate; +import java.time.OffsetDateTime; +import java.util.UUID; + +public record ClientRegistration( + UUID registrationId, + String clientCode, + String legalCompanyName, + String tradeName, + String billingEmail, + LocalDate contractStartDate, + LocalDate contractEndDate, + RegistrationStatus status, + OffsetDateTime createdAt, + OffsetDateTime updatedAt, + int version) { +} diff --git a/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/model/InstallationLifecycleResult.java b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/model/InstallationLifecycleResult.java new file mode 100644 index 0000000..8214fea --- /dev/null +++ b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/model/InstallationLifecycleResult.java @@ -0,0 +1,10 @@ +package com.cygnus.cloud.tenant.model; + +import java.util.UUID; + +public record InstallationLifecycleResult( + UUID installationId, + UUID tenantId, + String clientId, + String installationCode, + String status) {} diff --git a/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/model/IssuedLicenseKey.java b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/model/IssuedLicenseKey.java new file mode 100644 index 0000000..3c234eb --- /dev/null +++ b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/model/IssuedLicenseKey.java @@ -0,0 +1,9 @@ +package com.cygnus.cloud.tenant.model; + +import java.util.UUID; + +public record IssuedLicenseKey( + UUID activationKeyId, + String licenseKey, + String keyHint) { +} diff --git a/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/model/LicenseActivationContext.java b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/model/LicenseActivationContext.java new file mode 100644 index 0000000..ce34b61 --- /dev/null +++ b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/model/LicenseActivationContext.java @@ -0,0 +1,24 @@ +package com.cygnus.cloud.tenant.model; + +import java.time.Instant; +import java.util.UUID; + +public record LicenseActivationContext( + LicenseActivationKey activationKey, + String clientCode, + RegistrationStatus registrationStatus, + ClientStatus tenantStatus, + String clientSlug, + String clientName, + String packageCode, + String licenseType, + LicenseStatus licenseStatus, + Instant validFrom, + Instant validUntil, + int maxInstallations, + int consumingInstallations) { + + public boolean hasCapacity() { + return consumingInstallations < maxInstallations; + } +} diff --git a/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/model/LicenseActivationKey.java b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/model/LicenseActivationKey.java new file mode 100644 index 0000000..f53fecd --- /dev/null +++ b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/model/LicenseActivationKey.java @@ -0,0 +1,32 @@ +package com.cygnus.cloud.tenant.model; + +import java.time.OffsetDateTime; +import java.util.UUID; + +public record LicenseActivationKey( + UUID activationKeyId, + UUID registrationId, + UUID tenantId, + UUID licenseId, + String keyHash, + String keyHint, + ActivationKeyStatus status, + OffsetDateTime expiresAt, + int failedAttempts, + int maximumAttempts, + OffsetDateTime lockedUntil, + OffsetDateTime createdAt, + OffsetDateTime lastUsedAt) { + + public boolean canAttemptAt(OffsetDateTime now) { + if (expiresAt != null && !expiresAt.isAfter(now)) { + return false; + } + if (status == ActivationKeyStatus.ACTIVE) { + return failedAttempts < maximumAttempts; + } + return status == ActivationKeyStatus.LOCKED + && lockedUntil != null + && !lockedUntil.isAfter(now); + } +} diff --git a/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/model/LicenseDeliveryDetails.java b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/model/LicenseDeliveryDetails.java new file mode 100644 index 0000000..d6fd91c --- /dev/null +++ b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/model/LicenseDeliveryDetails.java @@ -0,0 +1,7 @@ +package com.cygnus.cloud.tenant.model; + +public record LicenseDeliveryDetails( + String clientCode, + String tenantSlug, + String packageCode, + int maximumInstallations) {} diff --git a/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/model/RegisteredInstallation.java b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/model/RegisteredInstallation.java new file mode 100644 index 0000000..741cd31 --- /dev/null +++ b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/model/RegisteredInstallation.java @@ -0,0 +1,12 @@ +package com.cygnus.cloud.tenant.model; + +import java.util.UUID; + +public record RegisteredInstallation( + UUID installationId, + UUID installationUuid, + String clientId, + String installationCode, + int securityVersion, + String status) { +} diff --git a/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/model/RegistrationStatus.java b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/model/RegistrationStatus.java new file mode 100644 index 0000000..09c37cf --- /dev/null +++ b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/model/RegistrationStatus.java @@ -0,0 +1,8 @@ +package com.cygnus.cloud.tenant.model; + +public enum RegistrationStatus { + DRAFT, + ACTIVE, + SUSPENDED, + TERMINATED +} diff --git a/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/repository/ClientAdministrationRepository.java b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/repository/ClientAdministrationRepository.java new file mode 100644 index 0000000..b922213 --- /dev/null +++ b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/repository/ClientAdministrationRepository.java @@ -0,0 +1,124 @@ +package com.cygnus.cloud.tenant.repository; + +import com.cygnus.cloud.database.ReactiveDatabaseClient; +import com.cygnus.cloud.tenant.model.LicenseDeliveryDetails; +import io.vertx.sqlclient.Tuple; +import java.time.OffsetDateTime; +import java.util.UUID; +import org.springframework.stereotype.Repository; +import reactor.core.publisher.Mono; + +@Repository +public class ClientAdministrationRepository { + + private static final String INSERT_REGISTRATION = """ + INSERT INTO identity.client_registration_details ( + registration_id, client_code, legal_company_name, trade_name, + pan, cin, gst_number, billing_address_line1, + billing_address_line2, billing_city, billing_state, + billing_postal_code, billing_country, billing_email, + primary_contact_name, primary_contact_email, + primary_contact_number, alternate_contact_name, + alternate_contact_email, alternate_contact_number, + contract_start_date, contract_end_date, status, created_by) + VALUES ($1, upper($2), $3, $4, $5, $6, $7, $8, $9, $10, $11, + $12, upper($13), $14, $15, $16, $17, $18, $19, $20, + $21, $22, $23, $24) + """; + + private static final String INSERT_TENANT = """ + INSERT INTO identity.client_account ( + tenant_id, registration_id, client_slug, client_name, + status, security_version) + VALUES ($1, $2, $3, $4, 'ACTIVE', 1) + """; + + private static final String INSERT_LICENSE = """ + INSERT INTO identity.client_license ( + license_id, tenant_id, license_type, package_code, + valid_from, valid_until, status, max_users, + max_installations) + SELECT $1, account.tenant_id, $3, $4, $5, $6, 'ACTIVE', $7, $8 + FROM identity.client_account account + WHERE account.tenant_id = $2 + AND account.status = 'ACTIVE' + """; + + private static final String FIND_LICENSE_DELIVERY_DETAILS = """ + SELECT registration.client_code, + account.client_slug, + license.package_code, + license.max_installations + FROM identity.client_license license + JOIN identity.client_account account + ON account.tenant_id = license.tenant_id + JOIN identity.client_registration_details registration + ON registration.registration_id = account.registration_id + WHERE license.tenant_id = $1 + AND license.license_id = $2 + """; + + private final ReactiveDatabaseClient database; + + public ClientAdministrationRepository(ReactiveDatabaseClient database) { + this.database = database; + } + + public Mono createRegistration(Tuple values) { + return database.preparedUpdate(INSERT_REGISTRATION, values) + .map(count -> count == 1); + } + + public Mono createTenant( + UUID tenantId, + UUID registrationId, + String slug, + String name) { + return database.preparedUpdate( + INSERT_TENANT, + Tuple.of(tenantId, registrationId, slug, name)) + .map(count -> count == 1); + } + + public Mono createLicense( + UUID licenseId, + UUID tenantId, + String licenseType, + String packageCode, + OffsetDateTime validFrom, + OffsetDateTime validUntil, + Integer maxUsers, + int maxInstallations) { + return database.preparedUpdate( + INSERT_LICENSE, + Tuple.of( + licenseId, + tenantId, + licenseType, + packageCode, + validFrom, + validUntil, + maxUsers, + maxInstallations)) + .map(count -> count == 1); + } + + public Mono findLicenseDeliveryDetails( + UUID tenantId, UUID licenseId) { + return database.preparedQuery( + FIND_LICENSE_DELIVERY_DETAILS, + Tuple.of(tenantId, licenseId)) + .flatMap(rows -> { + var iterator = rows.iterator(); + if (!iterator.hasNext()) { + return Mono.empty(); + } + var row = iterator.next(); + return Mono.just(new LicenseDeliveryDetails( + row.getString("client_code"), + row.getString("client_slug"), + row.getString("package_code"), + row.getInteger("max_installations"))); + }); + } +} diff --git a/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/repository/InstallationActivationRepository.java b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/repository/InstallationActivationRepository.java new file mode 100644 index 0000000..4bd5252 --- /dev/null +++ b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/repository/InstallationActivationRepository.java @@ -0,0 +1,273 @@ +package com.cygnus.cloud.tenant.repository; + +import com.cygnus.cloud.database.ReactiveDatabaseClient; +import com.cygnus.cloud.tenant.model.ActivationSession; +import com.cygnus.cloud.tenant.model.RegisteredInstallation; +import io.vertx.sqlclient.Row; +import io.vertx.sqlclient.SqlConnection; +import io.vertx.sqlclient.Tuple; +import java.time.OffsetDateTime; +import java.util.Set; +import java.util.UUID; +import org.springframework.stereotype.Repository; +import reactor.core.publisher.Mono; + +@Repository +public class InstallationActivationRepository { + + private static final String INSERT_SESSION = """ + INSERT INTO identity.installation_activation_session ( + activation_session_id, activation_key_id, registration_id, + tenant_id, license_id, installation_uuid, token_hash, + status, expires_at, source_ip, installer_version) + VALUES ($1, $2, $3, $4, $5, $6, $7, 'PENDING', $8, $9::inet, $10) + """; + + private static final String LOCK_SESSION_AND_LICENSE = """ + SELECT session.activation_session_id, session.registration_id, + session.tenant_id, session.license_id, + session.installation_uuid, session.status AS session_status, + session.expires_at, + registration.client_code, registration.status AS registration_status, + account.client_slug, account.status AS tenant_status, + license.package_code, license.status AS license_status, + license.valid_from, license.valid_until, + license.max_installations, + ( + SELECT count(*)::integer + FROM identity.client_installation installation + WHERE installation.tenant_id = session.tenant_id + AND installation.license_id = session.license_id + AND installation.status IN ('PENDING', 'ACTIVE', 'SUSPENDED') + ) AS consuming_installations + FROM identity.installation_activation_session session + JOIN identity.client_registration_details registration + ON registration.registration_id = session.registration_id + JOIN identity.client_account account + ON account.registration_id = session.registration_id + AND account.tenant_id = session.tenant_id + JOIN identity.client_license license + ON license.tenant_id = session.tenant_id + AND license.license_id = session.license_id + WHERE session.token_hash = $1 + FOR UPDATE OF session, license + """; + + private static final String INSERT_INSTALLATION = """ + INSERT INTO identity.client_installation ( + installation_id, tenant_id, client_id, installation_code, + assertion_public_key, allowed_scopes, enabled, + security_version, license_id, installation_uuid, + installation_name, status, registered_at, + software_version, environment) + VALUES ($1, $2, $3, $4, $5, $6, true, 1, $7, $8, $9, + 'ACTIVE', $10, $11, $12) + """; + + private static final String CONSUME_SESSION = """ + UPDATE identity.installation_activation_session + SET status = 'CONSUMED', consumed_at = $2 + WHERE activation_session_id = $1 + AND status = 'PENDING' + """; + + private static final String INSERT_AUDIT = """ + INSERT INTO identity.installation_audit_event ( + audit_event_id, registration_id, tenant_id, license_id, + installation_id, event_type, actor_type, actor_id, + reason, event_data, occurred_at) + VALUES ($1, $2, $3, $4, $5, 'INSTALLATION_REGISTERED', + 'INSTALLER', $6, 'License-authorized installation', + $7::jsonb, $8) + """; + + private final ReactiveDatabaseClient database; + + public InstallationActivationRepository(ReactiveDatabaseClient database) { + this.database = database; + } + + public Mono insertSession( + UUID sessionId, + UUID activationKeyId, + UUID registrationId, + UUID tenantId, + UUID licenseId, + UUID installationUuid, + String tokenHash, + OffsetDateTime expiresAt, + String sourceIp, + String installerVersion) { + Tuple values = Tuple.tuple() + .addUUID(sessionId) + .addUUID(activationKeyId) + .addUUID(registrationId) + .addUUID(tenantId) + .addUUID(licenseId) + .addUUID(installationUuid) + .addString(tokenHash) + .addOffsetDateTime(expiresAt) + .addString(sourceIp) + .addString(installerVersion); + return database.preparedUpdate(INSERT_SESSION, values).map(count -> count == 1); + } + + public Mono register( + String tokenHash, + String installationCode, + String installationName, + String assertionPublicKey, + Set scopes, + String softwareVersion, + String environment, + OffsetDateTime now) { + return database.inTransaction(connection -> lockContext(connection, tokenHash) + .switchIfEmpty(Mono.error(new IllegalArgumentException( + "Activation session is invalid"))) + .flatMap(context -> validate(context, now)) + .flatMap(context -> insertInstallation( + connection, + context, + installationCode, + installationName, + assertionPublicKey, + scopes, + softwareVersion, + environment, + now))); + } + + private Mono lockContext( + SqlConnection connection, String tokenHash) { + return database.preparedQuery( + connection, LOCK_SESSION_AND_LICENSE, Tuple.of(tokenHash)) + .flatMap(rows -> { + java.util.Iterator iterator = rows.iterator(); + return iterator.hasNext() + ? Mono.just(context(iterator.next())) + : Mono.empty(); + }); + } + + private Mono validate( + RegistrationContext context, OffsetDateTime now) { + boolean valid = "PENDING".equals(context.sessionStatus) + && context.expiresAt.isAfter(now) + && "ACTIVE".equals(context.registrationStatus) + && "ACTIVE".equals(context.tenantStatus) + && "ACTIVE".equals(context.licenseStatus) + && !now.isBefore(context.validFrom) + && now.isBefore(context.validUntil) + && context.consumingInstallations < context.maxInstallations; + return valid + ? Mono.just(context) + : Mono.error(new IllegalStateException( + "Activation, license, or installation capacity is invalid")); + } + + private Mono insertInstallation( + SqlConnection connection, + RegistrationContext context, + String installationCode, + String installationName, + String assertionPublicKey, + Set scopes, + String softwareVersion, + String environment, + OffsetDateTime now) { + UUID installationId = UUID.randomUUID(); + Tuple insert = Tuple.tuple() + .addUUID(installationId) + .addUUID(context.tenantId) + .addString(context.clientSlug) + .addString(installationCode) + .addString(assertionPublicKey) + .addArrayOfString(scopes.toArray(String[]::new)) + .addUUID(context.licenseId) + .addUUID(context.installationUuid) + .addString(installationName) + .addOffsetDateTime(now) + .addString(softwareVersion) + .addString(environment); + return database.preparedQuery(connection, INSERT_INSTALLATION, insert) + .flatMap(rows -> rows.rowCount() == 1 + ? consumeAndAudit(connection, context, installationId, now) + : Mono.error(new IllegalStateException( + "Installation could not be registered"))) + .thenReturn(new RegisteredInstallation( + installationId, + context.installationUuid, + context.clientSlug, + installationCode, + 1, + "ACTIVE")); + } + + private Mono consumeAndAudit( + SqlConnection connection, + RegistrationContext context, + UUID installationId, + OffsetDateTime now) { + return database.preparedQuery( + connection, + CONSUME_SESSION, + Tuple.of(context.sessionId, now)) + .flatMap(rows -> rows.rowCount() == 1 + ? database.preparedQuery( + connection, + INSERT_AUDIT, + Tuple.of( + UUID.randomUUID(), + context.registrationId, + context.tenantId, + context.licenseId, + installationId, + context.installationUuid.toString(), + "{}", + now)) + : Mono.error(new IllegalStateException( + "Activation session was already consumed"))) + .then(); + } + + private RegistrationContext context(Row row) { + return new RegistrationContext( + row.getUUID("activation_session_id"), + row.getUUID("registration_id"), + row.getUUID("tenant_id"), + row.getUUID("license_id"), + row.getUUID("installation_uuid"), + row.getString("session_status"), + row.getOffsetDateTime("expires_at"), + row.getString("client_code"), + row.getString("registration_status"), + row.getString("client_slug"), + row.getString("tenant_status"), + row.getString("package_code"), + row.getString("license_status"), + row.getOffsetDateTime("valid_from"), + row.getOffsetDateTime("valid_until"), + row.getInteger("max_installations"), + row.getInteger("consuming_installations")); + } + + private record RegistrationContext( + UUID sessionId, + UUID registrationId, + UUID tenantId, + UUID licenseId, + UUID installationUuid, + String sessionStatus, + OffsetDateTime expiresAt, + String clientCode, + String registrationStatus, + String clientSlug, + String tenantStatus, + String packageCode, + String licenseStatus, + OffsetDateTime validFrom, + OffsetDateTime validUntil, + int maxInstallations, + int consumingInstallations) { + } +} diff --git a/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/repository/InstallationLifecycleRepository.java b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/repository/InstallationLifecycleRepository.java new file mode 100644 index 0000000..f5d92eb --- /dev/null +++ b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/repository/InstallationLifecycleRepository.java @@ -0,0 +1,72 @@ +package com.cygnus.cloud.tenant.repository; + +import com.cygnus.cloud.database.ReactiveDatabaseClient; +import com.cygnus.cloud.tenant.model.InstallationLifecycleResult; +import io.vertx.sqlclient.Tuple; +import java.util.UUID; +import org.springframework.stereotype.Repository; +import reactor.core.publisher.Mono; + +@Repository +public class InstallationLifecycleRepository { + + private static final String RETIRE = """ + WITH retired AS ( + UPDATE identity.client_installation + SET status = $3, + enabled = false, + security_version = security_version + 1, + retired_at = now(), + retired_by = $4, + retirement_reason = $5 + WHERE installation_id = $1 + AND tenant_id = $2 + AND status IN ('PENDING', 'ACTIVE', 'SUSPENDED') + RETURNING installation_id, tenant_id, client_id, + installation_code, license_id, status + ), audited AS ( + INSERT INTO identity.installation_audit_event ( + audit_event_id, registration_id, tenant_id, license_id, + installation_id, event_type, actor_type, actor_id, + reason, event_data) + SELECT gen_random_uuid(), account.registration_id, retired.tenant_id, + retired.license_id, retired.installation_id, retired.status, + 'ADMIN', $4, $5, '{}'::jsonb + FROM retired + JOIN identity.client_account account + ON account.tenant_id = retired.tenant_id + ) + SELECT installation_id, tenant_id, client_id, installation_code, status + FROM retired + """; + + private final ReactiveDatabaseClient database; + + public InstallationLifecycleRepository(ReactiveDatabaseClient database) { + this.database = database; + } + + public Mono retire( + UUID tenantId, + UUID installationId, + String status, + String actor, + String reason) { + return database.preparedQuery( + RETIRE, + Tuple.of(installationId, tenantId, status, actor, reason)) + .flatMap(rows -> { + var iterator = rows.iterator(); + if (!iterator.hasNext()) { + return Mono.empty(); + } + var row = iterator.next(); + return Mono.just(new InstallationLifecycleResult( + row.getUUID("installation_id"), + row.getUUID("tenant_id"), + row.getString("client_id"), + row.getString("installation_code"), + row.getString("status"))); + }); + } +} diff --git a/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/repository/LicenseActivationRepository.java b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/repository/LicenseActivationRepository.java new file mode 100644 index 0000000..226ff2a --- /dev/null +++ b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/repository/LicenseActivationRepository.java @@ -0,0 +1,182 @@ +package com.cygnus.cloud.tenant.repository; + +import com.cygnus.cloud.database.ReactiveDatabaseClient; +import com.cygnus.cloud.tenant.model.ActivationKeyStatus; +import com.cygnus.cloud.tenant.model.ClientStatus; +import com.cygnus.cloud.tenant.model.LicenseActivationContext; +import com.cygnus.cloud.tenant.model.LicenseActivationKey; +import com.cygnus.cloud.tenant.model.LicenseStatus; +import com.cygnus.cloud.tenant.model.RegistrationStatus; +import io.vertx.sqlclient.Row; +import io.vertx.sqlclient.Tuple; +import java.time.OffsetDateTime; +import java.util.UUID; +import org.springframework.stereotype.Repository; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +@Repository +public class LicenseActivationRepository { + + private static final String FIND_CANDIDATES = """ + SELECT activation.activation_key_id, activation.registration_id, + activation.tenant_id, activation.license_id, + activation.key_hash, activation.key_hint, activation.status, + activation.expires_at, activation.failed_attempts, + activation.maximum_attempts, activation.locked_until, + activation.created_at, activation.last_used_at, + registration.client_code, + registration.status AS registration_status, + account.status AS tenant_status, + account.client_slug, account.client_name, + license.package_code, license.license_type, + license.status AS license_status, + license.valid_from, license.valid_until, + license.max_installations, + ( + SELECT count(*)::integer + FROM identity.client_installation installation + WHERE installation.tenant_id = activation.tenant_id + AND installation.license_id = activation.license_id + AND installation.status IN ('PENDING', 'ACTIVE', 'SUSPENDED') + ) AS consuming_installations + FROM identity.license_activation_key activation + JOIN identity.client_registration_details registration + ON registration.registration_id = activation.registration_id + JOIN identity.client_account account + ON account.registration_id = activation.registration_id + AND account.tenant_id = activation.tenant_id + JOIN identity.client_license license + ON license.tenant_id = activation.tenant_id + AND license.license_id = activation.license_id + WHERE upper(registration.client_code) = upper($1) + AND activation.key_hint = $2 + """; + + private static final String INSERT_KEY = """ + INSERT INTO identity.license_activation_key ( + activation_key_id, registration_id, tenant_id, license_id, + key_hash, key_hint, status, expires_at, created_by) + SELECT $1, account.registration_id, account.tenant_id, + license.license_id, $4, $5, 'ACTIVE', $6, $7 + FROM identity.client_account account + JOIN identity.client_license license + ON license.tenant_id = account.tenant_id + WHERE account.tenant_id = $2 + AND license.license_id = $3 + AND account.status = 'ACTIVE' + AND license.status = 'ACTIVE' + """; + + private static final String RECORD_SUCCESS = """ + UPDATE identity.license_activation_key + SET failed_attempts = 0, + locked_until = NULL, + status = 'ACTIVE', + last_used_at = $2 + WHERE activation_key_id = $1 + """; + + private static final String RECORD_FAILURE = """ + UPDATE identity.license_activation_key + SET failed_attempts = LEAST(failed_attempts + 1, maximum_attempts), + status = CASE + WHEN failed_attempts + 1 >= maximum_attempts THEN 'LOCKED' + ELSE status + END, + locked_until = CASE + WHEN failed_attempts + 1 >= maximum_attempts THEN $2 + ELSE locked_until + END + WHERE activation_key_id = $1 + """; + + private static final String REVOKE_KEY = """ + UPDATE identity.license_activation_key + SET status = 'REVOKED', + locked_until = NULL + WHERE activation_key_id = $1 + AND status IN ('ACTIVE', 'LOCKED') + """; + + private final ReactiveDatabaseClient database; + + public LicenseActivationRepository(ReactiveDatabaseClient database) { + this.database = database; + } + + public Flux findCandidates( + String clientCode, String keyHint) { + return database.preparedQuery(FIND_CANDIDATES, Tuple.of(clientCode, keyHint)) + .flatMapMany(Flux::fromIterable) + .map(this::context); + } + + public Mono insert( + UUID activationKeyId, + UUID tenantId, + UUID licenseId, + String keyHash, + String keyHint, + OffsetDateTime expiresAt, + String createdBy) { + Tuple values = Tuple.tuple() + .addUUID(activationKeyId) + .addUUID(tenantId) + .addUUID(licenseId) + .addString(keyHash) + .addString(keyHint) + .addOffsetDateTime(expiresAt) + .addString(createdBy); + return database.preparedUpdate(INSERT_KEY, values).map(count -> count == 1); + } + + public Mono recordSuccess(UUID activationKeyId, OffsetDateTime now) { + return database.preparedUpdate( + RECORD_SUCCESS, Tuple.of(activationKeyId, now)) + .map(count -> count == 1); + } + + public Mono recordFailure( + UUID activationKeyId, OffsetDateTime lockedUntil) { + return database.preparedUpdate( + RECORD_FAILURE, Tuple.of(activationKeyId, lockedUntil)) + .map(count -> count == 1); + } + + public Mono revoke(UUID activationKeyId) { + return database.preparedUpdate(REVOKE_KEY, Tuple.of(activationKeyId)) + .map(count -> count == 1); + } + + private LicenseActivationContext context(Row row) { + LicenseActivationKey key = new LicenseActivationKey( + row.getUUID("activation_key_id"), + row.getUUID("registration_id"), + row.getUUID("tenant_id"), + row.getUUID("license_id"), + row.getString("key_hash"), + row.getString("key_hint"), + ActivationKeyStatus.valueOf(row.getString("status")), + row.getOffsetDateTime("expires_at"), + row.getInteger("failed_attempts"), + row.getInteger("maximum_attempts"), + row.getOffsetDateTime("locked_until"), + row.getOffsetDateTime("created_at"), + row.getOffsetDateTime("last_used_at")); + return new LicenseActivationContext( + key, + row.getString("client_code"), + RegistrationStatus.valueOf(row.getString("registration_status")), + ClientStatus.valueOf(row.getString("tenant_status")), + row.getString("client_slug"), + row.getString("client_name"), + row.getString("package_code"), + row.getString("license_type"), + LicenseStatus.valueOf(row.getString("license_status")), + row.getOffsetDateTime("valid_from").toInstant(), + row.getOffsetDateTime("valid_until").toInstant(), + row.getInteger("max_installations"), + row.getInteger("consuming_installations")); + } +} diff --git a/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/repository/TenantRegistrationRepository.java b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/repository/TenantRegistrationRepository.java index 9ef1bd3..ae2eed5 100644 --- a/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/repository/TenantRegistrationRepository.java +++ b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/repository/TenantRegistrationRepository.java @@ -37,9 +37,18 @@ public class TenantRegistrationRepository { WHERE installation.client_id = $1 AND installation.installation_code = $2 AND installation.enabled = true + AND installation.status = 'ACTIVE' AND account.status = 'ACTIVE' """; + private static final String TOUCH_INSTALLATION = """ + UPDATE identity.client_installation + SET last_seen_at = $2, + last_authenticated_at = $2 + WHERE installation_id = $1 + AND (last_seen_at IS NULL OR last_seen_at < $2 - INTERVAL '5 minutes') + """; + private static final String FIND_CURRENT_LICENSE = """ SELECT license_id, tenant_id, license_type, package_code, valid_from, valid_until, status, max_users, max_installations, @@ -82,6 +91,15 @@ public class TenantRegistrationRepository { .flatMap(rows -> first(rows, this::license)); } + public Mono touchInstallation(UUID installationId, Instant instant) { + return database.preparedUpdate( + TOUCH_INSTALLATION, + Tuple.of( + installationId, + instant.atOffset(java.time.ZoneOffset.UTC))) + .then(); + } + private Mono first( Iterable rows, java.util.function.Function mapper) { java.util.Iterator iterator = rows.iterator(); diff --git a/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/service/ActivationRateLimitException.java b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/service/ActivationRateLimitException.java new file mode 100644 index 0000000..ba67956 --- /dev/null +++ b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/service/ActivationRateLimitException.java @@ -0,0 +1,7 @@ +package com.cygnus.cloud.tenant.service; + +public class ActivationRateLimitException extends RuntimeException { + public ActivationRateLimitException() { + super("Installation activation rate limit exceeded"); + } +} diff --git a/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/service/ActivationRateLimiter.java b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/service/ActivationRateLimiter.java new file mode 100644 index 0000000..ff79a13 --- /dev/null +++ b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/service/ActivationRateLimiter.java @@ -0,0 +1,49 @@ +package com.cygnus.cloud.tenant.service; + +import com.cygnus.cloud.cache.ReactiveCacheService; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.time.Duration; +import java.util.HexFormat; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Mono; + +@Service +public class ActivationRateLimiter { + + private static final String NAMESPACE = "installation-activation-rate"; + private final ReactiveCacheService cache; + private final int maximumAttempts; + private final Duration window; + + public ActivationRateLimiter( + ReactiveCacheService cache, + @Value("${cygnus.activation-rate-limit.maximum-attempts:10}") + int maximumAttempts, + @Value("${cygnus.activation-rate-limit.window:10m}") + Duration window) { + this.cache = cache; + this.maximumAttempts = maximumAttempts; + this.window = window; + } + + public Mono check(String sourceIp, String clientCode) { + String identity = (sourceIp == null ? "unknown" : sourceIp) + + '|' + + clientCode.toUpperCase(java.util.Locale.ROOT); + return cache.increment(NAMESPACE, digest(identity), window) + .flatMap(attempts -> attempts <= maximumAttempts + ? Mono.empty() + : Mono.error(new ActivationRateLimitException())); + } + + private String digest(String value) { + try { + return HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256") + .digest(value.getBytes(StandardCharsets.UTF_8))); + } catch (Exception exception) { + throw new IllegalStateException("SHA-256 is unavailable", exception); + } + } +} diff --git a/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/service/InstallationActivationException.java b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/service/InstallationActivationException.java new file mode 100644 index 0000000..ba5fe22 --- /dev/null +++ b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/service/InstallationActivationException.java @@ -0,0 +1,12 @@ +package com.cygnus.cloud.tenant.service; + +public class InstallationActivationException extends RuntimeException { + + public InstallationActivationException(String message) { + super(message); + } + + public InstallationActivationException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/service/InstallationActivationService.java b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/service/InstallationActivationService.java new file mode 100644 index 0000000..4a31551 --- /dev/null +++ b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/service/InstallationActivationService.java @@ -0,0 +1,173 @@ +package com.cygnus.cloud.tenant.service; + +import com.cygnus.cloud.tenant.model.ActivationSession; +import com.cygnus.cloud.tenant.model.LicenseActivationContext; +import com.cygnus.cloud.tenant.model.RegisteredInstallation; +import com.cygnus.cloud.tenant.repository.InstallationActivationRepository; +import java.nio.charset.StandardCharsets; +import java.security.KeyFactory; +import java.security.MessageDigest; +import java.security.SecureRandom; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.X509EncodedKeySpec; +import java.time.Clock; +import java.time.Duration; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.util.Base64; +import java.util.Set; +import java.util.UUID; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Mono; + +@Service +public class InstallationActivationService { + + private static final Duration SESSION_TTL = Duration.ofMinutes(20); + private static final Set INSTALLATION_SCOPES = Set.of("identity.login"); + + private final LicenseKeyService licenseKeyService; + private final InstallationActivationRepository repository; + private final SecureRandom random; + private final Clock clock; + + @Autowired + public InstallationActivationService( + LicenseKeyService licenseKeyService, + InstallationActivationRepository repository, + Clock clock) { + this(licenseKeyService, repository, clock, new SecureRandom()); + } + + InstallationActivationService( + LicenseKeyService licenseKeyService, + InstallationActivationRepository repository, + Clock clock, + SecureRandom random) { + this.licenseKeyService = licenseKeyService; + this.repository = repository; + this.clock = clock; + this.random = random; + } + + public Mono validateAndCreateSession( + String clientCode, + String licenseKey, + UUID installationUuid, + String sourceIp, + String installerVersion) { + return licenseKeyService.validate(clientCode, licenseKey) + .flatMap(context -> createSession( + context, + installationUuid, + sourceIp, + installerVersion)); + } + + public Mono register( + String activationToken, + String installationCode, + String installationName, + String assertionPublicKey, + String softwareVersion, + String environment) { + validatePublicKey(assertionPublicKey); + OffsetDateTime now = now(); + return repository.register( + digest(activationToken), + installationCode, + installationName, + assertionPublicKey, + INSTALLATION_SCOPES, + softwareVersion, + environment, + now) + .onErrorMap( + error -> !(error instanceof InstallationActivationException), + error -> new InstallationActivationException( + "Installation registration failed", error)); + } + + private Mono createSession( + LicenseActivationContext context, + UUID installationUuid, + String sourceIp, + String installerVersion) { + String token = randomToken(); + UUID sessionId = UUID.randomUUID(); + OffsetDateTime expiresAt = now().plus(SESSION_TTL); + return repository.insertSession( + sessionId, + context.activationKey().activationKeyId(), + context.activationKey().registrationId(), + context.activationKey().tenantId(), + context.activationKey().licenseId(), + installationUuid, + digest(token), + expiresAt, + sourceIp, + installerVersion) + .flatMap(inserted -> inserted + ? Mono.just(new ActivationSession( + sessionId, + context.activationKey().registrationId(), + context.activationKey().tenantId(), + context.activationKey().licenseId(), + installationUuid, + token, + expiresAt, + context.clientCode(), + context.clientSlug(), + context.packageCode(), + context.maxInstallations())) + : Mono.error(new InstallationActivationException( + "Activation session could not be created"))); + } + + private String randomToken() { + byte[] bytes = new byte[32]; + random.nextBytes(bytes); + return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); + } + + private String digest(String value) { + if (value == null || value.isBlank()) { + throw new InstallationActivationException( + "Activation token is required"); + } + try { + byte[] digest = MessageDigest.getInstance("SHA-256") + .digest(value.getBytes(StandardCharsets.UTF_8)); + return Base64.getUrlEncoder().withoutPadding().encodeToString(digest); + } catch (Exception exception) { + throw new InstallationActivationException( + "Activation token could not be processed", exception); + } + } + + private void validatePublicKey(String pem) { + try { + String encoded = pem + .replace("-----BEGIN PUBLIC KEY-----", "") + .replace("-----END PUBLIC KEY-----", "") + .replaceAll("\\s", ""); + RSAPublicKey key = (RSAPublicKey) KeyFactory.getInstance("RSA") + .generatePublic(new X509EncodedKeySpec( + Base64.getDecoder().decode(encoded))); + if (key.getModulus().bitLength() < 3072) { + throw new InstallationActivationException( + "Installation public key must be at least 3072-bit RSA"); + } + } catch (InstallationActivationException exception) { + throw exception; + } catch (Exception exception) { + throw new InstallationActivationException( + "Installation public key is invalid"); + } + } + + private OffsetDateTime now() { + return OffsetDateTime.ofInstant(clock.instant(), ZoneOffset.UTC); + } +} diff --git a/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/service/InstallationLifecycleService.java b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/service/InstallationLifecycleService.java new file mode 100644 index 0000000..0b35147 --- /dev/null +++ b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/service/InstallationLifecycleService.java @@ -0,0 +1,35 @@ +package com.cygnus.cloud.tenant.service; + +import com.cygnus.cloud.tenant.model.InstallationLifecycleResult; +import com.cygnus.cloud.tenant.repository.InstallationLifecycleRepository; +import java.util.UUID; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Mono; + +@Service +public class InstallationLifecycleService { + + private final InstallationLifecycleRepository repository; + private final TenantRegistrationService registrations; + + public InstallationLifecycleService( + InstallationLifecycleRepository repository, + TenantRegistrationService registrations) { + this.repository = repository; + this.registrations = registrations; + } + + public Mono retire( + UUID tenantId, + UUID installationId, + String status, + String actor, + String reason) { + return repository.retire(tenantId, installationId, status, actor, reason) + .switchIfEmpty(Mono.error( + new IllegalArgumentException("Active installation not found"))) + .flatMap(result -> registrations + .evictInstallation(result.clientId(), result.installationCode()) + .thenReturn(result)); + } +} diff --git a/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/service/LicenseKeyException.java b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/service/LicenseKeyException.java new file mode 100644 index 0000000..9c5d989 --- /dev/null +++ b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/service/LicenseKeyException.java @@ -0,0 +1,8 @@ +package com.cygnus.cloud.tenant.service; + +public class LicenseKeyException extends RuntimeException { + + public LicenseKeyException(String message) { + super(message); + } +} diff --git a/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/service/LicenseKeyService.java b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/service/LicenseKeyService.java new file mode 100644 index 0000000..ad2d881 --- /dev/null +++ b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/service/LicenseKeyService.java @@ -0,0 +1,192 @@ +package com.cygnus.cloud.tenant.service; + +import com.cygnus.cloud.tenant.model.ClientStatus; +import com.cygnus.cloud.tenant.model.IssuedLicenseKey; +import com.cygnus.cloud.tenant.model.LicenseActivationContext; +import com.cygnus.cloud.tenant.model.LicenseStatus; +import com.cygnus.cloud.tenant.model.RegistrationStatus; +import com.cygnus.cloud.tenant.repository.LicenseActivationRepository; +import java.security.SecureRandom; +import java.time.Clock; +import java.time.Duration; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.util.HexFormat; +import java.util.Locale; +import java.util.List; +import java.util.UUID; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.core.scheduler.Schedulers; + +@Service +public class LicenseKeyService { + + private static final String PREFIX = "CYGNUS"; + private static final int RANDOM_BYTES = 20; + private static final Duration LOCK_DURATION = Duration.ofMinutes(30); + + private final LicenseActivationRepository repository; + private final BCryptPasswordEncoder encoder; + private final SecureRandom random; + private final Clock clock; + + @Autowired + public LicenseKeyService( + LicenseActivationRepository repository, + Clock clock) { + this(repository, clock, new BCryptPasswordEncoder(12), new SecureRandom()); + } + + LicenseKeyService( + LicenseActivationRepository repository, + Clock clock, + BCryptPasswordEncoder encoder, + SecureRandom random) { + this.repository = repository; + this.clock = clock; + this.encoder = encoder; + this.random = random; + } + + public Mono issue( + UUID tenantId, + UUID licenseId, + OffsetDateTime expiresAt, + String createdBy) { + return Mono.fromCallable(this::newPlaintextKey) + .subscribeOn(Schedulers.boundedElastic()) + .flatMap(plaintext -> hash(plaintext) + .flatMap(hash -> { + UUID id = UUID.randomUUID(); + String hint = hint(plaintext); + return repository.insert( + id, + tenantId, + licenseId, + hash, + hint, + expiresAt, + createdBy) + .flatMap(inserted -> inserted + ? Mono.just(new IssuedLicenseKey( + id, plaintext, hint)) + : Mono.error(new LicenseKeyException( + "Active tenant and license were not found"))); + })); + } + + public Mono validate( + String clientCode, String plaintextKey) { + String normalizedCode = normalizeClientCode(clientCode); + String normalizedKey = normalizeKey(plaintextKey); + OffsetDateTime now = OffsetDateTime.ofInstant(clock.instant(), ZoneOffset.UTC); + + return repository.findCandidates(normalizedCode, hint(normalizedKey)) + .collectList() + .flatMap(candidates -> matchingCandidate(candidates, normalizedKey)) + .flatMap(candidate -> validateContext(candidate, now)) + .flatMap(candidate -> repository + .recordSuccess(candidate.activationKey().activationKeyId(), now) + .thenReturn(candidate)); + } + + private Mono matchingCandidate( + List candidates, String plaintextKey) { + if (candidates.isEmpty()) { + return Mono.error(new LicenseKeyException( + "The client code or license key is invalid")); + } + return Flux.fromIterable(candidates) + .filterWhen(candidate -> matches( + plaintextKey, candidate.activationKey().keyHash())) + .next() + .switchIfEmpty(Flux.fromIterable(candidates) + .flatMap(candidate -> recordRejectedAttempt( + candidate.activationKey().activationKeyId())) + .then(Mono.error(new LicenseKeyException( + "The client code or license key is invalid")))); + } + + private Mono validateContext( + LicenseActivationContext context, OffsetDateTime now) { + if (!context.activationKey().canAttemptAt(now)) { + return Mono.error(new LicenseKeyException( + "The license key is expired, locked, or inactive")); + } + if (context.registrationStatus() != RegistrationStatus.ACTIVE + || context.tenantStatus() != ClientStatus.ACTIVE) { + return Mono.error(new LicenseKeyException( + "The client registration or tenant is inactive")); + } + if (context.licenseStatus() != LicenseStatus.ACTIVE + || clock.instant().isBefore(context.validFrom()) + || !clock.instant().isBefore(context.validUntil())) { + return Mono.error(new LicenseKeyException("The license is not active")); + } + if (!context.hasCapacity()) { + return Mono.error(new LicenseKeyException( + "The installation limit for this tenant has been reached")); + } + return Mono.just(context); + } + + public Mono recordRejectedAttempt(UUID activationKeyId) { + OffsetDateTime lockedUntil = OffsetDateTime.ofInstant( + clock.instant().plus(LOCK_DURATION), ZoneOffset.UTC); + return repository.recordFailure(activationKeyId, lockedUntil); + } + + public Mono revoke(UUID activationKeyId) { + return repository.revoke(activationKeyId); + } + + private Mono hash(String plaintext) { + return Mono.fromCallable(() -> encoder.encode(plaintext)) + .subscribeOn(Schedulers.boundedElastic()); + } + + private Mono matches(String plaintext, String hash) { + return Mono.fromCallable(() -> encoder.matches(plaintext, hash)) + .subscribeOn(Schedulers.boundedElastic()); + } + + private String newPlaintextKey() { + byte[] bytes = new byte[RANDOM_BYTES]; + random.nextBytes(bytes); + String raw = HexFormat.of().formatHex(bytes).toUpperCase(Locale.ROOT); + return PREFIX + "-" + groups(raw); + } + + private String groups(String value) { + return String.join( + "-", + value.substring(0, 8), + value.substring(8, 16), + value.substring(16, 24), + value.substring(24, 32), + value.substring(32, 40)); + } + + private String hint(String key) { + String compact = key.replace("-", ""); + return compact.substring(Math.max(0, compact.length() - 8)); + } + + private String normalizeClientCode(String value) { + if (value == null || value.isBlank()) { + throw new LicenseKeyException("Client code is required"); + } + return value.trim().toUpperCase(Locale.ROOT); + } + + private String normalizeKey(String value) { + if (value == null || value.isBlank()) { + throw new LicenseKeyException("License key is required"); + } + return value.trim().toUpperCase(Locale.ROOT); + } +} diff --git a/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/service/RegistrationEmailService.java b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/service/RegistrationEmailService.java new file mode 100644 index 0000000..ec2a872 --- /dev/null +++ b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/service/RegistrationEmailService.java @@ -0,0 +1,71 @@ +package com.cygnus.cloud.tenant.service; + +import com.cygnus.cloud.tenant.model.IssuedLicenseKey; +import java.time.OffsetDateTime; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.mail.SimpleMailMessage; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Mono; +import reactor.core.scheduler.Schedulers; + +@Service +public class RegistrationEmailService { + + private final ObjectProvider mailSenderProvider; + private final String from; + + public RegistrationEmailService( + ObjectProvider mailSenderProvider, + @Value("${cygnus.registration-email.from:noreply@cygnus.invalid}") + String from) { + this.mailSenderProvider = mailSenderProvider; + this.from = from; + } + + public Mono sendLicense( + String recipient, + String clientCode, + String tenantSlug, + String packageCode, + int maximumInstallations, + OffsetDateTime validUntil, + IssuedLicenseKey issued) { + return Mono.fromRunnable(() -> { + JavaMailSender mailSender = mailSenderProvider.getIfAvailable(); + if (mailSender == null) { + throw new IllegalStateException( + "SMTP is not configured; set spring.mail.host and credentials"); + } + SimpleMailMessage message = new SimpleMailMessage(); + message.setFrom(from); + message.setTo(recipient); + message.setSubject("Cygnus registration and installation license"); + message.setText(""" + Your Cygnus tenant is ready. + + Client code: %s + Tenant: %s + Package: %s + Maximum installations: %d + License key: %s + License key expiry: %s + + Docker and Docker Compose v2 are mandatory. The installation + wizard will not continue until both are available and running. + Keep this license key confidential and enter it only in the + official Cygnus installation wizard. + """.formatted( + clientCode, + tenantSlug, + packageCode, + maximumInstallations, + issued.licenseKey(), + validUntil)); + mailSender.send(message); + }) + .subscribeOn(Schedulers.boundedElastic()) + .then(); + } +} diff --git a/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/service/TenantRegistrationService.java b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/service/TenantRegistrationService.java index 8333586..fd97dff 100644 --- a/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/service/TenantRegistrationService.java +++ b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/service/TenantRegistrationService.java @@ -88,6 +88,11 @@ public class TenantRegistrationService { .onErrorReturn(false); } + public Mono touchInstallation(UUID installationId, Instant instant) { + return repository.touchInstallation(installationId, instant) + .onErrorResume(exception -> Mono.empty()); + } + private Duration licenseTtl(ClientLicense license) { Duration remaining = Duration.between(clock.instant(), license.validUntil()); if (remaining.isNegative() || remaining.isZero()) { diff --git a/cygnus-cloud-service/src/main/resources/application.yml b/cygnus-cloud-service/src/main/resources/application.yml index 0b4dac5..e8773dd 100644 --- a/cygnus-cloud-service/src/main/resources/application.yml +++ b/cygnus-cloud-service/src/main/resources/application.yml @@ -1,6 +1,17 @@ spring: application: name: cygnus-cloud-service + mail: + host: ${CYGNUS_MAIL_HOST:} + port: ${CYGNUS_MAIL_PORT:587} + username: ${CYGNUS_MAIL_USERNAME:} + password: ${CYGNUS_MAIL_PASSWORD:} + properties: + mail: + smtp: + auth: ${CYGNUS_MAIL_SMTP_AUTH:true} + starttls: + enable: ${CYGNUS_MAIL_STARTTLS:true} data: redis: host: ${REDIS_HOST:192.168.0.111} @@ -43,6 +54,11 @@ cygnus: cache: key-prefix: ${CYGNUS_CACHE_PREFIX:cygnus} default-ttl: ${CYGNUS_CACHE_TTL:10m} + registration-email: + from: ${CYGNUS_REGISTRATION_EMAIL_FROM:noreply@cygnus.invalid} + activation-rate-limit: + maximum-attempts: ${CYGNUS_ACTIVATION_MAX_ATTEMPTS:10} + window: ${CYGNUS_ACTIVATION_RATE_WINDOW:10m} server: port: ${CYGNUS_CLOUD_PORT:8090} diff --git a/cygnus-cloud-service/src/main/resources/db/identity/004_client_registration_activation.sql b/cygnus-cloud-service/src/main/resources/db/identity/004_client_registration_activation.sql new file mode 100644 index 0000000..102efe2 --- /dev/null +++ b/cygnus-cloud-service/src/main/resources/db/identity/004_client_registration_activation.sql @@ -0,0 +1,369 @@ +BEGIN; + +-- Client registration and controlled installation activation. +-- This migration is additive and preserves all existing tenant, license, and +-- installation identifiers. Plaintext license/activation keys must never be +-- stored in these tables. + +CREATE TABLE IF NOT EXISTS identity.client_registration_details ( + registration_id uuid PRIMARY KEY, + client_code character varying(40) NOT NULL, + legal_company_name character varying(240) NOT NULL, + trade_name character varying(240), + pan character varying(20), + cin character varying(30), + gst_number character varying(30), + billing_address_line1 character varying(300), + billing_address_line2 character varying(300), + billing_city character varying(120), + billing_state character varying(120), + billing_postal_code character varying(20), + billing_country character varying(2) NOT NULL DEFAULT 'IN', + billing_email character varying(254), + primary_contact_name character varying(160), + primary_contact_email character varying(254), + primary_contact_number character varying(30), + alternate_contact_name character varying(160), + alternate_contact_email character varying(254), + alternate_contact_number character varying(30), + contract_start_date date, + contract_end_date date, + status character varying(20) NOT NULL DEFAULT 'DRAFT', + created_at timestamp with time zone NOT NULL DEFAULT now(), + updated_at timestamp with time zone NOT NULL DEFAULT now(), + created_by character varying(120) NOT NULL, + version integer NOT NULL DEFAULT 1, + CONSTRAINT ck_identity_registration_code + CHECK (client_code ~ '^[A-Z0-9]+(?:-[A-Z0-9]+)*$'), + CONSTRAINT ck_identity_registration_country + CHECK (billing_country ~ '^[A-Z]{2}$'), + CONSTRAINT ck_identity_registration_period + CHECK (contract_end_date IS NULL + OR contract_start_date IS NULL + OR contract_end_date >= contract_start_date), + CONSTRAINT ck_identity_registration_status + CHECK (status IN ('DRAFT', 'ACTIVE', 'SUSPENDED', 'TERMINATED')), + CONSTRAINT ck_identity_registration_version + CHECK (version > 0) +); + +CREATE UNIQUE INDEX IF NOT EXISTS uq_identity_registration_client_code_ci + ON identity.client_registration_details (upper(client_code)); +CREATE INDEX IF NOT EXISTS ix_identity_registration_status_contract + ON identity.client_registration_details (status, contract_end_date); + +ALTER TABLE identity.client_account + ADD COLUMN IF NOT EXISTS registration_id uuid; +ALTER TABLE identity.client_account + ADD COLUMN IF NOT EXISTS security_version integer NOT NULL DEFAULT 1; + +-- Stable bridge registration for the tenant introduced by migration 002. +INSERT INTO identity.client_registration_details ( + registration_id, client_code, legal_company_name, trade_name, + status, contract_start_date, created_by) +VALUES ('00000000-0000-4000-8000-000000000010', + 'MATRIX', 'Matrix', 'Matrix', 'ACTIVE', DATE '2020-01-01', 'migration-004') +ON CONFLICT (registration_id) DO UPDATE SET + legal_company_name = EXCLUDED.legal_company_name, + updated_at = now(); + +UPDATE identity.client_account + SET registration_id = '00000000-0000-4000-8000-000000000010' + WHERE registration_id IS NULL; + +ALTER TABLE identity.client_account + ALTER COLUMN registration_id SET NOT NULL; + +DO $migration$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM pg_constraint + WHERE conname = 'fk_identity_client_account_registration' + AND conrelid = 'identity.client_account'::regclass) THEN + ALTER TABLE identity.client_account + ADD CONSTRAINT fk_identity_client_account_registration + FOREIGN KEY (registration_id) + REFERENCES identity.client_registration_details (registration_id) + NOT VALID; + END IF; + + IF NOT EXISTS ( + SELECT 1 FROM pg_constraint + WHERE conname = 'ck_identity_client_account_security_version' + AND conrelid = 'identity.client_account'::regclass) THEN + ALTER TABLE identity.client_account + ADD CONSTRAINT ck_identity_client_account_security_version + CHECK (security_version > 0) NOT VALID; + END IF; +END +$migration$; + +ALTER TABLE identity.client_account + VALIDATE CONSTRAINT fk_identity_client_account_registration; +ALTER TABLE identity.client_account + VALIDATE CONSTRAINT ck_identity_client_account_security_version; + +CREATE INDEX IF NOT EXISTS ix_identity_client_account_registration + ON identity.client_account (registration_id, status); +CREATE UNIQUE INDEX IF NOT EXISTS uq_identity_client_account_registration_tenant + ON identity.client_account (registration_id, tenant_id); + +UPDATE identity.client_license + SET max_installations = 1 + WHERE max_installations IS NULL; +ALTER TABLE identity.client_license + ALTER COLUMN max_installations SET DEFAULT 1; +ALTER TABLE identity.client_license + ALTER COLUMN max_installations SET NOT NULL; +CREATE UNIQUE INDEX IF NOT EXISTS uq_identity_license_tenant_id + ON identity.client_license (tenant_id, license_id); + +ALTER TABLE identity.client_installation + ADD COLUMN IF NOT EXISTS license_id uuid; +ALTER TABLE identity.client_installation + ADD COLUMN IF NOT EXISTS installation_uuid uuid; +ALTER TABLE identity.client_installation + ADD COLUMN IF NOT EXISTS installation_name character varying(160); +ALTER TABLE identity.client_installation + ADD COLUMN IF NOT EXISTS status character varying(24); +ALTER TABLE identity.client_installation + ADD COLUMN IF NOT EXISTS registered_at timestamp with time zone; +ALTER TABLE identity.client_installation + ADD COLUMN IF NOT EXISTS last_seen_at timestamp with time zone; +ALTER TABLE identity.client_installation + ADD COLUMN IF NOT EXISTS software_version character varying(40); +ALTER TABLE identity.client_installation + ADD COLUMN IF NOT EXISTS environment character varying(30); +ALTER TABLE identity.client_installation + ADD COLUMN IF NOT EXISTS retired_at timestamp with time zone; +ALTER TABLE identity.client_installation + ADD COLUMN IF NOT EXISTS retired_by character varying(120); +ALTER TABLE identity.client_installation + ADD COLUMN IF NOT EXISTS retirement_reason character varying(500); + +UPDATE identity.client_installation installation + SET license_id = ( + SELECT license.license_id + FROM identity.client_license license + WHERE license.tenant_id = installation.tenant_id + ORDER BY CASE license.status WHEN 'ACTIVE' THEN 0 ELSE 1 END, + license.valid_until DESC + LIMIT 1) + WHERE installation.license_id IS NULL; + +DO $migration$ +BEGIN + IF EXISTS ( + SELECT 1 + FROM identity.client_installation + WHERE license_id IS NULL) THEN + RAISE EXCEPTION + 'Migration 004 cannot associate every existing installation with a license'; + END IF; +END +$migration$; + +UPDATE identity.client_installation + SET installation_uuid = installation_id + WHERE installation_uuid IS NULL; +UPDATE identity.client_installation + SET installation_name = installation_code + WHERE installation_name IS NULL; +UPDATE identity.client_installation + SET status = CASE WHEN enabled THEN 'ACTIVE' ELSE 'SUSPENDED' END + WHERE status IS NULL; +UPDATE identity.client_installation + SET registered_at = created_at + WHERE registered_at IS NULL; + +ALTER TABLE identity.client_installation + ALTER COLUMN license_id SET NOT NULL; +ALTER TABLE identity.client_installation + ALTER COLUMN installation_uuid SET NOT NULL; +ALTER TABLE identity.client_installation + ALTER COLUMN installation_name SET NOT NULL; +ALTER TABLE identity.client_installation + ALTER COLUMN status SET NOT NULL; +ALTER TABLE identity.client_installation + ALTER COLUMN status SET DEFAULT 'PENDING'; +ALTER TABLE identity.client_installation + ALTER COLUMN registered_at SET NOT NULL; +ALTER TABLE identity.client_installation + ALTER COLUMN registered_at SET DEFAULT now(); + +DO $migration$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM pg_constraint + WHERE conname = 'fk_identity_installation_tenant_license' + AND conrelid = 'identity.client_installation'::regclass) THEN + ALTER TABLE identity.client_installation + ADD CONSTRAINT fk_identity_installation_tenant_license + FOREIGN KEY (tenant_id, license_id) + REFERENCES identity.client_license (tenant_id, license_id) + NOT VALID; + END IF; + + IF NOT EXISTS ( + SELECT 1 FROM pg_constraint + WHERE conname = 'ck_identity_installation_status' + AND conrelid = 'identity.client_installation'::regclass) THEN + ALTER TABLE identity.client_installation + ADD CONSTRAINT ck_identity_installation_status + CHECK (status IN ( + 'PENDING', 'ACTIVE', 'SUSPENDED', 'DECOMMISSIONED', 'REVOKED', 'FAILED' + )) NOT VALID; + END IF; + + IF NOT EXISTS ( + SELECT 1 FROM pg_constraint + WHERE conname = 'ck_identity_installation_retirement' + AND conrelid = 'identity.client_installation'::regclass) THEN + ALTER TABLE identity.client_installation + ADD CONSTRAINT ck_identity_installation_retirement + CHECK ( + (status NOT IN ('DECOMMISSIONED', 'REVOKED') + AND retired_at IS NULL) + OR + (status IN ('DECOMMISSIONED', 'REVOKED') + AND retired_at IS NOT NULL + AND retirement_reason IS NOT NULL) + ) NOT VALID; + END IF; +END +$migration$; + +ALTER TABLE identity.client_installation + VALIDATE CONSTRAINT fk_identity_installation_tenant_license; +ALTER TABLE identity.client_installation + VALIDATE CONSTRAINT ck_identity_installation_status; +ALTER TABLE identity.client_installation + VALIDATE CONSTRAINT ck_identity_installation_retirement; + +CREATE UNIQUE INDEX IF NOT EXISTS uq_identity_installation_uuid + ON identity.client_installation (installation_uuid); +CREATE INDEX IF NOT EXISTS ix_identity_installation_capacity + ON identity.client_installation (tenant_id, license_id, status) + WHERE status IN ('PENDING', 'ACTIVE', 'SUSPENDED'); + +CREATE TABLE IF NOT EXISTS identity.license_activation_key ( + activation_key_id uuid PRIMARY KEY, + registration_id uuid NOT NULL, + tenant_id uuid NOT NULL, + license_id uuid NOT NULL, + key_hash character varying(255) NOT NULL, + key_hint character varying(16) NOT NULL, + status character varying(20) NOT NULL DEFAULT 'ACTIVE', + expires_at timestamp with time zone, + failed_attempts integer NOT NULL DEFAULT 0, + maximum_attempts integer NOT NULL DEFAULT 10, + locked_until timestamp with time zone, + created_at timestamp with time zone NOT NULL DEFAULT now(), + last_used_at timestamp with time zone, + created_by character varying(120) NOT NULL, + revoked_at timestamp with time zone, + revoked_by character varying(120), + revocation_reason character varying(500), + CONSTRAINT fk_identity_activation_key_registration + FOREIGN KEY (registration_id) + REFERENCES identity.client_registration_details (registration_id), + CONSTRAINT fk_identity_activation_key_registration_tenant + FOREIGN KEY (registration_id, tenant_id) + REFERENCES identity.client_account (registration_id, tenant_id), + CONSTRAINT fk_identity_activation_key_tenant_license + FOREIGN KEY (tenant_id, license_id) + REFERENCES identity.client_license (tenant_id, license_id), + CONSTRAINT uq_identity_activation_key_hash UNIQUE (key_hash), + CONSTRAINT ck_identity_activation_key_status + CHECK (status IN ('ACTIVE', 'LOCKED', 'REVOKED', 'EXPIRED')), + CONSTRAINT ck_identity_activation_key_attempts + CHECK (failed_attempts >= 0 + AND maximum_attempts > 0 + AND failed_attempts <= maximum_attempts), + CONSTRAINT ck_identity_activation_key_revocation + CHECK ((status <> 'REVOKED' AND revoked_at IS NULL) + OR (status = 'REVOKED' + AND revoked_at IS NOT NULL + AND revocation_reason IS NOT NULL)) +); + +CREATE INDEX IF NOT EXISTS ix_identity_activation_key_validation + ON identity.license_activation_key + (registration_id, tenant_id, license_id, status, expires_at); + +CREATE TABLE IF NOT EXISTS identity.installation_activation_session ( + activation_session_id uuid PRIMARY KEY, + activation_key_id uuid NOT NULL, + registration_id uuid NOT NULL, + tenant_id uuid NOT NULL, + license_id uuid NOT NULL, + installation_uuid uuid NOT NULL, + token_hash character varying(255) NOT NULL, + status character varying(20) NOT NULL DEFAULT 'PENDING', + expires_at timestamp with time zone NOT NULL, + created_at timestamp with time zone NOT NULL DEFAULT now(), + consumed_at timestamp with time zone, + source_ip inet, + installer_version character varying(40), + CONSTRAINT fk_identity_activation_session_key + FOREIGN KEY (activation_key_id) + REFERENCES identity.license_activation_key (activation_key_id), + CONSTRAINT fk_identity_activation_session_registration + FOREIGN KEY (registration_id) + REFERENCES identity.client_registration_details (registration_id), + CONSTRAINT fk_identity_activation_session_registration_tenant + FOREIGN KEY (registration_id, tenant_id) + REFERENCES identity.client_account (registration_id, tenant_id), + CONSTRAINT fk_identity_activation_session_tenant_license + FOREIGN KEY (tenant_id, license_id) + REFERENCES identity.client_license (tenant_id, license_id), + CONSTRAINT uq_identity_activation_session_token UNIQUE (token_hash), + CONSTRAINT uq_identity_activation_session_uuid UNIQUE (installation_uuid), + CONSTRAINT ck_identity_activation_session_status + CHECK (status IN ('PENDING', 'CONSUMED', 'EXPIRED', 'REVOKED')), + CONSTRAINT ck_identity_activation_session_period + CHECK (expires_at > created_at), + CONSTRAINT ck_identity_activation_session_consumed + CHECK ((status <> 'CONSUMED' AND consumed_at IS NULL) + OR (status = 'CONSUMED' AND consumed_at IS NOT NULL)) +); + +CREATE INDEX IF NOT EXISTS ix_identity_activation_session_pending + ON identity.installation_activation_session (tenant_id, expires_at) + WHERE status = 'PENDING'; + +CREATE TABLE IF NOT EXISTS identity.installation_audit_event ( + audit_event_id uuid PRIMARY KEY, + registration_id uuid NOT NULL, + tenant_id uuid NOT NULL, + license_id uuid, + installation_id uuid, + event_type character varying(50) NOT NULL, + actor_type character varying(30) NOT NULL, + actor_id character varying(160) NOT NULL, + reason character varying(500), + event_data jsonb NOT NULL DEFAULT '{}'::jsonb, + occurred_at timestamp with time zone NOT NULL DEFAULT now(), + CONSTRAINT fk_identity_installation_audit_registration + FOREIGN KEY (registration_id) + REFERENCES identity.client_registration_details (registration_id), + CONSTRAINT fk_identity_installation_audit_registration_tenant + FOREIGN KEY (registration_id, tenant_id) + REFERENCES identity.client_account (registration_id, tenant_id), + CONSTRAINT fk_identity_installation_audit_tenant_license + FOREIGN KEY (tenant_id, license_id) + REFERENCES identity.client_license (tenant_id, license_id), + CONSTRAINT fk_identity_installation_audit_installation + FOREIGN KEY (installation_id) + REFERENCES identity.client_installation (installation_id), + CONSTRAINT ck_identity_installation_audit_actor + CHECK (actor_type IN ('ADMIN', 'INSTALLER', 'SYSTEM', 'CLIENT')) +); + +CREATE INDEX IF NOT EXISTS ix_identity_installation_audit_tenant_time + ON identity.installation_audit_event (tenant_id, occurred_at DESC); +CREATE INDEX IF NOT EXISTS ix_identity_installation_audit_installation_time + ON identity.installation_audit_event (installation_id, occurred_at DESC) + WHERE installation_id IS NOT NULL; + +COMMIT; diff --git a/cygnus-cloud-service/src/test/java/com/cygnus/cloud/security/MachineTokenFlowTest.java b/cygnus-cloud-service/src/test/java/com/cygnus/cloud/security/MachineTokenFlowTest.java index 24040da..36c6f7d 100644 --- a/cygnus-cloud-service/src/test/java/com/cygnus/cloud/security/MachineTokenFlowTest.java +++ b/cygnus-cloud-service/src/test/java/com/cygnus/cloud/security/MachineTokenFlowTest.java @@ -108,6 +108,8 @@ class MachineTokenFlowTest { OffsetDateTime.now(ZoneOffset.UTC)); when(registrations.findCurrentLicense(installation.tenantId(), NOW)) .thenReturn(Mono.just(license)); + when(registrations.touchInstallation(installation.installationId(), NOW)) + .thenReturn(Mono.empty()); } @Test diff --git a/cygnus-cloud-service/src/test/java/com/cygnus/cloud/tenant/service/ActivationRateLimiterTest.java b/cygnus-cloud-service/src/test/java/com/cygnus/cloud/tenant/service/ActivationRateLimiterTest.java new file mode 100644 index 0000000..81aca1b --- /dev/null +++ b/cygnus-cloud-service/src/test/java/com/cygnus/cloud/tenant/service/ActivationRateLimiterTest.java @@ -0,0 +1,38 @@ +package com.cygnus.cloud.tenant.service; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.cygnus.cloud.cache.ReactiveCacheService; +import java.time.Duration; +import org.junit.jupiter.api.Test; +import reactor.core.publisher.Mono; + +class ActivationRateLimiterTest { + + @Test + void permitsAttemptsWithinLimit() { + ReactiveCacheService cache = mock(ReactiveCacheService.class); + when(cache.increment(anyString(), anyString(), any(Duration.class))) + .thenReturn(Mono.just(10L)); + ActivationRateLimiter limiter = + new ActivationRateLimiter(cache, 10, Duration.ofMinutes(10)); + assertDoesNotThrow(() -> limiter.check("127.0.0.1", "ACME").block()); + } + + @Test + void blocksAttemptsBeyondLimit() { + ReactiveCacheService cache = mock(ReactiveCacheService.class); + when(cache.increment(anyString(), anyString(), any(Duration.class))) + .thenReturn(Mono.just(11L)); + ActivationRateLimiter limiter = + new ActivationRateLimiter(cache, 10, Duration.ofMinutes(10)); + assertThrows( + ActivationRateLimitException.class, + () -> limiter.check("127.0.0.1", "ACME").block()); + } +} diff --git a/cygnus-cloud-service/src/test/java/com/cygnus/cloud/tenant/service/LicenseKeyServiceTest.java b/cygnus-cloud-service/src/test/java/com/cygnus/cloud/tenant/service/LicenseKeyServiceTest.java new file mode 100644 index 0000000..f4ea4e7 --- /dev/null +++ b/cygnus-cloud-service/src/test/java/com/cygnus/cloud/tenant/service/LicenseKeyServiceTest.java @@ -0,0 +1,154 @@ +package com.cygnus.cloud.tenant.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.cygnus.cloud.tenant.model.ActivationKeyStatus; +import com.cygnus.cloud.tenant.model.ClientStatus; +import com.cygnus.cloud.tenant.model.LicenseActivationContext; +import com.cygnus.cloud.tenant.model.LicenseActivationKey; +import com.cygnus.cloud.tenant.model.LicenseStatus; +import com.cygnus.cloud.tenant.model.RegistrationStatus; +import com.cygnus.cloud.tenant.repository.LicenseActivationRepository; +import java.security.SecureRandom; +import java.time.Clock; +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.util.UUID; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +class LicenseKeyServiceTest { + + private static final Instant NOW = Instant.parse("2026-07-26T10:00:00Z"); + + private LicenseActivationRepository repository; + private LicenseKeyService service; + private BCryptPasswordEncoder encoder; + + @BeforeEach + void setUp() { + repository = mock(LicenseActivationRepository.class); + encoder = new BCryptPasswordEncoder(4); + service = new LicenseKeyService( + repository, + Clock.fixed(NOW, ZoneOffset.UTC), + encoder, + new SecureRandom(new byte[] {1, 2, 3, 4})); + } + + @Test + void issuesReadableKeyButPersistsOnlyHash() { + when(repository.insert( + any(), any(), any(), anyString(), anyString(), any(), anyString())) + .thenReturn(Mono.just(true)); + + StepVerifier.create(service.issue( + UUID.randomUUID(), + UUID.randomUUID(), + OffsetDateTime.parse("2026-08-26T10:00:00Z"), + "admin")) + .assertNext(issued -> { + assertThat(issued.licenseKey()) + .matches("CYGNUS-[A-F0-9]{8}(?:-[A-F0-9]{8}){4}"); + assertThat(issued.keyHint()).hasSize(8); + }) + .verifyComplete(); + + verify(repository).insert( + any(), any(), any(), anyString(), anyString(), any(), eq("admin")); + } + + @Test + void validatesActiveKeyWhenInstallationCapacityExists() { + String plaintext = "CYGNUS-01234567-89ABCDEF-01234567-89ABCDEF-01234567"; + LicenseActivationContext context = context( + plaintext, LicenseStatus.ACTIVE, 2, 1); + when(repository.findCandidates("MATRIX", "01234567")) + .thenReturn(Flux.just(context)); + when(repository.recordSuccess(any(), any())).thenReturn(Mono.just(true)); + + StepVerifier.create(service.validate("matrix", plaintext)) + .expectNext(context) + .verifyComplete(); + } + + @Test + void rejectsValidKeyWhenCapacityIsExhausted() { + String plaintext = "CYGNUS-01234567-89ABCDEF-01234567-89ABCDEF-01234567"; + when(repository.findCandidates("MATRIX", "01234567")) + .thenReturn(Flux.just(context( + plaintext, LicenseStatus.ACTIVE, 2, 2))); + + StepVerifier.create(service.validate("matrix", plaintext)) + .expectErrorMatches(error -> error instanceof LicenseKeyException + && error.getMessage().contains("installation limit")) + .verify(); + } + + @Test + void recordsRejectedAttemptWhenHintMatchesButHashDoesNot() { + String correct = "CYGNUS-AAAAAAAA-AAAAAAAA-AAAAAAAA-AAAAAAAA-01234567"; + String wrong = "CYGNUS-BBBBBBBB-BBBBBBBB-BBBBBBBB-BBBBBBBB-01234567"; + LicenseActivationContext context = context( + correct, LicenseStatus.ACTIVE, 2, 0); + when(repository.findCandidates("MATRIX", "01234567")) + .thenReturn(Flux.just(context)); + when(repository.recordFailure(any(), any())).thenReturn(Mono.just(true)); + + StepVerifier.create(service.validate("MATRIX", wrong)) + .expectError(LicenseKeyException.class) + .verify(); + + verify(repository).recordFailure( + eq(context.activationKey().activationKeyId()), any()); + } + + private LicenseActivationContext context( + String plaintext, + LicenseStatus licenseStatus, + int maximum, + int consuming) { + UUID registrationId = UUID.randomUUID(); + UUID tenantId = UUID.randomUUID(); + UUID licenseId = UUID.randomUUID(); + LicenseActivationKey key = new LicenseActivationKey( + UUID.randomUUID(), + registrationId, + tenantId, + licenseId, + encoder.encode(plaintext), + "01234567", + ActivationKeyStatus.ACTIVE, + OffsetDateTime.parse("2026-08-26T10:00:00Z"), + 0, + 10, + null, + OffsetDateTime.parse("2026-07-01T10:00:00Z"), + null); + return new LicenseActivationContext( + key, + "MATRIX", + RegistrationStatus.ACTIVE, + ClientStatus.ACTIVE, + "matrix", + "Matrix", + "PROFESSIONAL", + "YEARLY", + licenseStatus, + NOW.minusSeconds(60), + NOW.plusSeconds(3600), + maximum, + consuming); + } +} diff --git a/cygnus-cloud-service/target/classes/application.yml b/cygnus-cloud-service/target/classes/application.yml index 0225ebc..e8773dd 100644 --- a/cygnus-cloud-service/target/classes/application.yml +++ b/cygnus-cloud-service/target/classes/application.yml @@ -1,6 +1,17 @@ spring: application: name: cygnus-cloud-service + mail: + host: ${CYGNUS_MAIL_HOST:} + port: ${CYGNUS_MAIL_PORT:587} + username: ${CYGNUS_MAIL_USERNAME:} + password: ${CYGNUS_MAIL_PASSWORD:} + properties: + mail: + smtp: + auth: ${CYGNUS_MAIL_SMTP_AUTH:true} + starttls: + enable: ${CYGNUS_MAIL_STARTTLS:true} data: redis: host: ${REDIS_HOST:192.168.0.111} @@ -36,7 +47,6 @@ cygnus: assertion-decryption-private-key: ${CYGNUS_ASSERTION_DECRYPTION_PRIVATE_KEY:file:./config/keys/assertion-decryption-private.pem} access-token-private-key: ${CYGNUS_ACCESS_TOKEN_PRIVATE_KEY:file:./config/keys/access-token-private.pem} access-token-public-key: ${CYGNUS_ACCESS_TOKEN_PUBLIC_KEY:file:./config/keys/access-token-public.pem} - clients: {} login-encryption: key-id: ${CYGNUS_LOGIN_KEY_ID:cygnus-login-2026-01} private-key-location: ${CYGNUS_LOGIN_PRIVATE_KEY:file:./config/keys/login-private.pem} @@ -44,6 +54,11 @@ cygnus: cache: key-prefix: ${CYGNUS_CACHE_PREFIX:cygnus} default-ttl: ${CYGNUS_CACHE_TTL:10m} + registration-email: + from: ${CYGNUS_REGISTRATION_EMAIL_FROM:noreply@cygnus.invalid} + activation-rate-limit: + maximum-attempts: ${CYGNUS_ACTIVATION_MAX_ATTEMPTS:10} + window: ${CYGNUS_ACTIVATION_RATE_WINDOW:10m} server: port: ${CYGNUS_CLOUD_PORT:8090} diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/CygnusCloudServiceApplication.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/CygnusCloudServiceApplication.class index 801c4eb..7232b49 100644 Binary files a/cygnus-cloud-service/target/classes/com/cygnus/cloud/CygnusCloudServiceApplication.class and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/CygnusCloudServiceApplication.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/cache/CacheProperties.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/cache/CacheProperties.class index cc07f55..14d9048 100644 Binary files a/cygnus-cloud-service/target/classes/com/cygnus/cloud/cache/CacheProperties.class and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/cache/CacheProperties.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/cache/ReactiveCacheService.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/cache/ReactiveCacheService.class index 850d98d..282b21a 100644 Binary files a/cygnus-cloud-service/target/classes/com/cygnus/cloud/cache/ReactiveCacheService.class and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/cache/ReactiveCacheService.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/database/DatabaseProperties.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/database/DatabaseProperties.class index 64bd9cb..002d601 100644 Binary files a/cygnus-cloud-service/target/classes/com/cygnus/cloud/database/DatabaseProperties.class and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/database/DatabaseProperties.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/database/ReactiveDatabaseClient.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/database/ReactiveDatabaseClient.class index ce1283b..2c39e49 100644 Binary files a/cygnus-cloud-service/target/classes/com/cygnus/cloud/database/ReactiveDatabaseClient.class and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/database/ReactiveDatabaseClient.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/database/VertxDatabaseConfiguration.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/database/VertxDatabaseConfiguration.class index 2149851..9842cb4 100644 Binary files a/cygnus-cloud-service/target/classes/com/cygnus/cloud/database/VertxDatabaseConfiguration.class and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/database/VertxDatabaseConfiguration.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/identity/api/CloudLoginController.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/identity/api/CloudLoginController.class index 19b85b6..ab20dfd 100644 Binary files a/cygnus-cloud-service/target/classes/com/cygnus/cloud/identity/api/CloudLoginController.class and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/identity/api/CloudLoginController.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/identity/api/EncryptedLoginRequest.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/identity/api/EncryptedLoginRequest.class index 57beea7..e0697fb 100644 Binary files a/cygnus-cloud-service/target/classes/com/cygnus/cloud/identity/api/EncryptedLoginRequest.class and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/identity/api/EncryptedLoginRequest.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/identity/api/IdentityErrorHandler.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/identity/api/IdentityErrorHandler.class index 6348aae..199276c 100644 Binary files a/cygnus-cloud-service/target/classes/com/cygnus/cloud/identity/api/IdentityErrorHandler.class and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/identity/api/IdentityErrorHandler.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/identity/api/LoginEncryptionProperties.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/identity/api/LoginEncryptionProperties.class index 9ffa7bc..720fb0a 100644 Binary files a/cygnus-cloud-service/target/classes/com/cygnus/cloud/identity/api/LoginEncryptionProperties.class and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/identity/api/LoginEncryptionProperties.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/identity/api/LoginPayload.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/identity/api/LoginPayload.class index e93d93f..036d227 100644 Binary files a/cygnus-cloud-service/target/classes/com/cygnus/cloud/identity/api/LoginPayload.class and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/identity/api/LoginPayload.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/identity/api/RsaLoginPayloadDecryptor.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/identity/api/RsaLoginPayloadDecryptor.class index 41cf73c..c7344c3 100644 Binary files a/cygnus-cloud-service/target/classes/com/cygnus/cloud/identity/api/RsaLoginPayloadDecryptor.class and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/identity/api/RsaLoginPayloadDecryptor.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/identity/model/AuthenticatedIdentity.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/identity/model/AuthenticatedIdentity.class index 9f47ce9..49fc6dd 100644 Binary files a/cygnus-cloud-service/target/classes/com/cygnus/cloud/identity/model/AuthenticatedIdentity.class and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/identity/model/AuthenticatedIdentity.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/identity/model/IdentityUser.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/identity/model/IdentityUser.class index e1ebe7a..0083642 100644 Binary files a/cygnus-cloud-service/target/classes/com/cygnus/cloud/identity/model/IdentityUser.class and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/identity/model/IdentityUser.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/identity/model/MenuItem.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/identity/model/MenuItem.class index 2c7384e..ea03570 100644 Binary files a/cygnus-cloud-service/target/classes/com/cygnus/cloud/identity/model/MenuItem.class and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/identity/model/MenuItem.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/identity/repository/IdentityRepository.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/identity/repository/IdentityRepository.class index 981f437..008de9a 100644 Binary files a/cygnus-cloud-service/target/classes/com/cygnus/cloud/identity/repository/IdentityRepository.class and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/identity/repository/IdentityRepository.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/identity/repository/IdentityRowMapper.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/identity/repository/IdentityRowMapper.class index 940864b..cda91da 100644 Binary files a/cygnus-cloud-service/target/classes/com/cygnus/cloud/identity/repository/IdentityRowMapper.class and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/identity/repository/IdentityRowMapper.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/identity/service/AuthenticationException.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/identity/service/AuthenticationException.class index cbba308..b17d372 100644 Binary files a/cygnus-cloud-service/target/classes/com/cygnus/cloud/identity/service/AuthenticationException.class and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/identity/service/AuthenticationException.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/identity/service/IdentityAuthenticationService.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/identity/service/IdentityAuthenticationService.class index a76cbf3..2d7d149 100644 Binary files a/cygnus-cloud-service/target/classes/com/cygnus/cloud/identity/service/IdentityAuthenticationService.class and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/identity/service/IdentityAuthenticationService.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/identity/service/LegacyPasswordVerifier.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/identity/service/LegacyPasswordVerifier.class index a5274af..c72e3e9 100644 Binary files a/cygnus-cloud-service/target/classes/com/cygnus/cloud/identity/service/LegacyPasswordVerifier.class and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/identity/service/LegacyPasswordVerifier.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/identity/service/LoginRequestReplayService.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/identity/service/LoginRequestReplayService.class index 8637ef2..d382a24 100644 Binary files a/cygnus-cloud-service/target/classes/com/cygnus/cloud/identity/service/LoginRequestReplayService.class and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/identity/service/LoginRequestReplayService.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/security/AccessTokenIssuer.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/security/AccessTokenIssuer.class index 9534b3f..903c7cd 100644 Binary files a/cygnus-cloud-service/target/classes/com/cygnus/cloud/security/AccessTokenIssuer.class and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/security/AccessTokenIssuer.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/security/AudienceValidator.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/security/AudienceValidator.class index 2ec1eba..9cfa8ae 100644 Binary files a/cygnus-cloud-service/target/classes/com/cygnus/cloud/security/AudienceValidator.class and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/security/AudienceValidator.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/security/ClientAssertionValidator.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/security/ClientAssertionValidator.class index 77a3523..929c6fa 100644 Binary files a/cygnus-cloud-service/target/classes/com/cygnus/cloud/security/ClientAssertionValidator.class and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/security/ClientAssertionValidator.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/security/CloudSecurityConfiguration.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/security/CloudSecurityConfiguration.class index c7bc89d..0592fc5 100644 Binary files a/cygnus-cloud-service/target/classes/com/cygnus/cloud/security/CloudSecurityConfiguration.class and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/security/CloudSecurityConfiguration.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/security/CommunicationSecurityProperties$MachineClient.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/security/CommunicationSecurityProperties$MachineClient.class deleted file mode 100644 index bf8c094..0000000 Binary files a/cygnus-cloud-service/target/classes/com/cygnus/cloud/security/CommunicationSecurityProperties$MachineClient.class and /dev/null differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/security/CommunicationSecurityProperties.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/security/CommunicationSecurityProperties.class index d923c4b..00d66d3 100644 Binary files a/cygnus-cloud-service/target/classes/com/cygnus/cloud/security/CommunicationSecurityProperties.class and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/security/CommunicationSecurityProperties.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/security/JwtReplayProtectionService.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/security/JwtReplayProtectionService.class index 0d152c4..afa553c 100644 Binary files a/cygnus-cloud-service/target/classes/com/cygnus/cloud/security/JwtReplayProtectionService.class and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/security/JwtReplayProtectionService.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/security/MachineAuthenticationException.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/security/MachineAuthenticationException.class index 41d4cc5..bb5b347 100644 Binary files a/cygnus-cloud-service/target/classes/com/cygnus/cloud/security/MachineAuthenticationException.class and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/security/MachineAuthenticationException.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/security/MachineClientPrincipal.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/security/MachineClientPrincipal.class index 17b6fa7..cbbe4a0 100644 Binary files a/cygnus-cloud-service/target/classes/com/cygnus/cloud/security/MachineClientPrincipal.class and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/security/MachineClientPrincipal.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/security/MachineSecurityConfigurationValidator.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/security/MachineSecurityConfigurationValidator.class index bacf18d..3d4e646 100644 Binary files a/cygnus-cloud-service/target/classes/com/cygnus/cloud/security/MachineSecurityConfigurationValidator.class and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/security/MachineSecurityConfigurationValidator.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/security/MachineTokenController.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/security/MachineTokenController.class index 1a1676a..b03152a 100644 Binary files a/cygnus-cloud-service/target/classes/com/cygnus/cloud/security/MachineTokenController.class and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/security/MachineTokenController.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/security/MachineTokenErrorHandler.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/security/MachineTokenErrorHandler.class index f2af0a0..e05b867 100644 Binary files a/cygnus-cloud-service/target/classes/com/cygnus/cloud/security/MachineTokenErrorHandler.class and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/security/MachineTokenErrorHandler.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/security/PemKeyLoader.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/security/PemKeyLoader.class index 4d7a186..fc899d3 100644 Binary files a/cygnus-cloud-service/target/classes/com/cygnus/cloud/security/PemKeyLoader.class and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/security/PemKeyLoader.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/security/TokenResponse.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/security/TokenResponse.class index 1469b25..5937c7f 100644 Binary files a/cygnus-cloud-service/target/classes/com/cygnus/cloud/security/TokenResponse.class and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/security/TokenResponse.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/system/SystemConfiguration.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/system/SystemConfiguration.class index 64c4175..872d50d 100644 Binary files a/cygnus-cloud-service/target/classes/com/cygnus/cloud/system/SystemConfiguration.class and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/system/SystemConfiguration.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/system/SystemInfoController.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/system/SystemInfoController.class index 33bfdff..683f7eb 100644 Binary files a/cygnus-cloud-service/target/classes/com/cygnus/cloud/system/SystemInfoController.class and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/system/SystemInfoController.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/system/SystemInfoResponse.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/system/SystemInfoResponse.class index afc2748..1610c7d 100644 Binary files a/cygnus-cloud-service/target/classes/com/cygnus/cloud/system/SystemInfoResponse.class and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/system/SystemInfoResponse.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/api/ActivationValidationRequest.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/api/ActivationValidationRequest.class new file mode 100644 index 0000000..c85bdf3 Binary files /dev/null and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/api/ActivationValidationRequest.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/api/ActivationValidationResponse.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/api/ActivationValidationResponse.class new file mode 100644 index 0000000..5fbac16 Binary files /dev/null and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/api/ActivationValidationResponse.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/api/ClientAdministrationController.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/api/ClientAdministrationController.class new file mode 100644 index 0000000..f78950c Binary files /dev/null and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/api/ClientAdministrationController.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/api/CreateClientRegistrationRequest.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/api/CreateClientRegistrationRequest.class new file mode 100644 index 0000000..9d3bb8a Binary files /dev/null and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/api/CreateClientRegistrationRequest.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/api/CreateLicenseRequest.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/api/CreateLicenseRequest.class new file mode 100644 index 0000000..f3fc207 Binary files /dev/null and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/api/CreateLicenseRequest.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/api/CreateTenantRequest.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/api/CreateTenantRequest.class new file mode 100644 index 0000000..9ef4c1b Binary files /dev/null and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/api/CreateTenantRequest.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/api/InstallationActivationController.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/api/InstallationActivationController.class new file mode 100644 index 0000000..5a48e26 Binary files /dev/null and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/api/InstallationActivationController.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/api/InstallationActivationErrorHandler.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/api/InstallationActivationErrorHandler.class new file mode 100644 index 0000000..8acf66d Binary files /dev/null and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/api/InstallationActivationErrorHandler.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/api/InstallationRegistrationRequest.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/api/InstallationRegistrationRequest.class new file mode 100644 index 0000000..d96f42d Binary files /dev/null and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/api/InstallationRegistrationRequest.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/api/IssueLicenseKeyRequest.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/api/IssueLicenseKeyRequest.class new file mode 100644 index 0000000..bcda0a2 Binary files /dev/null and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/api/IssueLicenseKeyRequest.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/api/RetireInstallationRequest.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/api/RetireInstallationRequest.class new file mode 100644 index 0000000..76059c2 Binary files /dev/null and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/api/RetireInstallationRequest.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/model/ActivationKeyStatus.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/model/ActivationKeyStatus.class new file mode 100644 index 0000000..2c22d81 Binary files /dev/null and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/model/ActivationKeyStatus.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/model/ActivationSession.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/model/ActivationSession.class new file mode 100644 index 0000000..ac29f97 Binary files /dev/null and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/model/ActivationSession.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/model/ClientAccount.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/model/ClientAccount.class new file mode 100644 index 0000000..4c7a1a4 Binary files /dev/null and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/model/ClientAccount.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/model/ClientInstallation.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/model/ClientInstallation.class new file mode 100644 index 0000000..d36b9d6 Binary files /dev/null and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/model/ClientInstallation.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/model/ClientLicense.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/model/ClientLicense.class new file mode 100644 index 0000000..17c1404 Binary files /dev/null and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/model/ClientLicense.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/model/ClientRegistration.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/model/ClientRegistration.class new file mode 100644 index 0000000..e788b98 Binary files /dev/null and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/model/ClientRegistration.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/model/ClientStatus.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/model/ClientStatus.class new file mode 100644 index 0000000..2edcf80 Binary files /dev/null and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/model/ClientStatus.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/model/InstallationLifecycleResult.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/model/InstallationLifecycleResult.class new file mode 100644 index 0000000..160c4d9 Binary files /dev/null and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/model/InstallationLifecycleResult.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/model/IssuedLicenseKey.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/model/IssuedLicenseKey.class new file mode 100644 index 0000000..90f28d7 Binary files /dev/null and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/model/IssuedLicenseKey.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/model/LicenseActivationContext.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/model/LicenseActivationContext.class new file mode 100644 index 0000000..8b25d52 Binary files /dev/null and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/model/LicenseActivationContext.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/model/LicenseActivationKey.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/model/LicenseActivationKey.class new file mode 100644 index 0000000..85f08ed Binary files /dev/null and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/model/LicenseActivationKey.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/model/LicenseDeliveryDetails.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/model/LicenseDeliveryDetails.class new file mode 100644 index 0000000..83e5e57 Binary files /dev/null and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/model/LicenseDeliveryDetails.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/model/LicenseStatus.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/model/LicenseStatus.class new file mode 100644 index 0000000..f2f2535 Binary files /dev/null and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/model/LicenseStatus.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/model/RegisteredInstallation.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/model/RegisteredInstallation.class new file mode 100644 index 0000000..f83d393 Binary files /dev/null and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/model/RegisteredInstallation.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/model/RegistrationStatus.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/model/RegistrationStatus.class new file mode 100644 index 0000000..7714783 Binary files /dev/null and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/model/RegistrationStatus.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/repository/ClientAdministrationRepository.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/repository/ClientAdministrationRepository.class new file mode 100644 index 0000000..efa25a7 Binary files /dev/null and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/repository/ClientAdministrationRepository.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/repository/InstallationActivationRepository$RegistrationContext.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/repository/InstallationActivationRepository$RegistrationContext.class new file mode 100644 index 0000000..fb5ab3f Binary files /dev/null and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/repository/InstallationActivationRepository$RegistrationContext.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/repository/InstallationActivationRepository.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/repository/InstallationActivationRepository.class new file mode 100644 index 0000000..e229fbd Binary files /dev/null and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/repository/InstallationActivationRepository.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/repository/InstallationLifecycleRepository.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/repository/InstallationLifecycleRepository.class new file mode 100644 index 0000000..9564477 Binary files /dev/null and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/repository/InstallationLifecycleRepository.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/repository/LicenseActivationRepository.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/repository/LicenseActivationRepository.class new file mode 100644 index 0000000..cb63f8d Binary files /dev/null and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/repository/LicenseActivationRepository.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/repository/TenantRegistrationRepository.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/repository/TenantRegistrationRepository.class new file mode 100644 index 0000000..60c07b9 Binary files /dev/null and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/repository/TenantRegistrationRepository.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/service/ActivationRateLimitException.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/service/ActivationRateLimitException.class new file mode 100644 index 0000000..14e21f6 Binary files /dev/null and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/service/ActivationRateLimitException.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/service/ActivationRateLimiter.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/service/ActivationRateLimiter.class new file mode 100644 index 0000000..6eb31cd Binary files /dev/null and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/service/ActivationRateLimiter.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/service/InstallationActivationException.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/service/InstallationActivationException.class new file mode 100644 index 0000000..6fef5c0 Binary files /dev/null and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/service/InstallationActivationException.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/service/InstallationActivationService.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/service/InstallationActivationService.class new file mode 100644 index 0000000..2d26de7 Binary files /dev/null and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/service/InstallationActivationService.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/service/InstallationLifecycleService.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/service/InstallationLifecycleService.class new file mode 100644 index 0000000..5d837f4 Binary files /dev/null and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/service/InstallationLifecycleService.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/service/LicenseKeyException.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/service/LicenseKeyException.class new file mode 100644 index 0000000..c61f076 Binary files /dev/null and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/service/LicenseKeyException.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/service/LicenseKeyService.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/service/LicenseKeyService.class new file mode 100644 index 0000000..0df9cf1 Binary files /dev/null and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/service/LicenseKeyService.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/service/RegistrationEmailService.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/service/RegistrationEmailService.class new file mode 100644 index 0000000..086146b Binary files /dev/null and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/service/RegistrationEmailService.class differ diff --git a/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/service/TenantRegistrationService.class b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/service/TenantRegistrationService.class new file mode 100644 index 0000000..0771967 Binary files /dev/null and b/cygnus-cloud-service/target/classes/com/cygnus/cloud/tenant/service/TenantRegistrationService.class differ diff --git a/cygnus-cloud-service/target/classes/db/identity/002_multitenant_licensing.sql b/cygnus-cloud-service/target/classes/db/identity/002_multitenant_licensing.sql new file mode 100644 index 0000000..99d7db4 --- /dev/null +++ b/cygnus-cloud-service/target/classes/db/identity/002_multitenant_licensing.sql @@ -0,0 +1,153 @@ +BEGIN; + +-- DMLP phase 2: introduce the tenant, installation and license source of truth. +-- The fixed legacy tenant is a migration bridge for the identity data copied by +-- 001_identity_login_schema.sql. Replace its migration license through the +-- administration workflow before commercial enforcement is enabled. + +CREATE TABLE IF NOT EXISTS identity.client_account ( + tenant_id uuid PRIMARY KEY, + client_slug character varying(80) NOT NULL, + client_name character varying(200) NOT NULL, + status character varying(20) NOT NULL, + created_at timestamp with time zone NOT NULL DEFAULT now(), + updated_at timestamp with time zone NOT NULL DEFAULT now(), + CONSTRAINT uq_identity_client_account_slug UNIQUE (client_slug), + CONSTRAINT ck_identity_client_account_slug + CHECK (client_slug ~ '^[a-z0-9]+(?:-[a-z0-9]+)*$'), + CONSTRAINT ck_identity_client_account_status + CHECK (status IN ('ACTIVE', 'SUSPENDED', 'CANCELLED')) +); + +CREATE TABLE IF NOT EXISTS identity.client_installation ( + installation_id uuid PRIMARY KEY, + tenant_id uuid NOT NULL, + client_id character varying(80) NOT NULL, + installation_code character varying(100) NOT NULL, + assertion_public_key text NOT NULL, + allowed_scopes text[] NOT NULL DEFAULT ARRAY[]::text[], + enabled boolean NOT NULL DEFAULT true, + security_version integer NOT NULL DEFAULT 1, + last_authenticated_at timestamp with time zone, + created_at timestamp with time zone NOT NULL DEFAULT now(), + updated_at timestamp with time zone NOT NULL DEFAULT now(), + CONSTRAINT fk_identity_installation_tenant + FOREIGN KEY (tenant_id) + REFERENCES identity.client_account (tenant_id), + CONSTRAINT uq_identity_installation_client_code + UNIQUE (client_id, installation_code), + CONSTRAINT ck_identity_installation_client_id + CHECK (client_id ~ '^[A-Za-z0-9][A-Za-z0-9_-]*$'), + CONSTRAINT ck_identity_installation_code + CHECK (installation_code ~ '^[A-Za-z0-9][A-Za-z0-9_-]*$'), + CONSTRAINT ck_identity_installation_security_version + CHECK (security_version > 0) +); + +CREATE TABLE IF NOT EXISTS identity.client_license ( + license_id uuid PRIMARY KEY, + tenant_id uuid NOT NULL, + license_type character varying(30) NOT NULL, + package_code character varying(50) NOT NULL, + valid_from timestamp with time zone NOT NULL, + valid_until timestamp with time zone NOT NULL, + status character varying(20) NOT NULL, + max_users integer, + max_installations integer, + created_at timestamp with time zone NOT NULL DEFAULT now(), + updated_at timestamp with time zone NOT NULL DEFAULT now(), + CONSTRAINT fk_identity_license_tenant + FOREIGN KEY (tenant_id) + REFERENCES identity.client_account (tenant_id), + CONSTRAINT ck_identity_license_period + CHECK (valid_until > valid_from), + CONSTRAINT ck_identity_license_status + CHECK (status IN ('ACTIVE', 'SUSPENDED', 'EXPIRED', 'CANCELLED')), + CONSTRAINT ck_identity_license_limits + CHECK ((max_users IS NULL OR max_users > 0) + AND (max_installations IS NULL OR max_installations > 0)) +); + +CREATE INDEX IF NOT EXISTS ix_identity_installation_tenant_enabled + ON identity.client_installation (tenant_id, enabled); +CREATE INDEX IF NOT EXISTS ix_identity_installation_lookup + ON identity.client_installation (client_id, installation_code, enabled); +CREATE INDEX IF NOT EXISTS ix_identity_license_tenant_period + ON identity.client_license (tenant_id, status, valid_from, valid_until); + +-- Add tenant ownership without changing any legacy primary-key values. +ALTER TABLE identity.company + ADD COLUMN IF NOT EXISTS tenant_id uuid; +ALTER TABLE identity.company_branch + ADD COLUMN IF NOT EXISTS tenant_id uuid; +ALTER TABLE identity.user_group + ADD COLUMN IF NOT EXISTS tenant_id uuid; +ALTER TABLE identity.app_user + ADD COLUMN IF NOT EXISTS tenant_id uuid; +ALTER TABLE identity.permission + ADD COLUMN IF NOT EXISTS tenant_id uuid; +ALTER TABLE identity.denied_pages + ADD COLUMN IF NOT EXISTS tenant_id uuid; +ALTER TABLE identity.user_loginhistory + ADD COLUMN IF NOT EXISTS tenant_id uuid; + +-- Seed a stable bridge tenant for all identity data that already exists. +INSERT INTO identity.client_account + (tenant_id, client_slug, client_name, status) +VALUES ('00000000-0000-4000-8000-000000000001', 'matrix', 'Matrix', 'ACTIVE') +ON CONFLICT (tenant_id) DO UPDATE SET + client_slug = EXCLUDED.client_slug, + client_name = EXCLUDED.client_name, + updated_at = now(); + +-- A non-expiring migration bridge keeps current users operational. It must be +-- replaced by a commercial license before license administration goes live. +INSERT INTO identity.client_license + (license_id, tenant_id, license_type, package_code, + valid_from, valid_until, status) +VALUES ('00000000-0000-4000-8000-000000000002', + '00000000-0000-4000-8000-000000000001', + 'MIGRATION', 'LEGACY_FULL', + '2020-01-01 00:00:00+00', '2099-12-31 23:59:59+00', 'ACTIVE') +ON CONFLICT (license_id) DO NOTHING; + +UPDATE identity.company + SET tenant_id = '00000000-0000-4000-8000-000000000001' + WHERE tenant_id IS NULL; +UPDATE identity.company_branch + SET tenant_id = '00000000-0000-4000-8000-000000000001' + WHERE tenant_id IS NULL; +UPDATE identity.user_group + SET tenant_id = '00000000-0000-4000-8000-000000000001' + WHERE tenant_id IS NULL; +UPDATE identity.app_user + SET tenant_id = '00000000-0000-4000-8000-000000000001' + WHERE tenant_id IS NULL; +UPDATE identity.permission + SET tenant_id = '00000000-0000-4000-8000-000000000001' + WHERE tenant_id IS NULL; +UPDATE identity.denied_pages + SET tenant_id = '00000000-0000-4000-8000-000000000001' + WHERE tenant_id IS NULL; +UPDATE identity.user_loginhistory + SET tenant_id = '00000000-0000-4000-8000-000000000001' + WHERE tenant_id IS NULL; + +CREATE INDEX IF NOT EXISTS ix_identity_company_tenant + ON identity.company (tenant_id, company_id); +CREATE INDEX IF NOT EXISTS ix_identity_branch_tenant + ON identity.company_branch (tenant_id, company_id, branch_id); +CREATE INDEX IF NOT EXISTS ix_identity_group_tenant + ON identity.user_group (tenant_id, group_id); +CREATE INDEX IF NOT EXISTS ix_identity_user_tenant_login + ON identity.app_user (tenant_id, upper(loginid)); +CREATE INDEX IF NOT EXISTS ix_identity_permission_tenant_group_page + ON identity.permission (tenant_id, group_id, page_id) + WHERE permission <> '000'; +CREATE INDEX IF NOT EXISTS ix_identity_denied_tenant_user_page + ON identity.denied_pages (tenant_id, user_id, page_id) + WHERE isdenied = 1; +CREATE INDEX IF NOT EXISTS ix_identity_login_history_tenant_user_time + ON identity.user_loginhistory (tenant_id, user_id, logintime DESC); + +COMMIT; diff --git a/cygnus-cloud-service/target/classes/db/identity/003_tenant_isolation_constraints.sql b/cygnus-cloud-service/target/classes/db/identity/003_tenant_isolation_constraints.sql new file mode 100644 index 0000000..8bf85f5 --- /dev/null +++ b/cygnus-cloud-service/target/classes/db/identity/003_tenant_isolation_constraints.sql @@ -0,0 +1,231 @@ +BEGIN; + +-- DMLP phase 3: make tenant ownership mandatory and enforce same-tenant +-- relationships without changing any legacy primary keys. + +ALTER TABLE identity.company + ALTER COLUMN tenant_id SET NOT NULL; +ALTER TABLE identity.company_branch + ALTER COLUMN tenant_id SET NOT NULL; +ALTER TABLE identity.user_group + ALTER COLUMN tenant_id SET NOT NULL; +ALTER TABLE identity.app_user + ALTER COLUMN tenant_id SET NOT NULL; +ALTER TABLE identity.permission + ALTER COLUMN tenant_id SET NOT NULL; +ALTER TABLE identity.denied_pages + ALTER COLUMN tenant_id SET NOT NULL; +ALTER TABLE identity.user_loginhistory + ALTER COLUMN tenant_id SET NOT NULL; + +CREATE UNIQUE INDEX IF NOT EXISTS uq_identity_company_tenant_id + ON identity.company (tenant_id, company_id); +CREATE UNIQUE INDEX IF NOT EXISTS uq_identity_branch_tenant_id + ON identity.company_branch (tenant_id, branch_id, company_id); +CREATE UNIQUE INDEX IF NOT EXISTS uq_identity_group_tenant_id + ON identity.user_group (tenant_id, group_id); +CREATE UNIQUE INDEX IF NOT EXISTS uq_identity_user_tenant_id + ON identity.app_user (tenant_id, user_id); + +DO $migration$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM pg_constraint + WHERE conname = 'fk_identity_company_tenant' + AND conrelid = 'identity.company'::regclass) THEN + ALTER TABLE identity.company + ADD CONSTRAINT fk_identity_company_tenant + FOREIGN KEY (tenant_id) + REFERENCES identity.client_account (tenant_id) + NOT VALID; + END IF; + + IF NOT EXISTS ( + SELECT 1 FROM pg_constraint + WHERE conname = 'fk_identity_branch_tenant' + AND conrelid = 'identity.company_branch'::regclass) THEN + ALTER TABLE identity.company_branch + ADD CONSTRAINT fk_identity_branch_tenant + FOREIGN KEY (tenant_id) + REFERENCES identity.client_account (tenant_id) + NOT VALID; + END IF; + + IF NOT EXISTS ( + SELECT 1 FROM pg_constraint + WHERE conname = 'fk_identity_branch_company_tenant' + AND conrelid = 'identity.company_branch'::regclass) THEN + ALTER TABLE identity.company_branch + ADD CONSTRAINT fk_identity_branch_company_tenant + FOREIGN KEY (tenant_id, company_id) + REFERENCES identity.company (tenant_id, company_id) + NOT VALID; + END IF; + + IF NOT EXISTS ( + SELECT 1 FROM pg_constraint + WHERE conname = 'fk_identity_group_tenant' + AND conrelid = 'identity.user_group'::regclass) THEN + ALTER TABLE identity.user_group + ADD CONSTRAINT fk_identity_group_tenant + FOREIGN KEY (tenant_id) + REFERENCES identity.client_account (tenant_id) + NOT VALID; + END IF; + + IF NOT EXISTS ( + SELECT 1 FROM pg_constraint + WHERE conname = 'fk_identity_user_tenant' + AND conrelid = 'identity.app_user'::regclass) THEN + ALTER TABLE identity.app_user + ADD CONSTRAINT fk_identity_user_tenant + FOREIGN KEY (tenant_id) + REFERENCES identity.client_account (tenant_id) + NOT VALID; + END IF; + + IF NOT EXISTS ( + SELECT 1 FROM pg_constraint + WHERE conname = 'fk_identity_user_group_tenant' + AND conrelid = 'identity.app_user'::regclass) THEN + ALTER TABLE identity.app_user + ADD CONSTRAINT fk_identity_user_group_tenant + FOREIGN KEY (tenant_id, group_id) + REFERENCES identity.user_group (tenant_id, group_id) + NOT VALID; + END IF; + + IF NOT EXISTS ( + SELECT 1 FROM pg_constraint + WHERE conname = 'fk_identity_user_company_tenant' + AND conrelid = 'identity.app_user'::regclass) THEN + ALTER TABLE identity.app_user + ADD CONSTRAINT fk_identity_user_company_tenant + FOREIGN KEY (tenant_id, company_id) + REFERENCES identity.company (tenant_id, company_id) + NOT VALID; + END IF; + + IF NOT EXISTS ( + SELECT 1 FROM pg_constraint + WHERE conname = 'fk_identity_user_branch_tenant' + AND conrelid = 'identity.app_user'::regclass) THEN + ALTER TABLE identity.app_user + ADD CONSTRAINT fk_identity_user_branch_tenant + FOREIGN KEY (tenant_id, branch_id, company_id) + REFERENCES identity.company_branch (tenant_id, branch_id, company_id) + NOT VALID; + END IF; + + IF NOT EXISTS ( + SELECT 1 FROM pg_constraint + WHERE conname = 'fk_identity_permission_tenant' + AND conrelid = 'identity.permission'::regclass) THEN + ALTER TABLE identity.permission + ADD CONSTRAINT fk_identity_permission_tenant + FOREIGN KEY (tenant_id) + REFERENCES identity.client_account (tenant_id) + NOT VALID; + END IF; + + IF NOT EXISTS ( + SELECT 1 FROM pg_constraint + WHERE conname = 'fk_identity_permission_group_tenant' + AND conrelid = 'identity.permission'::regclass) THEN + ALTER TABLE identity.permission + ADD CONSTRAINT fk_identity_permission_group_tenant + FOREIGN KEY (tenant_id, group_id) + REFERENCES identity.user_group (tenant_id, group_id) + NOT VALID; + END IF; + + IF NOT EXISTS ( + SELECT 1 FROM pg_constraint + WHERE conname = 'fk_identity_permission_page' + AND conrelid = 'identity.permission'::regclass) THEN + ALTER TABLE identity.permission + ADD CONSTRAINT fk_identity_permission_page + FOREIGN KEY (page_id) + REFERENCES identity.pages (page_id) + NOT VALID; + END IF; + + IF NOT EXISTS ( + SELECT 1 FROM pg_constraint + WHERE conname = 'fk_identity_denied_tenant' + AND conrelid = 'identity.denied_pages'::regclass) THEN + ALTER TABLE identity.denied_pages + ADD CONSTRAINT fk_identity_denied_tenant + FOREIGN KEY (tenant_id) + REFERENCES identity.client_account (tenant_id) + NOT VALID; + END IF; + + IF NOT EXISTS ( + SELECT 1 FROM pg_constraint + WHERE conname = 'fk_identity_denied_user_tenant' + AND conrelid = 'identity.denied_pages'::regclass) THEN + ALTER TABLE identity.denied_pages + ADD CONSTRAINT fk_identity_denied_user_tenant + FOREIGN KEY (tenant_id, user_id) + REFERENCES identity.app_user (tenant_id, user_id) + NOT VALID; + END IF; + + IF NOT EXISTS ( + SELECT 1 FROM pg_constraint + WHERE conname = 'fk_identity_denied_page' + AND conrelid = 'identity.denied_pages'::regclass) THEN + ALTER TABLE identity.denied_pages + ADD CONSTRAINT fk_identity_denied_page + FOREIGN KEY (page_id) + REFERENCES identity.pages (page_id) + NOT VALID; + END IF; + + IF NOT EXISTS ( + SELECT 1 FROM pg_constraint + WHERE conname = 'fk_identity_login_history_tenant' + AND conrelid = 'identity.user_loginhistory'::regclass) THEN + ALTER TABLE identity.user_loginhistory + ADD CONSTRAINT fk_identity_login_history_tenant + FOREIGN KEY (tenant_id) + REFERENCES identity.client_account (tenant_id) + NOT VALID; + END IF; + +END +$migration$; + +ALTER TABLE identity.company + VALIDATE CONSTRAINT fk_identity_company_tenant; +ALTER TABLE identity.company_branch + VALIDATE CONSTRAINT fk_identity_branch_tenant; +ALTER TABLE identity.company_branch + VALIDATE CONSTRAINT fk_identity_branch_company_tenant; +ALTER TABLE identity.user_group + VALIDATE CONSTRAINT fk_identity_group_tenant; +ALTER TABLE identity.app_user + VALIDATE CONSTRAINT fk_identity_user_tenant; +ALTER TABLE identity.app_user + VALIDATE CONSTRAINT fk_identity_user_group_tenant; +ALTER TABLE identity.app_user + VALIDATE CONSTRAINT fk_identity_user_company_tenant; +ALTER TABLE identity.app_user + VALIDATE CONSTRAINT fk_identity_user_branch_tenant; +ALTER TABLE identity.permission + VALIDATE CONSTRAINT fk_identity_permission_tenant; +ALTER TABLE identity.permission + VALIDATE CONSTRAINT fk_identity_permission_group_tenant; +ALTER TABLE identity.permission + VALIDATE CONSTRAINT fk_identity_permission_page; +ALTER TABLE identity.denied_pages + VALIDATE CONSTRAINT fk_identity_denied_tenant; +ALTER TABLE identity.denied_pages + VALIDATE CONSTRAINT fk_identity_denied_user_tenant; +ALTER TABLE identity.denied_pages + VALIDATE CONSTRAINT fk_identity_denied_page; +ALTER TABLE identity.user_loginhistory + VALIDATE CONSTRAINT fk_identity_login_history_tenant; + +COMMIT; diff --git a/cygnus-cloud-service/target/classes/db/identity/004_client_registration_activation.sql b/cygnus-cloud-service/target/classes/db/identity/004_client_registration_activation.sql new file mode 100644 index 0000000..102efe2 --- /dev/null +++ b/cygnus-cloud-service/target/classes/db/identity/004_client_registration_activation.sql @@ -0,0 +1,369 @@ +BEGIN; + +-- Client registration and controlled installation activation. +-- This migration is additive and preserves all existing tenant, license, and +-- installation identifiers. Plaintext license/activation keys must never be +-- stored in these tables. + +CREATE TABLE IF NOT EXISTS identity.client_registration_details ( + registration_id uuid PRIMARY KEY, + client_code character varying(40) NOT NULL, + legal_company_name character varying(240) NOT NULL, + trade_name character varying(240), + pan character varying(20), + cin character varying(30), + gst_number character varying(30), + billing_address_line1 character varying(300), + billing_address_line2 character varying(300), + billing_city character varying(120), + billing_state character varying(120), + billing_postal_code character varying(20), + billing_country character varying(2) NOT NULL DEFAULT 'IN', + billing_email character varying(254), + primary_contact_name character varying(160), + primary_contact_email character varying(254), + primary_contact_number character varying(30), + alternate_contact_name character varying(160), + alternate_contact_email character varying(254), + alternate_contact_number character varying(30), + contract_start_date date, + contract_end_date date, + status character varying(20) NOT NULL DEFAULT 'DRAFT', + created_at timestamp with time zone NOT NULL DEFAULT now(), + updated_at timestamp with time zone NOT NULL DEFAULT now(), + created_by character varying(120) NOT NULL, + version integer NOT NULL DEFAULT 1, + CONSTRAINT ck_identity_registration_code + CHECK (client_code ~ '^[A-Z0-9]+(?:-[A-Z0-9]+)*$'), + CONSTRAINT ck_identity_registration_country + CHECK (billing_country ~ '^[A-Z]{2}$'), + CONSTRAINT ck_identity_registration_period + CHECK (contract_end_date IS NULL + OR contract_start_date IS NULL + OR contract_end_date >= contract_start_date), + CONSTRAINT ck_identity_registration_status + CHECK (status IN ('DRAFT', 'ACTIVE', 'SUSPENDED', 'TERMINATED')), + CONSTRAINT ck_identity_registration_version + CHECK (version > 0) +); + +CREATE UNIQUE INDEX IF NOT EXISTS uq_identity_registration_client_code_ci + ON identity.client_registration_details (upper(client_code)); +CREATE INDEX IF NOT EXISTS ix_identity_registration_status_contract + ON identity.client_registration_details (status, contract_end_date); + +ALTER TABLE identity.client_account + ADD COLUMN IF NOT EXISTS registration_id uuid; +ALTER TABLE identity.client_account + ADD COLUMN IF NOT EXISTS security_version integer NOT NULL DEFAULT 1; + +-- Stable bridge registration for the tenant introduced by migration 002. +INSERT INTO identity.client_registration_details ( + registration_id, client_code, legal_company_name, trade_name, + status, contract_start_date, created_by) +VALUES ('00000000-0000-4000-8000-000000000010', + 'MATRIX', 'Matrix', 'Matrix', 'ACTIVE', DATE '2020-01-01', 'migration-004') +ON CONFLICT (registration_id) DO UPDATE SET + legal_company_name = EXCLUDED.legal_company_name, + updated_at = now(); + +UPDATE identity.client_account + SET registration_id = '00000000-0000-4000-8000-000000000010' + WHERE registration_id IS NULL; + +ALTER TABLE identity.client_account + ALTER COLUMN registration_id SET NOT NULL; + +DO $migration$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM pg_constraint + WHERE conname = 'fk_identity_client_account_registration' + AND conrelid = 'identity.client_account'::regclass) THEN + ALTER TABLE identity.client_account + ADD CONSTRAINT fk_identity_client_account_registration + FOREIGN KEY (registration_id) + REFERENCES identity.client_registration_details (registration_id) + NOT VALID; + END IF; + + IF NOT EXISTS ( + SELECT 1 FROM pg_constraint + WHERE conname = 'ck_identity_client_account_security_version' + AND conrelid = 'identity.client_account'::regclass) THEN + ALTER TABLE identity.client_account + ADD CONSTRAINT ck_identity_client_account_security_version + CHECK (security_version > 0) NOT VALID; + END IF; +END +$migration$; + +ALTER TABLE identity.client_account + VALIDATE CONSTRAINT fk_identity_client_account_registration; +ALTER TABLE identity.client_account + VALIDATE CONSTRAINT ck_identity_client_account_security_version; + +CREATE INDEX IF NOT EXISTS ix_identity_client_account_registration + ON identity.client_account (registration_id, status); +CREATE UNIQUE INDEX IF NOT EXISTS uq_identity_client_account_registration_tenant + ON identity.client_account (registration_id, tenant_id); + +UPDATE identity.client_license + SET max_installations = 1 + WHERE max_installations IS NULL; +ALTER TABLE identity.client_license + ALTER COLUMN max_installations SET DEFAULT 1; +ALTER TABLE identity.client_license + ALTER COLUMN max_installations SET NOT NULL; +CREATE UNIQUE INDEX IF NOT EXISTS uq_identity_license_tenant_id + ON identity.client_license (tenant_id, license_id); + +ALTER TABLE identity.client_installation + ADD COLUMN IF NOT EXISTS license_id uuid; +ALTER TABLE identity.client_installation + ADD COLUMN IF NOT EXISTS installation_uuid uuid; +ALTER TABLE identity.client_installation + ADD COLUMN IF NOT EXISTS installation_name character varying(160); +ALTER TABLE identity.client_installation + ADD COLUMN IF NOT EXISTS status character varying(24); +ALTER TABLE identity.client_installation + ADD COLUMN IF NOT EXISTS registered_at timestamp with time zone; +ALTER TABLE identity.client_installation + ADD COLUMN IF NOT EXISTS last_seen_at timestamp with time zone; +ALTER TABLE identity.client_installation + ADD COLUMN IF NOT EXISTS software_version character varying(40); +ALTER TABLE identity.client_installation + ADD COLUMN IF NOT EXISTS environment character varying(30); +ALTER TABLE identity.client_installation + ADD COLUMN IF NOT EXISTS retired_at timestamp with time zone; +ALTER TABLE identity.client_installation + ADD COLUMN IF NOT EXISTS retired_by character varying(120); +ALTER TABLE identity.client_installation + ADD COLUMN IF NOT EXISTS retirement_reason character varying(500); + +UPDATE identity.client_installation installation + SET license_id = ( + SELECT license.license_id + FROM identity.client_license license + WHERE license.tenant_id = installation.tenant_id + ORDER BY CASE license.status WHEN 'ACTIVE' THEN 0 ELSE 1 END, + license.valid_until DESC + LIMIT 1) + WHERE installation.license_id IS NULL; + +DO $migration$ +BEGIN + IF EXISTS ( + SELECT 1 + FROM identity.client_installation + WHERE license_id IS NULL) THEN + RAISE EXCEPTION + 'Migration 004 cannot associate every existing installation with a license'; + END IF; +END +$migration$; + +UPDATE identity.client_installation + SET installation_uuid = installation_id + WHERE installation_uuid IS NULL; +UPDATE identity.client_installation + SET installation_name = installation_code + WHERE installation_name IS NULL; +UPDATE identity.client_installation + SET status = CASE WHEN enabled THEN 'ACTIVE' ELSE 'SUSPENDED' END + WHERE status IS NULL; +UPDATE identity.client_installation + SET registered_at = created_at + WHERE registered_at IS NULL; + +ALTER TABLE identity.client_installation + ALTER COLUMN license_id SET NOT NULL; +ALTER TABLE identity.client_installation + ALTER COLUMN installation_uuid SET NOT NULL; +ALTER TABLE identity.client_installation + ALTER COLUMN installation_name SET NOT NULL; +ALTER TABLE identity.client_installation + ALTER COLUMN status SET NOT NULL; +ALTER TABLE identity.client_installation + ALTER COLUMN status SET DEFAULT 'PENDING'; +ALTER TABLE identity.client_installation + ALTER COLUMN registered_at SET NOT NULL; +ALTER TABLE identity.client_installation + ALTER COLUMN registered_at SET DEFAULT now(); + +DO $migration$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM pg_constraint + WHERE conname = 'fk_identity_installation_tenant_license' + AND conrelid = 'identity.client_installation'::regclass) THEN + ALTER TABLE identity.client_installation + ADD CONSTRAINT fk_identity_installation_tenant_license + FOREIGN KEY (tenant_id, license_id) + REFERENCES identity.client_license (tenant_id, license_id) + NOT VALID; + END IF; + + IF NOT EXISTS ( + SELECT 1 FROM pg_constraint + WHERE conname = 'ck_identity_installation_status' + AND conrelid = 'identity.client_installation'::regclass) THEN + ALTER TABLE identity.client_installation + ADD CONSTRAINT ck_identity_installation_status + CHECK (status IN ( + 'PENDING', 'ACTIVE', 'SUSPENDED', 'DECOMMISSIONED', 'REVOKED', 'FAILED' + )) NOT VALID; + END IF; + + IF NOT EXISTS ( + SELECT 1 FROM pg_constraint + WHERE conname = 'ck_identity_installation_retirement' + AND conrelid = 'identity.client_installation'::regclass) THEN + ALTER TABLE identity.client_installation + ADD CONSTRAINT ck_identity_installation_retirement + CHECK ( + (status NOT IN ('DECOMMISSIONED', 'REVOKED') + AND retired_at IS NULL) + OR + (status IN ('DECOMMISSIONED', 'REVOKED') + AND retired_at IS NOT NULL + AND retirement_reason IS NOT NULL) + ) NOT VALID; + END IF; +END +$migration$; + +ALTER TABLE identity.client_installation + VALIDATE CONSTRAINT fk_identity_installation_tenant_license; +ALTER TABLE identity.client_installation + VALIDATE CONSTRAINT ck_identity_installation_status; +ALTER TABLE identity.client_installation + VALIDATE CONSTRAINT ck_identity_installation_retirement; + +CREATE UNIQUE INDEX IF NOT EXISTS uq_identity_installation_uuid + ON identity.client_installation (installation_uuid); +CREATE INDEX IF NOT EXISTS ix_identity_installation_capacity + ON identity.client_installation (tenant_id, license_id, status) + WHERE status IN ('PENDING', 'ACTIVE', 'SUSPENDED'); + +CREATE TABLE IF NOT EXISTS identity.license_activation_key ( + activation_key_id uuid PRIMARY KEY, + registration_id uuid NOT NULL, + tenant_id uuid NOT NULL, + license_id uuid NOT NULL, + key_hash character varying(255) NOT NULL, + key_hint character varying(16) NOT NULL, + status character varying(20) NOT NULL DEFAULT 'ACTIVE', + expires_at timestamp with time zone, + failed_attempts integer NOT NULL DEFAULT 0, + maximum_attempts integer NOT NULL DEFAULT 10, + locked_until timestamp with time zone, + created_at timestamp with time zone NOT NULL DEFAULT now(), + last_used_at timestamp with time zone, + created_by character varying(120) NOT NULL, + revoked_at timestamp with time zone, + revoked_by character varying(120), + revocation_reason character varying(500), + CONSTRAINT fk_identity_activation_key_registration + FOREIGN KEY (registration_id) + REFERENCES identity.client_registration_details (registration_id), + CONSTRAINT fk_identity_activation_key_registration_tenant + FOREIGN KEY (registration_id, tenant_id) + REFERENCES identity.client_account (registration_id, tenant_id), + CONSTRAINT fk_identity_activation_key_tenant_license + FOREIGN KEY (tenant_id, license_id) + REFERENCES identity.client_license (tenant_id, license_id), + CONSTRAINT uq_identity_activation_key_hash UNIQUE (key_hash), + CONSTRAINT ck_identity_activation_key_status + CHECK (status IN ('ACTIVE', 'LOCKED', 'REVOKED', 'EXPIRED')), + CONSTRAINT ck_identity_activation_key_attempts + CHECK (failed_attempts >= 0 + AND maximum_attempts > 0 + AND failed_attempts <= maximum_attempts), + CONSTRAINT ck_identity_activation_key_revocation + CHECK ((status <> 'REVOKED' AND revoked_at IS NULL) + OR (status = 'REVOKED' + AND revoked_at IS NOT NULL + AND revocation_reason IS NOT NULL)) +); + +CREATE INDEX IF NOT EXISTS ix_identity_activation_key_validation + ON identity.license_activation_key + (registration_id, tenant_id, license_id, status, expires_at); + +CREATE TABLE IF NOT EXISTS identity.installation_activation_session ( + activation_session_id uuid PRIMARY KEY, + activation_key_id uuid NOT NULL, + registration_id uuid NOT NULL, + tenant_id uuid NOT NULL, + license_id uuid NOT NULL, + installation_uuid uuid NOT NULL, + token_hash character varying(255) NOT NULL, + status character varying(20) NOT NULL DEFAULT 'PENDING', + expires_at timestamp with time zone NOT NULL, + created_at timestamp with time zone NOT NULL DEFAULT now(), + consumed_at timestamp with time zone, + source_ip inet, + installer_version character varying(40), + CONSTRAINT fk_identity_activation_session_key + FOREIGN KEY (activation_key_id) + REFERENCES identity.license_activation_key (activation_key_id), + CONSTRAINT fk_identity_activation_session_registration + FOREIGN KEY (registration_id) + REFERENCES identity.client_registration_details (registration_id), + CONSTRAINT fk_identity_activation_session_registration_tenant + FOREIGN KEY (registration_id, tenant_id) + REFERENCES identity.client_account (registration_id, tenant_id), + CONSTRAINT fk_identity_activation_session_tenant_license + FOREIGN KEY (tenant_id, license_id) + REFERENCES identity.client_license (tenant_id, license_id), + CONSTRAINT uq_identity_activation_session_token UNIQUE (token_hash), + CONSTRAINT uq_identity_activation_session_uuid UNIQUE (installation_uuid), + CONSTRAINT ck_identity_activation_session_status + CHECK (status IN ('PENDING', 'CONSUMED', 'EXPIRED', 'REVOKED')), + CONSTRAINT ck_identity_activation_session_period + CHECK (expires_at > created_at), + CONSTRAINT ck_identity_activation_session_consumed + CHECK ((status <> 'CONSUMED' AND consumed_at IS NULL) + OR (status = 'CONSUMED' AND consumed_at IS NOT NULL)) +); + +CREATE INDEX IF NOT EXISTS ix_identity_activation_session_pending + ON identity.installation_activation_session (tenant_id, expires_at) + WHERE status = 'PENDING'; + +CREATE TABLE IF NOT EXISTS identity.installation_audit_event ( + audit_event_id uuid PRIMARY KEY, + registration_id uuid NOT NULL, + tenant_id uuid NOT NULL, + license_id uuid, + installation_id uuid, + event_type character varying(50) NOT NULL, + actor_type character varying(30) NOT NULL, + actor_id character varying(160) NOT NULL, + reason character varying(500), + event_data jsonb NOT NULL DEFAULT '{}'::jsonb, + occurred_at timestamp with time zone NOT NULL DEFAULT now(), + CONSTRAINT fk_identity_installation_audit_registration + FOREIGN KEY (registration_id) + REFERENCES identity.client_registration_details (registration_id), + CONSTRAINT fk_identity_installation_audit_registration_tenant + FOREIGN KEY (registration_id, tenant_id) + REFERENCES identity.client_account (registration_id, tenant_id), + CONSTRAINT fk_identity_installation_audit_tenant_license + FOREIGN KEY (tenant_id, license_id) + REFERENCES identity.client_license (tenant_id, license_id), + CONSTRAINT fk_identity_installation_audit_installation + FOREIGN KEY (installation_id) + REFERENCES identity.client_installation (installation_id), + CONSTRAINT ck_identity_installation_audit_actor + CHECK (actor_type IN ('ADMIN', 'INSTALLER', 'SYSTEM', 'CLIENT')) +); + +CREATE INDEX IF NOT EXISTS ix_identity_installation_audit_tenant_time + ON identity.installation_audit_event (tenant_id, occurred_at DESC); +CREATE INDEX IF NOT EXISTS ix_identity_installation_audit_installation_time + ON identity.installation_audit_event (installation_id, occurred_at DESC) + WHERE installation_id IS NOT NULL; + +COMMIT; diff --git a/cygnus-cloud-service/target/cygnus-cloud-service-1.0.0-SNAPSHOT.jar b/cygnus-cloud-service/target/cygnus-cloud-service-1.0.0-SNAPSHOT.jar index b46ce3d..bf668fe 100644 Binary files a/cygnus-cloud-service/target/cygnus-cloud-service-1.0.0-SNAPSHOT.jar and b/cygnus-cloud-service/target/cygnus-cloud-service-1.0.0-SNAPSHOT.jar differ diff --git a/cygnus-cloud-service/target/cygnus-cloud-service-1.0.0-SNAPSHOT.jar.original b/cygnus-cloud-service/target/cygnus-cloud-service-1.0.0-SNAPSHOT.jar.original index cbbf17e..5eb1769 100644 Binary files a/cygnus-cloud-service/target/cygnus-cloud-service-1.0.0-SNAPSHOT.jar.original and b/cygnus-cloud-service/target/cygnus-cloud-service-1.0.0-SNAPSHOT.jar.original differ diff --git a/cygnus-cloud-service/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/cygnus-cloud-service/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst index 5baed86..e158196 100644 --- a/cygnus-cloud-service/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst +++ b/cygnus-cloud-service/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst @@ -1,38 +1,44 @@ com/cygnus/cloud/security/JwtReplayProtectionService.class -com/cygnus/cloud/identity/repository/IdentityRowMapper.class com/cygnus/cloud/security/TokenResponse.class com/cygnus/cloud/identity/api/IdentityErrorHandler.class com/cygnus/cloud/security/MachineTokenController.class -com/cygnus/cloud/cache/CacheProperties.class -com/cygnus/cloud/identity/service/AuthenticationException.class com/cygnus/cloud/database/ReactiveDatabaseClient.class -com/cygnus/cloud/security/AudienceValidator.class -com/cygnus/cloud/security/MachineSecurityConfigurationValidator.class +com/cygnus/cloud/tenant/api/IssueLicenseKeyRequest.class com/cygnus/cloud/identity/api/EncryptedLoginRequest.class com/cygnus/cloud/security/AccessTokenIssuer.class -com/cygnus/cloud/identity/api/LoginEncryptionProperties.class -com/cygnus/cloud/identity/service/LoginRequestReplayService.class -com/cygnus/cloud/identity/service/IdentityAuthenticationService.class com/cygnus/cloud/security/MachineTokenErrorHandler.class com/cygnus/cloud/security/MachineClientPrincipal.class -com/cygnus/cloud/system/SystemInfoResponse.class -com/cygnus/cloud/identity/api/LoginPayload.class com/cygnus/cloud/security/CommunicationSecurityProperties.class com/cygnus/cloud/system/SystemConfiguration.class com/cygnus/cloud/security/MachineAuthenticationException.class -com/cygnus/cloud/security/PemKeyLoader.class -com/cygnus/cloud/security/CloudSecurityConfiguration.class +com/cygnus/cloud/tenant/repository/ClientAdministrationRepository.class com/cygnus/cloud/database/VertxDatabaseConfiguration.class -com/cygnus/cloud/security/CommunicationSecurityProperties$MachineClient.class com/cygnus/cloud/identity/repository/IdentityRepository.class com/cygnus/cloud/identity/service/LegacyPasswordVerifier.class -com/cygnus/cloud/system/SystemInfoController.class -com/cygnus/cloud/CygnusCloudServiceApplication.class -com/cygnus/cloud/cache/ReactiveCacheService.class +com/cygnus/cloud/tenant/api/CreateTenantRequest.class com/cygnus/cloud/identity/model/MenuItem.class com/cygnus/cloud/identity/model/AuthenticatedIdentity.class com/cygnus/cloud/identity/api/CloudLoginController.class com/cygnus/cloud/security/ClientAssertionValidator.class +com/cygnus/cloud/tenant/api/ClientAdministrationController.class com/cygnus/cloud/identity/model/IdentityUser.class +com/cygnus/cloud/identity/repository/IdentityRowMapper.class +com/cygnus/cloud/cache/CacheProperties.class +com/cygnus/cloud/identity/service/AuthenticationException.class +com/cygnus/cloud/security/AudienceValidator.class +com/cygnus/cloud/security/MachineSecurityConfigurationValidator.class +com/cygnus/cloud/tenant/service/RegistrationEmailService.class +com/cygnus/cloud/identity/api/LoginEncryptionProperties.class +com/cygnus/cloud/identity/service/LoginRequestReplayService.class +com/cygnus/cloud/identity/service/IdentityAuthenticationService.class +com/cygnus/cloud/system/SystemInfoResponse.class +com/cygnus/cloud/tenant/api/CreateLicenseRequest.class +com/cygnus/cloud/identity/api/LoginPayload.class +com/cygnus/cloud/security/PemKeyLoader.class +com/cygnus/cloud/security/CloudSecurityConfiguration.class +com/cygnus/cloud/system/SystemInfoController.class +com/cygnus/cloud/CygnusCloudServiceApplication.class +com/cygnus/cloud/cache/ReactiveCacheService.class +com/cygnus/cloud/tenant/api/CreateClientRegistrationRequest.class com/cygnus/cloud/identity/api/RsaLoginPayloadDecryptor.class com/cygnus/cloud/database/DatabaseProperties.class diff --git a/cygnus-cloud-service/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/cygnus-cloud-service/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst index 1a14caf..77e363e 100644 --- a/cygnus-cloud-service/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst +++ b/cygnus-cloud-service/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -35,3 +35,43 @@ /Users/maddy/Projects/matrix/cygnus-cloud-service/src/main/java/com/cygnus/cloud/system/SystemConfiguration.java /Users/maddy/Projects/matrix/cygnus-cloud-service/src/main/java/com/cygnus/cloud/system/SystemInfoController.java /Users/maddy/Projects/matrix/cygnus-cloud-service/src/main/java/com/cygnus/cloud/system/SystemInfoResponse.java +/Users/maddy/Projects/matrix/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/api/ActivationValidationRequest.java +/Users/maddy/Projects/matrix/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/api/ActivationValidationResponse.java +/Users/maddy/Projects/matrix/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/api/ClientAdministrationController.java +/Users/maddy/Projects/matrix/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/api/CreateClientRegistrationRequest.java +/Users/maddy/Projects/matrix/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/api/CreateLicenseRequest.java +/Users/maddy/Projects/matrix/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/api/CreateTenantRequest.java +/Users/maddy/Projects/matrix/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/api/InstallationActivationController.java +/Users/maddy/Projects/matrix/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/api/InstallationActivationErrorHandler.java +/Users/maddy/Projects/matrix/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/api/InstallationRegistrationRequest.java +/Users/maddy/Projects/matrix/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/api/IssueLicenseKeyRequest.java +/Users/maddy/Projects/matrix/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/api/RetireInstallationRequest.java +/Users/maddy/Projects/matrix/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/model/ActivationKeyStatus.java +/Users/maddy/Projects/matrix/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/model/ActivationSession.java +/Users/maddy/Projects/matrix/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/model/ClientAccount.java +/Users/maddy/Projects/matrix/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/model/ClientInstallation.java +/Users/maddy/Projects/matrix/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/model/ClientLicense.java +/Users/maddy/Projects/matrix/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/model/ClientRegistration.java +/Users/maddy/Projects/matrix/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/model/ClientStatus.java +/Users/maddy/Projects/matrix/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/model/InstallationLifecycleResult.java +/Users/maddy/Projects/matrix/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/model/IssuedLicenseKey.java +/Users/maddy/Projects/matrix/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/model/LicenseActivationContext.java +/Users/maddy/Projects/matrix/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/model/LicenseActivationKey.java +/Users/maddy/Projects/matrix/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/model/LicenseDeliveryDetails.java +/Users/maddy/Projects/matrix/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/model/LicenseStatus.java +/Users/maddy/Projects/matrix/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/model/RegisteredInstallation.java +/Users/maddy/Projects/matrix/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/model/RegistrationStatus.java +/Users/maddy/Projects/matrix/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/repository/ClientAdministrationRepository.java +/Users/maddy/Projects/matrix/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/repository/InstallationActivationRepository.java +/Users/maddy/Projects/matrix/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/repository/InstallationLifecycleRepository.java +/Users/maddy/Projects/matrix/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/repository/LicenseActivationRepository.java +/Users/maddy/Projects/matrix/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/repository/TenantRegistrationRepository.java +/Users/maddy/Projects/matrix/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/service/ActivationRateLimitException.java +/Users/maddy/Projects/matrix/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/service/ActivationRateLimiter.java +/Users/maddy/Projects/matrix/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/service/InstallationActivationException.java +/Users/maddy/Projects/matrix/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/service/InstallationActivationService.java +/Users/maddy/Projects/matrix/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/service/InstallationLifecycleService.java +/Users/maddy/Projects/matrix/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/service/LicenseKeyException.java +/Users/maddy/Projects/matrix/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/service/LicenseKeyService.java +/Users/maddy/Projects/matrix/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/service/RegistrationEmailService.java +/Users/maddy/Projects/matrix/cygnus-cloud-service/src/main/java/com/cygnus/cloud/tenant/service/TenantRegistrationService.java diff --git a/cygnus-cloud-service/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst b/cygnus-cloud-service/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst index 21989b5..2d8ac71 100644 --- a/cygnus-cloud-service/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst +++ b/cygnus-cloud-service/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst @@ -1,6 +1,6 @@ com/cygnus/cloud/identity/service/IdentityAuthenticationServiceTest.class +com/cygnus/cloud/system/SystemInfoControllerTest.class com/cygnus/cloud/InfrastructureConfigurationTest.class com/cygnus/cloud/identity/service/LegacyPasswordVerifierTest.class com/cygnus/cloud/security/MachineTokenFlowTest.class com/cygnus/cloud/system/SystemInfoControllerTest$FixedClockConfiguration.class -com/cygnus/cloud/system/SystemInfoControllerTest.class diff --git a/cygnus-cloud-service/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst b/cygnus-cloud-service/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst index 8df0fb7..f3407fa 100644 --- a/cygnus-cloud-service/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst +++ b/cygnus-cloud-service/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst @@ -3,3 +3,7 @@ /Users/maddy/Projects/matrix/cygnus-cloud-service/src/test/java/com/cygnus/cloud/identity/service/LegacyPasswordVerifierTest.java /Users/maddy/Projects/matrix/cygnus-cloud-service/src/test/java/com/cygnus/cloud/security/MachineTokenFlowTest.java /Users/maddy/Projects/matrix/cygnus-cloud-service/src/test/java/com/cygnus/cloud/system/SystemInfoControllerTest.java +/Users/maddy/Projects/matrix/cygnus-cloud-service/src/test/java/com/cygnus/cloud/tenant/model/ClientLicenseTest.java +/Users/maddy/Projects/matrix/cygnus-cloud-service/src/test/java/com/cygnus/cloud/tenant/service/ActivationRateLimiterTest.java +/Users/maddy/Projects/matrix/cygnus-cloud-service/src/test/java/com/cygnus/cloud/tenant/service/LicenseKeyServiceTest.java +/Users/maddy/Projects/matrix/cygnus-cloud-service/src/test/java/com/cygnus/cloud/tenant/service/TenantRegistrationServiceTest.java diff --git a/cygnus-cloud-service/target/surefire-reports/TEST-com.cygnus.cloud.InfrastructureConfigurationTest.xml b/cygnus-cloud-service/target/surefire-reports/TEST-com.cygnus.cloud.InfrastructureConfigurationTest.xml index b073da0..99d4a82 100644 --- a/cygnus-cloud-service/target/surefire-reports/TEST-com.cygnus.cloud.InfrastructureConfigurationTest.xml +++ b/cygnus-cloud-service/target/surefire-reports/TEST-com.cygnus.cloud.InfrastructureConfigurationTest.xml @@ -1,9 +1,9 @@ - + - + @@ -15,9 +15,10 @@ - + + - + @@ -28,13 +29,14 @@ - + - + + @@ -51,7 +53,7 @@ - + @@ -60,145 +62,21 @@ + - - - + - \ No newline at end of file diff --git a/cygnus-cloud-service/target/surefire-reports/TEST-com.cygnus.cloud.identity.service.IdentityAuthenticationServiceTest.xml b/cygnus-cloud-service/target/surefire-reports/TEST-com.cygnus.cloud.identity.service.IdentityAuthenticationServiceTest.xml index 096e692..e42bf29 100644 --- a/cygnus-cloud-service/target/surefire-reports/TEST-com.cygnus.cloud.identity.service.IdentityAuthenticationServiceTest.xml +++ b/cygnus-cloud-service/target/surefire-reports/TEST-com.cygnus.cloud.identity.service.IdentityAuthenticationServiceTest.xml @@ -1,20 +1,22 @@ - + - + + - + + - + @@ -25,11 +27,12 @@ - + - + + @@ -53,141 +56,9 @@ + - - (DefaultMockitoSession.java:43) - at org.mockito.internal.session.DefaultMockitoSessionBuilder.startMocking(DefaultMockitoSessionBuilder.java:83) - at org.mockito.junit.jupiter.MockitoExtension.beforeEach(MockitoExtension.java:160) - at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) - at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) -Caused by: java.lang.IllegalStateException: Internal problem occurred, please report it. Mockito is unable to load the default implementation of class that is a part of Mockito distribution. Failed to load interface org.mockito.plugins.MockMaker - at org.mockito.internal.configuration.plugins.DefaultMockitoPlugins.create(DefaultMockitoPlugins.java:105) - at org.mockito.internal.configuration.plugins.DefaultMockitoPlugins.getDefaultPlugin(DefaultMockitoPlugins.java:79) - at org.mockito.internal.configuration.plugins.PluginLoader.loadPlugin(PluginLoader.java:75) - at org.mockito.internal.configuration.plugins.PluginLoader.loadPlugin(PluginLoader.java:49) - at org.mockito.internal.configuration.plugins.PluginRegistry.(PluginRegistry.java:29) - at org.mockito.internal.configuration.plugins.Plugins.(Plugins.java:26) - at org.mockito.internal.MockitoCore.(MockitoCore.java:71) - at org.mockito.Mockito.(Mockito.java:1777) - at org.mockito.junit.jupiter.MockitoExtension.beforeEach(MockitoExtension.java:156) - ... 2 more -Caused by: java.lang.reflect.InvocationTargetException - at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:502) - at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:486) - at org.mockito.internal.configuration.plugins.DefaultMockitoPlugins.create(DefaultMockitoPlugins.java:103) - ... 10 more -Caused by: org.mockito.exceptions.base.MockitoInitializationException: -Could not initialize inline Byte Buddy mock maker. - -It appears as if your JDK does not supply a working agent attachment mechanism. -Java : 21 -JVM vendor name : Microsoft -JVM vendor version : 21.0.8+9-LTS -JVM name : OpenJDK 64-Bit Server VM -JVM version : 21.0.8+9-LTS -JVM info : mixed mode, sharing -OS name : Mac OS X -OS version : 26.5.2 - - at org.mockito.internal.creation.bytebuddy.InlineDelegateByteBuddyMockMaker.(InlineDelegateByteBuddyMockMaker.java:275) - at org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker.(InlineByteBuddyMockMaker.java:23) - ... 13 more -Caused by: java.lang.IllegalStateException: Could not self-attach to current VM using external process - set a property net.bytebuddy.agent.attacher.dump to dump the process output to a file at the specified location - at net.bytebuddy.agent.ByteBuddyAgent.installExternal(ByteBuddyAgent.java:672) - at net.bytebuddy.agent.ByteBuddyAgent.install(ByteBuddyAgent.java:599) - at net.bytebuddy.agent.ByteBuddyAgent.install(ByteBuddyAgent.java:579) - at net.bytebuddy.agent.ByteBuddyAgent.install(ByteBuddyAgent.java:531) - at net.bytebuddy.agent.ByteBuddyAgent.install(ByteBuddyAgent.java:508) - at org.mockito.internal.PremainAttachAccess.getInstrumentation(PremainAttachAccess.java:80) - at org.mockito.internal.creation.bytebuddy.InlineDelegateByteBuddyMockMaker.(InlineDelegateByteBuddyMockMaker.java:138) - ... 14 more -]]> - - - - (DefaultMockitoSession.java:43) - at org.mockito.internal.session.DefaultMockitoSessionBuilder.startMocking(DefaultMockitoSessionBuilder.java:83) - at org.mockito.junit.jupiter.MockitoExtension.beforeEach(MockitoExtension.java:160) - at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) - at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) -Caused by: java.lang.IllegalStateException: Internal problem occurred, please report it. Mockito is unable to load the default implementation of class that is a part of Mockito distribution. Failed to load interface org.mockito.plugins.MockMaker - at org.mockito.internal.configuration.plugins.DefaultMockitoPlugins.create(DefaultMockitoPlugins.java:105) - at org.mockito.internal.configuration.plugins.DefaultMockitoPlugins.getDefaultPlugin(DefaultMockitoPlugins.java:79) - at org.mockito.internal.configuration.plugins.PluginLoader.loadPlugin(PluginLoader.java:75) - at org.mockito.internal.configuration.plugins.PluginLoader.loadPlugin(PluginLoader.java:49) - at org.mockito.internal.configuration.plugins.PluginRegistry.(PluginRegistry.java:29) - at org.mockito.internal.configuration.plugins.Plugins.(Plugins.java:26) - at org.mockito.internal.MockitoCore.(MockitoCore.java:71) - at org.mockito.Mockito.(Mockito.java:1777) - at org.mockito.junit.jupiter.MockitoExtension.beforeEach(MockitoExtension.java:156) - ... 2 more -Caused by: java.lang.reflect.InvocationTargetException - at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:502) - at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:486) - at org.mockito.internal.configuration.plugins.DefaultMockitoPlugins.create(DefaultMockitoPlugins.java:103) - ... 10 more -Caused by: org.mockito.exceptions.base.MockitoInitializationException: -Could not initialize inline Byte Buddy mock maker. - -It appears as if your JDK does not supply a working agent attachment mechanism. -Java : 21 -JVM vendor name : Microsoft -JVM vendor version : 21.0.8+9-LTS -JVM name : OpenJDK 64-Bit Server VM -JVM version : 21.0.8+9-LTS -JVM info : mixed mode, sharing -OS name : Mac OS X -OS version : 26.5.2 - - at org.mockito.internal.creation.bytebuddy.InlineDelegateByteBuddyMockMaker.(InlineDelegateByteBuddyMockMaker.java:275) - at org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker.(InlineByteBuddyMockMaker.java:23) - ... 13 more -Caused by: java.lang.IllegalStateException: Could not self-attach to current VM using external process - set a property net.bytebuddy.agent.attacher.dump to dump the process output to a file at the specified location - at net.bytebuddy.agent.ByteBuddyAgent.installExternal(ByteBuddyAgent.java:672) - at net.bytebuddy.agent.ByteBuddyAgent.install(ByteBuddyAgent.java:599) - at net.bytebuddy.agent.ByteBuddyAgent.install(ByteBuddyAgent.java:579) - at net.bytebuddy.agent.ByteBuddyAgent.install(ByteBuddyAgent.java:531) - at net.bytebuddy.agent.ByteBuddyAgent.install(ByteBuddyAgent.java:508) - at org.mockito.internal.PremainAttachAccess.getInstrumentation(PremainAttachAccess.java:80) - at org.mockito.internal.creation.bytebuddy.InlineDelegateByteBuddyMockMaker.(InlineDelegateByteBuddyMockMaker.java:138) - ... 14 more -]]> - + + \ No newline at end of file diff --git a/cygnus-cloud-service/target/surefire-reports/TEST-com.cygnus.cloud.identity.service.LegacyPasswordVerifierTest.xml b/cygnus-cloud-service/target/surefire-reports/TEST-com.cygnus.cloud.identity.service.LegacyPasswordVerifierTest.xml index b1a58b2..9f3d094 100644 --- a/cygnus-cloud-service/target/surefire-reports/TEST-com.cygnus.cloud.identity.service.LegacyPasswordVerifierTest.xml +++ b/cygnus-cloud-service/target/surefire-reports/TEST-com.cygnus.cloud.identity.service.LegacyPasswordVerifierTest.xml @@ -1,20 +1,22 @@ - + - + + - + + - + @@ -25,11 +27,12 @@ - + - + + @@ -53,7 +56,8 @@ + - + \ No newline at end of file diff --git a/cygnus-cloud-service/target/surefire-reports/TEST-com.cygnus.cloud.security.MachineTokenFlowTest.xml b/cygnus-cloud-service/target/surefire-reports/TEST-com.cygnus.cloud.security.MachineTokenFlowTest.xml index efd1d93..17d8a53 100644 --- a/cygnus-cloud-service/target/surefire-reports/TEST-com.cygnus.cloud.security.MachineTokenFlowTest.xml +++ b/cygnus-cloud-service/target/surefire-reports/TEST-com.cygnus.cloud.security.MachineTokenFlowTest.xml @@ -1,9 +1,9 @@ - + - + @@ -13,9 +13,10 @@ - + + - + @@ -26,11 +27,12 @@ - + - + + @@ -54,12 +56,14 @@ + - - + - - + + + \ No newline at end of file diff --git a/cygnus-cloud-service/target/surefire-reports/TEST-com.cygnus.cloud.system.SystemInfoControllerTest.xml b/cygnus-cloud-service/target/surefire-reports/TEST-com.cygnus.cloud.system.SystemInfoControllerTest.xml index 37dbc86..6affdb8 100644 --- a/cygnus-cloud-service/target/surefire-reports/TEST-com.cygnus.cloud.system.SystemInfoControllerTest.xml +++ b/cygnus-cloud-service/target/surefire-reports/TEST-com.cygnus.cloud.system.SystemInfoControllerTest.xml @@ -1,9 +1,9 @@ - + - + @@ -15,9 +15,10 @@ - + + - + @@ -28,13 +29,14 @@ - + - + + @@ -51,7 +53,7 @@ - + @@ -60,12 +62,13 @@ + - - + \ No newline at end of file diff --git a/cygnus-cloud-service/target/surefire-reports/TEST-com.cygnus.cloud.tenant.model.ClientLicenseTest.xml b/cygnus-cloud-service/target/surefire-reports/TEST-com.cygnus.cloud.tenant.model.ClientLicenseTest.xml new file mode 100644 index 0000000..45d55f2 --- /dev/null +++ b/cygnus-cloud-service/target/surefire-reports/TEST-com.cygnus.cloud.tenant.model.ClientLicenseTest.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/cygnus-cloud-service/target/surefire-reports/TEST-com.cygnus.cloud.tenant.service.ActivationRateLimiterTest.xml b/cygnus-cloud-service/target/surefire-reports/TEST-com.cygnus.cloud.tenant.service.ActivationRateLimiterTest.xml new file mode 100644 index 0000000..b2738bc --- /dev/null +++ b/cygnus-cloud-service/target/surefire-reports/TEST-com.cygnus.cloud.tenant.service.ActivationRateLimiterTest.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/cygnus-cloud-service/target/surefire-reports/TEST-com.cygnus.cloud.tenant.service.LicenseKeyServiceTest.xml b/cygnus-cloud-service/target/surefire-reports/TEST-com.cygnus.cloud.tenant.service.LicenseKeyServiceTest.xml new file mode 100644 index 0000000..9326d3e --- /dev/null +++ b/cygnus-cloud-service/target/surefire-reports/TEST-com.cygnus.cloud.tenant.service.LicenseKeyServiceTest.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/cygnus-cloud-service/target/surefire-reports/TEST-com.cygnus.cloud.tenant.service.TenantRegistrationServiceTest.xml b/cygnus-cloud-service/target/surefire-reports/TEST-com.cygnus.cloud.tenant.service.TenantRegistrationServiceTest.xml new file mode 100644 index 0000000..c89de81 --- /dev/null +++ b/cygnus-cloud-service/target/surefire-reports/TEST-com.cygnus.cloud.tenant.service.TenantRegistrationServiceTest.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/cygnus-cloud-service/target/surefire-reports/com.cygnus.cloud.InfrastructureConfigurationTest.txt b/cygnus-cloud-service/target/surefire-reports/com.cygnus.cloud.InfrastructureConfigurationTest.txt index f933ad5..e859ae6 100644 --- a/cygnus-cloud-service/target/surefire-reports/com.cygnus.cloud.InfrastructureConfigurationTest.txt +++ b/cygnus-cloud-service/target/surefire-reports/com.cygnus.cloud.InfrastructureConfigurationTest.txt @@ -1,130 +1,4 @@ ------------------------------------------------------------------------------- Test set: com.cygnus.cloud.InfrastructureConfigurationTest ------------------------------------------------------------------------------- -Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 2.766 s <<< FAILURE! -- in com.cygnus.cloud.InfrastructureConfigurationTest -com.cygnus.cloud.InfrastructureConfigurationTest.communicationInfrastructureStartsWithoutOpeningExternalConnections -- Time elapsed: 0.001 s <<< ERROR! -java.lang.IllegalStateException: Failed to load ApplicationContext for [ReactiveWebMergedContextConfiguration@5c7c22ce testClass = com.cygnus.cloud.InfrastructureConfigurationTest, locations = [], classes = [com.cygnus.cloud.CygnusCloudServiceApplication], contextInitializerClasses = [], activeProfiles = [], propertySourceDescriptors = [], propertySourceProperties = ["cygnus.security.enabled=false", "org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true", "server.port=0"], contextCustomizers = [org.springframework.boot.web.server.context.SpringBootTestRandomPortContextCustomizer@6ad6443, org.springframework.boot.test.context.PropertyMappingContextCustomizer@0, org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@545d2560, org.springframework.boot.test.http.client.DisableReactorResourceFactoryGlobalResourcesContextCustomizerFactory$DisableReactorResourceFactoryGlobalResourcesContextCustomizerCustomizer@46bb0bdf, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@7ceb6c45, org.springframework.boot.test.autoconfigure.OnFailureConditionReportContextCustomizerFactory$OnFailureConditionReportContextCustomizer@723742b2, org.springframework.test.context.support.DynamicPropertiesContextCustomizer@0, org.springframework.boot.test.context.SpringBootTestAnnotation@605d3a8b], contextLoader = org.springframework.boot.test.context.SpringBootContextLoader, parent = null] - at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.lambda$loadContext$0(DefaultCacheAwareContextLoaderDelegate.java:195) - at org.springframework.test.context.cache.DefaultContextCache.put(DefaultContextCache.java:214) - at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:160) - at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:128) - at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:156) - at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:111) - at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:260) - at org.springframework.test.context.junit.jupiter.SpringExtension.postProcessTestInstance(SpringExtension.java:242) - at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184) - at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197) - at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:179) - at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197) - at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1708) - at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509) - at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) - at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151) - at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174) - at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) - at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:596) - at java.base/java.util.Optional.orElseGet(Optional.java:364) - at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) - at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) -Caused by: org.springframework.context.ApplicationContextException: Failed to start bean 'webServerStartStop' - at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:423) - at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:409) - at org.springframework.context.support.DefaultLifecycleProcessor$LifecycleGroup.start(DefaultLifecycleProcessor.java:613) - at java.base/java.lang.Iterable.forEach(Iterable.java:75) - at org.springframework.context.support.DefaultLifecycleProcessor.startBeans(DefaultLifecycleProcessor.java:379) - at org.springframework.context.support.DefaultLifecycleProcessor.onRefresh(DefaultLifecycleProcessor.java:306) - at org.springframework.context.support.AbstractApplicationContext.finishRefresh(AbstractApplicationContext.java:1013) - at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:624) - at org.springframework.boot.web.server.reactive.context.ReactiveWebServerApplicationContext.refresh(ReactiveWebServerApplicationContext.java:69) - at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:756) - at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:445) - at org.springframework.boot.SpringApplication.run(SpringApplication.java:321) - at org.springframework.boot.test.context.SpringBootContextLoader.lambda$loadContext$2(SpringBootContextLoader.java:155) - at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:58) - at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:46) - at org.springframework.boot.SpringApplication.withHook(SpringApplication.java:1465) - at org.springframework.boot.test.context.SpringBootContextLoader$ContextLoaderHook.run(SpringBootContextLoader.java:600) - at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:155) - at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:114) - at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:247) - at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.lambda$loadContext$0(DefaultCacheAwareContextLoaderDelegate.java:167) - ... 21 more -Caused by: org.springframework.boot.web.server.WebServerException: Unable to start Netty - at org.springframework.boot.reactor.netty.NettyWebServer.start(NettyWebServer.java:140) - at org.springframework.boot.web.server.reactive.context.WebServerManager.start(WebServerManager.java:55) - at org.springframework.boot.web.server.reactive.context.WebServerStartStopLifecycle.start(WebServerStartStopLifecycle.java:41) - at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:420) - ... 41 more -Caused by: reactor.netty.ChannelBindException: Failed to bind on [0.0.0.0:0] - Suppressed: java.lang.Exception: #block terminated with an error - at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:145) - at reactor.core.publisher.Mono.block(Mono.java:1800) - at reactor.netty.transport.ServerTransport.bindNow(ServerTransport.java:160) - at reactor.netty.transport.ServerTransport.bindNow(ServerTransport.java:145) - at org.springframework.boot.reactor.netty.NettyWebServer.startHttpServer(NettyWebServer.java:188) - at org.springframework.boot.reactor.netty.NettyWebServer.start(NettyWebServer.java:123) - at org.springframework.boot.web.server.reactive.context.WebServerManager.start(WebServerManager.java:55) - at org.springframework.boot.web.server.reactive.context.WebServerStartStopLifecycle.start(WebServerStartStopLifecycle.java:41) - at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:420) - at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:409) - at org.springframework.context.support.DefaultLifecycleProcessor$LifecycleGroup.start(DefaultLifecycleProcessor.java:613) - at java.base/java.lang.Iterable.forEach(Iterable.java:75) - at org.springframework.context.support.DefaultLifecycleProcessor.startBeans(DefaultLifecycleProcessor.java:379) - at org.springframework.context.support.DefaultLifecycleProcessor.onRefresh(DefaultLifecycleProcessor.java:306) - at org.springframework.context.support.AbstractApplicationContext.finishRefresh(AbstractApplicationContext.java:1013) - at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:624) - at org.springframework.boot.web.server.reactive.context.ReactiveWebServerApplicationContext.refresh(ReactiveWebServerApplicationContext.java:69) - at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:756) - at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:445) - at org.springframework.boot.SpringApplication.run(SpringApplication.java:321) - at org.springframework.boot.test.context.SpringBootContextLoader.lambda$loadContext$2(SpringBootContextLoader.java:155) - at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:58) - at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:46) - at org.springframework.boot.SpringApplication.withHook(SpringApplication.java:1465) - at org.springframework.boot.test.context.SpringBootContextLoader$ContextLoaderHook.run(SpringBootContextLoader.java:600) - at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:155) - at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:114) - at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:247) - at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.lambda$loadContext$0(DefaultCacheAwareContextLoaderDelegate.java:167) - at org.springframework.test.context.cache.DefaultContextCache.put(DefaultContextCache.java:214) - at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:160) - at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:128) - at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:156) - at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:111) - at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:260) - at org.springframework.test.context.junit.jupiter.SpringExtension.postProcessTestInstance(SpringExtension.java:242) - at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184) - at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197) - at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:179) - at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197) - at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1708) - at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509) - at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) - at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151) - at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174) - at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) - at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:596) - at java.base/java.util.Optional.orElseGet(Optional.java:364) - at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) - at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) -Caused by: java.net.SocketException: Operation not permitted - at java.base/sun.nio.ch.Net.bind0(Native Method) - at java.base/sun.nio.ch.Net.bind(Net.java:565) - at java.base/sun.nio.ch.ServerSocketChannelImpl.netBind(ServerSocketChannelImpl.java:344) - at java.base/sun.nio.ch.ServerSocketChannelImpl.bind(ServerSocketChannelImpl.java:301) - at io.netty.channel.socket.nio.NioServerSocketChannel.doBind(NioServerSocketChannel.java:148) - at io.netty.channel.AbstractChannel$AbstractUnsafe.bind(AbstractChannel.java:431) - at io.netty.channel.DefaultChannelPipeline$HeadContext.bind(DefaultChannelPipeline.java:1353) - at io.netty.channel.AbstractChannelHandlerContext.bind(AbstractChannelHandlerContext.java:500) - at io.netty.channel.DefaultChannelPipeline.bind(DefaultChannelPipeline.java:994) - at io.netty.channel.Channel.bind(Channel.java:302) - at reactor.netty.transport.TransportConnector.lambda$null$0(TransportConnector.java:93) - at io.netty.util.concurrent.AbstractEventExecutor.runTask(AbstractEventExecutor.java:148) - at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:141) - at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:535) - at io.netty.channel.SingleThreadIoEventLoop.run(SingleThreadIoEventLoop.java:201) - at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:1195) - at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) - at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) - at java.base/java.lang.Thread.run(Thread.java:1583) - +Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.664 s -- in com.cygnus.cloud.InfrastructureConfigurationTest diff --git a/cygnus-cloud-service/target/surefire-reports/com.cygnus.cloud.identity.service.IdentityAuthenticationServiceTest.txt b/cygnus-cloud-service/target/surefire-reports/com.cygnus.cloud.identity.service.IdentityAuthenticationServiceTest.txt index 32be2f7..2718551 100644 --- a/cygnus-cloud-service/target/surefire-reports/com.cygnus.cloud.identity.service.IdentityAuthenticationServiceTest.txt +++ b/cygnus-cloud-service/target/surefire-reports/com.cygnus.cloud.identity.service.IdentityAuthenticationServiceTest.txt @@ -1,134 +1,4 @@ ------------------------------------------------------------------------------- Test set: com.cygnus.cloud.identity.service.IdentityAuthenticationServiceTest ------------------------------------------------------------------------------- -Tests run: 2, Failures: 0, Errors: 2, Skipped: 0, Time elapsed: 21.33 s <<< FAILURE! -- in com.cygnus.cloud.identity.service.IdentityAuthenticationServiceTest -com.cygnus.cloud.identity.service.IdentityAuthenticationServiceTest.rejectsInactiveUserWithoutLoadingMenu -- Time elapsed: 21.32 s <<< ERROR! -java.lang.IllegalStateException: Could not initialize plugin: interface org.mockito.plugins.MockMaker (alternate: null) - at org.mockito.internal.configuration.plugins.PluginLoader$1.invoke(PluginLoader.java:85) - at jdk.proxy2/jdk.proxy2.$Proxy46.isTypeMockable(Unknown Source) - at org.mockito.internal.util.MockUtil.typeMockabilityOf(MockUtil.java:78) - at org.mockito.internal.util.MockCreationValidator.validateType(MockCreationValidator.java:22) - at org.mockito.internal.creation.MockSettingsImpl.validatedSettings(MockSettingsImpl.java:275) - at org.mockito.internal.creation.MockSettingsImpl.build(MockSettingsImpl.java:236) - at org.mockito.internal.MockitoCore.mock(MockitoCore.java:82) - at org.mockito.Mockito.mock(Mockito.java:2198) - at org.mockito.internal.configuration.MockAnnotationProcessor.processAnnotationForMock(MockAnnotationProcessor.java:79) - at org.mockito.internal.configuration.MockAnnotationProcessor.process(MockAnnotationProcessor.java:28) - at org.mockito.internal.configuration.MockAnnotationProcessor.process(MockAnnotationProcessor.java:25) - at org.mockito.internal.configuration.IndependentAnnotationEngine.createMockFor(IndependentAnnotationEngine.java:44) - at org.mockito.internal.configuration.IndependentAnnotationEngine.process(IndependentAnnotationEngine.java:72) - at org.mockito.internal.configuration.InjectingAnnotationEngine.processIndependentAnnotations(InjectingAnnotationEngine.java:62) - at org.mockito.internal.configuration.InjectingAnnotationEngine.process(InjectingAnnotationEngine.java:47) - at org.mockito.MockitoAnnotations.openMocks(MockitoAnnotations.java:81) - at org.mockito.internal.framework.DefaultMockitoSession.(DefaultMockitoSession.java:43) - at org.mockito.internal.session.DefaultMockitoSessionBuilder.startMocking(DefaultMockitoSessionBuilder.java:83) - at org.mockito.junit.jupiter.MockitoExtension.beforeEach(MockitoExtension.java:160) - at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) - at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) -Caused by: java.lang.IllegalStateException: Internal problem occurred, please report it. Mockito is unable to load the default implementation of class that is a part of Mockito distribution. Failed to load interface org.mockito.plugins.MockMaker - at org.mockito.internal.configuration.plugins.DefaultMockitoPlugins.create(DefaultMockitoPlugins.java:105) - at org.mockito.internal.configuration.plugins.DefaultMockitoPlugins.getDefaultPlugin(DefaultMockitoPlugins.java:79) - at org.mockito.internal.configuration.plugins.PluginLoader.loadPlugin(PluginLoader.java:75) - at org.mockito.internal.configuration.plugins.PluginLoader.loadPlugin(PluginLoader.java:49) - at org.mockito.internal.configuration.plugins.PluginRegistry.(PluginRegistry.java:29) - at org.mockito.internal.configuration.plugins.Plugins.(Plugins.java:26) - at org.mockito.internal.MockitoCore.(MockitoCore.java:71) - at org.mockito.Mockito.(Mockito.java:1777) - at org.mockito.junit.jupiter.MockitoExtension.beforeEach(MockitoExtension.java:156) - ... 2 more -Caused by: java.lang.reflect.InvocationTargetException - at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:502) - at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:486) - at org.mockito.internal.configuration.plugins.DefaultMockitoPlugins.create(DefaultMockitoPlugins.java:103) - ... 10 more -Caused by: org.mockito.exceptions.base.MockitoInitializationException: -Could not initialize inline Byte Buddy mock maker. - -It appears as if your JDK does not supply a working agent attachment mechanism. -Java : 21 -JVM vendor name : Microsoft -JVM vendor version : 21.0.8+9-LTS -JVM name : OpenJDK 64-Bit Server VM -JVM version : 21.0.8+9-LTS -JVM info : mixed mode, sharing -OS name : Mac OS X -OS version : 26.5.2 - - at org.mockito.internal.creation.bytebuddy.InlineDelegateByteBuddyMockMaker.(InlineDelegateByteBuddyMockMaker.java:275) - at org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker.(InlineByteBuddyMockMaker.java:23) - ... 13 more -Caused by: java.lang.IllegalStateException: Could not self-attach to current VM using external process - set a property net.bytebuddy.agent.attacher.dump to dump the process output to a file at the specified location - at net.bytebuddy.agent.ByteBuddyAgent.installExternal(ByteBuddyAgent.java:672) - at net.bytebuddy.agent.ByteBuddyAgent.install(ByteBuddyAgent.java:599) - at net.bytebuddy.agent.ByteBuddyAgent.install(ByteBuddyAgent.java:579) - at net.bytebuddy.agent.ByteBuddyAgent.install(ByteBuddyAgent.java:531) - at net.bytebuddy.agent.ByteBuddyAgent.install(ByteBuddyAgent.java:508) - at org.mockito.internal.PremainAttachAccess.getInstrumentation(PremainAttachAccess.java:80) - at org.mockito.internal.creation.bytebuddy.InlineDelegateByteBuddyMockMaker.(InlineDelegateByteBuddyMockMaker.java:138) - ... 14 more - -com.cygnus.cloud.identity.service.IdentityAuthenticationServiceTest.authenticatesActiveUserAndReturnsStructuredSessionData -- Time elapsed: 0.003 s <<< ERROR! -java.lang.IllegalStateException: Could not initialize plugin: interface org.mockito.plugins.MockMaker (alternate: null) - at org.mockito.internal.configuration.plugins.PluginLoader$1.invoke(PluginLoader.java:85) - at jdk.proxy2/jdk.proxy2.$Proxy46.isTypeMockable(Unknown Source) - at org.mockito.internal.util.MockUtil.typeMockabilityOf(MockUtil.java:78) - at org.mockito.internal.util.MockCreationValidator.validateType(MockCreationValidator.java:22) - at org.mockito.internal.creation.MockSettingsImpl.validatedSettings(MockSettingsImpl.java:275) - at org.mockito.internal.creation.MockSettingsImpl.build(MockSettingsImpl.java:236) - at org.mockito.internal.MockitoCore.mock(MockitoCore.java:82) - at org.mockito.Mockito.mock(Mockito.java:2198) - at org.mockito.internal.configuration.MockAnnotationProcessor.processAnnotationForMock(MockAnnotationProcessor.java:79) - at org.mockito.internal.configuration.MockAnnotationProcessor.process(MockAnnotationProcessor.java:28) - at org.mockito.internal.configuration.MockAnnotationProcessor.process(MockAnnotationProcessor.java:25) - at org.mockito.internal.configuration.IndependentAnnotationEngine.createMockFor(IndependentAnnotationEngine.java:44) - at org.mockito.internal.configuration.IndependentAnnotationEngine.process(IndependentAnnotationEngine.java:72) - at org.mockito.internal.configuration.InjectingAnnotationEngine.processIndependentAnnotations(InjectingAnnotationEngine.java:62) - at org.mockito.internal.configuration.InjectingAnnotationEngine.process(InjectingAnnotationEngine.java:47) - at org.mockito.MockitoAnnotations.openMocks(MockitoAnnotations.java:81) - at org.mockito.internal.framework.DefaultMockitoSession.(DefaultMockitoSession.java:43) - at org.mockito.internal.session.DefaultMockitoSessionBuilder.startMocking(DefaultMockitoSessionBuilder.java:83) - at org.mockito.junit.jupiter.MockitoExtension.beforeEach(MockitoExtension.java:160) - at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) - at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) -Caused by: java.lang.IllegalStateException: Internal problem occurred, please report it. Mockito is unable to load the default implementation of class that is a part of Mockito distribution. Failed to load interface org.mockito.plugins.MockMaker - at org.mockito.internal.configuration.plugins.DefaultMockitoPlugins.create(DefaultMockitoPlugins.java:105) - at org.mockito.internal.configuration.plugins.DefaultMockitoPlugins.getDefaultPlugin(DefaultMockitoPlugins.java:79) - at org.mockito.internal.configuration.plugins.PluginLoader.loadPlugin(PluginLoader.java:75) - at org.mockito.internal.configuration.plugins.PluginLoader.loadPlugin(PluginLoader.java:49) - at org.mockito.internal.configuration.plugins.PluginRegistry.(PluginRegistry.java:29) - at org.mockito.internal.configuration.plugins.Plugins.(Plugins.java:26) - at org.mockito.internal.MockitoCore.(MockitoCore.java:71) - at org.mockito.Mockito.(Mockito.java:1777) - at org.mockito.junit.jupiter.MockitoExtension.beforeEach(MockitoExtension.java:156) - ... 2 more -Caused by: java.lang.reflect.InvocationTargetException - at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:502) - at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:486) - at org.mockito.internal.configuration.plugins.DefaultMockitoPlugins.create(DefaultMockitoPlugins.java:103) - ... 10 more -Caused by: org.mockito.exceptions.base.MockitoInitializationException: -Could not initialize inline Byte Buddy mock maker. - -It appears as if your JDK does not supply a working agent attachment mechanism. -Java : 21 -JVM vendor name : Microsoft -JVM vendor version : 21.0.8+9-LTS -JVM name : OpenJDK 64-Bit Server VM -JVM version : 21.0.8+9-LTS -JVM info : mixed mode, sharing -OS name : Mac OS X -OS version : 26.5.2 - - at org.mockito.internal.creation.bytebuddy.InlineDelegateByteBuddyMockMaker.(InlineDelegateByteBuddyMockMaker.java:275) - at org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker.(InlineByteBuddyMockMaker.java:23) - ... 13 more -Caused by: java.lang.IllegalStateException: Could not self-attach to current VM using external process - set a property net.bytebuddy.agent.attacher.dump to dump the process output to a file at the specified location - at net.bytebuddy.agent.ByteBuddyAgent.installExternal(ByteBuddyAgent.java:672) - at net.bytebuddy.agent.ByteBuddyAgent.install(ByteBuddyAgent.java:599) - at net.bytebuddy.agent.ByteBuddyAgent.install(ByteBuddyAgent.java:579) - at net.bytebuddy.agent.ByteBuddyAgent.install(ByteBuddyAgent.java:531) - at net.bytebuddy.agent.ByteBuddyAgent.install(ByteBuddyAgent.java:508) - at org.mockito.internal.PremainAttachAccess.getInstrumentation(PremainAttachAccess.java:80) - at org.mockito.internal.creation.bytebuddy.InlineDelegateByteBuddyMockMaker.(InlineDelegateByteBuddyMockMaker.java:138) - ... 14 more - +Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.029 s -- in com.cygnus.cloud.identity.service.IdentityAuthenticationServiceTest diff --git a/cygnus-cloud-service/target/surefire-reports/com.cygnus.cloud.identity.service.LegacyPasswordVerifierTest.txt b/cygnus-cloud-service/target/surefire-reports/com.cygnus.cloud.identity.service.LegacyPasswordVerifierTest.txt index 5d0bfc0..c96f211 100644 --- a/cygnus-cloud-service/target/surefire-reports/com.cygnus.cloud.identity.service.LegacyPasswordVerifierTest.txt +++ b/cygnus-cloud-service/target/surefire-reports/com.cygnus.cloud.identity.service.LegacyPasswordVerifierTest.txt @@ -1,4 +1,4 @@ ------------------------------------------------------------------------------- Test set: com.cygnus.cloud.identity.service.LegacyPasswordVerifierTest ------------------------------------------------------------------------------- -Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.052 s -- in com.cygnus.cloud.identity.service.LegacyPasswordVerifierTest +Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.017 s -- in com.cygnus.cloud.identity.service.LegacyPasswordVerifierTest diff --git a/cygnus-cloud-service/target/surefire-reports/com.cygnus.cloud.security.MachineTokenFlowTest.txt b/cygnus-cloud-service/target/surefire-reports/com.cygnus.cloud.security.MachineTokenFlowTest.txt index cdd89e5..cbd80e2 100644 --- a/cygnus-cloud-service/target/surefire-reports/com.cygnus.cloud.security.MachineTokenFlowTest.txt +++ b/cygnus-cloud-service/target/surefire-reports/com.cygnus.cloud.security.MachineTokenFlowTest.txt @@ -1,4 +1,4 @@ ------------------------------------------------------------------------------- Test set: com.cygnus.cloud.security.MachineTokenFlowTest ------------------------------------------------------------------------------- -Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.101 s -- in com.cygnus.cloud.security.MachineTokenFlowTest +Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.113 s -- in com.cygnus.cloud.security.MachineTokenFlowTest diff --git a/cygnus-cloud-service/target/surefire-reports/com.cygnus.cloud.system.SystemInfoControllerTest.txt b/cygnus-cloud-service/target/surefire-reports/com.cygnus.cloud.system.SystemInfoControllerTest.txt index cb024eb..bebc35d 100644 --- a/cygnus-cloud-service/target/surefire-reports/com.cygnus.cloud.system.SystemInfoControllerTest.txt +++ b/cygnus-cloud-service/target/surefire-reports/com.cygnus.cloud.system.SystemInfoControllerTest.txt @@ -1,4 +1,4 @@ ------------------------------------------------------------------------------- Test set: com.cygnus.cloud.system.SystemInfoControllerTest ------------------------------------------------------------------------------- -Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.383 s -- in com.cygnus.cloud.system.SystemInfoControllerTest +Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.333 s -- in com.cygnus.cloud.system.SystemInfoControllerTest diff --git a/cygnus-cloud-service/target/surefire-reports/com.cygnus.cloud.tenant.model.ClientLicenseTest.txt b/cygnus-cloud-service/target/surefire-reports/com.cygnus.cloud.tenant.model.ClientLicenseTest.txt new file mode 100644 index 0000000..fe19d81 --- /dev/null +++ b/cygnus-cloud-service/target/surefire-reports/com.cygnus.cloud.tenant.model.ClientLicenseTest.txt @@ -0,0 +1,4 @@ +------------------------------------------------------------------------------- +Test set: com.cygnus.cloud.tenant.model.ClientLicenseTest +------------------------------------------------------------------------------- +Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.043 s -- in com.cygnus.cloud.tenant.model.ClientLicenseTest diff --git a/cygnus-cloud-service/target/surefire-reports/com.cygnus.cloud.tenant.service.ActivationRateLimiterTest.txt b/cygnus-cloud-service/target/surefire-reports/com.cygnus.cloud.tenant.service.ActivationRateLimiterTest.txt new file mode 100644 index 0000000..1e10e01 --- /dev/null +++ b/cygnus-cloud-service/target/surefire-reports/com.cygnus.cloud.tenant.service.ActivationRateLimiterTest.txt @@ -0,0 +1,4 @@ +------------------------------------------------------------------------------- +Test set: com.cygnus.cloud.tenant.service.ActivationRateLimiterTest +------------------------------------------------------------------------------- +Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.008 s -- in com.cygnus.cloud.tenant.service.ActivationRateLimiterTest diff --git a/cygnus-cloud-service/target/surefire-reports/com.cygnus.cloud.tenant.service.LicenseKeyServiceTest.txt b/cygnus-cloud-service/target/surefire-reports/com.cygnus.cloud.tenant.service.LicenseKeyServiceTest.txt new file mode 100644 index 0000000..f4b2b73 --- /dev/null +++ b/cygnus-cloud-service/target/surefire-reports/com.cygnus.cloud.tenant.service.LicenseKeyServiceTest.txt @@ -0,0 +1,4 @@ +------------------------------------------------------------------------------- +Test set: com.cygnus.cloud.tenant.service.LicenseKeyServiceTest +------------------------------------------------------------------------------- +Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.069 s -- in com.cygnus.cloud.tenant.service.LicenseKeyServiceTest diff --git a/cygnus-cloud-service/target/surefire-reports/com.cygnus.cloud.tenant.service.TenantRegistrationServiceTest.txt b/cygnus-cloud-service/target/surefire-reports/com.cygnus.cloud.tenant.service.TenantRegistrationServiceTest.txt new file mode 100644 index 0000000..aa30900 --- /dev/null +++ b/cygnus-cloud-service/target/surefire-reports/com.cygnus.cloud.tenant.service.TenantRegistrationServiceTest.txt @@ -0,0 +1,4 @@ +------------------------------------------------------------------------------- +Test set: com.cygnus.cloud.tenant.service.TenantRegistrationServiceTest +------------------------------------------------------------------------------- +Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.436 s -- in com.cygnus.cloud.tenant.service.TenantRegistrationServiceTest diff --git a/cygnus-cloud-service/target/test-classes/com/cygnus/cloud/InfrastructureConfigurationTest.class b/cygnus-cloud-service/target/test-classes/com/cygnus/cloud/InfrastructureConfigurationTest.class index 32d7ad0..8c135e0 100644 Binary files a/cygnus-cloud-service/target/test-classes/com/cygnus/cloud/InfrastructureConfigurationTest.class and b/cygnus-cloud-service/target/test-classes/com/cygnus/cloud/InfrastructureConfigurationTest.class differ diff --git a/cygnus-cloud-service/target/test-classes/com/cygnus/cloud/identity/service/IdentityAuthenticationServiceTest.class b/cygnus-cloud-service/target/test-classes/com/cygnus/cloud/identity/service/IdentityAuthenticationServiceTest.class index 7eaec5e..d1437d7 100644 Binary files a/cygnus-cloud-service/target/test-classes/com/cygnus/cloud/identity/service/IdentityAuthenticationServiceTest.class and b/cygnus-cloud-service/target/test-classes/com/cygnus/cloud/identity/service/IdentityAuthenticationServiceTest.class differ diff --git a/cygnus-cloud-service/target/test-classes/com/cygnus/cloud/identity/service/LegacyPasswordVerifierTest.class b/cygnus-cloud-service/target/test-classes/com/cygnus/cloud/identity/service/LegacyPasswordVerifierTest.class index 6bb4e0c..81087e1 100644 Binary files a/cygnus-cloud-service/target/test-classes/com/cygnus/cloud/identity/service/LegacyPasswordVerifierTest.class and b/cygnus-cloud-service/target/test-classes/com/cygnus/cloud/identity/service/LegacyPasswordVerifierTest.class differ diff --git a/cygnus-cloud-service/target/test-classes/com/cygnus/cloud/security/MachineTokenFlowTest.class b/cygnus-cloud-service/target/test-classes/com/cygnus/cloud/security/MachineTokenFlowTest.class index c403df5..e3f6f27 100644 Binary files a/cygnus-cloud-service/target/test-classes/com/cygnus/cloud/security/MachineTokenFlowTest.class and b/cygnus-cloud-service/target/test-classes/com/cygnus/cloud/security/MachineTokenFlowTest.class differ diff --git a/cygnus-cloud-service/target/test-classes/com/cygnus/cloud/system/SystemInfoControllerTest$FixedClockConfiguration.class b/cygnus-cloud-service/target/test-classes/com/cygnus/cloud/system/SystemInfoControllerTest$FixedClockConfiguration.class index 12451cc..8828acf 100644 Binary files a/cygnus-cloud-service/target/test-classes/com/cygnus/cloud/system/SystemInfoControllerTest$FixedClockConfiguration.class and b/cygnus-cloud-service/target/test-classes/com/cygnus/cloud/system/SystemInfoControllerTest$FixedClockConfiguration.class differ diff --git a/cygnus-cloud-service/target/test-classes/com/cygnus/cloud/system/SystemInfoControllerTest.class b/cygnus-cloud-service/target/test-classes/com/cygnus/cloud/system/SystemInfoControllerTest.class index 551275e..629a7d6 100644 Binary files a/cygnus-cloud-service/target/test-classes/com/cygnus/cloud/system/SystemInfoControllerTest.class and b/cygnus-cloud-service/target/test-classes/com/cygnus/cloud/system/SystemInfoControllerTest.class differ diff --git a/cygnus-cloud-service/target/test-classes/com/cygnus/cloud/tenant/model/ClientLicenseTest.class b/cygnus-cloud-service/target/test-classes/com/cygnus/cloud/tenant/model/ClientLicenseTest.class new file mode 100644 index 0000000..555bc71 Binary files /dev/null and b/cygnus-cloud-service/target/test-classes/com/cygnus/cloud/tenant/model/ClientLicenseTest.class differ diff --git a/cygnus-cloud-service/target/test-classes/com/cygnus/cloud/tenant/service/ActivationRateLimiterTest.class b/cygnus-cloud-service/target/test-classes/com/cygnus/cloud/tenant/service/ActivationRateLimiterTest.class new file mode 100644 index 0000000..5dee220 Binary files /dev/null and b/cygnus-cloud-service/target/test-classes/com/cygnus/cloud/tenant/service/ActivationRateLimiterTest.class differ diff --git a/cygnus-cloud-service/target/test-classes/com/cygnus/cloud/tenant/service/LicenseKeyServiceTest.class b/cygnus-cloud-service/target/test-classes/com/cygnus/cloud/tenant/service/LicenseKeyServiceTest.class new file mode 100644 index 0000000..98c904b Binary files /dev/null and b/cygnus-cloud-service/target/test-classes/com/cygnus/cloud/tenant/service/LicenseKeyServiceTest.class differ diff --git a/cygnus-cloud-service/target/test-classes/com/cygnus/cloud/tenant/service/TenantRegistrationServiceTest.class b/cygnus-cloud-service/target/test-classes/com/cygnus/cloud/tenant/service/TenantRegistrationServiceTest.class new file mode 100644 index 0000000..de5d2d9 Binary files /dev/null and b/cygnus-cloud-service/target/test-classes/com/cygnus/cloud/tenant/service/TenantRegistrationServiceTest.class differ diff --git a/cygnus-installer/README.md b/cygnus-installer/README.md new file mode 100644 index 0000000..2cdc751 --- /dev/null +++ b/cygnus-installer/README.md @@ -0,0 +1,26 @@ +# Cygnus installation wizard + +The wizard validates Docker, activates a client license, generates a unique +3072-bit installation signing key, registers the installation, and writes the +deployment files. The license key is never persisted. + +```bash +java -jar target/cygnus-installer-1.0.0-SNAPSHOT.jar +``` + +Set `CYGNUS_CLOUD_URL` before execution. The wizard asks where deployment +files should be written and offers `CYGNUS_INSTALL_OUTPUT` as the default. + +The selected output directory contains: + +```text +compose.yml +config/ + installation.yml + keys/ + client-signing-private.pem + client-signing-public.pem +``` + +The private key is created with owner-only permissions on POSIX systems. The +plaintext installation license key is never written to the output directory. diff --git a/cygnus-installer/pom.xml b/cygnus-installer/pom.xml new file mode 100644 index 0000000..358c8ee --- /dev/null +++ b/cygnus-installer/pom.xml @@ -0,0 +1,48 @@ + + + 4.0.0 + + com.cygnus + cygnus-parent + 1.0.0-SNAPSHOT + ../pom.xml + + cygnus-installer + Cygnus Installation Wizard + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + + + + org.springframework.boot + spring-boot-starter-webflux + + + org.springframework.boot + spring-boot-starter-webflux-test + test + + + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot.version} + + repackage + + + + + diff --git a/cygnus-installer/src/main/java/com/cygnus/installer/ActivationClient.java b/cygnus-installer/src/main/java/com/cygnus/installer/ActivationClient.java new file mode 100644 index 0000000..634b299 --- /dev/null +++ b/cygnus-installer/src/main/java/com/cygnus/installer/ActivationClient.java @@ -0,0 +1,50 @@ +package com.cygnus.installer; + +import java.util.UUID; +import org.springframework.stereotype.Service; +import org.springframework.web.reactive.function.client.WebClient; + +@Service +public class ActivationClient { + + private final WebClient webClient; + + public ActivationClient(WebClient webClient) { + this.webClient = webClient; + } + + public ActivationDtos.ValidationResponse validate( + String clientCode, + String licenseKey, + UUID installationUuid, + String installerVersion) { + return webClient.post() + .uri("/api/v1/installations/activation/validate") + .bodyValue(new ActivationDtos.ValidationRequest( + clientCode, licenseKey, installationUuid, installerVersion)) + .retrieve() + .bodyToMono(ActivationDtos.ValidationResponse.class) + .block(); + } + + public ActivationDtos.RegistrationResponse register( + ActivationDtos.ValidationResponse validation, + String installationCode, + String installationName, + String assertionPublicKey, + String softwareVersion, + String environment) { + return webClient.post() + .uri("/api/v1/installations/register") + .bodyValue(new ActivationDtos.RegistrationRequest( + validation.activationToken(), + installationCode, + installationName, + assertionPublicKey, + softwareVersion, + environment)) + .retrieve() + .bodyToMono(ActivationDtos.RegistrationResponse.class) + .block(); + } +} diff --git a/cygnus-installer/src/main/java/com/cygnus/installer/ActivationDtos.java b/cygnus-installer/src/main/java/com/cygnus/installer/ActivationDtos.java new file mode 100644 index 0000000..8018e60 --- /dev/null +++ b/cygnus-installer/src/main/java/com/cygnus/installer/ActivationDtos.java @@ -0,0 +1,37 @@ +package com.cygnus.installer; + +import java.time.OffsetDateTime; +import java.util.UUID; + +final class ActivationDtos { + private ActivationDtos() {} + + record ValidationRequest( + String clientCode, + String licenseKey, + UUID installationUuid, + String installerVersion) {} + + record ValidationResponse( + String activationToken, + OffsetDateTime expiresAt, + UUID tenantId, + String tenantSlug, + String packageCode, + int maximumInstallations) {} + + record RegistrationRequest( + String activationToken, + String installationCode, + String installationName, + String assertionPublicKey, + String softwareVersion, + String environment) {} + + record RegistrationResponse( + UUID installationId, + UUID tenantId, + UUID clientId, + String installationCode, + String status) {} +} diff --git a/cygnus-installer/src/main/java/com/cygnus/installer/CommandExecutor.java b/cygnus-installer/src/main/java/com/cygnus/installer/CommandExecutor.java new file mode 100644 index 0000000..6a57c18 --- /dev/null +++ b/cygnus-installer/src/main/java/com/cygnus/installer/CommandExecutor.java @@ -0,0 +1,34 @@ +package com.cygnus.installer; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.List; +import java.util.concurrent.TimeUnit; +import org.springframework.stereotype.Component; + +@Component +public class CommandExecutor { + + public CommandResult execute(List command, Duration timeout) { + try { + Process process = new ProcessBuilder(command) + .redirectErrorStream(true) + .start(); + boolean completed = process.waitFor(timeout.toMillis(), TimeUnit.MILLISECONDS); + if (!completed) { + process.destroyForcibly(); + return new CommandResult(124, "Command timed out"); + } + return new CommandResult( + process.exitValue(), + new String(process.getInputStream().readAllBytes(), StandardCharsets.UTF_8) + .trim()); + } catch (IOException e) { + return new CommandResult(127, e.getMessage()); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return new CommandResult(130, "Command interrupted"); + } + } +} diff --git a/cygnus-installer/src/main/java/com/cygnus/installer/CommandResult.java b/cygnus-installer/src/main/java/com/cygnus/installer/CommandResult.java new file mode 100644 index 0000000..0718513 --- /dev/null +++ b/cygnus-installer/src/main/java/com/cygnus/installer/CommandResult.java @@ -0,0 +1,7 @@ +package com.cygnus.installer; + +public record CommandResult(int exitCode, String output) { + public boolean successful() { + return exitCode == 0; + } +} diff --git a/cygnus-installer/src/main/java/com/cygnus/installer/CygnusInstallerApplication.java b/cygnus-installer/src/main/java/com/cygnus/installer/CygnusInstallerApplication.java new file mode 100644 index 0000000..695abd8 --- /dev/null +++ b/cygnus-installer/src/main/java/com/cygnus/installer/CygnusInstallerApplication.java @@ -0,0 +1,27 @@ +package com.cygnus.installer; + +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.WebApplicationType; + +@SpringBootApplication +public class CygnusInstallerApplication implements CommandLineRunner { + + private final InstallationWizard wizard; + + public CygnusInstallerApplication(InstallationWizard wizard) { + this.wizard = wizard; + } + + public static void main(String[] args) { + SpringApplication application = new SpringApplication(CygnusInstallerApplication.class); + application.setWebApplicationType(WebApplicationType.NONE); + System.exit(SpringApplication.exit(application.run(args))); + } + + @Override + public void run(String... args) { + wizard.run(); + } +} diff --git a/cygnus-installer/src/main/java/com/cygnus/installer/DeploymentWriter.java b/cygnus-installer/src/main/java/com/cygnus/installer/DeploymentWriter.java new file mode 100644 index 0000000..6647b2d --- /dev/null +++ b/cygnus-installer/src/main/java/com/cygnus/installer/DeploymentWriter.java @@ -0,0 +1,68 @@ +package com.cygnus.installer; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.PosixFilePermissions; +import java.util.UUID; +import org.springframework.stereotype.Service; + +@Service +public class DeploymentWriter { + + public Path write( + Path output, + UUID installationUuid, + String installationCode, + ActivationDtos.ValidationResponse validation, + ActivationDtos.RegistrationResponse registration, + InstallationKeyPair keys, + String cloudUrl) { + try { + Path config = output.resolve("config"); + Path keyDirectory = config.resolve("keys"); + Files.createDirectories(keyDirectory); + writePrivate(keyDirectory.resolve("client-signing-private.pem"), keys.privateKeyPem()); + Files.writeString( + keyDirectory.resolve("client-signing-public.pem"), keys.publicKeyPem()); + Files.writeString(config.resolve("installation.yml"), """ + cygnus: + cloud-url: "%s" + tenant-id: "%s" + client-id: "%s" + installation-id: "%s" + installation-uuid: "%s" + installation-code: "%s" + signing-private-key: "file:./config/keys/client-signing-private.pem" + """.formatted( + cloudUrl, + validation.tenantId(), + registration.clientId(), + registration.installationId(), + installationUuid, + installationCode)); + Files.writeString(output.resolve("compose.yml"), """ + services: + cygnus-onprem: + image: ${CYGNUS_IMAGE:?Set CYGNUS_IMAGE} + restart: unless-stopped + volumes: + - ./config:/opt/cygnus/config:ro + environment: + CYGNUS_INSTALLATION_CONFIG: /opt/cygnus/config/installation.yml + """); + return output; + } catch (IOException e) { + throw new IllegalStateException("Could not write installation files", e); + } + } + + private void writePrivate(Path path, String content) throws IOException { + Files.writeString(path, content); + try { + Files.setPosixFilePermissions(path, PosixFilePermissions.fromString("rw-------")); + } catch (UnsupportedOperationException ignored) { + // Non-POSIX platforms use their native ACLs. + } + } +} diff --git a/cygnus-installer/src/main/java/com/cygnus/installer/DockerPrerequisiteChecker.java b/cygnus-installer/src/main/java/com/cygnus/installer/DockerPrerequisiteChecker.java new file mode 100644 index 0000000..05d355d --- /dev/null +++ b/cygnus-installer/src/main/java/com/cygnus/installer/DockerPrerequisiteChecker.java @@ -0,0 +1,31 @@ +package com.cygnus.installer; + +import java.time.Duration; +import java.util.List; +import org.springframework.stereotype.Service; + +@Service +public class DockerPrerequisiteChecker { + + private static final Duration TIMEOUT = Duration.ofSeconds(15); + private final CommandExecutor executor; + + public DockerPrerequisiteChecker(CommandExecutor executor) { + this.executor = executor; + } + + public void verify() { + require(List.of("docker", "--version"), "Docker CLI is not installed"); + require(List.of("docker", "info"), "Docker engine is not running"); + require( + List.of("docker", "compose", "version"), + "Docker Compose v2 is not installed"); + } + + private void require(List command, String message) { + CommandResult result = executor.execute(command, TIMEOUT); + if (!result.successful()) { + throw new PrerequisiteException(message + ". " + result.output()); + } + } +} diff --git a/cygnus-installer/src/main/java/com/cygnus/installer/InstallationKeyPair.java b/cygnus-installer/src/main/java/com/cygnus/installer/InstallationKeyPair.java new file mode 100644 index 0000000..7db5d69 --- /dev/null +++ b/cygnus-installer/src/main/java/com/cygnus/installer/InstallationKeyPair.java @@ -0,0 +1,3 @@ +package com.cygnus.installer; + +public record InstallationKeyPair(String privateKeyPem, String publicKeyPem) {} diff --git a/cygnus-installer/src/main/java/com/cygnus/installer/InstallationKeyService.java b/cygnus-installer/src/main/java/com/cygnus/installer/InstallationKeyService.java new file mode 100644 index 0000000..2b95986 --- /dev/null +++ b/cygnus-installer/src/main/java/com/cygnus/installer/InstallationKeyService.java @@ -0,0 +1,28 @@ +package com.cygnus.installer; + +import java.security.KeyPairGenerator; +import java.util.Base64; +import org.springframework.stereotype.Service; + +@Service +public class InstallationKeyService { + + public InstallationKeyPair generate() { + try { + KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA"); + generator.initialize(3072); + var pair = generator.generateKeyPair(); + return new InstallationKeyPair( + pem("PRIVATE KEY", pair.getPrivate().getEncoded()), + pem("PUBLIC KEY", pair.getPublic().getEncoded())); + } catch (Exception e) { + throw new IllegalStateException("Could not generate installation signing key", e); + } + } + + private String pem(String type, byte[] encoded) { + return "-----BEGIN " + type + "-----\n" + + Base64.getMimeEncoder(64, new byte[] {'\n'}).encodeToString(encoded) + + "\n-----END " + type + "-----\n"; + } +} diff --git a/cygnus-installer/src/main/java/com/cygnus/installer/InstallationWizard.java b/cygnus-installer/src/main/java/com/cygnus/installer/InstallationWizard.java new file mode 100644 index 0000000..aedf34b --- /dev/null +++ b/cygnus-installer/src/main/java/com/cygnus/installer/InstallationWizard.java @@ -0,0 +1,106 @@ +package com.cygnus.installer; + +import java.io.Console; +import java.nio.file.Path; +import java.util.Scanner; +import java.util.UUID; +import org.springframework.stereotype.Service; + +@Service +public class InstallationWizard { + + private final DockerPrerequisiteChecker prerequisites; + private final ActivationClient activationClient; + private final InstallationKeyService keyService; + private final DeploymentWriter deploymentWriter; + private final InstallerProperties properties; + + public InstallationWizard( + DockerPrerequisiteChecker prerequisites, + ActivationClient activationClient, + InstallationKeyService keyService, + DeploymentWriter deploymentWriter, + InstallerProperties properties) { + this.prerequisites = prerequisites; + this.activationClient = activationClient; + this.keyService = keyService; + this.deploymentWriter = deploymentWriter; + this.properties = properties; + } + + public void run() { + System.out.println("Cygnus installation wizard"); + prerequisites.verify(); + System.out.println("Docker prerequisites: OK"); + + Console console = System.console(); + Scanner scanner = console == null ? new Scanner(System.in) : null; + String clientCode = prompt(console, scanner, "Client code"); + String licenseKey = secret(console, scanner, "License key"); + String installationCode = prompt(console, scanner, "Installation code"); + String installationName = prompt(console, scanner, "Installation name"); + String environment = prompt(console, scanner, "Environment (prod/uat/dev)"); + String outputDirectory = promptWithDefault( + console, + scanner, + "Output directory", + properties.outputDirectory()); + + UUID installationUuid = UUID.randomUUID(); + var validation = activationClient.validate( + clientCode, licenseKey, installationUuid, properties.version()); + var keys = keyService.generate(); + var registration = activationClient.register( + validation, + installationCode, + installationName, + keys.publicKeyPem(), + properties.version(), + environment); + Path output = deploymentWriter.write( + Path.of(outputDirectory).toAbsolutePath().normalize(), + installationUuid, + installationCode, + validation, + registration, + keys, + properties.cloudUrl()); + System.out.println("Installation registered: " + registration.installationId()); + System.out.println("Deployment files created at: " + output); + } + + private String prompt(Console console, Scanner scanner, String label) { + String value = console != null + ? console.readLine("%s: ", label) + : readScanner(scanner, label); + if (value == null || value.isBlank()) { + throw new IllegalArgumentException(label + " is required"); + } + return value.trim(); + } + + private String secret(Console console, Scanner scanner, String label) { + if (console == null) { + return prompt(null, scanner, label); + } + char[] value = console.readPassword("%s: ", label); + if (value == null || value.length == 0) { + throw new IllegalArgumentException(label + " is required"); + } + return new String(value); + } + + private String promptWithDefault( + Console console, Scanner scanner, String label, String defaultValue) { + String prompt = label + " [" + defaultValue + "]"; + String value = console != null + ? console.readLine("%s: ", prompt) + : readScanner(scanner, prompt); + return value == null || value.isBlank() ? defaultValue : value.trim(); + } + + private String readScanner(Scanner scanner, String label) { + System.out.print(label + ": "); + return scanner.nextLine(); + } +} diff --git a/cygnus-installer/src/main/java/com/cygnus/installer/InstallerConfiguration.java b/cygnus-installer/src/main/java/com/cygnus/installer/InstallerConfiguration.java new file mode 100644 index 0000000..5274e81 --- /dev/null +++ b/cygnus-installer/src/main/java/com/cygnus/installer/InstallerConfiguration.java @@ -0,0 +1,16 @@ +package com.cygnus.installer; + +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.reactive.function.client.WebClient; + +@Configuration +@EnableConfigurationProperties(InstallerProperties.class) +public class InstallerConfiguration { + + @Bean + WebClient installerWebClient(InstallerProperties properties) { + return WebClient.builder().baseUrl(properties.cloudUrl()).build(); + } +} diff --git a/cygnus-installer/src/main/java/com/cygnus/installer/InstallerProperties.java b/cygnus-installer/src/main/java/com/cygnus/installer/InstallerProperties.java new file mode 100644 index 0000000..d95cb5f --- /dev/null +++ b/cygnus-installer/src/main/java/com/cygnus/installer/InstallerProperties.java @@ -0,0 +1,9 @@ +package com.cygnus.installer; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "cygnus.installer") +public record InstallerProperties( + String cloudUrl, + String outputDirectory, + String version) {} diff --git a/cygnus-installer/src/main/java/com/cygnus/installer/PrerequisiteException.java b/cygnus-installer/src/main/java/com/cygnus/installer/PrerequisiteException.java new file mode 100644 index 0000000..f2f2648 --- /dev/null +++ b/cygnus-installer/src/main/java/com/cygnus/installer/PrerequisiteException.java @@ -0,0 +1,7 @@ +package com.cygnus.installer; + +public class PrerequisiteException extends RuntimeException { + public PrerequisiteException(String message) { + super(message); + } +} diff --git a/cygnus-installer/src/main/resources/application.yml b/cygnus-installer/src/main/resources/application.yml new file mode 100644 index 0000000..47e863e --- /dev/null +++ b/cygnus-installer/src/main/resources/application.yml @@ -0,0 +1,13 @@ +spring: + main: + banner-mode: "off" + application: + name: cygnus-installer +cygnus: + installer: + cloud-url: ${CYGNUS_CLOUD_URL:http://localhost:8090} + output-directory: ${CYGNUS_INSTALL_OUTPUT:./cygnus-installation} + version: ${CYGNUS_INSTALLER_VERSION:1.0.0} +logging: + level: + root: WARN diff --git a/cygnus-installer/src/test/java/com/cygnus/installer/DockerPrerequisiteCheckerTest.java b/cygnus-installer/src/test/java/com/cygnus/installer/DockerPrerequisiteCheckerTest.java new file mode 100644 index 0000000..00b9613 --- /dev/null +++ b/cygnus-installer/src/test/java/com/cygnus/installer/DockerPrerequisiteCheckerTest.java @@ -0,0 +1,32 @@ +package com.cygnus.installer; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.time.Duration; +import java.util.List; +import org.junit.jupiter.api.Test; + +class DockerPrerequisiteCheckerTest { + + @Test + void blocksInstallationWhenDockerIsUnavailable() { + CommandExecutor executor = mock(CommandExecutor.class); + when(executor.execute(any(List.class), any(Duration.class))) + .thenReturn(new CommandResult(127, "not found")); + DockerPrerequisiteChecker checker = new DockerPrerequisiteChecker(executor); + assertThrows(PrerequisiteException.class, checker::verify); + } + + @Test + void acceptsDockerEngineAndComposeV2() { + CommandExecutor executor = mock(CommandExecutor.class); + when(executor.execute(any(List.class), any(Duration.class))) + .thenReturn(new CommandResult(0, "ok")); + DockerPrerequisiteChecker checker = new DockerPrerequisiteChecker(executor); + assertDoesNotThrow(checker::verify); + } +} diff --git a/cygnus-installer/target/classes/application.yml b/cygnus-installer/target/classes/application.yml new file mode 100644 index 0000000..47e863e --- /dev/null +++ b/cygnus-installer/target/classes/application.yml @@ -0,0 +1,13 @@ +spring: + main: + banner-mode: "off" + application: + name: cygnus-installer +cygnus: + installer: + cloud-url: ${CYGNUS_CLOUD_URL:http://localhost:8090} + output-directory: ${CYGNUS_INSTALL_OUTPUT:./cygnus-installation} + version: ${CYGNUS_INSTALLER_VERSION:1.0.0} +logging: + level: + root: WARN diff --git a/cygnus-installer/target/classes/com/cygnus/installer/ActivationClient.class b/cygnus-installer/target/classes/com/cygnus/installer/ActivationClient.class new file mode 100644 index 0000000..b1a936a Binary files /dev/null and b/cygnus-installer/target/classes/com/cygnus/installer/ActivationClient.class differ diff --git a/cygnus-installer/target/classes/com/cygnus/installer/ActivationDtos$RegistrationRequest.class b/cygnus-installer/target/classes/com/cygnus/installer/ActivationDtos$RegistrationRequest.class new file mode 100644 index 0000000..94a8c29 Binary files /dev/null and b/cygnus-installer/target/classes/com/cygnus/installer/ActivationDtos$RegistrationRequest.class differ diff --git a/cygnus-installer/target/classes/com/cygnus/installer/ActivationDtos$RegistrationResponse.class b/cygnus-installer/target/classes/com/cygnus/installer/ActivationDtos$RegistrationResponse.class new file mode 100644 index 0000000..288dcf3 Binary files /dev/null and b/cygnus-installer/target/classes/com/cygnus/installer/ActivationDtos$RegistrationResponse.class differ diff --git a/cygnus-installer/target/classes/com/cygnus/installer/ActivationDtos$ValidationRequest.class b/cygnus-installer/target/classes/com/cygnus/installer/ActivationDtos$ValidationRequest.class new file mode 100644 index 0000000..539a66e Binary files /dev/null and b/cygnus-installer/target/classes/com/cygnus/installer/ActivationDtos$ValidationRequest.class differ diff --git a/cygnus-installer/target/classes/com/cygnus/installer/ActivationDtos$ValidationResponse.class b/cygnus-installer/target/classes/com/cygnus/installer/ActivationDtos$ValidationResponse.class new file mode 100644 index 0000000..81c11c5 Binary files /dev/null and b/cygnus-installer/target/classes/com/cygnus/installer/ActivationDtos$ValidationResponse.class differ diff --git a/cygnus-installer/target/classes/com/cygnus/installer/ActivationDtos.class b/cygnus-installer/target/classes/com/cygnus/installer/ActivationDtos.class new file mode 100644 index 0000000..1b12e08 Binary files /dev/null and b/cygnus-installer/target/classes/com/cygnus/installer/ActivationDtos.class differ diff --git a/cygnus-installer/target/classes/com/cygnus/installer/CommandExecutor.class b/cygnus-installer/target/classes/com/cygnus/installer/CommandExecutor.class new file mode 100644 index 0000000..0e28bb2 Binary files /dev/null and b/cygnus-installer/target/classes/com/cygnus/installer/CommandExecutor.class differ diff --git a/cygnus-installer/target/classes/com/cygnus/installer/CommandResult.class b/cygnus-installer/target/classes/com/cygnus/installer/CommandResult.class new file mode 100644 index 0000000..3b409b6 Binary files /dev/null and b/cygnus-installer/target/classes/com/cygnus/installer/CommandResult.class differ diff --git a/cygnus-installer/target/classes/com/cygnus/installer/CygnusInstallerApplication.class b/cygnus-installer/target/classes/com/cygnus/installer/CygnusInstallerApplication.class new file mode 100644 index 0000000..88f26d0 Binary files /dev/null and b/cygnus-installer/target/classes/com/cygnus/installer/CygnusInstallerApplication.class differ diff --git a/cygnus-installer/target/classes/com/cygnus/installer/DeploymentWriter.class b/cygnus-installer/target/classes/com/cygnus/installer/DeploymentWriter.class new file mode 100644 index 0000000..5a7facb Binary files /dev/null and b/cygnus-installer/target/classes/com/cygnus/installer/DeploymentWriter.class differ diff --git a/cygnus-installer/target/classes/com/cygnus/installer/DockerPrerequisiteChecker.class b/cygnus-installer/target/classes/com/cygnus/installer/DockerPrerequisiteChecker.class new file mode 100644 index 0000000..5042c96 Binary files /dev/null and b/cygnus-installer/target/classes/com/cygnus/installer/DockerPrerequisiteChecker.class differ diff --git a/cygnus-installer/target/classes/com/cygnus/installer/InstallationKeyPair.class b/cygnus-installer/target/classes/com/cygnus/installer/InstallationKeyPair.class new file mode 100644 index 0000000..dc8fe12 Binary files /dev/null and b/cygnus-installer/target/classes/com/cygnus/installer/InstallationKeyPair.class differ diff --git a/cygnus-installer/target/classes/com/cygnus/installer/InstallationKeyService.class b/cygnus-installer/target/classes/com/cygnus/installer/InstallationKeyService.class new file mode 100644 index 0000000..a815aba Binary files /dev/null and b/cygnus-installer/target/classes/com/cygnus/installer/InstallationKeyService.class differ diff --git a/cygnus-installer/target/classes/com/cygnus/installer/InstallationWizard.class b/cygnus-installer/target/classes/com/cygnus/installer/InstallationWizard.class new file mode 100644 index 0000000..c2a769a Binary files /dev/null and b/cygnus-installer/target/classes/com/cygnus/installer/InstallationWizard.class differ diff --git a/cygnus-installer/target/classes/com/cygnus/installer/InstallerConfiguration.class b/cygnus-installer/target/classes/com/cygnus/installer/InstallerConfiguration.class new file mode 100644 index 0000000..26e37cc Binary files /dev/null and b/cygnus-installer/target/classes/com/cygnus/installer/InstallerConfiguration.class differ diff --git a/cygnus-installer/target/classes/com/cygnus/installer/InstallerProperties.class b/cygnus-installer/target/classes/com/cygnus/installer/InstallerProperties.class new file mode 100644 index 0000000..f596041 Binary files /dev/null and b/cygnus-installer/target/classes/com/cygnus/installer/InstallerProperties.class differ diff --git a/cygnus-installer/target/classes/com/cygnus/installer/PrerequisiteException.class b/cygnus-installer/target/classes/com/cygnus/installer/PrerequisiteException.class new file mode 100644 index 0000000..baebf90 Binary files /dev/null and b/cygnus-installer/target/classes/com/cygnus/installer/PrerequisiteException.class differ diff --git a/cygnus-installer/target/cygnus-installer-1.0.0-SNAPSHOT.jar b/cygnus-installer/target/cygnus-installer-1.0.0-SNAPSHOT.jar new file mode 100644 index 0000000..0a0eae4 Binary files /dev/null and b/cygnus-installer/target/cygnus-installer-1.0.0-SNAPSHOT.jar differ diff --git a/cygnus-installer/target/cygnus-installer-1.0.0-SNAPSHOT.jar.original b/cygnus-installer/target/cygnus-installer-1.0.0-SNAPSHOT.jar.original new file mode 100644 index 0000000..21bdebf Binary files /dev/null and b/cygnus-installer/target/cygnus-installer-1.0.0-SNAPSHOT.jar.original differ diff --git a/cygnus-installer/target/maven-archiver/pom.properties b/cygnus-installer/target/maven-archiver/pom.properties new file mode 100644 index 0000000..3f4c068 --- /dev/null +++ b/cygnus-installer/target/maven-archiver/pom.properties @@ -0,0 +1,3 @@ +artifactId=cygnus-installer +groupId=com.cygnus +version=1.0.0-SNAPSHOT diff --git a/cygnus-installer/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/cygnus-installer/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst new file mode 100644 index 0000000..f0f707f --- /dev/null +++ b/cygnus-installer/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst @@ -0,0 +1,17 @@ +com/cygnus/installer/ActivationDtos$ValidationResponse.class +com/cygnus/installer/ActivationDtos.class +com/cygnus/installer/DockerPrerequisiteChecker.class +com/cygnus/installer/CommandResult.class +com/cygnus/installer/InstallerConfiguration.class +com/cygnus/installer/CygnusInstallerApplication.class +com/cygnus/installer/InstallationWizard.class +com/cygnus/installer/CommandExecutor.class +com/cygnus/installer/DeploymentWriter.class +com/cygnus/installer/InstallerProperties.class +com/cygnus/installer/InstallationKeyService.class +com/cygnus/installer/ActivationClient.class +com/cygnus/installer/ActivationDtos$RegistrationRequest.class +com/cygnus/installer/ActivationDtos$RegistrationResponse.class +com/cygnus/installer/ActivationDtos$ValidationRequest.class +com/cygnus/installer/PrerequisiteException.class +com/cygnus/installer/InstallationKeyPair.class diff --git a/cygnus-installer/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/cygnus-installer/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst new file mode 100644 index 0000000..3c5aa82 --- /dev/null +++ b/cygnus-installer/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -0,0 +1,13 @@ +/Users/maddy/Projects/matrix/cygnus-installer/src/main/java/com/cygnus/installer/ActivationClient.java +/Users/maddy/Projects/matrix/cygnus-installer/src/main/java/com/cygnus/installer/ActivationDtos.java +/Users/maddy/Projects/matrix/cygnus-installer/src/main/java/com/cygnus/installer/CommandExecutor.java +/Users/maddy/Projects/matrix/cygnus-installer/src/main/java/com/cygnus/installer/CommandResult.java +/Users/maddy/Projects/matrix/cygnus-installer/src/main/java/com/cygnus/installer/CygnusInstallerApplication.java +/Users/maddy/Projects/matrix/cygnus-installer/src/main/java/com/cygnus/installer/DeploymentWriter.java +/Users/maddy/Projects/matrix/cygnus-installer/src/main/java/com/cygnus/installer/DockerPrerequisiteChecker.java +/Users/maddy/Projects/matrix/cygnus-installer/src/main/java/com/cygnus/installer/InstallationKeyPair.java +/Users/maddy/Projects/matrix/cygnus-installer/src/main/java/com/cygnus/installer/InstallationKeyService.java +/Users/maddy/Projects/matrix/cygnus-installer/src/main/java/com/cygnus/installer/InstallationWizard.java +/Users/maddy/Projects/matrix/cygnus-installer/src/main/java/com/cygnus/installer/InstallerConfiguration.java +/Users/maddy/Projects/matrix/cygnus-installer/src/main/java/com/cygnus/installer/InstallerProperties.java +/Users/maddy/Projects/matrix/cygnus-installer/src/main/java/com/cygnus/installer/PrerequisiteException.java diff --git a/cygnus-installer/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst b/cygnus-installer/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst new file mode 100644 index 0000000..4399feb --- /dev/null +++ b/cygnus-installer/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst @@ -0,0 +1 @@ +com/cygnus/installer/DockerPrerequisiteCheckerTest.class diff --git a/cygnus-installer/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst b/cygnus-installer/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst new file mode 100644 index 0000000..d6f5bc5 --- /dev/null +++ b/cygnus-installer/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst @@ -0,0 +1 @@ +/Users/maddy/Projects/matrix/cygnus-installer/src/test/java/com/cygnus/installer/DockerPrerequisiteCheckerTest.java diff --git a/cygnus-installer/target/surefire-reports/TEST-com.cygnus.installer.DockerPrerequisiteCheckerTest.xml b/cygnus-installer/target/surefire-reports/TEST-com.cygnus.installer.DockerPrerequisiteCheckerTest.xml new file mode 100644 index 0000000..a01b3a9 --- /dev/null +++ b/cygnus-installer/target/surefire-reports/TEST-com.cygnus.installer.DockerPrerequisiteCheckerTest.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/cygnus-installer/target/surefire-reports/com.cygnus.installer.DockerPrerequisiteCheckerTest.txt b/cygnus-installer/target/surefire-reports/com.cygnus.installer.DockerPrerequisiteCheckerTest.txt new file mode 100644 index 0000000..7bd0538 --- /dev/null +++ b/cygnus-installer/target/surefire-reports/com.cygnus.installer.DockerPrerequisiteCheckerTest.txt @@ -0,0 +1,4 @@ +------------------------------------------------------------------------------- +Test set: com.cygnus.installer.DockerPrerequisiteCheckerTest +------------------------------------------------------------------------------- +Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.412 s -- in com.cygnus.installer.DockerPrerequisiteCheckerTest diff --git a/cygnus-installer/target/test-classes/com/cygnus/installer/DockerPrerequisiteCheckerTest.class b/cygnus-installer/target/test-classes/com/cygnus/installer/DockerPrerequisiteCheckerTest.class new file mode 100644 index 0000000..4d8ec4e Binary files /dev/null and b/cygnus-installer/target/test-classes/com/cygnus/installer/DockerPrerequisiteCheckerTest.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/admin/controller/Admin.class b/cygnus-onprem-app/target/classes/matrix/nimble/admin/controller/Admin.class index 5c97e29..8b647f6 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/admin/controller/Admin.class and b/cygnus-onprem-app/target/classes/matrix/nimble/admin/controller/Admin.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/admin/model/MaintenanceHandler.class b/cygnus-onprem-app/target/classes/matrix/nimble/admin/model/MaintenanceHandler.class index a633782..1f2d975 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/admin/model/MaintenanceHandler.class and b/cygnus-onprem-app/target/classes/matrix/nimble/admin/model/MaintenanceHandler.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/admin/model/PortfolioBranch.class b/cygnus-onprem-app/target/classes/matrix/nimble/admin/model/PortfolioBranch.class index 975f9eb..8650e9d 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/admin/model/PortfolioBranch.class and b/cygnus-onprem-app/target/classes/matrix/nimble/admin/model/PortfolioBranch.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/admin/model/PortfolioHandler.class b/cygnus-onprem-app/target/classes/matrix/nimble/admin/model/PortfolioHandler.class index 8369d02..8ab7911 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/admin/model/PortfolioHandler.class and b/cygnus-onprem-app/target/classes/matrix/nimble/admin/model/PortfolioHandler.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/admin/model/User.class b/cygnus-onprem-app/target/classes/matrix/nimble/admin/model/User.class index f621143..f460a22 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/admin/model/User.class and b/cygnus-onprem-app/target/classes/matrix/nimble/admin/model/User.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/admin/model/UserHandler.class b/cygnus-onprem-app/target/classes/matrix/nimble/admin/model/UserHandler.class index de75c55..4d0ca6b 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/admin/model/UserHandler.class and b/cygnus-onprem-app/target/classes/matrix/nimble/admin/model/UserHandler.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/cloud/cache/OnPremRedisCacheService.class b/cygnus-onprem-app/target/classes/matrix/nimble/cloud/cache/OnPremRedisCacheService.class index a212558..6c9ab64 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/cloud/cache/OnPremRedisCacheService.class and b/cygnus-onprem-app/target/classes/matrix/nimble/cloud/cache/OnPremRedisCacheService.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/cloud/cache/OnPremRedisConfiguration.class b/cygnus-onprem-app/target/classes/matrix/nimble/cloud/cache/OnPremRedisConfiguration.class index 5fc25b5..3feab82 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/cloud/cache/OnPremRedisConfiguration.class and b/cygnus-onprem-app/target/classes/matrix/nimble/cloud/cache/OnPremRedisConfiguration.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/cloud/identity/CloudAuthenticationException$Reason.class b/cygnus-onprem-app/target/classes/matrix/nimble/cloud/identity/CloudAuthenticationException$Reason.class index ddca0a0..5f2117d 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/cloud/identity/CloudAuthenticationException$Reason.class and b/cygnus-onprem-app/target/classes/matrix/nimble/cloud/identity/CloudAuthenticationException$Reason.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/cloud/identity/CloudAuthenticationException.class b/cygnus-onprem-app/target/classes/matrix/nimble/cloud/identity/CloudAuthenticationException.class index 675594c..ca637fb 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/cloud/identity/CloudAuthenticationException.class and b/cygnus-onprem-app/target/classes/matrix/nimble/cloud/identity/CloudAuthenticationException.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/cloud/identity/CloudAuthenticationGateway.class b/cygnus-onprem-app/target/classes/matrix/nimble/cloud/identity/CloudAuthenticationGateway.class index 1046c65..5ce5609 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/cloud/identity/CloudAuthenticationGateway.class and b/cygnus-onprem-app/target/classes/matrix/nimble/cloud/identity/CloudAuthenticationGateway.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/cloud/identity/CloudIdentityConfiguration.class b/cygnus-onprem-app/target/classes/matrix/nimble/cloud/identity/CloudIdentityConfiguration.class index 5b0f8ed..c52f91c 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/cloud/identity/CloudIdentityConfiguration.class and b/cygnus-onprem-app/target/classes/matrix/nimble/cloud/identity/CloudIdentityConfiguration.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/cloud/identity/CloudSessionMapper.class b/cygnus-onprem-app/target/classes/matrix/nimble/cloud/identity/CloudSessionMapper.class index d9c9490..a96f5d2 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/cloud/identity/CloudSessionMapper.class and b/cygnus-onprem-app/target/classes/matrix/nimble/cloud/identity/CloudSessionMapper.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/cloud/identity/SessionMenuRenderer.class b/cygnus-onprem-app/target/classes/matrix/nimble/cloud/identity/SessionMenuRenderer.class index 4f4b88d..0ca4dc0 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/cloud/identity/SessionMenuRenderer.class and b/cygnus-onprem-app/target/classes/matrix/nimble/cloud/identity/SessionMenuRenderer.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/controller/Ajax.class b/cygnus-onprem-app/target/classes/matrix/nimble/controller/Ajax.class index 0b516bf..a59fa80 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/controller/Ajax.class and b/cygnus-onprem-app/target/classes/matrix/nimble/controller/Ajax.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/controller/AjaxV2.class b/cygnus-onprem-app/target/classes/matrix/nimble/controller/AjaxV2.class index 44e23c4..543054c 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/controller/AjaxV2.class and b/cygnus-onprem-app/target/classes/matrix/nimble/controller/AjaxV2.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/controller/CaseUpload.class b/cygnus-onprem-app/target/classes/matrix/nimble/controller/CaseUpload.class index 21f05dc..04c8d6a 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/controller/CaseUpload.class and b/cygnus-onprem-app/target/classes/matrix/nimble/controller/CaseUpload.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/controller/DocAjax.class b/cygnus-onprem-app/target/classes/matrix/nimble/controller/DocAjax.class index 2b5855a..54d7df8 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/controller/DocAjax.class and b/cygnus-onprem-app/target/classes/matrix/nimble/controller/DocAjax.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/controller/General.class b/cygnus-onprem-app/target/classes/matrix/nimble/controller/General.class index d7e92cc..6aaeabc 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/controller/General.class and b/cygnus-onprem-app/target/classes/matrix/nimble/controller/General.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/controller/SessionController.class b/cygnus-onprem-app/target/classes/matrix/nimble/controller/SessionController.class index 07d6d5a..4fd58ec 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/controller/SessionController.class and b/cygnus-onprem-app/target/classes/matrix/nimble/controller/SessionController.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/edp/cutoff/controller/CutOff.class b/cygnus-onprem-app/target/classes/matrix/nimble/edp/cutoff/controller/CutOff.class index f1ca5dd..2751247 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/edp/cutoff/controller/CutOff.class and b/cygnus-onprem-app/target/classes/matrix/nimble/edp/cutoff/controller/CutOff.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/edp/cutoff/model/Allocation.class b/cygnus-onprem-app/target/classes/matrix/nimble/edp/cutoff/model/Allocation.class index 04af865..3b053df 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/edp/cutoff/model/Allocation.class and b/cygnus-onprem-app/target/classes/matrix/nimble/edp/cutoff/model/Allocation.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/edp/cutoff/model/AutoCutThread.class b/cygnus-onprem-app/target/classes/matrix/nimble/edp/cutoff/model/AutoCutThread.class index d9d24a9..6d7b543 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/edp/cutoff/model/AutoCutThread.class and b/cygnus-onprem-app/target/classes/matrix/nimble/edp/cutoff/model/AutoCutThread.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/edp/cutoff/model/CutOffList.class b/cygnus-onprem-app/target/classes/matrix/nimble/edp/cutoff/model/CutOffList.class index c44a057..d84fd9a 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/edp/cutoff/model/CutOffList.class and b/cygnus-onprem-app/target/classes/matrix/nimble/edp/cutoff/model/CutOffList.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/edp/cutoff/model/CutOffRecord.class b/cygnus-onprem-app/target/classes/matrix/nimble/edp/cutoff/model/CutOffRecord.class index e66c25a..5ca2db2 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/edp/cutoff/model/CutOffRecord.class and b/cygnus-onprem-app/target/classes/matrix/nimble/edp/cutoff/model/CutOffRecord.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/edp/cutoff/model/PhotoCases.class b/cygnus-onprem-app/target/classes/matrix/nimble/edp/cutoff/model/PhotoCases.class index 626901f..a767cf7 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/edp/cutoff/model/PhotoCases.class and b/cygnus-onprem-app/target/classes/matrix/nimble/edp/cutoff/model/PhotoCases.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/edp/cutoff/model/ReferenceSheet.class b/cygnus-onprem-app/target/classes/matrix/nimble/edp/cutoff/model/ReferenceSheet.class index 3217190..cec0e4a 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/edp/cutoff/model/ReferenceSheet.class and b/cygnus-onprem-app/target/classes/matrix/nimble/edp/cutoff/model/ReferenceSheet.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/edp/cutoff/model/SMS.class b/cygnus-onprem-app/target/classes/matrix/nimble/edp/cutoff/model/SMS.class index a736f54..ff27bb3 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/edp/cutoff/model/SMS.class and b/cygnus-onprem-app/target/classes/matrix/nimble/edp/cutoff/model/SMS.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/edp/cutoff/model/SMSRecord.class b/cygnus-onprem-app/target/classes/matrix/nimble/edp/cutoff/model/SMSRecord.class index ccbf92a..ab792f3 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/edp/cutoff/model/SMSRecord.class and b/cygnus-onprem-app/target/classes/matrix/nimble/edp/cutoff/model/SMSRecord.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/edp/cutoff/model/SendToOperation.class b/cygnus-onprem-app/target/classes/matrix/nimble/edp/cutoff/model/SendToOperation.class index b619fda..5e7f5af 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/edp/cutoff/model/SendToOperation.class and b/cygnus-onprem-app/target/classes/matrix/nimble/edp/cutoff/model/SendToOperation.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/edp/cutoff/model/Telesheet.class b/cygnus-onprem-app/target/classes/matrix/nimble/edp/cutoff/model/Telesheet.class index 815d506..661d29e 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/edp/cutoff/model/Telesheet.class and b/cygnus-onprem-app/target/classes/matrix/nimble/edp/cutoff/model/Telesheet.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/edp/dedupe/controller/Dedupe.class b/cygnus-onprem-app/target/classes/matrix/nimble/edp/dedupe/controller/Dedupe.class index a5355ef..3d7d800 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/edp/dedupe/controller/Dedupe.class and b/cygnus-onprem-app/target/classes/matrix/nimble/edp/dedupe/controller/Dedupe.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/edp/dedupe/model/DedupeDetails.class b/cygnus-onprem-app/target/classes/matrix/nimble/edp/dedupe/model/DedupeDetails.class index 5aa87c7..46ddea2 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/edp/dedupe/model/DedupeDetails.class and b/cygnus-onprem-app/target/classes/matrix/nimble/edp/dedupe/model/DedupeDetails.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/edp/dedupe/model/DedupeDo.class b/cygnus-onprem-app/target/classes/matrix/nimble/edp/dedupe/model/DedupeDo.class index a5ecf65..5c440af 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/edp/dedupe/model/DedupeDo.class and b/cygnus-onprem-app/target/classes/matrix/nimble/edp/dedupe/model/DedupeDo.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/edp/dedupe/model/DedupeHandler.class b/cygnus-onprem-app/target/classes/matrix/nimble/edp/dedupe/model/DedupeHandler.class index 5fd5ca8..797cdf9 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/edp/dedupe/model/DedupeHandler.class and b/cygnus-onprem-app/target/classes/matrix/nimble/edp/dedupe/model/DedupeHandler.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/edp/dedupe/model/DedupeRecord.class b/cygnus-onprem-app/target/classes/matrix/nimble/edp/dedupe/model/DedupeRecord.class index 4e3c0fb..afb16ad 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/edp/dedupe/model/DedupeRecord.class and b/cygnus-onprem-app/target/classes/matrix/nimble/edp/dedupe/model/DedupeRecord.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/edp/dedupe/model/DedupeRun.class b/cygnus-onprem-app/target/classes/matrix/nimble/edp/dedupe/model/DedupeRun.class index 9fbcae6..67ca473 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/edp/dedupe/model/DedupeRun.class and b/cygnus-onprem-app/target/classes/matrix/nimble/edp/dedupe/model/DedupeRun.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/edp/dedupe/model/SendDedupe.class b/cygnus-onprem-app/target/classes/matrix/nimble/edp/dedupe/model/SendDedupe.class index 05cc777..fe155f4 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/edp/dedupe/model/SendDedupe.class and b/cygnus-onprem-app/target/classes/matrix/nimble/edp/dedupe/model/SendDedupe.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/edp/documents/controller/EDPDocuments.class b/cygnus-onprem-app/target/classes/matrix/nimble/edp/documents/controller/EDPDocuments.class index d59694f..26d379f 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/edp/documents/controller/EDPDocuments.class and b/cygnus-onprem-app/target/classes/matrix/nimble/edp/documents/controller/EDPDocuments.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/edp/documents/model/DocTelesheet.class b/cygnus-onprem-app/target/classes/matrix/nimble/edp/documents/model/DocTelesheet.class index d3e1915..0924e3c 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/edp/documents/model/DocTelesheet.class and b/cygnus-onprem-app/target/classes/matrix/nimble/edp/documents/model/DocTelesheet.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/edp/documents/model/EDPDocumentHandler.class b/cygnus-onprem-app/target/classes/matrix/nimble/edp/documents/model/EDPDocumentHandler.class index 803bcde..50f0464 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/edp/documents/model/EDPDocumentHandler.class and b/cygnus-onprem-app/target/classes/matrix/nimble/edp/documents/model/EDPDocumentHandler.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/edp/output/model/ReportOutput.class b/cygnus-onprem-app/target/classes/matrix/nimble/edp/output/model/ReportOutput.class index 323186b..ffc1672 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/edp/output/model/ReportOutput.class and b/cygnus-onprem-app/target/classes/matrix/nimble/edp/output/model/ReportOutput.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/edp/punching/controller/CasePucnhing.class b/cygnus-onprem-app/target/classes/matrix/nimble/edp/punching/controller/CasePucnhing.class index 2a1816b..7c75583 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/edp/punching/controller/CasePucnhing.class and b/cygnus-onprem-app/target/classes/matrix/nimble/edp/punching/controller/CasePucnhing.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/edp/punching/controller/EditCasesGrid.class b/cygnus-onprem-app/target/classes/matrix/nimble/edp/punching/controller/EditCasesGrid.class index e9152e7..ea1fa2c 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/edp/punching/controller/EditCasesGrid.class and b/cygnus-onprem-app/target/classes/matrix/nimble/edp/punching/controller/EditCasesGrid.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/edp/punching/controller/Punching.class b/cygnus-onprem-app/target/classes/matrix/nimble/edp/punching/controller/Punching.class index a540638..441b5d4 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/edp/punching/controller/Punching.class and b/cygnus-onprem-app/target/classes/matrix/nimble/edp/punching/controller/Punching.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/edp/punching/model/CaseDetails.class b/cygnus-onprem-app/target/classes/matrix/nimble/edp/punching/model/CaseDetails.class index 42c061e..bea2579 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/edp/punching/model/CaseDetails.class and b/cygnus-onprem-app/target/classes/matrix/nimble/edp/punching/model/CaseDetails.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/edp/punching/model/CaseGrid.class b/cygnus-onprem-app/target/classes/matrix/nimble/edp/punching/model/CaseGrid.class index 7484a5a..f1f94ac 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/edp/punching/model/CaseGrid.class and b/cygnus-onprem-app/target/classes/matrix/nimble/edp/punching/model/CaseGrid.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/edp/punching/model/DocDetails.class b/cygnus-onprem-app/target/classes/matrix/nimble/edp/punching/model/DocDetails.class index afea711..5c0e614 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/edp/punching/model/DocDetails.class and b/cygnus-onprem-app/target/classes/matrix/nimble/edp/punching/model/DocDetails.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/edp/punching/model/PunchingHandler.class b/cygnus-onprem-app/target/classes/matrix/nimble/edp/punching/model/PunchingHandler.class index db14e98..2a1323c 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/edp/punching/model/PunchingHandler.class and b/cygnus-onprem-app/target/classes/matrix/nimble/edp/punching/model/PunchingHandler.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/edp/tools/controller/EDPTools.class b/cygnus-onprem-app/target/classes/matrix/nimble/edp/tools/controller/EDPTools.class index 3e3aea3..5614e21 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/edp/tools/controller/EDPTools.class and b/cygnus-onprem-app/target/classes/matrix/nimble/edp/tools/controller/EDPTools.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/edp/tools/model/AddEditProduct.class b/cygnus-onprem-app/target/classes/matrix/nimble/edp/tools/model/AddEditProduct.class index 4d27c36..af5605b 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/edp/tools/model/AddEditProduct.class and b/cygnus-onprem-app/target/classes/matrix/nimble/edp/tools/model/AddEditProduct.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/edp/tools/model/ColonyFuncs.class b/cygnus-onprem-app/target/classes/matrix/nimble/edp/tools/model/ColonyFuncs.class index eb63604..dfa4d0a 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/edp/tools/model/ColonyFuncs.class and b/cygnus-onprem-app/target/classes/matrix/nimble/edp/tools/model/ColonyFuncs.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/edp/tools/model/CutOffPRecord.class b/cygnus-onprem-app/target/classes/matrix/nimble/edp/tools/model/CutOffPRecord.class index e9ffa34..1679bd0 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/edp/tools/model/CutOffPRecord.class and b/cygnus-onprem-app/target/classes/matrix/nimble/edp/tools/model/CutOffPRecord.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/edp/tools/model/CutoffPending.class b/cygnus-onprem-app/target/classes/matrix/nimble/edp/tools/model/CutoffPending.class index fce08fb..dfee09a 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/edp/tools/model/CutoffPending.class and b/cygnus-onprem-app/target/classes/matrix/nimble/edp/tools/model/CutoffPending.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/edp/tools/model/DelCaseDetails.class b/cygnus-onprem-app/target/classes/matrix/nimble/edp/tools/model/DelCaseDetails.class index 7d867ea..cb8d4e3 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/edp/tools/model/DelCaseDetails.class and b/cygnus-onprem-app/target/classes/matrix/nimble/edp/tools/model/DelCaseDetails.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/edp/tools/model/DelCaseGrid.class b/cygnus-onprem-app/target/classes/matrix/nimble/edp/tools/model/DelCaseGrid.class index 3a3da7f..691c9a7 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/edp/tools/model/DelCaseGrid.class and b/cygnus-onprem-app/target/classes/matrix/nimble/edp/tools/model/DelCaseGrid.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/edp/tools/model/EdpToolsHandler.class b/cygnus-onprem-app/target/classes/matrix/nimble/edp/tools/model/EdpToolsHandler.class index 8382f93..e6ba740 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/edp/tools/model/EdpToolsHandler.class and b/cygnus-onprem-app/target/classes/matrix/nimble/edp/tools/model/EdpToolsHandler.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/hr/controller/HR.class b/cygnus-onprem-app/target/classes/matrix/nimble/hr/controller/HR.class index b5332ec..5ed6051 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/hr/controller/HR.class and b/cygnus-onprem-app/target/classes/matrix/nimble/hr/controller/HR.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/hr/model/EmployeeHandler.class b/cygnus-onprem-app/target/classes/matrix/nimble/hr/model/EmployeeHandler.class index b801c9e..9fafa52 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/hr/model/EmployeeHandler.class and b/cygnus-onprem-app/target/classes/matrix/nimble/hr/model/EmployeeHandler.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/mis/controller/MIS.class b/cygnus-onprem-app/target/classes/matrix/nimble/mis/controller/MIS.class index ef7bab9..785d39e 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/mis/controller/MIS.class and b/cygnus-onprem-app/target/classes/matrix/nimble/mis/controller/MIS.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/mis/controller/Monitoring.class b/cygnus-onprem-app/target/classes/matrix/nimble/mis/controller/Monitoring.class index 6e2632d..059ccd7 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/mis/controller/Monitoring.class and b/cygnus-onprem-app/target/classes/matrix/nimble/mis/controller/Monitoring.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/mis/model/Employee.class b/cygnus-onprem-app/target/classes/matrix/nimble/mis/model/Employee.class index df16324..2062948 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/mis/model/Employee.class and b/cygnus-onprem-app/target/classes/matrix/nimble/mis/model/Employee.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/mis/model/Filters.class b/cygnus-onprem-app/target/classes/matrix/nimble/mis/model/Filters.class index 1a31405..3af13dc 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/mis/model/Filters.class and b/cygnus-onprem-app/target/classes/matrix/nimble/mis/model/Filters.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/mis/model/MISHandler.class b/cygnus-onprem-app/target/classes/matrix/nimble/mis/model/MISHandler.class index 0e77a1a..74262ff 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/mis/model/MISHandler.class and b/cygnus-onprem-app/target/classes/matrix/nimble/mis/model/MISHandler.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/mis/model/MonitoringHandler.class b/cygnus-onprem-app/target/classes/matrix/nimble/mis/model/MonitoringHandler.class index 9942e0f..bd1b7b0 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/mis/model/MonitoringHandler.class and b/cygnus-onprem-app/target/classes/matrix/nimble/mis/model/MonitoringHandler.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/model/DownloadUploadSettings.class b/cygnus-onprem-app/target/classes/matrix/nimble/model/DownloadUploadSettings.class index c309462..79c1e6c 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/model/DownloadUploadSettings.class and b/cygnus-onprem-app/target/classes/matrix/nimble/model/DownloadUploadSettings.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/model/GeneralHandler.class b/cygnus-onprem-app/target/classes/matrix/nimble/model/GeneralHandler.class index 0c34def..a0dfff6 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/model/GeneralHandler.class and b/cygnus-onprem-app/target/classes/matrix/nimble/model/GeneralHandler.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/model/Login.class b/cygnus-onprem-app/target/classes/matrix/nimble/model/Login.class index 48197c3..fe5ad76 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/model/Login.class and b/cygnus-onprem-app/target/classes/matrix/nimble/model/Login.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/model/LoginKey.class b/cygnus-onprem-app/target/classes/matrix/nimble/model/LoginKey.class index 6e4bbcd..3fb90af 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/model/LoginKey.class and b/cygnus-onprem-app/target/classes/matrix/nimble/model/LoginKey.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/model/MailboxHandler.class b/cygnus-onprem-app/target/classes/matrix/nimble/model/MailboxHandler.class index 33d5aa8..af12a42 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/model/MailboxHandler.class and b/cygnus-onprem-app/target/classes/matrix/nimble/model/MailboxHandler.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/model/Session.class b/cygnus-onprem-app/target/classes/matrix/nimble/model/Session.class index 4fb48c9..474322d 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/model/Session.class and b/cygnus-onprem-app/target/classes/matrix/nimble/model/Session.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/model/UploadHandler.class b/cygnus-onprem-app/target/classes/matrix/nimble/model/UploadHandler.class index 42be011..77c728c 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/model/UploadHandler.class and b/cygnus-onprem-app/target/classes/matrix/nimble/model/UploadHandler.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/operation/documents/controller/OprDocuments.class b/cygnus-onprem-app/target/classes/matrix/nimble/operation/documents/controller/OprDocuments.class index bff7480..996fbe0 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/operation/documents/controller/OprDocuments.class and b/cygnus-onprem-app/target/classes/matrix/nimble/operation/documents/controller/OprDocuments.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/operation/documents/model/BankDoc.class b/cygnus-onprem-app/target/classes/matrix/nimble/operation/documents/model/BankDoc.class index ab5f965..cc16a9c 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/operation/documents/model/BankDoc.class and b/cygnus-onprem-app/target/classes/matrix/nimble/operation/documents/model/BankDoc.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/operation/documents/model/OprDocumentHandler.class b/cygnus-onprem-app/target/classes/matrix/nimble/operation/documents/model/OprDocumentHandler.class index 73b727f..c632ffc 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/operation/documents/model/OprDocumentHandler.class and b/cygnus-onprem-app/target/classes/matrix/nimble/operation/documents/model/OprDocumentHandler.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/controller/BankReports.class b/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/controller/BankReports.class index 37e3897..b6242d7 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/controller/BankReports.class and b/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/controller/BankReports.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/controller/TrackerGenerator.class b/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/controller/TrackerGenerator.class index d1c5c30..72a9d13 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/controller/TrackerGenerator.class and b/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/controller/TrackerGenerator.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/BankReportHandler.class b/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/BankReportHandler.class index 5bc300d..4ffe352 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/BankReportHandler.class and b/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/BankReportHandler.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/CaseList.class b/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/CaseList.class index c6dc2ec..f4412dd 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/CaseList.class and b/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/CaseList.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/CombinedExcelReporting.class b/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/CombinedExcelReporting.class index f8fd97b..69ef413 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/CombinedExcelReporting.class and b/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/CombinedExcelReporting.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/CombinedReporting.class b/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/CombinedReporting.class index eddc46a..7c1bf9c 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/CombinedReporting.class and b/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/CombinedReporting.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/CombinedWordlReporting.class b/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/CombinedWordlReporting.class index 0112ce0..c778acc 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/CombinedWordlReporting.class and b/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/CombinedWordlReporting.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/CombinedWordlReportingTabular.class b/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/CombinedWordlReportingTabular.class index 826f49b..1c6e802 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/CombinedWordlReportingTabular.class and b/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/CombinedWordlReportingTabular.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/ExcelTrackers.class b/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/ExcelTrackers.class index 3d13ee4..feedab0 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/ExcelTrackers.class and b/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/ExcelTrackers.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/PartWiseReporting.class b/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/PartWiseReporting.class index d6e7eee..e30dcaa 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/PartWiseReporting.class and b/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/PartWiseReporting.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/PartwiseExcelReporting.class b/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/PartwiseExcelReporting.class index 2bf758e..5bd27d2 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/PartwiseExcelReporting.class and b/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/PartwiseExcelReporting.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/ReportEmailJobExecutor$ReportEmailThreadFactory.class b/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/ReportEmailJobExecutor$ReportEmailThreadFactory.class index 1fc8884..b1c994e 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/ReportEmailJobExecutor$ReportEmailThreadFactory.class and b/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/ReportEmailJobExecutor$ReportEmailThreadFactory.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/ReportEmailJobExecutor.class b/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/ReportEmailJobExecutor.class index 8f7c6e3..a1d8356 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/ReportEmailJobExecutor.class and b/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/ReportEmailJobExecutor.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/ReportSettings.class b/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/ReportSettings.class index bd663d1..5c71c03 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/ReportSettings.class and b/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/ReportSettings.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/SendReports.class b/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/SendReports.class index cdc46fd..7ce0f7e 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/SendReports.class and b/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/SendReports.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/TrackerGeneratorHelper.class b/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/TrackerGeneratorHelper.class index a05f258..08451c7 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/TrackerGeneratorHelper.class and b/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/TrackerGeneratorHelper.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/TrackerSetting.class b/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/TrackerSetting.class index 3ef75a2..9ebd018 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/TrackerSetting.class and b/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/TrackerSetting.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/TrackerSheet.class b/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/TrackerSheet.class index 680c427..7387962 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/TrackerSheet.class and b/cygnus-onprem-app/target/classes/matrix/nimble/operation/output/model/TrackerSheet.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/operation/reporting/controller/Operations.class b/cygnus-onprem-app/target/classes/matrix/nimble/operation/reporting/controller/Operations.class index 0c1b4f7..3fc965b 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/operation/reporting/controller/Operations.class and b/cygnus-onprem-app/target/classes/matrix/nimble/operation/reporting/controller/Operations.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/operation/reporting/model/FinalDone.class b/cygnus-onprem-app/target/classes/matrix/nimble/operation/reporting/model/FinalDone.class index ab395ea..ce7a856 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/operation/reporting/model/FinalDone.class and b/cygnus-onprem-app/target/classes/matrix/nimble/operation/reporting/model/FinalDone.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/operation/reporting/model/NegUpdRequest.class b/cygnus-onprem-app/target/classes/matrix/nimble/operation/reporting/model/NegUpdRequest.class index 894773c..ebecece 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/operation/reporting/model/NegUpdRequest.class and b/cygnus-onprem-app/target/classes/matrix/nimble/operation/reporting/model/NegUpdRequest.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/operation/reporting/model/OfficeReport.class b/cygnus-onprem-app/target/classes/matrix/nimble/operation/reporting/model/OfficeReport.class index 752c8ff..4445b6e 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/operation/reporting/model/OfficeReport.class and b/cygnus-onprem-app/target/classes/matrix/nimble/operation/reporting/model/OfficeReport.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/operation/reporting/model/OperatorReporting.class b/cygnus-onprem-app/target/classes/matrix/nimble/operation/reporting/model/OperatorReporting.class index e0c4490..c77b301 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/operation/reporting/model/OperatorReporting.class and b/cygnus-onprem-app/target/classes/matrix/nimble/operation/reporting/model/OperatorReporting.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/operation/reporting/model/OtherReport.class b/cygnus-onprem-app/target/classes/matrix/nimble/operation/reporting/model/OtherReport.class index 7a5bd2d..9b17e7c 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/operation/reporting/model/OtherReport.class and b/cygnus-onprem-app/target/classes/matrix/nimble/operation/reporting/model/OtherReport.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/operation/reporting/model/RefrenceReport.class b/cygnus-onprem-app/target/classes/matrix/nimble/operation/reporting/model/RefrenceReport.class index 04a043a..1adba57 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/operation/reporting/model/RefrenceReport.class and b/cygnus-onprem-app/target/classes/matrix/nimble/operation/reporting/model/RefrenceReport.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/operation/reporting/model/ReportingHandler.class b/cygnus-onprem-app/target/classes/matrix/nimble/operation/reporting/model/ReportingHandler.class index 4618940..2b09aa0 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/operation/reporting/model/ReportingHandler.class and b/cygnus-onprem-app/target/classes/matrix/nimble/operation/reporting/model/ReportingHandler.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/operation/reporting/model/ResidenceReport.class b/cygnus-onprem-app/target/classes/matrix/nimble/operation/reporting/model/ResidenceReport.class index 65b0523..7f6eb2b 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/operation/reporting/model/ResidenceReport.class and b/cygnus-onprem-app/target/classes/matrix/nimble/operation/reporting/model/ResidenceReport.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/operation/reporting/model/ScannerReporting.class b/cygnus-onprem-app/target/classes/matrix/nimble/operation/reporting/model/ScannerReporting.class index a660730..91e3d4b 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/operation/reporting/model/ScannerReporting.class and b/cygnus-onprem-app/target/classes/matrix/nimble/operation/reporting/model/ScannerReporting.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/operation/reporting/model/TelecallingReport.class b/cygnus-onprem-app/target/classes/matrix/nimble/operation/reporting/model/TelecallingReport.class index b6d0824..f4200e1 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/operation/reporting/model/TelecallingReport.class and b/cygnus-onprem-app/target/classes/matrix/nimble/operation/reporting/model/TelecallingReport.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/operation/reporting/model/TelecallingReporting.class b/cygnus-onprem-app/target/classes/matrix/nimble/operation/reporting/model/TelecallingReporting.class index defb0db..e8c58ea 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/operation/reporting/model/TelecallingReporting.class and b/cygnus-onprem-app/target/classes/matrix/nimble/operation/reporting/model/TelecallingReporting.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/controller/OperationTools.class b/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/controller/OperationTools.class index 608ec43..a7e5e3e 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/controller/OperationTools.class and b/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/controller/OperationTools.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/model/AllocatedBelts.class b/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/model/AllocatedBelts.class index be49bca..1b20109 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/model/AllocatedBelts.class and b/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/model/AllocatedBelts.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/model/AllocatedCases.class b/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/model/AllocatedCases.class index ea578a5..327eb51 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/model/AllocatedCases.class and b/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/model/AllocatedCases.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/model/AppEmails.class b/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/model/AppEmails.class index 4e4a4aa..4f8faa5 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/model/AppEmails.class and b/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/model/AppEmails.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/model/Belt.class b/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/model/Belt.class index 9faa35e..0d852a0 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/model/Belt.class and b/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/model/Belt.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/model/BeltTransfer.class b/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/model/BeltTransfer.class index c1f6e2c..2677033 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/model/BeltTransfer.class and b/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/model/BeltTransfer.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/model/CaseTransfer.class b/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/model/CaseTransfer.class index 7b4414b..0e29b3a 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/model/CaseTransfer.class and b/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/model/CaseTransfer.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/model/Colony.class b/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/model/Colony.class index 47bfafe..6464cf3 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/model/Colony.class and b/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/model/Colony.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/model/DocTagger.class b/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/model/DocTagger.class index d216680..dcc35d9 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/model/DocTagger.class and b/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/model/DocTagger.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/model/Holiday.class b/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/model/Holiday.class index 24c5872..cbef09c 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/model/Holiday.class and b/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/model/Holiday.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/model/HolidayFilter.class b/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/model/HolidayFilter.class index 9d19b97..22a3fc3 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/model/HolidayFilter.class and b/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/model/HolidayFilter.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/model/OclCases.class b/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/model/OclCases.class index 621a894..5e073f7 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/model/OclCases.class and b/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/model/OclCases.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/model/OperatorAllocation.class b/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/model/OperatorAllocation.class index d7dd963..44d637d 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/model/OperatorAllocation.class and b/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/model/OperatorAllocation.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/model/ToolsHandler$1.class b/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/model/ToolsHandler$1.class index f8c5a9d..38b1f27 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/model/ToolsHandler$1.class and b/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/model/ToolsHandler$1.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/model/ToolsHandler.class b/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/model/ToolsHandler.class index e6b414c..34c5c96 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/model/ToolsHandler.class and b/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/model/ToolsHandler.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/model/Verifier.class b/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/model/Verifier.class index 45f073c..63a630a 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/model/Verifier.class and b/cygnus-onprem-app/target/classes/matrix/nimble/operation/tools/model/Verifier.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/utilities/CommonFunctions.class b/cygnus-onprem-app/target/classes/matrix/nimble/utilities/CommonFunctions.class index 6b57937..03e789d 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/utilities/CommonFunctions.class and b/cygnus-onprem-app/target/classes/matrix/nimble/utilities/CommonFunctions.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/utilities/DBFunctions.class b/cygnus-onprem-app/target/classes/matrix/nimble/utilities/DBFunctions.class index 249ad28..1bbf996 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/utilities/DBFunctions.class and b/cygnus-onprem-app/target/classes/matrix/nimble/utilities/DBFunctions.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/utilities/DatabaseConnectionPool.class b/cygnus-onprem-app/target/classes/matrix/nimble/utilities/DatabaseConnectionPool.class index 20c5684..f3f0b0f 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/utilities/DatabaseConnectionPool.class and b/cygnus-onprem-app/target/classes/matrix/nimble/utilities/DatabaseConnectionPool.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/utilities/DatabasePoolLifecycleListener.class b/cygnus-onprem-app/target/classes/matrix/nimble/utilities/DatabasePoolLifecycleListener.class index 083b8b6..e1fbed9 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/utilities/DatabasePoolLifecycleListener.class and b/cygnus-onprem-app/target/classes/matrix/nimble/utilities/DatabasePoolLifecycleListener.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/utilities/EmailFunctions$1.class b/cygnus-onprem-app/target/classes/matrix/nimble/utilities/EmailFunctions$1.class index 977c429..b9db753 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/utilities/EmailFunctions$1.class and b/cygnus-onprem-app/target/classes/matrix/nimble/utilities/EmailFunctions$1.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/utilities/EmailFunctions.class b/cygnus-onprem-app/target/classes/matrix/nimble/utilities/EmailFunctions.class index 4747e8d..abdb95b 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/utilities/EmailFunctions.class and b/cygnus-onprem-app/target/classes/matrix/nimble/utilities/EmailFunctions.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/utilities/ExcelFormatter.class b/cygnus-onprem-app/target/classes/matrix/nimble/utilities/ExcelFormatter.class index 346d095..c4d5e11 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/utilities/ExcelFormatter.class and b/cygnus-onprem-app/target/classes/matrix/nimble/utilities/ExcelFormatter.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/utilities/FTPFunctions$1.class b/cygnus-onprem-app/target/classes/matrix/nimble/utilities/FTPFunctions$1.class index 62c2197..7ef4099 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/utilities/FTPFunctions$1.class and b/cygnus-onprem-app/target/classes/matrix/nimble/utilities/FTPFunctions$1.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/utilities/FTPFunctions.class b/cygnus-onprem-app/target/classes/matrix/nimble/utilities/FTPFunctions.class index 6968a1e..db6fae6 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/utilities/FTPFunctions.class and b/cygnus-onprem-app/target/classes/matrix/nimble/utilities/FTPFunctions.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/utilities/FileFunctions.class b/cygnus-onprem-app/target/classes/matrix/nimble/utilities/FileFunctions.class index d7b07ab..3274573 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/utilities/FileFunctions.class and b/cygnus-onprem-app/target/classes/matrix/nimble/utilities/FileFunctions.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/utilities/GlobalClass.class b/cygnus-onprem-app/target/classes/matrix/nimble/utilities/GlobalClass.class index a896aff..1a50feb 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/utilities/GlobalClass.class and b/cygnus-onprem-app/target/classes/matrix/nimble/utilities/GlobalClass.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/utilities/MSSQLFunctions.class b/cygnus-onprem-app/target/classes/matrix/nimble/utilities/MSSQLFunctions.class index 0bb15a4..b181194 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/utilities/MSSQLFunctions.class and b/cygnus-onprem-app/target/classes/matrix/nimble/utilities/MSSQLFunctions.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/utilities/ModuleFunctions.class b/cygnus-onprem-app/target/classes/matrix/nimble/utilities/ModuleFunctions.class index b636dea..9e6111c 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/utilities/ModuleFunctions.class and b/cygnus-onprem-app/target/classes/matrix/nimble/utilities/ModuleFunctions.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/utilities/NimbleException.class b/cygnus-onprem-app/target/classes/matrix/nimble/utilities/NimbleException.class index 97cdc61..3853560 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/utilities/NimbleException.class and b/cygnus-onprem-app/target/classes/matrix/nimble/utilities/NimbleException.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/utilities/PDFFormatter.class b/cygnus-onprem-app/target/classes/matrix/nimble/utilities/PDFFormatter.class index 19a16c4..0ea6271 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/utilities/PDFFormatter.class and b/cygnus-onprem-app/target/classes/matrix/nimble/utilities/PDFFormatter.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/utilities/SMSFunctions.class b/cygnus-onprem-app/target/classes/matrix/nimble/utilities/SMSFunctions.class index 8fbaf72..8daa073 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/utilities/SMSFunctions.class and b/cygnus-onprem-app/target/classes/matrix/nimble/utilities/SMSFunctions.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/utilities/StringFunctions.class b/cygnus-onprem-app/target/classes/matrix/nimble/utilities/StringFunctions.class index b898fe4..69efc57 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/utilities/StringFunctions.class and b/cygnus-onprem-app/target/classes/matrix/nimble/utilities/StringFunctions.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/utilities/TPDBFunctions.class b/cygnus-onprem-app/target/classes/matrix/nimble/utilities/TPDBFunctions.class index 9731933..8d2725d 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/utilities/TPDBFunctions.class and b/cygnus-onprem-app/target/classes/matrix/nimble/utilities/TPDBFunctions.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/utilities/XLCell$Border.class b/cygnus-onprem-app/target/classes/matrix/nimble/utilities/XLCell$Border.class index 0153cfe..b49c469 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/utilities/XLCell$Border.class and b/cygnus-onprem-app/target/classes/matrix/nimble/utilities/XLCell$Border.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/utilities/XLCell.class b/cygnus-onprem-app/target/classes/matrix/nimble/utilities/XLCell.class index a841677..6acdbc4 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/utilities/XLCell.class and b/cygnus-onprem-app/target/classes/matrix/nimble/utilities/XLCell.class differ diff --git a/cygnus-onprem-app/target/classes/matrix/nimble/utilities/ZipUtility.class b/cygnus-onprem-app/target/classes/matrix/nimble/utilities/ZipUtility.class index 7e01eaa..63aef9c 100644 Binary files a/cygnus-onprem-app/target/classes/matrix/nimble/utilities/ZipUtility.class and b/cygnus-onprem-app/target/classes/matrix/nimble/utilities/ZipUtility.class differ diff --git a/cygnus-onprem-app/target/matrix.war b/cygnus-onprem-app/target/matrix.war index 291a77d..1b9411d 100644 Binary files a/cygnus-onprem-app/target/matrix.war and b/cygnus-onprem-app/target/matrix.war differ diff --git a/cygnus-onprem-app/target/matrix/WEB-INF/classes/matrix/nimble/controller/SessionController.class b/cygnus-onprem-app/target/matrix/WEB-INF/classes/matrix/nimble/controller/SessionController.class index 30f0900..4fd58ec 100644 Binary files a/cygnus-onprem-app/target/matrix/WEB-INF/classes/matrix/nimble/controller/SessionController.class and b/cygnus-onprem-app/target/matrix/WEB-INF/classes/matrix/nimble/controller/SessionController.class differ diff --git a/cygnus-onprem-app/target/matrix/WEB-INF/lib/cygnus-cloud-client-1.0.0-SNAPSHOT.jar b/cygnus-onprem-app/target/matrix/WEB-INF/lib/cygnus-cloud-client-1.0.0-SNAPSHOT.jar index 78e52c2..93a6c8c 100644 Binary files a/cygnus-onprem-app/target/matrix/WEB-INF/lib/cygnus-cloud-client-1.0.0-SNAPSHOT.jar and b/cygnus-onprem-app/target/matrix/WEB-INF/lib/cygnus-cloud-client-1.0.0-SNAPSHOT.jar differ diff --git a/cygnus-onprem-app/target/surefire-reports/TEST-matrix.nimble.BranchMigrationTest.xml b/cygnus-onprem-app/target/surefire-reports/TEST-matrix.nimble.BranchMigrationTest.xml index 90376b0..d1066ca 100644 --- a/cygnus-onprem-app/target/surefire-reports/TEST-matrix.nimble.BranchMigrationTest.xml +++ b/cygnus-onprem-app/target/surefire-reports/TEST-matrix.nimble.BranchMigrationTest.xml @@ -3,7 +3,7 @@ - + @@ -13,10 +13,10 @@ - + - + @@ -30,7 +30,7 @@ - + @@ -59,5 +59,5 @@ - + \ No newline at end of file diff --git a/cygnus-onprem-app/target/surefire-reports/TEST-matrix.nimble.DevelopmentLauncherTest.xml b/cygnus-onprem-app/target/surefire-reports/TEST-matrix.nimble.DevelopmentLauncherTest.xml index 1ebf87c..ed9c38f 100644 --- a/cygnus-onprem-app/target/surefire-reports/TEST-matrix.nimble.DevelopmentLauncherTest.xml +++ b/cygnus-onprem-app/target/surefire-reports/TEST-matrix.nimble.DevelopmentLauncherTest.xml @@ -1,9 +1,9 @@ - + - + @@ -13,10 +13,10 @@ - + - + @@ -30,7 +30,7 @@ - + @@ -59,5 +59,5 @@ - + \ No newline at end of file diff --git a/cygnus-onprem-app/target/surefire-reports/TEST-matrix.nimble.FileFunctionsQueryCacheTest.xml b/cygnus-onprem-app/target/surefire-reports/TEST-matrix.nimble.FileFunctionsQueryCacheTest.xml index bb1cdf1..e71d9ff 100644 --- a/cygnus-onprem-app/target/surefire-reports/TEST-matrix.nimble.FileFunctionsQueryCacheTest.xml +++ b/cygnus-onprem-app/target/surefire-reports/TEST-matrix.nimble.FileFunctionsQueryCacheTest.xml @@ -1,9 +1,9 @@ - + - + @@ -13,10 +13,10 @@ - + + - - + @@ -30,8 +30,9 @@ - + + @@ -55,7 +56,8 @@ + - + \ No newline at end of file diff --git a/cygnus-onprem-app/target/surefire-reports/TEST-matrix.nimble.HomeShellStructureTest.xml b/cygnus-onprem-app/target/surefire-reports/TEST-matrix.nimble.HomeShellStructureTest.xml index 7754f7f..86ab3e6 100644 --- a/cygnus-onprem-app/target/surefire-reports/TEST-matrix.nimble.HomeShellStructureTest.xml +++ b/cygnus-onprem-app/target/surefire-reports/TEST-matrix.nimble.HomeShellStructureTest.xml @@ -1,9 +1,9 @@ - + - + @@ -12,10 +12,10 @@ - + - + @@ -29,7 +29,7 @@ - + @@ -58,7 +58,7 @@ - - - + + + \ No newline at end of file diff --git a/cygnus-onprem-app/target/surefire-reports/TEST-matrix.nimble.ManageUsersMigrationTest.xml b/cygnus-onprem-app/target/surefire-reports/TEST-matrix.nimble.ManageUsersMigrationTest.xml index 6c0a382..14943a0 100644 --- a/cygnus-onprem-app/target/surefire-reports/TEST-matrix.nimble.ManageUsersMigrationTest.xml +++ b/cygnus-onprem-app/target/surefire-reports/TEST-matrix.nimble.ManageUsersMigrationTest.xml @@ -3,7 +3,7 @@ - + @@ -13,10 +13,10 @@ - + - + @@ -30,7 +30,7 @@ - + @@ -59,5 +59,5 @@ - + \ No newline at end of file diff --git a/cygnus-onprem-app/target/surefire-reports/TEST-matrix.nimble.RoleVisibleBatchMigrationTest.xml b/cygnus-onprem-app/target/surefire-reports/TEST-matrix.nimble.RoleVisibleBatchMigrationTest.xml index 124884d..aea25e7 100644 --- a/cygnus-onprem-app/target/surefire-reports/TEST-matrix.nimble.RoleVisibleBatchMigrationTest.xml +++ b/cygnus-onprem-app/target/surefire-reports/TEST-matrix.nimble.RoleVisibleBatchMigrationTest.xml @@ -1,9 +1,9 @@ - + - + @@ -12,10 +12,10 @@ - + - + @@ -29,7 +29,7 @@ - + @@ -67,25 +67,25 @@ - + - + - - - - - + + + + + - - - + + + - - + + \ No newline at end of file diff --git a/cygnus-onprem-app/target/surefire-reports/TEST-matrix.nimble.SpringWebContextTest.xml b/cygnus-onprem-app/target/surefire-reports/TEST-matrix.nimble.SpringWebContextTest.xml index 1afbf49..462fb09 100644 --- a/cygnus-onprem-app/target/surefire-reports/TEST-matrix.nimble.SpringWebContextTest.xml +++ b/cygnus-onprem-app/target/surefire-reports/TEST-matrix.nimble.SpringWebContextTest.xml @@ -1,9 +1,9 @@ - + - + @@ -13,10 +13,10 @@ - + - + @@ -30,7 +30,7 @@ - + @@ -59,13 +59,13 @@ - - + - + \ No newline at end of file diff --git a/cygnus-onprem-app/target/surefire-reports/TEST-matrix.nimble.cloud.identity.CloudSessionMapperTest.xml b/cygnus-onprem-app/target/surefire-reports/TEST-matrix.nimble.cloud.identity.CloudSessionMapperTest.xml index b59fa6e..65aceff 100644 --- a/cygnus-onprem-app/target/surefire-reports/TEST-matrix.nimble.cloud.identity.CloudSessionMapperTest.xml +++ b/cygnus-onprem-app/target/surefire-reports/TEST-matrix.nimble.cloud.identity.CloudSessionMapperTest.xml @@ -1,9 +1,9 @@ - + - + @@ -13,10 +13,10 @@ - + + - - + @@ -30,8 +30,9 @@ - + + @@ -55,7 +56,8 @@ + - + \ No newline at end of file diff --git a/cygnus-onprem-app/target/surefire-reports/TEST-matrix.nimble.cloud.identity.SessionMenuRendererTest.xml b/cygnus-onprem-app/target/surefire-reports/TEST-matrix.nimble.cloud.identity.SessionMenuRendererTest.xml index 3e9fe93..67f9c78 100644 --- a/cygnus-onprem-app/target/surefire-reports/TEST-matrix.nimble.cloud.identity.SessionMenuRendererTest.xml +++ b/cygnus-onprem-app/target/surefire-reports/TEST-matrix.nimble.cloud.identity.SessionMenuRendererTest.xml @@ -1,9 +1,9 @@ - + - + @@ -12,10 +12,10 @@ - + + - - + @@ -29,8 +29,9 @@ - + + @@ -54,7 +55,8 @@ + - + \ No newline at end of file diff --git a/cygnus-onprem-app/target/surefire-reports/matrix.nimble.DevelopmentLauncherTest.txt b/cygnus-onprem-app/target/surefire-reports/matrix.nimble.DevelopmentLauncherTest.txt index c3e4797..2a3efbe 100644 --- a/cygnus-onprem-app/target/surefire-reports/matrix.nimble.DevelopmentLauncherTest.txt +++ b/cygnus-onprem-app/target/surefire-reports/matrix.nimble.DevelopmentLauncherTest.txt @@ -1,4 +1,4 @@ ------------------------------------------------------------------------------- Test set: matrix.nimble.DevelopmentLauncherTest ------------------------------------------------------------------------------- -Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0 s -- in matrix.nimble.DevelopmentLauncherTest +Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.001 s -- in matrix.nimble.DevelopmentLauncherTest diff --git a/cygnus-onprem-app/target/surefire-reports/matrix.nimble.FileFunctionsQueryCacheTest.txt b/cygnus-onprem-app/target/surefire-reports/matrix.nimble.FileFunctionsQueryCacheTest.txt index 52af5e5..af80129 100644 --- a/cygnus-onprem-app/target/surefire-reports/matrix.nimble.FileFunctionsQueryCacheTest.txt +++ b/cygnus-onprem-app/target/surefire-reports/matrix.nimble.FileFunctionsQueryCacheTest.txt @@ -1,4 +1,4 @@ ------------------------------------------------------------------------------- Test set: matrix.nimble.FileFunctionsQueryCacheTest ------------------------------------------------------------------------------- -Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.013 s -- in matrix.nimble.FileFunctionsQueryCacheTest +Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.007 s -- in matrix.nimble.FileFunctionsQueryCacheTest diff --git a/cygnus-onprem-app/target/surefire-reports/matrix.nimble.HomeShellStructureTest.txt b/cygnus-onprem-app/target/surefire-reports/matrix.nimble.HomeShellStructureTest.txt index 5d36e1a..8660989 100644 --- a/cygnus-onprem-app/target/surefire-reports/matrix.nimble.HomeShellStructureTest.txt +++ b/cygnus-onprem-app/target/surefire-reports/matrix.nimble.HomeShellStructureTest.txt @@ -1,4 +1,4 @@ ------------------------------------------------------------------------------- Test set: matrix.nimble.HomeShellStructureTest ------------------------------------------------------------------------------- -Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.044 s -- in matrix.nimble.HomeShellStructureTest +Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.031 s -- in matrix.nimble.HomeShellStructureTest diff --git a/cygnus-onprem-app/target/surefire-reports/matrix.nimble.RoleVisibleBatchMigrationTest.txt b/cygnus-onprem-app/target/surefire-reports/matrix.nimble.RoleVisibleBatchMigrationTest.txt index e5a9d39..d0e24b7 100644 --- a/cygnus-onprem-app/target/surefire-reports/matrix.nimble.RoleVisibleBatchMigrationTest.txt +++ b/cygnus-onprem-app/target/surefire-reports/matrix.nimble.RoleVisibleBatchMigrationTest.txt @@ -1,4 +1,4 @@ ------------------------------------------------------------------------------- Test set: matrix.nimble.RoleVisibleBatchMigrationTest ------------------------------------------------------------------------------- -Tests run: 30, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.045 s -- in matrix.nimble.RoleVisibleBatchMigrationTest +Tests run: 30, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.044 s -- in matrix.nimble.RoleVisibleBatchMigrationTest diff --git a/cygnus-onprem-app/target/surefire-reports/matrix.nimble.SpringWebContextTest.txt b/cygnus-onprem-app/target/surefire-reports/matrix.nimble.SpringWebContextTest.txt index 7d4c159..099dfee 100644 --- a/cygnus-onprem-app/target/surefire-reports/matrix.nimble.SpringWebContextTest.txt +++ b/cygnus-onprem-app/target/surefire-reports/matrix.nimble.SpringWebContextTest.txt @@ -1,4 +1,4 @@ ------------------------------------------------------------------------------- Test set: matrix.nimble.SpringWebContextTest ------------------------------------------------------------------------------- -Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.622 s -- in matrix.nimble.SpringWebContextTest +Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.594 s -- in matrix.nimble.SpringWebContextTest diff --git a/cygnus-onprem-app/target/surefire-reports/matrix.nimble.cloud.identity.CloudSessionMapperTest.txt b/cygnus-onprem-app/target/surefire-reports/matrix.nimble.cloud.identity.CloudSessionMapperTest.txt index 4720632..b9dcf18 100644 --- a/cygnus-onprem-app/target/surefire-reports/matrix.nimble.cloud.identity.CloudSessionMapperTest.txt +++ b/cygnus-onprem-app/target/surefire-reports/matrix.nimble.cloud.identity.CloudSessionMapperTest.txt @@ -1,4 +1,4 @@ ------------------------------------------------------------------------------- Test set: matrix.nimble.cloud.identity.CloudSessionMapperTest ------------------------------------------------------------------------------- -Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.008 s -- in matrix.nimble.cloud.identity.CloudSessionMapperTest +Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.007 s -- in matrix.nimble.cloud.identity.CloudSessionMapperTest diff --git a/cygnus-onprem-app/target/surefire-reports/matrix.nimble.cloud.identity.SessionMenuRendererTest.txt b/cygnus-onprem-app/target/surefire-reports/matrix.nimble.cloud.identity.SessionMenuRendererTest.txt index 418ece7..9563fd7 100644 --- a/cygnus-onprem-app/target/surefire-reports/matrix.nimble.cloud.identity.SessionMenuRendererTest.txt +++ b/cygnus-onprem-app/target/surefire-reports/matrix.nimble.cloud.identity.SessionMenuRendererTest.txt @@ -1,4 +1,4 @@ ------------------------------------------------------------------------------- Test set: matrix.nimble.cloud.identity.SessionMenuRendererTest ------------------------------------------------------------------------------- -Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.018 s -- in matrix.nimble.cloud.identity.SessionMenuRendererTest +Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.005 s -- in matrix.nimble.cloud.identity.SessionMenuRendererTest diff --git a/cygnus-onprem-app/target/test-classes/matrix/nimble/BranchMigrationTest.class b/cygnus-onprem-app/target/test-classes/matrix/nimble/BranchMigrationTest.class index 034fc5c..8472182 100644 Binary files a/cygnus-onprem-app/target/test-classes/matrix/nimble/BranchMigrationTest.class and b/cygnus-onprem-app/target/test-classes/matrix/nimble/BranchMigrationTest.class differ diff --git a/cygnus-onprem-app/target/test-classes/matrix/nimble/DatabaseAuditRunner.class b/cygnus-onprem-app/target/test-classes/matrix/nimble/DatabaseAuditRunner.class index ca8ea80..f2d89a3 100644 Binary files a/cygnus-onprem-app/target/test-classes/matrix/nimble/DatabaseAuditRunner.class and b/cygnus-onprem-app/target/test-classes/matrix/nimble/DatabaseAuditRunner.class differ diff --git a/cygnus-onprem-app/target/test-classes/matrix/nimble/DevelopmentLauncherTest.class b/cygnus-onprem-app/target/test-classes/matrix/nimble/DevelopmentLauncherTest.class index 09df52d..fafd609 100644 Binary files a/cygnus-onprem-app/target/test-classes/matrix/nimble/DevelopmentLauncherTest.class and b/cygnus-onprem-app/target/test-classes/matrix/nimble/DevelopmentLauncherTest.class differ diff --git a/cygnus-onprem-app/target/test-classes/matrix/nimble/EmbeddedTomcatServer.class b/cygnus-onprem-app/target/test-classes/matrix/nimble/EmbeddedTomcatServer.class index 64b165c..b509cf8 100644 Binary files a/cygnus-onprem-app/target/test-classes/matrix/nimble/EmbeddedTomcatServer.class and b/cygnus-onprem-app/target/test-classes/matrix/nimble/EmbeddedTomcatServer.class differ diff --git a/cygnus-onprem-app/target/test-classes/matrix/nimble/FileFunctionsQueryCacheTest.class b/cygnus-onprem-app/target/test-classes/matrix/nimble/FileFunctionsQueryCacheTest.class index 8113d9c..30da7c2 100644 Binary files a/cygnus-onprem-app/target/test-classes/matrix/nimble/FileFunctionsQueryCacheTest.class and b/cygnus-onprem-app/target/test-classes/matrix/nimble/FileFunctionsQueryCacheTest.class differ diff --git a/cygnus-onprem-app/target/test-classes/matrix/nimble/HomeShellStructureTest.class b/cygnus-onprem-app/target/test-classes/matrix/nimble/HomeShellStructureTest.class index 7876cd2..8ee7ec4 100644 Binary files a/cygnus-onprem-app/target/test-classes/matrix/nimble/HomeShellStructureTest.class and b/cygnus-onprem-app/target/test-classes/matrix/nimble/HomeShellStructureTest.class differ diff --git a/cygnus-onprem-app/target/test-classes/matrix/nimble/ManageUsersMigrationTest.class b/cygnus-onprem-app/target/test-classes/matrix/nimble/ManageUsersMigrationTest.class index ae8b18e..780213a 100644 Binary files a/cygnus-onprem-app/target/test-classes/matrix/nimble/ManageUsersMigrationTest.class and b/cygnus-onprem-app/target/test-classes/matrix/nimble/ManageUsersMigrationTest.class differ diff --git a/cygnus-onprem-app/target/test-classes/matrix/nimble/RoleVisibleBatchMigrationTest.class b/cygnus-onprem-app/target/test-classes/matrix/nimble/RoleVisibleBatchMigrationTest.class index d3044ce..1e0b657 100644 Binary files a/cygnus-onprem-app/target/test-classes/matrix/nimble/RoleVisibleBatchMigrationTest.class and b/cygnus-onprem-app/target/test-classes/matrix/nimble/RoleVisibleBatchMigrationTest.class differ diff --git a/cygnus-onprem-app/target/test-classes/matrix/nimble/SpringWebContextTest.class b/cygnus-onprem-app/target/test-classes/matrix/nimble/SpringWebContextTest.class index 39c5b13..87f615e 100644 Binary files a/cygnus-onprem-app/target/test-classes/matrix/nimble/SpringWebContextTest.class and b/cygnus-onprem-app/target/test-classes/matrix/nimble/SpringWebContextTest.class differ diff --git a/cygnus-onprem-app/target/test-classes/matrix/nimble/TomcatUiIT.class b/cygnus-onprem-app/target/test-classes/matrix/nimble/TomcatUiIT.class index be8427c..ff460fd 100644 Binary files a/cygnus-onprem-app/target/test-classes/matrix/nimble/TomcatUiIT.class and b/cygnus-onprem-app/target/test-classes/matrix/nimble/TomcatUiIT.class differ diff --git a/cygnus-onprem-app/target/test-classes/matrix/nimble/cloud/identity/CloudSessionMapperTest.class b/cygnus-onprem-app/target/test-classes/matrix/nimble/cloud/identity/CloudSessionMapperTest.class index fda789b..87ba7df 100644 Binary files a/cygnus-onprem-app/target/test-classes/matrix/nimble/cloud/identity/CloudSessionMapperTest.class and b/cygnus-onprem-app/target/test-classes/matrix/nimble/cloud/identity/CloudSessionMapperTest.class differ diff --git a/cygnus-onprem-app/target/test-classes/matrix/nimble/cloud/identity/SessionMenuRendererTest.class b/cygnus-onprem-app/target/test-classes/matrix/nimble/cloud/identity/SessionMenuRendererTest.class index 5a47c57..f48ab48 100644 Binary files a/cygnus-onprem-app/target/test-classes/matrix/nimble/cloud/identity/SessionMenuRendererTest.class and b/cygnus-onprem-app/target/test-classes/matrix/nimble/cloud/identity/SessionMenuRendererTest.class differ diff --git a/docs/client-registration-installation-domain.md b/docs/client-registration-installation-domain.md new file mode 100644 index 0000000..db3aacd --- /dev/null +++ b/docs/client-registration-installation-domain.md @@ -0,0 +1,56 @@ +# Client registration and installation domain + +## Ownership + +```text +client_registration_details +└── client_account (tenant) + ├── client_license + │ └── license_activation_key + └── client_installation +``` + +- A registration is the legal contracting and billing entity. +- A registration may own multiple isolated tenants. +- A tenant belongs to exactly one registration. +- A tenant may have license history; installation uses a specific license. +- `client_license.max_installations` defaults to one and is cloud-enforced. +- A license key authorizes activation but is never a runtime credential. +- Every installation has a unique UUID and signing key pair. +- The installation private key never leaves the customer server. + +## Installation capacity + +`PENDING`, `ACTIVE`, and `SUSPENDED` installations consume capacity. +`DECOMMISSIONED`, `REVOKED`, and `FAILED` installations do not. + +Registration must lock the applicable active license row, count consuming +installations, and insert the new `PENDING` installation in one transaction. +This prevents two concurrent requests from taking the same final slot. + +## Status transitions + +```text +PENDING -> ACTIVE -> SUSPENDED -> ACTIVE + | + +-> DECOMMISSIONED + +-> REVOKED + +PENDING -> FAILED +``` + +- Suspension is temporary and continues consuming capacity. +- Decommission permanently retires a legitimate old server. +- Revocation permanently invalidates a compromised or prohibited server. +- Decommission and revocation require an actor, timestamp, and reason. +- Historical installation rows are retained for audit. + +## Activation credentials + +The customer receives a client code and a random license activation key. +The database stores only a strong key hash and a non-secret display hint. +After validation, the cloud issues a single-use activation session with a +short expiry and binds it to the intended installation UUID. + +Client codes, tenant IDs, installation IDs, and key hints are identifiers, +not authentication secrets. diff --git a/docs/client-registration-installation-runbook.md b/docs/client-registration-installation-runbook.md new file mode 100644 index 0000000..70741e4 --- /dev/null +++ b/docs/client-registration-installation-runbook.md @@ -0,0 +1,85 @@ +# Client registration and controlled installation runbook + +## Lifecycle + +1. An administrator creates one legal registration. +2. One or more tenant accounts are attached to that registration. +3. A time-bound license defines package, user, and installation limits. +4. An activation key is generated, stored only as a BCrypt hash, and emailed. +5. The Java wizard blocks unless Docker, Docker Engine, and Compose v2 work. +6. The wizard validates the key, generates an RSA-3072 installation key, and + registers the installation in one database transaction. +7. The plaintext activation key is neither returned again nor written locally. +8. Runtime token issuance requires an active registration, tenant, license, and + installation. +9. An administrator can decommission or revoke an old installation before + activating its replacement. + +## Database migration + +Apply `004_client_registration_activation.sql` after identity migrations +001–003. The migration is additive and idempotent. Always back up production +before applying it and validate the constraints on a staging copy first. + +## Required cloud configuration + +- Database and Redis variables already used by the cloud service +- JWT and assertion encryption key locations +- `CYGNUS_MAIL_HOST`, `CYGNUS_MAIL_PORT`, `CYGNUS_MAIL_USERNAME`, + `CYGNUS_MAIL_PASSWORD`, and `CYGNUS_REGISTRATION_EMAIL_FROM` +- Optional `CYGNUS_ACTIVATION_MAX_ATTEMPTS` and + `CYGNUS_ACTIVATION_RATE_WINDOW` + +The cloud service can start without SMTP, but the activation-key email endpoint +will reject delivery until SMTP is configured. + +## Administrative API order + +All administration endpoints require an access token with +`cygnus.admin` scope. + +1. `POST /api/v1/admin/client-registrations` +2. `POST /api/v1/admin/client-registrations/{registrationId}/tenants` +3. `POST /api/v1/admin/tenants/{tenantId}/licenses` +4. `POST /api/v1/admin/tenants/{tenantId}/licenses/{licenseId}/activation-key` + +For replacements: + +- `POST /api/v1/admin/tenants/{tenantId}/installations/{id}/decommission` +- `POST /api/v1/admin/tenants/{tenantId}/installations/{id}/revoke` + +Both require a non-empty reason, invalidate the cached installation, increment +its security version, and write an audit event. + +## Running the wizard + +```bash +mvn -pl cygnus-installer -am package +CYGNUS_CLOUD_URL=https://cloud.example.com \ +java -jar cygnus-installer/target/cygnus-installer-1.0.0-SNAPSHOT.jar +``` + +After a successful run, review the generated `compose.yml`, set +`CYGNUS_IMAGE`, and start with `docker compose up -d`. + +The wizard asks for the output directory. Press Enter to accept +`CYGNUS_INSTALL_OUTPUT`, which defaults to `./cygnus-installation`. It creates: + +- `compose.yml` +- `config/installation.yml` +- `config/keys/client-signing-private.pem` +- `config/keys/client-signing-public.pem` + +The generated private key must remain readable only by the service account. +Never email or copy it to the cloud. The cloud stores only its public key. + +## Operational checks + +- Alert on repeated HTTP 429 activation responses. +- Alert on failed SMTP delivery and keep activation-key issue events auditable. +- Review installations that have stale `last_seen_at`. +- Revoke suspected keys/installations immediately. +- Do not increase `max_installations` to recover a failed server. Decommission + the old installation with a reason, then activate the replacement. +- Back up registration, tenant, license, installation, activation-key, session, + and installation-audit tables together. diff --git a/pom.xml b/pom.xml index bdece22..91e470c 100644 --- a/pom.xml +++ b/pom.xml @@ -14,6 +14,7 @@ cygnus-cloud-service cygnus-cloud-client + cygnus-installer cygnus-onprem-app