From 63181df05c557ca26f4750af2de83c382d51d845 Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Wed, 19 Jun 2019 17:57:32 +0200 Subject: [PATCH 01/56] Update version to 0.23.0 Update Jackson dependency --- pom.xml | 6 +++--- powerauth-push-client/pom.xml | 6 +++--- powerauth-push-model/pom.xml | 4 ++-- powerauth-push-server/pom.xml | 4 ++-- .../src/main/resources/application.properties | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pom.xml b/pom.xml index 0857f3247..7a07e8bd2 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ io.getlime.security powerauth-push-server-parent - 0.22.0 + 0.23.0 pom @@ -77,12 +77,12 @@ 1.4.197 4.5.6 4.1.4 - 2.9.8 + 2.9.9 1.2.2 2.0 1.2.5 1.1.0 - 0.22.0 + 0.23.0 0.13.8 2.9.2 1.4.9 diff --git a/powerauth-push-client/pom.xml b/powerauth-push-client/pom.xml index 2da0bab15..c005e5a8f 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.22.0 + 0.23.0 jar powerauth-push-server-parent io.getlime.security - 0.22.0 + 0.23.0 @@ -20,7 +20,7 @@ io.getlime.security powerauth-push-model - 0.22.0 + 0.23.0 diff --git a/powerauth-push-model/pom.xml b/powerauth-push-model/pom.xml index 57d7f997d..00300e9a5 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.22.0 + 0.23.0 jar powerauth-push-server-parent io.getlime.security - 0.22.0 + 0.23.0 diff --git a/powerauth-push-server/pom.xml b/powerauth-push-server/pom.xml index 0b448d25f..7a205dea3 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.22.0 + 0.23.0 war io.getlime.security powerauth-push-server-parent - 0.22.0 + 0.23.0 ../pom.xml diff --git a/powerauth-push-server/src/main/resources/application.properties b/powerauth-push-server/src/main/resources/application.properties index 65454d942..67d1cfa68 100644 --- a/powerauth-push-server/src/main/resources/application.properties +++ b/powerauth-push-server/src/main/resources/application.properties @@ -54,7 +54,7 @@ powerauth.push.service.campaign.batchSize=100000 powerauth.push.service.message.storage.enabled=false # APNs Configuration -powerauth.push.service.apns.useDevelopment=false +powerauth.push.service.apns.useDevelopment=true powerauth.push.service.apns.proxy.enabled=false powerauth.push.service.apns.proxy.host=127.0.0.1 powerauth.push.service.apns.proxy.port=8080 From 30cf89b3208c5b34f827cecc1d1691637f327545 Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Wed, 19 Jun 2019 18:01:18 +0200 Subject: [PATCH 02/56] Removed unwanted change --- powerauth-push-server/src/main/resources/application.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/powerauth-push-server/src/main/resources/application.properties b/powerauth-push-server/src/main/resources/application.properties index 67d1cfa68..65454d942 100644 --- a/powerauth-push-server/src/main/resources/application.properties +++ b/powerauth-push-server/src/main/resources/application.properties @@ -54,7 +54,7 @@ powerauth.push.service.campaign.batchSize=100000 powerauth.push.service.message.storage.enabled=false # APNs Configuration -powerauth.push.service.apns.useDevelopment=true +powerauth.push.service.apns.useDevelopment=false powerauth.push.service.apns.proxy.enabled=false powerauth.push.service.apns.proxy.host=127.0.0.1 powerauth.push.service.apns.proxy.port=8080 From 66cde79615fb0c9f08e6e395adf21d5e0f48042f Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Thu, 20 Jun 2019 13:22:38 +0200 Subject: [PATCH 03/56] Update Spring boot version to upgrade transitive Jackson dependencies --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 7a07e8bd2..6683cc155 100644 --- a/pom.xml +++ b/pom.xml @@ -14,7 +14,7 @@ org.springframework.boot spring-boot-starter-parent - 2.1.5.RELEASE + 2.1.6.RELEASE From 6c4739d0c59a906ed87dcf66e035f166c8a9579d Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Thu, 27 Jun 2019 13:46:04 +0200 Subject: [PATCH 04/56] Update coverity scan settings --- .travis.yml | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index b70074501..41fc85d63 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,19 +6,17 @@ branches: - master env: global: - - secure: "HdkS3LONKdXYlW83TLwobYK+YIZBujkG8JuEyfSlueFEwYZ2Pk0GmQZP4Q5b50w4VqE1tnAvHmSg4q/ShP+NreH0EzBdnwFaUV+pts0mdwqk50g6JqQE8dbj2AU4wPrkungW9E1xWH6iuxQJbQnJCtgqGMfXHPpML5/0XLJtie7/T+1rTJE9zDTct+IRk1s4aYjEMWm5Bkm4A+BDYAEwfoeesgQ1Qj46+FSLVFnO28x0RkSy/Ucq1SBolTCxw6X4Q9cMCpmkQ5kQj3cdSf968WQJazECzzY0Ev9sv1NfZCYhV5nb9zlIt0CMbcbogUA+rBAg0Vw/TdMz3y8UM5b7CU9TG90WvB8psA++d26XOVRz42oBkXwh6rO/yUdAaDEzTRzgjE1ESOBJSpcr3pjSGWb6DJgW2lNW6JjmBGmOqSsmBHVa3VGAXlxvS/aUHU/LH/oC7YkNf2jhjl+M1fZwt+mQO5QhFBhS2BB+VnFPMPFpyJcwdZJI6DJzXPSj7LFdYtjI7F68+s2a7lgzVtSL3vXVq2cD+qQP4AjR7Ekd05+1SBHLvaCsktrZJ6lcmKQUwyTGYel7uzmL6et9vZ1eS5b3u5YSeKNmxwcJ0XPBduh1n+NQX1vtpNSgJX4dSplmFn3grO4ujDptyFoyC4Qc8OGQWTAav+m14+OcKPPUZCI=" + - secure: "lN/1nBJvrLsBhDRJwoAKiu5Xv9dRyH7EJ4DC1AUgRfaz/2in8e9X8psluCxXiCkUqxJqjqIwp/IUWJyIgnUj1Tj8KT2dyR/wp68Q+tr3buRxGbZf9lAIirj44LB7ji5cxGKmuvJNY3M5hFaLoovquqacwB0FslnhssH/HpuSHMC2lyNXxnIduTLha/7AZAX7+xNTeRQ86cpVIh5/UJEhvqICdlywhsguAdeyyI/ZLRElLY07O3R9SJAaOgy95oDmSwq3IflIjBNhlxrWKYUUAkQFjM3HdwcmLbhP5aZAJWbrDRiAeXzJyqgvvo/oHS+AtSc8r68TvZ+O+H4d0XLbExrz4cFdXHKCJY/OuI6pwpXRWoXiPgEBpNodef5iGupU84ZxKiBGvSWEJNgGDSHLwUgVzEtOSPnvmeVGRHHi2hO9onOMKHPsJR8aHWg8VDcsf3kXIF7YVC2lvaO5Ubi9ESzjBoE3tQsQlcaHCOzYsXqI70DAH4lgZtabYRIWbFcbl+RSoNsrvh5HhMl17W0YM1a3+bXdF/Q926jFg506XDvYhyPy2DUoiwnSdtthxMJYYLTDSYjZDzykmCH0A2TM+iM2hJLP8LO/b7wLxj/lI2NhmRQzhI8pMhkBHa97kMH3eTX+QZYJt9PJR+MyixcBzcOaTLLm2kBoPZOFU7oKgI8=" before_install: - - echo -n | openssl s_client -connect scan.coverity.com:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' | sudo tee -a /etc/ssl/certs/ca- - -script: mvn clean package -DskipTests=true + - echo -n | openssl s_client -connect https://scan.coverity.com:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' | sudo tee -a /etc/ssl/certs/ca- addons: coverity_scan: project: - name: lime-company/powerauth-push-server - description: Build submitted via Travis CI - notification_email: petr@wultra.com - build_command_prepend: mvn clean - build_command: mvn compile -DskipTests=true - branch_pattern: master + name: "wultra/powerauth-push-server" + description: "Build submitted via Travis CI" + notification_email: roman.strobl@wultra.com + build_command_prepend: "mvn clean" + build_command: "mvn compile -DskipTests=true" + branch_pattern: coverity_scan \ No newline at end of file From 56c813d64e32fa96e4a6ff4ed93e8f07f785dcf8 Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Tue, 2 Jul 2019 17:46:34 +0200 Subject: [PATCH 05/56] Add coverity_scan branch into list of branches which are being built --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 41fc85d63..cfaa36e0e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ jdk: branches: only: - master + - coverity_scan env: global: - secure: "lN/1nBJvrLsBhDRJwoAKiu5Xv9dRyH7EJ4DC1AUgRfaz/2in8e9X8psluCxXiCkUqxJqjqIwp/IUWJyIgnUj1Tj8KT2dyR/wp68Q+tr3buRxGbZf9lAIirj44LB7ji5cxGKmuvJNY3M5hFaLoovquqacwB0FslnhssH/HpuSHMC2lyNXxnIduTLha/7AZAX7+xNTeRQ86cpVIh5/UJEhvqICdlywhsguAdeyyI/ZLRElLY07O3R9SJAaOgy95oDmSwq3IflIjBNhlxrWKYUUAkQFjM3HdwcmLbhP5aZAJWbrDRiAeXzJyqgvvo/oHS+AtSc8r68TvZ+O+H4d0XLbExrz4cFdXHKCJY/OuI6pwpXRWoXiPgEBpNodef5iGupU84ZxKiBGvSWEJNgGDSHLwUgVzEtOSPnvmeVGRHHi2hO9onOMKHPsJR8aHWg8VDcsf3kXIF7YVC2lvaO5Ubi9ESzjBoE3tQsQlcaHCOzYsXqI70DAH4lgZtabYRIWbFcbl+RSoNsrvh5HhMl17W0YM1a3+bXdF/Q926jFg506XDvYhyPy2DUoiwnSdtthxMJYYLTDSYjZDzykmCH0A2TM+iM2hJLP8LO/b7wLxj/lI2NhmRQzhI8pMhkBHa97kMH3eTX+QZYJt9PJR+MyixcBzcOaTLLm2kBoPZOFU7oKgI8=" From 16e2d88cb21f6c9d579fb91abbe7bbc8e4ce0afd Mon Sep 17 00:00:00 2001 From: snyk-test Date: Wed, 3 Jul 2019 08:08:44 +0000 Subject: [PATCH 06/56] fix: pom.xml to reduce vulnerabilities The following vulnerabilities are fixed with an upgrade: - https://snyk.io/vuln/SNYK-JAVA-COMFASTERXMLJACKSONCORE-450917 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 6683cc155..f4601c15a 100644 --- a/pom.xml +++ b/pom.xml @@ -15,7 +15,7 @@ org.springframework.boot spring-boot-starter-parent 2.1.6.RELEASE - + 2016 @@ -77,7 +77,7 @@ 1.4.197 4.5.6 4.1.4 - 2.9.9 + 2.9.9.1 1.2.2 2.0 1.2.5 From 04c92ab23bbaa8d00e2d1d499faba8739a6b1acf Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Wed, 10 Jul 2019 15:05:33 +0200 Subject: [PATCH 07/56] Fix #242: Build error due to Jackson dependencies --- pom.xml | 3 ++- powerauth-push-client/pom.xml | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index f4601c15a..b8d6f5b48 100644 --- a/pom.xml +++ b/pom.xml @@ -77,7 +77,8 @@ 1.4.197 4.5.6 4.1.4 - 2.9.9.1 + 2.9.9 + 2.9.9.1 1.2.2 2.0 1.2.5 diff --git a/powerauth-push-client/pom.xml b/powerauth-push-client/pom.xml index c005e5a8f..7a28e7417 100644 --- a/powerauth-push-client/pom.xml +++ b/powerauth-push-client/pom.xml @@ -37,7 +37,7 @@ com.fasterxml.jackson.core jackson-databind - ${jackson.version} + ${jackson-databind.version} com.mashape.unirest From b2d001b66d930a9274a1d2a1abf03f4e56678f0c Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Fri, 12 Jul 2019 16:10:01 +0200 Subject: [PATCH 08/56] Fix #244: Set proxy host and port for GoogleCredential --- .../io/getlime/push/service/fcm/FcmClient.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) 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 e4c808283..a6b1cfedd 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 @@ -17,6 +17,9 @@ package io.getlime.push.service.fcm; import com.google.api.client.googleapis.auth.oauth2.GoogleCredential; +import com.google.api.client.http.HttpTransport; +import com.google.api.client.http.javanet.NetHttpTransport; +import com.google.api.client.json.jackson2.JacksonFactory; import com.google.firebase.messaging.Message; import io.getlime.push.configuration.PushServiceConfiguration; import io.getlime.push.errorhandling.exceptions.FcmInitializationFailedException; @@ -37,6 +40,8 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; +import java.net.InetSocketAddress; +import java.net.Proxy; import java.util.Collections; import java.util.function.Consumer; @@ -143,8 +148,15 @@ public void setFcmSendMessageUrl(String fcmSendMessageUrl) { public void initializeGoogleCredential() throws FcmInitializationFailedException { try { InputStream is = new ByteArrayInputStream(privateKey); + HttpTransport httpTransport; + if (proxyHost != null) { + Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyPort)); + httpTransport = new NetHttpTransport.Builder().setProxy(proxy).build(); + } else { + httpTransport = new NetHttpTransport.Builder().build(); + } googleCredential = GoogleCredential - .fromStream(is) + .fromStream(is, httpTransport, JacksonFactory.getDefaultInstance()) .createScoped(Collections.singletonList("https://www.googleapis.com/auth/firebase.messaging")); } catch (IOException ex) { throw new FcmInitializationFailedException("Error occurred while initializing Google Credential using FCM private key: " + ex.getMessage(), ex); From ce5aaf9a4935ba9203c0e5c070d4c67a54542fdb Mon Sep 17 00:00:00 2001 From: snyk-test Date: Mon, 29 Jul 2019 21:40:53 +0000 Subject: [PATCH 09/56] fix: pom.xml to reduce vulnerabilities The following vulnerabilities are fixed with an upgrade: - https://snyk.io/vuln/SNYK-JAVA-COMFASTERXMLJACKSONCORE-455617 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b8d6f5b48..1a18ec586 100644 --- a/pom.xml +++ b/pom.xml @@ -78,7 +78,7 @@ 4.5.6 4.1.4 2.9.9 - 2.9.9.1 + 2.9.9.2 1.2.2 2.0 1.2.5 From 83ea6c12fdf91334c0dfaf41244bb180e49fc45a Mon Sep 17 00:00:00 2001 From: snyk-test Date: Tue, 30 Jul 2019 08:08:54 +0000 Subject: [PATCH 10/56] fix: pom.xml to reduce vulnerabilities The following vulnerabilities are fixed with an upgrade: - https://snyk.io/vuln/SNYK-JAVA-COMFASTERXMLJACKSONCORE-455617 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index b8d6f5b48..e702270d7 100644 --- a/pom.xml +++ b/pom.xml @@ -77,8 +77,8 @@ 1.4.197 4.5.6 4.1.4 - 2.9.9 - 2.9.9.1 + 2.10.0.pr1 + 2.9.9.2 1.2.2 2.0 1.2.5 From 40187dfa7ab16051c82414b62653c16fb2a666e9 Mon Sep 17 00:00:00 2001 From: snyk-test Date: Tue, 6 Aug 2019 21:40:40 +0000 Subject: [PATCH 11/56] fix: pom.xml to reduce vulnerabilities The following vulnerabilities are fixed with an upgrade: - https://snyk.io/vuln/SNYK-JAVA-COMFASTERXMLJACKSONCORE-455617 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e702270d7..edeceb9f3 100644 --- a/pom.xml +++ b/pom.xml @@ -78,7 +78,7 @@ 4.5.6 4.1.4 2.10.0.pr1 - 2.9.9.2 + 2.9.9.3 1.2.2 2.0 1.2.5 From 955f86a220bb6367593716e61f2af49866d393fb Mon Sep 17 00:00:00 2001 From: Petr Dvorak Date: Wed, 7 Aug 2019 14:52:02 +0200 Subject: [PATCH 12/56] Fix version typo --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 0179ee644..1a18ec586 100644 --- a/pom.xml +++ b/pom.xml @@ -77,7 +77,7 @@ 1.4.197 4.5.6 4.1.4 - 2.9.9.2 + 2.9.9 2.9.9.2 1.2.2 2.0 From 21fd7a72fe45c2afad823c0f18b45f6d90f8ec67 Mon Sep 17 00:00:00 2001 From: Petr Dvorak Date: Thu, 29 Aug 2019 14:02:00 +0200 Subject: [PATCH 13/56] Fix #226: Upgrade Jackson dependency --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1a18ec586..f922a0cac 100644 --- a/pom.xml +++ b/pom.xml @@ -78,7 +78,7 @@ 4.5.6 4.1.4 2.9.9 - 2.9.9.2 + 2.9.9.3 1.2.2 2.0 1.2.5 From af6e48671bb20e279f25e75f3f3b9f7fed88eed6 Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Wed, 11 Sep 2019 13:48:04 +0200 Subject: [PATCH 14/56] Fix #253: Remove unused application properties --- .../src/main/resources/application.properties | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/powerauth-push-server/src/main/resources/application.properties b/powerauth-push-server/src/main/resources/application.properties index 65454d942..160b2b671 100644 --- a/powerauth-push-server/src/main/resources/application.properties +++ b/powerauth-push-server/src/main/resources/application.properties @@ -5,11 +5,6 @@ spring.profiles.active=ext spring.mvc.view.prefix=/WEB-INF/jsp/ spring.mvc.view.suffix=.jsp -# Database Keep-Alive -spring.datasource.test-while-idle=true -spring.datasource.test-on-borrow=true -spring.datasource.validation-query=SELECT 1 - # Database Configuration - MySQL spring.datasource.url=jdbc:mysql://localhost:3306/powerauth spring.datasource.username=powerauth @@ -54,7 +49,7 @@ powerauth.push.service.campaign.batchSize=100000 powerauth.push.service.message.storage.enabled=false # APNs Configuration -powerauth.push.service.apns.useDevelopment=false +powerauth.push.service.apns.useDevelopment=true powerauth.push.service.apns.proxy.enabled=false powerauth.push.service.apns.proxy.host=127.0.0.1 powerauth.push.service.apns.proxy.port=8080 From 4ccfce405e840a0e4d549ab97419c36ea41d253d Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Wed, 11 Sep 2019 13:48:57 +0200 Subject: [PATCH 15/56] Fix default properties --- powerauth-push-server/src/main/resources/application.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/powerauth-push-server/src/main/resources/application.properties b/powerauth-push-server/src/main/resources/application.properties index 160b2b671..2ef6013bb 100644 --- a/powerauth-push-server/src/main/resources/application.properties +++ b/powerauth-push-server/src/main/resources/application.properties @@ -49,7 +49,7 @@ powerauth.push.service.campaign.batchSize=100000 powerauth.push.service.message.storage.enabled=false # APNs Configuration -powerauth.push.service.apns.useDevelopment=true +powerauth.push.service.apns.useDevelopment=false powerauth.push.service.apns.proxy.enabled=false powerauth.push.service.apns.proxy.host=127.0.0.1 powerauth.push.service.apns.proxy.port=8080 From 586d6d06ecf8b290aa2e5d309c7249381885965f Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Thu, 12 Sep 2019 16:24:25 +0200 Subject: [PATCH 16/56] Fix #256: Switch to 0.23.0-SNAPSHOT version --- pom.xml | 4 ++-- powerauth-push-client/pom.xml | 6 +++--- powerauth-push-model/pom.xml | 4 ++-- powerauth-push-server/pom.xml | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index f922a0cac..ca4df1eeb 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ io.getlime.security powerauth-push-server-parent - 0.23.0 + 0.23.0-SNAPSHOT pom @@ -83,7 +83,7 @@ 2.0 1.2.5 1.1.0 - 0.23.0 + 0.23.0-SNAPSHOT 0.13.8 2.9.2 1.4.9 diff --git a/powerauth-push-client/pom.xml b/powerauth-push-client/pom.xml index 7a28e7417..5d922c545 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.23.0 + 0.23.0-SNAPSHOT jar powerauth-push-server-parent io.getlime.security - 0.23.0 + 0.23.0-SNAPSHOT @@ -20,7 +20,7 @@ io.getlime.security powerauth-push-model - 0.23.0 + 0.23.0-SNAPSHOT diff --git a/powerauth-push-model/pom.xml b/powerauth-push-model/pom.xml index 00300e9a5..245468383 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.23.0 + 0.23.0-SNAPSHOT jar powerauth-push-server-parent io.getlime.security - 0.23.0 + 0.23.0-SNAPSHOT diff --git a/powerauth-push-server/pom.xml b/powerauth-push-server/pom.xml index 7a205dea3..5ad5daef8 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.23.0 + 0.23.0-SNAPSHOT war io.getlime.security powerauth-push-server-parent - 0.23.0 + 0.23.0-SNAPSHOT ../pom.xml From f543a67ca417ee457ce133bad780cdd1049687f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Dvo=C5=99=C3=A1k?= Date: Wed, 18 Sep 2019 20:36:58 +0200 Subject: [PATCH 17/56] Improve the push server DB documentation --- docs/Push-Server-Database.md | 64 ++++++++++++++---------------------- 1 file changed, 24 insertions(+), 40 deletions(-) diff --git a/docs/Push-Server-Database.md b/docs/Push-Server-Database.md index ce55c4abb..32e97bf5b 100644 --- a/docs/Push-Server-Database.md +++ b/docs/Push-Server-Database.md @@ -19,7 +19,7 @@ You can download DDL scripts for supported databases: ### Push Devices Table -**Table name**: `push_device` +**Table name**: `push_device_registration` **Purpose**: Stores push tokens specific for a given device. @@ -27,14 +27,14 @@ You can download DDL scripts for supported databases: | Name | Type | Info | Note | |---|---|---|---| -| id | BIGINT(20) | primary key, index, autoincrement | Unique device registration ID. | -| activation_id | VARCHAR(37) | index | Application name, for example "Mobile Banking". | -| user_id | BIGINT(20) | index | Associated user ID | -| app_id | BIGINT(20) | index | Associated application ID | -| platform | VARCHAR(30) | - | Mobile OS Platform ("ios", "android") | -| push_token | VARCHAR(255) | - | Push token associated with a given device. Type of the token is determined by the `platform` column. | -| timestamp_created | TIMESTAMP | - | Timestamp of the last device registration. | -| is_active | INT(11) | - | PowerAuth 2.0 activation status (boolean), used as an activation status cache so that communication with PowerAuth 2.0 Server can be minimal. | +| `id` | BIGINT(20) | primary key, index, autoincrement | Unique device registration ID. | +| `activation_id` | VARCHAR(37) | index | Activation ID associated with given push token record. | +| `user_id` | BIGINT(20) | index | Associated user ID. | +| `app_id` | BIGINT(20) | index | Associated application ID. | +| `platform` | VARCHAR(30) | - | Mobile OS Platform ("ios", "android"). | +| `push_token` | VARCHAR(255) | - | Push token associated with a given device. Type of the token is determined by the `platform` column. | +| `timestamp_last_registered` | TIMESTAMP | - | Timestamp of the last device registration. | +| `is_active` | INT(11) | - | PowerAuth activation status (boolean), used as an activation status cache so that communication with PowerAuth Server can be minimal. | ### Push Service Credentials Table @@ -47,7 +47,7 @@ You can download DDL scripts for supported databases: | Name | Type | Info | Note | |---|---|---|---| | id | BIGINT(20) | primary key, index, autoincrement | Unique credential record ID. | -| app_id | BIGINT(20) | index | Associated application ID | +| app_id | BIGINT(20) | index | Associated application ID. | | ios_key_id | VARCHAR(255) | - | Key ID used for identifying a private key in APNs service. | | ios_private_key | BLOB | - | Binary representation of P8 file with private key used for Apple's APNs service. | | ios_team_id | VARCHAR(255) | - | Team ID used for sending push notifications. | @@ -68,12 +68,12 @@ You can download DDL scripts for supported databases: | id | BIGINT(20) | primary key, index, autoincrement | Unique message record ID. | | device_registration_id | INT | index | Associated device registration (device that is used to receive the message), for the purpose of resend on fail operation. | | user_id | BIGINT(20) | index | Associated user ID. | -| activation_id | VARCHAR(37) | index | PowerAuth 2.0 activation ID. | -| silent | INT | - | Flag indicating if the message was "silent" (0 = NO, 1 = YES) | -| personal | INT | - | Flag indicating if the message was "personal" - sent only on active devices (0 = NO, 1 = YES) | +| activation_id | VARCHAR(37) | index | PowerAuth activation ID. | +| is_silent | INT | - | Flag indicating if the message was "silent" (0 = NO, 1 = YES). | +| is_personal | INT | - | Flag indicating if the message was "personal" - sent only on active devices (0 = NO, 1 = YES). | | message_body | TEXT | - | Payload of the message in a unified server format. This format is later translated in a platform specific payload. | | timestamp_created | TIMESTAMP | - | Date and time when the record was created. | -| status | INT | - | Value indicating message send status. (-1 = FAILED, 0 = PENDING, 1 = SENT) | +| status | INT | - | Value indicating message send status. (-1 = FAILED, 0 = PENDING, 1 = SENT). | ### Push Campaigns Table @@ -86,11 +86,12 @@ You can download DDL scripts for supported databases: | Name | Type | Info | Note | |---|---|---|---| | id | BIGINT(20) | primary key, index, autoincrement | Unique campaign record ID. | -| appid | BIGINT(20) | index | Associated Application identifier | -| message | TEXT | - | Certain notification that is written in unified format | -| sent| INT(1) | - | Flag indicating if campaign was successfully sent | -| timestamp_created | TIMESTAMP | - | Timestamp of campaign creation | -| timestamp_sent | TIMESTAMP | - | Timestamp of campaign successful sending | +| appid | BIGINT(20) | index | Associated Application identifier. | +| message | TEXT | - | Certain notification that is written in unified format. | +| sent| INT(1) | - | Flag indicating if campaign was successfully sent. | +| timestamp_created | TIMESTAMP | - | Timestamp of campaign creation. | +| timestamp_sent | TIMESTAMP | - | Timestamp of campaign sending initiation. | +| timestamp_completed | TIMESTAMP | - | Timestamp of campaign successful sending (all messages sent). | ### Push Campaign Users Table @@ -102,24 +103,7 @@ You can download DDL scripts for supported databases: | Name | Type | Info | Note | |---|---|---|---| -| id | BIGINT(20) | primary key, index, autoincrement | Unique user ID | -| campaign_id | BIGINT(20) | index | Identifier of campaign that is user related to | -| user_id | BIGINT(20) | index | Identifier of user, can occur multiple times in different campaigns | -| timestamp_created | TIMESTAMP | - | Timestamp of user creation | - -### Push Campaign Devices Table - -**Table name**: `push_campaign_device` - -**Purpose**: Stores devices related to certain campaign to ensure that each device will receive only one message - -**Columns**: - -| Name | Type | Info | Note | -|---|---|---|---| -| id | BIGINT(20) | primary key, index, autoincrement | Unique device ID | -| campaign_id | BIGINT(20) | index | Identifier of campaign that is device related to | -| platform | VARCHAR(20) | - | Platform that is device running on | -| token | VARCHAR(255) | - | Push token associated with a given device | -| status | INT(11) | - | Status used in concurrent sending. States: sent, sending, failed | -| timestamp_created | TIMESTAMP | - | Timestamp of device creation | +| id | BIGINT(20) | primary key, index, autoincrement | Unique user ID. | +| campaign_id | BIGINT(20) | index | Identifier of campaign that is user related to. | +| user_id | BIGINT(20) | index | Identifier of user, can occur multiple times in different campaigns. | +| timestamp_created | TIMESTAMP | - | Timestamp of user creation (assignment to the campaign). | From 95ab46cf5b3965eeb74850b0eabb88c3a66e64cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Dvo=C5=99=C3=A1k?= Date: Wed, 18 Sep 2019 21:24:08 +0200 Subject: [PATCH 18/56] Remove broken anchor link --- docs/Push-Server-Database.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/Push-Server-Database.md b/docs/Push-Server-Database.md index 32e97bf5b..27aeda33d 100644 --- a/docs/Push-Server-Database.md +++ b/docs/Push-Server-Database.md @@ -14,7 +14,6 @@ You can download DDL scripts for supported databases: - [Push messages](#push-messages-table) - [Push campaigns](#push-campaigns-table) - [Push campaign users](#push-campaign-users-table) -- [Push campaign devices](#push-campaign-devices-table) ### Push Devices Table From 6d79b4f4532b9d6488e88447383e791d545e6a08 Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Tue, 15 Oct 2019 14:27:55 +0200 Subject: [PATCH 19/56] Fix #269: Proxy authentication support for FCM --- .../getlime/push/service/fcm/FcmClient.java | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) 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 e4c808283..45e76b46b 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 @@ -17,6 +17,9 @@ package io.getlime.push.service.fcm; import com.google.api.client.googleapis.auth.oauth2.GoogleCredential; +import com.google.api.client.http.HttpTransport; +import com.google.api.client.http.javanet.NetHttpTransport; +import com.google.api.client.json.jackson2.JacksonFactory; import com.google.firebase.messaging.Message; import io.getlime.push.configuration.PushServiceConfiguration; import io.getlime.push.errorhandling.exceptions.FcmInitializationFailedException; @@ -37,6 +40,10 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; +import java.net.Authenticator; +import java.net.InetSocketAddress; +import java.net.PasswordAuthentication; +import java.net.Proxy; import java.util.Collections; import java.util.function.Consumer; @@ -143,14 +150,41 @@ public void setFcmSendMessageUrl(String fcmSendMessageUrl) { public void initializeGoogleCredential() throws FcmInitializationFailedException { try { InputStream is = new ByteArrayInputStream(privateKey); + HttpTransport httpTransport; + if (proxyHost != null) { + Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyPort)); + httpTransport = new NetHttpTransport.Builder().setProxy(proxy).build(); + if (proxyUsername != null && proxyPassword != null) { + setProxyAuthentication(); + } + } else { + httpTransport = new NetHttpTransport.Builder().build(); + } googleCredential = GoogleCredential - .fromStream(is) + .fromStream(is, httpTransport, JacksonFactory.getDefaultInstance()) .createScoped(Collections.singletonList("https://www.googleapis.com/auth/firebase.messaging")); } catch (IOException ex) { throw new FcmInitializationFailedException("Error occurred while initializing Google Credential using FCM private key: " + ex.getMessage(), ex); } } + /** + * Set proxy authentication for FCM. + */ + private void setProxyAuthentication() { + Authenticator.setDefault(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + if (getRequestorType() == RequestorType.PROXY) { + if (getRequestingHost().equals(proxyHost) && getRequestingPort() == proxyPort) { + return new PasswordAuthentication(proxyUsername, proxyPassword.toCharArray()); + } + } + return null; + } + }); + } + /** * Refresh and retrieve access token for FCM. * @return FCM access token. From 532ac1c58dc2bec5dc8f74dbe6ea8b90b4d15863 Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Fri, 18 Oct 2019 14:46:26 +0200 Subject: [PATCH 20/56] Update version for testing --- pom.xml | 2 +- powerauth-push-client/pom.xml | 6 +++--- powerauth-push-model/pom.xml | 4 ++-- powerauth-push-server/pom.xml | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 0857f3247..ad76bdd44 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ io.getlime.security powerauth-push-server-parent - 0.22.0 + 0.22.1-SNAPSHOT pom diff --git a/powerauth-push-client/pom.xml b/powerauth-push-client/pom.xml index 2da0bab15..8184722ed 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.22.0 + 0.22.1-SNAPSHOT jar powerauth-push-server-parent io.getlime.security - 0.22.0 + 0.22.1-SNAPSHOT @@ -20,7 +20,7 @@ io.getlime.security powerauth-push-model - 0.22.0 + 0.22.1-SNAPSHOT diff --git a/powerauth-push-model/pom.xml b/powerauth-push-model/pom.xml index 57d7f997d..ed4ae32fd 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.22.0 + 0.22.1-SNAPSHOT jar powerauth-push-server-parent io.getlime.security - 0.22.0 + 0.22.1-SNAPSHOT diff --git a/powerauth-push-server/pom.xml b/powerauth-push-server/pom.xml index 0b448d25f..58894ddf2 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.22.0 + 0.22.1-SNAPSHOT war io.getlime.security powerauth-push-server-parent - 0.22.0 + 0.22.1-SNAPSHOT ../pom.xml From 80a77c60fac35b65add4a2e602f8e2b71f0a18ee Mon Sep 17 00:00:00 2001 From: Petr Dvorak Date: Mon, 28 Oct 2019 16:25:43 +0100 Subject: [PATCH 21/56] Fix #270: Update dependencies --- pom.xml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index ca4df1eeb..f13b3a544 100644 --- a/pom.xml +++ b/pom.xml @@ -73,24 +73,24 @@ 1.8 - 27.0.1-jre - 1.4.197 - 4.5.6 + 28.1-jre + 1.4.200 + 4.5.10 4.1.4 - 2.9.9 - 2.9.9.3 + 2.9.10 + 2.9.10.1 1.2.2 2.0 1.2.5 1.1.0 0.23.0-SNAPSHOT - 0.13.8 + 0.13.10 2.9.2 1.4.9 - 1.27.0 - 6.8.1 + 1.30.5 + 6.10.0 2.3.1 - 3.0.8 + 3.0.10 1.4.0 1.5.1 From 78794684b0a9208159dc1e0b303cc9b9a3372588 Mon Sep 17 00:00:00 2001 From: Petr Dvorak Date: Mon, 28 Oct 2019 18:07:19 +0100 Subject: [PATCH 22/56] Fix H2 version - downgrade due to h2database/h2database#2078 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f13b3a544..a8f444b9e 100644 --- a/pom.xml +++ b/pom.xml @@ -74,7 +74,7 @@ 28.1-jre - 1.4.200 + 1.4.199 4.5.10 4.1.4 2.9.10 From 176d3e50f5e973968b7cb1f57f24e6682116208c Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Sat, 2 Nov 2019 10:13:05 -0400 Subject: [PATCH 23/56] Enable Spring actuator --- powerauth-push-server/pom.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/powerauth-push-server/pom.xml b/powerauth-push-server/pom.xml index 5ad5daef8..c1f1ea055 100644 --- a/powerauth-push-server/pom.xml +++ b/powerauth-push-server/pom.xml @@ -62,6 +62,10 @@ org.springframework.boot spring-boot-starter-webflux + + org.springframework.boot + spring-boot-starter-actuator + From c29af03676bd730518d3afa2387e6b2393dc5c18 Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Sat, 2 Nov 2019 10:14:01 -0400 Subject: [PATCH 24/56] Update Spring boot version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a8f444b9e..baf283364 100644 --- a/pom.xml +++ b/pom.xml @@ -14,7 +14,7 @@ org.springframework.boot spring-boot-starter-parent - 2.1.6.RELEASE + 2.1.9.RELEASE From 8a8865f155d4f7a16d612ba4036865eea4f49164 Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Mon, 11 Nov 2019 11:09:21 +0100 Subject: [PATCH 25/56] Set apns-push-type for iOS 13 and higher --- .../io/getlime/push/service/PushSendingWorker.java | 13 ++++++------- 1 file changed, 6 insertions(+), 7 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 935e96e4d..d3668638c 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 @@ -19,10 +19,7 @@ import com.google.firebase.messaging.AndroidConfig; import com.google.firebase.messaging.AndroidNotification; import com.google.firebase.messaging.Message; -import com.turo.pushy.apns.ApnsClient; -import com.turo.pushy.apns.ApnsClientBuilder; -import com.turo.pushy.apns.DeliveryPriority; -import com.turo.pushy.apns.PushNotificationResponse; +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; @@ -311,9 +308,11 @@ 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 String payload = buildApnsPayload(pushMessageBody, attributes == null ? false : attributes.getSilent()); // In case there are no attributes, the message is not silent - Date validUntil = pushMessageBody.getValidUntil(); - final SimpleApnsPushNotification pushNotification = new SimpleApnsPushNotification(token, iosTopic, payload, validUntil, DeliveryPriority.IMMEDIATE, pushMessageBody.getCollapseKey()); + 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 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 -> { From 3670ef6f44e3a3796c5282fab11545b0675b590d Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Tue, 12 Nov 2019 17:48:16 +0100 Subject: [PATCH 26/56] Update Coverity scan configuration --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index cfaa36e0e..4c879ddfa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,13 +1,13 @@ language: java jdk: - - oraclejdk8 + - openjdk11 branches: only: - master - coverity_scan env: global: - - secure: "lN/1nBJvrLsBhDRJwoAKiu5Xv9dRyH7EJ4DC1AUgRfaz/2in8e9X8psluCxXiCkUqxJqjqIwp/IUWJyIgnUj1Tj8KT2dyR/wp68Q+tr3buRxGbZf9lAIirj44LB7ji5cxGKmuvJNY3M5hFaLoovquqacwB0FslnhssH/HpuSHMC2lyNXxnIduTLha/7AZAX7+xNTeRQ86cpVIh5/UJEhvqICdlywhsguAdeyyI/ZLRElLY07O3R9SJAaOgy95oDmSwq3IflIjBNhlxrWKYUUAkQFjM3HdwcmLbhP5aZAJWbrDRiAeXzJyqgvvo/oHS+AtSc8r68TvZ+O+H4d0XLbExrz4cFdXHKCJY/OuI6pwpXRWoXiPgEBpNodef5iGupU84ZxKiBGvSWEJNgGDSHLwUgVzEtOSPnvmeVGRHHi2hO9onOMKHPsJR8aHWg8VDcsf3kXIF7YVC2lvaO5Ubi9ESzjBoE3tQsQlcaHCOzYsXqI70DAH4lgZtabYRIWbFcbl+RSoNsrvh5HhMl17W0YM1a3+bXdF/Q926jFg506XDvYhyPy2DUoiwnSdtthxMJYYLTDSYjZDzykmCH0A2TM+iM2hJLP8LO/b7wLxj/lI2NhmRQzhI8pMhkBHa97kMH3eTX+QZYJt9PJR+MyixcBzcOaTLLm2kBoPZOFU7oKgI8=" + - secure: "hUmHJZJmFBXV8j8SCVOEWGTcRlqAcllfWDwXz+owY5cWIOtDRvMrZXOvgupRsAHzRL7BUAsKo8k7/eqeZVfjO+zSlYLr3/06krCv5T/8GQTwHv3nTNq3r03GLZpYypuUapfUFCCVQchafWvYuNs7O5IsL1YHcRB5MjwbobARw1AezkEs+0n9kIeEofTFDZntILKXYuEO4Xa44KOzOEenxHieBeKgUpa43JFH/Qoh96qumqt3US+F4raHTwDG8KgNKc7bEzMdGxZ8MrfwljZP0teuc6B6/mPsIh8ZNdWTnsxq04M6Qe3fvCrkyUOyHZsAVA0xoSK2pTuMkAQOF0c9tLYcs1aOg4gM/tOsbgQa8iS5loC+zxPo5UgixIwGg/QxnQTHZLrOTCpsQqGbtqK5qTP/2v6FXKOUQ8tnvth8DQpsyI/FUKe0KEZYs5WSen5anXIkWE7fITlVUXaUvNAILIzM2nAdaYnhdnyUaVNfjvIVIwiciEzBL7FiuZnVRcfGaX2HTqp9embnFW43kpkSypvDUwaBFhsLDLVgr6xyurYwdWD6Ot+gwN6BMT8ajPnqSRTtiesdYGOu+uQ5AnwkRpeD87r6C1yHTAeyMlPVpVoslpw6cocEWlBNOR4oMwHoqcV9/ygjHdHBnZeWO7oEoJtds1oY6WmFzZM2TuToPbk=" before_install: - echo -n | openssl s_client -connect https://scan.coverity.com:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' | sudo tee -a /etc/ssl/certs/ca- From d04005d950cfc34f2f216c8e448900cb7864abfb Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Tue, 12 Nov 2019 18:03:06 +0100 Subject: [PATCH 27/56] Update Coverity scan configuration --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 4c879ddfa..16c497e85 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,5 +19,5 @@ addons: description: "Build submitted via Travis CI" notification_email: roman.strobl@wultra.com build_command_prepend: "mvn clean" - build_command: "mvn compile -DskipTests=true" + build_command: "mvn -DskipTests=true compile" branch_pattern: coverity_scan \ No newline at end of file From d79a819fa915876e976c5b33ca7fae71af32de18 Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Tue, 12 Nov 2019 19:31:19 +0100 Subject: [PATCH 28/56] Specify custom Maven build command --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 16c497e85..683913410 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,7 @@ language: java jdk: - openjdk11 +script: mvn -DskipTests=true clean package branches: only: - master From 6ab6b7fcaa1c2012c4d197e5fa4afbd1da2f9515 Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Tue, 12 Nov 2019 20:15:12 +0100 Subject: [PATCH 29/56] Update Spring boot dependencies due to security advisories --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index baf283364..0ecfede9c 100644 --- a/pom.xml +++ b/pom.xml @@ -14,7 +14,7 @@ org.springframework.boot spring-boot-starter-parent - 2.1.9.RELEASE + 2.2.1.RELEASE @@ -77,8 +77,8 @@ 1.4.199 4.5.10 4.1.4 - 2.9.10 - 2.9.10.1 + 2.10.0 + 2.10.0 1.2.2 2.0 1.2.5 From 2e7440ecd14ad4560ae7401da803b030b31e1623 Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Tue, 12 Nov 2019 20:21:56 +0100 Subject: [PATCH 30/56] Revert Jackson library upgrade due to compilation error --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 0ecfede9c..518cc35d5 100644 --- a/pom.xml +++ b/pom.xml @@ -77,8 +77,8 @@ 1.4.199 4.5.10 4.1.4 - 2.10.0 - 2.10.0 + 2.9.10 + 2.9.10.1 1.2.2 2.0 1.2.5 From e5d03b6a29a92cab52e18f1b8c19f667c9dabe40 Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Tue, 12 Nov 2019 21:39:49 +0100 Subject: [PATCH 31/56] Upgrade Jackson and parameterize TypeReference parameter --- pom.xml | 4 ++-- .../io/getlime/push/client/PushServerClient.java | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 518cc35d5..0ecfede9c 100644 --- a/pom.xml +++ b/pom.xml @@ -77,8 +77,8 @@ 1.4.199 4.5.10 4.1.4 - 2.9.10 - 2.9.10.1 + 2.10.0 + 2.10.0 1.2.2 2.0 1.2.5 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 38ae58251..b7aff9a49 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 @@ -631,7 +631,7 @@ 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, Map params, TypeReference typeReference) throws PushServerClientException { try { HttpResponse response = Unirest.get(serviceBaseUrl + url) .header("Accept", "application/json") @@ -664,7 +664,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 TypeReference() {}); } /** @@ -676,7 +676,7 @@ 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, TypeReference typeReference) throws PushServerClientException { try { // Fetch post response from given URL and for provided request object HttpResponse response = Unirest.post(serviceBaseUrl + url) @@ -709,7 +709,7 @@ private T postObjectImpl(String url, Object request, TypeReference typeRefer * @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 TypeReference() {}); + return putObjectImpl(url, request, new TypeReference() {}); } /** @@ -721,7 +721,7 @@ 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, TypeReference typeReference) throws PushServerClientException { try { HttpResponse response = Unirest.put(serviceBaseUrl + url) .header("Accept", "application/json") @@ -754,7 +754,7 @@ private T putObjectImpl(String url, Object request, TypeReference typeRefere * @throws PushServerClientException In case of network, response / JSON processing, or other IO error. * @throws IOException In case JSON processing fails. */ - private T checkHttpStatus(TypeReference typeReference, HttpResponse response) throws IOException, PushServerClientException { + private T checkHttpStatus(TypeReference typeReference, HttpResponse response) throws IOException, PushServerClientException { if (response.getStatus() == 200) { return jacksonObjectMapper.readValue(response.getRawBody(), typeReference); } else { From 36bf8c154d151e447b4b899449d7ec65da232afd Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Thu, 14 Nov 2019 13:26:09 +0100 Subject: [PATCH 32/56] Fix failing tests (invalid response type in client) --- .../io/getlime/push/client/PushServerClient.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 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 b7aff9a49..e987c5073 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 @@ -631,7 +631,7 @@ 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, Map params, TypeReference typeReference) throws PushServerClientException { try { HttpResponse response = Unirest.get(serviceBaseUrl + url) .header("Accept", "application/json") @@ -664,7 +664,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 TypeReference() {}); } /** @@ -676,7 +676,7 @@ 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, TypeReference typeReference) throws PushServerClientException { try { // Fetch post response from given URL and for provided request object HttpResponse response = Unirest.post(serviceBaseUrl + url) @@ -709,7 +709,7 @@ private T postObjectImpl(String url, Object request, TypeReference typeRe * @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 TypeReference() {}); + return putObjectImpl(url, request, new TypeReference() {}); } /** @@ -721,7 +721,7 @@ 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, TypeReference typeReference) throws PushServerClientException { try { HttpResponse response = Unirest.put(serviceBaseUrl + url) .header("Accept", "application/json") @@ -754,9 +754,10 @@ private T putObjectImpl(String url, Object request, TypeReference typeRef * @throws PushServerClientException In case of network, response / JSON processing, or other IO error. * @throws IOException In case JSON processing fails. */ - private T checkHttpStatus(TypeReference typeReference, HttpResponse response) throws IOException, PushServerClientException { + @SuppressWarnings("unchecked") + private T checkHttpStatus(TypeReference typeReference, HttpResponse response) throws IOException, PushServerClientException { if (response.getStatus() == 200) { - return jacksonObjectMapper.readValue(response.getRawBody(), typeReference); + return (T) jacksonObjectMapper.readValue(response.getRawBody(), typeReference); } else { try { // Response body contains data, return Exception with status code and error response From 8a57c24b578c4a6bc468caf603869e5261a4dba3 Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Wed, 20 Nov 2019 16:22:54 +0100 Subject: [PATCH 33/56] Fix #252: Add "associated activations" to registration request Fix #262: Make sure push token is unique for given activation ID --- .../getlime/push/client/PushServerClient.java | 42 ++++++- .../CreateDeviceForActivationsRequest.java | 89 +++++++++++++++ .../CreateDeviceRequestValidator.java | 26 +++++ .../PushServiceConfiguration.java | 20 ++++ .../controller/rest/PushDeviceController.java | 105 +++++++++++++++--- .../push/repository/PushDeviceRepository.java | 17 ++- .../service/PushMessageSenderService.java | 4 +- .../src/main/resources/application.properties | 3 + 8 files changed, 275 insertions(+), 31 deletions(-) create mode 100644 powerauth-push-model/src/main/java/io/getlime/push/model/request/CreateDeviceForActivationsRequest.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 e987c5073..d419a5833 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 @@ -126,7 +126,7 @@ public ObjectResponse getServiceStatus() throws PushServe /** * Register anonymous device to the push server. * - * @param appId PowerAuth 2.0 application app ID. + * @param appId PowerAuth application app ID. * @param token Token received from the push service provider (APNs, FCM). * @param platform Mobile platform (iOS, Android). * @return True if device registration was successful, false otherwise. @@ -139,10 +139,10 @@ public boolean createDevice(Long appId, String token, MobilePlatform platform) t /** * Register device associated with activation ID to the push server. * - * @param appId PowerAuth 2.0 application app ID. + * @param appId PowerAuth application app ID. * @param token Token received from the push service provider (APNs, FCM). * @param platform Mobile platform (iOS, Android). - * @param activationId PowerAuth 2.0 activation ID. + * @param activationId PowerAuth activation ID. * @return True if device registration was successful, false otherwise. * @throws PushServerClientException In case of network, response / JSON processing, or other IO error. */ @@ -166,10 +166,40 @@ public boolean createDevice(Long appId, String token, MobilePlatform platform, S return response.getStatus().equals(Response.Status.OK); } + /** + * Register device associated with multiple activation IDs to the push server. + * + * @param appId PowerAuth application app ID. + * @param token Token received from the push service provider (APNs, FCM). + * @param platform Mobile platform (iOS, Android). + * @param activationIds PowerAuth activation IDs. + * @return True if device registration was successful, false otherwise. + * @throws PushServerClientException In case of network, response / JSON processing, or other IO error. + */ + public boolean createDeviceForActivations(Long appId, String token, MobilePlatform platform, List activationIds) throws PushServerClientException { + CreateDeviceForActivationsRequest request = new CreateDeviceForActivationsRequest(); + request.setAppId(appId); + request.setToken(token); + request.setPlatform(platform.value()); + request.getActivationIds().addAll(activationIds); + + // Validate request on the client side. + String error = CreateDeviceRequestValidator.validate(request); + if (error != null) { + throw new PushServerClientException(error); + } + + logger.info("Calling create device service, appId: {}, token: {}, platform: {} - start", appId, maskToken(token), platform.value()); + Response response = postObjectImpl("/push/device/create/multi", new ObjectRequest<>(request)); + logger.info("Calling create device service, appId: {}, token: {}, platform: {} - finish", appId, maskToken(token), platform.value()); + + return response.getStatus().equals(Response.Status.OK); + } + /** * Remove device from the push server * - * @param appId PowerAuth 2.0 application app ID. + * @param appId PowerAuth application app ID. * @param token Token received from the push service provider. * @return True if device removal was successful, false otherwise. * @throws PushServerClientException In case of network, response / JSON processing, or other IO error. @@ -221,7 +251,7 @@ public boolean updateDeviceStatus(String activationId) throws PushServerClientEx /** * Send a single push message to application with given ID. * - * @param appId PowerAuth 2.0 application app ID. + * @param appId PowerAuth application app ID. * @param pushMessage Push message to be sent. * @return SendMessageResponse in case everything went OK, ErrorResponse in case of an error. * @throws PushServerClientException In case of network, response / JSON processing, or other IO error. @@ -249,7 +279,7 @@ public ObjectResponse sendPushMessage(Long appId, PushMes /** * Send a push message batch to application with given ID. * - * @param appId PowerAuth 2.0 application app ID. + * @param appId PowerAuth application app ID. * @param batch Push message batch to be sent. * @return SendMessageResponse in case everything went OK, ErrorResponse in case of an error. * @throws PushServerClientException In case of network, response / JSON processing, or other IO error. 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 new file mode 100644 index 000000000..c62e0c1a7 --- /dev/null +++ b/powerauth-push-model/src/main/java/io/getlime/push/model/request/CreateDeviceForActivationsRequest.java @@ -0,0 +1,89 @@ +/* + * 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.model.request; + +import java.util.ArrayList; +import java.util.List; + +/** + * Request object used for device registration in case multiple associated activations are used. + * + * @author Petr Dvorak, petr@wultra.com + */ +public class CreateDeviceForActivationsRequest { + + private Long appId; + private String token; + private String platform; + private List activationIds = new ArrayList<>(); + + /** + * Get app ID associated with given device registration. + * @return App ID. + */ + public Long getAppId() { + return appId; + } + + /** + * Set app ID associated with given device registration. + * @param appId App ID. + */ + public void setAppId(Long appId) { + this.appId = appId; + } + + /** + * Get APNs / FCM push token. + * @return Push token value. + */ + public String getToken() { + return token; + } + + /** + * Set APNs / FCM push token. + * @param token Push token value. + */ + public void setToken(String token) { + this.token = token; + } + + /** + * Get the platform name, either "ios" or "android". + * @return Platform name, "ios" or "android". + */ + public String getPlatform() { + return platform; + } + + /** + * Set the platform name. + * @param platform Platform name. + */ + public void setPlatform(String platform) { + this.platform = platform; + } + + /** + * Get PowerAuth activation IDs associated with given device registration. + * @return Activation ID. + */ + public List getActivationIds() { + return activationIds; + } + +} diff --git a/powerauth-push-model/src/main/java/io/getlime/push/model/validator/CreateDeviceRequestValidator.java b/powerauth-push-model/src/main/java/io/getlime/push/model/validator/CreateDeviceRequestValidator.java index d6ee99d8f..002dfbaaa 100644 --- a/powerauth-push-model/src/main/java/io/getlime/push/model/validator/CreateDeviceRequestValidator.java +++ b/powerauth-push-model/src/main/java/io/getlime/push/model/validator/CreateDeviceRequestValidator.java @@ -15,6 +15,7 @@ */ package io.getlime.push.model.validator; +import io.getlime.push.model.request.CreateDeviceForActivationsRequest; import io.getlime.push.model.request.CreateDeviceRequest; /** @@ -49,4 +50,29 @@ public static String validate(CreateDeviceRequest request) { return null; } + public static String validate(CreateDeviceForActivationsRequest request) { + if (request == null) { + return "Request must not be empty."; + } + if (request.getAppId() == null) { + return "App ID must not be null."; + } + if (request.getAppId() < 1) { + return "App ID must be a positive number."; + } + if (request.getActivationIds() == null) { + return "Activation ID list must not be null."; + } + if (request.getActivationIds().isEmpty()) { + return "Activation ID list must not empty."; + } + if (request.getPlatform() == null || request.getPlatform().isEmpty()) { + return "Platform must not be null or empty."; + } + if (request.getToken() == null || request.getToken().isEmpty()) { + return "Push token must not be null or empty."; + } + return null; + } + } 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 ee06bf631..b3c23691f 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 @@ -87,6 +87,10 @@ public class PushServiceConfiguration { @Value("${powerauth.push.service.message.storage.enabled}") private boolean messageStorageEnabled; + // Whether multiple activations are enabled per registered device + @Value("${powerauth.push.service.registration.multipleActivations.enabled}") + private boolean registrationOfMultipleActivationsEnabled; + /** * FCM connect timeout in milliseconds. */ @@ -387,6 +391,22 @@ public void setMessageStorageEnabled(boolean messageStorageEnabled) { this.messageStorageEnabled = messageStorageEnabled; } + /** + * Get whether multiple activations are enabled per registered device. + * @return Whether multiple activations are enabled per registered device. + */ + public boolean isRegistrationOfMultipleActivationsEnabled() { + return registrationOfMultipleActivationsEnabled; + } + + /** + * Set whether multiple activations are enabled per registered device. + * @param registrationOfMultipleActivationsEnabled Whether multiple activations are enabled per registered device. + */ + public void setRegistrationOfMultipleActivationsEnabled(boolean registrationOfMultipleActivationsEnabled) { + this.registrationOfMultipleActivationsEnabled = registrationOfMultipleActivationsEnabled; + } + /** * Get FCM connect timeout in milliseconds. * @return FCM connect timeout. diff --git a/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/PushDeviceController.java b/powerauth-push-server/src/main/java/io/getlime/push/controller/rest/PushDeviceController.java index cc273b7ad..26a1fb2e5 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 @@ -19,7 +19,9 @@ 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; import io.getlime.push.model.request.CreateDeviceRequest; import io.getlime.push.model.request.DeleteDeviceRequest; import io.getlime.push.model.request.UpdateDeviceStatusRequest; @@ -49,17 +51,15 @@ @RequestMapping(value = "push/device") public class PushDeviceController { - private PushDeviceRepository pushDeviceRepository; - private PowerAuthServiceClient client; + private final PushDeviceRepository pushDeviceRepository; + private final PowerAuthServiceClient client; + private final PushServiceConfiguration config; @Autowired - public PushDeviceController(PushDeviceRepository pushDeviceRepository) { + public PushDeviceController(PushDeviceRepository pushDeviceRepository, PowerAuthServiceClient client, PushServiceConfiguration config) { this.pushDeviceRepository = pushDeviceRepository; - } - - @Autowired - void setClient(PowerAuthServiceClient client) { this.client = client; + this.config = config; } /** @@ -87,26 +87,95 @@ void setClient(PowerAuthServiceClient client) { String pushToken = requestedObject.getToken(); String platform = requestedObject.getPlatform(); String activationId = requestedObject.getActivationId(); - PushDeviceRegistrationEntity device = pushDeviceRepository.findFirstByAppIdAndPushToken(appId, pushToken); - if (device == null) { + // Make sure push token is unique for activation ID, in case activation ID is specified in request. See: https://github.com/wultra/powerauth-push-server/issues/262 + // Both Apple and Google issue new push token despite the fact the old one didn't expire yet. The old push tokens need to be deleted. + if (activationId != null) { + pushDeviceRepository.deleteByAppIdAndActivationId(appId, activationId); + } + List devices = pushDeviceRepository.findByAppIdAndPushToken(appId, pushToken); + PushDeviceRegistrationEntity device; + if (devices.isEmpty()) { device = new PushDeviceRegistrationEntity(); device.setAppId(appId); device.setPushToken(pushToken); + } else if (devices.size() == 1) { + device = devices.get(0); + } else { + // Push token can be associated with multiple device registrations only when associated activations are enabled. + // Push device registration must be done using /push/device/create/multi endpoint in this case. + throw new PushServerException("Multiple device registrations found for push token. Use /push/device/create/multi endpoint for this scenario."); } device.setTimestampLastRegistered(new Date()); device.setPlatform(platform); if (activationId != null) { - 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()); - } + updateActivationForDevice(device, activationId); } pushDeviceRepository.save(device); return new Response(); } + /** + * Create a new device registration for multiple associated activations. + * @param request Device registration request. + * @return Device registration status. + * @throws PushServerException In case request object is invalid. + */ + @RequestMapping(value = "create/multi", method = RequestMethod.POST) + @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." + + "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---" + + "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 activationId value," + + " so that there are no incorrect bindings.") + public @ResponseBody Response createDeviceMultipleActivations(@RequestBody ObjectRequest request) throws PushServerException { + CreateDeviceForActivationsRequest requestedObject = request.getRequestObject(); + String errorMessage; + if (!config.isRegistrationOfMultipleActivationsEnabled()) { + errorMessage = "Registration of multiple associated activations per device is not enabled."; + } else { + errorMessage = CreateDeviceRequestValidator.validate(requestedObject); + } + if (errorMessage != null) { + throw new PushServerException(errorMessage); + } + Long appId = requestedObject.getAppId(); + String pushToken = requestedObject.getToken(); + String platform = requestedObject.getPlatform(); + List activationIds = requestedObject.getActivationIds(); + + activationIds.forEach(activationId -> { + // Make sure push token is unique for given activation ID. See: https://github.com/wultra/powerauth-push-server/issues/262 + // Both Apple and Google issue new push token despite the fact the old one didn't expire yet. The old push token + // needs to be deleted. + pushDeviceRepository.deleteByAppIdAndActivationId(appId, activationId); + // Register device for given activation ID. Device registration is always new because of the previous delete step. + PushDeviceRegistrationEntity device = new PushDeviceRegistrationEntity(); + device.setAppId(appId); + device.setPushToken(pushToken); + device.setTimestampLastRegistered(new Date()); + device.setPlatform(platform); + updateActivationForDevice(device, activationId); + pushDeviceRepository.save(device); + }); + return new Response(); + } + + /** + * Update activation for given device in case activation exists in PowerAuth server and it is not in REMOVED state. + * @param device Push device registration entity. + * @param activationId Activation ID. + */ + private void updateActivationForDevice(PushDeviceRegistrationEntity device, String activationId) { + 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()); + } + } + /** * Update activation status for given device registration. * @param request Status update request. @@ -152,9 +221,9 @@ void setClient(PowerAuthServiceClient client) { } Long appId = requestedObject.getAppId(); String pushToken = requestedObject.getToken(); - PushDeviceRegistrationEntity device = pushDeviceRepository.findFirstByAppIdAndPushToken(appId, pushToken); - if (device != null) { - pushDeviceRepository.delete(device); + List devices = pushDeviceRepository.findByAppIdAndPushToken(appId, pushToken); + if (!devices.isEmpty()) { + pushDeviceRepository.deleteAll(devices); } return new Response(); } diff --git a/powerauth-push-server/src/main/java/io/getlime/push/repository/PushDeviceRepository.java b/powerauth-push-server/src/main/java/io/getlime/push/repository/PushDeviceRepository.java index b3c7940ea..a4051f1af 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/repository/PushDeviceRepository.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/repository/PushDeviceRepository.java @@ -33,12 +33,12 @@ public interface PushDeviceRepository extends CrudRepository { /** - * Find first device for given app ID and push token. + * Find all device registrations for given app ID and push token. * @param appId App ID. * @param pushToken Push token. - * @return Device registration with provided values. + * @return Device registrations matching provided values. */ - PushDeviceRegistrationEntity findFirstByAppIdAndPushToken(Long appId, String pushToken); + List findByAppIdAndPushToken(Long appId, String pushToken); /** * Find all device registrations by given activation ID. In normal case, the list will contain only one value. @@ -48,7 +48,7 @@ public interface PushDeviceRepository extends CrudRepository findByActivationId(String activationId); /** - * Find all device registration by given user ID and app ID. This list represents all devices that a single user + * Find all device registrations by given user ID and app ID. This list represents all devices that a single user * has registered. * @param userId User ID. * @param appId App ID. @@ -57,7 +57,7 @@ public interface PushDeviceRepository extends CrudRepository findByUserIdAndAppId(String userId, Long appId); /** - * Find all device registration by given user ID, app ID and activation ID. This list should contain one record + * Find all device registrations by given user ID, app ID and activation ID. This list should contain one record * only under normal circumstances. * @param userId User ID. * @param appId App ID. @@ -65,4 +65,11 @@ public interface PushDeviceRepository extends CrudRepository findByUserIdAndAppIdAndActivationId(String userId, Long appId, String activationId); + + /** + * Delete all device registrations for given app ID and activation ID. + * @param appId + * @param activationId + */ + void deleteByAppIdAndActivationId(Long appId, String activationId); } 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 750a48e21..41e60781c 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 @@ -255,7 +255,7 @@ public void sendCampaignMessage(final Long appId, String platform, final String } case FAILED_DELETE: { updateStatusAndPersist(pushMessageObject, PushMessageEntity.Status.FAILED); - pushDeviceRepository.delete(pushDeviceRepository.findFirstByAppIdAndPushToken(appId, token)); + pushDeviceRepository.deleteAll(pushDeviceRepository.findByAppIdAndPushToken(appId, token)); break; } } @@ -277,7 +277,7 @@ public void sendCampaignMessage(final Long appId, String platform, final String } case FAILED_DELETE: { updateStatusAndPersist(pushMessageObject, PushMessageEntity.Status.FAILED); - pushDeviceRepository.delete(pushDeviceRepository.findFirstByAppIdAndPushToken(appId, token)); + pushDeviceRepository.deleteAll(pushDeviceRepository.findByAppIdAndPushToken(appId, token)); break; } } diff --git a/powerauth-push-server/src/main/resources/application.properties b/powerauth-push-server/src/main/resources/application.properties index 2ef6013bb..8910e0758 100644 --- a/powerauth-push-server/src/main/resources/application.properties +++ b/powerauth-push-server/src/main/resources/application.properties @@ -48,6 +48,9 @@ powerauth.push.service.campaign.batchSize=100000 # Whether persistent storing of sent messages is enabled powerauth.push.service.message.storage.enabled=false +# Whether push registration supports associated activations +powerauth.push.service.registration.multipleActivations.enabled=false + # APNs Configuration powerauth.push.service.apns.useDevelopment=false powerauth.push.service.apns.proxy.enabled=false From f0e3a7d1a1e7ab2987611b5c2b076c590477bdaf Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Wed, 20 Nov 2019 16:26:45 +0100 Subject: [PATCH 34/56] Update comments --- .../push/model/request/CreateDeviceForActivationsRequest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 c62e0c1a7..23174190d 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 @@ -1,5 +1,5 @@ /* - * Copyright 2016 Wultra s.r.o. + * Copyright 2019 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. @@ -21,7 +21,7 @@ /** * Request object used for device registration in case multiple associated activations are used. * - * @author Petr Dvorak, petr@wultra.com + * @author Roman Strobl, roman.strobl@wultra.com */ public class CreateDeviceForActivationsRequest { From fc7560d39b217705fbfc8bf484d26d1168dad564 Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Wed, 20 Nov 2019 16:53:53 +0100 Subject: [PATCH 35/56] Update documentation --- docs/Push-Server-API.md | 61 +++++++++++++++++-- .../controller/rest/PushDeviceController.java | 4 +- 2 files changed, 57 insertions(+), 8 deletions(-) diff --git a/docs/Push-Server-API.md b/docs/Push-Server-API.md index e6b1d45d1..a5404167f 100644 --- a/docs/Push-Server-API.md +++ b/docs/Push-Server-API.md @@ -8,7 +8,7 @@ Push Server provides a simple to use RESTful API for the 3rd party integration p - [Campaign](#campaign) - [Administration](#administration) -Following endpoints are published in PowerAuth 2.0 Push Server RESTful API: +Following endpoints are published in PowerAuth Push Server RESTful API: ## Methods @@ -26,6 +26,7 @@ Following endpoints are published in PowerAuth 2.0 Push Server RESTful API: #### Device Management - `POST` [/push/device/create](#create-device) - Create new device registration +- `POST` [/push/device/create/multi](#create-device-for-multiple-associated-activations) - Create new device registration for multiple activations - `POST` [/push/device/delete](#delete-device) - Remove registered device - `POST` [/push/device/status/update](#update-device-status) - Update the status of the activation so that when activation associated with given device is not active, no notifications are sent to the device. @@ -66,7 +67,7 @@ Following endpoints are published in PowerAuth 2.0 Push Server RESTful API: ### Error Handling -PowerAuth 2.0 Push Server uses following format for error response body, accompanied with an appropriate HTTP status code. Besides the HTTP error codes that application server may return regardless of server application (such as 404 when resource is not found or 503 when server is down), following status codes may be returned: +PowerAuth Push Server uses following format for error response body, accompanied with an appropriate HTTP status code. Besides the HTTP error codes that application server may return regardless of server application (such as 404 when resource is not found or 503 when server is down), following status codes may be returned: |`status`|`HTTP code` |Description| |--- |--- |---| @@ -74,7 +75,7 @@ PowerAuth 2.0 Push Server uses following format for error response body, accompa |ERROR |400 |Issue with a request format, or issue of the business logic| |ERROR |401 | Unauthorized, invalid security token configuration| -All error responses that are produced by the PowerAuth 2.0 Push Server have following body: +All error responses that are produced by the PowerAuth Push Server have following body: ```json @@ -118,7 +119,7 @@ Send a system status response, with basic information about the running applicat "status": "OK", "responseObject": { "applicationName": "powerauth-push", - "applicationDisplayName": "PowerAuth 2.0 Push Server", + "applicationDisplayName": "PowerAuth Push Server", "applicationEnvironment": "", "version": "0.21.0", "buildTime": "2019-01-22T14:59:14.954+0000", @@ -141,9 +142,9 @@ Then it has to forward the push token to the push server end-point. After that p ### Create Device -Create a new device push token (platform specific). Optionally, the call may include also `activationId`, so that the token is associated with given user in the PowerAuth 2.0 Server. +Create a new device push token (platform specific). Optionally, the call may include also `activationId`, so that the token is associated with given user in the PowerAuth Server. -_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 activationId value, so that there are no incorrect bindings._ +_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._ @@ -184,6 +185,54 @@ _Note: Activation ID is optional._ } ``` +### Create Device for Multiple Associated Activations + +Create a new device push token (platform specific). The call must include `activationIds` which contains list of activations to be associated with the registered device. + +_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
+ +#### **Request** + +```json +{ + "requestObject": { + "appId": 2, + "token": "1234567890987654321234567890", + "platform": "ios", + "activationIds": [ + "49414e31-f3df-4cea-87e6-f214ca3b8412", + "26c94bf8-f594-4bd8-9c51-93449926b644" + ] + } +} +``` + +- `appId` - Application that device is using. +- `token` - Identifier for device. +- `platform` - "_ios_ | _android_" +- `activationIds` - Associated activation identifiers + +_Note: Activation ID list is mandatory._ + +#### **Response** + +```json +{ + "status": "OK" +} +``` + ### Delete Device Removes registered device based on the push token value. 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 26a1fb2e5..5efbc8757 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 @@ -75,7 +75,7 @@ public PushDeviceController(PushDeviceRepository pushDeviceRepository, PowerAuth "If such device already exist, date on last registration is updated and also platform might be changed\n" + "\n---" + "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 activationId value," + + "It's the service that calls this endpoint responsibility to assure that the device is somehow authenticated before the push token is assigned with given activation ID," + " so that there are no incorrect bindings.") public @ResponseBody Response createDevice(@RequestBody ObjectRequest request) throws PushServerException { CreateDeviceRequest requestedObject = request.getRequestObject(); @@ -127,7 +127,7 @@ public PushDeviceController(PushDeviceRepository pushDeviceRepository, PowerAuth "If such device already exist, date on last registration is updated and also platform might be changed\n" + "\n---" + "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 activationId value," + + "It's the service that calls this endpoint responsibility to assure that the device is somehow authenticated before the push token is assigned with given activation IDs," + " so that there are no incorrect bindings.") public @ResponseBody Response createDeviceMultipleActivations(@RequestBody ObjectRequest request) throws PushServerException { CreateDeviceForActivationsRequest requestedObject = request.getRequestObject(); From b54ed596ec8bc1990248f9349a572efd9c306646 Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Wed, 20 Nov 2019 17:24:42 +0100 Subject: [PATCH 36/56] Separate delete and save actions --- .../io/getlime/push/controller/rest/PushDeviceController.java | 4 ++++ 1 file changed, 4 insertions(+) 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 5efbc8757..81de092a3 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 @@ -150,6 +150,9 @@ public PushDeviceController(PushDeviceRepository pushDeviceRepository, PowerAuth // Both Apple and Google issue new push token despite the fact the old one didn't expire yet. The old push token // needs to be deleted. pushDeviceRepository.deleteByAppIdAndActivationId(appId, activationId); + }); + + activationIds.forEach(activationId -> { // Register device for given activation ID. Device registration is always new because of the previous delete step. PushDeviceRegistrationEntity device = new PushDeviceRegistrationEntity(); device.setAppId(appId); @@ -159,6 +162,7 @@ public PushDeviceController(PushDeviceRepository pushDeviceRepository, PowerAuth updateActivationForDevice(device, activationId); pushDeviceRepository.save(device); }); + return new Response(); } From d5d9873594922f3275aeb0aa15ee132d91fe4a73 Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Fri, 22 Nov 2019 12:58:41 +0100 Subject: [PATCH 37/56] Add test for device registration with multiple activations Simplify assertTrue statements --- .../push/client/PushServerClientTest.java | 35 ++++++--- ...shServerClientTestMultipleActivations.java | 75 +++++++++++++++++++ ...ation-test-multiple-activations.properties | 37 +++++++++ 3 files changed, 138 insertions(+), 9 deletions(-) create mode 100644 powerauth-push-server/src/test/java/io/getlime/push/client/PushServerClientTestMultipleActivations.java create mode 100644 powerauth-push-server/src/test/resources/application-test-multiple-activations.properties diff --git a/powerauth-push-server/src/test/java/io/getlime/push/client/PushServerClientTest.java b/powerauth-push-server/src/test/java/io/getlime/push/client/PushServerClientTest.java index b46088134..4e388890b 100644 --- a/powerauth-push-server/src/test/java/io/getlime/push/client/PushServerClientTest.java +++ b/powerauth-push-server/src/test/java/io/getlime/push/client/PushServerClientTest.java @@ -45,6 +45,8 @@ import java.util.*; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * Class used for testing client-server methods @@ -60,6 +62,7 @@ public class PushServerClientTest { private static final String MOCK_ACTIVATION_ID = "11111111-1111-1111-1111-111111111111"; + private static final String MOCK_ACTIVATION_ID_2 = "22222222-2222-2222-2222-222222222222"; private static final Long MOCK_APPLICATION_ID = 1L; private static final String MOCK_PUSH_TOKEN = "1234567890987654321234567890"; @@ -102,26 +105,26 @@ public void getServiceStatusTest() throws Exception { @Test public void createDeviceTest() throws Exception { boolean actual = pushServerClient.createDevice(MOCK_APPLICATION_ID, MOCK_PUSH_TOKEN, MobilePlatform.iOS); - assertThat(actual).isTrue(); + assertTrue(actual); } @Test public void createDeviceWithActivationIDTest() throws Exception { boolean actual = pushServerClient.createDevice(MOCK_APPLICATION_ID, MOCK_PUSH_TOKEN, MobilePlatform.iOS, MOCK_ACTIVATION_ID); - assertThat(actual).isTrue(); + assertTrue(actual); } @Test public void deleteDeviceTest() throws Exception { boolean actual = pushServerClient.deleteDevice(MOCK_APPLICATION_ID, MOCK_PUSH_TOKEN); - assertThat(actual).isTrue(); + assertTrue(actual); } @Test public void updateDeviceStatusTest() throws Exception { boolean actual = pushServerClient.updateDeviceStatus(MOCK_ACTIVATION_ID); - assertThat(actual).isTrue(); + assertTrue(actual); } @@ -226,7 +229,7 @@ public void createCampaignTest() throws Exception { @Test public void deleteCampaignTest() throws Exception { boolean actual = pushServerClient.deleteCampaign(2L); - assertThat(actual).isTrue(); + assertTrue(actual); } @Test @@ -254,7 +257,7 @@ public void addUsersToCampaignTest() throws Exception { ListOfUsers listOfUsers = new ListOfUsers(); listOfUsers.addAll(Arrays.asList("1234567890", "1234567891", "1234567893")); boolean actual = pushServerClient.addUsersToCampaign(1L, listOfUsers); - assertThat(actual).isTrue(); + assertTrue(actual); } @@ -275,7 +278,7 @@ public void deleteUsersFromCampaignTest() throws Exception { ListOfUsers listOfUsers = new ListOfUsers(); listOfUsers.addAll(Arrays.asList("1234567890", "1234567891", "1234567893")); boolean actual = pushServerClient.deleteUsersFromCampaign(3L, listOfUsers); - assertThat(actual).isTrue(); + assertTrue(actual); } @Test @@ -285,12 +288,26 @@ public void sendTestingCampaignTest() throws Exception { exception.expect(HasPropertyWithValue.hasProperty("error", HasPropertyWithValue.hasProperty("message", CoreMatchers.is("Application not found")))); } boolean actual = pushServerClient.sendTestCampaign(1L, "1234567890"); - assertThat(actual).isTrue(); + assertTrue(actual); } @Test public void sendCampaignTest() throws Exception { boolean actual = pushServerClient.sendCampaign(1L); - assertThat(actual).isTrue(); + assertTrue(actual); + } + + @Test + public void createDeviceWithMultipleActivationsTest() throws Exception { + List activationIds = new ArrayList<>(); + activationIds.add(MOCK_ACTIVATION_ID); + activationIds.add(MOCK_ACTIVATION_ID_2); + Exception exception = null; + try { + pushServerClient.createDeviceForActivations(MOCK_APPLICATION_ID, MOCK_PUSH_TOKEN, MobilePlatform.iOS, activationIds); + } catch (Exception ex) { + exception = ex; + } + assertNotNull(exception); } } diff --git a/powerauth-push-server/src/test/java/io/getlime/push/client/PushServerClientTestMultipleActivations.java b/powerauth-push-server/src/test/java/io/getlime/push/client/PushServerClientTestMultipleActivations.java new file mode 100644 index 000000000..d26f5b52b --- /dev/null +++ b/powerauth-push-server/src/test/java/io/getlime/push/client/PushServerClientTestMultipleActivations.java @@ -0,0 +1,75 @@ +/* + * 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.client; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringRunner; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Class used for testing client-server methods + * All tests cover each method from {@link PushServerClient}. + * Methods are named with suffix "Test" and are just compared with expected server responses. + * Using in memory H2 create/drop database. + * + * @author Roman Strobl, roman.strobl@wultra.com + */ +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@TestPropertySource(locations = "classpath:application-test-multiple-activations.properties") +public class PushServerClientTestMultipleActivations { + + private static final String MOCK_ACTIVATION_ID = "11111111-1111-1111-1111-111111111111"; + private static final String MOCK_ACTIVATION_ID_2 = "22222222-2222-2222-2222-222222222222"; + private static final Long MOCK_APPLICATION_ID = 1L; + private static final String MOCK_PUSH_TOKEN = "1234567890987654321234567890"; + + @MockBean + private PushServerClient pushServerClient; + + @LocalServerPort + private int port; + + @Before + public void setPushServerClientsUrl() { + pushServerClient = new PushServerClient("http://localhost:" + port); + } + + @Rule + public final ExpectedException exception = ExpectedException.none(); + + @Test + public void createDeviceWithMultipleActivationsTest() throws Exception { + List activationIds = new ArrayList<>(); + activationIds.add(MOCK_ACTIVATION_ID); + activationIds.add(MOCK_ACTIVATION_ID_2); + boolean result = pushServerClient.createDeviceForActivations(MOCK_APPLICATION_ID, MOCK_PUSH_TOKEN, MobilePlatform.iOS, activationIds); + assertTrue(result); + } +} 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 new file mode 100644 index 000000000..cda32062a --- /dev/null +++ b/powerauth-push-server/src/test/resources/application-test-multiple-activations.properties @@ -0,0 +1,37 @@ +# +# Copyright 2016 Wultra s.r.o. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + + +# H2 +spring.h2.console.enabled=true +spring.h2.console.path=/h2 +# Datasource +spring.datasource.url=jdbc:h2:file:~/powerauth +spring.datasource.username=sa +spring.datasource.password= +spring.datasource.driver-class-name=org.h2.Driver + +# Hibernate Configuration +spring.jpa.hibernate.ddl-auto=create-drop + +# PowerAuth 2.0 Service Configuration +powerauth.service.url=${POWERAUTH_ENDPOINT}/powerauth-java-server/soap +powerauth.service.security.clientToken= +powerauth.service.security.clientSecret= +powerauth.service.ssl.acceptInvalidSslCertificate=false + +# Whether push registration supports associated activations +powerauth.push.service.registration.multipleActivations.enabled=true \ No newline at end of file From 0896e75ddc69e1c120e588e3f7419f2320c596be Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Tue, 26 Nov 2019 16:30:11 +0100 Subject: [PATCH 38/56] Use device registration lookup instead of delete to satisfy all use cases Add unique indexes on table push_device_registration Do not allow registration requests with null activation ID --- docs/PowerAuth-Push-Server-0.23.0.md | 25 ++++ docs/sql/mysql/create_push_server_schema.sql | 3 +- docs/sql/oracle/create_push_server_schema.sql | 3 +- .../CreateDeviceRequestValidator.java | 3 + .../controller/rest/PushDeviceController.java | 131 ++++++++++++++---- .../push/repository/PushDeviceRepository.java | 8 ++ 6 files changed, 146 insertions(+), 27 deletions(-) create mode 100644 docs/PowerAuth-Push-Server-0.23.0.md diff --git a/docs/PowerAuth-Push-Server-0.23.0.md b/docs/PowerAuth-Push-Server-0.23.0.md new file mode 100644 index 000000000..000820661 --- /dev/null +++ b/docs/PowerAuth-Push-Server-0.23.0.md @@ -0,0 +1,25 @@ +# Migration from 0.22.0 to 0.23.0 + +## Database changes + +Following DB changes occurred between version 0.22.0 and 0.23.0: +- Added unique index (`activationId`, `push_token`) in table `push_device_registration` +- The existing `push_device_activation` index in table `push_device_registration` was changed to unique + +The DDL script for Oracle: +```sql +CREATE UNIQUE INDEX PUSH_DEVICE_ACTIVATION_TOKEN ON PUSH_DEVICE_REGISTRATION(ACTIVATION_ID, PUSH_TOKEN); + +DROP INDEX PUSH_DEVICE_ACTIVATION; +CREATE UNIQUE INDEX PUSH_DEVICE_ACTIVATION ON PUSH_DEVICE_REGISTRATION(ACTIVATION_ID); +``` + +The DDL script for MySQL: +```sql +CREATE UNIQUE INDEX `push_device_activation_token` ON `push_device_registration`(`activation_id`, `push_token`); + +DROP INDEX `push_device_activation` ON `push_device_registration`; +CREATE UNIQUE INDEX `push_device_activation` ON `push_device_registration`(`activation_id`); +``` + +In case either of the index updates fails, delete existing duplicate rows. Rows with newest timestamp_last_registered should be preserved. diff --git a/docs/sql/mysql/create_push_server_schema.sql b/docs/sql/mysql/create_push_server_schema.sql index 1e702789c..a7e1bbf10 100644 --- a/docs/sql/mysql/create_push_server_schema.sql +++ b/docs/sql/mysql/create_push_server_schema.sql @@ -62,7 +62,8 @@ CREATE UNIQUE INDEX `push_app_cred_app` ON `push_app_credentials`(`app_id`); CREATE INDEX `push_device_app_token` ON `push_device_registration`(`app_id`, `push_token`); CREATE INDEX `push_device_user_app` ON `push_device_registration`(`user_id`, `app_id`); -CREATE INDEX `push_device_activation` ON `push_device_registration`(`activation_id`); +CREATE UNIQUE INDEX `push_device_activation` ON `push_device_registration`(`activation_id`); +CREATE UNIQUE INDEX `push_device_activation_token` ON `push_device_registration`(`activation_id`, `push_token`); CREATE INDEX `push_message_status` ON `push_message`(`status`); diff --git a/docs/sql/oracle/create_push_server_schema.sql b/docs/sql/oracle/create_push_server_schema.sql index 0b9282b07..5aa7b2a24 100644 --- a/docs/sql/oracle/create_push_server_schema.sql +++ b/docs/sql/oracle/create_push_server_schema.sql @@ -66,7 +66,8 @@ CREATE UNIQUE INDEX PUSH_APP_CRED_APP ON PUSH_APP_CREDENTIALS(APP_ID); CREATE INDEX PUSH_DEVICE_APP_TOKEN ON PUSH_DEVICE_REGISTRATION(APP_ID, PUSH_TOKEN); CREATE INDEX PUSH_DEVICE_USER_APP ON PUSH_DEVICE_REGISTRATION(USER_ID, APP_ID); -CREATE INDEX PUSH_DEVICE_ACTIVATION ON PUSH_DEVICE_REGISTRATION(ACTIVATION_ID); +CREATE UNIQUE INDEX PUSH_DEVICE_ACTIVATION ON PUSH_DEVICE_REGISTRATION(ACTIVATION_ID); +CREATE UNIQUE INDEX PUSH_DEVICE_ACTIVATION_TOKEN ON PUSH_DEVICE_REGISTRATION(ACTIVATION_ID, PUSH_TOKEN); CREATE INDEX PUSH_MESSAGE_STATUS ON PUSH_MESSAGE(STATUS); diff --git a/powerauth-push-model/src/main/java/io/getlime/push/model/validator/CreateDeviceRequestValidator.java b/powerauth-push-model/src/main/java/io/getlime/push/model/validator/CreateDeviceRequestValidator.java index 002dfbaaa..e4492e9de 100644 --- a/powerauth-push-model/src/main/java/io/getlime/push/model/validator/CreateDeviceRequestValidator.java +++ b/powerauth-push-model/src/main/java/io/getlime/push/model/validator/CreateDeviceRequestValidator.java @@ -41,6 +41,9 @@ public static String validate(CreateDeviceRequest request) { if (request.getAppId() < 1) { return "App ID must be a positive number."; } + if (request.getActivationId() == null) { + return "Activation ID must not be null."; + } if (request.getPlatform() == null || request.getPlatform().isEmpty()) { return "Platform must not be null or empty."; } 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 81de092a3..d94cabe15 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,6 +32,8 @@ 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; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestBody; @@ -40,7 +42,10 @@ import org.springframework.web.bind.annotation.ResponseBody; import java.util.Date; +import java.util.HashSet; import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; /** * Controller responsible for device registration related business processes. @@ -51,6 +56,8 @@ @RequestMapping(value = "push/device") public class PushDeviceController { + private static final Logger logger = LoggerFactory.getLogger(PushDeviceController.class); + private final PushDeviceRepository pushDeviceRepository; private final PowerAuthServiceClient client; private final PushServiceConfiguration config; @@ -87,29 +94,28 @@ public PushDeviceController(PushDeviceRepository pushDeviceRepository, PowerAuth String pushToken = requestedObject.getToken(); String platform = requestedObject.getPlatform(); String activationId = requestedObject.getActivationId(); - // Make sure push token is unique for activation ID, in case activation ID is specified in request. See: https://github.com/wultra/powerauth-push-server/issues/262 - // Both Apple and Google issue new push token despite the fact the old one didn't expire yet. The old push tokens need to be deleted. - if (activationId != null) { - pushDeviceRepository.deleteByAppIdAndActivationId(appId, activationId); - } - List devices = pushDeviceRepository.findByAppIdAndPushToken(appId, pushToken); + List devices = lookupDeviceRegistrations(appId, activationId, pushToken); PushDeviceRegistrationEntity device; if (devices.isEmpty()) { + // The device registration is new, create a new entity. device = new PushDeviceRegistrationEntity(); device.setAppId(appId); device.setPushToken(pushToken); } else if (devices.size() == 1) { + // An existing row was found by one of the lookup methods, update this row. This means that either: + // 1. A row with same activation ID and push token is updated, in this case only the last registration timestamp changes. + // 2. A row with same activation ID but different push token is updated. A new push token has been issued by Google or Apple for an activation. + // 3. A row with same push token but different activation ID is updated. The user removed an activation and created a new one, the push token remains the same. device = devices.get(0); } else { - // Push token can be associated with multiple device registrations only when associated activations are enabled. + // Multiple existing rows have been found. This can only occur during lookup by push token. + // Push token can be associated with multiple activations only when associated activations are enabled. // Push device registration must be done using /push/device/create/multi endpoint in this case. - throw new PushServerException("Multiple device registrations found for push token. Use /push/device/create/multi endpoint for this scenario."); + throw new PushServerException("Multiple device registrations found for push token. Use the /push/device/create/multi endpoint for this scenario."); } device.setTimestampLastRegistered(new Date()); device.setPlatform(platform); - if (activationId != null) { - updateActivationForDevice(device, activationId); - } + updateActivationForDevice(device, activationId); pushDeviceRepository.save(device); return new Response(); } @@ -145,27 +151,102 @@ public PushDeviceController(PushDeviceRepository pushDeviceRepository, PowerAuth String platform = requestedObject.getPlatform(); List activationIds = requestedObject.getActivationIds(); - activationIds.forEach(activationId -> { - // Make sure push token is unique for given activation ID. See: https://github.com/wultra/powerauth-push-server/issues/262 - // Both Apple and Google issue new push token despite the fact the old one didn't expire yet. The old push token - // needs to be deleted. - pushDeviceRepository.deleteByAppIdAndActivationId(appId, activationId); - }); + // Initialize loop variables. + AtomicBoolean registrationFailed = new AtomicBoolean(false); + Set usedDeviceRegistrationIds = new HashSet<>(); activationIds.forEach(activationId -> { - // Register device for given activation ID. Device registration is always new because of the previous delete step. - PushDeviceRegistrationEntity device = new PushDeviceRegistrationEntity(); - device.setAppId(appId); - device.setPushToken(pushToken); - device.setTimestampLastRegistered(new Date()); - device.setPlatform(platform); - updateActivationForDevice(device, activationId); - pushDeviceRepository.save(device); + try { + List devices = lookupDeviceRegistrations(appId, activationId, pushToken); + PushDeviceRegistrationEntity device; + if (devices.isEmpty()) { + // The device registration is new, create a new entity. + device = initDeviceRegistrationEntity(appId, pushToken); + } else if (devices.size() == 1) { + device = devices.get(0); + if (usedDeviceRegistrationIds.contains(device.getId())) { + // The row has already been used for another activation within this request. Create a new row instead. + device = initDeviceRegistrationEntity(appId, pushToken); + } + // Add the device registration entity ID into list of used entities to avoid merging multiple rows into one. + usedDeviceRegistrationIds.add(device.getId()); + } else { + // Multiple existing rows have been found. This can only occur during lookup by push token. + // It is not clear how original rows should be mapped to new rows because they were not looked up + // using an activation ID. Delete existing rows and create a new row. + pushDeviceRepository.deleteByAppIdAndActivationId(appId, activationId); + device = initDeviceRegistrationEntity(appId, pushToken); + } + device.setAppId(appId); + device.setPushToken(pushToken); + device.setTimestampLastRegistered(new Date()); + device.setPlatform(platform); + updateActivationForDevice(device, activationId); + pushDeviceRepository.save(device); + } catch (PushServerException ex) { + logger.error(ex.getMessage(), ex); + registrationFailed.set(true); + } }); + if (registrationFailed.get()) { + throw new PushServerException("Device registration failed"); + } + return new Response(); } + /** + * Initialize a new device registration entity for given app ID and push token. + * @param appId App ID. + * @param pushToken Push token. + * @return New device registration entity. + */ + private PushDeviceRegistrationEntity initDeviceRegistrationEntity(Long appId, String pushToken) { + PushDeviceRegistrationEntity device = new PushDeviceRegistrationEntity(); + device.setAppId(appId); + device.setPushToken(pushToken); + return device; + } + + /** + * Lookup device registrations using app ID, activation ID and push token. + *
+ * The query priorities are ranging from most exact to least exact match: + *
    + *
  • Lookup by activation ID and push token.
  • + *
  • Lookup by activation ID.
  • + *
  • Lookup by application ID and push token.
  • + *
