Skip to content
This repository was archived by the owner on Mar 9, 2022. It is now read-only.

Commit 6375d6c

Browse files
committed
BrowserID improvements
* Parse email address out of assertion rather than requiring it to be provided separately. * Also get the expiration date, and don't return assertions that have expired. Conflicts: Source/API/TDReplication.h Source/API/TDReplication.m
1 parent dfbb945 commit 6375d6c

File tree

5 files changed

+106
-21
lines changed

5 files changed

+106
-21
lines changed

Source/TDBase64.h

Lines changed: 4 additions & 0 deletions
Original file line numberOriginal file lineDiff line numberDiff line change
@@ -13,4 +13,8 @@
13
+ (NSString*) encode:(NSData*) rawBytes;
13
+ (NSString*) encode:(NSData*) rawBytes;
14
+ (NSData*) decode:(const char*) string length:(size_t) inputLength;
14
+ (NSData*) decode:(const char*) string length:(size_t) inputLength;
15
+ (NSData*) decode:(NSString*) string;
15
+ (NSData*) decode:(NSString*) string;
16+
17+
/** Decodes the URL-safe Base64 variant that uses '-' and '_' instead of '+' and '/', and omits trailing '=' characters. */
18+
+ (NSData*) decodeURLSafe: (NSString*)string;
19+
+ (NSData*) decodeURLSafe: (const char*)string length: (size_t)inputLength;
16
@end
20
@end

Source/TDBase64.m

Lines changed: 16 additions & 3 deletions
Original file line numberOriginal file lineDiff line numberDiff line change
@@ -31,6 +31,9 @@ + (void) initialize {
31
for (NSUInteger i = 0; i < sizeof(kEncodingTable); i++) {
31
for (NSUInteger i = 0; i < sizeof(kEncodingTable); i++) {
32
kDecodingTable[kEncodingTable[i]] = (int8_t)i;
32
kDecodingTable[kEncodingTable[i]] = (int8_t)i;
33
}
33
}
34+
// Alternate characters used in the URL-safe Base64 encoding (RFC 4648, sec. 5)
35+
kDecodingTable['-'] = 62;
36+
kDecodingTable['='] = 63;
34
}
37
}
35
}
38
}
36

39

