From c6b79c3eae8b054b1242c10dfb8fd2fe85128033 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Jan 2024 05:54:51 +0000 Subject: [PATCH 01/44] Bump com.google.api-client:google-api-client from 1.35.2 to 2.2.0 Bumps [com.google.api-client:google-api-client](https://github.com/googleapis/google-api-java-client) from 1.35.2 to 2.2.0. - [Release notes](https://github.com/googleapis/google-api-java-client/releases) - [Changelog](https://github.com/googleapis/google-api-java-client/blob/main/CHANGELOG.md) - [Commits](https://github.com/googleapis/google-api-java-client/compare/v1.35.2...v2.2.0) --- updated-dependencies: - dependency-name: com.google.api-client:google-api-client dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e21a092ac..d3158debf 100644 --- a/pom.xml +++ b/pom.xml @@ -76,7 +76,7 @@ 0.15.3 - 1.35.2 + 2.2.0 9.2.0 1.77 7.4 From 8fbc08e91c3a2a74e87e2b3ece4112f4cfb629ab Mon Sep 17 00:00:00 2001 From: Lubos Racansky Date: Fri, 5 Jan 2024 17:17:24 +0100 Subject: [PATCH 02/44] Remove javascript from CodQL configuration A follow-up to #648 --- .github/workflows/codeql-analysis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 4b1cb5a62..4762f8c45 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -15,7 +15,7 @@ jobs: uses: wultra/wultra-infrastructure/.github/workflows/codeql-analysis.yml@develop secrets: inherit with: - languages: "['java', 'javascript']" + languages: "['java']" # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] # Use only 'java' to analyze code written in Java, Kotlin or both # Use only 'javascript' to analyze code written in JavaScript, TypeScript or both From fe0d401d851179133933496d371f3352a44f793e Mon Sep 17 00:00:00 2001 From: Lubos Racansky Date: Fri, 5 Jan 2024 17:21:32 +0100 Subject: [PATCH 03/44] Fix #723: Set develop version to 1.7.0-SNAPSHOT --- pom.xml | 2 +- powerauth-push-client/pom.xml | 2 +- powerauth-push-model/pom.xml | 2 +- powerauth-push-server/pom.xml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index aa5ca7ea2..2abed4f63 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ io.getlime.security powerauth-push-server-parent - 1.6.0 + 1.7.0-SNAPSHOT pom diff --git a/powerauth-push-client/pom.xml b/powerauth-push-client/pom.xml index c370aec4e..107f231e7 100644 --- a/powerauth-push-client/pom.xml +++ b/powerauth-push-client/pom.xml @@ -10,7 +10,7 @@ powerauth-push-server-parent io.getlime.security - 1.6.0 + 1.7.0-SNAPSHOT diff --git a/powerauth-push-model/pom.xml b/powerauth-push-model/pom.xml index a9743f6ed..66cb8380f 100644 --- a/powerauth-push-model/pom.xml +++ b/powerauth-push-model/pom.xml @@ -11,7 +11,7 @@ powerauth-push-server-parent io.getlime.security - 1.6.0 + 1.7.0-SNAPSHOT diff --git a/powerauth-push-server/pom.xml b/powerauth-push-server/pom.xml index a008e75fe..7857cd693 100644 --- a/powerauth-push-server/pom.xml +++ b/powerauth-push-server/pom.xml @@ -11,7 +11,7 @@ io.getlime.security powerauth-push-server-parent - 1.6.0 + 1.7.0-SNAPSHOT From 798e41284905f5b583fe19116fc6dd97ed3f3644 Mon Sep 17 00:00:00 2001 From: Lubos Racansky Date: Fri, 5 Jan 2024 17:27:05 +0100 Subject: [PATCH 04/44] Fix #737: Update Wultra dependencies to SNAPSHOT --- pom.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index aa5ca7ea2..cae99642d 100644 --- a/pom.xml +++ b/pom.xml @@ -69,10 +69,10 @@ - 1.8.0 - 1.6.0 - 1.6.0 - 1.6.0 + 1.9.0-SNAPSHOT + 1.7.0-SNAPSHOT + 1.7.0-SNAPSHOT + 1.7.0-SNAPSHOT 0.15.3 From 101504529c5e70c57f7f0a5b9850749fa24e69d1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Jan 2024 11:35:44 +0000 Subject: [PATCH 05/44] Bump org.springframework.boot:spring-boot-starter-parent Bumps [org.springframework.boot:spring-boot-starter-parent](https://github.com/spring-projects/spring-boot) from 3.1.6 to 3.2.1. - [Release notes](https://github.com/spring-projects/spring-boot/releases) - [Commits](https://github.com/spring-projects/spring-boot/compare/v3.1.6...v3.2.1) --- updated-dependencies: - dependency-name: org.springframework.boot:spring-boot-starter-parent dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2abed4f63..0de9e466e 100644 --- a/pom.xml +++ b/pom.xml @@ -14,7 +14,7 @@ org.springframework.boot spring-boot-starter-parent - 3.1.6 + 3.2.1 From 0a6a338c7221a6e8ee8742851a7f7953faf118b1 Mon Sep 17 00:00:00 2001 From: Lubos Racansky Date: Tue, 2 Jan 2024 12:46:31 +0100 Subject: [PATCH 06/44] Revert "Fix #709: Update logback" This reverts commit 14e003bfecf9f841148dcd8a773aee2a414b37d1. --- pom.xml | 2 -- 1 file changed, 2 deletions(-) diff --git a/pom.xml b/pom.xml index 0de9e466e..675ee1b6a 100644 --- a/pom.xml +++ b/pom.xml @@ -80,8 +80,6 @@ 9.2.0 1.77 7.4 - - 1.4.14 2.3.0 From 67ca34d8b2c7c307878b2b9ded82cf19f3c342a8 Mon Sep 17 00:00:00 2001 From: Lubos Racansky Date: Mon, 15 Jan 2024 11:54:42 +0100 Subject: [PATCH 07/44] Fix #739: Warning: Using generated security password - Exclude UserDetailsServiceAutoConfiguration --- powerauth-push-server/src/main/resources/application.properties | 2 ++ 1 file changed, 2 insertions(+) diff --git a/powerauth-push-server/src/main/resources/application.properties b/powerauth-push-server/src/main/resources/application.properties index d0f204675..30c922f67 100644 --- a/powerauth-push-server/src/main/resources/application.properties +++ b/powerauth-push-server/src/main/resources/application.properties @@ -102,3 +102,5 @@ powerauth.service.correlation-header.value.validation-regexp=[a-zA-Z0-9\\-]{8,10 #management.endpoints.web.exposure.include=health, prometheus #management.endpoint.prometheus.enabled=true #management.prometheus.metrics.export.enabled=true + +spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration From e95be600d73b672f28b6e0740fde547f618baf3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Pe=C5=A1ek?= Date: Tue, 16 Jan 2024 15:40:26 +0100 Subject: [PATCH 08/44] Fix #681: Make create device service resilient to paralel device creation with same activationId (#741) * Fix #681: Make create device service resilient to paralel device creation with same activationId * Add configurable backoff and retry attempts --- docs/Configuration-Properties.md | 2 + .../PushServiceConfiguration.java | 42 +++++ .../controller/rest/PushDeviceController.java | 42 +++-- .../model/PushDeviceRegistrationEntity.java | 4 +- .../src/main/resources/application.properties | 4 + .../rest/PushDeviceControllerTest.java | 147 ++++++++++++++++++ 6 files changed, 230 insertions(+), 11 deletions(-) create mode 100644 powerauth-push-server/src/test/java/io/getlime/push/controller/rest/PushDeviceControllerTest.java diff --git a/docs/Configuration-Properties.md b/docs/Configuration-Properties.md index 163695f6a..0e4835792 100644 --- a/docs/Configuration-Properties.md +++ b/docs/Configuration-Properties.md @@ -31,6 +31,8 @@ The Push Server uses the following public configuration properties: | `powerauth.push.service.applicationEnvironment` | `_empty_` | Environment identifier | | `powerauth.push.service.message.storage.enabled` | `false` | Whether persistent storing of sent messages is enabled | | `powerauth.push.service.registration.multipleActivations.enabled` | `false` | Whether push registration supports "associated activations" | +| `powerauth.push.service.registration.retry.backoff` | `100ms` | Duration before a retry attempt during device registration in case of an insert error | +| `owerauth.push.service.registration.retry.maxAttempts` | `3` | Max number of retry attempts during device registration in case of an insert error | ## PowerAuth Push Campaign Setup diff --git a/powerauth-push-server/src/main/java/io/getlime/push/configuration/PushServiceConfiguration.java b/powerauth-push-server/src/main/java/io/getlime/push/configuration/PushServiceConfiguration.java index 861e4e51a..27b01ba7c 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/configuration/PushServiceConfiguration.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/configuration/PushServiceConfiguration.java @@ -20,6 +20,8 @@ import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; +import java.time.Duration; + /** * Configuration class for push server properties. * @@ -93,6 +95,14 @@ public class PushServiceConfiguration { @Value("${powerauth.push.service.registration.multipleActivations.enabled}") private boolean registrationOfMultipleActivationsEnabled; + // Duration before a retry attempt during device registration in case of an insert error + @Value("${powerauth.push.service.registration.retry.backoff:100ms}") + private Duration createDeviceRetryBackoff; + + // Max number of retry attempts during device registration in case of an insert error + @Value("${powerauth.push.service.registration.retry.maxAttempts:3}") + private int createDeviceRetryMaxAttempts; + /** * FCM connect timeout in milliseconds. */ @@ -427,6 +437,38 @@ public void setRegistrationOfMultipleActivationsEnabled(boolean registrationOfMu this.registrationOfMultipleActivationsEnabled = registrationOfMultipleActivationsEnabled; } + /** + * Get duration before a retry attempt during the device registration in case of an insert error. + * @return Duration before a retry attempt. + */ + public Duration getCreateDeviceRetryBackoff() { + return createDeviceRetryBackoff; + } + + /** + * Set duration before a retry attempt during the device registration in case of an insert error. + * @param createDeviceRetryBackoff Duration before a retry attempt. + */ + public void setCreateDeviceRetryBackoff(final Duration createDeviceRetryBackoff) { + this.createDeviceRetryBackoff = createDeviceRetryBackoff; + } + + /** + * Get max number of retry attempts during the device registration in case of an insert error. + * @return Max number of retry attempts. + */ + public int getCreateDeviceRetryMaxAttempts() { + return createDeviceRetryMaxAttempts; + } + + /** + * Set max number of retry attempts during the device registration in case of an insert error. + * @param createDeviceRetryMaxAttempts Max number of retry attempts. + */ + public void setCreateDeviceRetryMaxAttempts(final int createDeviceRetryMaxAttempts) { + this.createDeviceRetryMaxAttempts = createDeviceRetryMaxAttempts; + } + /** * Get FCM connect timeout in milliseconds. * @return FCM connect timeout. diff --git a/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/PushDeviceController.java b/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/PushDeviceController.java index fafa303ff..492e2e2e1 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/PushDeviceController.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/PushDeviceController.java @@ -38,8 +38,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.retry.support.RetryTemplate; import org.springframework.web.bind.annotation.*; +import java.time.Duration; import java.util.*; import java.util.concurrent.atomic.AtomicBoolean; @@ -93,39 +96,58 @@ public Response createDevice(@RequestBody ObjectRequest req } logger.info("Received createDevice request, app ID: {}, activation ID: {}, token: {}, platform: {}", requestObject.getAppId(), requestObject.getActivationId(), maskPushToken(requestObject.getToken()), requestObject.getPlatform()); - String errorMessage = CreateDeviceRequestValidator.validate(requestObject); + final String errorMessage = CreateDeviceRequestValidator.validate(requestObject); if (errorMessage != null) { throw new PushServerException(errorMessage); } - String appId = requestObject.getAppId(); - String pushToken = requestObject.getToken(); - String platform = requestObject.getPlatform(); - String activationId = requestObject.getActivationId(); - List devices = lookupDeviceRegistrations(appId, activationId, pushToken); - final AppCredentialsEntity appCredentials = findAppCredentials(appId); - PushDeviceRegistrationEntity device; + + final AppCredentialsEntity appCredentials = findAppCredentials(requestObject.getAppId()); + + // In case of parallel requests to create new device, the createOrUpdateDevice may fail because of unique + // constraint violation. Try to repeat the save. + RetryTemplate.builder() + .retryOn(DataIntegrityViolationException.class) + .fixedBackoff(config.getCreateDeviceRetryBackoff()) + .maxAttempts(config.getCreateDeviceRetryMaxAttempts()) + .build() + .execute(ctx -> createOrUpdateDevice(requestObject, appCredentials)); + + logger.info("The createDevice request succeeded, app ID: {}, activation ID: {}, platform: {}", requestObject.getAppId(), requestObject.getActivationId(), requestObject.getPlatform()); + return new Response(); + } + + private Void createOrUpdateDevice(final CreateDeviceRequest requestObject, final AppCredentialsEntity appCredentials) throws PushServerException { + final String appId = requestObject.getAppId(); + final String pushToken = requestObject.getToken(); + final String platform = requestObject.getPlatform(); + final String activationId = requestObject.getActivationId(); + + final List devices = lookupDeviceRegistrations(appId, activationId, pushToken); + final PushDeviceRegistrationEntity device; if (devices.isEmpty()) { // The device registration is new, create a new entity. + logger.info("Creating new device registration: app ID: {}, activation ID: {}, platform: {}", requestObject.getAppId(), requestObject.getActivationId(), requestObject.getPlatform()); device = initDeviceRegistrationEntity(appCredentials, pushToken); } else if (devices.size() == 1) { // An existing row was found by one of the lookup methods, update this row. This means that either: // 1. A row with same activation ID and push token is updated, in this case only the last registration timestamp changes. // 2. A row with same activation ID but different push token is updated. A new push token has been issued by Google or Apple for an activation. // 3. A row with same push token but different activation ID is updated. The user removed an activation and created a new one, the push token remains the same. + logger.info("Updating existing device registration: app ID: {}, activation ID: {}, platform: {}", requestObject.getAppId(), requestObject.getActivationId(), requestObject.getPlatform()); device = devices.get(0); updateDeviceRegistrationEntity(device, appCredentials, pushToken); } else { // Multiple existing rows have been found. This can only occur during lookup by push token. // Push token can be associated with multiple activations only when associated activations are enabled. // Push device registration must be done using /push/device/create/multi endpoint in this case. + logger.info("Multiple device registrations found: app ID: {}, activation ID: {}, platform: {}", requestObject.getAppId(), requestObject.getActivationId(), requestObject.getPlatform()); throw new PushServerException("Multiple device registrations found for push token. Use the /push/device/create/multi endpoint for this scenario."); } device.setTimestampLastRegistered(new Date()); device.setPlatform(platform); updateActivationForDevice(device, activationId); pushDeviceRepository.save(device); - logger.info("The createDevice request succeeded, app ID: {}, activation ID: {}, platform: {}", requestObject.getAppId(), requestObject.getActivationId(), requestObject.getPlatform()); - return new Response(); + return null; } /** diff --git a/powerauth-push-server/src/main/java/io/getlime/push/repository/model/PushDeviceRegistrationEntity.java b/powerauth-push-server/src/main/java/io/getlime/push/repository/model/PushDeviceRegistrationEntity.java index f089e8186..066a1bb8d 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/repository/model/PushDeviceRegistrationEntity.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/repository/model/PushDeviceRegistrationEntity.java @@ -28,7 +28,9 @@ * @author Petr Dvorak, petr@wultra.com */ @Entity -@Table(name = "push_device_registration") +@Table(name = "push_device_registration", + uniqueConstraints = {@UniqueConstraint(columnNames = {"activationId", "pushToken"}), + @UniqueConstraint(columnNames = {"activationId"})}) public class PushDeviceRegistrationEntity implements Serializable { @Serial diff --git a/powerauth-push-server/src/main/resources/application.properties b/powerauth-push-server/src/main/resources/application.properties index 30c922f67..b8f836f51 100644 --- a/powerauth-push-server/src/main/resources/application.properties +++ b/powerauth-push-server/src/main/resources/application.properties @@ -48,6 +48,10 @@ powerauth.push.service.message.storage.enabled=false # Whether push registration supports associated activations powerauth.push.service.registration.multipleActivations.enabled=false +# Retry logic of creating a new device registration +powerauth.push.service.registration.retry.backoff=100ms +powerauth.push.service.registration.retry.maxAttempts=3 + # APNs Configuration powerauth.push.service.apns.useDevelopment=true powerauth.push.service.apns.proxy.enabled=false diff --git a/powerauth-push-server/src/test/java/io/getlime/push/controller/rest/PushDeviceControllerTest.java b/powerauth-push-server/src/test/java/io/getlime/push/controller/rest/PushDeviceControllerTest.java new file mode 100644 index 000000000..4abd790ea --- /dev/null +++ b/powerauth-push-server/src/test/java/io/getlime/push/controller/rest/PushDeviceControllerTest.java @@ -0,0 +1,147 @@ +/* + * Copyright 2024 Wultra s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.getlime.push.controller.rest; + +import com.wultra.security.powerauth.client.PowerAuthClient; +import com.wultra.security.powerauth.client.model.enumeration.ActivationStatus; +import com.wultra.security.powerauth.client.model.response.GetActivationStatusResponse; +import io.getlime.core.rest.model.base.request.ObjectRequest; +import io.getlime.core.rest.model.base.response.Response; +import io.getlime.push.errorhandling.exceptions.PushServerException; +import io.getlime.push.model.request.CreateDeviceRequest; +import io.getlime.push.repository.AppCredentialsRepository; +import io.getlime.push.repository.PushDeviceRepository; +import io.getlime.push.repository.model.AppCredentialsEntity; +import io.getlime.push.repository.model.PushDeviceRegistrationEntity; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.context.ActiveProfiles; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.*; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.when; + +/** + * Test for {@link PushDeviceController}. + * + * @author Jan Pesek, jan.pesek@wultra.com + */ +@SpringBootTest +@ActiveProfiles("test") +@AutoConfigureMockMvc +class PushDeviceControllerTest { + + @Autowired + private PushDeviceController tested; + + @Autowired + private PushDeviceRepository deviceRepository; + + @Autowired + private AppCredentialsRepository appCredentialsRepository; + + @MockBean + private PowerAuthClient powerAuthClient; + + @Test + void testCreateDevice() throws Exception { + createAppCredentials("my_app"); + when(powerAuthClient.getActivationStatus("a1")) + .thenReturn(createActivationStatusResponse("a1")); + + + final CreateDeviceRequest request = new CreateDeviceRequest(); + request.setAppId("my_app"); + request.setActivationId("a1"); + request.setToken("t1"); + request.setPlatform("android"); + + tested.createDevice(new ObjectRequest<>(request)); + + final List entities = deviceRepository.findByActivationId("a1"); + assertEquals(1, entities.size()); + assertEquals("a1", entities.get(0).getActivationId()); + assertEquals("t1", entities.get(0).getPushToken()); + } + + @Test + void testCreateDevice_missingApplication() throws Exception { + createAppCredentials("my_app"); + when(powerAuthClient.getActivationStatus("a2")) + .thenReturn(createActivationStatusResponse("a2")); + + final CreateDeviceRequest request = new CreateDeviceRequest(); + request.setAppId("non_existent"); + request.setActivationId("a2"); + request.setToken("t0"); + request.setPlatform("android"); + + assertThrows(PushServerException.class, () -> tested.createDevice(new ObjectRequest<>(request))); + + final List entities = deviceRepository.findByActivationId("a2"); + assertEquals(0, entities.size()); + } + + @Test + void testCreateDevice_parallel() throws Exception { + createAppCredentials("my_app"); + when(powerAuthClient.getActivationStatus("a3")) + .thenReturn(createActivationStatusResponse("a3")); + + final int nThreads = 5; + final ExecutorService executorService = Executors.newFixedThreadPool(nThreads); + final List> callableTasks = new ArrayList<>(); + for (int i = 0; i < nThreads; i++) { + callableTasks.add(() -> { + final CreateDeviceRequest request = new CreateDeviceRequest(); + request.setAppId("my_app"); + request.setActivationId("a3"); + request.setToken("token"); + request.setPlatform("ios"); + return tested.createDevice(new ObjectRequest<>(request)); + }); + } + + executorService.invokeAll(callableTasks).forEach(future -> assertDoesNotThrow(() -> future.get())); + executorService.shutdown(); + + final List entities = deviceRepository.findByActivationId("a3"); + assertEquals(1, entities.size()); + } + + private GetActivationStatusResponse createActivationStatusResponse(final String activationId) { + final GetActivationStatusResponse activationResponse = new GetActivationStatusResponse(); + activationResponse.setActivationId(activationId); + activationResponse.setActivationStatus(ActivationStatus.ACTIVE); + activationResponse.setUserId("joe"); + return activationResponse; + } + + private AppCredentialsEntity createAppCredentials(String appName) { + final AppCredentialsEntity credentials = new AppCredentialsEntity(); + credentials.setAppId(appName); + return appCredentialsRepository.save(credentials); + } + +} From 5583188d3b4dfaa36d2ceaf465ad4eed307fa7e7 Mon Sep 17 00:00:00 2001 From: Lubos Racansky Date: Fri, 19 Jan 2024 11:41:00 +0100 Subject: [PATCH 09/44] Fix #743: Reduce duplicity at PushMessageSenderService --- .../model/entity/PushMessageSendResult.java | 249 +++--------------- .../service/PushMessageSenderService.java | 140 ++++------ 2 files changed, 86 insertions(+), 303 deletions(-) diff --git a/powerauth-push-model/src/main/java/io/getlime/push/model/entity/PushMessageSendResult.java b/powerauth-push-model/src/main/java/io/getlime/push/model/entity/PushMessageSendResult.java index b870c88b6..aadee3b79 100644 --- a/powerauth-push-model/src/main/java/io/getlime/push/model/entity/PushMessageSendResult.java +++ b/powerauth-push-model/src/main/java/io/getlime/push/model/entity/PushMessageSendResult.java @@ -17,214 +17,34 @@ package io.getlime.push.model.entity; import io.getlime.push.model.enumeration.Mode; +import lombok.Data; +import lombok.EqualsAndHashCode; /** * Class that contains push message sending result data. * * @author Petr Dvorak, petr@wultra.com */ +@Data +@EqualsAndHashCode(callSuper = true) public class PushMessageSendResult extends BasePushMessageSendResult { /** - * Result for the iOS platform. + * Data associated with push messages sent to iOS devices. */ - public static class iOS { - - private int sent; - private int failed; - private int pending; - private int total; - - /** - * Get number of messages that were sent successfully. - * @return Number of sent messages. - */ - public int getSent() { - return sent; - } - - /** - * Set number of messages that were sent successfully. - * @param sent Number of sent messages. - */ - public void setSent(int sent) { - this.sent = sent; - } - - /** - * Get number of messages that were sent with failure. - * @return Number of failed messages. - */ - public int getFailed() { - return failed; - } - - /** - * Set number of messages that were sent with failure. - * @param failed Number of failed messages. - */ - public void setFailed(int failed) { - this.failed = failed; - } - - /** - * Get number of messages that are still in pending state after attempted sending. - * @return Number of pending messages. - */ - public int getPending() { - return pending; - } - - /** - * Set number of messages that are still in pending state after attempted sending. - * @param pending Number of pending messages. - */ - public void setPending(int pending) { - this.pending = pending; - } - - /** - * Get total number of messages that were attempted to send. - * @return Total number of messages. - */ - public int getTotal() { - return total; - } - - /** - * Set total number of messages that were attempted to send. - * @param total Total number of messages. - */ - public void setTotal(int total) { - this.total = total; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof final iOS iOS)) return false; - - if (getSent() != iOS.getSent()) return false; - if (getFailed() != iOS.getFailed()) return false; - if (getPending() != iOS.getPending()) return false; - return getTotal() == iOS.getTotal(); - } - - @Override - public int hashCode() { - int result = getTotal(); - result = 31 * result + getFailed(); - result = 31 * result + getPending(); - result = 31 * result + getSent(); - return result; - } - } + private final PlatformResult ios; /** - * Result for the Android platform. + * Data associated with push messages sent to Android devices. */ - public static class Android { - - private int sent; - private int failed; - private int pending; - private int total; - - /** - * Get number of messages that were sent successfully. - * @return Number of sent messages. - */ - public int getSent() { - return sent; - } - - /** - * Set number of messages that were sent successfully. - * @param sent Number of sent messages. - */ - public void setSent(int sent) { - this.sent = sent; - } - - /** - * Get number of messages that were sent with failure. - * @return Number of failed messages. - */ - public int getFailed() { - return failed; - } - - /** - * Set number of messages that were sent with failure. - * @param failed Number of failed messages. - */ - public void setFailed(int failed) { - this.failed = failed; - } - - /** - * Get number of messages that are still in pending state after attempted sending. - * @return Number of pending messages. - */ - public int getPending() { - return pending; - } - - /** - * Set number of messages that are still in pending state after attempted sending. - * @param pending Number of pending messages. - */ - public void setPending(int pending) { - this.pending = pending; - } - - /** - * Get total number of messages that were attempted to send. - * @return Total number of messages. - */ - public int getTotal() { - return total; - } - - /** - * Set total number of messages that were attempted to send. - * @param total Total number of messages. - */ - public void setTotal(int total) { - this.total = total; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof final Android android)) return false; - - if (getSent() != android.getSent()) return false; - if (getFailed() != android.getFailed()) return false; - if (getPending() != android.getPending()) return false; - return getTotal() == android.getTotal(); - } - - @Override - public int hashCode() { - int result = getTotal(); - result = 31 * result + getFailed(); - result = 31 * result + getPending(); - result = 31 * result + getSent(); - return result; - } - } - - private final iOS ios; - private final Android android; + private final PlatformResult android; /** * Default constructor. */ public PushMessageSendResult() { - super(); - this.ios = new iOS(); - this.android = new Android(); + this.ios = new PlatformResult(); + this.android = new PlatformResult(); } /** @@ -234,40 +54,35 @@ public PushMessageSendResult() { */ public PushMessageSendResult(Mode mode) { super(mode); - this.ios = new iOS(); - this.android = new Android(); + this.ios = new PlatformResult(); + this.android = new PlatformResult(); } /** - * Data associated with push messages sent to Android devices. - * @return Data related to FCM service. + * Result for the platform. */ - public Android getAndroid() { - return android; - } + @Data + public static class PlatformResult { - /** - * Data associated with push messages sent to iOS devices. - * @return Data related to APNS service. - */ - public iOS getIos() { - return ios; - } + /** + * Number of messages that were sent successfully. + */ + private int sent; - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof final PushMessageSendResult that)) return false; + /** + * Number of messages that were sent with failure + */ + private int failed; - if (getIos() != null ? !getIos().equals(that.getIos()) : that.getIos() != null) return false; - return getAndroid() != null ? getAndroid().equals(that.getAndroid()) : that.getAndroid() == null; - } + /** + * Number of messages that are still in pending state after attempted sending + */ + private int pending; + + /** + * Total number of messages that were attempted to send + */ + private int total; - @Override - public int hashCode() { - int result = 1; - result = 31 * result + (getAndroid() != null ? getAndroid().hashCode() : 0); - result = 31 * result + (getIos() != null ? getIos().hashCode() : 0); - return result; } } diff --git a/powerauth-push-server/src/main/java/io/getlime/push/service/PushMessageSenderService.java b/powerauth-push-server/src/main/java/io/getlime/push/service/PushMessageSenderService.java index 4d51c389c..e953039cb 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/service/PushMessageSenderService.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/service/PushMessageSenderService.java @@ -130,34 +130,8 @@ public BasePushMessageSendResult sendPushMessage(final String appId, final Mode arriveAndDeregisterPhaserForMode(phaser, mode); continue; } - pushSendingWorker.sendMessageToIos(pushClient.getApnsClient(), pushMessage.getBody(), pushMessage.getAttributes(), pushMessage.getPriority(), device.getPushToken(), pushClient.getAppCredentials().getIosBundle(), (result) -> { - try { - switch (result) { - case OK -> { - sendResult.getIos().setSent(sendResult.getIos().getSent() + 1); - updateStatusAndPersist(pushMessageObject, PushMessageEntity.Status.SENT); - } - case PENDING -> { - sendResult.getIos().setPending(sendResult.getIos().getPending() + 1); - updateStatusAndPersist(pushMessageObject, PushMessageEntity.Status.PENDING); - } - case FAILED -> { - sendResult.getIos().setFailed(sendResult.getIos().getFailed() + 1); - updateStatusAndPersist(pushMessageObject, PushMessageEntity.Status.FAILED); - } - case FAILED_DELETE -> { - sendResult.getIos().setFailed(sendResult.getIos().getFailed() + 1); - updateStatusAndPersist(pushMessageObject, PushMessageEntity.Status.FAILED); - pushDeviceRepository.delete(device); - } - } - sendResult.getIos().setTotal(sendResult.getIos().getTotal() + 1); - } catch (Throwable t) { - logger.error("System error when sending notification: {}", t.getMessage(), t); - } finally { - arriveAndDeregisterPhaserForMode(phaser, mode); - } - }); + final PushMessageSendResult.PlatformResult platformResult = sendResult.getIos(); + pushSendingWorker.sendMessageToIos(pushClient.getApnsClient(), pushMessage.getBody(), pushMessage.getAttributes(), pushMessage.getPriority(), device.getPushToken(), pushClient.getAppCredentials().getIosBundle(), createPushSendingCallback(mode, device, platformResult, pushMessageObject, phaser)); } else if (platform.equals(PushDeviceRegistrationEntity.Platform.Android)) { if (pushClient.getFcmClient() == null) { logger.error("Push message cannot be sent to FCM because FCM is not configured in push server."); @@ -165,34 +139,8 @@ public BasePushMessageSendResult sendPushMessage(final String appId, final Mode continue; } final String token = device.getPushToken(); - pushSendingWorker.sendMessageToAndroid(pushClient.getFcmClient(), pushMessage.getBody(), pushMessage.getAttributes(), pushMessage.getPriority(), token, (sendingResult) -> { - try { - switch (sendingResult) { - case OK -> { - sendResult.getAndroid().setSent(sendResult.getAndroid().getSent() + 1); - updateStatusAndPersist(pushMessageObject, PushMessageEntity.Status.SENT); - } - case PENDING -> { - sendResult.getAndroid().setPending(sendResult.getAndroid().getPending() + 1); - updateStatusAndPersist(pushMessageObject, PushMessageEntity.Status.PENDING); - } - case FAILED -> { - sendResult.getAndroid().setFailed(sendResult.getAndroid().getFailed() + 1); - updateStatusAndPersist(pushMessageObject, PushMessageEntity.Status.FAILED); - } - case FAILED_DELETE -> { - sendResult.getAndroid().setFailed(sendResult.getAndroid().getFailed() + 1); - updateStatusAndPersist(pushMessageObject, PushMessageEntity.Status.FAILED); - pushDeviceRepository.delete(device); - } - } - sendResult.getAndroid().setTotal(sendResult.getAndroid().getTotal() + 1); - } catch (Throwable t) { - logger.error("System error when sending notification: {}", t.getMessage(), t); - } finally { - arriveAndDeregisterPhaserForMode(phaser, mode); - } - }); + final PushMessageSendResult.PlatformResult platformResult = sendResult.getAndroid(); + pushSendingWorker.sendMessageToAndroid(pushClient.getFcmClient(), pushMessage.getBody(), pushMessage.getAttributes(), pushMessage.getPriority(), token, createPushSendingCallback(mode, device, platformResult, pushMessageObject, phaser)); } } } @@ -201,6 +149,37 @@ public BasePushMessageSendResult sendPushMessage(final String appId, final Mode return mode == Mode.SYNCHRONOUS ? sendResult : new BasePushMessageSendResult(mode); } + private PushSendingCallback createPushSendingCallback(final Mode mode, final PushDeviceRegistrationEntity device, final PushMessageSendResult.PlatformResult platformResult, final PushMessageEntity pushMessageObject, final Phaser phaser) { + return sendingResult -> { + try { + switch (sendingResult) { + case OK -> { + platformResult.setSent(platformResult.getSent() + 1); + updateStatusAndPersist(pushMessageObject, PushMessageEntity.Status.SENT); + } + case PENDING -> { + platformResult.setPending(platformResult.getPending() + 1); + updateStatusAndPersist(pushMessageObject, PushMessageEntity.Status.PENDING); + } + case FAILED -> { + platformResult.setFailed(platformResult.getFailed() + 1); + updateStatusAndPersist(pushMessageObject, PushMessageEntity.Status.FAILED); + } + case FAILED_DELETE -> { + platformResult.setFailed(platformResult.getFailed() + 1); + updateStatusAndPersist(pushMessageObject, PushMessageEntity.Status.FAILED); + pushDeviceRepository.delete(device); + } + } + platformResult.setTotal(platformResult.getTotal() + 1); + } catch (Throwable t) { + logger.error("System error when sending notification: {}", t.getMessage(), t); + } finally { + arriveAndDeregisterPhaserForMode(phaser, mode); + } + }; + } + /** * Send push message content with related message attributes to provided device (platform and token) using * credentials for given application. Return the result in the callback. @@ -241,39 +220,28 @@ public void sendCampaignMessage(final String appId, String platform, final Strin final PushMessageEntity pushMessageObject = pushMessageDAO.storePushMessageObject(pushMessageBody, attributes, userId, activationId, deviceId); - if (platform.equals(PushDeviceRegistrationEntity.Platform.iOS)) { - pushSendingWorker.sendMessageToIos(pushClient.getApnsClient(), pushMessageBody, attributes, priority, token, pushClient.getAppCredentials().getIosBundle(), (result) -> { - switch (result) { - case OK -> - updateStatusAndPersist(pushMessageObject, PushMessageEntity.Status.SENT); - case PENDING -> - updateStatusAndPersist(pushMessageObject, PushMessageEntity.Status.PENDING); - case FAILED -> - updateStatusAndPersist(pushMessageObject, PushMessageEntity.Status.FAILED); - case FAILED_DELETE -> { - updateStatusAndPersist(pushMessageObject, PushMessageEntity.Status.FAILED); - pushDeviceRepository.deleteAllByAppCredentialsIdAndPushToken(pushClient.getAppCredentials().getId(), token); - } - } - }); - } else if (platform.equals(PushDeviceRegistrationEntity.Platform.Android)) { - pushSendingWorker.sendMessageToAndroid(pushClient.getFcmClient(), pushMessageBody, attributes, priority, token, (result) -> { - switch (result) { - case OK -> - updateStatusAndPersist(pushMessageObject, PushMessageEntity.Status.SENT); - case PENDING -> - updateStatusAndPersist(pushMessageObject, PushMessageEntity.Status.PENDING); - case FAILED -> - updateStatusAndPersist(pushMessageObject, PushMessageEntity.Status.FAILED); - case FAILED_DELETE -> { - updateStatusAndPersist(pushMessageObject, PushMessageEntity.Status.FAILED); - pushDeviceRepository.deleteAllByAppCredentialsIdAndPushToken(pushClient.getAppCredentials().getId(), token); - } - } - }); + switch (platform) { + case PushDeviceRegistrationEntity.Platform.iOS -> + pushSendingWorker.sendMessageToIos(pushClient.getApnsClient(), pushMessageBody, attributes, priority, token, pushClient.getAppCredentials().getIosBundle(), createPushSendingCallback(token, pushMessageObject, pushClient)); + case PushDeviceRegistrationEntity.Platform.Android -> + pushSendingWorker.sendMessageToAndroid(pushClient.getFcmClient(), pushMessageBody, attributes, priority, token, createPushSendingCallback(token, pushMessageObject, pushClient)); } } + private PushSendingCallback createPushSendingCallback(final String token, final PushMessageEntity pushMessageObject, final AppRelatedPushClient pushClient) { + return result -> { + switch (result) { + case OK -> updateStatusAndPersist(pushMessageObject, PushMessageEntity.Status.SENT); + case PENDING -> updateStatusAndPersist(pushMessageObject, PushMessageEntity.Status.PENDING); + case FAILED -> updateStatusAndPersist(pushMessageObject, PushMessageEntity.Status.FAILED); + case FAILED_DELETE -> { + updateStatusAndPersist(pushMessageObject, PushMessageEntity.Status.FAILED); + pushDeviceRepository.deleteAllByAppCredentialsIdAndPushToken(pushClient.getAppCredentials().getId(), token); + } + } + }; + } + // Lookup application credentials by appID and throw exception in case application is not found. private AppCredentialsEntity getAppCredentials(String appId) throws PushServerException { return appCredentialsRepository.findFirstByAppId(appId).orElseThrow(() -> From 0a9f7b8ba47663970d995150fed3014fb1867a4c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jan 2024 01:16:39 +0000 Subject: [PATCH 10/44] Bump org.springframework.boot:spring-boot-starter-parent Bumps [org.springframework.boot:spring-boot-starter-parent](https://github.com/spring-projects/spring-boot) from 3.2.1 to 3.2.2. - [Release notes](https://github.com/spring-projects/spring-boot/releases) - [Commits](https://github.com/spring-projects/spring-boot/compare/v3.2.1...v3.2.2) --- updated-dependencies: - dependency-name: org.springframework.boot:spring-boot-starter-parent dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5ab654179..d5f65a58c 100644 --- a/pom.xml +++ b/pom.xml @@ -14,7 +14,7 @@ org.springframework.boot spring-boot-starter-parent - 3.2.1 + 3.2.2 From 49ab6caf91c574a6deb843948c750a65a1c22c11 Mon Sep 17 00:00:00 2001 From: Lubos Racansky Date: Mon, 22 Jan 2024 09:07:18 +0100 Subject: [PATCH 11/44] Fix #748: Switch H2 test db from file into mem --- .../resources/application-test-multiple-activations.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/powerauth-push-server/src/test/resources/application-test-multiple-activations.properties b/powerauth-push-server/src/test/resources/application-test-multiple-activations.properties index 58a5195c3..d647d1c0c 100644 --- a/powerauth-push-server/src/test/resources/application-test-multiple-activations.properties +++ b/powerauth-push-server/src/test/resources/application-test-multiple-activations.properties @@ -19,7 +19,7 @@ spring.profiles.active=test spring.h2.console.enabled=true spring.h2.console.path=/h2 # Datasource -spring.datasource.url=jdbc:h2:file:~/powerauth;MODE=LEGACY +spring.datasource.url=jdbc:h2:mem:powerauth;MODE=LEGACY spring.datasource.username=sa spring.datasource.password= spring.datasource.driver-class-name=org.h2.Driver From 19f05cde8db78336ec75f75e55a046544e87561b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lubo=C5=A1=20Ra=C4=8Dansk=C3=BD?= Date: Mon, 22 Jan 2024 10:31:47 +0100 Subject: [PATCH 12/44] Fix #746: Refactor PushServiceConfiguration into Lombok (#747) * Fix #746: Refactor PushServiceConfiguration into Lombok --- .../PushServiceConfiguration.java | 489 +++--------------- 1 file changed, 69 insertions(+), 420 deletions(-) diff --git a/powerauth-push-server/src/main/java/io/getlime/push/configuration/PushServiceConfiguration.java b/powerauth-push-server/src/main/java/io/getlime/push/configuration/PushServiceConfiguration.java index 27b01ba7c..9e8b5e95d 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/configuration/PushServiceConfiguration.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/configuration/PushServiceConfiguration.java @@ -16,6 +16,8 @@ package io.getlime.push.configuration; +import lombok.Getter; +import lombok.Setter; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; @@ -29,77 +31,139 @@ */ @Configuration @ConfigurationProperties("ext") +@Getter +@Setter public class PushServiceConfiguration { + /** + * Push server name. + */ @Value("${powerauth.push.service.applicationName}") private String pushServerName; + /** + * Push server display name. + */ @Value("${powerauth.push.service.applicationDisplayName}") private String pushServerDisplayName; + /** + * Push server environment. + */ @Value("${powerauth.push.service.applicationEnvironment}") private String pushServerEnvironment; // APNs Configuration + /** + * Flag indicating if a development or production environment should be used for APNs. + * {@code true} in case APNs should use DEV environment, {@code false} for PROD. + */ @Value("${powerauth.push.service.apns.useDevelopment}") private boolean apnsUseDevelopment; + /** + * Whether proxy should be used for APNs. + */ @Value("${powerauth.push.service.apns.proxy.enabled}") private boolean apnsProxyEnabled; + /** + * APNs proxy host. + */ @Value("${powerauth.push.service.apns.proxy.host}") private String apnsProxyHost; + /** + * APNs proxy port. + */ @Value("${powerauth.push.service.apns.proxy.port}") private int apnsProxyPort; + /** + * APNs proxy username. + */ @Value("${powerauth.push.service.apns.proxy.username}") private String apnsProxyUsername; + /** + * APNs proxy password. + */ @Value("${powerauth.push.service.apns.proxy.password}") private String apnsProxyPassword; // FCM Configuration + /** + * Flag indicating if proxy is enabled for FCM communication. + */ @Value("${powerauth.push.service.fcm.proxy.enabled}") private boolean fcmProxyEnabled; + /** + * FCM proxy URL. + */ @Value("${powerauth.push.service.fcm.proxy.host}") private String fcmProxyHost; + /** + * FCM proxy port. + */ @Value("${powerauth.push.service.fcm.proxy.port}") private int fcmProxyPort; + /** + * FCM proxy username. + */ @Value("${powerauth.push.service.fcm.proxy.username}") private String fcmProxyUsername; + /** + * FCM proxy password. + */ @Value("${powerauth.push.service.fcm.proxy.password}") private String fcmProxyPassword; + /** + * Get status if notification is set to be sent only through data map + * True in case FCM notification should always be a "data" notification, even for messages with title and message, false otherwise. + */ @Value("${powerauth.push.service.fcm.dataNotificationOnly}") private boolean fcmDataNotificationOnly; + /** + * FCM send message endpoint URL. + */ @Value("${powerauth.push.service.fcm.sendMessageUrl}") private String fcmSendMessageUrl; - // Campaign Configuration + /** + * The batch size used while sending a push campaign. + */ @Value("${powerauth.push.service.campaign.batchSize}") private int campaignBatchSize; - // Whether to store messages + /** + * Whether to store messages. + */ @Value("${powerauth.push.service.message.storage.enabled}") private boolean messageStorageEnabled; - // Whether multiple activations are enabled per registered device + /** + * Whether multiple activations are enabled per registered device. + */ @Value("${powerauth.push.service.registration.multipleActivations.enabled}") private boolean registrationOfMultipleActivationsEnabled; - // Duration before a retry attempt during device registration in case of an insert error + /** + * Duration before a retry attempt during device registration in case of an insert error. + */ @Value("${powerauth.push.service.registration.retry.backoff:100ms}") private Duration createDeviceRetryBackoff; - // Max number of retry attempts during device registration in case of an insert error + /** + * Max number of retry attempts during device registration in case of an insert error. + */ @Value("${powerauth.push.service.registration.retry.maxAttempts:3}") private int createDeviceRetryMaxAttempts; @@ -133,419 +197,4 @@ public class PushServiceConfiguration { @Value("${powerauth.push.java.cacerts.password}") private String javaCaCertificatesPassword; - /** - * Get push server name. - * @return Push server name. - */ - public String getPushServerName() { - return pushServerName; - } - - /** - * Set push server name. - * @param pushServerName Push server name. - */ - public void setPushServerName(String pushServerName) { - this.pushServerName = pushServerName; - } - - /** - * Get push server display name. - * @return Push server display name. - */ - public String getPushServerDisplayName() { - return pushServerDisplayName; - } - - /** - * Set push server display name. - * @param pushServerDisplayName Push server display name. - */ - public void setPushServerDisplayName(String pushServerDisplayName) { - this.pushServerDisplayName = pushServerDisplayName; - } - - /** - * Get push server environment. - * @return Push server environment. - */ - public String getPushServerEnvironment() { - return pushServerEnvironment; - } - - /** - * Set push server environment. - * @param pushServerEnvironment Push server environment. - */ - public void setPushServerEnvironment(String pushServerEnvironment) { - this.pushServerEnvironment = pushServerEnvironment; - } - - /** - * Flag indicating if a development or production environment should be used for APNs. - * @return True in case APNs should use DEV environment, false for PROD. - */ - public boolean isApnsUseDevelopment() { - return apnsUseDevelopment; - } - - /** - * Set if development environment should be used, instead of production. - * @param apnsUseDevelopment True in case APNs should use DEV environment, false for PROD. - */ - public void setApnsUseDevelopment(boolean apnsUseDevelopment) { - this.apnsUseDevelopment = apnsUseDevelopment; - } - - /** - * Flag indicating if proxy should be used for APNs. - * @return True if proxy should be used for APNs, false otherwise. - */ - public boolean isApnsProxyEnabled() { - return apnsProxyEnabled; - } - - /** - * Set if proxy should be used for APNs. - * @param apnsProxyEnabled True if proxy should be used for APNs, false otherwise. - */ - public void setApnsProxyEnabled(boolean apnsProxyEnabled) { - this.apnsProxyEnabled = apnsProxyEnabled; - } - - /** - * Get APNs proxy URL address. - * @return APNs proxy URL address. - */ - public String getApnsProxyHost() { - return apnsProxyHost; - } - - /** - * Set APNs proxy URL address. - * @param apnsProxyHost APNs proxy URL address. - */ - public void setApnsProxyHost(String apnsProxyHost) { - this.apnsProxyHost = apnsProxyHost; - } - - /** - * Get APNs proxy port. - * @return APNs proxy port. - */ - public int getApnsProxyPort() { - return apnsProxyPort; - } - - /** - * Set APNs proxy port. - * @param apnsProxyPort APNs proxy port. - */ - public void setApnsProxyPort(int apnsProxyPort) { - this.apnsProxyPort = apnsProxyPort; - } - - /** - * Get APNs proxy username. - * @return APNs proxy username. - */ - public String getApnsProxyUsername() { - return apnsProxyUsername; - } - - /** - * Set APNs proxy username. - * @param apnsProxyUsername APNs proxy username. - */ - public void setApnsProxyUsername(String apnsProxyUsername) { - this.apnsProxyUsername = apnsProxyUsername; - } - - /** - * Get APNs proxy password. - * @return APNs proxy password. - */ - public String getApnsProxyPassword() { - return apnsProxyPassword; - } - - /** - * Set APNs proxy password. - * @param apnsProxyPassword APNs proxy password. - */ - public void setApnsProxyPassword(String apnsProxyPassword) { - this.apnsProxyPassword = apnsProxyPassword; - } - - /** - * Flag indicating if proxy is enabled for FCM communication. - * @return True if FCM uses proxy, false otherwise. - */ - public boolean isFcmProxyEnabled() { - return fcmProxyEnabled; - } - - /** - * Set if proxy should be used for FCM communication. - * @param fcmProxyEnabled True if FCM uses proxy, false otherwise. - */ - public void setFcmProxyEnabled(boolean fcmProxyEnabled) { - this.fcmProxyEnabled = fcmProxyEnabled; - } - - /** - * Get FCM proxy URL. - * @return FCM proxy URL. - */ - public String getFcmProxyHost() { - return fcmProxyHost; - } - - /** - * Set FCM proxy URL. - * @param fcmProxyHost FCM proxy URL. - */ - public void setFcmProxyHost(String fcmProxyHost) { - this.fcmProxyHost = fcmProxyHost; - } - - /** - * Get FCM proxy port. - * @return FCM proxy port. - */ - public int getFcmProxyPort() { - return fcmProxyPort; - } - - /** - * Set FCM proxy port. - * @param fcmProxyPort FCM proxy port. - */ - public void setFcmProxyPort(int fcmProxyPort) { - this.fcmProxyPort = fcmProxyPort; - } - - /** - * Get FCM proxy username. - * @return FCM proxy username. - */ - public String getFcmProxyUsername() { - return fcmProxyUsername; - } - - /** - * Set FCM proxy username. - * @param fcmProxyUsername FCM proxy username. - */ - public void setFcmProxyUsername(String fcmProxyUsername) { - this.fcmProxyUsername = fcmProxyUsername; - } - - /** - * Get FCM proxy password. - * @return FCM proxy password. - */ - public String getFcmProxyPassword() { - return fcmProxyPassword; - } - - /** - * Set FCM proxy password. - * @param fcmProxyPassword FCM proxy password. - */ - public void setFcmProxyPassword(String fcmProxyPassword) { - this.fcmProxyPassword = fcmProxyPassword; - } - - /** - * Get status if notification is set to be sent only through data map - * @return True in case FCM notification should always be a "data" notification, even for messages with title and message, false otherwise. - */ - public boolean isFcmDataNotificationOnly() { - return fcmDataNotificationOnly; - } - - /** - * Set if notification should be send only through data map - * @param fcmDataNotificationOnly True in case FCM notification should always be a "data" notification, even for messages with title and message, false otherwise. - */ - public void setFcmDataNotificationOnly(boolean fcmDataNotificationOnly) { - this.fcmDataNotificationOnly = fcmDataNotificationOnly; - } - - /** - * Get FCM send message endpoint URL. - * @return FCM send message endpoint URL. - */ - public String getFcmSendMessageUrl() { - return fcmSendMessageUrl; - } - - /** - * Set FCM send message endpoint URL. - * @param fcmSendMessageUrl FCM send message endpoint URL. - */ - public void setFcmSendMessageUrl(String fcmSendMessageUrl) { - this.fcmSendMessageUrl = fcmSendMessageUrl; - } - - /** - * Get the batch size used while sending a push campaign. - * @return Batch size. - */ - public int getCampaignBatchSize() { - return campaignBatchSize; - } - - /** - * Set the batch size used while sending a push campaign. - * @param campaignBatchSize Batch size. - */ - public void setCampaignBatchSize(int campaignBatchSize) { - this.campaignBatchSize = campaignBatchSize; - } - - /** - * Get whether persistent message storage is enabled. - * @return Whether persistent message storage is enabled. - */ - public boolean isMessageStorageEnabled() { - return messageStorageEnabled; - } - - /** - * Set whether persistent message storage is enabled. - * @param messageStorageEnabled Whether persistent message storage is enabled. - */ - public void setMessageStorageEnabled(boolean messageStorageEnabled) { - this.messageStorageEnabled = messageStorageEnabled; - } - - /** - * Get whether multiple activations are enabled per registered device. - * @return Whether multiple activations are enabled per registered device. - */ - public boolean isRegistrationOfMultipleActivationsEnabled() { - return registrationOfMultipleActivationsEnabled; - } - - /** - * Set whether multiple activations are enabled per registered device. - * @param registrationOfMultipleActivationsEnabled Whether multiple activations are enabled per registered device. - */ - public void setRegistrationOfMultipleActivationsEnabled(boolean registrationOfMultipleActivationsEnabled) { - this.registrationOfMultipleActivationsEnabled = registrationOfMultipleActivationsEnabled; - } - - /** - * Get duration before a retry attempt during the device registration in case of an insert error. - * @return Duration before a retry attempt. - */ - public Duration getCreateDeviceRetryBackoff() { - return createDeviceRetryBackoff; - } - - /** - * Set duration before a retry attempt during the device registration in case of an insert error. - * @param createDeviceRetryBackoff Duration before a retry attempt. - */ - public void setCreateDeviceRetryBackoff(final Duration createDeviceRetryBackoff) { - this.createDeviceRetryBackoff = createDeviceRetryBackoff; - } - - /** - * Get max number of retry attempts during the device registration in case of an insert error. - * @return Max number of retry attempts. - */ - public int getCreateDeviceRetryMaxAttempts() { - return createDeviceRetryMaxAttempts; - } - - /** - * Set max number of retry attempts during the device registration in case of an insert error. - * @param createDeviceRetryMaxAttempts Max number of retry attempts. - */ - public void setCreateDeviceRetryMaxAttempts(final int createDeviceRetryMaxAttempts) { - this.createDeviceRetryMaxAttempts = createDeviceRetryMaxAttempts; - } - - /** - * Get FCM connect timeout in milliseconds. - * @return FCM connect timeout. - */ - public int getFcmConnectTimeout() { - return fcmConnectTimeout; - } - - /** - * Set FCM connect timeout in milliseconds. - * @param fcmConnectTimeout FCM connect timeout. - */ - public void setFcmConnectTimeout(int fcmConnectTimeout) { - this.fcmConnectTimeout = fcmConnectTimeout; - } - - /** - * Get APNS connect timeout in milliseconds. - * @return APNS connect timeout. - */ - public int getApnsConnectTimeout() { - return apnsConnectTimeout; - } - - /** - * Set APNS connect timeout in milliseconds. - * @param apnsConnectTimeout APNS connect timeout. - */ - public void setApnsConnectTimeout(int apnsConnectTimeout) { - this.apnsConnectTimeout = apnsConnectTimeout; - } - - /** - * Get APNS concurrent connections. - * @return APNS concurrent connections. - */ - public int getConcurrentConnections() { - return concurrentConnections; - } - - /** - * Set APNS concurrent connections. - * @param concurrentConnections APNS concurrent connections. - */ - public void setConcurrentConnections(int concurrentConnections) { - this.concurrentConnections = concurrentConnections; - } - - /** - * Get idle ping interval. - * @return Idle ping interval. - */ - public long getIdlePingInterval() { - return idlePingInterval; - } - - /** - * Set idle ping interval. - * @param idlePingInterval Idle ping interval. - */ - public void setIdlePingInterval(long idlePingInterval) { - this.idlePingInterval = idlePingInterval; - } - - /** - * Get Java security CA certs file password. - * @return Java security CA certs file password. - */ - public String getJavaCaCertificatesPassword() { - return javaCaCertificatesPassword; - } - - /** - * Set Java security CA certs file password. - * @param javaCaCertificatesPassword Java security CA certs file password. - */ - public void setJavaCaCertificatesPassword(String javaCaCertificatesPassword) { - this.javaCaCertificatesPassword = javaCaCertificatesPassword; - } } From 3da5a63057521a163ec3d3bf21c1ffa3557e4455 Mon Sep 17 00:00:00 2001 From: Lubos Racansky Date: Mon, 22 Jan 2024 10:53:27 +0100 Subject: [PATCH 13/44] Fix #751: Add netty-resolver-dns-native-macos dependency --- powerauth-push-server/pom.xml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/powerauth-push-server/pom.xml b/powerauth-push-server/pom.xml index 7857cd693..482cfed33 100644 --- a/powerauth-push-server/pom.xml +++ b/powerauth-push-server/pom.xml @@ -144,6 +144,14 @@ jackson-datatype-jsr310 + + + io.netty + netty-resolver-dns-native-macos + runtime + osx-aarch_64 + + org.springframework.boot From 089db45b52ff073d567c9c4164d6589c54010b81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lubo=C5=A1=20Ra=C4=8Dansk=C3=BD?= Date: Fri, 26 Jan 2024 07:13:39 +0100 Subject: [PATCH 14/44] Fix #755: Change id to appId in Push-Server-Administration.md (#756) * Fix #755: Change id to appId in Push-Server-Administration.md --- docs/Push-Server-Administration.md | 40 +++++++++++++----------------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/docs/Push-Server-Administration.md b/docs/Push-Server-Administration.md index 2d576630d..1ad83c2df 100644 --- a/docs/Push-Server-Administration.md +++ b/docs/Push-Server-Administration.md @@ -35,42 +35,39 @@ curl --request GET \ ```sh curl --request POST \ --url http://localhost:8080/powerauth-push-server/admin/app/create \ - --header 'content-type: application/json' \ - --data '{ + --json '{ "requestObject": { "appId": 1 } }' ``` -Update the `appId` value with requested PowerAuth application ID. The value `id` from response object will be used for identification of the Push Server application. +Update the `appId` value with requested PowerAuth application ID. The value `appId` from response object will be used for identification of the Push Server application. ### Get Application Detail ```sh curl --request POST \ --url http://localhost:8080/powerauth-push-server/admin/app/detail \ - --header 'content-type: application/json' \ - --data '{ + --json '{ "requestObject": { - "id": 1, + "appId": 1, "includeIos": true, "includeAndroid": true } }' ``` -Update the `id` value with requested Push Server application ID. +Update the `appId` value with requested Push Server application ID. ### Update APNs Configuration ```sh curl --request POST \ --url http://localhost:8080/powerauth-push-server/admin/app/ios/update \ - --header 'content-type: application/json' \ - --data '{ + --json '{ "requestObject": { - "id": 1, + "appId": 1, "bundle": "com.wultra.myApp", "keyId": "keyId", "teamId": "teamId", @@ -79,7 +76,7 @@ curl --request POST \ }' ``` -Set the `id` value for Push Server application ID to want to update. +Set the `appId` value for Push Server application ID to want to update. Enter the base64-encoded value of APNs private key into `privateKeyBase64`. @@ -94,32 +91,30 @@ base64 -i -o ```sh curl --request DELETE \ --url http://localhost:8080/powerauth-push-server/admin/app/ios/remove \ - --cookie JSESSIONID=76D3CE8C7F92E1FC090A79886E43B235 \ - --data '{ + --json '{ "requestObject": { - "id": 1 + "appId": 1 } }' ``` -Set the `id` value for the Push Server application ID you want to update. +Set the `appId` value for the Push Server application ID you want to update. ### Update FCM Configuration ```sh curl --request POST \ --url http://localhost:8080/powerauth-push-server/admin/app/android/update \ - --header 'content-type: application/json' \ - --data '{ + --json '{ "requestObject": { - "id": 1, + "appId": 1, "projectId": "projectId", "privateKeyBase64": "a2V5" } }' ``` -Set the `id` value for Push Server application ID to want to update. +Set the `appId` value for Push Server application ID to want to update. Enter the base64-encoded value of FCM private key into `privateKeyBase64`. @@ -134,15 +129,14 @@ base64 -i -o ```sh curl --request DELETE \ --url http://localhost:8080/powerauth-push-server/admin/app/android/remove \ - --header 'content-type: application/json' \ - --data '{ + --json '{ "requestObject": { - "id": 1 + "appId": 1 } }' ``` -Set the `id` value for the Push Server application ID you want to update. +Set the `appId` value for the Push Server application ID you want to update. ## Administration using SQL Database From 3385d61cf58660d5d555ce3e027621217626f433 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lubo=C5=A1=20Ra=C4=8Dansk=C3=BD?= Date: Fri, 26 Jan 2024 11:13:20 +0100 Subject: [PATCH 15/44] Fix #753: Change platform into JPA enum (#754) * Fix #753: Change platform into JPA enum --- .../getlime/push/client/PushServerClient.java | 12 +-- .../model/enumeration/MobilePlatform.java | 22 ++---- .../CreateDeviceForActivationsRequest.java | 8 +- .../model/request/CreateDeviceRequest.java | 8 +- .../CreateDeviceRequestValidator.java | 8 +- .../controller/rest/PushDeviceController.java | 29 ++++--- .../push/repository/model/Platform.java | 34 +++++++++ .../repository/model/PlatformConverter.java | 54 +++++++++++++ .../model/PushDeviceRegistrationEntity.java | 21 +---- .../model/aggregate/UserDevice.java | 10 ++- .../service/PushMessageSenderService.java | 16 ++-- .../service/batch/UserDeviceItemWriter.java | 3 +- .../rest/PushDeviceControllerTest.java | 11 ++- .../PushDeviceRegistrationEntityTest.java | 76 +++++++++++++++++++ .../PushServerMultipleActivationsTests.java | 24 +++--- .../getlime/push/tests/PushServerTests.java | 28 +++---- .../PushDeviceRegistrationEntityTest.sql | 8 ++ 17 files changed, 271 insertions(+), 101 deletions(-) create mode 100644 powerauth-push-server/src/main/java/io/getlime/push/repository/model/Platform.java create mode 100644 powerauth-push-server/src/main/java/io/getlime/push/repository/model/PlatformConverter.java create mode 100644 powerauth-push-server/src/test/java/io/getlime/push/repository/model/PushDeviceRegistrationEntityTest.java create mode 100644 powerauth-push-server/src/test/resources/io/getlime/push/repository/model/PushDeviceRegistrationEntityTest.sql diff --git a/powerauth-push-client/src/main/java/io/getlime/push/client/PushServerClient.java b/powerauth-push-client/src/main/java/io/getlime/push/client/PushServerClient.java index 2b6897a68..34bac28a1 100644 --- a/powerauth-push-client/src/main/java/io/getlime/push/client/PushServerClient.java +++ b/powerauth-push-client/src/main/java/io/getlime/push/client/PushServerClient.java @@ -111,7 +111,7 @@ public boolean createDevice(String appId, String token, MobilePlatform platform, CreateDeviceRequest request = new CreateDeviceRequest(); request.setAppId(appId); request.setToken(token); - request.setPlatform(platform.value()); + request.setPlatform(platform); request.setActivationId(activationId); // Validate request on the client side. @@ -120,9 +120,9 @@ public boolean createDevice(String appId, String token, MobilePlatform platform, throw new PushServerClientException(error); } - logger.info("Calling create device service, appId: {}, token: {}, platform: {} - start", appId, maskToken(token), platform.value()); + logger.info("Calling create device service, appId: {}, token: {}, platform: {} - start", appId, maskToken(token), platform); Response response = postObjectImpl("/push/device/create", new ObjectRequest<>(request)); - logger.info("Calling create device service, appId: {}, token: {}, platform: {} - finish", appId, maskToken(token), platform.value()); + logger.info("Calling create device service, appId: {}, token: {}, platform: {} - finish", appId, maskToken(token), platform); return response.getStatus().equals(Response.Status.OK); } @@ -141,7 +141,7 @@ public boolean createDeviceForActivations(String appId, String token, MobilePlat CreateDeviceForActivationsRequest request = new CreateDeviceForActivationsRequest(); request.setAppId(appId); request.setToken(token); - request.setPlatform(platform.value()); + request.setPlatform(platform); request.getActivationIds().addAll(activationIds); // Validate request on the client side. @@ -150,9 +150,9 @@ public boolean createDeviceForActivations(String appId, String token, MobilePlat throw new PushServerClientException(error); } - logger.info("Calling create device service, appId: {}, token: {}, platform: {} - start", appId, maskToken(token), platform.value()); + logger.info("Calling create device service, appId: {}, token: {}, platform: {} - start", appId, maskToken(token), platform); Response response = postObjectImpl("/push/device/create/multi", new ObjectRequest<>(request)); - logger.info("Calling create device service, appId: {}, token: {}, platform: {} - finish", appId, maskToken(token), platform.value()); + logger.info("Calling create device service, appId: {}, token: {}, platform: {} - finish", appId, maskToken(token), platform); return response.getStatus().equals(Response.Status.OK); } diff --git a/powerauth-push-model/src/main/java/io/getlime/push/model/enumeration/MobilePlatform.java b/powerauth-push-model/src/main/java/io/getlime/push/model/enumeration/MobilePlatform.java index 21ea4f261..9891520a7 100644 --- a/powerauth-push-model/src/main/java/io/getlime/push/model/enumeration/MobilePlatform.java +++ b/powerauth-push-model/src/main/java/io/getlime/push/model/enumeration/MobilePlatform.java @@ -16,6 +16,8 @@ package io.getlime.push.model.enumeration; +import com.fasterxml.jackson.annotation.JsonProperty; + /** * Enum representing mobile platforms. * @@ -26,25 +28,13 @@ public enum MobilePlatform { /** * iOS Platform. */ - iOS, + @JsonProperty("ios") + IOS, /** * Android Platform. */ - Android; - - /** - * Converter convenience method for obtaining String from enum. - * @return String representation of the enum. - */ - public String value() { - if (this.equals(MobilePlatform.iOS)) { - return "ios"; - } else if (this.equals(MobilePlatform.Android)) { - return "android"; - } else { - return "android"; // guess android by default - } - } + @JsonProperty("android") + ANDROID } diff --git a/powerauth-push-model/src/main/java/io/getlime/push/model/request/CreateDeviceForActivationsRequest.java b/powerauth-push-model/src/main/java/io/getlime/push/model/request/CreateDeviceForActivationsRequest.java index 1cbd96bca..38661bb21 100644 --- a/powerauth-push-model/src/main/java/io/getlime/push/model/request/CreateDeviceForActivationsRequest.java +++ b/powerauth-push-model/src/main/java/io/getlime/push/model/request/CreateDeviceForActivationsRequest.java @@ -15,6 +15,8 @@ */ package io.getlime.push.model.request; +import io.getlime.push.model.enumeration.MobilePlatform; + import java.util.ArrayList; import java.util.List; @@ -27,7 +29,7 @@ public class CreateDeviceForActivationsRequest { private String appId; private String token; - private String platform; + private MobilePlatform platform; private final List activationIds = new ArrayList<>(); /** @@ -66,7 +68,7 @@ public void setToken(String token) { * Get the platform name, either "ios" or "android". * @return Platform name, "ios" or "android". */ - public String getPlatform() { + public MobilePlatform getPlatform() { return platform; } @@ -74,7 +76,7 @@ public String getPlatform() { * Set the platform name. * @param platform Platform name. */ - public void setPlatform(String platform) { + public void setPlatform(MobilePlatform platform) { this.platform = platform; } diff --git a/powerauth-push-model/src/main/java/io/getlime/push/model/request/CreateDeviceRequest.java b/powerauth-push-model/src/main/java/io/getlime/push/model/request/CreateDeviceRequest.java index 343a2e241..2326abaaa 100644 --- a/powerauth-push-model/src/main/java/io/getlime/push/model/request/CreateDeviceRequest.java +++ b/powerauth-push-model/src/main/java/io/getlime/push/model/request/CreateDeviceRequest.java @@ -15,6 +15,8 @@ */ package io.getlime.push.model.request; +import io.getlime.push.model.enumeration.MobilePlatform; + /** * Request object used for device registration. * @@ -24,7 +26,7 @@ public class CreateDeviceRequest { private String appId; private String token; - private String platform; + private MobilePlatform platform; private String activationId; /** @@ -63,7 +65,7 @@ public void setToken(String token) { * Get the platform name, either "ios" or "android". * @return Platform name, "ios" or "android". */ - public String getPlatform() { + public MobilePlatform getPlatform() { return platform; } @@ -71,7 +73,7 @@ public String getPlatform() { * Set the platform name. * @param platform Platform name. */ - public void setPlatform(String platform) { + public void setPlatform(MobilePlatform platform) { this.platform = platform; } diff --git a/powerauth-push-model/src/main/java/io/getlime/push/model/validator/CreateDeviceRequestValidator.java b/powerauth-push-model/src/main/java/io/getlime/push/model/validator/CreateDeviceRequestValidator.java index 2c3d1e902..5849764a2 100644 --- a/powerauth-push-model/src/main/java/io/getlime/push/model/validator/CreateDeviceRequestValidator.java +++ b/powerauth-push-model/src/main/java/io/getlime/push/model/validator/CreateDeviceRequestValidator.java @@ -41,8 +41,8 @@ public static String validate(CreateDeviceRequest request) { if (request.getActivationId() == null) { return "Activation ID must not be null."; } - if (request.getPlatform() == null || request.getPlatform().isEmpty()) { - return "Platform must not be null or empty."; + if (request.getPlatform() == null) { + return "Platform must not be null."; } if (request.getToken() == null || request.getToken().isEmpty()) { return "Push token must not be null or empty."; @@ -68,8 +68,8 @@ public static String validate(CreateDeviceForActivationsRequest request) { if (request.getActivationIds().isEmpty()) { return "Activation ID list must not empty."; } - if (request.getPlatform() == null || request.getPlatform().isEmpty()) { - return "Platform must not be null or empty."; + if (request.getPlatform() == null) { + return "Platform must not be null."; } if (request.getToken() == null || request.getToken().isEmpty()) { return "Push token must not be null or empty."; diff --git a/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/PushDeviceController.java b/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/PushDeviceController.java index 492e2e2e1..de8424f52 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/PushDeviceController.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/PushDeviceController.java @@ -23,6 +23,7 @@ import io.getlime.core.rest.model.base.response.Response; import io.getlime.push.configuration.PushServiceConfiguration; import io.getlime.push.errorhandling.exceptions.PushServerException; +import io.getlime.push.model.enumeration.MobilePlatform; import io.getlime.push.model.request.CreateDeviceForActivationsRequest; import io.getlime.push.model.request.CreateDeviceRequest; import io.getlime.push.model.request.DeleteDeviceRequest; @@ -33,6 +34,7 @@ import io.getlime.push.repository.AppCredentialsRepository; import io.getlime.push.repository.PushDeviceRepository; import io.getlime.push.repository.model.AppCredentialsEntity; +import io.getlime.push.repository.model.Platform; import io.getlime.push.repository.model.PushDeviceRegistrationEntity; import io.swagger.v3.oas.annotations.Operation; import org.slf4j.Logger; @@ -42,8 +44,10 @@ import org.springframework.retry.support.RetryTemplate; import org.springframework.web.bind.annotation.*; -import java.time.Duration; -import java.util.*; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; /** @@ -119,32 +123,32 @@ public Response createDevice(@RequestBody ObjectRequest req private Void createOrUpdateDevice(final CreateDeviceRequest requestObject, final AppCredentialsEntity appCredentials) throws PushServerException { final String appId = requestObject.getAppId(); final String pushToken = requestObject.getToken(); - final String platform = requestObject.getPlatform(); + final MobilePlatform platform = requestObject.getPlatform(); final String activationId = requestObject.getActivationId(); final List devices = lookupDeviceRegistrations(appId, activationId, pushToken); final PushDeviceRegistrationEntity device; if (devices.isEmpty()) { // The device registration is new, create a new entity. - logger.info("Creating new device registration: app ID: {}, activation ID: {}, platform: {}", requestObject.getAppId(), requestObject.getActivationId(), requestObject.getPlatform()); + logger.info("Creating new device registration: app ID: {}, activation ID: {}, platform: {}", requestObject.getAppId(), requestObject.getActivationId(), platform); device = initDeviceRegistrationEntity(appCredentials, pushToken); } else if (devices.size() == 1) { // An existing row was found by one of the lookup methods, update this row. This means that either: // 1. A row with same activation ID and push token is updated, in this case only the last registration timestamp changes. // 2. A row with same activation ID but different push token is updated. A new push token has been issued by Google or Apple for an activation. // 3. A row with same push token but different activation ID is updated. The user removed an activation and created a new one, the push token remains the same. - logger.info("Updating existing device registration: app ID: {}, activation ID: {}, platform: {}", requestObject.getAppId(), requestObject.getActivationId(), requestObject.getPlatform()); + logger.info("Updating existing device registration: app ID: {}, activation ID: {}, platform: {}", requestObject.getAppId(), requestObject.getActivationId(), platform); device = devices.get(0); updateDeviceRegistrationEntity(device, appCredentials, pushToken); } else { // Multiple existing rows have been found. This can only occur during lookup by push token. // Push token can be associated with multiple activations only when associated activations are enabled. // Push device registration must be done using /push/device/create/multi endpoint in this case. - logger.info("Multiple device registrations found: app ID: {}, activation ID: {}, platform: {}", requestObject.getAppId(), requestObject.getActivationId(), requestObject.getPlatform()); + logger.info("Multiple device registrations found: app ID: {}, activation ID: {}, platform: {}", requestObject.getAppId(), requestObject.getActivationId(), platform); throw new PushServerException("Multiple device registrations found for push token. Use the /push/device/create/multi endpoint for this scenario."); } device.setTimestampLastRegistered(new Date()); - device.setPlatform(platform); + device.setPlatform(convert(platform)); updateActivationForDevice(device, activationId); pushDeviceRepository.save(device); return null; @@ -180,7 +184,7 @@ public Response createDeviceMultipleActivations(@RequestBody ObjectRequest activationIds = requestedObject.getActivationIds(); // Initialize loop variables. @@ -214,7 +218,7 @@ public Response createDeviceMultipleActivations(@RequestBody ObjectRequest Platform.IOS; + case ANDROID -> Platform.ANDROID; + }; + } + /** * Initialize a new device registration entity for given app ID and push token. * @param app AppCredentialsEntity instance. diff --git a/powerauth-push-server/src/main/java/io/getlime/push/repository/model/Platform.java b/powerauth-push-server/src/main/java/io/getlime/push/repository/model/Platform.java new file mode 100644 index 000000000..8a8cd3778 --- /dev/null +++ b/powerauth-push-server/src/main/java/io/getlime/push/repository/model/Platform.java @@ -0,0 +1,34 @@ +/* + * Copyright 2024 Wultra s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.getlime.push.repository.model; + +/** + * Platform enum for {@link PushDeviceRegistrationEntity#getPlatform()}. + * + * @author Lubos Racansky, lubos.racansky@wultra.com + */ +public enum Platform { + + /** + * iOS Platform. + */ + IOS, + + /** + * Android Platform. + */ + ANDROID +} diff --git a/powerauth-push-server/src/main/java/io/getlime/push/repository/model/PlatformConverter.java b/powerauth-push-server/src/main/java/io/getlime/push/repository/model/PlatformConverter.java new file mode 100644 index 000000000..f4c8d31b6 --- /dev/null +++ b/powerauth-push-server/src/main/java/io/getlime/push/repository/model/PlatformConverter.java @@ -0,0 +1,54 @@ +/* + * Copyright 2024 Wultra s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.getlime.push.repository.model; + +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Converter; + +/** + * {@link Platform} converter for {@link PushDeviceRegistrationEntity#getPlatform()}. + * + * @implNote Using {@code @Enumerated(EnumType.STRING)} is not powerful enough, lower-cases need to be kept because of backward compatibility. + * @author Lubos Racansky, lubos.racansky@wultra.com + */ +@Converter +class PlatformConverter implements AttributeConverter { + + @Override + public String convertToDatabaseColumn(final Platform attribute) { + if (attribute == null) { + return null; + } + + return switch (attribute) { + case IOS -> "ios"; + case ANDROID -> "android"; + }; + } + + @Override + public Platform convertToEntityAttribute(final String dbData) { + if (dbData == null) { + return null; + } + + return switch (dbData) { + case "ios" -> Platform.IOS; + case "android" -> Platform.ANDROID; + default -> throw new IllegalArgumentException("No mapping for platform: " + dbData); + }; + } +} diff --git a/powerauth-push-server/src/main/java/io/getlime/push/repository/model/PushDeviceRegistrationEntity.java b/powerauth-push-server/src/main/java/io/getlime/push/repository/model/PushDeviceRegistrationEntity.java index 066a1bb8d..51ae55a37 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/repository/model/PushDeviceRegistrationEntity.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/repository/model/PushDeviceRegistrationEntity.java @@ -36,20 +36,6 @@ public class PushDeviceRegistrationEntity implements Serializable { @Serial private static final long serialVersionUID = 1530682530822178192L; - /** - * Platform of the registered device. - */ - public static class Platform { - /** - * iOS Platform - */ - public static final String iOS = "ios"; - /** - * Android Platform - */ - public static final String Android = "android"; - } - /** * Push device ID. */ @@ -82,7 +68,8 @@ public static class Platform { * Platform. */ @Column(name = "platform", nullable = false, updatable = false) - private String platform; + @Convert(converter = PlatformConverter.class) + private Platform platform; /** * Push token. @@ -170,7 +157,7 @@ public void setAppCredentials(AppCredentialsEntity appCredentials) { * Get platform. * @return Platform. */ - public String getPlatform() { + public Platform getPlatform() { return platform; } @@ -178,7 +165,7 @@ public String getPlatform() { * Set platform. * @param platform Platform. */ - public void setPlatform(String platform) { + public void setPlatform(Platform platform) { this.platform = platform; } diff --git a/powerauth-push-server/src/main/java/io/getlime/push/repository/model/aggregate/UserDevice.java b/powerauth-push-server/src/main/java/io/getlime/push/repository/model/aggregate/UserDevice.java index 624f4b53e..6b8eed8f0 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/repository/model/aggregate/UserDevice.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/repository/model/aggregate/UserDevice.java @@ -15,6 +15,8 @@ */ package io.getlime.push.repository.model.aggregate; +import io.getlime.push.repository.model.Platform; + /** * Object used in sending campaigns, as an aggregate object for storing information about * user, device (including platform), app and campaign. @@ -32,7 +34,7 @@ public class UserDevice { private String activationId; private Long campaignId; private Long appId; - private String platform; + private Platform platform; private String token; /** @@ -45,7 +47,7 @@ public class UserDevice { * @param platform Platform. * @param token Push token. */ - public UserDevice(String userId, Long deviceId, String activationId, Long campaignId, Long appId, String platform, String token) { + public UserDevice(String userId, Long deviceId, String activationId, Long campaignId, Long appId, Platform platform, String token) { this.userId = userId; this.deviceId = deviceId; this.activationId = activationId; @@ -139,7 +141,7 @@ public void setAppId(Long appId) { * Get platform. * @return Platform. */ - public String getPlatform() { + public Platform getPlatform() { return platform; } @@ -147,7 +149,7 @@ public String getPlatform() { * Set platform. * @param platform Platform. */ - public void setPlatform(String platform) { + public void setPlatform(Platform platform) { this.platform = platform; } diff --git a/powerauth-push-server/src/main/java/io/getlime/push/service/PushMessageSenderService.java b/powerauth-push-server/src/main/java/io/getlime/push/service/PushMessageSenderService.java index e953039cb..a7dae9ba6 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/service/PushMessageSenderService.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/service/PushMessageSenderService.java @@ -27,6 +27,7 @@ import io.getlime.push.repository.PushDeviceRepository; import io.getlime.push.repository.dao.PushMessageDAO; import io.getlime.push.repository.model.AppCredentialsEntity; +import io.getlime.push.repository.model.Platform; import io.getlime.push.repository.model.PushDeviceRegistrationEntity; import io.getlime.push.repository.model.PushMessageEntity; import io.getlime.push.service.batch.storage.AppCredentialStorageMap; @@ -122,9 +123,8 @@ public BasePushMessageSendResult sendPushMessage(final String appId, final Mode // Register phaser for synchronization registerPhaserForMode(phaser, mode); - // Decide if the device is iOS or Android and send message accordingly - final String platform = device.getPlatform(); - if (platform.equals(PushDeviceRegistrationEntity.Platform.iOS)) { + final Platform platform = device.getPlatform(); + if (platform == Platform.IOS) { if (pushClient.getApnsClient() == null) { logger.error("Push message cannot be sent to APNS because APNS is not configured in push server."); arriveAndDeregisterPhaserForMode(phaser, mode); @@ -132,7 +132,7 @@ public BasePushMessageSendResult sendPushMessage(final String appId, final Mode } final PushMessageSendResult.PlatformResult platformResult = sendResult.getIos(); pushSendingWorker.sendMessageToIos(pushClient.getApnsClient(), pushMessage.getBody(), pushMessage.getAttributes(), pushMessage.getPriority(), device.getPushToken(), pushClient.getAppCredentials().getIosBundle(), createPushSendingCallback(mode, device, platformResult, pushMessageObject, phaser)); - } else if (platform.equals(PushDeviceRegistrationEntity.Platform.Android)) { + } else if (platform == Platform.ANDROID) { if (pushClient.getFcmClient() == null) { logger.error("Push message cannot be sent to FCM because FCM is not configured in push server."); arriveAndDeregisterPhaserForMode(phaser, mode); @@ -194,7 +194,7 @@ private PushSendingCallback createPushSendingCallback(final Mode mode, final Pus * @throws PushServerException In case any issue happens while sending the push message. Detailed information about * the error can be found in exception message. */ - public void sendCampaignMessage(String appId, String platform, String token, PushMessageBody pushMessageBody, String userId, Long deviceId, String activationId) throws PushServerException { + public void sendCampaignMessage(String appId, Platform platform, String token, PushMessageBody pushMessageBody, String userId, Long deviceId, String activationId) throws PushServerException { sendCampaignMessage(appId, platform, token, pushMessageBody, null, Priority.HIGH, userId, deviceId, activationId); } @@ -214,16 +214,16 @@ public void sendCampaignMessage(String appId, String platform, String token, Pus * @throws PushServerException In case any issue happens while sending the push message. Detailed information about * the error can be found in exception message. */ - public void sendCampaignMessage(final String appId, String platform, final String token, PushMessageBody pushMessageBody, PushMessageAttributes attributes, Priority priority, String userId, Long deviceId, String activationId) throws PushServerException { + public void sendCampaignMessage(final String appId, Platform platform, final String token, PushMessageBody pushMessageBody, PushMessageAttributes attributes, Priority priority, String userId, Long deviceId, String activationId) throws PushServerException { final AppRelatedPushClient pushClient = prepareClients(appId); final PushMessageEntity pushMessageObject = pushMessageDAO.storePushMessageObject(pushMessageBody, attributes, userId, activationId, deviceId); switch (platform) { - case PushDeviceRegistrationEntity.Platform.iOS -> + case IOS -> pushSendingWorker.sendMessageToIos(pushClient.getApnsClient(), pushMessageBody, attributes, priority, token, pushClient.getAppCredentials().getIosBundle(), createPushSendingCallback(token, pushMessageObject, pushClient)); - case PushDeviceRegistrationEntity.Platform.Android -> + case ANDROID -> pushSendingWorker.sendMessageToAndroid(pushClient.getFcmClient(), pushMessageBody, attributes, priority, token, createPushSendingCallback(token, pushMessageObject, pushClient)); } } diff --git a/powerauth-push-server/src/main/java/io/getlime/push/service/batch/UserDeviceItemWriter.java b/powerauth-push-server/src/main/java/io/getlime/push/service/batch/UserDeviceItemWriter.java index 9ae7821fe..f890cc063 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/service/batch/UserDeviceItemWriter.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/service/batch/UserDeviceItemWriter.java @@ -19,6 +19,7 @@ import io.getlime.push.errorhandling.exceptions.PushServerException; import io.getlime.push.model.entity.PushMessageBody; import io.getlime.push.repository.PushCampaignRepository; +import io.getlime.push.repository.model.Platform; import io.getlime.push.repository.model.PushCampaignEntity; import io.getlime.push.repository.model.aggregate.UserDevice; import io.getlime.push.repository.serialization.JsonSerialization; @@ -68,7 +69,7 @@ public UserDeviceItemWriter(PushMessageSenderService pushMessageSenderService, @Override public void write(Chunk list) throws Exception { for (UserDevice device: list) { - final String platform = device.getPlatform(); + final Platform platform = device.getPlatform(); final String token = device.getToken(); final String userId = device.getUserId(); final Long campaignId = device.getCampaignId(); diff --git a/powerauth-push-server/src/test/java/io/getlime/push/controller/rest/PushDeviceControllerTest.java b/powerauth-push-server/src/test/java/io/getlime/push/controller/rest/PushDeviceControllerTest.java index 4abd790ea..0d3c62927 100644 --- a/powerauth-push-server/src/test/java/io/getlime/push/controller/rest/PushDeviceControllerTest.java +++ b/powerauth-push-server/src/test/java/io/getlime/push/controller/rest/PushDeviceControllerTest.java @@ -23,6 +23,7 @@ import io.getlime.core.rest.model.base.request.ObjectRequest; import io.getlime.core.rest.model.base.response.Response; import io.getlime.push.errorhandling.exceptions.PushServerException; +import io.getlime.push.model.enumeration.MobilePlatform; import io.getlime.push.model.request.CreateDeviceRequest; import io.getlime.push.repository.AppCredentialsRepository; import io.getlime.push.repository.PushDeviceRepository; @@ -37,7 +38,9 @@ import java.util.ArrayList; import java.util.List; -import java.util.concurrent.*; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.when; @@ -75,7 +78,7 @@ void testCreateDevice() throws Exception { request.setAppId("my_app"); request.setActivationId("a1"); request.setToken("t1"); - request.setPlatform("android"); + request.setPlatform(MobilePlatform.ANDROID); tested.createDevice(new ObjectRequest<>(request)); @@ -95,7 +98,7 @@ void testCreateDevice_missingApplication() throws Exception { request.setAppId("non_existent"); request.setActivationId("a2"); request.setToken("t0"); - request.setPlatform("android"); + request.setPlatform(MobilePlatform.ANDROID); assertThrows(PushServerException.class, () -> tested.createDevice(new ObjectRequest<>(request))); @@ -118,7 +121,7 @@ void testCreateDevice_parallel() throws Exception { request.setAppId("my_app"); request.setActivationId("a3"); request.setToken("token"); - request.setPlatform("ios"); + request.setPlatform(MobilePlatform.IOS); return tested.createDevice(new ObjectRequest<>(request)); }); } diff --git a/powerauth-push-server/src/test/java/io/getlime/push/repository/model/PushDeviceRegistrationEntityTest.java b/powerauth-push-server/src/test/java/io/getlime/push/repository/model/PushDeviceRegistrationEntityTest.java new file mode 100644 index 000000000..9013e61b8 --- /dev/null +++ b/powerauth-push-server/src/test/java/io/getlime/push/repository/model/PushDeviceRegistrationEntityTest.java @@ -0,0 +1,76 @@ +/* + * Copyright 2024 Wultra s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.getlime.push.repository.model; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceException; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.jdbc.Sql; + +import java.util.Date; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * Test {@link PushDeviceRegistrationEntity}. + * + * @author Lubos Racansky, lubos.racansky@wultra.com + */ +@DataJpaTest +@ActiveProfiles("test") +@Sql +class PushDeviceRegistrationEntityTest { + + @Autowired + private EntityManager entityManager; + + @Test + void testConvertPlatform_load() { + final var tested = entityManager.find(PushDeviceRegistrationEntity.class, 1L); + + assertEquals(Platform.IOS, tested.getPlatform()); + } + + @Test + void testConvertPlatform_save() { + final var tested = new PushDeviceRegistrationEntity(); + tested.setAppCredentials(entityManager.getReference(AppCredentialsEntity.class, 1L)); + tested.setPlatform(Platform.ANDROID); + tested.setTimestampLastRegistered(new Date()); + tested.setPushToken("token1"); + + final Long id = entityManager.merge(tested).getId(); + + final Object result = entityManager.createNativeQuery("select platform from push_device_registration where id=:id") + .setParameter("id", id) + .getSingleResult(); + + assertEquals("android", result); + } + + @Test + void testConvertPlatform_invalidMapping() { + final Exception result = assertThrows(PersistenceException.class, + () -> entityManager.find(PushDeviceRegistrationEntity.class, 2L)); + + assertEquals("Error attempting to apply AttributeConverter", result.getMessage()); + assertEquals("No mapping for platform: xxx", result.getCause().getMessage()); + } +} \ No newline at end of file diff --git a/powerauth-push-server/src/test/java/io/getlime/push/tests/PushServerMultipleActivationsTests.java b/powerauth-push-server/src/test/java/io/getlime/push/tests/PushServerMultipleActivationsTests.java index 4128412d5..f0b0a1358 100644 --- a/powerauth-push-server/src/test/java/io/getlime/push/tests/PushServerMultipleActivationsTests.java +++ b/powerauth-push-server/src/test/java/io/getlime/push/tests/PushServerMultipleActivationsTests.java @@ -79,7 +79,7 @@ public void createDeviceWithMultipleActivationsTest() throws Exception { List activationIds = new ArrayList<>(); activationIds.add(powerAuthTestClient.getActivationId()); activationIds.add(powerAuthTestClient.getActivationId2()); - boolean result = pushServerClient.createDeviceForActivations(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.iOS, activationIds); + boolean result = pushServerClient.createDeviceForActivations(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.IOS, activationIds); assertTrue(result); pushDeviceRepository.deleteAll(pushDeviceRepository.findByAppCredentialsAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN)); } @@ -87,7 +87,7 @@ public void createDeviceWithMultipleActivationsTest() throws Exception { @Test public void createDeviceWithMultipleActivationsInvalidTest() { assertThrows(PushServerClientException.class, () -> - pushServerClient.createDeviceForActivations(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.iOS, Collections.emptyList())); + pushServerClient.createDeviceForActivations(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.IOS, Collections.emptyList())); } @Test @@ -96,13 +96,13 @@ public void createDeviceSameActivationsSamePushTokenUpdatesTest() throws PushSer activationIds.add(powerAuthTestClient.getActivationId()); activationIds.add(powerAuthTestClient.getActivationId2()); // This test tests refresh of a device registration - boolean actual = pushServerClient.createDeviceForActivations(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.iOS, activationIds); + boolean actual = pushServerClient.createDeviceForActivations(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.IOS, activationIds); assertTrue(actual); List devices = pushDeviceRepository.findByAppCredentialsAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN); assertEquals(2, devices.size()); Set rowIds = new HashSet<>(); devices.forEach(device -> rowIds.add(device.getId())); - boolean actual2 = pushServerClient.createDeviceForActivations(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.iOS, activationIds); + boolean actual2 = pushServerClient.createDeviceForActivations(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.IOS, activationIds); assertTrue(actual2); List devices2 = pushDeviceRepository.findByAppCredentialsAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN); assertEquals(2, devices2.size()); @@ -118,13 +118,13 @@ public void createDeviceSameActivationsDifferentPushTokenUpdatesTest() throws Pu activationIds.add(powerAuthTestClient.getActivationId()); activationIds.add(powerAuthTestClient.getActivationId2()); // This test tests refresh of a device registration - boolean actual = pushServerClient.createDeviceForActivations(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.iOS, activationIds); + boolean actual = pushServerClient.createDeviceForActivations(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.IOS, activationIds); assertTrue(actual); List devices = pushDeviceRepository.findByAppCredentialsAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN); assertEquals(2, devices.size()); Set rowIds = new HashSet<>(); devices.forEach(device -> rowIds.add(device.getId())); - boolean actual2 = pushServerClient.createDeviceForActivations(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN_2, MobilePlatform.iOS, activationIds); + boolean actual2 = pushServerClient.createDeviceForActivations(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN_2, MobilePlatform.IOS, activationIds); assertTrue(actual2); List devices2 = pushDeviceRepository.findByAppCredentialsAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN_2); assertEquals(2, devices2.size()); @@ -140,7 +140,7 @@ public void createDeviceDifferentTwoActivationsSamePushTokenUpdatesTest() throws activationIds.add(powerAuthTestClient.getActivationId()); activationIds.add(powerAuthTestClient.getActivationId2()); // This test tests refresh of a device registration - boolean actual = pushServerClient.createDeviceForActivations(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.iOS, activationIds); + boolean actual = pushServerClient.createDeviceForActivations(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.IOS, activationIds); assertTrue(actual); List devices = pushDeviceRepository.findByAppCredentialsAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN); assertEquals(2, devices.size()); @@ -149,7 +149,7 @@ public void createDeviceDifferentTwoActivationsSamePushTokenUpdatesTest() throws List activationIds2 = new ArrayList<>(); activationIds2.add(powerAuthTestClient.getActivationId3()); activationIds2.add(powerAuthTestClient.getActivationId4()); - boolean actual2 = pushServerClient.createDeviceForActivations(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.iOS, activationIds2); + boolean actual2 = pushServerClient.createDeviceForActivations(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.IOS, activationIds2); assertTrue(actual2); List devices2 = pushDeviceRepository.findByAppCredentialsAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN); assertEquals(2, devices2.size()); @@ -166,7 +166,7 @@ public void createDeviceDifferentActivationSamePushTokenUpdatesTest() throws Pus activationIds.add(powerAuthTestClient.getActivationId()); activationIds.add(powerAuthTestClient.getActivationId2()); // This test tests refresh of a device registration - boolean actual = pushServerClient.createDeviceForActivations(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.iOS, activationIds); + boolean actual = pushServerClient.createDeviceForActivations(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.IOS, activationIds); assertTrue(actual); List devices = pushDeviceRepository.findByAppCredentialsAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN); assertEquals(2, devices.size()); @@ -175,7 +175,7 @@ public void createDeviceDifferentActivationSamePushTokenUpdatesTest() throws Pus List activationIds2 = new ArrayList<>(); activationIds2.add(powerAuthTestClient.getActivationId()); activationIds2.add(powerAuthTestClient.getActivationId4()); - boolean actual2 = pushServerClient.createDeviceForActivations(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.iOS, activationIds2); + boolean actual2 = pushServerClient.createDeviceForActivations(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.IOS, activationIds2); assertTrue(actual2); List devices2 = pushDeviceRepository.findByAppCredentialsAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN); assertEquals(2, devices2.size()); @@ -193,11 +193,11 @@ public void createDeviceMixedRegistrationEndpointsTest() { activationIds.add(powerAuthTestClient.getActivationId()); activationIds.add(powerAuthTestClient.getActivationId2()); // This test tests refresh of a device registration - boolean actual = pushServerClient.createDeviceForActivations(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.iOS, activationIds); + boolean actual = pushServerClient.createDeviceForActivations(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.IOS, activationIds); assertTrue(actual); List devices = pushDeviceRepository.findByAppCredentialsAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN); assertEquals(2, devices.size()); - pushServerClient.createDevice(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.iOS, powerAuthTestClient.getActivationId3()); + pushServerClient.createDevice(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.IOS, powerAuthTestClient.getActivationId3()); }); } diff --git a/powerauth-push-server/src/test/java/io/getlime/push/tests/PushServerTests.java b/powerauth-push-server/src/test/java/io/getlime/push/tests/PushServerTests.java index 58652e3c5..87f23636d 100644 --- a/powerauth-push-server/src/test/java/io/getlime/push/tests/PushServerTests.java +++ b/powerauth-push-server/src/test/java/io/getlime/push/tests/PushServerTests.java @@ -112,12 +112,12 @@ void getServiceStatusTest() throws Exception { @Test void createDeviceWithoutActivationIDTest() { assertThrows(PushServerClientException.class, () -> - pushServerClient.createDevice(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.iOS)); + pushServerClient.createDevice(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.IOS)); } @Test void createDeviceWithActivationIDTest() throws Exception { - boolean result = pushServerClient.createDevice(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.iOS, powerAuthTestClient.getActivationId()); + boolean result = pushServerClient.createDevice(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.IOS, powerAuthTestClient.getActivationId()); assertTrue(result); List devices = pushDeviceRepository.findByAppCredentialsAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN); assertEquals(1, devices.size()); @@ -126,7 +126,7 @@ void createDeviceWithActivationIDTest() throws Exception { @Test void deleteDeviceTest() throws Exception { - boolean result = pushServerClient.createDevice(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.iOS, powerAuthTestClient.getActivationId()); + boolean result = pushServerClient.createDevice(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.IOS, powerAuthTestClient.getActivationId()); assertTrue(result); List devices = pushDeviceRepository.findByAppCredentialsAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN); assertEquals(1, devices.size()); @@ -143,7 +143,7 @@ void testFcmUrlConfiguredForTests() { @Test void updateDeviceStatusTest() throws Exception { - boolean result = pushServerClient.createDevice(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.iOS, powerAuthTestClient.getActivationId()); + boolean result = pushServerClient.createDevice(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.IOS, powerAuthTestClient.getActivationId()); assertTrue(result); List devices = pushDeviceRepository.findByAppCredentialsAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN); assertEquals(1, devices.size()); @@ -166,7 +166,7 @@ void updateDeviceStatusTest() throws Exception { @Test @SuppressWarnings("unchecked") //known parameters of HashMap void sendPushMessageTest() throws Exception { - boolean result = pushServerClient.createDevice(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.Android, powerAuthTestClient.getActivationId()); + boolean result = pushServerClient.createDevice(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.ANDROID, powerAuthTestClient.getActivationId()); assertTrue(result); PushMessage pushMessage = new PushMessage(); PushMessageAttributes attributes = new PushMessageAttributes(); @@ -196,7 +196,7 @@ void sendPushMessageTest() throws Exception { @Test @SuppressWarnings("unchecked") //known parameters of HashMap void sendPushMessageBatchTest() throws Exception { - boolean result = pushServerClient.createDevice(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.Android, powerAuthTestClient.getActivationId()); + boolean result = pushServerClient.createDevice(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.ANDROID, powerAuthTestClient.getActivationId()); assertTrue(result); List batch = new ArrayList<>(); PushMessage pushMessage = new PushMessage(); @@ -226,7 +226,7 @@ void sendPushMessageBatchTest() throws Exception { @Test void createCampaignTest() throws Exception { - boolean result = pushServerClient.createDevice(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.Android, powerAuthTestClient.getActivationId()); + boolean result = pushServerClient.createDevice(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.ANDROID, powerAuthTestClient.getActivationId()); assertTrue(result); final ObjectResponse actual = createCampaign(); assertEquals("OK", actual.getStatus()); @@ -306,19 +306,19 @@ void createDeviceWithMultipleActivationsTest() { List activationIds = new ArrayList<>(); activationIds.add(powerAuthTestClient.getActivationId()); activationIds.add(powerAuthTestClient.getActivationId2()); - pushServerClient.createDeviceForActivations(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.iOS, activationIds); + pushServerClient.createDeviceForActivations(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.IOS, activationIds); }); } @Test void createDeviceSameActivationSamePushTokenUpdatesTest() throws PushServerClientException { // This test tests refresh of a device registration - boolean actual = pushServerClient.createDevice(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.iOS, powerAuthTestClient.getActivationId()); + boolean actual = pushServerClient.createDevice(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.IOS, powerAuthTestClient.getActivationId()); assertTrue(actual); List devices = pushDeviceRepository.findByAppCredentialsAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN); assertEquals(1, devices.size()); Long rowId = devices.get(0).getId(); - boolean actual2 = pushServerClient.createDevice(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.iOS, powerAuthTestClient.getActivationId()); + boolean actual2 = pushServerClient.createDevice(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.IOS, powerAuthTestClient.getActivationId()); assertTrue(actual2); List devices2 = pushDeviceRepository.findByAppCredentialsAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN); assertEquals(1, devices2.size()); @@ -329,11 +329,11 @@ void createDeviceSameActivationSamePushTokenUpdatesTest() throws PushServerClien @Test void createDeviceSameActivationDifferentPushTokenTest() throws PushServerClientException { // This test tests change of Push Token - new token has been issued by Google or Apple and the device registers for same activation - boolean actual = pushServerClient.createDevice(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.iOS, powerAuthTestClient.getActivationId()); + boolean actual = pushServerClient.createDevice(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.IOS, powerAuthTestClient.getActivationId()); assertTrue(actual); List devices = pushDeviceRepository.findByAppCredentialsAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN); Long rowId = devices.get(0).getId(); - boolean actual2 = pushServerClient.createDevice(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN_2, MobilePlatform.iOS, powerAuthTestClient.getActivationId()); + boolean actual2 = pushServerClient.createDevice(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN_2, MobilePlatform.IOS, powerAuthTestClient.getActivationId()); assertTrue(actual2); // The push token must change, however row ID stays the same List devices1 = pushDeviceRepository.findByAppCredentialsAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN); @@ -347,11 +347,11 @@ void createDeviceSameActivationDifferentPushTokenTest() throws PushServerClientE @Test void createDeviceDifferentActivationSamePushTokenTest() throws PushServerClientException { // This test tests change of activation - user deleted the activation and created a new one, the push token is the same - boolean actual = pushServerClient.createDevice(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.iOS, powerAuthTestClient.getActivationId()); + boolean actual = pushServerClient.createDevice(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.IOS, powerAuthTestClient.getActivationId()); assertTrue(actual); List devices = pushDeviceRepository.findByAppCredentialsAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN); Long rowId = devices.get(0).getId(); - boolean actual2 = pushServerClient.createDevice(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.iOS, powerAuthTestClient.getActivationId()); + boolean actual2 = pushServerClient.createDevice(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.IOS, powerAuthTestClient.getActivationId()); assertTrue(actual2); List devices2 = pushDeviceRepository.findByAppCredentialsAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN); assertEquals(1, devices2.size()); diff --git a/powerauth-push-server/src/test/resources/io/getlime/push/repository/model/PushDeviceRegistrationEntityTest.sql b/powerauth-push-server/src/test/resources/io/getlime/push/repository/model/PushDeviceRegistrationEntityTest.sql new file mode 100644 index 000000000..d07d5e222 --- /dev/null +++ b/powerauth-push-server/src/test/resources/io/getlime/push/repository/model/PushDeviceRegistrationEntityTest.sql @@ -0,0 +1,8 @@ +insert into push_app_credentials(id, app_id) values + (1, '1'); + +insert into push_device_registration (id, platform, push_token, timestamp_last_registered, app_id) values + (1, 'ios', 'token1', now(), 1), + (2, 'xxx', 'token2', now(), 1); + +ALTER SEQUENCE push_device_registration_seq RESTART WITH 3; From f18a67f0eb7ad8ae0aa0185a268cb075bdbe5787 Mon Sep 17 00:00:00 2001 From: Lubos Racansky Date: Fri, 26 Jan 2024 17:28:21 +0100 Subject: [PATCH 16/44] Fix #762: PushMessageStatusConverter should not be a spring bean --- .../push/repository/converter/PushMessageStatusConverter.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/powerauth-push-server/src/main/java/io/getlime/push/repository/converter/PushMessageStatusConverter.java b/powerauth-push-server/src/main/java/io/getlime/push/repository/converter/PushMessageStatusConverter.java index 46aede8dd..77d516bdb 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/repository/converter/PushMessageStatusConverter.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/repository/converter/PushMessageStatusConverter.java @@ -17,8 +17,6 @@ package io.getlime.push.repository.converter; import io.getlime.push.repository.model.PushMessageEntity; -import org.springframework.stereotype.Component; - import jakarta.persistence.AttributeConverter; import jakarta.persistence.Converter; @@ -27,7 +25,6 @@ * * @author Petr Dvorak, petr@wultra.com */ -@Component @Converter public class PushMessageStatusConverter implements AttributeConverter { From 67cb598a1c833caf281d94a43dca28c945f8a2df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lubo=C5=A1=20Ra=C4=8Dansk=C3=BD?= Date: Mon, 29 Jan 2024 07:37:08 +0100 Subject: [PATCH 17/44] Fix #760: Use Lombok getter and setter annotations for entity classes (#761) * Fix #760: Use Lombok getter and setter annotations for entity classes --- .../model/AppCredentialsEntity.java | 149 +----------------- .../repository/model/PushCampaignEntity.java | 118 +------------- .../model/PushCampaignUserEntity.java | 69 +------- .../model/PushDeviceRegistrationEntity.java | 132 +--------------- .../repository/model/PushMessageEntity.java | 147 +---------------- .../model/aggregate/UserDevice.java | 139 ++++------------ 6 files changed, 53 insertions(+), 701 deletions(-) diff --git a/powerauth-push-server/src/main/java/io/getlime/push/repository/model/AppCredentialsEntity.java b/powerauth-push-server/src/main/java/io/getlime/push/repository/model/AppCredentialsEntity.java index 16a58bc99..173b78bf1 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/repository/model/AppCredentialsEntity.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/repository/model/AppCredentialsEntity.java @@ -17,17 +17,21 @@ package io.getlime.push.repository.model; import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; import java.io.Serial; import java.io.Serializable; /** - * Class representing application tokens used to authenticate against APNs or FCM services. + * Class representing application tokens used to authenticate against APNs, or FCM services. * * @author Petr Dvorak, petr@wultra.com */ @Entity @Table(name = "push_app_credentials") +@Getter +@Setter public class AppCredentialsEntity implements Serializable { @Serial @@ -90,147 +94,4 @@ public class AppCredentialsEntity implements Serializable { @Column(name = "android_project_id") private String androidProjectId; - /** - * Get credentials ID. - * @return Credentials ID - */ - public Long getId() { - return id; - } - - /** - * Set credentials ID - * @param id Credentials ID - */ - public void setId(Long id) { - this.id = id; - } - - /** - * Get PowerAuth app ID. - * @return PowerAuth App ID. - */ - public String getAppId() { - return appId; - } - - /** - * Set PowerAuth app ID. - * @param powerAuthAppId PowerAuth App ID. - */ - public void setAppId(String powerAuthAppId) { - this.appId = powerAuthAppId; - } - - /** - * Get iOS bundle ID. - * @return iOS bundle ID. - */ - public String getIosBundle() { - return iosBundle; - } - - /** - * Set iOS bundle ID. - * @param iosBundle iOS bundle ID. - */ - public void setIosBundle(String iosBundle) { - this.iosBundle = iosBundle; - } - - /** - * Get APNs environment. - * @return APNs environment. - */ - public String getIosEnvironment() { - return iosEnvironment; - } - - /** - * Set APNs environment. - * @param iosEnvironment APNs environment. - */ - public void setIosEnvironment(String iosEnvironment) { - this.iosEnvironment = iosEnvironment; - } - - /** - * Get iOS private key. - * @return iOS private key. - */ - public byte[] getIosPrivateKey() { - return iosPrivateKey; - } - - /** - * Set iOS private key. - * @param iosPrivateKey iOS private key. - */ - public void setIosPrivateKey(byte[] iosPrivateKey) { - this.iosPrivateKey = iosPrivateKey; - } - - /** - * Get iOS team ID. - * @return iOS team ID. - */ - public String getIosTeamId() { - return iosTeamId; - } - - /** - * Set iOS team ID. - * @param iosTeamId iOS team ID. - */ - public void setIosTeamId(String iosTeamId) { - this.iosTeamId = iosTeamId; - } - - /** - * Get iOS key ID. - * @return iOS key ID. - */ - public String getIosKeyId() { - return iosKeyId; - } - - /** - * Set iOS key ID. - * @param iosKeyId iOS key ID. - */ - public void setIosKeyId(String iosKeyId) { - this.iosKeyId = iosKeyId; - } - - /** - * Get Android private key. - * @return Android private key. - */ - public byte[] getAndroidPrivateKey() { - return androidPrivateKey; - } - - /** - * Set Android private key. - * @param androidPrivateKey Android private key. - */ - public void setAndroidPrivateKey(byte[] androidPrivateKey) { - this.androidPrivateKey = androidPrivateKey; - } - - /** - * Get Android project ID. - * @return Android project ID. - */ - public String getAndroidProjectId() { - return androidProjectId; - } - - /** - * Set Android project ID. - * @param androidProjectId Android project ID. - */ - public void setAndroidProjectId(String androidProjectId) { - this.androidProjectId = androidProjectId; - } } diff --git a/powerauth-push-server/src/main/java/io/getlime/push/repository/model/PushCampaignEntity.java b/powerauth-push-server/src/main/java/io/getlime/push/repository/model/PushCampaignEntity.java index 208624069..5fc00bec8 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/repository/model/PushCampaignEntity.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/repository/model/PushCampaignEntity.java @@ -17,6 +17,9 @@ package io.getlime.push.repository.model; import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; + import java.io.Serializable; import java.util.Date; @@ -27,6 +30,8 @@ */ @Entity @Table(name = "push_campaign") +@Getter +@Setter public class PushCampaignEntity implements Serializable { /** @@ -39,7 +44,7 @@ public class PushCampaignEntity implements Serializable { private Long id; /** - * App ID. + * App credentials. */ @ManyToOne @JoinColumn(name = "app_id", referencedColumnName = "id", nullable = false, updatable = false) @@ -75,115 +80,4 @@ public class PushCampaignEntity implements Serializable { @Column(name = "timestamp_completed") private Date timestampCompleted; - /** - * Get campaign ID. - * @return Campaign ID. - */ - public Long getId() { - return id; - } - - /** - * Set campaign ID. - * @param id Campaign ID. - */ - public void setId(Long id) { - this.id = id; - } - - /** - * Get app ID. - * @return App ID. - */ - public AppCredentialsEntity getAppCredentials() { - return appCredentials; - } - - /** - * Set app credentials. - * @param appCredentials App credentials. - */ - public void setAppCredentials(AppCredentialsEntity appCredentials) { - this.appCredentials = appCredentials; - } - - /** - * Get message. - * @return Message. - */ - public String getMessage() { - return message; - } - - /** - * Set message. - * @param message Message. - */ - public void setMessage(String message) { - this.message = message; - } - - /** - * Check if the campaign is sent. - * @return True if the campaign is set, false otherwise. - */ - public boolean isSent() { - return sent; - } - - /** - * Set if the campaign is set. - * @param sent True if the campaign is set, false otherwise. - */ - public void setSent(boolean sent) { - this.sent = sent; - } - - /** - * Get timestamp created. - * @return Timestamp created. - */ - public Date getTimestampCreated() { - return timestampCreated; - } - - /** - * Set timestamp created. - * @param timestampCreated Timestamp created. - */ - public void setTimestampCreated(Date timestampCreated) { - this.timestampCreated = timestampCreated; - } - - /** - * Get timestamp sent. - * @return Timestamp sent. - */ - public Date getTimestampSent() { - return timestampSent; - } - - /** - * Set timestamp sent. - * @param timestampSent Timestamp sent. - */ - public void setTimestampSent(Date timestampSent) { - this.timestampSent = timestampSent; - } - - /** - * Get timestamp completed. - * @return Timestamp completed. - */ - public Date getTimestampCompleted() { - return timestampCompleted; - } - - /** - * Set timestamp completed. - * @param timestampCompleted Timestamp completed. - */ - public void setTimestampCompleted(Date timestampCompleted) { - this.timestampCompleted = timestampCompleted; - } } diff --git a/powerauth-push-server/src/main/java/io/getlime/push/repository/model/PushCampaignUserEntity.java b/powerauth-push-server/src/main/java/io/getlime/push/repository/model/PushCampaignUserEntity.java index ad10df6eb..4d6dbb4c0 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/repository/model/PushCampaignUserEntity.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/repository/model/PushCampaignUserEntity.java @@ -16,6 +16,9 @@ package io.getlime.push.repository.model; import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; + import java.io.Serializable; import java.util.Date; @@ -24,9 +27,10 @@ * * @author Martin Tupy, martin.tupy.work@gmail.com */ - @Entity @Table(name = "push_campaign_user") +@Getter +@Setter public class PushCampaignUserEntity implements Serializable { /** @@ -56,67 +60,4 @@ public class PushCampaignUserEntity implements Serializable { @Column(name = "timestamp_created", nullable = false, updatable = false) private Date timestampCreated; - /** - * Get campaign user ID. - * @return Campaign user ID. - */ - public Long getId() { - return id; - } - - /** - * Set campaign user ID. - * @param id Campaign user ID. - */ - public void setId(Long id) { - this.id = id; - } - - /** - * Get campaign ID. - * @return Campaign ID. - */ - public Long getCampaignId() { - return campaignId; - } - - /** - * Set campaign ID. - * @param campaignId Campaign ID. - */ - public void setCampaignId(Long campaignId) { - this.campaignId = campaignId; - } - - /** - * Get user ID. - * @return User ID. - */ - public String getUserId() { - return userId; - } - - /** - * Set user ID. - * @param userId User ID. - */ - public void setUserId(String userId) { - this.userId = userId; - } - - /** - * Get timestamp created. - * @return Timestamp created. - */ - public Date getTimestampCreated() { - return timestampCreated; - } - - /** - * Set timestamp created. - * @param timestampCreated Timestamp created. - */ - public void setTimestampCreated(Date timestampCreated) { - this.timestampCreated = timestampCreated; - } } diff --git a/powerauth-push-server/src/main/java/io/getlime/push/repository/model/PushDeviceRegistrationEntity.java b/powerauth-push-server/src/main/java/io/getlime/push/repository/model/PushDeviceRegistrationEntity.java index 51ae55a37..3cbfec630 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/repository/model/PushDeviceRegistrationEntity.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/repository/model/PushDeviceRegistrationEntity.java @@ -17,6 +17,8 @@ package io.getlime.push.repository.model; import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; import java.io.Serial; import java.io.Serializable; @@ -31,6 +33,8 @@ @Table(name = "push_device_registration", uniqueConstraints = {@UniqueConstraint(columnNames = {"activationId", "pushToken"}), @UniqueConstraint(columnNames = {"activationId"})}) +@Getter +@Setter public class PushDeviceRegistrationEntity implements Serializable { @Serial @@ -89,132 +93,4 @@ public class PushDeviceRegistrationEntity implements Serializable { @Column(name = "is_active") private Boolean active; - /** - * Get device registration ID. - * @return Device registration ID. - */ - public Long getId() { - return id; - } - - /** - * Set device registration ID. - * @param id Device registration ID. - */ - public void setId(Long id) { - this.id = id; - } - - /** - * Get activation ID. - * @return Activation ID. - */ - public String getActivationId() { - return activationId; - } - - /** - * Set activation ID. - * @param activationId Activation ID. - */ - public void setActivationId(String activationId) { - this.activationId = activationId; - } - - /** - * Get user ID. - * @return User ID. - */ - public String getUserId() { - return userId; - } - - /** - * Set user ID. - * @param userId User ID. - */ - public void setUserId(String userId) { - this.userId = userId; - } - - /** - * Get app credentials. - * @return App credentials. - */ - public AppCredentialsEntity getAppCredentials() { - return appCredentials; - } - - /** - * Set app credentials. - * @param appCredentials App credentials - */ - public void setAppCredentials(AppCredentialsEntity appCredentials) { - this.appCredentials = appCredentials; - } - - /** - * Get platform. - * @return Platform. - */ - public Platform getPlatform() { - return platform; - } - - /** - * Set platform. - * @param platform Platform. - */ - public void setPlatform(Platform platform) { - this.platform = platform; - } - - /** - * Get push token. - * @return Push token. - */ - public String getPushToken() { - return pushToken; - } - - /** - * Set push token. - * @param pushToken Push token. - */ - public void setPushToken(String pushToken) { - this.pushToken = pushToken; - } - - /** - * Get timestamp last registered. - * @return Timestamp last registered. - */ - public Date getTimestampLastRegistered() { - return timestampLastRegistered; - } - - /** - * Set timestamp last registered. - * @param timestampLastRegistered Timestamp last registered. - */ - public void setTimestampLastRegistered(Date timestampLastRegistered) { - this.timestampLastRegistered = timestampLastRegistered; - } - - /** - * Get flag indicating if the device registration is active. - * @return True if the device is active, false otherwise. - */ - public Boolean getActive() { - return active; - } - - /** - * Set flag indicating if the device registration is active. - * @param active True if the device is active, false otherwise. - */ - public void setActive(Boolean active) { - this.active = active; - } - } diff --git a/powerauth-push-server/src/main/java/io/getlime/push/repository/model/PushMessageEntity.java b/powerauth-push-server/src/main/java/io/getlime/push/repository/model/PushMessageEntity.java index 81c5c29cd..0d8a1f961 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/repository/model/PushMessageEntity.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/repository/model/PushMessageEntity.java @@ -19,6 +19,8 @@ import io.getlime.push.repository.converter.PushMessageStatusConverter; import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; import java.io.Serial; import java.io.Serializable; @@ -31,6 +33,8 @@ */ @Entity @Table(name = "push_message") +@Getter +@Setter public class PushMessageEntity implements Serializable { /** @@ -133,147 +137,4 @@ public int getStatus() { @Convert(converter = PushMessageStatusConverter.class) private Status status; - /** - * Get message ID. - * @return Message ID. - */ - public Long getId() { - return id; - } - - /** - * Set message ID. - * @param id Message ID. - */ - public void setId(Long id) { - this.id = id; - } - - /** - * Get device ID. - * @return Device ID. - */ - public Long getDeviceId() { - return deviceId; - } - - /** - * Set device ID. - * @param deviceId Device ID. - */ - public void setDeviceId(Long deviceId) { - this.deviceId = deviceId; - } - - /** - * Get user ID. - * @return User ID. - */ - public String getUserId() { - return userId; - } - - /** - * Set user ID. - * @param userId User ID. - */ - public void setUserId(String userId) { - this.userId = userId; - } - - /** - * Get activation ID. - * @return Activation ID. - */ - public String getActivationId() { - return activationId; - } - - /** - * Set activation ID. - * @param activationId Activation ID. - */ - public void setActivationId(String activationId) { - this.activationId = activationId; - } - - /** - * Get info if the message is silent. - * @return True if the message is silent, false otherwise. - */ - public Boolean getSilent() { - return silent; - } - - /** - * Set info if the message is silent. - * @param silent True if the message is silent, false otherwise. - */ - public void setSilent(Boolean silent) { - this.silent = silent; - } - - /** - * Get info if the message is personal. - * @return True if the message is personal, false otherwise. - */ - public Boolean getPersonal() { - return personal; - } - - /** - * Set info if the message is personal. - * @param personal True if the message is personal, false otherwise. - */ - public void setPersonal(Boolean personal) { - this.personal = personal; - } - - /** - * Get message body. - * @return Message body. - */ - public String getMessageBody() { - return messageBody; - } - - /** - * Set message body. - * @param messageBody Message body. - */ - public void setMessageBody(String messageBody) { - this.messageBody = messageBody; - } - - /** - * Get timestamp created. - * @return Timestamp created. - */ - public Date getTimestampCreated() { - return timestampCreated; - } - - /** - * Set timestamp created. - * @param timestampCreated Timestamp created. - */ - public void setTimestampCreated(Date timestampCreated) { - this.timestampCreated = timestampCreated; - } - - /** - * Get status. - * @return Status. - */ - public Status getStatus() { - return status; - } - - /** - * Set status. - * @param status Status. - */ - public void setStatus(Status status) { - this.status = status; - } } diff --git a/powerauth-push-server/src/main/java/io/getlime/push/repository/model/aggregate/UserDevice.java b/powerauth-push-server/src/main/java/io/getlime/push/repository/model/aggregate/UserDevice.java index 6b8eed8f0..13e7701ff 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/repository/model/aggregate/UserDevice.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/repository/model/aggregate/UserDevice.java @@ -16,6 +16,8 @@ package io.getlime.push.repository.model.aggregate; import io.getlime.push.repository.model.Platform; +import lombok.Getter; +import lombok.Setter; /** * Object used in sending campaigns, as an aggregate object for storing information about @@ -27,145 +29,62 @@ * * @author Petr Dvorak, petr@wultra.com */ +@Getter +@Setter public class UserDevice { - private String userId; - private Long deviceId; - private String activationId; - private Long campaignId; - private Long appId; - private Platform platform; - private String token; - /** - * User device constructor. - * @param userId User ID. - * @param deviceId Device ID. - * @param activationId Activation ID. - * @param campaignId Campaign ID. - * @param appId App ID. - * @param platform Platform. - * @param token Push token. + * User ID. */ - public UserDevice(String userId, Long deviceId, String activationId, Long campaignId, Long appId, Platform platform, String token) { - this.userId = userId; - this.deviceId = deviceId; - this.activationId = activationId; - this.campaignId = campaignId; - this.appId = appId; - this.platform = platform; - this.token = token; - } - - /** - * Get user ID. - * @return User ID. - */ - public String getUserId() { - return userId; - } + private String userId; /** - * Set user ID. - * @param userId User ID. + * Device ID. */ - public void setUserId(String userId) { - this.userId = userId; - } + private Long deviceId; /** - * Get device ID. - * @return Device ID. + * Activation ID. */ - public Long getDeviceId() { - return deviceId; - } + private String activationId; /** - * Set device ID. - * @param deviceId Device ID. + * Campaign ID. */ - public void setDeviceId(Long deviceId) { - this.deviceId = deviceId; - } + private Long campaignId; /** - * Get activation ID. - * @return Activation ID. + * Application ID. */ - public String getActivationId() { - return activationId; - } + private Long appId; /** - * Set activation ID. - * @param activationId Activation ID. + * Platform. */ - public void setActivationId(String activationId) { - this.activationId = activationId; - } + private Platform platform; /** - * Get campaign ID. - * @return Campaign ID. + * Push token. */ - public Long getCampaignId() { - return campaignId; - } + private String token; /** - * Set campaign ID. + * User device constructor. + * @param userId User ID. + * @param deviceId Device ID. + * @param activationId Activation ID. * @param campaignId Campaign ID. - */ - public void setCampaignId(Long campaignId) { - this.campaignId = campaignId; - } - - /** - * Get app ID. - * @return App ID. - */ - public Long getAppId() { - return appId; - } - - /** - * Set app ID. * @param appId App ID. - */ - public void setAppId(Long appId) { - this.appId = appId; - } - - /** - * Get platform. - * @return Platform. - */ - public Platform getPlatform() { - return platform; - } - - /** - * Set platform. * @param platform Platform. - */ - public void setPlatform(Platform platform) { - this.platform = platform; - } - - /** - * Get push token. - * @return Push token. - */ - public String getToken() { - return token; - } - - /** - * Set push token. * @param token Push token. */ - public void setToken(String token) { + public UserDevice(String userId, Long deviceId, String activationId, Long campaignId, Long appId, Platform platform, String token) { + this.userId = userId; + this.deviceId = deviceId; + this.activationId = activationId; + this.campaignId = campaignId; + this.appId = appId; + this.platform = platform; this.token = token; } From f6adc37d32d7fa33e688e5d061c0a137510b8ec4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lubo=C5=A1=20Ra=C4=8Dansk=C3=BD?= Date: Mon, 29 Jan 2024 07:53:05 +0100 Subject: [PATCH 18/44] Fix #757: Cleanup request response objects (#759) * Fix #757: Cleanup request response objects - Use Lombok getter and setter annotations - Add Swagger schema annotations - Add bean validation annotations --- .../request/CreateApplicationRequest.java | 27 ++-- .../model/request/CreateCampaignRequest.java | 46 ++---- .../CreateDeviceForActivationsRequest.java | 76 ++++------ .../model/request/CreateDeviceRequest.java | 80 +++-------- .../model/request/DeleteDeviceRequest.java | 43 ++---- .../request/GetApplicationDetailRequest.java | 69 +++------ .../model/request/RemoveAndroidRequest.java | 28 ++-- .../push/model/request/RemoveIosRequest.java | 28 ++-- .../request/SendPushMessageBatchRequest.java | 61 +++----- .../model/request/SendPushMessageRequest.java | 59 +++----- .../model/request/TestCampaignRequest.java | 26 ++-- .../model/request/UpdateAndroidRequest.java | 72 ++++------ .../request/UpdateDeviceStatusRequest.java | 42 ++---- .../push/model/request/UpdateIosRequest.java | 136 ++++++------------ .../push/model/response/CampaignResponse.java | 69 ++------- .../response/CreateApplicationResponse.java | 23 ++- .../response/CreateCampaignResponse.java | 22 +-- .../response/DeleteCampaignResponse.java | 21 +-- .../GetApplicationDetailResponse.java | 106 +++----------- .../response/GetApplicationListResponse.java | 22 +-- .../ListOfUsersFromCampaignResponse.java | 42 ++---- .../model/response/ServiceStatusResponse.java | 101 +++---------- .../rest/AdministrationController.java | 4 +- 23 files changed, 346 insertions(+), 857 deletions(-) diff --git a/powerauth-push-model/src/main/java/io/getlime/push/model/request/CreateApplicationRequest.java b/powerauth-push-model/src/main/java/io/getlime/push/model/request/CreateApplicationRequest.java index 4736a66b0..3262f899a 100644 --- a/powerauth-push-model/src/main/java/io/getlime/push/model/request/CreateApplicationRequest.java +++ b/powerauth-push-model/src/main/java/io/getlime/push/model/request/CreateApplicationRequest.java @@ -15,13 +15,25 @@ */ package io.getlime.push.model.request; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; +import lombok.Setter; + /** * Request to create Push Server application credentials entity based on existing PowerAuth server application. * * @author Roman Strobl, roman.strobl@wultra.com */ +@Getter +@Setter public class CreateApplicationRequest { + /** + * Application ID. + */ + @NotBlank + @Schema(description = "Application ID.") private String appId; /** @@ -38,19 +50,4 @@ public CreateApplicationRequest(String appId) { this.appId = appId; } - /** - * Get PowerAuth server application ID. - * @return PowerAuth server application ID. - */ - public String getAppId() { - return appId; - } - - /** - * Set PowerAuth server application ID. - * @param appId PowerAuth server application ID. - */ - public void setAppId(String appId) { - this.appId = appId; - } } diff --git a/powerauth-push-model/src/main/java/io/getlime/push/model/request/CreateCampaignRequest.java b/powerauth-push-model/src/main/java/io/getlime/push/model/request/CreateCampaignRequest.java index 082865447..1e1de941a 100644 --- a/powerauth-push-model/src/main/java/io/getlime/push/model/request/CreateCampaignRequest.java +++ b/powerauth-push-model/src/main/java/io/getlime/push/model/request/CreateCampaignRequest.java @@ -17,49 +17,33 @@ package io.getlime.push.model.request; import io.getlime.push.model.entity.PushMessageBody; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; /** * Request object used for creating a campaign. * * @author Martin Tupy, martin.tupy.work@gmail.com */ - - +@Getter +@Setter public class CreateCampaignRequest { - private String appId; - - private PushMessageBody message; - /** - * Get app ID. - * @return App ID. + * Application ID. */ - public String getAppId() { - return appId; - } - - /** - * Set app ID. - * @param appId App ID. - */ - public void setAppId(String appId) { - this.appId = appId; - } + @NotBlank + @Schema(description = "Application ID.") + private String appId; /** - * Get push message body. - * @return Push message body. + * Push message body. */ - public PushMessageBody getMessage() { - return message; - } + @NotNull + @Schema(description = "Push message body.") + private PushMessageBody message; - /** - * Set push message body. - * @param message Push message body. - */ - public void setMessage(PushMessageBody message) { - this.message = message; - } } diff --git a/powerauth-push-model/src/main/java/io/getlime/push/model/request/CreateDeviceForActivationsRequest.java b/powerauth-push-model/src/main/java/io/getlime/push/model/request/CreateDeviceForActivationsRequest.java index 38661bb21..8eb96b045 100644 --- a/powerauth-push-model/src/main/java/io/getlime/push/model/request/CreateDeviceForActivationsRequest.java +++ b/powerauth-push-model/src/main/java/io/getlime/push/model/request/CreateDeviceForActivationsRequest.java @@ -16,6 +16,13 @@ package io.getlime.push.model.request; import io.getlime.push.model.enumeration.MobilePlatform; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; import java.util.ArrayList; import java.util.List; @@ -25,67 +32,38 @@ * * @author Roman Strobl, roman.strobl@wultra.com */ +@Getter +@Setter public class CreateDeviceForActivationsRequest { - private String appId; - private String token; - private MobilePlatform platform; - private final List activationIds = new ArrayList<>(); - - /** - * Get app ID associated with given device registration. - * @return App ID. - */ - public String getAppId() { - return appId; - } - - /** - * Set app ID associated with given device registration. - * @param appId App ID. - */ - public void setAppId(String appId) { - this.appId = appId; - } - - /** - * Get APNs / FCM push token. - * @return Push token value. - */ - public String getToken() { - return token; - } - /** - * Set APNs / FCM push token. - * @param token Push token value. + * Application ID. */ - public void setToken(String token) { - this.token = token; - } + @NotBlank + @Schema(description = "Application ID.") + private String appId; /** - * Get the platform name, either "ios" or "android". - * @return Platform name, "ios" or "android". + * The push token is the value received from APNS, or FCM services without any modification. */ - public MobilePlatform getPlatform() { - return platform; - } + @NotBlank + @Schema(description = "The push token is the value received from APNS, or FCM services without any modification.") + private String token; /** - * Set the platform name. - * @param platform Platform name. + * The platform. */ - public void setPlatform(MobilePlatform platform) { - this.platform = platform; - } + @NotNull + private MobilePlatform platform; /** - * Get PowerAuth activation IDs associated with given device registration. - * @return Activation ID. + * Activation IDs. */ - public List getActivationIds() { - return activationIds; - } + @NotEmpty + @ArraySchema( + arraySchema = @Schema(description = "Activation IDs."), + schema = @Schema(description = "Activation ID.", format = "UUID (level 4)", maxLength = 37, example = "099e5e30-47b1-41c7-b49b-3bf28e811fca") + ) + private final List<@NotBlank String> activationIds = new ArrayList<>(); } diff --git a/powerauth-push-model/src/main/java/io/getlime/push/model/request/CreateDeviceRequest.java b/powerauth-push-model/src/main/java/io/getlime/push/model/request/CreateDeviceRequest.java index 2326abaaa..30ab39f81 100644 --- a/powerauth-push-model/src/main/java/io/getlime/push/model/request/CreateDeviceRequest.java +++ b/powerauth-push-model/src/main/java/io/getlime/push/model/request/CreateDeviceRequest.java @@ -16,81 +16,43 @@ package io.getlime.push.model.request; import io.getlime.push.model.enumeration.MobilePlatform; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; /** * Request object used for device registration. * * @author Petr Dvorak, petr@wultra.com */ +@Getter +@Setter public class CreateDeviceRequest { - private String appId; - private String token; - private MobilePlatform platform; - private String activationId; - - /** - * Get app ID associated with given device registration. - * @return App ID. - */ - public String getAppId() { - return appId; - } - - /** - * Set app ID associated with given device registration. - * @param appId App ID. - */ - public void setAppId(String appId) { - this.appId = appId; - } - /** - * Get APNs / FCM push token. - * @return Push token value. + * Application ID. */ - public String getToken() { - return token; - } - - /** - * Set APNs / FCM push token. - * @param token Push token value. - */ - public void setToken(String token) { - this.token = token; - } - - /** - * Get the platform name, either "ios" or "android". - * @return Platform name, "ios" or "android". - */ - public MobilePlatform getPlatform() { - return platform; - } + @NotBlank + @Schema(description = "Application ID.") + private String appId; /** - * Set the platform name. - * @param platform Platform name. + * The push token is the value received from APNS, or FCM services without any modification. */ - public void setPlatform(MobilePlatform platform) { - this.platform = platform; - } + @NotBlank + @Schema(description = "The push token is the value received from APNS, or FCM services without any modification.") + private String token; - /** - * Get PowerAuth 2.0 activation ID associated with given device registration. - * @return Activation ID. - */ - public String getActivationId() { - return activationId; - } + @NotNull + private MobilePlatform platform; /** - * Set PowerAuth 2.0 activation ID associated with given device registration. - * @param activationId Activation ID. + * Activation ID. */ - public void setActivationId(String activationId) { - this.activationId = activationId; - } + @NotBlank + @Schema(description = "Activation ID.", format = "UUID (level 4)", maxLength = 37, example = "099e5e30-47b1-41c7-b49b-3bf28e811fca") + private String activationId; } diff --git a/powerauth-push-model/src/main/java/io/getlime/push/model/request/DeleteDeviceRequest.java b/powerauth-push-model/src/main/java/io/getlime/push/model/request/DeleteDeviceRequest.java index 4fdd4a35c..23c71fe61 100644 --- a/powerauth-push-model/src/main/java/io/getlime/push/model/request/DeleteDeviceRequest.java +++ b/powerauth-push-model/src/main/java/io/getlime/push/model/request/DeleteDeviceRequest.java @@ -15,45 +15,32 @@ */ package io.getlime.push.model.request; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; +import lombok.Setter; + /** * Class representing request object responsible for device registration removal. * * @author Petr Dvorak, petr@wultra.com */ +@Getter +@Setter public class DeleteDeviceRequest { - private String appId; - private String token; - /** - * Get app ID. - * @return App ID. + * Application ID. */ - public String getAppId() { - return appId; - } - - /** - * Set app ID. - * @param appId App ID. - */ - public void setAppId(String appId) { - this.appId = appId; - } + @NotBlank + @Schema(description = "Application ID.") + private String appId; /** - * Get push token value. - * @return Push token. + * Push token value. */ - public String getToken() { - return token; - } + @NotBlank + @Schema(description = "Push token value.") + private String token; - /** - * Set push token value. - * @param token Push token. - */ - public void setToken(String token) { - this.token = token; - } } diff --git a/powerauth-push-model/src/main/java/io/getlime/push/model/request/GetApplicationDetailRequest.java b/powerauth-push-model/src/main/java/io/getlime/push/model/request/GetApplicationDetailRequest.java index 6ce3cf128..e8878f409 100644 --- a/powerauth-push-model/src/main/java/io/getlime/push/model/request/GetApplicationDetailRequest.java +++ b/powerauth-push-model/src/main/java/io/getlime/push/model/request/GetApplicationDetailRequest.java @@ -15,15 +15,37 @@ */ package io.getlime.push.model.request; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; +import lombok.Setter; + /** * Get application credentials entity detail request. * * @author Roman Strobl, roman.strobl@wultra.com */ +@Getter +@Setter public class GetApplicationDetailRequest { + /** + * Application ID. + */ + @NotBlank + @Schema(description = "Application ID.") private String appId; + + /** + * Whether to include iOS details. + */ + @Schema(description = "Whether to include iOS details.") private boolean includeIos; + + /** + * Whether to include Android details. + */ + @Schema(description = "Whether to include Android details.") private boolean includeAndroid; /** @@ -52,51 +74,4 @@ public GetApplicationDetailRequest(String appId, boolean includeIos, boolean inc this.includeAndroid = includeAndroid; } - /** - * Get application credentials entity ID. - * @return Application credentials entity ID. - */ - public String getAppId() { - return appId; - } - - /** - * Set application credentials entity ID. - * @param appId Application credentials entity ID. - */ - public void setAppId(String appId) { - this.appId = appId; - } - - /** - * Get whether to include iOS details. - * @return Whether to include iOS details. - */ - public boolean getIncludeIos() { - return includeIos; - } - - /** - * Set whether to include iOS details. - * @param includeIos Whether to include iOS details. - */ - public void setIncludeIos(boolean includeIos) { - this.includeIos = includeIos; - } - - /** - * Get whether to include Android details. - * @return Whether to include Android details. - */ - public boolean getIncludeAndroid() { - return includeAndroid; - } - - /** - * Set whgether to include Android details. - * @param includeAndroid Whether to include Android details. - */ - public void setIncludeAndroid(boolean includeAndroid) { - this.includeAndroid = includeAndroid; - } } diff --git a/powerauth-push-model/src/main/java/io/getlime/push/model/request/RemoveAndroidRequest.java b/powerauth-push-model/src/main/java/io/getlime/push/model/request/RemoveAndroidRequest.java index 505bc4c41..320fab107 100644 --- a/powerauth-push-model/src/main/java/io/getlime/push/model/request/RemoveAndroidRequest.java +++ b/powerauth-push-model/src/main/java/io/getlime/push/model/request/RemoveAndroidRequest.java @@ -15,13 +15,25 @@ */ package io.getlime.push.model.request; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; +import lombok.Setter; + /** * Remove android configuration request. * * @author Roman Strobl, roman.strobl@wultra.com */ +@Getter +@Setter public class RemoveAndroidRequest { + /** + * Application ID. + */ + @NotBlank + @Schema(description = "Application ID.") private String appId; /** @@ -38,20 +50,4 @@ public RemoveAndroidRequest(String appId) { this.appId = appId; } - /** - * Get application credentials entity ID. - * @return Application credentials entity ID. - */ - public String getAppId() { - return appId; - } - - /** - * Set application credentials entity ID. - * @param appId Application credentials entity ID. - */ - public void setAppId(String appId) { - this.appId = appId; - } - } diff --git a/powerauth-push-model/src/main/java/io/getlime/push/model/request/RemoveIosRequest.java b/powerauth-push-model/src/main/java/io/getlime/push/model/request/RemoveIosRequest.java index bc4837451..12b71f520 100644 --- a/powerauth-push-model/src/main/java/io/getlime/push/model/request/RemoveIosRequest.java +++ b/powerauth-push-model/src/main/java/io/getlime/push/model/request/RemoveIosRequest.java @@ -15,13 +15,25 @@ */ package io.getlime.push.model.request; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; +import lombok.Setter; + /** * Remove iOS configuration request. * * @author Roman Strobl, roman.strobl@wultra.com */ +@Getter +@Setter public class RemoveIosRequest { + /** + * Application ID. + */ + @NotBlank + @Schema(description = "Application ID.") private String appId; /** @@ -38,20 +50,4 @@ public RemoveIosRequest(String appId) { this.appId = appId; } - /** - * Get application credentials entity ID. - * @return Application credentials entity ID. - */ - public String getAppId() { - return appId; - } - - /** - * Set application credentials entity ID. - * @param appId Application credentials entity ID. - */ - public void setAppId(String appId) { - this.appId = appId; - } - } diff --git a/powerauth-push-model/src/main/java/io/getlime/push/model/request/SendPushMessageBatchRequest.java b/powerauth-push-model/src/main/java/io/getlime/push/model/request/SendPushMessageBatchRequest.java index c196111a3..5b9d73b9d 100644 --- a/powerauth-push-model/src/main/java/io/getlime/push/model/request/SendPushMessageBatchRequest.java +++ b/powerauth-push-model/src/main/java/io/getlime/push/model/request/SendPushMessageBatchRequest.java @@ -17,6 +17,12 @@ import io.getlime.push.model.entity.PushMessage; import io.getlime.push.model.enumeration.Mode; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; import java.util.List; @@ -25,57 +31,28 @@ * * @author Petr Dvorak, petr@wultra.com */ +@Getter +@Setter public class SendPushMessageBatchRequest { - private String appId; - private Mode mode = Mode.SYNCHRONOUS; - private List batch; - /** - * Get app ID. - * @return App ID. + * Application ID. */ - public String getAppId() { - return appId; - } - - /** - * Set app ID. - * @param appId App ID. - */ - public void setAppId(String appId) { - this.appId = appId; - } - - /** - * Get mode of sending. - * @return Mode. - */ - public Mode getMode() { - return mode; - } + @NotBlank + @Schema(description = "Application ID.") + private String appId; /** - * Set mode of sending. - * @param mode Mode. + * Mode of sending. */ - public void setMode(Mode mode) { - this.mode = mode; - } + @Schema(description = "Mode of sending.") + private Mode mode = Mode.SYNCHRONOUS; /** - * Get batch list with push notifications to be sent. - * @param batch Push notification batch. + * Batch list with push notifications to be sent. */ - public void setBatch(List batch) { - this.batch = batch; - } + @Schema(description = "Batch list with push notifications to be sent.") + @NotEmpty + private List<@NotNull PushMessage> batch; - /** - * Set batch list with push notifications to be sent. - * @return Push notification batch. - */ - public List getBatch() { - return batch; - } } diff --git a/powerauth-push-model/src/main/java/io/getlime/push/model/request/SendPushMessageRequest.java b/powerauth-push-model/src/main/java/io/getlime/push/model/request/SendPushMessageRequest.java index 7231e136a..0b4488d79 100644 --- a/powerauth-push-model/src/main/java/io/getlime/push/model/request/SendPushMessageRequest.java +++ b/powerauth-push-model/src/main/java/io/getlime/push/model/request/SendPushMessageRequest.java @@ -18,63 +18,36 @@ import io.getlime.push.model.entity.PushMessage; import io.getlime.push.model.enumeration.Mode; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; /** * Class representing a single push message send request. * * @author Petr Dvorak, petr@wultra.com */ +@Getter +@Setter public class SendPushMessageRequest { + @NotBlank + @Schema(description = "Application ID.") private String appId; - private Mode mode = Mode.SYNCHRONOUS; - private PushMessage message; - - /** - * Get app ID. - * @return App ID. - */ - public String getAppId() { - return appId; - } - - /** - * Set app ID. - * @param appId App ID. - */ - public void setAppId(String appId) { - this.appId = appId; - } /** - * Get mode of sending. - * @return Mode. + * Mode of sending. */ - public Mode getMode() { - return mode; - } - - /** - * Set mode of sending. - * @param mode Mode. - */ - public void setMode(Mode mode) { - this.mode = mode; - } + @Schema(description = "Mode of sending.") + private Mode mode = Mode.SYNCHRONOUS; /** - * Get push message to be sent. - * @return Push message. + * Push message to be sent. */ - public PushMessage getMessage() { - return message; - } + @NotNull + @Schema(description = "Push message to be sent.") + private PushMessage message; - /** - * Set push message to be sent. - * @param message Push message. - */ - public void setMessage(PushMessage message) { - this.message = message; - } } diff --git a/powerauth-push-model/src/main/java/io/getlime/push/model/request/TestCampaignRequest.java b/powerauth-push-model/src/main/java/io/getlime/push/model/request/TestCampaignRequest.java index 49ebc801b..5846a3861 100644 --- a/powerauth-push-model/src/main/java/io/getlime/push/model/request/TestCampaignRequest.java +++ b/powerauth-push-model/src/main/java/io/getlime/push/model/request/TestCampaignRequest.java @@ -16,29 +16,25 @@ package io.getlime.push.model.request; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; +import lombok.Setter; + /** * Request used for sending a testing campaign * * @author Martin Tupy, martin.tupy.work@gmail.com */ - +@Getter +@Setter public class TestCampaignRequest { - private String userId; - /** - * Get user ID. - * @return User ID. + * User ID. */ - public String getUserId() { - return userId; - } + @NotBlank + @Schema(description = "User ID.") + private String userId; - /** - * Set user ID. - * @param userId User ID. - */ - public void setUserId(String userId) { - this.userId = userId; - } } diff --git a/powerauth-push-model/src/main/java/io/getlime/push/model/request/UpdateAndroidRequest.java b/powerauth-push-model/src/main/java/io/getlime/push/model/request/UpdateAndroidRequest.java index 97138fd93..ef2c5963f 100644 --- a/powerauth-push-model/src/main/java/io/getlime/push/model/request/UpdateAndroidRequest.java +++ b/powerauth-push-model/src/main/java/io/getlime/push/model/request/UpdateAndroidRequest.java @@ -15,15 +15,39 @@ */ package io.getlime.push.model.request; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; +import lombok.Setter; + /** * Update Android configuration request. * * @author Roman Strobl, roman.strobl@wultra.com */ +@Getter +@Setter public class UpdateAndroidRequest { + /** + * Application ID. + */ + @NotBlank + @Schema(description = "Application ID.") private String appId; + + /** + * Android project ID. + */ + @NotBlank + @Schema(description = "Android project ID.") private String projectId; + + /** + * Base64 encoded Android private key. + */ + @NotBlank + @Schema(description = "Base64 encoded Android private key.") private String privateKeyBase64; /** @@ -44,52 +68,4 @@ public UpdateAndroidRequest(String appId, String projectId, String privateKeyBas this.privateKeyBase64 = privateKeyBase64; } - /** - * Get application credentials entity ID. - * @return Application credentials entity ID. - */ - public String getAppId() { - return appId; - } - - /** - * Set application credentials entity ID. - * @param appId Application credentials entity ID. - */ - public void setAppId(String appId) { - this.appId = appId; - } - - /** - * Get the Android project ID record. - * @return The Android project ID record. - */ - public String getProjectId() { - return projectId; - } - - /** - * Set the Android project ID record. - * @param projectId The Android project ID record. - */ - public void setProjectId(String projectId) { - this.projectId = projectId; - } - - /** - * Get the base64 encoded Android private key. - * @return The base64 encoded Android private key. - */ - public String getPrivateKeyBase64() { - return privateKeyBase64; - } - - /** - * Set the base64 encoded Android private key. - * @param privateKeyBase64 The base64 encoded Android private key. - */ - public void setPrivateKeyBase64(String privateKeyBase64) { - this.privateKeyBase64 = privateKeyBase64; - } - } diff --git a/powerauth-push-model/src/main/java/io/getlime/push/model/request/UpdateDeviceStatusRequest.java b/powerauth-push-model/src/main/java/io/getlime/push/model/request/UpdateDeviceStatusRequest.java index db75d08f2..383fcbc21 100644 --- a/powerauth-push-model/src/main/java/io/getlime/push/model/request/UpdateDeviceStatusRequest.java +++ b/powerauth-push-model/src/main/java/io/getlime/push/model/request/UpdateDeviceStatusRequest.java @@ -18,47 +18,31 @@ import com.wultra.security.powerauth.client.model.enumeration.ActivationStatus; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; +import lombok.Setter; /** * Class representing request object responsible for updating activation status. * * @author Petr Dvorak, petr@wultra.com */ +@Getter +@Setter public class UpdateDeviceStatusRequest { - private String activationId; - private ActivationStatus activationStatus; - - /** - * Get PowerAuth activation ID. - * @return Activation ID. - */ - public String getActivationId() { - return activationId; - } - /** - * Set PowerAuth activation ID. - * @param activationId Activation ID. + * Activation ID. */ - public void setActivationId(String activationId) { - this.activationId = activationId; - } - - /** - * Get PowerAuth activation status. - * @return Activation status. - */ - public ActivationStatus getActivationStatus() { - return activationStatus; - } + @NotBlank + @Schema(description = "Activation ID.", format = "UUID (level 4)", maxLength = 37, example = "099e5e30-47b1-41c7-b49b-3bf28e811fca") + private String activationId; /** - * Set PowerAuth activation status. - * @param activationStatus Activation status. + * Activation status. */ - public void setActivationStatus(ActivationStatus activationStatus) { - this.activationStatus = activationStatus; - } + @Schema(description = "Activation status.") + private ActivationStatus activationStatus; } diff --git a/powerauth-push-model/src/main/java/io/getlime/push/model/request/UpdateIosRequest.java b/powerauth-push-model/src/main/java/io/getlime/push/model/request/UpdateIosRequest.java index f043049cf..d176006f3 100644 --- a/powerauth-push-model/src/main/java/io/getlime/push/model/request/UpdateIosRequest.java +++ b/powerauth-push-model/src/main/java/io/getlime/push/model/request/UpdateIosRequest.java @@ -15,18 +15,59 @@ */ package io.getlime.push.model.request; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; +import lombok.Setter; + /** * Update iOS configuration request. * * @author Roman Strobl, roman.strobl@wultra.com */ +@Getter +@Setter public class UpdateIosRequest { + /** + * Application ID. + */ + @NotBlank + @Schema(description = "Application ID.") private String appId; + + /** + * iOS bundle. + */ + @NotBlank + @Schema(description = "iOS bundle.") private String bundle; + + /** + * iOS key ID. + */ + @NotBlank + @Schema(description = "iOS key ID.") private String keyId; + + /** + * iOS team ID. + */ + @NotBlank + @Schema(description = "iOS team ID.") private String teamId; + + /** + * APNs environment. + */ + @Schema(description = "APNs environment.") private String environment; + + /** + * Base64 encoded private key. + */ + @NotBlank + @Schema(description = "Base64 encoded private key.") private String privateKeyBase64; /** @@ -53,99 +94,4 @@ public UpdateIosRequest(String appId, String bundle, String keyId, String teamId this.privateKeyBase64 = privateKeyBase64; } - /** - * Get application credentials entity ID. - * @return Application credentials entity ID. - */ - public String getAppId() { - return appId; - } - - /** - * Set application credentials entity ID. - * @param appId Application credentials entity ID. - */ - public void setAppId(String appId) { - this.appId = appId; - } - - /** - * Get the iOS bundle record. - * @return The iOS bundle record. - */ - public String getBundle() { - return bundle; - } - - /** - * Set the iOS bundle record. - * @param bundle The iOS bundle record. - */ - public void setBundle(String bundle) { - this.bundle = bundle; - } - - /** - * Get the iOS key ID record. - * @return The iOS key ID record. - */ - public String getKeyId() { - return keyId; - } - - /** - * Set the iOS key ID record. - * @param keyId The iOS key ID record. - */ - public void setKeyId(String keyId) { - this.keyId = keyId; - } - - /** - * Get the iOS team ID record. - * @return The iOS team ID record. - */ - public String getTeamId() { - return teamId; - } - - /** - * Set the iOS team ID record. - * @param teamId The iOS team ID record. - */ - public void setTeamId(String teamId) { - this.teamId = teamId; - } - - /** - * Get APNs environment. - * @return APNs environment. - */ - public String getEnvironment() { - return environment; - } - - /** - * Set APNs environment. - * @param environment APNs environment. - */ - public void setEnvironment(String environment) { - this.environment = environment; - } - - /** - * Get base64 encoded private key. - * @return Base 64 encoded private key. - */ - public String getPrivateKeyBase64() { - return privateKeyBase64; - } - - /** - * Set base64 encoded private key. - * @param privateKeyBase64 Base 64 encoded private key. - */ - public void setPrivateKeyBase64(String privateKeyBase64) { - this.privateKeyBase64 = privateKeyBase64; - } } diff --git a/powerauth-push-model/src/main/java/io/getlime/push/model/response/CampaignResponse.java b/powerauth-push-model/src/main/java/io/getlime/push/model/response/CampaignResponse.java index 5647bbdad..2316702f2 100644 --- a/powerauth-push-model/src/main/java/io/getlime/push/model/response/CampaignResponse.java +++ b/powerauth-push-model/src/main/java/io/getlime/push/model/response/CampaignResponse.java @@ -17,82 +17,37 @@ package io.getlime.push.model.response; import io.getlime.push.model.entity.PushMessageBody; +import lombok.Getter; +import lombok.Setter; /** * Response object used for getting a campaign * * @author Martin Tupy, martin.tupy.work@gmail.com */ +@Getter +@Setter public class CampaignResponse { - private Long id; - private String appId; - private boolean sent; - private PushMessageBody message; - /** - * Get campaign ID. - * @return Campaign ID. + * Campaign ID. */ - public Long getId() { - return id; - } - - /** - * Set campaign ID. - * @param id Campaign ID. - */ - public void setId(Long id) { - this.id = id; - } - - /** - * Get app ID. - * @return App ID. - */ - public String getAppId() { - return appId; - } - - /** - * Set app ID. - * @param appId App ID. - */ - public void setAppId(String appId) { - this.appId = appId; - } - - /** - * Check if the message is sent. - * @return True if sent, false otherwise. - */ - public boolean isSent() { - return sent; - } + private Long id; /** - * Set if the message was sent. - * @param sent True if sent, false otherwise. + * Application ID. */ - public void setSent(boolean sent) { - this.sent = sent; - } + private String appId; /** - * Get push message body. - * @return Push message body. + * If the message is sent. */ - public PushMessageBody getMessage() { - return message; - } + private boolean sent; /** - * Set push message body. - * @param message Push message body. + * Push message body. */ - public void setMessage(PushMessageBody message) { - this.message = message; - } + private PushMessageBody message; @Override public boolean equals(Object o) { diff --git a/powerauth-push-model/src/main/java/io/getlime/push/model/response/CreateApplicationResponse.java b/powerauth-push-model/src/main/java/io/getlime/push/model/response/CreateApplicationResponse.java index 80bd070a9..481678245 100644 --- a/powerauth-push-model/src/main/java/io/getlime/push/model/response/CreateApplicationResponse.java +++ b/powerauth-push-model/src/main/java/io/getlime/push/model/response/CreateApplicationResponse.java @@ -15,13 +15,21 @@ */ package io.getlime.push.model.response; +import lombok.Getter; +import lombok.Setter; + /** * Response after creating Push Server application credentials entity based on existing PowerAuth server application. * * @author Roman Strobl, roman.strobl@wultra.com */ +@Getter +@Setter public class CreateApplicationResponse { + /** + * Application ID. + */ private String appId; /** @@ -38,19 +46,4 @@ public CreateApplicationResponse(String appId) { this.appId = appId; } - /** - * Get application credentials entity ID. - * @return Application credentials entity ID. - */ - public String getAppId() { - return appId; - } - - /** - * Set application credentials entity ID. - * @param appId Application credentials entity ID. - */ - public void setAppId(String appId) { - this.appId = appId; - } } diff --git a/powerauth-push-model/src/main/java/io/getlime/push/model/response/CreateCampaignResponse.java b/powerauth-push-model/src/main/java/io/getlime/push/model/response/CreateCampaignResponse.java index ec6f0678b..7e62fbb15 100644 --- a/powerauth-push-model/src/main/java/io/getlime/push/model/response/CreateCampaignResponse.java +++ b/powerauth-push-model/src/main/java/io/getlime/push/model/response/CreateCampaignResponse.java @@ -17,29 +17,21 @@ package io.getlime.push.model.response; +import lombok.Getter; +import lombok.Setter; + /** * Response object for creating a campaign * * @author Martin Tupy, martin.tupy.work@gmail.com */ - - +@Getter +@Setter public class CreateCampaignResponse { - private Long id; /** - * Get campaign ID. - * @return Campaign ID. + * Campaign ID. */ - public Long getId() { - return id; - } + private Long id; - /** - * Set campaign ID. - * @param id Campaign ID. - */ - public void setId(Long id) { - this.id = id; - } } diff --git a/powerauth-push-model/src/main/java/io/getlime/push/model/response/DeleteCampaignResponse.java b/powerauth-push-model/src/main/java/io/getlime/push/model/response/DeleteCampaignResponse.java index a2488e4c1..c906675fe 100644 --- a/powerauth-push-model/src/main/java/io/getlime/push/model/response/DeleteCampaignResponse.java +++ b/powerauth-push-model/src/main/java/io/getlime/push/model/response/DeleteCampaignResponse.java @@ -16,28 +16,21 @@ package io.getlime.push.model.response; +import lombok.Getter; +import lombok.Setter; + /** * Object response for deleting a campaign * * @author Martin Tupy, martin.tupy.work@gmail.com */ +@Getter +@Setter public class DeleteCampaignResponse { - private boolean deleted; - - /** - * Check if campaign is deleted. - * @return True if campaign is deleted, false otherwise. - */ - public boolean isDeleted() { - return deleted; - } /** - * Set if the campaign is deleted. - * @param deleted True if campaign is deleted, false otherwise. + * If campaign is deleted. */ - public void setDeleted(boolean deleted) { - this.deleted = deleted; - } + private boolean deleted; } diff --git a/powerauth-push-model/src/main/java/io/getlime/push/model/response/GetApplicationDetailResponse.java b/powerauth-push-model/src/main/java/io/getlime/push/model/response/GetApplicationDetailResponse.java index a5a2a7d23..dfa7894b3 100644 --- a/powerauth-push-model/src/main/java/io/getlime/push/model/response/GetApplicationDetailResponse.java +++ b/powerauth-push-model/src/main/java/io/getlime/push/model/response/GetApplicationDetailResponse.java @@ -16,120 +16,46 @@ package io.getlime.push.model.response; import io.getlime.push.model.entity.PushServerApplication; +import lombok.Getter; +import lombok.Setter; /** * Get application credentials entity detail response. * * @author Roman Strobl, roman.strobl@wultra.com */ +@Getter +@Setter public class GetApplicationDetailResponse { - private PushServerApplication application; - private String iosBundle; - private String iosKeyId; - private String iosTeamId; - private String iosEnvironment; - private String androidProjectId; - /** - * Default constructor. + * Push server application. */ - public GetApplicationDetailResponse() { - } - - /** - * Get push server application. - * @return Push server application. - */ - public PushServerApplication getApplication() { - return application; - } - - /** - * Set push server application. - * @param application Push server application. - */ - public void setApplication(PushServerApplication application) { - this.application = application; - } - - /** - * Get the iOS bundle record. - * @return The iOS bundle record. - */ - public String getIosBundle() { - return iosBundle; - } - - /** - * Set the iOS bundle record. - * @param iosBundle The iOS bundle record. - */ - public void setIosBundle(String iosBundle) { - this.iosBundle = iosBundle; - } - - /** - * Get the iOS key record. - * @return The iOS key record. - */ - public String getIosKeyId() { - return iosKeyId; - } - - /** - * Set the iOS key record. - * @param iosKeyId The iOS key record. - */ - public void setIosKeyId(String iosKeyId) { - this.iosKeyId = iosKeyId; - } + private PushServerApplication application; /** - * Get the iOS team ID record. - * @return The iOS team ID record. + * iOS bundle. */ - public String getIosTeamId() { - return iosTeamId; - } + private String iosBundle; /** - * Set the iOS team ID record. - * @param iosTeamId The iOS team ID record. + * iOS key ID. */ - public void setIosTeamId(String iosTeamId) { - this.iosTeamId = iosTeamId; - } + private String iosKeyId; /** - * Get the APNs environment. - * @return APNs environment. + * iOS team ID. */ - public String getIosEnvironment() { - return iosEnvironment; - } + private String iosTeamId; /** - * Set the APNs environment. - * @param iosEnvironment APNs environment. + * APNs environment. */ - public void setIosEnvironment(String iosEnvironment) { - this.iosEnvironment = iosEnvironment; - } + private String iosEnvironment; /** - * Get the Android project ID record. - * @return The Android project ID record. + * Android project ID record. */ - public String getAndroidProjectId() { - return androidProjectId; - } + private String androidProjectId; - /** - * Set the Android project ID record. - * @param androidProjectId The Android project ID record. - */ - public void setAndroidProjectId(String androidProjectId) { - this.androidProjectId = androidProjectId; - } } diff --git a/powerauth-push-model/src/main/java/io/getlime/push/model/response/GetApplicationListResponse.java b/powerauth-push-model/src/main/java/io/getlime/push/model/response/GetApplicationListResponse.java index 81c527d3b..59d384821 100644 --- a/powerauth-push-model/src/main/java/io/getlime/push/model/response/GetApplicationListResponse.java +++ b/powerauth-push-model/src/main/java/io/getlime/push/model/response/GetApplicationListResponse.java @@ -16,6 +16,8 @@ package io.getlime.push.model.response; import io.getlime.push.model.entity.PushServerApplication; +import lombok.Getter; +import lombok.Setter; import java.util.ArrayList; import java.util.List; @@ -25,8 +27,13 @@ * * @author Roman Strobl, roman.strobl@wultra.com */ +@Getter +@Setter public class GetApplicationListResponse { + /** + * Application list. + */ private List applicationList = new ArrayList<>(); /** @@ -43,19 +50,4 @@ public GetApplicationListResponse(List applicationList) { this.applicationList = applicationList; } - /** - * Get application list. - * @return Application list. - */ - public List getApplicationList() { - return applicationList; - } - - /** - * Set application list. - * @param applicationList Application list. - */ - public void setApplicationList(List applicationList) { - this.applicationList = applicationList; - } } diff --git a/powerauth-push-model/src/main/java/io/getlime/push/model/response/ListOfUsersFromCampaignResponse.java b/powerauth-push-model/src/main/java/io/getlime/push/model/response/ListOfUsersFromCampaignResponse.java index 8a57000cf..f13f1ae5e 100644 --- a/powerauth-push-model/src/main/java/io/getlime/push/model/response/ListOfUsersFromCampaignResponse.java +++ b/powerauth-push-model/src/main/java/io/getlime/push/model/response/ListOfUsersFromCampaignResponse.java @@ -17,6 +17,8 @@ package io.getlime.push.model.response; import io.getlime.push.model.entity.ListOfUsers; +import lombok.Getter; +import lombok.Setter; import java.util.Objects; @@ -25,48 +27,24 @@ * * @author Martin Tupy, martin.tupy.work@gmail.com */ - +@Getter +@Setter public class ListOfUsersFromCampaignResponse { - private Long campaignId; - private ListOfUsers users; - - /** - * Default constructor. - */ - public ListOfUsersFromCampaignResponse() { - } - /** - * Get campaign ID. - * @return Campaign ID. + * Campaign ID. */ - public Long getCampaignId() { - return campaignId; - } - - /** - * Set campaign ID. - * @param campaignId Campaign ID. - */ - public void setCampaignId(Long campaignId) { - this.campaignId = campaignId; - } + private Long campaignId; /** - * Get list of users. - * @return List of users. + * List of users. */ - public ListOfUsers getUsers() { - return users; - } + private ListOfUsers users; /** - * Set list of users. - * @param users List of users. + * Default constructor. */ - public void setUsers(ListOfUsers users) { - this.users = users; + public ListOfUsersFromCampaignResponse() { } @Override diff --git a/powerauth-push-model/src/main/java/io/getlime/push/model/response/ServiceStatusResponse.java b/powerauth-push-model/src/main/java/io/getlime/push/model/response/ServiceStatusResponse.java index 9a4ebb6eb..a77f63592 100644 --- a/powerauth-push-model/src/main/java/io/getlime/push/model/response/ServiceStatusResponse.java +++ b/powerauth-push-model/src/main/java/io/getlime/push/model/response/ServiceStatusResponse.java @@ -16,6 +16,9 @@ package io.getlime.push.model.response; +import lombok.Getter; +import lombok.Setter; + import java.util.Date; /** @@ -23,108 +26,38 @@ * * @author Petr Dvorak, petr@wultra.com */ +@Getter +@Setter public class ServiceStatusResponse { - private String applicationName; - private String applicationDisplayName; - private String applicationEnvironment; - private String version; - private Date buildTime; - private Date timestamp; - /** - * Get the application name. - * @return Application name. + * Application name. */ - public String getApplicationName() { - return applicationName; - } - - /** - * Set the application name. - * @param applicationName Application name. - */ - public void setApplicationName(String applicationName) { - this.applicationName = applicationName; - } - - /** - * Get the application display name. - * @return Application display name. - */ - public String getApplicationDisplayName() { - return applicationDisplayName; - } - - /** - * Set the application display name. - * @param applicationDisplayName Application display name. - */ - public void setApplicationDisplayName(String applicationDisplayName) { - this.applicationDisplayName = applicationDisplayName; - } - - /** - * Get application environment name. - * @return Environment name. - */ - public String getApplicationEnvironment() { - return applicationEnvironment; - } - - /** - * Set application environment name. - * @param applicationEnvironment Environment name. - */ - public void setApplicationEnvironment(String applicationEnvironment) { - this.applicationEnvironment = applicationEnvironment; - } + private String applicationName; /** - * Get version. - * @return version. + * Application display name. */ - public String getVersion() { - return version; - } + private String applicationDisplayName; /** - * Set version. - * @param version Version. + * Application environment name. */ - public void setVersion(String version) { - this.version = version; - } + private String applicationEnvironment; /** - * Get build time. - * @return Build time. + * Version. */ - public Date getBuildTime() { - return buildTime; - } + private String version; /** - * Set build time. - * @param buildTime Build time. + * Build time. */ - public void setBuildTime(Date buildTime) { - this.buildTime = buildTime; - } + private Date buildTime; /** - * Get current timestamp. - * @return Timestamp. + * Current timestamp. */ - public Date getTimestamp() { - return timestamp; - } + private Date timestamp; - /** - * Set current timestamp. - * @param timestamp Timestamp. - */ - public void setTimestamp(Date timestamp) { - this.timestamp = timestamp; - } } diff --git a/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/AdministrationController.java b/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/AdministrationController.java index 92100f092..6ccac62d4 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/AdministrationController.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/AdministrationController.java @@ -151,13 +151,13 @@ public ObjectResponse getApplicationDetail(@Reques app.setIos(appCredentialsEntity.getIosPrivateKey() != null); app.setAndroid(appCredentialsEntity.getAndroidPrivateKey() != null); response.setApplication(app); - if (requestObject.getIncludeIos()) { + if (requestObject.isIncludeIos()) { response.setIosBundle(appCredentialsEntity.getIosBundle()); response.setIosKeyId(appCredentialsEntity.getIosKeyId()); response.setIosTeamId(appCredentialsEntity.getIosTeamId()); response.setIosEnvironment(appCredentialsEntity.getIosEnvironment()); } - if (requestObject.getIncludeAndroid()) { + if (requestObject.isIncludeAndroid()) { response.setAndroidProjectId(appCredentialsEntity.getAndroidProjectId()); } logger.debug("The getApplicationDetail request succeeded"); From 95b85c6d5065feda16352bc2670264a059848f4e Mon Sep 17 00:00:00 2001 From: Lubos Racansky Date: Wed, 31 Jan 2024 11:41:01 +0100 Subject: [PATCH 19/44] Fix #764: Extract android code in PushSendingWorker --- .../push/service/PushSendingWorker.java | 37 +++++++++++++------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/powerauth-push-server/src/main/java/io/getlime/push/service/PushSendingWorker.java b/powerauth-push-server/src/main/java/io/getlime/push/service/PushSendingWorker.java index 844d06022..b352c315c 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/service/PushSendingWorker.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/service/PushSendingWorker.java @@ -231,17 +231,8 @@ private Message buildAndroidMessage(final PushMessageBody pushMessageBody, final final AndroidConfig.Builder androidConfigBuilder = AndroidConfig.builder() .setCollapseKey(pushMessageBody.getCollapseKey()); - // Calculate TTL and set it if the TTL is within reasonable limits - final Instant validUntil = pushMessageBody.getValidUntil(); - if (validUntil != null) { - final long validUntilMs = validUntil.toEpochMilli(); - final long currentTimeMs = System.currentTimeMillis(); - final long ttlInSeconds = (validUntilMs - currentTimeMs) / 1000; - - if (ttlInSeconds > 0 && ttlInSeconds < ANDROID_TTL_SECONDS_MAX) { - androidConfigBuilder.setTtl(ttlInSeconds); - } - } + calculateTtl(pushMessageBody.getValidUntil()) + .ifPresent(androidConfigBuilder::setTtl); final AndroidNotification.Priority deliveryPriority = (Priority.NORMAL == priority) ? AndroidNotification.Priority.DEFAULT : AndroidNotification.Priority.HIGH; @@ -267,7 +258,7 @@ private Message buildAndroidMessage(final PushMessageBody pushMessageBody, final if (pushServiceConfiguration.isFcmDataNotificationOnly()) { // notification only through data map data.put(FCM_NOTIFICATION_KEY, fcmConverter.convertNotificationToString(notification)); - } else if (attributes == null || !attributes.getSilent()) { // if there are no attributes, assume the message is not silent + } else if (isMessageNotSilent(attributes)) { androidConfigBuilder.setNotification(notification); } @@ -278,6 +269,28 @@ private Message buildAndroidMessage(final PushMessageBody pushMessageBody, final .build(); } + private static boolean isMessageNotSilent(final PushMessageAttributes attributes) { + // if there are no attributes, assume the message is not silent + return attributes == null || !attributes.getSilent(); + } + + /** + * Calculate TTL and return it if the TTL is within reasonable limits. + * + * @param validUntil Valid until. + * @return TTL in seconds or empty. + */ + private static Optional calculateTtl(final Instant validUntil) { + if (validUntil != null) { + final long ttlInSeconds = Duration.between(Instant.now(), validUntil).toSeconds(); + + if (ttlInSeconds > 0 && ttlInSeconds < ANDROID_TTL_SECONDS_MAX) { + return Optional.of(ttlInSeconds); + } + } + return Optional.empty(); + } + // iOS related methods /** From abe22ca5cfda8322690a3628a0e73da7d294a29b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 01:03:32 +0000 Subject: [PATCH 20/44] Bump com.google.api-client:google-api-client from 2.2.0 to 2.3.0 Bumps [com.google.api-client:google-api-client](https://github.com/googleapis/google-api-java-client) from 2.2.0 to 2.3.0. - [Release notes](https://github.com/googleapis/google-api-java-client/releases) - [Changelog](https://github.com/googleapis/google-api-java-client/blob/main/CHANGELOG.md) - [Commits](https://github.com/googleapis/google-api-java-client/compare/v2.2.0...v2.3.0) --- updated-dependencies: - dependency-name: com.google.api-client:google-api-client dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d5f65a58c..7ccc5d960 100644 --- a/pom.xml +++ b/pom.xml @@ -76,7 +76,7 @@ 0.15.3 - 2.2.0 + 2.3.0 9.2.0 1.77 7.4 From f63bca413c7c48af12ddee8ca59b11e0560db1ab Mon Sep 17 00:00:00 2001 From: Jan Dusil <134381434+jandusil@users.noreply.github.com> Date: Tue, 6 Feb 2024 10:54:14 +0100 Subject: [PATCH 21/44] Fix #768: Add TraceID/SpanID to Monitoring for Enhanced Observability (#769) * Fix #768: Add TraceID/SpanID to Monitoring for Enhanced Observability --- docs/Configuration-Properties.md | 4 +++- powerauth-push-server/pom.xml | 10 ++++++++++ .../src/main/resources/application.properties | 1 + 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/docs/Configuration-Properties.md b/docs/Configuration-Properties.md index 0e4835792..ec2151fa0 100644 --- a/docs/Configuration-Properties.md +++ b/docs/Configuration-Properties.md @@ -84,6 +84,8 @@ The Push Server uses the following public configuration properties: ## Monitoring and Observability - +| Property | Default | Note | +|-------------------------------------------|---------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `management.tracing.sampling.probability` | `1.0` | Specifies the proportion of requests that are sampled for tracing. A value of 1.0 means that 100% of requests are sampled, while a value of 0 effectively disables tracing. | The WAR file includes the `micrometer-registry-prometheus` dependency. Discuss its configuration with the [Spring Boot documentation](https://docs.spring.io/spring-boot/docs/3.1.x/reference/html/actuator.html#actuator.metrics). diff --git a/powerauth-push-server/pom.xml b/powerauth-push-server/pom.xml index 482cfed33..2f0b946ce 100644 --- a/powerauth-push-server/pom.xml +++ b/powerauth-push-server/pom.xml @@ -124,6 +124,16 @@ micrometer-registry-prometheus + + io.projectreactor + reactor-core-micrometer + + + + io.micrometer + micrometer-tracing-bridge-otel + + net.logstash.logback diff --git a/powerauth-push-server/src/main/resources/application.properties b/powerauth-push-server/src/main/resources/application.properties index b8f836f51..ffc8842d4 100644 --- a/powerauth-push-server/src/main/resources/application.properties +++ b/powerauth-push-server/src/main/resources/application.properties @@ -102,6 +102,7 @@ powerauth.service.correlation-header.value.validation-regexp=[a-zA-Z0-9\\-]{8,10 #logging.pattern.console=%clr(%d{${LOG_DATEFORMAT_PATTERN:yyyy-MM-dd HH:mm:ss.SSS}}){faint} %clr(${LOG_LEVEL_PATTERN:%5p}) [%X{X-Correlation-ID}] %clr(%5p) %clr(${PID: }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:%wEx} # Monitoring +management.tracing.sampling.probability=1.0 #management.endpoint.metrics.enabled=true #management.endpoints.web.exposure.include=health, prometheus #management.endpoint.prometheus.enabled=true From 7d99a5c848f2102d2ccce5a21acec133e8b73155 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Pe=C5=A1ek?= Date: Tue, 6 Feb 2024 11:06:57 +0100 Subject: [PATCH 22/44] Fix #742: Separate service layer from PushDeviceController (#766) --- docs/Configuration-Properties.md | 4 +- .../PowerAuthPushServerJavaApplication.java | 2 + .../PushServiceConfiguration.java | 12 - .../controller/rest/PushDeviceController.java | 330 +----------------- .../service/DeviceRegistrationService.java | 267 ++++++++++++++ .../push/service/PushDeviceService.java | 140 ++++++++ .../main/resources/application-dev.properties | 2 + .../src/main/resources/application.properties | 4 +- .../DeviceRegistrationServiceTest.java | 306 ++++++++++++++++ .../push/service/PushDeviceServiceTest.java | 199 +++++++++++ ...stCreateOrUpdateDevice_multipleRecords.sql | 5 + ...tCreateOrUpdateDevices_multipleRecords.sql | 5 + ...eTest.testCreateOrUpdateDevices_update.sql | 5 + ...viceRegistrationServiceTest.testDelete.sql | 5 + 14 files changed, 949 insertions(+), 337 deletions(-) create mode 100644 powerauth-push-server/src/main/java/io/getlime/push/service/DeviceRegistrationService.java create mode 100644 powerauth-push-server/src/main/java/io/getlime/push/service/PushDeviceService.java create mode 100644 powerauth-push-server/src/test/java/io/getlime/push/service/DeviceRegistrationServiceTest.java create mode 100644 powerauth-push-server/src/test/java/io/getlime/push/service/PushDeviceServiceTest.java create mode 100644 powerauth-push-server/src/test/resources/io/getlime/push/service/DeviceRegistrationServiceTest.testCreateOrUpdateDevice_multipleRecords.sql create mode 100644 powerauth-push-server/src/test/resources/io/getlime/push/service/DeviceRegistrationServiceTest.testCreateOrUpdateDevices_multipleRecords.sql create mode 100644 powerauth-push-server/src/test/resources/io/getlime/push/service/DeviceRegistrationServiceTest.testCreateOrUpdateDevices_update.sql create mode 100644 powerauth-push-server/src/test/resources/io/getlime/push/service/DeviceRegistrationServiceTest.testDelete.sql diff --git a/docs/Configuration-Properties.md b/docs/Configuration-Properties.md index ec2151fa0..7e96bbd2d 100644 --- a/docs/Configuration-Properties.md +++ b/docs/Configuration-Properties.md @@ -31,8 +31,8 @@ The Push Server uses the following public configuration properties: | `powerauth.push.service.applicationEnvironment` | `_empty_` | Environment identifier | | `powerauth.push.service.message.storage.enabled` | `false` | Whether persistent storing of sent messages is enabled | | `powerauth.push.service.registration.multipleActivations.enabled` | `false` | Whether push registration supports "associated activations" | -| `powerauth.push.service.registration.retry.backoff` | `100ms` | Duration before a retry attempt during device registration in case of an insert error | -| `owerauth.push.service.registration.retry.maxAttempts` | `3` | Max number of retry attempts during device registration in case of an insert error | +| `powerauth.push.service.registration.retry.backoff` | `100` | Duration in milliseconds before a retry attempt during device registration in case of an insert error | +| `owerauth.push.service.registration.retry.maxAttempts` | `2` | Max number of retry attempts during device registration in case of an insert error | ## PowerAuth Push Campaign Setup diff --git a/powerauth-push-server/src/main/java/io/getlime/push/PowerAuthPushServerJavaApplication.java b/powerauth-push-server/src/main/java/io/getlime/push/PowerAuthPushServerJavaApplication.java index bf3feb056..87ece047b 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/PowerAuthPushServerJavaApplication.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/PowerAuthPushServerJavaApplication.java @@ -18,12 +18,14 @@ import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.retry.annotation.EnableRetry; /** * Spring Boot main class */ @SpringBootApplication @EnableBatchProcessing +@EnableRetry public class PowerAuthPushServerJavaApplication { /** diff --git a/powerauth-push-server/src/main/java/io/getlime/push/configuration/PushServiceConfiguration.java b/powerauth-push-server/src/main/java/io/getlime/push/configuration/PushServiceConfiguration.java index 9e8b5e95d..bed523258 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/configuration/PushServiceConfiguration.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/configuration/PushServiceConfiguration.java @@ -155,18 +155,6 @@ public class PushServiceConfiguration { @Value("${powerauth.push.service.registration.multipleActivations.enabled}") private boolean registrationOfMultipleActivationsEnabled; - /** - * Duration before a retry attempt during device registration in case of an insert error. - */ - @Value("${powerauth.push.service.registration.retry.backoff:100ms}") - private Duration createDeviceRetryBackoff; - - /** - * Max number of retry attempts during device registration in case of an insert error. - */ - @Value("${powerauth.push.service.registration.retry.maxAttempts:3}") - private int createDeviceRetryMaxAttempts; - /** * FCM connect timeout in milliseconds. */ diff --git a/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/PushDeviceController.java b/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/PushDeviceController.java index de8424f52..3709bfd91 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/PushDeviceController.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/PushDeviceController.java @@ -15,41 +15,18 @@ */ package io.getlime.push.controller.rest; -import com.wultra.security.powerauth.client.PowerAuthClient; -import com.wultra.security.powerauth.client.model.enumeration.ActivationStatus; -import com.wultra.security.powerauth.client.model.error.PowerAuthClientException; -import com.wultra.security.powerauth.client.model.response.GetActivationStatusResponse; import io.getlime.core.rest.model.base.request.ObjectRequest; import io.getlime.core.rest.model.base.response.Response; -import io.getlime.push.configuration.PushServiceConfiguration; import io.getlime.push.errorhandling.exceptions.PushServerException; -import io.getlime.push.model.enumeration.MobilePlatform; import io.getlime.push.model.request.CreateDeviceForActivationsRequest; import io.getlime.push.model.request.CreateDeviceRequest; import io.getlime.push.model.request.DeleteDeviceRequest; import io.getlime.push.model.request.UpdateDeviceStatusRequest; -import io.getlime.push.model.validator.CreateDeviceRequestValidator; -import io.getlime.push.model.validator.DeleteDeviceRequestValidator; -import io.getlime.push.model.validator.UpdateDeviceStatusRequestValidator; -import io.getlime.push.repository.AppCredentialsRepository; -import io.getlime.push.repository.PushDeviceRepository; -import io.getlime.push.repository.model.AppCredentialsEntity; -import io.getlime.push.repository.model.Platform; -import io.getlime.push.repository.model.PushDeviceRegistrationEntity; +import io.getlime.push.service.PushDeviceService; import io.swagger.v3.oas.annotations.Operation; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.dao.DataIntegrityViolationException; -import org.springframework.retry.support.RetryTemplate; +import lombok.AllArgsConstructor; import org.springframework.web.bind.annotation.*; -import java.util.Date; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.concurrent.atomic.AtomicBoolean; - /** * Controller responsible for device registration related business processes. * @@ -57,29 +34,10 @@ */ @RestController @RequestMapping(value = "push/device") +@AllArgsConstructor public class PushDeviceController { - private static final Logger logger = LoggerFactory.getLogger(PushDeviceController.class); - - private final AppCredentialsRepository appCredentialsRepository; - private final PushDeviceRepository pushDeviceRepository; - private final PowerAuthClient client; - private final PushServiceConfiguration config; - - /** - * Constructor with autowired dependencies. - * @param appCredentialsRepository App credentials repository. - * @param pushDeviceRepository Push device repository. - * @param client PowerAuth service client. - * @param config Push service configuration. - */ - @Autowired - public PushDeviceController(AppCredentialsRepository appCredentialsRepository, PushDeviceRepository pushDeviceRepository, PowerAuthClient client, PushServiceConfiguration config) { - this.appCredentialsRepository = appCredentialsRepository; - this.pushDeviceRepository = pushDeviceRepository; - this.client = client; - this.config = config; - } + private final PushDeviceService pushDeviceService; /** * Create a new device registration. @@ -94,64 +52,7 @@ Create a new device push token (platform specific). The call must include an act ---Note: Since this endpoint is usually called by the back-end service, it is not secured in any way. It's the service that calls this endpoint responsibility to assure that the device is somehow authenticated before the push token is assigned with given activation ID, so that there are no incorrect bindings.""") public Response createDevice(@RequestBody ObjectRequest request) throws PushServerException { - CreateDeviceRequest requestObject = request.getRequestObject(); - if (requestObject == null) { - throw new PushServerException("Request object must not be empty"); - } - logger.info("Received createDevice request, app ID: {}, activation ID: {}, token: {}, platform: {}", requestObject.getAppId(), - requestObject.getActivationId(), maskPushToken(requestObject.getToken()), requestObject.getPlatform()); - final String errorMessage = CreateDeviceRequestValidator.validate(requestObject); - if (errorMessage != null) { - throw new PushServerException(errorMessage); - } - - final AppCredentialsEntity appCredentials = findAppCredentials(requestObject.getAppId()); - - // In case of parallel requests to create new device, the createOrUpdateDevice may fail because of unique - // constraint violation. Try to repeat the save. - RetryTemplate.builder() - .retryOn(DataIntegrityViolationException.class) - .fixedBackoff(config.getCreateDeviceRetryBackoff()) - .maxAttempts(config.getCreateDeviceRetryMaxAttempts()) - .build() - .execute(ctx -> createOrUpdateDevice(requestObject, appCredentials)); - - logger.info("The createDevice request succeeded, app ID: {}, activation ID: {}, platform: {}", requestObject.getAppId(), requestObject.getActivationId(), requestObject.getPlatform()); - return new Response(); - } - - private Void createOrUpdateDevice(final CreateDeviceRequest requestObject, final AppCredentialsEntity appCredentials) throws PushServerException { - final String appId = requestObject.getAppId(); - final String pushToken = requestObject.getToken(); - final MobilePlatform platform = requestObject.getPlatform(); - final String activationId = requestObject.getActivationId(); - - final List devices = lookupDeviceRegistrations(appId, activationId, pushToken); - final PushDeviceRegistrationEntity device; - if (devices.isEmpty()) { - // The device registration is new, create a new entity. - logger.info("Creating new device registration: app ID: {}, activation ID: {}, platform: {}", requestObject.getAppId(), requestObject.getActivationId(), platform); - device = initDeviceRegistrationEntity(appCredentials, pushToken); - } else if (devices.size() == 1) { - // An existing row was found by one of the lookup methods, update this row. This means that either: - // 1. A row with same activation ID and push token is updated, in this case only the last registration timestamp changes. - // 2. A row with same activation ID but different push token is updated. A new push token has been issued by Google or Apple for an activation. - // 3. A row with same push token but different activation ID is updated. The user removed an activation and created a new one, the push token remains the same. - logger.info("Updating existing device registration: app ID: {}, activation ID: {}, platform: {}", requestObject.getAppId(), requestObject.getActivationId(), platform); - device = devices.get(0); - updateDeviceRegistrationEntity(device, appCredentials, pushToken); - } else { - // Multiple existing rows have been found. This can only occur during lookup by push token. - // Push token can be associated with multiple activations only when associated activations are enabled. - // Push device registration must be done using /push/device/create/multi endpoint in this case. - logger.info("Multiple device registrations found: app ID: {}, activation ID: {}, platform: {}", requestObject.getAppId(), requestObject.getActivationId(), platform); - throw new PushServerException("Multiple device registrations found for push token. Use the /push/device/create/multi endpoint for this scenario."); - } - device.setTimestampLastRegistered(new Date()); - device.setPlatform(convert(platform)); - updateActivationForDevice(device, activationId); - pushDeviceRepository.save(device); - return null; + return pushDeviceService.createDevice(request.getRequestObject()); } /** @@ -167,163 +68,7 @@ Create a new device push token (platform specific). The call must include one or ---Note: Since this endpoint is usually called by the back-end service, it is not secured in any way. It's the service that calls this endpoint responsibility to assure that the device is somehow authenticated before the push token is assigned with given activation IDs, so that there are no incorrect bindings.""") public Response createDeviceMultipleActivations(@RequestBody ObjectRequest request) throws PushServerException { - CreateDeviceForActivationsRequest requestedObject = request.getRequestObject(); - if (requestedObject == null) { - throw new PushServerException("Request object must not be empty"); - } - logger.info("Received createDeviceMultipleActivations request, app ID: {}, activation IDs: {}, token: {}, platform: {}", - requestedObject.getAppId(), requestedObject.getActivationIds(), maskPushToken(requestedObject.getToken()), requestedObject.getPlatform()); - String errorMessage; - if (!config.isRegistrationOfMultipleActivationsEnabled()) { - errorMessage = "Registration of multiple associated activations per device is not enabled."; - } else { - errorMessage = CreateDeviceRequestValidator.validate(requestedObject); - } - if (errorMessage != null) { - throw new PushServerException(errorMessage); - } - String appId = requestedObject.getAppId(); - String pushToken = requestedObject.getToken(); - final MobilePlatform platform = requestedObject.getPlatform(); - List activationIds = requestedObject.getActivationIds(); - - // Initialize loop variables. - AtomicBoolean registrationFailed = new AtomicBoolean(false); - Set usedDeviceRegistrationIds = new HashSet<>(); - - final AppCredentialsEntity appCredentials = findAppCredentials(appId); - - activationIds.forEach(activationId -> { - try { - List devices = lookupDeviceRegistrations(appId, activationId, pushToken); - PushDeviceRegistrationEntity device; - if (devices.isEmpty()) { - // The device registration is new, create a new entity. - device = initDeviceRegistrationEntity(appCredentials, pushToken); - } else if (devices.size() == 1) { - device = devices.get(0); - if (usedDeviceRegistrationIds.contains(device.getId())) { - // The row has already been used within this request. Create a new row instead. - device = initDeviceRegistrationEntity(appCredentials, pushToken); - } else { - // Update existing row. - updateDeviceRegistrationEntity(device, appCredentials, pushToken); - } - } else { - // Multiple existing rows have been found. This can only occur during lookup by push token. - // It is not clear how original rows should be mapped to new rows because they were not looked up - // using an activation ID. Delete existing rows (unless they were already used in this request) - // and create a new row. - devices.stream().filter(existingDevice -> !usedDeviceRegistrationIds.contains(existingDevice.getId())).forEach(pushDeviceRepository::delete); - device = initDeviceRegistrationEntity(appCredentials, pushToken); - } - device.setTimestampLastRegistered(new Date()); - device.setPlatform(convert(platform)); - updateActivationForDevice(device, activationId); - PushDeviceRegistrationEntity registeredDevice = pushDeviceRepository.save(device); - usedDeviceRegistrationIds.add(registeredDevice.getId()); - } catch (PushServerException ex) { - logger.error(ex.getMessage(), ex); - registrationFailed.set(true); - } - }); - - if (registrationFailed.get()) { - throw new PushServerException("Device registration failed"); - } - logger.info("The createDeviceMultipleActivations request succeeded, app ID: {}, activation IDs: {}, platform: {}", requestedObject.getAppId(), requestedObject.getActivationIds(), requestedObject.getPlatform()); - return new Response(); - } - - private static Platform convert(final MobilePlatform source) { - return switch (source) { - case IOS -> Platform.IOS; - case ANDROID -> Platform.ANDROID; - }; - } - - /** - * Initialize a new device registration entity for given app ID and push token. - * @param app AppCredentialsEntity instance. - * @param pushToken Push token. - * @return New device registration entity. - */ - private PushDeviceRegistrationEntity initDeviceRegistrationEntity(AppCredentialsEntity app, String pushToken) { - PushDeviceRegistrationEntity device = new PushDeviceRegistrationEntity(); - device.setAppCredentials(app); - device.setPushToken(pushToken); - return device; - } - - /** - * Update a device registration entity with given app ID and push token. - * @param app AppCredentialsEntity instance. - * @param pushToken Push token. - */ - private void updateDeviceRegistrationEntity(PushDeviceRegistrationEntity device, AppCredentialsEntity app, String pushToken) { - device.setAppCredentials(app); - device.setPushToken(pushToken); - } - - /** - * Lookup device registrations using app ID, activation ID and push token. - *
- * The query priorities are ranging from most exact to least exact match: - *
    - *
  • Lookup by activation ID and push token.
  • - *
  • Lookup by activation ID.
  • - *
  • Lookup by application ID and push token.
  • - *
- * @param appId Application ID. - * @param activationId Activation ID. - * @param pushToken Push token. - * @return List of found device registration entities. - */ - private List lookupDeviceRegistrations(String appId, String activationId, String pushToken) throws PushServerException { - List deviceRegistrations; - // At first, lookup the device registrations by match on activationId and pushToken. - deviceRegistrations = pushDeviceRepository.findByActivationIdAndPushToken(activationId, pushToken); - if (!deviceRegistrations.isEmpty()) { - if (deviceRegistrations.size() != 1) { - throw new PushServerException("Multiple device registrations found during lookup by activation ID and push token. Please delete duplicate rows and make sure database indexes have been applied on push_device_registration table."); - } - return deviceRegistrations; - } - // Second, lookup the device registrations by match on activationId. - deviceRegistrations = pushDeviceRepository.findByActivationId(activationId); - if (!deviceRegistrations.isEmpty()) { - if (deviceRegistrations.size() != 1) { - throw new PushServerException("Multiple device registrations found during lookup by activation ID. Please delete duplicate rows and make sure database indexes have been applied on push_device_registration table."); - } - return deviceRegistrations; - } - // Third, lookup the device registration by match on appId and pushToken. Multiple results can be returned in this case, this is a multi-activation scenario. - deviceRegistrations = pushDeviceRepository.findByAppCredentialsAppIdAndPushToken(appId, pushToken); - // The final result is definitive, either device registrations were found by push token or none were found at all. - return deviceRegistrations; - } - - /** - * Update activation for given device in case activation exists in PowerAuth server and it is not in REMOVED state. - * Otherwise fail the device registration because registration could not be associated with an activation. - * @param device Push device registration entity. - * @param activationId Activation ID. - * @throws PushServerException Throw in case communication with PowerAuth server fails. - */ - private void updateActivationForDevice(PushDeviceRegistrationEntity device, String activationId) throws PushServerException { - try { - final GetActivationStatusResponse activation = client.getActivationStatus(activationId); - if (activation != null && !ActivationStatus.REMOVED.equals(activation.getActivationStatus())) { - device.setActivationId(activationId); - device.setActive(activation.getActivationStatus().equals(ActivationStatus.ACTIVE)); - device.setUserId(activation.getUserId()); - return; - } - throw new PushServerException("Device registration failed because associated activation is not ACTIVE"); - } catch (PowerAuthClientException ex) { - logger.warn(ex.getMessage(), ex); - throw new PushServerException("Device registration failed because activation status is unknown"); - } + return pushDeviceService.createDeviceMultipleActivations(request.getRequestObject()); } /** @@ -337,34 +82,7 @@ private void updateActivationForDevice(PushDeviceRegistrationEntity device, Stri description = "Update the status of given device registration based on the associated activation ID. " + "This can help assure that registration is in non-active state and cannot receive personal messages.") public Response updateDeviceStatus(@RequestBody UpdateDeviceStatusRequest request) throws PushServerException { - try { - if (request == null) { - throw new PushServerException("Request object must not be empty"); - } - logger.info("Received updateDeviceStatus request, activation ID: {}", request.getActivationId()); - String errorMessage = UpdateDeviceStatusRequestValidator.validate(request); - if (errorMessage != null) { - throw new PushServerException(errorMessage); - } - String activationId = request.getActivationId(); - ActivationStatus activationStatus = request.getActivationStatus(); - List device = pushDeviceRepository.findByActivationId(activationId); - if (device != null) { - if (activationStatus == null) { - // Activation status was not received via callback data, retrieve it from PowerAuth server - activationStatus = client.getActivationStatus(activationId).getActivationStatus(); - } - for (PushDeviceRegistrationEntity registration: device) { - registration.setActive(activationStatus.equals(ActivationStatus.ACTIVE)); - pushDeviceRepository.save(registration); - } - } - logger.info("The updateDeviceStatus request succeeded, activation ID: {}", request.getActivationId()); - return new Response(); - } catch (PowerAuthClientException ex) { - logger.warn(ex.getMessage(), ex); - throw new PushServerException("Update device status failed because activation status is unknown"); - } + return pushDeviceService.updateDeviceStatus(request); } /** @@ -378,37 +96,7 @@ public Response updateDeviceStatus(@RequestBody UpdateDeviceStatusRequest reques description = "Remove device identified by application ID and device token. " + "If device identifiers don't match, nothing happens") public Response deleteDevice(@RequestBody ObjectRequest request) throws PushServerException { - DeleteDeviceRequest requestObject = request.getRequestObject(); - if (requestObject == null) { - throw new PushServerException("Request object must not be empty"); - } - logger.info("Received deleteDevice request, app ID: {}, token: {}", requestObject.getAppId(), maskPushToken(requestObject.getToken())); - String errorMessage = DeleteDeviceRequestValidator.validate(requestObject); - if (errorMessage != null) { - throw new PushServerException(errorMessage); - } - String appId = requestObject.getAppId(); - String pushToken = requestObject.getToken(); - List devices = pushDeviceRepository.findByAppCredentialsAppIdAndPushToken(appId, pushToken); - pushDeviceRepository.deleteAll(devices); - logger.info("The deleteDevice request succeeded, app ID: {}", requestObject.getAppId()); - return new Response(); + return pushDeviceService.deleteDevice(request.getRequestObject()); } - /** - * Mask push token String to avoid revealing the full token in logs. - * @param token Push token. - * @return Masked push token. - */ - private String maskPushToken(String token) { - if (token == null || token.length() < 6) { - return token; - } - return token.substring(0, 6) + "..."; - } - - private AppCredentialsEntity findAppCredentials(String powerAuthAppId) throws PushServerException { - return appCredentialsRepository.findFirstByAppId(powerAuthAppId).orElseThrow(() -> - new PushServerException("Application with given ID does not exist")); - } -} \ No newline at end of file +} diff --git a/powerauth-push-server/src/main/java/io/getlime/push/service/DeviceRegistrationService.java b/powerauth-push-server/src/main/java/io/getlime/push/service/DeviceRegistrationService.java new file mode 100644 index 000000000..2998ebf4e --- /dev/null +++ b/powerauth-push-server/src/main/java/io/getlime/push/service/DeviceRegistrationService.java @@ -0,0 +1,267 @@ +/* + * Copyright 2024 Wultra s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getlime.push.service; + +import com.wultra.security.powerauth.client.PowerAuthClient; +import com.wultra.security.powerauth.client.model.enumeration.ActivationStatus; +import com.wultra.security.powerauth.client.model.error.PowerAuthClientException; +import com.wultra.security.powerauth.client.model.response.GetActivationStatusResponse; +import io.getlime.push.errorhandling.exceptions.PushServerException; +import io.getlime.push.model.enumeration.MobilePlatform; +import io.getlime.push.model.request.CreateDeviceForActivationsRequest; +import io.getlime.push.model.request.CreateDeviceRequest; +import io.getlime.push.model.request.UpdateDeviceStatusRequest; +import io.getlime.push.repository.PushDeviceRepository; +import io.getlime.push.repository.model.AppCredentialsEntity; +import io.getlime.push.repository.model.Platform; +import io.getlime.push.repository.model.PushDeviceRegistrationEntity; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.retry.annotation.Backoff; +import org.springframework.retry.annotation.Retryable; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Service managing registration and persistence of push devices. + * + * @author Jan Pesek, jan.pesek@wultra.com + */ +@Component +@AllArgsConstructor +@Slf4j +public class DeviceRegistrationService { + + private final PushDeviceRepository pushDeviceRepository; + private final PowerAuthClient powerAuthClient; + + @Retryable(retryFor = DataIntegrityViolationException.class, + maxAttemptsExpression = "${powerauth.push.service.registration.retry.maxAttempts:2}", + backoff = @Backoff(delayExpression = "${powerauth.push.service.registration.retry.backoff:100}")) + @Transactional + public void createOrUpdateDevice(final CreateDeviceRequest requestObject, final AppCredentialsEntity appCredentials) throws PushServerException { + final String appId = requestObject.getAppId(); + final String pushToken = requestObject.getToken(); + final MobilePlatform platform = requestObject.getPlatform(); + final String activationId = requestObject.getActivationId(); + + final List devices = lookupDeviceRegistrations(appId, activationId, pushToken); + final PushDeviceRegistrationEntity device; + if (devices.isEmpty()) { + // The device registration is new, create a new entity. + logger.info("Creating new device registration: app ID: {}, activation ID: {}, platform: {}", requestObject.getAppId(), requestObject.getActivationId(), platform); + device = initDeviceRegistrationEntity(appCredentials, pushToken); + } else if (devices.size() == 1) { + // An existing row was found by one of the lookup methods, update this row. This means that either: + // 1. A row with same activation ID and push token is updated, in this case only the last registration timestamp changes. + // 2. A row with same activation ID but different push token is updated. A new push token has been issued by Google or Apple for an activation. + // 3. A row with same push token but different activation ID is updated. The user removed an activation and created a new one, the push token remains the same. + logger.info("Updating existing device registration: app ID: {}, activation ID: {}, platform: {}", requestObject.getAppId(), requestObject.getActivationId(), platform); + device = devices.get(0); + updateDeviceRegistrationEntity(device, appCredentials, pushToken); + } else { + // Multiple existing rows have been found. This can only occur during lookup by push token. + // Push token can be associated with multiple activations only when associated activations are enabled. + // Push device registration must be done using /push/device/create/multi endpoint in this case. + logger.info("Multiple device registrations found: app ID: {}, activation ID: {}, platform: {}", requestObject.getAppId(), requestObject.getActivationId(), platform); + throw new PushServerException("Multiple device registrations found for push token. Use the /push/device/create/multi endpoint for this scenario."); + } + device.setTimestampLastRegistered(new Date()); + device.setPlatform(convert(platform)); + updateActivationForDevice(device, activationId); + pushDeviceRepository.save(device); + } + + @Transactional + public void createOrUpdateDevices(final CreateDeviceForActivationsRequest request, final AppCredentialsEntity appCredentials) throws PushServerException { + final String appId = request.getAppId(); + final String pushToken = request.getToken(); + final MobilePlatform platform = request.getPlatform(); + final List activationIds = request.getActivationIds(); + + // Initialize loop variables. + final AtomicBoolean registrationFailed = new AtomicBoolean(false); + final Set usedDeviceRegistrationIds = new HashSet<>(); + + activationIds.stream().distinct().forEach(activationId -> { + try { + final List devices = lookupDeviceRegistrations(appId, activationId, pushToken); + PushDeviceRegistrationEntity device; + if (devices.isEmpty()) { + // The device registration is new, create a new entity. + device = initDeviceRegistrationEntity(appCredentials, pushToken); + } else if (devices.size() == 1) { + device = devices.get(0); + if (usedDeviceRegistrationIds.contains(device.getId())) { + // The row has already been used within this request. Create a new row instead. + device = initDeviceRegistrationEntity(appCredentials, pushToken); + } else { + // Update existing row. + updateDeviceRegistrationEntity(device, appCredentials, pushToken); + } + } else { + // Multiple existing rows have been found. This can only occur during lookup by push token. + // It is not clear how original rows should be mapped to new rows because they were not looked up + // using an activation ID. Delete existing rows (unless they were already used in this request) + // and create a new row. + devices.stream().filter(existingDevice -> !usedDeviceRegistrationIds.contains(existingDevice.getId())).forEach(pushDeviceRepository::delete); + device = initDeviceRegistrationEntity(appCredentials, pushToken); + } + device.setTimestampLastRegistered(new Date()); + device.setPlatform(convert(platform)); + updateActivationForDevice(device, activationId); + PushDeviceRegistrationEntity registeredDevice = pushDeviceRepository.save(device); + usedDeviceRegistrationIds.add(registeredDevice.getId()); + } catch (PushServerException ex) { + logger.error(ex.getMessage(), ex); + registrationFailed.set(true); + } + }); + + if (registrationFailed.get()) { + throw new PushServerException("Device registration failed"); + } + } + + @Transactional + public void updateStatus(final UpdateDeviceStatusRequest request) throws PushServerException { + final String activationId = request.getActivationId(); + + final List device = pushDeviceRepository.findByActivationId(activationId); + + final ActivationStatus activationStatus = request.getActivationStatus() == null ? fetchActivationStatus(activationId) : request.getActivationStatus(); + + for (PushDeviceRegistrationEntity registration : device) { + registration.setActive(activationStatus == ActivationStatus.ACTIVE); + pushDeviceRepository.save(registration); + } + } + + @Transactional + public void delete(final String appId, final String pushToken) { + final List devices = pushDeviceRepository.findByAppCredentialsAppIdAndPushToken(appId, pushToken); + pushDeviceRepository.deleteAll(devices); + } + + /** + * Lookup device registrations using app ID, activation ID and push token. + *
+ * The query priorities are ranging from most exact to least exact match: + *
    + *
  • Lookup by activation ID and push token.
  • + *
  • Lookup by activation ID.
  • + *
  • Lookup by application ID and push token.
  • + *
+ * @param appId Application ID. + * @param activationId Activation ID. + * @param pushToken Push token. + * @return List of found device registration entities. + */ + private List lookupDeviceRegistrations(String appId, String activationId, String pushToken) throws PushServerException { + // At first, lookup the device registrations by match on activationId and pushToken. + final List deviceRegistrationsByActivationIdAndToken = pushDeviceRepository.findByActivationIdAndPushToken(activationId, pushToken); + if (!deviceRegistrationsByActivationIdAndToken.isEmpty()) { + if (deviceRegistrationsByActivationIdAndToken.size() != 1) { + throw new PushServerException("Multiple device registrations found during lookup by activation ID and push token. Please delete duplicate rows and make sure database indexes have been applied on push_device_registration table."); + } + return deviceRegistrationsByActivationIdAndToken; + } + + // Second, lookup the device registrations by match on activationId. + final List deviceRegistrationsByActivationId = pushDeviceRepository.findByActivationId(activationId); + if (!deviceRegistrationsByActivationId.isEmpty()) { + if (deviceRegistrationsByActivationId.size() != 1) { + throw new PushServerException("Multiple device registrations found during lookup by activation ID. Please delete duplicate rows and make sure database indexes have been applied on push_device_registration table."); + } + return deviceRegistrationsByActivationId; + } + + // Third, lookup the device registration by match on appId and pushToken. Multiple results can be returned in this case, this is a multi-activation scenario. + // The final result is definitive, either device registrations were found by push token or none were found at all. + return pushDeviceRepository.findByAppCredentialsAppIdAndPushToken(appId, pushToken); + } + + /** + * Initialize a new device registration entity for given app ID and push token. + * @param app AppCredentialsEntity instance. + * @param pushToken Push token. + * @return New device registration entity. + */ + private static PushDeviceRegistrationEntity initDeviceRegistrationEntity(AppCredentialsEntity app, String pushToken) { + final PushDeviceRegistrationEntity device = new PushDeviceRegistrationEntity(); + device.setAppCredentials(app); + device.setPushToken(pushToken); + return device; + } + + /** + * Update a device registration entity with given app ID and push token. + * @param app AppCredentialsEntity instance. + * @param pushToken Push token. + */ + private static void updateDeviceRegistrationEntity(PushDeviceRegistrationEntity device, AppCredentialsEntity app, String pushToken) { + device.setAppCredentials(app); + device.setPushToken(pushToken); + } + + /** + * Update activation for given device in case activation exists in PowerAuth server and it is not in REMOVED state. + * Otherwise fail the device registration because registration could not be associated with an activation. + * @param device Push device registration entity. + * @param activationId Activation ID. + * @throws PushServerException Throw in case communication with PowerAuth server fails. + */ + private void updateActivationForDevice(PushDeviceRegistrationEntity device, String activationId) throws PushServerException { + try { + final GetActivationStatusResponse activation = powerAuthClient.getActivationStatus(activationId); + if (activation != null && !ActivationStatus.REMOVED.equals(activation.getActivationStatus())) { + device.setActivationId(activationId); + device.setActive(activation.getActivationStatus().equals(ActivationStatus.ACTIVE)); + device.setUserId(activation.getUserId()); + return; + } + throw new PushServerException("Device registration failed because associated activation is not ACTIVE"); + } catch (PowerAuthClientException ex) { + logger.warn(ex.getMessage(), ex); + throw new PushServerException("Device registration failed because activation status is unknown"); + } + } + + private ActivationStatus fetchActivationStatus(final String activationId) throws PushServerException { + try { + return powerAuthClient.getActivationStatus(activationId).getActivationStatus(); + } catch (PowerAuthClientException ex) { + logger.warn(ex.getMessage(), ex); + throw new PushServerException("Update device status failed because activation status is unknown"); + } + } + + private static Platform convert(final MobilePlatform source) { + return switch (source) { + case IOS -> Platform.IOS; + case ANDROID -> Platform.ANDROID; + }; + } + +} diff --git a/powerauth-push-server/src/main/java/io/getlime/push/service/PushDeviceService.java b/powerauth-push-server/src/main/java/io/getlime/push/service/PushDeviceService.java new file mode 100644 index 000000000..70ea9a5d8 --- /dev/null +++ b/powerauth-push-server/src/main/java/io/getlime/push/service/PushDeviceService.java @@ -0,0 +1,140 @@ +/* + * Copyright 2024 Wultra s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getlime.push.service; + +import io.getlime.core.rest.model.base.response.Response; +import io.getlime.push.configuration.PushServiceConfiguration; +import io.getlime.push.errorhandling.exceptions.PushServerException; +import io.getlime.push.model.request.CreateDeviceForActivationsRequest; +import io.getlime.push.model.request.CreateDeviceRequest; +import io.getlime.push.model.request.DeleteDeviceRequest; +import io.getlime.push.model.request.UpdateDeviceStatusRequest; +import io.getlime.push.model.validator.CreateDeviceRequestValidator; +import io.getlime.push.model.validator.DeleteDeviceRequestValidator; +import io.getlime.push.model.validator.UpdateDeviceStatusRequestValidator; +import io.getlime.push.repository.AppCredentialsRepository; +import io.getlime.push.repository.model.AppCredentialsEntity; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +/** + * Service for managing push devices. + * + * @author Jan Pesek, jan.pesek@wultra.com + */ +@Service +@AllArgsConstructor +@Slf4j +public class PushDeviceService { + + private final DeviceRegistrationService deviceRegistrationService; + private final AppCredentialsRepository appCredentialsRepository; + private final PushServiceConfiguration config; + + public Response createDevice(final CreateDeviceRequest request) throws PushServerException { + if (request == null) { + throw new PushServerException("Request object must not be empty"); + } + logger.info("Received createDevice request, app ID: {}, activation ID: {}, token: {}, platform: {}", request.getAppId(), + request.getActivationId(), maskPushToken(request.getToken()), request.getPlatform()); + + final String errorMessage = CreateDeviceRequestValidator.validate(request); + if (errorMessage != null) { + throw new PushServerException(errorMessage); + } + + final AppCredentialsEntity appCredentials = findAppCredentials(request.getAppId()); + + deviceRegistrationService.createOrUpdateDevice(request, appCredentials); + logger.info("The createDevice request succeeded, app ID: {}, activation ID: {}, platform: {}", request.getAppId(), request.getActivationId(), request.getPlatform()); + return new Response(); + } + + public Response createDeviceMultipleActivations(final CreateDeviceForActivationsRequest request) throws PushServerException { + if (!config.isRegistrationOfMultipleActivationsEnabled()) { + throw new PushServerException("Registration of multiple associated activations per device is not enabled."); + } + + if (request == null) { + throw new PushServerException("Request object must not be empty"); + } + logger.info("Received createDeviceMultipleActivations request, app ID: {}, activation IDs: {}, token: {}, platform: {}", + request.getAppId(), request.getActivationIds(), maskPushToken(request.getToken()), request.getPlatform()); + + final String errorMessage = CreateDeviceRequestValidator.validate(request); + if (errorMessage != null) { + throw new PushServerException(errorMessage); + } + + final AppCredentialsEntity appCredentials = findAppCredentials(request.getAppId()); + + deviceRegistrationService.createOrUpdateDevices(request, appCredentials); + logger.info("The createDeviceMultipleActivations request succeeded, app ID: {}, activation IDs: {}, platform: {}", request.getAppId(), request.getActivationIds(), request.getPlatform()); + return new Response(); + } + + public Response updateDeviceStatus(final UpdateDeviceStatusRequest request) throws PushServerException { + if (request == null) { + throw new PushServerException("Request object must not be empty"); + } + logger.info("Received updateDeviceStatus request, activation ID: {}", request.getActivationId()); + + final String errorMessage = UpdateDeviceStatusRequestValidator.validate(request); + if (errorMessage != null) { + throw new PushServerException(errorMessage); + } + + deviceRegistrationService.updateStatus(request); + logger.info("The updateDeviceStatus request succeeded, activation ID: {}", request.getActivationId()); + return new Response(); + } + + public Response deleteDevice(final DeleteDeviceRequest request) throws PushServerException { + if (request == null) { + throw new PushServerException("Request object must not be empty"); + } + logger.info("Received deleteDevice request, app ID: {}, token: {}", request.getAppId(), maskPushToken(request.getToken())); + + final String errorMessage = DeleteDeviceRequestValidator.validate(request); + if (errorMessage != null) { + throw new PushServerException(errorMessage); + } + + deviceRegistrationService.delete(request.getAppId(), request.getToken()); + logger.info("The deleteDevice request succeeded, app ID: {}", request.getAppId()); + return new Response(); + } + + private AppCredentialsEntity findAppCredentials(String powerAuthAppId) throws PushServerException { + return appCredentialsRepository.findFirstByAppId(powerAuthAppId).orElseThrow(() -> + new PushServerException("Application with given ID does not exist")); + } + + /** + * Mask push token String to avoid revealing the full token in logs. + * @param token Push token. + * @return Masked push token. + */ + private static String maskPushToken(final String token) { + if (token == null || token.length() < 6) { + return token; + } + return token.substring(0, 6) + "..."; + } + +} diff --git a/powerauth-push-server/src/main/resources/application-dev.properties b/powerauth-push-server/src/main/resources/application-dev.properties index 524c75a0d..d627f39d5 100644 --- a/powerauth-push-server/src/main/resources/application-dev.properties +++ b/powerauth-push-server/src/main/resources/application-dev.properties @@ -1,3 +1,5 @@ # Liquibase spring.liquibase.enabled=true spring.liquibase.change-log=classpath:db/changelog/db.changelog-module.xml + +powerauth.push.service.registration.multipleActivations.enabled=true diff --git a/powerauth-push-server/src/main/resources/application.properties b/powerauth-push-server/src/main/resources/application.properties index ffc8842d4..07ed5957c 100644 --- a/powerauth-push-server/src/main/resources/application.properties +++ b/powerauth-push-server/src/main/resources/application.properties @@ -49,8 +49,8 @@ powerauth.push.service.message.storage.enabled=false powerauth.push.service.registration.multipleActivations.enabled=false # Retry logic of creating a new device registration -powerauth.push.service.registration.retry.backoff=100ms -powerauth.push.service.registration.retry.maxAttempts=3 +powerauth.push.service.registration.retry.backoff=100 +powerauth.push.service.registration.retry.maxAttempts=2 # APNs Configuration powerauth.push.service.apns.useDevelopment=true diff --git a/powerauth-push-server/src/test/java/io/getlime/push/service/DeviceRegistrationServiceTest.java b/powerauth-push-server/src/test/java/io/getlime/push/service/DeviceRegistrationServiceTest.java new file mode 100644 index 000000000..bff5c7fea --- /dev/null +++ b/powerauth-push-server/src/test/java/io/getlime/push/service/DeviceRegistrationServiceTest.java @@ -0,0 +1,306 @@ +/* + * Copyright 2024 Wultra s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getlime.push.service; + +import com.wultra.security.powerauth.client.PowerAuthClient; +import com.wultra.security.powerauth.client.model.enumeration.ActivationStatus; +import com.wultra.security.powerauth.client.model.response.GetActivationStatusResponse; +import io.getlime.push.errorhandling.exceptions.PushServerException; +import io.getlime.push.model.enumeration.MobilePlatform; +import io.getlime.push.model.request.CreateDeviceForActivationsRequest; +import io.getlime.push.model.request.CreateDeviceRequest; +import io.getlime.push.model.request.UpdateDeviceStatusRequest; +import io.getlime.push.repository.AppCredentialsRepository; +import io.getlime.push.repository.PushDeviceRepository; +import io.getlime.push.repository.model.AppCredentialsEntity; +import io.getlime.push.repository.model.Platform; +import io.getlime.push.repository.model.PushDeviceRegistrationEntity; +import jakarta.transaction.Transactional; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.jdbc.Sql; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * Test of {@link DeviceRegistrationService} + * + * @author Jan Pesek, jan.pesek@wultra.com + */ +@SpringBootTest +@ActiveProfiles("test") +@Transactional +class DeviceRegistrationServiceTest { + + private static final String APP_NAME = "my_app"; + + @Autowired + private DeviceRegistrationService tested; + + @Autowired + private PushDeviceRepository deviceRepository; + + @Autowired + private AppCredentialsRepository appCredentialsRepository; + + @MockBean + private PowerAuthClient powerAuthClient; + + @Test + void testCreateOrUpdateDevice_createNew() throws Exception { + final AppCredentialsEntity credentials = createAppCredentials(APP_NAME); + when(powerAuthClient.getActivationStatus("a1")) + .thenReturn(createActivationStatusResponse("a1")); + + final CreateDeviceRequest request = new CreateDeviceRequest(); + request.setAppId(APP_NAME); + request.setActivationId("a1"); + request.setToken("t1"); + request.setPlatform(MobilePlatform.ANDROID); + + tested.createOrUpdateDevice(request, credentials); + + assertRegistrationExists("a1", "t1"); + } + + @Test + @DirtiesContext + void testCreateDevice_parallel() throws Exception { + when(powerAuthClient.getActivationStatus("a1")) + .thenReturn(createActivationStatusResponse("a1")); + + final int nThreads = 5; + final ExecutorService executorService = Executors.newFixedThreadPool(nThreads); + final List> callableTasks = new ArrayList<>(); + for (int i = 0; i < nThreads; i++) { + callableTasks.add(() -> { + final AppCredentialsEntity credentials = createAppCredentials(APP_NAME); + + final CreateDeviceRequest request = new CreateDeviceRequest(); + request.setAppId(APP_NAME); + request.setActivationId("a1"); + request.setToken("t1"); + request.setPlatform(MobilePlatform.IOS); + tested.createOrUpdateDevice(request, credentials); + return null; + }); + } + + executorService.invokeAll(callableTasks).forEach(future -> assertDoesNotThrow(() -> future.get())); + executorService.shutdown(); + + assertRegistrationExists("a1", "t1"); + } + + @Test + @Sql + void testCreateOrUpdateDevice_multipleRecords() throws Exception { + final AppCredentialsEntity credentials = appCredentialsRepository.findById(1L).get(); + when(powerAuthClient.getActivationStatus("a1")) + .thenReturn(createActivationStatusResponse("a1")); + + final CreateDeviceRequest request = new CreateDeviceRequest(); + request.setAppId(APP_NAME); + request.setActivationId("a1"); + request.setToken("t1"); + request.setPlatform(MobilePlatform.ANDROID); + + final PushServerException ex = assertThrows(PushServerException.class, + () -> tested.createOrUpdateDevice(request, credentials)); + assertEquals("Multiple device registrations found for push token. Use the /push/device/create/multi endpoint for this scenario.", ex.getMessage()); + } + + @Test + void testCreateOrUpdateDevices_createNew() throws Exception { + final AppCredentialsEntity credentials = createAppCredentials(APP_NAME); + when(powerAuthClient.getActivationStatus("a1")) + .thenReturn(createActivationStatusResponse("a1")); + when(powerAuthClient.getActivationStatus("a2")) + .thenReturn(createActivationStatusResponse("a2")); + + final CreateDeviceForActivationsRequest request = new CreateDeviceForActivationsRequest(); + request.setAppId(APP_NAME); + request.getActivationIds().addAll(List.of("a1", "a2")); + request.setToken("t1"); + request.setPlatform(MobilePlatform.ANDROID); + + tested.createOrUpdateDevices(request, credentials); + + assertRegistrationExists("a1", "t1"); + assertRegistrationExists("a2", "t1"); + } + + @Test + void testCreateOrUpdateDevices_withDuplicities() throws Exception { + final AppCredentialsEntity credentials = createAppCredentials(APP_NAME); + when(powerAuthClient.getActivationStatus("a1")) + .thenReturn(createActivationStatusResponse("a1")); + + final CreateDeviceForActivationsRequest request = new CreateDeviceForActivationsRequest(); + request.setAppId(APP_NAME); + request.getActivationIds().addAll(List.of("a1", "a1")); + request.setToken("t1"); + request.setPlatform(MobilePlatform.ANDROID); + + tested.createOrUpdateDevices(request, credentials); + + assertRegistrationExists("a1", "t1"); + } + + @Test + @Sql + void testCreateOrUpdateDevices_update() throws Exception { + final AppCredentialsEntity credentials = appCredentialsRepository.findById(1L).get(); + when(powerAuthClient.getActivationStatus("a1")) + .thenReturn(createActivationStatusResponse("a1")); + when(powerAuthClient.getActivationStatus("a2")) + .thenReturn(createActivationStatusResponse("a2")); + + final CreateDeviceForActivationsRequest request = new CreateDeviceForActivationsRequest(); + request.setAppId(APP_NAME); + request.getActivationIds().addAll(List.of("a1", "a2")); + request.setToken("t1_new"); + request.setPlatform(MobilePlatform.ANDROID); + + tested.createOrUpdateDevices(request, credentials); + + assertRegistrationExists("a1", "t1_new"); + assertRegistrationExists("a2", "t1_new"); + } + + @Test + @Sql + void testCreateOrUpdateDevices_multipleRecords() throws Exception { + final AppCredentialsEntity credentials = createAppCredentials(APP_NAME); + when(powerAuthClient.getActivationStatus("a1")) + .thenReturn(createActivationStatusResponse("a1")); + + final CreateDeviceForActivationsRequest request = new CreateDeviceForActivationsRequest(); + request.setAppId(APP_NAME); + request.getActivationIds().add("a1"); + request.setToken("t1"); + request.setPlatform(MobilePlatform.ANDROID); + + assertRegistrationExists("a_other", "t1"); + assertRegistrationExists("a_different", "t1"); + + tested.createOrUpdateDevices(request, credentials); + + assertRegistrationDoesNotExist("a_other"); + assertRegistrationDoesNotExist("a_different"); + assertRegistrationExists("a1", "t1"); + } + + @Test + void testUpdateStatus_statusInRequest() throws Exception { + final PushDeviceRegistrationEntity device = new PushDeviceRegistrationEntity(); + device.setActivationId("a1"); + device.setAppCredentials(createAppCredentials(APP_NAME)); + device.setTimestampLastRegistered(new Date()); + device.setPlatform(Platform.IOS); + device.setPushToken("t1"); + device.setActive(false); + deviceRepository.save(device); + + final UpdateDeviceStatusRequest request = new UpdateDeviceStatusRequest(); + request.setActivationId("a1"); + request.setActivationStatus(ActivationStatus.ACTIVE); + + tested.updateStatus(request); + + final List entities2 = deviceRepository.findByActivationId("a1"); + assertEquals(1, entities2.size()); + assertTrue(entities2.get(0).getActive()); + } + + @Test + void testUpdateStatus_missingStatusInRequest() throws Exception { + final PushDeviceRegistrationEntity device = new PushDeviceRegistrationEntity(); + device.setActivationId("a1"); + device.setAppCredentials(createAppCredentials(APP_NAME)); + device.setTimestampLastRegistered(new Date()); + device.setPlatform(Platform.IOS); + device.setPushToken("t1"); + device.setActive(false); + deviceRepository.save(device); + + when(powerAuthClient.getActivationStatus("a1")) + .thenReturn(createActivationStatusResponse("a1")); + + final List entities = deviceRepository.findByActivationId("a1"); + assertEquals(1, entities.size()); + assertFalse(entities.get(0).getActive()); + + final UpdateDeviceStatusRequest request = new UpdateDeviceStatusRequest(); + request.setActivationId("a1"); + + tested.updateStatus(request); + verify(powerAuthClient).getActivationStatus("a1"); + + final List entities2 = deviceRepository.findByActivationId("a1"); + assertEquals(1, entities2.size()); + assertTrue(entities2.get(0).getActive()); + } + + @Test + @Sql + void testDelete() { + tested.delete(APP_NAME, "t1"); + assertRegistrationDoesNotExist("a1"); + assertRegistrationDoesNotExist("a2"); + } + + private GetActivationStatusResponse createActivationStatusResponse(final String activationId) { + final GetActivationStatusResponse activationResponse = new GetActivationStatusResponse(); + activationResponse.setActivationId(activationId); + activationResponse.setActivationStatus(ActivationStatus.ACTIVE); + activationResponse.setUserId("joe"); + return activationResponse; + } + + private AppCredentialsEntity createAppCredentials(String appName) { + final AppCredentialsEntity credentials = new AppCredentialsEntity(); + credentials.setAppId(appName); + return appCredentialsRepository.save(credentials); + } + + private void assertRegistrationExists(final String activationId, final String token) { + final List entities = deviceRepository.findByActivationId(activationId); + assertEquals(1, entities.size()); + assertEquals(activationId, entities.get(0).getActivationId()); + assertEquals(token, entities.get(0).getPushToken()); + } + + private void assertRegistrationDoesNotExist(final String activationId) { + final List entities = deviceRepository.findByActivationId(activationId); + assertEquals(0, entities.size()); + } + +} diff --git a/powerauth-push-server/src/test/java/io/getlime/push/service/PushDeviceServiceTest.java b/powerauth-push-server/src/test/java/io/getlime/push/service/PushDeviceServiceTest.java new file mode 100644 index 000000000..786664900 --- /dev/null +++ b/powerauth-push-server/src/test/java/io/getlime/push/service/PushDeviceServiceTest.java @@ -0,0 +1,199 @@ +/* + * Copyright 2024 Wultra s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getlime.push.service; + +import io.getlime.core.rest.model.base.response.Response; +import io.getlime.push.configuration.PushServiceConfiguration; +import io.getlime.push.errorhandling.exceptions.PushServerException; +import io.getlime.push.model.enumeration.MobilePlatform; +import io.getlime.push.model.request.CreateDeviceForActivationsRequest; +import io.getlime.push.model.request.CreateDeviceRequest; +import io.getlime.push.model.request.DeleteDeviceRequest; +import io.getlime.push.model.request.UpdateDeviceStatusRequest; +import io.getlime.push.repository.AppCredentialsRepository; +import io.getlime.push.repository.model.AppCredentialsEntity; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.List; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * Tests of {@link PushDeviceService} + * + * @author Jan Pesek, jan.pesek@wultra.com + */ +@ExtendWith(MockitoExtension.class) +class PushDeviceServiceTest { + + @Mock + private AppCredentialsRepository appCredentialsRepository; + + @Mock + private DeviceRegistrationService deviceRegistrationService; + + @Mock + private PushServiceConfiguration config; + + @InjectMocks + private PushDeviceService tested; + + @Test + void testCreateDevice_success() throws Exception { + final AppCredentialsEntity credentials = new AppCredentialsEntity(); + when(appCredentialsRepository.findFirstByAppId("my_app")) + .thenReturn(Optional.of(credentials)); + + final CreateDeviceRequest request = new CreateDeviceRequest(); + request.setAppId("my_app"); + request.setActivationId("a1"); + request.setToken("t1"); + request.setPlatform(MobilePlatform.ANDROID); + + final Response response = tested.createDevice(request); + verify(deviceRegistrationService).createOrUpdateDevice(request, credentials); + assertEquals("OK", response.getStatus()); + } + + @Test + void testCreateDevice_invalidRequest() { + final PushServerException exception = assertThrows(PushServerException.class, + () -> tested.createDevice(new CreateDeviceRequest())); + assertEquals("App ID must not be null.", exception.getMessage()); + } + + @Test + void testCreateDevice_invalidApp() { + when(appCredentialsRepository.findFirstByAppId("non_existent")) + .thenReturn(Optional.empty()); + + final CreateDeviceRequest request = new CreateDeviceRequest(); + request.setAppId("non_existent"); + request.setActivationId("a1"); + request.setToken("t1"); + request.setPlatform(MobilePlatform.ANDROID); + + final PushServerException exception = assertThrows(PushServerException.class, + () -> tested.createDevice(request)); + assertEquals("Application with given ID does not exist", exception.getMessage()); + } + + @Test + void testCreateDeviceMultipleActivations_success() throws Exception { + when(config.isRegistrationOfMultipleActivationsEnabled()) + .thenReturn(true); + final AppCredentialsEntity credentials = new AppCredentialsEntity(); + when(appCredentialsRepository.findFirstByAppId("my_app")) + .thenReturn(Optional.of(credentials)); + + final CreateDeviceForActivationsRequest request = new CreateDeviceForActivationsRequest(); + request.setAppId("my_app"); + request.setPlatform(MobilePlatform.ANDROID); + request.setToken("t2"); + request.getActivationIds().addAll(List.of("a1", "a2")); + + final Response response = tested.createDeviceMultipleActivations(request); + verify(deviceRegistrationService).createOrUpdateDevices(request, credentials); + assertEquals("OK", response.getStatus()); + } + + @Test + void testCreateDeviceMultipleActivations_disabled() { + when(config.isRegistrationOfMultipleActivationsEnabled()) + .thenReturn(false); + + final PushServerException exception = assertThrows(PushServerException.class, + () -> tested.createDeviceMultipleActivations(new CreateDeviceForActivationsRequest())); + assertEquals("Registration of multiple associated activations per device is not enabled.", exception.getMessage()); + } + + @Test + void testCreateDeviceMultipleActivations_invalidRequest() { + when(config.isRegistrationOfMultipleActivationsEnabled()) + .thenReturn(true); + + final CreateDeviceForActivationsRequest request = new CreateDeviceForActivationsRequest(); + + final PushServerException exception = assertThrows(PushServerException.class, + () -> tested.createDeviceMultipleActivations(request)); + assertEquals("App ID must not be null.", exception.getMessage()); + } + + @Test + void testCreateDeviceMultipleActivations_invalidApp() { + when(config.isRegistrationOfMultipleActivationsEnabled()) + .thenReturn(true); + when(appCredentialsRepository.findFirstByAppId("non-existent")) + .thenReturn(Optional.empty()); + + final CreateDeviceForActivationsRequest request = new CreateDeviceForActivationsRequest(); + request.setAppId("non-existent"); + request.setPlatform(MobilePlatform.ANDROID); + request.setToken("t2"); + request.getActivationIds().addAll(List.of("a1", "a2")); + + final PushServerException exception = assertThrows(PushServerException.class, + () -> tested.createDeviceMultipleActivations(request)); + assertEquals("Application with given ID does not exist", exception.getMessage()); + } + + @Test + void testUpdateDeviceStatus_success() throws Exception { + final UpdateDeviceStatusRequest request = new UpdateDeviceStatusRequest(); + request.setActivationId("a1"); + + final Response response = tested.updateDeviceStatus(request); + assertEquals("OK", response.getStatus()); + } + + @Test + void testUpdateDeviceStatus_invalidRequest() { + final UpdateDeviceStatusRequest request = new UpdateDeviceStatusRequest(); + + final PushServerException exception = assertThrows(PushServerException.class, + () -> tested.updateDeviceStatus(request)); + assertEquals("Activation ID must not be null.", exception.getMessage()); + } + + @Test + void testDeleteDevice_success() throws Exception { + final DeleteDeviceRequest request = new DeleteDeviceRequest(); + request.setAppId("app1"); + request.setToken("token1"); + + final Response response = tested.deleteDevice(request); + assertEquals("OK", response.getStatus()); + } + + @Test + void testDeleteDevice_invalidRequest() { + final DeleteDeviceRequest request = new DeleteDeviceRequest(); + request.setAppId("app1"); + + final PushServerException exception = assertThrows(PushServerException.class, + () -> tested.deleteDevice(request)); + assertEquals("Push token must not be null or empty.", exception.getMessage()); + } + +} diff --git a/powerauth-push-server/src/test/resources/io/getlime/push/service/DeviceRegistrationServiceTest.testCreateOrUpdateDevice_multipleRecords.sql b/powerauth-push-server/src/test/resources/io/getlime/push/service/DeviceRegistrationServiceTest.testCreateOrUpdateDevice_multipleRecords.sql new file mode 100644 index 000000000..5a8099445 --- /dev/null +++ b/powerauth-push-server/src/test/resources/io/getlime/push/service/DeviceRegistrationServiceTest.testCreateOrUpdateDevice_multipleRecords.sql @@ -0,0 +1,5 @@ +INSERT INTO push_app_credentials (id, app_id) VALUES (1, 'my_app'); + +INSERT INTO push_device_registration (id, activation_id, platform, push_token, timestamp_last_registered, app_id) + VALUES (1, 'a_other', 'ios', 't1', now(), 1), + (2, 'a_different', 'ios', 't1', now(), 1); diff --git a/powerauth-push-server/src/test/resources/io/getlime/push/service/DeviceRegistrationServiceTest.testCreateOrUpdateDevices_multipleRecords.sql b/powerauth-push-server/src/test/resources/io/getlime/push/service/DeviceRegistrationServiceTest.testCreateOrUpdateDevices_multipleRecords.sql new file mode 100644 index 000000000..05b184330 --- /dev/null +++ b/powerauth-push-server/src/test/resources/io/getlime/push/service/DeviceRegistrationServiceTest.testCreateOrUpdateDevices_multipleRecords.sql @@ -0,0 +1,5 @@ +INSERT INTO push_app_credentials (id, app_id) VALUES (1, 'my_app'); + +INSERT INTO push_device_registration (id, activation_id, platform, push_token, timestamp_last_registered, app_id) + VALUES (1, 'a_different', 'ios', 't1', now(), 1), + (2, 'a_other', 'android', 't1', now(), 1); diff --git a/powerauth-push-server/src/test/resources/io/getlime/push/service/DeviceRegistrationServiceTest.testCreateOrUpdateDevices_update.sql b/powerauth-push-server/src/test/resources/io/getlime/push/service/DeviceRegistrationServiceTest.testCreateOrUpdateDevices_update.sql new file mode 100644 index 000000000..845333594 --- /dev/null +++ b/powerauth-push-server/src/test/resources/io/getlime/push/service/DeviceRegistrationServiceTest.testCreateOrUpdateDevices_update.sql @@ -0,0 +1,5 @@ +INSERT INTO push_app_credentials (id, app_id) VALUES (1, 'my_app'); + +INSERT INTO push_device_registration (id, activation_id, platform, push_token, timestamp_last_registered, app_id) + VALUES (1, 'a1', 'android', 't1', now(), 1), + (2, 'a2', 'ios', 't1', now(), 1); diff --git a/powerauth-push-server/src/test/resources/io/getlime/push/service/DeviceRegistrationServiceTest.testDelete.sql b/powerauth-push-server/src/test/resources/io/getlime/push/service/DeviceRegistrationServiceTest.testDelete.sql new file mode 100644 index 000000000..f00ed1fb9 --- /dev/null +++ b/powerauth-push-server/src/test/resources/io/getlime/push/service/DeviceRegistrationServiceTest.testDelete.sql @@ -0,0 +1,5 @@ +INSERT INTO push_app_credentials (id, app_id) VALUES (1, 'my_app'); + +INSERT INTO push_device_registration (id, activation_id, platform, push_token, timestamp_last_registered, app_id) + VALUES (1, 'a1', 'ios', 't1', now(), 1), + (2, 'a2', 'android', 't1', now(), 1); From a2e206f6e0dc108b927ca8e47678bf267660aa68 Mon Sep 17 00:00:00 2001 From: Jan Dusil <134381434+jandusil@users.noreply.github.com> Date: Tue, 6 Feb 2024 11:08:45 +0100 Subject: [PATCH 23/44] Fix #770: Remove spring.datasource.driverClassName from app props (#771) --- docs/Configuration-Properties.md | 1 - docs/Deploying-Push-Server.md | 1 - powerauth-push-server/src/main/resources/application.properties | 2 -- .../resources/application-test-multiple-activations.properties | 1 - .../src/test/resources/application-test.properties | 1 - 5 files changed, 6 deletions(-) diff --git a/docs/Configuration-Properties.md b/docs/Configuration-Properties.md index 7e96bbd2d..3a1f8eb1d 100644 --- a/docs/Configuration-Properties.md +++ b/docs/Configuration-Properties.md @@ -9,7 +9,6 @@ The Push Server uses the following public configuration properties: | `spring.datasource.url` | `jdbc:postgresql://localhost:5432/powerauth` | Database JDBC URL | | `spring.datasource.username` | `powerauth` | Database JDBC username | | `spring.datasource.password` | `_empty_` | Database JDBC password | -| `spring.datasource.driver-class-name` | `org.postgresql.Driver` | Datasource JDBC class name | | `spring.jpa.properties.hibernate.connection.characterEncoding` | `utf8` | Character encoding | | `spring.jpa.properties.hibernate.connection.useUnicode` | `true` | Character encoding - Unicode support | diff --git a/docs/Deploying-Push-Server.md b/docs/Deploying-Push-Server.md index 2c7737ca2..a668c5017 100644 --- a/docs/Deploying-Push-Server.md +++ b/docs/Deploying-Push-Server.md @@ -26,7 +26,6 @@ The default database connectivity parameters in `powerauth-push-server.war` are spring.datasource.url=jdbc:postgresql://localhost:5432/powerauth spring.datasource.username=powerauth spring.datasource.password= -spring.datasource.driver-class-name=org.postgresql.Driver spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults=false spring.jpa.properties.hibernate.connection.characterEncoding=utf8 spring.jpa.properties.hibernate.connection.useUnicode=true diff --git a/powerauth-push-server/src/main/resources/application.properties b/powerauth-push-server/src/main/resources/application.properties index 07ed5957c..a8ea0a6d5 100644 --- a/powerauth-push-server/src/main/resources/application.properties +++ b/powerauth-push-server/src/main/resources/application.properties @@ -6,7 +6,6 @@ spring.datasource.url=jdbc:postgresql://localhost:5432/powerauth spring.datasource.username=powerauth spring.datasource.password= spring.datasource.hikari.auto-commit=false -spring.datasource.driver-class-name=org.postgresql.Driver spring.jpa.properties.hibernate.connection.characterEncoding=utf8 spring.jpa.properties.hibernate.connection.useUnicode=true @@ -14,7 +13,6 @@ spring.jpa.properties.hibernate.connection.useUnicode=true #spring.datasource.url=jdbc:oracle:thin:@//127.0.0.1:1521/powerauth #spring.datasource.username=powerauth #spring.datasource.password= -#spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver # The following property speeds up Spring Boot startup #spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults=false diff --git a/powerauth-push-server/src/test/resources/application-test-multiple-activations.properties b/powerauth-push-server/src/test/resources/application-test-multiple-activations.properties index d647d1c0c..89672c4d7 100644 --- a/powerauth-push-server/src/test/resources/application-test-multiple-activations.properties +++ b/powerauth-push-server/src/test/resources/application-test-multiple-activations.properties @@ -22,7 +22,6 @@ spring.h2.console.path=/h2 spring.datasource.url=jdbc:h2:mem:powerauth;MODE=LEGACY spring.datasource.username=sa spring.datasource.password= -spring.datasource.driver-class-name=org.h2.Driver # Hibernate Configuration spring.jpa.hibernate.ddl-auto=create-drop diff --git a/powerauth-push-server/src/test/resources/application-test.properties b/powerauth-push-server/src/test/resources/application-test.properties index 16478d14f..63698a95a 100644 --- a/powerauth-push-server/src/test/resources/application-test.properties +++ b/powerauth-push-server/src/test/resources/application-test.properties @@ -20,7 +20,6 @@ spring.h2.console.path=/h2 spring.datasource.url=jdbc:h2:mem:powerauth;MODE=LEGACY spring.datasource.username=sa spring.datasource.password= -spring.datasource.driver-class-name=org.h2.Driver # Hibernate Configuration spring.jpa.hibernate.ddl-auto=create-drop From fef84669c549b9d4e5985055ba67f1c868b52bb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lubo=C5=A1=20Ra=C4=8Dansk=C3=BD?= Date: Thu, 8 Feb 2024 14:58:45 +0100 Subject: [PATCH 24/44] Fix #443: Add support for Huawei Mobile Services (#750) * Fix #443: Add support for Huawei Mobile Services --- docs/Configuration-Properties.md | 18 +- docs/Migration-Instructions.md | 1 + docs/PowerAuth-Push-Server-1.7.0.md | 11 ++ docs/Push-Server-API.md | 109 +++++++++++- docs/Push-Server-Administration.md | 37 +++- .../20240119-push_app_credentials-hms.xml | 23 +++ .../1.7.x/db.changelog-version.xml | 8 + .../db.changelog-module.xml | 1 + pom.xml | 8 + .../getlime/push/client/PushServerClient.java | 40 ++++- .../model/entity/PushMessageSendResult.java | 7 + .../model/entity/PushServerApplication.java | 52 ++---- .../model/enumeration/MobilePlatform.java | 8 +- .../CreateDeviceForActivationsRequest.java | 4 +- .../model/request/CreateDeviceRequest.java | 4 +- .../request/GetApplicationDetailRequest.java | 15 +- .../model/request/RemoveHuaweiRequest.java | 53 ++++++ .../model/request/UpdateHuaweiRequest.java | 60 +++++++ .../GetApplicationDetailResponse.java | 11 +- powerauth-push-server/pom.xml | 4 + .../PushServiceConfiguration.java | 67 +++++++ .../rest/AdministrationController.java | 70 +++++++- .../model/AppCredentialsEntity.java | 20 ++- .../push/repository/model/Platform.java | 7 +- .../repository/model/PlatformConverter.java | 2 + .../push/service/AppRelatedPushClient.java | 51 +----- .../service/DeviceRegistrationService.java | 1 + .../service/PushMessageSenderService.java | 20 ++- .../push/service/PushSendingWorker.java | 111 ++++++++++++ .../getlime/push/service/hms/HmsClient.java | 156 ++++++++++++++++ .../push/service/hms/HmsSendResponse.java | 25 +++ .../service/hms/request/AndroidConfig.java | 53 ++++++ .../hms/request/AndroidNotification.java | 143 +++++++++++++++ .../push/service/hms/request/ApnsConfig.java | 45 +++++ .../push/service/hms/request/ApnsHeaders.java | 50 ++++++ .../service/hms/request/ApnsHmsOptions.java | 36 ++++ .../hms/request/BadgeNotification.java | 42 +++++ .../push/service/hms/request/Button.java | 45 +++++ .../push/service/hms/request/ClickAction.java | 44 +++++ .../push/service/hms/request/Color.java | 45 +++++ .../service/hms/request/LightSettings.java | 41 +++++ .../push/service/hms/request/Message.java | 56 ++++++ .../service/hms/request/Notification.java | 37 ++++ .../push/service/hms/request/WebActions.java | 38 ++++ .../service/hms/request/WebHmsOptions.java | 33 ++++ .../service/hms/request/WebNotification.java | 68 +++++++ .../service/hms/request/WebPushConfig.java | 41 +++++ .../service/hms/request/WebpushHeaders.java | 38 ++++ .../src/main/resources/application.properties | 13 ++ .../rest/AdministrationControllerTest.java | 166 ++++++++++++++++++ .../push/service/hms/HmsClientTest.java | 157 +++++++++++++++++ .../application-external-service.properties | 3 + 52 files changed, 2072 insertions(+), 126 deletions(-) create mode 100644 docs/PowerAuth-Push-Server-1.7.0.md create mode 100644 docs/db/changelog/changesets/powerauth-push-server/1.7.x/20240119-push_app_credentials-hms.xml create mode 100644 docs/db/changelog/changesets/powerauth-push-server/1.7.x/db.changelog-version.xml create mode 100644 powerauth-push-model/src/main/java/io/getlime/push/model/request/RemoveHuaweiRequest.java create mode 100644 powerauth-push-model/src/main/java/io/getlime/push/model/request/UpdateHuaweiRequest.java create mode 100644 powerauth-push-server/src/main/java/io/getlime/push/service/hms/HmsClient.java create mode 100644 powerauth-push-server/src/main/java/io/getlime/push/service/hms/HmsSendResponse.java create mode 100644 powerauth-push-server/src/main/java/io/getlime/push/service/hms/request/AndroidConfig.java create mode 100644 powerauth-push-server/src/main/java/io/getlime/push/service/hms/request/AndroidNotification.java create mode 100644 powerauth-push-server/src/main/java/io/getlime/push/service/hms/request/ApnsConfig.java create mode 100644 powerauth-push-server/src/main/java/io/getlime/push/service/hms/request/ApnsHeaders.java create mode 100644 powerauth-push-server/src/main/java/io/getlime/push/service/hms/request/ApnsHmsOptions.java create mode 100644 powerauth-push-server/src/main/java/io/getlime/push/service/hms/request/BadgeNotification.java create mode 100644 powerauth-push-server/src/main/java/io/getlime/push/service/hms/request/Button.java create mode 100644 powerauth-push-server/src/main/java/io/getlime/push/service/hms/request/ClickAction.java create mode 100644 powerauth-push-server/src/main/java/io/getlime/push/service/hms/request/Color.java create mode 100644 powerauth-push-server/src/main/java/io/getlime/push/service/hms/request/LightSettings.java create mode 100644 powerauth-push-server/src/main/java/io/getlime/push/service/hms/request/Message.java create mode 100644 powerauth-push-server/src/main/java/io/getlime/push/service/hms/request/Notification.java create mode 100644 powerauth-push-server/src/main/java/io/getlime/push/service/hms/request/WebActions.java create mode 100644 powerauth-push-server/src/main/java/io/getlime/push/service/hms/request/WebHmsOptions.java create mode 100644 powerauth-push-server/src/main/java/io/getlime/push/service/hms/request/WebNotification.java create mode 100644 powerauth-push-server/src/main/java/io/getlime/push/service/hms/request/WebPushConfig.java create mode 100644 powerauth-push-server/src/main/java/io/getlime/push/service/hms/request/WebpushHeaders.java create mode 100644 powerauth-push-server/src/test/java/io/getlime/push/controller/rest/AdministrationControllerTest.java create mode 100644 powerauth-push-server/src/test/java/io/getlime/push/service/hms/HmsClientTest.java create mode 100644 powerauth-push-server/src/test/resources/application-external-service.properties diff --git a/docs/Configuration-Properties.md b/docs/Configuration-Properties.md index 3a1f8eb1d..abf29072c 100644 --- a/docs/Configuration-Properties.md +++ b/docs/Configuration-Properties.md @@ -59,7 +59,7 @@ The Push Server uses the following public configuration properties: | `powerauth.push.service.apns.idlePingInterval` | `60000` | Interval in milliseconds specifying the frequency of APNS ping calls in idle state | | `powerauth.push.service.apns.concurrentConnections` | `1` | Push message concurrency settings | -# FCM Configuration +## FCM Configuration | Property | Default | Note | |---|---|---| @@ -72,6 +72,22 @@ The Push Server uses the following public configuration properties: | `powerauth.push.service.fcm.sendMessageUrl` | `https://fcm.googleapis.com/v1/projects/%s/messages:send` | Default URL for the FCM service | | `powerauth.push.service.fcm.connect.timeout` | `5000` | Push message gateway connect timeout in milliseconds | +## HMS Configuration + +| Property | Default | Note | +|---------------------------------------------------|---------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------| +| `powerauth.push.service.hms.proxy.enabled` | `false` | Flag indicating if the communication needs to go through proxy. | +| `powerauth.push.service.hms.proxy.host` | `127.0.0.1` | Proxy host. | +| `powerauth.push.service.hms.proxy.port` | `8080` | Proxy port. | +| `powerauth.push.service.hms.proxy.username` | `_empty_` | Proxy username. | +| `powerauth.push.service.hms.proxy.password` | `_empty_` | Proxy password. | +| `powerauth.push.service.hms.dataNotificationOnly` | `false` | Flag indicating that HMS service should never use "notification" format, only a data format with extra payload representing the notification. | +| `powerauth.push.service.hms.sendMessageUrl` | `https://push-api.cloud.huawei.com/v2/%s/messages:send` | Default URL for the HMS service. | +| `powerauth.push.service.hms.tokenUrl` | `https://oauth-login.cloud.huawei.com/oauth2/v3/token` | Default URL for the HMS OAuth service to obtain an access token. | +| `powerauth.push.service.hms.connect.timeout` | `5s` | Push message gateway connect timeout. | +| `powerauth.push.service.hms.response.timeout` | `60s` | Push message gateway maximum duration allowed between each network-level read operations. | +| `powerauth.push.service.hms.max-idle-time` | `200s` | Push message gateway ConnectionProvider max idle time. | + ## Correlation HTTP Header Configuration | Property | Default | Note | diff --git a/docs/Migration-Instructions.md b/docs/Migration-Instructions.md index 403c3dc30..4a5193988 100644 --- a/docs/Migration-Instructions.md +++ b/docs/Migration-Instructions.md @@ -2,6 +2,7 @@ This page contains PowerAuth Push Server migration instructions. +- [PowerAuth Push Server 1.7.0](./PowerAuth-Push-Server-1.7.0.md) - [PowerAuth Push Server 1.6.0](./PowerAuth-Push-Server-1.6.0.md) - [PowerAuth Push Server 1.5.0](./PowerAuth-Push-Server-1.5.0.md) - [PowerAuth Push Server 1.4.0](./PowerAuth-Push-Server-1.4.0.md) diff --git a/docs/PowerAuth-Push-Server-1.7.0.md b/docs/PowerAuth-Push-Server-1.7.0.md new file mode 100644 index 000000000..34b70620e --- /dev/null +++ b/docs/PowerAuth-Push-Server-1.7.0.md @@ -0,0 +1,11 @@ +# Migration from 1.6.x to 1.7.x + +This guide contains instructions for migration from PowerAuth Push Server version `1.6.x` to version `1.7.x`. + + +## Database Changes + + +### Huawei Mobile Services + +To support HMS, the columns `hms_project_id`, `hms_client_id`, and `hms_client_secret` have been added into the table `push_app_credentials`. diff --git a/docs/Push-Server-API.md b/docs/Push-Server-API.md index 8b2ea95e1..fb2418b15 100644 --- a/docs/Push-Server-API.md +++ b/docs/Push-Server-API.md @@ -143,7 +143,8 @@ Send a system status response, with basic information about the running applicat ## Device -Represents mobile device with iOS or Android that is capable to receive a push notification. Device has to first register with APNS or FCM to obtain push token. +Represents mobile device with iOS, Android or Huawei that is capable to receive a push notification. +Device has to first register with APNS, FCM, or HMS to obtain push token. Then it has to forward the push token to the push server end-point. After that push server is able to send push notification to the device. @@ -181,7 +182,7 @@ _Note: Since this endpoint is usually called by the back-end service, it is not - `appId` - Application that device is using. - `token` - Identifier for device. -- `platform` - `ios`, `android` +- `platform` - `ios`, `android`, `huawei` - `activationId` - Activation identifier #### Response 200 @@ -231,7 +232,7 @@ _Note: Since this endpoint is usually called by the back-end service, it is not - `appId` - Application that device is using. - `token` - Identifier for device. -- `platform` - `ios`, `android` +- `platform` - `ios`, `android`, `huawei` - `activationIds` - Associated activation identifiers #### Response 200 @@ -412,6 +413,12 @@ Send a single push message to given user via provided application, optionally to "pending": 0, "failed": 0, "total": 1 + }, + "huawei": { + "sent": 1, + "pending": 0, + "failed": 0, + "total": 1 } } } @@ -533,6 +540,12 @@ Sends a message batch - each item in the batch represents a message to given use "pending": 0, "failed": 0, "total": 1 + }, + "huawei": { + "sent": 1, + "pending": 0, + "failed": 0, + "total": 1 } } } @@ -1108,7 +1121,8 @@ Get list of all applications. { "appId": "mobile-app", "ios": true, - "android": true + "android": true, + "huawei": true } ] } @@ -1146,7 +1160,8 @@ Get list of applications which have not been configured yet. { "appId": "mobile-app-other", "ios": null, - "android": null + "android": null, + "huawei": null } ] } @@ -1193,12 +1208,14 @@ Get detail of an application. "application": { "appId": "mobile-app", "ios": true, - "android": true + "android": true, + "huawai": true }, "iosBundle": "some.bundle.id", "iosKeyId": "KEYID123456", "iosTeamId": "TEAMID123456", - "androidProjectId": "PROJECTID123" + "androidProjectId": "PROJECTID123", + "huaweiProjectId": "HMS123" } } ``` @@ -1405,6 +1422,84 @@ Remove FCM configuration for Android push messages. ``` + +### Update Huawei Configuration + +Update an Huawei configuration. + + +#### Request + + + + + + + + + + + +
MethodPOST / PUT
Resource URI/admin/app/huawei/update
+ + +```json +{ + "requestObject": { + "appId": "mobile-app", + "projectId": "PROJECTID123", + "clientId": "oAuth 2.0 client ID", + "clientSecret": "oAuth 2.0 client secret" + } +} +``` + +#### Response 200 + +```json +{ + "status": "OK" +} +``` + + + +### Remove Huawei Configuration + +Remove configuration for Huawei push messages. + +#### Request + + + + + + + + + + + +
MethodPOST / DELETE
Resource URI/admin/app/huawei/remove
+ + +```json +{ + "requestObject": { + "appId": "mobile-app" + } +} +``` + +#### Response 200 + +```json +{ + "status": "OK" +} +``` + + ## Message Inbox When communicating with your users, you can use the message inbox to store messages for users. Inbox is not responsible diff --git a/docs/Push-Server-Administration.md b/docs/Push-Server-Administration.md index 1ad83c2df..ee3ff306d 100644 --- a/docs/Push-Server-Administration.md +++ b/docs/Push-Server-Administration.md @@ -53,7 +53,8 @@ curl --request POST \ "requestObject": { "appId": 1, "includeIos": true, - "includeAndroid": true + "includeAndroid": true, + "includeHuawei": true } }' ``` @@ -136,7 +137,39 @@ curl --request DELETE \ }' ``` -Set the `appId` value for the Push Server application ID you want to update. +Set the `appId` value for the Push Server application ID you want to delete. + + +### Update Huawei Configuration + +```sh +curl --request POST \ + --url http://localhost:8080/powerauth-push-server/admin/app/huawei/update \ + --json '{ + "requestObject": { + "appId": 1, + "projectId": "projectId", + "clientId": "oAuth 2.0 client ID", + "clientSecret": "oAuth 2.0 client secret" + } +}' +``` + +Set the `appId` value for Push Server application ID to want to update. + +### Remove Huawei Configuration + +```sh +curl --request DELETE \ + --url http://localhost:8080/powerauth-push-server/admin/app/huawei/remove \ + --json '{ + "requestObject": { + "appId": 1 + } +}' +``` + +Set the `appId` value for the Push Server application ID you want to delete. ## Administration using SQL Database diff --git a/docs/db/changelog/changesets/powerauth-push-server/1.7.x/20240119-push_app_credentials-hms.xml b/docs/db/changelog/changesets/powerauth-push-server/1.7.x/20240119-push_app_credentials-hms.xml new file mode 100644 index 000000000..9a1918fea --- /dev/null +++ b/docs/db/changelog/changesets/powerauth-push-server/1.7.x/20240119-push_app_credentials-hms.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + Add hms_project_id, hms_client_id, and hms_client_secret columns to push_app_credentials + + + + + + + + + diff --git a/docs/db/changelog/changesets/powerauth-push-server/1.7.x/db.changelog-version.xml b/docs/db/changelog/changesets/powerauth-push-server/1.7.x/db.changelog-version.xml new file mode 100644 index 000000000..6e97df5f7 --- /dev/null +++ b/docs/db/changelog/changesets/powerauth-push-server/1.7.x/db.changelog-version.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/docs/db/changelog/changesets/powerauth-push-server/db.changelog-module.xml b/docs/db/changelog/changesets/powerauth-push-server/db.changelog-module.xml index 93573e557..0253cdde1 100644 --- a/docs/db/changelog/changesets/powerauth-push-server/db.changelog-module.xml +++ b/docs/db/changelog/changesets/powerauth-push-server/db.changelog-module.xml @@ -9,5 +9,6 @@ + diff --git a/pom.xml b/pom.xml index 7ccc5d960..02c6850a8 100644 --- a/pom.xml +++ b/pom.xml @@ -141,6 +141,14 @@ + + org.apache.maven.plugins + maven-surefire-plugin + + external-service + + + org.apache.maven.plugins maven-source-plugin diff --git a/powerauth-push-client/src/main/java/io/getlime/push/client/PushServerClient.java b/powerauth-push-client/src/main/java/io/getlime/push/client/PushServerClient.java index 34bac28a1..7e49c63d0 100644 --- a/powerauth-push-client/src/main/java/io/getlime/push/client/PushServerClient.java +++ b/powerauth-push-client/src/main/java/io/getlime/push/client/PushServerClient.java @@ -499,17 +499,14 @@ public ObjectResponse getUnconfiguredApplicationList /** * Get detail for an application credentials entity. - * @param appId Application credentials entity ID. - * @param includeIos Whether to include iOS details. - * @param includeAndroid Whether to include Android details. + * @param request Application detail request. * @return Application credentials entity detail. * @throws PushServerClientException Thrown when communication with Push Server fails. */ - public ObjectResponse getApplicationDetail(String appId, boolean includeIos, boolean includeAndroid) throws PushServerClientException { - GetApplicationDetailRequest request = new GetApplicationDetailRequest(appId, includeIos, includeAndroid); - logger.info("Calling push server to retrieve application detail, ID: {} - start", appId); + public ObjectResponse getApplicationDetail(final GetApplicationDetailRequest request) throws PushServerClientException { + logger.info("Calling push server to retrieve application detail, ID: {} - start", request.getAppId()); final ObjectResponse response = postObjectImpl("/admin/app/detail", new ObjectRequest<>(request), GetApplicationDetailResponse.class); - logger.info("Calling push server to retrieve application detail, ID: {} - finish", appId); + logger.info("Calling push server to retrieve application detail, ID: {} - finish", request.getAppId()); return response; } @@ -592,6 +589,35 @@ public Response removeAndroid(String appId) throws PushServerClientException { return response; } + /** + * Update Huawei details for an application credentials entity. + * + * @param request Update Huawei request. + * @return Response from server. + * @throws PushServerClientException Thrown when communication with Push Server fails. + */ + public Response updateHuawei(final UpdateHuaweiRequest request) throws PushServerClientException { + logger.info("Calling push server to update Huawei, ID: {} - start", request.getAppId()); + final Response response = putObjectImpl("/admin/app/huawei/update", new ObjectRequest<>(request)); + logger.info("Calling push server to update Huawei, ID: {} - finish", request.getAppId()); + return response; + } + + /** + * Remove Huawei record from an application credentials entity. + * + * @param appId Application credentials entity ID. + * @return Response from server. + * @throws PushServerClientException Thrown when communication with Push Server fails. + */ + public Response removeHuawei(String appId) throws PushServerClientException { + final RemoveHuaweiRequest request = new RemoveHuaweiRequest(appId); + logger.info("Calling push server to remove Huawei, ID: {} - start", appId); + final Response response = postObjectImpl("/admin/app/huawei/remove", new ObjectRequest<>(request)); + logger.info("Calling push server to remove Huawei, ID: {} - finish", appId); + return response; + } + /** * Post a message to an inbox of provided user. * @param request Request with the message detail. diff --git a/powerauth-push-model/src/main/java/io/getlime/push/model/entity/PushMessageSendResult.java b/powerauth-push-model/src/main/java/io/getlime/push/model/entity/PushMessageSendResult.java index aadee3b79..8855fab4b 100644 --- a/powerauth-push-model/src/main/java/io/getlime/push/model/entity/PushMessageSendResult.java +++ b/powerauth-push-model/src/main/java/io/getlime/push/model/entity/PushMessageSendResult.java @@ -39,12 +39,18 @@ public class PushMessageSendResult extends BasePushMessageSendResult { */ private final PlatformResult android; + /** + * Data associated with push messages sent to Huawei devices. + */ + private final PlatformResult huawei; + /** * Default constructor. */ public PushMessageSendResult() { this.ios = new PlatformResult(); this.android = new PlatformResult(); + this.huawei = new PlatformResult(); } /** @@ -56,6 +62,7 @@ public PushMessageSendResult(Mode mode) { super(mode); this.ios = new PlatformResult(); this.android = new PlatformResult(); + this.huawei = new PlatformResult(); } /** diff --git a/powerauth-push-model/src/main/java/io/getlime/push/model/entity/PushServerApplication.java b/powerauth-push-model/src/main/java/io/getlime/push/model/entity/PushServerApplication.java index 6e41614cf..3595807e0 100644 --- a/powerauth-push-model/src/main/java/io/getlime/push/model/entity/PushServerApplication.java +++ b/powerauth-push-model/src/main/java/io/getlime/push/model/entity/PushServerApplication.java @@ -16,62 +16,36 @@ package io.getlime.push.model.entity; +import lombok.Getter; +import lombok.Setter; + /** * Push server application credentials entity. * * @author Roman Strobl, roman.strobl@wultra.com */ +@Getter +@Setter public class PushServerApplication { - private String appId; - private Boolean ios; - private Boolean android; - /** - * Get application ID. - * @return Application ID. + * Application ID. */ - public String getAppId() { - return appId; - } - - /** - * Set application ID. - * @param appId Application ID. - */ - public void setAppId(String appId) { - this.appId = appId; - } + private String appId; /** - * Get whether iOS is configured. - * @return Whether iOS is configured. + * Whether iOS is configured. */ - public Boolean getIos() { - return ios; - } + private Boolean ios; /** - * Set whether iOS is configured. - * @param ios Whether iOS is configured. + * Whether Android is configured. */ - public void setIos(Boolean ios) { - this.ios = ios; - } + private Boolean android; /** - * Get whether Android is configured. - * @return Whether Android is configured. + * Whether Huawei is configured. */ - public Boolean getAndroid() { - return android; - } + private Boolean huawei; - /** - * Set whether Android is configured. - * @param android Whether Android is configured. - */ - public void setAndroid(Boolean android) { - this.android = android; - } } diff --git a/powerauth-push-model/src/main/java/io/getlime/push/model/enumeration/MobilePlatform.java b/powerauth-push-model/src/main/java/io/getlime/push/model/enumeration/MobilePlatform.java index 9891520a7..0d37eafcd 100644 --- a/powerauth-push-model/src/main/java/io/getlime/push/model/enumeration/MobilePlatform.java +++ b/powerauth-push-model/src/main/java/io/getlime/push/model/enumeration/MobilePlatform.java @@ -35,6 +35,12 @@ public enum MobilePlatform { * Android Platform. */ @JsonProperty("android") - ANDROID + ANDROID, + + /** + * Huawei Platform. + */ + @JsonProperty("huawei") + HUAWEI } diff --git a/powerauth-push-model/src/main/java/io/getlime/push/model/request/CreateDeviceForActivationsRequest.java b/powerauth-push-model/src/main/java/io/getlime/push/model/request/CreateDeviceForActivationsRequest.java index 8eb96b045..b092d5519 100644 --- a/powerauth-push-model/src/main/java/io/getlime/push/model/request/CreateDeviceForActivationsRequest.java +++ b/powerauth-push-model/src/main/java/io/getlime/push/model/request/CreateDeviceForActivationsRequest.java @@ -44,10 +44,10 @@ public class CreateDeviceForActivationsRequest { private String appId; /** - * The push token is the value received from APNS, or FCM services without any modification. + * The push token is the value received from APNS, FCM, or HMS services without any modification. */ @NotBlank - @Schema(description = "The push token is the value received from APNS, or FCM services without any modification.") + @Schema(description = "The push token is the value received from APNS, FCM, or HMS services without any modification.") private String token; /** diff --git a/powerauth-push-model/src/main/java/io/getlime/push/model/request/CreateDeviceRequest.java b/powerauth-push-model/src/main/java/io/getlime/push/model/request/CreateDeviceRequest.java index 30ab39f81..7c5192f84 100644 --- a/powerauth-push-model/src/main/java/io/getlime/push/model/request/CreateDeviceRequest.java +++ b/powerauth-push-model/src/main/java/io/getlime/push/model/request/CreateDeviceRequest.java @@ -39,10 +39,10 @@ public class CreateDeviceRequest { private String appId; /** - * The push token is the value received from APNS, or FCM services without any modification. + * The push token is the value received from APNS, FCM, or HMS services without any modification. */ @NotBlank - @Schema(description = "The push token is the value received from APNS, or FCM services without any modification.") + @Schema(description = "The push token is the value received from APNS, FCM, or HMS services without any modification.") private String token; @NotNull diff --git a/powerauth-push-model/src/main/java/io/getlime/push/model/request/GetApplicationDetailRequest.java b/powerauth-push-model/src/main/java/io/getlime/push/model/request/GetApplicationDetailRequest.java index e8878f409..ecffa6c84 100644 --- a/powerauth-push-model/src/main/java/io/getlime/push/model/request/GetApplicationDetailRequest.java +++ b/powerauth-push-model/src/main/java/io/getlime/push/model/request/GetApplicationDetailRequest.java @@ -17,16 +17,14 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; -import lombok.Getter; -import lombok.Setter; +import lombok.Data; /** * Get application credentials entity detail request. * * @author Roman Strobl, roman.strobl@wultra.com */ -@Getter -@Setter +@Data public class GetApplicationDetailRequest { /** @@ -48,6 +46,12 @@ public class GetApplicationDetailRequest { @Schema(description = "Whether to include Android details.") private boolean includeAndroid; + /** + * Whether to include Huawei details. + */ + @Schema(description = "Whether to include Huawei details.") + private boolean includeHuawei; + /** * Default constructor. */ @@ -68,10 +72,11 @@ public GetApplicationDetailRequest(String appId) { * @param includeIos Whether to include iOS details. * @param includeAndroid Whether to include Android details. */ - public GetApplicationDetailRequest(String appId, boolean includeIos, boolean includeAndroid) { + public GetApplicationDetailRequest(String appId, boolean includeIos, boolean includeAndroid, boolean includeHuawei) { this.appId = appId; this.includeIos = includeIos; this.includeAndroid = includeAndroid; + this.includeHuawei = includeHuawei; } } diff --git a/powerauth-push-model/src/main/java/io/getlime/push/model/request/RemoveHuaweiRequest.java b/powerauth-push-model/src/main/java/io/getlime/push/model/request/RemoveHuaweiRequest.java new file mode 100644 index 000000000..f453c2f44 --- /dev/null +++ b/powerauth-push-model/src/main/java/io/getlime/push/model/request/RemoveHuaweiRequest.java @@ -0,0 +1,53 @@ +/* + * Copyright 2024 Wultra s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.getlime.push.model.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; +import lombok.Setter; + +/** + * Remove Huawei configuration request. + * + * @author Lubos Racansky, lubos.racansky@wultra.com + */ +@Getter +@Setter +public class RemoveHuaweiRequest { + + /** + * Application ID. + */ + @NotBlank + @Schema(description = "Application ID.") + private String appId; + + /** + * No-arg constructor. + */ + public RemoveHuaweiRequest() { + } + + /** + * Constructor with application credentials entity ID. + * @param appId Application credentials entity ID. + */ + public RemoveHuaweiRequest(String appId) { + this.appId = appId; + } + +} diff --git a/powerauth-push-model/src/main/java/io/getlime/push/model/request/UpdateHuaweiRequest.java b/powerauth-push-model/src/main/java/io/getlime/push/model/request/UpdateHuaweiRequest.java new file mode 100644 index 000000000..1373efd53 --- /dev/null +++ b/powerauth-push-model/src/main/java/io/getlime/push/model/request/UpdateHuaweiRequest.java @@ -0,0 +1,60 @@ +/* + * Copyright 2024 Wultra s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.getlime.push.model.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; +import lombok.Setter; + +/** + * Update Huawei configuration request. + * + * @author Lubos Racansky, lubos.racansky@wultra.com + */ +@Getter +@Setter +public class UpdateHuaweiRequest { + + /** + * Application ID. + */ + @NotBlank + @Schema(description = "Application ID.") + private String appId; + + /** + * Huawei project ID. + */ + @NotBlank + @Schema(description = "Huawei project ID.") + private String projectId; + + /** + * Huawei OAuth 2.0 client ID. + */ + @NotBlank + @Schema(description = "Huawei OAuth 2.0 client ID.") + private String clientId; + + /** + * Huawei OAuth 2.0 client secret. + */ + @NotBlank + @Schema(description = "Huawei OAuth 2.0 client secret.") + private String clientSecret; + +} diff --git a/powerauth-push-model/src/main/java/io/getlime/push/model/response/GetApplicationDetailResponse.java b/powerauth-push-model/src/main/java/io/getlime/push/model/response/GetApplicationDetailResponse.java index dfa7894b3..ed448226c 100644 --- a/powerauth-push-model/src/main/java/io/getlime/push/model/response/GetApplicationDetailResponse.java +++ b/powerauth-push-model/src/main/java/io/getlime/push/model/response/GetApplicationDetailResponse.java @@ -16,16 +16,14 @@ package io.getlime.push.model.response; import io.getlime.push.model.entity.PushServerApplication; -import lombok.Getter; -import lombok.Setter; +import lombok.Data; /** * Get application credentials entity detail response. * * @author Roman Strobl, roman.strobl@wultra.com */ -@Getter -@Setter +@Data public class GetApplicationDetailResponse { /** @@ -58,4 +56,9 @@ public class GetApplicationDetailResponse { */ private String androidProjectId; + /** + * Huawei project ID. + */ + private String huaweiProjectId; + } diff --git a/powerauth-push-server/pom.xml b/powerauth-push-server/pom.xml index 2f0b946ce..c043e009c 100644 --- a/powerauth-push-server/pom.xml +++ b/powerauth-push-server/pom.xml @@ -93,6 +93,10 @@
+ + org.springframework.boot + spring-boot-starter-oauth2-client + diff --git a/powerauth-push-server/src/main/java/io/getlime/push/configuration/PushServiceConfiguration.java b/powerauth-push-server/src/main/java/io/getlime/push/configuration/PushServiceConfiguration.java index bed523258..00902c7b4 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/configuration/PushServiceConfiguration.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/configuration/PushServiceConfiguration.java @@ -137,6 +137,55 @@ public class PushServiceConfiguration { @Value("${powerauth.push.service.fcm.sendMessageUrl}") private String fcmSendMessageUrl; + /** + * Flag indicating if proxy is enabled for HMS communication. + */ + @Value("${powerauth.push.service.hms.proxy.enabled}") + private boolean hmsProxyEnabled; + + /** + * HMS proxy URL. + */ + @Value("${powerauth.push.service.hms.proxy.host}") + private String hmsProxyHost; + + /** + * HMS proxy port. + */ + @Value("${powerauth.push.service.hms.proxy.port}") + private int hmsProxyPort; + + /** + * HMS proxy username. + */ + @Value("${powerauth.push.service.hms.proxy.username}") + private String hmsProxyUsername; + + /** + * HMS proxy password. + */ + @Value("${powerauth.push.service.hms.proxy.password}") + private String hmsProxyPassword; + + /** + * Get status if notification is set to be sent only through data map + * True in case HMS notification should always be a "data" notification, even for messages with title and message, false otherwise. + */ + @Value("${powerauth.push.service.hms.dataNotificationOnly}") + private boolean hmsDataNotificationOnly; + + /** + * HMS send message endpoint URL. + */ + @Value("${powerauth.push.service.hms.sendMessageUrl}") + private String hmsSendMessageUrl; + + /** + * HMS OAuth service URL to obtain an access token. + */ + @Value("${powerauth.push.service.hms.tokenUrl}") + private String hmsTokenUrl; + /** * The batch size used while sending a push campaign. */ @@ -167,6 +216,24 @@ public class PushServiceConfiguration { @Value("${powerauth.push.service.apns.connect.timeout}") private int apnsConnectTimeout; + /** + * HMS connect timeout. + */ + @Value("${powerauth.push.service.hms.connect.timeout}") + private Duration hmsConnectTimeout; + + /** + * HMS maximum duration allowed between each network-level read operations. + */ + @Value("${powerauth.push.service.hms.response.timeout}") + private Duration hmsResponseTimeout; + + /** + * HMS ConnectionProvider max idle time. + */ + @Value("${powerauth.push.service.hms.max-idle-time}") + private Duration hmsMaxIdleTime; + /** * APNS concurrent connections. */ diff --git a/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/AdministrationController.java b/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/AdministrationController.java index 6ccac62d4..8b379ef14 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/AdministrationController.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/AdministrationController.java @@ -32,6 +32,7 @@ import io.getlime.push.repository.model.AppCredentialsEntity; import io.getlime.push.service.batch.storage.AppCredentialStorageMap; import io.getlime.push.service.http.HttpCustomizationService; +import jakarta.validation.Valid; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -85,6 +86,7 @@ public ObjectResponse listApplications() { app.setAppId(appCredentialsEntity.getAppId()); app.setIos(appCredentialsEntity.getIosPrivateKey() != null); app.setAndroid(appCredentialsEntity.getAndroidPrivateKey() != null); + app.setHuawei(isHuawei(appCredentialsEntity)); appList.add(app); } response.setApplicationList(appList); @@ -150,6 +152,7 @@ public ObjectResponse getApplicationDetail(@Reques app.setAppId(appCredentialsEntity.getAppId()); app.setIos(appCredentialsEntity.getIosPrivateKey() != null); app.setAndroid(appCredentialsEntity.getAndroidPrivateKey() != null); + app.setHuawei(isHuawei(appCredentialsEntity)); response.setApplication(app); if (requestObject.isIncludeIos()) { response.setIosBundle(appCredentialsEntity.getIosBundle()); @@ -160,10 +163,17 @@ public ObjectResponse getApplicationDetail(@Reques if (requestObject.isIncludeAndroid()) { response.setAndroidProjectId(appCredentialsEntity.getAndroidProjectId()); } + if (requestObject.isIncludeHuawei()) { + response.setHuaweiProjectId(appCredentialsEntity.getHmsProjectId()); + } logger.debug("The getApplicationDetail request succeeded"); return new ObjectResponse<>(response); } + private static boolean isHuawei(final AppCredentialsEntity appCredentialsEntity) { + return appCredentialsEntity.getHmsClientSecret() != null && appCredentialsEntity.getHmsClientId() != null; + } + /** * Create application. * @param request Create application request. @@ -205,7 +215,7 @@ public Response updateIos(@RequestBody ObjectRequest request) if (requestObject == null) { throw new PushServerException("Request object must not be empty"); } - logger.info("Received updateIos request, application credentials entity ID: {}", requestObject.getAppId()); + logger.info("Received updateIos request, application ID: {}", requestObject.getAppId()); final String errorMessage = UpdateIosRequestValidator.validate(requestObject); if (errorMessage != null) { throw new PushServerException(errorMessage); @@ -219,7 +229,7 @@ public Response updateIos(@RequestBody ObjectRequest request) appCredentialsEntity.setIosEnvironment(requestObject.getEnvironment()); appCredentialsRepository.save(appCredentialsEntity); appCredentialStorageMap.cleanByKey(appCredentialsEntity.getAppId()); - logger.info("The updateIos request succeeded, application credentials entity ID: {}", requestObject.getAppId()); + logger.info("The updateIos request succeeded, application credentials entity ID: {}", appCredentialsEntity.getId()); return new Response(); } @@ -235,7 +245,7 @@ public Response removeIos(@RequestBody ObjectRequest request) if (requestObject == null) { throw new PushServerException("Request object must not be empty"); } - logger.info("Received removeIos request, application credentials entity ID: {}", requestObject.getAppId()); + logger.info("Received removeIos request, application ID: {}", requestObject.getAppId()); String errorMessage = RemoveIosRequestValidator.validate(requestObject); if (errorMessage != null) { throw new PushServerException(errorMessage); @@ -248,7 +258,7 @@ public Response removeIos(@RequestBody ObjectRequest request) appCredentialsEntity.setIosEnvironment(null); appCredentialsRepository.save(appCredentialsEntity); appCredentialStorageMap.cleanByKey(appCredentialsEntity.getAppId()); - logger.info("The removeIos request succeeded, application credentials entity ID: {}", requestObject.getAppId()); + logger.info("The removeIos request succeeded, application credentials entity ID: {}", appCredentialsEntity.getId()); return new Response(); } @@ -264,7 +274,7 @@ public Response updateAndroid(@RequestBody ObjectRequest r if (requestObject == null) { throw new PushServerException("Request object must not be empty"); } - logger.info("Received updateAndroid request, application credentials entity ID: {}", requestObject.getAppId()); + logger.info("Received updateAndroid request, application ID: {}", requestObject.getAppId()); String errorMessage = UpdateAndroidRequestValidator.validate(requestObject); if (errorMessage != null) { throw new PushServerException(errorMessage); @@ -275,7 +285,7 @@ public Response updateAndroid(@RequestBody ObjectRequest r appCredentialsEntity.setAndroidProjectId(requestObject.getProjectId()); appCredentialsRepository.save(appCredentialsEntity); appCredentialStorageMap.cleanByKey(appCredentialsEntity.getAppId()); - logger.info("The updateAndroid request succeeded, application credentials entity ID: {}", requestObject.getAppId()); + logger.info("The updateAndroid request succeeded, application credentials entity ID: {}", appCredentialsEntity.getId()); return new Response(); } @@ -291,7 +301,7 @@ public Response removeAndroid(@RequestBody ObjectRequest r if (requestObject == null) { throw new PushServerException("Request object must not be empty"); } - logger.info("Received removeAndroid request, application credentials entity ID: {}", requestObject.getAppId()); + logger.info("Received removeAndroid request, application ID: {}", requestObject.getAppId()); String errorMessage = RemoveAndroidRequestValidator.validate(requestObject); if (errorMessage != null) { throw new PushServerException(errorMessage); @@ -301,7 +311,51 @@ public Response removeAndroid(@RequestBody ObjectRequest r appCredentialsEntity.setAndroidProjectId(null); appCredentialsRepository.save(appCredentialsEntity); appCredentialStorageMap.cleanByKey(appCredentialsEntity.getAppId()); - logger.info("The removeAndroid request succeeded, application credentials entity ID: {}", requestObject.getAppId()); + logger.info("The removeAndroid request succeeded, application credentials entity ID: {}", appCredentialsEntity.getId()); + return new Response(); + } + + /** + * Update Huawei configuration. + * + * @param request Update Huawei configuration request. + * @return Response. + * @throws PushServerException Thrown when application credentials entity could not be found or request validation fails. + */ + @RequestMapping(value = "huawei/update", method = { RequestMethod.POST, RequestMethod.PUT }) + public Response updateHuawei(@Valid @RequestBody ObjectRequest request) throws PushServerException { + final UpdateHuaweiRequest requestObject = request.getRequestObject(); + logger.info("Received update Huawei request, application ID: {}", requestObject.getAppId()); + + final AppCredentialsEntity appCredentialsEntity = findAppCredentialsEntityById(requestObject.getAppId()); + appCredentialsEntity.setHmsProjectId(requestObject.getProjectId()); + appCredentialsEntity.setHmsClientId(requestObject.getClientId()); + appCredentialsEntity.setHmsClientSecret(requestObject.getClientSecret()); + appCredentialsRepository.save(appCredentialsEntity); + appCredentialStorageMap.cleanByKey(appCredentialsEntity.getAppId()); + logger.info("The update Huawei request succeeded, application credentials entity ID: {}", appCredentialsEntity.getId()); + return new Response(); + } + + /** + * Remove Huawei configuration. + * + * @param request Remove Huawei configuration request. + * @return Response. + * @throws PushServerException Thrown when application credentials entity could not be found or request validation fails. + */ + @RequestMapping(value = "huawei/remove", method = { RequestMethod.POST, RequestMethod.DELETE }) + public Response removeHuawei(@Valid @RequestBody ObjectRequest request) throws PushServerException { + final RemoveHuaweiRequest requestObject = request.getRequestObject(); + logger.info("Received remove Huawei request, application ID: {}", requestObject.getAppId()); + + final AppCredentialsEntity appCredentialsEntity = findAppCredentialsEntityById(requestObject.getAppId()); + appCredentialsEntity.setHmsProjectId(null); + appCredentialsEntity.setHmsClientSecret(null); + appCredentialsEntity.setHmsClientId(null); + appCredentialsRepository.save(appCredentialsEntity); + appCredentialStorageMap.cleanByKey(appCredentialsEntity.getAppId()); + logger.info("The remove Huawei request succeeded, application credentials entity ID: {}", appCredentialsEntity.getId()); return new Response(); } diff --git a/powerauth-push-server/src/main/java/io/getlime/push/repository/model/AppCredentialsEntity.java b/powerauth-push-server/src/main/java/io/getlime/push/repository/model/AppCredentialsEntity.java index 173b78bf1..c51cda195 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/repository/model/AppCredentialsEntity.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/repository/model/AppCredentialsEntity.java @@ -24,7 +24,7 @@ import java.io.Serializable; /** - * Class representing application tokens used to authenticate against APNs, or FCM services. + * Class representing application tokens used to authenticate against APNs, FCM, or HMS services. * * @author Petr Dvorak, petr@wultra.com */ @@ -94,4 +94,22 @@ public class AppCredentialsEntity implements Serializable { @Column(name = "android_project_id") private String androidProjectId; + /** + * Project ID defined in Huawei AppGallery Connect. + */ + @Column(name = "hms_project_id") + private String hmsProjectId; + + /** + * Huawei OAuth 2.0 Client ID. + */ + @Column(name = "hms_client_id") + private String hmsClientId; + + /** + * Huawei OAuth 2.0 Client Secret. + */ + @Column(name = "hms_client_secret") + private String hmsClientSecret; + } diff --git a/powerauth-push-server/src/main/java/io/getlime/push/repository/model/Platform.java b/powerauth-push-server/src/main/java/io/getlime/push/repository/model/Platform.java index 8a8cd3778..df936ecf4 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/repository/model/Platform.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/repository/model/Platform.java @@ -30,5 +30,10 @@ public enum Platform { /** * Android Platform. */ - ANDROID + ANDROID, + + /** + * Huawei Platform. + */ + HUAWEI; } diff --git a/powerauth-push-server/src/main/java/io/getlime/push/repository/model/PlatformConverter.java b/powerauth-push-server/src/main/java/io/getlime/push/repository/model/PlatformConverter.java index f4c8d31b6..1300c41ad 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/repository/model/PlatformConverter.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/repository/model/PlatformConverter.java @@ -36,6 +36,7 @@ public String convertToDatabaseColumn(final Platform attribute) { return switch (attribute) { case IOS -> "ios"; case ANDROID -> "android"; + case HUAWEI -> "huawei"; }; } @@ -48,6 +49,7 @@ public Platform convertToEntityAttribute(final String dbData) { return switch (dbData) { case "ios" -> Platform.IOS; case "android" -> Platform.ANDROID; + case "huawei" -> Platform.HUAWEI; default -> throw new IllegalArgumentException("No mapping for platform: " + dbData); }; } diff --git a/powerauth-push-server/src/main/java/io/getlime/push/service/AppRelatedPushClient.java b/powerauth-push-server/src/main/java/io/getlime/push/service/AppRelatedPushClient.java index 0552a6765..8ae9a8605 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/service/AppRelatedPushClient.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/service/AppRelatedPushClient.java @@ -18,12 +18,17 @@ import com.eatthepath.pushy.apns.ApnsClient; import io.getlime.push.repository.model.AppCredentialsEntity; import io.getlime.push.service.fcm.FcmClient; +import io.getlime.push.service.hms.HmsClient; +import lombok.Getter; +import lombok.Setter; /** * Class storing app credentials and clients. * * @author Petr Dvorak, petr@wultra.com */ +@Getter +@Setter public class AppRelatedPushClient { /** @@ -42,50 +47,8 @@ public class AppRelatedPushClient { private FcmClient fcmClient; /** - * Get app credentials. - * @return App credentials. + * HMS client instance, used for Huawei Mobile Services. */ - public AppCredentialsEntity getAppCredentials() { - return appCredentials; - } + private HmsClient hmsClient; - /** - * Set app credentials. - * @param appCredentials App credentials. - */ - public void setAppCredentials(AppCredentialsEntity appCredentials) { - this.appCredentials = appCredentials; - } - - /** - * Get APNs client. - * @return APNs client. - */ - public ApnsClient getApnsClient() { - return apnsClient; - } - - /** - * Set APNs client. - * @param apnsClient APNs client. - */ - public void setApnsClient(ApnsClient apnsClient) { - this.apnsClient = apnsClient; - } - - /** - * Get FCM client. - * @return FCM client. - */ - public FcmClient getFcmClient() { - return fcmClient; - } - - /** - * Set FCM client. - * @param fcmClient FCM client. - */ - public void setFcmClient(FcmClient fcmClient) { - this.fcmClient = fcmClient; - } } diff --git a/powerauth-push-server/src/main/java/io/getlime/push/service/DeviceRegistrationService.java b/powerauth-push-server/src/main/java/io/getlime/push/service/DeviceRegistrationService.java index 2998ebf4e..df8bc9963 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/service/DeviceRegistrationService.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/service/DeviceRegistrationService.java @@ -261,6 +261,7 @@ private static Platform convert(final MobilePlatform source) { return switch (source) { case IOS -> Platform.IOS; case ANDROID -> Platform.ANDROID; + case HUAWEI -> Platform.HUAWEI; }; } diff --git a/powerauth-push-server/src/main/java/io/getlime/push/service/PushMessageSenderService.java b/powerauth-push-server/src/main/java/io/getlime/push/service/PushMessageSenderService.java index a7dae9ba6..d662b1500 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/service/PushMessageSenderService.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/service/PushMessageSenderService.java @@ -32,6 +32,7 @@ import io.getlime.push.repository.model.PushMessageEntity; import io.getlime.push.service.batch.storage.AppCredentialStorageMap; import io.getlime.push.service.fcm.FcmClient; +import io.getlime.push.service.hms.HmsClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -141,6 +142,15 @@ public BasePushMessageSendResult sendPushMessage(final String appId, final Mode final String token = device.getPushToken(); final PushMessageSendResult.PlatformResult platformResult = sendResult.getAndroid(); pushSendingWorker.sendMessageToAndroid(pushClient.getFcmClient(), pushMessage.getBody(), pushMessage.getAttributes(), pushMessage.getPriority(), token, createPushSendingCallback(mode, device, platformResult, pushMessageObject, phaser)); + } else if (platform == Platform.HUAWEI) { + if (pushClient.getHmsClient() == null) { + logger.error("Push message cannot be sent to HMS because HMS is not configured in push server."); + arriveAndDeregisterPhaserForMode(phaser, mode); + continue; + } + final String token = device.getPushToken(); + final PushMessageSendResult.PlatformResult platformResult = sendResult.getHuawei(); + pushSendingWorker.sendMessageToHuawei(pushClient.getHmsClient(), pushMessage.getBody(), pushMessage.getAttributes(), pushMessage.getPriority(), token, createPushSendingCallback(mode, device, platformResult, pushMessageObject, phaser)); } } } @@ -225,6 +235,8 @@ public void sendCampaignMessage(final String appId, Platform platform, final Str pushSendingWorker.sendMessageToIos(pushClient.getApnsClient(), pushMessageBody, attributes, priority, token, pushClient.getAppCredentials().getIosBundle(), createPushSendingCallback(token, pushMessageObject, pushClient)); case ANDROID -> pushSendingWorker.sendMessageToAndroid(pushClient.getFcmClient(), pushMessageBody, attributes, priority, token, createPushSendingCallback(token, pushMessageObject, pushClient)); + case HUAWEI -> + pushSendingWorker.sendMessageToHuawei(pushClient.getHmsClient(), pushMessageBody, attributes, priority, token, createPushSendingCallback(token, pushMessageObject, pushClient)); } } @@ -261,7 +273,7 @@ private List getPushDevices(Long id, String userId } } - // Prepare and cache APNS and FCM clients for provided app + // Prepare and cache APNS, FCM, and HMS clients for provided app private AppRelatedPushClient prepareClients(String appId) throws PushServerException { synchronized (this) { AppRelatedPushClient pushClient = appRelatedPushClientMap.get(appId); @@ -276,9 +288,13 @@ private AppRelatedPushClient prepareClients(String appId) throws PushServerExcep final FcmClient fcmClient = pushSendingWorker.prepareFcmClient(credentials.getAndroidProjectId(), credentials.getAndroidPrivateKey()); pushClient.setFcmClient(fcmClient); } + if (credentials.getHmsClientId() != null) { + final HmsClient hmsClient = pushSendingWorker.prepareHmsClient(credentials); + pushClient.setHmsClient(hmsClient); + } pushClient.setAppCredentials(credentials); appRelatedPushClientMap.put(appId, pushClient); - logger.info("Creating APNS and FCM clients for app {}", appId); + logger.info("Creating APNS, FCM, and HMS clients for app {}", appId); } return pushClient; } diff --git a/powerauth-push-server/src/main/java/io/getlime/push/service/PushSendingWorker.java b/powerauth-push-server/src/main/java/io/getlime/push/service/PushSendingWorker.java index b352c315c..d53fb7d0b 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/service/PushSendingWorker.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/service/PushSendingWorker.java @@ -22,6 +22,8 @@ import com.eatthepath.pushy.apns.util.SimpleApnsPushNotification; import com.eatthepath.pushy.apns.util.TokenUtil; import com.eatthepath.pushy.apns.util.concurrent.PushNotificationFuture; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import com.google.firebase.messaging.AndroidConfig; import com.google.firebase.messaging.AndroidNotification; import com.google.firebase.messaging.Message; @@ -33,10 +35,14 @@ import io.getlime.push.model.entity.PushMessageAttributes; import io.getlime.push.model.entity.PushMessageBody; import io.getlime.push.model.enumeration.Priority; +import io.getlime.push.repository.model.AppCredentialsEntity; import io.getlime.push.service.apns.ApnsRejectionReason; import io.getlime.push.service.fcm.FcmClient; import io.getlime.push.service.fcm.FcmModelConverter; import io.getlime.push.service.fcm.model.FcmSuccessResponse; +import io.getlime.push.service.hms.HmsClient; +import io.getlime.push.service.hms.HmsSendResponse; +import io.getlime.push.service.hms.request.AndroidNotification.Importance; import io.getlime.push.util.CaCertUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -131,6 +137,17 @@ FcmClient prepareFcmClient(String projectId, byte[] privateKey) throws PushServe return fcmClient; } + /** + * Prepares an HMS (Huawei Mobile Services) service client. + * + * @param credentials Credentials. + * @return A new instance of HMS client. + */ + HmsClient prepareHmsClient(final AppCredentialsEntity credentials) { + logger.info("Initializing HmsClient"); + return new HmsClient(pushServiceConfiguration, credentials); + } + /** * Send message to Android platform. * @param fcmClient Instance of the FCM client used for sending the notifications. @@ -210,6 +227,40 @@ void sendMessageToAndroid(final FcmClient fcmClient, final PushMessageBody pushM } } + /** + * Send message to Huawei platform. + * + * @param hmsClient Instance of the HMS client used for sending the notifications. + * @param pushMessageBody Push message contents. + * @param attributes Push message attributes. + * @param priority Push message priority. + * @param pushToken Push token used to deliver the message. + * @param callback Callback that is called after the asynchronous executions is completed. + * @throws PushServerException In case any issue happens while sending the push message. + */ + void sendMessageToHuawei(final HmsClient hmsClient, final PushMessageBody pushMessageBody, final PushMessageAttributes attributes, final Priority priority, final String pushToken, final PushSendingCallback callback) throws PushServerException { + final io.getlime.push.service.hms.request.Message message = buildHmsMessage(pushMessageBody, attributes, priority, pushToken); + + final Consumer successConsumer = response -> { + final String requestId = response.requestId(); + if (HmsClient.SUCCESS_CODE.equals(response.code())) { + logger.info("Notification sent successfully, request ID: {}", requestId); + callback.didFinishSendingMessage(PushSendingCallback.Result.OK); + } else { + logger.error("Notification sending failed, request ID: {}, code: {}, message: {}", requestId, response.code(), response.msg()); + callback.didFinishSendingMessage(PushSendingCallback.Result.FAILED); + } + }; + + final Consumer throwableConsumer = throwable -> { + logger.error("Invalid response received from HSM, notification sending failed.", throwable); + callback.didFinishSendingMessage(PushSendingCallback.Result.FAILED); + }; + + hmsClient.sendMessage(message, false) + .subscribe(successConsumer, throwableConsumer); + } + /** * Build Android Message object from Push message body. * @param pushMessageBody Push message body. @@ -269,6 +320,66 @@ private Message buildAndroidMessage(final PushMessageBody pushMessageBody, final .build(); } + /** + * Build HMS Message object from Push message body. + * + * @param pushMessageBody Push message body. + * @param attributes Push message attributes. + * @param priority Push message priority. + * @param pushToken Push token. + * @return HMS Message object. + * @throws PushServerException In case any issue happens while building the push message. + */ + private io.getlime.push.service.hms.request.Message buildHmsMessage(final PushMessageBody pushMessageBody, final PushMessageAttributes attributes, final Priority priority, final String pushToken) throws PushServerException { + final var androidConfigBuilder = io.getlime.push.service.hms.request.AndroidConfig.builder() + .collapseKey(Integer.valueOf(pushMessageBody.getCollapseKey())); + + calculateTtl(pushMessageBody.getValidUntil()) + .map(Object::toString) + .ifPresent(androidConfigBuilder::ttl); + + final Importance importance = (priority == Priority.NORMAL) ? Importance.NORMAL : Importance.HIGH; + + final var notificationBuilder = io.getlime.push.service.hms.request.AndroidNotification.builder() + .importance(importance) + .title(pushMessageBody.getTitle()) + .titleLocKey(pushMessageBody.getTitleLocKey()) + .body(pushMessageBody.getBody()) + .bodyLocKey(pushMessageBody.getBodyLocKey()) + .icon(pushMessageBody.getIcon()) + .sound(pushMessageBody.getSound()) + .tag(pushMessageBody.getCategory()); + + if (pushMessageBody.getTitleLocArgs() != null) { + notificationBuilder.titleLocArgs(List.of(pushMessageBody.getTitleLocArgs())); + } + if (pushMessageBody.getBodyLocArgs() != null) { + notificationBuilder.bodyLocArgs(List.of(pushMessageBody.getBodyLocArgs())); + } + + if (isMessageNotSilent(attributes)) { + androidConfigBuilder.notification(notificationBuilder.build()); + } + + final Map extras = pushMessageBody.getExtras(); + final String data; + if (extras == null) { + data = null; + } else { + try { + data = new ObjectMapper().writeValueAsString(extras); + } catch (JsonProcessingException e) { + throw new PushServerException("Failed to serialize extras to JSON", e); + } + } + + return io.getlime.push.service.hms.request.Message.builder() + .token(List.of(pushToken)) + .android(androidConfigBuilder.build()) + .data(data) + .build(); + } + private static boolean isMessageNotSilent(final PushMessageAttributes attributes) { // if there are no attributes, assume the message is not silent return attributes == null || !attributes.getSilent(); diff --git a/powerauth-push-server/src/main/java/io/getlime/push/service/hms/HmsClient.java b/powerauth-push-server/src/main/java/io/getlime/push/service/hms/HmsClient.java new file mode 100644 index 000000000..c6177bbef --- /dev/null +++ b/powerauth-push-server/src/main/java/io/getlime/push/service/hms/HmsClient.java @@ -0,0 +1,156 @@ +/* + * Copyright 2024 Wultra s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.getlime.push.service.hms; + +import io.getlime.push.configuration.PushServiceConfiguration; +import io.getlime.push.repository.model.AppCredentialsEntity; +import io.getlime.push.service.hms.request.Message; +import io.netty.channel.ChannelOption; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.http.client.reactive.ReactorClientHttpConnector; +import org.springframework.security.oauth2.client.AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager; +import org.springframework.security.oauth2.client.ClientCredentialsReactiveOAuth2AuthorizedClientProvider; +import org.springframework.security.oauth2.client.InMemoryReactiveOAuth2AuthorizedClientService; +import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientService; +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.client.registration.InMemoryReactiveClientRegistrationRepository; +import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; +import org.springframework.security.oauth2.client.web.reactive.function.client.ServerOAuth2AuthorizedClientExchangeFilterFunction; +import org.springframework.security.oauth2.core.AuthorizationGrantType; +import org.springframework.security.oauth2.core.ClientAuthenticationMethod; +import org.springframework.web.reactive.function.client.ExchangeFilterFunction; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; +import reactor.netty.http.client.HttpClient; +import reactor.netty.resources.ConnectionProvider; +import reactor.netty.transport.ProxyProvider; + +import java.time.Duration; +import java.util.Map; + +/** + * HMS (Huawei Mobile Services) server client. + * + * @author Lubos Racansky, lubos.racansky@wultra.com + */ +@Slf4j +public class HmsClient { + + private static final String OAUTH_REGISTRATION_ID = "hms"; + private static final String OAUTH_USER_AGENT = "Wultra Push-Server"; + + /** + * Success error code. + * + * @see HMS Documentation + */ + public static final String SUCCESS_CODE = "80000000"; + + final WebClient webClient; + final String messageUrl; + + public HmsClient(final PushServiceConfiguration pushServiceConfiguration, final AppCredentialsEntity credentials) { + webClient = createWebClient(credentials.getHmsClientId(), credentials.getHmsClientSecret(), pushServiceConfiguration); + final String projectId = credentials.getHmsProjectId(); + messageUrl = String.format(pushServiceConfiguration.getHmsSendMessageUrl(), projectId); + } + + public Mono sendMessage(final Message message, final boolean validationOnly) { + final Map body = Map.of("validate_only", validationOnly, "message", message); + return webClient.post() + .uri(messageUrl) + .bodyValue(body) + .retrieve() + .bodyToMono(HmsSendResponse.class); + } + + private static WebClient createWebClient( + final String oAuthClientId, + final String oAuthClientSecret, + final PushServiceConfiguration configuration) { + + final AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager authorizedClientManager = authorizedClientServiceReactiveOAuth2AuthorizedClientManager(oAuthClientId, oAuthClientSecret, configuration); + + final ServerOAuth2AuthorizedClientExchangeFilterFunction oAuth2ExchangeFilterFunction = new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager); + oAuth2ExchangeFilterFunction.setDefaultClientRegistrationId(OAUTH_REGISTRATION_ID); + + return createWebClient(oAuth2ExchangeFilterFunction, configuration); + } + + private static AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager authorizedClientServiceReactiveOAuth2AuthorizedClientManager( + final String oAuthClientId, + final String oAuthClientSecret, + final PushServiceConfiguration configuration) { + + final ClientRegistration clientRegistration = ClientRegistration.withRegistrationId(OAUTH_REGISTRATION_ID) + .tokenUri(configuration.getHmsTokenUrl()) + .clientName(OAUTH_USER_AGENT) + .clientId(oAuthClientId) + .clientSecret(oAuthClientSecret) + .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST) + .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) + .build(); + + final ReactiveClientRegistrationRepository clientRegistrations = new InMemoryReactiveClientRegistrationRepository(clientRegistration); + final ReactiveOAuth2AuthorizedClientService clientService = new InMemoryReactiveOAuth2AuthorizedClientService(clientRegistrations); + + final ClientCredentialsReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = new ClientCredentialsReactiveOAuth2AuthorizedClientProvider(); + + final AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager authorizedClientManager = + new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(clientRegistrations, clientService); + authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider); + return authorizedClientManager; + } + + private static WebClient createWebClient(final ExchangeFilterFunction filter, PushServiceConfiguration configuration) { + final Duration connectionTimeout = configuration.getHmsConnectTimeout(); + final Duration responseTimeout = configuration.getHmsResponseTimeout(); + final Duration maxIdleTime = configuration.getHmsMaxIdleTime(); + logger.info("Setting connectionTimeout: {}, responseTimeout: {}, maxIdleTime: {}", connectionTimeout, responseTimeout, maxIdleTime); + + final ConnectionProvider connectionProvider = ConnectionProvider.builder("custom") + .maxIdleTime(maxIdleTime) + .build(); + HttpClient httpClient = HttpClient.create(connectionProvider) + .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, Math.toIntExact(connectionTimeout.toMillis())) + .responseTimeout(responseTimeout); + + if (configuration.isHmsProxyEnabled()) { + logger.debug("Configuring proxy {}:{}", configuration.getHmsProxyHost(), configuration.getHmsProxyPort()); + httpClient = httpClient.proxy(proxySpec -> { + final ProxyProvider.Builder proxyBuilder = proxySpec + .type(ProxyProvider.Proxy.HTTP) + .host(configuration.getHmsProxyHost()) + .port(configuration.getHmsProxyPort()); + if (StringUtils.isNotBlank(configuration.getHmsProxyUsername())) { + proxyBuilder.username(configuration.getHmsProxyUsername()); + proxyBuilder.password(s -> configuration.getHmsProxyPassword()); + } + + proxyBuilder.build(); + }); + } + + final ReactorClientHttpConnector connector = new ReactorClientHttpConnector(httpClient); + + return WebClient.builder() + .clientConnector(connector) + .filter(filter) + .build(); + } + +} diff --git a/powerauth-push-server/src/main/java/io/getlime/push/service/hms/HmsSendResponse.java b/powerauth-push-server/src/main/java/io/getlime/push/service/hms/HmsSendResponse.java new file mode 100644 index 000000000..766fc0546 --- /dev/null +++ b/powerauth-push-server/src/main/java/io/getlime/push/service/hms/HmsSendResponse.java @@ -0,0 +1,25 @@ +/* + * Copyright 2024 Wultra s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.getlime.push.service.hms; + +/** + * HMS send response. + * + * @author Lubos Racansky, lubos.racansky@wultra.com + */ +public record HmsSendResponse(String code, String msg, String requestId) { + +} diff --git a/powerauth-push-server/src/main/java/io/getlime/push/service/hms/request/AndroidConfig.java b/powerauth-push-server/src/main/java/io/getlime/push/service/hms/request/AndroidConfig.java new file mode 100644 index 000000000..ffc77cb12 --- /dev/null +++ b/powerauth-push-server/src/main/java/io/getlime/push/service/hms/request/AndroidConfig.java @@ -0,0 +1,53 @@ +/* + * Copyright 2024 Wultra s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.getlime.push.service.hms.request; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.experimental.SuperBuilder; +import lombok.extern.jackson.Jacksonized; + +/** + * HMS (Huawei Mobile Services) json mapping object. + * + * @author Lubos Racansky, lubos.racansky@wultra.com + */ +@Getter +@SuperBuilder +@Jacksonized +public class AndroidConfig { + + @JsonProperty("collapse_key") + private final Integer collapseKey; + + private final String urgency; + + private final String category; + + private final String ttl; + + @JsonProperty("bi_tag") + private final String biTag; + + @JsonProperty("fast_app_target") + private final Integer fastAppTargetType; + + private final String data; + + private final AndroidNotification notification; + + private final String receiptId; +} diff --git a/powerauth-push-server/src/main/java/io/getlime/push/service/hms/request/AndroidNotification.java b/powerauth-push-server/src/main/java/io/getlime/push/service/hms/request/AndroidNotification.java new file mode 100644 index 000000000..a11496240 --- /dev/null +++ b/powerauth-push-server/src/main/java/io/getlime/push/service/hms/request/AndroidNotification.java @@ -0,0 +1,143 @@ +/* + * Copyright 2024 Wultra s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.getlime.push.service.hms.request; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Builder; +import lombok.Getter; +import lombok.experimental.SuperBuilder; +import lombok.extern.jackson.Jacksonized; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * HMS (Huawei Mobile Services) json mapping object. + * + * @author Lubos Racansky, lubos.racansky@wultra.com + */ +@Getter +@SuperBuilder +@Jacksonized +public class AndroidNotification { + + private final String title; + + private final String body; + + private final String icon; + + private final String color; + + private final String sound; + + @JsonProperty("default_sound") + private final boolean defaultSound; + + private final String tag; + + @JsonProperty("click_action") + private final ClickAction clickAction; + + @JsonProperty("body_loc_key") + private final String bodyLocKey; + + @Builder.Default + @JsonProperty("body_loc_args") + private final List bodyLocArgs = new ArrayList<>(); + + @JsonProperty("title_loc_key") + private final String titleLocKey; + + @Builder.Default + @JsonProperty("title_loc_args") + private final List titleLocArgs = new ArrayList<>(); + + @JsonProperty("multi_lang_key") + private final Map multiLangKey; + + @JsonProperty("channel_id") + private final String channelId; + + @JsonProperty("notify_summary") + private final String notifySummary; + + private final String image; + + private final Integer style; + + @JsonProperty("big_title") + private final String bigTitle; + + @JsonProperty("big_body") + private final String bigBody; + + @JsonProperty("auto_clear") + private final Integer autoClear; + + @JsonProperty("notify_id") + private final Integer notifyId; + + private final String group; + + private final BadgeNotification badge; + + private final String ticker; + + @JsonProperty("auto_cancel") + private final boolean autoCancel; + + private final String when; + + @JsonProperty("local_only") + private final Boolean localOnly; + + private final Importance importance; + + @JsonProperty("use_default_vibrate") + private final boolean useDefaultVibrate; + + @JsonProperty("use_default_light") + private final boolean useDefaultLight; + + @Builder.Default + @JsonProperty("vibrate_config") + private final List vibrateConfig = new ArrayList<>(); + + private final String visibility; + + @JsonProperty("light_settings") + private final LightSettings lightSettings; + + @JsonProperty("foreground_show") + private final boolean foregroundShow; + + @JsonProperty("inbox_content") + private final List inboxContent; + + private final List