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

Commit

Permalink
BrowserID improvements
Browse files Browse the repository at this point in the history
* 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
snej committed Jan 17, 2013
1 parent dfbb945 commit 6375d6c
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 21 deletions.
4 changes: 4 additions & 0 deletions Source/TDBase64.h
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -13,4 +13,8 @@
+ (NSString*) encode:(NSData*) rawBytes; + (NSString*) encode:(NSData*) rawBytes;
+ (NSData*) decode:(const char*) string length:(size_t) inputLength; + (NSData*) decode:(const char*) string length:(size_t) inputLength;
+ (NSData*) decode:(NSString*) string; + (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 @end
19 changes: 16 additions & 3 deletions Source/TDBase64.m
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ + (void) initialize {
for (NSUInteger i = 0; i < sizeof(kEncodingTable); i++) { for (NSUInteger i = 0; i < sizeof(kEncodingTable); i++) {
kDecodingTable[kEncodingTable[i]] = (int8_t)i; kDecodingTable[kEncodingTable[i]] = (int8_t)i;
} }
// Alternate characters used in the URL-safe Base64 encoding (RFC 4648, sec. 5)
kDecodingTable['-'] = 62;
kDecodingTable['='] = 63;
} }
} }


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




+ (NSData*) decode: (const char*)string length: (size_t)inputLength { + (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; return nil;
}

while (inputLength > 0 && string[inputLength - 1] == '=') { while (inputLength > 0 && string[inputLength - 1] == '=') {
inputLength--; inputLength--;
} }
Expand Down Expand Up @@ -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 @end
6 changes: 3 additions & 3 deletions Source/TDBrowserIDAuthorizer.h
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@


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


+ (void) registerAssertion: (NSString*)assertion + (NSString*) registerAssertion: (NSString*)assertion;
forEmailAddress: (NSString*)email
toSite: (NSURL*)site;


- (id) initWithEmailAddress: (NSString*)emailAddress; - (id) initWithEmailAddress: (NSString*)emailAddress;


@property (readonly) NSString* emailAddress; @property (readonly) NSString* emailAddress;


- (NSString*) assertionForSite: (NSURL*)site;

@end @end
94 changes: 80 additions & 14 deletions Source/TDBrowserIDAuthorizer.m
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -7,14 +7,42 @@
// //


#import "TDBrowserIDAuthorizer.h" #import "TDBrowserIDAuthorizer.h"

#import "TDBase64.h"


static NSMutableDictionary* sAssertions; static NSMutableDictionary* sAssertions;




@implementation TDBrowserIDAuthorizer @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 { + (NSURL*) originForSite: (NSURL*)url {
NSString* scheme = url.scheme.lowercaseString; NSString* scheme = url.scheme.lowercaseString;
NSMutableString* str = [NSMutableString stringWithFormat: @"%@://%@", NSMutableString* str = [NSMutableString stringWithFormat: @"%@://%@",
Expand All @@ -30,27 +58,27 @@ + (NSURL*) originForSite: (NSURL*)url {
} }




+ (void) registerAssertion: (NSString*)assertion + (NSString*) registerAssertion: (NSString*)assertion {
forEmailAddress: (NSString*)email NSString* email, *origin;
toSite: (NSURL*)site NSDate* exp;
{ if (!parseAssertion(assertion, &email, &origin, &exp))
return nil;
id key = @[email, origin];
@synchronized(self) { @synchronized(self) {
if (!sAssertions) if (!sAssertions)
sAssertions = [NSMutableDictionary dictionary]; sAssertions = [NSMutableDictionary dictionary];
id key = @[email, [self originForSite: site]];
sAssertions[key] = assertion; sAssertions[key] = assertion;
} }
return email;
} }




+ (NSString*) takeAssertionForEmailAddress: (NSString*)email + (NSString*) takeAssertionForEmailAddress: (NSString*)email
site: (NSURL*)site site: (NSURL*)site
{ {
id key = @[email, [[self originForSite: site] absoluteString]];
@synchronized(self) { @synchronized(self) {
id key = @[email, [self originForSite: site]]; return sAssertions[key];
NSString* assertion = sAssertions[key];
//[sAssertions removeObjectForKey: key];
return assertion;
} }
} }


Expand All @@ -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 - (NSString*) authorizeURLRequest: (NSMutableURLRequest*)request
forRealm: (NSString*)realm forRealm: (NSString*)realm
{ {
Expand All @@ -91,10 +131,36 @@ - (NSString*) loginPath {




- (NSDictionary*) loginParametersForSite: (NSURL*)site { - (NSDictionary*) loginParametersForSite: (NSURL*)site {
NSString* assertion = [[self class] takeAssertionForEmailAddress: _emailAddress site: site]; NSString* assertion = [self assertionForSite: site];
if (!assertion) return assertion ? @{@"assertion": assertion} : nil;
return nil;
return @{@"assertion": assertion};
} }


@end @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);
}
4 changes: 3 additions & 1 deletion TouchDB.xcodeproj/project.pbxproj
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -1836,7 +1836,7 @@
08FB7793FE84155DC02AAC07 /* Project object */ = { 08FB7793FE84155DC02AAC07 /* Project object */ = {
isa = PBXProject; isa = PBXProject;
attributes = { attributes = {
LastUpgradeCheck = 0440; LastUpgradeCheck = 0450;
}; };
buildConfigurationList = 1DEB923508733DC60010E9CD /* Build configuration list for PBXProject "TouchDB" */; buildConfigurationList = 1DEB923508733DC60010E9CD /* Build configuration list for PBXProject "TouchDB" */;
compatibilityVersion = "Xcode 3.2"; compatibilityVersion = "Xcode 3.2";
Expand Down Expand Up @@ -2706,13 +2706,15 @@
275A29261649C58900B0D8EE /* Debug */ = { 275A29261649C58900B0D8EE /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
COMBINE_HIDPI_IMAGES = YES;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
}; };
name = Debug; name = Debug;
}; };
275A29271649C58900B0D8EE /* Release */ = { 275A29271649C58900B0D8EE /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
COMBINE_HIDPI_IMAGES = YES;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
}; };
name = Release; name = Release;
Expand Down

0 comments on commit 6375d6c

Please sign in to comment.