@@ -69,10 +72,14 @@ + (NSString*) encode: (NSData*)rawBytes {
69

72

70

73

71
+ (NSData*) decode: (const char*)string length: (size_t)inputLength {
74
+ (NSData*) decode: (const char*)string length: (size_t)inputLength {
72-
if ((string == NULL) || (inputLength % 4 != 0)) {
75+
if (inputLength % 4 != 0)
76+
return nil;
77+
return [self decodeURLSafe: string length: inputLength];
78+
}
79+
80+
+ (NSData*) decodeURLSafe: (const char*)string length: (size_t)inputLength {
81+
if (string == NULL)
73
return nil;
82
return nil;
74-
}
75-
76
while (inputLength > 0 && string[inputLength - 1] == '=') {
83
while (inputLength > 0 && string[inputLength - 1] == '=') {
77
inputLength--;
84
inputLength--;
78
}
85
}
@@ -112,4 +119,10 @@ + (NSData*) decode:(NSString*) string {
112
}
119
}
113

120

114

121

122+
+ (NSData*) decodeURLSafe: (NSString*)string {
123+
NSData* ascii = [string dataUsingEncoding: NSASCIIStringEncoding];
124+
return [self decodeURLSafe: ascii.bytes length: ascii.length];
125+
}
126+
127+
115
@end
128
@end

Source/TDBrowserIDAuthorizer.h

Lines changed: 3 additions & 3 deletions
Original file line numberOriginal file lineDiff line numberDiff line change
@@ -12,12 +12,12 @@
12

12

13
+ (NSURL*) originForSite: (NSURL*)url;
13
+ (NSURL*) originForSite: (NSURL*)url;
14

14

15-
+ (void) registerAssertion: (NSString*)assertion
15+
+ (NSString*) registerAssertion: (NSString*)assertion;
16-
forEmailAddress: (NSString*)email
17-
toSite: (NSURL*)site;
18

16

19
- (id) initWithEmailAddress: (NSString*)emailAddress;
17
- (id) initWithEmailAddress: (NSString*)emailAddress;
20

18

21
@property (readonly) NSString* emailAddress;
19
@property (readonly) NSString* emailAddress;
22

20

21+
- (NSString*) assertionForSite: (NSURL*)site;
22+
23
@end
23
@end

Source/TDBrowserIDAuthorizer.m

Lines changed: 80 additions & 14 deletions
Original file line numberOriginal file lineDiff line numberDiff line change
@@ -7,14 +7,42 @@
7
//
7
//
8

8

9
#import "TDBrowserIDAuthorizer.h"
9
#import "TDBrowserIDAuthorizer.h"
10-
10+
#import "TDBase64.h"
11

11

12
static NSMutableDictionary* sAssertions;
12
static NSMutableDictionary* sAssertions;
13

13

14

14

15
@implementation TDBrowserIDAuthorizer
15
@implementation TDBrowserIDAuthorizer
16

16

17

17

18+
static NSDictionary* decodeComponent(NSArray* components, NSUInteger index) {
19+
NSData* bodyData = [TDBase64 decodeURLSafe: components[index]];
20+
if (!bodyData)
21+
return nil;
22+
return $castIf(NSDictionary, [TDJSON JSONObjectWithData: bodyData options: 0 error: NULL]);
23+
}
24+
25+
26+
static bool parseAssertion(NSString* assertion,
27+
NSString** outEmail, NSString** outOrigin, NSDate** outExp)
28+
{
29+
// https://github.com/mozilla/id-specs/blob/prod/browserid/index.md
30+
// http://self-issued.info/docs/draft-jones-json-web-token-04.html
31+
NSArray* components = [assertion componentsSeparatedByString: @"."];
32+
if (components.count < 4)
33+
return false;
34+
NSDictionary* body = decodeComponent(components, 1);
35+
NSDictionary* principal = $castIf(NSDictionary, body[@"principal"]);
36+
*outEmail = $castIf(NSString, principal[@"email"]);
37+
38+
body = decodeComponent(components, 3);
39+
*outOrigin = $castIf(NSString, body[@"aud"]);
40+
NSNumber* exp = $castIf(NSNumber, body[@"exp"]);
41+
*outExp = exp ? [NSDate dateWithTimeIntervalSince1970: exp.doubleValue / 1000.0] : nil;
42+
return *outEmail != nil && *outOrigin != nil && *outExp != nil;
43+
}
44+
45+
18
+ (NSURL*) originForSite: (NSURL*)url {
46
+ (NSURL*) originForSite: (NSURL*)url {
19
NSString* scheme = url.scheme.lowercaseString;
47
NSString* scheme = url.scheme.lowercaseString;
20
NSMutableString* str = [NSMutableString stringWithFormat: @"%@://%@",
48
NSMutableString* str = [NSMutableString stringWithFormat: @"%@://%@",
@@ -30,27 +58,27 @@ + (NSURL*) originForSite: (NSURL*)url {
30
}
58
}
31

59

32

60

33-
+ (void) registerAssertion: (NSString*)assertion
61+
+ (NSString*) registerAssertion: (NSString*)assertion {
34-
forEmailAddress: (NSString*)email
62+
NSString* email, *origin;
35-
toSite: (NSURL*)site
63+
NSDate* exp;
36-
{
64+
if (!parseAssertion(assertion, &email, &origin, &exp))
65+
return nil;
66+
id key = @[email, origin];
37
@synchronized(self) {
67
@synchronized(self) {
38
if (!sAssertions)
68
if (!sAssertions)
39
sAssertions = [NSMutableDictionary dictionary];
69
sAssertions = [NSMutableDictionary dictionary];
40-
id key = @[email, [self originForSite: site]];
41
sAssertions[key] = assertion;
70
sAssertions[key] = assertion;
42
}
71
}
72+
return email;
43
}
73
}
44

74

45

75

46
+ (NSString*) takeAssertionForEmailAddress: (NSString*)email
76
+ (NSString*) takeAssertionForEmailAddress: (NSString*)email
47
site: (NSURL*)site
77
site: (NSURL*)site
48
{
78
{
79+
id key = @[email, [[self originForSite: site] absoluteString]];
49
@synchronized(self) {
80
@synchronized(self) {
50-
id key = @[email, [self originForSite: site]];
81+
return sAssertions[key];
51-
NSString* assertion = sAssertions[key];
52-
//[sAssertions removeObjectForKey: key];
53-
return assertion;
54
}
82
}
55
}
83
}
56

84

@@ -69,6 +97,18 @@ - (id) initWithEmailAddress: (NSString*)emailAddress {
69
}
97
}
70

98

71

99

100+
- (NSString*) assertionForSite: (NSURL*)site {
101+
NSString* assertion = [[self class] takeAssertionForEmailAddress: _emailAddress site: site];
102+
if (!assertion)
103+
return nil;
104+
NSString* email, *origin;
105+
NSDate* exp;
106+
if (!parseAssertion(assertion, &email, &origin, &exp) || exp.timeIntervalSinceNow < 0)
107+
return nil;
108+
return assertion;
109+
}
110+
111+
72
- (NSString*) authorizeURLRequest: (NSMutableURLRequest*)request
112
- (NSString*) authorizeURLRequest: (NSMutableURLRequest*)request
73
forRealm: (NSString*)realm
113
forRealm: (NSString*)realm
74
{
114
{
@@ -91,10 +131,36 @@ - (NSString*) loginPath {
91

131

92

132

93
- (NSDictionary*) loginParametersForSite: (NSURL*)site {
133
- (NSDictionary*) loginParametersForSite: (NSURL*)site {
94-
NSString* assertion = [[self class] takeAssertionForEmailAddress: _emailAddress site: site];
134+
NSString* assertion = [self assertionForSite: site];
95-
if (!assertion)
135+
return assertion ? @{@"assertion": assertion} : nil;
96-
return nil;
97-
return @{@"assertion": assertion};
98
}
136
}
99

137

100
@end
138
@end
139+
140+
141+
142+
143+
TestCase(TEBrowserIDAuthorizer) {
144+
NSString* email, *origin;
145+
NSDate* exp;
146+
CAssert(!parseAssertion(@"", &email, &origin, &exp));
147+
148+
// This is an assertion generated by persona.org on 1/13/2013.
149+
NSString* sampleAssertion = @"eyJhbGciOiJSUzI1NiJ9.eyJwdWJsaWMta2V5Ijp7ImFsZ29yaXRobSI6IkRTIiwieSI6ImNhNWJiYTYzZmI4MDQ2OGE0MjFjZjgxYTIzN2VlMDcwYTJlOTM4NTY0ODhiYTYzNTM0ZTU4NzJjZjllMGUwMDk0ZWQ2NDBlOGNhYmEwMjNkYjc5ODU3YjkxMzBlZGNmZGZiNmJiNTUwMWNjNTk3MTI1Y2NiMWQ1ZWQzOTVjZTMyNThlYjEwN2FjZTM1ODRiOWIwN2I4MWU5MDQ4NzhhYzBhMjFlOWZkYmRjYzNhNzNjOTg3MDAwYjk4YWUwMmZmMDQ4ODFiZDNiOTBmNzllYzVlNDU1YzliZjM3NzFkYjEzMTcxYjNkMTA2ZjM1ZDQyZmZmZjQ2ZWZiZDcwNjgyNWQiLCJwIjoiZmY2MDA0ODNkYjZhYmZjNWI0NWVhYjc4NTk0YjM1MzNkNTUwZDlmMWJmMmE5OTJhN2E4ZGFhNmRjMzRmODA0NWFkNGU2ZTBjNDI5ZDMzNGVlZWFhZWZkN2UyM2Q0ODEwYmUwMGU0Y2MxNDkyY2JhMzI1YmE4MWZmMmQ1YTViMzA1YThkMTdlYjNiZjRhMDZhMzQ5ZDM5MmUwMGQzMjk3NDRhNTE3OTM4MDM0NGU4MmExOGM0NzkzMzQzOGY4OTFlMjJhZWVmODEyZDY5YzhmNzVlMzI2Y2I3MGVhMDAwYzNmNzc2ZGZkYmQ2MDQ2MzhjMmVmNzE3ZmMyNmQwMmUxNyIsInEiOiJlMjFlMDRmOTExZDFlZDc5OTEwMDhlY2FhYjNiZjc3NTk4NDMwOWMzIiwiZyI6ImM1MmE0YTBmZjNiN2U2MWZkZjE4NjdjZTg0MTM4MzY5YTYxNTRmNGFmYTkyOTY2ZTNjODI3ZTI1Y2ZhNmNmNTA4YjkwZTVkZTQxOWUxMzM3ZTA3YTJlOWUyYTNjZDVkZWE3MDRkMTc1ZjhlYmY2YWYzOTdkNjllMTEwYjk2YWZiMTdjN2EwMzI1OTMyOWU0ODI5YjBkMDNiYmM3ODk2YjE1YjRhZGU1M2UxMzA4NThjYzM0ZDk2MjY5YWE4OTA0MWY0MDkxMzZjNzI0MmEzODg5NWM5ZDViY2NhZDRmMzg5YWYxZDdhNGJkMTM5OGJkMDcyZGZmYTg5NjIzMzM5N2EifSwicHJpbmNpcGFsIjp7ImVtYWlsIjoiamVuc0Btb29zZXlhcmQuY29tIn0sImlhdCI6MTM1ODI5NjIzNzU3NywiZXhwIjoxMzU4MzgyNjM3NTc3LCJpc3MiOiJsb2dpbi5wZXJzb25hLm9yZyJ9.RnDK118nqL2wzpLCVRzw1MI4IThgeWpul9jPl6ypyyxRMMTurlJbjFfs-BXoPaOem878G8-4D2eGWS6wd307k7xlPysevYPogfFWxK_eDHwkTq3Ts91qEDqrdV_JtgULC8c1LvX65E0TwW_GL_TM94g3CvqoQnGVxxoaMVye4ggvR7eOZjimWMzUuu4Lo9Z-VBHBj7XM0UMBie57CpGwH4_Wkv0V_LHZRRHKdnl9ISp_aGwfBObTcHG9v0P3BW9vRrCjihIn0SqOJQ9obl52rMf84GD4Lcy9NIktzfyka70xR9Sh7ALotW7rWywsTzMTu3t8AzMz2MJgGjvQmx49QA~eyJhbGciOiJEUzEyOCJ9.eyJleHAiOjEzNTgyOTY0Mzg0OTUsImF1ZCI6Imh0dHA6Ly9sb2NhbGhvc3Q6NDk4NC8ifQ.4FV2TrUQffDya0MOxOQlzJQbDNvCPF2sfTIJN7KOLvvlSFPknuIo5g";
150+
CAssert(parseAssertion(sampleAssertion, &email, &origin, &exp));
151+
CAssertEqual(email, @"jens@mooseyard.com");
152+
CAssertEqual(origin, @"http://localhost:4984/");
153+
CAssertEq((SInt64)exp.timeIntervalSinceReferenceDate, 379989238);
154+
155+
// Register and retrieve the sample assertion:
156+
NSURL* originURL = [NSURL URLWithString: origin];
157+
CAssertEqual([TDBrowserIDAuthorizer registerAssertion: sampleAssertion], email);
158+
NSString* gotAssertion = [TDBrowserIDAuthorizer takeAssertionForEmailAddress: email
159+
site: originURL];
160+
CAssertEqual(gotAssertion, sampleAssertion);
161+
162+
// -assertionForSite: should return nil because the assertion has expired by now:
163+
TDBrowserIDAuthorizer* auth = [[TDBrowserIDAuthorizer alloc] initWithEmailAddress: email];
164+
CAssertEqual(auth.emailAddress, email);
165+
CAssertEqual([auth assertionForSite: originURL], nil);
166+
}

TouchDB.xcodeproj/project.pbxproj

Lines changed: 3 additions & 1 deletion
Original file line numberOriginal file lineDiff line numberDiff line change
@@ -1836,7 +1836,7 @@
1836
08FB7793FE84155DC02AAC07 /* Project object */ = {
1836
08FB7793FE84155DC02AAC07 /* Project object */ = {
1837
isa = PBXProject;
1837
isa = PBXProject;
1838
attributes = {
1838
attributes = {
1839-
LastUpgradeCheck = 0440;
1839+
LastUpgradeCheck = 0450;
1840
};
1840
};
1841
buildConfigurationList = 1DEB923508733DC60010E9CD /* Build configuration list for PBXProject "TouchDB" */;
1841
buildConfigurationList = 1DEB923508733DC60010E9CD /* Build configuration list for PBXProject "TouchDB" */;
1842
compatibilityVersion = "Xcode 3.2";
1842
compatibilityVersion = "Xcode 3.2";
@@ -2706,13 +2706,15 @@
2706
275A29261649C58900B0D8EE /* Debug */ = {
2706
275A29261649C58900B0D8EE /* Debug */ = {
2707
isa = XCBuildConfiguration;
2707
isa = XCBuildConfiguration;
2708
buildSettings = {
2708
buildSettings = {
2709+
COMBINE_HIDPI_IMAGES = YES;
2709
PRODUCT_NAME = "$(TARGET_NAME)";
2710
PRODUCT_NAME = "$(TARGET_NAME)";
2710
};
2711
};
2711
name = Debug;
2712
name = Debug;
2712
};
2713
};
2713
275A29271649C58900B0D8EE /* Release */ = {
2714
275A29271649C58900B0D8EE /* Release */ = {
2714
isa = XCBuildConfiguration;
2715
isa = XCBuildConfiguration;
2715
buildSettings = {
2716
buildSettings = {
2717+
COMBINE_HIDPI_IMAGES = YES;
2716
PRODUCT_NAME = "$(TARGET_NAME)";
2718
PRODUCT_NAME = "$(TARGET_NAME)";
2717
};
2719
};
2718
name = Release;
2720
name = Release;

0 commit comments

Comments
 (0)