From 3c42380ae2a89dd2037dcd1f594476fccfa79374 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20=C5=A0trobl?= Date: Wed, 13 Nov 2024 19:11:15 +0800 Subject: [PATCH] Fix #906: Allow customization of APNs environment on push device level (#907) --- docs-private/Developer-How-To-Start.md | 6 +- docs/PowerAuth-Push-Server-1.10.0.md | 16 ++ docs/Push-Server-API.md | 4 + docs/Push-Server-Database.md | 1 + ...241108-device-registration-environment.xml | 17 ++ .../1.10.x/db.changelog-version.xml | 1 + docs/sql/mssql/migration_1.9.0_1.10.0.sql | 5 + docs/sql/oracle/migration_1.9.0_1.10.0.sql | 4 + .../sql/postgresql/migration_1.9.0_1.10.0.sql | 4 + .../getlime/push/client/PushServerClient.java | 216 +++++++++++------- .../CreateDeviceForActivationsRequest.java | 12 +- .../model/request/CreateDeviceRequest.java | 12 +- .../CreateDeviceRequestValidator.java | 4 + .../model/PushDeviceRegistrationEntity.java | 6 + .../model/aggregate/UserDevice.java | 12 +- .../push/service/AppRelatedPushClient.java | 9 +- .../AppRelatedPushClientCacheLoader.java | 13 +- .../service/DeviceRegistrationService.java | 5 + .../push/service/PushDeviceService.java | 12 +- .../service/PushMessageSenderService.java | 59 +++-- .../push/service/PushSendingWorker.java | 36 +-- .../service/batch/UserDeviceItemReader.java | 2 +- .../service/batch/UserDeviceItemWriter.java | 4 +- .../PushServerAppCredentialConfiguration.java | 26 ++- .../rest/PushDeviceControllerTest.java | 5 +- .../push/service/ApnsEnvironmentProdTest.java | 132 +++++++++++ .../push/service/ApnsEnvironmentTest.java | 205 +++++++++++++++++ .../DeviceRegistrationServiceTest.java | 18 +- .../push/service/PushDeviceServiceTest.java | 27 ++- .../push/service/PushSendingWorkerTest.java | 1 + .../PushServerMultipleActivationsTests.java | 103 ++++++++- .../getlime/push/tests/PushServerTests.java | 112 +++++++-- .../application-test-apns-prod.properties | 36 +++ 33 files changed, 940 insertions(+), 185 deletions(-) create mode 100644 docs/db/changelog/changesets/powerauth-push-server/1.10.x/20241108-device-registration-environment.xml create mode 100644 powerauth-push-server/src/test/java/io/getlime/push/service/ApnsEnvironmentProdTest.java create mode 100644 powerauth-push-server/src/test/java/io/getlime/push/service/ApnsEnvironmentTest.java create mode 100644 powerauth-push-server/src/test/resources/application-test-apns-prod.properties diff --git a/docs-private/Developer-How-To-Start.md b/docs-private/Developer-How-To-Start.md index 798cf8582..d0c4c41b0 100644 --- a/docs-private/Developer-How-To-Start.md +++ b/docs-private/Developer-How-To-Start.md @@ -27,18 +27,18 @@ To generate SQL script run following command: #### PostgreSQL ```shell -liquibase --changeLogFile=./docs/db/changelog/changesets/powerauth-push-server/db.changelog-module.xml --output-file=./docs/sql/postgresql/migration_1.7.0_1.8.0.sql updateSQL --url=offline:postgresql +liquibase --changeLogFile=./docs/db/changelog/changesets/powerauth-push-server/db.changelog-module.xml --output-file=./docs/sql/postgresql/update.sql updateSQL --url=offline:postgresql ``` #### Oracle ```shell -liquibase --changeLogFile=./docs/db/changelog/changesets/powerauth-push-server/db.changelog-module.xml --output-file=./docs/sql/oracle/migration_1.7.0_1.8.0.sql updateSQL --url=offline:oracle +liquibase --changeLogFile=./docs/db/changelog/changesets/powerauth-push-server/db.changelog-module.xml --output-file=./docs/sql/oracle/update.sql updateSQL --url=offline:oracle ``` #### MSSQL ```shell -liquibase --changeLogFile=./docs/db/changelog/changesets/powerauth-push-server/db.changelog-module.xml --output-file=./docs/sql/mssql/migration_1.7.0_1.8.0.sql updateSQL --url=offline:mssql +liquibase --changeLogFile=./docs/db/changelog/changesets/powerauth-push-server/db.changelog-module.xml --output-file=./docs/sql/mssql/update.sql updateSQL --url=offline:mssql ``` diff --git a/docs/PowerAuth-Push-Server-1.10.0.md b/docs/PowerAuth-Push-Server-1.10.0.md index dff838120..c0b482391 100644 --- a/docs/PowerAuth-Push-Server-1.10.0.md +++ b/docs/PowerAuth-Push-Server-1.10.0.md @@ -46,3 +46,19 @@ The unconfigured application list endpoint `POST /admin/app/unconfigured/list` u The application detail endpoint `POST /admin/app/detail` uses new platform enumerations in both request and response. The push message send endpoint `POST /push/message/send` contains new platform enumerations in the response for the count of sent messages. + +### Customization of APNs Environment per Registered Device + +It is now possible to specify an APNs environment per device registration. The change is reflected in the REST API endpoints `/push/device/create` and `/push/device/create/multi` by addition of the `environment` parameter. + +The allowed values of the `environment` parameter are: +- `development` - development APNs host is used for sending push messages +- `production` - production APNs host is used for sending push messages + +For platforms other than APNs the parameter is not used, `null` value is allowed. + +For existing device registrations if the `environment` value is not specified during registration, the `environment` is decided based on application credential settings in table `push_app_credentials`, column `apns_environment`. If the environment is not configured on application level either, a global server setting is used as a fallback value. + +The global setting is controlled by property `powerauth.push.service.apns.useDevelopment`. In case the property is set to `false`, delivery to `development` APNs host is not allowed for devices registered with the `development` environment. + +This change is reflected in database by addition of parameter `environment` in table `push_device_registration`. diff --git a/docs/Push-Server-API.md b/docs/Push-Server-API.md index ac07119f1..38c0d838a 100644 --- a/docs/Push-Server-API.md +++ b/docs/Push-Server-API.md @@ -183,6 +183,7 @@ _Note: Since this endpoint is usually called by the back-end service, it is not "appId": "mobile-app", "token": "1234567890987654321234567890", "platform": "apns", + "environment": "development", "activationId": "49414e31-f3df-4cea-87e6-f214ca3b8412" } } @@ -191,6 +192,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` - `apns`, `fcm`, `hms` +- `environment` - `development` or `production` for APNs, `null` otherwise - `activationId` - Activation identifier #### Response 200 @@ -230,6 +232,7 @@ _Note: Since this endpoint is usually called by the back-end service, it is not "appId": "mobile-app", "token": "1234567890987654321234567890", "platform": "apns", + "environment": "development", "activationIds": [ "49414e31-f3df-4cea-87e6-f214ca3b8412", "26c94bf8-f594-4bd8-9c51-93449926b644" @@ -241,6 +244,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` - `apns`, `fcm`, `hms` +- `environment` - `development` or `production` for APNs, `null` otherwise - `activationIds` - Associated activation identifiers #### Response 200 diff --git a/docs/Push-Server-Database.md b/docs/Push-Server-Database.md index aee8d032b..0bbed5494 100644 --- a/docs/Push-Server-Database.md +++ b/docs/Push-Server-Database.md @@ -36,6 +36,7 @@ Stores push tokens specific for a given device. | `user_id` | INTEGER | index | Associated user ID. | | `app_id` | INTEGER | index | Associated application ID. | | `platform` | VARCHAR(30) | - | Mobile OS Platform ("apns", "fcm", "hms", "ios" (deprecated), "android" (deprecated), "huawei" (deprecated). | +| `environment` | VARCHAR(255) | - | APNs environment - `development` or `production`, `null` value is used for other platforms. | | `push_token` | VARCHAR(255) | - | Push token associated with a given device. Type of the token is determined by the `platform` column. | | `timestamp_last_registered` | TIMESTAMP | - | Timestamp of the last device registration. | | `is_active` | BOOLEAN | - | PowerAuth activation status (boolean), used as an activation status cache so that communication with PowerAuth Server can be minimal. | diff --git a/docs/db/changelog/changesets/powerauth-push-server/1.10.x/20241108-device-registration-environment.xml b/docs/db/changelog/changesets/powerauth-push-server/1.10.x/20241108-device-registration-environment.xml new file mode 100644 index 000000000..c1b6ec19a --- /dev/null +++ b/docs/db/changelog/changesets/powerauth-push-server/1.10.x/20241108-device-registration-environment.xml @@ -0,0 +1,17 @@ + + + + + + + + + Add columns environment to push_device_registration table + + + + + + diff --git a/docs/db/changelog/changesets/powerauth-push-server/1.10.x/db.changelog-version.xml b/docs/db/changelog/changesets/powerauth-push-server/1.10.x/db.changelog-version.xml index 6aff64b6f..d4b1948c3 100644 --- a/docs/db/changelog/changesets/powerauth-push-server/1.10.x/db.changelog-version.xml +++ b/docs/db/changelog/changesets/powerauth-push-server/1.10.x/db.changelog-version.xml @@ -4,5 +4,6 @@ xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.9.xsd"> + \ No newline at end of file diff --git a/docs/sql/mssql/migration_1.9.0_1.10.0.sql b/docs/sql/mssql/migration_1.9.0_1.10.0.sql index f73fab331..536161261 100644 --- a/docs/sql/mssql/migration_1.9.0_1.10.0.sql +++ b/docs/sql/mssql/migration_1.9.0_1.10.0.sql @@ -33,4 +33,9 @@ GO UPDATE push_app_credentials SET fcm_private_key = android_private_key, fcm_project_id = android_project_id; GO +-- Changeset powerauth-push-server/1.10.x/20241108-device-registration-environment.xml::1::Roman Strobl +-- Add columns environment to push_device_registration table +ALTER TABLE push_device_registration ADD environment varchar(255); +GO + diff --git a/docs/sql/oracle/migration_1.9.0_1.10.0.sql b/docs/sql/oracle/migration_1.9.0_1.10.0.sql index 17d80a38b..0203028bb 100644 --- a/docs/sql/oracle/migration_1.9.0_1.10.0.sql +++ b/docs/sql/oracle/migration_1.9.0_1.10.0.sql @@ -24,3 +24,7 @@ UPDATE push_app_credentials SET apns_bundle = ios_bundle, apns_environment = ios -- Changeset powerauth-push-server/1.10.x/20241029-migrate-android-to-fcm.xml::4::Roman Strobl -- Migrate existing android_* columns to fcm_* columns UPDATE push_app_credentials SET fcm_private_key = android_private_key, fcm_project_id = android_project_id; + +-- Changeset powerauth-push-server/1.10.x/20241108-device-registration-environment.xml::1::Roman Strobl +-- Add columns environment to push_device_registration table +ALTER TABLE push_device_registration ADD environment VARCHAR2(255); diff --git a/docs/sql/postgresql/migration_1.9.0_1.10.0.sql b/docs/sql/postgresql/migration_1.9.0_1.10.0.sql index 4d0daf764..0f2ebc7b2 100644 --- a/docs/sql/postgresql/migration_1.9.0_1.10.0.sql +++ b/docs/sql/postgresql/migration_1.9.0_1.10.0.sql @@ -23,3 +23,7 @@ UPDATE push_app_credentials SET apns_bundle = ios_bundle, apns_environment = ios -- Changeset powerauth-push-server/1.10.x/20241029-migrate-android-to-fcm.xml::4::Roman Strobl -- Migrate existing android_* columns to fcm_* columns UPDATE push_app_credentials SET fcm_private_key = android_private_key, fcm_project_id = android_project_id; + +-- Changeset powerauth-push-server/1.10.x/20241108-device-registration-environment.xml::1::Roman Strobl +-- Add columns environment to push_device_registration table +ALTER TABLE push_device_registration ADD environment VARCHAR(255); 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 7210e998e..89ba6d9b3 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 @@ -95,9 +95,9 @@ public PushServerClient(final RestClientConfiguration config, final Module... mo */ public ObjectResponse getServiceStatus() throws PushServerClientException { - logger.info("Calling push server status service - start"); + logger.info("call={}, callType={}, action: getServiceStatus, state: initiated", "/push/service/status", "GET"); final ObjectResponse result = getObjectImpl("/push/service/status", null, ServiceStatusResponse.class); - logger.info("Calling push server status service - finish"); + logger.info("call={}, callType={}, action: getServiceStatus, state: succeeded", "/push/service/status", "GET"); return result; } @@ -105,12 +105,15 @@ public ObjectResponse getServiceStatus() throws PushServe /** * Register anonymous device to the push server. * + * @deprecated use {@link #createDevice(CreateDeviceRequest)} + * * @param appId PowerAuth application app ID. * @param token Token received from the push service provider (APNs, FCM). * @param platform Mobile platform (APNs, FCM, HMS). * @return True if device registration was successful, false otherwise. * @throws PushServerClientException In case of network, response / JSON processing, or other IO error. */ + @Deprecated public boolean createDevice(String appId, String token, MobilePlatform platform) throws PushServerClientException { return createDevice(appId, token, platform, null); } @@ -118,6 +121,8 @@ public boolean createDevice(String appId, String token, MobilePlatform platform) /** * Register device associated with activation ID to the push server. * + * @deprecated use {@link #createDevice(CreateDeviceRequest)} + * * @param appId PowerAuth application app ID. * @param token Token received from the push service provider (APNs, FCM). * @param platform Mobile platform (APNs, FCM, HMS). @@ -125,6 +130,7 @@ public boolean createDevice(String appId, String token, MobilePlatform platform) * @return True if device registration was successful, false otherwise. * @throws PushServerClientException In case of network, response / JSON processing, or other IO error. */ + @Deprecated public boolean createDevice(String appId, String token, MobilePlatform platform, String activationId) throws PushServerClientException { CreateDeviceRequest request = new CreateDeviceRequest(); request.setAppId(appId); @@ -138,9 +144,31 @@ 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); + logger.info("call={}, callType={}, action: createDevice, state: initiated, appId: {}, token: {}, platform: {}, activationId: {}", + "/push/device/create", "POST", appId, maskToken(token), platform, activationId); + Response response = postObjectImpl("/push/device/create", new ObjectRequest<>(request)); + logger.info("call={}, callType={}, action: createDevice, state: succeeded", "/push/device/create", "POST"); + + return response.getStatus().equals(Response.Status.OK); + } + + /** + * Register a device to the push server. + * + * @param request Create device request. + * @return True if device registration was successful, false otherwise. + * @throws PushServerClientException In case of network, response / JSON processing, or other IO error. + */ + public boolean createDevice(final CreateDeviceRequest request) throws PushServerClientException { + String error = CreateDeviceRequestValidator.validate(request); + if (error != null) { + throw new PushServerClientException(error); + } + + logger.info("call={}, callType={}, action: createDevice, state: initiated, appId: {}, token: {}, platform: {}, environment: {}", + "/push/device/create", "POST", request.getAppId(), maskToken(request.getToken()), request.getPlatform(), request.getEnvironment()); Response response = postObjectImpl("/push/device/create", new ObjectRequest<>(request)); - logger.info("Calling create device service, appId: {}, token: {}, platform: {} - finish", appId, maskToken(token), platform); + logger.info("call={}, callType={}, action: createDevice, state: succeeded", "/push/device/create", "POST"); return response.getStatus().equals(Response.Status.OK); } @@ -148,6 +176,8 @@ public boolean createDevice(String appId, String token, MobilePlatform platform, /** * Register device associated with multiple activation IDs to the push server. * + * @deprecated use {@link #createDeviceForActivations(CreateDeviceForActivationsRequest)} + * * @param appId PowerAuth application app ID. * @param token Token received from the push service provider (APNs, FCM). * @param platform Mobile platform (APNs, FCM, HMS). @@ -155,6 +185,7 @@ public boolean createDevice(String appId, String token, MobilePlatform platform, * @return True if device registration was successful, false otherwise. * @throws PushServerClientException In case of network, response / JSON processing, or other IO error. */ + @Deprecated public boolean createDeviceForActivations(String appId, String token, MobilePlatform platform, List activationIds) throws PushServerClientException { CreateDeviceForActivationsRequest request = new CreateDeviceForActivationsRequest(); request.setAppId(appId); @@ -168,9 +199,32 @@ 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); + logger.info("call={}, callType={}, action: createDeviceForActivations, state: initiated, appId: {}, token: {}, platform: {}, activationIds: {}", + "/push/device/create/multi", "POST", request.getAppId(), maskToken(request.getToken()), request.getPlatform(), request.getActivationIds()); + Response response = postObjectImpl("/push/device/create/multi", new ObjectRequest<>(request)); + logger.info("call={}, callType={}, action: createDeviceForActivations, state: succeeded", "/push/device/create/multi", "POST"); + + return response.getStatus().equals(Response.Status.OK); + } + + /** + * Register device associated with multiple activation IDs to the push server. + * + * @param request Create device for activations request. + * @return True if device registration was successful, false otherwise. + * @throws PushServerClientException In case of network, response / JSON processing, or other IO error. + */ + public boolean createDeviceForActivations(final CreateDeviceForActivationsRequest request) throws PushServerClientException { + // Validate request on the client side. + String error = CreateDeviceRequestValidator.validate(request); + if (error != null) { + throw new PushServerClientException(error); + } + + logger.info("call={}, callType={}, action: createDeviceForActivations, state: initiated, appId: {}, token: {}, platform: {}, environment: {}, activationIds: {}", + "/push/device/create/multi", "POST", request.getAppId(), maskToken(request.getToken()), request.getPlatform(), request.getEnvironment(), request.getActivationIds()); Response response = postObjectImpl("/push/device/create/multi", new ObjectRequest<>(request)); - logger.info("Calling create device service, appId: {}, token: {}, platform: {} - finish", appId, maskToken(token), platform); + logger.info("call={}, callType={}, action: createDeviceForActivations, state: succeeded", "/push/device/create/multi", "POST"); return response.getStatus().equals(Response.Status.OK); } @@ -194,9 +248,10 @@ public boolean deleteDevice(String appId, String token) throws PushServerClientE throw new PushServerClientException(error); } - logger.info("Calling push server delete device service, appId: {}, token: {} - start", appId, maskToken(token)); + logger.info("call={}, callType={}, action: deleteDevice, state: initiated, appId: {}, token: {}", + "/push/device/delete", "POST", appId, maskToken(token)); Response response = postObjectImpl("/push/device/delete", new ObjectRequest<>(request)); - logger.info("Calling push server delete device service, appId: {}, token: {} - finish", appId, maskToken(token)); + logger.info("call={}, callType={}, action: deleteDevice, state: succeeded", "/push/device/delete", "POST"); return response.getStatus().equals(Response.Status.OK); } @@ -218,11 +273,12 @@ public boolean updateDeviceStatus(String activationId) throws PushServerClientEx throw new PushServerClientException(error); } - logger.info("Calling push server update device status, activation ID: {} - start", activationId); + logger.info("call={}, callType={}, action: updateDeviceStatus, state: initiated, activationId: {}", + "/push/device/status/update", "POST", activationId); // Note that there is just plain 'request' in the request, not 'new ObjectRequest<>(request)'. // This is due to the fact that standard PowerAuth Server callback format is used here. final Response response = postImpl("/push/device/status/update", request, new ParameterizedTypeReference<>(){}); - logger.info("Calling push server update device status, activation ID: {} - finish", activationId); + logger.info("call={}, callType={}, action: updateDeviceStatus, state: succeeded", "/push/device/status/update", "POST"); return response.getStatus().equals(Response.Status.OK); } @@ -260,9 +316,10 @@ public ObjectResponse sendPushMessage(String appId, Mode throw new PushServerClientException(error); } - logger.info("Calling push server to send a push message, app ID: {}, user ID: {} - start", appId, pushMessage.getUserId()); + logger.info("call={}, callType={}, action: sendPushMessage, state: initiated, appId: {}, userId: {}", + "/push/message/send", "POST", appId, pushMessage.getUserId()); final ObjectResponse result = postObjectImpl("/push/message/send", new ObjectRequest<>(request), PushMessageSendResult.class); - logger.info("Calling push server to send a push message, app ID: {}, user ID: {} - finish", appId, pushMessage.getUserId()); + logger.info("call={}, callType={}, action: sendPushMessage, state: succeeded", "/push/message/send", "POST"); return result; } @@ -300,9 +357,10 @@ public ObjectResponse sendPushMessageBatch(String appId, throw new PushServerClientException(error); } - logger.info("Calling push server to send a push message batch, app ID: {} - start", appId); + logger.info("call={}, callType={}, action: sendPushMessageBatch, state: initiated, appId: {}, batchSize: {}", + "/push/message/batch/send", "POST", appId, batch.size()); final ObjectResponse result = postObjectImpl("/push/message/batch/send", new ObjectRequest<>(request), PushMessageSendResult.class); - logger.info("Calling push server to send a push message batch, app ID: {} - finish", appId); + logger.info("call={}, callType={}, action: sendPushMessageBatch, state: succeeded", "/push/message/batch/send", "POST"); return result; } @@ -326,9 +384,11 @@ public ObjectResponse createCampaign(String appId, PushM throw new PushServerClientException(error); } - logger.info("Calling push server to create a push campaign, app ID: {} - start", appId); + logger.info("call={}, callType={}, action: createCampaign, state: initiated, appId: {}", + "/push/campaign/create", "POST", appId); final ObjectResponse result = postObjectImpl("/push/campaign/create", new ObjectRequest<>(request), CreateCampaignResponse.class); - logger.info("Calling push server to create a push campaign, app ID: {} - finish", appId); + logger.info("call={}, callType={}, action: createCampaign, state: succeeded", "/push/campaign/create", "POST"); + return result; } @@ -343,9 +403,10 @@ public ObjectResponse createCampaign(String appId, PushM public boolean deleteCampaign(Long campaignId) throws PushServerClientException { final String campaignIdSanitized = URLEncoder.encode(String.valueOf(campaignId), StandardCharsets.UTF_8); - logger.info("Calling push server to delete a push campaign, campaign ID: {} - start", campaignId); + logger.info("call={}, callType={}, action: deleteCampaign, state: initiated, campaignId: {}", + "/push/campaign/{campaignId}/delete", "POST", campaignId); final ObjectResponse response = postObjectImpl("/push/campaign/" + campaignIdSanitized + "/delete", null, DeleteCampaignResponse.class); - logger.info("Calling push server to delete a push campaign, campaign ID: {} - finish", campaignId); + logger.info("call={}, callType={}, action: deleteCampaign, state: succeeded", "/push/campaign/{campaignId}/delete", "POST"); return response.getStatus().equals(Response.Status.OK); } @@ -361,9 +422,10 @@ public ObjectResponse getListOfCampaigns(boolean all) t MultiValueMap params = new LinkedMultiValueMap<>(); params.put("all", Collections.singletonList(Boolean.valueOf(all).toString())); - logger.info("Calling push server to obtain a push campaign list - start"); + logger.info("call={}, callType={}, action: getListOfCampaigns, state: initiated", + "/push/campaign/list", "GET"); final ObjectResponse result = getObjectImpl("/push/campaign/list", params, ListOfCampaignsResponse.class); - logger.info("Calling push server to obtain a push campaign list - finish"); + logger.info("call={}, callType={}, action: getListOfCampaigns, state: succeeded", "/push/campaign/list", "GET"); return result; } @@ -378,9 +440,9 @@ public ObjectResponse getListOfCampaigns(boolean all) t public ObjectResponse getCampaign(Long campaignId) throws PushServerClientException { final String campaignIdSanitized = URLEncoder.encode(String.valueOf(campaignId), StandardCharsets.UTF_8); - logger.info("Calling push server to obtain a push campaign detail, campaign ID: {} - start", campaignId); + logger.info("call={}, callType={}, action: getCampaign, state: initiated, campaignId: {}", "/push/campaign/{campaignId}/detail", "GET", campaignId); final ObjectResponse result = getObjectImpl("/push/campaign/" + campaignIdSanitized + "/detail", null, CampaignResponse.class); - logger.info("Calling push server to obtain a push campaign detail, campaign ID: {} - finish", campaignId); + logger.info("call={}, callType={}, action: getCampaign, state: succeeded", "/push/campaign/{campaignId}/detail", "GET"); return result; } @@ -397,9 +459,9 @@ public boolean addUsersToCampaign(Long campaignId, List users) throws Pu final ListOfUsers listOfUsers = new ListOfUsers(users); final String campaignIdSanitized = URLEncoder.encode(String.valueOf(campaignId), StandardCharsets.UTF_8); - logger.info("Calling push server to add users to campaign, campaign ID: {} - start", campaignId); + logger.info("call={}, callType={}, action: addUsersToCampaign, state: initiated, campaignId: {}, userCount: {}", "/push/campaign/{campaignId}/user/add", "PUT", campaignId, users.size()); final Response response = putObjectImpl("/push/campaign/" + campaignIdSanitized + "/user/add", new ObjectRequest<>(listOfUsers)); - logger.info("Calling push server to add users to campaign, campaign ID: {} - finish", campaignId); + logger.info("call={}, callType={}, action: addUsersToCampaign, state: succeeded", "/push/campaign/{campaignId}/user/add", "PUT"); if (response == null) { throw new PushServerClientException(new Error("PUSH_SERVER_CLIENT_ERROR", "Network communication has failed.")); @@ -422,9 +484,9 @@ public PagedResponse getListOfUsersFromCampaign final MultiValueMap params = buildPages(page, size); final ParameterizedTypeReference> typeReference = new ParameterizedTypeReference<>() {}; - logger.info("Calling push server to get users from the campaign, campaign ID: {} - start", campaignId); + logger.info("call={}, callType={}, action: getListOfUsersFromCampaign, state: initiated, campaignId: {}, page: {}, size: {}", "/push/campaign/{campaignId}/user/list", "GET", campaignId, page, size); final PagedResponse result = getImpl("/push/campaign/" + campaignIdSanitized + "/user/list", params, typeReference); - logger.info("Calling push server to get users from the campaign, campaign ID: {} - finish", campaignId); + logger.info("call={}, callType={}, action: getListOfUsersFromCampaign, state: succeeded", "/push/campaign/{campaignId}/user/list", "GET"); return result; } @@ -441,9 +503,9 @@ public boolean deleteUsersFromCampaign(Long campaignId, List users) thro final ListOfUsers listOfUsers = new ListOfUsers(users); final String campaignIdSanitized = URLEncoder.encode(String.valueOf(campaignId), StandardCharsets.UTF_8); - logger.info("Calling push server to remove users from the campaign, campaign ID: {} - start", campaignId); + logger.info("call={}, callType={}, action: deleteUsersFromCampaign, state: initiated, campaignId: {}, userCount: {}", "/push/campaign/{campaignId}/user/delete", "POST", campaignId, users.size()); final Response response = postObjectImpl("/push/campaign/" + campaignIdSanitized + "/user/delete", new ObjectRequest<>(listOfUsers)); - logger.info("Calling push server to remove users from the campaign, campaign ID: {} - finish", campaignId); + logger.info("call={}, callType={}, action: deleteUsersFromCampaign, state: succeeded", "/push/campaign/{campaignId}/user/delete", "POST"); return response.getStatus().equals(Response.Status.OK); } @@ -467,9 +529,9 @@ public boolean sendTestCampaign(Long campaignId, String userId) throws PushServe throw new PushServerClientException(error); } - logger.info("Calling push server to send test campaign, campaign ID: {}, user ID: {} - start", campaignId, userId); + logger.info("call={}, callType={}, action: sendTestCampaign, state: initiated, campaignId: {}, userId: {}", "/push/campaign/send/test/{campaignId}", "POST", campaignId, userId); final Response response = postObjectImpl("/push/campaign/send/test/" + campaignIdSanitized, new ObjectRequest<>(request)); - logger.info("Calling push server to send test campaign, campaign ID: {}, user ID: {} - finish", campaignId, userId); + logger.info("call={}, callType={}, action: sendTestCampaign, state: succeeded", "/push/campaign/send/test/{campaignId}", "POST"); return response.getStatus().equals(Response.Status.OK); } @@ -484,9 +546,9 @@ public boolean sendTestCampaign(Long campaignId, String userId) throws PushServe public boolean sendCampaign(Long campaignId) throws PushServerClientException { final String campaignIdSanitized = URLEncoder.encode(String.valueOf(campaignId), StandardCharsets.UTF_8); - logger.info("Calling push server to send a production campaign, campaign ID: {} - start", campaignId); + logger.info("call={}, callType={}, action: sendCampaign, state: initiated, campaignId: {}", "/push/campaign/send/live/{campaignId}", "POST", campaignId); final Response response = postObjectImpl("/push/campaign/send/live/" + campaignIdSanitized, null); - logger.info("Calling push server to send a production campaign, campaign ID: {} - finish", campaignId); + logger.info("call={}, callType={}, action: sendCampaign, state: succeeded", "/push/campaign/send/live/{campaignId}", "POST"); return response.getStatus().equals(Response.Status.OK); } @@ -497,9 +559,9 @@ public boolean sendCampaign(Long campaignId) throws PushServerClientException { * @throws PushServerClientException Thrown when communication with Push Server fails. */ public ObjectResponse getApplicationList() throws PushServerClientException { - logger.info("Calling push server to retrieve list of applications - start"); + logger.info("call={}, callType={}, action: getApplicationList, state: initiated", "/admin/app/list", "GET"); final ObjectResponse response = getObjectImpl("/admin/app/list", null, GetApplicationListResponse.class); - logger.info("Calling push server to retrieve list of applications - finish"); + logger.info("call={}, callType={}, action: getApplicationList, state: succeeded", "/admin/app/list", "GET"); return response; } @@ -509,9 +571,9 @@ public ObjectResponse getApplicationList() throws Pu * @throws PushServerClientException Thrown when communication with Push Server fails. */ public ObjectResponse getUnconfiguredApplicationList() throws PushServerClientException { - logger.info("Calling push server to retrieve list of unconfigured applications - start"); + logger.info("call={}, callType={}, action: getUnconfiguredApplicationList, state: initiated", "/admin/app/unconfigured/list", "GET"); final ObjectResponse response = getObjectImpl("/admin/app/unconfigured/list", null, GetApplicationListResponse.class); - logger.info("Calling push server to retrieve list of unconfigured applications - finish"); + logger.info("call={}, callType={}, action: getUnconfiguredApplicationList, state: succeeded", "/admin/app/unconfigured/list", "GET"); return response; } @@ -522,9 +584,9 @@ public ObjectResponse getUnconfiguredApplicationList * @throws PushServerClientException Thrown when communication with Push Server fails. */ public ObjectResponse getApplicationDetail(final GetApplicationDetailRequest request) throws PushServerClientException { - logger.info("Calling push server to retrieve application detail, ID: {} - start", request.getAppId()); + logger.info("call={}, callType={}, action: getApplicationDetail, state: initiated, appId: {}", "/admin/app/detail", "POST", 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", request.getAppId()); + logger.info("call={}, callType={}, action: getApplicationDetail, state: succeeded", "/admin/app/detail", "POST"); return response; } @@ -536,9 +598,9 @@ public ObjectResponse getApplicationDetail(final G */ public ObjectResponse createApplication(String appId) throws PushServerClientException { final CreateApplicationRequest request = new CreateApplicationRequest(appId); - logger.info("Calling push server to create application, app ID: {} - start", appId); + logger.info("call={}, callType={}, action: createApplication, state: initiated, appId: {}", "/admin/app/create", "POST", appId); final ObjectResponse response = postObjectImpl("/admin/app/create", new ObjectRequest<>(request), CreateApplicationResponse.class); - logger.info("Calling push server to create application, app ID: {} - finish", appId); + logger.info("call={}, callType={}, action: createApplication, state: succeeded", "/admin/app/create", "POST"); return response; } @@ -560,9 +622,9 @@ public ObjectResponse createApplication(String appId) public Response updateIos(String appId, String bundle, String keyId, String teamId, ApnsEnvironment environment, byte[] privateKey) throws PushServerClientException { final String privateKeyBase64 = Base64.getEncoder().encodeToString(privateKey); final UpdateIosRequest request = new UpdateIosRequest(appId, bundle, keyId, teamId, environment, privateKeyBase64); - logger.info("Calling push server to update iOS, ID: {} - start", appId); + logger.info("call={}, callType={}, action: updateIos, state: initiated, appId: {}", "/admin/app/ios/update", "PUT", appId); final Response response = putObjectImpl("/admin/app/ios/update", new ObjectRequest<>(request)); - logger.info("Calling push server to update iOS, ID: {} - finish", appId); + logger.info("call={}, callType={}, action: updateIos, state: succeeded", "/admin/app/ios/update", "PUT"); return response; } @@ -574,9 +636,9 @@ public Response updateIos(String appId, String bundle, String keyId, String team * @throws PushServerClientException Thrown when communication with Push Server fails. */ public Response updateApns(final UpdateApnsRequest updateRequest) throws PushServerClientException { - logger.info("Calling push server to update APNs, ID: {} - start", updateRequest.getAppId()); + logger.info("call={}, callType={}, action: updateApns, state: initiated, appId: {}", "/admin/app/apns", "PUT", updateRequest.getAppId()); final Response response = putObjectImpl("/admin/app/apns", new ObjectRequest<>(updateRequest)); - logger.info("Calling push server to update APNs, ID: {} - finish", updateRequest.getAppId()); + logger.info("call={}, callType={}, action: updateApns, state: succeeded", "/admin/app/apns", "PUT"); return response; } @@ -592,9 +654,9 @@ public Response updateApns(final UpdateApnsRequest updateRequest) throws PushSer @Deprecated public Response removeIos(String appId) throws PushServerClientException { final RemoveIosRequest request = new RemoveIosRequest(appId); - logger.info("Calling push server to remove iOS, ID: {} - start", appId); + logger.info("call={}, callType={}, action: removeIos, state: initiated, appId: {}", "/admin/app/ios/remove", "POST", appId); final Response response = postObjectImpl("/admin/app/ios/remove", new ObjectRequest<>(request)); - logger.info("Calling push server to remove iOS, ID: {} - finish", appId); + logger.info("call={}, callType={}, action: removeIos, state: succeeded", "/admin/app/ios/remove", "POST"); return response; } @@ -606,9 +668,9 @@ public Response removeIos(String appId) throws PushServerClientException { * @throws PushServerClientException Thrown when communication with Push Server fails. */ public Response removeApns(String appId) throws PushServerClientException { - logger.info("Calling push server to remove APNs, ID: {} - start", appId); + logger.info("call={}, callType={}, action: removeApns, state: initiated, appId: {}", "/admin/app/apns", "DELETE", appId); final Response response = deleteImpl("/admin/app/apns?appId=" + appId); - logger.info("Calling push server to remove APNs, ID: {} - finish", appId); + logger.info("call={}, callType={}, action: removeApns, state: succeeded", "/admin/app/apns", "DELETE"); return response; } @@ -627,9 +689,9 @@ public Response removeApns(String appId) throws PushServerClientException { public Response updateAndroid(String appId, String projectId, byte[] privateKey) throws PushServerClientException { final String privateKeyBase64 = Base64.getEncoder().encodeToString(privateKey); final UpdateAndroidRequest request = new UpdateAndroidRequest(appId, projectId, privateKeyBase64); - logger.info("Calling push server to update android, ID: {} - start", appId); + logger.info("call={}, callType={}, action: updateAndroid, state: initiated, appId: {}", "/admin/app/android/update", "PUT", appId); final Response response = putObjectImpl("/admin/app/android/update", new ObjectRequest<>(request)); - logger.info("Calling push server to update android, ID: {} - finish", appId); + logger.info("call={}, callType={}, action: updateAndroid, state: succeeded", "/admin/app/android/update", "PUT"); return response; } @@ -641,9 +703,9 @@ public Response updateAndroid(String appId, String projectId, byte[] privateKey) * @throws PushServerClientException Thrown when communication with Push Server fails. */ public Response updateFcm(final UpdateFcmRequest updateRequest) throws PushServerClientException { - logger.info("Calling push server to update FCM, ID: {} - start", updateRequest.getAppId()); + logger.info("call={}, callType={}, action: updateFcm, state: initiated, appId: {}", "/admin/app/fcm", "PUT", updateRequest.getAppId()); final Response response = putObjectImpl("/admin/app/fcm", new ObjectRequest<>(updateRequest)); - logger.info("Calling push server to update FCM, ID: {} - finish", updateRequest.getAppId()); + logger.info("call={}, callType={}, action: updateFcm, state: succeeded", "/admin/app/fcm", "PUT"); return response; } @@ -659,9 +721,9 @@ public Response updateFcm(final UpdateFcmRequest updateRequest) throws PushServe @Deprecated public Response removeAndroid(String appId) throws PushServerClientException { final RemoveAndroidRequest request = new RemoveAndroidRequest(appId); - logger.info("Calling push server to remove android, ID: {} - start", appId); + logger.info("call={}, callType={}, action: removeAndroid, state: initiated, appId: {}", "/admin/app/android/remove", "POST", appId); final Response response = postObjectImpl("/admin/app/android/remove", new ObjectRequest<>(request)); - logger.info("Calling push server to remove android, ID: {} - finish", appId); + logger.info("call={}, callType={}, action: removeAndroid, state: succeeded", "/admin/app/android/remove", "POST"); return response; } @@ -673,9 +735,9 @@ public Response removeAndroid(String appId) throws PushServerClientException { * @throws PushServerClientException Thrown when communication with Push Server fails. */ public Response removeFcm(String appId) throws PushServerClientException { - logger.info("Calling push server to remove FCM, ID: {} - start", appId); + logger.info("call={}, callType={}, action: removeFcm, state: initiated, appId: {}", "/admin/app/fcm", "DELETE", appId); final Response response = deleteImpl("/admin/app/fcm?appId=" + appId); - logger.info("Calling push server to remove FCM, ID: {} - finish", appId); + logger.info("call={}, callType={}, action: removeFcm, state: succeeded", "/admin/app/fcm", "DELETE"); return response; } @@ -690,9 +752,9 @@ public Response removeFcm(String appId) throws PushServerClientException { */ @Deprecated public Response updateHuawei(final UpdateHuaweiRequest request) throws PushServerClientException { - logger.info("Calling push server to update Huawei, ID: {} - start", request.getAppId()); + logger.info("call={}, callType={}, action: updateHuawei, state: initiated, appId: {}", "/admin/app/huawei/update", "PUT", 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()); + logger.info("call={}, callType={}, action: updateHuawei, state: succeeded", "/admin/app/huawei/update", "PUT"); return response; } @@ -704,15 +766,15 @@ public Response updateHuawei(final UpdateHuaweiRequest request) throws PushServe * @throws PushServerClientException Thrown when communication with Push Server fails. */ public Response updateHms(final UpdateHmsRequest request) throws PushServerClientException { - logger.info("Calling push server to update HMS, ID: {} - start", request.getAppId()); + logger.info("call={}, callType={}, action: updateHms, state: initiated, appId: {}", "/admin/app/hms", "PUT", request.getAppId()); final Response response = putObjectImpl("/admin/app/hms", new ObjectRequest<>(request)); - logger.info("Calling push server to update HMS, ID: {} - finish", request.getAppId()); + logger.info("call={}, callType={}, action: updateHms, state: succeeded", "/admin/app/hms", "PUT"); return response; } /** * Remove Huawei record from an application credentials entity. - * + * * @deprecated use {@link #removeHms(String)} * * @param appId Application credentials entity ID. @@ -722,9 +784,9 @@ public Response updateHms(final UpdateHmsRequest request) throws PushServerClien @Deprecated public Response removeHuawei(String appId) throws PushServerClientException { final RemoveHuaweiRequest request = new RemoveHuaweiRequest(appId); - logger.info("Calling push server to remove Huawei, ID: {} - start", appId); + logger.info("call={}, callType={}, action: removeHuawei, state: initiated, appId: {}", "/admin/app/huawei/remove", "POST", appId); final Response response = postObjectImpl("/admin/app/huawei/remove", new ObjectRequest<>(request)); - logger.info("Calling push server to remove Huawei, ID: {} - finish", appId); + logger.info("call={}, callType={}, action: removeHuawei, state: succeeded", "/admin/app/huawei/remove", "POST"); return response; } @@ -736,9 +798,9 @@ public Response removeHuawei(String appId) throws PushServerClientException { * @throws PushServerClientException Thrown when communication with Push Server fails. */ public Response removeHms(String appId) throws PushServerClientException { - logger.info("Calling push server to remove HMS, ID: {} - start", appId); + logger.info("call={}, callType={}, action: removeHms, state: initiated, appId: {}", "/admin/app/hms", "DELETE", appId); final Response response = deleteImpl("/admin/app/hms?appId=" + appId); - logger.info("Calling push server to remove HMS, ID: {} - finish", appId); + logger.info("call={}, callType={}, action: removeHms, state: succeeded", "/admin/app/hms", "DELETE"); return response; } @@ -749,9 +811,9 @@ public Response removeHms(String appId) throws PushServerClientException { * @throws PushServerClientException Thrown when communication with Push Server fails. */ public ObjectResponse postMessage(CreateInboxMessageRequest request) throws PushServerClientException { - logger.info("Calling push server to send message to inbox of: {}, subject: {} - start", request.getUserId(), request.getSubject()); + logger.info("call={}, callType={}, action: postMessage, state: initiated, userId: {}, subject: {}", "/inbox/messages", "POST", request.getUserId(), request.getSubject()); final ObjectResponse response = postObjectImpl("/inbox/messages", new ObjectRequest<>(request), GetInboxMessageDetailResponse.class); - logger.info("Calling push server to send message to inbox of: {}, subject: {} - finish", request.getUserId(), request.getSubject()); + logger.info("call={}, callType={}, action: postMessage, state: succeeded", "/inbox/messages", "POST"); return response; } @@ -772,9 +834,9 @@ public PagedResponse fetchMessageListForUser(String userId, params.add("onlyUnread", Boolean.toString(onlyUnread)); final ParameterizedTypeReference> typeReference = new ParameterizedTypeReference<>() {}; - logger.info("Calling push server fetch messages for user: {} - start", userId); + logger.info("call={}, callType={}, action: fetchMessageListForUser, state: initiated, userId: {}, onlyUnread: {}", "/inbox/messages/list", "GET", userId, onlyUnread); final PagedResponse result = getImpl("/inbox/messages/list", params, typeReference); - logger.info("Calling push server fetch messages for user: {} - finish", userId); + logger.info("call={}, callType={}, action: fetchMessageListForUser, state: succeeded", "/inbox/messages/list", "GET"); return result; } @@ -792,9 +854,9 @@ public ObjectResponse fetchMessageCountForUser(Str params.add("appId", appId); final ParameterizedTypeReference> typeReference = new ParameterizedTypeReference<>() {}; - logger.info("Calling push server fetch message count for user: {} - start", userId); + logger.info("call={}, callType={}, action: fetchMessageCountForUser, state: initiated, userId: {}, appId: {}", "/inbox/messages/count", "GET", userId, appId); final ObjectResponse result = getImpl("/inbox/messages/count", params, typeReference); - logger.info("Calling push server fetch message count for user: {} - finish", userId); + logger.info("call={}, callType={}, action: fetchMessageCountForUser, state: succeeded", "/inbox/messages/count", "GET"); return result; } @@ -810,9 +872,9 @@ public Response readAllMessages(String userId, String appId) throws PushServerCl final ReadAllInboxMessagesRequest request = new ReadAllInboxMessagesRequest(); request.setUserId(userId); request.setAppId(appId); - logger.info("Calling push server to mark all messages read in inbox of user: {} - start", userId); + logger.info("call={}, callType={}, action: readAllMessages, state: initiated, userId: {}, appId: {}", "/inbox/messages/read-all", "POST", userId, appId); final Response response = postObjectImpl("/inbox/messages/read-all", new ObjectRequest<>(request)); - logger.info("Calling push server to mark all messages read in inbox of user: {} - finish", userId); + logger.info("call={}, callType={}, action: readAllMessages, state: succeeded", "/inbox/messages/read-all", "POST"); return response; } @@ -826,9 +888,9 @@ public ObjectResponse fetchMessageDetail(String i final MultiValueMap params = new LinkedMultiValueMap<>(); params.add("id", inboxId); - logger.info("Calling push server fetch message ID: {} - start", inboxId); + logger.info("call={}, callType={}, action: fetchMessageDetail, state: initiated, inboxId: {}", "/inbox/messages/detail", "GET", inboxId); final ObjectResponse result = getObjectImpl("/inbox/messages/detail", params, GetInboxMessageDetailResponse.class); - logger.info("Calling push server fetch message ID: {} - finish", inboxId); + logger.info("call={}, callType={}, action: fetchMessageDetail, state: succeeded", "/inbox/messages/detail", "GET"); return result; } @@ -842,9 +904,9 @@ public ObjectResponse fetchMessageDetail(String i public ObjectResponse readMessage(String inboxId) throws PushServerClientException { final ReadInboxMessageRequest request = new ReadInboxMessageRequest(); request.setInboxId(inboxId); - logger.info("Calling push server to read message to inbox of: {} - start", inboxId); + logger.info("call={}, callType={}, action: readMessage, state: initiated, inboxId: {}", "/inbox/messages/read", "POST", inboxId); final ObjectResponse response = postObjectImpl("/inbox/messages/read", new ObjectRequest<>(request), GetInboxMessageDetailResponse.class); - logger.info("Calling push server to read message to inbox of: {} - finish", inboxId); + logger.info("call={}, callType={}, action: readMessage, state: succeeded", "/inbox/messages/read", "POST"); return response; } 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 b092d5519..2464bb35c 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,14 +15,14 @@ */ package io.getlime.push.model.request; +import io.getlime.push.model.enumeration.ApnsEnvironment; 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 lombok.*; import java.util.ArrayList; import java.util.List; @@ -34,6 +34,9 @@ */ @Getter @Setter +@Builder +@AllArgsConstructor +@NoArgsConstructor public class CreateDeviceForActivationsRequest { /** @@ -56,6 +59,11 @@ public class CreateDeviceForActivationsRequest { @NotNull private MobilePlatform platform; + /** + * Environment for APNs (optional). + */ + private ApnsEnvironment environment; + /** * Activation IDs. */ 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 7c5192f84..e2e45690a 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,12 +15,12 @@ */ package io.getlime.push.model.request; +import io.getlime.push.model.enumeration.ApnsEnvironment; 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; +import lombok.*; /** * Request object used for device registration. @@ -29,6 +29,9 @@ */ @Getter @Setter +@Builder +@AllArgsConstructor +@NoArgsConstructor public class CreateDeviceRequest { /** @@ -48,6 +51,11 @@ public class CreateDeviceRequest { @NotNull private MobilePlatform platform; + /** + * Environment for APNs (optional). + */ + private ApnsEnvironment environment; + /** * Activation ID. */ 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 5849764a2..047855a6d 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 @@ -15,6 +15,7 @@ */ package io.getlime.push.model.validator; +import io.getlime.push.model.enumeration.MobilePlatform; import io.getlime.push.model.request.CreateDeviceForActivationsRequest; import io.getlime.push.model.request.CreateDeviceRequest; @@ -71,6 +72,9 @@ public static String validate(CreateDeviceForActivationsRequest request) { if (request.getPlatform() == null) { return "Platform must not be null."; } + if (request.getPlatform() != MobilePlatform.APNS && request.getEnvironment() != null) { + return "Environment specified for a platform that does not support environment setting."; + } 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/repository/model/PushDeviceRegistrationEntity.java b/powerauth-push-server/src/main/java/io/getlime/push/repository/model/PushDeviceRegistrationEntity.java index 3cbfec630..39c02a0b7 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 @@ -81,6 +81,12 @@ public class PushDeviceRegistrationEntity implements Serializable { @Column(name = "push_token", nullable = false) private String pushToken; + /** + * Environment for APNs (optional). + */ + @Column(name = "environment") + private String environment; + /** * Timestamp last registered. */ 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 13e7701ff..fd474d50c 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,7 @@ */ package io.getlime.push.repository.model.aggregate; +import io.getlime.push.model.enumeration.ApnsEnvironment; import io.getlime.push.repository.model.Platform; import lombok.Getter; import lombok.Setter; @@ -63,6 +64,11 @@ public class UserDevice { */ private Platform platform; + /** + * APNs environment (optional). + */ + private String environment; + /** * Push token. */ @@ -76,15 +82,17 @@ public class UserDevice { * @param campaignId Campaign ID. * @param appId App ID. * @param platform Platform. + * @param environment APNs environment (optional). * @param token Push token. */ - public UserDevice(String userId, Long deviceId, String activationId, Long campaignId, Long appId, Platform platform, String token) { + public UserDevice(String userId, Long deviceId, String activationId, Long campaignId, Long appId, Platform platform, String environment, String token) { this.userId = userId; this.deviceId = deviceId; this.activationId = activationId; this.campaignId = campaignId; this.appId = appId; this.platform = platform; + this.environment = environment; this.token = token; } @@ -98,6 +106,7 @@ public boolean equals(Object o) { if (!campaignId.equals(that.campaignId)) return false; if (!appId.equals(that.appId)) return false; if (!platform.equals(that.platform)) return false; + if (environment != that.environment) return false; return token.equals(that.token); } @@ -106,6 +115,7 @@ public int hashCode() { int result = campaignId.hashCode(); result = 31 * result + appId.hashCode(); result = 31 * result + platform.hashCode(); + result = 31 * result + environment.hashCode(); result = 31 * result + token.hashCode(); return result; } 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 4ba2611e1..cdfe4a4df 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 @@ -37,9 +37,14 @@ public class AppRelatedPushClient { private AppCredentialsEntity appCredentials; /** - * APNS client instance, used for Apple Push Notification service. + * APNS client instance, used for Apple Push Notification service in development mode. */ - private ApnsClient apnsClient; + private ApnsClient apnsClientDevelopment; + + /** + * APNS client instance, used for Apple Push Notification service in production mode. + */ + private ApnsClient apnsClientProduction; /** * FCM client instance, used for Firebase Cloud Messaging. diff --git a/powerauth-push-server/src/main/java/io/getlime/push/service/AppRelatedPushClientCacheLoader.java b/powerauth-push-server/src/main/java/io/getlime/push/service/AppRelatedPushClientCacheLoader.java index 9546846f7..b3c54e999 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/service/AppRelatedPushClientCacheLoader.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/service/AppRelatedPushClientCacheLoader.java @@ -17,7 +17,9 @@ import com.eatthepath.pushy.apns.ApnsClient; import com.github.benmanes.caffeine.cache.CacheLoader; +import io.getlime.push.configuration.PushServiceConfiguration; import io.getlime.push.errorhandling.exceptions.PushServerException; +import io.getlime.push.model.enumeration.ApnsEnvironment; import io.getlime.push.repository.AppCredentialsRepository; import io.getlime.push.repository.model.AppCredentialsEntity; import io.getlime.push.service.fcm.FcmClient; @@ -43,6 +45,8 @@ public class AppRelatedPushClientCacheLoader implements CacheLoader devices = lookupDeviceRegistrations(appId, activationId, pushToken); @@ -89,6 +91,7 @@ public void createOrUpdateDevice(final CreateDeviceRequest requestObject, final } device.setTimestampLastRegistered(new Date()); device.setPlatform(convert(platform)); + device.setEnvironment(environment != null ? environment.getKey() : null); updateActivationForDevice(device, activationId); pushDeviceRepository.save(device); } @@ -98,6 +101,7 @@ public void createOrUpdateDevices(final CreateDeviceForActivationsRequest reques final String appId = request.getAppId(); final String pushToken = request.getToken(); final MobilePlatform platform = request.getPlatform(); + final ApnsEnvironment environment = request.getEnvironment(); final List activationIds = request.getActivationIds(); // Initialize loop variables. @@ -130,6 +134,7 @@ public void createOrUpdateDevices(final CreateDeviceForActivationsRequest reques } device.setTimestampLastRegistered(new Date()); device.setPlatform(convert(platform)); + device.setEnvironment(environment != null ? environment.getKey() : null); updateActivationForDevice(device, activationId); PushDeviceRegistrationEntity registeredDevice = pushDeviceRepository.save(device); usedDeviceRegistrationIds.add(registeredDevice.getId()); 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 index 70ea9a5d8..3a8da31c0 100644 --- 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 @@ -50,8 +50,8 @@ public Response createDevice(final CreateDeviceRequest request) throws PushServe 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()); + logger.info("Received createDevice request, app ID: {}, activation ID: {}, token: {}, platform: {}, environment: {}", request.getAppId(), + request.getActivationId(), maskPushToken(request.getToken()), request.getPlatform(), request.getEnvironment()); final String errorMessage = CreateDeviceRequestValidator.validate(request); if (errorMessage != null) { @@ -61,7 +61,7 @@ public Response createDevice(final CreateDeviceRequest request) throws PushServe 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()); + logger.info("The createDevice request succeeded, app ID: {}, activation ID: {}, platform: {}, environment: {}", request.getAppId(), request.getActivationId(), request.getPlatform(), request.getEnvironment()); return new Response(); } @@ -73,8 +73,8 @@ public Response createDeviceMultipleActivations(final CreateDeviceForActivations 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()); + logger.info("Received createDeviceMultipleActivations request, app ID: {}, activation IDs: {}, token: {}, platform: {}, environment: {}", + request.getAppId(), request.getActivationIds(), maskPushToken(request.getToken()), request.getPlatform(), request.getEnvironment()); final String errorMessage = CreateDeviceRequestValidator.validate(request); if (errorMessage != null) { @@ -84,7 +84,7 @@ public Response createDeviceMultipleActivations(final CreateDeviceForActivations 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()); + logger.info("The createDeviceMultipleActivations request succeeded, app ID: {}, activation IDs: {}, platform: {}, environment: {}", request.getAppId(), request.getActivationIds(), request.getPlatform(), request.getEnvironment()); return new Response(); } 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 7d73c8918..63f8c0b02 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 @@ -16,16 +16,18 @@ package io.getlime.push.service; +import com.eatthepath.pushy.apns.ApnsClient; import com.github.benmanes.caffeine.cache.LoadingCache; import io.getlime.push.configuration.PushServiceConfiguration; import io.getlime.push.errorhandling.exceptions.PushServerException; import io.getlime.push.model.entity.*; +import io.getlime.push.model.enumeration.ApnsEnvironment; import io.getlime.push.model.enumeration.Mode; import io.getlime.push.model.enumeration.Priority; import io.getlime.push.model.validator.PushMessageValidator; -import io.getlime.push.repository.AppCredentialsRepository; 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; @@ -47,7 +49,6 @@ public class PushMessageSenderService { private final PushSendingWorker pushSendingWorker; - private final AppCredentialsRepository appCredentialsRepository; private final PushDeviceRepository pushDeviceRepository; private final PushMessageDAO pushMessageDAO; private final LoadingCache appRelatedPushClientCache; @@ -78,7 +79,8 @@ public BasePushMessageSendResult sendPushMessage(final String appId, final Mode validatePushMessage(pushMessage); // Fetch connected devices - final List devices = getPushDevices(pushClient.getAppCredentials().getId(), pushMessage.getUserId(), pushMessage.getActivationId()); + final AppCredentialsEntity appCredentials = pushClient.getAppCredentials(); + final List devices = getPushDevices(appCredentials.getId(), pushMessage.getUserId(), pushMessage.getActivationId()); // Iterate over all devices for given user for (final PushDeviceRegistrationEntity device : devices) { @@ -96,13 +98,17 @@ public BasePushMessageSendResult sendPushMessage(final String appId, final Mode final Platform platform = device.getPlatform(); if (platform == Platform.IOS || platform == Platform.APNS) { - if (pushClient.getApnsClient() == null) { - logger.error("Push message cannot be sent to APNS because APNS is not configured in push server."); + final PushMessageSendResult.PlatformResult platformResult = sendResult.getApns(); + final PushSendingCallback callback = createPushSendingCallback(mode, device, platformResult, pushMessageObject, phaser); + final String apnsEnvironment = resolveApnsEnvironment(device.getEnvironment(), appCredentials.getApnsEnvironment()); + if (apnsEnvironment == null) { + logger.error("Push message cannot be sent because APNs development host is requested, however the server is in production mode. Check configuration of application property 'powerauth.push.service.apns.useDevelopment'."); + callback.didFinishSendingMessage(PushSendingCallback.Result.FAILED); arriveAndDeregisterPhaserForMode(phaser, mode); continue; } - final PushMessageSendResult.PlatformResult platformResult = sendResult.getApns(); - pushSendingWorker.sendMessageToApns(pushClient.getApnsClient(), pushMessage.getBody(), pushMessage.getAttributes(), pushMessage.getPriority(), device.getPushToken(), pushClient.getAppCredentials().getApnsBundle(), createPushSendingCallback(mode, device, platformResult, pushMessageObject, phaser)); + final ApnsClient apnsClient = ApnsEnvironment.PRODUCTION.getKey().equals(apnsEnvironment) ? pushClient.getApnsClientProduction() : pushClient.getApnsClientDevelopment(); + pushSendingWorker.sendMessageToApns(apnsClient, pushMessage.getBody(), pushMessage.getAttributes(), pushMessage.getPriority(), device.getPushToken(), pushClient.getAppCredentials().getApnsBundle(), callback); } else if (platform == Platform.ANDROID || platform == Platform.FCM) { if (pushClient.getFcmClient() == null) { logger.error("Push message cannot be sent to FCM because FCM is not configured in push server."); @@ -174,8 +180,8 @@ 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, 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); + public void sendCampaignMessage(String appId, Platform platform, String environment, String token, PushMessageBody pushMessageBody, String userId, Long deviceId, String activationId) throws PushServerException { + sendCampaignMessage(appId, platform, environment, token, pushMessageBody, null, Priority.HIGH, userId, deviceId, activationId); } /** @@ -183,7 +189,8 @@ public void sendCampaignMessage(String appId, Platform platform, String token, P * credentials for given application. Return the result in the callback. * * @param appId App ID. - * @param platform Mobile platform (iOS, Android). + * @param platform Mobile platform (APNs, FCM, HMS). + * @param environment APNs environment (optional). * @param token Push message token. * @param pushMessageBody Push message body. * @param attributes Push message attributes. @@ -194,14 +201,22 @@ public void sendCampaignMessage(String appId, Platform platform, String token, P * @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, Platform platform, final String token, PushMessageBody pushMessageBody, PushMessageAttributes attributes, Priority priority, String userId, Long deviceId, String activationId) throws PushServerException { + public void sendCampaignMessage(final String appId, final Platform platform, final String environment, final String token, final PushMessageBody pushMessageBody, final PushMessageAttributes attributes, final Priority priority, final String userId, final Long deviceId, final String activationId) throws PushServerException { final AppRelatedPushClient pushClient = prepareClients(appId); final PushMessageEntity pushMessageObject = pushMessageDAO.storePushMessageObject(pushMessageBody, attributes, userId, activationId, deviceId); switch (platform) { - case IOS, APNS -> - pushSendingWorker.sendMessageToApns(pushClient.getApnsClient(), pushMessageBody, attributes, priority, token, pushClient.getAppCredentials().getApnsBundle(), createPushSendingCallback(token, pushMessageObject, pushClient)); + case IOS, APNS -> { + final String environmentAppConfig = pushClient.getAppCredentials().getApnsEnvironment(); + final String apnsEnvironment = resolveApnsEnvironment(environment, environmentAppConfig); + if (apnsEnvironment == null) { + logger.error("Push message cannot be sent because APNs development host is requested, however the server is in production mode. Check configuration of application property 'powerauth.push.service.apns.useDevelopment'."); + return; + } + final ApnsClient apnsClient = ApnsEnvironment.PRODUCTION.getKey().equals(apnsEnvironment) ? pushClient.getApnsClientProduction() : pushClient.getApnsClientDevelopment(); + pushSendingWorker.sendMessageToApns(apnsClient, pushMessageBody, attributes, priority, token, pushClient.getAppCredentials().getApnsBundle(), createPushSendingCallback(token, pushMessageObject, pushClient)); + } case ANDROID, FCM -> pushSendingWorker.sendMessageToFcm(pushClient.getFcmClient(), pushMessageBody, attributes, priority, token, createPushSendingCallback(token, pushMessageObject, pushClient)); case HUAWEI, HMS -> @@ -278,6 +293,22 @@ private void updateStatusAndPersist(PushMessageEntity pushMessageObject, PushMes } } + private String resolveApnsEnvironment(final String environmentDevice, final String environmentAppConfig) { + String environment = environmentAppConfig; + if (environment == null) { + environment = configuration.isApnsUseDevelopment() ? ApnsEnvironment.DEVELOPMENT.getKey() : ApnsEnvironment.PRODUCTION.getKey(); + } + if (environmentDevice != null) { + if (environmentDevice.equals(ApnsEnvironment.PRODUCTION.getKey()) || environment.equals(ApnsEnvironment.DEVELOPMENT.getKey())) { + environment = environmentDevice; + } else { + // The case when configuration is in production mode and device is in development mode is not allowed + environment = null; + } + } + return environment; + } + /** * Arrive and deregister phaser based on the mode. For {@link Mode#SYNCHRONOUS}, the method provides deregistration. Otherwise, * for {@link Mode#ASYNCHRONOUS}, it is a noop method. @@ -298,7 +329,7 @@ private static void arriveAndDeregisterPhaserForMode(Phaser phaser, Mode mode) { * @param phaser Phaser. * @param mode Mode. */ - private static void registerPhaserForMode(Phaser phaser,Mode mode) { + private static void registerPhaserForMode(Phaser phaser, Mode mode) { if (mode == Mode.SYNCHRONOUS && phaser != null) { phaser.register(); } 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 2cd41cbde..e9086393d 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 @@ -34,6 +34,7 @@ import io.getlime.push.errorhandling.exceptions.PushServerException; import io.getlime.push.model.entity.PushMessageAttributes; import io.getlime.push.model.entity.PushMessageBody; +import io.getlime.push.model.enumeration.ApnsEnvironment; import io.getlime.push.model.enumeration.Priority; import io.getlime.push.repository.model.AppCredentialsEntity; import io.getlime.push.service.apns.ApnsRejectionReason; @@ -418,8 +419,7 @@ private static Optional calculateTtl(final Instant validUntil) { * @implSpec APNS environment {@code development} or {@code production} values can be used to override global settings. * If {@code null} or unknown value is passed, the global configuration is used. */ - ApnsClient prepareApnsClient(final AppCredentialsEntity credentials) throws PushServerException { - final String environment = credentials.getApnsEnvironment(); + ApnsClient prepareApnsClient(final AppCredentialsEntity credentials, final ApnsEnvironment environment) throws PushServerException { final ApnsClientBuilder apnsClientBuilder = new ApnsClientBuilder() .setProxyHandlerFactory(apnsClientProxy()) .setConcurrentConnections(pushServiceConfiguration.getConcurrentConnections()) @@ -427,28 +427,10 @@ ApnsClient prepareApnsClient(final AppCredentialsEntity credentials) throws Push .setIdlePingInterval(Duration.ofMillis(pushServiceConfiguration.getIdlePingInterval())) .setTrustedServerCertificateChain(caCertUtil.allCerts()); - final String appId = credentials.getAppId(); - // Determine the APNs environment by looking at per-app config first and if no recognized value is present, - // use the default configuration. Note that "equalsIgnoreCase" optimizes for null parameter, so the first two - // if-else branches are performed quickly (we do not need to worry about the fact that "null" will likely be - // the most common value there). - if ("development".equalsIgnoreCase(environment)) { - logger.info("Using APNs development host, application ID: {}", appId); + if (environment == ApnsEnvironment.DEVELOPMENT) { apnsClientBuilder.setApnsServer(ApnsClientBuilder.DEVELOPMENT_APNS_HOST); - } else if ("production".equalsIgnoreCase(environment)) { - logger.info("Using APNs production host, application ID: {}", appId); - apnsClientBuilder.setApnsServer(ApnsClientBuilder.PRODUCTION_APNS_HOST); } else { - if (environment != null) { - logger.warn("Invalid APNS host environment specified: \"{}\". Use \"development\" or \"production\", application ID: {}", environment, appId); - } - if (pushServiceConfiguration.isApnsUseDevelopment()) { - logger.info("Using APNs development host by applying the global push server configuration, application ID: {}", appId); - apnsClientBuilder.setApnsServer(ApnsClientBuilder.DEVELOPMENT_APNS_HOST); - } else { - logger.info("Using APNs production host by applying the global push server configuration, application ID: {}", appId); - apnsClientBuilder.setApnsServer(ApnsClientBuilder.PRODUCTION_APNS_HOST); - } + apnsClientBuilder.setApnsServer(ApnsClientBuilder.PRODUCTION_APNS_HOST); } final String teamId = credentials.getApnsTeamId(); @@ -502,10 +484,10 @@ private HttpProxyHandlerFactory apnsClientProxy() { * @param attributes Push message attributes. * @param priority Push message priority. * @param pushToken Push token. - * @param iosTopic APNs topic, usually same as bundle ID. + * @param apnsTopic APNs topic, usually same as bundle ID. * @param callback Callback that is called after the asynchronous executions is completed. */ - void sendMessageToApns(final ApnsClient apnsClient, final PushMessageBody pushMessageBody, final PushMessageAttributes attributes, final Priority priority, final String pushToken, final String iosTopic, final PushSendingCallback callback) { + void sendMessageToApns(final ApnsClient apnsClient, final PushMessageBody pushMessageBody, final PushMessageAttributes attributes, final Priority priority, final String pushToken, final String apnsTopic, final PushSendingCallback callback) { final String token = TokenUtil.sanitizeTokenString(pushToken); final boolean isSilent = attributes != null && attributes.getSilent(); // In case there are no attributes, the message is not silent @@ -513,7 +495,7 @@ void sendMessageToApns(final ApnsClient apnsClient, final PushMessageBody pushMe final Instant validUntil = pushMessageBody.getValidUntil(); final PushType pushType = isSilent ? PushType.BACKGROUND : PushType.ALERT; // iOS 13 and higher requires apns-push-type value to be set final DeliveryPriority deliveryPriority = (Priority.NORMAL == priority) ? DeliveryPriority.CONSERVE_POWER : DeliveryPriority.IMMEDIATE; - final SimpleApnsPushNotification pushNotification = new SimpleApnsPushNotification(token, iosTopic, payload, validUntil, deliveryPriority, pushType, pushMessageBody.getCollapseKey()); + final SimpleApnsPushNotification pushNotification = new SimpleApnsPushNotification(token, apnsTopic, payload, validUntil, deliveryPriority, pushType, pushMessageBody.getCollapseKey()); final PushNotificationFuture> sendNotificationFuture = apnsClient.sendNotification(pushNotification); sendNotificationFuture.whenCompleteAsync((response, cause) -> { @@ -540,11 +522,11 @@ void sendMessageToApns(final ApnsClient apnsClient, final PushMessageBody pushMe logger.debug("Deleting push token: {}.", pushToken); callback.didFinishSendingMessage(PushSendingCallback.Result.FAILED_DELETE); } else if (ApnsRejectionReason.DEVICE_TOKEN_NOT_FOR_TOPIC.isEqualToText(rejectionReason)) { - logger.warn("Notification was sent to incorrect topic: {}.", iosTopic); + logger.warn("Notification was sent to incorrect topic: {}.", apnsTopic); logger.debug("Deleting push token: {}", pushToken); callback.didFinishSendingMessage(PushSendingCallback.Result.FAILED_DELETE); } else if (ApnsRejectionReason.TOPIC_DISALLOWED.isEqualToText(rejectionReason)) { - logger.warn("Notification was sent to incorrect topic: {}.", iosTopic); + logger.warn("Notification was sent to incorrect topic: {}.", apnsTopic); logger.debug("Deleting push token: {}.", pushToken); callback.didFinishSendingMessage(PushSendingCallback.Result.FAILED_DELETE); } else if (ApnsRejectionReason.EXPIRED_PROVIDER_TOKEN.isEqualToText(rejectionReason)) { diff --git a/powerauth-push-server/src/main/java/io/getlime/push/service/batch/UserDeviceItemReader.java b/powerauth-push-server/src/main/java/io/getlime/push/service/batch/UserDeviceItemReader.java index fb2a20316..08bd71c20 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/service/batch/UserDeviceItemReader.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/service/batch/UserDeviceItemReader.java @@ -46,7 +46,7 @@ public UserDeviceItemReader(EntityManagerFactory entityManagerFactory, PushServi // Configure queries and reader this.setEntityManagerFactory(entityManagerFactory); this.setQueryString("select " + - " new io.getlime.push.repository.model.aggregate.UserDevice(d.userId, d.id, d.activationId, c.campaignId, d.appCredentials.id, d.platform, d.pushToken) " + + " new io.getlime.push.repository.model.aggregate.UserDevice(d.userId, d.id, d.activationId, c.campaignId, d.appCredentials.id, d.platform, d.environment, d.pushToken) " + " from PushCampaignUserEntity c, PushDeviceRegistrationEntity d " + " where c.userId = d.userId and c.campaignId = :campaignId"); // Map parameters to query 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 b747f6f0a..88cada341 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 @@ -18,6 +18,7 @@ import io.getlime.push.errorhandling.exceptions.PushServerException; import io.getlime.push.model.entity.PushMessageBody; +import io.getlime.push.model.enumeration.ApnsEnvironment; import io.getlime.push.repository.PushCampaignRepository; import io.getlime.push.repository.model.Platform; import io.getlime.push.repository.model.PushCampaignEntity; @@ -72,6 +73,7 @@ public UserDeviceItemWriter(PushMessageSenderService pushMessageSenderService, public void write(Chunk list) throws Exception { for (UserDevice device: list) { final Platform platform = device.getPlatform(); + final String environment = device.getEnvironment(); final String token = device.getToken(); final String userId = device.getUserId(); final Long campaignId = device.getCampaignId(); @@ -87,7 +89,7 @@ public void write(Chunk list) throws Exception { final PushMessageBody messageBody = jsonSerialization.deserializePushMessageBody(campaign.getMessage()); // Send the push message using push sender service - pushMessageSenderService.sendCampaignMessage(campaign.getAppCredentials().getAppId(), platform, token, messageBody, userId, deviceId, activationId); + pushMessageSenderService.sendCampaignMessage(campaign.getAppCredentials().getAppId(), platform, environment, token, messageBody, userId, deviceId, activationId); } } } diff --git a/powerauth-push-server/src/test/java/io/getlime/push/configuration/PushServerAppCredentialConfiguration.java b/powerauth-push-server/src/test/java/io/getlime/push/configuration/PushServerAppCredentialConfiguration.java index dd8bf5bfb..09097dd32 100644 --- a/powerauth-push-server/src/test/java/io/getlime/push/configuration/PushServerAppCredentialConfiguration.java +++ b/powerauth-push-server/src/test/java/io/getlime/push/configuration/PushServerAppCredentialConfiguration.java @@ -20,6 +20,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import java.nio.charset.StandardCharsets; + /** * Configuration class for push server app credentials. * @@ -36,12 +38,24 @@ public PushServerAppCredentialConfiguration(AppCredentialsRepository appCredenti } public void configure(String applicationId) { - if (appCredentialsRepository.findFirstByAppId(applicationId).isEmpty()) { - final AppCredentialsEntity testCredentials = new AppCredentialsEntity(); - testCredentials.setAppId(applicationId); - testCredentials.setFcmProjectId("test-project"); - testCredentials.setFcmPrivateKey(new byte[128]); - appCredentialsRepository.save(testCredentials); + configure(applicationId, null); + } + + public void configure(String applicationId, String environment) { + final AppCredentialsEntity testCredentials; + if (appCredentialsRepository.findFirstByAppId(applicationId).isPresent()) { + testCredentials = appCredentialsRepository.findFirstByAppId(applicationId).get(); + } else { + testCredentials = new AppCredentialsEntity(); } + testCredentials.setAppId(applicationId); + testCredentials.setApnsBundle("test-bundle"); + testCredentials.setApnsPrivateKey("-----BEGIN PRIVATE KEY-----\nMIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg2Xdp5cQvLRDA+kLZ\nmEICtfn0xvPEwHnMWvc+DGXvkbqhRANCAATBeR4PYm+hF8GqH2gxfu0E8yCmHQpd\nn8AB7B/Yhr5N8T+EdOxcfHRA6ayvUG9dFhuyMi7c2v9Yv8i6cVZmXQgz\n-----END PRIVATE KEY-----\n".getBytes(StandardCharsets.UTF_8)); + testCredentials.setApnsKeyId("test-key"); + testCredentials.setApnsTeamId("test-team-id"); + testCredentials.setApnsEnvironment(environment); + testCredentials.setFcmProjectId("test-project"); + testCredentials.setFcmPrivateKey(new byte[128]); + appCredentialsRepository.save(testCredentials); } } 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 0d3c62927..f0c4ec24e 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.ApnsEnvironment; import io.getlime.push.model.enumeration.MobilePlatform; import io.getlime.push.model.request.CreateDeviceRequest; import io.getlime.push.repository.AppCredentialsRepository; @@ -78,7 +79,8 @@ void testCreateDevice() throws Exception { request.setAppId("my_app"); request.setActivationId("a1"); request.setToken("t1"); - request.setPlatform(MobilePlatform.ANDROID); + request.setPlatform(MobilePlatform.APNS); + request.setEnvironment(ApnsEnvironment.PRODUCTION); tested.createDevice(new ObjectRequest<>(request)); @@ -86,6 +88,7 @@ void testCreateDevice() throws Exception { assertEquals(1, entities.size()); assertEquals("a1", entities.get(0).getActivationId()); assertEquals("t1", entities.get(0).getPushToken()); + assertEquals(ApnsEnvironment.PRODUCTION.getKey(), entities.get(0).getEnvironment()); } @Test diff --git a/powerauth-push-server/src/test/java/io/getlime/push/service/ApnsEnvironmentProdTest.java b/powerauth-push-server/src/test/java/io/getlime/push/service/ApnsEnvironmentProdTest.java new file mode 100644 index 000000000..7cd62568d --- /dev/null +++ b/powerauth-push-server/src/test/java/io/getlime/push/service/ApnsEnvironmentProdTest.java @@ -0,0 +1,132 @@ +/* + * 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.github.benmanes.caffeine.cache.LoadingCache; +import io.getlime.core.rest.model.base.response.ObjectResponse; +import io.getlime.push.api.PowerAuthTestClient; +import io.getlime.push.client.PushServerClient; +import io.getlime.push.client.PushServerClientException; +import io.getlime.push.client.PushServerTestClientFactory; +import io.getlime.push.configuration.PushServerAppCredentialConfiguration; +import io.getlime.push.model.entity.PushMessage; +import io.getlime.push.model.entity.PushMessageBody; +import io.getlime.push.model.entity.PushMessageSendResult; +import io.getlime.push.model.enumeration.ApnsEnvironment; +import io.getlime.push.model.enumeration.MobilePlatform; +import io.getlime.push.model.enumeration.Mode; +import io.getlime.push.model.request.CreateDeviceRequest; +import org.junit.jupiter.api.BeforeEach; +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.web.server.LocalServerPort; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.TestPropertySource; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Test for configuration of production APNs environment vs development push messages. + * + * @author Roman Strobl, roman.strobl@wultra.com + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) +@TestPropertySource(locations = "classpath:application-test-apns-prod.properties") +@ActiveProfiles("test") +public class ApnsEnvironmentProdTest { + + private static final String MOCK_PUSH_TOKEN = "1234567890987654321234567890"; + + @Autowired + private PushServerTestClientFactory testClientFactory; + + @Autowired + private PushServerAppCredentialConfiguration appCredentialConfig; + + @Autowired + private LoadingCache appRelatedPushClientCache; + + private PowerAuthTestClient powerAuthTestClient; + private PushServerClient pushServerClient; + + @LocalServerPort + private int port; + + @BeforeEach + void setUp() throws Exception { + pushServerClient = testClientFactory.createPushServerClient("http://localhost:" + port); + powerAuthTestClient = testClientFactory.createPowerAuthTestClient(); + appRelatedPushClientCache.invalidateAll(); + } + + @Test + public void testApnsProdNoDevelopmentPushMessages() throws PushServerClientException { + appCredentialConfig.configure(powerAuthTestClient.getApplicationId(), null); + + CreateDeviceRequest request = CreateDeviceRequest.builder() + .appId(powerAuthTestClient.getApplicationId()) + .token(MOCK_PUSH_TOKEN) + .platform(MobilePlatform.APNS) + .environment(ApnsEnvironment.DEVELOPMENT) + .activationId(powerAuthTestClient.getActivationId()) + .build(); + boolean result = pushServerClient.createDevice(request); + assertTrue(result); + + final PushMessage pushMessage = preparePushMessage(); + + final ObjectResponse actual = pushServerClient.sendPushMessage(powerAuthTestClient.getApplicationId(), Mode.SYNCHRONOUS, pushMessage); + assertEquals("OK", actual.getStatus()); + assertEquals(0, actual.getResponseObject().getApns().getSent()); + assertEquals(1, actual.getResponseObject().getApns().getFailed()); + } + + @Test + public void testApnsProdAppConfigNoDevelopmentPushMessages() throws PushServerClientException { + appCredentialConfig.configure(powerAuthTestClient.getApplicationId(), ApnsEnvironment.PRODUCTION.getKey()); + CreateDeviceRequest request = CreateDeviceRequest.builder() + .appId(powerAuthTestClient.getApplicationId()) + .token(MOCK_PUSH_TOKEN) + .platform(MobilePlatform.APNS) + .environment(ApnsEnvironment.DEVELOPMENT) + .activationId(powerAuthTestClient.getActivationId()) + .build(); + boolean result = pushServerClient.createDevice(request); + assertTrue(result); + + final PushMessage pushMessage = preparePushMessage(); + + final ObjectResponse actual = pushServerClient.sendPushMessage(powerAuthTestClient.getApplicationId(), Mode.SYNCHRONOUS, pushMessage); + assertEquals("OK", actual.getStatus()); + assertEquals(0, actual.getResponseObject().getApns().getSent()); + assertEquals(1, actual.getResponseObject().getApns().getFailed()); + } + + private PushMessage preparePushMessage() { + final PushMessageBody pushMessageBody = new PushMessageBody(); + pushMessageBody.setTitle("Balance update"); + pushMessageBody.setBody("Your balance is now $745.00"); + + final PushMessage pushMessage = new PushMessage(); + pushMessage.setUserId(PushServerTestClientFactory.TEST_USER_ID); + pushMessage.setActivationId(powerAuthTestClient.getActivationId()); + pushMessage.setBody(pushMessageBody); + + return pushMessage; + } +} diff --git a/powerauth-push-server/src/test/java/io/getlime/push/service/ApnsEnvironmentTest.java b/powerauth-push-server/src/test/java/io/getlime/push/service/ApnsEnvironmentTest.java new file mode 100644 index 000000000..9932c1a30 --- /dev/null +++ b/powerauth-push-server/src/test/java/io/getlime/push/service/ApnsEnvironmentTest.java @@ -0,0 +1,205 @@ +/* + * 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.eatthepath.pushy.apns.ApnsClient; +import com.github.benmanes.caffeine.cache.LoadingCache; +import io.getlime.push.api.PowerAuthTestClient; +import io.getlime.push.client.PushServerClient; +import io.getlime.push.client.PushServerClientException; +import io.getlime.push.client.PushServerTestClientFactory; +import io.getlime.push.configuration.PushServerAppCredentialConfiguration; +import io.getlime.push.errorhandling.exceptions.PushServerException; +import io.getlime.push.model.entity.PushMessage; +import io.getlime.push.model.entity.PushMessageBody; +import io.getlime.push.model.enumeration.ApnsEnvironment; +import io.getlime.push.model.enumeration.MobilePlatform; +import io.getlime.push.model.enumeration.Mode; +import io.getlime.push.model.request.CreateDeviceRequest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.stubbing.Answer; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.TestPropertySource; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.*; + +/** + * Test for configuration of production APNs environment vs development push messages. + * + * @author Roman Strobl, roman.strobl@wultra.com + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) +@TestPropertySource(locations = "classpath:application-test.properties") +@TestPropertySource(properties = "server.port=54726") +@ActiveProfiles("test") +public class ApnsEnvironmentTest { + + private static final String MOCK_PUSH_TOKEN = "1234567890987654321234567890"; + + @Autowired + private PushServerTestClientFactory testClientFactory; + + @Autowired + private PushServerAppCredentialConfiguration appCredentialConfig; + + @Autowired + private LoadingCache appRelatedPushClientCache; + + @SpyBean + private PushSendingWorker pushSendingWorker; + + private PowerAuthTestClient powerAuthTestClient; + private PushServerClient pushServerClient; + + @LocalServerPort + private int port; + + @BeforeEach + void setUp() throws Exception { + pushServerClient = testClientFactory.createPushServerClient("http://localhost:" + port); + powerAuthTestClient = testClientFactory.createPowerAuthTestClient(); + appRelatedPushClientCache.invalidateAll(); + } + + @Test + public void testApnsConfigDevelopmentDevice() throws PushServerClientException, PushServerException { + appCredentialConfig.configure(powerAuthTestClient.getApplicationId(), "development"); + prepareMocks(); + CreateDeviceRequest request = CreateDeviceRequest.builder() + .appId(powerAuthTestClient.getApplicationId()) + .token(MOCK_PUSH_TOKEN) + .platform(MobilePlatform.APNS) + .environment(ApnsEnvironment.DEVELOPMENT) + .activationId(powerAuthTestClient.getActivationId()) + .build(); + boolean result = pushServerClient.createDevice(request); + assertTrue(result); + + pushServerClient.sendPushMessage(powerAuthTestClient.getApplicationId(), Mode.SYNCHRONOUS, preparePushMessage()); + ArgumentCaptor apnsClientCaptor = ArgumentCaptor.forClass(ApnsClient.class); + verify(pushSendingWorker).sendMessageToApns(apnsClientCaptor.capture(), any(), any(), any(), any(), any(), any()); + assertEquals(appRelatedPushClientCache.get(powerAuthTestClient.getApplicationId()).getApnsClientDevelopment(), apnsClientCaptor.getValue()); + } + + @Test + public void testApnsConfigProductionDevice() throws PushServerClientException, PushServerException { + appCredentialConfig.configure(powerAuthTestClient.getApplicationId(), "development"); + prepareMocks(); + CreateDeviceRequest request = CreateDeviceRequest.builder() + .appId(powerAuthTestClient.getApplicationId()) + .token(MOCK_PUSH_TOKEN) + .platform(MobilePlatform.APNS) + .environment(ApnsEnvironment.PRODUCTION) + .activationId(powerAuthTestClient.getActivationId()) + .build(); + boolean result = pushServerClient.createDevice(request); + assertTrue(result); + + pushServerClient.sendPushMessage(powerAuthTestClient.getApplicationId(), Mode.SYNCHRONOUS, preparePushMessage()); + ArgumentCaptor apnsClientCaptor = ArgumentCaptor.forClass(ApnsClient.class); + verify(pushSendingWorker).sendMessageToApns(apnsClientCaptor.capture(), any(), any(), any(), any(), any(), any()); + assertEquals(appRelatedPushClientCache.get(powerAuthTestClient.getApplicationId()).getApnsClientProduction(), apnsClientCaptor.getValue()); + } + + @Test + public void testApnsConfigDevelopmentAppConfig() throws PushServerClientException, PushServerException { + appCredentialConfig.configure(powerAuthTestClient.getApplicationId(), "development"); + prepareMocks(); + CreateDeviceRequest request = CreateDeviceRequest.builder() + .appId(powerAuthTestClient.getApplicationId()) + .token(MOCK_PUSH_TOKEN) + .platform(MobilePlatform.APNS) + .activationId(powerAuthTestClient.getActivationId()) + .build(); + boolean result = pushServerClient.createDevice(request); + assertTrue(result); + + pushServerClient.sendPushMessage(powerAuthTestClient.getApplicationId(), Mode.SYNCHRONOUS, preparePushMessage()); + ArgumentCaptor apnsClientCaptor = ArgumentCaptor.forClass(ApnsClient.class); + verify(pushSendingWorker).sendMessageToApns(apnsClientCaptor.capture(), any(), any(), any(), any(), any(), any()); + assertEquals(appRelatedPushClientCache.get(powerAuthTestClient.getApplicationId()).getApnsClientDevelopment(), apnsClientCaptor.getValue()); + } + + @Test + public void testApnsConfigProductionAppConfig() throws PushServerClientException, PushServerException { + appCredentialConfig.configure(powerAuthTestClient.getApplicationId(), "production"); + prepareMocks(); + CreateDeviceRequest request = CreateDeviceRequest.builder() + .appId(powerAuthTestClient.getApplicationId()) + .token(MOCK_PUSH_TOKEN) + .platform(MobilePlatform.APNS) + .activationId(powerAuthTestClient.getActivationId()) + .build(); + boolean result = pushServerClient.createDevice(request); + assertTrue(result); + + pushServerClient.sendPushMessage(powerAuthTestClient.getApplicationId(), Mode.SYNCHRONOUS, preparePushMessage()); + ArgumentCaptor apnsClientCaptor = ArgumentCaptor.forClass(ApnsClient.class); + verify(pushSendingWorker).sendMessageToApns(apnsClientCaptor.capture(), any(), any(), any(), any(), any(), any()); + assertEquals(appRelatedPushClientCache.get(powerAuthTestClient.getApplicationId()).getApnsClientProduction(), apnsClientCaptor.getValue()); + } + + @Test + public void testApnsConfigDevelopmentGlobal() throws PushServerClientException, PushServerException { + appCredentialConfig.configure(powerAuthTestClient.getApplicationId(), null); + prepareMocks(); + CreateDeviceRequest request = CreateDeviceRequest.builder() + .appId(powerAuthTestClient.getApplicationId()) + .token(MOCK_PUSH_TOKEN) + .platform(MobilePlatform.APNS) + .activationId(powerAuthTestClient.getActivationId()) + .build(); + boolean result = pushServerClient.createDevice(request); + assertTrue(result); + + pushServerClient.sendPushMessage(powerAuthTestClient.getApplicationId(), Mode.SYNCHRONOUS, preparePushMessage()); + ArgumentCaptor apnsClientCaptor = ArgumentCaptor.forClass(ApnsClient.class); + verify(pushSendingWorker).sendMessageToApns(apnsClientCaptor.capture(), any(), any(), any(), any(), any(), any()); + assertEquals(appRelatedPushClientCache.get(powerAuthTestClient.getApplicationId()).getApnsClientDevelopment(), apnsClientCaptor.getValue()); + } + + private void prepareMocks() throws PushServerException { + doCallRealMethod().when(pushSendingWorker).prepareApnsClient(any(), any()); + doCallRealMethod().when(pushSendingWorker).prepareFcmClient(any(), any()); + doAnswer((Answer) invocation -> { + PushSendingCallback callback = invocation.getArgument(6); + callback.didFinishSendingMessage(PushSendingCallback.Result.OK); + return null; + }).when(pushSendingWorker).sendMessageToApns(any(), any(), any(), any(), any(), any(), any()); + } + + private PushMessage preparePushMessage() { + final PushMessageBody pushMessageBody = new PushMessageBody(); + pushMessageBody.setTitle("Balance update"); + pushMessageBody.setBody("Your balance is now $745.00"); + + final PushMessage pushMessage = new PushMessage(); + pushMessage.setUserId(PushServerTestClientFactory.TEST_USER_ID); + pushMessage.setActivationId(powerAuthTestClient.getActivationId()); + pushMessage.setBody(pushMessageBody); + + return pushMessage; + } + +} 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 index bff5c7fea..44d94ede5 100644 --- 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 @@ -83,7 +83,7 @@ void testCreateOrUpdateDevice_createNew() throws Exception { request.setAppId(APP_NAME); request.setActivationId("a1"); request.setToken("t1"); - request.setPlatform(MobilePlatform.ANDROID); + request.setPlatform(MobilePlatform.FCM); tested.createOrUpdateDevice(request, credentials); @@ -107,7 +107,7 @@ void testCreateDevice_parallel() throws Exception { request.setAppId(APP_NAME); request.setActivationId("a1"); request.setToken("t1"); - request.setPlatform(MobilePlatform.IOS); + request.setPlatform(MobilePlatform.APNS); tested.createOrUpdateDevice(request, credentials); return null; }); @@ -130,7 +130,7 @@ void testCreateOrUpdateDevice_multipleRecords() throws Exception { request.setAppId(APP_NAME); request.setActivationId("a1"); request.setToken("t1"); - request.setPlatform(MobilePlatform.ANDROID); + request.setPlatform(MobilePlatform.FCM); final PushServerException ex = assertThrows(PushServerException.class, () -> tested.createOrUpdateDevice(request, credentials)); @@ -149,7 +149,7 @@ void testCreateOrUpdateDevices_createNew() throws Exception { request.setAppId(APP_NAME); request.getActivationIds().addAll(List.of("a1", "a2")); request.setToken("t1"); - request.setPlatform(MobilePlatform.ANDROID); + request.setPlatform(MobilePlatform.FCM); tested.createOrUpdateDevices(request, credentials); @@ -167,7 +167,7 @@ void testCreateOrUpdateDevices_withDuplicities() throws Exception { request.setAppId(APP_NAME); request.getActivationIds().addAll(List.of("a1", "a1")); request.setToken("t1"); - request.setPlatform(MobilePlatform.ANDROID); + request.setPlatform(MobilePlatform.FCM); tested.createOrUpdateDevices(request, credentials); @@ -187,7 +187,7 @@ void testCreateOrUpdateDevices_update() throws Exception { request.setAppId(APP_NAME); request.getActivationIds().addAll(List.of("a1", "a2")); request.setToken("t1_new"); - request.setPlatform(MobilePlatform.ANDROID); + request.setPlatform(MobilePlatform.FCM); tested.createOrUpdateDevices(request, credentials); @@ -206,7 +206,7 @@ void testCreateOrUpdateDevices_multipleRecords() throws Exception { request.setAppId(APP_NAME); request.getActivationIds().add("a1"); request.setToken("t1"); - request.setPlatform(MobilePlatform.ANDROID); + request.setPlatform(MobilePlatform.FCM); assertRegistrationExists("a_other", "t1"); assertRegistrationExists("a_different", "t1"); @@ -224,7 +224,7 @@ void testUpdateStatus_statusInRequest() throws Exception { device.setActivationId("a1"); device.setAppCredentials(createAppCredentials(APP_NAME)); device.setTimestampLastRegistered(new Date()); - device.setPlatform(Platform.IOS); + device.setPlatform(Platform.APNS); device.setPushToken("t1"); device.setActive(false); deviceRepository.save(device); @@ -246,7 +246,7 @@ void testUpdateStatus_missingStatusInRequest() throws Exception { device.setActivationId("a1"); device.setAppCredentials(createAppCredentials(APP_NAME)); device.setTimestampLastRegistered(new Date()); - device.setPlatform(Platform.IOS); + device.setPlatform(Platform.APNS); device.setPushToken("t1"); device.setActive(false); deviceRepository.save(device); 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 index 786664900..75d32c9f7 100644 --- 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 @@ -19,6 +19,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.ApnsEnvironment; import io.getlime.push.model.enumeration.MobilePlatform; import io.getlime.push.model.request.CreateDeviceForActivationsRequest; import io.getlime.push.model.request.CreateDeviceRequest; @@ -69,7 +70,25 @@ void testCreateDevice_success() throws Exception { request.setAppId("my_app"); request.setActivationId("a1"); request.setToken("t1"); - request.setPlatform(MobilePlatform.ANDROID); + request.setPlatform(MobilePlatform.FCM); + + final Response response = tested.createDevice(request); + verify(deviceRegistrationService).createOrUpdateDevice(request, credentials); + assertEquals("OK", response.getStatus()); + } + + @Test + void testCreateDevice_APNs_environment_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.APNS); + request.setEnvironment(ApnsEnvironment.DEVELOPMENT); final Response response = tested.createDevice(request); verify(deviceRegistrationService).createOrUpdateDevice(request, credentials); @@ -92,7 +111,7 @@ void testCreateDevice_invalidApp() { request.setAppId("non_existent"); request.setActivationId("a1"); request.setToken("t1"); - request.setPlatform(MobilePlatform.ANDROID); + request.setPlatform(MobilePlatform.FCM); final PushServerException exception = assertThrows(PushServerException.class, () -> tested.createDevice(request)); @@ -109,7 +128,7 @@ void testCreateDeviceMultipleActivations_success() throws Exception { final CreateDeviceForActivationsRequest request = new CreateDeviceForActivationsRequest(); request.setAppId("my_app"); - request.setPlatform(MobilePlatform.ANDROID); + request.setPlatform(MobilePlatform.FCM); request.setToken("t2"); request.getActivationIds().addAll(List.of("a1", "a2")); @@ -149,7 +168,7 @@ void testCreateDeviceMultipleActivations_invalidApp() { final CreateDeviceForActivationsRequest request = new CreateDeviceForActivationsRequest(); request.setAppId("non-existent"); - request.setPlatform(MobilePlatform.ANDROID); + request.setPlatform(MobilePlatform.FCM); request.setToken("t2"); request.getActivationIds().addAll(List.of("a1", "a2")); diff --git a/powerauth-push-server/src/test/java/io/getlime/push/service/PushSendingWorkerTest.java b/powerauth-push-server/src/test/java/io/getlime/push/service/PushSendingWorkerTest.java index e01b4ab0b..25f4e9832 100644 --- a/powerauth-push-server/src/test/java/io/getlime/push/service/PushSendingWorkerTest.java +++ b/powerauth-push-server/src/test/java/io/getlime/push/service/PushSendingWorkerTest.java @@ -61,6 +61,7 @@ class PushSendingWorkerTest { @Test void testSendMessageToFcmError() throws FcmMissingTokenException { final RestClientException simulatedException = new RestClientException("Simulated INVALID_ARGUMENT error"); + when(pushServiceConfiguration.isFcmDataNotificationOnly()).thenReturn(false); when(fcmModelConverter.convertExceptionToErrorCode(simulatedException)).thenReturn(MessagingErrorCode.INVALID_ARGUMENT); doAnswer(invocation -> { final Consumer onError = invocation.getArgument(3); 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 f0b0a1358..a5ed63576 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 @@ -21,7 +21,10 @@ import io.getlime.push.client.PushServerClientException; import io.getlime.push.client.PushServerTestClientFactory; import io.getlime.push.configuration.PushServerAppCredentialConfiguration; +import io.getlime.push.model.enumeration.ApnsEnvironment; 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.repository.PushDeviceRepository; import io.getlime.push.repository.model.PushDeviceRegistrationEntity; import org.junit.jupiter.api.BeforeEach; @@ -79,15 +82,28 @@ 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); + CreateDeviceForActivationsRequest request = CreateDeviceForActivationsRequest.builder() + .appId(powerAuthTestClient.getApplicationId()) + .token(MOCK_PUSH_TOKEN) + .platform(MobilePlatform.APNS) + .environment(ApnsEnvironment.DEVELOPMENT) + .build(); + request.getActivationIds().addAll(activationIds); + boolean result = pushServerClient.createDeviceForActivations(request); assertTrue(result); pushDeviceRepository.deleteAll(pushDeviceRepository.findByAppCredentialsAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN)); } @Test public void createDeviceWithMultipleActivationsInvalidTest() { + CreateDeviceForActivationsRequest request = CreateDeviceForActivationsRequest.builder() + .appId(powerAuthTestClient.getApplicationId()) + .token(MOCK_PUSH_TOKEN) + .platform(MobilePlatform.APNS) + .environment(ApnsEnvironment.DEVELOPMENT) + .build(); assertThrows(PushServerClientException.class, () -> - pushServerClient.createDeviceForActivations(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.IOS, Collections.emptyList())); + pushServerClient.createDeviceForActivations(request)); } @Test @@ -96,13 +112,20 @@ 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); + CreateDeviceForActivationsRequest request = CreateDeviceForActivationsRequest.builder() + .appId(powerAuthTestClient.getApplicationId()) + .token(MOCK_PUSH_TOKEN) + .platform(MobilePlatform.APNS) + .environment(ApnsEnvironment.DEVELOPMENT) + .build(); + request.getActivationIds().addAll(activationIds); + boolean actual = pushServerClient.createDeviceForActivations(request); 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(request); assertTrue(actual2); List devices2 = pushDeviceRepository.findByAppCredentialsAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN); assertEquals(2, devices2.size()); @@ -118,13 +141,27 @@ 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); + CreateDeviceForActivationsRequest request = CreateDeviceForActivationsRequest.builder() + .appId(powerAuthTestClient.getApplicationId()) + .token(MOCK_PUSH_TOKEN) + .platform(MobilePlatform.APNS) + .environment(ApnsEnvironment.DEVELOPMENT) + .build(); + request.getActivationIds().addAll(activationIds); + boolean actual = pushServerClient.createDeviceForActivations(request); 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); + CreateDeviceForActivationsRequest request2 = CreateDeviceForActivationsRequest.builder() + .appId(powerAuthTestClient.getApplicationId()) + .token(MOCK_PUSH_TOKEN_2) + .platform(MobilePlatform.APNS) + .environment(ApnsEnvironment.DEVELOPMENT) + .build(); + request2.getActivationIds().addAll(activationIds); + boolean actual2 = pushServerClient.createDeviceForActivations(request2); assertTrue(actual2); List devices2 = pushDeviceRepository.findByAppCredentialsAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN_2); assertEquals(2, devices2.size()); @@ -140,7 +177,14 @@ 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); + CreateDeviceForActivationsRequest request = CreateDeviceForActivationsRequest.builder() + .appId(powerAuthTestClient.getApplicationId()) + .token(MOCK_PUSH_TOKEN) + .platform(MobilePlatform.APNS) + .environment(ApnsEnvironment.DEVELOPMENT) + .build(); + request.getActivationIds().addAll(activationIds); + boolean actual = pushServerClient.createDeviceForActivations(request); assertTrue(actual); List devices = pushDeviceRepository.findByAppCredentialsAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN); assertEquals(2, devices.size()); @@ -149,7 +193,14 @@ 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); + CreateDeviceForActivationsRequest request2 = CreateDeviceForActivationsRequest.builder() + .appId(powerAuthTestClient.getApplicationId()) + .token(MOCK_PUSH_TOKEN) + .platform(MobilePlatform.APNS) + .environment(ApnsEnvironment.DEVELOPMENT) + .build(); + request2.getActivationIds().addAll(activationIds2); + boolean actual2 = pushServerClient.createDeviceForActivations(request2); assertTrue(actual2); List devices2 = pushDeviceRepository.findByAppCredentialsAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN); assertEquals(2, devices2.size()); @@ -166,7 +217,14 @@ 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); + CreateDeviceForActivationsRequest request = CreateDeviceForActivationsRequest.builder() + .appId(powerAuthTestClient.getApplicationId()) + .token(MOCK_PUSH_TOKEN) + .platform(MobilePlatform.APNS) + .environment(ApnsEnvironment.DEVELOPMENT) + .build(); + request.getActivationIds().addAll(activationIds); + boolean actual = pushServerClient.createDeviceForActivations(request); assertTrue(actual); List devices = pushDeviceRepository.findByAppCredentialsAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN); assertEquals(2, devices.size()); @@ -175,7 +233,14 @@ 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); + CreateDeviceForActivationsRequest request2 = CreateDeviceForActivationsRequest.builder() + .appId(powerAuthTestClient.getApplicationId()) + .token(MOCK_PUSH_TOKEN) + .platform(MobilePlatform.APNS) + .environment(ApnsEnvironment.DEVELOPMENT) + .build(); + request2.getActivationIds().addAll(activationIds2); + boolean actual2 = pushServerClient.createDeviceForActivations(request2); assertTrue(actual2); List devices2 = pushDeviceRepository.findByAppCredentialsAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN); assertEquals(2, devices2.size()); @@ -193,11 +258,25 @@ 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); + CreateDeviceForActivationsRequest request = CreateDeviceForActivationsRequest.builder() + .appId(powerAuthTestClient.getApplicationId()) + .token(MOCK_PUSH_TOKEN) + .platform(MobilePlatform.APNS) + .environment(ApnsEnvironment.DEVELOPMENT) + .build(); + request.getActivationIds().addAll(activationIds); + boolean actual = pushServerClient.createDeviceForActivations(request); 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()); + CreateDeviceRequest request2 = CreateDeviceRequest.builder() + .appId(powerAuthTestClient.getApplicationId()) + .token(MOCK_PUSH_TOKEN) + .platform(MobilePlatform.APNS) + .environment(ApnsEnvironment.DEVELOPMENT) + .activationId(powerAuthTestClient.getActivationId3()) + .build(); + pushServerClient.createDevice(request2); }); } 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 5e2381a2a..75cbc49ec 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 @@ -26,8 +26,11 @@ import io.getlime.push.configuration.PushServerAppCredentialConfiguration; import io.getlime.push.model.base.PagedResponse; import io.getlime.push.model.entity.*; +import io.getlime.push.model.enumeration.ApnsEnvironment; import io.getlime.push.model.enumeration.MobilePlatform; import io.getlime.push.model.enumeration.Mode; +import io.getlime.push.model.request.CreateDeviceForActivationsRequest; +import io.getlime.push.model.request.CreateDeviceRequest; import io.getlime.push.model.response.*; import io.getlime.push.repository.PushDeviceRepository; import io.getlime.push.repository.model.PushDeviceRegistrationEntity; @@ -111,13 +114,26 @@ void getServiceStatusTest() throws Exception { @Test void createDeviceWithoutActivationIDTest() { + CreateDeviceRequest request = CreateDeviceRequest.builder() + .appId(powerAuthTestClient.getActivationId()) + .token(MOCK_PUSH_TOKEN) + .platform(MobilePlatform.APNS) + .environment(ApnsEnvironment.DEVELOPMENT) + .build(); assertThrows(PushServerClientException.class, () -> - pushServerClient.createDevice(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.APNS)); + pushServerClient.createDevice(request)); } @Test void createDeviceWithActivationIDTest() throws Exception { - boolean result = pushServerClient.createDevice(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.APNS, powerAuthTestClient.getActivationId()); + CreateDeviceRequest request = CreateDeviceRequest.builder() + .appId(powerAuthTestClient.getApplicationId()) + .token(MOCK_PUSH_TOKEN) + .platform(MobilePlatform.APNS) + .environment(ApnsEnvironment.DEVELOPMENT) + .activationId(powerAuthTestClient.getActivationId()) + .build(); + boolean result = pushServerClient.createDevice(request); assertTrue(result); List devices = pushDeviceRepository.findByAppCredentialsAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN); assertEquals(1, devices.size()); @@ -126,7 +142,14 @@ void createDeviceWithActivationIDTest() throws Exception { @Test void deleteDeviceTest() throws Exception { - boolean result = pushServerClient.createDevice(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.APNS, powerAuthTestClient.getActivationId()); + CreateDeviceRequest request = CreateDeviceRequest.builder() + .appId(powerAuthTestClient.getApplicationId()) + .token(MOCK_PUSH_TOKEN) + .platform(MobilePlatform.APNS) + .environment(ApnsEnvironment.DEVELOPMENT) + .activationId(powerAuthTestClient.getActivationId()) + .build(); + boolean result = pushServerClient.createDevice(request); assertTrue(result); List devices = pushDeviceRepository.findByAppCredentialsAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN); assertEquals(1, devices.size()); @@ -143,7 +166,14 @@ void testFcmUrlConfiguredForTests() { @Test void updateDeviceStatusTest() throws Exception { - boolean result = pushServerClient.createDevice(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.APNS, powerAuthTestClient.getActivationId()); + CreateDeviceRequest request = CreateDeviceRequest.builder() + .appId(powerAuthTestClient.getApplicationId()) + .token(MOCK_PUSH_TOKEN) + .platform(MobilePlatform.APNS) + .environment(ApnsEnvironment.DEVELOPMENT) + .activationId(powerAuthTestClient.getActivationId()) + .build(); + boolean result = pushServerClient.createDevice(request); assertTrue(result); List devices = pushDeviceRepository.findByAppCredentialsAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN); assertEquals(1, devices.size()); @@ -165,7 +195,13 @@ void updateDeviceStatusTest() throws Exception { @Test void sendPushMessageTest() throws Exception { - final boolean result = pushServerClient.createDevice(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.FCM, powerAuthTestClient.getActivationId()); + CreateDeviceRequest request = CreateDeviceRequest.builder() + .appId(powerAuthTestClient.getApplicationId()) + .token(MOCK_PUSH_TOKEN) + .platform(MobilePlatform.FCM) + .activationId(powerAuthTestClient.getActivationId()) + .build(); + final boolean result = pushServerClient.createDevice(request); assertTrue(result); final PushMessageAttributes attributes = new PushMessageAttributes(); @@ -198,7 +234,13 @@ void sendPushMessageTest() throws Exception { @Test void sendPushMessageBatchTest() throws Exception { - boolean result = pushServerClient.createDevice(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.FCM, powerAuthTestClient.getActivationId()); + CreateDeviceRequest request = CreateDeviceRequest.builder() + .appId(powerAuthTestClient.getApplicationId()) + .token(MOCK_PUSH_TOKEN) + .platform(MobilePlatform.FCM) + .activationId(powerAuthTestClient.getActivationId()) + .build(); + boolean result = pushServerClient.createDevice(request); assertTrue(result); final PushMessageBody pushMessageBody = new PushMessageBody(); @@ -232,7 +274,13 @@ void sendPushMessageBatchTest() throws Exception { @Test void createCampaignTest() throws Exception { - boolean result = pushServerClient.createDevice(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.FCM, powerAuthTestClient.getActivationId()); + CreateDeviceRequest request = CreateDeviceRequest.builder() + .appId(powerAuthTestClient.getApplicationId()) + .token(MOCK_PUSH_TOKEN) + .platform(MobilePlatform.FCM) + .activationId(powerAuthTestClient.getActivationId()) + .build(); + boolean result = pushServerClient.createDevice(request); assertTrue(result); final ObjectResponse actual = createCampaign(); assertEquals("OK", actual.getStatus()); @@ -295,7 +343,13 @@ void deleteUsersFromCampaignTest() throws Exception { @Test void sendTestingCampaignTest() throws Exception { - boolean result = pushServerClient.createDevice(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.FCM, powerAuthTestClient.getActivationId()); + CreateDeviceRequest request = CreateDeviceRequest.builder() + .appId(powerAuthTestClient.getApplicationId()) + .token(MOCK_PUSH_TOKEN) + .platform(MobilePlatform.FCM) + .activationId(powerAuthTestClient.getActivationId()) + .build(); + boolean result = pushServerClient.createDevice(request); assertTrue(result); final Long campaignId = createCampaign().getResponseObject().getId(); @@ -316,19 +370,32 @@ void createDeviceWithMultipleActivationsTest() { List activationIds = new ArrayList<>(); activationIds.add(powerAuthTestClient.getActivationId()); activationIds.add(powerAuthTestClient.getActivationId2()); - pushServerClient.createDeviceForActivations(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.APNS, activationIds); + CreateDeviceForActivationsRequest request = CreateDeviceForActivationsRequest.builder() + .appId(powerAuthTestClient.getApplicationId()) + .token(MOCK_PUSH_TOKEN) + .platform(MobilePlatform.APNS) + .environment(ApnsEnvironment.DEVELOPMENT) + .build(); + request.getActivationIds().addAll(activationIds); + pushServerClient.createDeviceForActivations(request); }); } @Test void createDeviceSameActivationSamePushTokenUpdatesTest() throws PushServerClientException { // This test tests refresh of a device registration - boolean actual = pushServerClient.createDevice(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.APNS, powerAuthTestClient.getActivationId()); + CreateDeviceRequest request = CreateDeviceRequest.builder() + .appId(powerAuthTestClient.getApplicationId()) + .token(MOCK_PUSH_TOKEN) + .platform(MobilePlatform.FCM) + .activationId(powerAuthTestClient.getActivationId()) + .build(); + boolean actual = pushServerClient.createDevice(request); 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.APNS, powerAuthTestClient.getActivationId()); + boolean actual2 = pushServerClient.createDevice(request); assertTrue(actual2); List devices2 = pushDeviceRepository.findByAppCredentialsAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN); assertEquals(1, devices2.size()); @@ -339,11 +406,18 @@ 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.APNS, powerAuthTestClient.getActivationId()); + CreateDeviceRequest request = CreateDeviceRequest.builder() + .appId(powerAuthTestClient.getApplicationId()) + .token(MOCK_PUSH_TOKEN) + .platform(MobilePlatform.FCM) + .activationId(powerAuthTestClient.getActivationId()) + .build(); + boolean actual = pushServerClient.createDevice(request); 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.APNS, powerAuthTestClient.getActivationId()); + request.setToken(MOCK_PUSH_TOKEN_2); + boolean actual2 = pushServerClient.createDevice(request); assertTrue(actual2); // The push token must change, however row ID stays the same List devices1 = pushDeviceRepository.findByAppCredentialsAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN); @@ -357,11 +431,18 @@ 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.APNS, powerAuthTestClient.getActivationId()); + CreateDeviceRequest request = CreateDeviceRequest.builder() + .appId(powerAuthTestClient.getApplicationId()) + .token(MOCK_PUSH_TOKEN) + .platform(MobilePlatform.FCM) + .activationId(powerAuthTestClient.getActivationId()) + .build(); + boolean actual = pushServerClient.createDevice(request); 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.APNS, powerAuthTestClient.getActivationId()); + request.setActivationId(powerAuthTestClient.getActivationId2()); + boolean actual2 = pushServerClient.createDevice(request); assertTrue(actual2); List devices2 = pushDeviceRepository.findByAppCredentialsAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN); assertEquals(1, devices2.size()); @@ -384,4 +465,5 @@ private ObjectResponse createCampaign() throws Exception pushMessageBody.setExtras(extras); return pushServerClient.createCampaign(powerAuthTestClient.getApplicationId(), pushMessageBody); } + } diff --git a/powerauth-push-server/src/test/resources/application-test-apns-prod.properties b/powerauth-push-server/src/test/resources/application-test-apns-prod.properties new file mode 100644 index 000000000..7eee2d336 --- /dev/null +++ b/powerauth-push-server/src/test/resources/application-test-apns-prod.properties @@ -0,0 +1,36 @@ +# +# Copyright 2016 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. + +# H2 +spring.h2.console.enabled=true +spring.h2.console.path=/h2 +# Datasource +spring.datasource.url=jdbc:h2:mem:powerauth;MODE=LEGACY +spring.datasource.username=sa +spring.datasource.password= + +# Hibernate Configuration +spring.jpa.hibernate.ddl-auto=create-drop + +# Override FCM url for tests, use high port number to avoid conflicts +server.port=54725 + +# Spring batch schema initialization for tests +spring.sql.init.mode=embedded + +spring.liquibase.enabled=false + +# Disallow test APNs host +powerauth.push.service.apns.useDevelopment=false \ No newline at end of file