Skip to content

Commit

Permalink
added Token Based Reconnection and everything is tested
Browse files Browse the repository at this point in the history
  • Loading branch information
Andres Canal committed Jul 11, 2016
1 parent c5ab86f commit 8ffcfca
Show file tree
Hide file tree
Showing 12 changed files with 506 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//
// XMPPStream+XMPPTBRAuthentication.h
// XMPPFramework
//
// Created by Andres Canal on 7/6/16.
// Copyright © 2016 Inaka. All rights reserved.
//

#import "XMPPFramework.h"
#import "XMPPStream.h"

@interface XMPPStream (XMPPTBRAuthentication)

- (BOOL)authenticateWithTBR:(NSString *)authToken error:(NSError **)errPtr;
- (BOOL)supportsTBRAuthentication;

@end
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//
// XMPPStream+XMPPTBRAuthentication.m
// XMPPFramework
//
// Created by Andres Canal on 7/6/16.
// Copyright © 2016 Inaka. All rights reserved.
//

#import "XMPPStream+XMPPTBRAuthentication.h"
#import "XMPPTBRAuthentication.h"
#import "XMPPInternal.h"

@implementation XMPPStream (TBRAuthentication)

- (BOOL)supportsTBRAuthentication{
return [self supportsAuthenticationMechanism:[XMPPTBRAuthentication mechanismName]];
}

- (BOOL)authenticateWithTBR:(nonnull NSString *)authToken error:(NSError **)errPtr {

__block BOOL result = YES;
__block NSError *err = nil;

dispatch_block_t block = ^{ @autoreleasepool {

if ([self supportsTBRAuthentication]) {

XMPPTBRAuthentication *tbrAuthentication = [[XMPPTBRAuthentication alloc] initWithStream:self
token:authToken];

result = [self authenticate:tbrAuthentication error:&err];
} else {
NSString *errMsg = @"The server does not support Token-based reconnection.";
NSDictionary *info = @{NSLocalizedDescriptionKey : errMsg};

err = [NSError errorWithDomain:XMPPStreamErrorDomain code:XMPPStreamUnsupportedAction userInfo:info];

result = NO;
}
}};

if (dispatch_get_specific(self.xmppQueueTag))
block();
else
dispatch_sync(self.xmppQueue, block);

if (errPtr)
*errPtr = err;

return result;
}

@end
17 changes: 17 additions & 0 deletions Authentication/Token-Based-Reconnection/XMPPTBRAuthentication.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//
// XMPPTBRAuthentication.h
// XMPPFramework
//
// Created by Andres Canal on 7/6/16.
// Copyright © 2016 Inaka. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "XMPPSASLAuthentication.h"
#import "XMPPStream.h"

@interface XMPPTBRAuthentication : NSObject <XMPPSASLAuthentication>

- (nonnull instancetype)initWithStream:(nonnull XMPPStream *)stream token:(nonnull NSString *)aToken;

@end
71 changes: 71 additions & 0 deletions Authentication/Token-Based-Reconnection/XMPPTBRAuthentication.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//
// XMPPTBRAuthentication.m
// XMPPFramework
//
// Created by Andres Canal on 7/6/16.
// Copyright © 2016 Inaka. All rights reserved.
//

#import "XMPPTBRAuthentication.h"
#import "XMPPInternal.h"
#import "NSXMLElement+XMPP.h"

@implementation XMPPTBRAuthentication {
#if __has_feature(objc_arc_weak)
__weak XMPPStream *xmppStream;
#else
__unsafe_unretained XMPPStream *xmppStream;
#endif
NSString *authToken;
}

+ (NSString *)mechanismName {
return @"X-OAUTH";
}

- (id)initWithStream:(nonnull XMPPStream *)stream password:(nonnull NSString *)password {
return [super init];
}

- (id)initWithStream:(nonnull XMPPStream *)stream token:(nonnull NSString *)aToken {
if( self = [super init]) {
xmppStream = stream;
authToken = aToken;
}

return self;
}

