Skip to content

Commit c687980

Browse files
authored
Correct OIDServiceDiscovery's NSCoding implementation (#721)
* Improve NSCoding implementation. * Test new and old encodings. * Ensure that we're handling the old encoding without errors. * No need to supply an empty failure string. * Provide forward compatibility.
1 parent d35a616 commit c687980

File tree

3 files changed

+165
-7
lines changed

3 files changed

+165
-7
lines changed

Source/AppAuthCore/OIDServiceConfiguration.m

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,8 +185,17 @@ - (nullable instancetype)initWithCoder:(NSCoder *)aDecoder {
185185
return nil;
186186
}
187187

188-
OIDServiceDiscovery *discoveryDocument = [aDecoder decodeObjectOfClasses:[NSSet setWithObjects:[OIDServiceDiscovery class], [NSArray class], nil]
189-
forKey:kDiscoveryDocumentKey];
188+
NSSet<Class> *allowedClasses = [NSSet setWithArray:@[[OIDServiceDiscovery class],
189+
// The following classes are required in
190+
// order to support secure decoding of the
191+
// old OIDServiceDiscovery encoding.
192+
[NSDictionary class],
193+
[NSArray class],
194+
[NSString class],
195+
[NSNumber class],
196+
[NSNull class]]];
197+
OIDServiceDiscovery *discoveryDocument = [aDecoder decodeObjectOfClasses:allowedClasses
198+
forKey:kDiscoveryDocumentKey];
190199

