diff --git a/app/build.gradle b/app/build.gradle index c7724b7a2..7d39033fb 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -317,7 +317,8 @@ dependencies { spotbugsPlugins 'com.h3xstream.findsecbugs:findsecbugs-plugin:1.11.0' spotbugsPlugins 'com.mebigfatguy.fb-contrib:fb-contrib:7.4.7' - gplayImplementation "com.google.firebase:firebase-messaging:20.1.2" + gplayImplementation 'com.google.android.gms:play-services-base:18.0.1' + gplayImplementation "com.google.firebase:firebase-messaging:23.0.0" } task ktlint(type: JavaExec, group: "verification") { diff --git a/app/src/generic/java/com/nextcloud/talk/utils/ClosedInterfaceImpl.java b/app/src/generic/java/com/nextcloud/talk/utils/ClosedInterfaceImpl.java index 66496bd31..19ca5448b 100644 --- a/app/src/generic/java/com/nextcloud/talk/utils/ClosedInterfaceImpl.java +++ b/app/src/generic/java/com/nextcloud/talk/utils/ClosedInterfaceImpl.java @@ -2,7 +2,9 @@ * Nextcloud Talk application * * @author Mario Danic + * @author Marcel Hibbe * Copyright (C) 2017-2018 Mario Danic + * Copyright (C) 2022 Marcel Hibbe * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -33,4 +35,12 @@ public class ClosedInterfaceImpl implements ClosedInterface { public boolean isGooglePlayServicesAvailable() { return false; } + + @Override + public void setUpPushTokenRegistration() { + // no push notifications for generic build flavour :( + // If you want to develop push notifications without google play services, here is a good place to start... + // Also have a look at app/src/gplay/AndroidManifest.xml to see how to include a service that handles push + // notifications. + } } diff --git a/app/src/gplay/java/com/nextcloud/talk/jobs/GetFirebasePushTokenWorker.kt b/app/src/gplay/java/com/nextcloud/talk/jobs/GetFirebasePushTokenWorker.kt new file mode 100644 index 000000000..38206210a --- /dev/null +++ b/app/src/gplay/java/com/nextcloud/talk/jobs/GetFirebasePushTokenWorker.kt @@ -0,0 +1,68 @@ +/* + * Nextcloud Talk application + * + * @author Marcel Hibbe + * Copyright (C) 2022 Marcel Hibbe + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.nextcloud.talk.jobs + +import android.annotation.SuppressLint +import android.content.Context +import android.util.Log +import androidx.work.Data +import androidx.work.OneTimeWorkRequest +import androidx.work.WorkManager +import androidx.work.Worker +import androidx.work.WorkerParameters +import com.google.android.gms.tasks.OnCompleteListener +import com.google.firebase.messaging.FirebaseMessaging +import com.nextcloud.talk.utils.preferences.AppPreferences +import javax.inject.Inject + +class GetFirebasePushTokenWorker(val context: Context, workerParameters: WorkerParameters) : + Worker(context, workerParameters) { + + @JvmField + @Inject + var appPreferences: AppPreferences? = null + + @SuppressLint("LongLogTag") + override fun doWork(): Result { + FirebaseMessaging.getInstance().token.addOnCompleteListener(OnCompleteListener { task -> + if (!task.isSuccessful) { + Log.w(TAG, "Fetching FCM registration token failed", task.exception) + return@OnCompleteListener + } + + val token = task.result + + appPreferences?.pushToken = token + + val data: Data = Data.Builder().putString(PushRegistrationWorker.ORIGIN, "GetFirebasePushTokenWorker").build() + val pushRegistrationWork = OneTimeWorkRequest.Builder(PushRegistrationWorker::class.java) + .setInputData(data) + .build() + WorkManager.getInstance(context).enqueue(pushRegistrationWork) + }) + + return Result.success() + } + + companion object { + const val TAG = "GetFirebasePushTokenWorker" + } +} diff --git a/app/src/gplay/java/com/nextcloud/talk/services/firebase/MagicFirebaseMessagingService.kt b/app/src/gplay/java/com/nextcloud/talk/services/firebase/MagicFirebaseMessagingService.kt index a6cd04693..1cb242863 100644 --- a/app/src/gplay/java/com/nextcloud/talk/services/firebase/MagicFirebaseMessagingService.kt +++ b/app/src/gplay/java/com/nextcloud/talk/services/firebase/MagicFirebaseMessagingService.kt @@ -131,7 +131,11 @@ class MagicFirebaseMessagingService : FirebaseMessagingService() { sharedApplication!!.componentApplication.inject(this) appPreferences!!.pushToken = token Log.d(TAG, "onNewToken. token = $token") - val pushRegistrationWork = OneTimeWorkRequest.Builder(PushRegistrationWorker::class.java).build() + + val data: Data = Data.Builder().putString(PushRegistrationWorker.ORIGIN, "onNewToken").build() + val pushRegistrationWork = OneTimeWorkRequest.Builder(PushRegistrationWorker::class.java) + .setInputData(data) + .build() WorkManager.getInstance().enqueue(pushRegistrationWork) } diff --git a/app/src/gplay/java/com/nextcloud/talk/utils/ClosedInterfaceImpl.java b/app/src/gplay/java/com/nextcloud/talk/utils/ClosedInterfaceImpl.java deleted file mode 100644 index 7be5866f2..000000000 --- a/app/src/gplay/java/com/nextcloud/talk/utils/ClosedInterfaceImpl.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Nextcloud Talk application - * - * @author Mario Danic - * Copyright (C) 2017-2018 Mario Danic - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.nextcloud.talk.utils; - - -import android.content.Intent; - -import com.google.android.gms.common.ConnectionResult; -import com.google.android.gms.common.GoogleApiAvailability; -import com.google.android.gms.security.ProviderInstaller; -import com.google.android.gms.security.ProviderInstaller.ProviderInstallListener; -import com.nextcloud.talk.application.NextcloudTalkApplication; -import com.nextcloud.talk.interfaces.ClosedInterface; - -public class ClosedInterfaceImpl implements ClosedInterface, ProviderInstallListener { - @Override - public void providerInstallerInstallIfNeededAsync() { - ProviderInstaller.installIfNeededAsync(NextcloudTalkApplication.Companion.getSharedApplication().getApplicationContext(), - this); - } - - @Override - public boolean isGooglePlayServicesAvailable() { - GoogleApiAvailability api = GoogleApiAvailability.getInstance(); - int code = - api.isGooglePlayServicesAvailable(NextcloudTalkApplication.Companion.getSharedApplication().getApplicationContext()); - return code == ConnectionResult.SUCCESS; - } - - @Override - public void onProviderInstalled() { - - } - - @Override - public void onProviderInstallFailed(int i, Intent intent) { - - } -} diff --git a/app/src/gplay/java/com/nextcloud/talk/utils/ClosedInterfaceImpl.kt b/app/src/gplay/java/com/nextcloud/talk/utils/ClosedInterfaceImpl.kt new file mode 100644 index 000000000..7dc8ff74d --- /dev/null +++ b/app/src/gplay/java/com/nextcloud/talk/utils/ClosedInterfaceImpl.kt @@ -0,0 +1,121 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * @author Marcel Hibbe + * Copyright (C) 2017-2019 Mario Danic + * Copyright (C) 2022 Marcel Hibbe + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.nextcloud.talk.utils + +import android.content.Intent +import androidx.work.Data +import androidx.work.ExistingPeriodicWorkPolicy +import androidx.work.OneTimeWorkRequest +import androidx.work.PeriodicWorkRequest +import androidx.work.WorkManager +import autodagger.AutoInjector +import com.google.android.gms.common.ConnectionResult +import com.google.android.gms.common.GoogleApiAvailability +import com.google.android.gms.security.ProviderInstaller +import com.nextcloud.talk.application.NextcloudTalkApplication +import com.nextcloud.talk.interfaces.ClosedInterface +import com.nextcloud.talk.jobs.GetFirebasePushTokenWorker +import com.nextcloud.talk.jobs.PushRegistrationWorker +import java.util.concurrent.TimeUnit + +@AutoInjector(NextcloudTalkApplication::class) +class ClosedInterfaceImpl : ClosedInterface, ProviderInstaller.ProviderInstallListener { + + override val isGooglePlayServicesAvailable: Boolean = isGPlayServicesAvailable() + + override fun providerInstallerInstallIfNeededAsync() { + NextcloudTalkApplication.sharedApplication?.let { + ProviderInstaller.installIfNeededAsync( + it.applicationContext, + this + ) + } + } + + override fun onProviderInstalled() { + } + + override fun onProviderInstallFailed(p0: Int, p1: Intent?) { + } + + private fun isGPlayServicesAvailable(): Boolean { + val api = GoogleApiAvailability.getInstance() + val code = + NextcloudTalkApplication.sharedApplication?.let { + api.isGooglePlayServicesAvailable( + it.applicationContext + ) + } + return code == ConnectionResult.SUCCESS + } + + override fun setUpPushTokenRegistration() { + registerLocalToken() + setUpPeriodicLocalTokenRegistration() + setUpPeriodicTokenRefreshFromFCM() + } + + private fun registerLocalToken() { + val data: Data = Data.Builder().putString(PushRegistrationWorker.ORIGIN, "ClosedInterfaceImpl#registerLocalToken").build() + val pushRegistrationWork = OneTimeWorkRequest.Builder(PushRegistrationWorker::class.java) + .setInputData(data) + .build() + WorkManager.getInstance().enqueue(pushRegistrationWork) + } + + private fun setUpPeriodicLocalTokenRegistration() { + val data: Data = Data.Builder().putString(PushRegistrationWorker.ORIGIN, "ClosedInterfaceImpl#setUpPeriodicLocalTokenRegistration").build() + + val periodicTokenRegistration = PeriodicWorkRequest.Builder( + PushRegistrationWorker::class.java, + 24, + TimeUnit.HOURS, + 10, + TimeUnit.HOURS + ) + .setInputData(data) + .build() + + WorkManager.getInstance() + .enqueueUniquePeriodicWork( + "periodicTokenRegistration", ExistingPeriodicWorkPolicy.REPLACE, + periodicTokenRegistration + ) + } + + private fun setUpPeriodicTokenRefreshFromFCM() { + val periodicTokenRefreshFromFCM = PeriodicWorkRequest.Builder( + GetFirebasePushTokenWorker::class.java, + 30, + TimeUnit.DAYS, + 10, + TimeUnit.DAYS, + ) + .build() + + WorkManager.getInstance() + .enqueueUniquePeriodicWork( + "periodicTokenRefreshFromFCM", ExistingPeriodicWorkPolicy.REPLACE, + periodicTokenRefreshFromFCM + ) + } +} diff --git a/app/src/main/java/com/nextcloud/talk/api/NcApi.java b/app/src/main/java/com/nextcloud/talk/api/NcApi.java index b568dd210..c75b9c083 100644 --- a/app/src/main/java/com/nextcloud/talk/api/NcApi.java +++ b/app/src/main/java/com/nextcloud/talk/api/NcApi.java @@ -283,8 +283,8 @@ public interface NcApi { @FormUrlEncoded @POST - Observable registerDeviceForNotificationsWithProxy(@Url String url, - @FieldMap Map fields); + Observable registerDeviceForNotificationsWithPushProxy(@Url String url, + @FieldMap Map fields); /* diff --git a/app/src/main/java/com/nextcloud/talk/application/NextcloudTalkApplication.kt b/app/src/main/java/com/nextcloud/talk/application/NextcloudTalkApplication.kt index 0dda6430c..4c391d3b5 100644 --- a/app/src/main/java/com/nextcloud/talk/application/NextcloudTalkApplication.kt +++ b/app/src/main/java/com/nextcloud/talk/application/NextcloudTalkApplication.kt @@ -2,8 +2,10 @@ * * Nextcloud Talk application * - * @author Mario Danic - * Copyright (C) 2017 Mario Danic (mario@lovelyhq.com) + * @author Mario Danic + * @author Marcel Hibbe + * Copyright (C) 2017 Mario Danic + * Copyright (C) 2022 Marcel Hibbe * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -53,7 +55,6 @@ import com.nextcloud.talk.dagger.modules.DatabaseModule import com.nextcloud.talk.dagger.modules.RestModule import com.nextcloud.talk.jobs.AccountRemovalWorker import com.nextcloud.talk.jobs.CapabilitiesWorker -import com.nextcloud.talk.jobs.PushRegistrationWorker import com.nextcloud.talk.jobs.SignalingSettingsWorker import com.nextcloud.talk.utils.ClosedInterfaceImpl import com.nextcloud.talk.utils.DeviceUtils @@ -129,6 +130,7 @@ class NextcloudTalkApplication : MultiDexApplication(), LifecycleObserver { //region Overridden methods override fun onCreate() { + Log.d(TAG, "onCreate") sharedApplication = this val securityKeyManager = SecurityKeyManager.getInstance() @@ -165,7 +167,6 @@ class NextcloudTalkApplication : MultiDexApplication(), LifecycleObserver { ClosedInterfaceImpl().providerInstallerInstallIfNeededAsync() DeviceUtils.ignoreSpecialBatteryFeatures() - val pushRegistrationWork = OneTimeWorkRequest.Builder(PushRegistrationWorker::class.java).build() val accountRemovalWork = OneTimeWorkRequest.Builder(AccountRemovalWorker::class.java).build() val periodicCapabilitiesUpdateWork = PeriodicWorkRequest.Builder( CapabilitiesWorker::class.java, @@ -174,11 +175,10 @@ class NextcloudTalkApplication : MultiDexApplication(), LifecycleObserver { val capabilitiesUpdateWork = OneTimeWorkRequest.Builder(CapabilitiesWorker::class.java).build() val signalingSettingsWork = OneTimeWorkRequest.Builder(SignalingSettingsWorker::class.java).build() - WorkManager.getInstance().enqueue(pushRegistrationWork) - WorkManager.getInstance().enqueue(accountRemovalWork) - WorkManager.getInstance().enqueue(capabilitiesUpdateWork) - WorkManager.getInstance().enqueue(signalingSettingsWork) - WorkManager.getInstance().enqueueUniquePeriodicWork( + WorkManager.getInstance(applicationContext).enqueue(accountRemovalWork) + WorkManager.getInstance(applicationContext).enqueue(capabilitiesUpdateWork) + WorkManager.getInstance(applicationContext).enqueue(signalingSettingsWork) + WorkManager.getInstance(applicationContext).enqueueUniquePeriodicWork( "DailyCapabilitiesUpdateWork", ExistingPeriodicWorkPolicy.REPLACE, periodicCapabilitiesUpdateWork diff --git a/app/src/main/java/com/nextcloud/talk/controllers/AccountVerificationController.java b/app/src/main/java/com/nextcloud/talk/controllers/AccountVerificationController.java index 47aaaf42f..59d9d9f4a 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/AccountVerificationController.java +++ b/app/src/main/java/com/nextcloud/talk/controllers/AccountVerificationController.java @@ -377,7 +377,12 @@ public class AccountVerificationController extends BaseController { } private void registerForPush() { - OneTimeWorkRequest pushRegistrationWork = new OneTimeWorkRequest.Builder(PushRegistrationWorker.class).build(); + Data data = + new Data.Builder().putString(PushRegistrationWorker.ORIGIN, "AccountVerificationController#registerForPush").build(); + + OneTimeWorkRequest pushRegistrationWork = new OneTimeWorkRequest.Builder(PushRegistrationWorker.class) + .setInputData(data) + .build(); WorkManager.getInstance().enqueue(pushRegistrationWork); } diff --git a/app/src/main/java/com/nextcloud/talk/controllers/ConversationsListController.java b/app/src/main/java/com/nextcloud/talk/controllers/ConversationsListController.java index e2a152572..76b3867ba 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/ConversationsListController.java +++ b/app/src/main/java/com/nextcloud/talk/controllers/ConversationsListController.java @@ -85,6 +85,7 @@ import com.nextcloud.talk.models.json.conversations.Conversation; import com.nextcloud.talk.models.json.participants.Participant; import com.nextcloud.talk.ui.dialog.ChooseAccountDialogFragment; import com.nextcloud.talk.utils.ApiUtils; +import com.nextcloud.talk.utils.ClosedInterfaceImpl; import com.nextcloud.talk.utils.ConductorRemapping; import com.nextcloud.talk.utils.DisplayUtils; import com.nextcloud.talk.utils.KeyboardUtils; @@ -285,6 +286,9 @@ public class ConversationsListController extends BaseController implements Searc @Override protected void onAttach(@NonNull View view) { super.onAttach(view); + + new ClosedInterfaceImpl().setUpPushTokenRegistration(); + if (!eventBus.isRegistered(this)) { eventBus.register(this); } diff --git a/app/src/main/java/com/nextcloud/talk/controllers/WebViewLoginController.java b/app/src/main/java/com/nextcloud/talk/controllers/WebViewLoginController.java index 542ecfbad..1d5f7a05b 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/WebViewLoginController.java +++ b/app/src/main/java/com/nextcloud/talk/controllers/WebViewLoginController.java @@ -76,6 +76,7 @@ import javax.inject.Inject; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.core.content.res.ResourcesCompat; +import androidx.work.Data; import androidx.work.OneTimeWorkRequest; import androidx.work.WorkManager; import autodagger.AutoInjector; @@ -407,7 +408,12 @@ public class WebViewLoginController extends BaseController { ApplicationWideMessageHolder.getInstance().setMessageType(finalMessageType); } - OneTimeWorkRequest pushRegistrationWork = new OneTimeWorkRequest.Builder(PushRegistrationWorker.class).build(); + Data data = + new Data.Builder().putString(PushRegistrationWorker.ORIGIN, + "WebViewLoginController#parseAndLoginFromWebView").build(); + OneTimeWorkRequest pushRegistrationWork = new OneTimeWorkRequest.Builder(PushRegistrationWorker.class) + .setInputData(data) + .build(); WorkManager.getInstance().enqueue(pushRegistrationWork); getRouter().popCurrentController(); diff --git a/app/src/main/java/com/nextcloud/talk/interfaces/ClosedInterface.kt b/app/src/main/java/com/nextcloud/talk/interfaces/ClosedInterface.kt index 8e678d5e1..6ece537a1 100644 --- a/app/src/main/java/com/nextcloud/talk/interfaces/ClosedInterface.kt +++ b/app/src/main/java/com/nextcloud/talk/interfaces/ClosedInterface.kt @@ -2,7 +2,9 @@ * Nextcloud Talk application * * @author Mario Danic + * @author Marcel Hibbe * Copyright (C) 2017-2018 Mario Danic + * Copyright (C) 2022 Marcel Hibbe * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,4 +26,5 @@ interface ClosedInterface { val isGooglePlayServicesAvailable: Boolean fun providerInstallerInstallIfNeededAsync() + fun setUpPushTokenRegistration() } diff --git a/app/src/main/java/com/nextcloud/talk/jobs/PushRegistrationWorker.java b/app/src/main/java/com/nextcloud/talk/jobs/PushRegistrationWorker.java index ae6ccc3af..71cbc29b2 100644 --- a/app/src/main/java/com/nextcloud/talk/jobs/PushRegistrationWorker.java +++ b/app/src/main/java/com/nextcloud/talk/jobs/PushRegistrationWorker.java @@ -2,7 +2,9 @@ * Nextcloud Talk application * * @author Mario Danic + * @author Marcel Hibbe * Copyright (C) 2017 Mario Danic + * Copyright (C) 2022 Marcel Hibbe * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,13 +23,19 @@ package com.nextcloud.talk.jobs; import android.content.Context; +import android.util.Log; + import androidx.annotation.NonNull; +import androidx.work.Data; import androidx.work.Worker; import androidx.work.WorkerParameters; + +import com.nextcloud.talk.utils.ClosedInterfaceImpl; import com.nextcloud.talk.utils.PushUtils; public class PushRegistrationWorker extends Worker { public static final String TAG = "PushRegistrationWorker"; + public static final String ORIGIN = "origin"; public PushRegistrationWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) { super(context, workerParams); @@ -36,10 +44,19 @@ public class PushRegistrationWorker extends Worker { @NonNull @Override public Result doWork() { - PushUtils pushUtils = new PushUtils(); - pushUtils.generateRsa2048KeyPair(); - pushUtils.pushRegistrationToServer(); + if(new ClosedInterfaceImpl().isGooglePlayServicesAvailable()){ + Data data = getInputData(); + String origin = data.getString("origin"); + Log.d(TAG, "PushRegistrationWorker called via " + origin); - return Result.success(); + PushUtils pushUtils = new PushUtils(); + pushUtils.generateRsa2048KeyPair(); + pushUtils.pushRegistrationToServer(); + + return Result.success(); + } + Log.w(TAG, "executing PushRegistrationWorker doesn't make sense because Google Play Services are not " + + "available"); + return Result.failure(); } } diff --git a/app/src/main/java/com/nextcloud/talk/utils/PushUtils.java b/app/src/main/java/com/nextcloud/talk/utils/PushUtils.java index 0708d25de..03b273d00 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/PushUtils.java +++ b/app/src/main/java/com/nextcloud/talk/utils/PushUtils.java @@ -2,7 +2,9 @@ * Nextcloud Talk application * * @author Mario Danic + * @author Marcel Hibbe * Copyright (C) 2017 Mario Danic + * Copyright (C) 2022 Marcel Hibbe * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -85,23 +87,19 @@ public class PushUtils { @Inject NcApi ncApi; - private File keysFile; - private File publicKeyFile; - private File privateKeyFile; + private final File publicKeyFile; + private final File privateKeyFile; - private String proxyServer; + private final String proxyServer; public PushUtils() { NextcloudTalkApplication.Companion.getSharedApplication().getComponentApplication().inject(this); - keysFile = NextcloudTalkApplication.Companion.getSharedApplication().getDir("PushKeyStore", Context.MODE_PRIVATE); - - publicKeyFile = new File(NextcloudTalkApplication.Companion.getSharedApplication().getDir("PushKeystore", - Context.MODE_PRIVATE), "push_key.pub"); - privateKeyFile = new File(NextcloudTalkApplication.Companion.getSharedApplication().getDir("PushKeystore", - Context.MODE_PRIVATE), "push_key.priv"); + String keyPath = NextcloudTalkApplication.Companion.getSharedApplication().getDir("PushKeystore", Context.MODE_PRIVATE).getAbsolutePath(); + publicKeyFile = new File(keyPath, "push_key.pub"); + privateKeyFile = new File(keyPath, "push_key.priv"); proxyServer = NextcloudTalkApplication.Companion.getSharedApplication().getResources(). - getString(R.string.nc_push_server_url); + getString(R.string.nc_push_server_url); } public SignatureVerification verifySignature(byte[] signatureBytes, byte[] subjectBytes) { @@ -118,9 +116,9 @@ public class PushUtils { for (UserEntity userEntity : userEntities) { if (!TextUtils.isEmpty(userEntity.getPushConfigurationState())) { pushConfigurationState = LoganSquare.parse(userEntity.getPushConfigurationState(), - PushConfigurationState.class); + PushConfigurationState.class); publicKey = (PublicKey) readKeyFromString(true, - pushConfigurationState.getUserPublicKey()); + pushConfigurationState.getUserPublicKey()); signature.initVerify(publicKey); signature.update(subjectBytes); if (signature.verify(signatureBytes)) { @@ -183,16 +181,13 @@ public class PushUtils { StringBuilder result = new StringBuilder(); for (byte individualByte : bytes) { result.append(Integer.toString((individualByte & 0xff) + 0x100, 16) - .substring(1)); + .substring(1)); } return result.toString(); } public int generateRsa2048KeyPair() { if (!publicKeyFile.exists() && !privateKeyFile.exists()) { - if (!keysFile.exists()) { - keysFile.mkdirs(); - } KeyPairGenerator keyGen = null; try { @@ -226,162 +221,159 @@ public class PushUtils { String token = appPreferences.getPushToken(); if (!TextUtils.isEmpty(token)) { - String credentials; String pushTokenHash = generateSHA512Hash(token).toLowerCase(); PublicKey devicePublicKey = (PublicKey) readKeyFromFile(true); if (devicePublicKey != null) { - byte[] publicKeyBytes = Base64.encode(devicePublicKey.getEncoded(), Base64.NO_WRAP); - String publicKey = new String(publicKeyBytes); - publicKey = publicKey.replaceAll("(.{64})", "$1\n"); + byte[] devicePublicKeyBytes = Base64.encode(devicePublicKey.getEncoded(), Base64.NO_WRAP); + String devicePublicKeyBase64 = new String(devicePublicKeyBytes); + devicePublicKeyBase64 = devicePublicKeyBase64.replaceAll("(.{64})", "$1\n"); - publicKey = "-----BEGIN PUBLIC KEY-----\n" + publicKey + "\n-----END PUBLIC KEY-----\n"; + devicePublicKeyBase64 = "-----BEGIN PUBLIC KEY-----\n" + devicePublicKeyBase64 + "\n-----END PUBLIC KEY-----\n"; if (userUtils.anyUserExists()) { - String providerValue; - PushConfigurationState accountPushData = null; for (Object userEntityObject : userUtils.getUsers()) { UserEntity userEntity = (UserEntity) userEntityObject; - providerValue = userEntity.getPushConfigurationState(); - if (!TextUtils.isEmpty(providerValue)) { - try { - accountPushData = LoganSquare.parse(providerValue, PushConfigurationState.class); - } catch (IOException e) { - Log.d(TAG, "Failed to parse account push data"); - accountPushData = null; - } - } else { - accountPushData = null; - } - if (((TextUtils.isEmpty(providerValue) || accountPushData == null) && !userEntity.getScheduledForDeletion()) || - (accountPushData != null && !accountPushData.getPushToken().equals(token) && !userEntity.getScheduledForDeletion())) { + if (!userEntity.getScheduledForDeletion()) { + Map nextcloudRegisterPushMap = new HashMap<>(); + nextcloudRegisterPushMap.put("format", "json"); + nextcloudRegisterPushMap.put("pushTokenHash", pushTokenHash); + nextcloudRegisterPushMap.put("devicePublicKey", devicePublicKeyBase64); + nextcloudRegisterPushMap.put("proxyServer", proxyServer); - Map queryMap = new HashMap<>(); - queryMap.put("format", "json"); - queryMap.put("pushTokenHash", pushTokenHash); - queryMap.put("devicePublicKey", publicKey); - queryMap.put("proxyServer", proxyServer); - - credentials = ApiUtils.getCredentials(userEntity.getUsername(), userEntity.getToken()); - - ncApi.registerDeviceForNotificationsWithNextcloud( - credentials, - ApiUtils.getUrlNextcloudPush(userEntity.getBaseUrl()), queryMap) - .subscribe(new Observer() { - @Override - public void onSubscribe(@NonNull Disposable d) { - // unused atm - } - - @Override - public void onNext(@NonNull PushRegistrationOverall pushRegistrationOverall) { - Map proxyMap = new HashMap<>(); - proxyMap.put("pushToken", token); - proxyMap.put("deviceIdentifier", pushRegistrationOverall.getOcs().getData(). - getDeviceIdentifier()); - proxyMap.put("deviceIdentifierSignature", pushRegistrationOverall.getOcs() - .getData().getSignature()); - proxyMap.put("userPublicKey", pushRegistrationOverall.getOcs() - .getData().getPublicKey()); - - ncApi.registerDeviceForNotificationsWithProxy( - ApiUtils.getUrlPushProxy(), proxyMap) - .subscribeOn(Schedulers.io()) - .subscribe(new Observer() { - @Override - public void onSubscribe(@NonNull Disposable d) { - // unused atm - } - - @Override - public void onNext(@NonNull Void aVoid) { - PushConfigurationState pushConfigurationState = - new PushConfigurationState(); - pushConfigurationState.setPushToken(token); - pushConfigurationState.setDeviceIdentifier( - pushRegistrationOverall.getOcs() - .getData().getDeviceIdentifier()); - pushConfigurationState.setDeviceIdentifierSignature( - pushRegistrationOverall - .getOcs().getData().getSignature()); - pushConfigurationState.setUserPublicKey( - pushRegistrationOverall.getOcs() - .getData().getPublicKey()); - pushConfigurationState.setUsesRegularPass(false); - - try { - userUtils.createOrUpdateUser(null, - null, null, - userEntity.getDisplayName(), - LoganSquare.serialize(pushConfigurationState), null, - null, userEntity.getId(), null, null, null) - .subscribe(new Observer() { - @Override - public void onSubscribe(@NonNull Disposable d) { - // unused atm - } - - @Override - public void onNext(@NonNull UserEntity userEntity) { - eventBus.post(new EventStatus(userEntity.getId(), EventStatus.EventType.PUSH_REGISTRATION, true)); - } - - @Override - public void onError(@NonNull Throwable e) { - eventBus.post(new EventStatus - (userEntity.getId(), - EventStatus.EventType - .PUSH_REGISTRATION, false)); - } - - @Override - public void onComplete() { - // unused atm - } - }); - } catch (IOException e) { - Log.e(TAG, "IOException while updating user", e); - } - } - - @Override - public void onError(@NonNull Throwable e) { - eventBus.post(new EventStatus(userEntity.getId(), - EventStatus.EventType.PUSH_REGISTRATION, false)); - } - - @Override - public void onComplete() { - // unused atm - } - }); - } - - @Override - public void onError(@NonNull Throwable e) { - eventBus.post(new EventStatus(userEntity.getId(), - EventStatus.EventType.PUSH_REGISTRATION, false)); - } - - @Override - public void onComplete() { - // unused atm - } - }); + registerDeviceWithNextcloud(nextcloudRegisterPushMap, token, userEntity); } } } } + } else { + Log.e(TAG, "push token was empty when trying to register at nextcloud server"); } } + private void registerDeviceWithNextcloud(Map nextcloudRegisterPushMap, String token, UserEntity userEntity) { + String credentials = ApiUtils.getCredentials(userEntity.getUsername(), userEntity.getToken()); + + ncApi.registerDeviceForNotificationsWithNextcloud( + credentials, + ApiUtils.getUrlNextcloudPush(userEntity.getBaseUrl()), + nextcloudRegisterPushMap) + .subscribe(new Observer() { + @Override + public void onSubscribe(@NonNull Disposable d) { + // unused atm + } + + @Override + public void onNext(@NonNull PushRegistrationOverall pushRegistrationOverall) { + Log.d(TAG, "pushTokenHash successfully registered at nextcloud server."); + + Map proxyMap = new HashMap<>(); + proxyMap.put("pushToken", token); + proxyMap.put("deviceIdentifier", pushRegistrationOverall.getOcs().getData(). + getDeviceIdentifier()); + proxyMap.put("deviceIdentifierSignature", pushRegistrationOverall.getOcs() + .getData().getSignature()); + proxyMap.put("userPublicKey", pushRegistrationOverall.getOcs() + .getData().getPublicKey()); + + registerDeviceWithPushProxy(proxyMap, userEntity); + } + + @Override + public void onError(@NonNull Throwable e) { + eventBus.post(new EventStatus(userEntity.getId(), + EventStatus.EventType.PUSH_REGISTRATION, false)); + } + + @Override + public void onComplete() { + // unused atm + } + }); + } + + private void registerDeviceWithPushProxy(Map proxyMap, UserEntity userEntity) { + ncApi.registerDeviceForNotificationsWithPushProxy(ApiUtils.getUrlPushProxy(), proxyMap) + .subscribeOn(Schedulers.io()) + .subscribe(new Observer() { + @Override + public void onSubscribe(@NonNull Disposable d) { + // unused atm + } + + @Override + public void onNext(@NonNull Void aVoid) { + try { + Log.d(TAG, "pushToken successfully registered at pushproxy."); + createOrUpdateUser(proxyMap, userEntity); + } catch (IOException e) { + Log.e(TAG, "IOException while updating user", e); + } + } + + @Override + public void onError(@NonNull Throwable e) { + eventBus.post(new EventStatus(userEntity.getId(), + EventStatus.EventType.PUSH_REGISTRATION, false)); + } + + @Override + public void onComplete() { + // unused atm + } + }); + } + + private void createOrUpdateUser(Map proxyMap, UserEntity userEntity) throws IOException { + PushConfigurationState pushConfigurationState = new PushConfigurationState(); + pushConfigurationState.setPushToken(proxyMap.get("pushToken")); + pushConfigurationState.setDeviceIdentifier(proxyMap.get("deviceIdentifier")); + pushConfigurationState.setDeviceIdentifierSignature(proxyMap.get("deviceIdentifierSignature")); + pushConfigurationState.setUserPublicKey(proxyMap.get("userPublicKey")); + pushConfigurationState.setUsesRegularPass(false); + + userUtils.createOrUpdateUser(null, + null, + null, + userEntity.getDisplayName(), + LoganSquare.serialize(pushConfigurationState), + null, + null, + userEntity.getId(), + null, + null, + null) + .subscribe(new Observer() { + @Override + public void onSubscribe(@NonNull Disposable d) { + // unused atm + } + + @Override + public void onNext(@NonNull UserEntity userEntity) { + eventBus.post(new EventStatus(userEntity.getId(), EventStatus.EventType.PUSH_REGISTRATION, true)); + } + + @Override + public void onError(@NonNull Throwable e) { + eventBus.post(new EventStatus(userEntity.getId(), EventStatus.EventType.PUSH_REGISTRATION, false)); + } + + @Override + public void onComplete() { + // unused atm + } + }); + } + private Key readKeyFromString(boolean readPublicKey, String keyString) { if (readPublicKey) { keyString = keyString.replaceAll("\\n", "").replace("-----BEGIN PUBLIC KEY-----", - "").replace("-----END PUBLIC KEY-----", ""); + "").replace("-----END PUBLIC KEY-----", ""); } else { keyString = keyString.replaceAll("\\n", "").replace("-----BEGIN PRIVATE KEY-----", - "").replace("-----END PRIVATE KEY-----", ""); + "").replace("-----END PRIVATE KEY-----", ""); } KeyFactory keyFactory = null; diff --git a/app/src/main/res/values/setup.xml b/app/src/main/res/values/setup.xml index a4c9ed908..8bf763543 100644 --- a/app/src/main/res/values/setup.xml +++ b/app/src/main/res/values/setup.xml @@ -3,7 +3,9 @@ ~ Nextcloud Talk application ~ ~ @author Mario Danic + ~ @author Marcel Hibbe ~ Copyright (C) 2017-2019 Mario Danic + ~ Copyright (C) 2022 Marcel Hibbe ~ ~ This program is free software: you can redistribute it and/or modify ~ it under the terms of the GNU General Public License as published by @@ -47,12 +49,15 @@ com.nextcloud.client nextcloud - + + + 829118773643-cq33cmhv7mnv7iq8mjv6rt7t15afc70k.apps.googleusercontent.com https://nextcloud-a7dea.firebaseio.com 829118773643 AIzaSyAWIyOcLafaFp8PFL61h64cy1NNZW2cU_s 1:829118773643:android:54b65087c544d819 + nextcloud-a7dea AIzaSyAWIyOcLafaFp8PFL61h64cy1NNZW2cU_s nextcloud-a7dea.appspot.com diff --git a/app/src/qa/java/com/nextcloud/talk/utils/ClosedInterfaceImpl.java b/app/src/qa/java/com/nextcloud/talk/utils/ClosedInterfaceImpl.java index 66496bd31..1625c9867 100644 --- a/app/src/qa/java/com/nextcloud/talk/utils/ClosedInterfaceImpl.java +++ b/app/src/qa/java/com/nextcloud/talk/utils/ClosedInterfaceImpl.java @@ -2,7 +2,9 @@ * Nextcloud Talk application * * @author Mario Danic + * @author Marcel Hibbe * Copyright (C) 2017-2018 Mario Danic + * Copyright (C) 2022 Marcel Hibbe * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -33,4 +35,9 @@ public class ClosedInterfaceImpl implements ClosedInterface { public boolean isGooglePlayServicesAvailable() { return false; } + + @Override + public void setUpPushTokenRegistration() { + // no push notifications for qa build flavour :( + } } diff --git a/scripts/analysis/findbugs-results.txt b/scripts/analysis/findbugs-results.txt index 4c38412cc..c02176735 100644 --- a/scripts/analysis/findbugs-results.txt +++ b/scripts/analysis/findbugs-results.txt @@ -1 +1 @@ -559 \ No newline at end of file +558 \ No newline at end of file diff --git a/scripts/analysis/lint-results.txt b/scripts/analysis/lint-results.txt index 96101281e..7d0dd5fec 100644 --- a/scripts/analysis/lint-results.txt +++ b/scripts/analysis/lint-results.txt @@ -1,2 +1,2 @@ DO NOT TOUCH; GENERATED BY DRONE - Lint Report: 1 error and 222 warnings + Lint Report: 1 error and 224 warnings