From 5c19aefe2aa0d70973507ca344c6a824d487623b Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 5 Jun 2020 16:48:57 +0200 Subject: [PATCH] Push support android (#559) * Cleaned up and hardened BloC logic #501 OT-779 Fixed method channel names Fixed logger usage Renamed notification_manager.dart to display_notification_manager.dart Added async suffix Updated libraries involved in push and notifications Fixed some names and const declarations * Moved key generation into the dart part #501 OT-779 Key handling includes generating, persisting, getting, converting Refactored the method channel handling on Android Refactored the method channel handling on Dart / Flutter side Cleaned up Android native part Renaming and wrapping to better understand sharing flows * Update Android components and try push workarounds #501 OT-779 Fixed MethodChannels.java typo Updated gradle Update libs Added more push callback for debugging * Updated background_fetch, adjust plugin #501 OT-779 * Fix logging * Adjust to new plugin init param * Fix logging * Only recreate LocalNotificationManager if called from background Renamed stuff * Use decrypt in memory * Fix background refresh * testing with in mem decryption * Update firebase * post rebase fixes * first addition of NotificationService extension * Rebase fixes * Cleanup * Cleanup and remove not working workarounds * Fix typo * Post rebase fixes * SecurityHelper additions * Adjust stuff Co-authored-by: Frank Gregor --- android/app/build.gradle | 5 +- android/app/src/main/AndroidManifest.xml | 4 +- .../com/openxchange/oxcoi/MainActivity.java | 108 ++--- .../com/openxchange/oxcoi/MethodChannel.java | 80 ++++ .../com/openxchange/oxcoi/SecurityHelper.java | 82 +--- android/build.gradle | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 4 +- ios/NotificationService/Info.plist | 31 ++ .../NotificationService.swift | 75 ++++ ios/OX Coi/Application/AppDelegate.swift | 23 +- .../Extensions/Foundation/String+Base64.swift | 23 +- .../UIKit/AppDelegate+Security.swift | 84 ++++ .../UIKit/AppDelegate+Sharing.swift | 105 +++-- .../UIKit/UIApplication+Firebase.swift | 80 ++-- .../UIKit/UIApplication+Logging.swift | 80 ++-- ios/OX Coi/Helper/Method.swift | 22 - ios/OX Coi/Helper/MethodChannel.swift | 81 ++++ ios/OX Coi/Helper/SecurityHelper.swift | 152 +++++++ ios/OX Coi/Info.plist | 4 + ios/Podfile | 10 +- ios/Runner.xcodeproj/project.pbxproj | 387 +++++++++++++++++- .../xcschemes/CoiSharing.xcscheme | 96 +++++ .../xcschemes/NotificationService.xcscheme | 94 +++++ .../xcshareddata/xcschemes/Runner.xcscheme | 2 +- .../xcschemes/development.xcscheme | 2 +- .../xcshareddata/xcschemes/stable.xcscheme | 2 +- lib/main.dart | 4 +- .../background_refresh_manager.dart | 76 ++-- lib/src/chat/chat.dart | 4 +- lib/src/chat/chat_bloc.dart | 6 +- lib/src/chat/chat_profile_single.dart | 1 - lib/src/contact/contact_change.dart | 6 +- lib/src/customer/customer_delegate.dart | 9 +- lib/src/data/push.dart | 74 ++++ lib/src/extensions/numbers_apis.dart | 1 + lib/src/gallery/gallery_bloc.dart | 2 - lib/src/invite/invite_bloc.dart | 26 +- lib/src/invite/invite_service.dart | 2 +- lib/src/l10n/l.dart | 3 + lib/src/log/log_bloc_delegate.dart | 4 +- lib/src/log/log_manager.dart | 15 +- lib/src/login/login.dart | 2 - lib/src/main/main_bloc.dart | 22 +- lib/src/message/message_attachment_bloc.dart | 18 +- lib/src/message/message_item_event_state.dart | 4 +- .../display_notification_manager.dart | 178 ++++++++ .../local_notification_manager.dart | 29 +- .../notifications/notification_manager.dart | 211 ---------- lib/src/platform/method_channel.dart | 30 ++ lib/src/platform/preferences.dart | 6 +- lib/src/push/push_bloc.dart | 289 ++++++------- lib/src/push/push_event_state.dart | 12 - lib/src/push/push_manager.dart | 105 +++-- lib/src/push/push_service.dart | 2 +- .../security_generator.dart} | 40 +- lib/src/security/security_manager.dart | 45 ++ lib/src/settings/settings_debug_bloc.dart | 4 +- .../settings/settings_notifications_bloc.dart | 1 - ...ed_data.dart => incoming_shared_data.dart} | 18 +- lib/src/share/outgoing_shared_data.dart | 56 +++ lib/src/share/share.dart | 4 +- lib/src/share/share_bloc.dart | 9 +- lib/src/share/share_event_state.dart | 6 +- lib/src/user/user_profile.dart | 1 - lib/src/utils/constants.dart | 2 + lib/src/utils/core.dart | 1 - lib/src/utils/http.dart | 2 +- pubspec.lock | 17 +- pubspec.yaml | 10 +- test/push/push.dart | 22 +- 70 files changed, 2102 insertions(+), 915 deletions(-) create mode 100644 android/app/src/main/java/com/openxchange/oxcoi/MethodChannel.java create mode 100644 ios/NotificationService/Info.plist create mode 100644 ios/NotificationService/NotificationService.swift rename lib/src/data/notification.dart => ios/OX Coi/Extensions/Foundation/String+Base64.swift (85%) create mode 100644 ios/OX Coi/Extensions/UIKit/AppDelegate+Security.swift delete mode 100644 ios/OX Coi/Helper/Method.swift create mode 100644 ios/OX Coi/Helper/MethodChannel.swift create mode 100644 ios/OX Coi/Helper/SecurityHelper.swift create mode 100644 ios/Runner.xcodeproj/xcshareddata/xcschemes/CoiSharing.xcscheme create mode 100644 ios/Runner.xcodeproj/xcshareddata/xcschemes/NotificationService.xcscheme create mode 100644 lib/src/data/push.dart create mode 100644 lib/src/notifications/display_notification_manager.dart delete mode 100644 lib/src/notifications/notification_manager.dart create mode 100644 lib/src/platform/method_channel.dart rename lib/src/{secure/generator.dart => security/security_generator.dart} (76%) create mode 100644 lib/src/security/security_manager.dart rename lib/src/share/{shared_data.dart => incoming_shared_data.dart} (75%) create mode 100644 lib/src/share/outgoing_shared_data.dart diff --git a/android/app/build.gradle b/android/app/build.gradle index 484ca8c8..eb5c65a0 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -113,11 +113,10 @@ flutter { } dependencies { - implementation 'com.google.crypto.tink:apps-webpush:1.3.0-rc1' - implementation 'com.google.firebase:firebase-core:17.2.1' + implementation 'com.google.crypto.tink:apps-webpush:1.3.0' implementation 'org.bouncycastle:bcprov-jdk16:1.46' - testImplementation 'junit:junit:4.12' + testImplementation 'junit:junit:4.13' androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 674c37b7..f67600c1 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -21,10 +21,10 @@ tools:replace="android:label"> sharedData = new HashMap<>(); private String startString = ""; - private static final String SHARED_MIME_TYPE = "shared_mime_type"; - private static final String SHARED_TEXT = "shared_text"; - private static final String SHARED_PATH = "shared_path"; - private static final String SHARED_FILE_NAME = "shared_file_name"; - // TODO create constants for channel methods - private static final String INTENT_CHANNEL_NAME = "oxcoi.intent"; - // TODO create constants for channel methods - private static final String SECURITY_CHANNEL_NAME = "oxcoi.security"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - handleIntent(getIntent()); + cacheDataFromPlatform(getIntent()); } @Override @@ -85,70 +74,53 @@ public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { super.configureFlutterEngine(flutterEngine); DartExecutor dartExecutor = flutterEngine.getDartExecutor(); setupSharingMethodChannel(dartExecutor); - SecurityHelper securityHelper = new SecurityHelper(this); - setupSecurityMethodChannel(dartExecutor, securityHelper); + setupSecurityMethodChannel(dartExecutor); + } + + @Override + protected void onNewIntent(@NonNull Intent intent) { + super.onNewIntent(intent); + cacheDataFromPlatform(intent); } private void setupSharingMethodChannel(DartExecutor dartExecutor) { - new MethodChannel(dartExecutor, INTENT_CHANNEL_NAME).setMethodCallHandler( + new io.flutter.plugin.common.MethodChannel(dartExecutor, MethodChannel.Sharing.NAME).setMethodCallHandler( (call, result) -> { - if (call.method.contentEquals("getSharedData")) { + if (call.method.contentEquals(MethodChannel.Sharing.Methods.GET_SHARE_DATA)) { result.success(sharedData); sharedData.clear(); - } else if (call.method.contentEquals("getInitialLink")) { + } else if (call.method.contentEquals(MethodChannel.Sharing.Methods.GET_INITIAL_LINK)) { if (startString != null && !startString.isEmpty()) { result.success(startString); startString = ""; } else { result.success(null); } - } else if (call.method.contentEquals("sendSharedData")) { - shareFile(call.arguments); + } else if (call.method.contentEquals(MethodChannel.Sharing.Methods.SEND_SHARE_DATA)) { + shareDataWithPlatform(call.arguments); result.success(null); } }); } - private void setupSecurityMethodChannel(DartExecutor dartExecutor, SecurityHelper securityHelper) { - new MethodChannel(dartExecutor, SECURITY_CHANNEL_NAME).setMethodCallHandler( + private void setupSecurityMethodChannel(DartExecutor dartExecutor) { + SecurityHelper securityHelper = new SecurityHelper(); + new io.flutter.plugin.common.MethodChannel(dartExecutor, MethodChannel.Security.NAME).setMethodCallHandler( (call, result) -> { - if (call.method.contentEquals("generateSecrets")) { - KeyPair keyPair = securityHelper.generateKey(); - if (keyPair != null) { - securityHelper.persistKeyPair(keyPair); - } else { - throw new IllegalStateException("Key pair is empty"); - } - String authSecret = securityHelper.generateAuthSecret(); - if (authSecret != null && !authSecret.isEmpty()) { - securityHelper.persisAuthSecret(authSecret); - } else { - throw new IllegalStateException("Auth secret is empty"); - } - result.success(null); - } else if (call.method.contentEquals("getKey")) { - String publicKeyBase64 = securityHelper.getPublicKeyBase64(); - result.success(publicKeyBase64); - } else if (call.method.contentEquals("getAuthSecret")) { - String authSecret = securityHelper.getAuthSecretFromPersistedData(); - result.success(authSecret); - } else if (call.method.contentEquals("decrypt")) { - String input = call.argument("input"); - byte[] inputBytes = Base64.decode(input, Base64.DEFAULT); - String decryptMessage = securityHelper.decryptMessage(inputBytes); + if (call.method.contentEquals(MethodChannel.Security.Methods.DECRYPT)) { + String encryptedBase64Content = call.argument(MethodChannel.Security.Arguments.CONTENT); + String privateKeyBase64 = call.argument(MethodChannel.Security.Arguments.PRIVATE_KEY); + String publicKeyBase64 = call.argument(MethodChannel.Security.Arguments.PUBLIC_KEY); + String authBase64 = call.argument(MethodChannel.Security.Arguments.AUTH); + byte[] inputBytes = Base64.decode(encryptedBase64Content, Base64.DEFAULT); + String decryptMessage = securityHelper.decryptMessage(inputBytes, privateKeyBase64, publicKeyBase64, authBase64); result.success(decryptMessage); } }); } - @Override - protected void onNewIntent(@NonNull Intent intent) { - super.onNewIntent(intent); - handleIntent(intent); - } - - private void handleIntent(Intent intent) { + private void cacheDataFromPlatform(Intent intent) { String action = intent.getAction(); String type = intent.getType(); Uri data = intent.getData(); @@ -156,25 +128,27 @@ private void handleIntent(Intent intent) { if (Intent.ACTION_SEND.equals(action) && type != null) { if (type.startsWith("text/")) { String text = intent.getStringExtra(Intent.EXTRA_TEXT); - sharedData.put(SHARED_MIME_TYPE, type); - sharedData.put(SHARED_TEXT, text); + sharedData.put(MethodChannel.Sharing.Arguments.MIME_TYPE, type); + sharedData.put(MethodChannel.Sharing.Arguments.TEXT, text); } else if (type.startsWith("application/") || type.startsWith("audio/") || type.startsWith("image/") || type.startsWith("video/")) { Uri uri = (Uri) Objects.requireNonNull(getIntent().getExtras()).get(Intent.EXTRA_STREAM); if (uri == null) { ClipData clipData = intent.getClipData(); - ClipData.Item item = clipData.getItemAt(0); - uri = item.getUri(); + if (clipData != null) { + ClipData.Item item = clipData.getItemAt(0); + uri = item.getUri(); + } } if (uri != null) { String text = intent.getStringExtra(Intent.EXTRA_TEXT); ShareHelper shareHelper = new ShareHelper(); String uriPath = shareHelper.getFilePathForUri(this, uri); if (text != null && !text.isEmpty()) { - sharedData.put(SHARED_TEXT, text); + sharedData.put(MethodChannel.Sharing.Arguments.TEXT, text); } - sharedData.put(SHARED_MIME_TYPE, type); - sharedData.put(SHARED_PATH, uriPath); - sharedData.put(SHARED_FILE_NAME, shareHelper.getFileName()); + sharedData.put(MethodChannel.Sharing.Arguments.MIME_TYPE, type); + sharedData.put(MethodChannel.Sharing.Arguments.PATH, uriPath); + sharedData.put(MethodChannel.Sharing.Arguments.NAME, shareHelper.getFileName()); } } } else if (Intent.ACTION_VIEW.equals(action) && data != null) { @@ -182,13 +156,13 @@ private void handleIntent(Intent intent) { } } - private void shareFile(Object arguments) { + private void shareDataWithPlatform(Object arguments) { @SuppressWarnings("unchecked") HashMap argsMap = (HashMap) arguments; - String title = argsMap.get("title"); - String path = argsMap.get("path"); - String mimeType = argsMap.get("mimeType"); - String text = argsMap.get("text"); + String title = argsMap.get(MethodChannel.Sharing.Arguments.TITLE); + String path = argsMap.get(MethodChannel.Sharing.Arguments.PATH); + String mimeType = argsMap.get(MethodChannel.Sharing.Arguments.MIME_TYPE); + String text = argsMap.get(MethodChannel.Sharing.Arguments.TEXT); Intent shareIntent = new Intent(Intent.ACTION_SEND); shareIntent.setType(mimeType); diff --git a/android/app/src/main/java/com/openxchange/oxcoi/MethodChannel.java b/android/app/src/main/java/com/openxchange/oxcoi/MethodChannel.java new file mode 100644 index 00000000..96cc7abf --- /dev/null +++ b/android/app/src/main/java/com/openxchange/oxcoi/MethodChannel.java @@ -0,0 +1,80 @@ +/* + * OPEN-XCHANGE legal information + * + * All intellectual property rights in the Software are protected by + * international copyright laws. + * + * + * In some countries OX, OX Open-Xchange and open xchange + * as well as the corresponding Logos OX Open-Xchange and OX are registered + * trademarks of the OX Software GmbH group of companies. + * The use of the Logos is not covered by the Mozilla Public License 2.0 (MPL 2.0). + * Instead, you are allowed to use these Logos according to the terms and + * conditions of the Creative Commons License, Version 2.5, Attribution, + * Non-commercial, ShareAlike, and the interpretation of the term + * Non-commercial applicable to the aforementioned license is published + * on the web site https://www.open-xchange.com/terms-and-conditions/. + * + * Please make sure that third-party modules and libraries are used + * according to their respective licenses. + * + * Any modifications to this package must retain all copyright notices + * of the original copyright holder(s) for the original code used. + * + * After any such modifications, the original and derivative code shall remain + * under the copyright of the copyright holder(s) and/or original author(s) as stated here: + * https://www.open-xchange.com/legal/. The contributing author shall be + * given Attribution for the derivative code and a license granting use. + * + * Copyright (C) 2016-2020 OX Software GmbH + * Mail: info@open-xchange.com + * + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * 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 Mozilla Public License 2.0 + * for more details. + */ + +package com.openxchange.oxcoi; + +class MethodChannel { + + abstract static class Security { + static final String NAME = "oxcoi.security"; + + abstract static class Methods { + static final String DECRYPT = "decrypt"; + } + + abstract static class Arguments { + static final String CONTENT = "encryptedBase64Content"; + static final String PRIVATE_KEY = "privateKeyBase64"; + static final String PUBLIC_KEY = "publicKeyBase64"; + static final String AUTH = "authBase64"; + } + } + + abstract static class Sharing { + static final String NAME = "oxcoi.sharing"; + + abstract static class Methods { + static final String GET_SHARE_DATA = "getSharedData"; + static final String SEND_SHARE_DATA = "sendSharedData"; + static final String GET_INITIAL_LINK = "getInitialLink"; + } + + abstract static class Arguments { + static final String MIME_TYPE = "mimeType"; + static final String TEXT = "text"; + static final String PATH = "path"; + static final String NAME = "fileName"; + static final String TITLE = "title"; + } + } +} + diff --git a/android/app/src/main/java/com/openxchange/oxcoi/SecurityHelper.java b/android/app/src/main/java/com/openxchange/oxcoi/SecurityHelper.java index 1d93a77d..79d6b83e 100644 --- a/android/app/src/main/java/com/openxchange/oxcoi/SecurityHelper.java +++ b/android/app/src/main/java/com/openxchange/oxcoi/SecurityHelper.java @@ -43,9 +43,6 @@ package com.openxchange.oxcoi; -import android.app.Activity; -import android.content.Context; -import android.content.SharedPreferences; import android.util.Base64; import com.google.crypto.tink.HybridDecrypt; @@ -55,87 +52,29 @@ import org.bouncycastle.jce.interfaces.ECPrivateKey; import org.bouncycastle.jce.interfaces.ECPublicKey; import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.bouncycastle.jce.provider.asymmetric.ec.KeyPairGenerator; import org.bouncycastle.jce.spec.ECParameterSpec; import org.bouncycastle.jce.spec.ECPrivateKeySpec; import org.bouncycastle.jce.spec.ECPublicKeySpec; import org.bouncycastle.math.ec.ECPoint; import java.math.BigInteger; -import java.security.InvalidAlgorithmParameterException; import java.security.KeyFactory; -import java.security.KeyPair; import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; import java.security.Security; -import java.security.spec.ECGenParameterSpec; import java.security.spec.InvalidKeySpecException; class SecurityHelper { - private static final String KEY_PUSH_PRIVATE = "KEY_PUSH_PRIVATE"; - private static final String KEY_PUSH_PUBLIC = "KEY_PUSH_PUBLIC"; - private static final String KEY_PUSH_AUTH = "KEY_PUSH_AUTH"; private static final String CURVE_NAME = "secp256r1"; private static final String KEY_ALGORITHM = "ECDH"; - private Activity activity; - - SecurityHelper(Activity activity) { + SecurityHelper() { Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME); Security.addProvider(new BouncyCastleProvider()); - this.activity = activity; - } - - String getPublicKeyBase64() { - ECPublicKey publicKey = getPublicKeyFromPersistedData(); - if (publicKey == null) { - return null; - } - return Base64.encodeToString(publicKey.getQ().getEncoded(), Base64.URL_SAFE); - } - - KeyPair generateKey() { - ECGenParameterSpec params = new ECGenParameterSpec(CURVE_NAME); - KeyPairGenerator generator = new KeyPairGenerator.ECDH(); - try { - generator.initialize(params, new SecureRandom()); - } catch (InvalidAlgorithmParameterException e) { - e.printStackTrace(); - } - return generator.generateKeyPair(); } - String generateAuthSecret() { - SecureRandom random = new SecureRandom(); - byte[] bytes = new byte[16]; - random.nextBytes(bytes); - return Base64.encodeToString(bytes, Base64.URL_SAFE); - } - - void persistKeyPair(KeyPair keyPair) { - ECPublicKey ecPublicKey = (ECPublicKey) keyPair.getPublic(); - ECPrivateKey ecPrivateKey = (ECPrivateKey) keyPair.getPrivate(); - String publicKeyBase64 = Base64.encodeToString(ecPublicKey.getQ().getEncoded(), Base64.URL_SAFE); - String privateKeyBase64 = Base64.encodeToString(ecPrivateKey.getD().toByteArray(), Base64.URL_SAFE); - SharedPreferences sharedPref = activity.getPreferences(Context.MODE_PRIVATE); - SharedPreferences.Editor editor = sharedPref.edit(); - editor.putString(KEY_PUSH_PRIVATE, privateKeyBase64); - editor.putString(KEY_PUSH_PUBLIC, publicKeyBase64); - editor.apply(); - } - - void persisAuthSecret(String authSecret) { - SharedPreferences sharedPref = activity.getPreferences(Context.MODE_PRIVATE); - SharedPreferences.Editor editor = sharedPref.edit(); - editor.putString(KEY_PUSH_AUTH, authSecret); - editor.apply(); - } - - private ECPrivateKey getPrivateKeyFromPersistedData() { - SharedPreferences sharedPref = activity.getPreferences(Context.MODE_PRIVATE); - String privateKeyBase64 = sharedPref.getString(KEY_PUSH_PRIVATE, ""); + private ECPrivateKey getPrivateKeyFromPersistedData(String privateKeyBase64) { byte[] privateKeyBytes = Base64.decode(privateKeyBase64, Base64.URL_SAFE); BigInteger privateKeyD = new BigInteger(privateKeyBytes); @@ -152,9 +91,7 @@ private ECPrivateKey getPrivateKeyFromPersistedData() { return null; } - private ECPublicKey getPublicKeyFromPersistedData() { - SharedPreferences sharedPref = activity.getPreferences(Context.MODE_PRIVATE); - String publicKeyBase64 = sharedPref.getString(KEY_PUSH_PUBLIC, ""); + private ECPublicKey getPublicKeyFromBase64String(String publicKeyBase64) { byte[] publicKeyBytes = Base64.decode(publicKeyBase64, Base64.URL_SAFE); ECParameterSpec ecParameterSpec = ECNamedCurveTable.getParameterSpec(CURVE_NAME); @@ -171,21 +108,16 @@ private ECPublicKey getPublicKeyFromPersistedData() { return null; } - String getAuthSecretFromPersistedData() { - SharedPreferences sharedPref = activity.getPreferences(Context.MODE_PRIVATE); - return sharedPref.getString(KEY_PUSH_AUTH, ""); - } - - String decryptMessage(byte[] encryptedContent) { - ECPrivateKey recipientPrivateKey = getPrivateKeyFromPersistedData(); - ECPublicKey recipientPublicKey = getPublicKeyFromPersistedData(); + String decryptMessage(byte[] encryptedContent, String privateKeyBase64, String publicKeyBase64, String authBase64) { + ECPrivateKey recipientPrivateKey = getPrivateKeyFromPersistedData(privateKeyBase64); + ECPublicKey recipientPublicKey = getPublicKeyFromBase64String(publicKeyBase64); try { java.security.interfaces.ECPublicKey recipientPublicKeyAsJavaSecurity = (java.security.interfaces.ECPublicKey) recipientPublicKey; if (recipientPublicKey == null) { throw new IllegalStateException("Public key is null"); } java.security.interfaces.ECPrivateKey recipientPrivateAsJavaSecurity = (java.security.interfaces.ECPrivateKey) recipientPrivateKey; - byte[] authSecret = Base64.decode(getAuthSecretFromPersistedData(), Base64.URL_SAFE); + byte[] authSecret = Base64.decode(authBase64, Base64.URL_SAFE); HybridDecrypt hybridDecrypt = new WebPushHybridDecrypt.Builder() .withAuthSecret(authSecret) .withRecipientPublicKey(recipientPublicKeyAsJavaSecurity) diff --git a/android/build.gradle b/android/build.gradle index 9234dae2..b964b7fd 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -5,7 +5,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.5.3' + classpath 'com.android.tools.build:gradle:3.6.3' classpath 'com.google.gms:google-services:4.3.3' } } diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 55fb03a1..b30f0213 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Thu Nov 28 13:37:47 CET 2019 +#Wed Apr 29 13:11:03 CEST 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip diff --git a/ios/NotificationService/Info.plist b/ios/NotificationService/Info.plist new file mode 100644 index 00000000..7db9ec9c --- /dev/null +++ b/ios/NotificationService/Info.plist @@ -0,0 +1,31 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + NotificationService + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + NSExtension + + NSExtensionPointIdentifier + com.apple.usernotifications.service + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).NotificationService + + + diff --git a/ios/NotificationService/NotificationService.swift b/ios/NotificationService/NotificationService.swift new file mode 100644 index 00000000..80b9965d --- /dev/null +++ b/ios/NotificationService/NotificationService.swift @@ -0,0 +1,75 @@ +/* +* OPEN-XCHANGE legal information +* +* All intellectual property rights in the Software are protected by +* international copyright laws. +* +* +* In some countries OX, OX Open-Xchange and open xchange +* as well as the corresponding Logos OX Open-Xchange and OX are registered +* trademarks of the OX Software GmbH group of companies. +* The use of the Logos is not covered by the Mozilla Public License 2.0 (MPL 2.0). +* Instead, you are allowed to use these Logos according to the terms and +* conditions of the Creative Commons License, Version 2.5, Attribution, +* Non-commercial, ShareAlike, and the interpretation of the term +* Non-commercial applicable to the aforementioned license is published +* on the web site https://www.open-xchange.com/terms-and-conditions/. +* +* Please make sure that third-party modules and libraries are used +* according to their respective licenses. +* +* Any modifications to this package must retain all copyright notices +* of the original copyright holder(s) for the original code used. +* +* After any such modifications, the original and derivative code shall remainv +* under the copyright of the copyright holder(s) and/or original author(s) as stated here: +* https://www.open-xchange.com/legal/. The contributing author shall be +* given Attribution for the derivative code and a license granting use. +* +* Copyright (C) 2016-2019 OX Software GmbH +* Mail: info@open-xchange.com +* +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at http://mozilla.org/MPL/2.0/. +* +* 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 Mozilla Public License 2.0 +* for more details. +*/ + +import UserNotifications + +// https://medium.com/@mail2ashislaha/rich-push-notification-with-firebase-cloud-messaging-fcm-and-pusher-in-ios-platform-8b4e9922120 + +class NotificationService: UNNotificationServiceExtension { + + var contentHandler: ((UNNotificationContent) -> Void)? + var bestAttemptContent: UNMutableNotificationContent? + + override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { + self.contentHandler = contentHandler + bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) + + NSLog("[NotificationService] Received Notification") + + if let bestAttemptContent = bestAttemptContent { + // Modify the notification content here... + bestAttemptContent.title = "[modified] \(bestAttemptContent.title)" + bestAttemptContent.body = " [modified] \(bestAttemptContent.body)" + + contentHandler(bestAttemptContent) + } + } + + override func serviceExtensionTimeWillExpire() { + // Called just before the extension will be terminated by the system. + // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used. + if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent { + contentHandler(bestAttemptContent) + } + } + +} diff --git a/ios/OX Coi/Application/AppDelegate.swift b/ios/OX Coi/Application/AppDelegate.swift index 56ff4085..3687c4c8 100644 --- a/ios/OX Coi/Application/AppDelegate.swift +++ b/ios/OX Coi/Application/AppDelegate.swift @@ -53,30 +53,34 @@ class AppDelegate: FlutterAppDelegate { var backgroundTask: UIBackgroundTaskIdentifier = .invalid override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + NSLog("[AppDelegate] didFinishLaunchingWithOptions") UIApplication.setupLogging() UIApplication.setupFirebase() - UNUserNotificationCenter.current().delegate = self GeneratedPluginRegistrant.register(with: self) - application.setMinimumBackgroundFetchInterval(60 * 5) +// application.setMinimumBackgroundFetchInterval(60 * 5) setupSharingMethodChannel() + setupSecurityMethodChannel() return super.application(application, didFinishLaunchingWithOptions: launchOptions) } override func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool { + NSLog("[AppDelegate] open url") startString = url.absoluteString - self.setupSharingMethodChannel() + setupSharingMethodChannel() return true } override func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { + NSLog("[AppDelegate] didRegisterForRemoteNotificationsWithDeviceToken") Messaging.messaging().apnsToken = deviceToken } - + override func applicationDidEnterBackground(_ application: UIApplication) { + NSLog("[AppDelegate] applicationDidEnterBackground") if UserDefaults.applicationShouldTerminate { UserDefaults.applicationShouldTerminate = false let sel = Selector(("terminateWithSuccess")) @@ -89,4 +93,15 @@ class AppDelegate: FlutterAppDelegate { } } + override func applicationDidBecomeActive(_ application: UIApplication) { + NSLog("[AppDelegate] applicationDidBecomeActive") + // https://github.com/flutter/flutter/issues/47203#issuecomment-590834018 + signal(SIGPIPE, SIG_IGN) + } + + override func applicationWillEnterForeground(_ application: UIApplication) { + NSLog("[AppDelegate] applicationWillEnterForeground") + signal(SIGPIPE, SIG_IGN) + } + } diff --git a/lib/src/data/notification.dart b/ios/OX Coi/Extensions/Foundation/String+Base64.swift similarity index 85% rename from lib/src/data/notification.dart rename to ios/OX Coi/Extensions/Foundation/String+Base64.swift index 0e6b5fb8..4c8b5474 100644 --- a/lib/src/data/notification.dart +++ b/ios/OX Coi/Extensions/Foundation/String+Base64.swift @@ -40,16 +40,19 @@ * for more details. */ -class NotificationData { - String content; - bool valid; +import Foundation - NotificationData.fromJson(Map json) : content = json['data']['content'] { - valid = content != null && content.isNotEmpty; - } +extension String { + + var base64DecodedString: String? { + guard let data = Data(base64Encoded: self) else { + return nil + } + return String(data: data, encoding: .utf8) + } + + var base64EncodedString: String { + Data(self.utf8).base64EncodedString() + } - @override - String toString() { - return content; - } } diff --git a/ios/OX Coi/Extensions/UIKit/AppDelegate+Security.swift b/ios/OX Coi/Extensions/UIKit/AppDelegate+Security.swift new file mode 100644 index 00000000..80aa43dc --- /dev/null +++ b/ios/OX Coi/Extensions/UIKit/AppDelegate+Security.swift @@ -0,0 +1,84 @@ +/* +* OPEN-XCHANGE legal information +* +* All intellectual property rights in the Software are protected by +* international copyright laws. +* +* +* In some countries OX, OX Open-Xchange and open xchange +* as well as the corresponding Logos OX Open-Xchange and OX are registered +* trademarks of the OX Software GmbH group of companies. +* The use of the Logos is not covered by the Mozilla Public License 2.0 (MPL 2.0). +* Instead, you are allowed to use these Logos according to the terms and +* conditions of the Creative Commons License, Version 2.5, Attribution, +* Non-commercial, ShareAlike, and the interpretation of the term +* Non-commercial applicable to the aforementioned license is published +* on the web site https://www.open-xchange.com/terms-and-conditions/. +* +* Please make sure that third-party modules and libraries are used +* according to their respective licenses. +* +* Any modifications to this package must retain all copyright notices +* of the original copyright holder(s) for the original code used. +* +* After any such modifications, the original and derivative code shall remain +* under the copyright of the copyright holder(s) and/or original author(s) as stated here: +* https://www.open-xchange.com/legal/. The contributing author shall be +* given Attribution for the derivative code and a license granting use. +* +* Copyright (C) 2016-2020 OX Software GmbH +* Mail: info@open-xchange.com +* +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at http://mozilla.org/MPL/2.0/. +* +* 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 Mozilla Public License 2.0 +* for more details. +*/ + +import UIKit + +extension AppDelegate { + + internal func setupSecurityMethodChannel() { + guard let controller = window.rootViewController as? FlutterViewController else { + return + } + + let channel = FlutterMethodChannel(name: MethodChannel.Security.Name, binaryMessenger: controller.binaryMessenger) + channel.setMethodCallHandler {(call: FlutterMethodCall, result: FlutterResult) -> Void in + switch call.method { + case MethodChannel.Security.Method.Decrypt: + if let messageDict = call.arguments as? [String: String] { +// do { +// let securityHelper = try SecurityHelper(message: messageDict) +// let decryptedMessage = try securityHelper.getDecryptedMessage() +// result(decryptedMessage) +// +// } catch { +// throw SecurityHelperError.decryptMessageFailed(error: error.localizedDescription) +// } + return + } + + default: + break + } + result(nil) + } + } +} + +/* + String encryptedBase64Content = call.argument(MethodChannels.Security.Arguments.CONTENT); + String privateKeyBase64 = call.argument(MethodChannels.Security.Arguments.PRIVATE_KEY); + String publicKeyBase64 = call.argument(MethodChannels.Security.Arguments.PUBLIC_KEY); + String authBase64 = call.argument(MethodChannels.Security.Arguments.AUTH); + byte[] inputBytes = Base64.decode(encryptedBase64Content, Base64.DEFAULT); + String decryptMessage = securityHelper.decryptMessage(inputBytes, privateKeyBase64, publicKeyBase64, authBase64); + result.success(decryptMessage); + */ diff --git a/ios/OX Coi/Extensions/UIKit/AppDelegate+Sharing.swift b/ios/OX Coi/Extensions/UIKit/AppDelegate+Sharing.swift index 38ad227b..703ba6e6 100644 --- a/ios/OX Coi/Extensions/UIKit/AppDelegate+Sharing.swift +++ b/ios/OX Coi/Extensions/UIKit/AppDelegate+Sharing.swift @@ -1,58 +1,49 @@ /* - * OPEN-XCHANGE legal information - * - * All intellectual property rights in the Software are protected by - * international copyright laws. - * - * - * In some countries OX, OX Open-Xchange and open xchange - * as well as the corresponding Logos OX Open-Xchange and OX are registered - * trademarks of the OX Software GmbH group of companies. - * The use of the Logos is not covered by the Mozilla Public License 2.0 (MPL 2.0). - * Instead, you are allowed to use these Logos according to the terms and - * conditions of the Creative Commons License, Version 2.5, Attribution, - * Non-commercial, ShareAlike, and the interpretation of the term - * Non-commercial applicable to the aforementioned license is published - * on the web site https://www.open-xchange.com/terms-and-conditions/. - * - * Please make sure that third-party modules and libraries are used - * according to their respective licenses. - * - * Any modifications to this package must retain all copyright notices - * of the original copyright holder(s) for the original code used. - * - * After any such modifications, the original and derivative code shall remain - * under the copyright of the copyright holder(s) and/or original author(s) as stated here: - * https://www.open-xchange.com/legal/. The contributing author shall be - * given Attribution for the derivative code and a license granting use. - * - * Copyright (C) 2016-2019 OX Software GmbH - * Mail: info@open-xchange.com - * - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - * 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 Mozilla Public License 2.0 - * for more details. - */ +* OPEN-XCHANGE legal information +* +* All intellectual property rights in the Software are protected by +* international copyright laws. +* +* +* In some countries OX, OX Open-Xchange and open xchange +* as well as the corresponding Logos OX Open-Xchange and OX are registered +* trademarks of the OX Software GmbH group of companies. +* The use of the Logos is not covered by the Mozilla Public License 2.0 (MPL 2.0). +* Instead, you are allowed to use these Logos according to the terms and +* conditions of the Creative Commons License, Version 2.5, Attribution, +* Non-commercial, ShareAlike, and the interpretation of the term +* Non-commercial applicable to the aforementioned license is published +* on the web site https://www.open-xchange.com/terms-and-conditions/. +* +* Please make sure that third-party modules and libraries are used +* according to their respective licenses. +* +* Any modifications to this package must retain all copyright notices +* of the original copyright holder(s) for the original code used. +* +* After any such modifications, the original and derivative code shall remain +* under the copyright of the copyright holder(s) and/or original author(s) as stated here: +* https://www.open-xchange.com/legal/. The contributing author shall be +* given Attribution for the derivative code and a license granting use. +* +* Copyright (C) 2016-2020 OX Software GmbH +* Mail: info@open-xchange.com +* +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at http://mozilla.org/MPL/2.0/. +* +* 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 Mozilla Public License 2.0 +* for more details. +*/ import Foundation -fileprivate let INTENT_CHANNEL_NAME = "oxcoi.intent" - extension AppDelegate { - fileprivate struct Constant { - static let path = "path" - static let text = "text" - static let mimeType = "text/plain" - static let keyPrefixShared = "shared" - } - fileprivate enum DataType: String { case url case text @@ -64,10 +55,10 @@ extension AppDelegate { return } - let methodChannel = FlutterMethodChannel(name: INTENT_CHANNEL_NAME, binaryMessenger: controller.binaryMessenger) + let methodChannel = FlutterMethodChannel(name: MethodChannel.Sharing.Intent, binaryMessenger: controller.binaryMessenger) methodChannel.setMethodCallHandler {(call: FlutterMethodCall, result: FlutterResult) -> Void in switch call.method { - case Method.Invite.InviteLink: + case MethodChannel.Sharing.Method.InitialLink: if let startString = self.startString { if !startString.isEmpty { result(self.startString) @@ -76,12 +67,12 @@ extension AppDelegate { } } - case Method.Sharing.SendSharedData: + case MethodChannel.Sharing.Method.SendSharedData: if let args = call.arguments as? [String: String] { self.shareFile(arguments: args) } - case Method.Sharing.GetSharedData: + case MethodChannel.Sharing.Method.GetSharedData: result(self.getSharedData()) self.clearSharedData() return @@ -95,13 +86,13 @@ extension AppDelegate { private func shareFile(arguments: [String: String]) { var itemTemp: Any? - if let path = arguments[Constant.path] { + if let path = arguments[MethodChannel.Sharing.Argument.path] { if !path.isEmpty { itemTemp = URL(fileURLWithPath: path) } } - if let text = arguments[Constant.text] { + if let text = arguments[MethodChannel.Sharing.Argument.text] { if !text.isEmpty { itemTemp = text } @@ -126,7 +117,7 @@ extension AppDelegate { } if userDefaults.object(forKey: SharedData.DataType) as? String == DataType.url.rawValue { - dict = [SharedData.MimeType: Constant.mimeType, SharedData.Text: userDefaults.object(forKey: SharedData.Text)] + dict = [SharedData.MimeType: MethodChannel.Sharing.Argument.mimeType, SharedData.Text: userDefaults.object(forKey: SharedData.Text)] return dict } else if userDefaults.object(forKey: SharedData.DataType) as? String == DataType.text.rawValue { @@ -152,7 +143,7 @@ extension AppDelegate { } for key in userDefaults.dictionaryRepresentation().keys { - if key.hasPrefix(Constant.keyPrefixShared) { + if key.hasPrefix(MethodChannel.Sharing.Argument.keyPrefixShared) { userDefaults.removeObject(forKey: key) } } diff --git a/ios/OX Coi/Extensions/UIKit/UIApplication+Firebase.swift b/ios/OX Coi/Extensions/UIKit/UIApplication+Firebase.swift index ebb7d06a..d4f28646 100644 --- a/ios/OX Coi/Extensions/UIKit/UIApplication+Firebase.swift +++ b/ios/OX Coi/Extensions/UIKit/UIApplication+Firebase.swift @@ -1,44 +1,44 @@ /* - * OPEN-XCHANGE legal information - * - * All intellectual property rights in the Software are protected by - * international copyright laws. - * - * - * In some countries OX, OX Open-Xchange and open xchange - * as well as the corresponding Logos OX Open-Xchange and OX are registered - * trademarks of the OX Software GmbH group of companies. - * The use of the Logos is not covered by the Mozilla Public License 2.0 (MPL 2.0). - * Instead, you are allowed to use these Logos according to the terms and - * conditions of the Creative Commons License, Version 2.5, Attribution, - * Non-commercial, ShareAlike, and the interpretation of the term - * Non-commercial applicable to the aforementioned license is published - * on the web site https://www.open-xchange.com/terms-and-conditions/. - * - * Please make sure that third-party modules and libraries are used - * according to their respective licenses. - * - * Any modifications to this package must retain all copyright notices - * of the original copyright holder(s) for the original code used. - * - * After any such modifications, the original and derivative code shall remain - * under the copyright of the copyright holder(s) and/or original author(s) as stated here: - * https://www.open-xchange.com/legal/. The contributing author shall be - * given Attribution for the derivative code and a license granting use. - * - * Copyright (C) 2016-2020 OX Software GmbH - * Mail: info@open-xchange.com - * - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - * 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 Mozilla Public License 2.0 - * for more details. - */ +* OPEN-XCHANGE legal information +* +* All intellectual property rights in the Software are protected by +* international copyright laws. +* +* +* In some countries OX, OX Open-Xchange and open xchange +* as well as the corresponding Logos OX Open-Xchange and OX are registered +* trademarks of the OX Software GmbH group of companies. +* The use of the Logos is not covered by the Mozilla Public License 2.0 (MPL 2.0). +* Instead, you are allowed to use these Logos according to the terms and +* conditions of the Creative Commons License, Version 2.5, Attribution, +* Non-commercial, ShareAlike, and the interpretation of the term +* Non-commercial applicable to the aforementioned license is published +* on the web site https://www.open-xchange.com/terms-and-conditions/. +* +* Please make sure that third-party modules and libraries are used +* according to their respective licenses. +* +* Any modifications to this package must retain all copyright notices +* of the original copyright holder(s) for the original code used. +* +* After any such modifications, the original and derivative code shall remain +* under the copyright of the copyright holder(s) and/or original author(s) as stated here: +* https://www.open-xchange.com/legal/. The contributing author shall be +* given Attribution for the derivative code and a license granting use. +* +* Copyright (C) 2016-2020 OX Software GmbH +* Mail: info@open-xchange.com +* +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at http://mozilla.org/MPL/2.0/. +* +* 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 Mozilla Public License 2.0 +* for more details. +*/ import Foundation import Firebase diff --git a/ios/OX Coi/Extensions/UIKit/UIApplication+Logging.swift b/ios/OX Coi/Extensions/UIKit/UIApplication+Logging.swift index 50c934b0..c10d015a 100644 --- a/ios/OX Coi/Extensions/UIKit/UIApplication+Logging.swift +++ b/ios/OX Coi/Extensions/UIKit/UIApplication+Logging.swift @@ -1,44 +1,44 @@ /* - * OPEN-XCHANGE legal information - * - * All intellectual property rights in the Software are protected by - * international copyright laws. - * - * - * In some countries OX, OX Open-Xchange and open xchange - * as well as the corresponding Logos OX Open-Xchange and OX are registered - * trademarks of the OX Software GmbH group of companies. - * The use of the Logos is not covered by the Mozilla Public License 2.0 (MPL 2.0). - * Instead, you are allowed to use these Logos according to the terms and - * conditions of the Creative Commons License, Version 2.5, Attribution, - * Non-commercial, ShareAlike, and the interpretation of the term - * Non-commercial applicable to the aforementioned license is published - * on the web site https://www.open-xchange.com/terms-and-conditions/. - * - * Please make sure that third-party modules and libraries are used - * according to their respective licenses. - * - * Any modifications to this package must retain all copyright notices - * of the original copyright holder(s) for the original code used. - * - * After any such modifications, the original and derivative code shall remain - * under the copyright of the copyright holder(s) and/or original author(s) as stated here: - * https://www.open-xchange.com/legal/. The contributing author shall be - * given Attribution for the derivative code and a license granting use. - * - * Copyright (C) 2016-2019 OX Software GmbH - * Mail: info@open-xchange.com - * - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - * 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 Mozilla Public License 2.0 - * for more details. - */ +* OPEN-XCHANGE legal information +* +* All intellectual property rights in the Software are protected by +* international copyright laws. +* +* +* In some countries OX, OX Open-Xchange and open xchange +* as well as the corresponding Logos OX Open-Xchange and OX are registered +* trademarks of the OX Software GmbH group of companies. +* The use of the Logos is not covered by the Mozilla Public License 2.0 (MPL 2.0). +* Instead, you are allowed to use these Logos according to the terms and +* conditions of the Creative Commons License, Version 2.5, Attribution, +* Non-commercial, ShareAlike, and the interpretation of the term +* Non-commercial applicable to the aforementioned license is published +* on the web site https://www.open-xchange.com/terms-and-conditions/. +* +* Please make sure that third-party modules and libraries are used +* according to their respective licenses. +* +* Any modifications to this package must retain all copyright notices +* of the original copyright holder(s) for the original code used. +* +* After any such modifications, the original and derivative code shall remain +* under the copyright of the copyright holder(s) and/or original author(s) as stated here: +* https://www.open-xchange.com/legal/. The contributing author shall be +* given Attribution for the derivative code and a license granting use. +* +* Copyright (C) 2016-2019 OX Software GmbH +* Mail: info@open-xchange.com +* +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at http://mozilla.org/MPL/2.0/. +* +* 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 Mozilla Public License 2.0 +* for more details. +*/ import UIKit import SwiftyBeaver diff --git a/ios/OX Coi/Helper/Method.swift b/ios/OX Coi/Helper/Method.swift deleted file mode 100644 index 0f6b76ba..00000000 --- a/ios/OX Coi/Helper/Method.swift +++ /dev/null @@ -1,22 +0,0 @@ -// -// Method.swift -// Runner -// -// Created by Cihan Oezkan2 on 21.11.19. -// Copyright © 2019 OX Software GmbH. All rights reserved. -// - -import Foundation - -struct Method {} - -extension Method { - struct Invite { - static let InviteLink = "getInitialLink" - } - - struct Sharing { - static let SendSharedData = "sendSharedData" - static let GetSharedData = "getSharedData" - } -} diff --git a/ios/OX Coi/Helper/MethodChannel.swift b/ios/OX Coi/Helper/MethodChannel.swift new file mode 100644 index 00000000..63abc329 --- /dev/null +++ b/ios/OX Coi/Helper/MethodChannel.swift @@ -0,0 +1,81 @@ +/* +* OPEN-XCHANGE legal information +* +* All intellectual property rights in the Software are protected by +* international copyright laws. +* +* +* In some countries OX, OX Open-Xchange and open xchange +* as well as the corresponding Logos OX Open-Xchange and OX are registered +* trademarks of the OX Software GmbH group of companies. +* The use of the Logos is not covered by the Mozilla Public License 2.0 (MPL 2.0). +* Instead, you are allowed to use these Logos according to the terms and +* conditions of the Creative Commons License, Version 2.5, Attribution, +* Non-commercial, ShareAlike, and the interpretation of the term +* Non-commercial applicable to the aforementioned license is published +* on the web site https://www.open-xchange.com/terms-and-conditions/. +* +* Please make sure that third-party modules and libraries are used +* according to their respective licenses. +* +* Any modifications to this package must retain all copyright notices +* of the original copyright holder(s) for the original code used. +* +* After any such modifications, the original and derivative code shall remain +* under the copyright of the copyright holder(s) and/or original author(s) as stated here: +* https://www.open-xchange.com/legal/. The contributing author shall be +* given Attribution for the derivative code and a license granting use. +* +* Copyright (C) 2016-2019 OX Software GmbH +* Mail: info@open-xchange.com +* +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at http://mozilla.org/MPL/2.0/. +* +* 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 Mozilla Public License 2.0 +* for more details. +*/ + +import Foundation + +struct MethodChannel {} + +extension MethodChannel { + + struct Sharing { + static let Intent = "oxcoi.sharing" + + struct Method { + static let InitialLink = "getInitialLink" + static let SendSharedData = "sendSharedData" + static let GetSharedData = "getSharedData" + } + + struct Argument { + static let path = "path" + static let text = "text" + static let mimeType = "text/plain" + static let keyPrefixShared = "shared" + } + } + + struct Security { + static let Name = "oxcoi.security" + + struct Method { + static let Decrypt = "decrypt" + } + + struct Argument { + static let Content = "encryptedBase64Content" + static let PrivateKey = "privateKeyBase64" + static let PublicKey = "publicKeyBase64" + static let Auth = "authBase64" + } + } + +} diff --git a/ios/OX Coi/Helper/SecurityHelper.swift b/ios/OX Coi/Helper/SecurityHelper.swift new file mode 100644 index 00000000..2febf623 --- /dev/null +++ b/ios/OX Coi/Helper/SecurityHelper.swift @@ -0,0 +1,152 @@ +/* + * OPEN-XCHANGE legal information + * + * All intellectual property rights in the Software are protected by + * international copyright laws. + * + * + * In some countries OX, OX Open-Xchange and open xchange + * as well as the corresponding Logos OX Open-Xchange and OX are registered + * trademarks of the OX Software GmbH group of companies. + * The use of the Logos is not covered by the Mozilla Public License 2.0 (MPL 2.0). + * Instead, you are allowed to use these Logos according to the terms and + * conditions of the Creative Commons License, Version 2.5, Attribution, + * Non-commercial, ShareAlike, and the interpretation of the term + * Non-commercial applicable to the aforementioned license is published + * on the web site https://www.open-xchange.com/terms-and-conditions/. + * + * Please make sure that third-party modules and libraries are used + * according to their respective licenses. + * + * Any modifications to this package must retain all copyright notices + * of the original copyright holder(s) for the original code used. + * + * After any such modifications, the original and derivative code shall remain + * under the copyright of the copyright holder(s) and/or original author(s) as stated here: + * https://www.open-xchange.com/legal/. The contributing author shall be + * given Attribution for the derivative code and a license granting use. + * + * Copyright (C) 2016-2020 OX Software GmbH + * Mail: info@open-xchange.com + * + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * 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 Mozilla Public License 2.0 + * for more details. + */ + +import Foundation +import Tink +import CryptoKit +import GMEllipticCurveCrypto + +class SecurityHelper { + + typealias PrivateKeyType = (privateKey: P256.KeyAgreement.PrivateKey, privateKeyData: Data) + typealias PublicKeyType = (publicKey: P256.KeyAgreement.PublicKey, publicKeyData: Data) + + var enryptedMessage: EncryptedPushMessage? + + init(message: [String: String]) throws { + do { + let data = try JSONSerialization.data(withJSONObject: message, options: .prettyPrinted) + self.enryptedMessage = try JSONDecoder().decode(EncryptedPushMessage.self, from: data) + + } catch { + throw SecurityHelperError.initFailed(error: error.localizedDescription) + } + } + + func getDecryptedMessage() throws -> String? { + do { + guard let enryptedMessage = enryptedMessage else { + return nil + } + + let privateKeyType = try privateKey(from: enryptedMessage.privateKeyBase64) + let publicKeyType = try publicKey(from: enryptedMessage.publicKeyBase64) + + guard let authSecretData = Data(base64Encoded: enryptedMessage.authBase64, options: .ignoreUnknownCharacters), + let authSecret = String(data: authSecretData, encoding: .utf8) else { + throw SecurityHelperError.authSecretGenerationFailed(error: "Could not generate Data or String from authBase64: \(enryptedMessage.authBase64)") + } + +// let keysetHandle = TINKKeysetHandle() +// let hybridDecrypt: TINKHybridDecrypt = TINKHybridDecryptFactory + + } catch { + throw SecurityHelperError.messageDecryptionFailed(error: error.localizedDescription) + } + + return nil + } + + func privateKey(from base64EncodedKey: String) throws -> PrivateKeyType { + do { + guard let data = Data(base64Encoded: base64EncodedKey, options: .ignoreUnknownCharacters) else { + throw SecurityHelperError.privateKeyGenerationFailed(error: "Could not generate Data from base64EncodedKey: \(base64EncodedKey)") + } + + var result: PrivateKeyType + result.privateKey = try P256.KeyAgreement.PrivateKey(rawRepresentation: data) + result.privateKeyData = data + + return result + + } catch { + throw SecurityHelperError.privateKeyGenerationFailed(error: error.localizedDescription) + } + } + + func publicKey(from base64EncodedKey: String) throws -> PublicKeyType { + do { + guard let data = Data(base64Encoded: base64EncodedKey, options: .ignoreUnknownCharacters) else { + throw SecurityHelperError.publicKeyGenerationFailed(error: "Could not generate Data from base64EncodedKey: \(base64EncodedKey)") + } + + var result: PublicKeyType + result.publicKey = try P256.KeyAgreement.PublicKey(rawRepresentation: data) + result.publicKeyData = data + + return result + + } catch { + throw SecurityHelperError.publicKeyGenerationFailed(error: error.localizedDescription) + } + } + +} + +extension String { + var base64Decoded: String? { + get { + if let data = Data(base64Encoded: self, options: .ignoreUnknownCharacters), + let decodedString = String(data: data, encoding: .utf8) { + return decodedString + } + return nil + } + } +} + +struct EncryptedPushMessage: Decodable { + let encryptedBase64Content: String + let privateKeyBase64: String + let publicKeyBase64: String + let authBase64: String +} + +enum SecurityHelperError: Error { + case initFailed(error: String) + case messageDecryptionFailed(error: String) + case authSecretGenerationFailed(error: String) + case privateKeyGenerationFailed(error: String) + case publicKeyGenerationFailed(error: String) + case decryptMessageFailed(error: String) +} + diff --git a/ios/OX Coi/Info.plist b/ios/OX Coi/Info.plist index bc72b884..73264ffc 100644 --- a/ios/OX Coi/Info.plist +++ b/ios/OX Coi/Info.plist @@ -96,5 +96,9 @@ UIViewControllerBasedStatusBarAppearance + BGTaskSchedulerPermittedIdentifiers + + com.transistorsoft.fetch + diff --git a/ios/Podfile b/ios/Podfile index 41a84e47..94a1c5fe 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -1,4 +1,4 @@ -platform :ios, '12.4' +platform :ios, '13.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' @@ -31,6 +31,14 @@ def parse_KV_file(file, separator='=') generated_key_values end +target 'NotificationService' do + use_frameworks! + use_modular_headers! + + pod 'Tink' + pod "GMEllipticCurveCrypto" +end + target 'Runner' do use_frameworks! use_modular_headers! diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index d8bce17b..c020c8d3 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -16,9 +16,14 @@ 64B6D5B22449CC38001C73DE /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64B6D5B12449CC38001C73DE /* ShareViewController.swift */; }; 64B6D5B52449CC38001C73DE /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 64B6D5B32449CC38001C73DE /* MainInterface.storyboard */; }; 64B6D5B92449CC38001C73DE /* CoiSharing.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 64B6D5AF2449CC38001C73DE /* CoiSharing.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 7E655F52459F699C423A9A47 /* Pods_NotificationService.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1BD1DAE490FC79A5BFE85464 /* Pods_NotificationService.framework */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + FD0FBDFE24729708002B5EB5 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD0FBDFD24729708002B5EB5 /* NotificationService.swift */; }; + FD0FBE0224729708002B5EB5 /* NotificationService.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = FD0FBDFB24729708002B5EB5 /* NotificationService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + FD0FBE0D2472B4AF002B5EB5 /* AppDelegate+Security.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD0FBE0C2472B4AF002B5EB5 /* AppDelegate+Security.swift */; }; + FD0FBE122472B619002B5EB5 /* MethodChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD0FBE112472B619002B5EB5 /* MethodChannel.swift */; }; FD139C3D23FFF55700E891D5 /* UserDefaults+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD139C3C23FFF55700E891D5 /* UserDefaults+Extension.swift */; }; FD2D3A5523E09F900070F327 /* UIApplication+Firebase.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2D3A5423E09F900070F327 /* UIApplication+Firebase.swift */; }; FD2D3A5723E0B1360070F327 /* GoogleService-Info-Production.plist in Resources */ = {isa = PBXBuildFile; fileRef = FD2D3A5623E0B1360070F327 /* GoogleService-Info-Production.plist */; }; @@ -26,10 +31,13 @@ FD2FD6F323E099F900B39EA2 /* GoogleService-Info-Development.plist in Resources */ = {isa = PBXBuildFile; fileRef = FD2FD6F223E099F900B39EA2 /* GoogleService-Info-Development.plist */; }; FD51737323266F05007458CC /* .swiftlint.yml in Resources */ = {isa = PBXBuildFile; fileRef = FD51737223266F04007458CC /* .swiftlint.yml */; }; FD51737623267295007458CC /* UIApplication+Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD51737523267295007458CC /* UIApplication+Logging.swift */; }; + FD5485E3247EB2810012FB26 /* MethodChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD0FBE112472B619002B5EB5 /* MethodChannel.swift */; }; + FD5485E7247EB9610012FB26 /* String+Base64.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5485E6247EB9610012FB26 /* String+Base64.swift */; }; FD5F8B06236AEA7500E05845 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = FD5F8B08236AEA7500E05845 /* InfoPlist.strings */; }; + FD64C8802486D29D0047B348 /* SecurityHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD64C87F2486D29D0047B348 /* SecurityHelper.swift */; }; + FD64C8812486D29D0047B348 /* SecurityHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD64C87F2486D29D0047B348 /* SecurityHelper.swift */; }; FD961FB223D73ADC00AD0D90 /* AppDelegate+Sharing.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD961FB123D73ADC00AD0D90 /* AppDelegate+Sharing.swift */; }; FDC49D33246D4E1F00FDF561 /* Sharing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6490EEC7246BCAC500B5E428 /* Sharing.swift */; }; - FDF8F9B523D7421D0009B368 /* Method.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A955E3F238677DF00DF7F3F /* Method.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -40,6 +48,13 @@ remoteGlobalIDString = 64B6D5AE2449CC38001C73DE; remoteInfo = CoiSharing; }; + FD0FBE0024729708002B5EB5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = FD0FBDFA24729708002B5EB5; + remoteInfo = NotificationService; + }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -50,6 +65,7 @@ dstSubfolderSpec = 13; files = ( 64B6D5B92449CC38001C73DE /* CoiSharing.appex in Embed App Extensions */, + FD0FBE0224729708002B5EB5 /* NotificationService.appex in Embed App Extensions */, ); name = "Embed App Extensions"; runOnlyForDeploymentPostprocessing = 0; @@ -68,12 +84,12 @@ /* Begin PBXFileReference section */ 125F4957EFC6D57D2B18CDF8 /* Pods-Runner.release-development.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release-development.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release-development.xcconfig"; sourceTree = ""; }; + 1BD1DAE490FC79A5BFE85464 /* Pods_NotificationService.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_NotificationService.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 2421A2112375966400BBE6C8 /* Release-stable.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = "Release-stable.xcconfig"; path = "Flutter/Release-stable.xcconfig"; sourceTree = ""; }; 2421A2132375A7CE00BBE6C8 /* Debug-development.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = "Debug-development.xcconfig"; path = "Flutter/Debug-development.xcconfig"; sourceTree = ""; }; 242D004E2370336900491A66 /* exportOptionsdevelopment.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = exportOptionsdevelopment.plist; path = ../exportOptionsdevelopment.plist; sourceTree = ""; }; 24DD530523214B20005ED412 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; 2A0DB6B723992A9800257C03 /* exportOptionsstable.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = exportOptionsstable.plist; sourceTree = SOURCE_ROOT; }; - 2A955E3F238677DF00DF7F3F /* Method.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Method.swift; path = "OX Coi/Helper/Method.swift"; sourceTree = SOURCE_ROOT; }; 2AB5BDA12396A33B005FDB10 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GeneratedPluginRegistrant.h; path = ../../Runner/GeneratedPluginRegistrant.h; sourceTree = ""; }; 2AB5BDA22396A33B005FDB10 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GeneratedPluginRegistrant.m; path = ../../Runner/GeneratedPluginRegistrant.m; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; @@ -115,10 +131,18 @@ 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 9810C5C2A61932B256D32535 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + A12B80A93EA4641880AFD3D0 /* Pods-NotificationService.release-stable.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificationService.release-stable.xcconfig"; path = "Target Support Files/Pods-NotificationService/Pods-NotificationService.release-stable.xcconfig"; sourceTree = ""; }; AE7959C2D0CF25C3DA63C8A5 /* Pods-Runner.development.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.development.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.development.xcconfig"; sourceTree = ""; }; + B7774FABB905B849FEB3BEF5 /* Pods-NotificationService.release-development.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificationService.release-development.xcconfig"; path = "Target Support Files/Pods-NotificationService/Pods-NotificationService.release-development.xcconfig"; sourceTree = ""; }; + DD35EBF3BB1F487852F715F9 /* Pods-NotificationService.debug-development.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificationService.debug-development.xcconfig"; path = "Target Support Files/Pods-NotificationService/Pods-NotificationService.debug-development.xcconfig"; sourceTree = ""; }; DD93F73C7246AEDD7418AB12 /* Pods-Runner.debug-stable.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug-stable.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug-stable.xcconfig"; sourceTree = ""; }; F41DB3006AA42C6AFAF21669 /* Pods-Runner.release-stable.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release-stable.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release-stable.xcconfig"; sourceTree = ""; }; F90D6A58DFAD88989F0B373D /* Pods-Runner.debug-development.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug-development.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug-development.xcconfig"; sourceTree = ""; }; + FD0FBDFB24729708002B5EB5 /* NotificationService.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = NotificationService.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + FD0FBDFD24729708002B5EB5 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = ""; }; + FD0FBDFF24729708002B5EB5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + FD0FBE0C2472B4AF002B5EB5 /* AppDelegate+Security.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+Security.swift"; sourceTree = ""; }; + FD0FBE112472B619002B5EB5 /* MethodChannel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MethodChannel.swift; sourceTree = ""; }; FD139C3C23FFF55700E891D5 /* UserDefaults+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserDefaults+Extension.swift"; sourceTree = ""; }; FD2D3A5423E09F900070F327 /* UIApplication+Firebase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIApplication+Firebase.swift"; sourceTree = ""; }; FD2D3A5623E0B1360070F327 /* GoogleService-Info-Production.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info-Production.plist"; sourceTree = ""; }; @@ -127,11 +151,13 @@ FD2FD6F223E099F900B39EA2 /* GoogleService-Info-Development.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info-Development.plist"; sourceTree = ""; }; FD51737223266F04007458CC /* .swiftlint.yml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = .swiftlint.yml; sourceTree = ""; }; FD51737523267295007458CC /* UIApplication+Logging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIApplication+Logging.swift"; sourceTree = ""; }; + FD5485E6247EB9610012FB26 /* String+Base64.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Base64.swift"; sourceTree = ""; }; FD5F8B07236AEA7500E05845 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; FD5F8B10236AF0EF00E05845 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/InfoPlist.strings; sourceTree = ""; }; FD5F8B11236AF0F200E05845 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/InfoPlist.strings; sourceTree = ""; }; FD5F8B12236AF0F400E05845 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/InfoPlist.strings; sourceTree = ""; }; FD5F8B13236AF0F600E05845 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoPlist.strings; sourceTree = ""; }; + FD64C87F2486D29D0047B348 /* SecurityHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecurityHelper.swift; sourceTree = ""; }; FD8644DB23E097F100A25158 /* Release-development.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = "Release-development.xcconfig"; path = "Flutter/Release-development.xcconfig"; sourceTree = ""; }; FD961FB123D73ADC00AD0D90 /* AppDelegate+Sharing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+Sharing.swift"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -152,6 +178,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + FD0FBDF824729708002B5EB5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 7E655F52459F699C423A9A47 /* Pods_NotificationService.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -166,6 +200,9 @@ F41DB3006AA42C6AFAF21669 /* Pods-Runner.release-stable.xcconfig */, 125F4957EFC6D57D2B18CDF8 /* Pods-Runner.release-development.xcconfig */, DD93F73C7246AEDD7418AB12 /* Pods-Runner.debug-stable.xcconfig */, + DD35EBF3BB1F487852F715F9 /* Pods-NotificationService.debug-development.xcconfig */, + B7774FABB905B849FEB3BEF5 /* Pods-NotificationService.release-development.xcconfig */, + A12B80A93EA4641880AFD3D0 /* Pods-NotificationService.release-stable.xcconfig */, ); path = Pods; sourceTree = ""; @@ -213,6 +250,7 @@ 97C146F01CF9000F007C117D /* OX Coi */, 9740EEB11CF90186004384FC /* Flutter */, 64B6D5B02449CC38001C73DE /* CoiSharing */, + FD0FBDFC24729708002B5EB5 /* NotificationService */, 97C146EF1CF9000F007C117D /* Products */, 5BC001BF3473EA87F51AA764 /* Pods */, A3B7A0ADF7909F744D5E7588 /* Frameworks */, @@ -224,6 +262,7 @@ children = ( 97C146EE1CF9000F007C117D /* Runner.app */, 64B6D5AF2449CC38001C73DE /* CoiSharing.appex */, + FD0FBDFB24729708002B5EB5 /* NotificationService.appex */, ); name = Products; sourceTree = ""; @@ -257,10 +296,20 @@ isa = PBXGroup; children = ( 899B7188477F2A02FA10829E /* Pods_Runner.framework */, + 1BD1DAE490FC79A5BFE85464 /* Pods_NotificationService.framework */, ); name = Frameworks; sourceTree = ""; }; + FD0FBDFC24729708002B5EB5 /* NotificationService */ = { + isa = PBXGroup; + children = ( + FD0FBDFD24729708002B5EB5 /* NotificationService.swift */, + FD0FBDFF24729708002B5EB5 /* Info.plist */, + ); + path = NotificationService; + sourceTree = ""; + }; FD2E2E25231FFF8A00ADF5C5 /* Application */ = { isa = PBXGroup; children = ( @@ -294,6 +343,7 @@ FD3A4DC123D758A40036F2BE /* Foundation */ = { isa = PBXGroup; children = ( + FD5485E6247EB9610012FB26 /* String+Base64.swift */, FD139C3C23FFF55700E891D5 /* UserDefaults+Extension.swift */, ); path = Foundation; @@ -311,6 +361,7 @@ FD517377232672C9007458CC /* UIKit */ = { isa = PBXGroup; children = ( + FD0FBE0C2472B4AF002B5EB5 /* AppDelegate+Security.swift */, FD961FB123D73ADC00AD0D90 /* AppDelegate+Sharing.swift */, FD2D3A5423E09F900070F327 /* UIApplication+Firebase.swift */, FD51737523267295007458CC /* UIApplication+Logging.swift */, @@ -321,7 +372,8 @@ FD51B1B423200F4D006F665B /* Helper */ = { isa = PBXGroup; children = ( - 2A955E3F238677DF00DF7F3F /* Method.swift */, + FD0FBE112472B619002B5EB5 /* MethodChannel.swift */, + FD64C87F2486D29D0047B348 /* SecurityHelper.swift */, ); path = Helper; sourceTree = ""; @@ -366,12 +418,31 @@ ); dependencies = ( 64B6D5B82449CC38001C73DE /* PBXTargetDependency */, + FD0FBE0124729708002B5EB5 /* PBXTargetDependency */, ); name = Runner; productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; }; + FD0FBDFA24729708002B5EB5 /* NotificationService */ = { + isa = PBXNativeTarget; + buildConfigurationList = FD0FBE0624729708002B5EB5 /* Build configuration list for PBXNativeTarget "NotificationService" */; + buildPhases = ( + 77695565A3D680FC5EDD5B81 /* [CP] Check Pods Manifest.lock */, + FD0FBDF724729708002B5EB5 /* Sources */, + FD0FBDF824729708002B5EB5 /* Frameworks */, + FD0FBDF924729708002B5EB5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = NotificationService; + productName = NotificationService; + productReference = FD0FBDFB24729708002B5EB5 /* NotificationService.appex */; + productType = "com.apple.product-type.app-extension"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -394,6 +465,9 @@ }; }; }; + FD0FBDFA24729708002B5EB5 = { + CreatedOnToolsVersion = 11.4.1; + }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; @@ -433,6 +507,7 @@ targets = ( 97C146ED1CF9000F007C117D /* Runner */, 64B6D5AE2449CC38001C73DE /* CoiSharing */, + FD0FBDFA24729708002B5EB5 /* NotificationService */, ); }; /* End PBXProject section */ @@ -463,6 +538,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + FD0FBDF924729708002B5EB5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ @@ -517,6 +599,28 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed\n/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin\n"; }; + 77695565A3D680FC5EDD5B81 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-NotificationService-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -577,6 +681,7 @@ buildActionMask = 2147483647; files = ( 6490EEC8246BCAC500B5E428 /* Sharing.swift in Sources */, + FD5485E3247EB2810012FB26 /* MethodChannel.swift in Sources */, 64B6D5B22449CC38001C73DE /* ShareViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -585,17 +690,29 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - FDF8F9B523D7421D0009B368 /* Method.swift in Sources */, FD961FB223D73ADC00AD0D90 /* AppDelegate+Sharing.swift in Sources */, FD2E2E22231FFEBB00ADF5C5 /* AppDelegate.swift in Sources */, FD51737623267295007458CC /* UIApplication+Logging.swift in Sources */, + FD0FBE122472B619002B5EB5 /* MethodChannel.swift in Sources */, FDC49D33246D4E1F00FDF561 /* Sharing.swift in Sources */, + FD64C8802486D29D0047B348 /* SecurityHelper.swift in Sources */, 2AB5BDA32396A33B005FDB10 /* GeneratedPluginRegistrant.m in Sources */, + FD0FBE0D2472B4AF002B5EB5 /* AppDelegate+Security.swift in Sources */, + FD5485E7247EB9610012FB26 /* String+Base64.swift in Sources */, FD2D3A5523E09F900070F327 /* UIApplication+Firebase.swift in Sources */, FD139C3D23FFF55700E891D5 /* UserDefaults+Extension.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; + FD0FBDF724729708002B5EB5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + FD0FBDFE24729708002B5EB5 /* NotificationService.swift in Sources */, + FD64C8812486D29D0047B348 /* SecurityHelper.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -604,6 +721,11 @@ target = 64B6D5AE2449CC38001C73DE /* CoiSharing */; targetProxy = 64B6D5B72449CC38001C73DE /* PBXContainerItemProxy */; }; + FD0FBE0124729708002B5EB5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = FD0FBDFA24729708002B5EB5 /* NotificationService */; + targetProxy = FD0FBE0024729708002B5EB5 /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ @@ -709,7 +831,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.4; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -723,7 +845,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 2421A2132375A7CE00BBE6C8 /* Debug-development.xcconfig */; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)"; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; BUNDLE_DISPLAY_NAME = "${PRODUCT_BUNDLE_DISPLAY_NAME}"; CLANG_ENABLE_MODULES = YES; @@ -741,7 +863,7 @@ ); GCC_OPTIMIZATION_LEVEL = 0; INFOPLIST_FILE = "$(SRCROOT)/OX Coi/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 12.4; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -944,7 +1066,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.4; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; @@ -957,7 +1079,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 2421A2112375966400BBE6C8 /* Release-stable.xcconfig */; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)"; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; BUNDLE_DISPLAY_NAME = "${PRODUCT_BUNDLE_DISPLAY_NAME}"; CLANG_ENABLE_MODULES = YES; @@ -974,7 +1096,7 @@ "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = "$(SRCROOT)/OX Coi/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 12.4; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1177,7 +1299,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.4; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; @@ -1190,7 +1312,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = FD8644DB23E097F100A25158 /* Release-development.xcconfig */; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)"; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; BUNDLE_DISPLAY_NAME = "${PRODUCT_BUNDLE_DISPLAY_NAME}"; CLANG_ENABLE_MODULES = YES; @@ -1208,7 +1330,7 @@ "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = "$(SRCROOT)/OX Coi/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 12.4; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1370,6 +1492,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + APPLICATION_EXTENSION_API_ONLY = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -1422,7 +1545,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; INFOPLIST_FILE = CoiSharing/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 13.4; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1447,6 +1570,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + APPLICATION_EXTENSION_API_ONLY = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -1494,7 +1618,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; INFOPLIST_FILE = CoiSharing/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 13.4; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1519,6 +1643,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + APPLICATION_EXTENSION_API_ONLY = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -1564,7 +1689,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; INFOPLIST_FILE = CoiSharing/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 13.4; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1585,6 +1710,226 @@ }; name = "Release-stable"; }; + FD0FBE0324729708002B5EB5 /* Debug-development */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = DD35EBF3BB1F487852F715F9 /* Pods-NotificationService.debug-development.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + APPLICATION_EXTENSION_API_ONLY = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = 528HQ38877; + ENABLE_BITCODE = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INFOPLIST_FILE = NotificationService/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.openxchange.oxcoi.dev.NotificationService; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = "Debug-development"; + }; + FD0FBE0424729708002B5EB5 /* Release-development */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = B7774FABB905B849FEB3BEF5 /* Pods-NotificationService.release-development.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + APPLICATION_EXTENSION_API_ONLY = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = 528HQ38877; + ENABLE_BITCODE = NO; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INFOPLIST_FILE = NotificationService/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.openxchange.oxcoi.dev.NotificationService; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = "Release-development"; + }; + FD0FBE0524729708002B5EB5 /* Release-stable */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A12B80A93EA4641880AFD3D0 /* Pods-NotificationService.release-stable.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + APPLICATION_EXTENSION_API_ONLY = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = 267G7KX6FQ; + ENABLE_BITCODE = NO; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INFOPLIST_FILE = NotificationService/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.openxchange.oxcoi.NotificationService; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = "Release-stable"; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -1618,6 +1963,16 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = "Release-stable"; }; + FD0FBE0624729708002B5EB5 /* Build configuration list for PBXNativeTarget "NotificationService" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + FD0FBE0324729708002B5EB5 /* Debug-development */, + FD0FBE0424729708002B5EB5 /* Release-development */, + FD0FBE0524729708002B5EB5 /* Release-stable */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = "Release-stable"; + }; /* End XCConfigurationList section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/CoiSharing.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/CoiSharing.xcscheme new file mode 100644 index 00000000..15e259cb --- /dev/null +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/CoiSharing.xcscheme @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/NotificationService.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/NotificationService.xcscheme new file mode 100644 index 00000000..76a7524f --- /dev/null +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/NotificationService.xcscheme @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 78193027..aa09703f 100644 --- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ { if (state is MainStateSuccess) { _navigation.popUntilRoot(context); if (state.configured && !state.needsOnboarding && !state.hasAuthenticationError && state.notificationsActivated) { - NotificationManager().setup(context); + DisplayNotificationManager().setupAsync(context); } } }, diff --git a/lib/src/background_refresh/background_refresh_manager.dart b/lib/src/background_refresh/background_refresh_manager.dart index 196b2fd0..c27cd44f 100644 --- a/lib/src/background_refresh/background_refresh_manager.dart +++ b/lib/src/background_refresh/background_refresh_manager.dart @@ -43,30 +43,38 @@ import 'package:background_fetch/background_fetch.dart'; import 'package:delta_chat_core/delta_chat_core.dart'; import 'package:logging/logging.dart'; +import 'package:ox_coi/src/log/log_manager.dart'; import 'package:ox_coi/src/notifications/local_notification_manager.dart'; import 'package:ox_coi/src/utils/constants.dart'; -void backgroundHeadlessTask() async { +const loggerName = "background_refresh_manager"; + +void backgroundHeadlessTask(String taskId) async { + final logManager = LogManager(); + await logManager.setup(logToFile: true, logLevel: Level.INFO); + final logger = Logger(loggerName); + logger.info("Callback (background) triggered"); var core = DeltaChatCore(); - var init = await core.init(dbName); - if (init) { - await core.start(); + var isSetup = await core.setupAsync(dbName: dbName, minimalSetup: true); + if (isSetup) { + logger.info("Callback (background) checking for new messages"); await getMessages(); - await core.stop(); + await core.tearDownAsync(); } - BackgroundFetch.finish(); + logger.info("Callback (background) finishing"); + BackgroundFetch.finish(taskId); } Future getMessages() async { - var context = Context(); + final localNotificationManager = LocalNotificationManager.newInstance(); + localNotificationManager.setup(registerListeners: false); + final context = Context(); await context.interruptIdleForIncomingMessages(); - var localNotificationManager = LocalNotificationManager(); - localNotificationManager.setup(); await localNotificationManager.triggerNotificationAsync(); } class BackgroundRefreshManager { - final Logger _logger = Logger("background_refresh_manager"); + final _logger = Logger(loggerName); static BackgroundRefreshManager _instance; @@ -77,39 +85,47 @@ class BackgroundRefreshManager { BackgroundRefreshManager._internal(); setupAndStart() { - BackgroundFetch.registerHeadlessTask(backgroundHeadlessTask); + BackgroundFetch.registerHeadlessTask(backgroundHeadlessTask).then((value) { + _logger.info("Register headless task"); + }); BackgroundFetch.configure( - BackgroundFetchConfig( - minimumFetchInterval: 15, - stopOnTerminate: false, - enableHeadless: true, - startOnBoot: true, - ), - _callback); - _running = true; - _logger.info("Configured and started background fetch"); - } - - Future _callback() async { - await getMessages(); - BackgroundFetch.finish(); + BackgroundFetchConfig( + minimumFetchInterval: 15, + stopOnTerminate: false, + enableHeadless: true, + startOnBoot: true, + requiredNetworkType: NetworkType.ANY, + ), + (String taskId) async { + _logger.info("Callback (foreground) triggered, no actions required"); + BackgroundFetch.finish(taskId); + }, + ).then((value) { + _logger.info("Configured and started background fetch"); + _running = true; + }); } void start() async { if (_running) { return; } - await BackgroundFetch.start(); - _logger.info("Started background fetch"); - _running = true; + await BackgroundFetch.start().then((int status) { + _logger.info("Start success: $status"); + _running = true; + }).catchError((e) { + print('Start FAILURE: $e'); + _running = false; + }); } void stop() { if (!_running) { return; } - BackgroundFetch.stop(); - _logger.info("Stopped background fetch"); + BackgroundFetch.stop().then((int status) { + _logger.info('Stop success: $status'); + }); _running = false; } } diff --git a/lib/src/chat/chat.dart b/lib/src/chat/chat.dart index 7d447981..3b5dc997 100644 --- a/lib/src/chat/chat.dart +++ b/lib/src/chat/chat.dart @@ -74,7 +74,7 @@ import 'package:ox_coi/src/message_list/message_list_bloc.dart'; import 'package:ox_coi/src/message_list/message_list_event_state.dart'; import 'package:ox_coi/src/navigation/navigatable.dart'; import 'package:ox_coi/src/navigation/navigation.dart'; -import 'package:ox_coi/src/share/shared_data.dart'; +import 'package:ox_coi/src/share/incoming_shared_data.dart'; import 'package:ox_coi/src/ui/dimensions.dart'; import 'package:ox_coi/src/utils/image.dart'; import 'package:ox_coi/src/utils/keyMapping.dart'; @@ -95,7 +95,7 @@ class Chat extends StatefulWidget { final String newMessage; final String newPath; final int newFileType; - final SharedData sharedData; + final IncomingSharedData sharedData; final bool headlessStart; Chat({@required this.chatId, this.messageId, this.newMessage, this.newPath, this.newFileType, this.sharedData, this.headlessStart = false}); diff --git a/lib/src/chat/chat_bloc.dart b/lib/src/chat/chat_bloc.dart index d1dbe73d..63d6d8d8 100644 --- a/lib/src/chat/chat_bloc.dart +++ b/lib/src/chat/chat_bloc.dart @@ -52,7 +52,7 @@ import 'package:ox_coi/src/data/repository_stream_handler.dart'; import 'package:ox_coi/src/extensions/color_apis.dart'; import 'package:ox_coi/src/l10n/l.dart'; import 'package:ox_coi/src/l10n/l10n.dart'; -import 'package:ox_coi/src/notifications/notification_manager.dart'; +import 'package:ox_coi/src/notifications/display_notification_manager.dart'; class ChatBloc extends Bloc { final _chatRepository = RepositoryManager.get(RepositoryType.chat); @@ -207,7 +207,7 @@ class ChatBloc extends Bloc { } void _removeNotifications() { - final notificationManager = NotificationManager(); - notificationManager.cancelNotification(_chatId); + final notificationManager = DisplayNotificationManager(); + notificationManager.cancelNotificationAsync(_chatId); } } diff --git a/lib/src/chat/chat_profile_single.dart b/lib/src/chat/chat_profile_single.dart index ad4cd9cb..2c96f2e7 100644 --- a/lib/src/chat/chat_profile_single.dart +++ b/lib/src/chat/chat_profile_single.dart @@ -54,7 +54,6 @@ import 'package:ox_coi/src/data/contact_repository.dart'; import 'package:ox_coi/src/extensions/color_apis.dart'; import 'package:ox_coi/src/l10n/l.dart'; import 'package:ox_coi/src/l10n/l10n.dart'; -import 'package:ox_coi/src/message_list/message_list_bloc.dart'; import 'package:ox_coi/src/message_list/message_list_flagged.dart'; import 'package:ox_coi/src/navigation/navigation.dart'; import 'package:ox_coi/src/utils/keyMapping.dart'; diff --git a/lib/src/contact/contact_change.dart b/lib/src/contact/contact_change.dart index 8e21d55b..9d7270c7 100644 --- a/lib/src/contact/contact_change.dart +++ b/lib/src/contact/contact_change.dart @@ -40,7 +40,7 @@ * for more details. */ -import 'package:delta_chat_core/delta_chat_core.dart' as Core; +import 'package:delta_chat_core/delta_chat_core.dart' as dcc; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -102,7 +102,7 @@ class _ContactChangeState extends State { ContactItemBloc _contactItemBloc = ContactItemBloc(); - Repository chatRepository; + Repository chatRepository; @override void initState() { @@ -162,7 +162,7 @@ class _ContactChangeState extends State { _navigation.pop(context); } else { if (state.contactStateData.id != null) { - Core.Context coreContext = Core.Context(); + dcc.Context coreContext = dcc.Context(); var chatId = await coreContext.createChatByContactId(state.contactStateData.id); chatRepository.putIfAbsent(id: chatId); _navigation.pushAndRemoveUntil( diff --git a/lib/src/customer/customer_delegate.dart b/lib/src/customer/customer_delegate.dart index a55f3505..aec6847e 100644 --- a/lib/src/customer/customer_delegate.dart +++ b/lib/src/customer/customer_delegate.dart @@ -85,12 +85,6 @@ class CustomerDelegate with DynamicScreenCustomerDelegate { final changeNotifier = CustomerDelegateChangeNotifier(); final _config = Config(); - MainBloc _mainBloc; - - void dispose() { - _mainBloc.close(); - } - @override Future buttonPressedAsync({BuildContext context, data}) async { // TODO: NEEDS TO BE DISCUSSED! @@ -192,8 +186,7 @@ class CustomerDelegate with DynamicScreenCustomerDelegate { Customer.needsOnboarding = false; Navigation().popUntilRoot(context); - _mainBloc = BlocProvider.of(context); - _mainBloc.add(AppLoaded()); + BlocProvider.of(context).add(AppLoaded()); } } diff --git a/lib/src/data/push.dart b/lib/src/data/push.dart new file mode 100644 index 00000000..05d60aef --- /dev/null +++ b/lib/src/data/push.dart @@ -0,0 +1,74 @@ +/* + * OPEN-XCHANGE legal information + * + * All intellectual property rights in the Software are protected by + * international copyright laws. + * + * + * In some countries OX, OX Open-Xchange and open xchange + * as well as the corresponding Logos OX Open-Xchange and OX are registered + * trademarks of the OX Software GmbH group of companies. + * The use of the Logos is not covered by the Mozilla Public License 2.0 (MPL 2.0). + * Instead, you are allowed to use these Logos according to the terms and + * conditions of the Creative Commons License, Version 2.5, Attribution, + * Non-commercial, ShareAlike, and the interpretation of the term + * Non-commercial applicable to the aforementioned license is published + * on the web site https://www.open-xchange.com/terms-and-conditions/. + * + * Please make sure that third-party modules and libraries are used + * according to their respective licenses. + * + * Any modifications to this package must retain all copyright notices + * of the original copyright holder(s) for the original code used. + * + * After any such modifications, the original and derivative code shall remain + * under the copyright of the copyright holder(s) and/or original author(s) as stated here: + * https://www.open-xchange.com/legal/. The contributing author shall be + * given Attribution for the derivative code and a license granting use. + * + * Copyright (C) 2016-2020 OX Software GmbH + * Mail: info@open-xchange.com + * + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * 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 Mozilla Public License 2.0 + * for more details. + */ + +import 'dart:io'; + +class Push { + String content; + bool valid; + + Push.fromJson(Map json) { + try { + content = Platform.isIOS ? json['content'] : json['data']['content']; + valid = content != null && content.isNotEmpty; + } catch (error) { + throw NotificationDataException(error, json: json); + } + } + + @override + String toString() { + return content; + } +} + +class NotificationDataException implements Exception { + final Map json; + final String error; + + NotificationDataException(this.error, {this.json}); + + @override + String toString() { + return "Invalid notification data: '$error'\nJSON: ${json.toString()}"; + } +} diff --git a/lib/src/extensions/numbers_apis.dart b/lib/src/extensions/numbers_apis.dart index fc4b922c..e1e019dc 100644 --- a/lib/src/extensions/numbers_apis.dart +++ b/lib/src/extensions/numbers_apis.dart @@ -6,6 +6,7 @@ const _kilobyte = 1024; const _megabyte = 1024 * _kilobyte; extension Convert on int { + String byteToPrintableSize() { String unit; double result; diff --git a/lib/src/gallery/gallery_bloc.dart b/lib/src/gallery/gallery_bloc.dart index 5dcbdde5..76b36c52 100644 --- a/lib/src/gallery/gallery_bloc.dart +++ b/lib/src/gallery/gallery_bloc.dart @@ -40,8 +40,6 @@ * for more details. */ -import 'dart:io'; - import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:ox_coi/src/gallery/gallery_event_state.dart'; import 'package:video_player/video_player.dart'; diff --git a/lib/src/invite/invite_bloc.dart b/lib/src/invite/invite_bloc.dart index b2e4c665..81f8f5d5 100644 --- a/lib/src/invite/invite_bloc.dart +++ b/lib/src/invite/invite_bloc.dart @@ -46,7 +46,6 @@ import 'dart:typed_data'; import 'package:bloc/bloc.dart'; import 'package:delta_chat_core/delta_chat_core.dart'; -import 'package:flutter/services.dart'; import 'package:http/http.dart'; import 'package:mime/mime.dart'; import 'package:ox_coi/src/data/config.dart'; @@ -58,7 +57,8 @@ import 'package:ox_coi/src/extensions/string_apis.dart'; import 'package:ox_coi/src/invite/invite_service.dart'; import 'package:ox_coi/src/l10n/l.dart'; import 'package:ox_coi/src/l10n/l10n.dart'; -import 'package:ox_coi/src/share/shared_data.dart'; +import 'package:ox_coi/src/platform/method_channel.dart'; +import 'package:ox_coi/src/share/outgoing_shared_data.dart'; import 'package:ox_coi/src/utils/http.dart'; import 'package:ox_coi/src/utils/image.dart'; import 'package:path_provider/path_provider.dart'; @@ -68,7 +68,6 @@ import 'invite_event_state.dart'; class InviteBloc extends Bloc { final Repository _contactRepository = RepositoryManager.get(RepositoryType.contact); final Repository _chatRepository = RepositoryManager.get(RepositoryType.chat); - static const platform = const MethodChannel(SharedData.sharingChannelName); InviteService inviteService = InviteService(); @override @@ -92,16 +91,14 @@ class InviteBloc extends Bloc { Stream createInviteUrl(String message) async* { InviteServiceRequest requestInviteService = await _createInviteServiceRequest(message ?? ""); var response = await inviteService.createInviteUrl(requestInviteService); - bool valid = validateHttpResponse(response); + bool valid = isHttpResponseValid(response); if (valid) { InviteServiceResponse responseInviteService = _getInviteResponse(response); - Map argsMap = { - 'title': '', - 'path': '', - 'mimeType': 'text/*', - 'text': '${responseInviteService.endpoint} \n ${L10n.get(L.inviteShareText)}' - }; - sendSharedData(argsMap); + final shareData = OutgoingSharedData( + mimeType: 'text/*', + text: '${responseInviteService.endpoint} \n ${L10n.get(L.inviteShareText)}', + ); + sendSharedData(shareData.toMap()); yield InviteStateSuccess(); } else { yield InviteStateFailure(errorMessage: response.reasonPhrase); @@ -117,7 +114,7 @@ class InviteBloc extends Bloc { String id = sharedLink.substring(startIndex); if (id.isNotEmpty) { Response response = await inviteService.getInvite(id); - bool valid = validateHttpResponse(response); + bool valid = isHttpResponseValid(response); if (valid) { InviteServiceResponse responseInviteService = _getInviteResponse(response); String imageString = responseInviteService.sender.image; @@ -204,7 +201,8 @@ class InviteBloc extends Bloc { return inviteResponse; } - Future _getInitialLink() async => await platform.invokeMethod('getInitialLink'); + Future _getInitialLink() async => await SharingChannel.instance.invokeMethod(SharingChannel.kMethodGetInitialLink); - void sendSharedData(Map argsMap) async => await platform.invokeMethod('sendSharedData', argsMap); + Future sendSharedData(Map shareData) async => + await SharingChannel.instance.invokeMethod(SharingChannel.kMethodSendSharedData, shareData); } diff --git a/lib/src/invite/invite_service.dart b/lib/src/invite/invite_service.dart index 83037df1..7edc09bd 100644 --- a/lib/src/invite/invite_service.dart +++ b/lib/src/invite/invite_service.dart @@ -54,7 +54,7 @@ import 'package:ox_coi/src/utils/http.dart'; class InviteService { static InviteService _instance; - var _logger = Logger("invite_service"); + final _logger = Logger("invite_service"); var headers = {"Content-type": "application/json"}; factory InviteService() => _instance ??= InviteService._internal(); diff --git a/lib/src/l10n/l.dart b/lib/src/l10n/l.dart index f26bc547..49f35603 100644 --- a/lib/src/l10n/l.dart +++ b/lib/src/l10n/l.dart @@ -454,6 +454,9 @@ class L { "Here you can choose your favorite theme. If you choose '%s', the theme may change automatically. This depends on whether you have selected 'Automatic' in the system preferences or not."); static final settingsAppearanceSystemThemeDescription = translationKey("Current System theme is: %s"); + static final notificationChannelTitle = translationKey("Message notifications"); + static final notificationChannelDescription = translationKey("Notifications for incoming messages"); + static final dynamicScreenSkipButtonTitle = L.translationKey("Skip"); static final dynamicScreenBackButtonTitle = L.translationKey("Back"); static final dynamicScreenNextButtonTitle = L.translationKey("Next"); diff --git a/lib/src/log/log_bloc_delegate.dart b/lib/src/log/log_bloc_delegate.dart index c69c143c..50eabed9 100644 --- a/lib/src/log/log_bloc_delegate.dart +++ b/lib/src/log/log_bloc_delegate.dart @@ -48,12 +48,12 @@ class LogBlocDelegate implements BlocDelegate { @override void onEvent(Bloc bloc, Object event) { - _logger.info(event.toString()); + _logger.info('Event { bloc: ${bloc.runtimeType}, event: ${event.toString()} }'); } @override void onError(Bloc bloc, Object error, StackTrace stacktrace) { - _logger.warning("Error: $error (Stacktrace: $stacktrace)"); + _logger.warning('Error { bloc: ${bloc.runtimeType}, error: $error, stacktrace: $stacktrace }'); } @override diff --git a/lib/src/log/log_manager.dart b/lib/src/log/log_manager.dart index 70584060..de64ea40 100644 --- a/lib/src/log/log_manager.dart +++ b/lib/src/log/log_manager.dart @@ -58,15 +58,18 @@ import 'package:synchronized/extension.dart'; class LogManager { static const _coreLoggerName = "dcc"; static const _logManagerLoggerName = "logManager"; - static const _maxLogFileCount = 10; + static const _maxLogFileCount = 100; static const _logFolder = "logs"; static LogManager _instance; final _coreLoggerSubject = PublishSubject(); final _core = DeltaChatCore(); + bool _isLogging = false; File _logFile; + bool get isLogging => _isLogging; + factory LogManager() { _instance ??= LogManager._internal(); return _instance; @@ -77,6 +80,10 @@ class LogManager { get currentLogFile => _logFile; Future setup({@required bool logToFile, @required Level logLevel}) async { + if (_isLogging) { + return; + } + _isLogging = true; BlocSupervisor.delegate = LogBlocDelegate(); if (logToFile) { _logFile = await _setupAndGetLogFile(); @@ -170,7 +177,11 @@ class LogManager { final dccLogLevel = event.data2; final message = "$dccLogLevel: $dccLogMessage"; final logRecord = LogRecord(Level.INFO, message, _coreLoggerName); - _writeToLogFile(logRecord); + if (Platform.isIOS) { + _logEntry(logRecord, true); // Enables iOS developers to receive DCC logs during Android Studio sessions + } else if (Platform.isAndroid) { + _writeToLogFile(logRecord); + } } Future logDeviceInfo() async { diff --git a/lib/src/login/login.dart b/lib/src/login/login.dart index 5ceb7b77..24a4dc3c 100644 --- a/lib/src/login/login.dart +++ b/lib/src/login/login.dart @@ -48,8 +48,6 @@ import 'package:ox_coi/src/l10n/l.dart'; import 'package:ox_coi/src/l10n/l10n.dart'; import 'package:ox_coi/src/login/login_bloc.dart'; import 'package:ox_coi/src/login/login_events_state.dart'; -import 'package:ox_coi/src/main/main_bloc.dart'; -import 'package:ox_coi/src/main/main_event_state.dart'; import 'package:ox_coi/src/navigation/navigatable.dart'; import 'package:ox_coi/src/navigation/navigation.dart'; import 'package:ox_coi/src/ui/dimensions.dart'; diff --git a/lib/src/main/main_bloc.dart b/lib/src/main/main_bloc.dart index 6955e987..64865b58 100644 --- a/lib/src/main/main_bloc.dart +++ b/lib/src/main/main_bloc.dart @@ -125,19 +125,19 @@ class MainBloc extends Bloc { yield MainStateFailure(error: error.toString()); } } else if (event is AppLoaded) { - final bool configured = await _context.isConfigured(); - if (configured) { - await _setupLoggedInAppState(); - } + final bool configured = await _context.isConfigured(); + if (configured) { + await _setupLoggedInAppState(); + } - final needsOnboarding = Customer.needsOnboarding; - if (needsOnboarding) { - await Customer().configureOnboardingAsync(); - } + final needsOnboarding = Customer.needsOnboarding; + if (needsOnboarding) { + await Customer().configureOnboardingAsync(); + } final notificationsActivated = await Permission.notification.isGranted; if (!needsOnboarding && notificationsActivated) { - LocalNotificationManager().setup(); + LocalNotificationManager().setup(registerListeners: true); if (_config.coiSupported) { await PushManager().setup(_pushBloc); String pushState = await getPreference(preferenceNotificationsPushStatus); @@ -189,7 +189,7 @@ class MainBloc extends Bloc { } Future _initCore() async { - await core.init(dbName); + await core.setupAsync(dbName: dbName, minimalSetup: false); } Future _setupDefaultValues() async { @@ -288,4 +288,4 @@ class MainBloc extends Bloc { add(DatabaseDeleteErrorEncountered(error: error)); } } -} +} \ No newline at end of file diff --git a/lib/src/message/message_attachment_bloc.dart b/lib/src/message/message_attachment_bloc.dart index e3282a43..7ddab31c 100644 --- a/lib/src/message/message_attachment_bloc.dart +++ b/lib/src/message/message_attachment_bloc.dart @@ -47,22 +47,21 @@ import 'dart:io'; import 'package:bloc/bloc.dart'; import 'package:crypto/crypto.dart'; import 'package:delta_chat_core/delta_chat_core.dart' as Core; -import 'package:flutter/services.dart'; import 'package:logging/logging.dart'; import 'package:open_file/open_file.dart'; import 'package:ox_coi/src/data/repository.dart'; import 'package:ox_coi/src/data/repository_manager.dart'; import 'package:ox_coi/src/extensions/numbers_apis.dart'; import 'package:ox_coi/src/message/message_attachment_event_state.dart'; -import 'package:ox_coi/src/share/shared_data.dart'; +import 'package:ox_coi/src/platform/method_channel.dart'; +import 'package:ox_coi/src/share/outgoing_shared_data.dart'; import 'package:ox_coi/src/utils/constants.dart'; import 'package:ox_coi/src/utils/video.dart'; import 'package:path/path.dart'; import 'package:video_thumbnail/video_thumbnail.dart'; class MessageAttachmentBloc extends Bloc { - static const platform = const MethodChannel(SharedData.sharingChannelName); - final _logger = Logger("message_attachment_bloc"); + static final _logger = Logger("message_attachment_bloc"); Repository _messageListRepository; @override @@ -107,10 +106,15 @@ class MessageAttachmentBloc extends Bloc{'title': '$text', 'path': '$filePath', 'mimeType': '$mime', 'text': '$text'}; - await platform.invokeMethod('sendSharedData', argsMap); + final shareData = OutgoingSharedData( + title: text, + path: filePath, + mimeType: mimeType, + text: text, + ); + await SharingChannel.instance.invokeMethod(SharingChannel.kMethodSendSharedData, shareData.toMap()); } Core.ChatMsg _getMessage(int messageId) { diff --git a/lib/src/message/message_item_event_state.dart b/lib/src/message/message_item_event_state.dart index 9b105fa2..02fb3bad 100644 --- a/lib/src/message/message_item_event_state.dart +++ b/lib/src/message/message_item_event_state.dart @@ -40,8 +40,6 @@ * for more details. */ -import 'dart:ui'; - import 'package:equatable/equatable.dart'; import 'package:meta/meta.dart'; import 'package:metadata_fetch/metadata_fetch.dart'; @@ -229,7 +227,7 @@ class MessageStateData extends Equatable { ]; } -class ChatStateData extends Equatable{ +class ChatStateData extends Equatable { final int id; final String name; diff --git a/lib/src/notifications/display_notification_manager.dart b/lib/src/notifications/display_notification_manager.dart new file mode 100644 index 00000000..96a449d3 --- /dev/null +++ b/lib/src/notifications/display_notification_manager.dart @@ -0,0 +1,178 @@ +/* + * OPEN-XCHANGE legal information + * + * All intellectual property rights in the Software are protected by + * international copyright laws. + * + * + * In some countries OX, OX Open-Xchange and open xchange + * as well as the corresponding Logos OX Open-Xchange and OX are registered + * trademarks of the OX Software GmbH group of companies. + * The use of the Logos is not covered by the Mozilla Public License 2.0 (MPL 2.0). + * Instead, you are allowed to use these Logos according to the terms and + * conditions of the Creative Commons License, Version 2.5, Attribution, + * Non-commercial, ShareAlike, and the interpretation of the term + * Non-commercial applicable to the aforementioned license is published + * on the web site https://www.open-xchange.com/terms-and-conditions/. + * + * Please make sure that third-party modules and libraries are used + * according to their respective licenses. + * + * Any modifications to this package must retain all copyright notices + * of the original copyright holder(s) for the original code used. + * + * After any such modifications, the original and derivative code shall remain + * under the copyright of the copyright holder(s) and/or original author(s) as stated here: + * https://www.open-xchange.com/legal/. The contributing author shall be + * given Attribution for the derivative code and a license granting use. + * + * Copyright (C) 2016-2020 OX Software GmbH + * Mail: info@open-xchange.com + * + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * 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 Mozilla Public License 2.0 + * for more details. + */ + +import 'package:delta_chat_core/delta_chat_core.dart' as dcc; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +import 'package:ox_coi/src/chat/chat.dart'; +import 'package:ox_coi/src/data/repository_manager.dart'; +import 'package:ox_coi/src/extensions/string_apis.dart'; +import 'package:ox_coi/src/l10n/l.dart'; +import 'package:ox_coi/src/l10n/l10n.dart'; +import 'package:ox_coi/src/lifecycle/lifecycle_bloc.dart'; +import 'package:ox_coi/src/navigation/navigatable.dart'; +import 'package:ox_coi/src/navigation/navigation.dart'; +import 'package:ox_coi/src/utils/constants.dart'; + +class DisplayNotificationManager { + static const _androidIconPath = '@mipmap/ic_notification'; + static const _payloadIdSeparator = "_"; + static const _payloadChatIdPosition = 0; + static const _payloadMessageIdPosition = 1; + + final _flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); + + final platformChannelSpecifics = NotificationDetails( + AndroidNotificationDetails( + kNotificationChannelMainId, + L10n.get(L.notificationChannelTitle), + L10n.get(L.notificationChannelDescription), + importance: Importance.Max, + priority: Priority.High, + ), + IOSNotificationDetails(), + ); + + static DisplayNotificationManager _instance; + + BuildContext _buildContext; + + factory DisplayNotificationManager() => _instance ??= new DisplayNotificationManager._internal(); + + DisplayNotificationManager._internal(); + + Future setupAsync(BuildContext buildContext) async { + this._buildContext = buildContext; + final initializationSettingsAndroid = AndroidInitializationSettings(_androidIconPath); + final initializationSettingsIOS = IOSInitializationSettings(); + final initializationSettings = InitializationSettings(initializationSettingsAndroid, initializationSettingsIOS); + await _flutterLocalNotificationsPlugin.initialize(initializationSettings, onSelectNotification: onSelectNotification); + } + + Future onSelectNotification(String payload) { + Navigation navigation = Navigation(); + if (!payload.isNullOrEmpty()) { + int chatId = getIdFromPayload(payload, _payloadChatIdPosition); + int messageId = getIdFromPayload(payload, _payloadMessageIdPosition); + var isChatOpened = navigation.current?.equal(Navigatable(Type.chat, params: [chatId, messageId])); + if (isChatOpened == null || !isChatOpened) { + navigation.pushAndRemoveUntil( + _buildContext, + MaterialPageRoute( + builder: (context) { + return Chat( + chatId: chatId, + messageId: messageId, + headlessStart: true, + ); + }, + ), + ModalRoute.withName(Navigation.root), + Navigatable(Type.rootChildren), + ); + } else { + navigation.popUntilRoot(_buildContext); + } + } else { + navigation.popUntilRoot(_buildContext); + } + return Future.value(true); + } + + int getIdFromPayload(String payload, int idPosition) { + var hasSeparator = payload.contains(_payloadIdSeparator); + if (!hasSeparator && idPosition > _payloadChatIdPosition) { + return null; + } + String idString = hasSeparator ? payload.split(_payloadIdSeparator)[idPosition] : payload; + return idString != null ? int.parse(idString) : null; + } + + Future showNotificationFromPushAsync(String fromEmail, dcc.DecryptedChatMessage decryptedChatMessage) async { + if (_buildContext != null && _isAppResumed()) { + return; + } + final contactRepository = RepositoryManager.get(RepositoryType.contact); + String name = fromEmail; + int chatId = decryptedChatMessage.chatId; + await Future.forEach(contactRepository.getAll(), (dcc.Contact contact) async { + final address = await contact.getAddress(); + if (address == fromEmail) { + final contactName = await contact.getName(); + name = contactName.isNotEmpty ? contactName : fromEmail; + } + }); + await _flutterLocalNotificationsPlugin.show(chatId, name, decryptedChatMessage.content, platformChannelSpecifics, payload: chatId != 0 ? chatId.toString() : null); + } + + Future showNotificationFromLocalAsync(int chatId, String title, String body, {String payload}) async { + if (_buildContext != null) { + final navigation = Navigation(); + if (_isAppInForeground(navigation)) { + final isChatOpened = navigation.current.equal(Navigatable(Type.chat, params: [chatId])); + final isChatListOpened = navigation.current.equal(Navigatable(Type.chatList)); + if (isChatOpened || isChatListOpened) { + return; + } + } + } + await _flutterLocalNotificationsPlugin.show(chatId, title, body, platformChannelSpecifics, payload: payload); + } + + bool _isAppInForeground(Navigation navigation) => navigation.hasElements() && _isAppResumed(); + + bool _isAppResumed() => BlocProvider.of(_buildContext).currentBackgroundState == AppLifecycleState.resumed.toString(); + + Future isAppLaunchedFromNotificationAsync() async { + final notificationAppLaunchDetails = await _flutterLocalNotificationsPlugin.getNotificationAppLaunchDetails(); + return notificationAppLaunchDetails.didNotificationLaunchApp; + } + + Future cancelNotificationAsync(int id) async { + await _flutterLocalNotificationsPlugin.cancel(id); + } + + Future cancelAllNotificationsAsync() async { + await _flutterLocalNotificationsPlugin.cancelAll(); + } +} diff --git a/lib/src/notifications/local_notification_manager.dart b/lib/src/notifications/local_notification_manager.dart index 0e021da5..d75fb954 100644 --- a/lib/src/notifications/local_notification_manager.dart +++ b/lib/src/notifications/local_notification_manager.dart @@ -44,12 +44,13 @@ import 'dart:collection'; import 'dart:convert'; import 'package:delta_chat_core/delta_chat_core.dart'; +import 'package:flutter/widgets.dart'; import 'package:logging/logging.dart'; import 'package:ox_coi/src/data/chat_message_repository.dart'; import 'package:ox_coi/src/data/repository_manager.dart'; import 'package:ox_coi/src/l10n/l.dart'; import 'package:ox_coi/src/l10n/l10n.dart'; -import 'package:ox_coi/src/notifications/notification_manager.dart'; +import 'package:ox_coi/src/notifications/display_notification_manager.dart'; import 'package:ox_coi/src/platform/preferences.dart'; import 'package:rxdart/rxdart.dart'; @@ -64,15 +65,22 @@ class LocalNotificationManager { final _core = DeltaChatCore(); final _context = Context(); - NotificationManager _notificationManager; + DisplayNotificationManager _notificationManager; bool _listenersRegistered = false; factory LocalNotificationManager() => _instance ??= LocalNotificationManager._internal(); LocalNotificationManager._internal(); - void setup() { - _registerListeners(); + LocalNotificationManager.newInstance() { + _instance = LocalNotificationManager._internal(); + } + + void setup({@required bool registerListeners}) { + _notificationManager = DisplayNotificationManager(); + if (registerListeners) { + _registerListeners(); + } } void tearDown() { @@ -82,7 +90,6 @@ class LocalNotificationManager { void _registerListeners() { if (!_listenersRegistered) { _listenersRegistered = true; - _notificationManager = NotificationManager(); _messageSubject.listen(_messagesUpdated); _core.addListener(eventIdList: [Event.incomingMsg, Event.msgsChanged], streamController: _messageSubject); } @@ -102,11 +109,11 @@ class LocalNotificationManager { Future triggerNotificationAsync() async { _logger.info("Local notification triggered"); - await createChatNotificationsAsync(); - await createInviteNotificationsAsync(); + await _createChatNotificationsAsync(); + await _createInviteNotificationsAsync(); } - Future createChatNotificationsAsync() async { + Future _createChatNotificationsAsync() async { final HashMap notificationHistory = await _getNotificationHistoryAsync(); final List freshMessages = await _context.getFreshMessages(); _temporaryMessageRepository.putIfAbsent(ids: freshMessages); @@ -129,7 +136,7 @@ class LocalNotificationManager { final teaser = await message.getSummaryText(200); final payload = chatId?.toString(); _logger.info("Creating chat notification for chat id $chatId with message id $messageId"); - _notificationManager.showNotificationFromLocal(chatId, title, teaser, payload: payload); + _notificationManager.showNotificationFromLocalAsync(chatId, title, teaser, payload: payload); } } }); @@ -161,7 +168,7 @@ class LocalNotificationManager { await setPreference(preferenceTarget, notificationHistoryString); } - Future createInviteNotificationsAsync() async { + Future _createInviteNotificationsAsync() async { final HashMap notificationInviteHistory = await _getNotificationHistoryAsync(isInvite: true); final List openInvites = await _context.getChatMessages(Chat.typeInvite); _temporaryMessageRepository.putIfAbsent(ids: openInvites); @@ -185,7 +192,7 @@ class LocalNotificationManager { final teaser = await message.getSummaryText(200); final payload = "${Chat.typeInvite.toString()}_$messageId"; _logger.info("Creating invite notification for sender id $senderId with message id $messageId"); - _notificationManager.showNotificationFromLocal(Chat.typeInvite, title, teaser, payload: payload); + _notificationManager.showNotificationFromLocalAsync(Chat.typeInvite, title, teaser, payload: payload); } }); diff --git a/lib/src/notifications/notification_manager.dart b/lib/src/notifications/notification_manager.dart deleted file mode 100644 index dd5b1dd4..00000000 --- a/lib/src/notifications/notification_manager.dart +++ /dev/null @@ -1,211 +0,0 @@ -/* - * OPEN-XCHANGE legal information - * - * All intellectual property rights in the Software are protected by - * international copyright laws. - * - * - * In some countries OX, OX Open-Xchange and open xchange - * as well as the corresponding Logos OX Open-Xchange and OX are registered - * trademarks of the OX Software GmbH group of companies. - * The use of the Logos is not covered by the Mozilla Public License 2.0 (MPL 2.0). - * Instead, you are allowed to use these Logos according to the terms and - * conditions of the Creative Commons License, Version 2.5, Attribution, - * Non-commercial, ShareAlike, and the interpretation of the term - * Non-commercial applicable to the aforementioned license is published - * on the web site https://www.open-xchange.com/terms-and-conditions/. - * - * Please make sure that third-party modules and libraries are used - * according to their respective licenses. - * - * Any modifications to this package must retain all copyright notices - * of the original copyright holder(s) for the original code used. - * - * After any such modifications, the original and derivative code shall remain - * under the copyright of the copyright holder(s) and/or original author(s) as stated here: - * https://www.open-xchange.com/legal/. The contributing author shall be - * given Attribution for the derivative code and a license granting use. - * - * Copyright (C) 2016-2020 OX Software GmbH - * Mail: info@open-xchange.com - * - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - * 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 Mozilla Public License 2.0 - * for more details. - */ - -import 'package:delta_chat_core/delta_chat_core.dart' as DeltaChatCore; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_local_notifications/flutter_local_notifications.dart'; -import 'package:ox_coi/src/chat/chat.dart'; -import 'package:ox_coi/src/data/repository.dart'; -import 'package:ox_coi/src/data/repository_manager.dart'; -import 'package:ox_coi/src/extensions/string_apis.dart'; -import 'package:ox_coi/src/lifecycle/lifecycle_bloc.dart'; -import 'package:ox_coi/src/navigation/navigatable.dart'; -import 'package:ox_coi/src/navigation/navigation.dart'; - -class NotificationManager { - static const idSeparator = "_"; - static const chatIdPosition = 0; - static const messageIdPosition = 1; - - final FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); - - - //TODO: Add better AndroidNotificationDetails - final platformChannelSpecifics = NotificationDetails( - AndroidNotificationDetails( - 'com.android.oxcoi.notification.single', - 'Message notification', - 'Notification for incoming messages', - importance: Importance.Max, - priority: Priority.High, - ), - IOSNotificationDetails(), - ); - - static NotificationManager _instance; - - BuildContext _buildContext; - - factory NotificationManager() => _instance ??= new NotificationManager._internal(); - - NotificationManager._internal(); - - void setup(BuildContext buildContext) { - this._buildContext = buildContext; - //localNotification setup - var initializationSettingsAndroid = AndroidInitializationSettings('@mipmap/ic_notification'); - var initializationSettingsIOS = IOSInitializationSettings(onDidReceiveLocalNotification: onDidReceiveLocalNotification); - var initializationSettings = InitializationSettings(initializationSettingsAndroid, initializationSettingsIOS); - _flutterLocalNotificationsPlugin.initialize(initializationSettings, onSelectNotification: onSelectNotification); - } - - Future onDidReceiveLocalNotification(int id, String title, String body, String payload) { - //TODO: Use payload to navigate to the right location/chat - debugPrint("NotificationManager.onDidRecieveLocalNotification() payload = $payload"); - return Future.value(false); - } - - Future onSelectNotification(String payload) { - Navigation navigation = Navigation(); - if (!payload.isNullOrEmpty()) { - int chatId = getIdFromPayload(payload, chatIdPosition); - int messageId = getIdFromPayload(payload, messageIdPosition); - var isChatOpened = navigation.current?.equal(Navigatable(Type.chat, params: [chatId, messageId])); - if (isChatOpened == null || !isChatOpened) { - navigation.pushAndRemoveUntil( - _buildContext, - MaterialPageRoute( - builder: (context) { - return Chat( - chatId: chatId, - messageId: messageId, - headlessStart: true, - ); - }, - ), - ModalRoute.withName(Navigation.root), - Navigatable(Type.rootChildren), - ); - } else { - navigation.popUntilRoot(_buildContext); - } - } else { - navigation.popUntilRoot(_buildContext); - } - return Future.value(true); - } - - int getIdFromPayload(String payload, int idPosition) { - var hasSeparator = payload.contains(idSeparator); - if (!hasSeparator && idPosition > chatIdPosition) { - return null; - } - String idString = hasSeparator ? payload.split(idSeparator)[idPosition] : payload; - return idString != null ? int.parse(idString) : null; - } - - Future showNotificationFromPush(String fromEmail, String body) async { - if (_buildContext != null) { - // Ignoring false positive https://github.com/felangel/bloc/issues/587 - // ignore: close_sinks - var lifecycleBloc = BlocProvider.of(_buildContext); - if (lifecycleBloc.currentBackgroundState == AppLifecycleState.resumed.toString()) { - return; - } - } - Repository _contactRepository = RepositoryManager.get(RepositoryType.contact); - String name = fromEmail; - int chatId; - await Future.forEach(_contactRepository.getAll(), (DeltaChatCore.Contact contact) async { - String address = await contact.getAddress(); - if (address == fromEmail) { - var contactName = await contact.getName(); - name = contactName.isNotEmpty ? contactName : fromEmail; - var context = DeltaChatCore.Context(); - chatId = await context.getChatByContactId(contact.id); - } - }); - await _flutterLocalNotificationsPlugin.show(chatId, name, body, platformChannelSpecifics, payload: chatId != 0 ? chatId.toString() : null); - } - - Future showNotificationFromLocal(int chatId, String title, String body, {String payload}) async { - if (_buildContext != null) { - // Ignoring false positive https://github.com/felangel/bloc/issues/587 - // ignore: close_sinks - var lifecycleBloc = BlocProvider.of(_buildContext); - var navigation = Navigation(); - if (navigation.hasElements() && lifecycleBloc?.currentBackgroundState == AppLifecycleState.resumed.toString()) { - var isChatOpened = navigation.current.equal(Navigatable(Type.chat, params: [chatId])); - var isChatListOpened = navigation.current.equal(Navigatable(Type.chatList)); - if (isChatOpened || isChatListOpened) { - return; - } - } - } - await _flutterLocalNotificationsPlugin.show(chatId, title, body, platformChannelSpecifics, payload: payload); - } - - //show group notification (Android only) - Future showGroupNotification(int chatId, String title, String body, {String payload}) async { - //TODO: Add better names - String groupKey = 'com.android.oxcoi.WORK_EMAIL'; - String groupChannelId = 'com.android.oxcoi.notification.group'; - String groupChannelName = 'Group notification'; - String groupChannelDescription = 'Notification for grouped messages'; - - AndroidNotificationDetails androidNotificationSpecifics = new AndroidNotificationDetails( - groupChannelId, groupChannelName, groupChannelDescription, - importance: Importance.Max, priority: Priority.High, groupKey: groupKey); - NotificationDetails notificationPlatformSpecifics = new NotificationDetails(androidNotificationSpecifics, null); - await _flutterLocalNotificationsPlugin.show(chatId, title, body, notificationPlatformSpecifics, payload: payload); - - AndroidNotificationDetails androidSummaryNotificationSpecifics = new AndroidNotificationDetails( - groupChannelId, groupChannelName, groupChannelDescription, - importance: Importance.Max, priority: Priority.High, groupKey: groupKey, setAsGroupSummary: true); - NotificationDetails notificationSummaryPlatformSpecifics = new NotificationDetails(androidSummaryNotificationSpecifics, null); - await _flutterLocalNotificationsPlugin.show(chatId, "", "", notificationSummaryPlatformSpecifics, payload: payload); - } - - Future isAppLaunchedFromNotification() async { - var notificationAppLaunchDetails = await _flutterLocalNotificationsPlugin.getNotificationAppLaunchDetails(); - return notificationAppLaunchDetails.didNotificationLaunchApp; - } - - Future cancelNotification(int id) async { - await _flutterLocalNotificationsPlugin.cancel(id); - } - - Future cancelAllNotifications() async { - await _flutterLocalNotificationsPlugin.cancelAll(); - } -} diff --git a/lib/src/platform/method_channel.dart b/lib/src/platform/method_channel.dart new file mode 100644 index 00000000..ee8e398a --- /dev/null +++ b/lib/src/platform/method_channel.dart @@ -0,0 +1,30 @@ +import 'package:flutter/services.dart'; + +class SecurityChannel { + static const _name = 'oxcoi.security'; + + static const kMethodDecrypt = 'decrypt'; + + static const kArgumentContent = 'encryptedBase64Content'; + static const kArgumentPrivateKey = 'privateKeyBase64'; + static const kArgumentPublicKey = 'publicKeyBase64'; + static const kArgumentAuth = 'authBase64'; + + static const instance = const MethodChannel(_name); +} + +class SharingChannel { + static const _name = 'oxcoi.sharing'; + + static const kMethodGetSharedData = 'getSharedData'; + static const kMethodSendSharedData = 'sendSharedData'; + static const kMethodGetInitialLink = 'getInitialLink'; + + static const kArgumentMimeType = 'mimeType'; + static const kArgumentText = 'text'; + static const kArgumentPath = 'path'; + static const kArgumentFileName = 'fileName'; + static const kArgumentTitle = 'title'; + + static const instance = const MethodChannel(_name); +} diff --git a/lib/src/platform/preferences.dart b/lib/src/platform/preferences.dart index 8688edfc..5326e212 100644 --- a/lib/src/platform/preferences.dart +++ b/lib/src/platform/preferences.dart @@ -59,9 +59,9 @@ const preferenceNeedsOnboarding = "preferenceNeedsOnboarding"; const preferenceNotificationHistory = "preferenceNotificationHistory"; const preferenceNotificationInviteHistory = "preferenceNotificationInviteHistory"; -const preferenceNotificationsAuth = "preferenceNotificationsAuth"; // Unused -const preferenceNotificationsP256dhPublic = "preferenceNotificationsP256dhPublic"; // Unused -const preferenceNotificationsP256dhPrivate = "preferenceNotificationsP256dhPrivate"; // Unused +const preferenceNotificationsAuth = "preferenceNotificationAuth"; +const preferenceNotificationKeyPublic = "preferenceNotificationKeyPublic"; +const preferenceNotificationKeyPrivate = "preferenceNotificationKeyPrivate"; Future getPreference(String key) async { SharedPreferences sharedPreferences = await getSharedPreferences(); diff --git a/lib/src/push/push_bloc.dart b/lib/src/push/push_bloc.dart index af430303..4eb71948 100644 --- a/lib/src/push/push_bloc.dart +++ b/lib/src/push/push_bloc.dart @@ -45,7 +45,6 @@ import 'dart:convert'; import 'package:bloc/bloc.dart'; import 'package:delta_chat_core/delta_chat_core.dart'; import 'package:flutter/foundation.dart'; -import 'package:flutter/services.dart'; import 'package:http/http.dart'; import 'package:logging/logging.dart'; import 'package:ox_coi/src/data/push_metadata.dart'; @@ -55,7 +54,8 @@ import 'package:ox_coi/src/platform/preferences.dart'; import 'package:ox_coi/src/platform/system_information.dart'; import 'package:ox_coi/src/push/push_event_state.dart'; import 'package:ox_coi/src/push/push_manager.dart'; -import 'package:ox_coi/src/secure/generator.dart'; +import 'package:ox_coi/src/security/security_generator.dart'; +import 'package:ox_coi/src/security/security_manager.dart'; import 'package:ox_coi/src/utils/http.dart'; import 'package:rxdart/rxdart.dart'; @@ -72,111 +72,41 @@ enum PushSetupState { } class PushBloc extends Bloc { - var _logger = Logger("push_bloc"); - var _pushManager = PushManager(); - var pushService = PushService(); - var _core = DeltaChatCore(); - var _context = Context(); - bool _listenersRegistered = false; static const _subscribeListenerId = 1001; static const _validateListenerId = 1002; - PublishSubject _pushSubject = new PublishSubject(); - static const securityChannelName = const MethodChannel("oxcoi.security"); + final _logger = Logger("push_bloc"); + final _pushManager = PushManager(); + final pushService = PushService(); + final _core = DeltaChatCore(); + final _context = Context(); + final _pushSubject = PublishSubject(); + + bool _listenersRegistered = false; @override PushState get initialState => PushStateInitial(); @override Stream mapEventToState(PushEvent event) async* { - bool pushAvailable = await _isWebPushAvailable(); + final pushAvailable = await _isWebPushAvailableAsync(); if (!pushAvailable) { yield PushStateSuccess(pushAvailable: false, pushSetupState: PushSetupState.initial); - _setNotificationPushStatus(PushSetupState.initial); + _setNotificationPushStatusAsync(PushSetupState.initial); return; } if (event is RegisterPushResource) { - try { - RequestPushRegistration requestPushRegistration = await _createRegistrationRequest(); - var response = await pushService.registerPush(requestPushRegistration); - var valid = validateHttpResponse(response); - if (valid) { - ResponsePushResource pushResource = _getPushResource(response); - await _persistPushResource(pushResource); - yield PushStateSuccess( - pushAvailable: true, - pushSetupState: PushSetupState.resourceRegistered, - ); - _setNotificationPushStatus(PushSetupState.resourceRegistered); - add(SubscribeMetadata(pushResource: pushResource)); - } - } catch (error) { - yield PushStateFailure(error: error.toString()); - } + yield* _registerPushResource(); } else if (event is GetPushResource) { - try { - String id = await _getId(); - var response = await pushService.getPush(id); - var valid = validateHttpResponse(response); - if (valid) { - yield PushStateSuccess( - pushAvailable: true, - pushSetupState: PushSetupState.unchanged, - ); - } - } catch (error) { - yield PushStateFailure(error: error.toString()); - } + yield* _getPushResource(); } else if (event is PatchPushResource) { - try { - String id = await _getId(); - RequestPushPatch requestPushPatch = _createPatchRequest(event.pushToken); - var response = await pushService.patchPush(id, requestPushPatch); - var valid = validateHttpResponse(response); - if (valid) { - ResponsePushResource pushResource = _getPushResource(response); - await _persistPushResource(pushResource); - yield PushStateSuccess( - pushAvailable: true, - pushSetupState: PushSetupState.resourceRegistered, - ); - _setNotificationPushStatus(PushSetupState.resourceRegistered); - // TODO is requesting a new metadata subscription actually needed? Does the push resource ID or endpoint changes? - add(SubscribeMetadata(pushResource: pushResource)); - } - } catch (error) { - yield PushStateFailure(error: error.toString()); - } + yield* _patchPushResource(event); } else if (event is DeletePushResource) { - try { - String id = await _getId(); - var response = await pushService.deletePush(id); - var valid = validateHttpResponse(response); - if (valid) { - _removePushResource(); - yield PushStateSuccess( - pushAvailable: true, - pushSetupState: PushSetupState.initial, - ); - _setNotificationPushStatus(PushSetupState.initial); - } - } catch (error) { - yield PushStateFailure(error: error.toString()); - } + yield* _deletePushResource(); } else if (event is SubscribeMetadata) { - await _subscribeMetaData(event.pushResource); - yield PushStateSuccess( - pushAvailable: true, - pushSetupState: PushSetupState.sendMetadataSubscribe, - ); - _setNotificationPushStatus(PushSetupState.sendMetadataSubscribe); + yield* _subscribeMetadata(event); } else if (event is ValidateMetadata) { - await _confirmValidation(event.validation); - yield PushStateSuccess( - pushAvailable: true, - pushSetupState: PushSetupState.sendMetadataValidate, - ); - _setNotificationPushStatus(PushSetupState.sendMetadataValidate); + yield* _validateMetadata(event); } } @@ -186,67 +116,154 @@ class PushBloc extends Bloc { return super.close(); } - Future _createRegistrationRequest() async { - var appId = await getPackageName(); - var pushToken = await _pushManager.getPushToken(); - var publicKey = await _getCoiServerPublicKey(); - publicKey = publicKey.replaceAll("\n", ""); - var requestPushRegistration = RequestPushRegistration(appId, publicKey, pushToken); - return requestPushRegistration; + Stream _registerPushResource() async* { + try { + final requestPushRegistration = await _createRegistrationRequestAsync(); + final response = await pushService.registerPush(requestPushRegistration); + final valid = isHttpResponseValid(response); + if (valid) { + final pushResource = _createPushResource(response); + await _persistPushResourceAsync(pushResource); + yield PushStateSuccess( + pushAvailable: true, + pushSetupState: PushSetupState.resourceRegistered, + ); + _setNotificationPushStatusAsync(PushSetupState.resourceRegistered); + add(SubscribeMetadata(pushResource: pushResource)); + } + } catch (error) { + yield PushStateFailure(error: error.toString()); + } + } + + Stream _getPushResource() async* { + try { + final id = await _getIdAsync(); + final response = await pushService.getPush(id); + final valid = isHttpResponseValid(response); + if (valid) { + yield PushStateSuccess( + pushAvailable: true, + pushSetupState: PushSetupState.unchanged, + ); + } + } catch (error) { + yield PushStateFailure(error: error.toString()); + } + } + + Stream _patchPushResource(PatchPushResource event) async* { + try { + final id = await _getIdAsync(); + final requestPushPatch = _createPatchRequest(event.pushToken); + final response = await pushService.patchPush(id, requestPushPatch); + final valid = isHttpResponseValid(response); + if (valid) { + final pushResource = _createPushResource(response); + await _persistPushResourceAsync(pushResource); + yield PushStateSuccess( + pushAvailable: true, + pushSetupState: PushSetupState.resourceRegistered, + ); + _setNotificationPushStatusAsync(PushSetupState.resourceRegistered); + add(SubscribeMetadata(pushResource: pushResource)); + } + } catch (error) { + yield PushStateFailure(error: error.toString()); + } + } + + Stream _deletePushResource() async* { + try { + final id = await _getIdAsync(); + final response = await pushService.deletePush(id); + final valid = isHttpResponseValid(response); + if (valid) { + _removePushResourceAsync(); + yield PushStateSuccess( + pushAvailable: true, + pushSetupState: PushSetupState.initial, + ); + _setNotificationPushStatusAsync(PushSetupState.initial); + } + } catch (error) { + yield PushStateFailure(error: error.toString()); + } + } + + Stream _subscribeMetadata(SubscribeMetadata event) async* { + await _subscribeMetaDataAsync(event.pushResource); + yield PushStateSuccess( + pushAvailable: true, + pushSetupState: PushSetupState.sendMetadataSubscribe, + ); + _setNotificationPushStatusAsync(PushSetupState.sendMetadataSubscribe); + } + + Stream _validateMetadata(ValidateMetadata event) async* { + await _confirmValidationAsync(event.validation); + yield PushStateSuccess( + pushAvailable: true, + pushSetupState: PushSetupState.sendMetadataValidate, + ); + _setNotificationPushStatusAsync(PushSetupState.sendMetadataValidate); } - RequestPushPatch _createPatchRequest(String pushToken) { - var requestPushRegistration = RequestPushPatch(pushToken); - return requestPushRegistration; + Future _createRegistrationRequestAsync() async { + final appId = await getPackageName(); + final pushToken = await _pushManager.getPushTokenAsync(); + var publicKey = await _getCoiServerPublicKeyAsync(); + publicKey = publicKey.replaceAll("\n", ""); + return RequestPushRegistration(appId, publicKey, pushToken); } - Future _getId() async { - var pushResourceJsonString = await getPreference(preferenceNotificationsPush); - var pushResourceJsonMap = jsonDecode(pushResourceJsonString); - var pushResource = ResponsePushResource.fromJson(pushResourceJsonMap); + RequestPushPatch _createPatchRequest(String pushToken) => RequestPushPatch(pushToken); + + Future _getIdAsync() async { + final pushResourceJsonString = await getPreference(preferenceNotificationsPush); + final pushResourceJsonMap = jsonDecode(pushResourceJsonString); + final pushResource = ResponsePushResource.fromJson(pushResourceJsonMap); return pushResource.id; } - ResponsePushResource _getPushResource(Response response) { + ResponsePushResource _createPushResource(Response response) { var pushResource; if (response.body != null && response.body.isNotEmpty) { - Map responseMap = jsonDecode(response.body); + final responseMap = jsonDecode(response.body); pushResource = ResponsePushResource.fromJson(responseMap); } return pushResource; } - Future _persistPushResource(ResponsePushResource pushResource) async { + Future _persistPushResourceAsync(ResponsePushResource pushResource) async { if (pushResource != null) { await setPreference(preferenceNotificationsPush, jsonEncode(pushResource)); } } - Future _removePushResource() async { + Future _removePushResourceAsync() async { await removePreference(preferenceNotificationsPush); } - Future _isWebPushAvailable() async { - _context = Context(); + Future _isWebPushAvailableAsync() async { return await _context.isWebPushSupported() == 1; } - Future _getCoiServerPublicKey() async { - _context = Context(); + Future _getCoiServerPublicKeyAsync() async { return await _context.getWebPushVapidKey(); } - Future _subscribeMetaData(ResponsePushResource responsePushResource) async { - await _generateSecrets(); - String publicKey = await _getKey(); - //TODO decide where to get / load / generate all keys, we should prefer Dart if possible - String auth = await _getAuthSecret(); - String clientEndpoint = generateUuid(); + Future _subscribeMetaDataAsync(ResponsePushResource responsePushResource) async { + await generateAndPersistPushKeyPairAsync(); + final publicKey = await getPushPublicKeyAsync(); + await generateAndPersistPushAuthAsync(); + final auth = await getPushAuthAsync(); + final clientEndpoint = generateUuid(); await setPreference(preferenceNotificationsEndpoint, clientEndpoint); - String client = await getAppName(); - String device = await getDeviceName(); - var pushSubscribeMetaData = PushSubscribeMetaData( + final client = await getAppName(); + final device = await getDeviceName(); + final pushSubscribeMetaData = PushSubscribeMetaData( client: client, device: device, resource: PushSubscribeMetaDataResource( @@ -258,20 +275,19 @@ class PushBloc extends Bloc { ), ); _registerListeners(); - _context = Context(); - String encodedBody = json.encode(pushSubscribeMetaData); + final encodedBody = json.encode(pushSubscribeMetaData); await _context.subscribeWebPush(clientEndpoint, encodedBody, _subscribeListenerId); } - Future _confirmValidation(String message) async { - String clientEndpoint = await getPreference(preferenceNotificationsEndpoint); + Future _confirmValidationAsync(String message) async { + final clientEndpoint = await getPreference(preferenceNotificationsEndpoint); await _context.validateWebPush(clientEndpoint, message, _validateListenerId); } - void _registerListeners() async { + void _registerListeners() { if (!_listenersRegistered) { _listenersRegistered = true; - _pushSubject.listen(_metadataSuccessCallback, onError: _errorCallback); + _pushSubject.listen(_metadataSuccessCallbackAsync, onError: _errorCallback); _core.addListener(eventIdList: [Event.setMetaDataDone, Event.webPushSubscription], streamController: _pushSubject); } } @@ -283,26 +299,21 @@ class PushBloc extends Bloc { } } - void _metadataSuccessCallback(Event event) { - var data1 = event.data1; - if (data1 == _subscribeListenerId) { - _setNotificationPushStatus(PushSetupState.metadataSubscribed); - } else if (data1 == _validateListenerId) { - _setNotificationPushStatus(PushSetupState.metadataValidated); + Future _metadataSuccessCallbackAsync(Event event) async { + final listenerId = event.data1; + if (listenerId == _subscribeListenerId) { + await _setNotificationPushStatusAsync(PushSetupState.metadataSubscribed); + } else if (listenerId == _validateListenerId) { + await _setNotificationPushStatusAsync(PushSetupState.metadataValidated); } } _errorCallback(error) { - _logger.info("An error occured while listening: $error"); + _logger.warning("An error occured while listening: $error"); } - _setNotificationPushStatus(PushSetupState state) { - setPreference(preferenceNotificationsPushStatus, describeEnum(state)); + Future _setNotificationPushStatusAsync(PushSetupState state) async { + await setPreference(preferenceNotificationsPushStatus, describeEnum(state)); } - Future _generateSecrets() async => await securityChannelName.invokeMethod('generateSecrets'); - - Future _getKey() async => await securityChannelName.invokeMethod('getKey'); - - Future _getAuthSecret() async => await securityChannelName.invokeMethod('getAuthSecret'); } diff --git a/lib/src/push/push_event_state.dart b/lib/src/push/push_event_state.dart index 0bfb465c..65e51b7b 100644 --- a/lib/src/push/push_event_state.dart +++ b/lib/src/push/push_event_state.dart @@ -70,18 +70,6 @@ class ValidateMetadata extends PushEvent { ValidateMetadata({@required this.validation}); } -class PushActionFailed extends PushEvent { - final String error; - - PushActionFailed({@required this.error}); -} - -class PushActionDone extends PushEvent { - final ResponsePushResource responsePushResource; - - PushActionDone({@required this.responsePushResource}); -} - abstract class PushState {} class PushStateInitial extends PushState {} diff --git a/lib/src/push/push_manager.dart b/lib/src/push/push_manager.dart index 473de451..14481b7b 100644 --- a/lib/src/push/push_manager.dart +++ b/lib/src/push/push_manager.dart @@ -42,23 +42,27 @@ import 'dart:convert'; +import 'package:delta_chat_core/delta_chat_core.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:ox_coi/src/data/notification.dart'; +import 'package:logging/logging.dart'; +import 'package:ox_coi/src/data/push.dart'; import 'package:ox_coi/src/data/push_chat_message.dart'; import 'package:ox_coi/src/data/push_validation.dart'; import 'package:ox_coi/src/extensions/string_apis.dart'; -import 'package:ox_coi/src/notifications/notification_manager.dart'; +import 'package:ox_coi/src/notifications/display_notification_manager.dart'; +import 'package:ox_coi/src/platform/method_channel.dart'; import 'package:ox_coi/src/platform/preferences.dart'; import 'package:ox_coi/src/push/push_bloc.dart'; import 'package:ox_coi/src/push/push_event_state.dart'; +import 'package:ox_coi/src/security/security_manager.dart'; + +const loggerName = "push_manager"; class PushManager { - static const securityChannelName = const MethodChannel("oxcoi.security"); - FirebaseMessaging _firebaseMessaging = new FirebaseMessaging(); - var _notificationManager = NotificationManager(); + final _logger = Logger(loggerName); + final _firebaseMessaging = FirebaseMessaging(); + final _notificationManager = DisplayNotificationManager(); + PushBloc _pushBloc; static PushManager _instance; @@ -69,68 +73,101 @@ class PushManager { Future setup(PushBloc pushBloc) async { this._pushBloc = pushBloc; - //firebase setup + _firebaseMessaging.configure( onMessage: (Map message) async { - print('on message $message'); - var notificationData = NotificationData.fromJson(message); - if (notificationData.valid) { - String decryptedContent = await decrypt(notificationData.content); - if (_isValidationPush(decryptedContent)) { - var validation = _getPushValidation(decryptedContent).validation; - _pushBloc.add(ValidateMetadata(validation: validation)); + _logger.info("Received: $message"); + final pushData = Push.fromJson(message); + if (pushData.valid) { + _logger.info("Data is valid"); + _logger.info("Decrypt AES started"); + final decryptedPush = await decryptAesAsync(pushData.content); + _logger.info("Decrypt AES done"); + if (_isValidationPush(decryptedPush)) { + _logger.info("Data is validation message"); + final pushValidationMessage = _getPushValidation(decryptedPush).validation; + _logger.info("Decrypted data: $pushValidationMessage"); + _pushBloc.add(ValidateMetadata(validation: pushValidationMessage)); } else { - var pushChatMessage = _getPushChatMessage(decryptedContent); - String fromEmail = pushChatMessage.fromEmail; - String body = "I sent you a new chat message"; - await _notificationManager.showNotificationFromPush(fromEmail, body); + _logger.info("Data is chat message"); + final pushChatMessage = _getPushChatMessage(decryptedPush); + final fromEmail = pushChatMessage.fromEmail; + _logger.info("Decrypted data: $pushChatMessage for $fromEmail"); + var contentType = pushChatMessage.contentType; + if (contentType.isNullOrEmpty()) { + contentType = "text/plain; charset=utf-8"; + _logger.info("Manually setting content type to avoid null / empty value"); + } + _logger.info("Decrypt PGP started"); + final decryptedChatMessage = await decryptPgpAsync(contentType, pushChatMessage, fromEmail); + _logger.info("Decrypt PGP done"); + _logger.info( + "Decrypted and mapped data: $fromEmail sent in chat ${decryptedChatMessage.chatId} the message '${decryptedChatMessage.content}'"); + await _notificationManager.showNotificationFromPushAsync(fromEmail, decryptedChatMessage); } + } else { + _logger.info("Data is *NOT* valid"); } return Future(null); }, onResume: (Map message) { //TODO: Add functionality - print('on resume $message'); + _logger.info("onResume $message"); return Future(null); }, onLaunch: (Map message) { //TODO: Add functionality - print('on launch $message'); + _logger.info("onLaunch $message"); return Future(null); }, ); _firebaseMessaging.requestNotificationPermissions(const IosNotificationSettings(sound: true, badge: true, alert: true)); - _firebaseMessaging.getToken().then((token) { - //TODO Use in production - //_pushBloc.add(PatchPushResource(pushToken: token)); - }); } - Future getPushToken() async { + Future getPushTokenAsync() async { return await _firebaseMessaging.getToken(); } - Future getPushResource() async { + Future getPushResourceAsync() async { return await getPreference(preferenceNotificationsPush); } - Future decrypt(String base64content) async { - return await securityChannelName.invokeMethod('decrypt', {"input": base64content}); + Future decryptAesAsync(String encryptedBase64Content) async { + final privateKey = await getPushPrivateKeyAsync(); + final publicKey = await getPushPublicKeyAsync(); + final auth = await getPushAuthAsync(); + + _logger.info("PrivateKey: $privateKey"); + _logger.info("PublicKey: $publicKey"); + _logger.info("Auth: $auth"); + + return await SecurityChannel.instance.invokeMethod(SecurityChannel.kMethodDecrypt, { + SecurityChannel.kArgumentContent: encryptedBase64Content, + SecurityChannel.kArgumentPrivateKey: privateKey, + SecurityChannel.kArgumentPublicKey: publicKey, + SecurityChannel.kArgumentAuth: auth, + }); + } + + Future decryptPgpAsync(String contentType, PushChatMessage pushChatMessage, String fromEmail) async { + final context = Context(); + final decrypted = await context.decryptInMemory(contentType, pushChatMessage.content, fromEmail); + return DecryptedChatMessage.fromMethodChannel(decrypted); } bool _isValidationPush(String decryptedContent) { - var pushValidationMap = jsonDecode(decryptedContent); - var pushValidation = PushValidation.fromJson(pushValidationMap); + final pushValidationMap = jsonDecode(decryptedContent); + final pushValidation = PushValidation.fromJson(pushValidationMap); return !pushValidation.validation.isNullOrEmpty(); } PushValidation _getPushValidation(String decryptedContent) { - var pushValidationMap = jsonDecode(decryptedContent); + final pushValidationMap = jsonDecode(decryptedContent); return PushValidation.fromJson(pushValidationMap); } PushChatMessage _getPushChatMessage(String decryptedContent) { - var pushValidationMap = jsonDecode(decryptedContent); + final pushValidationMap = jsonDecode(decryptedContent); return PushChatMessage.fromJson(pushValidationMap); } } diff --git a/lib/src/push/push_service.dart b/lib/src/push/push_service.dart index 46862dfe..85f0e779 100644 --- a/lib/src/push/push_service.dart +++ b/lib/src/push/push_service.dart @@ -54,7 +54,7 @@ import 'package:ox_coi/src/utils/http.dart'; class PushService { static PushService _instance; - var _logger = Logger("push_service"); + final _logger = Logger("push_service"); var headers = {"Content-type": "application/json"}; factory PushService() => _instance ??= PushService._internal(); diff --git a/lib/src/secure/generator.dart b/lib/src/security/security_generator.dart similarity index 76% rename from lib/src/secure/generator.dart rename to lib/src/security/security_generator.dart index f51d2b21..46273387 100644 --- a/lib/src/secure/generator.dart +++ b/lib/src/security/security_generator.dart @@ -40,49 +40,41 @@ * for more details. */ -import 'dart:convert'; import 'dart:math'; import 'dart:typed_data'; import 'package:pointycastle/api.dart'; -import 'package:pointycastle/ecc/api.dart'; import 'package:pointycastle/ecc/curves/secp256r1.dart'; import 'package:pointycastle/key_generators/api.dart'; import 'package:pointycastle/key_generators/ec_key_generator.dart'; import 'package:pointycastle/random/fortuna_random.dart'; import 'package:uuid/uuid.dart'; -AsymmetricKeyPair generateEcKeyPair() { - var domainParameters = ECCurve_secp256r1(); - var params = ECKeyGeneratorParameters(domainParameters); - var generator = ECKeyGenerator(); - generator.init(ParametersWithRandom(params, _getSecureRandom())); - return generator.generateKeyPair(); -} - -String getPublicEcKey(AsymmetricKeyPair keyPair) { - ECPublicKey publicKey = keyPair.publicKey; - var encoded = publicKey.Q.getEncoded(false); - return base64UrlEncode(encoded); +String generateUuid() { + final uuid = Uuid(); + return uuid.v4(); } -String getPrivateEcKey(AsymmetricKeyPair keyPair) { - ECPrivateKey privateKey = keyPair.privateKey; - return privateKey.d.toString(); +Uint8List generateRandomBytes([int length = 16]) { + final secureRandom = _getSecureRandom(); + return secureRandom.nextBytes(length); } -String generateUuid() { - var uuid = new Uuid(); - return uuid.v4(); +AsymmetricKeyPair generateEcKeyPair() { + final domainParameters = ECCurve_secp256r1(); + final generatorParameters = ECKeyGeneratorParameters(domainParameters); + final generator = ECKeyGenerator(); + generator.init(ParametersWithRandom(generatorParameters, _getSecureRandom())); + return generator.generateKeyPair(); } SecureRandom _getSecureRandom() { - var secureRandom = FortunaRandom(); - var random = Random.secure(); + final secureRandom = FortunaRandom(); + final seedingRandom = Random.secure(); List seeds = []; for (int i = 0; i < 32; i++) { - seeds.add(random.nextInt(255)); + seeds.add(seedingRandom.nextInt(255)); } - secureRandom.seed(new KeyParameter(Uint8List.fromList(seeds))); + secureRandom.seed(KeyParameter(Uint8List.fromList(seeds))); return secureRandom; } diff --git a/lib/src/security/security_manager.dart b/lib/src/security/security_manager.dart new file mode 100644 index 00000000..bb09fbed --- /dev/null +++ b/lib/src/security/security_manager.dart @@ -0,0 +1,45 @@ +import 'dart:convert'; + +import 'package:ox_coi/src/platform/preferences.dart'; +import 'package:ox_coi/src/security/security_generator.dart'; +import 'package:pointycastle/pointycastle.dart'; +// ignore: implementation_imports +import 'package:pointycastle/src/utils.dart'; // Required implementation import to allow encoding / decoding of BigInt <-> ByteArray + +Future generateAndPersistPushKeyPairAsync() async { + final keyPair = generateEcKeyPair(); + final publicKey = _extractBase64PublicEcKey(keyPair); + final privateKey = _extractBase64PrivateEcKey(keyPair); + await setPreference(preferenceNotificationKeyPublic, publicKey); + await setPreference(preferenceNotificationKeyPrivate, privateKey); +} + +String _extractBase64PublicEcKey(AsymmetricKeyPair keyPair) { + final ECPublicKey publicKey = keyPair.publicKey; + final encodedKey = publicKey.Q.getEncoded(false); + return base64UrlEncode(encodedKey); +} + +String _extractBase64PrivateEcKey(AsymmetricKeyPair keyPair) { + final ECPrivateKey privateKey = keyPair.privateKey; + final encodedKey = encodeBigInt(privateKey.d); + return base64UrlEncode(encodedKey); +} + +Future generateAndPersistPushAuthAsync() async { + final auth = generateRandomBytes(); + final encodedAuth = base64UrlEncode(auth); + await setPreference(preferenceNotificationsAuth, encodedAuth); +} + +Future getPushPrivateKeyAsync() async { + return await getPreference(preferenceNotificationKeyPrivate); +} + +Future getPushPublicKeyAsync() async { + return await getPreference(preferenceNotificationKeyPublic); +} + +Future getPushAuthAsync() async { + return await getPreference(preferenceNotificationsAuth); +} \ No newline at end of file diff --git a/lib/src/settings/settings_debug_bloc.dart b/lib/src/settings/settings_debug_bloc.dart index 8504d264..7ad901e7 100644 --- a/lib/src/settings/settings_debug_bloc.dart +++ b/lib/src/settings/settings_debug_bloc.dart @@ -72,8 +72,8 @@ class SettingsDebugBloc extends Bloc { void loadDebug() async { var pushManager = PushManager(); - String token = await pushManager.getPushToken(); - String pushResource = await pushManager.getPushResource(); + String token = await pushManager.getPushTokenAsync(); + String pushResource = await pushManager.getPushResourceAsync(); String endpoint = await getPreference(preferenceNotificationsEndpoint); String pushServiceUrl = await getPreference(preferenceNotificationsPushServiceUrl); String pushState = await getPreference(preferenceNotificationsPushStatus); diff --git a/lib/src/settings/settings_notifications_bloc.dart b/lib/src/settings/settings_notifications_bloc.dart index 909a29eb..e77a95fa 100644 --- a/lib/src/settings/settings_notifications_bloc.dart +++ b/lib/src/settings/settings_notifications_bloc.dart @@ -42,7 +42,6 @@ import 'package:bloc/bloc.dart'; import 'package:ox_coi/src/background_refresh/background_refresh_manager.dart'; -import 'package:ox_coi/src/customer/customer.dart'; import 'package:ox_coi/src/data/config.dart'; import 'package:ox_coi/src/platform/preferences.dart'; import 'package:ox_coi/src/settings/settings_notifications_event_state.dart'; diff --git a/lib/src/share/shared_data.dart b/lib/src/share/incoming_shared_data.dart similarity index 75% rename from lib/src/share/shared_data.dart rename to lib/src/share/incoming_shared_data.dart index 896e9f9e..fc319de5 100644 --- a/lib/src/share/shared_data.dart +++ b/lib/src/share/incoming_shared_data.dart @@ -40,22 +40,18 @@ * for more details. */ -class SharedData { - static const String sharedMimeType = "shared_mime_type"; - static const String sharedText = "shared_text"; - static const String sharedPath = "shared_path"; - static const String sharedFileName = "shared_file_name"; - static const String sharingChannelName = "oxcoi.intent"; +import 'package:ox_coi/src/platform/method_channel.dart'; +class IncomingSharedData { String mimeType; String text; String path; String fileName; - SharedData(Map data) { - mimeType = data.containsKey(sharedMimeType) ? data[sharedMimeType] : ""; - text = data.containsKey(sharedText) ? data[sharedText] : ""; - path = data.containsKey(sharedPath) ? data[sharedPath] : ""; - fileName = data.containsKey(sharedFileName) ? data[sharedFileName] : ""; + IncomingSharedData(Map data) { + mimeType = data.containsKey(SharingChannel.kArgumentMimeType) ? data[SharingChannel.kArgumentMimeType] : ""; + text = data.containsKey(SharingChannel.kArgumentText) ? data[SharingChannel.kArgumentText] : ""; + path = data.containsKey(SharingChannel.kArgumentPath) ? data[SharingChannel.kArgumentPath] : ""; + fileName = data.containsKey(SharingChannel.kArgumentFileName) ? data[SharingChannel.kArgumentFileName] : ""; } } diff --git a/lib/src/share/outgoing_shared_data.dart b/lib/src/share/outgoing_shared_data.dart new file mode 100644 index 00000000..133e40bb --- /dev/null +++ b/lib/src/share/outgoing_shared_data.dart @@ -0,0 +1,56 @@ +/* + * OPEN-XCHANGE legal information + * + * All intellectual property rights in the Software are protected by + * international copyright laws. + * + * + * In some countries OX, OX Open-Xchange and open xchange + * as well as the corresponding Logos OX Open-Xchange and OX are registered + * trademarks of the OX Software GmbH group of companies. + * The use of the Logos is not covered by the Mozilla Public License 2.0 (MPL 2.0). + * Instead, you are allowed to use these Logos according to the terms and + * conditions of the Creative Commons License, Version 2.5, Attribution, + * Non-commercial, ShareAlike, and the interpretation of the term + * Non-commercial applicable to the aforementioned license is published + * on the web site https://www.open-xchange.com/terms-and-conditions/. + * + * Please make sure that third-party modules and libraries are used + * according to their respective licenses. + * + * Any modifications to this package must retain all copyright notices + * of the original copyright holder(s) for the original code used. + * + * After any such modifications, the original and derivative code shall remain + * under the copyright of the copyright holder(s) and/or original author(s) as stated here: + * https://www.open-xchange.com/legal/. The contributing author shall be + * given Attribution for the derivative code and a license granting use. + * + * Copyright (C) 2016-2020 OX Software GmbH + * Mail: info@open-xchange.com + * + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * 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 Mozilla Public License 2.0 + * for more details. + */ + +import 'package:flutter/widgets.dart'; + +class OutgoingSharedData { + final String title; + final String path; + final String mimeType; + final String text; + + OutgoingSharedData({this.title = '', this.path = '', @required this.mimeType, @required this.text}); + + Map toMap() { + return {'title': '$text', 'path': '$path', 'mimeType': '$mimeType', 'text': '$text'}; + } +} diff --git a/lib/src/share/share.dart b/lib/src/share/share.dart index bc9ee1dc..c995eb0a 100644 --- a/lib/src/share/share.dart +++ b/lib/src/share/share.dart @@ -54,7 +54,7 @@ import 'package:ox_coi/src/navigation/navigatable.dart'; import 'package:ox_coi/src/navigation/navigation.dart'; import 'package:ox_coi/src/share/share_bloc.dart'; import 'package:ox_coi/src/share/share_event_state.dart'; -import 'package:ox_coi/src/share/shared_data.dart'; +import 'package:ox_coi/src/share/incoming_shared_data.dart'; import 'package:ox_coi/src/ui/dimensions.dart'; import 'package:ox_coi/src/utils/key_generator.dart'; import 'package:ox_coi/src/widgets/dynamic_appbar.dart'; @@ -63,7 +63,7 @@ import 'package:ox_coi/src/widgets/state_info.dart'; class Share extends StatefulWidget { final List msgIds; final MessageActionTag messageActionTag; - final SharedData sharedData; + final IncomingSharedData sharedData; Share({this.msgIds, this.messageActionTag, this.sharedData}); diff --git a/lib/src/share/share_bloc.dart b/lib/src/share/share_bloc.dart index 2358cdc3..94155b34 100644 --- a/lib/src/share/share_bloc.dart +++ b/lib/src/share/share_bloc.dart @@ -42,19 +42,18 @@ import 'package:bloc/bloc.dart'; import 'package:delta_chat_core/delta_chat_core.dart'; -import 'package:flutter/services.dart'; import 'package:ox_coi/src/chatlist/chat_list_bloc.dart'; import 'package:ox_coi/src/chatlist/chat_list_event_state.dart'; import 'package:ox_coi/src/contact/contact_list_bloc.dart'; import 'package:ox_coi/src/contact/contact_list_event_state.dart'; import 'package:ox_coi/src/data/contact_repository.dart'; +import 'package:ox_coi/src/platform/method_channel.dart'; +import 'package:ox_coi/src/share/incoming_shared_data.dart'; import 'package:ox_coi/src/share/share_event_state.dart'; -import 'package:ox_coi/src/share/shared_data.dart'; class ShareBloc extends Bloc { ChatListBloc _chatListBloc = ChatListBloc(); ContactListBloc _contactListBloc = ContactListBloc(); - static const platform = const MethodChannel(SharedData.sharingChannelName); @override ShareState get initialState => ShareStateInitial(); @@ -133,10 +132,10 @@ class ShareBloc extends Bloc { return; } if (data.length > 0) { - var sharedData = SharedData(data); + var sharedData = IncomingSharedData(data); add(SharedDataLoaded(sharedData: sharedData)); } } - Future _getSharedData() async => await platform.invokeMethod('getSharedData'); + Future _getSharedData() async => await SharingChannel.instance.invokeMethod(SharingChannel.kMethodGetSharedData); } diff --git a/lib/src/share/share_event_state.dart b/lib/src/share/share_event_state.dart index 5e537660..0d28966f 100644 --- a/lib/src/share/share_event_state.dart +++ b/lib/src/share/share_event_state.dart @@ -41,7 +41,7 @@ */ import 'package:meta/meta.dart'; -import 'package:ox_coi/src/share/shared_data.dart'; +import 'package:ox_coi/src/share/incoming_shared_data.dart'; abstract class ShareEvent {} @@ -72,7 +72,7 @@ class ForwardMessages extends ShareEvent { class LoadSharedData extends ShareEvent {} class SharedDataLoaded extends ShareEvent { - final SharedData sharedData; + final IncomingSharedData sharedData; SharedDataLoaded({@required this.sharedData}); } @@ -87,7 +87,7 @@ class ShareStateSuccess extends ShareState { final List chatAndContactIds; final int chatIdCount; final int contactIdCount; - final SharedData sharedData; + final IncomingSharedData sharedData; ShareStateSuccess({ this.chatAndContactIds, diff --git a/lib/src/user/user_profile.dart b/lib/src/user/user_profile.dart index 0b147f99..abb9abfc 100644 --- a/lib/src/user/user_profile.dart +++ b/lib/src/user/user_profile.dart @@ -55,7 +55,6 @@ import 'package:ox_coi/src/l10n/l10n.dart'; import 'package:ox_coi/src/main/main_bloc.dart'; import 'package:ox_coi/src/main/main_event_state.dart'; import 'package:ox_coi/src/main/root_child.dart'; -import 'package:ox_coi/src/message_list/message_list_bloc.dart'; import 'package:ox_coi/src/message_list/message_list_flagged.dart'; import 'package:ox_coi/src/navigation/navigatable.dart'; import 'package:ox_coi/src/navigation/navigation.dart'; diff --git a/lib/src/utils/constants.dart b/lib/src/utils/constants.dart index ac58ea38..7deb95e5 100644 --- a/lib/src/utils/constants.dart +++ b/lib/src/utils/constants.dart @@ -65,6 +65,8 @@ const customerOnboardingConfigPath = "assets/customer/json/onboarding.json"; const projectUrl = "https://coi.me"; const issueUrl = "https://github.com/open-xchange/ox-coi/issues"; const featureRequestUrl = "https://openxchange.userecho.com/communities/4-ox-coi-messenger"; +const kNotificationChannelMainId = 'com.android.oxcoi.notification.single'; +const kNotificationChannelGroupId = 'com.android.oxcoi.notification.group'; // IMAP https://tools.ietf.org/html/rfc5530 const imapErrorAuthenticationFailed = '[AUTHENTICATIONFAILED]'; diff --git a/lib/src/utils/core.dart b/lib/src/utils/core.dart index c09ccb7a..1842a145 100644 --- a/lib/src/utils/core.dart +++ b/lib/src/utils/core.dart @@ -41,7 +41,6 @@ */ import 'package:delta_chat_core/delta_chat_core.dart'; -import 'package:flutter/material.dart'; import 'package:ox_coi/src/l10n/l.dart'; import 'package:ox_coi/src/l10n/l10n.dart'; diff --git a/lib/src/utils/http.dart b/lib/src/utils/http.dart index f498df90..d0ae3727 100644 --- a/lib/src/utils/http.dart +++ b/lib/src/utils/http.dart @@ -45,7 +45,7 @@ import 'dart:io'; import 'package:http/http.dart'; import 'package:http/io_client.dart'; -bool validateHttpResponse(Response response) { +bool isHttpResponseValid(Response response) { return response.statusCode == 200 || response.statusCode == 201; } diff --git a/pubspec.lock b/pubspec.lock index 0a291709..418ab46f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -49,7 +49,7 @@ packages: name: background_fetch url: "https://pub.dartlang.org" source: hosted - version: "0.4.0" + version: "0.5.6" bloc: dependency: "direct main" description: @@ -266,7 +266,7 @@ packages: name: firebase_messaging url: "https://pub.dartlang.org" source: hosted - version: "6.0.9" + version: "6.0.16" fixnum: dependency: transitive description: @@ -311,7 +311,14 @@ packages: name: flutter_local_notifications url: "https://pub.dartlang.org" source: hosted - version: "0.9.1+3" + version: "1.4.2" + flutter_local_notifications_platform_interface: + dependency: transitive + description: + name: flutter_local_notifications_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" flutter_localizations: dependency: "direct main" description: flutter @@ -636,7 +643,7 @@ packages: name: permission_handler url: "https://pub.dartlang.org" source: hosted - version: "5.0.0+hotfix.6" + version: "5.0.0+hotfix.8" permission_handler_platform_interface: dependency: transitive description: @@ -757,7 +764,7 @@ packages: name: shared_preferences url: "https://pub.dartlang.org" source: hosted - version: "0.5.6" + version: "0.5.7+3" shared_preferences_macos: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index b98b6f0a..1ee40822 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,7 +12,7 @@ dependencies: flutter_localizations: sdk: flutter app_settings: ^3.0.0+1 - background_fetch: ^0.4.0 + background_fetch: ^0.5.6 bloc: ^3.0.0 cached_network_image: ^2.0.0 contacts_service: ^0.3.10 @@ -22,11 +22,11 @@ dependencies: date_format: ^1.0.8 device_info: ^0.4.2+1 equatable: ^1.0.2 - firebase_messaging: ^6.0.9 + firebase_messaging: ^6.0.16 file_picker: ^1.5.1 flutter_bloc: ^3.1.0 flutter_html: ^0.11.1 - flutter_local_notifications: ^0.9.1+3 + flutter_local_notifications: ^1.4.2 flutter_markdown: ^0.3.4 flutter_sound: git: @@ -49,10 +49,10 @@ dependencies: path: ^1.6.4 path_provider: ^1.5.1 provider: ^4.0.4 - permission_handler: ^5.0.0+hotfix.6 + permission_handler: ^5.0.0+hotfix.8 pointycastle: ^1.0.2 rxdart: ^0.23.1 - shared_preferences: ^0.5.6 + shared_preferences: ^0.5.7+3 sprintf: ^4.0.2 sqflite: ^1.3.0 superellipse_shape: ^0.1.5 diff --git a/test/push/push.dart b/test/push/push.dart index 15df5977..537a613b 100644 --- a/test/push/push.dart +++ b/test/push/push.dart @@ -44,12 +44,19 @@ import 'dart:convert'; import 'dart:math'; import 'dart:typed_data'; +import 'package:ox_coi/src/security/security_generator.dart'; import 'package:pointycastle/export.dart'; import 'package:test/test.dart'; import 'package:uuid/uuid.dart'; - void main() { + test("secure randoms", () { + final bytes = generateRandomBytes(); + print("Bytes size (16): ${bytes.length}"); + final bytes32 = generateRandomBytes(32); + print("Bytes size (32): ${bytes32.length}"); + }); + test('p256dh', () { var domainParameters = ECCurve_secp256r1(); var params = ECKeyGeneratorParameters(domainParameters); @@ -57,19 +64,20 @@ void main() { generator.init(ParametersWithRandom(params, getSecureRandom())); var generateKeyPair = generator.generateKeyPair(); ECPublicKey publicKey = generateKeyPair.publicKey; - print("Point: ${publicKey.Q}"); + print("Public Point: ${publicKey.Q}"); var bytes = utf8.encode(publicKey.Q.toString()); - var base64Str = base64Url.encode(bytes); - print("Base64 $base64Str"); + var base64Str = base64UrlEncode(bytes); + ECPrivateKey privateKey = generateKeyPair.privateKey; + print("Base64: $base64Str"); + print("Private Point: ${privateKey.d}"); }); test('UUID', () { var uuid = new Uuid(); - print("Secret ${uuid.v4()}"); + print("UUID ${uuid.v4()}"); }); } - SecureRandom getSecureRandom() { var secureRandom = FortunaRandom(); var random = Random.secure(); @@ -77,6 +85,6 @@ SecureRandom getSecureRandom() { for (int i = 0; i < 32; i++) { seeds.add(random.nextInt(255)); } - secureRandom.seed(new KeyParameter(Uint8List.fromList(seeds))); + secureRandom.seed(KeyParameter(Uint8List.fromList(seeds))); return secureRandom; } \ No newline at end of file