- (BOOL)start:(NSError **)errPtr {

if(!authToken) {
NSString *errMsg = @"Missing auth token.";
NSDictionary *info = @{NSLocalizedDescriptionKey : errMsg};

NSError *err = [NSError errorWithDomain:XMPPStreamErrorDomain code:XMPPStreamInvalidParameter userInfo:info];

if (errPtr) *errPtr = err;
return NO;
}

// <auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="X-OAUTH">auth_token</auth>

NSXMLElement *auth = [NSXMLElement elementWithName:@"auth" xmlns:@"urn:ietf:params:xml:ns:xmpp-sasl"];
[auth addAttributeWithName:@"mechanism" stringValue:@"X-OAUTH"];
auth.stringValue = authToken;

[xmppStream sendAuthElement:auth];

return true;
}

- (XMPPHandleAuthResponse)handleAuth:(NSXMLElement *)auth {

if ([[auth name] isEqualToString:@"success"]) {
return XMPP_AUTH_SUCCESS;
}

return XMPP_AUTH_FAIL;
}

@end
21 changes: 21 additions & 0 deletions Authentication/Token-Based-Reconnection/XMPPTBReconnection.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//
// XMPPTBReconnection.h
// XMPPFramework
//
// Created by Andres Canal on 7/5/16.
// Copyright © 2016 Inaka. All rights reserved.
//

#import "XMPPFramework.h"
#import "XMPPIDTracker.h"

@interface XMPPTBReconnection : XMPPModule {
XMPPIDTracker *responseTracker;
}
- (void) getAuthToken;
@end

@protocol XMPPTBReconnectionDelegate
- (void)xmppTBReconnection:(nonnull XMPPTBReconnection *)sender didReceiveToken:(nonnull NSDictionary<NSString *, NSString *> *) token;
- (void)xmppTBReconnection:(nonnull XMPPTBReconnection *)sender didFailToReceiveToken:(nonnull XMPPIQ *)iq;
@end
81 changes: 81 additions & 0 deletions Authentication/Token-Based-Reconnection/XMPPTBReconnection.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
//
// XMPPTBReconnection.m
// XMPPFramework
//
// Created by Andres Canal on 7/5/16.
// Copyright © 2016 Inaka. All rights reserved.
//

#import "XMPPTBReconnection.h"
#import "XMPPFramework.h"
#import "XMPPIQ.h"

@implementation XMPPTBReconnection

- (void) getAuthToken {
// <iq from='crone1@shakespeare.lit/desktop'
// id='create1'
// to='coven@muclight.shakespeare.lit'
// type='get'>
// <query xmlns='erlang-solutions.com:xmpp:token-auth:0'/>
// </iq>

dispatch_block_t block = ^{ @autoreleasepool {

NSString *iqID = [XMPPStream generateUUID];
NSXMLElement *iq = [NSXMLElement elementWithName:@"iq"];
[iq addAttributeWithName:@"id" stringValue:iqID];
[iq addAttributeWithName:@"to" stringValue:self.xmppStream.myJID.full];
[iq addAttributeWithName:@"type" stringValue:@"get"];

NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:@"erlang-solutions.com:xmpp:token-auth:0"];
[iq addChild:query];

[responseTracker addID:iqID
target:self
selector:@selector(handleGetAuthToken:withInfo:)
timeout:60.0];

[xmppStream sendElement:iq];
}};

if (dispatch_get_specific(moduleQueueTag))
block();
else
dispatch_async(moduleQueue, block);
}

- (void)handleGetAuthToken:(XMPPIQ *)iq withInfo:(id <XMPPTrackingInfo>)info {
if ([[iq type] isEqualToString:@"result"]){
NSXMLElement *items = [iq elementForName:@"items"];

NSMutableDictionary *tokensDictionary = [[NSMutableDictionary alloc] init];
for (NSXMLElement *element in items.children) {
tokensDictionary[element.name] = element.stringValue;
}
[multicastDelegate xmppTBReconnection:self didReceiveToken:tokensDictionary];
}else{
[multicastDelegate xmppTBReconnection:self didFailToReceiveToken:iq];
}
}

- (BOOL)activate:(XMPPStream *)aXmppStream {
if ([super activate:aXmppStream]){
responseTracker = [[XMPPIDTracker alloc] initWithDispatchQueue:moduleQueue];

return YES;
}
return NO;
}