191200
return [self initWithAuthorizationEndpoint:authorizationEndpoint
192201
tokenEndpoint:tokenEndpoint

Source/AppAuthCore/OIDServiceDiscovery.m

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@
2323

2424
NS_ASSUME_NONNULL_BEGIN
2525

26+
/*! @brief The key for the @c discoveryDictionary property.
27+
*/
28+
static NSString *const kDiscoveryDictionaryKey = @"discoveryDictionary";
29+
2630
/*! Field keys associated with an OpenID Connect Discovery Document. */
2731
static NSString *const kIssuerKey = @"issuer";
2832
static NSString *const kAuthorizationEndpointKey = @"authorization_endpoint";
@@ -192,7 +196,27 @@ + (BOOL)supportsSecureCoding {
192196

193197
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder {
194198
NSError *error;
195-
NSDictionary *dictionary = [[NSDictionary alloc] initWithCoder:aDecoder];
199+
NSDictionary *dictionary;
200+
if ([aDecoder containsValueForKey:kDiscoveryDictionaryKey]) {
201+
// We're decoding a collection type (NSDictionary) from NSJSONSerialization's
202+
// +JSONObjectWithData, so we need to include all classes that could potentially be contained
203+
// within.
204+
NSSet<Class> *allowedClasses = [NSSet setWithArray:@[[NSDictionary class],
205+
[NSArray class],
206+
[NSString class],
207+
[NSNumber class],
208+
[NSNull class]]];
209+
dictionary = [aDecoder decodeObjectOfClasses:allowedClasses
210+
forKey:kDiscoveryDictionaryKey];
211+
} else {
212+
// Decode using the old encoding which delegated to NSDictionary's encodeWithCoder:
213+
// implementation:
214+
//
215+
// - (void)encodeWithCoder:(NSCoder *)aCoder {
216+
// [_discoveryDictionary encodeWithCoder:aCoder];
217+
// }
218+
dictionary = [[NSDictionary alloc] initWithCoder:aDecoder];
219+
}
196220
self = [self initWithDictionary:dictionary error:&error];
197221
if (error) {
198222
return nil;
@@ -201,6 +225,8 @@ - (nullable instancetype)initWithCoder:(NSCoder *)aDecoder {
201225
}
202226

203227
- (void)encodeWithCoder:(NSCoder *)aCoder {
228+
[aCoder encodeObject:_discoveryDictionary forKey:kDiscoveryDictionaryKey];
229+
// Provide forward compatibilty by continuing to add the old encoding.
204230
[_discoveryDictionary encodeWithCoder:aCoder];
205231
}
206232

UnitTests/OIDServiceDiscoveryTests.m

Lines changed: 127 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,42 @@
3030
#pragma GCC diagnostic push
3131
#pragma GCC diagnostic ignored "-Wgnu"
3232

33+
// Define a subclass of @c OIDServiceDiscovery that provides the old NSCoding decoding
34+
// implementation.
35+
@interface OIDServiceDiscoveryOldDecoding : OIDServiceDiscovery
36+
@end
37+
38+
@implementation OIDServiceDiscoveryOldDecoding
39+
40+
+ (BOOL)supportsSecureCoding {
41+
return YES;
42+
}
43+
44+
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder {
45+
NSError *error;
46+
NSDictionary *dictionary = [[NSDictionary alloc] initWithCoder:aDecoder];
47+
self = [self initWithDictionary:dictionary error:&error];
48+
if (error) {
49+
return nil;
50+
}
51+
return self;
52+
}
53+
54+
@end
55+
56+
// Define a subclass of @c OIDServiceDiscovery that provides the old NSCoding encoding
57+
// implementation.
58+
@interface OIDServiceDiscoveryOldEncoding : OIDServiceDiscovery
59+
@end
60+
61+
@implementation OIDServiceDiscoveryOldEncoding
62+
63+
- (void)encodeWithCoder:(NSCoder *)coder {
64+
[self.discoveryDictionary encodeWithCoder:coder];
65+
}
66+
67+
@end
68+
3369
/*! Testing URL used when testing URL conversions. */
3470
static NSString *const kTestURL = @"http://www.google.com/";
3571

@@ -110,7 +146,7 @@ + (NSDictionary *)completeServiceDiscoveryDictionary {
110146
kJWKSURLKey : @"http://www.example.com/jwks",
111147
kRegistrationEndpointKey : @"Registration Endpoint",
112148
kEndSessionEndpointKey : @"https://www.example.com/logout",
113-
kScopesSupportedKey : @"Scopes Supported",
149+
kScopesSupportedKey : @[ @"scope1", @"scope2" ],
114150
kResponseTypesSupportedKey : @"Response Types Supported",
115151
kResponseModesSupportedKey : @"Response Modes Supported",
116152
kGrantTypesSupportedKey : @"Grant Types Supported",
@@ -411,10 +447,97 @@ - (void)testSecureCoding {
411447
OIDServiceDiscovery *discovery =
412448
[[OIDServiceDiscovery alloc] initWithDictionary:serviceDiscoveryDictionary
413449
error:&error];
414-
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:discovery];
415-
OIDServiceDiscovery *unarchived = [NSKeyedUnarchiver unarchiveObjectWithData:data];
450+
NSData *data;
451+
if (@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, *)) {
452+
data = [NSKeyedArchiver archivedDataWithRootObject:discovery
453+
requiringSecureCoding:YES
454+
error:&error];
455+
} else {
456+
data = [NSKeyedArchiver archivedDataWithRootObject:discovery];
457+
}
458+
459+
OIDServiceDiscovery *unarchived;
460+
if (@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, *)) {
461+
unarchived = [NSKeyedUnarchiver unarchivedObjectOfClass:[OIDServiceDiscovery class]
462+
fromData:data
463+
error:&error];
464+
} else {
465+
unarchived = [NSKeyedUnarchiver unarchiveObjectWithData:data];
466+
}
467+
468+
XCTAssertEqualObjects(discovery.discoveryDictionary, unarchived.discoveryDictionary);
469+
}
416470

417-
XCTAssertEqualObjects(discovery.discoveryDictionary, unarchived.discoveryDictionary, @"");
471+
/*! @brief To ensure backward compatibility, test our ability to decode the old encoding of
472+
@c OIDServiceDiscovery.
473+
*/
474+
- (void)testSecureCodingDecodeOld {
475+
NSError *error;
476+
NSDictionary *serviceDiscoveryDictionary = [[self class] completeServiceDiscoveryDictionary];
477+
OIDServiceDiscoveryOldEncoding *discovery =
478+
[[OIDServiceDiscoveryOldEncoding alloc] initWithDictionary:serviceDiscoveryDictionary
479+
error:&error];
480+
NSData *data;
481+
if (@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, *)) {
482+
data = [NSKeyedArchiver archivedDataWithRootObject:discovery
483+
requiringSecureCoding:YES
484+
error:&error];
485+
} else {
486+
data = [NSKeyedArchiver archivedDataWithRootObject:discovery];
487+
}
488+
489+
OIDServiceDiscovery *unarchived;
490+
if (@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, *)) {
491+
NSSet<Class> *allowedClasses = [NSSet setWithArray:@[[OIDServiceDiscovery class],
492+
[NSDictionary class],
493+
[NSArray class],
494+
[NSString class],
495+
[NSNumber class],
496+
[NSNull class]]];
497+
unarchived = [NSKeyedUnarchiver unarchivedObjectOfClasses:allowedClasses
498+
fromData:data
499+
error:&error];
500+
} else {
501+
unarchived = [NSKeyedUnarchiver unarchiveObjectWithData:data];
502+
}
503+
504+
XCTAssertEqualObjects(discovery.discoveryDictionary, unarchived.discoveryDictionary);
505+
}
506+
507+
/*! @brief To ensure forward compatibility, test the ability of the old implementation to decode
508+
the new encoding of @c OIDServiceDiscovery.
509+
*/
510+
- (void)testSecureCodingOldDecodeNew {
511+
NSError *error;
512+
NSDictionary *serviceDiscoveryDictionary = [[self class] completeServiceDiscoveryDictionary];
513+
OIDServiceDiscoveryOldDecoding *discovery =
514+
[[OIDServiceDiscoveryOldDecoding alloc] initWithDictionary:serviceDiscoveryDictionary
515+
error:&error];
516+
NSData *data;
517+
if (@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, *)) {
518+
data = [NSKeyedArchiver archivedDataWithRootObject:discovery
519+
requiringSecureCoding:YES
520+
error:&error];
521+
} else {
522+
data = [NSKeyedArchiver archivedDataWithRootObject:discovery];
523+
}
524+
525+
OIDServiceDiscovery *unarchived;
526+
if (@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, *)) {
527+
NSSet<Class> *allowedClasses = [NSSet setWithArray:@[[OIDServiceDiscoveryOldDecoding class],
528+
[NSDictionary class],
529+
[NSArray class],
530+
[NSString class],
531+
[NSNumber class],
532+
[NSNull class]]];
533+
unarchived = [NSKeyedUnarchiver unarchivedObjectOfClasses:allowedClasses
534+
fromData:data
535+
error:&error];
536+
} else {
537+
unarchived = [NSKeyedUnarchiver unarchiveObjectWithData:data];
538+
}
539+
XCTAssertNil(error);
540+
XCTAssertEqualObjects(discovery.discoveryDictionary, unarchived.discoveryDictionary);
418541
}
419542

420543
/*! @brief Tests the NSCopying implementation by round-tripping an instance through the copying

0 commit comments

Comments
 (0)