- Update
This commit is contained in:
1 parent
d281d765ea
commit
5b074a5176
1261 files changed
+199158
-7303
No files matched your search
@@ -0,0 +1,56 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "XMPPSASLAuthentication.h"
|
||||
#import "XMPPStream.h"
|
||||
|
||||
|
||||
@interface XMPPXFacebookPlatformAuthentication : NSObject <XMPPSASLAuthentication>
|
||||
|
||||
/**
|
||||
* You should use this init method (as opposed the one defined in the XMPPSASLAuthentication protocol).
|
||||
**/
|
||||
- (id)initWithStream:(XMPPStream *)stream appId:(NSString *)appId accessToken:(NSString *)accessToken;
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@interface XMPPStream (XMPPXFacebookPlatformAuthentication)
|
||||
|
||||
/**
|
||||
* Facebook Chat X-FACEBOOK-PLATFORM SASL authentication initialization.
|
||||
* This is a convienence init method to help configure Facebook Chat.
|
||||
**/
|
||||
- (id)initWithFacebookAppId:(NSString *)fbAppId;
|
||||
|
||||
/**
|
||||
* The appId can be passed to custom authentication classes.
|
||||
* For example, the appId is used for Facebook Chat X-FACEBOOK-PLATFORM SASL authentication.
|
||||
**/
|
||||
@property (readwrite, copy) NSString *facebookAppId;
|
||||
|
||||
/**
|
||||
* Returns whether or not the server supports X-FACEBOOK-PLATFORM authentication.
|
||||
*
|
||||
* This information is available after the stream is connected.
|
||||
* In other words, after the delegate has received xmppStreamDidConnect: notification.
|
||||
**/
|
||||
- (BOOL)supportsXFacebookPlatformAuthentication;
|
||||
|
||||
/**
|
||||
* This method attempts to start the facebook oauth authentication process.
|
||||
*
|
||||
* This method is asynchronous.
|
||||
*
|
||||
* If there is something immediately wrong,
|
||||
* such as the stream is not connected or doesn't have a set appId or accessToken,
|
||||
* the method will return NO and set the error.
|
||||
* Otherwise the delegate callbacks are used to communicate auth success or failure.
|
||||
*
|
||||
* @see xmppStreamDidAuthenticate:
|
||||
* @see xmppStream:didNotAuthenticate:
|
||||
**/
|
||||
- (BOOL)authenticateWithFacebookAccessToken:(NSString *)accessToken error:(NSError **)errPtr;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,327 @@
|
||||
#import "XMPPXFacebookPlatformAuthentication.h"
|
||||
#import "XMPP.h"
|
||||
#import "XMPPLogging.h"
|
||||
#import "XMPPInternal.h"
|
||||
#import "NSData+XMPP.h"
|
||||
|
||||
#import <objc/runtime.h>
|
||||
|
||||
#if ! __has_feature(objc_arc)
|
||||
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
|
||||
#endif
|
||||
|
||||
// Log levels: off, error, warn, info, verbose
|
||||
#if DEBUG
|
||||
static const int xmppLogLevel = XMPP_LOG_LEVEL_INFO; // | XMPP_LOG_FLAG_TRACE;
|
||||
#else
|
||||
static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN;
|
||||
#endif
|
||||
|
||||
static NSString *const XMPPFacebookChatHostName = @"chat.facebook.com";
|
||||
|
||||
static char facebookAppIdKey;
|
||||
|
||||
@interface XMPPXFacebookPlatformAuthentication ()
|
||||
{
|
||||
#if __has_feature(objc_arc_weak)
|
||||
__weak XMPPStream *xmppStream;
|
||||
#else
|
||||
__unsafe_unretained XMPPStream *xmppStream;
|
||||
#endif
|
||||
|
||||
BOOL awaitingChallenge;
|
||||
|
||||
NSString *appId;
|
||||
NSString *accessToken;
|
||||
NSString *nonce;
|
||||
NSString *method;
|
||||
}
|
||||
|
||||
- (NSDictionary *)dictionaryFromChallenge:(NSXMLElement *)challenge;
|
||||
- (NSString *)base64EncodedFullResponse;
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@implementation XMPPXFacebookPlatformAuthentication
|
||||
|
||||
+ (NSString *)mechanismName
|
||||
{
|
||||
return @"X-FACEBOOK-PLATFORM";
|
||||
}
|
||||
|
||||
- (id)initWithStream:(XMPPStream *)stream password:(NSString *)password
|
||||
{
|
||||
if ((self = [super init]))
|
||||
{
|
||||
xmppStream = stream;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initWithStream:(XMPPStream *)stream appId:(NSString *)inAppId accessToken:(NSString *)inAccessToken
|
||||
{
|
||||
if ((self = [super init]))
|
||||
{
|
||||
xmppStream = stream;
|
||||
appId = inAppId;
|
||||
accessToken = inAccessToken;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)start:(NSError **)errPtr
|
||||
{
|
||||
if (!appId || !accessToken)
|
||||
{
|
||||
NSString *errMsg = @"Missing facebook appId and/or accessToken.";
|
||||
NSDictionary *info = @{NSLocalizedDescriptionKey : errMsg};
|
||||
|
||||
NSError *err = [NSError errorWithDomain:XMPPStreamErrorDomain code:XMPPStreamInvalidState userInfo:info];
|
||||
|
||||
if (errPtr) *errPtr = err;
|
||||
return NO;
|
||||
}
|
||||
|
||||
// <auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="X-FACEBOOK-PLATFORM" />
|
||||
|
||||
NSXMLElement *auth = [NSXMLElement elementWithName:@"auth" xmlns:@"urn:ietf:params:xml:ns:xmpp-sasl"];
|
||||
[auth addAttributeWithName:@"mechanism" stringValue:@"X-FACEBOOK-PLATFORM"];
|
||||
|
||||
[xmppStream sendAuthElement:auth];
|
||||
awaitingChallenge = YES;
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (XMPPHandleAuthResponse)handleAuth1:(NSXMLElement *)authResponse
|
||||
{
|
||||
XMPPLogTrace();
|
||||
|
||||
// We're expecting a challenge response.
|
||||
// If we get anything else we're going to assume it's some kind of failure response.
|
||||
|
||||
if (![[authResponse name] isEqualToString:@"challenge"])
|
||||
{
|
||||
return XMPP_AUTH_FAIL;
|
||||
}
|
||||
|
||||
// Extract components from incoming challenge
|
||||
|
||||
NSDictionary *auth = [self dictionaryFromChallenge:authResponse];
|
||||
|
||||
nonce = auth[@"nonce"];
|
||||
method = auth[@"method"];
|
||||
|
||||
// Create and send challenge response element
|
||||
|
||||
NSXMLElement *response = [NSXMLElement elementWithName:@"response" xmlns:@"urn:ietf:params:xml:ns:xmpp-sasl"];
|
||||
[response setStringValue:[self base64EncodedFullResponse]];
|
||||
|
||||
[xmppStream sendAuthElement:response];
|
||||
awaitingChallenge = NO;
|
||||
|
||||
return XMPP_AUTH_CONTINUE;
|
||||
}
|
||||
|
||||
- (XMPPHandleAuthResponse)handleAuth2:(NSXMLElement *)authResponse
|
||||
{
|
||||
XMPPLogTrace();
|
||||
|
||||
// We're expecting a success response.
|
||||
// If we get anything else we can safely assume it's the equivalent of a failure response.
|
||||
|
||||
if ([[authResponse name] isEqualToString:@"success"])
|
||||
{
|
||||
return XMPP_AUTH_SUCCESS;
|
||||
}
|
||||
else
|
||||
{
|
||||
return XMPP_AUTH_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
- (XMPPHandleAuthResponse)handleAuth:(NSXMLElement *)authResponse
|
||||
{
|
||||
if (awaitingChallenge)
|
||||
{
|
||||
return [self handleAuth1:authResponse];
|
||||
}
|
||||
else
|
||||
{
|
||||
return [self handleAuth2:authResponse];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSDictionary *)dictionaryFromChallenge:(NSXMLElement *)challenge
|
||||
{
|
||||
// The value of the challenge stanza is base 64 encoded.
|
||||
// Once "decoded", it's just a string of key=value pairs separated by ampersands.
|
||||
|
||||
NSData *base64Data = [[challenge stringValue] dataUsingEncoding:NSASCIIStringEncoding];
|
||||
NSData *decodedData = [base64Data xmpp_base64Decoded];
|
||||
|
||||
NSString *authStr = [[NSString alloc] initWithData:decodedData encoding:NSUTF8StringEncoding];
|
||||
|
||||
XMPPLogVerbose(@"%@: decoded challenge: %@", THIS_FILE, authStr);
|
||||
|
||||
NSArray *components = [authStr componentsSeparatedByString:@"&"];
|
||||
NSMutableDictionary *auth = [NSMutableDictionary dictionaryWithCapacity:3];
|
||||
|
||||
for (NSString *component in components)
|
||||
{
|
||||
NSRange separator = [component rangeOfString:@"="];
|
||||
if (separator.location != NSNotFound)
|
||||
{
|
||||
NSString *key = [[component substringToIndex:separator.location]
|
||||
stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
||||
|
||||
NSString *value = [[component substringFromIndex:separator.location+1]
|
||||
stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
||||
|
||||
if ([value hasPrefix:@"\""] && [value hasSuffix:@"\""] && [value length] > 2)
|
||||
{
|
||||
// Strip quotes from value
|
||||
value = [value substringWithRange:NSMakeRange(1,([value length]-2))];
|
||||
}
|
||||
|
||||
auth[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return auth;
|
||||
}
|
||||
|
||||
- (NSString *)base64EncodedFullResponse
|
||||
{
|
||||
if (!appId || !accessToken || !method || !nonce)
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
srand([[NSDate date] timeIntervalSince1970]);
|
||||
|
||||
NSMutableString *buffer = [NSMutableString stringWithCapacity:250];
|
||||
[buffer appendFormat:@"method=%@&", method];
|
||||
[buffer appendFormat:@"nonce=%@&", nonce];
|
||||
[buffer appendFormat:@"access_token=%@&", accessToken];
|
||||
[buffer appendFormat:@"api_key=%@&", appId];
|
||||
[buffer appendFormat:@"call_id=%d&", rand()];
|
||||
[buffer appendFormat:@"v=%@",@"1.0"];
|
||||
|
||||
XMPPLogVerbose(@"XMPPXFacebookPlatformAuthentication: response for facebook: %@", buffer);
|
||||
|
||||
NSData *utf8data = [buffer dataUsingEncoding:NSUTF8StringEncoding];
|
||||
|
||||
return [utf8data xmpp_base64Encoded];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@implementation XMPPStream (XMPPXFacebookPlatformAuthentication)
|
||||
|
||||
- (id)initWithFacebookAppId:(NSString *)fbAppId
|
||||
{
|
||||
if ((self = [self init])) // Note: Using [self init], NOT [super init]
|
||||
{
|
||||
self.facebookAppId = fbAppId;
|
||||
self.myJID = [XMPPJID jidWithString:XMPPFacebookChatHostName];
|
||||
|
||||
// As of October 8, 2011, Facebook doesn't have their XMPP SRV records set.
|
||||
// And, as per the XMPP specification, we MUST check the XMPP SRV records for an IP address,
|
||||
// before falling back to a traditional A record lookup.
|
||||
//
|
||||
// So we're setting the hostname as a minor optimization to avoid the SRV timeout delay.
|
||||
|
||||
self.hostName = XMPPFacebookChatHostName;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *)facebookAppId
|
||||
{
|
||||
__block NSString *result = nil;
|
||||
|
||||
dispatch_block_t block = ^{
|
||||
result = objc_getAssociatedObject(self, &facebookAppIdKey);
|
||||
};
|
||||
|
||||
if (dispatch_get_specific(self.xmppQueueTag))
|
||||
block();
|
||||
else
|
||||
dispatch_sync(self.xmppQueue, block);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void)setFacebookAppId:(NSString *)inFacebookAppId
|
||||
{
|
||||
NSString *newFacebookAppId = [inFacebookAppId copy];
|
||||
|
||||
dispatch_block_t block = ^{
|
||||
objc_setAssociatedObject(self, &facebookAppIdKey, newFacebookAppId, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
};
|
||||
|
||||
if (dispatch_get_specific(self.xmppQueueTag))
|
||||
block();
|
||||
else
|
||||
dispatch_async(self.xmppQueue, block);
|
||||
}
|
||||
|
||||
- (BOOL)supportsXFacebookPlatformAuthentication
|
||||
{
|
||||
return [self supportsAuthenticationMechanism:[XMPPXFacebookPlatformAuthentication mechanismName]];
|
||||
}
|
||||
|
||||
/**
|
||||
* This method attempts to connect to the Facebook Chat servers
|
||||
* using the Facebook OAuth token returned by the Facebook OAuth 2.0 authentication process.
|
||||
**/
|
||||
- (BOOL)authenticateWithFacebookAccessToken:(NSString *)accessToken error:(NSError **)errPtr
|
||||
{
|
||||
XMPPLogTrace();
|
||||
|
||||
__block BOOL result = YES;
|
||||
__block NSError *err = nil;
|
||||
|
||||
dispatch_block_t block = ^{ @autoreleasepool {
|
||||
|
||||
if ([self supportsXFacebookPlatformAuthentication])
|
||||
{
|
||||
XMPPXFacebookPlatformAuthentication *facebookAuth =
|
||||
[[XMPPXFacebookPlatformAuthentication alloc] initWithStream:self
|
||||
appId:self.facebookAppId
|
||||
accessToken:accessToken];
|
||||
|
||||
result = [self authenticate:facebookAuth error:&err];
|
||||
}
|
||||
else
|
||||
{
|
||||
NSString *errMsg = @"The server does not support X-FACEBOOK-PLATFORM authentication.";
|
||||
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
|
||||
Reference in new issue
Block a user