- (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq {
NSString *type = [iq type];
if ([type isEqualToString:@"result"] || [type isEqualToString:@"error"]){
return [responseTracker invokeForID:[iq elementID] withObject:iq];
}

return NO;
}


@end
2 changes: 1 addition & 1 deletion Xcode/Testing-Shared/XMPPMockStream.h
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@
- (void)fakeIQResponse:(XMPPIQ *) iq;
- (void)fakeMessageResponse:(XMPPMessage *) message;

@property BOOL supportsTBR;
@property (nonatomic, copy) void (^elementReceived)(XMPPElement *element);

@end
11 changes: 11 additions & 0 deletions Xcode/Testing-Shared/XMPPMockStream.m
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ - (BOOL) isAuthenticated {
return YES;
}

- (BOOL)supportsTBRAuthentication{
return self.supportsTBR;
}

- (void)fakeMessageResponse:(XMPPMessage *) message {
[self injectElement:message];
}
Expand All @@ -37,4 +41,11 @@ - (void)sendElement:(XMPPElement *)element {
}
}

- (void)sendAuthElement:(XMPPElement *)element {
[super sendAuthElement:element];
if(self.elementReceived) {
self.elementReceived(element);
}
}

@end
77 changes: 77 additions & 0 deletions Xcode/Testing-Shared/XMPPTBRAuthenticationTests.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
//
// XMPPTBRAuthenticationTests.m
// XMPPFrameworkTests
//
// Created by Andres Canal on 7/6/16.
//
//

#import <XCTest/XCTest.h>
#import "XMPPMockStream.h"
#import "XMPPTBReconnection.h"
#import "XMPPTBRAuthentication.h"
#import "XMPPJID.h"
#import "XMPPStream+XMPPTBRAuthentication.h"

@interface XMPPTBRAuthenticationTests : XCTestCase <XMPPStreamDelegate>
@property (nonatomic, strong) XCTestExpectation *delegateExpectation;
@end

@implementation XMPPTBRAuthenticationTests

- (void)testTBRNotSupported {
NSError *error;

XMPPMockStream *streamTest = [[XMPPMockStream alloc] init];
streamTest.supportsTBR = NO;
[streamTest authenticateWithTBR:@"token-token-token" error:&error];

XCTAssertEqualObjects(error.domain, XMPPStreamErrorDomain);
XCTAssertEqual(error.code, XMPPStreamUnsupportedAction);
XCTAssertEqualObjects(error.userInfo[NSLocalizedDescriptionKey], @"The server does not support Token-based reconnection.");
}

- (void)testSuccessTBR {
self.delegateExpectation = [self expectationWithDescription:@"TBR expectation"];

XMPPMockStream *streamTest = [[XMPPMockStream alloc] init];
streamTest.supportsTBR = YES;
streamTest.myJID = [XMPPJID jidWithString:@"andres@test.com"];

streamTest.elementReceived = ^void(NSXMLElement *element) {
XCTAssertEqualObjects(element.name, @"auth");
XCTAssertEqualObjects([element attributeForName:@"mechanism"].stringValue, @"X-OAUTH");
XCTAssertEqualObjects(element.stringValue, @"token-token-token");

[self.delegateExpectation fulfill];
};

[streamTest authenticateWithTBR:@"token-token-token" error:nil];

[self waitForExpectationsWithTimeout:5 handler:^(NSError * _Nullable error) {
if(error){
XCTFail(@"Expectation Failed with error: %@", error);
}
}];
}

- (void)testFailureHandleAuth {
XMPPMockStream *streamTest = [[XMPPMockStream alloc] init];
XMPPTBRAuthentication *auth = [[XMPPTBRAuthentication alloc] initWithStream:streamTest token:@"test"];

NSXMLElement *failureElement = [NSXMLElement elementWithName:@"failure" xmlns:@"urn:ietf:params:xml:ns:xmpp-sasl"];
NSXMLElement *notAuthorized = [NSXMLElement elementWithName:@"not-authorized"];
[failureElement addChild:notAuthorized];

XCTAssertEqual([auth handleAuth:failureElement], XMPP_AUTH_FAIL);
}

- (void)testSuccessHandleAuth {
XMPPMockStream *streamTest = [[XMPPMockStream alloc] init];
XMPPTBRAuthentication *auth = [[XMPPTBRAuthentication alloc] initWithStream:streamTest token:@"test"];

NSXMLElement *successElement = [NSXMLElement elementWithName:@"success" xmlns:@"urn:ietf:params:xml:ns:xmpp-sasl"];
XCTAssertEqual([auth handleAuth:successElement], XMPP_AUTH_SUCCESS);
}

@end
Loading

0 comments on commit 8ffcfca

Please sign in to comment.