Permalink
Browse files

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
  • Loading branch information...
1 parent dfbb945 commit 6375d6c72221d7daaa869f03c6759b4e7c1713cc @snej snej committed Jan 16, 2013
Showing with 106 additions and 21 deletions.
  1. +4 −0 Source/TDBase64.h
  2. +16 −3 Source/TDBase64.m
  3. +3 −3 Source/TDBrowserIDAuthorizer.h
  4. +80 −14 Source/TDBrowserIDAuthorizer.m
  5. +3 −1 TouchDB.xcodeproj/project.pbxproj
View
4 Source/TDBase64.h
@@ -13,4 +13,8 @@
+ (NSString*) encode:(NSData*) rawBytes;
+ (NSData*) decode:(const char*) string length:(size_t) inputLength;
+ (NSData*) decode:(NSString*) string;
+
+/** Decodes the URL-safe Base64 variant that uses '-' and '_' instead of '+' and '/', and omits trailing '=' characters. */
++ (NSData*) decodeURLSafe: (NSString*)string;
++ (NSData*) decodeURLSafe: (const char*)string length: (size_t)inputLength;
@end
View
19 Source/TDBase64.m
@@ -31,6 +31,9 @@ + (void) initialize {
for (NSUInteger i = 0; i < sizeof(kEncodingTable); i++) {
kDecodingTable[kEncodingTable[i]] = (int8_t)i;
}
+ // Alternate characters used in the URL-safe Base64 encoding (RFC 4648, sec. 5)
+ kDecodingTable['-'] = 62;
+ kDecodingTable['='] = 63;
}
}
@@ -69,10 +72,14 @@ + (NSString*) encode: (NSData*)rawBytes {
+ (NSData*) decode: (const char*)string length: (size_t)inputLength {
- if ((string == NULL) || (inputLength % 4 != 0)) {
+ if (inputLength % 4 != 0)
+ return nil;
+ return [self decodeURLSafe: string length: inputLength];
+}
+
++ (NSData*) decodeURLSafe: (const char*)string length: (size_t)inputLength {
+ if (string == NULL)
return nil;
- }
-
while (inputLength > 0 && string[inputLength - 1] == '=') {
inputLength--;
}
@@ -112,4 +119,10 @@ + (NSData*) decode:(NSString*) string {
}
++ (NSData*) decodeURLSafe: (NSString*)string {
+ NSData* ascii = [string dataUsingEncoding: NSASCIIStringEncoding];
+ return [self decodeURLSafe: ascii.bytes length: ascii.length];
+}
+
+
@end
View
6 Source/TDBrowserIDAuthorizer.h
@@ -12,12 +12,12 @@
+ (NSURL*) originForSite: (NSURL*)url;
-+ (void) registerAssertion: (NSString*)assertion
- forEmailAddress: (NSString*)email
- toSite: (NSURL*)site;
++ (NSString*) registerAssertion: (NSString*)assertion;
- (id) initWithEmailAddress: (NSString*)emailAddress;
@property (readonly) NSString* emailAddress;
+- (NSString*) assertionForSite: (NSURL*)site;
+
@end
View
94 Source/TDBrowserIDAuthorizer.m
@@ -7,14 +7,42 @@
//
#import "TDBrowserIDAuthorizer.h"
-
+#import "TDBase64.h"
static NSMutableDictionary* sAssertions;
@implementation TDBrowserIDAuthorizer
+static NSDictionary* decodeComponent(NSArray* components, NSUInteger index) {
+ NSData* bodyData = [TDBase64 decodeURLSafe: components[index]];
+ if (!bodyData)
+ return nil;
+ return $castIf(NSDictionary, [TDJSON JSONObjectWithData: bodyData options: 0 error: NULL]);
+}
+
+
+static bool parseAssertion(NSString* assertion,
+ NSString** outEmail, NSString** outOrigin, NSDate** outExp)
+{
+ // https://github.com/mozilla/id-specs/blob/prod/browserid/index.md
+ // http://self-issued.info/docs/draft-jones-json-web-token-04.html
+ NSArray* components = [assertion componentsSeparatedByString: @"."];
+ if (components.count < 4)
+ return false;
+ NSDictionary* body = decodeComponent(components, 1);
+ NSDictionary* principal = $castIf(NSDictionary, body[@"principal"]);
+ *outEmail = $castIf(NSString, principal[@"email"]);
+
+ body = decodeComponent(components, 3);
+ *outOrigin = $castIf(NSString, body[@"aud"]);
+ NSNumber* exp = $castIf(NSNumber, body[@"exp"]);
+ *outExp = exp ? [NSDate dateWithTimeIntervalSince1970: exp.doubleValue / 1000.0] : nil;
+ return *outEmail != nil && *outOrigin != nil && *outExp != nil;
+}
+
+
+ (NSURL*) originForSite: (NSURL*)url {
NSString* scheme = url.scheme.lowercaseString;
NSMutableString* str = [NSMutableString stringWithFormat: @"%@://%@",
@@ -30,27 +58,27 @@ + (NSURL*) originForSite: (NSURL*)url {
}
-+ (void) registerAssertion: (NSString*)assertion
- forEmailAddress: (NSString*)email
- toSite: (NSURL*)site
-{
++ (NSString*) registerAssertion: (NSString*)assertion {
+ NSString* email, *origin;
+ NSDate* exp;
+ if (!parseAssertion(assertion, &email, &origin, &exp))
+ return nil;
+ id key = @[email, origin];
@synchronized(self) {
if (!sAssertions)
sAssertions = [NSMutableDictionary dictionary];
- id key = @[email, [self originForSite: site]];
sAssertions[key] = assertion;
}
+ return email;
}
+ (NSString*) takeAssertionForEmailAddress: (NSString*)email
site: (NSURL*)site
{
+ id key = @[email, [[self originForSite: site] absoluteString]];
@synchronized(self) {
- id key = @[email, [self originForSite: site]];
- NSString* assertion = sAssertions[key];
- //[sAssertions removeObjectForKey: key];
- return assertion;
+ return sAssertions[key];
}
}
@@ -69,6 +97,18 @@ - (id) initWithEmailAddress: (NSString*)emailAddress {
}
+- (NSString*) assertionForSite: (NSURL*)site {
+ NSString* assertion = [[self class] takeAssertionForEmailAddress: _emailAddress site: site];
+ if (!assertion)
+ return nil;
+ NSString* email, *origin;
+ NSDate* exp;
+ if (!parseAssertion(assertion, &email, &origin, &exp) || exp.timeIntervalSinceNow < 0)
+ return nil;
+ return assertion;
+}
+
+
- (NSString*) authorizeURLRequest: (NSMutableURLRequest*)request
forRealm: (NSString*)realm
{
@@ -91,10 +131,36 @@ - (NSString*) loginPath {
- (NSDictionary*) loginParametersForSite: (NSURL*)site {
- NSString* assertion = [[self class] takeAssertionForEmailAddress: _emailAddress site: site];
- if (!assertion)
- return nil;
- return @{@"assertion": assertion};
+ NSString* assertion = [self assertionForSite: site];
+ return assertion ? @{@"assertion": assertion} : nil;
}
@end
+
+
+
+
+TestCase(TEBrowserIDAuthorizer) {
+ NSString* email, *origin;
+ NSDate* exp;
+ CAssert(!parseAssertion(@"", &email, &origin, &exp));
+
+ // This is an assertion generated by persona.org on 1/13/2013.
+ NSString* sampleAssertion = @"eyJhbGciOiJSUzI1NiJ9.eyJwdWJsaWMta2V5Ijp7ImFsZ29yaXRobSI6IkRTIiwieSI6ImNhNWJiYTYzZmI4MDQ2OGE0MjFjZjgxYTIzN2VlMDcwYTJlOTM4NTY0ODhiYTYzNTM0ZTU4NzJjZjllMGUwMDk0ZWQ2NDBlOGNhYmEwMjNkYjc5ODU3YjkxMzBlZGNmZGZiNmJiNTUwMWNjNTk3MTI1Y2NiMWQ1ZWQzOTVjZTMyNThlYjEwN2FjZTM1ODRiOWIwN2I4MWU5MDQ4NzhhYzBhMjFlOWZkYmRjYzNhNzNjOTg3MDAwYjk4YWUwMmZmMDQ4ODFiZDNiOTBmNzllYzVlNDU1YzliZjM3NzFkYjEzMTcxYjNkMTA2ZjM1ZDQyZmZmZjQ2ZWZiZDcwNjgyNWQiLCJwIjoiZmY2MDA0ODNkYjZhYmZjNWI0NWVhYjc4NTk0YjM1MzNkNTUwZDlmMWJmMmE5OTJhN2E4ZGFhNmRjMzRmODA0NWFkNGU2ZTBjNDI5ZDMzNGVlZWFhZWZkN2UyM2Q0ODEwYmUwMGU0Y2MxNDkyY2JhMzI1YmE4MWZmMmQ1YTViMzA1YThkMTdlYjNiZjRhMDZhMzQ5ZDM5MmUwMGQzMjk3NDRhNTE3OTM4MDM0NGU4MmExOGM0NzkzMzQzOGY4OTFlMjJhZWVmODEyZDY5YzhmNzVlMzI2Y2I3MGVhMDAwYzNmNzc2ZGZkYmQ2MDQ2MzhjMmVmNzE3ZmMyNmQwMmUxNyIsInEiOiJlMjFlMDRmOTExZDFlZDc5OTEwMDhlY2FhYjNiZjc3NTk4NDMwOWMzIiwiZyI6ImM1MmE0YTBmZjNiN2U2MWZkZjE4NjdjZTg0MTM4MzY5YTYxNTRmNGFmYTkyOTY2ZTNjODI3ZTI1Y2ZhNmNmNTA4YjkwZTVkZTQxOWUxMzM3ZTA3YTJlOWUyYTNjZDVkZWE3MDRkMTc1ZjhlYmY2YWYzOTdkNjllMTEwYjk2YWZiMTdjN2EwMzI1OTMyOWU0ODI5YjBkMDNiYmM3ODk2YjE1YjRhZGU1M2UxMzA4NThjYzM0ZDk2MjY5YWE4OTA0MWY0MDkxMzZjNzI0MmEzODg5NWM5ZDViY2NhZDRmMzg5YWYxZDdhNGJkMTM5OGJkMDcyZGZmYTg5NjIzMzM5N2EifSwicHJpbmNpcGFsIjp7ImVtYWlsIjoiamVuc0Btb29zZXlhcmQuY29tIn0sImlhdCI6MTM1ODI5NjIzNzU3NywiZXhwIjoxMzU4MzgyNjM3NTc3LCJpc3MiOiJsb2dpbi5wZXJzb25hLm9yZyJ9.RnDK118nqL2wzpLCVRzw1MI4IThgeWpul9jPl6ypyyxRMMTurlJbjFfs-BXoPaOem878G8-4D2eGWS6wd307k7xlPysevYPogfFWxK_eDHwkTq3Ts91qEDqrdV_JtgULC8c1LvX65E0TwW_GL_TM94g3CvqoQnGVxxoaMVye4ggvR7eOZjimWMzUuu4Lo9Z-VBHBj7XM0UMBie57CpGwH4_Wkv0V_LHZRRHKdnl9ISp_aGwfBObTcHG9v0P3BW9vRrCjihIn0SqOJQ9obl52rMf84GD4Lcy9NIktzfyka70xR9Sh7ALotW7rWywsTzMTu3t8AzMz2MJgGjvQmx49QA~eyJhbGciOiJEUzEyOCJ9.eyJleHAiOjEzNTgyOTY0Mzg0OTUsImF1ZCI6Imh0dHA6Ly9sb2NhbGhvc3Q6NDk4NC8ifQ.4FV2TrUQffDya0MOxOQlzJQbDNvCPF2sfTIJN7KOLvvlSFPknuIo5g";
+ CAssert(parseAssertion(sampleAssertion, &email, &origin, &exp));
+ CAssertEqual(email, @"jens@mooseyard.com");
+ CAssertEqual(origin, @"http://localhost:4984/");
+ CAssertEq((SInt64)exp.timeIntervalSinceReferenceDate, 379989238);
+
+ // Register and retrieve the sample assertion:
+ NSURL* originURL = [NSURL URLWithString: origin];
+ CAssertEqual([TDBrowserIDAuthorizer registerAssertion: sampleAssertion], email);
+ NSString* gotAssertion = [TDBrowserIDAuthorizer takeAssertionForEmailAddress: email
+ site: originURL];
+ CAssertEqual(gotAssertion, sampleAssertion);
+
+ // -assertionForSite: should return nil because the assertion has expired by now:
+ TDBrowserIDAuthorizer* auth = [[TDBrowserIDAuthorizer alloc] initWithEmailAddress: email];
+ CAssertEqual(auth.emailAddress, email);
+ CAssertEqual([auth assertionForSite: originURL], nil);
+}
View
4 TouchDB.xcodeproj/project.pbxproj
@@ -1836,7 +1836,7 @@
08FB7793FE84155DC02AAC07 /* Project object */ = {
isa = PBXProject;
attributes = {
- LastUpgradeCheck = 0440;
+ LastUpgradeCheck = 0450;
};
buildConfigurationList = 1DEB923508733DC60010E9CD /* Build configuration list for PBXProject "TouchDB" */;
compatibilityVersion = "Xcode 3.2";
@@ -2706,13 +2706,15 @@
275A29261649C58900B0D8EE /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
+ COMBINE_HIDPI_IMAGES = YES;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
};
275A29271649C58900B0D8EE /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
+ COMBINE_HIDPI_IMAGES = YES;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Release;

0 comments on commit 6375d6c

Please sign in to comment.