diff --git a/.pubnub.yml b/.pubnub.yml index 1436e626..6163e3e3 100644 --- a/.pubnub.yml +++ b/.pubnub.yml @@ -1,5 +1,15 @@ --- changelog: + - + changes: + - + text: "Fixes issue of exception from server when publishKey os null with publish call." + type: bug + - + text: "Fixes missing url component in file publish message for sendFile and support for message encryption." + type: bug + date: Aug 5, 20 + version: v1.4.3 - changes: - @@ -256,4 +266,4 @@ supported-platforms: platforms: - "Dart SDK >=2.6.0 <3.0.0" version: "PubNub Dart SDK" -version: "1.4.2" +version: "1.4.3" diff --git a/CHANGELOG.md b/CHANGELOG.md index 933c830c..ef75544e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## [v1.4.3](https://github.com/pubnub/dart/releases/tag/v1.4.3) +August 5 2020 + +[Full Changelog](https://github.com/pubnub/dart/compare/v1.4.2...v1.4.3) + +- 🐛 Fixes issue of exception from server when publishKey os null with publish call. +- 🐛 Fixes missing url component in file publish message for sendFile and support for message encryption. + ## [v1.4.2](https://github.com/pubnub/dart/releases/tag/v1.4.2) July 27 2020 diff --git a/lib/src/core/core.dart b/lib/src/core/core.dart index becd6343..edf4d937 100644 --- a/lib/src/core/core.dart +++ b/lib/src/core/core.dart @@ -23,7 +23,7 @@ class Core { ParserModule parser; CryptoModule crypto; - static String version = '1.4.2'; + static String version = '1.4.3'; Core( {Keyset defaultKeyset, diff --git a/lib/src/dx/_endpoints/history.dart b/lib/src/dx/_endpoints/history.dart index a109c4d4..a6842737 100644 --- a/lib/src/dx/_endpoints/history.dart +++ b/lib/src/dx/_endpoints/history.dart @@ -1,6 +1,8 @@ import 'package:pubnub/src/core/core.dart'; import 'package:pubnub/src/dx/_utils/utils.dart'; +typedef decryptFunction = List Function(CipherKey key, String data); + class FetchHistoryParams extends Parameters { Keyset keyset; String channel; @@ -113,10 +115,13 @@ class BatchHistoryResultEntry { BatchHistoryResultEntry._(); - factory BatchHistoryResultEntry.fromJson(Map object) { + factory BatchHistoryResultEntry.fromJson(Map object, + {CipherKey cipherKey, Function decryptFunction}) { return BatchHistoryResultEntry._() ..timetoken = Timetoken(object['timestamp'] as int) - ..message = object['message']; + ..message = cipherKey == null + ? object['message'] + : decryptFunction(cipherKey, object['message']); } } @@ -125,7 +130,8 @@ class BatchHistoryResult extends Result { BatchHistoryResult._(); - factory BatchHistoryResult.fromJson(Map object) { + factory BatchHistoryResult.fromJson(Map object, + {CipherKey cipherKey, Function decryptFunction}) { var result = DefaultResult.fromJson(object); return BatchHistoryResult._() @@ -133,7 +139,8 @@ class BatchHistoryResult extends Result { (key, value) => MapEntry( key, (value as List) - .map((entry) => BatchHistoryResultEntry.fromJson(entry)) + .map((entry) => BatchHistoryResultEntry.fromJson(entry, + cipherKey: cipherKey, decryptFunction: decryptFunction)) .toList())); } } diff --git a/lib/src/dx/batch/batch.dart b/lib/src/dx/batch/batch.dart index 525c8788..d5676fef 100644 --- a/lib/src/dx/batch/batch.dart +++ b/lib/src/dx/batch/batch.dart @@ -29,7 +29,9 @@ class BatchDx { end: end, reverse: reverse, includeMeta: includeMeta), - serialize: (object, [_]) => BatchHistoryResult.fromJson(object)); + serialize: (object, [_]) => BatchHistoryResult.fromJson(object, + cipherKey: keyset.cipherKey, + decryptFunction: _core.crypto.decrypt)); } /// Get multiple channels' message count using one REST call. diff --git a/lib/src/dx/file/file.dart b/lib/src/dx/file/file.dart index a9339d56..f531d152 100644 --- a/lib/src/dx/file/file.dart +++ b/lib/src/dx/file/file.dart @@ -18,7 +18,7 @@ class FileDx { /// This method allows to send [file] to [channel] /// If file upload operation , It also publish [fileMessage] along with file data `fileId` and `fileName` /// - /// Provide [cipherKey] to encrypt file content if you want to override default `cipherKey` of `Keyset` + /// Provide [cipherKey] to encrypt file content & fileEvent message if you want to override default `cipherKey` of `Keyset` /// * It gives priority of [cipherKey] provided in method argument over `keyset`'s `cipherKey` /// /// It retries for publishing [fileMessage] till default value of PubNub configuration value @@ -51,6 +51,7 @@ class FileDx { Keyset keyset, String using}) async { keyset ??= _core.keysets.get(using, defaultIfNameIsNull: true); + Ensure(keyset.publishKey).isNotNull('publish key for file upload message'); var requestPayload = await _core.parser.encode(GenerateFileUploadUrlBody(fileName)); @@ -73,9 +74,11 @@ class FileDx { form['file'] = _fileManager.createMultipartFile(_fileManager.read(file), fileName: fileName); } - var publishMessage = FileMessage( - fileUploadDetails.data.map((k, v) => MapEntry('$k', '$v')), - message: fileMessage); + var fileInfo = fileUploadDetails.data.map((k, v) => MapEntry('$k', '$v')); + fileInfo['url'] = getFileUrl(channel, '${fileUploadDetails.data['id']}', + '${fileUploadDetails.data['name']}') + .toString(); + var publishMessage = FileMessage(fileInfo, message: fileMessage); var publishFileResult = PublishFileMessageResult(); var retryCount = keyset.fileMessagePublishRetryLimit; var s3Response = await customFlow( @@ -90,6 +93,7 @@ class FileDx { ttl: fileMessageTtl, storeMessage: storeFileMessage, meta: fileMessageMeta, + cipherKey: cipherKey, keyset: keyset, using: using); } catch (e) { @@ -110,6 +114,8 @@ class FileDx { /// In case `sendFile` method doesn't publish message to [channel], this method /// can be used to explicitly publish message /// + /// Provide [cipherKey] to encrypt `message` it takes precedence over `keyset`'s cipherKey + /// /// You can override the default account configuration on message /// saving using [storeMessage] flag - `true` to save and `false` to discard. /// Leave this option unset if you want to use the default. @@ -128,12 +134,17 @@ class FileDx { {bool storeMessage, int ttl, dynamic meta, + CipherKey cipherKey, Keyset keyset, String using}) async { keyset ??= _core.keysets.get(using, defaultIfNameIsNull: true); Ensure(keyset.publishKey).isNotNull('publish key'); var messagePayload = await _core.parser.encode(message); + if (cipherKey != null || keyset.cipherKey != null) { + messagePayload = await _core.parser.encode( + _core.crypto.encrypt(cipherKey ?? keyset.cipherKey, messagePayload)); + } if (meta != null) meta = await _core.parser.encode(meta); return defaultFlow( logger: _logger, diff --git a/lib/src/dx/publish/publish.dart b/lib/src/dx/publish/publish.dart index e94ff01e..980c9857 100644 --- a/lib/src/dx/publish/publish.dart +++ b/lib/src/dx/publish/publish.dart @@ -27,6 +27,7 @@ mixin PublishDx on Core { keyset ??= super.keysets.get(using, defaultIfNameIsNull: true); Ensure(keyset).isNotNull('keyset'); + Ensure(keyset.publishKey).isNotNull('publishKey'); var payload = await super.parser.encode(message); diff --git a/lib/src/dx/subscribe/subscription.dart b/lib/src/dx/subscribe/subscription.dart index 8a81f710..e22410a5 100644 --- a/lib/src/dx/subscribe/subscription.dart +++ b/lib/src/dx/subscribe/subscription.dart @@ -85,7 +85,9 @@ class Subscription extends Disposable { (presenceChannels.contains(envelope['c']) || presenceChannelGroups.contains(envelope['b']))); }).asyncMap((envelope) async { - if (envelope['e'] == null && _keyset.cipherKey != null) { + if ((envelope['e'] == null || envelope['e'] == 4) && + !envelope['b'].endsWith('-pnpres') && + _keyset.cipherKey != null) { envelope['d'] = await _core.parser .decode(_core.crypto.decrypt(_keyset.cipherKey, envelope['d'])); } diff --git a/pubspec.yaml b/pubspec.yaml index d20d1d24..6152abc1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: pubnub description: PubNub SDK v5 for Dart lang (with Flutter support) that allows you to create real-time applications -version: 1.4.2 +version: 1.4.3 homepage: https://www.pubnub.com/docs environment: diff --git a/test/dx/file_test.dart b/test/dx/file_test.dart index 03da0cd5..85cca6ce 100644 --- a/test/dx/file_test.dart +++ b/test/dx/file_test.dart @@ -48,6 +48,53 @@ void main() { expect(result, isA()); }); + test('#publishFileMessage withEncryption', () async { + when( + path: _publishFileMessageUrlEncryption, + method: 'GET', + ).then(status: 200, body: _publishFileMessageSuccessResponse); + var message = + FileMessage({'id': 'some', 'name': 'cat_file.jpg'}, message: 'msg'); + var result = await pubnub.files.publishFileMessage('channel', message, + cipherKey: CipherKey.fromUtf8('cipherKey')); + expect(result, isA()); + }); + + test('#publishFileMessage withEncryption defaultKeyset', () async { + pubnub = PubNub( + networking: FakeNetworkingModule(), + defaultKeyset: Keyset( + subscribeKey: 'test', + publishKey: 'test', + cipherKey: CipherKey.fromUtf8('cipherKey'))); + when( + path: _publishFileMessageUrlEncryption, + method: 'GET', + ).then(status: 200, body: _publishFileMessageSuccessResponse); + var message = + FileMessage({'id': 'some', 'name': 'cat_file.jpg'}, message: 'msg'); + var result = await pubnub.files.publishFileMessage('channel', message); + expect(result, isA()); + }); + + test('#publishFileMessage cipherKey precedence', () async { + pubnub = PubNub( + networking: FakeNetworkingModule(), + defaultKeyset: Keyset( + subscribeKey: 'test', + publishKey: 'test', + cipherKey: CipherKey.fromUtf8('default_cipherKey'))); + when( + path: _publishFileMessageUrlEncryption, + method: 'GET', + ).then(status: 200, body: _publishFileMessageSuccessResponse); + var message = + FileMessage({'id': 'some', 'name': 'cat_file.jpg'}, message: 'msg'); + var result = await pubnub.files.publishFileMessage('channel', message, + cipherKey: CipherKey.fromUtf8('cipherKey')); + expect(result, isA()); + }); + test('#publishFileMessage failure', () async { when( path: _publishFileMessageUrl1, @@ -72,8 +119,9 @@ void main() { expect(result, isA()); }); test('#SendFile', () async { - var pubnub = FakePubNub(); var keyset = Keyset(subscribeKey: 'test', publishKey: 'test'); + var pubnub = FakePubNub() + ..keysets.add(keyset, name: 'default', useAsDefault: true); when( path: _generateFileUploadUrl, method: 'POST', @@ -92,8 +140,9 @@ void main() { }); test('#SendFile #FileMessagePublish retry', () async { - var pubnub = FakePubNub(); var keyset = Keyset(subscribeKey: 'test', publishKey: 'test'); + var pubnub = FakePubNub() + ..keysets.add(keyset, name: 'default', useAsDefault: true); when( path: _generateFileUploadUrl, method: 'POST', diff --git a/test/dx/fixtures/file.dart b/test/dx/fixtures/file.dart index 0aeb7aae..a889e9c9 100644 --- a/test/dx/fixtures/file.dart +++ b/test/dx/fixtures/file.dart @@ -123,8 +123,11 @@ final _generateFileUploadUrlResponse = ''' var _publishFileMessageUrl1 = 'v1/files/publish-file/test/test/0/channel/0/%7B%22message%22:%22msg%22,%22file%22:%7B%22id%22:%22some%22,%22name%22:%22cat_file.jpg%22%7D%7D?pnsdk=PubNub-Dart%2F${PubNub.version}'; +var _publishFileMessageUrlEncryption = + 'v1/files/publish-file/test/test/0/channel/0/%22X3LuZh36Z3vi4HFJSxdqD7XN%2FTsyUiPBmDfVaRipvaYs8wQE6OOloLTjGSTnZXIb0knFDIr8jPniWrnUYtdoTQ==%22?pnsdk=PubNub-Dart%2F${PubNub.version}'; + var _publishFileMessageUrl2 = - 'v1/files/publish-file/test/test/0/channel/0/%7B%22message%22:%22msg%22,%22file%22:%7B%22id%22:%225a3eb38c-483a-4b25-ac01-c4e20deba6d6%22,%22name%22:%22cat_file.jpg%22%7D%7D?pnsdk=PubNub-Dart%2F${PubNub.version}'; + 'v1/files/publish-file/test/test/0/channel/0/%7B%22message%22:%22msg%22,%22file%22:%7B%22id%22:%225a3eb38c-483a-4b25-ac01-c4e20deba6d6%22,%22name%22:%22cat_file.jpg%22,%22url%22:%22https:%2F%2Fps.pndsn.com%2Fv1%2Ffiles%2Ftest%2Fchannels%2Fchannel%2Ffiles%2F5a3eb38c-483a-4b25-ac01-c4e20deba6d6%2Fcat_file.jpg%3Fpnsdk=PubNub-Dart%252F1.4.2%22%7D%7D?pnsdk=PubNub-Dart%2F${PubNub.version}'; var _generateFileUploadUrl = 'v1/files/test/channels/channel/generate-upload-url?pnsdk=PubNub-Dart%2F${PubNub.version}';