Skip to content

Commit 1dad9c5

Browse files
use secure coding and secure archiving for messaging (#4848)
1 parent 3a0e868 commit 1dad9c5

File tree

10 files changed

+153
-77
lines changed

10 files changed

+153
-77
lines changed

Example/Messaging/Tests/FIRMessagingPendingTopicsListTest.m

Lines changed: 1 addition & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -20,47 +20,7 @@
2020
#import "Firebase/Messaging/FIRMessagingDefines.h"
2121
#import "Firebase/Messaging/FIRMessagingPendingTopicsList.h"
2222
#import "Firebase/Messaging/FIRMessagingTopicsCommon.h"
23-
24-
typedef void (^MockDelegateSubscriptionHandler)(NSString *topic,
25-
FIRMessagingTopicAction action,
26-
FIRMessagingTopicOperationCompletion completion);
27-
28-
/**
29-
* This object lets us provide a stub delegate where we can customize the behavior by providing
30-
* blocks. We need to use this instead of stubbing a OCMockProtocol because our delegate methods
31-
* take primitive values (e.g. action), which is not easy to use from OCMock
32-
* @see http://stackoverflow.com/a/6332023
33-
*/
34-
@interface MockPendingTopicsListDelegate: NSObject <FIRMessagingPendingTopicsListDelegate>
35-
36-
@property(nonatomic, assign) BOOL isReady;
37-
@property(nonatomic, copy) MockDelegateSubscriptionHandler subscriptionHandler;
38-
@property(nonatomic, copy) void(^updateHandler)(void);
39-
40-
@end
41-
42-
@implementation MockPendingTopicsListDelegate
43-
44-
- (BOOL)pendingTopicsListCanRequestTopicUpdates:(FIRMessagingPendingTopicsList *)list {
45-
return self.isReady;
46-
}
47-
48-
- (void)pendingTopicsList:(FIRMessagingPendingTopicsList *)list
49-
requestedUpdateForTopic:(NSString *)topic
50-
action:(FIRMessagingTopicAction)action
51-
completion:(FIRMessagingTopicOperationCompletion)completion {
52-
if (self.subscriptionHandler) {
53-
self.subscriptionHandler(topic, action, completion);
54-
}
55-
}
56-
57-
- (void)pendingTopicsListDidUpdate:(FIRMessagingPendingTopicsList *)list {
58-
if (self.updateHandler) {
59-
self.updateHandler();
60-
}
61-
}
62-
63-
@end
23+
#import "Example/Messaging/Tests/FIRMessagingTestUtilities.h"
6424

6525
@interface FIRMessagingPendingTopicsListTest : XCTestCase
6626

@@ -220,7 +180,6 @@ - (void)testCompletionOfTopicUpdatesInSameThread {
220180
}
221181

222182
- (void)testAddingTopicToCurrentBatchWhileCurrentBatchTopicsInFlight {
223-
224183
FIRMessagingPendingTopicsList *pendingTopics = [[FIRMessagingPendingTopicsList alloc] init];
225184
pendingTopics.delegate = self.alwaysReadyDelegate;
226185

Example/Messaging/Tests/FIRMessagingPubSubTest.m

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,29 @@
1515
*/
1616

1717
#import <XCTest/XCTest.h>
18+
#import <OCMock/OCMock.h>
1819

20+
#import <GoogleUtilities/GULReachabilityChecker.h>
21+
22+
#import "Firebase/Messaging/FIRMessagingClient.h"
1923
#import "Firebase/Messaging/FIRMessagingPubSub.h"
24+
#import "Firebase/Messaging/FIRMessagingPendingTopicsList.h"
25+
#import "Firebase/Messaging/FIRMessagingRmqManager.h"
26+
#import "Example/Messaging/Tests/FIRMessagingTestUtilities.h"
27+
28+
@interface FIRMessagingPubSub (ExposedForTest)
29+
30+
@property(nonatomic, readwrite, strong) FIRMessagingPendingTopicsList *pendingTopicUpdates;
31+
- (void)archivePendingTopicsList:(FIRMessagingPendingTopicsList *)topicsList;
32+
- (void)restorePendingTopicsList;
33+
34+
@end
35+
36+
@interface FIRMessagingPendingTopicsList (ExposedForTest)
37+
38+
@property(nonatomic, strong) NSMutableArray <FIRMessagingTopicBatch *> *topicBatches;
39+
40+
@end
2041

2142
@interface FIRMessagingPubSubTest : XCTestCase
2243
@end
@@ -87,4 +108,33 @@ - (void)testRemoveTopicPrefix {
87108
XCTAssertEqualObjects(topic, kTopicName);
88109
}
89110

111+
-(void)testTopicListArchive {
112+
MockPendingTopicsListDelegate *notReadyDelegate = [[MockPendingTopicsListDelegate alloc] init];
113+
notReadyDelegate.isReady = NO;
114+
FIRMessagingPendingTopicsList *topicList = [[FIRMessagingPendingTopicsList alloc] init];
115+
topicList.delegate = notReadyDelegate;
116+
117+
// There should be 3 batches as actions are different than the last ones.
118+
[topicList addOperationForTopic:@"/topics/0" withAction:FIRMessagingTopicActionSubscribe completion:nil];
119+
[topicList addOperationForTopic:@"/topics/1" withAction:FIRMessagingTopicActionUnsubscribe completion:nil];
120+
[topicList addOperationForTopic:@"/topics/2" withAction:FIRMessagingTopicActionSubscribe completion:nil];
121+
XCTAssertEqual(topicList.numberOfBatches, 3);
122+
123+
id mockClientDelegate = OCMStrictProtocolMock(@protocol(FIRMessagingClientDelegate));
124+
id mockReachability = OCMClassMock([GULReachabilityChecker class]);
125+
id mockRmqManager = OCMClassMock([FIRMessagingRmqManager class]);
126+
FIRMessagingClient *client = [[FIRMessagingClient alloc]
127+
initWithDelegate:mockClientDelegate
128+
reachability:mockReachability
129+
rmq2Manager:mockRmqManager];
130+
FIRMessagingPubSub *pubSub = [[FIRMessagingPubSub alloc] initWithClient:client];
131+
[pubSub archivePendingTopicsList:topicList];
132+
[pubSub restorePendingTopicsList];
133+
XCTAssertEqual(pubSub.pendingTopicUpdates.numberOfBatches, 3);
134+
NSMutableArray <FIRMessagingTopicBatch *> *topicBatches = pubSub.pendingTopicUpdates.topicBatches;
135+
XCTAssertTrue([topicBatches[0].topics containsObject:@"/topics/0"]);
136+
XCTAssertTrue([topicBatches[1].topics containsObject:@"/topics/1"]);
137+
XCTAssertTrue([topicBatches[2].topics containsObject:@"/topics/2"]);
138+
}
139+
90140
@end

Example/Messaging/Tests/FIRMessagingTestUtilities.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,31 @@
1919
#import <FirebaseMessaging/FIRMessaging.h>
2020
#import <FirebaseInstanceID/FIRInstanceID.h>
2121

22+
#import "Firebase/Messaging/FIRMessagingTopicsCommon.h"
23+
#import "Firebase/Messaging/FIRMessagingPendingTopicsList.h"
24+
2225
@class GULUserDefaults;
2326

2427
NS_ASSUME_NONNULL_BEGIN
2528

29+
typedef void (^MockDelegateSubscriptionHandler)(NSString *topic,
30+
FIRMessagingTopicAction action,
31+
FIRMessagingTopicOperationCompletion completion);
32+
33+
/**
34+
* This object lets us provide a stub delegate where we can customize the behavior by providing
35+
* blocks. We need to use this instead of stubbing a OCMockProtocol because our delegate methods
36+
* take primitive values (e.g. action), which is not easy to use from OCMock
37+
* @see http://stackoverflow.com/a/6332023
38+
*/
39+
@interface MockPendingTopicsListDelegate: NSObject <FIRMessagingPendingTopicsListDelegate>
40+
41+
@property(nonatomic, assign) BOOL isReady;
42+
@property(nonatomic, copy) MockDelegateSubscriptionHandler subscriptionHandler;
43+
@property(nonatomic, copy) void(^updateHandler)(void);
44+
45+
@end
46+
2647
@interface FIRMessaging (TestUtilities)
2748
// Surface the user defaults instance to clean up after tests.
2849
@property(nonatomic, strong) NSUserDefaults *messagingUserDefaults;
@@ -45,4 +66,6 @@ NS_ASSUME_NONNULL_BEGIN
4566

4667
@end
4768

69+
70+
4871
NS_ASSUME_NONNULL_END

Example/Messaging/Tests/FIRMessagingTestUtilities.m

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,29 @@ - (void)removeDatabase;
6565

6666
@end
6767

68+
@implementation MockPendingTopicsListDelegate
69+
70+
- (BOOL)pendingTopicsListCanRequestTopicUpdates:(FIRMessagingPendingTopicsList *)list {
71+
return self.isReady;
72+
}
73+
74+
- (void)pendingTopicsList:(FIRMessagingPendingTopicsList *)list
75+
requestedUpdateForTopic:(NSString *)topic
76+
action:(FIRMessagingTopicAction)action
77+
completion:(FIRMessagingTopicOperationCompletion)completion {
78+
if (self.subscriptionHandler) {
79+
self.subscriptionHandler(topic, action, completion);
80+
}
81+
}
82+
83+
- (void)pendingTopicsListDidUpdate:(FIRMessagingPendingTopicsList *)list {
84+
if (self.updateHandler) {
85+
self.updateHandler();
86+
}
87+
}
88+
89+
@end
90+
6891
@implementation FIRMessagingTestUtilities
6992

7093
- (instancetype)initWithUserDefaults:(GULUserDefaults *)userDefaults withRMQManager:(BOOL)withRMQManager {

Firebase/Messaging/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# unreleased
2+
- [fixed] Use secure coding for Messaging's pending topics. (#3686)
3+
14
# 2020-02 -- v 4.2.1
25
- [added] Firebase Pod support for watchOS: `pod 'Firebase/Messaging'` in addition to `pod 'FirebaseMessaging'`. (#4807)
36
- [fixed] Fix FIRMessagingExtensionHelper crash in unit tests when `attachment == nil`. (#4689)

Firebase/Messaging/FIRMMessageCode.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,9 @@ typedef NS_ENUM(NSInteger, FIRMessagingMessageCode) {
119119
kFIRMessagingMessageCodePubSub001 = 9001, // I-FCM009001
120120
kFIRMessagingMessageCodePubSub002 = 9002, // I-FCM009002
121121
kFIRMessagingMessageCodePubSub003 = 9003, // I-FCM009003
122+
kFIRMessagingMessageCodePubSubArchiveError = 9004,
123+
kFIRMessagingMessageCodePubSubUnarchiveError = 9005,
124+
122125
// FIRMessagingReceiver.m
123126
kFIRMessagingMessageCodeReceiver000 = 10000, // I-FCM010000
124127
kFIRMessagingMessageCodeReceiver001 = 10001, // I-FCM010001

Firebase/Messaging/FIRMessagingPendingTopicsList.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ NS_ASSUME_NONNULL_BEGIN
3030
* topics is unique, as it doesn't make sense to apply the same action to the same topic
3131
* repeatedly; the result would be the same as the first time.
3232
*/
33-
@interface FIRMessagingTopicBatch : NSObject <NSCoding>
33+
@interface FIRMessagingTopicBatch : NSObject <NSSecureCoding>
3434

3535
@property(nonatomic, readonly, assign) FIRMessagingTopicAction action;
3636
@property(nonatomic, readonly, copy) NSMutableSet <NSString *> *topics;
@@ -101,7 +101,7 @@ NS_ASSUME_NONNULL_BEGIN
101101
*
102102
* @see FIRMessagingPendingTopicsListDelegate
103103
*/
104-
@interface FIRMessagingPendingTopicsList : NSObject <NSCoding>
104+
@interface FIRMessagingPendingTopicsList : NSObject <NSSecureCoding>
105105

106106
@property(nonatomic, weak) NSObject <FIRMessagingPendingTopicsListDelegate> *delegate;
107107

Firebase/Messaging/FIRMessagingPendingTopicsList.m

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,10 @@
1717
#import "Firebase/Messaging/FIRMessagingPendingTopicsList.h"
1818

1919
#import "Firebase/Messaging/FIRMessaging_Private.h"
20+
#import "Firebase/Messaging/FIRMessagingDefines.h"
2021
#import "Firebase/Messaging/FIRMessagingLogger.h"
2122
#import "Firebase/Messaging/FIRMessagingPubSub.h"
2223

23-
#import "Firebase/Messaging/FIRMessagingDefines.h"
24-
2524
NSString *const kPendingTopicBatchActionKey = @"action";
2625
NSString *const kPendingTopicBatchTopicsKey = @"topics";
2726

@@ -48,7 +47,11 @@ - (instancetype)initWithAction:(FIRMessagingTopicAction)action {
4847
return self;
4948
}
5049

51-
#pragma mark NSCoding
50+
#pragma mark NSSecureCoding
51+
52+
+ (BOOL)supportsSecureCoding {
53+
return YES;
54+
}
5255

5356
- (void)encodeWithCoder:(NSCoder *)aCoder {
5457
[aCoder encodeInteger:self.action forKey:kPendingTopicBatchActionKey];
@@ -65,10 +68,9 @@ - (instancetype)initWithCoder:(NSCoder *)aDecoder {
6568
}
6669

6770
if (self = [self initWithAction:action]) {
68-
NSSet *topics = [aDecoder decodeObjectForKey:kPendingTopicBatchTopicsKey];
69-
if ([topics isKindOfClass:[NSSet class]]) {
70-
_topics = [topics mutableCopy];
71-
}
71+
_topics = [aDecoder decodeObjectOfClasses:
72+
[NSSet setWithObjects:NSMutableSet.class, NSString.class, nil]
73+
forKey:kPendingTopicBatchTopicsKey];
7274
_topicHandlers = [NSMutableDictionary dictionary];
7375
}
7476
return self;
@@ -109,7 +111,11 @@ + (void)pruneTopicBatches:(NSMutableArray <FIRMessagingTopicBatch *> *)topicBatc
109111
}
110112
}
111113

112-
#pragma mark NSCoding
114+
#pragma mark NSSecureCoding
115+
116+
+ (BOOL)supportsSecureCoding {
117+
return YES;
118+
}
113119

114120
- (void)encodeWithCoder:(NSCoder *)aCoder {
115121
[aCoder encodeObject:[NSDate date] forKey:kPendingTopicsTimestampEncodingKey];
@@ -119,10 +125,13 @@ - (void)encodeWithCoder:(NSCoder *)aCoder {
119125
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder {
120126

121127
if (self = [self init]) {
122-
_archiveDate = [aDecoder decodeObjectForKey:kPendingTopicsTimestampEncodingKey];
123-
NSArray *archivedBatches = [aDecoder decodeObjectForKey:kPendingBatchesEncodingKey];
124-
if (archivedBatches) {
125-
_topicBatches = [archivedBatches mutableCopy];
128+
_archiveDate = [aDecoder decodeObjectOfClass:NSDate.class
129+
forKey:kPendingTopicsTimestampEncodingKey];
130+
_topicBatches =
131+
[aDecoder decodeObjectOfClasses:
132+
[NSSet setWithObjects:NSMutableArray.class,FIRMessagingTopicBatch.class, nil]
133+
forKey:kPendingBatchesEncodingKey];
134+
if (_topicBatches) {
126135
[FIRMessagingPendingTopicsList pruneTopicBatches:_topicBatches];
127136
}
128137
_topicsInFlight = [NSMutableSet set];

Firebase/Messaging/FIRMessagingPubSub.m

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
#import "Firebase/Messaging/FIRMessagingPubSub.h"
1818

19+
#import <GoogleUtilities/GULSecureCoding.h>
1920
#import <GoogleUtilities/GULUserDefaults.h>
2021
#import <FirebaseMessaging/FIRMessaging.h>
2122

@@ -183,10 +184,14 @@ - (BOOL)pendingTopicsListCanRequestTopicUpdates:(FIRMessagingPendingTopicsList *
183184

184185
- (void)archivePendingTopicsList:(FIRMessagingPendingTopicsList *)topicsList {
185186
GULUserDefaults *defaults = [GULUserDefaults standardUserDefaults];
186-
#pragma clang diagnostic push
187-
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
188-
NSData *pendingData = [NSKeyedArchiver archivedDataWithRootObject:topicsList];
189-
#pragma clang diagnostic pop
187+
NSError *error;
188+
NSData *pendingData = [GULSecureCoding archivedDataWithRootObject:topicsList error:&error];
189+
if (error) {
190+
FIRMessagingLoggerError(kFIRMessagingMessageCodePubSubArchiveError,
191+
@"Failed to archive topic list data %@",
192+
error);
193+
return;
194+
}
190195
[defaults setObject:pendingData forKey:kPendingSubscriptionsListKey];
191196
[defaults synchronize];
192197
}
@@ -195,23 +200,24 @@ - (void)restorePendingTopicsList {
195200
GULUserDefaults *defaults = [GULUserDefaults standardUserDefaults];
196201
NSData *pendingData = [defaults objectForKey:kPendingSubscriptionsListKey];
197202
FIRMessagingPendingTopicsList *subscriptions;
198-
@try {
199-
if (pendingData) {
200-
#pragma clang diagnostic push
201-
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
202-
subscriptions = [NSKeyedUnarchiver unarchiveObjectWithData:pendingData];
203-
#pragma clang diagnostic pop
204-
}
205-
} @catch (NSException *exception) {
206-
// Nothing we can do, just continue as if we don't have pending subscriptions
207-
} @finally {
208-
if (subscriptions) {
209-
self.pendingTopicUpdates = subscriptions;
210-
} else {
211-
self.pendingTopicUpdates = [[FIRMessagingPendingTopicsList alloc] init];
203+
if (pendingData) {
204+
NSError *error;
205+
subscriptions = [GULSecureCoding unarchivedObjectOfClasses:
206+
[NSSet setWithObjects:FIRMessagingPendingTopicsList.class, nil]
207+
fromData:pendingData
208+
error:&error];
209+
if (error) {
210+
FIRMessagingLoggerError(kFIRMessagingMessageCodePubSubUnarchiveError,
211+
@"Failed to unarchive topic list data %@",
212+
error);
212213
}
213-
self.pendingTopicUpdates.delegate = self;
214214
}
215+
if (subscriptions) {
216+
self.pendingTopicUpdates = subscriptions;
217+
} else {
218+
self.pendingTopicUpdates = [[FIRMessagingPendingTopicsList alloc] init];
219+
}
220+
self.pendingTopicUpdates.delegate = self;
215221
}
216222

217223
#pragma mark - Private Helpers

GoogleUtilities/Environment/GULSecureCoding.m

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ + (nullable id)unarchivedObjectOfClasses:(NSSet<Class> *)classes
2323
error:(NSError **)outError {
2424
id object;
2525
#if __has_builtin(__builtin_available)
26-
if (@available(macOS 10.13, iOS 11.0, tvOS 11.0, *)) {
26+
if (@available(macOS 10.13, iOS 11.0, tvOS 11.0, watchOS 4.0, *)) {
2727
object = [NSKeyedUnarchiver unarchivedObjectOfClasses:classes fromData:data error:outError];
2828
} else
2929
#endif // __has_builtin(__builtin_available)
@@ -62,7 +62,7 @@ + (nullable id)unarchivedObjectOfClass:(Class)class
6262
+ (nullable NSData *)archivedDataWithRootObject:(id<NSCoding>)object error:(NSError **)outError {
6363
NSData *archiveData;
6464
#if __has_builtin(__builtin_available)
65-
if (@available(macOS 10.13, iOS 11.0, tvOS 11.0, *)) {
65+
if (@available(macOS 10.13, iOS 11.0, tvOS 11.0, watchOS 4.0, *)) {
6666
archiveData = [NSKeyedArchiver archivedDataWithRootObject:object
6767
requiringSecureCoding:YES
6868
error:outError];

0 commit comments

Comments
 (0)