+ * @param appId Application ID. + * @param activationId Activation ID. + * @param pushToken Push token. + * @return List of found device registration entities. + */ + private List lookupDeviceRegistrations(Long appId, String activationId, String pushToken) throws PushServerException { + List deviceRegistrations; + // At first, lookup the device registrations by match on activationId and pushToken. + deviceRegistrations = pushDeviceRepository.findByActivationIdAndPushToken(activationId, pushToken); + if (!deviceRegistrations.isEmpty()) { + if (deviceRegistrations.size() != 1) { + throw new PushServerException("Multiple device registrations found during lookup by activation ID and push token. Please delete duplicate rows and make sure database indexes have been applied on push_device_registration table."); + } + return deviceRegistrations; + } + // Second, lookup the device registrations by match on activationId. + deviceRegistrations = pushDeviceRepository.findByActivationId(activationId); + if (!deviceRegistrations.isEmpty()) { + if (deviceRegistrations.size() != 1) { + throw new PushServerException("Multiple device registrations found during lookup by activation ID. Please delete duplicate rows and make sure database indexes have been applied on push_device_registration table."); + } + return deviceRegistrations; + } + // Third, lookup the device registration by match on appId and pushToken. Multiple results can be returned in this case, this is a multi-activation scenario. + deviceRegistrations = pushDeviceRepository.findByAppIdAndPushToken(appId, pushToken); + // The final result is definitive, either device registrations were found by push token or none were found at all. + return deviceRegistrations; + } + /** * Update activation for given device in case activation exists in PowerAuth server and it is not in REMOVED state. * @param device Push device registration entity. diff --git a/powerauth-push-server/src/main/java/io/getlime/push/repository/PushDeviceRepository.java b/powerauth-push-server/src/main/java/io/getlime/push/repository/PushDeviceRepository.java index a4051f1af..0624d4a6d 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/repository/PushDeviceRepository.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/repository/PushDeviceRepository.java @@ -40,6 +40,14 @@ public interface PushDeviceRepository extends CrudRepository findByAppIdAndPushToken(Long appId, String pushToken); + /** + * Find all device registrations for given activation ID and push token. + * @param activationId Activation ID. + * @param pushToken Push token. + * @return Device registrations matching provided values. + */ + List findByActivationIdAndPushToken(String activationId, String pushToken); + /** * Find all device registrations by given activation ID. In normal case, the list will contain only one value. * @param activationId Activation ID. From 4833dc74393443eed3477b2e8db3761de9789ed1 Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Tue, 26 Nov 2019 16:49:24 +0100 Subject: [PATCH 39/56] Update documentation --- docs/Push-Server-API.md | 6 +----- .../getlime/push/controller/rest/PushDeviceController.java | 4 ++-- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/docs/Push-Server-API.md b/docs/Push-Server-API.md index a5404167f..a7d63d685 100644 --- a/docs/Push-Server-API.md +++ b/docs/Push-Server-API.md @@ -142,7 +142,7 @@ Then it has to forward the push token to the push server end-point. After that p ### Create Device -Create a new device push token (platform specific). Optionally, the call may include also `activationId`, so that the token is associated with given user in the PowerAuth Server. +Create a new device push token (platform specific). The call must include `activationId`, so that the token is associated with given user in the PowerAuth Server. _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._ @@ -175,8 +175,6 @@ _Note: Since this endpoint is usually called by the back-end service, it is not - `platform` - "_ios_ | _android_" - `activationId` - Activation identifier -_Note: Activation ID is optional._ - #### **Response** ```json @@ -223,8 +221,6 @@ _Note: Since this endpoint is usually called by the back-end service, it is not - `platform` - "_ios_ | _android_" - `activationIds` - Associated activation identifiers -_Note: Activation ID list is mandatory._ - #### **Response** ```json 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 d94cabe15..4217f65d2 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 @@ -77,8 +77,8 @@ public PushDeviceController(PushDeviceRepository pushDeviceRepository, PowerAuth */ @RequestMapping(value = "create", method = RequestMethod.POST) @ApiOperation(value = "Create a device", - notes = "Create a new device push token (platform specific). Optionally, the call may include also activationId, so that the token is associated with given user." + - "Request body should contain application ID, device token, device's platform and optionally an activation ID. " + + notes = "Create a new device push token (platform specific). The call must include activationId, 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---" + "Note: Since this endpoint is usually called by the back-end service, it is not secured in any way. " + From d6a190199b5926266c93028ce0fded7c18590c05 Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Tue, 26 Nov 2019 16:59:55 +0100 Subject: [PATCH 40/56] Update API documentation --- .../io/getlime/push/controller/rest/PushDeviceController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 4217f65d2..00c83e60e 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 @@ -77,7 +77,7 @@ public PushDeviceController(PushDeviceRepository pushDeviceRepository, PowerAuth */ @RequestMapping(value = "create", method = RequestMethod.POST) @ApiOperation(value = "Create a device", - notes = "Create a new device push token (platform specific). The call must include activationId, so that the token is associated with given user." + + 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." + "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---" + From e1883c444d6cd3cd943591de5bb34a38693bcc3d Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Tue, 26 Nov 2019 17:09:23 +0100 Subject: [PATCH 41/56] Simplify device registration code --- .../io/getlime/push/controller/rest/PushDeviceController.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) 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 00c83e60e..4072c0e74 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 @@ -98,9 +98,7 @@ public PushDeviceController(PushDeviceRepository pushDeviceRepository, PowerAuth PushDeviceRegistrationEntity device; if (devices.isEmpty()) { // The device registration is new, create a new entity. - device = new PushDeviceRegistrationEntity(); - device.setAppId(appId); - device.setPushToken(pushToken); + device = initDeviceRegistrationEntity(appId, pushToken); } else if (devices.size() == 1) { // An existing row was found by one of the lookup methods, update this row. This means that either: // 1. A row with same activation ID and push token is updated, in this case only the last registration timestamp changes. From 1273596e7f3c6c3ac72b0c15fa3bf061f6d5f404 Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Wed, 27 Nov 2019 00:22:05 +0100 Subject: [PATCH 42/56] Improve cleanup logic for multiple activations --- .../io/getlime/push/controller/rest/PushDeviceController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 4072c0e74..99e292944 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 @@ -172,7 +172,7 @@ public PushDeviceController(PushDeviceRepository pushDeviceRepository, PowerAuth // Multiple existing rows have been found. This can only occur during lookup by push token. // It is not clear how original rows should be mapped to new rows because they were not looked up // using an activation ID. Delete existing rows and create a new row. - pushDeviceRepository.deleteByAppIdAndActivationId(appId, activationId); + devices.forEach(pushDeviceRepository::delete); device = initDeviceRegistrationEntity(appId, pushToken); } device.setAppId(appId); From 7c410b2b319e3c45bcedc573d0762e1a3faff9a9 Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Wed, 27 Nov 2019 00:43:35 +0100 Subject: [PATCH 43/56] Simplify delete logic --- .../io/getlime/push/controller/rest/PushDeviceController.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) 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 99e292944..696670d6a 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 @@ -305,9 +305,7 @@ private void updateActivationForDevice(PushDeviceRegistrationEntity device, Stri Long appId = requestedObject.getAppId(); String pushToken = requestedObject.getToken(); List devices = pushDeviceRepository.findByAppIdAndPushToken(appId, pushToken); - if (!devices.isEmpty()) { - pushDeviceRepository.deleteAll(devices); - } + devices.forEach(pushDeviceRepository::delete); return new Response(); } } \ No newline at end of file From 7f59fda276de1a280e7c08e89fb05e049dab49e6 Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Wed, 27 Nov 2019 00:44:05 +0100 Subject: [PATCH 44/56] Remove unused method --- .../io/getlime/push/repository/PushDeviceRepository.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/powerauth-push-server/src/main/java/io/getlime/push/repository/PushDeviceRepository.java b/powerauth-push-server/src/main/java/io/getlime/push/repository/PushDeviceRepository.java index 0624d4a6d..a44f3ff43 100644 --- a/powerauth-push-server/src/main/java/io/getlime/push/repository/PushDeviceRepository.java +++ b/powerauth-push-server/src/main/java/io/getlime/push/repository/PushDeviceRepository.java @@ -74,10 +74,4 @@ public interface PushDeviceRepository extends CrudRepository findByUserIdAndAppIdAndActivationId(String userId, Long appId, String activationId); - /** - * Delete all device registrations for given app ID and activation ID. - * @param appId - * @param activationId - */ - void deleteByAppIdAndActivationId(Long appId, String activationId); } From e8815935bd21a548eb6f6170e26d2512c1a74e67 Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Wed, 27 Nov 2019 13:53:21 +0100 Subject: [PATCH 45/56] Update tests --- .../java/io/getlime/push/client/PushServerClientTest.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/powerauth-push-server/src/test/java/io/getlime/push/client/PushServerClientTest.java b/powerauth-push-server/src/test/java/io/getlime/push/client/PushServerClientTest.java index 4e388890b..e8558add3 100644 --- a/powerauth-push-server/src/test/java/io/getlime/push/client/PushServerClientTest.java +++ b/powerauth-push-server/src/test/java/io/getlime/push/client/PushServerClientTest.java @@ -102,10 +102,9 @@ public void getServiceStatusTest() throws Exception { assertThat(actual.getResponseObject().getApplicationName()).isEqualTo(expected.getResponseObject().getApplicationName()); } - @Test - public void createDeviceTest() throws Exception { - boolean actual = pushServerClient.createDevice(MOCK_APPLICATION_ID, MOCK_PUSH_TOKEN, MobilePlatform.iOS); - assertTrue(actual); + @Test(expected = PushServerClientException.class) + public void createDeviceWithoutActivationIDTest() throws Exception { + pushServerClient.createDevice(MOCK_APPLICATION_ID, MOCK_PUSH_TOKEN, MobilePlatform.iOS); } @Test From 919746522b16b53bf50aa0917ecfe11e6ea9e6b7 Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Thu, 28 Nov 2019 18:53:47 +0100 Subject: [PATCH 46/56] Additional tests for device registration Tests are now integrated with PowerAuth server Minor device registration logic changes based on test results --- powerauth-push-server/pom.xml | 18 ++ .../controller/rest/PushDeviceController.java | 33 ++- .../java/io/getlime/push/ApplicationTest.java | 8 +- ...shServerClientTestMultipleActivations.java | 75 ------- .../PushServerMultipleActivationsTests.java | 208 ++++++++++++++++++ ...erClientTest.java => PushServerTests.java} | 196 ++++++++++++----- .../push/shared/PowerAuthTestClient.java | 184 ++++++++++++++++ .../shared/PushServerTestClientFactory.java | 44 ++++ 8 files changed, 622 insertions(+), 144 deletions(-) delete mode 100644 powerauth-push-server/src/test/java/io/getlime/push/client/PushServerClientTestMultipleActivations.java create mode 100644 powerauth-push-server/src/test/java/io/getlime/push/client/PushServerMultipleActivationsTests.java rename powerauth-push-server/src/test/java/io/getlime/push/client/{PushServerClientTest.java => PushServerTests.java} (59%) create mode 100644 powerauth-push-server/src/test/java/io/getlime/push/shared/PowerAuthTestClient.java create mode 100644 powerauth-push-server/src/test/java/io/getlime/push/shared/PushServerTestClientFactory.java diff --git a/powerauth-push-server/pom.xml b/powerauth-push-server/pom.xml index c1f1ea055..a0dbb8119 100644 --- a/powerauth-push-server/pom.xml +++ b/powerauth-push-server/pom.xml @@ -203,6 +203,24 @@ ${h2.version} test
+ + io.getlime.security + powerauth-java-crypto + ${powerauth.version} + test + + + io.getlime.security + powerauth-restful-model + ${powerauth.version} + test + + + org.bouncycastle + bcprov-jdk15on + 1.64 + test + 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 696670d6a..645e4d8ef 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 @@ -105,6 +105,7 @@ public PushDeviceController(PushDeviceRepository pushDeviceRepository, PowerAuth // 2. A row with same activation ID but different push token is updated. A new push token has been issued by Google or Apple for an activation. // 3. A row with same push token but different activation ID is updated. The user removed an activation and created a new one, the push token remains the same. device = devices.get(0); + updateDeviceRegistrationEntity(device, appId, pushToken); } else { // Multiple existing rows have been found. This can only occur during lookup by push token. // Push token can be associated with multiple activations only when associated activations are enabled. @@ -163,24 +164,25 @@ public PushDeviceController(PushDeviceRepository pushDeviceRepository, PowerAuth } else if (devices.size() == 1) { device = devices.get(0); if (usedDeviceRegistrationIds.contains(device.getId())) { - // The row has already been used for another activation within this request. Create a new row instead. + // The row has already been used within this request. Create a new row instead. device = initDeviceRegistrationEntity(appId, pushToken); + } else { + // Update existing row. + updateDeviceRegistrationEntity(device, appId, pushToken); } - // Add the device registration entity ID into list of used entities to avoid merging multiple rows into one. - usedDeviceRegistrationIds.add(device.getId()); } else { // Multiple existing rows have been found. This can only occur during lookup by push token. // It is not clear how original rows should be mapped to new rows because they were not looked up - // using an activation ID. Delete existing rows and create a new row. - devices.forEach(pushDeviceRepository::delete); + // using an activation ID. Delete existing rows (unless they were already used in this request) + // and create a new row. + devices.stream().filter(existingDevice -> !usedDeviceRegistrationIds.contains(existingDevice.getId())).forEach(pushDeviceRepository::delete); device = initDeviceRegistrationEntity(appId, pushToken); } - device.setAppId(appId); - device.setPushToken(pushToken); device.setTimestampLastRegistered(new Date()); device.setPlatform(platform); updateActivationForDevice(device, activationId); - pushDeviceRepository.save(device); + PushDeviceRegistrationEntity registeredDevice = pushDeviceRepository.save(device); + usedDeviceRegistrationIds.add(registeredDevice.getId()); } catch (PushServerException ex) { logger.error(ex.getMessage(), ex); registrationFailed.set(true); @@ -207,6 +209,16 @@ private PushDeviceRegistrationEntity initDeviceRegistrationEntity(Long appId, St return device; } + /** + * Update a device registration entity with given app ID and push token. + * @param appId App ID. + * @param pushToken Push token. + */ + private void updateDeviceRegistrationEntity(PushDeviceRegistrationEntity device, Long appId, String pushToken) { + device.setAppId(appId); + device.setPushToken(pushToken); + } + /** * Lookup device registrations using app ID, activation ID and push token. *
@@ -247,16 +259,19 @@ private List lookupDeviceRegistrations(Long appId, /** * Update activation for given device in case activation exists in PowerAuth server and it is not in REMOVED state. + * Otherwise fail the device registration because registration could not be associated with an activation. * @param device Push device registration entity. * @param activationId Activation ID. */ - private void updateActivationForDevice(PushDeviceRegistrationEntity device, String activationId) { + 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; } + throw new PushServerException("Device registration failed because associated activation is not ACTIVE."); } /** 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 3ce589b5d..9a72238fa 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 @@ -27,8 +27,8 @@ @TestPropertySource(locations = "classpath:application-test.properties") public class ApplicationTest { - @Test - public void contextLoads() throws Exception { - } + @Test + public void contextLoads() { + } - } +} diff --git a/powerauth-push-server/src/test/java/io/getlime/push/client/PushServerClientTestMultipleActivations.java b/powerauth-push-server/src/test/java/io/getlime/push/client/PushServerClientTestMultipleActivations.java deleted file mode 100644 index d26f5b52b..000000000 --- a/powerauth-push-server/src/test/java/io/getlime/push/client/PushServerClientTestMultipleActivations.java +++ /dev/null @@ -1,75 +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.client; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.boot.web.server.LocalServerPort; -import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit4.SpringRunner; - -import java.util.ArrayList; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertTrue; - -/** - * Class used for testing client-server methods - * All tests cover each method from {@link PushServerClient}. - * Methods are named with suffix "Test" and are just compared with expected server responses. - * Using in memory H2 create/drop database. - * - * @author Roman Strobl, roman.strobl@wultra.com - */ -@RunWith(SpringRunner.class) -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@TestPropertySource(locations = "classpath:application-test-multiple-activations.properties") -public class PushServerClientTestMultipleActivations { - - private static final String MOCK_ACTIVATION_ID = "11111111-1111-1111-1111-111111111111"; - private static final String MOCK_ACTIVATION_ID_2 = "22222222-2222-2222-2222-222222222222"; - private static final Long MOCK_APPLICATION_ID = 1L; - private static final String MOCK_PUSH_TOKEN = "1234567890987654321234567890"; - - @MockBean - private PushServerClient pushServerClient; - - @LocalServerPort - private int port; - - @Before - public void setPushServerClientsUrl() { - pushServerClient = new PushServerClient("http://localhost:" + port); - } - - @Rule - public final ExpectedException exception = ExpectedException.none(); - - @Test - public void createDeviceWithMultipleActivationsTest() throws Exception { - List activationIds = new ArrayList<>(); - activationIds.add(MOCK_ACTIVATION_ID); - activationIds.add(MOCK_ACTIVATION_ID_2); - boolean result = pushServerClient.createDeviceForActivations(MOCK_APPLICATION_ID, MOCK_PUSH_TOKEN, MobilePlatform.iOS, activationIds); - assertTrue(result); - } -} 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 new file mode 100644 index 000000000..4276d496e --- /dev/null +++ b/powerauth-push-server/src/test/java/io/getlime/push/client/PushServerMultipleActivationsTests.java @@ -0,0 +1,208 @@ +/* + * Copyright 2019 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.client; + +import io.getlime.push.repository.PushDeviceRepository; +import io.getlime.push.repository.model.PushDeviceRegistrationEntity; +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; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringRunner; + +import java.util.*; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Class used for testing multi-activation registrations. + * + * @author Roman Strobl, roman.strobl@wultra.com + */ +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@TestPropertySource(locations = "classpath:application-test-multiple-activations.properties") +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public class PushServerMultipleActivationsTests { + + private static final String MOCK_PUSH_TOKEN = "1234567890987654321234567890"; + private static final String MOCK_PUSH_TOKEN_2 = "9876543212345678901234567890"; + + @Autowired + private PushDeviceRepository pushDeviceRepository; + + @LocalServerPort + private int port; + + @Value("${powerauth.service.url}") + private String powerAuthServiceUrl; + + @Autowired + private PushServerTestClientFactory testClientFactory; + + @MockBean + private PushServerClient pushServerClient; + + @MockBean + private PowerAuthTestClient powerAuthTestClient; + + @Rule + public final ExpectedException exception = ExpectedException.none(); + + @Before + public void setUp() throws Exception { + pushServerClient = testClientFactory.createPushServerClient("http://localhost:" + port); + powerAuthTestClient = testClientFactory.createPowerAuthTestClient(); + } + + @Test + public void createDeviceWithMultipleActivationsTest() throws Exception { + List activationIds = new ArrayList<>(); + activationIds.add(powerAuthTestClient.getActivationId()); + activationIds.add(powerAuthTestClient.getActivationId2()); + boolean result = pushServerClient.createDeviceForActivations(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.iOS, activationIds); + assertTrue(result); + pushDeviceRepository.findByAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN).forEach(pushDeviceRepository::delete); + } + + @Test(expected = PushServerClientException.class) + public void createDeviceWithMultipleActivationsInvalidTest() throws Exception { + pushServerClient.createDeviceForActivations(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.iOS, Collections.emptyList()); + } + + @Test + public void createDeviceSameActivationsSamePushTokenUpdatesTest() throws PushServerClientException { + List activationIds = new ArrayList<>(); + activationIds.add(powerAuthTestClient.getActivationId()); + activationIds.add(powerAuthTestClient.getActivationId2()); + // This test tests refresh of a device registration + boolean actual = pushServerClient.createDeviceForActivations(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.iOS, activationIds); + assertTrue(actual); + List devices = pushDeviceRepository.findByAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN); + assertEquals(2, devices.size()); + Set rowIds = new HashSet<>(); + devices.forEach(device -> rowIds.add(device.getId())); + boolean actual2 = pushServerClient.createDeviceForActivations(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.iOS, activationIds); + assertTrue(actual2); + List devices2 = pushDeviceRepository.findByAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN); + assertEquals(2, devices2.size()); + Set rowIds2 = new HashSet<>(); + devices2.forEach(device -> rowIds2.add(device.getId())); + assertEquals(rowIds, rowIds2); + pushDeviceRepository.findByAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN).forEach(pushDeviceRepository::delete); + } + + @Test + public void createDeviceSameActivationsDifferentPushTokenUpdatesTest() throws PushServerClientException { + List activationIds = new ArrayList<>(); + activationIds.add(powerAuthTestClient.getActivationId()); + activationIds.add(powerAuthTestClient.getActivationId2()); + // This test tests refresh of a device registration + boolean actual = pushServerClient.createDeviceForActivations(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.iOS, activationIds); + assertTrue(actual); + List devices = pushDeviceRepository.findByAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN); + assertEquals(2, devices.size()); + Set rowIds = new HashSet<>(); + devices.forEach(device -> rowIds.add(device.getId())); + boolean actual2 = pushServerClient.createDeviceForActivations(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN_2, MobilePlatform.iOS, activationIds); + assertTrue(actual2); + List devices2 = pushDeviceRepository.findByAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN_2); + assertEquals(2, devices2.size()); + Set rowIds2 = new HashSet<>(); + devices2.forEach(device -> rowIds2.add(device.getId())); + assertEquals(rowIds, rowIds2); + pushDeviceRepository.findByAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN_2).forEach(pushDeviceRepository::delete); + } + + @Test + public void createDeviceDifferentTwoActivationsSamePushTokenUpdatesTest() throws PushServerClientException { + List activationIds = new ArrayList<>(); + activationIds.add(powerAuthTestClient.getActivationId()); + activationIds.add(powerAuthTestClient.getActivationId2()); + // This test tests refresh of a device registration + boolean actual = pushServerClient.createDeviceForActivations(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.iOS, activationIds); + assertTrue(actual); + List devices = pushDeviceRepository.findByAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN); + assertEquals(2, devices.size()); + Set rowIds = new HashSet<>(); + devices.forEach(device -> rowIds.add(device.getId())); + List activationIds2 = new ArrayList<>(); + activationIds2.add(powerAuthTestClient.getActivationId3()); + activationIds2.add(powerAuthTestClient.getActivationId4()); + boolean actual2 = pushServerClient.createDeviceForActivations(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.iOS, activationIds2); + assertTrue(actual2); + List devices2 = pushDeviceRepository.findByAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN); + assertEquals(2, devices2.size()); + Set rowIds2 = new HashSet<>(); + devices2.forEach(device -> rowIds2.add(device.getId())); + // Row IDs do not match, because the activations are different + assertNotEquals(rowIds, rowIds2); + pushDeviceRepository.findByAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN_2).forEach(pushDeviceRepository::delete); + } + + @Test + public void createDeviceDifferentActivationSamePushTokenUpdatesTest() throws PushServerClientException { + List activationIds = new ArrayList<>(); + activationIds.add(powerAuthTestClient.getActivationId()); + activationIds.add(powerAuthTestClient.getActivationId2()); + // This test tests refresh of a device registration + boolean actual = pushServerClient.createDeviceForActivations(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.iOS, activationIds); + assertTrue(actual); + List devices = pushDeviceRepository.findByAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN); + assertEquals(2, devices.size()); + Set rowIds = new HashSet<>(); + devices.forEach(device -> rowIds.add(device.getId())); + List activationIds2 = new ArrayList<>(); + activationIds2.add(powerAuthTestClient.getActivationId()); + activationIds2.add(powerAuthTestClient.getActivationId4()); + boolean actual2 = pushServerClient.createDeviceForActivations(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.iOS, activationIds2); + assertTrue(actual2); + List devices2 = pushDeviceRepository.findByAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN); + assertEquals(2, devices2.size()); + Set rowIds2 = new HashSet<>(); + devices2.forEach(device -> rowIds2.add(device.getId())); + // Row IDs do not match, because the one of activations is different + assertNotEquals(rowIds, rowIds2); + pushDeviceRepository.findByAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN_2).forEach(pushDeviceRepository::delete); + } + + @Test(expected = PushServerClientException.class) + public void createDeviceMixedRegistrationEndpointsTest() throws PushServerClientException { + List activationIds = new ArrayList<>(); + activationIds.add(powerAuthTestClient.getActivationId()); + activationIds.add(powerAuthTestClient.getActivationId2()); + // This test tests refresh of a device registration + boolean actual = pushServerClient.createDeviceForActivations(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.iOS, activationIds); + assertTrue(actual); + List devices = pushDeviceRepository.findByAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN); + assertEquals(2, devices.size()); + pushServerClient.createDevice(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.iOS, powerAuthTestClient.getActivationId3()); + } + +} diff --git a/powerauth-push-server/src/test/java/io/getlime/push/client/PushServerClientTest.java b/powerauth-push-server/src/test/java/io/getlime/push/client/PushServerTests.java similarity index 59% rename from powerauth-push-server/src/test/java/io/getlime/push/client/PushServerClientTest.java rename to powerauth-push-server/src/test/java/io/getlime/push/client/PushServerTests.java index e8558add3..2f0f5988e 100644 --- a/powerauth-push-server/src/test/java/io/getlime/push/client/PushServerClientTest.java +++ b/powerauth-push-server/src/test/java/io/getlime/push/client/PushServerTests.java @@ -27,14 +27,20 @@ import io.getlime.push.model.request.SendPushMessageRequest; import io.getlime.push.model.response.*; import io.getlime.push.repository.AppCredentialsRepository; +import io.getlime.push.repository.PushDeviceRepository; +import io.getlime.push.repository.model.PushDeviceRegistrationEntity; +import io.getlime.push.shared.PowerAuthTestClient; +import io.getlime.push.shared.PushServerTestClientFactory; import org.hamcrest.CoreMatchers; import org.hamcrest.beans.HasPropertyWithValue; 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.test.web.client.TestRestTemplate; @@ -45,8 +51,8 @@ import java.util.*; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.*; /** * Class used for testing client-server methods @@ -55,16 +61,16 @@ * Using in memory H2 create/drop database. * * @author Martin Tupy, martin.tupy.work@gmail.com + * @author Roman Strobl, roman.strobl@wultra.com */ @RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @TestPropertySource(locations = "classpath:application-test.properties") -public class PushServerClientTest { +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public class PushServerTests { - private static final String MOCK_ACTIVATION_ID = "11111111-1111-1111-1111-111111111111"; - private static final String MOCK_ACTIVATION_ID_2 = "22222222-2222-2222-2222-222222222222"; - private static final Long MOCK_APPLICATION_ID = 1L; private static final String MOCK_PUSH_TOKEN = "1234567890987654321234567890"; + private static final String MOCK_PUSH_TOKEN_2 = "9876543212345678901234567890"; @Autowired private TestRestTemplate restTemplate; @@ -72,19 +78,31 @@ public class PushServerClientTest { @Autowired private ObjectMapper mapper; + @Autowired + private AppCredentialsRepository appCredentialsRepository; + + @Autowired + private PushDeviceRepository pushDeviceRepository; + + @Autowired + private PushServerTestClientFactory testClientFactory; + @MockBean private PushServerClient pushServerClient; - @Autowired - private AppCredentialsRepository appCredentialsRepository; + @MockBean + private PowerAuthTestClient powerAuthTestClient; @LocalServerPort private int port; + @Value("${powerauth.service.url}") + private String powerAuthServiceUrl; + @Before - public void setPushServerClientsUrl() { - pushServerClient = new PushServerClient("http://localhost:" + port); - mapper = new ObjectMapper(); + public void setUp() throws Exception { + pushServerClient = testClientFactory.createPushServerClient("http://localhost:" + port); + powerAuthTestClient = testClientFactory.createPowerAuthTestClient(); } @Rule @@ -94,8 +112,8 @@ public void setPushServerClientsUrl() { public void getServiceStatusTest() throws Exception { ObjectResponse actual = pushServerClient.getServiceStatus(); String body = restTemplate.getForEntity("http://localhost:" + port + "/push/service/status", String.class).getBody(); - ObjectResponse expected = mapper.readValue(body, new TypeReference>() { - }); + assertNotNull(body); + ObjectResponse expected = mapper.readValue(body, new TypeReference>() {}); assertThat(actual.getStatus()).isEqualTo(expected.getStatus()); assertThat(actual.getResponseObject().getApplicationDisplayName()).isEqualTo(expected.getResponseObject().getApplicationDisplayName()); assertThat(actual.getResponseObject().getApplicationEnvironment()).isEqualTo(expected.getResponseObject().getApplicationEnvironment()); @@ -104,29 +122,53 @@ public void getServiceStatusTest() throws Exception { @Test(expected = PushServerClientException.class) public void createDeviceWithoutActivationIDTest() throws Exception { - pushServerClient.createDevice(MOCK_APPLICATION_ID, MOCK_PUSH_TOKEN, MobilePlatform.iOS); + pushServerClient.createDevice(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.iOS); } @Test public void createDeviceWithActivationIDTest() throws Exception { - boolean actual = pushServerClient.createDevice(MOCK_APPLICATION_ID, MOCK_PUSH_TOKEN, MobilePlatform.iOS, MOCK_ACTIVATION_ID); - assertTrue(actual); + boolean result = pushServerClient.createDevice(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.iOS, powerAuthTestClient.getActivationId()); + assertTrue(result); + List devices = pushDeviceRepository.findByAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN); + assertEquals(1, devices.size()); + devices.forEach(pushDeviceRepository::delete); } @Test public void deleteDeviceTest() throws Exception { - boolean actual = pushServerClient.deleteDevice(MOCK_APPLICATION_ID, MOCK_PUSH_TOKEN); - assertTrue(actual); + boolean result = pushServerClient.createDevice(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.iOS, powerAuthTestClient.getActivationId()); + assertTrue(result); + List devices = pushDeviceRepository.findByAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN); + assertEquals(1, devices.size()); + boolean result2 = pushServerClient.deleteDevice(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN); + assertTrue(result2); + List devices2 = pushDeviceRepository.findByAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN); + assertEquals(0, devices2.size()); } @Test public void updateDeviceStatusTest() throws Exception { - boolean actual = pushServerClient.updateDeviceStatus(MOCK_ACTIVATION_ID); - assertTrue(actual); + boolean result = pushServerClient.createDevice(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.iOS, powerAuthTestClient.getActivationId()); + assertTrue(result); + List devices = pushDeviceRepository.findByAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN); + assertEquals(1, devices.size()); + powerAuthTestClient.blockActivation(powerAuthTestClient.getActivationId()); + boolean result2 = pushServerClient.updateDeviceStatus(powerAuthTestClient.getActivationId()); + assertTrue(result2); + List devices2 = pushDeviceRepository.findByAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN); + assertEquals(1, devices2.size()); + assertFalse(devices2.get(0).getActive()); + powerAuthTestClient.unblockActivation(powerAuthTestClient.getActivationId()); + boolean result3 = pushServerClient.updateDeviceStatus(powerAuthTestClient.getActivationId()); + assertTrue(result3); + List devices3 = pushDeviceRepository.findByAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN); + assertEquals(1, devices3.size()); + assertTrue(devices3.get(0).getActive()); + boolean result4 = pushServerClient.deleteDevice(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN); + assertTrue(result4); } - @Test @SuppressWarnings("unchecked") //known parameters of HashMap public void sendPushMessageTest() throws Exception { @@ -148,17 +190,17 @@ public void sendPushMessageTest() throws Exception { pushMessageBody.setExtras((Map) new HashMap().put("_comment", "Any custom data.")); attributes.setSilent(true); attributes.setPersonal(true); - pushMessage.setUserId("123"); - pushMessage.setActivationId("49414e31-f3df-4cea-87e6-f214ca3b8412"); + pushMessage.setUserId("Test_User"); + pushMessage.setActivationId(powerAuthTestClient.getActivationId()); pushMessage.setAttributes(attributes); pushMessage.setBody(pushMessageBody); pushMessage.setAttributes(attributes); - request.setAppId(2L); + request.setAppId(powerAuthTestClient.getApplicationId()); request.setMessage(pushMessage); - ObjectResponse actual = pushServerClient.sendPushMessage(2L, pushMessage); + ObjectResponse actual = pushServerClient.sendPushMessage(powerAuthTestClient.getApplicationId(), pushMessage); String body = restTemplate.postForEntity("http://localhost:" + port + "/push/message/send", new ObjectRequest<>(request), String.class).getBody(); - ObjectResponse expected = mapper.readValue(body, new TypeReference>() { - }); + assertNotNull(body); + ObjectResponse expected = mapper.readValue(body, new TypeReference>() {}); assertThat(actual.getStatus()).isEqualTo(expected.getStatus()); assertThat(actual.getResponseObject()).isEqualTo(expected.getResponseObject()); } @@ -186,18 +228,18 @@ public void sendPushMessageBatchTest() throws Exception { pushMessageBody.setExtras((Map) new HashMap().put("_comment", "Any custom data.")); attributes.setSilent(true); attributes.setPersonal(true); - pushMessage.setUserId("123"); - pushMessage.setActivationId("49414e31-f3df-4cea-87e6-f214ca3b8412"); + pushMessage.setUserId("Test_User"); + pushMessage.setActivationId(powerAuthTestClient.getActivationId()); pushMessage.setAttributes(attributes); pushMessage.setBody(pushMessageBody); pushMessage.setAttributes(attributes); batch.add(pushMessage); - request.setAppId(2L); + request.setAppId(powerAuthTestClient.getApplicationId()); request.setBatch(batch); ObjectResponse actual = pushServerClient.sendPushMessage(2L, pushMessage); String body = restTemplate.postForEntity("http://localhost:" + port + "/push/message/send", new ObjectRequest<>(request), String.class).getBody(); - ObjectResponse expected = mapper.readValue(body, new TypeReference>() { - }); + assertNotNull(body); + ObjectResponse expected = mapper.readValue(body, new TypeReference>() {}); assertThat(actual.getStatus()).isEqualTo(expected.getStatus()); assertThat(actual.getResponseObject()).isEqualTo(expected.getResponseObject()); } @@ -215,12 +257,12 @@ public void createCampaignTest() throws Exception { pushMessageBody.setCollapseKey("balance-update"); pushMessageBody.setValidUntil(new Date()); pushMessageBody.setExtras((Map) new HashMap().put("_comment", "Any custom data.")); - campaignRequest.setAppId(1L); + campaignRequest.setAppId(powerAuthTestClient.getApplicationId()); campaignRequest.setMessage(pushMessageBody); - ObjectResponse actual = pushServerClient.createCampaign(1L, pushMessageBody); + ObjectResponse actual = pushServerClient.createCampaign(powerAuthTestClient.getApplicationId(), pushMessageBody); String body = restTemplate.postForEntity("http://localhost:" + port + "/push/campaign/create", new ObjectRequest<>(campaignRequest), String.class).getBody(); - ObjectResponse expected = mapper.readValue(body, new TypeReference>(){ - }); + assertNotNull(body); + ObjectResponse expected = mapper.readValue(body, new TypeReference>(){}); assertThat(actual.getStatus()).isEqualTo(expected.getStatus()); assertThat(actual.getResponseObject().getId() + 1).isEqualTo(expected.getResponseObject().getId()); } @@ -235,8 +277,8 @@ public void deleteCampaignTest() throws Exception { public void getListOfCampaignsTest() throws Exception{ ObjectResponse actual = pushServerClient.getListOfCampaigns(true); String body = restTemplate.getForEntity("http://localhost:" + port + "/push/campaign/list?all=true", String.class).getBody(); - ObjectResponse expected = mapper.readValue(body, new TypeReference>() { - }); + assertNotNull(body); + ObjectResponse expected = mapper.readValue(body, new TypeReference>() {}); assertThat(actual.getStatus()).isEqualTo(expected.getStatus()); assertThat(actual.getResponseObject().containsAll(expected.getResponseObject())).isTrue(); } @@ -245,8 +287,8 @@ public void getListOfCampaignsTest() throws Exception{ public void getCampaignTest() throws Exception{ ObjectResponse actual = pushServerClient.getCampaign(1L); String body = restTemplate.getForEntity("http://localhost:" + port + "/push/campaign/1/detail", String.class).getBody(); - ObjectResponse expected = mapper.readValue(body, new TypeReference>() { - }); + assertNotNull(body); + ObjectResponse expected = mapper.readValue(body, new TypeReference>() {}); assertThat(actual.getStatus()).isEqualTo(expected.getStatus()); assertThat(actual.getResponseObject()).isEqualTo(expected.getResponseObject()); } @@ -257,19 +299,17 @@ public void addUsersToCampaignTest() throws Exception { listOfUsers.addAll(Arrays.asList("1234567890", "1234567891", "1234567893")); boolean actual = pushServerClient.addUsersToCampaign(1L, listOfUsers); assertTrue(actual); - } @Test public void getListOfUsersFromCampaignTest() throws Exception { PagedResponse actual = pushServerClient.getListOfUsersFromCampaign(10L, 0, 3); String body = restTemplate.getForEntity("http://localhost:" + port + "/push/campaign/10/user/list?page=0&size=3", String.class).getBody(); - PagedResponse expected = mapper.readValue(body, new TypeReference>() { - }); + assertNotNull(body); + PagedResponse expected = mapper.readValue(body, new TypeReference>() {}); assertThat(actual.getResponseObject()).isEqualTo(expected.getResponseObject()); assertThat(actual.getStatus()).isEqualTo(expected.getStatus()); assertThat(actual.getPage()).isEqualTo(expected.getPage()); - assertThat(actual.getPage()).isEqualTo(expected.getPage()); } @Test @@ -286,7 +326,7 @@ public void sendTestingCampaignTest() throws Exception { exception.expect(PushServerClientException.class); exception.expect(HasPropertyWithValue.hasProperty("error", HasPropertyWithValue.hasProperty("message", CoreMatchers.is("Application not found")))); } - boolean actual = pushServerClient.sendTestCampaign(1L, "1234567890"); + boolean actual = pushServerClient.sendTestCampaign(1L, "Test_User"); assertTrue(actual); } @@ -296,17 +336,61 @@ public void sendCampaignTest() throws Exception { assertTrue(actual); } - @Test - public void createDeviceWithMultipleActivationsTest() throws Exception { + @Test(expected = PushServerClientException.class) + public void createDeviceWithMultipleActivationsTest() throws PushServerClientException { List activationIds = new ArrayList<>(); - activationIds.add(MOCK_ACTIVATION_ID); - activationIds.add(MOCK_ACTIVATION_ID_2); - Exception exception = null; - try { - pushServerClient.createDeviceForActivations(MOCK_APPLICATION_ID, MOCK_PUSH_TOKEN, MobilePlatform.iOS, activationIds); - } catch (Exception ex) { - exception = ex; - } - assertNotNull(exception); + activationIds.add(powerAuthTestClient.getActivationId()); + activationIds.add(powerAuthTestClient.getActivationId2()); + pushServerClient.createDeviceForActivations(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.iOS, activationIds); } + + @Test + public void createDeviceSameActivationSamePushTokenUpdatesTest() throws PushServerClientException { + // This test tests refresh of a device registration + boolean actual = pushServerClient.createDevice(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.iOS, powerAuthTestClient.getActivationId()); + assertTrue(actual); + List devices = pushDeviceRepository.findByAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN); + assertEquals(1, devices.size()); + Long rowId = devices.get(0).getId(); + boolean actual2 = pushServerClient.createDevice(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.iOS, powerAuthTestClient.getActivationId()); + assertTrue(actual2); + List devices2 = pushDeviceRepository.findByAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN); + assertEquals(1, devices2.size()); + assertEquals(rowId, devices2.get(0).getId()); + devices2.forEach(pushDeviceRepository::delete); + } + + @Test + public void createDeviceSameActivationDifferentPushTokenTest() throws PushServerClientException { + // This test tests change of Push Token - new token has been issued by Google or Apple and the device registers for same activation + boolean actual = pushServerClient.createDevice(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.iOS, powerAuthTestClient.getActivationId()); + assertTrue(actual); + List devices = pushDeviceRepository.findByAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN); + Long rowId = devices.get(0).getId(); + boolean actual2 = pushServerClient.createDevice(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN_2, MobilePlatform.iOS, powerAuthTestClient.getActivationId()); + assertTrue(actual2); + // The push token must change, however row ID stays the same + List devices1 = pushDeviceRepository.findByAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN); + List devices2 = pushDeviceRepository.findByAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN_2); + assertEquals(0, devices1.size()); + assertEquals(1, devices2.size()); + assertEquals(rowId, devices2.get(0).getId()); + devices2.forEach(pushDeviceRepository::delete); + } + + @Test + public void createDeviceDifferentActivationSamePushTokenTest() throws PushServerClientException { + // This test tests change of activation - user deleted the activation and created a new one, the push token is the same + boolean actual = pushServerClient.createDevice(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.iOS, powerAuthTestClient.getActivationId()); + assertTrue(actual); + List devices = pushDeviceRepository.findByAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN); + Long rowId = devices.get(0).getId(); + boolean actual2 = pushServerClient.createDevice(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.iOS, powerAuthTestClient.getActivationId()); + assertTrue(actual2); + List devices2 = pushDeviceRepository.findByAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN); + assertEquals(1, devices2.size()); + assertEquals(rowId, devices2.get(0).getId()); + devices2.forEach(pushDeviceRepository::delete); + } + } 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 new file mode 100644 index 000000000..038fc38bf --- /dev/null +++ b/powerauth-push-server/src/test/java/io/getlime/push/shared/PowerAuthTestClient.java @@ -0,0 +1,184 @@ +package io.getlime.push.shared; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.io.BaseEncoding; +import io.getlime.powerauth.soap.v3.*; +import io.getlime.security.powerauth.crypto.client.activation.PowerAuthClientActivation; +import io.getlime.security.powerauth.crypto.lib.config.PowerAuthConfiguration; +import io.getlime.security.powerauth.crypto.lib.encryptor.ecies.EciesEncryptor; +import io.getlime.security.powerauth.crypto.lib.encryptor.ecies.EciesFactory; +import io.getlime.security.powerauth.crypto.lib.encryptor.ecies.model.EciesCryptogram; +import io.getlime.security.powerauth.crypto.lib.encryptor.ecies.model.EciesSharedInfo1; +import io.getlime.security.powerauth.provider.CryptoProviderUtil; +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; +import java.nio.charset.StandardCharsets; +import java.security.KeyPair; +import java.security.interfaces.ECPublicKey; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +@Service +public class PowerAuthTestClient { + + private final PowerAuthClientActivation activation = new PowerAuthClientActivation(); + private final EciesFactory eciesFactory = new EciesFactory(); + private CryptoProviderUtil keyConversion; + private PowerAuthServiceClient powerAuthClient; + + private Long applicationId; + private String applicationKey; + private String applicationSecret; + private String masterPublicKey; + + private String activationId; + private String activationId2; + private String activationId3; + private String activationId4; + + 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); + keyConversion = PowerAuthConfiguration.INSTANCE.getKeyConvertor(); + } + + 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) { + if (app.getApplicationName().equals(applicationName)) { + applicationExists = true; + applicationId = app.getId(); + } + } + if (!applicationExists) { + io.getlime.powerauth.soap.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); + masterPublicKey = detail.getMasterPublicKey(); + boolean versionExists = false; + for (GetApplicationDetailResponse.Versions appVersion: detail.getVersions()) { + if (appVersion.getApplicationVersionName().equals(applicationVersion)) { + versionExists = true; + applicationKey = appVersion.getApplicationKey(); + applicationSecret = appVersion.getApplicationSecret(); + if (!appVersion.isSupported()) { + powerAuthClient.supportApplicationVersion(appVersion.getApplicationVersionId()); + } + } + } + if (!versionExists) { + CreateApplicationVersionResponse versionResponse = powerAuthClient.createApplicationVersion(applicationId, applicationVersion); + applicationKey = versionResponse.getApplicationKey(); + applicationSecret = versionResponse.getApplicationSecret(); + } + + return applicationId; + } + + public String createActivation(String userId) throws Exception { + // Create activations for test + InitActivationRequest initRequest = new InitActivationRequest(); + initRequest.setApplicationId(applicationId); + initRequest.setUserId(userId); + InitActivationResponse initResponse = powerAuthClient.initActivation(initRequest); + + // Generate device key pair + KeyPair deviceKeyPair = activation.generateDeviceKeyPair(); + byte[] devicePublicKeyBytes = keyConversion.convertPublicKeyToBytes(deviceKeyPair.getPublic()); + String devicePublicKeyBase64 = BaseEncoding.base64().encode(devicePublicKeyBytes); + + // Create activation layer 2 request which is decryptable only on PowerAuth server + ActivationLayer2Request requestL2 = new ActivationLayer2Request(); + requestL2.setActivationName("Test activation"); + requestL2.setDevicePublicKey(devicePublicKeyBase64); + + // Encrypt request data using ECIES in application scope with sharedInfo1 = /pa/activation + byte[] masterKeyBytes = BaseEncoding.base64().decode(masterPublicKey); + ECPublicKey masterPK = (ECPublicKey) keyConversion.convertBytesToPublicKey(masterKeyBytes); + byte[] applicationSecretBytes = applicationSecret.getBytes(StandardCharsets.UTF_8); + + EciesEncryptor eciesEncryptorL2 = eciesFactory.getEciesEncryptorForApplication(masterPK, applicationSecretBytes, EciesSharedInfo1.ACTIVATION_LAYER_2); + ByteArrayOutputStream baosL2 = new ByteArrayOutputStream(); + objectMapper.writeValue(baosL2, requestL2); + EciesCryptogram eciesCryptogramL2 = eciesEncryptorL2.encryptRequest(baosL2.toByteArray()); + + String ephemeralPublicKey = BaseEncoding.base64().encode(eciesCryptogramL2.getEphemeralPublicKey()); + String encryptedData = BaseEncoding.base64().encode(eciesCryptogramL2.getEncryptedData()); + String mac = BaseEncoding.base64().encode(eciesCryptogramL2.getMac()); + + // Prepare activation + PrepareActivationResponse prepareResponse = powerAuthClient.prepareActivation(initResponse.getActivationCode(), applicationKey, ephemeralPublicKey, encryptedData, mac); + assertNotNull(prepareResponse.getActivationId()); + + // Commit activation + CommitActivationResponse commitResponse = powerAuthClient.commitActivation(initResponse.getActivationId(), "test"); + assertEquals(initResponse.getActivationId(), commitResponse.getActivationId()); + + return initResponse.getActivationId(); + } + + public void blockActivation(String activationId) { + powerAuthClient.blockActivation(activationId, "TEST", "test"); + } + + public void unblockActivation(String activationId) { + powerAuthClient.unblockActivation(activationId, "test"); + } + + public Long getApplicationId() { + return applicationId; + } + + public void setApplicationId(Long applicationId) { + this.applicationId = applicationId; + } + + public String getActivationId() { + return activationId; + } + + public void setActivationId(String activationId) { + this.activationId = activationId; + } + + public String getActivationId2() { + return activationId2; + } + + public void setActivationId2(String activationId2) { + this.activationId2 = activationId2; + } + + public String getActivationId3() { + return activationId3; + } + + public void setActivationId3(String activationId3) { + this.activationId3 = activationId3; + } + + public String getActivationId4() { + return activationId4; + } + + public void setActivationId4(String activationId4) { + this.activationId4 = activationId4; + } +} 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 new file mode 100644 index 000000000..6c607bf6d --- /dev/null +++ b/powerauth-push-server/src/test/java/io/getlime/push/shared/PushServerTestClientFactory.java @@ -0,0 +1,44 @@ +package io.getlime.push.shared; + +import io.getlime.push.client.PushServerClient; +import io.getlime.security.powerauth.crypto.lib.config.PowerAuthConfiguration; +import io.getlime.security.powerauth.provider.CryptoProviderUtilFactory; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.security.Security; + +@Service +public class PushServerTestClientFactory { + + private static final String TEST_APPLICATION_NAME = "Push_Server_Tests"; + private static final String TEST_APPLICATION_VERSION = "default"; + private static final String TEST_USER_ID = "Test_User"; + + @Value("${powerauth.service.url}") + private String powerAuthServiceUrl; + + public PushServerClient createPushServerClient(String baseUrl) { + return new PushServerClient(baseUrl); + } + + public PowerAuthTestClient createPowerAuthTestClient() throws Exception { + Security.addProvider(new BouncyCastleProvider()); + PowerAuthConfiguration.INSTANCE.setKeyConvertor(CryptoProviderUtilFactory.getCryptoProviderUtils()); + + PowerAuthTestClient powerAuthTestClient = new PowerAuthTestClient(); + powerAuthTestClient.initializeClient(powerAuthServiceUrl); + Long applicationId = powerAuthTestClient.initializeApplication(TEST_APPLICATION_NAME, TEST_APPLICATION_VERSION); + String activationId = powerAuthTestClient.createActivation(TEST_USER_ID); + String activationId2 = powerAuthTestClient.createActivation(TEST_USER_ID); + String activationId3 = powerAuthTestClient.createActivation(TEST_USER_ID); + String activationId4 = powerAuthTestClient.createActivation(TEST_USER_ID); + powerAuthTestClient.setApplicationId(applicationId); + powerAuthTestClient.setActivationId(activationId); + powerAuthTestClient.setActivationId2(activationId2); + powerAuthTestClient.setActivationId3(activationId3); + powerAuthTestClient.setActivationId4(activationId4); + return powerAuthTestClient; + } +} From 78a4478a204975229d46ec7052f1ccc3c5e7d8b2 Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Thu, 28 Nov 2019 19:05:19 +0100 Subject: [PATCH 47/56] Fixed deleting of rows after test execution --- .../push/client/PushServerMultipleActivationsTests.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 4276d496e..8af47bc32 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 @@ -163,7 +163,7 @@ public void createDeviceDifferentTwoActivationsSamePushTokenUpdatesTest() throws devices2.forEach(device -> rowIds2.add(device.getId())); // Row IDs do not match, because the activations are different assertNotEquals(rowIds, rowIds2); - pushDeviceRepository.findByAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN_2).forEach(pushDeviceRepository::delete); + pushDeviceRepository.findByAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN).forEach(pushDeviceRepository::delete); } @Test @@ -189,7 +189,7 @@ public void createDeviceDifferentActivationSamePushTokenUpdatesTest() throws Pus devices2.forEach(device -> rowIds2.add(device.getId())); // Row IDs do not match, because the one of activations is different assertNotEquals(rowIds, rowIds2); - pushDeviceRepository.findByAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN_2).forEach(pushDeviceRepository::delete); + pushDeviceRepository.findByAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN).forEach(pushDeviceRepository::delete); } @Test(expected = PushServerClientException.class) From 8857de1c61dc0f499fc16aa76053285fc618f720 Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Fri, 29 Nov 2019 12:50:21 +0100 Subject: [PATCH 48/56] Parameterize BC version --- pom.xml | 1 + powerauth-push-server/pom.xml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 0ecfede9c..d526be5cd 100644 --- a/pom.xml +++ b/pom.xml @@ -93,6 +93,7 @@ 3.0.10 1.4.0 1.5.1 + 1.64 diff --git a/powerauth-push-server/pom.xml b/powerauth-push-server/pom.xml index a0dbb8119..eb76060c1 100644 --- a/powerauth-push-server/pom.xml +++ b/powerauth-push-server/pom.xml @@ -218,7 +218,7 @@ org.bouncycastle bcprov-jdk15on - 1.64 + ${bc.version} test From 0c2a014dac7fecfc9073de4ca42d08572a91af7f Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Mon, 2 Dec 2019 16:11:11 +0100 Subject: [PATCH 49/56] Use nonce for crypto 3.1 --- .../java/io/getlime/push/shared/PowerAuthTestClient.java | 5 +++-- 1 file changed, 3 insertions(+), 2 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 038fc38bf..a16367ade 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 @@ -117,14 +117,15 @@ public String createActivation(String userId) throws Exception { EciesEncryptor eciesEncryptorL2 = eciesFactory.getEciesEncryptorForApplication(masterPK, applicationSecretBytes, EciesSharedInfo1.ACTIVATION_LAYER_2); ByteArrayOutputStream baosL2 = new ByteArrayOutputStream(); objectMapper.writeValue(baosL2, requestL2); - EciesCryptogram eciesCryptogramL2 = eciesEncryptorL2.encryptRequest(baosL2.toByteArray()); + EciesCryptogram eciesCryptogramL2 = eciesEncryptorL2.encryptRequest(baosL2.toByteArray(), true); String ephemeralPublicKey = BaseEncoding.base64().encode(eciesCryptogramL2.getEphemeralPublicKey()); String encryptedData = BaseEncoding.base64().encode(eciesCryptogramL2.getEncryptedData()); String mac = BaseEncoding.base64().encode(eciesCryptogramL2.getMac()); + String nonce = BaseEncoding.base64().encode(eciesCryptogramL2.getNonce()); // Prepare activation - PrepareActivationResponse prepareResponse = powerAuthClient.prepareActivation(initResponse.getActivationCode(), applicationKey, ephemeralPublicKey, encryptedData, mac); + PrepareActivationResponse prepareResponse = powerAuthClient.prepareActivation(initResponse.getActivationCode(), applicationKey, ephemeralPublicKey, encryptedData, mac, nonce); assertNotNull(prepareResponse.getActivationId()); // Commit activation From 186a4af0d2e94940802e81e3428cc5578c36d999 Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Mon, 2 Dec 2019 17:22:01 +0100 Subject: [PATCH 50/56] Prepare controller to test push message delivery against local endpoint, fix broken tests --- .../push/service/PushSendingWorker.java | 5 +- .../getlime/push/service/fcm/FcmClient.java | 5 +- .../getlime/push/client/PushServerTests.java | 67 +++++++++---------- .../push/rest/TestPushMessageController.java | 36 ++++++++++ .../resources/application-test.properties | 3 + 5 files changed, 78 insertions(+), 38 deletions(-) create mode 100644 powerauth-push-server/src/test/java/io/getlime/push/rest/TestPushMessageController.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 d3668638c..7b9bc0ff4 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 @@ -109,13 +109,14 @@ FcmClient prepareFcmClient(String projectId, byte[] privateKey) throws PushServe fcmClient.setProxySettings(proxyHost, proxyPort, proxyUsername, proxyPassword); } fcmClient.initializeWebClient(); - fcmClient.initializeGoogleCredential(); String fcmUrl = pushServiceConfiguration.getFcmSendMessageUrl(); if (fcmUrl.contains("projects/%s/")) { + // Initialize Google Credential for production FCM URL + fcmClient.initializeGoogleCredential(); // Configure project ID in FCM URL in case the project ID parameter is expected in configured URL fcmClient.setFcmSendMessageUrl(String.format(fcmUrl, projectId)); } else { - // Set FCM url as is (e.g. for testing) + // Set FCM url as is (for testing) fcmClient.setFcmSendMessageUrl(fcmUrl); } return fcmClient; 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 45e76b46b..40dcadbc2 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 @@ -192,8 +192,9 @@ protected PasswordAuthentication getPasswordAuthentication() { */ private String getAccessToken() throws FcmMissingTokenException { if (googleCredential == null) { - // In case FCM registration failed, access token is not available - throw new FcmMissingTokenException("FCM access token is not available because Google Credential initialization failed"); + // In case FCM registration failed, access token is not available. + // Exception is not thrown to allow test execution. + return null; } try { String accessToken = googleCredential.getAccessToken(); 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 2f0f5988e..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 @@ -18,7 +18,6 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; -import io.getlime.core.rest.model.base.request.ObjectRequest; import io.getlime.core.rest.model.base.response.ObjectResponse; import io.getlime.push.model.base.PagedResponse; import io.getlime.push.model.entity.*; @@ -28,11 +27,10 @@ import io.getlime.push.model.response.*; import io.getlime.push.repository.AppCredentialsRepository; import io.getlime.push.repository.PushDeviceRepository; +import io.getlime.push.repository.model.AppCredentialsEntity; import io.getlime.push.repository.model.PushDeviceRegistrationEntity; import io.getlime.push.shared.PowerAuthTestClient; import io.getlime.push.shared.PushServerTestClientFactory; -import org.hamcrest.CoreMatchers; -import org.hamcrest.beans.HasPropertyWithValue; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -64,7 +62,7 @@ * @author Roman Strobl, roman.strobl@wultra.com */ @RunWith(SpringRunner.class) -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) @TestPropertySource(locations = "classpath:application-test.properties") @TestInstance(TestInstance.Lifecycle.PER_CLASS) public class PushServerTests { @@ -99,10 +97,18 @@ public class PushServerTests { @Value("${powerauth.service.url}") private String powerAuthServiceUrl; + @Value("${powerauth.push.service.fcm.sendMessageUrl}") + private String fcmUrlForTests; + @Before public void setUp() throws Exception { pushServerClient = testClientFactory.createPushServerClient("http://localhost:" + port); powerAuthTestClient = testClientFactory.createPowerAuthTestClient(); + AppCredentialsEntity testCredentials = new AppCredentialsEntity(); + testCredentials.setAppId(powerAuthTestClient.getApplicationId()); + testCredentials.setAndroidProjectId("test-project"); + testCredentials.setAndroidPrivateKey(new byte[128]); + appCredentialsRepository.save(testCredentials); } @Rule @@ -146,6 +152,11 @@ public void deleteDeviceTest() throws Exception { assertEquals(0, devices2.size()); } + @Test + public void testFcmUrlConfiguredForTests() { + assertEquals("http://localhost:" + port + "/mockfcm/message:send", fcmUrlForTests); + } + @Test public void updateDeviceStatusTest() throws Exception { @@ -172,10 +183,8 @@ public void updateDeviceStatusTest() throws Exception { @Test @SuppressWarnings("unchecked") //known parameters of HashMap public void sendPushMessageTest() throws Exception { - if (appCredentialsRepository.count() < 1) { - exception.expect(PushServerClientException.class); - exception.expect(HasPropertyWithValue.hasProperty("error", HasPropertyWithValue.hasProperty("message", CoreMatchers.is("Application not found")))); - } + boolean result = pushServerClient.createDevice(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.Android, powerAuthTestClient.getActivationId()); + assertTrue(result); SendPushMessageRequest request = new SendPushMessageRequest(); PushMessage pushMessage = new PushMessage(); PushMessageAttributes attributes = new PushMessageAttributes(); @@ -188,7 +197,7 @@ public void sendPushMessageTest() throws Exception { pushMessageBody.setCollapseKey("balance-update"); pushMessageBody.setValidUntil(new Date()); pushMessageBody.setExtras((Map) new HashMap().put("_comment", "Any custom data.")); - attributes.setSilent(true); + attributes.setSilent(false); attributes.setPersonal(true); pushMessage.setUserId("Test_User"); pushMessage.setActivationId(powerAuthTestClient.getActivationId()); @@ -198,21 +207,17 @@ public void sendPushMessageTest() throws Exception { request.setAppId(powerAuthTestClient.getApplicationId()); request.setMessage(pushMessage); ObjectResponse actual = pushServerClient.sendPushMessage(powerAuthTestClient.getApplicationId(), pushMessage); - String body = restTemplate.postForEntity("http://localhost:" + port + "/push/message/send", new ObjectRequest<>(request), String.class).getBody(); - assertNotNull(body); - ObjectResponse expected = mapper.readValue(body, new TypeReference>() {}); - assertThat(actual.getStatus()).isEqualTo(expected.getStatus()); - assertThat(actual.getResponseObject()).isEqualTo(expected.getResponseObject()); + assertThat(actual.getStatus()).isEqualTo("OK"); + List devices = pushDeviceRepository.findByAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN); + devices.forEach(pushDeviceRepository::delete); } @Test @SuppressWarnings("unchecked") //known parameters of HashMap public void sendPushMessageBatchTest() throws Exception { - if (appCredentialsRepository.count() < 1) { - exception.expect(PushServerClientException.class); - exception.expect(HasPropertyWithValue.hasProperty("error", HasPropertyWithValue.hasProperty("message", CoreMatchers.is("Application not found")))); - } + boolean result = pushServerClient.createDevice(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.Android, powerAuthTestClient.getActivationId()); + assertTrue(result); SendPushMessageBatchRequest request = new SendPushMessageBatchRequest(); List batch = new ArrayList<>(); PushMessage pushMessage = new PushMessage(); @@ -226,7 +231,7 @@ public void sendPushMessageBatchTest() throws Exception { pushMessageBody.setCollapseKey("balance-update"); pushMessageBody.setValidUntil(new Date()); pushMessageBody.setExtras((Map) new HashMap().put("_comment", "Any custom data.")); - attributes.setSilent(true); + attributes.setSilent(false); attributes.setPersonal(true); pushMessage.setUserId("Test_User"); pushMessage.setActivationId(powerAuthTestClient.getActivationId()); @@ -236,17 +241,17 @@ public void sendPushMessageBatchTest() throws Exception { batch.add(pushMessage); request.setAppId(powerAuthTestClient.getApplicationId()); request.setBatch(batch); - ObjectResponse actual = pushServerClient.sendPushMessage(2L, pushMessage); - String body = restTemplate.postForEntity("http://localhost:" + port + "/push/message/send", new ObjectRequest<>(request), String.class).getBody(); - assertNotNull(body); - ObjectResponse expected = mapper.readValue(body, new TypeReference>() {}); - assertThat(actual.getStatus()).isEqualTo(expected.getStatus()); - assertThat(actual.getResponseObject()).isEqualTo(expected.getResponseObject()); + ObjectResponse actual = pushServerClient.sendPushMessageBatch(powerAuthTestClient.getApplicationId(), batch); + assertThat(actual.getStatus()).isEqualTo("OK"); + List devices = pushDeviceRepository.findByAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN); + devices.forEach(pushDeviceRepository::delete); } @Test @SuppressWarnings("unchecked") //known parameters of HashMap public void createCampaignTest() throws Exception { + boolean result = pushServerClient.createDevice(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN, MobilePlatform.Android, powerAuthTestClient.getActivationId()); + assertTrue(result); CreateCampaignRequest campaignRequest = new CreateCampaignRequest(); PushMessageBody pushMessageBody = new PushMessageBody(); pushMessageBody.setTitle("Balance update"); @@ -260,11 +265,9 @@ public void createCampaignTest() throws Exception { campaignRequest.setAppId(powerAuthTestClient.getApplicationId()); campaignRequest.setMessage(pushMessageBody); ObjectResponse actual = pushServerClient.createCampaign(powerAuthTestClient.getApplicationId(), pushMessageBody); - String body = restTemplate.postForEntity("http://localhost:" + port + "/push/campaign/create", new ObjectRequest<>(campaignRequest), String.class).getBody(); - assertNotNull(body); - ObjectResponse expected = mapper.readValue(body, new TypeReference>(){}); - assertThat(actual.getStatus()).isEqualTo(expected.getStatus()); - assertThat(actual.getResponseObject().getId() + 1).isEqualTo(expected.getResponseObject().getId()); + assertThat(actual.getStatus()).isEqualTo("OK"); + List devices = pushDeviceRepository.findByAppIdAndPushToken(powerAuthTestClient.getApplicationId(), MOCK_PUSH_TOKEN); + devices.forEach(pushDeviceRepository::delete); } @Test @@ -322,10 +325,6 @@ public void deleteUsersFromCampaignTest() throws Exception { @Test public void sendTestingCampaignTest() throws Exception { - if (appCredentialsRepository.count() < 1) { - exception.expect(PushServerClientException.class); - exception.expect(HasPropertyWithValue.hasProperty("error", HasPropertyWithValue.hasProperty("message", CoreMatchers.is("Application not found")))); - } boolean actual = pushServerClient.sendTestCampaign(1L, "Test_User"); assertTrue(actual); } diff --git a/powerauth-push-server/src/test/java/io/getlime/push/rest/TestPushMessageController.java b/powerauth-push-server/src/test/java/io/getlime/push/rest/TestPushMessageController.java new file mode 100644 index 000000000..c507c8dcd --- /dev/null +++ b/powerauth-push-server/src/test/java/io/getlime/push/rest/TestPushMessageController.java @@ -0,0 +1,36 @@ +/* + * Copyright 2019 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.rest; + +import io.getlime.push.service.fcm.model.FcmSuccessResponse; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.security.SecureRandom; + +/** + * Push message controller for testing push message delivery. + */ +@RestController +public class TestPushMessageController { + + @PostMapping("/mockfcm/message:send") + public FcmSuccessResponse testSendMessage() { + FcmSuccessResponse response = new FcmSuccessResponse(); + response.setName("projects/test-project/messages/" + new SecureRandom().nextInt(1000000)); + return response; + } +} diff --git a/powerauth-push-server/src/test/resources/application-test.properties b/powerauth-push-server/src/test/resources/application-test.properties index dd78f606e..9b04c8a5a 100644 --- a/powerauth-push-server/src/test/resources/application-test.properties +++ b/powerauth-push-server/src/test/resources/application-test.properties @@ -33,3 +33,6 @@ powerauth.service.security.clientToken= powerauth.service.security.clientSecret= powerauth.service.ssl.acceptInvalidSslCertificate=false +# Override FCM url for tests, use high port number to avoid conflicts +server.port=54723 +powerauth.push.service.fcm.sendMessageUrl=http://localhost:${server.port}/mockfcm/message:send From 765baf3713cde974d6eca56e1eb83126c87f6a5c Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Mon, 2 Dec 2019 17:25:57 +0100 Subject: [PATCH 51/56] Update test configuration --- .../getlime/push/client/PushServerMultipleActivationsTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 8af47bc32..da9444801 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 @@ -46,7 +46,7 @@ * @author Roman Strobl, roman.strobl@wultra.com */ @RunWith(SpringRunner.class) -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) @TestPropertySource(locations = "classpath:application-test-multiple-activations.properties") @TestInstance(TestInstance.Lifecycle.PER_CLASS) public class PushServerMultipleActivationsTests { From 5a049a184d1ab2dc583da1e980e77df7d9f033fe Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Mon, 2 Dec 2019 17:28:56 +0100 Subject: [PATCH 52/56] Update test configuration --- .../application-test-multiple-activations.properties | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/powerauth-push-server/src/test/resources/application-test-multiple-activations.properties b/powerauth-push-server/src/test/resources/application-test-multiple-activations.properties index cda32062a..27ba64e63 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 @@ -34,4 +34,7 @@ powerauth.service.security.clientSecret= powerauth.service.ssl.acceptInvalidSslCertificate=false # Whether push registration supports associated activations -powerauth.push.service.registration.multipleActivations.enabled=true \ No newline at end of file +powerauth.push.service.registration.multipleActivations.enabled=true + +# Test port configuration +server.port=54724 \ No newline at end of file From 34fabaf6d45650ba0f281fb08258049b844e8e5e Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Mon, 2 Dec 2019 20:40:15 +0100 Subject: [PATCH 53/56] Create profile for testing --- .../java/io/getlime/push/rest/TestPushMessageController.java | 2 ++ .../resources/application-test-multiple-activations.properties | 2 +- .../src/test/resources/application-test.properties | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/powerauth-push-server/src/test/java/io/getlime/push/rest/TestPushMessageController.java b/powerauth-push-server/src/test/java/io/getlime/push/rest/TestPushMessageController.java index c507c8dcd..ea6e1a6cf 100644 --- a/powerauth-push-server/src/test/java/io/getlime/push/rest/TestPushMessageController.java +++ b/powerauth-push-server/src/test/java/io/getlime/push/rest/TestPushMessageController.java @@ -16,6 +16,7 @@ package io.getlime.push.rest; import io.getlime.push.service.fcm.model.FcmSuccessResponse; +import org.springframework.context.annotation.Profile; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; @@ -25,6 +26,7 @@ * Push message controller for testing push message delivery. */ @RestController +@Profile("test") public class TestPushMessageController { @PostMapping("/mockfcm/message:send") 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 27ba64e63..cd66c953e 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 @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # - +spring.profiles.active=test # H2 spring.h2.console.enabled=true diff --git a/powerauth-push-server/src/test/resources/application-test.properties b/powerauth-push-server/src/test/resources/application-test.properties index 9b04c8a5a..60173cf96 100644 --- a/powerauth-push-server/src/test/resources/application-test.properties +++ b/powerauth-push-server/src/test/resources/application-test.properties @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # - +spring.profiles.active=test # H2 spring.h2.console.enabled=true From 09102eb4054631a34dfd71c07d2a3b5a7d55dd37 Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Thu, 5 Dec 2019 21:37:55 +0100 Subject: [PATCH 54/56] Add migration instructions link --- docs/Migration-Instructions.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Migration-Instructions.md b/docs/Migration-Instructions.md index b2db5280e..c80755014 100644 --- a/docs/Migration-Instructions.md +++ b/docs/Migration-Instructions.md @@ -4,3 +4,4 @@ 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.23.0](./PowerAuth-Push-Server-0.23.0.md) From 598b0f1cf1df8ea4d61b52d1b917569d9755e79e Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Sat, 7 Dec 2019 18:26:25 +0100 Subject: [PATCH 55/56] Update version to 0.23.0, upgrade Jackson dependencies --- pom.xml | 8 ++++---- powerauth-push-client/pom.xml | 6 +++--- powerauth-push-model/pom.xml | 4 ++-- powerauth-push-server/pom.xml | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pom.xml b/pom.xml index d526be5cd..2f7089019 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ io.getlime.security powerauth-push-server-parent - 0.23.0-SNAPSHOT + 0.23.0 pom @@ -77,13 +77,13 @@ 1.4.199 4.5.10 4.1.4 - 2.10.0 - 2.10.0 + 2.10.1 + 2.10.1 1.2.2 2.0 1.2.5 1.1.0 - 0.23.0-SNAPSHOT + 0.23.0 0.13.10 2.9.2 1.4.9 diff --git a/powerauth-push-client/pom.xml b/powerauth-push-client/pom.xml index 5d922c545..7a28e7417 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.23.0-SNAPSHOT + 0.23.0 jar powerauth-push-server-parent io.getlime.security - 0.23.0-SNAPSHOT + 0.23.0 @@ -20,7 +20,7 @@ io.getlime.security powerauth-push-model - 0.23.0-SNAPSHOT + 0.23.0 diff --git a/powerauth-push-model/pom.xml b/powerauth-push-model/pom.xml index 245468383..00300e9a5 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.23.0-SNAPSHOT + 0.23.0 jar powerauth-push-server-parent io.getlime.security - 0.23.0-SNAPSHOT + 0.23.0 diff --git a/powerauth-push-server/pom.xml b/powerauth-push-server/pom.xml index eb76060c1..dd3b5f163 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.23.0-SNAPSHOT + 0.23.0 war io.getlime.security powerauth-push-server-parent - 0.23.0-SNAPSHOT + 0.23.0 ../pom.xml From cb86f0445ac66abae2bd5e02cf2e811d82925ac3 Mon Sep 17 00:00:00 2001 From: Roman Strobl Date: Mon, 9 Dec 2019 15:15:23 +0100 Subject: [PATCH 56/56] Document changes in device registration --- docs/Deploying-Push-Server.md | 8 ++++++++ docs/PowerAuth-Push-Server-0.23.0.md | 20 ++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/docs/Deploying-Push-Server.md b/docs/Deploying-Push-Server.md index 28a8e7c7b..de204e45f 100644 --- a/docs/Deploying-Push-Server.md +++ b/docs/Deploying-Push-Server.md @@ -60,6 +60,14 @@ You can enable storing of sent messages in database using following property: powerauth.push.service.message.storage.enabled=true ``` +### Enabling Multiple Associated Activations in Device Registration + +You can enable registration of multiple associated activations for a push token using following property: + +``` +powerauth.push.service.registration.multipleActivations.enabled=true +``` + ### APNS Environment Configuration In order to separate development and production environment on APNS, you may want to set following property: diff --git a/docs/PowerAuth-Push-Server-0.23.0.md b/docs/PowerAuth-Push-Server-0.23.0.md index 000820661..430fd9f9c 100644 --- a/docs/PowerAuth-Push-Server-0.23.0.md +++ b/docs/PowerAuth-Push-Server-0.23.0.md @@ -23,3 +23,23 @@ CREATE UNIQUE INDEX `push_device_activation` ON `push_device_registration`(`acti ``` In case either of the index updates fails, delete existing duplicate rows. Rows with newest timestamp_last_registered should be preserved. + +## Device Registration Changes + +Following changes of device registration have been applied in release `0.23.0`: +- A device can no longer be registered with the same `activationId` and multiple related `pushtoken` values. +This change was introduced because Google and Apple do not always expire existing push tokens. When the device +receives a new push token, the device registration endpoint updates the `pushtoken` value for an existing activation instead of +creating a new device registration. Thus the old push token is removed from database. +- It is no longer possible to register a device without associated activation. The `activationId` parameter must be +always sent with device registration request. +- It is possible to re-register a device with same `activationId` and `pushtoken`. The registration timestamp is updated in this case. +- It is possible to register a device with multiple `activationIds` associated with a single `pushtoken`. Such +device registration must be initiated using the new endpoint created for this use case: +[Create Device for Multiple Associated Activations](./Push-Server-API.md#create-device-for-multiple-associated-activations). +Multiple activations are used in master-child activation schemes. The functionality needs to be enabled +using a configuration property [for enabling multiple activations](./Deploying-Push-Server.md#enabling-multiple-associated-activations-in-device-registration), +because it is less secure than the case when a single associated activation is allowed for a push token. +- Database indexes are now applied to enforce database consistency for device registrations: + - The (`activationId`) value must be unique in the device registration table. Each `activationId` must have exactly one associated `pushtoken`. + - The (`activationId`, `pushtoken`) combination must be unique in the device registration table.