From 103a806031bf97d71adbc1312a8097c6089fd443 Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Wed, 20 May 2020 22:00:46 +0200 Subject: [PATCH 01/51] Fix typo in log message --- .../main/java/io/getlime/push/service/PushSendingWorker.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 c6c4476cd..9b385ce88 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 @@ -338,7 +338,7 @@ void sendMessageToIos(final ApnsClient apnsClient, final PushMessageBody pushMes callback.didFinishSendingMessage(PushSendingCallback.Result.FAILED); } } else { - logger.info("Notification sent, APNS ID: {}", pushNotificationResponse.getApnsId()); + logger.info("Notification sent, APNs ID: {}", pushNotificationResponse.getApnsId()); callback.didFinishSendingMessage(PushSendingCallback.Result.OK); } } else { From 3dcc166c6cbd98e9a07df18a523269e3bca35d1b Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Tue, 26 May 2020 19:11:56 +0200 Subject: [PATCH 02/51] Fix formatting issues in documentation --- docs/Push-Server-API.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/Push-Server-API.md b/docs/Push-Server-API.md index 0a146a1bb..b3b91cdb8 100644 --- a/docs/Push-Server-API.md +++ b/docs/Push-Server-API.md @@ -488,6 +488,7 @@ Further campaign comes with: Create a campaign with application that campaign is using and certain message that contains parameters of message object. #### **Request** + @@ -543,6 +544,7 @@ _note: identifier of campaign is generated automatically_ Delete a specific campaign. Also users associated with this campaign are going to be deleted. If deletion was applied then deleted status is true. #### **Request** +
Method
@@ -583,6 +585,7 @@ Delete a specific campaign. Also users associated with this campaign are going t Return details of a specific campaign. #### **Request** +
Method
@@ -634,6 +637,7 @@ Return details of a specific campaign. Return list of actually registered campaigns, based on `all` parameter. This parameter decides if return campaigns that are 'only sent'(statement _false_) or return all registered campaigns (statement _true_). #### **Request** +
Method
@@ -700,6 +704,7 @@ Return list of actually registered campaigns, based on `all` parameter. This par Associate users to a specific campaign. Users are identified in request body as an array of strings. #### **Request** +
Method
@@ -740,6 +745,7 @@ Associate users to a specific campaign. Users are identified in request body as Return list users from a specific campaign. Users are shown in paginated format based on parameters assigned in URI. #### **Request** +
Method
@@ -791,6 +797,7 @@ Return list users from a specific campaign. Users are shown in paginated format Delete users associated with a specific campaign. Users are identified request body as an array of strings. #### **Request** +
Method
@@ -831,6 +838,7 @@ Delete users associated with a specific campaign. Users are identified request b Send message from a specific campaign on test user to check rightness of that campaign. #### **Request** +
Method
@@ -870,6 +878,7 @@ Send message from a specific campaign to devices belonged to users associated wi If sending was successful then `sent` parameter is set on _true_ and `timestampSent` is set on current time. #### **Request** +
Method
From 8fee6f2aabe0da85417772e07cdbf6a53636c607 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Dvo=C5=99=C3=A1k?= Date: Tue, 26 May 2020 20:16:38 +0200 Subject: [PATCH 03/51] Update Push-Server-API.md Add missing element and format the docs to avoid this issue. --- docs/Push-Server-API.md | 455 ++++++++++++++++++++-------------------- 1 file changed, 228 insertions(+), 227 deletions(-) diff --git a/docs/Push-Server-API.md b/docs/Push-Server-API.md index b3b91cdb8..af2131c6d 100644 --- a/docs/Push-Server-API.md +++ b/docs/Push-Server-API.md @@ -102,14 +102,14 @@ Describes basic information of application. Send a system status response, with basic information about the running application.
Method
- - - - - - - - + + + + + + + +
MethodGET
Resource URI/push/service/status
MethodGET
Resource URI/push/service/status
#### **Response** @@ -147,14 +147,14 @@ Create a new device push token (platform specific). The call must include `activ _Note: Since this endpoint is usually called by the back-end service, it is not secured in any way. It's the service that calls this endpoint responsibility to assure that the device is somehow authenticated before the push token is assigned with given activation ID, so that there are no incorrect bindings._ - - - - - - - - + + + + + + + +
MethodPOST
Resource URI/push/device/create
MethodPOST
Resource URI/push/device/create
#### **Request** @@ -190,14 +190,14 @@ Create a new device push token (platform specific). The call must include `activ _Note: Since this endpoint is usually called by the back-end service, it is not secured in any way. It's the service that calls this endpoint responsibility to assure that the device is somehow authenticated before the push token is assigned with given activation IDs, so that there are no incorrect bindings._ - - - - - - - - + + + + + + + +
MethodPOST
Resource URI/push/device/create/multi
MethodPOST
Resource URI/push/device/create/multi
#### **Request** @@ -234,14 +234,14 @@ _Note: Since this endpoint is usually called by the back-end service, it is not Removes registered device based on the push token value. - - - - - - - - + + + + + + + +
MethodPOST / DELETE
Resource URI/push/device/remove
MethodPOST / DELETE
Resource URI/push/device/remove
#### **Request** @@ -271,14 +271,14 @@ Removes registered device based on the push token value. Update the status of given device registration based on the associated activation ID. This can help assure that registration is in non-active state and cannot receive personal messages. - - - - - - - - + + + + + + + +
MethodPOST / PUT
Resource URI/push/device/status/update
MethodPOST / PUT
Resource URI/push/device/status/update
#### **Request** @@ -308,14 +308,14 @@ Represents a single notification sent to the device. It provides an abstraction Send a single push message to given user via provided application, optionally to the specific device represented by given `activationId`. - - - - - - - - + + + + + + + +
MethodPOST
Resource URI/push/message/send
MethodPOST
Resource URI/push/message/send
#### **Request** @@ -373,14 +373,14 @@ Send a single push message to given user via provided application, optionally to Sends a message message batch - each item in the batch represents a message to given user. The message is sent via provided application (optionally to the specific device represented by given `activationId`). - - - - - - - - + + + + + + + +
MethodPOST
Resource URI/push/message/batch/send
MethodPOST
Resource URI/push/message/batch/send
#### **Request** @@ -490,14 +490,14 @@ Create a campaign with application that campaign is using and certain message th #### **Request** - - - - - - - - + + + + + + + +
MethodPOST
Resource URI/push/campaign/create
MethodPOST
Resource URI/push/campaign/create
```json @@ -546,18 +546,18 @@ Delete a specific campaign. Also users associated with this campaign are going t #### **Request** - - - - - - - - - - - - + + + + + + + + + + + +
MethodPOST / DELETE
Resource URI/push/campaign/${ID}/delete
Var ${ID} Campaign identifier
MethodPOST / DELETE
Resource URI/push/campaign/${ID}/delete
Var ${ID} Campaign identifier
```json @@ -587,18 +587,18 @@ Return details of a specific campaign. #### **Request** - - - - - - - - - - - - + + + + + + + + + + + +
MethodGET
Resource URI/push/campaign/${ID}/detail
Var ${ID} Campaign identifier
MethodGET
Resource URI/push/campaign/${ID}/detail
Var ${ID} Campaign identifier
#### **Response** @@ -639,14 +639,14 @@ Return list of actually registered campaigns, based on `all` parameter. This par #### **Request** - - - - - - - - + + + + + + + +
MethodGET
Resource URI/push/campaign/list/?all={true|false}
MethodGET
Resource URI/push/campaign/list/?all={true|false}
#### **Response** @@ -706,18 +706,18 @@ Associate users to a specific campaign. Users are identified in request body as #### **Request** - - - - - - - - - - - - + + + + + + + + + + + +
MethodPOST / PUT
Resource URI/push/campaign/${ID}/user/add
Var ${ID} Campaign identifier
MethodPOST / PUT
Resource URI/push/campaign/${ID}/user/add
Var ${ID} Campaign identifier
```json @@ -747,26 +747,26 @@ Return list users from a specific campaign. Users are shown in paginated format #### **Request** - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + +
MethodGET
Resource URI/push/campaign/${ID}/user/list?page=${PAGE}&size=${SIZE}
Var ${ID} Campaign identifier
Var ${PAGE} Nubmer of page to show
Var ${SIZE}Number of elements per page
MethodGET
Resource URI/push/campaign/${ID}/user/list?page=${PAGE}&size=${SIZE}
Var ${ID} Campaign identifier
Var ${PAGE} Nubmer of page to show
Var ${SIZE}Number of elements per page
#### **Response** @@ -799,18 +799,18 @@ Delete users associated with a specific campaign. Users are identified request b #### **Request** - - - - - - - - - - - - + + + + + + + + + + + +
MethodPOST / DELETE
Resource URI/push/campaign/${ID}/user/delete
Var ${ID} Campaign identifier
MethodPOST / DELETE
Resource URI/push/campaign/${ID}/user/delete
Var ${ID} Campaign identifier
```json @@ -840,17 +840,18 @@ Send message from a specific campaign on test user to check rightness of that ca #### **Request** - - - - - - - - - - - + + + + + + + + + + + +
MethodPOST
Resource URI/push/campaign/send/test/${ID}
Var ${ID} Campaign identifier
MethodPOST
Resource URI/push/campaign/send/test/${ID}
Var ${ID}Campaign identifier
```json @@ -880,18 +881,18 @@ If sending was successful then `sent` parameter is set on _true_ and `timestampS #### **Request** - - - - - - - - - - - - + + + + + + + + + + + +
MethodPOST
Resource URI/push/campaign/send/live/${ID}
Var ${ID} Campaign identifier
MethodPOST
Resource URI/push/campaign/send/live/${ID}
Var ${ID} Campaign identifier
- empty request body @@ -913,14 +914,14 @@ Get list of all applications. #### **Request** - - - - - - - - + + + + + + + +
MethodGET
Resource URI/admin/app/list
MethodGET
Resource URI/admin/app/list
#### **Response** @@ -949,14 +950,14 @@ Get list of applications which have not been configured yet. #### **Request** - - - - - - - - + + + + + + + +
MethodGET
Resource URI/admin/app/unconfigured/list
MethodGET
Resource URI/admin/app/unconfigured/list
#### **Response** @@ -985,14 +986,14 @@ Get detail of an application. #### **Request** - - - - - - - - + + + + + + + +
MethodPOST
Resource URI/admin/app/detail
MethodPOST
Resource URI/admin/app/detail
```json @@ -1033,14 +1034,14 @@ Create a new supported application. #### **Request** - - - - - - - - + + + + + + + +
MethodPOST
Resource URI/admin/app/create
MethodPOST
Resource URI/admin/app/create
```json @@ -1069,14 +1070,14 @@ Update an iOS configuration. #### **Request** - - - - - - - - + + + + + + + +
MethodPOST / PUT
Resource URI/admin/app/ios/update
MethodPOST / PUT
Resource URI/admin/app/ios/update
```json @@ -1106,14 +1107,14 @@ Remove an iOS configuration. #### **Request** - - - - - - - - + + + + + + + +
MethodPOST / DELETE
Resource URI/admin/app/ios/remove
MethodPOST / DELETE
Resource URI/admin/app/ios/remove
```json @@ -1139,14 +1140,14 @@ Update an Android configuration. #### **Request** - - - - - - - - + + + + + + + +
MethodPOST / PUT
Resource URI/admin/app/android/update
MethodPOST / PUT
Resource URI/admin/app/android/update
```json @@ -1172,14 +1173,14 @@ Update an Android configuration. #### **Request** - - - - - - - - + + + + + + + +
MethodPOST / DELETE
Resource URI/admin/app/android/remove
MethodPOST / DELETE
Resource URI/admin/app/android/remove
```json From de57e965cd0d8516a5353c3350095f4cce2e10a3 Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Tue, 26 May 2020 21:54:30 +0200 Subject: [PATCH 04/51] Update list of migration instructions --- docs/Migration-Instructions.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/Migration-Instructions.md b/docs/Migration-Instructions.md index c80755014..6ec27ee2f 100644 --- a/docs/Migration-Instructions.md +++ b/docs/Migration-Instructions.md @@ -2,6 +2,7 @@ This page contains PowerAuth Push Server migration instructions. -- [PowerAuth Push Server 0.21.0](./PowerAuth-Push-Server-0.21.0.md) -- [PowerAuth Push Server 0.22.0](./PowerAuth-Push-Server-0.22.0.md) +- [PowerAuth Push Server 0.24.0](./PowerAuth-Push-Server-0.24.0.md) - [PowerAuth Push Server 0.23.0](./PowerAuth-Push-Server-0.23.0.md) +- [PowerAuth Push Server 0.22.0](./PowerAuth-Push-Server-0.22.0.md) +- [PowerAuth Push Server 0.21.0](./PowerAuth-Push-Server-0.21.0.md) From cdb6978fbe7afcf556de72a302aedd9bcc52e1b8 Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Fri, 26 Jun 2020 19:46:19 +0200 Subject: [PATCH 05/51] Fix #310: Migrate to PowerAuth REST client --- docs/Deploying-Push-Server.md | 14 +-- docs/Deploying-Wildfly.md | 2 +- docs/Push-Server-Integration.md | 6 +- pom.xml | 4 +- powerauth-push-client/pom.xml | 6 +- powerauth-push-model/pom.xml | 4 +- powerauth-push-server/pom.xml | 33 +----- .../PowerAuthWebServiceConfiguration.java | 103 ++---------------- .../rest/AdministrationController.java | 10 +- .../controller/rest/PushDeviceController.java | 10 +- .../src/main/resources/application.properties | 2 +- .../getlime/push/client/PushServerTests.java | 2 +- .../push/shared/PowerAuthTestClient.java | 23 ++-- .../shared/PushServerTestClientFactory.java | 4 +- ...ation-test-multiple-activations.properties | 2 +- .../resources/application-test.properties | 2 +- 16 files changed, 58 insertions(+), 169 deletions(-) diff --git a/docs/Deploying-Push-Server.md b/docs/Deploying-Push-Server.md index de204e45f..86386c434 100644 --- a/docs/Deploying-Push-Server.md +++ b/docs/Deploying-Push-Server.md @@ -36,10 +36,10 @@ As you can see, these credentials are the same as for the PowerAuth Server. You ## Configuration -The default implementation of a PowerAuth Push Server has only one compulsory configuration parameter `powerauth.service.url` that configures the SOAP endpoint location of a PowerAuth Server. The default value for this property points to `localhost`: +The default implementation of a PowerAuth Push Server has only one compulsory configuration parameter `powerauth.service.url` that configures the REST endpoint location of a PowerAuth Server. The default value for this property points to `localhost`: ```bash -powerauth.service.url=http://localhost:8080/powerauth-java-server/soap +powerauth.service.url=http://localhost:8080/powerauth-java-server/rest ``` There are several optional configuration options you may want to set up. @@ -104,7 +104,7 @@ In order to run PowerAuth Push server behind the proxy, you simply need to confi ### Disabling SSL Validation During Development -_(optional)_ While this is **strongly discouraged in production environment** (we cannot emphasize this enough), some development environments may use self-signed certificate for HTTPS communication. In case PowerAuth SOAP service uses HTTPS with such certificate, and in case you are not able to correctly configure a custom keystore in your server container, you may disable SSL certificate validation by setting this property: +_(optional)_ While this is **strongly discouraged in production environment** (we cannot emphasize this enough), some development environments may use self-signed certificate for HTTPS communication. In case PowerAuth REST service uses HTTPS with such certificate, and in case you are not able to correctly configure a custom keystore in your server container, you may disable SSL certificate validation by setting this property: ```bash powerauth.service.ssl.acceptInvalidSslCertificate=true @@ -112,7 +112,7 @@ powerauth.service.ssl.acceptInvalidSslCertificate=true ### Setting Up Credentials -_(optional)_ In case PowerAuth Server uses a [restricted access flag in the server configuration](https://github.com/wultra/powerauth-server/blob/develop/docs/Deploying-PowerAuth-Server.md#enabling-powerauth-server-security), you need to configure credentials for the PowerAuth Push Server so that it can connect to the SOAP service: +_(optional)_ In case PowerAuth Server uses a [restricted access flag in the server configuration](https://github.com/wultra/powerauth-server/blob/develop/docs/Deploying-PowerAuth-Server.md#enabling-powerauth-server-security), you need to configure credentials for the PowerAuth Push Server so that it can connect to the REST service: ```sh powerauth.service.security.clientToken= @@ -121,7 +121,7 @@ powerauth.service.security.clientSecret= The credentials are stored in the `pa_integration` table. -_Note: For SOAP interface, PowerAuth Server uses WS-Security, `UsernameToken` validation (plain text password). The RESTful interface is secured using Basic HTTP Authentication (pre-emptive)._ +_Note: The RESTful interface is secured using Basic HTTP Authentication (pre-emptive)._ ## Using up ALPN @@ -141,7 +141,7 @@ The default configuration works best with Apache Tomcat server running on defaul To deploy PowerAuth Push Server to Apache Tomcat, simply copy the WAR file in your `webapps` folder or deploy it using the "Tomcat Web Application Manager" application (usually deployed on default Tomcat address `http://localhost:8080/manager`). -*__Important note: Since PowerAuth Push Server is a very simple application with direct access to the PowerAuth Server SOAP services, it must not be under any circumstances published publicly and must be constrained to the in-house closed infrastructure. The only exception to this rule is the requirement to open up ports for the purpose of communication with APNs and FCM services - the push notifications apparently would not work without access to the primary push service providers.__* +*__Important note: Since PowerAuth Push Server is a very simple application with direct access to the PowerAuth Server REST services, it must not be under any circumstances published publicly and must be constrained to the in-house closed infrastructure. The only exception to this rule is the requirement to open up ports for the purpose of communication with APNs and FCM services - the push notifications apparently would not work without access to the primary push service providers.__* ## Outside the Container @@ -153,7 +153,7 @@ java -jar powerauth-push-server.war _Note: You can overwrite the port using `-Dserver.port=8090` parameter to avoid port conflicts._ -*__Important note: Since PowerAuth Push Server is a very simple application with direct access to the PowerAuth Server SOAP services, it must not be under any circumstances published publicly and must be constrained to the in-house closed infrastructure. The only exception to this rule is the requirement to open up ports for the purpose of communication with APNs and FCM services - the push notifications apparently would not work without access to the primary push service providers.__* +*__Important note: Since PowerAuth Push Server is a very simple application with direct access to the PowerAuth Server REST services, it must not be under any circumstances published publicly and must be constrained to the in-house closed infrastructure. The only exception to this rule is the requirement to open up ports for the purpose of communication with APNs and FCM services - the push notifications apparently would not work without access to the primary push service providers.__* ## Deploying Push Server On JBoss / Wildfly diff --git a/docs/Deploying-Wildfly.md b/docs/Deploying-Wildfly.md index 9db20d577..87f58e394 100644 --- a/docs/Deploying-Wildfly.md +++ b/docs/Deploying-Wildfly.md @@ -81,7 +81,7 @@ Use the `logback.xml` file to configure logging, for example: The `application-ext.properties` file is used to override default configuration properties, for example: ``` # PowerAuth 2.0 Client configuration -powerauth.service.url=http://[host]:[port]/powerauth-java-server/soap +powerauth.service.url=http://[host]:[port]/powerauth-java-server/rest ``` Push Server Spring application uses the `ext` Spring profile which activates overriding of default properties by `application-ext.properties`. diff --git a/docs/Push-Server-Integration.md b/docs/Push-Server-Integration.md index 7fc05c84f..baabb65fc 100644 --- a/docs/Push-Server-Integration.md +++ b/docs/Push-Server-Integration.md @@ -4,10 +4,10 @@ In order to register devices and send push notifications, the Push Server needs ## Prerequisites for the tutorial -- Running PowerAuth Server with available SOAP interface. -- Running PowerAuth Push Server with available RESTful interface. +- Running PowerAuth Server with available REST interface. +- Running PowerAuth Push Server with available REST interface. - Knowledge of applications based on Spring Framework. -- Software: IDE - Spring Tool Suite, Java EE Application Server (Pivotal Server, Tomcat, ...) +- Software: IDE, Application Server (Tomcat, Wildfly, ...) ## Common integration steps diff --git a/pom.xml b/pom.xml index 716d2badc..2bf7ea85d 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ io.getlime.security powerauth-push-server-parent - 0.24.0 + 1.0.0-SNAPSHOT pom @@ -83,7 +83,7 @@ 2.0 1.2.5 1.1.0 - 0.24.0 + 1.0.0-SNAPSHOT 0.13.10 2.9.2 3.7.02 diff --git a/powerauth-push-client/pom.xml b/powerauth-push-client/pom.xml index ea6d5c1f1..c4d35c7ed 100644 --- a/powerauth-push-client/pom.xml +++ b/powerauth-push-client/pom.xml @@ -5,13 +5,13 @@ 4.0.0 powerauth-push-client PowerAuth Push Server RESTful Client - 0.24.0 + 1.0.0-SNAPSHOT jar powerauth-push-server-parent io.getlime.security - 0.24.0 + 1.0.0-SNAPSHOT @@ -20,7 +20,7 @@ io.getlime.security powerauth-push-model - 0.24.0 + 1.0.0-SNAPSHOT diff --git a/powerauth-push-model/pom.xml b/powerauth-push-model/pom.xml index ef2d1ec70..0d7678148 100644 --- a/powerauth-push-model/pom.xml +++ b/powerauth-push-model/pom.xml @@ -6,13 +6,13 @@ powerauth-push-model PowerAuth Push Server RESTful Model Classes - 0.24.0 + 1.0.0-SNAPSHOT jar powerauth-push-server-parent io.getlime.security - 0.24.0 + 1.0.0-SNAPSHOT diff --git a/powerauth-push-server/pom.xml b/powerauth-push-server/pom.xml index 9f3e29be9..8bfa84928 100644 --- a/powerauth-push-server/pom.xml +++ b/powerauth-push-server/pom.xml @@ -6,13 +6,13 @@ powerauth-push-server PowerAuth Push Server powerauth-push-server - 0.24.0 + 1.0.0-SNAPSHOT war io.getlime.security powerauth-push-server-parent - 0.24.0 + 1.0.0-SNAPSHOT ../pom.xml @@ -135,7 +135,7 @@ io.getlime.security - powerauth-java-client-spring + powerauth-rest-client-spring ${powerauth.version} @@ -151,33 +151,6 @@ ${jackson.version} - - - javax.xml.bind - jaxb-api - ${jaxb.version} - - - com.sun.xml.bind - jaxb-impl - ${jaxb.version} - - - com.sun.istack - istack-commons-runtime - ${istack.version} - - - javax.xml.soap - javax.xml.soap-api - ${jaxws.version} - - - com.sun.xml.messaging.saaj - saaj-impl - ${saaj.version} - - org.springframework.boot diff --git a/powerauth-push-server/src/main/java/io/getlime/push/configuration/PowerAuthWebServiceConfiguration.java b/powerauth-push-server/src/main/java/io/getlime/push/configuration/PowerAuthWebServiceConfiguration.java index ef69f0538..d95b5f47f 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/configuration/PowerAuthWebServiceConfiguration.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/configuration/PowerAuthWebServiceConfiguration.java @@ -15,18 +15,14 @@ */ package io.getlime.push.configuration; +import com.wultra.security.powerauth.client.PowerAuthClient; +import com.wultra.security.powerauth.rest.client.PowerAuthRestClient; +import com.wultra.security.powerauth.rest.client.PowerAuthRestClientConfiguration; import io.getlime.push.client.PushServerClient; -import io.getlime.security.powerauth.soap.spring.client.PowerAuthServiceClient; -import org.apache.wss4j.dom.WSConstants; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; -import org.springframework.oxm.jaxb.Jaxb2Marshaller; -import org.springframework.ws.client.support.interceptor.ClientInterceptor; -import org.springframework.ws.soap.security.wss4j2.Wss4jSecurityInterceptor; - -import javax.net.ssl.*; /** * Default PowerAuth Service configuration. @@ -38,7 +34,7 @@ public class PowerAuthWebServiceConfiguration { @Value("${powerauth.service.url}") - private String powerAuthServiceUrl; + private String powerAuthRestUrl; @Value("${powerauth.push.service.url}") private String pushServiceUrl; @@ -53,93 +49,18 @@ public class PowerAuthWebServiceConfiguration { private String clientSecret; /** - * Return WS-Security interceptor instance using UsernameToken authentication. - * - * @return Wss4jSecurityInterceptor instance. - */ - @Bean - public Wss4jSecurityInterceptor securityInterceptor() { - Wss4jSecurityInterceptor wss4jSecurityInterceptor = new Wss4jSecurityInterceptor(); - wss4jSecurityInterceptor.setSecurementActions("UsernameToken"); - wss4jSecurityInterceptor.setSecurementUsername(clientToken); - wss4jSecurityInterceptor.setSecurementPassword(clientSecret); - wss4jSecurityInterceptor.setSecurementPasswordType(WSConstants.PW_TEXT); - return wss4jSecurityInterceptor; - } - - /** - * Marshaller for PowerAuth SOAP service communication. - * - * @return JAXB marshaller with correctly configured context path. + * Initialize PowerAuth REST client. + * @return PowerAuth REST client. */ @Bean - public Jaxb2Marshaller marshaller() { - Jaxb2Marshaller marshaller = new Jaxb2Marshaller(); - marshaller.setContextPaths("io.getlime.powerauth.soap.v3"); - return marshaller; + public PowerAuthClient powerAuthClient() { + PowerAuthRestClientConfiguration config = new PowerAuthRestClientConfiguration(); + config.setPowerAuthClientToken(clientToken); + config.setPowerAuthClientSecret(clientSecret); + config.setAcceptInvalidSslCertificate(acceptInvalidSslCertificate); + return new PowerAuthRestClient(powerAuthRestUrl, config); } - /** - * Prepare a correctly configured PowerAuthServiceClient instance with the service - * URL specified using 'powerauth.service.url' server property. - * - * @param marshaller JAXB marshaller - * @return Correctly configured PowerAuthServiceClient instance with the service - * URL specified using 'powerauth.service.url' server property - */ - @Bean - public PowerAuthServiceClient powerAuthClient(Jaxb2Marshaller marshaller) { - PowerAuthServiceClient client = new PowerAuthServiceClient(); - client.setDefaultUri(powerAuthServiceUrl); - client.setMarshaller(marshaller); - client.setUnmarshaller(marshaller); - - // if invalid SSL certificates should be accepted - if (acceptInvalidSslCertificate) { - - HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() { - @Override - public boolean verify(String hostname, SSLSession session) { - return true; - } - }); - - TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() { - - public java.security.cert.X509Certificate[] getAcceptedIssuers() { - return null; - } - - public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) { - } - - public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) { - } - - }}; - - try { - SSLContext sc = SSLContext.getInstance("SSL"); - sc.init(null, trustAllCerts, new java.security.SecureRandom()); - HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); - } catch (Exception e) { - // ... ignore - } - - } - - - // if there is a configuration with security credentials, add interceptor - if (!clientToken.isEmpty()) { - ClientInterceptor[] interceptors = new ClientInterceptor[]{ - securityInterceptor() - }; - client.setInterceptors(interceptors); - } - return client; - } - - /** * Initialize PowerAuth 2.0 Push server client. * @return Push server client. diff --git a/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/AdministrationController.java b/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/AdministrationController.java index 396d91198..a1ec59855 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/AdministrationController.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/AdministrationController.java @@ -16,6 +16,7 @@ package io.getlime.push.controller.rest; import com.google.common.io.BaseEncoding; +import com.wultra.security.powerauth.client.PowerAuthClient; import io.getlime.core.rest.model.base.request.ObjectRequest; import io.getlime.core.rest.model.base.response.ObjectResponse; import io.getlime.core.rest.model.base.response.Response; @@ -29,7 +30,6 @@ import io.getlime.push.repository.AppCredentialsRepository; import io.getlime.push.repository.model.AppCredentialsEntity; import io.getlime.push.service.batch.storage.AppCredentialStorageMap; -import io.getlime.security.powerauth.soap.spring.client.PowerAuthServiceClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -46,7 +46,7 @@ public class AdministrationController { private static final Logger logger = LoggerFactory.getLogger(AdministrationController.class); - private final PowerAuthServiceClient powerAuthClient; + private final PowerAuthClient powerAuthClient; private final AppCredentialsRepository appCredentialsRepository; private final AppCredentialStorageMap appCredentialStorageMap; @@ -57,7 +57,7 @@ public class AdministrationController { * @param appCredentialStorageMap Application credentials storage map. */ @Autowired - public AdministrationController(PowerAuthServiceClient powerAuthClient, AppCredentialsRepository appCredentialsRepository, AppCredentialStorageMap appCredentialStorageMap) { + public AdministrationController(PowerAuthClient powerAuthClient, AppCredentialsRepository appCredentialsRepository, AppCredentialStorageMap appCredentialStorageMap) { this.powerAuthClient = powerAuthClient; this.appCredentialsRepository = appCredentialsRepository; this.appCredentialStorageMap = appCredentialStorageMap; @@ -96,7 +96,7 @@ public ObjectResponse listUnconfiguredApplications() logger.debug("Received listUnconfiguredApplications request"); GetApplicationListResponse response = new GetApplicationListResponse(); // Get all applications in PA Server - final List applicationList = powerAuthClient.getApplicationList(); + final List applicationList = powerAuthClient.getApplicationList(); // Get all applications that are already set up final Iterable appCredentials = appCredentialsRepository.findAll(); @@ -106,7 +106,7 @@ public ObjectResponse listUnconfiguredApplications() for (AppCredentialsEntity appCred: appCredentials) { identifiers.add(appCred.getAppId()); } - for (io.getlime.powerauth.soap.v3.GetApplicationListResponse.Applications app : applicationList) { + for (com.wultra.security.powerauth.client.v3.GetApplicationListResponse.Applications app : applicationList) { if (!identifiers.contains(app.getId())) { PushServerApplication applicationToAdd = new PushServerApplication(); applicationToAdd.setId(app.getId()); diff --git a/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/PushDeviceController.java b/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/PushDeviceController.java index 051377966..3efb59c49 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/PushDeviceController.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/PushDeviceController.java @@ -15,10 +15,11 @@ */ package io.getlime.push.controller.rest; +import com.wultra.security.powerauth.client.PowerAuthClient; +import com.wultra.security.powerauth.client.v3.ActivationStatus; +import com.wultra.security.powerauth.client.v3.GetActivationStatusResponse; import io.getlime.core.rest.model.base.request.ObjectRequest; import io.getlime.core.rest.model.base.response.Response; -import io.getlime.powerauth.soap.v3.ActivationStatus; -import io.getlime.powerauth.soap.v3.GetActivationStatusResponse; import io.getlime.push.configuration.PushServiceConfiguration; import io.getlime.push.errorhandling.exceptions.PushServerException; import io.getlime.push.model.request.CreateDeviceForActivationsRequest; @@ -30,7 +31,6 @@ import io.getlime.push.model.validator.UpdateDeviceStatusRequestValidator; import io.getlime.push.repository.PushDeviceRepository; import io.getlime.push.repository.model.PushDeviceRegistrationEntity; -import io.getlime.security.powerauth.soap.spring.client.PowerAuthServiceClient; import io.swagger.annotations.ApiOperation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -55,11 +55,11 @@ public class PushDeviceController { private static final Logger logger = LoggerFactory.getLogger(PushDeviceController.class); private final PushDeviceRepository pushDeviceRepository; - private final PowerAuthServiceClient client; + private final PowerAuthClient client; private final PushServiceConfiguration config; @Autowired - public PushDeviceController(PushDeviceRepository pushDeviceRepository, PowerAuthServiceClient client, PushServiceConfiguration config) { + public PushDeviceController(PushDeviceRepository pushDeviceRepository, PowerAuthClient client, PushServiceConfiguration config) { this.pushDeviceRepository = pushDeviceRepository; this.client = client; this.config = config; diff --git a/powerauth-push-server/src/main/resources/application.properties b/powerauth-push-server/src/main/resources/application.properties index a20de9867..b15adbe99 100644 --- a/powerauth-push-server/src/main/resources/application.properties +++ b/powerauth-push-server/src/main/resources/application.properties @@ -28,7 +28,7 @@ spring.batch.job.enabled=false spring.jpa.hibernate.ddl-auto=none # PowerAuth 2.0 Service Configuration -powerauth.service.url=http://localhost:8080/powerauth-java-server/soap +powerauth.service.url=http://localhost:8080/powerauth-java-server/rest powerauth.service.security.clientToken= powerauth.service.security.clientSecret= powerauth.service.ssl.acceptInvalidSslCertificate=false diff --git a/powerauth-push-server/src/test/java/io/getlime/push/client/PushServerTests.java b/powerauth-push-server/src/test/java/io/getlime/push/client/PushServerTests.java index 154fef73d..a9551e5b0 100644 --- a/powerauth-push-server/src/test/java/io/getlime/push/client/PushServerTests.java +++ b/powerauth-push-server/src/test/java/io/getlime/push/client/PushServerTests.java @@ -97,7 +97,7 @@ public class PushServerTests { private int port; @Value("${powerauth.service.url}") - private String powerAuthServiceUrl; + private String powerAuthRestUrl; @Value("${powerauth.push.service.fcm.sendMessageUrl}") private String fcmUrlForTests; diff --git a/powerauth-push-server/src/test/java/io/getlime/push/shared/PowerAuthTestClient.java b/powerauth-push-server/src/test/java/io/getlime/push/shared/PowerAuthTestClient.java index 881cc7694..bf2a4671b 100644 --- a/powerauth-push-server/src/test/java/io/getlime/push/shared/PowerAuthTestClient.java +++ b/powerauth-push-server/src/test/java/io/getlime/push/shared/PowerAuthTestClient.java @@ -2,7 +2,9 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.io.BaseEncoding; -import io.getlime.powerauth.soap.v3.*; +import com.wultra.security.powerauth.client.PowerAuthClient; +import com.wultra.security.powerauth.client.v3.*; +import com.wultra.security.powerauth.rest.client.PowerAuthRestClient; import io.getlime.security.powerauth.crypto.client.activation.PowerAuthClientActivation; import io.getlime.security.powerauth.crypto.lib.encryptor.ecies.EciesEncryptor; import io.getlime.security.powerauth.crypto.lib.encryptor.ecies.EciesFactory; @@ -10,8 +12,6 @@ import io.getlime.security.powerauth.crypto.lib.encryptor.ecies.model.EciesSharedInfo1; import io.getlime.security.powerauth.crypto.lib.util.KeyConvertor; import io.getlime.security.powerauth.rest.api.model.request.v3.ActivationLayer2Request; -import io.getlime.security.powerauth.soap.spring.client.PowerAuthServiceClient; -import org.springframework.oxm.jaxb.Jaxb2Marshaller; import org.springframework.stereotype.Service; import java.io.ByteArrayOutputStream; @@ -29,7 +29,7 @@ public class PowerAuthTestClient { private final PowerAuthClientActivation activation = new PowerAuthClientActivation(); private final EciesFactory eciesFactory = new EciesFactory(); private final KeyConvertor keyConvertor = new KeyConvertor(); - private PowerAuthServiceClient powerAuthClient; + private PowerAuthClient powerAuthClient; private Long applicationId; private String applicationKey; @@ -43,32 +43,27 @@ public class PowerAuthTestClient { private ObjectMapper objectMapper = new ObjectMapper(); - public void initializeClient(String powerAuthServiceUrl) { - powerAuthClient = new PowerAuthServiceClient(); - powerAuthClient.setDefaultUri(powerAuthServiceUrl); - Jaxb2Marshaller marshaller = new Jaxb2Marshaller(); - marshaller.setContextPath("io.getlime.powerauth.soap.v3"); - powerAuthClient.setMarshaller(marshaller); - powerAuthClient.setUnmarshaller(marshaller); + public void initializeClient(String powerAuthRestUrl) { + powerAuthClient = new PowerAuthRestClient(powerAuthRestUrl); } public Long initializeApplication(String applicationName, String applicationVersion) { // Create application if it does not exist List applications = powerAuthClient.getApplicationList(); boolean applicationExists = false; - for (io.getlime.powerauth.soap.v3.GetApplicationListResponse.Applications app: applications) { + for (com.wultra.security.powerauth.client.v3.GetApplicationListResponse.Applications app: applications) { if (app.getApplicationName().equals(applicationName)) { applicationExists = true; applicationId = app.getId(); } } if (!applicationExists) { - io.getlime.powerauth.soap.v3.CreateApplicationResponse response = powerAuthClient.createApplication(applicationName); + com.wultra.security.powerauth.client.v3.CreateApplicationResponse response = powerAuthClient.createApplication(applicationName); applicationId = response.getApplicationId(); } // Create application version if it does not exist - io.getlime.powerauth.soap.v3.GetApplicationDetailResponse detail = powerAuthClient.getApplicationDetail(applicationId); + com.wultra.security.powerauth.client.v3.GetApplicationDetailResponse detail = powerAuthClient.getApplicationDetail(applicationId); masterPublicKey = detail.getMasterPublicKey(); boolean versionExists = false; for (GetApplicationDetailResponse.Versions appVersion: detail.getVersions()) { diff --git a/powerauth-push-server/src/test/java/io/getlime/push/shared/PushServerTestClientFactory.java b/powerauth-push-server/src/test/java/io/getlime/push/shared/PushServerTestClientFactory.java index 78963ae71..d26a44c7d 100644 --- a/powerauth-push-server/src/test/java/io/getlime/push/shared/PushServerTestClientFactory.java +++ b/powerauth-push-server/src/test/java/io/getlime/push/shared/PushServerTestClientFactory.java @@ -15,7 +15,7 @@ public class PushServerTestClientFactory { private static final String TEST_USER_ID = "Test_User"; @Value("${powerauth.service.url}") - private String powerAuthServiceUrl; + private String powerAuthRestUrl; public PushServerClient createPushServerClient(String baseUrl) { return new PushServerClient(baseUrl); @@ -25,7 +25,7 @@ public PowerAuthTestClient createPowerAuthTestClient() throws Exception { Security.addProvider(new BouncyCastleProvider()); PowerAuthTestClient powerAuthTestClient = new PowerAuthTestClient(); - powerAuthTestClient.initializeClient(powerAuthServiceUrl); + powerAuthTestClient.initializeClient(powerAuthRestUrl); Long applicationId = powerAuthTestClient.initializeApplication(TEST_APPLICATION_NAME, TEST_APPLICATION_VERSION); String activationId = powerAuthTestClient.createActivation(TEST_USER_ID); String activationId2 = powerAuthTestClient.createActivation(TEST_USER_ID); diff --git a/powerauth-push-server/src/test/resources/application-test-multiple-activations.properties b/powerauth-push-server/src/test/resources/application-test-multiple-activations.properties index cd66c953e..cb88b76e2 100644 --- a/powerauth-push-server/src/test/resources/application-test-multiple-activations.properties +++ b/powerauth-push-server/src/test/resources/application-test-multiple-activations.properties @@ -28,7 +28,7 @@ spring.datasource.driver-class-name=org.h2.Driver spring.jpa.hibernate.ddl-auto=create-drop # PowerAuth 2.0 Service Configuration -powerauth.service.url=${POWERAUTH_ENDPOINT}/powerauth-java-server/soap +powerauth.service.url=${POWERAUTH_ENDPOINT}/powerauth-java-server/rest powerauth.service.security.clientToken= powerauth.service.security.clientSecret= powerauth.service.ssl.acceptInvalidSslCertificate=false diff --git a/powerauth-push-server/src/test/resources/application-test.properties b/powerauth-push-server/src/test/resources/application-test.properties index 60173cf96..2774c06fd 100644 --- a/powerauth-push-server/src/test/resources/application-test.properties +++ b/powerauth-push-server/src/test/resources/application-test.properties @@ -28,7 +28,7 @@ spring.datasource.driver-class-name=org.h2.Driver spring.jpa.hibernate.ddl-auto=create-drop # PowerAuth 2.0 Service Configuration -powerauth.service.url=${POWERAUTH_ENDPOINT}/powerauth-java-server/soap +powerauth.service.url=${POWERAUTH_ENDPOINT}/powerauth-java-server/rest powerauth.service.security.clientToken= powerauth.service.security.clientSecret= powerauth.service.ssl.acceptInvalidSslCertificate=false From 0398a08394c9154a9ad334d6264735cae8e1f571 Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Fri, 26 Jun 2020 20:04:31 +0200 Subject: [PATCH 06/51] Remove unnecessary versions --- pom.xml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pom.xml b/pom.xml index 2bf7ea85d..869ae8a0b 100644 --- a/pom.xml +++ b/pom.xml @@ -89,10 +89,6 @@ 3.7.02 1.30.9 6.12.2 - 2.3.1 - 3.0.10 - 1.4.0 - 1.5.1 1.65 From cb3c37c57a5b855de9510336c4451209338bfa28 Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Tue, 30 Jun 2020 14:17:09 +0200 Subject: [PATCH 07/51] Error handling for PowerAuth client --- .../rest/AdministrationController.java | 140 ++++++++++-------- .../controller/rest/PushDeviceController.java | 60 +++++--- 2 files changed, 115 insertions(+), 85 deletions(-) diff --git a/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/AdministrationController.java b/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/AdministrationController.java index a1ec59855..e09c63b42 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/AdministrationController.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/AdministrationController.java @@ -17,6 +17,7 @@ import com.google.common.io.BaseEncoding; import com.wultra.security.powerauth.client.PowerAuthClient; +import com.wultra.security.powerauth.client.model.error.PowerAuthClientException; import io.getlime.core.rest.model.base.request.ObjectRequest; import io.getlime.core.rest.model.base.response.ObjectResponse; import io.getlime.core.rest.model.base.response.Response; @@ -66,57 +67,69 @@ public AdministrationController(PowerAuthClient powerAuthClient, AppCredentialsR /** * List applications configured in Push Server. * @return Application list response. + * @throws PushServerException Throw in case communication with PowerAuth server fails. */ @GetMapping(value = "list") - public ObjectResponse listApplications() { - logger.debug("Received listApplications request"); - final GetApplicationListResponse response = new GetApplicationListResponse(); - final Iterable appCredentials = appCredentialsRepository.findAll(); - final List appList = new ArrayList<>(); - for (AppCredentialsEntity appCredentialsEntity : appCredentials) { - PushServerApplication app = new PushServerApplication(); - app.setId(appCredentialsEntity.getId()); - app.setAppId(appCredentialsEntity.getAppId()); - app.setAppName(powerAuthClient.getApplicationDetail(appCredentialsEntity.getAppId()).getApplicationName()); - app.setIos(appCredentialsEntity.getIosPrivateKey() != null); - app.setAndroid(appCredentialsEntity.getAndroidPrivateKey() != null); - appList.add(app); + public ObjectResponse listApplications() throws PushServerException { + try { + logger.debug("Received listApplications request"); + final GetApplicationListResponse response = new GetApplicationListResponse(); + final Iterable appCredentials = appCredentialsRepository.findAll(); + final List appList = new ArrayList<>(); + for (AppCredentialsEntity appCredentialsEntity : appCredentials) { + PushServerApplication app = new PushServerApplication(); + app.setId(appCredentialsEntity.getId()); + app.setAppId(appCredentialsEntity.getAppId()); + app.setAppName(powerAuthClient.getApplicationDetail(appCredentialsEntity.getAppId()).getApplicationName()); + app.setIos(appCredentialsEntity.getIosPrivateKey() != null); + app.setAndroid(appCredentialsEntity.getAndroidPrivateKey() != null); + appList.add(app); + } + response.setApplicationList(appList); + logger.debug("The listApplications request succeeded"); + return new ObjectResponse<>(response); + } catch (PowerAuthClientException ex) { + logger.warn(ex.getMessage(), ex); + throw new PushServerException("Application list failed because application name could not be retrieved"); } - response.setApplicationList(appList); - logger.debug("The listApplications request succeeded"); - return new ObjectResponse<>(response); } /** * List applications which are not yet configured in Push Server. * @return Application list response. + * @throws PushServerException Throw in case communication with PowerAuth server fails. */ @GetMapping(value = "unconfigured/list") - public ObjectResponse listUnconfiguredApplications() { - logger.debug("Received listUnconfiguredApplications request"); - GetApplicationListResponse response = new GetApplicationListResponse(); - // Get all applications in PA Server - final List applicationList = powerAuthClient.getApplicationList(); + public ObjectResponse listUnconfiguredApplications() throws PushServerException { + try { + logger.debug("Received listUnconfiguredApplications request"); + GetApplicationListResponse response = new GetApplicationListResponse(); + // Get all applications in PA Server + final List applicationList = powerAuthClient.getApplicationList(); - // Get all applications that are already set up - final Iterable appCredentials = appCredentialsRepository.findAll(); + // Get all applications that are already set up + final Iterable appCredentials = appCredentialsRepository.findAll(); - // Compute intersection by app ID - Set identifiers = new HashSet<>(); - for (AppCredentialsEntity appCred: appCredentials) { - identifiers.add(appCred.getAppId()); - } - for (com.wultra.security.powerauth.client.v3.GetApplicationListResponse.Applications app : applicationList) { - if (!identifiers.contains(app.getId())) { - PushServerApplication applicationToAdd = new PushServerApplication(); - applicationToAdd.setId(app.getId()); - applicationToAdd.setAppName(app.getApplicationName()); - // add apps in intersection - response.getApplicationList().add(applicationToAdd); + // Compute intersection by app ID + Set identifiers = new HashSet<>(); + for (AppCredentialsEntity appCred: appCredentials) { + identifiers.add(appCred.getAppId()); + } + for (com.wultra.security.powerauth.client.v3.GetApplicationListResponse.Applications app : applicationList) { + if (!identifiers.contains(app.getId())) { + PushServerApplication applicationToAdd = new PushServerApplication(); + applicationToAdd.setId(app.getId()); + applicationToAdd.setAppName(app.getApplicationName()); + // add apps in intersection + response.getApplicationList().add(applicationToAdd); + } } + logger.debug("The listUnconfiguredApplications request succeeded"); + return new ObjectResponse<>(response); + } catch (PowerAuthClientException ex) { + logger.warn(ex.getMessage(), ex); + throw new PushServerException("Unconfigured application list failed because application name could not be retrieved"); } - logger.debug("The listUnconfiguredApplications request succeeded"); - return new ObjectResponse<>(response); } /** @@ -127,31 +140,36 @@ public ObjectResponse listUnconfiguredApplications() */ @PostMapping(value = "detail") public ObjectResponse getApplicationDetail(@RequestBody ObjectRequest request) throws PushServerException { - logger.debug("Received getApplicationDetail request"); - final GetApplicationDetailRequest requestObject = request.getRequestObject(); - String errorMessage = GetApplicationDetailRequestValidator.validate(requestObject); - if (errorMessage != null) { - throw new PushServerException(errorMessage); - } - final GetApplicationDetailResponse response = new GetApplicationDetailResponse(); - final AppCredentialsEntity appCredentialsEntity = findAppCredentialsEntityById(requestObject.getId()); - final PushServerApplication app = new PushServerApplication(); - app.setId(appCredentialsEntity.getId()); - app.setAppId(appCredentialsEntity.getAppId()); - app.setIos(appCredentialsEntity.getIosPrivateKey() != null); - app.setAndroid(appCredentialsEntity.getAndroidPrivateKey() != null); - app.setAppName(powerAuthClient.getApplicationDetail(appCredentialsEntity.getAppId()).getApplicationName()); - response.setApplication(app); - if (requestObject.getIncludeIos()) { - response.setIosBundle(appCredentialsEntity.getIosBundle()); - response.setIosKeyId(appCredentialsEntity.getIosKeyId()); - response.setIosTeamId(appCredentialsEntity.getIosTeamId()); - } - if (requestObject.getIncludeAndroid()) { - response.setAndroidProjectId(appCredentialsEntity.getAndroidProjectId()); + try { + logger.debug("Received getApplicationDetail request"); + final GetApplicationDetailRequest requestObject = request.getRequestObject(); + String errorMessage = GetApplicationDetailRequestValidator.validate(requestObject); + if (errorMessage != null) { + throw new PushServerException(errorMessage); + } + final GetApplicationDetailResponse response = new GetApplicationDetailResponse(); + final AppCredentialsEntity appCredentialsEntity = findAppCredentialsEntityById(requestObject.getId()); + final PushServerApplication app = new PushServerApplication(); + app.setId(appCredentialsEntity.getId()); + app.setAppId(appCredentialsEntity.getAppId()); + app.setIos(appCredentialsEntity.getIosPrivateKey() != null); + app.setAndroid(appCredentialsEntity.getAndroidPrivateKey() != null); + app.setAppName(powerAuthClient.getApplicationDetail(appCredentialsEntity.getAppId()).getApplicationName()); + response.setApplication(app); + if (requestObject.getIncludeIos()) { + response.setIosBundle(appCredentialsEntity.getIosBundle()); + response.setIosKeyId(appCredentialsEntity.getIosKeyId()); + response.setIosTeamId(appCredentialsEntity.getIosTeamId()); + } + if (requestObject.getIncludeAndroid()) { + response.setAndroidProjectId(appCredentialsEntity.getAndroidProjectId()); + } + logger.debug("The getApplicationDetail request succeeded"); + return new ObjectResponse<>(response); + } catch (PowerAuthClientException ex) { + logger.warn(ex.getMessage(), ex); + throw new PushServerException("Application detail failed because application name could not be retrieved"); } - logger.debug("The getApplicationDetail request succeeded"); - return new ObjectResponse<>(response); } /** diff --git a/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/PushDeviceController.java b/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/PushDeviceController.java index 3efb59c49..c07f6d2b6 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/PushDeviceController.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/PushDeviceController.java @@ -16,6 +16,7 @@ package io.getlime.push.controller.rest; import com.wultra.security.powerauth.client.PowerAuthClient; +import com.wultra.security.powerauth.client.model.error.PowerAuthClientException; import com.wultra.security.powerauth.client.v3.ActivationStatus; import com.wultra.security.powerauth.client.v3.GetActivationStatusResponse; import io.getlime.core.rest.model.base.request.ObjectRequest; @@ -269,16 +270,22 @@ private List lookupDeviceRegistrations(Long appId, * Otherwise fail the device registration because registration could not be associated with an activation. * @param device Push device registration entity. * @param activationId Activation ID. + * @throws PushServerException Throw in case communication with PowerAuth server fails. */ private void updateActivationForDevice(PushDeviceRegistrationEntity device, String activationId) throws PushServerException { - final GetActivationStatusResponse activation = client.getActivationStatus(activationId); - if (activation != null && !ActivationStatus.REMOVED.equals(activation.getActivationStatus())) { - device.setActivationId(activationId); - device.setActive(activation.getActivationStatus().equals(ActivationStatus.ACTIVE)); - device.setUserId(activation.getUserId()); - return; + try { + final GetActivationStatusResponse activation = client.getActivationStatus(activationId); + if (activation != null && !ActivationStatus.REMOVED.equals(activation.getActivationStatus())) { + device.setActivationId(activationId); + device.setActive(activation.getActivationStatus().equals(ActivationStatus.ACTIVE)); + device.setUserId(activation.getUserId()); + return; + } + throw new PushServerException("Device registration failed because associated activation is not ACTIVE"); + } catch (PowerAuthClientException ex) { + logger.warn(ex.getMessage(), ex); + throw new PushServerException("Device registration failed because activation status is unknown"); } - throw new PushServerException("Device registration failed because associated activation is not ACTIVE."); } /** @@ -292,25 +299,30 @@ private void updateActivationForDevice(PushDeviceRegistrationEntity device, Stri notes = "Update the status of given device registration based on the associated activation ID. " + "This can help assure that registration is in non-active state and cannot receive personal messages.") public Response updateDeviceStatus(@RequestBody UpdateDeviceStatusRequest request) throws PushServerException { - if (request == null) { - throw new PushServerException("Request object must not be empty"); - } - logger.info("Received updateDeviceStatus request, activation ID: {}", request.getActivationId()); - String errorMessage = UpdateDeviceStatusRequestValidator.validate(request); - if (errorMessage != null) { - throw new PushServerException(errorMessage); - } - String activationId = request.getActivationId(); - List device = pushDeviceRepository.findByActivationId(activationId); - if (device != null) { - ActivationStatus status = client.getActivationStatus(activationId).getActivationStatus(); - for (PushDeviceRegistrationEntity registration: device) { - registration.setActive(status.equals(ActivationStatus.ACTIVE)); - pushDeviceRepository.save(registration); + try { + if (request == null) { + throw new PushServerException("Request object must not be empty"); + } + logger.info("Received updateDeviceStatus request, activation ID: {}", request.getActivationId()); + String errorMessage = UpdateDeviceStatusRequestValidator.validate(request); + if (errorMessage != null) { + throw new PushServerException(errorMessage); } + String activationId = request.getActivationId(); + List device = pushDeviceRepository.findByActivationId(activationId); + if (device != null) { + ActivationStatus status = client.getActivationStatus(activationId).getActivationStatus(); + for (PushDeviceRegistrationEntity registration: device) { + registration.setActive(status.equals(ActivationStatus.ACTIVE)); + pushDeviceRepository.save(registration); + } + } + logger.info("The updateDeviceStatus request succeeded, activation ID: {}", request.getActivationId()); + return new Response(); + } catch (PowerAuthClientException ex) { + logger.warn(ex.getMessage(), ex); + throw new PushServerException("Update device status failed because activation status is unknown"); } - logger.info("The updateDeviceStatus request succeeded, activation ID: {}", request.getActivationId()); - return new Response(); } /** From 40707370b147d0700cfede856a940aab0d0cc3b1 Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Tue, 7 Jul 2020 16:20:57 +0200 Subject: [PATCH 08/51] Fix #312: Consider migration to WebClient --- docs/PowerAuth-Push-Server-1.0.0.md | 9 ++ pom.xml | 2 +- powerauth-push-client/pom.xml | 20 ++- .../getlime/push/client/PushServerClient.java | 138 +++++++++--------- .../getlime/push/client/PushServerTests.java | 23 --- 5 files changed, 96 insertions(+), 96 deletions(-) create mode 100644 docs/PowerAuth-Push-Server-1.0.0.md diff --git a/docs/PowerAuth-Push-Server-1.0.0.md b/docs/PowerAuth-Push-Server-1.0.0.md new file mode 100644 index 000000000..790b26b1b --- /dev/null +++ b/docs/PowerAuth-Push-Server-1.0.0.md @@ -0,0 +1,9 @@ +# Migration from 0.24.0 to 1.0.0 + +## Push Client Migrated to WebClient + +Push client now uses WebClient which is an HTTP client based on Spring WebFlux. We made this +change to unify HTTP clients across the whole PowerAuth stack. + +In case you use the provided Push client, the Unirest configuration is no longer required, so you can safely +remove any Unirest configuration from your project. diff --git a/pom.xml b/pom.xml index 716d2badc..83094ae3a 100644 --- a/pom.xml +++ b/pom.xml @@ -86,7 +86,7 @@ 0.24.0 0.13.10 2.9.2 - 3.7.02 + 1.0.1.RELEASE 1.30.9 6.12.2 2.3.1 diff --git a/powerauth-push-client/pom.xml b/powerauth-push-client/pom.xml index ea6d5c1f1..ae8fdb3f9 100644 --- a/powerauth-push-client/pom.xml +++ b/powerauth-push-client/pom.xml @@ -23,6 +23,21 @@ 0.24.0 + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-webflux + + + org.projectreactor + reactor-spring + ${reactor-spring.version} + + com.google.guava @@ -39,11 +54,6 @@ jackson-databind ${jackson-databind.version} - - com.konghq - unirest-java - ${unirest.version} - org.slf4j slf4j-api 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 936cafa51..4f897eef7 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 @@ -17,9 +17,7 @@ package io.getlime.push.client; import com.fasterxml.jackson.core.JsonParseException; -import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.io.BaseEncoding; import io.getlime.core.rest.model.base.entity.Error; import io.getlime.core.rest.model.base.request.ObjectRequest; @@ -34,18 +32,23 @@ import io.getlime.push.model.request.*; import io.getlime.push.model.response.*; import io.getlime.push.model.validator.*; -import kong.unirest.HttpResponse; -import kong.unirest.Unirest; -import kong.unirest.UnirestException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.MediaType; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.reactive.function.BodyInserters; +import org.springframework.web.reactive.function.client.ClientResponse; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; -import java.util.HashMap; +import java.util.Collections; import java.util.List; -import java.util.Map; +import java.util.Objects; /** * Simple class for interacting with the push server RESTful API. @@ -57,16 +60,14 @@ public class PushServerClient { private static final Logger logger = LoggerFactory.getLogger(PushServerClient.class); - private ObjectMapper mapper = new ObjectMapper(); - - private String serviceBaseUrl; + private final WebClient webClient; /** * Main constructor with the push server base URL. * @param serviceBaseUrl Push server instance base URL. */ public PushServerClient(String serviceBaseUrl) { - this.serviceBaseUrl = serviceBaseUrl; + this.webClient = WebClient.builder().baseUrl(serviceBaseUrl).build(); } // Client calls @@ -78,7 +79,7 @@ public PushServerClient(String serviceBaseUrl) { * @throws PushServerClientException In case of network, response / JSON processing, or other IO error. */ public ObjectResponse getServiceStatus() throws PushServerClientException { - TypeReference> typeReference = new TypeReference>() {}; + ParameterizedTypeReference> typeReference = new ParameterizedTypeReference>() {}; logger.info("Calling push server status service - start"); final ObjectResponse result = getObjectImpl("/push/service/status", null, typeReference); @@ -231,7 +232,7 @@ public ObjectResponse sendPushMessage(Long appId, PushMes throw new PushServerClientException(error); } - TypeReference> typeReference = new TypeReference>() {}; + ParameterizedTypeReference> typeReference = new ParameterizedTypeReference>() {}; logger.info("Calling push server to send a push message, app ID: {}, user ID: {} - start", appId, pushMessage.getUserId()); final ObjectResponse result = postObjectImpl("/push/message/send", new ObjectRequest<>(request), typeReference); @@ -259,7 +260,7 @@ public ObjectResponse sendPushMessageBatch(Long appId, Li throw new PushServerClientException(error); } - TypeReference> typeReference = new TypeReference>() {}; + ParameterizedTypeReference> typeReference = new ParameterizedTypeReference>() {}; logger.info("Calling push server to send a push message batch, app ID: {} - start", appId); final ObjectResponse result = postObjectImpl("/push/message/batch/send", new ObjectRequest<>(request), typeReference); @@ -287,7 +288,7 @@ public ObjectResponse createCampaign(Long appId, PushMes throw new PushServerClientException(error); } - TypeReference> typeReference = new TypeReference>() {}; + ParameterizedTypeReference> typeReference = new ParameterizedTypeReference>() {}; logger.info("Calling push server to create a push campaign, app ID: {} - start", appId); final ObjectResponse result = postObjectImpl("/push/campaign/create", new ObjectRequest<>(request), typeReference); @@ -307,7 +308,7 @@ public boolean deleteCampaign(Long campaignId) throws PushServerClientException try { String campaignIdSanitized = URLEncoder.encode(String.valueOf(campaignId), "UTF-8"); - TypeReference> typeReference = new TypeReference>() {}; + ParameterizedTypeReference> typeReference = new ParameterizedTypeReference>() {}; logger.info("Calling push server to delete a push campaign, campaign ID: {} - start", campaignId); ObjectResponse response = postObjectImpl("/push/campaign/" + campaignIdSanitized + "/delete", null, typeReference); @@ -327,10 +328,10 @@ public boolean deleteCampaign(Long campaignId) throws PushServerClientException * @throws PushServerClientException In case of network, response / JSON processing, or other IO error. */ public ObjectResponse getListOfCampaigns(boolean all) throws PushServerClientException { - Map params = new HashMap<>(); - params.put("all", all); + MultiValueMap params = new LinkedMultiValueMap<>(); + params.put("all", Collections.singletonList(Boolean.valueOf(all).toString())); - TypeReference> typeReference = new TypeReference>() {}; + ParameterizedTypeReference> typeReference = new ParameterizedTypeReference>() {}; logger.info("Calling push server to obtain a push campaign list - start"); final ObjectResponse result = getObjectImpl("/push/campaign/list", params, typeReference); @@ -350,7 +351,7 @@ public ObjectResponse getCampaign(Long campaignId) throws Push try { String campaignIdSanitized = URLEncoder.encode(String.valueOf(campaignId), "UTF-8"); - TypeReference> typeReference = new TypeReference>() {}; + ParameterizedTypeReference> typeReference = new ParameterizedTypeReference>() {}; logger.info("Calling push server to obtain a push campaign detail, campaign ID: {} - start", campaignId); final ObjectResponse result = getObjectImpl("/push/campaign/" + campaignIdSanitized + "/detail", null, typeReference); @@ -401,11 +402,11 @@ public boolean addUsersToCampaign(Long campaignId, List users) throws Pu public PagedResponse getListOfUsersFromCampaign(Long campaignId, int page, int size) throws PushServerClientException { try { String campaignIdSanitized = URLEncoder.encode(String.valueOf(campaignId), "UTF-8"); - Map params = new HashMap<>(); - params.put("page", page); - params.put("size", size); + MultiValueMap params = new LinkedMultiValueMap<>(); + params.put("page", Collections.singletonList(Integer.valueOf(page).toString())); + params.put("size", Collections.singletonList(Integer.valueOf(size).toString())); - TypeReference> typeReference = new TypeReference>() {}; + ParameterizedTypeReference> typeReference = new ParameterizedTypeReference>() {}; logger.info("Calling push server to get users from the campaign, campaign ID: {} - start", campaignId); final PagedResponse result = getObjectImpl("/push/campaign/" + campaignIdSanitized + "/user/list", params, typeReference); @@ -497,7 +498,7 @@ public boolean sendCampaign(Long campaignId) throws PushServerClientException { * @throws PushServerClientException Thrown when communication with Push Server fails. */ public ObjectResponse getApplicationList() throws PushServerClientException { - final TypeReference> typeReference = new TypeReference>() {}; + final ParameterizedTypeReference> typeReference = new ParameterizedTypeReference>() {}; logger.info("Calling push server to retrieve list of applications - start"); final ObjectResponse response = getObjectImpl("/admin/app/list", null, typeReference); logger.info("Calling push server to retrieve list of applications - finish"); @@ -510,7 +511,7 @@ public ObjectResponse getApplicationList() throws Pu * @throws PushServerClientException Thrown when communication with Push Server fails. */ public ObjectResponse getUnconfiguredApplicationList() throws PushServerClientException { - final TypeReference> typeReference = new TypeReference>() {}; + final ParameterizedTypeReference> typeReference = new ParameterizedTypeReference>() {}; logger.info("Calling push server to retrieve list of unconfigured applications - start"); final ObjectResponse response = getObjectImpl("/admin/app/unconfigured/list", null, typeReference); logger.info("Calling push server to retrieve list of unconfigured applications - finish"); @@ -526,7 +527,7 @@ public ObjectResponse getUnconfiguredApplicationList * @throws PushServerClientException Thrown when communication with Push Server fails. */ public ObjectResponse getApplicationDetail(Long id, boolean includeIos, boolean includeAndroid) throws PushServerClientException { - final TypeReference> typeReference = new TypeReference>() {}; + final ParameterizedTypeReference> typeReference = new ParameterizedTypeReference>() {}; GetApplicationDetailRequest request = new GetApplicationDetailRequest(id, includeIos, includeAndroid); logger.info("Calling push server to retrieve application detail, ID: {} - start", id); final ObjectResponse response = postObjectImpl("/admin/app/detail", new ObjectRequest<>(request), typeReference); @@ -541,7 +542,7 @@ public ObjectResponse getApplicationDetail(Long id * @throws PushServerClientException Thrown when communication with Push Server fails. */ public ObjectResponse createApplication(Long appId) throws PushServerClientException { - final TypeReference> typeReference = new TypeReference>() {}; + final ParameterizedTypeReference> typeReference = new ParameterizedTypeReference>() {}; final CreateApplicationRequest request = new CreateApplicationRequest(appId); logger.info("Calling push server to create application, app ID: {} - start", appId); final ObjectResponse response = postObjectImpl("/admin/app/create", new ObjectRequest<>(request), typeReference); @@ -625,17 +626,14 @@ public Response removeAndroid(Long id) throws PushServerClientException { * @throws PushServerClientException In case of network, response / JSON processing, or other IO error. * */ - private T getObjectImpl(String url, Map params, TypeReference typeReference) throws PushServerClientException { + private T getObjectImpl(String url, MultiValueMap params, ParameterizedTypeReference typeReference) throws PushServerClientException { try { - HttpResponse response = Unirest.get(serviceBaseUrl + url) - .header("Accept", "application/json") - .header("Content-Type", "application/json") - .queryString(params) - .asString(); - return checkHttpStatus(typeReference, response); - } catch (UnirestException e) { - logger.warn(e.getMessage(), e); - throw new PushServerClientException(e, new Error("PUSH_SERVER_CLIENT_ERROR", "Network communication has failed.")); + ClientResponse response = webClient.get() + .uri(uriBuilder -> uriBuilder.path(url).queryParams(params).build()) + .accept(MediaType.APPLICATION_JSON) + .exchange() + .block(); + return checkHttpStatus(typeReference, Objects.requireNonNull(response)); } catch (JsonParseException e) { logger.warn(e.getMessage(), e); throw new PushServerClientException(e, new Error("PUSH_SERVER_CLIENT_ERROR", "JSON parsing has failed.")); @@ -658,7 +656,7 @@ private T getObjectImpl(String url, Map params, TypeReferenc * @throws PushServerClientException In case of network, response / JSON processing, or other IO error. */ private T postObjectImpl(String url, Object request) throws PushServerClientException { - return postObjectImpl(url, request, new TypeReference() {}); + return postObjectImpl(url, request, new ParameterizedTypeReference() {}); } /** @@ -670,18 +668,21 @@ private T postObjectImpl(String url, Object request) throws PushServerClient * @return Object obtained after processing the response JSON. * @throws PushServerClientException In case of network, response / JSON processing, or other IO error. */ - private T postObjectImpl(String url, Object request, TypeReference typeReference) throws PushServerClientException { + private T postObjectImpl(String url, Object request, ParameterizedTypeReference typeReference) throws PushServerClientException { try { // Fetch post response from given URL and for provided request object - HttpResponse response = Unirest.post(serviceBaseUrl + url) - .header("Accept", "application/json") - .header("Content-Type", "application/json") - .body(request) - .asString(); - return checkHttpStatus(typeReference, response); - } catch (UnirestException e) { - logger.warn(e.getMessage(), e); - throw new PushServerClientException(e, new Error("PUSH_SERVER_CLIENT_ERROR", "Network communication has failed.")); + WebClient.RequestBodySpec spec = webClient.post() + .uri(uriBuilder -> uriBuilder.path(url).build()) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON); + Mono responseMono; + if (request != null) { + responseMono = spec.body(BodyInserters.fromValue(request)).exchange(); + } else { + responseMono = spec.exchange(); + } + ClientResponse response = responseMono.block(); + return checkHttpStatus(typeReference, Objects.requireNonNull(response)); } catch (JsonParseException e) { logger.warn(e.getMessage(), e); throw new PushServerClientException(e, new Error("PUSH_SERVER_CLIENT_ERROR", "JSON parsing has failed.")); @@ -703,7 +704,7 @@ private T postObjectImpl(String url, Object request, TypeReference T putObjectImpl(String url, Object request) throws PushServerClientException { - return putObjectImpl(url, request, new TypeReference() {}); + return putObjectImpl(url, request, new ParameterizedTypeReference() {}); } /** @@ -715,17 +716,20 @@ private T putObjectImpl(String url, Object request) throws PushServerClientE * @return Object obtained after processing the response JSON. * @throws PushServerClientException In case of network, response / JSON processing, or other IO error. */ - private T putObjectImpl(String url, Object request, TypeReference typeReference) throws PushServerClientException { + private T putObjectImpl(String url, Object request, ParameterizedTypeReference typeReference) throws PushServerClientException { try { - HttpResponse response = Unirest.put(serviceBaseUrl + url) - .header("Accept", "application/json") - .header("Content-Type", "application/json") - .body(request) - .asString(); - return checkHttpStatus(typeReference, response); - } catch (UnirestException e) { - logger.warn(e.getMessage(), e); - throw new PushServerClientException(e, new Error("PUSH_SERVER_CLIENT_ERROR", "Network communication has failed.")); + WebClient.RequestBodySpec spec = webClient.put() + .uri(uriBuilder -> uriBuilder.path(url).build()) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON); + Mono responseMono; + if (request != null) { + responseMono = spec.body(BodyInserters.fromValue(request)).exchange(); + } else { + responseMono = spec.exchange(); + } + ClientResponse response = responseMono.block(); + return checkHttpStatus(typeReference, Objects.requireNonNull(response)); } catch (JsonParseException e) { logger.warn(e.getMessage(), e); throw new PushServerClientException(e, new Error("PUSH_SERVER_CLIENT_ERROR", "JSON parsing has failed.")); @@ -749,17 +753,17 @@ private T putObjectImpl(String url, Object request, TypeReference * @throws IOException In case JSON processing fails. */ @SuppressWarnings("unchecked") - private T checkHttpStatus(TypeReference typeReference, HttpResponse response) throws IOException, PushServerClientException { - if (response.getStatus() == 200) { - return (T) mapper.readValue(response.getBody(), typeReference); + private T checkHttpStatus(ParameterizedTypeReference typeReference, ClientResponse response) throws IOException, PushServerClientException { + if (!response.statusCode().isError()) { + return (T) response.bodyToMono(typeReference).block(); } else { try { // Response body contains data, return Exception with status code and error response - ErrorResponse errorResponse = mapper.readValue(response.getBody(), ErrorResponse.class); - throw new PushServerClientException("Error HTTP response status code received: " + response.getStatus(), errorResponse.getResponseObject()); - } catch (IOException ex) { + ErrorResponse errorResponse = (ErrorResponse) response.bodyToMono(typeReference).block(); + throw new PushServerClientException("Error HTTP response status code received: " + response.rawStatusCode(), Objects.requireNonNull(errorResponse).getResponseObject()); + } catch (Exception ex) { logger.warn(ex.getMessage(), ex); - throw new PushServerClientException("Error HTTP response status code received: " + response.getStatus() + ". Check server log for error details."); + throw new PushServerClientException("Error HTTP response status code received: " + response.rawStatusCode() + ". Check server log for error details."); } } } diff --git a/powerauth-push-server/src/test/java/io/getlime/push/client/PushServerTests.java b/powerauth-push-server/src/test/java/io/getlime/push/client/PushServerTests.java index 154fef73d..b746cbf92 100644 --- a/powerauth-push-server/src/test/java/io/getlime/push/client/PushServerTests.java +++ b/powerauth-push-server/src/test/java/io/getlime/push/client/PushServerTests.java @@ -16,7 +16,6 @@ package io.getlime.push.client; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import io.getlime.core.rest.model.base.response.ObjectResponse; @@ -32,7 +31,6 @@ import io.getlime.push.repository.model.PushDeviceRegistrationEntity; import io.getlime.push.shared.PowerAuthTestClient; import io.getlime.push.shared.PushServerTestClientFactory; -import kong.unirest.Unirest; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -104,27 +102,6 @@ public class PushServerTests { @Before public void setUp() throws Exception { - - Unirest.config() - .setObjectMapper(new kong.unirest.ObjectMapper() { - - public String writeValue(Object value) { - try { - return mapper.writeValueAsString(value); - } catch (JsonProcessingException e) { - throw new RuntimeException(e); - } - } - - public T readValue(String value, Class valueType) { - try { - return mapper.readValue(value, valueType); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - }); - pushServerClient = testClientFactory.createPushServerClient("http://localhost:" + port); powerAuthTestClient = testClientFactory.createPowerAuthTestClient(); AppCredentialsEntity testCredentials = new AppCredentialsEntity(); From 86d0351a57c4242234bad9652d37f4116ce1000a Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Wed, 8 Jul 2020 15:20:17 +0200 Subject: [PATCH 09/51] Switch logic for status code --- .../src/main/java/io/getlime/push/client/PushServerClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 4f897eef7..c1849d027 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 @@ -754,7 +754,7 @@ private T putObjectImpl(String url, Object request, ParameterizedTypeReferen */ @SuppressWarnings("unchecked") private T checkHttpStatus(ParameterizedTypeReference typeReference, ClientResponse response) throws IOException, PushServerClientException { - if (!response.statusCode().isError()) { + if (response.statusCode().is2xxSuccessful()) { return (T) response.bodyToMono(typeReference).block(); } else { try { From c13785e997f74c6bdda97a8d61ccb5e87fc04c2c Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Thu, 9 Jul 2020 10:37:11 +0200 Subject: [PATCH 10/51] Exception handling in test client --- .../java/io/getlime/push/shared/PowerAuthTestClient.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/powerauth-push-server/src/test/java/io/getlime/push/shared/PowerAuthTestClient.java b/powerauth-push-server/src/test/java/io/getlime/push/shared/PowerAuthTestClient.java index bf2a4671b..9d84aa8f1 100644 --- a/powerauth-push-server/src/test/java/io/getlime/push/shared/PowerAuthTestClient.java +++ b/powerauth-push-server/src/test/java/io/getlime/push/shared/PowerAuthTestClient.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.io.BaseEncoding; import com.wultra.security.powerauth.client.PowerAuthClient; +import com.wultra.security.powerauth.client.model.error.PowerAuthClientException; import com.wultra.security.powerauth.client.v3.*; import com.wultra.security.powerauth.rest.client.PowerAuthRestClient; import io.getlime.security.powerauth.crypto.client.activation.PowerAuthClientActivation; @@ -47,7 +48,7 @@ public void initializeClient(String powerAuthRestUrl) { powerAuthClient = new PowerAuthRestClient(powerAuthRestUrl); } - public Long initializeApplication(String applicationName, String applicationVersion) { + public Long initializeApplication(String applicationName, String applicationVersion) throws PowerAuthClientException { // Create application if it does not exist List applications = powerAuthClient.getApplicationList(); boolean applicationExists = false; @@ -128,11 +129,11 @@ public String createActivation(String userId) throws Exception { return initResponse.getActivationId(); } - public void blockActivation(String activationId) { + public void blockActivation(String activationId) throws PowerAuthClientException { powerAuthClient.blockActivation(activationId, "TEST", "test"); } - public void unblockActivation(String activationId) { + public void unblockActivation(String activationId) throws PowerAuthClientException { powerAuthClient.unblockActivation(activationId, "test"); } From d0382be17d121ead81f9932a7c746346340c867e Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Mon, 13 Jul 2020 09:12:53 +0200 Subject: [PATCH 11/51] Fix #341: Remove push client instance from server --- .../PowerAuthWebServiceConfiguration.java | 13 ------ .../src/main/resources/application.properties | 3 -- .../PushSeverTestConfiguration.java | 42 +++++++++++++++++++ ...ation-test-multiple-activations.properties | 3 ++ .../resources/application-test.properties | 3 ++ 5 files changed, 48 insertions(+), 16 deletions(-) create mode 100644 powerauth-push-server/src/test/java/io/getlime/push/configuration/PushSeverTestConfiguration.java diff --git a/powerauth-push-server/src/main/java/io/getlime/push/configuration/PowerAuthWebServiceConfiguration.java b/powerauth-push-server/src/main/java/io/getlime/push/configuration/PowerAuthWebServiceConfiguration.java index d95b5f47f..dfc652f02 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/configuration/PowerAuthWebServiceConfiguration.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/configuration/PowerAuthWebServiceConfiguration.java @@ -18,7 +18,6 @@ import com.wultra.security.powerauth.client.PowerAuthClient; import com.wultra.security.powerauth.rest.client.PowerAuthRestClient; import com.wultra.security.powerauth.rest.client.PowerAuthRestClientConfiguration; -import io.getlime.push.client.PushServerClient; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; @@ -36,9 +35,6 @@ public class PowerAuthWebServiceConfiguration { @Value("${powerauth.service.url}") private String powerAuthRestUrl; - @Value("${powerauth.push.service.url}") - private String pushServiceUrl; - @Value("${powerauth.service.ssl.acceptInvalidSslCertificate}") private boolean acceptInvalidSslCertificate; @@ -61,13 +57,4 @@ public PowerAuthClient powerAuthClient() { return new PowerAuthRestClient(powerAuthRestUrl, config); } - /** - * Initialize PowerAuth 2.0 Push server client. - * @return Push server client. - */ - @Bean - public PushServerClient pushServerClient() { - return new PushServerClient(pushServiceUrl); - } - } diff --git a/powerauth-push-server/src/main/resources/application.properties b/powerauth-push-server/src/main/resources/application.properties index b15adbe99..18b5d3b34 100644 --- a/powerauth-push-server/src/main/resources/application.properties +++ b/powerauth-push-server/src/main/resources/application.properties @@ -38,9 +38,6 @@ powerauth.push.service.applicationName=powerauth-push powerauth.push.service.applicationDisplayName=PowerAuth 2.0 Push Server powerauth.push.service.applicationEnvironment= -# Base URL of Push Server Service -powerauth.push.service.url=http://localhost:8080/powerauth-push-server - # PowerAuth 2.0 Push Campaign Setup powerauth.push.service.campaign.batchSize=100000 diff --git a/powerauth-push-server/src/test/java/io/getlime/push/configuration/PushSeverTestConfiguration.java b/powerauth-push-server/src/test/java/io/getlime/push/configuration/PushSeverTestConfiguration.java new file mode 100644 index 000000000..e9939a249 --- /dev/null +++ b/powerauth-push-server/src/test/java/io/getlime/push/configuration/PushSeverTestConfiguration.java @@ -0,0 +1,42 @@ +/* + * Copyright 2020 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.configuration; + +import io.getlime.push.client.PushServerClient; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Push server test configuration. + * + * @author Roman Strobl, roman.strobl@wultra.com + */ +@Configuration +public class PushSeverTestConfiguration { + + @Value("${powerauth.push.service.url}") + private String pushServiceUrl; + + /** + * Initialize PowerAuth 2.0 Push server client. + * @return Push server client. + */ + @Bean + public PushServerClient pushServerClient() { + return new PushServerClient(pushServiceUrl); + } +} diff --git a/powerauth-push-server/src/test/resources/application-test-multiple-activations.properties b/powerauth-push-server/src/test/resources/application-test-multiple-activations.properties index cb88b76e2..026c82357 100644 --- a/powerauth-push-server/src/test/resources/application-test-multiple-activations.properties +++ b/powerauth-push-server/src/test/resources/application-test-multiple-activations.properties @@ -27,6 +27,9 @@ spring.datasource.driver-class-name=org.h2.Driver # Hibernate Configuration spring.jpa.hibernate.ddl-auto=create-drop +# Base URL of Push Server Service +powerauth.push.service.url=http://localhost:8080/powerauth-push-server + # PowerAuth 2.0 Service Configuration powerauth.service.url=${POWERAUTH_ENDPOINT}/powerauth-java-server/rest powerauth.service.security.clientToken= diff --git a/powerauth-push-server/src/test/resources/application-test.properties b/powerauth-push-server/src/test/resources/application-test.properties index 2774c06fd..5dc3bb219 100644 --- a/powerauth-push-server/src/test/resources/application-test.properties +++ b/powerauth-push-server/src/test/resources/application-test.properties @@ -27,6 +27,9 @@ spring.datasource.driver-class-name=org.h2.Driver # Hibernate Configuration spring.jpa.hibernate.ddl-auto=create-drop +# Base URL of Push Server Service +powerauth.push.service.url=http://localhost:8080/powerauth-push-server + # PowerAuth 2.0 Service Configuration powerauth.service.url=${POWERAUTH_ENDPOINT}/powerauth-java-server/rest powerauth.service.security.clientToken= From f47050b7a9f90b91c25309b73a5d4ec4c3af3835 Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Mon, 27 Jul 2020 14:55:03 +0200 Subject: [PATCH 12/51] Fix #115: Add 'ttl' mapping for FCM --- .../getlime/push/service/PushSendingWorker.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) 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 9b385ce88..ae14e32a1 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 @@ -75,6 +75,9 @@ public class PushSendingWorker { // APNS topic disallowed String private static final String APNS_TOPIC_DISALLOWED = "TopicDisallowed"; + // Maximum Android TTL value in seconds, see: https://firebase.google.com/docs/cloud-messaging/concept-options#ttl + private static final int ANDROID_TTL_SECONDS_MAX = 2_419_200; + private final PushServiceConfiguration pushServiceConfiguration; private final FcmModelConverter fcmConverter; @@ -215,6 +218,18 @@ private Message buildAndroidMessage(final PushMessageBody pushMessageBody, final AndroidConfig.Builder androidConfigBuilder = AndroidConfig.builder() .setCollapseKey(pushMessageBody.getCollapseKey()); + // Calculate TTL and set it if the TTL is within reasonable limits + Date validUntil = pushMessageBody.getValidUntil(); + if (validUntil != null) { + long validUntilMs = validUntil.getTime(); + long currentTimeMs = System.currentTimeMillis(); + long ttlInSeconds = (validUntilMs - currentTimeMs) / 1000; + + if (ttlInSeconds > 0 && ttlInSeconds < ANDROID_TTL_SECONDS_MAX) { + androidConfigBuilder.setTtl(ttlInSeconds); + } + } + AndroidNotification notification = AndroidNotification.builder() .setTitle(pushMessageBody.getTitle()) .setBody(pushMessageBody.getBody()) From a59317f50b9427bf4001e029452e79987e36e97c Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Tue, 4 Aug 2020 14:23:12 +0200 Subject: [PATCH 13/51] Fix #317: Optimize callbacks --- powerauth-push-model/pom.xml | 5 ++++ .../request/UpdateDeviceStatusRequest.java | 23 +++++++++++++++++-- .../controller/rest/PushDeviceController.java | 8 +++++-- 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/powerauth-push-model/pom.xml b/powerauth-push-model/pom.xml index 0d7678148..a5051629f 100644 --- a/powerauth-push-model/pom.xml +++ b/powerauth-push-model/pom.xml @@ -21,6 +21,11 @@ rest-model-base ${lime.rest.version} + + io.getlime.security + powerauth-client-model + ${powerauth.version} + \ No newline at end of file diff --git a/powerauth-push-model/src/main/java/io/getlime/push/model/request/UpdateDeviceStatusRequest.java b/powerauth-push-model/src/main/java/io/getlime/push/model/request/UpdateDeviceStatusRequest.java index 45ba6ea3e..87f7cf02c 100644 --- a/powerauth-push-model/src/main/java/io/getlime/push/model/request/UpdateDeviceStatusRequest.java +++ b/powerauth-push-model/src/main/java/io/getlime/push/model/request/UpdateDeviceStatusRequest.java @@ -16,6 +16,8 @@ package io.getlime.push.model.request; +import com.wultra.security.powerauth.client.v3.ActivationStatus; + /** * Class representing request object responsible for updating activation status. * @@ -24,9 +26,10 @@ public class UpdateDeviceStatusRequest { private String activationId; + private ActivationStatus activationStatus; /** - * Get PowerAuth 2.0 Activation ID. + * Get PowerAuth activation ID. * @return Activation ID. */ public String getActivationId() { @@ -34,11 +37,27 @@ public String getActivationId() { } /** - * Set PowerAuth 2.0 Activation ID. + * Set PowerAuth activation ID. * @param activationId Activation ID. */ public void setActivationId(String activationId) { this.activationId = activationId; } + /** + * Get PowerAuth activation status. + * @return Activation status. + */ + public ActivationStatus getActivationStatus() { + return activationStatus; + } + + /** + * Set PowerAuth activation status. + * @param activationStatus Activation status. + */ + public void setActivationStatus(ActivationStatus activationStatus) { + this.activationStatus = activationStatus; + } + } diff --git a/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/PushDeviceController.java b/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/PushDeviceController.java index c07f6d2b6..72697409f 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/PushDeviceController.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/PushDeviceController.java @@ -309,11 +309,15 @@ public Response updateDeviceStatus(@RequestBody UpdateDeviceStatusRequest reques throw new PushServerException(errorMessage); } String activationId = request.getActivationId(); + ActivationStatus activationStatus = request.getActivationStatus(); List device = pushDeviceRepository.findByActivationId(activationId); if (device != null) { - ActivationStatus status = client.getActivationStatus(activationId).getActivationStatus(); + if (activationStatus == null) { + // Activation status was not received via callback data, retrieve it from PowerAuth server + activationStatus = client.getActivationStatus(activationId).getActivationStatus(); + } for (PushDeviceRegistrationEntity registration: device) { - registration.setActive(status.equals(ActivationStatus.ACTIVE)); + registration.setActive(activationStatus.equals(ActivationStatus.ACTIVE)); pushDeviceRepository.save(registration); } } From 7c84cf830a81deab197ba965006571bc33b86578 Mon Sep 17 00:00:00 2001 From: Petr Dvorak Date: Mon, 10 Aug 2020 18:46:19 +0200 Subject: [PATCH 14/51] Fix #319: Update Pushy dependency --- pom.xml | 2 +- .../push/model/entity/PushMessageBody.java | 8 +- powerauth-push-server/pom.xml | 4 +- .../push/service/AppRelatedPushClient.java | 2 +- .../service/PushMessageSenderService.java | 2 +- .../push/service/PushSendingWorker.java | 101 +++++++++-------- .../service/apns/ApnsRejectionReason.java | 102 ++++++++++++++++++ .../getlime/push/client/PushServerTests.java | 7 +- 8 files changed, 165 insertions(+), 63 deletions(-) create mode 100644 powerauth-push-server/src/main/java/io/getlime/push/service/apns/ApnsRejectionReason.java diff --git a/pom.xml b/pom.xml index 75faab7f8..77770387e 100644 --- a/pom.xml +++ b/pom.xml @@ -84,7 +84,7 @@ 1.2.5 1.1.0 1.0.0-SNAPSHOT - 0.13.10 + 0.14.1 2.9.2 1.0.1.RELEASE 1.30.9 diff --git a/powerauth-push-model/src/main/java/io/getlime/push/model/entity/PushMessageBody.java b/powerauth-push-model/src/main/java/io/getlime/push/model/entity/PushMessageBody.java index c8c163ebe..c1094393d 100644 --- a/powerauth-push-model/src/main/java/io/getlime/push/model/entity/PushMessageBody.java +++ b/powerauth-push-model/src/main/java/io/getlime/push/model/entity/PushMessageBody.java @@ -16,7 +16,7 @@ package io.getlime.push.model.entity; -import java.util.Date; +import java.time.Instant; import java.util.Map; /** @@ -34,7 +34,7 @@ public class PushMessageBody { private String sound; private String category; private String collapseKey; - private Date validUntil; + private Instant validUntil; private Map extras; /** @@ -142,7 +142,7 @@ public void setCollapseKey(String collapseKey) { * Get notification delivery validity (timestamp message should live to in case it's not delivered immediately). * @return Validity timestamp. */ - public Date getValidUntil() { + public Instant getValidUntil() { return validUntil; } @@ -150,7 +150,7 @@ public Date getValidUntil() { * Set notification delivery validity (timestamp message should live to in case it's not delivered immediately). * @param validUntil Validity timestamp. */ - public void setValidUntil(Date validUntil) { + public void setValidUntil(Instant validUntil) { this.validUntil = validUntil; } diff --git a/powerauth-push-server/pom.xml b/powerauth-push-server/pom.xml index 8bfa84928..dd993260d 100644 --- a/powerauth-push-server/pom.xml +++ b/powerauth-push-server/pom.xml @@ -90,9 +90,9 @@ ${httpasyncclient.version} - com.turo + com.eatthepath pushy - ${pushy.version} + 0.14.1 com.google.api-client 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 7ad93bf02..d170bf4e3 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 @@ -15,7 +15,7 @@ */ package io.getlime.push.service; -import com.turo.pushy.apns.ApnsClient; +import com.eatthepath.pushy.apns.ApnsClient; import io.getlime.push.repository.model.AppCredentialsEntity; import io.getlime.push.service.fcm.FcmClient; 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 41e60781c..62512cd77 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,7 +16,7 @@ package io.getlime.push.service; -import com.turo.pushy.apns.ApnsClient; +import com.eatthepath.pushy.apns.ApnsClient; import io.getlime.push.configuration.PushServiceConfiguration; import io.getlime.push.errorhandling.exceptions.PushServerException; import io.getlime.push.model.entity.PushMessage; 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 9b385ce88..82eaabd1c 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 @@ -16,22 +16,23 @@ package io.getlime.push.service; +import com.eatthepath.pushy.apns.util.SimpleApnsPayloadBuilder; import com.google.firebase.messaging.AndroidConfig; import com.google.firebase.messaging.AndroidNotification; import com.google.firebase.messaging.Message; -import com.turo.pushy.apns.*; -import com.turo.pushy.apns.auth.ApnsSigningKey; -import com.turo.pushy.apns.proxy.HttpProxyHandlerFactory; -import com.turo.pushy.apns.util.ApnsPayloadBuilder; -import com.turo.pushy.apns.util.SimpleApnsPushNotification; -import com.turo.pushy.apns.util.TokenUtil; -import com.turo.pushy.apns.util.concurrent.PushNotificationFuture; -import com.turo.pushy.apns.util.concurrent.PushNotificationResponseListener; +import com.eatthepath.pushy.apns.*; +import com.eatthepath.pushy.apns.auth.ApnsSigningKey; +import com.eatthepath.pushy.apns.proxy.HttpProxyHandlerFactory; +import com.eatthepath.pushy.apns.util.ApnsPayloadBuilder; +import com.eatthepath.pushy.apns.util.SimpleApnsPushNotification; +import com.eatthepath.pushy.apns.util.TokenUtil; +import com.eatthepath.pushy.apns.util.concurrent.PushNotificationFuture; import io.getlime.push.configuration.PushServiceConfiguration; import io.getlime.push.errorhandling.exceptions.FcmMissingTokenException; 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.service.apns.ApnsRejectionReason; import io.getlime.push.service.fcm.FcmClient; import io.getlime.push.service.fcm.FcmModelConverter; import io.getlime.push.service.fcm.model.FcmErrorResponse; @@ -48,10 +49,10 @@ import java.net.InetSocketAddress; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; -import java.util.Date; +import java.time.Duration; +import java.time.Instant; import java.util.LinkedHashMap; import java.util.Map; -import java.util.concurrent.TimeUnit; import java.util.function.Consumer; @@ -66,15 +67,6 @@ public class PushSendingWorker { // Expected response String from FCM private static final String FCM_RESPONSE_VALID_REGEXP = "projects/.+/messages/.+"; - // APNS bad device token String - private static final String APNS_BAD_DEVICE_TOKEN = "BadDeviceToken"; - - // APNS device token not for topic String - private static final String APNS_DEVICE_TOKEN_NOT_FOR_TOPIC = "DeviceTokenNotForTopic"; - - // APNS topic disallowed String - private static final String APNS_TOPIC_DISALLOWED = "TopicDisallowed"; - private final PushServiceConfiguration pushServiceConfiguration; private final FcmModelConverter fcmConverter; @@ -251,7 +243,7 @@ private Message buildAndroidMessage(final PushMessageBody pushMessageBody, final ApnsClient prepareApnsClient(String teamId, String keyId, byte[] apnsPrivateKey) throws PushServerException { final ApnsClientBuilder apnsClientBuilder = new ApnsClientBuilder(); apnsClientBuilder.setProxyHandlerFactory(apnsClientProxy()); - apnsClientBuilder.setConnectionTimeout(pushServiceConfiguration.getApnsConnectTimeout(), TimeUnit.MILLISECONDS); + apnsClientBuilder.setConnectionTimeout(Duration.ofMillis(pushServiceConfiguration.getApnsConnectTimeout())); if (pushServiceConfiguration.isApnsUseDevelopment()) { logger.info("Using APNs development host"); apnsClientBuilder.setApnsServer(ApnsClientBuilder.DEVELOPMENT_APNS_HOST); @@ -311,43 +303,50 @@ void sendMessageToIos(final ApnsClient apnsClient, final PushMessageBody pushMes final String token = TokenUtil.sanitizeTokenString(pushToken); final boolean isSilent = attributes == null ? false : attributes.getSilent(); // In case there are no attributes, the message is not silent final String payload = buildApnsPayload(pushMessageBody, isSilent); - final Date validUntil = pushMessageBody.getValidUntil(); + 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 SimpleApnsPushNotification pushNotification = new SimpleApnsPushNotification(token, iosTopic, payload, validUntil, DeliveryPriority.IMMEDIATE, pushType, pushMessageBody.getCollapseKey()); final PushNotificationFuture> sendNotificationFuture = apnsClient.sendNotification(pushNotification); - sendNotificationFuture.addListener((PushNotificationResponseListener) future -> { - if (future.isSuccess()) { - final PushNotificationResponse pushNotificationResponse = future.getNow(); - if (pushNotificationResponse != null) { - if (!pushNotificationResponse.isAccepted()) { - logger.error("Notification rejected by the APNs gateway: {}", pushNotificationResponse.getRejectionReason()); - if (pushNotificationResponse.getRejectionReason().equals(APNS_BAD_DEVICE_TOKEN)) { - logger.error("\t... due to bad device token value."); - callback.didFinishSendingMessage(PushSendingCallback.Result.FAILED_DELETE); - } else if (pushNotificationResponse.getRejectionReason().equals(APNS_DEVICE_TOKEN_NOT_FOR_TOPIC)) { - logger.error("\t... due to device token not for topic error."); - callback.didFinishSendingMessage(PushSendingCallback.Result.FAILED_DELETE); - } else if (pushNotificationResponse.getRejectionReason().equals(APNS_TOPIC_DISALLOWED)) { - logger.error("\t... due to topic disallowed error."); - callback.didFinishSendingMessage(PushSendingCallback.Result.FAILED_DELETE); - } else if (pushNotificationResponse.getTokenInvalidationTimestamp() != null) { - logger.error("\t... and the token is invalid as of " + pushNotificationResponse.getTokenInvalidationTimestamp()); - callback.didFinishSendingMessage(PushSendingCallback.Result.FAILED_DELETE); - } else { - callback.didFinishSendingMessage(PushSendingCallback.Result.FAILED); - } + sendNotificationFuture.whenCompleteAsync((response, cause) -> { + if (response != null) { + if (response.isAccepted()) { + logger.info("Notification sent, APNs ID: {}", response.getApnsId()); + callback.didFinishSendingMessage(PushSendingCallback.Result.OK); + } else { + final String rejectionReason = response.getRejectionReason(); + logger.info("Notification rejected by the APNs gateway: {}", rejectionReason); + + // Determine if the push token should be deleted. + if (ApnsRejectionReason.BAD_DEVICE_TOKEN.isEqualToText(rejectionReason)) { + logger.debug("Deleting push token: {}", pushToken); + callback.didFinishSendingMessage(PushSendingCallback.Result.FAILED_DELETE); + } else if (ApnsRejectionReason.DEVICE_TOKEN_NOT_FOR_TOPIC.isEqualToText(rejectionReason)) { + logger.debug("Deleting push token: {}", pushToken); + callback.didFinishSendingMessage(PushSendingCallback.Result.FAILED_DELETE); + } else if (ApnsRejectionReason.TOPIC_DISALLOWED.isEqualToText(rejectionReason)) { + logger.debug("Deleting push token: {}", pushToken); + callback.didFinishSendingMessage(PushSendingCallback.Result.FAILED_DELETE); + } else if (ApnsRejectionReason.EXPIRED_PROVIDER_TOKEN.isEqualToText(rejectionReason)) { + logger.debug("Deleting push token: {}", pushToken); + callback.didFinishSendingMessage(PushSendingCallback.Result.FAILED_DELETE); + } else if (ApnsRejectionReason.INVALID_PROVIDER_TOKEN.isEqualToText(rejectionReason)) { + logger.debug("Deleting push token: {}", pushToken); + callback.didFinishSendingMessage(PushSendingCallback.Result.FAILED_DELETE); + } else if (response.getTokenInvalidationTimestamp().isPresent()) { + logger.info("Push token is invalid as of: {}", response.getTokenInvalidationTimestamp().get()); + logger.debug("Deleting push token: {}", pushToken); + callback.didFinishSendingMessage(PushSendingCallback.Result.FAILED_DELETE); } else { - logger.info("Notification sent, APNs ID: {}", pushNotificationResponse.getApnsId()); - callback.didFinishSendingMessage(PushSendingCallback.Result.OK); + logger.debug("Sending the push message failed with push token: {}", pushToken); + callback.didFinishSendingMessage(PushSendingCallback.Result.FAILED); } - } else { - logger.error("Notification rejected by the APNs gateway: unknown error, will retry"); - callback.didFinishSendingMessage(PushSendingCallback.Result.PENDING); } } else { - logger.error("Push Message Sending Failed", future.cause()); - callback.didFinishSendingMessage(PushSendingCallback.Result.FAILED); + // In this case, the delivery failed because the future failed, not because APNs rejected the + // notification payload. This means that we should be able to attempt resending the message. + logger.error("Push Message Sending Failed", cause); + callback.didFinishSendingMessage(PushSendingCallback.Result.PENDING); } }); } @@ -360,7 +359,7 @@ void sendMessageToIos(final ApnsClient apnsClient, final PushMessageBody pushMes * @return String with APNs JSON payload. */ private String buildApnsPayload(PushMessageBody push, boolean isSilent) { - final ApnsPayloadBuilder payloadBuilder = new ApnsPayloadBuilder(); + final ApnsPayloadBuilder payloadBuilder = new SimpleApnsPayloadBuilder(); payloadBuilder.setAlertTitle(push.getTitle()); payloadBuilder.setAlertBody(push.getBody()); payloadBuilder.setBadgeNumber(push.getBadge()); @@ -374,6 +373,6 @@ private String buildApnsPayload(PushMessageBody push, boolean isSilent) { payloadBuilder.addCustomProperty(entry.getKey(), entry.getValue()); } } - return payloadBuilder.buildWithDefaultMaximumLength(); + return payloadBuilder.build(); } } \ No newline at end of file diff --git a/powerauth-push-server/src/main/java/io/getlime/push/service/apns/ApnsRejectionReason.java b/powerauth-push-server/src/main/java/io/getlime/push/service/apns/ApnsRejectionReason.java new file mode 100644 index 000000000..717da3b26 --- /dev/null +++ b/powerauth-push-server/src/main/java/io/getlime/push/service/apns/ApnsRejectionReason.java @@ -0,0 +1,102 @@ +/* + * Copyright 2020 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.apns; + +import io.netty.handler.codec.http.HttpResponseStatus; + +/** + * APNs rejection reason codes, documented on the Apple site: + * https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/handling_notification_responses_from_apns + * + * @author Petr Dvorak, petr@wultra + */ +public enum ApnsRejectionReason { + + BAD_DEVICE_TOKEN("BadDeviceToken"), + + DEVICE_TOKEN_NOT_FOR_TOPIC("DeviceTokenNotForTopic"), + + TOPIC_DISALLOWED("TopicDisallowed"), + + BAD_COLLAPSE_ID("BadCollapseId"), + + BAD_EXPIRATION_DATE("BadExpirationDate"), + + BAD_MESSAGE_ID("BadMessageId"), + + BAD_PRIORITY("BadPriority"), + + BAD_TOPIC("BadTopic"), + + DUPLICATE_HEADERS("DuplicateHeaders"), + + IDLE_TIMEOUT("IdleTimeout"), + + INVALID_PUSH_TYPE("InvalidPushType"), + + MISSING_DEVICE_TOKEN("MissingDeviceToken"), + + MISSING_TOPIC("MissingTopic"), + + PAYLOAD_EMPTY("PayloadEmpty"), + + BAD_CERTIFICATE("BadCertificate"), + + BAD_CERTIFICATE_ENVIRONMENT("BadCertificateEnvironment"), + + EXPIRED_PROVIDER_TOKEN("ExpiredProviderToken"), + + FORBIDDEN("Forbidden"), + + INVALID_PROVIDER_TOKEN("InvalidProviderToken"), + + MISSING_PROVIDER_TOKEN("MissingProviderToken"), + + BAD_PATH("BadPath"), + + METHOD_NOT_ALLOWED("MethodNotAllowed"), + + UNREGISTERED("Unregistered"), + + PAYLOAD_TOO_LARGE("PayloadTooLarge"), + + TOO_MANY_PROVIDER_TOKEN_UPDATES("TooManyProviderTokenUpdates"), + + TOO_MANY_REQUESTS("TooManyRequests"), + + INTERNAL_SERVER_ERROR("InternalServerError"), + + SERVICE_UNAVAILABLE("ServiceUnavailable"), + + SHUTDOWN("Shutdown"); + + + private final String reasonText; + + ApnsRejectionReason(final String reasonText) { + this.reasonText = reasonText; + } + + public String getReasonText() { + return this.reasonText; + } + + public boolean isEqualToText(String reasonText) { + return this.reasonText.equals(reasonText); + } + +} diff --git a/powerauth-push-server/src/test/java/io/getlime/push/client/PushServerTests.java b/powerauth-push-server/src/test/java/io/getlime/push/client/PushServerTests.java index a36ed024c..63beca99e 100644 --- a/powerauth-push-server/src/test/java/io/getlime/push/client/PushServerTests.java +++ b/powerauth-push-server/src/test/java/io/getlime/push/client/PushServerTests.java @@ -46,6 +46,7 @@ import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit4.SpringRunner; +import java.time.Instant; import java.util.*; import static org.assertj.core.api.Assertions.assertThat; @@ -195,7 +196,7 @@ public void sendPushMessageTest() throws Exception { pushMessageBody.setSound("riff.wav"); pushMessageBody.setCategory("balance-update"); pushMessageBody.setCollapseKey("balance-update"); - pushMessageBody.setValidUntil(new Date()); + pushMessageBody.setValidUntil(Instant.now()); pushMessageBody.setExtras((Map) new HashMap().put("_comment", "Any custom data.")); attributes.setSilent(false); attributes.setPersonal(true); @@ -229,7 +230,7 @@ public void sendPushMessageBatchTest() throws Exception { pushMessageBody.setSound("riff.wav"); pushMessageBody.setCategory("balance-update"); pushMessageBody.setCollapseKey("balance-update"); - pushMessageBody.setValidUntil(new Date()); + pushMessageBody.setValidUntil(Instant.now()); pushMessageBody.setExtras((Map) new HashMap().put("_comment", "Any custom data.")); attributes.setSilent(false); attributes.setPersonal(true); @@ -260,7 +261,7 @@ public void createCampaignTest() throws Exception { pushMessageBody.setSound("riff.wav"); pushMessageBody.setCategory("balance-update"); pushMessageBody.setCollapseKey("balance-update"); - pushMessageBody.setValidUntil(new Date()); + pushMessageBody.setValidUntil(Instant.now()); pushMessageBody.setExtras((Map) new HashMap().put("_comment", "Any custom data.")); campaignRequest.setAppId(powerAuthTestClient.getApplicationId()); campaignRequest.setMessage(pushMessageBody); From 0db44413c01a0c7427b5f51253ba44ef9690c69d Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Mon, 24 Aug 2020 17:29:21 +0200 Subject: [PATCH 15/51] Fix #321: Error after migrating to Instant --- .../configuration/WebApplicationConfig.java | 4 +++- .../rest/PushCampaignController.java | 16 ++++++++------ .../rest/SendCampaignController.java | 8 ++++--- .../push/repository/dao/PushMessageDAO.java | 10 +++++---- .../repository/model/PushCampaignEntity.java | 2 +- ...ialization.java => JsonSerialization.java} | 22 ++++++++++++++----- .../service/batch/UserDeviceItemWriter.java | 12 +++++----- 7 files changed, 47 insertions(+), 27 deletions(-) rename powerauth-push-server/src/main/java/io/getlime/push/repository/serialization/{JSONSerialization.java => JsonSerialization.java} (75%) diff --git a/powerauth-push-server/src/main/java/io/getlime/push/configuration/WebApplicationConfig.java b/powerauth-push-server/src/main/java/io/getlime/push/configuration/WebApplicationConfig.java index 3b1c5da9d..55edabecd 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/configuration/WebApplicationConfig.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/configuration/WebApplicationConfig.java @@ -18,6 +18,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.util.StdDateFormat; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean; @@ -40,7 +41,8 @@ public class WebApplicationConfig implements WebMvcConfigurer { * * @return A new object mapper. */ - private ObjectMapper objectMapper() { + @Bean + public ObjectMapper objectMapper() { Jackson2ObjectMapperFactoryBean bean = new Jackson2ObjectMapperFactoryBean(); bean.setIndentOutput(true); bean.setDateFormat(new StdDateFormat()); diff --git a/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/PushCampaignController.java b/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/PushCampaignController.java index db24158e2..42d9628af 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/PushCampaignController.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/PushCampaignController.java @@ -30,7 +30,7 @@ import io.getlime.push.repository.PushCampaignUserRepository; import io.getlime.push.repository.model.PushCampaignEntity; import io.getlime.push.repository.model.PushCampaignUserEntity; -import io.getlime.push.repository.serialization.JSONSerialization; +import io.getlime.push.repository.serialization.JsonSerialization; import io.swagger.annotations.ApiOperation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -54,14 +54,16 @@ public class PushCampaignController { private static final Logger logger = LoggerFactory.getLogger(PushCampaignController.class); - private PushCampaignRepository pushCampaignRepository; - private PushCampaignUserRepository pushCampaignUserRepository; + private final PushCampaignRepository pushCampaignRepository; + private final PushCampaignUserRepository pushCampaignUserRepository; + private final JsonSerialization jsonSerialization; @Autowired public PushCampaignController(PushCampaignRepository pushCampaignRepository, - PushCampaignUserRepository pushCampaignUserRepository) { + PushCampaignUserRepository pushCampaignUserRepository, JsonSerialization jsonSerialization) { this.pushCampaignRepository = pushCampaignRepository; this.pushCampaignUserRepository = pushCampaignUserRepository; + this.jsonSerialization = jsonSerialization; } /** @@ -87,7 +89,7 @@ public ObjectResponse createCampaign(@RequestBody Object } PushCampaignEntity campaign = new PushCampaignEntity(); PushMessageBody message = requestObject.getMessage(); - String messageString = JSONSerialization.serializePushMessageBody(message); + String messageString = jsonSerialization.serializePushMessageBody(message); campaign.setAppId(requestObject.getAppId()); campaign.setSent(false); campaign.setTimestampCreated(new Date()); @@ -140,7 +142,7 @@ public ObjectResponse getCampaign(@PathVariable(value = "id") campaignResponse.setId(campaign.getId()); campaignResponse.setSent(campaign.isSent()); campaignResponse.setAppId(campaign.getAppId()); - PushMessageBody message = JSONSerialization.deserializePushMessageBody(campaign.getMessage()); + PushMessageBody message = jsonSerialization.deserializePushMessageBody(campaign.getMessage()); campaignResponse.setMessage(message); logger.debug("The getCampaign request succeeded, campaign ID: {}", campaignId); return new ObjectResponse<>(campaignResponse); @@ -174,7 +176,7 @@ public ObjectResponse getListOfCampaigns(@RequestParam( campaignResponse.setId(campaign.getId()); campaignResponse.setAppId(campaign.getAppId()); campaignResponse.setSent(campaign.isSent()); - PushMessageBody pushMessageBody = JSONSerialization.deserializePushMessageBody(campaign.getMessage()); + PushMessageBody pushMessageBody = jsonSerialization.deserializePushMessageBody(campaign.getMessage()); campaignResponse.setMessage(pushMessageBody); listOfCampaignsResponse.add(campaignResponse); } diff --git a/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/SendCampaignController.java b/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/SendCampaignController.java index ffbe66bbc..e6cc9a1ac 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/SendCampaignController.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/SendCampaignController.java @@ -24,7 +24,7 @@ import io.getlime.push.model.validator.TestCampaignRequestValidator; import io.getlime.push.repository.PushCampaignRepository; import io.getlime.push.repository.model.PushCampaignEntity; -import io.getlime.push.repository.serialization.JSONSerialization; +import io.getlime.push.repository.serialization.JsonSerialization; import io.getlime.push.service.PushMessageSenderService; import io.swagger.annotations.ApiOperation; import org.slf4j.Logger; @@ -60,16 +60,18 @@ public class SendCampaignController { private final Job job; private final PushCampaignRepository pushCampaignRepository; private final PushMessageSenderService pushMessageSenderService; + private final JsonSerialization jsonSerialization; @Autowired public SendCampaignController(JobLauncher jobLauncher, Job job, PushCampaignRepository pushCampaignRepository, - PushMessageSenderService pushMessageSenderService) { + PushMessageSenderService pushMessageSenderService, JsonSerialization jsonSerialization) { this.jobLauncher = jobLauncher; this.job = job; this.pushCampaignRepository = pushCampaignRepository; this.pushMessageSenderService = pushMessageSenderService; + this.jsonSerialization = jsonSerialization; } /** @@ -134,7 +136,7 @@ public Response sendTestCampaign(@PathVariable(value = "id") Long id, @RequestBo } PushMessage pushMessage = new PushMessage(); pushMessage.setUserId(request.getRequestObject().getUserId()); - pushMessage.setBody(JSONSerialization.deserializePushMessageBody(campaign.getMessage())); + pushMessage.setBody(jsonSerialization.deserializePushMessageBody(campaign.getMessage())); List message = new ArrayList<>(); message.add(pushMessage); pushMessageSenderService.sendPushMessage(campaign.getAppId(), message); diff --git a/powerauth-push-server/src/main/java/io/getlime/push/repository/dao/PushMessageDAO.java b/powerauth-push-server/src/main/java/io/getlime/push/repository/dao/PushMessageDAO.java index 078b6ad8c..0e1b07122 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/repository/dao/PushMessageDAO.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/repository/dao/PushMessageDAO.java @@ -21,7 +21,7 @@ import io.getlime.push.model.entity.PushMessageBody; import io.getlime.push.repository.PushMessageRepository; import io.getlime.push.repository.model.PushMessageEntity; -import io.getlime.push.repository.serialization.JSONSerialization; +import io.getlime.push.repository.serialization.JsonSerialization; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; @@ -37,11 +37,13 @@ @Transactional public class PushMessageDAO { - private PushMessageRepository pushMessageRepository; + private final PushMessageRepository pushMessageRepository; + private final JsonSerialization jsonSerialization; @Autowired - public PushMessageDAO(PushMessageRepository pushMessageRepository) { + public PushMessageDAO(PushMessageRepository pushMessageRepository, JsonSerialization jsonSerialization) { this.pushMessageRepository = pushMessageRepository; + this.jsonSerialization = jsonSerialization; } /** @@ -69,7 +71,7 @@ public PushMessageEntity storePushMessageObject(PushMessageBody pushMessageBody, } entity.setStatus(PushMessageEntity.Status.PENDING); entity.setTimestampCreated(new Date()); - String messageBody = JSONSerialization.serializePushMessageBody(pushMessageBody); + String messageBody = jsonSerialization.serializePushMessageBody(pushMessageBody); entity.setMessageBody(messageBody); return pushMessageRepository.save(entity); } diff --git a/powerauth-push-server/src/main/java/io/getlime/push/repository/model/PushCampaignEntity.java b/powerauth-push-server/src/main/java/io/getlime/push/repository/model/PushCampaignEntity.java index 85d2faa30..f5b09fe26 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/repository/model/PushCampaignEntity.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/repository/model/PushCampaignEntity.java @@ -38,7 +38,7 @@ public class PushCampaignEntity implements Serializable { @Column(name = "app_id", nullable = false, updatable = false) private Long appId; - + @Lob @Column(name = "message", nullable = false, updatable = false) private String message; diff --git a/powerauth-push-server/src/main/java/io/getlime/push/repository/serialization/JSONSerialization.java b/powerauth-push-server/src/main/java/io/getlime/push/repository/serialization/JsonSerialization.java similarity index 75% rename from powerauth-push-server/src/main/java/io/getlime/push/repository/serialization/JSONSerialization.java rename to powerauth-push-server/src/main/java/io/getlime/push/repository/serialization/JsonSerialization.java index 23a7299a0..6ea30371f 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/repository/serialization/JSONSerialization.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/repository/serialization/JsonSerialization.java @@ -22,6 +22,8 @@ import io.getlime.push.model.entity.PushMessageBody; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; import java.io.IOException; @@ -30,9 +32,17 @@ * * @author Petr Dvorak, petr@wultra.com */ -public class JSONSerialization { +@Service +public class JsonSerialization { - private static final Logger logger = LoggerFactory.getLogger(JSONSerialization.class); + private static final Logger logger = LoggerFactory.getLogger(JsonSerialization.class); + + private final ObjectMapper objectMapper; + + @Autowired + public JsonSerialization(ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + } /** * Parsing message from JSON to PushMessageBody object. @@ -41,10 +51,10 @@ public class JSONSerialization { * @return PushMessageBody * @throws PushServerException In case object mapping fails. */ - public static PushMessageBody deserializePushMessageBody(String message) throws PushServerException { + public PushMessageBody deserializePushMessageBody(String message) throws PushServerException { PushMessageBody pushMessageBody; try { - pushMessageBody = new ObjectMapper().readValue(message, PushMessageBody.class); + pushMessageBody = objectMapper.readValue(message, PushMessageBody.class); } catch (IOException e) { logger.error(e.getMessage(), e); throw new PushServerException("Failed parsing from JSON", e); @@ -59,10 +69,10 @@ public static PushMessageBody deserializePushMessageBody(String message) throws * @return JSON containing the message contents. * @throws PushServerException In case object mapping fails. */ - public static String serializePushMessageBody(PushMessageBody message) throws PushServerException { + public String serializePushMessageBody(PushMessageBody message) throws PushServerException { String messageString; try { - messageString = new ObjectMapper().writeValueAsString(message); + messageString = objectMapper.writeValueAsString(message); } catch (JsonProcessingException e) { logger.error(e.getMessage(), e); throw new PushServerException("Failed parsing into JSON", e); 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 bf8d4e9a8..bee2a811f 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 @@ -21,7 +21,7 @@ import io.getlime.push.repository.PushCampaignRepository; import io.getlime.push.repository.model.PushCampaignEntity; import io.getlime.push.repository.model.aggregate.UserDevice; -import io.getlime.push.repository.serialization.JSONSerialization; +import io.getlime.push.repository.serialization.JsonSerialization; import io.getlime.push.service.PushMessageSenderService; import io.getlime.push.service.batch.storage.CampaignMessageStorageMap; import org.springframework.batch.core.configuration.annotation.StepScope; @@ -41,17 +41,19 @@ @StepScope public class UserDeviceItemWriter implements ItemWriter { - private PushMessageSenderService pushMessageSenderService; - private PushCampaignRepository pushCampaignRepository; + private final PushMessageSenderService pushMessageSenderService; + private final PushCampaignRepository pushCampaignRepository; + private final JsonSerialization jsonSerialization; // Non-autowired fields private CampaignMessageStorageMap campaignStorageMap = new CampaignMessageStorageMap(); @Autowired public UserDeviceItemWriter(PushMessageSenderService pushMessageSenderService, - PushCampaignRepository pushCampaignRepository) { + PushCampaignRepository pushCampaignRepository, JsonSerialization jsonSerialization) { this.pushMessageSenderService = pushMessageSenderService; this.pushCampaignRepository = pushCampaignRepository; + this.jsonSerialization = jsonSerialization; } @Override @@ -73,7 +75,7 @@ public void write(List list) throws Exception { throw new PushServerException("Campaign with entered ID does not exist"); } final PushCampaignEntity campaignEntity = campaignEntityOptional.get(); - messageBody = JSONSerialization.deserializePushMessageBody(campaignEntity.getMessage()); + messageBody = jsonSerialization.deserializePushMessageBody(campaignEntity.getMessage()); campaignStorageMap.put(campaignId, messageBody); } From 5453bed5451fd47b7271f12189133255e80a426e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Dvo=C5=99=C3=A1k?= Date: Wed, 2 Sep 2020 12:08:37 +0200 Subject: [PATCH 16/51] Fix formatting --- docs/Push-Server-Administration.md | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/docs/Push-Server-Administration.md b/docs/Push-Server-Administration.md index c934dea90..9cd1d1337 100644 --- a/docs/Push-Server-Administration.md +++ b/docs/Push-Server-Administration.md @@ -8,29 +8,30 @@ The RESTful API is documented in [a dedicated chapter](./Push-Server-API.md). Insomnia is an easy to use RESTful API client. You can get Insomnia from [https://insomnia.rest](https://insomnia.rest) -To import the Push Server workspace into Insomnia, create a workspace using menu in the top left corner. Use the `Create Workspace` option and -then click `Import/Export`. You can import the workspace with all requests from [provided workspace export file](./data/Push_Server_Insomnia.zip). +To import the Push Server workspace into Insomnia, create a workspace using menu in the top left corner. Use the `Create Workspace` option and then click `Import/Export`. You can import the workspace with all requests from [provided workspace export file](./data/Push_Server_Insomnia.zip). -All requests which are described below are already prepared in the provided Insomnia worskpace, so you can easily -update the JSON requests and execute them. +All requests which are described below are already prepared in the provided Insomnia worskpace, so you can easily update the JSON requests and execute them. ## Administration using cURL Curl is a command line HTTP client. You can get cURL from [https://curl.haxx.se](https://curl.haxx.se) -### Retrieve Application List: +### Retrieve Application List + ``` curl --request GET \ --url http://localhost:8080/powerauth-push-server/admin/app/list ``` -### Retrieve Unconfigured Application List: +### Retrieve Unconfigured Application List + ``` curl --request GET \ --url http://localhost:8080/powerauth-push-server/admin/app/unconfigured/list ``` -### Create an Application: +### Create an Application + ``` curl --request POST \ --url http://localhost:8080/powerauth-push-server/admin/app/create \ @@ -42,10 +43,10 @@ curl --request POST \ }' ``` -Update the `appId` value with requested PowerAuth application ID. -The value `id` from response object will be used for identification of the Push Server application. +Update the `appId` value with requested PowerAuth application ID. The value `id` from response object will be used for identification of the Push Server application. + +### Get Application Detail -### Get Application Detail: ``` curl --request POST \ --url http://localhost:8080/powerauth-push-server/admin/app/detail \ @@ -62,6 +63,7 @@ curl --request POST \ Update the `id` value with requested Push Server application ID. ### Update APNs Configuration + ``` curl --request POST \ --url http://localhost:8080/powerauth-push-server/admin/app/ios/update \ @@ -82,11 +84,13 @@ Set the `id` value for Push Server application ID to want to update. Enter the base64-encoded value of APNs private key into `privateKeyBase64`. You can encode the file using `base64` command on Mac. You can also use `Certutil.exe` on Windows or OpenSSL on all platforms. + ``` base64 -i -o ``` ### Remove APNs Configuration + ``` curl --request DELETE \ --url http://localhost:8080/powerauth-push-server/admin/app/ios/remove \ From 44623cd8701af94986c333c5d5f547c63d8687b6 Mon Sep 17 00:00:00 2001 From: snyk-bot Date: Wed, 9 Sep 2020 08:09:38 +0000 Subject: [PATCH 17/51] fix: pom.xml to reduce vulnerabilities The following vulnerabilities are fixed with an upgrade: - https://snyk.io/vuln/SNYK-JAVA-IONETTY-469234 - https://snyk.io/vuln/SNYK-JAVA-IONETTY-543490 - https://snyk.io/vuln/SNYK-JAVA-IONETTY-543669 - https://snyk.io/vuln/SNYK-JAVA-ORGAPACHETOMCATEMBED-570072 - https://snyk.io/vuln/SNYK-JAVA-ORGAPACHETOMCATEMBED-584427 - https://snyk.io/vuln/SNYK-JAVA-ORGDOM4J-565810 - https://snyk.io/vuln/SNYK-JAVA-ORGHIBERNATE-584563 - https://snyk.io/vuln/SNYK-JAVA-ORGHIBERNATEVALIDATOR-568163 - https://snyk.io/vuln/SNYK-JAVA-ORGSPRINGFRAMEWORKBATCH-572008 - https://snyk.io/vuln/SNYK-JAVA-ORGYAML-537645 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 77770387e..c3405c273 100644 --- a/pom.xml +++ b/pom.xml @@ -88,7 +88,7 @@ 2.9.2 1.0.1.RELEASE 1.30.9 - 6.12.2 + 6.13.0 1.65 From f875d77288fded5d2b121a713263a48e2dbd9bf1 Mon Sep 17 00:00:00 2001 From: snyk-bot Date: Wed, 9 Sep 2020 21:40:43 +0000 Subject: [PATCH 18/51] fix: upgrade com.fasterxml.jackson.core:jackson-databind from 2.10.3 to 2.11.2 Snyk has created this PR to upgrade com.fasterxml.jackson.core:jackson-databind from 2.10.3 to 2.11.2. See this package in Maven Repository: https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind/ See this project in Snyk: https://app.snyk.io/org/wultra/project/0fb22103-7b20-43b1-a124-b93a0c539da2?utm_source=github&utm_medium=upgrade-pr --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c3405c273..9a033f7e2 100644 --- a/pom.xml +++ b/pom.xml @@ -78,7 +78,7 @@ 4.5.12 4.1.4 2.10.3 - 2.10.3 + 2.11.2 1.2.2 2.0 1.2.5 From 795920df0d5e3f83a61e03a3000f68bc62dae8fe Mon Sep 17 00:00:00 2001 From: snyk-bot Date: Wed, 9 Sep 2020 21:40:48 +0000 Subject: [PATCH 19/51] fix: upgrade com.fasterxml.jackson.core:jackson-core from 2.10.3 to 2.11.2 Snyk has created this PR to upgrade com.fasterxml.jackson.core:jackson-core from 2.10.3 to 2.11.2. See this package in Maven Repository: https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core/ See this project in Snyk: https://app.snyk.io/org/wultra/project/0fb22103-7b20-43b1-a124-b93a0c539da2?utm_source=github&utm_medium=upgrade-pr --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c3405c273..fb75e289a 100644 --- a/pom.xml +++ b/pom.xml @@ -77,7 +77,7 @@ 1.4.199 4.5.12 4.1.4 - 2.10.3 + 2.11.2 2.10.3 1.2.2 2.0 From 6d545b9c9d2791d3d88c2e034c00942d16b38852 Mon Sep 17 00:00:00 2001 From: snyk-bot Date: Thu, 10 Sep 2020 08:10:09 +0000 Subject: [PATCH 20/51] fix: upgrade io.springfox:springfox-swagger-ui from 2.9.2 to 2.10.5 Snyk has created this PR to upgrade io.springfox:springfox-swagger-ui from 2.9.2 to 2.10.5. See this package in Maven Repository: https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui/ See this project in Snyk: https://app.snyk.io/org/wultra/project/5a519d91-c3d1-4181-9a73-b1ff7474c422?utm_source=github&utm_medium=upgrade-pr --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1d75281d6..1b0edee8a 100644 --- a/pom.xml +++ b/pom.xml @@ -85,7 +85,7 @@ 1.1.0 1.0.0-SNAPSHOT 0.14.1 - 2.9.2 + 2.10.5 1.0.1.RELEASE 1.30.9 6.13.0 From 3d9f58123c16e3f04c70f1ad38fdb265a1f25110 Mon Sep 17 00:00:00 2001 From: snyk-bot Date: Thu, 10 Sep 2020 08:10:14 +0000 Subject: [PATCH 21/51] fix: upgrade com.google.api-client:google-api-client from 1.30.9 to 1.30.10 Snyk has created this PR to upgrade com.google.api-client:google-api-client from 1.30.9 to 1.30.10. See this package in Maven Repository: https://mvnrepository.com/artifact/com.google.api-client/google-api-client/ See this project in Snyk: https://app.snyk.io/org/wultra/project/5a519d91-c3d1-4181-9a73-b1ff7474c422?utm_source=github&utm_medium=upgrade-pr --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1d75281d6..621cb4228 100644 --- a/pom.xml +++ b/pom.xml @@ -87,7 +87,7 @@ 0.14.1 2.9.2 1.0.1.RELEASE - 1.30.9 + 1.30.10 6.13.0 1.65 From 4b6c19bf1705fc26b34d4e5c65bd0fbe360540f8 Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Thu, 10 Sep 2020 11:56:04 +0200 Subject: [PATCH 22/51] Fix #328: EnableSwagger2 annotation does not exist --- powerauth-push-server/pom.xml | 5 +++++ .../io/getlime/push/configuration/SwaggerConfiguration.java | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/powerauth-push-server/pom.xml b/powerauth-push-server/pom.xml index dd993260d..0f46de09a 100644 --- a/powerauth-push-server/pom.xml +++ b/powerauth-push-server/pom.xml @@ -194,6 +194,11 @@ springfox-swagger2 ${swagger.version} + + io.springfox + springfox-spring-webflux + ${swagger.version} + io.springfox springfox-swagger-ui diff --git a/powerauth-push-server/src/main/java/io/getlime/push/configuration/SwaggerConfiguration.java b/powerauth-push-server/src/main/java/io/getlime/push/configuration/SwaggerConfiguration.java index 3cb01ea52..b4c2781c6 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/configuration/SwaggerConfiguration.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/configuration/SwaggerConfiguration.java @@ -23,7 +23,7 @@ import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; -import springfox.documentation.swagger2.annotations.EnableSwagger2; +import springfox.documentation.swagger2.annotations.EnableSwagger2WebFlux; /** * Swagger configuration class for api documentation @@ -31,7 +31,7 @@ * @author Martin Tupy, martin.tupy.work@gmail.com */ @Configuration -@EnableSwagger2 +@EnableSwagger2WebFlux public class SwaggerConfiguration { /** From 806e2c8e8b4bbcc1051f24652841a756cad716ee Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Thu, 10 Sep 2020 18:51:02 +0200 Subject: [PATCH 23/51] Migrate to Open API Documentation --- pom.xml | 2 +- powerauth-push-server/pom.xml | 18 ++---- .../configuration/OpenApiConfiguration.java | 61 +++++++++++++++++++ .../configuration/SwaggerConfiguration.java | 49 --------------- .../rest/PushCampaignController.java | 30 ++++----- .../controller/rest/PushDeviceController.java | 18 +++--- .../rest/PushMessageController.java | 10 +-- .../rest/SendCampaignController.java | 10 +-- .../controller/rest/ServiceController.java | 6 +- 9 files changed, 103 insertions(+), 101 deletions(-) create mode 100644 powerauth-push-server/src/main/java/io/getlime/push/configuration/OpenApiConfiguration.java delete mode 100644 powerauth-push-server/src/main/java/io/getlime/push/configuration/SwaggerConfiguration.java diff --git a/pom.xml b/pom.xml index 9939df424..39f39f724 100644 --- a/pom.xml +++ b/pom.xml @@ -85,7 +85,7 @@ 1.1.0 1.0.0-SNAPSHOT 0.14.1 - 2.10.5 + 1.3.4 1.0.1.RELEASE 1.30.10 6.13.0 diff --git a/powerauth-push-server/pom.xml b/powerauth-push-server/pom.xml index 0f46de09a..77ab0311e 100644 --- a/powerauth-push-server/pom.xml +++ b/powerauth-push-server/pom.xml @@ -188,21 +188,11 @@ test - + - io.springfox - springfox-swagger2 - ${swagger.version} - - - io.springfox - springfox-spring-webflux - ${swagger.version} - - - io.springfox - springfox-swagger-ui - ${swagger.version} + org.springdoc + springdoc-openapi-ui + ${springdoc-openapi.version} diff --git a/powerauth-push-server/src/main/java/io/getlime/push/configuration/OpenApiConfiguration.java b/powerauth-push-server/src/main/java/io/getlime/push/configuration/OpenApiConfiguration.java new file mode 100644 index 000000000..e1d12766f --- /dev/null +++ b/powerauth-push-server/src/main/java/io/getlime/push/configuration/OpenApiConfiguration.java @@ -0,0 +1,61 @@ +/* + * 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. + */ + + +package io.getlime.push.configuration; + +import io.swagger.v3.oas.annotations.OpenAPIDefinition; +import io.swagger.v3.oas.annotations.info.Contact; +import io.swagger.v3.oas.annotations.info.Info; +import io.swagger.v3.oas.annotations.info.License; +import org.springdoc.core.GroupedOpenApi; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Swagger configuration class for api documentation + * + * @author Martin Tupy, martin.tupy.work@gmail.com + */ +@Configuration +@OpenAPIDefinition( + info = @Info( + title = "PowerAuth Push Server RESTful API Documentation", + version = "1.0", + license = @License( + name = "APL 2.0", + url = "https://www.apache.org/licenses/LICENSE-2.0" + ), + description = "Documentation for the PowerAuth Push Server RESTful API published by the PowerAuth Push Server.", + contact = @Contact( + name = "Wultra s.r.o.", + url = "https://www.wultra.com" + ) + ) +) +public class OpenApiConfiguration { + + @Bean + public GroupedOpenApi pushApiGroup() { + String[] packages = {"io.getlime.push.controller.rest"}; + + return GroupedOpenApi.builder() + .setGroup("push") + .packagesToScan(packages) + .build(); + } + +} diff --git a/powerauth-push-server/src/main/java/io/getlime/push/configuration/SwaggerConfiguration.java b/powerauth-push-server/src/main/java/io/getlime/push/configuration/SwaggerConfiguration.java deleted file mode 100644 index b4c2781c6..000000000 --- a/powerauth-push-server/src/main/java/io/getlime/push/configuration/SwaggerConfiguration.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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. - */ - - -package io.getlime.push.configuration; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import springfox.documentation.builders.PathSelectors; -import springfox.documentation.builders.RequestHandlerSelectors; -import springfox.documentation.spi.DocumentationType; -import springfox.documentation.spring.web.plugins.Docket; -import springfox.documentation.swagger2.annotations.EnableSwagger2WebFlux; - -/** - * Swagger configuration class for api documentation - * - * @author Martin Tupy, martin.tupy.work@gmail.com - */ -@Configuration -@EnableSwagger2WebFlux -public class SwaggerConfiguration { - - /** - * Bean definition for Swagger documentation. - * @return Docklet with definition of API documentation. - */ - @Bean - public Docket api() { - return new Docket(DocumentationType.SWAGGER_2) - .select() - .apis(RequestHandlerSelectors.basePackage("io.getlime.push.controller.rest")) - .paths(PathSelectors.any()) - .build(); - } -} diff --git a/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/PushCampaignController.java b/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/PushCampaignController.java index 42d9628af..d25dc5331 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/PushCampaignController.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/PushCampaignController.java @@ -31,7 +31,7 @@ import io.getlime.push.repository.model.PushCampaignEntity; import io.getlime.push.repository.model.PushCampaignUserEntity; import io.getlime.push.repository.serialization.JsonSerialization; -import io.swagger.annotations.ApiOperation; +import io.swagger.v3.oas.annotations.Operation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -74,8 +74,8 @@ public PushCampaignController(PushCampaignRepository pushCampaignRepository, * @throws PushServerException In case request is invalid. */ @PostMapping(value = "create") - @ApiOperation(value = "Create a campaign", - notes = "Creating a campaign requires in request body an application id to be related with " + + @Operation(summary = "Create a campaign", + description = "Creating a campaign requires in request body an application id to be related with " + "and a certain message that users will receive") public ObjectResponse createCampaign(@RequestBody ObjectRequest request) throws PushServerException { CreateCampaignRequest requestObject = request.getRequestObject(); @@ -108,8 +108,8 @@ public ObjectResponse createCampaign(@RequestBody Object * @return Remove campaign status response. */ @RequestMapping(value = "{id}/delete", method = { RequestMethod.POST, RequestMethod.DELETE }) - @ApiOperation(value = "Delete a campaign", - notes = "Specified with id. Also users associated with this campaign are going to be deleted. If deletion was applied then deleted status is true. False if such campaign does not exist") + @Operation(summary = "Delete a campaign", + description = "Specified with id. Also users associated with this campaign are going to be deleted. If deletion was applied then deleted status is true. False if such campaign does not exist") public ObjectResponse deleteCampaign(@PathVariable(value = "id") Long campaignId) { logger.info("Received deleteCampaign request, campaign ID: {}", campaignId); DeleteCampaignResponse deleteCampaignResponse = new DeleteCampaignResponse(); @@ -133,8 +133,8 @@ public ObjectResponse deleteCampaign(@PathVariable(value * @throws PushServerException In case campaign with provided ID does not exist. */ @GetMapping(value = "{id}/detail") - @ApiOperation(value = "Return details about campaign", - notes = "Campaign specified by id. Details contain campaign id, application id, status if campaign was sent and message.") + @Operation(summary = "Return details about campaign", + description = "Campaign specified by id. Details contain campaign id, application id, status if campaign was sent and message.") public ObjectResponse getCampaign(@PathVariable(value = "id") Long campaignId) throws PushServerException { logger.debug("Received getCampaign request, campaign ID: {}", campaignId); final PushCampaignEntity campaign = findPushCampaignById(campaignId); @@ -157,8 +157,8 @@ public ObjectResponse getCampaign(@PathVariable(value = "id") * @throws PushServerException In case campaign message cannot be deserialized. */ @GetMapping(value = "list") - @ApiOperation(value = "Return a detailed list of campaigns", - notes = "Restricted with all param. This parameter decides if return campaigns that are 'only sent'(statement false)" + + @Operation(summary = "Return a detailed list of campaigns", + description = "Restricted with all param. This parameter decides if return campaigns that are 'only sent'(statement false)" + " or return all registered campaigns (statement true). Details are same as in getCampaign method") public ObjectResponse getListOfCampaigns(@RequestParam(value = "all", required = false) boolean all) throws PushServerException { logger.debug("Received getListOfCampaigns request"); @@ -194,8 +194,8 @@ public ObjectResponse getListOfCampaigns(@RequestParam( * @throws PushServerException In case campaign with given ID does not exist. */ @RequestMapping(value = "{id}/user/add", method = { RequestMethod.POST, RequestMethod.PUT }) - @ApiOperation(value = "Associate users to campaign", - notes = "Users are identified in request body as an array of strings in request body.") + @Operation(summary = "Associate users to campaign", + description = "Users are identified in request body as an array of strings in request body.") public Response addUsersToCampaign(@PathVariable(value = "id") Long campaignId, @RequestBody ObjectRequest request) throws PushServerException { checkRequestNullity(request); logger.info("Received addUsersToCampaign request, campaign ID: {}, users: {}", campaignId, request.getRequestObject()); @@ -225,8 +225,8 @@ public Response addUsersToCampaign(@PathVariable(value = "id") Long campaignId, * @return Campaign id, list of users */ @GetMapping(value = "{id}/user/list") - @ApiOperation(value = "Return list of users", - notes = "Return all users' ids from campaign that is specified in URI {id} variable. " + + @Operation(summary = "Return list of users", + description = "Return all users' ids from campaign that is specified in URI {id} variable. " + "Users are shown in paginated format based on parameters assigned in URI. " + "Page param defines which page to show (start from 0) and size param which defines how many user ids to show per page") public PagedResponse getListOfUsersFromCampaign(@PathVariable(value = "id") Long id, Pageable pageable) { @@ -254,8 +254,8 @@ public PagedResponse getListOfUsersFromCampaign * @return Response status */ @RequestMapping(value = "{id}/user/delete", method = { RequestMethod.POST, RequestMethod.DELETE }) - @ApiOperation(value = "Delete users from campaign", - notes = "Delete users from certain campaign specified with {id} variable in URI." + + @Operation(summary = "Delete users from campaign", + description = "Delete users from certain campaign specified with {id} variable in URI." + "Users are described as list of their ids in Request body") public Response deleteUsersFromCampaign(@PathVariable(value = "id") Long id, @RequestBody ObjectRequest request) { logger.info("Received deleteUsersFromCampaign request, campaign ID: {}, users: {}", id, request.getRequestObject()); diff --git a/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/PushDeviceController.java b/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/PushDeviceController.java index 72697409f..15b32ca74 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/PushDeviceController.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/PushDeviceController.java @@ -32,7 +32,7 @@ import io.getlime.push.model.validator.UpdateDeviceStatusRequestValidator; import io.getlime.push.repository.PushDeviceRepository; import io.getlime.push.repository.model.PushDeviceRegistrationEntity; -import io.swagger.annotations.ApiOperation; +import io.swagger.v3.oas.annotations.Operation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -73,8 +73,8 @@ public PushDeviceController(PushDeviceRepository pushDeviceRepository, PowerAuth * @throws PushServerException In case request object is invalid. */ @PostMapping(value = "create") - @ApiOperation(value = "Create a device", - notes = "Create a new device push token (platform specific). The call must include an activation ID, so that the token is associated with given user." + + @Operation(summary = "Create a device", + description = "Create a new device push token (platform specific). The call must include an activation ID, so that the token is associated with given user." + "Request body should contain application ID, device token, device's platform and an activation ID. " + "If such device already exist, date on last registration is updated and also platform might be changed\n" + "\n---" + @@ -129,8 +129,8 @@ public Response createDevice(@RequestBody ObjectRequest req * @throws PushServerException In case request object is invalid. */ @PostMapping(value = "create/multi") - @ApiOperation(value = "Create a device for multiple associated activations", - notes = "Create a new device push token (platform specific). The call must include one or more activation IDs." + + @Operation(summary = "Create a device for multiple associated activations", + description = "Create a new device push token (platform specific). The call must include one or more activation IDs." + "Request body should contain application ID, device token, device's platform and list of activation IDs. " + "If such device already exist, date on last registration is updated and also platform might be changed\n" + "\n---" + @@ -295,8 +295,8 @@ private void updateActivationForDevice(PushDeviceRegistrationEntity device, Stri * @throws PushServerException In case request object is invalid. */ @RequestMapping(value = "status/update", method = {RequestMethod.POST, RequestMethod.PUT}) - @ApiOperation(value = "Update device status", - notes = "Update the status of given device registration based on the associated activation ID. " + + @Operation(summary = "Update device status", + description = "Update the status of given device registration based on the associated activation ID. " + "This can help assure that registration is in non-active state and cannot receive personal messages.") public Response updateDeviceStatus(@RequestBody UpdateDeviceStatusRequest request) throws PushServerException { try { @@ -336,8 +336,8 @@ public Response updateDeviceStatus(@RequestBody UpdateDeviceStatusRequest reques * @throws PushServerException In case request object is invalid. */ @RequestMapping(value = "delete", method = {RequestMethod.POST, RequestMethod.DELETE}) - @ApiOperation(value = "Delete a device", - notes = "Remove device identified by application ID and device token. " + + @Operation(summary = "Delete a device", + description = "Remove device identified by application ID and device token. " + "If device identifiers don't match, nothing happens") public Response deleteDevice(@RequestBody ObjectRequest request) throws PushServerException { DeleteDeviceRequest requestObject = request.getRequestObject(); diff --git a/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/PushMessageController.java b/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/PushMessageController.java index 1bad5fa29..f9ea8e0c5 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/PushMessageController.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/PushMessageController.java @@ -25,7 +25,7 @@ import io.getlime.push.model.validator.SendPushMessageBatchRequestValidator; import io.getlime.push.model.validator.SendPushMessageRequestValidator; import io.getlime.push.service.PushMessageSenderService; -import io.swagger.annotations.ApiOperation; +import io.swagger.v3.oas.annotations.Operation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -63,8 +63,8 @@ public PushMessageController(PushMessageSenderService pushMessageSenderService) * @throws PushServerException In case request object is invalid. */ @PostMapping(value = "send") - @ApiOperation(value = "Send a single Push message", - notes = "Send push message to user, defined in request body - message object by user ID and activation ID," + + @Operation(summary = "Send a single Push message", + description = "Send push message to user, defined in request body - message object by user ID and activation ID," + " using given application ID \n \n" + "Message contains attributes and body\n" + "Attributes describe whether message has to be " + @@ -102,8 +102,8 @@ public ObjectResponse sendPushMessage(@RequestBody Object * @throws PushServerException In case request object is invalid. */ @PostMapping(value = "batch/send") - @ApiOperation(value = "Send batch of push messages", - notes = "Send to each user in request body, assigned to application ID, message. Message and user definition is same as in \"send a single push message\" method. " + + @Operation(summary = "Send batch of push messages", + description = "Send to each user in request body, assigned to application ID, message. Message and user definition is same as in \"send a single push message\" method. " + "Users and their messages are inside request body - batch param.") public ObjectResponse sendPushMessageBatch(@RequestBody ObjectRequest request) throws PushServerException { SendPushMessageBatchRequest requestObject = request.getRequestObject(); diff --git a/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/SendCampaignController.java b/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/SendCampaignController.java index e6cc9a1ac..333032eae 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/SendCampaignController.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/SendCampaignController.java @@ -26,7 +26,7 @@ import io.getlime.push.repository.model.PushCampaignEntity; import io.getlime.push.repository.serialization.JsonSerialization; import io.getlime.push.service.PushMessageSenderService; -import io.swagger.annotations.ApiOperation; +import io.swagger.v3.oas.annotations.Operation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.batch.core.Job; @@ -82,8 +82,8 @@ public SendCampaignController(JobLauncher jobLauncher, * @throws PushServerException In case campaign with given ID is not found. */ @PostMapping(value = "live/{id}") - @ApiOperation(value = "Send a campaign", - notes = "Send message from a specific campaign to devices belonged to users associated with that campaign. Whereas each device gets a campaign only once.\n" + + @Operation(summary = "Send a campaign", + description = "Send message from a specific campaign to devices belonged to users associated with that campaign. Whereas each device gets a campaign only once.\n" + "\n" + "If sending was successful then sent parameter is set on true and timestampSent is set on current time.") public Response sendCampaign(@PathVariable(value = "id") Long id) throws PushServerException { @@ -120,8 +120,8 @@ public Response sendCampaign(@PathVariable(value = "id") Long id) throws PushSer * @throws PushServerException In case request object is invalid. */ @PostMapping(value = "test/{id}") - @ApiOperation(value = "Send a test campaign", - notes = "Send message from a specific campaign on test user identified in request body, userId param, to check rightness of that campaign.") + @Operation(summary = "Send a test campaign", + description = "Send message from a specific campaign on test user identified in request body, userId param, to check rightness of that campaign.") public Response sendTestCampaign(@PathVariable(value = "id") Long id, @RequestBody ObjectRequest request) throws PushServerException { logger.info("Received sendTestCampaign request, campaign ID: {}", id); final Optional campaignEntityOptional = pushCampaignRepository.findById(id); diff --git a/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/ServiceController.java b/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/ServiceController.java index 60ad8ee12..7e9d60a1e 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/ServiceController.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/ServiceController.java @@ -19,7 +19,7 @@ import io.getlime.core.rest.model.base.response.ObjectResponse; import io.getlime.push.configuration.PushServiceConfiguration; import io.getlime.push.model.response.ServiceStatusResponse; -import io.swagger.annotations.ApiOperation; +import io.swagger.v3.oas.annotations.Operation; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.info.BuildProperties; import org.springframework.web.bind.annotation.GetMapping; @@ -55,8 +55,8 @@ public void setBuildProperties(BuildProperties buildProperties) { * @return System status info. */ @GetMapping(value = "status") - @ApiOperation(value = "Service status", - notes = "Send a system status response, with basic information about the running application.") + @Operation(summary = "Service status", + description = "Send a system status response, with basic information about the running application.") public ObjectResponse getServiceStatus() { ServiceStatusResponse response = new ServiceStatusResponse(); response.setApplicationName(pushServiceConfiguration.getPushServerName()); From aed9f20a5423a59df30c706ffd695aed984d67b8 Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Fri, 11 Sep 2020 02:03:21 +0200 Subject: [PATCH 24/51] Update Open API Documentation version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 39f39f724..d0a1bcc56 100644 --- a/pom.xml +++ b/pom.xml @@ -85,7 +85,7 @@ 1.1.0 1.0.0-SNAPSHOT 0.14.1 - 1.3.4 + 1.4.6 1.0.1.RELEASE 1.30.10 6.13.0 From 14965931e0c2fa28c533b8e4b557ccbc99990e7f Mon Sep 17 00:00:00 2001 From: snyk-bot Date: Fri, 11 Sep 2020 08:10:10 +0000 Subject: [PATCH 25/51] fix: upgrade com.google.firebase:firebase-admin from 6.13.0 to 6.16.0 Snyk has created this PR to upgrade com.google.firebase:firebase-admin from 6.13.0 to 6.16.0. See this package in Maven Repository: https://mvnrepository.com/artifact/com.google.firebase/firebase-admin/ See this project in Snyk: https://app.snyk.io/org/wultra/project/5a519d91-c3d1-4181-9a73-b1ff7474c422?utm_source=github&utm_medium=upgrade-pr --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 9939df424..1cfbb9d77 100644 --- a/pom.xml +++ b/pom.xml @@ -88,7 +88,7 @@ 2.10.5 1.0.1.RELEASE 1.30.10 - 6.13.0 + 6.16.0 1.65 From b29155e49cf597402fbce6174258433873ed3905 Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Fri, 11 Sep 2020 12:49:40 +0200 Subject: [PATCH 26/51] Fix deprecation warning --- .../io/getlime/push/configuration/OpenApiConfiguration.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/powerauth-push-server/src/main/java/io/getlime/push/configuration/OpenApiConfiguration.java b/powerauth-push-server/src/main/java/io/getlime/push/configuration/OpenApiConfiguration.java index e1d12766f..9bddceb15 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/configuration/OpenApiConfiguration.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/configuration/OpenApiConfiguration.java @@ -53,7 +53,7 @@ public GroupedOpenApi pushApiGroup() { String[] packages = {"io.getlime.push.controller.rest"}; return GroupedOpenApi.builder() - .setGroup("push") + .group("push") .packagesToScan(packages) .build(); } From 5d4a9bd82e8f8bc7fe425aa8097d84e3e0633173 Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Thu, 17 Sep 2020 13:50:23 +0200 Subject: [PATCH 27/51] Fix #331: Disable the petstore example for Swagger docs --- .../src/main/resources/application.properties | 3 +++ 1 file changed, 3 insertions(+) diff --git a/powerauth-push-server/src/main/resources/application.properties b/powerauth-push-server/src/main/resources/application.properties index 18b5d3b34..903ec946d 100644 --- a/powerauth-push-server/src/main/resources/application.properties +++ b/powerauth-push-server/src/main/resources/application.properties @@ -76,3 +76,6 @@ spring.jpa.hibernate.use-new-id-generator-mappings=false # Disable open session in view to avoid startup warning of Spring boot spring.jpa.open-in-view=false + +# Disable swagger-ui default petstore url +springdoc.swagger-ui.disable-swagger-default-url=true \ No newline at end of file From 023f63d5478e18387eb54b5ca95bf3f14f2b5532 Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Mon, 21 Sep 2020 11:38:20 +0200 Subject: [PATCH 28/51] Fix copy-paste issue in administration documentation --- docs/Push-Server-Administration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Push-Server-Administration.md b/docs/Push-Server-Administration.md index 9cd1d1337..f1cdc4942 100644 --- a/docs/Push-Server-Administration.md +++ b/docs/Push-Server-Administration.md @@ -120,7 +120,7 @@ curl --request POST \ Set the `id` value for Push Server application ID to want to update. -Enter the base64-encoded value of APNs private key into `privateKeyBase64`. +Enter the base64-encoded value of FCM private key into `privateKeyBase64`. You can encode the file using `base64` command on Mac. You can also use `Certutil.exe` on Windows or OpenSSL on all platforms. ``` From b422739c20917a13d9f8441c8be73bee858fa529 Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Thu, 24 Sep 2020 20:23:37 +0200 Subject: [PATCH 29/51] Disable noisy DEBUG logging from netty which looks like errors but it is just regular processing --- .../src/main/resources/application.properties | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/powerauth-push-server/src/main/resources/application.properties b/powerauth-push-server/src/main/resources/application.properties index 903ec946d..d77f67988 100644 --- a/powerauth-push-server/src/main/resources/application.properties +++ b/powerauth-push-server/src/main/resources/application.properties @@ -78,4 +78,7 @@ spring.jpa.hibernate.use-new-id-generator-mappings=false spring.jpa.open-in-view=false # Disable swagger-ui default petstore url -springdoc.swagger-ui.disable-swagger-default-url=true \ No newline at end of file +springdoc.swagger-ui.disable-swagger-default-url=true + +# Disable internal logging for Netty due to noisy logging of native library loading +logging.level.io.netty.util.internal.NativeLibraryLoader=WARN From cb3fb7e9ca9a1c61f109d16b28bbae645d83dfde Mon Sep 17 00:00:00 2001 From: Petr Dvorak Date: Fri, 25 Sep 2020 21:35:22 +0200 Subject: [PATCH 30/51] Fix #338: Remove unused Spring WS dependency --- pom.xml | 6 +++--- powerauth-push-server/pom.xml | 18 ------------------ .../errorhandling/DefaultExceptionHandler.java | 6 +++--- 3 files changed, 6 insertions(+), 24 deletions(-) diff --git a/pom.xml b/pom.xml index ad1a74a3b..ed93d6eb4 100644 --- a/pom.xml +++ b/pom.xml @@ -14,7 +14,7 @@ org.springframework.boot spring-boot-starter-parent - 2.2.6.RELEASE + 2.3.4.RELEASE @@ -73,7 +73,7 @@ 1.8 - 28.2-jre + 29.0-jre 1.4.199 4.5.12 4.1.4 @@ -89,7 +89,7 @@ 1.0.1.RELEASE 1.30.10 6.16.0 - 1.65 + 1.66 diff --git a/powerauth-push-server/pom.xml b/powerauth-push-server/pom.xml index 77ab0311e..8303a9c7a 100644 --- a/powerauth-push-server/pom.xml +++ b/powerauth-push-server/pom.xml @@ -40,24 +40,6 @@ spring-boot-starter-tomcat provided - - org.springframework.ws - spring-ws-security - - - bcprov-jdk15on - org.bouncycastle - - - ehcache - net.sf.ehcache - - - geronimo-javamail_1.4_mail - org.apache.geronimo.javamail - - - org.springframework.boot spring-boot-starter-webflux diff --git a/powerauth-push-server/src/main/java/io/getlime/push/errorhandling/DefaultExceptionHandler.java b/powerauth-push-server/src/main/java/io/getlime/push/errorhandling/DefaultExceptionHandler.java index cdd552f48..372503e6e 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/errorhandling/DefaultExceptionHandler.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/errorhandling/DefaultExceptionHandler.java @@ -15,6 +15,7 @@ */ package io.getlime.push.errorhandling; +import com.wultra.security.powerauth.client.model.error.PowerAuthClientException; import io.getlime.core.rest.model.base.entity.Error; import io.getlime.core.rest.model.base.response.ErrorResponse; import io.getlime.push.errorhandling.exceptions.PushServerException; @@ -26,7 +27,6 @@ import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; -import org.springframework.ws.WebServiceException; /** * Implementation of a default exception handler for the push server service. @@ -63,9 +63,9 @@ public ErrorResponse handleDatabaseNotFound(Exception e) { } @ResponseStatus(HttpStatus.BAD_REQUEST) // 400 - @ExceptionHandler(WebServiceException.class) + @ExceptionHandler(PowerAuthClientException.class) @ResponseBody - public ErrorResponse handleWebServiceError(Exception e) { + public ErrorResponse handlePowerAuthClientException(Exception e) { logger.error(e.getMessage(), e); return new ErrorResponse(WebServiceError.Code.ERROR_PA_SERVER_COMM, e); } From 624f7e47505d0ffd997f67bc91be006deb13e6aa Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Tue, 29 Sep 2020 14:24:24 +0200 Subject: [PATCH 31/51] Fix #340: Add WebLogic descriptor to WAR file --- .../src/main/webapp/WEB-INF/weblogic.xml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 powerauth-push-server/src/main/webapp/WEB-INF/weblogic.xml diff --git a/powerauth-push-server/src/main/webapp/WEB-INF/weblogic.xml b/powerauth-push-server/src/main/webapp/WEB-INF/weblogic.xml new file mode 100644 index 000000000..000381700 --- /dev/null +++ b/powerauth-push-server/src/main/webapp/WEB-INF/weblogic.xml @@ -0,0 +1,9 @@ + + + + + javax.validation.* + org.hibernate.validator.* + + + From c5d4a6a2cf06e4c0f0c76047c423d5576115c6d9 Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Tue, 29 Sep 2020 16:49:25 +0200 Subject: [PATCH 32/51] Fix #334: Integrity check issues --- .../errorhandling/DataIntegrityError.java | 33 +++++++++++++++++++ .../DefaultExceptionHandler.java | 11 ++++++- 2 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 powerauth-push-server/src/main/java/io/getlime/push/errorhandling/DataIntegrityError.java diff --git a/powerauth-push-server/src/main/java/io/getlime/push/errorhandling/DataIntegrityError.java b/powerauth-push-server/src/main/java/io/getlime/push/errorhandling/DataIntegrityError.java new file mode 100644 index 000000000..5c0fe5b48 --- /dev/null +++ b/powerauth-push-server/src/main/java/io/getlime/push/errorhandling/DataIntegrityError.java @@ -0,0 +1,33 @@ +/* + * Copyright 2020 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.errorhandling; + +import io.getlime.core.rest.model.base.entity.Error; + +/** + * Exception for data integrity error. + * + * @author Roman Strobl, roman.strobl@wultracom + */ +public class DataIntegrityError extends Error { + public class Code extends Error.Code { + public static final String ERROR_DATA_INTEGRITY = "ERROR_DATA_INTEGRITY"; + + public Code() { + } + } +} diff --git a/powerauth-push-server/src/main/java/io/getlime/push/errorhandling/DefaultExceptionHandler.java b/powerauth-push-server/src/main/java/io/getlime/push/errorhandling/DefaultExceptionHandler.java index 372503e6e..9dfaf4725 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/errorhandling/DefaultExceptionHandler.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/errorhandling/DefaultExceptionHandler.java @@ -21,6 +21,7 @@ import io.getlime.push.errorhandling.exceptions.PushServerException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.dao.DataIntegrityViolationException; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ControllerAdvice; @@ -41,7 +42,7 @@ public class DefaultExceptionHandler { @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) // 500 @ExceptionHandler(Throwable.class) @ResponseBody - public ErrorResponse handleConflict(Throwable t) { + public ErrorResponse handleUnexpectedError(Throwable t) { logger.error(t.getMessage(), t); return new ErrorResponse(Error.Code.ERROR_GENERIC, t); } @@ -62,6 +63,14 @@ public ErrorResponse handleDatabaseNotFound(Exception e) { return new ErrorResponse(DatabaseError.Code.ERROR_DATABASE, e); } + @ResponseStatus(HttpStatus.CONFLICT) // 409 + @ExceptionHandler(DataIntegrityViolationException.class) + @ResponseBody + public ErrorResponse handleDataIntegrityViolationException(DataIntegrityViolationException e) { + logger.error(e.getMessage(), e); + return new ErrorResponse(DataIntegrityError.Code.ERROR_DATA_INTEGRITY, e); + } + @ResponseStatus(HttpStatus.BAD_REQUEST) // 400 @ExceptionHandler(PowerAuthClientException.class) @ResponseBody From eeffbddee94bf84580bdae194c625f52eac0593f Mon Sep 17 00:00:00 2001 From: Petr Dvorak Date: Thu, 1 Oct 2020 18:46:24 +0200 Subject: [PATCH 33/51] Fix #343: Add concurrency configuration for Pushy --- docs/Configuration-Properties.md | 67 +++++++++++++++++++ docs/Readme.md | 2 + docs/_Sidebar.md | 28 +++++--- .../PushServiceConfiguration.java | 22 ++++++ .../push/service/PushSendingWorker.java | 1 + .../src/main/resources/application.properties | 11 +-- 6 files changed, 117 insertions(+), 14 deletions(-) create mode 100644 docs/Configuration-Properties.md diff --git a/docs/Configuration-Properties.md b/docs/Configuration-Properties.md new file mode 100644 index 000000000..b6ba3aaac --- /dev/null +++ b/docs/Configuration-Properties.md @@ -0,0 +1,67 @@ +# Configuration Properties + +The Push Server uses the following public configuration properties: + +## Database Configuration + +| Property | Default | Note | +|---|---|---| +| `spring.datasource.url` | `jdbc:mysql://localhost:3306/powerauth` | Database JDBC URL | +| `spring.datasource.username` | `powerauth` | Database JDBC username | +| `spring.datasource.password` | `_empty_` | Database JDBC passwod | +| `spring.datasource.driver-class-name` | `com.mysql.jdbc.Driver` | Datasource JDBC class name | +| `spring.jpa.properties.hibernate.connection.characterEncoding` | `utf8` | Character encoding | +| `spring.jpa.properties.hibernate.connection.useUnicode` | `true` | Character encoding - Unicode support | + + +## PowerAuth Service Configuration + +| Property | Default | Note | +|---|---|---| +| `powerauth.service.url` | `http://localhost:8080/powerauth-java-server/rest` | PowerAuth service REST API base URL | +| `powerauth.service.security.clientToken` | `_empty_` | PowerAuth REST API authentication token | +| `powerauth.service.security.clientSecret` | `_empty_` | PowerAuth REST API authentication secret / password | +| `powerauth.service.ssl.acceptInvalidSslCertificate` | `false` | Flag indicating if connections using untrusted TLS certificate should be made to the PowerAuth Service | + +## PowerAuth Push Service Configuration + +| Property | Default | Note | +|---|---|---| +| `powerauth.push.service.applicationName` | `powerauth-push` | Technical name of the instance | +| `powerauth.push.service.applicationDisplayName` | `PowerAuth Push Server` | Display name of the instance | +| `powerauth.push.service.applicationEnvironment` | `_empty_` | Environment identifier | +| `powerauth.push.service.message.storage.enabled` | `false` | Whether persistent storing of sent messages is enabled | +| `powerauth.push.service.registration.multipleActivations.enabled` | `false` | Whether push registration supports "associated activations." | + +## PowerAuth Push Campaign Setup + +| Property | Default | Note | +|---|---|---| +| `powerauth.push.service.campaign.batchSize` | `100000` | Default batch size for a campaign sending. | + + +## APNs Configuration + +| Property | Default | Note | +|---|---|---| +| `powerauth.push.service.apns.useDevelopment` | `false` | Flag indicating that the development instance of APNS service should be used. | +| `powerauth.push.service.apns.proxy.enabled` | `false` | Flag indicating if the communication needs to go through proxy. | +| `powerauth.push.service.apns.proxy.host` | `127.0.0.1` | Proxy host | +| `powerauth.push.service.apns.proxy.port` | `8080` | Proxy port | +| `powerauth.push.service.apns.proxy.username` | `_empty_` | Proxy username | +| `powerauth.push.service.apns.proxy.password` | `_empty_` | Proxy password | +| `powerauth.push.service.apns.connect.timeout` | `5000` | Push message gateway connect timeout in milliseconds | +| `powerauth.push.service.apns.concurrentConnections` | `1` | Push message concurrency settings | + +# FCM Configuration + +| Property | Default | Note | +|---|---|---| +| `powerauth.push.service.fcm.proxy.enabled` | `false` | Flag indicating if the communication needs to go through proxy. | +| `powerauth.push.service.fcm.proxy.host` | `127.0.0.1` | Proxy host | +| `powerauth.push.service.fcm.proxy.port` | `8080` | Proxy port | +| `powerauth.push.service.fcm.proxy.username` | `_empty_` | Proxy username | +| `powerauth.push.service.fcm.proxy.password` | `_empty_` | Proxy password | +| `powerauth.push.service.fcm.dataNotificationOnly` | `false` | Flag indicating that FCM service should never use "notification" format, only a data format with extra payload representing the notification. | +| `powerauth.push.service.fcm.sendMessageUrl` | `https://fcm.googleapis.com/v1/projects/%s/messages:send` | Default URL for the FCM service. | +| `powerauth.push.service.fcm.connect.timeout` | `5000` | Push message gateway connect timeout in milliseconds | diff --git a/docs/Readme.md b/docs/Readme.md index 38787e737..c0b079ee3 100644 --- a/docs/Readme.md +++ b/docs/Readme.md @@ -5,7 +5,9 @@ PowerAuth Push Server is an optional application that facilitates sending APNs / ## Deployment Tutorials - [Deploy PowerAuth Push Server](./Deploying-Push-Server.md) +- [Deploy Push Server on JBoss / Wildfly](./Deploying-Wildfly.md) - [Migration Instructions](./Migration-Instructions.md) +- [Configuration Properties](./Configuration-Properties.md) ## Integration Tutorials diff --git a/docs/_Sidebar.md b/docs/_Sidebar.md index a992903a4..b2fca72cd 100644 --- a/docs/_Sidebar.md +++ b/docs/_Sidebar.md @@ -1,21 +1,29 @@ **Deployment Tutorials** -- [Home](./Readme.md) -- [Deploy Push Server](./Deploying-Push-Server.md) -- [Deploy Push Server on JBoss / Wildfly](./Deploying-Wildfly.md) -- [Migration Instructions](./Migration-Instructions.md) +[Home](./Readme.md) + +[Deploy Push Server](./Deploying-Push-Server.md) + +[Deploy Push Server on JBoss / Wildfly](./Deploying-Wildfly.md) + +[Migration Instructions](./Migration-Instructions.md) + +[Configuration Properties](./Configuration-Properties.md) **Integration Tutorials** -- [Integrate with Push Server](./Push-Server-Integration.md) +[Integrate with Push Server](./Push-Server-Integration.md) **Reference Manual** -- [Push Server RESTful API](./Push-Server-API.md) -- [Push Server Database Structure](./Push-Server-Database.md) -- [Push Server Administration](./Push-Server-Administration.md) +[Push Server RESTful API](./Push-Server-API.md) + +[Push Server Database Structure](./Push-Server-Database.md) + +[Push Server Administration](./Push-Server-Administration.md) **Technical Topics** -- [Mapping Abstract Payload to APNS/FCM](./Push-Message-Payload-Mapping.md) -- [Running Behind Proxy](./Running-Behind-Proxy.md) +[Mapping Abstract Payload to APNS/FCM](./Push-Message-Payload-Mapping.md) + +[Running Behind Proxy](./Running-Behind-Proxy.md) diff --git a/powerauth-push-server/src/main/java/io/getlime/push/configuration/PushServiceConfiguration.java b/powerauth-push-server/src/main/java/io/getlime/push/configuration/PushServiceConfiguration.java index b3c23691f..0dd039750 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/configuration/PushServiceConfiguration.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/configuration/PushServiceConfiguration.java @@ -103,6 +103,12 @@ public class PushServiceConfiguration { @Value("${powerauth.push.service.apns.connect.timeout}") private int apnsConnectTimeout; + /** + * APNS concurrent connections. + */ + @Value("${powerauth.push.service.apns.concurrentConnections}") + private int concurrentConnections; + /** * Get push server name. * @return Push server name. @@ -438,4 +444,20 @@ public int getApnsConnectTimeout() { public void setApnsConnectTimeout(int apnsConnectTimeout) { this.apnsConnectTimeout = apnsConnectTimeout; } + + /** + * Get APNS concurrent connections. + * @return APNS concurrent connections. + */ + public int getConcurrentConnections() { + return concurrentConnections; + } + + /** + * Set APNS concurrent connections. + * @param concurrentConnections APNS concurrent connections. + */ + public void setConcurrentConnections(int concurrentConnections) { + this.concurrentConnections = concurrentConnections; + } } 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 24ff8262b..dee80667d 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 @@ -259,6 +259,7 @@ private Message buildAndroidMessage(final PushMessageBody pushMessageBody, final ApnsClient prepareApnsClient(String teamId, String keyId, byte[] apnsPrivateKey) throws PushServerException { final ApnsClientBuilder apnsClientBuilder = new ApnsClientBuilder(); apnsClientBuilder.setProxyHandlerFactory(apnsClientProxy()); + apnsClientBuilder.setConcurrentConnections(pushServiceConfiguration.getConcurrentConnections()); apnsClientBuilder.setConnectionTimeout(Duration.ofMillis(pushServiceConfiguration.getApnsConnectTimeout())); if (pushServiceConfiguration.isApnsUseDevelopment()) { logger.info("Using APNs development host"); diff --git a/powerauth-push-server/src/main/resources/application.properties b/powerauth-push-server/src/main/resources/application.properties index d77f67988..67530174f 100644 --- a/powerauth-push-server/src/main/resources/application.properties +++ b/powerauth-push-server/src/main/resources/application.properties @@ -27,18 +27,18 @@ spring.batch.job.enabled=false # Hibernate Configuration spring.jpa.hibernate.ddl-auto=none -# PowerAuth 2.0 Service Configuration +# PowerAuth Service Configuration powerauth.service.url=http://localhost:8080/powerauth-java-server/rest powerauth.service.security.clientToken= powerauth.service.security.clientSecret= powerauth.service.ssl.acceptInvalidSslCertificate=false -# PowerAuth 2.0 Push Service Configuration +# PowerAuth Push Service Configuration powerauth.push.service.applicationName=powerauth-push -powerauth.push.service.applicationDisplayName=PowerAuth 2.0 Push Server +powerauth.push.service.applicationDisplayName=PowerAuth Push Server powerauth.push.service.applicationEnvironment= -# PowerAuth 2.0 Push Campaign Setup +# PowerAuth Push Campaign Setup powerauth.push.service.campaign.batchSize=100000 # Whether persistent storing of sent messages is enabled @@ -71,6 +71,9 @@ spring.jmx.default-domain=powerauth-push-server powerauth.push.service.fcm.connect.timeout=5000 powerauth.push.service.apns.connect.timeout=5000 +# Push message concurrency settings +powerauth.push.service.apns.concurrentConnections=1 + # Disable new ID generators, because the optimized 'pooled' algorithm is incompatible with non-optimized algorithm used in existing deployments spring.jpa.hibernate.use-new-id-generator-mappings=false From 3c8d362b79ffa4f439d2b7322ee97e8c6725c78e Mon Sep 17 00:00:00 2001 From: snyk-bot Date: Sat, 10 Oct 2020 08:39:51 +0000 Subject: [PATCH 34/51] fix: pom.xml to reduce vulnerabilities The following vulnerabilities are fixed with an upgrade: - https://snyk.io/vuln/SNYK-JAVA-ORGAPACHEHTTPCOMPONENTS-1016906 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ed93d6eb4..4cd1c0581 100644 --- a/pom.xml +++ b/pom.xml @@ -75,7 +75,7 @@ 29.0-jre 1.4.199 - 4.5.12 + 4.5.13 4.1.4 2.11.2 2.11.2 From b91d01ccde57e37b1b0a7cebd4a7c9d160d285dd Mon Sep 17 00:00:00 2001 From: Petr Dvorak Date: Sat, 10 Oct 2020 11:35:54 +0200 Subject: [PATCH 35/51] Fix #346: Add support for idle ping interval in APNS --- .../getlime/push/client/PushServerClientError.java | 2 +- .../push/model/entity/PushMessageSendResult.java | 4 ++-- .../request/CreateDeviceForActivationsRequest.java | 2 +- .../configuration/BatchSendingConfiguration.java | 7 ++++++- .../configuration/PushServiceConfiguration.java | 14 ++++++++++++++ .../controller/rest/PushMessageController.java | 2 +- .../io/getlime/push/service/PushSendingWorker.java | 3 ++- .../push/service/apns/ApnsRejectionReason.java | 2 -- .../service/batch/SendCampaignJobListener.java | 7 ++++--- .../service/batch/UserDeviceItemProcessor.java | 5 +++-- .../push/service/batch/UserDeviceItemWriter.java | 2 +- .../batch/storage/AppCredentialStorageMap.java | 2 +- .../batch/storage/CampaignMessageStorageMap.java | 2 +- .../batch/storage/UserDeviceStorageSet.java | 2 +- .../src/main/resources/application.properties | 3 +++ .../getlime/push/shared/PowerAuthTestClient.java | 2 +- 16 files changed, 42 insertions(+), 19 deletions(-) diff --git a/powerauth-push-client/src/main/java/io/getlime/push/client/PushServerClientError.java b/powerauth-push-client/src/main/java/io/getlime/push/client/PushServerClientError.java index 2c08ffb55..9a576a0f4 100644 --- a/powerauth-push-client/src/main/java/io/getlime/push/client/PushServerClientError.java +++ b/powerauth-push-client/src/main/java/io/getlime/push/client/PushServerClientError.java @@ -28,7 +28,7 @@ public class PushServerClientError extends Error implements Serializable { private static final long serialVersionUID = -3063064667769595550L; - private static String ERROR_VALIDATION = "ERROR_VALIDATION"; + private static final String ERROR_VALIDATION = "ERROR_VALIDATION"; public PushServerClientError(String message) { diff --git a/powerauth-push-model/src/main/java/io/getlime/push/model/entity/PushMessageSendResult.java b/powerauth-push-model/src/main/java/io/getlime/push/model/entity/PushMessageSendResult.java index 35a75420c..9a22da8e2 100644 --- a/powerauth-push-model/src/main/java/io/getlime/push/model/entity/PushMessageSendResult.java +++ b/powerauth-push-model/src/main/java/io/getlime/push/model/entity/PushMessageSendResult.java @@ -211,8 +211,8 @@ public int hashCode() { } } - private iOS ios; - private Android android; + private final iOS ios; + private final Android android; public PushMessageSendResult() { this.ios = new iOS(); 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 23174190d..ae6b6aea8 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 @@ -28,7 +28,7 @@ public class CreateDeviceForActivationsRequest { private Long appId; private String token; private String platform; - private List activationIds = new ArrayList<>(); + private final List activationIds = new ArrayList<>(); /** * Get app ID associated with given device registration. diff --git a/powerauth-push-server/src/main/java/io/getlime/push/configuration/BatchSendingConfiguration.java b/powerauth-push-server/src/main/java/io/getlime/push/configuration/BatchSendingConfiguration.java index 9712908cb..ec269a192 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/configuration/BatchSendingConfiguration.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/configuration/BatchSendingConfiguration.java @@ -38,6 +38,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.lang.NonNull; import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.transaction.PlatformTransactionManager; @@ -65,7 +66,7 @@ public class BatchSendingConfiguration implements BatchConfigurer { private final SendCampaignJobListener sendCampaignJobListener; private DataSource dataSource; - private EntityManagerFactory entityManagerFactory; + private final EntityManagerFactory entityManagerFactory; private PlatformTransactionManager transactionManager; private JobRepository jobRepository; private JobLauncher jobLauncher; @@ -117,21 +118,25 @@ public void setDataSource(DataSource dataSource) { } @Override + @NonNull public JobRepository getJobRepository() { return jobRepository; } @Override + @NonNull public PlatformTransactionManager getTransactionManager() { return transactionManager; } @Override + @NonNull public JobLauncher getJobLauncher() { return jobLauncher; } @Override + @NonNull public JobExplorer getJobExplorer() { return jobExplorer; } diff --git a/powerauth-push-server/src/main/java/io/getlime/push/configuration/PushServiceConfiguration.java b/powerauth-push-server/src/main/java/io/getlime/push/configuration/PushServiceConfiguration.java index 0dd039750..aedc3cc8c 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/configuration/PushServiceConfiguration.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/configuration/PushServiceConfiguration.java @@ -109,6 +109,12 @@ public class PushServiceConfiguration { @Value("${powerauth.push.service.apns.concurrentConnections}") private int concurrentConnections; + /** + * Interval specifying the frequency of APNS ping calls in idle state. + */ + @Value("${powerauth.push.service.apns.idlePingInterval}") + private long idlePingInterval; + /** * Get push server name. * @return Push server name. @@ -460,4 +466,12 @@ public int getConcurrentConnections() { public void setConcurrentConnections(int concurrentConnections) { this.concurrentConnections = concurrentConnections; } + + public long getIdlePingInterval() { + return idlePingInterval; + } + + public void setIdlePingInterval(long idlePingInterval) { + this.idlePingInterval = idlePingInterval; + } } diff --git a/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/PushMessageController.java b/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/PushMessageController.java index f9ea8e0c5..6054dc31e 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/PushMessageController.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/PushMessageController.java @@ -48,7 +48,7 @@ public class PushMessageController { private static final Logger logger = LoggerFactory.getLogger(PushMessageController.class); - private PushMessageSenderService pushMessageSenderService; + private final PushMessageSenderService pushMessageSenderService; @Autowired public PushMessageController(PushMessageSenderService pushMessageSenderService) { 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 dee80667d..3fbfe33cf 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 @@ -261,6 +261,7 @@ ApnsClient prepareApnsClient(String teamId, String keyId, byte[] apnsPrivateKey) apnsClientBuilder.setProxyHandlerFactory(apnsClientProxy()); apnsClientBuilder.setConcurrentConnections(pushServiceConfiguration.getConcurrentConnections()); apnsClientBuilder.setConnectionTimeout(Duration.ofMillis(pushServiceConfiguration.getApnsConnectTimeout())); + apnsClientBuilder.setIdlePingInterval(Duration.ofMillis(pushServiceConfiguration.getIdlePingInterval())); if (pushServiceConfiguration.isApnsUseDevelopment()) { logger.info("Using APNs development host"); apnsClientBuilder.setApnsServer(ApnsClientBuilder.DEVELOPMENT_APNS_HOST); @@ -318,7 +319,7 @@ private HttpProxyHandlerFactory apnsClientProxy() { void sendMessageToIos(final ApnsClient apnsClient, final PushMessageBody pushMessageBody, final PushMessageAttributes attributes, final String pushToken, final String iosTopic, final PushSendingCallback callback) { final String token = TokenUtil.sanitizeTokenString(pushToken); - final boolean isSilent = attributes == null ? false : attributes.getSilent(); // In case there are no attributes, the message is not silent + final boolean isSilent = attributes != null && attributes.getSilent(); // In case there are no attributes, the message is not silent final String payload = buildApnsPayload(pushMessageBody, isSilent); 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 diff --git a/powerauth-push-server/src/main/java/io/getlime/push/service/apns/ApnsRejectionReason.java b/powerauth-push-server/src/main/java/io/getlime/push/service/apns/ApnsRejectionReason.java index 717da3b26..11d78f10e 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/service/apns/ApnsRejectionReason.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/service/apns/ApnsRejectionReason.java @@ -16,8 +16,6 @@ package io.getlime.push.service.apns; -import io.netty.handler.codec.http.HttpResponseStatus; - /** * APNs rejection reason codes, documented on the Apple site: * https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/handling_notification_responses_from_apns diff --git a/powerauth-push-server/src/main/java/io/getlime/push/service/batch/SendCampaignJobListener.java b/powerauth-push-server/src/main/java/io/getlime/push/service/batch/SendCampaignJobListener.java index 2533a396f..20eff5c01 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/service/batch/SendCampaignJobListener.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/service/batch/SendCampaignJobListener.java @@ -23,6 +23,7 @@ import org.springframework.batch.core.configuration.annotation.JobScope; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.lang.NonNull; import org.springframework.stereotype.Component; import java.util.Date; @@ -32,7 +33,7 @@ @JobScope public class SendCampaignJobListener implements JobExecutionListener { - private PushCampaignRepository pushCampaignRepository; + private final PushCampaignRepository pushCampaignRepository; @Value("#{jobParameters['campaignId']}") private Long campaignId; @@ -43,14 +44,14 @@ public SendCampaignJobListener(PushCampaignRepository pushCampaignRepository) { } @Override - public void beforeJob(JobExecution jobExecution) { + public void beforeJob(@NonNull JobExecution jobExecution) { PushCampaignEntity campaign = findPushCampaignById(campaignId); campaign.setTimestampSent(new Date()); pushCampaignRepository.save(campaign); } @Override - public void afterJob(JobExecution jobExecution) { + public void afterJob(@NonNull JobExecution jobExecution) { PushCampaignEntity campaign = findPushCampaignById(campaignId); campaign.setTimestampCompleted(new Date()); campaign.setSent(true); diff --git a/powerauth-push-server/src/main/java/io/getlime/push/service/batch/UserDeviceItemProcessor.java b/powerauth-push-server/src/main/java/io/getlime/push/service/batch/UserDeviceItemProcessor.java index 5b42003db..2fd0874fa 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/service/batch/UserDeviceItemProcessor.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/service/batch/UserDeviceItemProcessor.java @@ -20,6 +20,7 @@ import io.getlime.push.service.batch.storage.UserDeviceStorageSet; import org.springframework.batch.core.configuration.annotation.StepScope; import org.springframework.batch.item.ItemProcessor; +import org.springframework.lang.NonNull; import org.springframework.stereotype.Component; /** @@ -31,7 +32,7 @@ @StepScope public class UserDeviceItemProcessor implements ItemProcessor { - private ItemStorageSet itemStore = new UserDeviceStorageSet<>(); + private final ItemStorageSet itemStore = new UserDeviceStorageSet<>(); /** * Decides if current userDevice is going to be processed to sending @@ -40,7 +41,7 @@ public class UserDeviceItemProcessor implements ItemProcessor { private final JsonSerialization jsonSerialization; // Non-autowired fields - private CampaignMessageStorageMap campaignStorageMap = new CampaignMessageStorageMap(); + private final CampaignMessageStorageMap campaignStorageMap = new CampaignMessageStorageMap(); @Autowired public UserDeviceItemWriter(PushMessageSenderService pushMessageSenderService, diff --git a/powerauth-push-server/src/main/java/io/getlime/push/service/batch/storage/AppCredentialStorageMap.java b/powerauth-push-server/src/main/java/io/getlime/push/service/batch/storage/AppCredentialStorageMap.java index bfe9a9bcb..a9620d2f8 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/service/batch/storage/AppCredentialStorageMap.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/service/batch/storage/AppCredentialStorageMap.java @@ -28,7 +28,7 @@ */ public class AppCredentialStorageMap implements ItemStorageMap { - private ConcurrentMap map = new ConcurrentHashMap<>(); + private final ConcurrentMap map = new ConcurrentHashMap<>(); @Override public AppRelatedPushClient get(Long key) { diff --git a/powerauth-push-server/src/main/java/io/getlime/push/service/batch/storage/CampaignMessageStorageMap.java b/powerauth-push-server/src/main/java/io/getlime/push/service/batch/storage/CampaignMessageStorageMap.java index 8a3d39b24..3f441e21b 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/service/batch/storage/CampaignMessageStorageMap.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/service/batch/storage/CampaignMessageStorageMap.java @@ -30,7 +30,7 @@ */ public class CampaignMessageStorageMap implements ItemStorageMap { - private ConcurrentMap mapStorage = new ConcurrentHashMap<>(); + private final ConcurrentMap mapStorage = new ConcurrentHashMap<>(); @Override public PushMessageBody get(Long key) { diff --git a/powerauth-push-server/src/main/java/io/getlime/push/service/batch/storage/UserDeviceStorageSet.java b/powerauth-push-server/src/main/java/io/getlime/push/service/batch/storage/UserDeviceStorageSet.java index a2550fc4c..e3a211d78 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/service/batch/storage/UserDeviceStorageSet.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/service/batch/storage/UserDeviceStorageSet.java @@ -26,7 +26,7 @@ */ public class UserDeviceStorageSet implements ItemStorageSet { - private Set items = new HashSet<>(); + private final Set items = new HashSet<>(); @Override public boolean exists(T item) { diff --git a/powerauth-push-server/src/main/resources/application.properties b/powerauth-push-server/src/main/resources/application.properties index 67530174f..46bde4537 100644 --- a/powerauth-push-server/src/main/resources/application.properties +++ b/powerauth-push-server/src/main/resources/application.properties @@ -71,6 +71,9 @@ spring.jmx.default-domain=powerauth-push-server powerauth.push.service.fcm.connect.timeout=5000 powerauth.push.service.apns.connect.timeout=5000 +# Push message networking settings +powerauth.push.service.apns.idlePingInterval=60000 + # Push message concurrency settings powerauth.push.service.apns.concurrentConnections=1 diff --git a/powerauth-push-server/src/test/java/io/getlime/push/shared/PowerAuthTestClient.java b/powerauth-push-server/src/test/java/io/getlime/push/shared/PowerAuthTestClient.java index 9d84aa8f1..1261d93e2 100644 --- a/powerauth-push-server/src/test/java/io/getlime/push/shared/PowerAuthTestClient.java +++ b/powerauth-push-server/src/test/java/io/getlime/push/shared/PowerAuthTestClient.java @@ -42,7 +42,7 @@ public class PowerAuthTestClient { private String activationId3; private String activationId4; - private ObjectMapper objectMapper = new ObjectMapper(); + private final ObjectMapper objectMapper = new ObjectMapper(); public void initializeClient(String powerAuthRestUrl) { powerAuthClient = new PowerAuthRestClient(powerAuthRestUrl); From df5415479026aa8c98e389ad97bde9988def86c6 Mon Sep 17 00:00:00 2001 From: Petr Dvorak Date: Wed, 21 Oct 2020 19:18:28 +0200 Subject: [PATCH 36/51] Fix #349: Update Pushy to 0.14.2 --- pom.xml | 14 ++++++-------- powerauth-push-server/pom.xml | 2 +- .../src/main/resources/application.properties | 4 ---- 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/pom.xml b/pom.xml index 4cd1c0581..cb2d98577 100644 --- a/pom.xml +++ b/pom.xml @@ -73,22 +73,20 @@ 1.8 - 29.0-jre + 30.0-jre 1.4.199 4.5.13 4.1.4 - 2.11.2 - 2.11.2 + 2.11.3 + 2.11.3 1.2.2 - 2.0 - 1.2.5 1.1.0 1.0.0-SNAPSHOT - 0.14.1 + 0.14.2 1.4.6 1.0.1.RELEASE - 1.30.10 - 6.16.0 + 1.30.11 + 7.0.1 1.66 diff --git a/powerauth-push-server/pom.xml b/powerauth-push-server/pom.xml index 8303a9c7a..ba761de78 100644 --- a/powerauth-push-server/pom.xml +++ b/powerauth-push-server/pom.xml @@ -74,7 +74,7 @@ com.eatthepath pushy - 0.14.1 + ${pushy.version} com.google.api-client diff --git a/powerauth-push-server/src/main/resources/application.properties b/powerauth-push-server/src/main/resources/application.properties index 46bde4537..6e621437a 100644 --- a/powerauth-push-server/src/main/resources/application.properties +++ b/powerauth-push-server/src/main/resources/application.properties @@ -1,10 +1,6 @@ # Allow externalization of properties using application-ext.properties spring.profiles.active=ext -# Spring MVC configuration -spring.mvc.view.prefix=/WEB-INF/jsp/ -spring.mvc.view.suffix=.jsp - # Database Configuration - MySQL spring.datasource.url=jdbc:mysql://localhost:3306/powerauth spring.datasource.username=powerauth From c724c46bb2375424a1c154c80aa58636bee26880 Mon Sep 17 00:00:00 2001 From: Petr Dvorak Date: Wed, 21 Oct 2020 19:52:40 +0200 Subject: [PATCH 37/51] Fix #347: Review the client rewrite to WebClient --- .../getlime/push/client/PushServerClient.java | 117 ++++++------------ 1 file changed, 41 insertions(+), 76 deletions(-) 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 c1849d027..dccf63439 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 @@ -16,8 +16,6 @@ package io.getlime.push.client; -import com.fasterxml.jackson.core.JsonParseException; -import com.fasterxml.jackson.databind.JsonMappingException; import com.google.common.io.BaseEncoding; import io.getlime.core.rest.model.base.entity.Error; import io.getlime.core.rest.model.base.request.ObjectRequest; @@ -35,6 +33,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; @@ -43,7 +42,6 @@ import org.springframework.web.reactive.function.client.WebClient; import reactor.core.publisher.Mono; -import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.Collections; @@ -626,24 +624,13 @@ public Response removeAndroid(Long id) throws PushServerClientException { * @throws PushServerClientException In case of network, response / JSON processing, or other IO error. * */ - private T getObjectImpl(String url, MultiValueMap params, ParameterizedTypeReference typeReference) throws PushServerClientException { - try { - ClientResponse response = webClient.get() - .uri(uriBuilder -> uriBuilder.path(url).queryParams(params).build()) - .accept(MediaType.APPLICATION_JSON) - .exchange() - .block(); - return checkHttpStatus(typeReference, Objects.requireNonNull(response)); - } catch (JsonParseException e) { - logger.warn(e.getMessage(), e); - throw new PushServerClientException(e, new Error("PUSH_SERVER_CLIENT_ERROR", "JSON parsing has failed.")); - } catch (JsonMappingException e) { - logger.warn(e.getMessage(), e); - throw new PushServerClientException(e, new Error("PUSH_SERVER_CLIENT_ERROR", "JSON mapping has failed.")); - } catch (IOException e) { - logger.warn(e.getMessage(), e); - throw new PushServerClientException(e, new Error("PUSH_SERVER_CLIENT_ERROR", "Unknown IO error.")); - } + private T getObjectImpl(String url, MultiValueMap params, ParameterizedTypeReference typeReference) throws PushServerClientException { + ClientResponse response = webClient.get() + .uri(uriBuilder -> uriBuilder.path(url).queryParams(params).build()) + .accept(MediaType.APPLICATION_JSON) + .exchange() + .block(); + return checkHttpStatus(typeReference, Objects.requireNonNull(response)); } @@ -655,8 +642,8 @@ private T getObjectImpl(String url, MultiValueMap params, Pa * @return Object obtained after processing the response JSON. * @throws PushServerClientException In case of network, response / JSON processing, or other IO error. */ - private T postObjectImpl(String url, Object request) throws PushServerClientException { - return postObjectImpl(url, request, new ParameterizedTypeReference() {}); + private T postObjectImpl(String url, Object request) throws PushServerClientException { + return postObjectImpl(url, request, new ParameterizedTypeReference() {}); } /** @@ -668,31 +655,8 @@ private T postObjectImpl(String url, Object request) throws PushServerClient * @return Object obtained after processing the response JSON. * @throws PushServerClientException In case of network, response / JSON processing, or other IO error. */ - private T postObjectImpl(String url, Object request, ParameterizedTypeReference typeReference) throws PushServerClientException { - try { - // Fetch post response from given URL and for provided request object - WebClient.RequestBodySpec spec = webClient.post() - .uri(uriBuilder -> uriBuilder.path(url).build()) - .accept(MediaType.APPLICATION_JSON) - .contentType(MediaType.APPLICATION_JSON); - Mono responseMono; - if (request != null) { - responseMono = spec.body(BodyInserters.fromValue(request)).exchange(); - } else { - responseMono = spec.exchange(); - } - ClientResponse response = responseMono.block(); - return checkHttpStatus(typeReference, Objects.requireNonNull(response)); - } catch (JsonParseException e) { - logger.warn(e.getMessage(), e); - throw new PushServerClientException(e, new Error("PUSH_SERVER_CLIENT_ERROR", "JSON parsing has failed.")); - } catch (JsonMappingException e) { - logger.warn(e.getMessage(), e); - throw new PushServerClientException(e, new Error("PUSH_SERVER_CLIENT_ERROR", "JSON mapping has failed.")); - } catch (IOException e) { - logger.warn(e.getMessage(), e); - throw new PushServerClientException(e, new Error("PUSH_SERVER_CLIENT_ERROR", "Unknown IO error.")); - } + private T postObjectImpl(String url, Object request, ParameterizedTypeReference typeReference) throws PushServerClientException { + return httpExchangeObjectImpl(HttpMethod.POST, url, request, typeReference); } /** @@ -703,8 +667,8 @@ private T postObjectImpl(String url, Object request, ParameterizedTypeRefere * @return Object obtained after processing the response JSON. * @throws PushServerClientException In case of network, response / JSON processing, or other IO error. */ - private T putObjectImpl(String url, Object request) throws PushServerClientException { - return putObjectImpl(url, request, new ParameterizedTypeReference() {}); + private T putObjectImpl(String url, Object request) throws PushServerClientException { + return putObjectImpl(url, request, new ParameterizedTypeReference() {}); } /** @@ -716,30 +680,33 @@ private T putObjectImpl(String url, Object request) throws PushServerClientE * @return Object obtained after processing the response JSON. * @throws PushServerClientException In case of network, response / JSON processing, or other IO error. */ - private T putObjectImpl(String url, Object request, ParameterizedTypeReference typeReference) throws PushServerClientException { - try { - WebClient.RequestBodySpec spec = webClient.put() - .uri(uriBuilder -> uriBuilder.path(url).build()) - .accept(MediaType.APPLICATION_JSON) - .contentType(MediaType.APPLICATION_JSON); - Mono responseMono; - if (request != null) { - responseMono = spec.body(BodyInserters.fromValue(request)).exchange(); - } else { - responseMono = spec.exchange(); - } - ClientResponse response = responseMono.block(); - return checkHttpStatus(typeReference, Objects.requireNonNull(response)); - } catch (JsonParseException e) { - logger.warn(e.getMessage(), e); - throw new PushServerClientException(e, new Error("PUSH_SERVER_CLIENT_ERROR", "JSON parsing has failed.")); - } catch (JsonMappingException e) { - logger.warn(e.getMessage(), e); - throw new PushServerClientException(e, new Error("PUSH_SERVER_CLIENT_ERROR", "JSON mapping has failed.")); - } catch (IOException e) { - logger.warn(e.getMessage(), e); - throw new PushServerClientException(e, new Error("PUSH_SERVER_CLIENT_ERROR", "Unknown IO error.")); + private T putObjectImpl(String url, Object request, ParameterizedTypeReference typeReference) throws PushServerClientException { + return httpExchangeObjectImpl(HttpMethod.PUT, url, request, typeReference); + } + + /** + * Exchange object response. + * + * @param method HTTP method + * @param url specific url of method + * @param request request body + * @param typeReference reference on type for parsing into JSON + * @return Object obtained after processing the response JSON. + * @throws PushServerClientException In case of network, response / JSON processing, or other IO error. + */ + private T httpExchangeObjectImpl(HttpMethod method, String url, Object request, ParameterizedTypeReference typeReference) throws PushServerClientException { + WebClient.RequestBodySpec spec = webClient.method(method) + .uri(uriBuilder -> uriBuilder.path(url).build()) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON); + Mono responseMono; + if (request != null) { + responseMono = spec.body(BodyInserters.fromValue(request)).exchange(); + } else { + responseMono = spec.exchange(); } + ClientResponse response = responseMono.block(); + return checkHttpStatus(typeReference, Objects.requireNonNull(response)); } /** @@ -750,10 +717,8 @@ private T putObjectImpl(String url, Object request, ParameterizedTypeReferen * @return In case response code is 200, returns instance of expected response type. Otherwise, it attempts to * reconstruct error response and returns the error response. * @throws PushServerClientException In case of network, response / JSON processing, or other IO error. - * @throws IOException In case JSON processing fails. */ - @SuppressWarnings("unchecked") - private T checkHttpStatus(ParameterizedTypeReference typeReference, ClientResponse response) throws IOException, PushServerClientException { + private T checkHttpStatus(ParameterizedTypeReference typeReference, ClientResponse response) throws PushServerClientException { if (response.statusCode().is2xxSuccessful()) { return (T) response.bodyToMono(typeReference).block(); } else { From 784f0665eb67d259ac000d7673bce1e8938d77f2 Mon Sep 17 00:00:00 2001 From: Petr Dvorak Date: Wed, 21 Oct 2020 20:19:24 +0200 Subject: [PATCH 38/51] Fix minor code issues from static analysis --- .../java/io/getlime/push/client/PushServerClient.java | 2 +- .../io/getlime/push/model/entity/PushMessageBody.java | 7 +++++-- .../model/response/ListOfUsersFromCampaignResponse.java | 8 ++++++-- .../push/controller/rest/PushCampaignController.java | 2 +- .../src/test/java/io/getlime/push/ApplicationTest.java | 5 +++++ .../push/client/PushServerMultipleActivationsTests.java | 9 --------- .../java/io/getlime/push/client/PushServerTests.java | 5 ----- 7 files changed, 18 insertions(+), 20 deletions(-) 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 dccf63439..2fa588d67 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 @@ -720,7 +720,7 @@ private T httpExchangeObjectImpl(HttpMethod method, String */ private T checkHttpStatus(ParameterizedTypeReference typeReference, ClientResponse response) throws PushServerClientException { if (response.statusCode().is2xxSuccessful()) { - return (T) response.bodyToMono(typeReference).block(); + return response.bodyToMono(typeReference).block(); } else { try { // Response body contains data, return Exception with status code and error response diff --git a/powerauth-push-model/src/main/java/io/getlime/push/model/entity/PushMessageBody.java b/powerauth-push-model/src/main/java/io/getlime/push/model/entity/PushMessageBody.java index c1094393d..4a50893cd 100644 --- a/powerauth-push-model/src/main/java/io/getlime/push/model/entity/PushMessageBody.java +++ b/powerauth-push-model/src/main/java/io/getlime/push/model/entity/PushMessageBody.java @@ -18,6 +18,7 @@ import java.time.Instant; import java.util.Map; +import java.util.Objects; /** * Class representing a message body - the information that do not serve as a "message descriptor" @@ -187,8 +188,10 @@ public boolean equals(Object o) { PushMessageBody that = (PushMessageBody) o; - if (title != null ? !title.equals(that.title) : that.title != null) return false; - return body != null ? body.equals(that.body) : that.body == null; + if (!Objects.equals(title, that.title)) { + return false; + } + return Objects.equals(body, that.body); } @Override diff --git a/powerauth-push-model/src/main/java/io/getlime/push/model/response/ListOfUsersFromCampaignResponse.java b/powerauth-push-model/src/main/java/io/getlime/push/model/response/ListOfUsersFromCampaignResponse.java index 64a569f94..8c8017aa3 100644 --- a/powerauth-push-model/src/main/java/io/getlime/push/model/response/ListOfUsersFromCampaignResponse.java +++ b/powerauth-push-model/src/main/java/io/getlime/push/model/response/ListOfUsersFromCampaignResponse.java @@ -18,6 +18,8 @@ import io.getlime.push.model.entity.ListOfUsers; +import java.util.Objects; + /** * Response used for getting a list of users from certain campaign * @@ -54,8 +56,10 @@ public boolean equals(Object o) { ListOfUsersFromCampaignResponse that = (ListOfUsersFromCampaignResponse) o; - if (campaignId != null ? !campaignId.equals(that.campaignId) : that.campaignId != null) return false; - return users != null ? users.equals(that.users) : that.users == null; + if (!Objects.equals(campaignId, that.campaignId)) { + return false; + } + return Objects.equals(users, that.users); } @Override diff --git a/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/PushCampaignController.java b/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/PushCampaignController.java index d25dc5331..b6dc4f66d 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/PushCampaignController.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/PushCampaignController.java @@ -273,7 +273,7 @@ public Response deleteUsersFromCampaign(@PathVariable(value = "id") Long id, @Re * @param request An object request to check the nullity * @throws PushServerException In case request object is null. */ - private void checkRequestNullity(ObjectRequest request) throws PushServerException { + private void checkRequestNullity(ObjectRequest request) throws PushServerException { if (request.getRequestObject() == null) { throw new PushServerException("Empty requestObject data"); } diff --git a/powerauth-push-server/src/test/java/io/getlime/push/ApplicationTest.java b/powerauth-push-server/src/test/java/io/getlime/push/ApplicationTest.java index 9a72238fa..70134c125 100644 --- a/powerauth-push-server/src/test/java/io/getlime/push/ApplicationTest.java +++ b/powerauth-push-server/src/test/java/io/getlime/push/ApplicationTest.java @@ -18,6 +18,8 @@ import org.junit.Test; import org.junit.runner.RunWith; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit4.SpringRunner; @@ -27,8 +29,11 @@ @TestPropertySource(locations = "classpath:application-test.properties") public class ApplicationTest { + private static final Logger logger = LoggerFactory.getLogger(ApplicationTest.class); + @Test public void contextLoads() { + logger.info("Context loaded"); } } diff --git a/powerauth-push-server/src/test/java/io/getlime/push/client/PushServerMultipleActivationsTests.java b/powerauth-push-server/src/test/java/io/getlime/push/client/PushServerMultipleActivationsTests.java index da9444801..c417eeb38 100644 --- a/powerauth-push-server/src/test/java/io/getlime/push/client/PushServerMultipleActivationsTests.java +++ b/powerauth-push-server/src/test/java/io/getlime/push/client/PushServerMultipleActivationsTests.java @@ -21,13 +21,10 @@ import io.getlime.push.shared.PowerAuthTestClient; import io.getlime.push.shared.PushServerTestClientFactory; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; import org.junit.jupiter.api.TestInstance; -import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.web.server.LocalServerPort; @@ -60,9 +57,6 @@ public class PushServerMultipleActivationsTests { @LocalServerPort private int port; - @Value("${powerauth.service.url}") - private String powerAuthServiceUrl; - @Autowired private PushServerTestClientFactory testClientFactory; @@ -72,9 +66,6 @@ public class PushServerMultipleActivationsTests { @MockBean private PowerAuthTestClient powerAuthTestClient; - @Rule - public final ExpectedException exception = ExpectedException.none(); - @Before public void setUp() throws Exception { pushServerClient = testClientFactory.createPushServerClient("http://localhost:" + port); diff --git a/powerauth-push-server/src/test/java/io/getlime/push/client/PushServerTests.java b/powerauth-push-server/src/test/java/io/getlime/push/client/PushServerTests.java index 63beca99e..37d2671ff 100644 --- a/powerauth-push-server/src/test/java/io/getlime/push/client/PushServerTests.java +++ b/powerauth-push-server/src/test/java/io/getlime/push/client/PushServerTests.java @@ -32,10 +32,8 @@ import io.getlime.push.shared.PowerAuthTestClient; import io.getlime.push.shared.PushServerTestClientFactory; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; import org.junit.jupiter.api.TestInstance; -import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -112,9 +110,6 @@ public void setUp() throws Exception { appCredentialsRepository.save(testCredentials); } - @Rule - public final ExpectedException exception = ExpectedException.none(); - @Test public void getServiceStatusTest() throws Exception { ObjectResponse actual = pushServerClient.getServiceStatus(); From 551e222f84b75ef348f2825764ac30883b0308df Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Mon, 16 Nov 2020 11:52:46 +0100 Subject: [PATCH 39/51] Fix #353: Update dependencies --- pom.xml | 10 ++++------ powerauth-push-server/pom.xml | 10 ---------- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/pom.xml b/pom.xml index cb2d98577..72f3153a7 100644 --- a/pom.xml +++ b/pom.xml @@ -14,7 +14,7 @@ org.springframework.boot spring-boot-starter-parent - 2.3.4.RELEASE + 2.3.5.RELEASE @@ -75,19 +75,17 @@ 30.0-jre 1.4.199 - 4.5.13 - 4.1.4 2.11.3 2.11.3 1.2.2 1.1.0 1.0.0-SNAPSHOT 0.14.2 - 1.4.6 + 1.5.0 1.0.1.RELEASE - 1.30.11 + 1.31.0 7.0.1 - 1.66 + 1.67 diff --git a/powerauth-push-server/pom.xml b/powerauth-push-server/pom.xml index ba761de78..ba7d8237b 100644 --- a/powerauth-push-server/pom.xml +++ b/powerauth-push-server/pom.xml @@ -61,16 +61,6 @@ - - org.apache.httpcomponents - httpclient - ${httpclient.version} - - - org.apache.httpcomponents - httpasyncclient - ${httpasyncclient.version} - com.eatthepath pushy From a8277298912c4b577d37c25559734e896f78dcf8 Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Mon, 16 Nov 2020 16:55:33 +0100 Subject: [PATCH 40/51] Fix #355: Remove reactor-spring dependency --- pom.xml | 1 - powerauth-push-client/pom.xml | 5 ----- 2 files changed, 6 deletions(-) diff --git a/pom.xml b/pom.xml index 72f3153a7..a09cea3fe 100644 --- a/pom.xml +++ b/pom.xml @@ -82,7 +82,6 @@ 1.0.0-SNAPSHOT 0.14.2 1.5.0 - 1.0.1.RELEASE 1.31.0 7.0.1 1.67 diff --git a/powerauth-push-client/pom.xml b/powerauth-push-client/pom.xml index 10e10581b..8702f1cf2 100644 --- a/powerauth-push-client/pom.xml +++ b/powerauth-push-client/pom.xml @@ -32,11 +32,6 @@ org.springframework.boot spring-boot-starter-webflux - - org.projectreactor - reactor-spring - ${reactor-spring.version} - From 55b0c7273c7021cded764ab4d607b29c2bc79ab3 Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Thu, 19 Nov 2020 17:45:51 +0100 Subject: [PATCH 41/51] Fix #359: Update jQuery version --- powerauth-push-server/src/main/resources/templates/home.html | 2 +- .../src/main/webapp/resources/js/jquery-3.4.1.min.js | 2 -- .../src/main/webapp/resources/js/jquery-3.5.1.min.js | 2 ++ 3 files changed, 3 insertions(+), 3 deletions(-) delete mode 100644 powerauth-push-server/src/main/webapp/resources/js/jquery-3.4.1.min.js create mode 100644 powerauth-push-server/src/main/webapp/resources/js/jquery-3.5.1.min.js diff --git a/powerauth-push-server/src/main/resources/templates/home.html b/powerauth-push-server/src/main/resources/templates/home.html index 6f6bbf21b..d3bda9eef 100644 --- a/powerauth-push-server/src/main/resources/templates/home.html +++ b/powerauth-push-server/src/main/resources/templates/home.html @@ -80,7 +80,7 @@
Contact Us
- + \ No newline at end of file diff --git a/powerauth-push-server/src/main/webapp/resources/js/jquery-3.4.1.min.js b/powerauth-push-server/src/main/webapp/resources/js/jquery-3.4.1.min.js deleted file mode 100644 index a1c07fd80..000000000 --- a/powerauth-push-server/src/main/webapp/resources/js/jquery-3.4.1.min.js +++ /dev/null @@ -1,2 +0,0 @@ -/*! jQuery v3.4.1 | (c) JS Foundation and other contributors | jquery.org/license */ -!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],E=C.document,r=Object.getPrototypeOf,s=t.slice,g=t.concat,u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},x=function(e){return null!=e&&e===e.window},c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.4.1",k=function(e,t){return new k.fn.init(e,t)},p=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;function d(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp($),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+$),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),ne=function(e,t,n){var r="0x"+t-65536;return r!=r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(m.childNodes),m.childNodes),t[m.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&((e?e.ownerDocument||e:m)!==C&&T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!A[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&U.test(t)){(s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=k),o=(l=h(t)).length;while(o--)l[o]="#"+s+" "+xe(l[o]);c=l.join(","),f=ee.test(t)&&ye(e.parentNode)||e}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){A(t,!0)}finally{s===k&&e.removeAttribute("id")}}}return g(t.replace(B,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[k]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:m;return r!==C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),m!==C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=k,!C.getElementsByName||!C.getElementsByName(k).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+k+"-]").length||v.push("~="),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+k+"+*").length||v.push(".#.+[+~]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",$)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e===C||e.ownerDocument===m&&y(m,e)?-1:t===C||t.ownerDocument===m&&y(m,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e===C?-1:t===C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]===m?-1:s[r]===m?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if((e.ownerDocument||e)!==C&&T(e),d.matchesSelector&&E&&!A[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){A(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=p[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&p(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?k.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?k.grep(e,function(e){return e===n!==r}):"string"!=typeof n?k.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(k.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||q,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:L.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof k?t[0]:t,k.merge(this,k.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),D.test(r[1])&&k.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(k):k.makeArray(e,this)}).prototype=k.fn,q=k(E);var H=/^(?:parents|prev(?:Until|All))/,O={children:!0,contents:!0,next:!0,prev:!0};function P(e,t){while((e=e[t])&&1!==e.nodeType);return e}k.fn.extend({has:function(e){var t=k(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i,ge={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?k.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;nx",y.noCloneChecked=!!me.cloneNode(!0).lastChild.defaultValue;var Te=/^key/,Ce=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Ee=/^([^.]*)(?:\.(.+)|)/;function ke(){return!0}function Se(){return!1}function Ne(e,t){return e===function(){try{return E.activeElement}catch(e){}}()==("focus"===t)}function Ae(e,t,n,r,i,o){var a,s;if("object"==typeof t){for(s in"string"!=typeof n&&(r=r||n,n=void 0),t)Ae(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=Se;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return k().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=k.guid++)),e.each(function(){k.event.add(this,t,i,r,n)})}function De(e,i,o){o?(Q.set(e,i,!1),k.event.add(e,i,{namespace:!1,handler:function(e){var t,n,r=Q.get(this,i);if(1&e.isTrigger&&this[i]){if(r.length)(k.event.special[i]||{}).delegateType&&e.stopPropagation();else if(r=s.call(arguments),Q.set(this,i,r),t=o(this,i),this[i](),r!==(n=Q.get(this,i))||t?Q.set(this,i,!1):n={},r!==n)return e.stopImmediatePropagation(),e.preventDefault(),n.value}else r.length&&(Q.set(this,i,{value:k.event.trigger(k.extend(r[0],k.Event.prototype),r.slice(1),this)}),e.stopImmediatePropagation())}})):void 0===Q.get(e,i)&&k.event.add(e,i,ke)}k.event={global:{},add:function(t,e,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Q.get(t);if(v){n.handler&&(n=(o=n).handler,i=o.selector),i&&k.find.matchesSelector(ie,i),n.guid||(n.guid=k.guid++),(u=v.events)||(u=v.events={}),(a=v.handle)||(a=v.handle=function(e){return"undefined"!=typeof k&&k.event.triggered!==e.type?k.event.dispatch.apply(t,arguments):void 0}),l=(e=(e||"").match(R)||[""]).length;while(l--)d=g=(s=Ee.exec(e[l])||[])[1],h=(s[2]||"").split(".").sort(),d&&(f=k.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=k.event.special[d]||{},c=k.extend({type:d,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&k.expr.match.needsContext.test(i),namespace:h.join(".")},o),(p=u[d])||((p=u[d]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(t,r,h,a)||t.addEventListener&&t.addEventListener(d,a)),f.add&&(f.add.call(t,c),c.handler.guid||(c.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,c):p.push(c),k.event.global[d]=!0)}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Q.hasData(e)&&Q.get(e);if(v&&(u=v.events)){l=(t=(t||"").match(R)||[""]).length;while(l--)if(d=g=(s=Ee.exec(t[l])||[])[1],h=(s[2]||"").split(".").sort(),d){f=k.event.special[d]||{},p=u[d=(r?f.delegateType:f.bindType)||d]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=p.length;while(o--)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));a&&!p.length&&(f.teardown&&!1!==f.teardown.call(e,h,v.handle)||k.removeEvent(e,d,v.handle),delete u[d])}else for(d in u)k.event.remove(e,d+t[l],n,r,!0);k.isEmptyObject(u)&&Q.remove(e,"handle events")}},dispatch:function(e){var t,n,r,i,o,a,s=k.event.fix(e),u=new Array(arguments.length),l=(Q.get(this,"events")||{})[s.type]||[],c=k.event.special[s.type]||{};for(u[0]=s,t=1;t\x20\t\r\n\f]*)[^>]*)\/>/gi,qe=/\s*$/g;function Oe(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&k(e).children("tbody")[0]||e}function Pe(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function Re(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Me(e,t){var n,r,i,o,a,s,u,l;if(1===t.nodeType){if(Q.hasData(e)&&(o=Q.access(e),a=Q.set(t,o),l=o.events))for(i in delete a.handle,a.events={},l)for(n=0,r=l[i].length;n")},clone:function(e,t,n){var r,i,o,a,s,u,l,c=e.cloneNode(!0),f=oe(e);if(!(y.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||k.isXMLDoc(e)))for(a=ve(c),r=0,i=(o=ve(e)).length;r").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Vt,Gt=[],Yt=/(=)\?(?=&|$)|\?\?/;k.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Gt.pop()||k.expando+"_"+kt++;return this[e]=!0,e}}),k.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Yt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Yt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Yt,"$1"+r):!1!==e.jsonp&&(e.url+=(St.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||k.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?k(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Gt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((Vt=E.implementation.createHTMLDocument("").body).innerHTML="
",2===Vt.childNodes.length),k.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=D.exec(e))?[t.createElement(i[1])]:(i=we([e],t,o),o&&o.length&&k(o).remove(),k.merge([],i.childNodes)));var r,i,o},k.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(k.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},k.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){k.fn[t]=function(e){return this.on(t,e)}}),k.expr.pseudos.animated=function(t){return k.grep(k.timers,function(e){return t===e.elem}).length},k.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=k.css(e,"position"),c=k(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=k.css(e,"top"),u=k.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,k.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},k.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){k.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===k.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===k.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=k(e).offset()).top+=k.css(e,"borderTopWidth",!0),i.left+=k.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-k.css(r,"marginTop",!0),left:t.left-i.left-k.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===k.css(e,"position"))e=e.offsetParent;return e||ie})}}),k.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;k.fn[t]=function(e){return _(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),k.each(["top","left"],function(e,n){k.cssHooks[n]=ze(y.pixelPosition,function(e,t){if(t)return t=_e(e,n),$e.test(t)?k(e).position()[n]+"px":t})}),k.each({Height:"height",Width:"width"},function(a,s){k.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){k.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return _(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?k.css(e,t,i):k.style(e,t,n,i)},s,n?e:void 0,n)}})}),k.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){k.fn[n]=function(e,t){return 0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function D(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||j,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,j=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function qe(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function Le(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function He(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Oe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Ut,Xt=[],Vt=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Xt.pop()||S.expando+"_"+Ct.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Vt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Vt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Vt,"$1"+r):!1!==e.jsonp&&(e.url+=(Et.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Xt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((Ut=E.implementation.createHTMLDocument("").body).innerHTML="
",2===Ut.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):("number"==typeof f.top&&(f.top+="px"),"number"==typeof f.left&&(f.left+="px"),c.css(f))}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=$e(y.pixelPosition,function(e,t){if(t)return t=Be(e,n),Me.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0 Date: Tue, 8 Dec 2020 16:15:12 +0100 Subject: [PATCH 42/51] Migrate REST clients to rest-client-base --- pom.xml | 2 +- powerauth-push-client/pom.xml | 4 - .../getlime/push/client/PushServerClient.java | 106 ++++++------------ powerauth-push-model/pom.xml | 7 +- powerauth-push-server/pom.xml | 4 - .../PowerAuthWebServiceConfiguration.java | 12 +- .../push/service/PushSendingWorker.java | 16 +-- .../getlime/push/service/fcm/FcmClient.java | 81 ++++++------- .../PushSeverTestConfiguration.java | 3 +- .../push/shared/PowerAuthTestClient.java | 2 +- .../shared/PushServerTestClientFactory.java | 3 +- 11 files changed, 100 insertions(+), 140 deletions(-) diff --git a/pom.xml b/pom.xml index cb2d98577..2d0cb8b0d 100644 --- a/pom.xml +++ b/pom.xml @@ -80,7 +80,7 @@ 2.11.3 2.11.3 1.2.2 - 1.1.0 + 1.2.0-SNAPSHOT 1.0.0-SNAPSHOT 0.14.2 1.4.6 diff --git a/powerauth-push-client/pom.xml b/powerauth-push-client/pom.xml index 10e10581b..f4d6db386 100644 --- a/powerauth-push-client/pom.xml +++ b/powerauth-push-client/pom.xml @@ -28,10 +28,6 @@ org.springframework.boot spring-boot-starter
- - org.springframework.boot - spring-boot-starter-webflux - org.projectreactor reactor-spring 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 2fa588d67..877dea724 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 @@ -17,9 +17,11 @@ package io.getlime.push.client; import com.google.common.io.BaseEncoding; +import com.wultra.core.rest.client.base.DefaultRestClient; +import com.wultra.core.rest.client.base.RestClient; +import com.wultra.core.rest.client.base.RestClientException; import io.getlime.core.rest.model.base.entity.Error; import io.getlime.core.rest.model.base.request.ObjectRequest; -import io.getlime.core.rest.model.base.response.ErrorResponse; import io.getlime.core.rest.model.base.response.ObjectResponse; import io.getlime.core.rest.model.base.response.Response; import io.getlime.push.model.base.PagedResponse; @@ -33,20 +35,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.ParameterizedTypeReference; -import org.springframework.http.HttpMethod; -import org.springframework.http.MediaType; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; -import org.springframework.web.reactive.function.BodyInserters; -import org.springframework.web.reactive.function.client.ClientResponse; -import org.springframework.web.reactive.function.client.WebClient; -import reactor.core.publisher.Mono; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.Collections; import java.util.List; -import java.util.Objects; /** * Simple class for interacting with the push server RESTful API. @@ -58,14 +53,19 @@ public class PushServerClient { private static final Logger logger = LoggerFactory.getLogger(PushServerClient.class); - private final WebClient webClient; + private final RestClient restClient; /** * Main constructor with the push server base URL. * @param serviceBaseUrl Push server instance base URL. + * @throws PushServerClientException Thrown in case REST client initialization fails. */ - public PushServerClient(String serviceBaseUrl) { - this.webClient = WebClient.builder().baseUrl(serviceBaseUrl).build(); + public PushServerClient(String serviceBaseUrl) throws PushServerClientException { + try { + this.restClient = DefaultRestClient.builder().baseUrl(serviceBaseUrl).build(); + } catch (RestClientException ex) { + throw new PushServerClientException("Rest client initialization failed, error: " + ex.getMessage()); + } } // Client calls @@ -624,14 +624,13 @@ public Response removeAndroid(Long id) throws PushServerClientException { * @throws PushServerClientException In case of network, response / JSON processing, or other IO error. * */ - private T getObjectImpl(String url, MultiValueMap params, ParameterizedTypeReference typeReference) throws PushServerClientException { - ClientResponse response = webClient.get() - .uri(uriBuilder -> uriBuilder.path(url).queryParams(params).build()) - .accept(MediaType.APPLICATION_JSON) - .exchange() - .block(); - return checkHttpStatus(typeReference, Objects.requireNonNull(response)); - + private T getObjectImpl(String url, MultiValueMap params, ParameterizedTypeReference typeReference) throws PushServerClientException { + try { + return restClient.get(url, params, null, typeReference).getBody(); + } catch (RestClientException ex) { + logger.warn(ex.getMessage(), ex); + throw new PushServerClientException(ex, new Error("PUSH_SERVER_CLIENT_ERROR", "HTTP GET request failed.")); + } } /** @@ -642,7 +641,7 @@ private T getObjectImpl(String url, MultiValueMap T postObjectImpl(String url, Object request) throws PushServerClientException { + private T postObjectImpl(String url, Object request) throws PushServerClientException { return postObjectImpl(url, request, new ParameterizedTypeReference() {}); } @@ -655,8 +654,13 @@ private T postObjectImpl(String url, Object request) throws * @return Object obtained after processing the response JSON. * @throws PushServerClientException In case of network, response / JSON processing, or other IO error. */ - private T postObjectImpl(String url, Object request, ParameterizedTypeReference typeReference) throws PushServerClientException { - return httpExchangeObjectImpl(HttpMethod.POST, url, request, typeReference); + private T postObjectImpl(String url, Object request, ParameterizedTypeReference typeReference) throws PushServerClientException { + try { + return restClient.post(url, request, typeReference).getBody(); + } catch (RestClientException ex) { + logger.warn(ex.getMessage(), ex); + throw new PushServerClientException(ex, new Error("PUSH_SERVER_CLIENT_ERROR", "HTTP POST request failed.")); + } } /** @@ -667,7 +671,7 @@ private T postObjectImpl(String url, Object request, Parame * @return Object obtained after processing the response JSON. * @throws PushServerClientException In case of network, response / JSON processing, or other IO error. */ - private T putObjectImpl(String url, Object request) throws PushServerClientException { + private T putObjectImpl(String url, Object request) throws PushServerClientException { return putObjectImpl(url, request, new ParameterizedTypeReference() {}); } @@ -680,56 +684,12 @@ private T putObjectImpl(String url, Object request) throws * @return Object obtained after processing the response JSON. * @throws PushServerClientException In case of network, response / JSON processing, or other IO error. */ - private T putObjectImpl(String url, Object request, ParameterizedTypeReference typeReference) throws PushServerClientException { - return httpExchangeObjectImpl(HttpMethod.PUT, url, request, typeReference); - } - - /** - * Exchange object response. - * - * @param method HTTP method - * @param url specific url of method - * @param request request body - * @param typeReference reference on type for parsing into JSON - * @return Object obtained after processing the response JSON. - * @throws PushServerClientException In case of network, response / JSON processing, or other IO error. - */ - private T httpExchangeObjectImpl(HttpMethod method, String url, Object request, ParameterizedTypeReference typeReference) throws PushServerClientException { - WebClient.RequestBodySpec spec = webClient.method(method) - .uri(uriBuilder -> uriBuilder.path(url).build()) - .accept(MediaType.APPLICATION_JSON) - .contentType(MediaType.APPLICATION_JSON); - Mono responseMono; - if (request != null) { - responseMono = spec.body(BodyInserters.fromValue(request)).exchange(); - } else { - responseMono = spec.exchange(); - } - ClientResponse response = responseMono.block(); - return checkHttpStatus(typeReference, Objects.requireNonNull(response)); - } - - /** - * Checks response status - * - * @param typeReference reference on type of response body from which map into JSON - * @param response prepared http response - * @return In case response code is 200, returns instance of expected response type. Otherwise, it attempts to - * reconstruct error response and returns the error response. - * @throws PushServerClientException In case of network, response / JSON processing, or other IO error. - */ - private T checkHttpStatus(ParameterizedTypeReference typeReference, ClientResponse response) throws PushServerClientException { - if (response.statusCode().is2xxSuccessful()) { - return response.bodyToMono(typeReference).block(); - } else { - try { - // Response body contains data, return Exception with status code and error response - ErrorResponse errorResponse = (ErrorResponse) response.bodyToMono(typeReference).block(); - throw new PushServerClientException("Error HTTP response status code received: " + response.rawStatusCode(), Objects.requireNonNull(errorResponse).getResponseObject()); - } catch (Exception ex) { - logger.warn(ex.getMessage(), ex); - throw new PushServerClientException("Error HTTP response status code received: " + response.rawStatusCode() + ". Check server log for error details."); - } + private T putObjectImpl(String url, Object request, ParameterizedTypeReference typeReference) throws PushServerClientException { + try { + return restClient.put(url, request, typeReference).getBody(); + } catch (RestClientException ex) { + logger.warn(ex.getMessage(), ex); + throw new PushServerClientException(ex, new Error("PUSH_SERVER_CLIENT_ERROR", "HTTP PUT request failed.")); } } diff --git a/powerauth-push-model/pom.xml b/powerauth-push-model/pom.xml index a5051629f..0f6de80f1 100644 --- a/powerauth-push-model/pom.xml +++ b/powerauth-push-model/pom.xml @@ -19,7 +19,12 @@ io.getlime.core rest-model-base - ${lime.rest.version} + ${rest-base.version} + + + io.getlime.core + rest-client-base + ${rest-base.version} io.getlime.security diff --git a/powerauth-push-server/pom.xml b/powerauth-push-server/pom.xml index ba761de78..66aa5f3de 100644 --- a/powerauth-push-server/pom.xml +++ b/powerauth-push-server/pom.xml @@ -40,10 +40,6 @@ spring-boot-starter-tomcat provided - - org.springframework.boot - spring-boot-starter-webflux - org.springframework.boot spring-boot-starter-thymeleaf diff --git a/powerauth-push-server/src/main/java/io/getlime/push/configuration/PowerAuthWebServiceConfiguration.java b/powerauth-push-server/src/main/java/io/getlime/push/configuration/PowerAuthWebServiceConfiguration.java index dfc652f02..1bfe23bc7 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/configuration/PowerAuthWebServiceConfiguration.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/configuration/PowerAuthWebServiceConfiguration.java @@ -16,8 +16,11 @@ package io.getlime.push.configuration; import com.wultra.security.powerauth.client.PowerAuthClient; +import com.wultra.security.powerauth.client.model.error.PowerAuthClientException; import com.wultra.security.powerauth.rest.client.PowerAuthRestClient; import com.wultra.security.powerauth.rest.client.PowerAuthRestClientConfiguration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; @@ -32,6 +35,8 @@ @ComponentScan(basePackages = {"io.getlime.security", "io.getlime.push"}) public class PowerAuthWebServiceConfiguration { + private static final Logger logger = LoggerFactory.getLogger(PowerAuthWebServiceConfiguration.class); + @Value("${powerauth.service.url}") private String powerAuthRestUrl; @@ -54,7 +59,12 @@ public PowerAuthClient powerAuthClient() { config.setPowerAuthClientToken(clientToken); config.setPowerAuthClientSecret(clientSecret); config.setAcceptInvalidSslCertificate(acceptInvalidSslCertificate); - return new PowerAuthRestClient(powerAuthRestUrl, config); + try { + return new PowerAuthRestClient(powerAuthRestUrl, config); + } catch (PowerAuthClientException ex) { + logger.error(ex.getMessage(), ex); + return null; + } } } 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 3fbfe33cf..3e40c1d04 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 @@ -16,17 +16,17 @@ package io.getlime.push.service; -import com.eatthepath.pushy.apns.util.SimpleApnsPayloadBuilder; -import com.google.firebase.messaging.AndroidConfig; -import com.google.firebase.messaging.AndroidNotification; -import com.google.firebase.messaging.Message; import com.eatthepath.pushy.apns.*; import com.eatthepath.pushy.apns.auth.ApnsSigningKey; import com.eatthepath.pushy.apns.proxy.HttpProxyHandlerFactory; import com.eatthepath.pushy.apns.util.ApnsPayloadBuilder; +import com.eatthepath.pushy.apns.util.SimpleApnsPayloadBuilder; import com.eatthepath.pushy.apns.util.SimpleApnsPushNotification; import com.eatthepath.pushy.apns.util.TokenUtil; import com.eatthepath.pushy.apns.util.concurrent.PushNotificationFuture; +import com.google.firebase.messaging.AndroidConfig; +import com.google.firebase.messaging.AndroidNotification; +import com.google.firebase.messaging.Message; import io.getlime.push.configuration.PushServiceConfiguration; import io.getlime.push.errorhandling.exceptions.FcmMissingTokenException; import io.getlime.push.errorhandling.exceptions.PushServerException; @@ -41,6 +41,7 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.web.reactive.function.client.ClientResponse; import org.springframework.web.reactive.function.client.WebClientResponseException; import javax.net.ssl.SSLException; @@ -132,9 +133,10 @@ void sendMessageToAndroid(final FcmClient fcmClient, final PushMessageBody pushM Message message = buildAndroidMessage(pushMessageBody, attributes, pushToken); // Callback when FCM request succeeds - Consumer onSuccess = body -> { - if (body.getName() != null && body.getName().matches(FCM_RESPONSE_VALID_REGEXP)) { - logger.info("Notification sent, response: {}", body.getName()); + Consumer onSuccess = body -> { + FcmSuccessResponse response = body.bodyToMono(FcmSuccessResponse.class).block(); + if (response != null && response.getName() != null && response.getName().matches(FCM_RESPONSE_VALID_REGEXP)) { + logger.info("Notification sent, response: {}", response.getName()); callback.didFinishSendingMessage(PushSendingCallback.Result.OK); return; } diff --git a/powerauth-push-server/src/main/java/io/getlime/push/service/fcm/FcmClient.java b/powerauth-push-server/src/main/java/io/getlime/push/service/fcm/FcmClient.java index a2d24b294..74a6e2c5a 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/service/fcm/FcmClient.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/service/fcm/FcmClient.java @@ -21,21 +21,20 @@ import com.google.auth.oauth2.AccessToken; import com.google.auth.oauth2.GoogleCredentials; import com.google.firebase.messaging.Message; +import com.wultra.core.rest.client.base.DefaultRestClient; +import com.wultra.core.rest.client.base.RestClient; +import com.wultra.core.rest.client.base.RestClientException; import io.getlime.push.configuration.PushServiceConfiguration; import io.getlime.push.errorhandling.exceptions.FcmInitializationFailedException; import io.getlime.push.errorhandling.exceptions.FcmMissingTokenException; -import io.getlime.push.service.fcm.model.FcmSuccessResponse; -import io.netty.channel.ChannelOption; +import io.getlime.push.errorhandling.exceptions.PushServerException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.io.buffer.DataBuffer; -import org.springframework.http.MediaType; -import org.springframework.http.client.reactive.ReactorClientHttpConnector; -import org.springframework.web.reactive.function.BodyInserters; -import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.reactive.function.client.ClientResponse; import reactor.core.publisher.Flux; -import reactor.netty.http.client.HttpClient; -import reactor.netty.tcp.ProxyProvider; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -74,8 +73,8 @@ public class FcmClient { // FCM converter for model classes private final FcmModelConverter fcmConverter; - // WebClient instance - private WebClient webClient; + // RestClient instance + private RestClient restClient; // Proxy settings private String proxyHost; @@ -107,29 +106,22 @@ public void setProxySettings(String proxyHost, int proxyPort, String proxyUserna /** * Initialize WebClient instance and configure it based on client configuration. + * @throws PushServerException Thrown in case REST client initialization fails. */ - public void initializeWebClient() { - HttpClient httpClient = HttpClient.create() - .tcpConfiguration(tcpClient -> { - tcpClient = tcpClient.option( - ChannelOption.CONNECT_TIMEOUT_MILLIS, - pushServiceConfiguration.getFcmConnectTimeout()); - if (proxyHost != null) { - tcpClient = tcpClient.proxy(proxySpec -> { - ProxyProvider.Builder builder = proxySpec - .type(ProxyProvider.Proxy.HTTP) - .host(proxyHost) - .port(proxyPort); - if (proxyUsername != null) { - builder.username(proxyUsername); - builder.password(s -> proxyPassword); - } - builder.build(); - }); - } - return tcpClient; - }); - webClient = WebClient.builder().clientConnector(new ReactorClientHttpConnector(httpClient)).build(); + public void initializeWebClient() throws PushServerException { + DefaultRestClient.Builder builder = DefaultRestClient.builder() + .connectionTimeout(pushServiceConfiguration.getFcmConnectTimeout()); + if (proxyHost != null) { + DefaultRestClient.ProxyBuilder proxyBuilder = builder.proxy().host(proxyHost).port(proxyPort); + if (proxyUsername != null) { + proxyBuilder.username(proxyUsername).password(proxyPassword); + } + } + try { + restClient = builder.build(); + } catch (RestClientException ex) { + throw new PushServerException("REST client initialization failed", ex); + } } /** @@ -210,9 +202,9 @@ private AccessToken getAccessToken() throws FcmMissingTokenException { * @param onError Callback called when request fails. * @throws FcmMissingTokenException Thrown when FCM is not configured. */ - public void exchange(Message message, boolean validationOnly, Consumer onSuccess, Consumer onError) throws FcmMissingTokenException { - if (webClient == null) { - logger.error("Push message delivery failed because WebClient is not initialized."); + public void exchange(Message message, boolean validationOnly, Consumer onSuccess, Consumer onError) throws FcmMissingTokenException { + if (restClient == null) { + logger.error("Push message delivery failed because RestClient is not initialized."); return; } if (projectId == null) { @@ -230,21 +222,18 @@ public void exchange(Message message, boolean validationOnly, Consumer headers = new LinkedMultiValueMap<>(); if (accessToken != null) { - requestBody.header("Authorization", "Bearer " + accessToken.getTokenValue()); + headers.add("Authorization", "Bearer " + accessToken.getTokenValue()); } - requestBody - .contentType(MediaType.APPLICATION_JSON) - .body(BodyInserters.fromDataBuffers(body)) - .retrieve() - .bodyToMono(FcmSuccessResponse.class) - .subscribe(onSuccess, onError); + try { + restClient.postNonBlocking(fcmSendMessageUrl, body, null, headers, onSuccess, onError); + } catch (RestClientException ex) { + logger.debug(ex.getMessage(), ex); + logger.error("Push message delivery failed because of a RestClient error: " + ex.getMessage()); + } } } diff --git a/powerauth-push-server/src/test/java/io/getlime/push/configuration/PushSeverTestConfiguration.java b/powerauth-push-server/src/test/java/io/getlime/push/configuration/PushSeverTestConfiguration.java index e9939a249..993e6c4a4 100644 --- a/powerauth-push-server/src/test/java/io/getlime/push/configuration/PushSeverTestConfiguration.java +++ b/powerauth-push-server/src/test/java/io/getlime/push/configuration/PushSeverTestConfiguration.java @@ -16,6 +16,7 @@ package io.getlime.push.configuration; import io.getlime.push.client.PushServerClient; +import io.getlime.push.client.PushServerClientException; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -36,7 +37,7 @@ public class PushSeverTestConfiguration { * @return Push server client. */ @Bean - public PushServerClient pushServerClient() { + public PushServerClient pushServerClient() throws PushServerClientException { return new PushServerClient(pushServiceUrl); } } diff --git a/powerauth-push-server/src/test/java/io/getlime/push/shared/PowerAuthTestClient.java b/powerauth-push-server/src/test/java/io/getlime/push/shared/PowerAuthTestClient.java index 1261d93e2..22de53b86 100644 --- a/powerauth-push-server/src/test/java/io/getlime/push/shared/PowerAuthTestClient.java +++ b/powerauth-push-server/src/test/java/io/getlime/push/shared/PowerAuthTestClient.java @@ -44,7 +44,7 @@ public class PowerAuthTestClient { private final ObjectMapper objectMapper = new ObjectMapper(); - public void initializeClient(String powerAuthRestUrl) { + public void initializeClient(String powerAuthRestUrl) throws PowerAuthClientException { powerAuthClient = new PowerAuthRestClient(powerAuthRestUrl); } diff --git a/powerauth-push-server/src/test/java/io/getlime/push/shared/PushServerTestClientFactory.java b/powerauth-push-server/src/test/java/io/getlime/push/shared/PushServerTestClientFactory.java index d26a44c7d..22fd0b047 100644 --- a/powerauth-push-server/src/test/java/io/getlime/push/shared/PushServerTestClientFactory.java +++ b/powerauth-push-server/src/test/java/io/getlime/push/shared/PushServerTestClientFactory.java @@ -1,6 +1,7 @@ package io.getlime.push.shared; import io.getlime.push.client.PushServerClient; +import io.getlime.push.client.PushServerClientException; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; @@ -17,7 +18,7 @@ public class PushServerTestClientFactory { @Value("${powerauth.service.url}") private String powerAuthRestUrl; - public PushServerClient createPushServerClient(String baseUrl) { + public PushServerClient createPushServerClient(String baseUrl) throws PushServerClientException { return new PushServerClient(baseUrl); } From 88b05ff04340a38d799278cbe3f9086b6f5f6836 Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Tue, 8 Dec 2020 17:20:06 +0100 Subject: [PATCH 43/51] Fix method name and JavaDoc --- .../main/java/io/getlime/push/service/PushSendingWorker.java | 2 +- .../src/main/java/io/getlime/push/service/fcm/FcmClient.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/powerauth-push-server/src/main/java/io/getlime/push/service/PushSendingWorker.java b/powerauth-push-server/src/main/java/io/getlime/push/service/PushSendingWorker.java index 3e40c1d04..8dc64307c 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 @@ -105,7 +105,7 @@ FcmClient prepareFcmClient(String projectId, byte[] privateKey) throws PushServe } fcmClient.setProxySettings(proxyHost, proxyPort, proxyUsername, proxyPassword); } - fcmClient.initializeWebClient(); + fcmClient.initializeRestClient(); String fcmUrl = pushServiceConfiguration.getFcmSendMessageUrl(); if (fcmUrl.contains("projects/%s/")) { // Initialize Google Credential for production FCM URL diff --git a/powerauth-push-server/src/main/java/io/getlime/push/service/fcm/FcmClient.java b/powerauth-push-server/src/main/java/io/getlime/push/service/fcm/FcmClient.java index 74a6e2c5a..d8a89736b 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/service/fcm/FcmClient.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/service/fcm/FcmClient.java @@ -105,10 +105,10 @@ public void setProxySettings(String proxyHost, int proxyPort, String proxyUserna } /** - * Initialize WebClient instance and configure it based on client configuration. + * Initialize RestClient instance and configure it based on client configuration. * @throws PushServerException Thrown in case REST client initialization fails. */ - public void initializeWebClient() throws PushServerException { + public void initializeRestClient() throws PushServerException { DefaultRestClient.Builder builder = DefaultRestClient.builder() .connectionTimeout(pushServiceConfiguration.getFcmConnectTimeout()); if (proxyHost != null) { From 770d72b665a3cdacdb6604442ecc4aad2dd8cc0b Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Wed, 9 Dec 2020 13:33:54 +0100 Subject: [PATCH 44/51] Fix #365: Use object methods from REST client --- .../getlime/push/client/PushServerClient.java | 117 +++++++++++------- .../getlime/push/service/fcm/FcmClient.java | 9 +- .../push/service/fcm/FcmModelConverter.java | 36 ------ .../fcm/model/MessageWithValidation.java | 77 ++++++++++++ 4 files changed, 152 insertions(+), 87 deletions(-) create mode 100644 powerauth-push-server/src/main/java/io/getlime/push/service/fcm/model/MessageWithValidation.java 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 877dea724..5ab1c9911 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 @@ -77,10 +77,9 @@ public PushServerClient(String serviceBaseUrl) throws PushServerClientException * @throws PushServerClientException In case of network, response / JSON processing, or other IO error. */ public ObjectResponse getServiceStatus() throws PushServerClientException { - ParameterizedTypeReference> typeReference = new ParameterizedTypeReference>() {}; logger.info("Calling push server status service - start"); - final ObjectResponse result = getObjectImpl("/push/service/status", null, typeReference); + final ObjectResponse result = getObjectImpl("/push/service/status", null, ServiceStatusResponse.class); logger.info("Calling push server status service - finish"); return result; @@ -205,7 +204,7 @@ public boolean updateDeviceStatus(String activationId) throws PushServerClientEx logger.info("Calling push server update device status, activation ID: {} - start", 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. - Response response = postObjectImpl("/push/device/status/update", request); + Response response = postImpl("/push/device/status/update", request, new ParameterizedTypeReference(){}); logger.info("Calling push server update device status, activation ID: {} - finish", activationId); return response.getStatus().equals(Response.Status.OK); @@ -230,10 +229,8 @@ public ObjectResponse sendPushMessage(Long appId, PushMes throw new PushServerClientException(error); } - ParameterizedTypeReference> typeReference = new ParameterizedTypeReference>() {}; - logger.info("Calling push server to send a push message, app ID: {}, user ID: {} - start", appId, pushMessage.getUserId()); - final ObjectResponse result = postObjectImpl("/push/message/send", new ObjectRequest<>(request), typeReference); + 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()); return result; @@ -258,10 +255,8 @@ public ObjectResponse sendPushMessageBatch(Long appId, Li throw new PushServerClientException(error); } - ParameterizedTypeReference> typeReference = new ParameterizedTypeReference>() {}; - logger.info("Calling push server to send a push message batch, app ID: {} - start", appId); - final ObjectResponse result = postObjectImpl("/push/message/batch/send", new ObjectRequest<>(request), typeReference); + 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); return result; @@ -286,10 +281,8 @@ public ObjectResponse createCampaign(Long appId, PushMes throw new PushServerClientException(error); } - ParameterizedTypeReference> typeReference = new ParameterizedTypeReference>() {}; - logger.info("Calling push server to create a push campaign, app ID: {} - start", appId); - final ObjectResponse result = postObjectImpl("/push/campaign/create", new ObjectRequest<>(request), typeReference); + 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); return result; @@ -306,10 +299,8 @@ public boolean deleteCampaign(Long campaignId) throws PushServerClientException try { String campaignIdSanitized = URLEncoder.encode(String.valueOf(campaignId), "UTF-8"); - ParameterizedTypeReference> typeReference = new ParameterizedTypeReference>() {}; - logger.info("Calling push server to delete a push campaign, campaign ID: {} - start", campaignId); - ObjectResponse response = postObjectImpl("/push/campaign/" + campaignIdSanitized + "/delete", null, typeReference); + ObjectResponse response = postObjectImpl("/push/campaign/" + campaignIdSanitized + "/delete", null, DeleteCampaignResponse.class); logger.info("Calling push server to delete a push campaign, campaign ID: {} - finish", campaignId); return response.getStatus().equals(Response.Status.OK); @@ -329,10 +320,8 @@ public ObjectResponse getListOfCampaigns(boolean all) t MultiValueMap params = new LinkedMultiValueMap<>(); params.put("all", Collections.singletonList(Boolean.valueOf(all).toString())); - ParameterizedTypeReference> typeReference = new ParameterizedTypeReference>() {}; - logger.info("Calling push server to obtain a push campaign list - start"); - final ObjectResponse result = getObjectImpl("/push/campaign/list", params, typeReference); + final ObjectResponse result = getObjectImpl("/push/campaign/list", params, ListOfCampaignsResponse.class); logger.info("Calling push server to obtain a push campaign list - finish"); return result; @@ -349,10 +338,8 @@ public ObjectResponse getCampaign(Long campaignId) throws Push try { String campaignIdSanitized = URLEncoder.encode(String.valueOf(campaignId), "UTF-8"); - ParameterizedTypeReference> typeReference = new ParameterizedTypeReference>() {}; - logger.info("Calling push server to obtain a push campaign detail, campaign ID: {} - start", campaignId); - final ObjectResponse result = getObjectImpl("/push/campaign/" + campaignIdSanitized + "/detail", null, typeReference); + 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); return result; @@ -405,9 +392,8 @@ public PagedResponse getListOfUsersFromCampaign params.put("size", Collections.singletonList(Integer.valueOf(size).toString())); ParameterizedTypeReference> typeReference = new ParameterizedTypeReference>() {}; - logger.info("Calling push server to get users from the campaign, campaign ID: {} - start", campaignId); - final PagedResponse result = getObjectImpl("/push/campaign/" + campaignIdSanitized + "/user/list", params, typeReference); + 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); return result; @@ -496,9 +482,8 @@ public boolean sendCampaign(Long campaignId) throws PushServerClientException { * @throws PushServerClientException Thrown when communication with Push Server fails. */ public ObjectResponse getApplicationList() throws PushServerClientException { - final ParameterizedTypeReference> typeReference = new ParameterizedTypeReference>() {}; logger.info("Calling push server to retrieve list of applications - start"); - final ObjectResponse response = getObjectImpl("/admin/app/list", null, typeReference); + final ObjectResponse response = getObjectImpl("/admin/app/list", null, GetApplicationListResponse.class); logger.info("Calling push server to retrieve list of applications - finish"); return response; } @@ -509,9 +494,8 @@ public ObjectResponse getApplicationList() throws Pu * @throws PushServerClientException Thrown when communication with Push Server fails. */ public ObjectResponse getUnconfiguredApplicationList() throws PushServerClientException { - final ParameterizedTypeReference> typeReference = new ParameterizedTypeReference>() {}; logger.info("Calling push server to retrieve list of unconfigured applications - start"); - final ObjectResponse response = getObjectImpl("/admin/app/unconfigured/list", null, typeReference); + final ObjectResponse response = getObjectImpl("/admin/app/unconfigured/list", null, GetApplicationListResponse.class); logger.info("Calling push server to retrieve list of unconfigured applications - finish"); return response; } @@ -525,10 +509,9 @@ public ObjectResponse getUnconfiguredApplicationList * @throws PushServerClientException Thrown when communication with Push Server fails. */ public ObjectResponse getApplicationDetail(Long id, boolean includeIos, boolean includeAndroid) throws PushServerClientException { - final ParameterizedTypeReference> typeReference = new ParameterizedTypeReference>() {}; GetApplicationDetailRequest request = new GetApplicationDetailRequest(id, includeIos, includeAndroid); logger.info("Calling push server to retrieve application detail, ID: {} - start", id); - final ObjectResponse response = postObjectImpl("/admin/app/detail", new ObjectRequest<>(request), typeReference); + final ObjectResponse response = postObjectImpl("/admin/app/detail", new ObjectRequest<>(request), GetApplicationDetailResponse.class); logger.info("Calling push server to retrieve application detail, ID: {} - finish", id); return response; } @@ -540,10 +523,9 @@ public ObjectResponse getApplicationDetail(Long id * @throws PushServerClientException Thrown when communication with Push Server fails. */ public ObjectResponse createApplication(Long appId) throws PushServerClientException { - final ParameterizedTypeReference> typeReference = new ParameterizedTypeReference>() {}; final CreateApplicationRequest request = new CreateApplicationRequest(appId); logger.info("Calling push server to create application, app ID: {} - start", appId); - final ObjectResponse response = postObjectImpl("/admin/app/create", new ObjectRequest<>(request), typeReference); + final ObjectResponse response = postObjectImpl("/admin/app/create", new ObjectRequest<>(request), CreateApplicationResponse.class); logger.info("Calling push server to create application, app ID: {} - finish", appId); return response; } @@ -615,16 +597,16 @@ public Response removeAndroid(Long id) throws PushServerClientException { // Generic HTTP client methods /** - * Prepare GET object response. + * Prepare GET response. * * @param url specific url of method. * @param params params to pass to url path, optional. - * @param typeReference reference on type for parsing into JSON. + * @param typeReference response type reference. * @return Object obtained after processing the response JSON. * @throws PushServerClientException In case of network, response / JSON processing, or other IO error. * */ - private T getObjectImpl(String url, MultiValueMap params, ParameterizedTypeReference typeReference) throws PushServerClientException { + private T getImpl(String url, MultiValueMap params, ParameterizedTypeReference typeReference) throws PushServerClientException { try { return restClient.get(url, params, null, typeReference).getBody(); } catch (RestClientException ex) { @@ -633,6 +615,43 @@ private T getObjectImpl(String url, MultiValueMap params, Pa } } + /** + * Prepare GET object response. + * + * @param url specific url of method. + * @param params params to pass to url path, optional. + * @param responseType response type. + * @return Object obtained after processing the response JSON. + * @throws PushServerClientException In case of network, response / JSON processing, or other IO error. + * + */ + private ObjectResponse getObjectImpl(String url, MultiValueMap params, Class responseType) throws PushServerClientException { + try { + return restClient.getObject(url, params, null, responseType); + } catch (RestClientException ex) { + logger.warn(ex.getMessage(), ex); + throw new PushServerClientException(ex, new Error("PUSH_SERVER_CLIENT_ERROR", "HTTP GET request failed.")); + } + } + + /** + * Prepare a generic POST response. + * + * @param url specific url of method + * @param request request body + * @param typeReference type reference + * @return Object obtained after processing the response JSON. + * @throws PushServerClientException In case of network, response / JSON processing, or other IO error. + */ + private T postImpl(String url, Object request, ParameterizedTypeReference typeReference) throws PushServerClientException { + try { + return restClient.post(url, request, typeReference).getBody(); + } catch (RestClientException ex) { + logger.warn(ex.getMessage(), ex); + throw new PushServerClientException(ex, new Error("PUSH_SERVER_CLIENT_ERROR", "HTTP POST request failed.")); + } + } + /** * Prepare POST object response. Uses default {@link Response} type reference for response. * @@ -641,8 +660,13 @@ private T getObjectImpl(String url, MultiValueMap params, Pa * @return Object obtained after processing the response JSON. * @throws PushServerClientException In case of network, response / JSON processing, or other IO error. */ - private T postObjectImpl(String url, Object request) throws PushServerClientException { - return postObjectImpl(url, request, new ParameterizedTypeReference() {}); + private Response postObjectImpl(String url, ObjectRequest request) throws PushServerClientException { + try { + return restClient.postObject(url, request); + } catch (RestClientException ex) { + logger.warn(ex.getMessage(), ex); + throw new PushServerClientException(ex, new Error("PUSH_SERVER_CLIENT_ERROR", "HTTP POST request failed.")); + } } /** @@ -650,13 +674,13 @@ private T postObjectImpl(String url, Object request) throws PushServerClient * * @param url specific url of method * @param request request body - * @param typeReference reference on type for parsing into JSON + * @param responseType response type * @return Object obtained after processing the response JSON. * @throws PushServerClientException In case of network, response / JSON processing, or other IO error. */ - private T postObjectImpl(String url, Object request, ParameterizedTypeReference typeReference) throws PushServerClientException { + private ObjectResponse postObjectImpl(String url, ObjectRequest request, Class responseType) throws PushServerClientException { try { - return restClient.post(url, request, typeReference).getBody(); + return restClient.postObject(url, request, responseType); } catch (RestClientException ex) { logger.warn(ex.getMessage(), ex); throw new PushServerClientException(ex, new Error("PUSH_SERVER_CLIENT_ERROR", "HTTP POST request failed.")); @@ -671,8 +695,13 @@ private T postObjectImpl(String url, Object request, ParameterizedTypeRefere * @return Object obtained after processing the response JSON. * @throws PushServerClientException In case of network, response / JSON processing, or other IO error. */ - private T putObjectImpl(String url, Object request) throws PushServerClientException { - return putObjectImpl(url, request, new ParameterizedTypeReference() {}); + private Response putObjectImpl(String url, ObjectRequest request) throws PushServerClientException { + try { + return restClient.putObject(url, request); + } catch (RestClientException ex) { + logger.warn(ex.getMessage(), ex); + throw new PushServerClientException(ex, new Error("PUSH_SERVER_CLIENT_ERROR", "HTTP POST request failed.")); + } } /** @@ -680,13 +709,13 @@ private T putObjectImpl(String url, Object request) throws PushServerClientE * * @param url specific url of method * @param request request body - * @param typeReference reference on type for parsing into JSON + * @param responseType response type * @return Object obtained after processing the response JSON. * @throws PushServerClientException In case of network, response / JSON processing, or other IO error. */ - private T putObjectImpl(String url, Object request, ParameterizedTypeReference typeReference) throws PushServerClientException { + private ObjectResponse putObjectImpl(String url, ObjectRequest request, Class responseType) throws PushServerClientException { try { - return restClient.put(url, request, typeReference).getBody(); + return restClient.putObject(url, request, responseType); } catch (RestClientException ex) { logger.warn(ex.getMessage(), ex); throw new PushServerClientException(ex, new Error("PUSH_SERVER_CLIENT_ERROR", "HTTP PUT request failed.")); diff --git a/powerauth-push-server/src/main/java/io/getlime/push/service/fcm/FcmClient.java b/powerauth-push-server/src/main/java/io/getlime/push/service/fcm/FcmClient.java index d8a89736b..c0a0aae29 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/service/fcm/FcmClient.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/service/fcm/FcmClient.java @@ -28,13 +28,12 @@ import io.getlime.push.errorhandling.exceptions.FcmInitializationFailedException; import io.getlime.push.errorhandling.exceptions.FcmMissingTokenException; import io.getlime.push.errorhandling.exceptions.PushServerException; +import io.getlime.push.service.fcm.model.MessageWithValidation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.core.io.buffer.DataBuffer; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.reactive.function.client.ClientResponse; -import reactor.core.publisher.Flux; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -216,11 +215,7 @@ public void exchange(Message message, boolean validationOnly, Consumer body = fcmConverter.convertMessageToFlux(message, validationOnly); - if (body == null) { - logger.error("Push message delivery failed because message is invalid."); - return; - } + MessageWithValidation body = new MessageWithValidation(message, validationOnly); AccessToken accessToken = getAccessToken(); MultiValueMap headers = new LinkedMultiValueMap<>(); diff --git a/powerauth-push-server/src/main/java/io/getlime/push/service/fcm/FcmModelConverter.java b/powerauth-push-server/src/main/java/io/getlime/push/service/fcm/FcmModelConverter.java index d0836048a..ad77746ba 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/service/fcm/FcmModelConverter.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/service/fcm/FcmModelConverter.java @@ -22,21 +22,14 @@ import com.google.api.client.json.JsonParser; import com.google.common.collect.ImmutableMap; import com.google.firebase.messaging.AndroidNotification; -import com.google.firebase.messaging.Message; import io.getlime.push.service.fcm.model.FcmErrorResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.core.io.buffer.DataBuffer; -import org.springframework.core.io.buffer.DefaultDataBuffer; -import org.springframework.core.io.buffer.DefaultDataBufferFactory; import org.springframework.stereotype.Service; import org.springframework.web.reactive.function.client.WebClientResponseException; -import reactor.core.publisher.Flux; import java.io.IOException; import java.io.StringWriter; -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; import java.util.Map; /** @@ -116,33 +109,4 @@ public String convertNotificationToString(AndroidNotification notification) { } } - /** - * Convert Message to payload for WebClient. - * - * @param message Message to send. - * @param validateOnly Whether to perform only validation. - * @return Flux of DataBuffer. - */ - public Flux convertMessageToFlux(Message message, boolean validateOnly) { - ImmutableMap.Builder payloadBuilder = ImmutableMap.builder().put("message", message); - if (validateOnly) { - payloadBuilder.put("validate_only", true); - } - ImmutableMap payload = payloadBuilder.build(); - String convertedMessage; - try { - StringWriter writer = new StringWriter(); - JsonGenerator gen = jsonFactory.createJsonGenerator(writer); - gen.serialize(payload); - gen.close(); - convertedMessage = writer.toString(); - } catch (IOException ex) { - logger.error("Json serialization failed: {}", ex.getMessage(), ex); - return null; - } - DefaultDataBufferFactory factory = new DefaultDataBufferFactory(); - DefaultDataBuffer dataBuffer = factory.wrap(ByteBuffer.wrap(convertedMessage.getBytes(StandardCharsets.UTF_8))); - return Flux.just(dataBuffer); - } - } diff --git a/powerauth-push-server/src/main/java/io/getlime/push/service/fcm/model/MessageWithValidation.java b/powerauth-push-server/src/main/java/io/getlime/push/service/fcm/model/MessageWithValidation.java new file mode 100644 index 000000000..28088558d --- /dev/null +++ b/powerauth-push-server/src/main/java/io/getlime/push/service/fcm/model/MessageWithValidation.java @@ -0,0 +1,77 @@ +/* + * Copyright 2020 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.fcm.model; + +import com.google.firebase.messaging.Message; + +/** + * Model class for FCM requests with message and validation mode. + * + * @author Roman Strobl, roman.strobl@wultra.com + */ +public class MessageWithValidation { + + private Message message; + private boolean validate_only; + + /** + * Default constructor. + */ + public MessageWithValidation() { + } + + /** + * Constructor with message and validation mode. + * @param message Message. + * @param validateOnly Validation mode. + */ + public MessageWithValidation(Message message, boolean validateOnly) { + this.message = message; + this.validate_only = validateOnly; + } + + /** + * Get the message. + * @return Message. + */ + public Message getMessage() { + return message; + } + + /** + * Set the message. + * @param message Message. + */ + public void setMessage(Message message) { + this.message = message; + } + + /** + * Get validation mode. + * @return Validation mode. + */ + public boolean isValidateOnly() { + return validate_only; + } + + /** + * Set validation mode. + * @param validateOnly Validation mode. + */ + public void setValidateOnly(boolean validateOnly) { + this.validate_only = validateOnly; + } +} From fbe03ce5e2350a087a058ea667dc5f3f721dc1ff Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Thu, 10 Dec 2020 18:59:48 +0100 Subject: [PATCH 45/51] Fix feedback from code review --- .../push/service/fcm/model/MessageWithValidation.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/powerauth-push-server/src/main/java/io/getlime/push/service/fcm/model/MessageWithValidation.java b/powerauth-push-server/src/main/java/io/getlime/push/service/fcm/model/MessageWithValidation.java index 28088558d..d22c0c9ce 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/service/fcm/model/MessageWithValidation.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/service/fcm/model/MessageWithValidation.java @@ -15,6 +15,7 @@ */ package io.getlime.push.service.fcm.model; +import com.fasterxml.jackson.annotation.JsonProperty; import com.google.firebase.messaging.Message; /** @@ -25,7 +26,9 @@ public class MessageWithValidation { private Message message; - private boolean validate_only; + + @JsonProperty("validate_only") + private boolean validateOnly; /** * Default constructor. @@ -40,7 +43,7 @@ public MessageWithValidation() { */ public MessageWithValidation(Message message, boolean validateOnly) { this.message = message; - this.validate_only = validateOnly; + this.validateOnly = validateOnly; } /** @@ -64,7 +67,7 @@ public void setMessage(Message message) { * @return Validation mode. */ public boolean isValidateOnly() { - return validate_only; + return validateOnly; } /** @@ -72,6 +75,6 @@ public boolean isValidateOnly() { * @param validateOnly Validation mode. */ public void setValidateOnly(boolean validateOnly) { - this.validate_only = validateOnly; + this.validateOnly = validateOnly; } } From d194896be1968844a8a14986a51e85309fa45862 Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Thu, 10 Dec 2020 21:28:54 +0100 Subject: [PATCH 46/51] Revert serialization change, fix error with blocking call when extracting the response --- .../push/service/PushSendingWorker.java | 10 ++- .../getlime/push/service/fcm/FcmClient.java | 9 ++- .../push/service/fcm/FcmModelConverter.java | 36 +++++++++ .../fcm/model/MessageWithValidation.java | 80 ------------------- 4 files changed, 50 insertions(+), 85 deletions(-) delete mode 100644 powerauth-push-server/src/main/java/io/getlime/push/service/fcm/model/MessageWithValidation.java 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 8dc64307c..2533bd859 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 @@ -40,6 +40,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import org.springframework.web.reactive.function.client.ClientResponse; import org.springframework.web.reactive.function.client.WebClientResponseException; @@ -132,9 +133,9 @@ void sendMessageToAndroid(final FcmClient fcmClient, final PushMessageBody pushM // Build Android message Message message = buildAndroidMessage(pushMessageBody, attributes, pushToken); - // Callback when FCM request succeeds - Consumer onSuccess = body -> { - FcmSuccessResponse response = body.bodyToMono(FcmSuccessResponse.class).block(); + // Extraction of FCM success response + Consumer> fcmConsumer = responseEntity -> { + FcmSuccessResponse response = responseEntity.getBody(); if (response != null && response.getName() != null && response.getName().matches(FCM_RESPONSE_VALID_REGEXP)) { logger.info("Notification sent, response: {}", response.getName()); callback.didFinishSendingMessage(PushSendingCallback.Result.OK); @@ -145,6 +146,9 @@ void sendMessageToAndroid(final FcmClient fcmClient, final PushMessageBody pushM callback.didFinishSendingMessage(PushSendingCallback.Result.FAILED); }; + // Callback when FCM request succeeds + Consumer onSuccess = body -> body.toEntity(FcmSuccessResponse.class).subscribe(fcmConsumer); + // Callback when FCM request fails Consumer onError = t -> { if (t instanceof WebClientResponseException) { diff --git a/powerauth-push-server/src/main/java/io/getlime/push/service/fcm/FcmClient.java b/powerauth-push-server/src/main/java/io/getlime/push/service/fcm/FcmClient.java index c0a0aae29..d8a89736b 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/service/fcm/FcmClient.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/service/fcm/FcmClient.java @@ -28,12 +28,13 @@ import io.getlime.push.errorhandling.exceptions.FcmInitializationFailedException; import io.getlime.push.errorhandling.exceptions.FcmMissingTokenException; import io.getlime.push.errorhandling.exceptions.PushServerException; -import io.getlime.push.service.fcm.model.MessageWithValidation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.core.io.buffer.DataBuffer; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.reactive.function.client.ClientResponse; +import reactor.core.publisher.Flux; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -215,7 +216,11 @@ public void exchange(Message message, boolean validationOnly, Consumer body = fcmConverter.convertMessageToFlux(message, validationOnly); + if (body == null) { + logger.error("Push message delivery failed because message is invalid."); + return; + } AccessToken accessToken = getAccessToken(); MultiValueMap headers = new LinkedMultiValueMap<>(); diff --git a/powerauth-push-server/src/main/java/io/getlime/push/service/fcm/FcmModelConverter.java b/powerauth-push-server/src/main/java/io/getlime/push/service/fcm/FcmModelConverter.java index ad77746ba..d0836048a 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/service/fcm/FcmModelConverter.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/service/fcm/FcmModelConverter.java @@ -22,14 +22,21 @@ import com.google.api.client.json.JsonParser; import com.google.common.collect.ImmutableMap; import com.google.firebase.messaging.AndroidNotification; +import com.google.firebase.messaging.Message; import io.getlime.push.service.fcm.model.FcmErrorResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.core.io.buffer.DataBuffer; +import org.springframework.core.io.buffer.DefaultDataBuffer; +import org.springframework.core.io.buffer.DefaultDataBufferFactory; import org.springframework.stereotype.Service; import org.springframework.web.reactive.function.client.WebClientResponseException; +import reactor.core.publisher.Flux; import java.io.IOException; import java.io.StringWriter; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; import java.util.Map; /** @@ -109,4 +116,33 @@ public String convertNotificationToString(AndroidNotification notification) { } } + /** + * Convert Message to payload for WebClient. + * + * @param message Message to send. + * @param validateOnly Whether to perform only validation. + * @return Flux of DataBuffer. + */ + public Flux convertMessageToFlux(Message message, boolean validateOnly) { + ImmutableMap.Builder payloadBuilder = ImmutableMap.builder().put("message", message); + if (validateOnly) { + payloadBuilder.put("validate_only", true); + } + ImmutableMap payload = payloadBuilder.build(); + String convertedMessage; + try { + StringWriter writer = new StringWriter(); + JsonGenerator gen = jsonFactory.createJsonGenerator(writer); + gen.serialize(payload); + gen.close(); + convertedMessage = writer.toString(); + } catch (IOException ex) { + logger.error("Json serialization failed: {}", ex.getMessage(), ex); + return null; + } + DefaultDataBufferFactory factory = new DefaultDataBufferFactory(); + DefaultDataBuffer dataBuffer = factory.wrap(ByteBuffer.wrap(convertedMessage.getBytes(StandardCharsets.UTF_8))); + return Flux.just(dataBuffer); + } + } diff --git a/powerauth-push-server/src/main/java/io/getlime/push/service/fcm/model/MessageWithValidation.java b/powerauth-push-server/src/main/java/io/getlime/push/service/fcm/model/MessageWithValidation.java deleted file mode 100644 index d22c0c9ce..000000000 --- a/powerauth-push-server/src/main/java/io/getlime/push/service/fcm/model/MessageWithValidation.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2020 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.fcm.model; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.google.firebase.messaging.Message; - -/** - * Model class for FCM requests with message and validation mode. - * - * @author Roman Strobl, roman.strobl@wultra.com - */ -public class MessageWithValidation { - - private Message message; - - @JsonProperty("validate_only") - private boolean validateOnly; - - /** - * Default constructor. - */ - public MessageWithValidation() { - } - - /** - * Constructor with message and validation mode. - * @param message Message. - * @param validateOnly Validation mode. - */ - public MessageWithValidation(Message message, boolean validateOnly) { - this.message = message; - this.validateOnly = validateOnly; - } - - /** - * Get the message. - * @return Message. - */ - public Message getMessage() { - return message; - } - - /** - * Set the message. - * @param message Message. - */ - public void setMessage(Message message) { - this.message = message; - } - - /** - * Get validation mode. - * @return Validation mode. - */ - public boolean isValidateOnly() { - return validateOnly; - } - - /** - * Set validation mode. - * @param validateOnly Validation mode. - */ - public void setValidateOnly(boolean validateOnly) { - this.validateOnly = validateOnly; - } -} From 65d97025a7a0e9e2f28a41803c536fd084c51e83 Mon Sep 17 00:00:00 2001 From: Petr Dvorak Date: Thu, 17 Dec 2020 10:28:48 +0100 Subject: [PATCH 47/51] Fix #369: Remove the SNAPSHOT version --- pom.xml | 6 +++--- powerauth-push-client/pom.xml | 6 +++--- powerauth-push-model/pom.xml | 4 ++-- powerauth-push-server/pom.xml | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/pom.xml b/pom.xml index 763e0b9ae..bc011cfe2 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ io.getlime.security powerauth-push-server-parent - 1.0.0-SNAPSHOT + 1.0.0 pom @@ -78,8 +78,8 @@ 2.11.3 2.11.3 1.2.2 - 1.2.0-SNAPSHOT - 1.0.0-SNAPSHOT + 1.2.0 + 1.0.0 0.14.2 1.5.0 1.31.0 diff --git a/powerauth-push-client/pom.xml b/powerauth-push-client/pom.xml index fc70b40bc..62c1e36c1 100644 --- a/powerauth-push-client/pom.xml +++ b/powerauth-push-client/pom.xml @@ -5,13 +5,13 @@ 4.0.0 powerauth-push-client PowerAuth Push Server RESTful Client - 1.0.0-SNAPSHOT + 1.0.0 jar powerauth-push-server-parent io.getlime.security - 1.0.0-SNAPSHOT + 1.0.0 @@ -20,7 +20,7 @@ io.getlime.security powerauth-push-model - 1.0.0-SNAPSHOT + 1.0.0 diff --git a/powerauth-push-model/pom.xml b/powerauth-push-model/pom.xml index 0f6de80f1..480b0c38a 100644 --- a/powerauth-push-model/pom.xml +++ b/powerauth-push-model/pom.xml @@ -6,13 +6,13 @@ powerauth-push-model PowerAuth Push Server RESTful Model Classes - 1.0.0-SNAPSHOT + 1.0.0 jar powerauth-push-server-parent io.getlime.security - 1.0.0-SNAPSHOT + 1.0.0 diff --git a/powerauth-push-server/pom.xml b/powerauth-push-server/pom.xml index a3246edd4..0206102a0 100644 --- a/powerauth-push-server/pom.xml +++ b/powerauth-push-server/pom.xml @@ -6,13 +6,13 @@ powerauth-push-server PowerAuth Push Server powerauth-push-server - 1.0.0-SNAPSHOT + 1.0.0 war io.getlime.security powerauth-push-server-parent - 1.0.0-SNAPSHOT + 1.0.0 ../pom.xml From 21a1597f3cebb5b49e333a82ae7ff26b545c5938 Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Fri, 18 Dec 2020 14:16:14 +0100 Subject: [PATCH 48/51] Fix #371: Update Spring Boot version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 763e0b9ae..d6028f2d5 100644 --- a/pom.xml +++ b/pom.xml @@ -14,7 +14,7 @@ org.springframework.boot spring-boot-starter-parent - 2.3.5.RELEASE + 2.3.7.RELEASE From 2bb3ad44e88d19d17addd58ca335bafdaba35252 Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Fri, 18 Dec 2020 16:52:48 +0100 Subject: [PATCH 49/51] Fix #373: Push server fails to start on Wildfly --- .../src/main/webapp/WEB-INF/jboss-deployment-structure.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/powerauth-push-server/src/main/webapp/WEB-INF/jboss-deployment-structure.xml b/powerauth-push-server/src/main/webapp/WEB-INF/jboss-deployment-structure.xml index 0e330e265..6112c1b87 100644 --- a/powerauth-push-server/src/main/webapp/WEB-INF/jboss-deployment-structure.xml +++ b/powerauth-push-server/src/main/webapp/WEB-INF/jboss-deployment-structure.xml @@ -4,6 +4,7 @@ + From f8cc295e14610def13480565a78eb0d853257af3 Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Mon, 21 Dec 2020 11:45:19 +0100 Subject: [PATCH 50/51] Fix #375: Add snapshot repository configuration --- pom.xml | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 89ee549e9..dac93633e 100644 --- a/pom.xml +++ b/pom.xml @@ -155,13 +155,26 @@ + + + ossrh-snapshots + http://oss.sonatype.org/content/repositories/snapshots/ + + false + + + true + + + + - ossrh + ossrh-snapshots-distribution https://oss.sonatype.org/content/repositories/snapshots/ - ossrh + ossrh-staging-distribution https://oss.sonatype.org/service/local/staging/deploy/maven2/ From c867ea975aba787ef14f439819e3f156649cfd15 Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Thu, 7 Jan 2021 17:57:17 +0100 Subject: [PATCH 51/51] Fix #382: Update migration guide list --- docs/Migration-Instructions.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Migration-Instructions.md b/docs/Migration-Instructions.md index 6ec27ee2f..cd3824d90 100644 --- a/docs/Migration-Instructions.md +++ b/docs/Migration-Instructions.md @@ -2,6 +2,7 @@ This page contains PowerAuth Push Server migration instructions. +- [PowerAuth Push Server 1.0.0](./PowerAuth-Push-Server-1.0.0.md) - [PowerAuth Push Server 0.24.0](./PowerAuth-Push-Server-0.24.0.md) - [PowerAuth Push Server 0.23.0](./PowerAuth-Push-Server-0.23.0.md) - [PowerAuth Push Server 0.22.0](./PowerAuth-Push-Server-0.22.0.md)