adium/adium
Clone
Summary
Browse
Changes
Graph
Fixed a couple of alignment issues with the group chat status icons.
adium-1.6
2012-08-06, Paul Wilde
e5dd91a2baea
Fixed a couple of alignment issues with the group chat status icons.
//
// AIFacebookXMPPAccount.m
// Adium
//
// Created by Colin Barrett on 11/18/10.
// Copyright 2010 __MyCompanyName__. All rights reserved.
//
#import "AIFacebookXMPPAccount.h"
#import "AIFacebookXMPPService.h"
#import <Adium/AIStatus.h>
#import <Adium/AIStatusControllerProtocol.h>
#import <Adium/AIListContact.h>
#import "adiumPurpleCore.h"
#import <libpurple/jabber.h>
#import "ESPurpleJabberAccount.h"
#import "auth_fb.h"
#import "auth.h"
#import <AIUtilities/AIKeychain.h>
#import "AIFacebookXMPPOAuthWebViewWindowController.h"
#import "JSONKit.h"
#import <Adium/AIAccountControllerProtocol.h>
#import <Adium/AIPasswordPromptController.h>
#import <Adium/AIService.h>
#import <libpurple/auth.h>
#import "auth_fb.h"
enum
{
AINoNetworkState
,
AIMeGraphAPINetworkState
,
AIPromoteSessionNetworkState
};
@interface
AIFacebookXMPPAccount
()
@property
(
nonatomic
,
copy
)
NSString
*
oAuthToken
;
@property
(
nonatomic
,
assign
)
NSUInteger
networkState
;
@property
(
nonatomic
,
assign
)
NSURLConnection
*
connection
;
// assign because NSURLConnection retains its delegate.
@property
(
nonatomic
,
retain
)
NSURLResponse
*
connectionResponse
;
@property
(
nonatomic
,
retain
)
NSMutableData
*
connectionData
;
-
(
void
)
meGraphAPIDidFinishLoading:
(
NSData
*
)
graphAPIData
response:
(
NSURLResponse
*
)
response
error:
(
NSError
*
)
inError
;
-
(
void
)
promoteSessionDidFinishLoading:
(
NSData
*
)
secretData
response:
(
NSURLResponse
*
)
response
error:
(
NSError
*
)
inError
;
@end
@implementation
AIFacebookXMPPAccount
@synthesize
oAuthWC
;
@synthesize
migrationData
;
@synthesize
oAuthToken
;
@synthesize
networkState
,
connection
,
connectionResponse
,
connectionData
;
+
(
BOOL
)
uidIsValidForFacebook:
(
NSString
*
)
inUID
{
return
((
inUID
.
length
>
0
)
&&
[
inUID
stringByTrimmingCharactersInSet
:
[
NSCharacterSet
decimalDigitCharacterSet
]].
length
==
0
);
}
-
(
id
)
initWithUID:
(
NSString
*
)
inUID
internalObjectID:
(
NSString
*
)
inInternalObjectID
service:
(
AIService
*
)
inService
{
if
((
self
=
[
super
initWithUID
:
inUID
internalObjectID
:
inInternalObjectID
service
:
inService
]))
{
if
(
!
[[
self
class
]
uidIsValidForFacebook
:
self
.
UID
])
{
[
self
setValue
:
[
NSNumber
numberWithBool
:
YES
]
forProperty
:
@"Prompt For Password On Next Connect"
notify
:
NotifyNever
];
}
}
return
self
;
}
-
(
void
)
dealloc
{
[
oAuthWC
release
];
[
oAuthToken
release
];
[
connection
cancel
];
[
connectionResponse
release
];
[
connectionData
release
];
[
super
dealloc
];
}
#pragma mark Connectivitiy
-
(
const
char
*
)
protocolPlugin
{
return
"prpl-jabber"
;
}
-
(
NSString
*
)
serverSuffix
{
return
@"@chat.facebook.com"
;
}
/* Specify a host for network-reachability-code purposes */
-
(
NSString
*
)
host
{
return
@"chat.facebook.com"
;
}
-
(
NSString
*
)
apiSecretAccountName
{
return
[
NSString
stringWithFormat
:
@"Adium.FB.%@"
,
[
self
internalObjectID
]];
}
-
(
void
)
configurePurpleAccount
{
[
super
configurePurpleAccount
];
purple_account_set_username
(
account
,
self
.
purpleAccountName
);
purple_account_set_string
(
account
,
"connection_security"
,
""
);
purple_account_set_string
(
account
,
"fb_api_key"
,
ADIUM_API_KEY
);
/*
//Uncomment along with storage code in promoteSessionDidFinishLoading::: to use the session secret.
NSString *apiSecret = [[AIKeychain defaultKeychain_error:NULL] findGenericPasswordForService:self.service.serviceID
account:self.apiSecretAccountName
keychainItem:NULL
error:NULL];
purple_account_set_string(account, "fb_api_secret", [apiSecret UTF8String]);
*/
purple_account_set_string
(
account
,
"fb_api_secret"
,
ADIUM_API_SECRET
);
}
/* Add the authentication mechanism for X-FACEBOOK-PLATFORM. Note that if the server offers it,
* it will be used preferentially over any other mechanism e.g. DIGEST-MD5. */
-
(
void
)
setFacebookMechEnabled:
(
BOOL
)
inEnabled
{
static
BOOL
enabledFacebookMech
=
NO
;
if
(
inEnabled
!=
enabledFacebookMech
)
{
if
(
inEnabled
)
jabber_auth_add_mech
(
jabber_auth_get_fb_mech
());
else
jabber_auth_remove_mech
(
jabber_auth_get_fb_mech
());
enabledFacebookMech
=
inEnabled
;
}
}
-
(
void
)
connect
{
[
self
setFacebookMechEnabled
:
YES
];
[
super
connect
];
}
-
(
void
)
didConnect
{
[
self
setFacebookMechEnabled
:
NO
];
[
super
didConnect
];
}
-
(
void
)
didDisconnect
{
[
self
setFacebookMechEnabled
:
NO
];
[
super
didDisconnect
];
}
-
(
const
char
*
)
purpleAccountName
{
NSString
*
userNameWithHost
=
nil
,
*
completeUserName
=
nil
;
BOOL
serverAppendedToUID
;
serverAppendedToUID
=
([
UID
rangeOfString
:
[
self
serverSuffix
]
options
:(
NSCaseInsensitiveSearch
|
NSBackwardsSearch
|
NSAnchoredSearch
)].
location
!=
NSNotFound
);
if
(
serverAppendedToUID
)
{
userNameWithHost
=
UID
;
}
else
{
userNameWithHost
=
[
UID
stringByAppendingString
:
[
self
serverSuffix
]];
}
completeUserName
=
[
NSString
stringWithFormat
:
@"-%@/Adium"
,
userNameWithHost
];
return
[
completeUserName
UTF8String
];
}
-
(
BOOL
)
encrypted
{
return
NO
;
}
-
(
BOOL
)
allowAccountUnregistrationIfSupportedByLibpurple
{
return
NO
;
}
/*!
* @brief Password entered callback
*
* Callback after the user enters her password for connecting; finish the connect process.
*/
-
(
void
)
passwordReturnedForConnect:
(
NSString
*
)
inPassword
returnCode:
(
AIPasswordPromptReturn
)
returnCode
context:
(
id
)
inContext
{
if
((
returnCode
==
AIPasswordPromptOKReturn
)
&&
(
inPassword
.
length
==
0
))
{
/* No password retrieved from the keychain */
[
self
requestFacebookAuthorization
];
}
else
{
[
self
setValue
:
nil
forProperty
:
@"mustPromptForPasswordOnNextConnect"
notify
:
NotifyNever
];
[
super
passwordReturnedForConnect
:
inPassword
returnCode
:
returnCode
context
:
inContext
];
}
}
-
(
void
)
retrievePasswordThenConnect
{
if
([
self
boolValueForProperty
:
@"Prompt For Password On Next Connect"
]
||
[
self
boolValueForProperty
:
@"mustPromptForPasswordOnNextConnect"
])
/* We attempted to connect, but we had incorrect authorization. Display our auth request window. */
[
self
requestFacebookAuthorization
];
else
{
/* Retrieve the user's password. Never prompt for a password, as we'll implement our own authorization handling
* if the password can't be retrieved.
*/
[
adium
.
accountController
passwordForAccount
:
self
promptOption
:
AIPromptNever
notifyingTarget
:
self
selector
:
@selector
(
passwordReturnedForConnect
:
returnCode
:
context
:
)
context
:
nil
];
}
}
#pragma mark Account configuration
-
(
void
)
setName:
(
NSString
*
)
name
UID:
(
NSString
*
)
inUID
{
[
self
filterAndSetUID
:
inUID
];
[
self
setFormattedUID
:
name
notify
:
NotifyNever
];
}
#pragma mark Contacts
/*!
* @brief Set an alias for a contact
*
* Normally, we consider the name a 'serverside alias' unless it matches the UID's characters
* However, the UID in facebook should never be presented to the user if possible; it's for internal use
* only. We'll therefore consider any alias a formatted UID such that it will replace the UID when displayed
* in Adium.
*/
-
(
void
)
updateContact:
(
AIListContact
*
)
theContact
toAlias:
(
NSString
*
)
purpleAlias
{
if
(
!
[
purpleAlias
isEqualToString
:
theContact
.
formattedUID
]
&&
!
[
purpleAlias
isEqualToString
:
theContact
.
UID
])
{
[
theContact
setFormattedUID
:
purpleAlias
notify
:
NotifyLater
];
//Apply any changes
[
theContact
notifyOfChangedPropertiesSilently
:
silentAndDelayed
];
}
}
-
(
NSMutableArray
*
)
arrayOfDictionariesFromPurpleNotifyUserInfo:
(
PurpleNotifyUserInfo
*
)
user_info
forContact:
(
AIListContact
*
)
contact
{
NSMutableArray
*
array
=
[
super
arrayOfDictionariesFromPurpleNotifyUserInfo
:
user_info
forContact
:
contact
];
NSString
*
displayUID
=
contact
.
UID
;
displayUID
=
[
displayUID
stringByTrimmingCharactersInSet
:
[
NSCharacterSet
characterSetWithCharactersInString
:
@"-"
]];
if
([
displayUID
hasSuffix
:
@"@chat.facebook.com"
])
displayUID
=
[
displayUID
substringToIndex
:
(
displayUID
.
length
-
@"@chat.facebook.com"
.
length
)];
[
array
addObject
:
[
NSDictionary
dictionaryWithObjectsAndKeys
:
AILocalizedString
(
@"Facebook ID"
,
nil
),
KEY_KEY
,
displayUID
,
KEY_VALUE
,
nil
]];
return
array
;
}
#pragma mark Authorization
-
(
void
)
requestFacebookAuthorization
{
self
.
oAuthWC
=
[[[
AIFacebookXMPPOAuthWebViewWindowController
alloc
]
init
]
autorelease
];
self
.
oAuthWC
.
account
=
self
;
[[
NSNotificationCenter
defaultCenter
]
postNotificationName
:
AIFacebookXMPPAuthProgressNotification
object
:
self
userInfo
:
[
NSDictionary
dictionaryWithObject
:
[
NSNumber
numberWithInt
:
AIFacebookXMPPAuthProgressPromptingUser
]
forKey
:
KEY_FB_XMPP_AUTH_STEP
]];
if
(
!
[[
self
class
]
uidIsValidForFacebook
:
self
.
UID
])
{
/* We have a UID which isn't a Facebook numeric username. That can come from:
* 1. The setup wizard
* 2. Facebook-HTTP account from Adium <= 1.4.2
*/
self
.
oAuthWC
.
autoFillUsername
=
self
.
UID
;
self
.
oAuthWC
.
autoFillPassword
=
[
adium
.
accountController
passwordForAccount
:
self
];
self
.
oAuthWC
.
isMigrating
=
!
[
self
.
service
.
serviceID
isEqualToString
:
FACEBOOK_XMPP_SERVICE_ID
];
self
.
migrationData
=
[
NSDictionary
dictionaryWithObjectsAndKeys
:
self
.
UID
,
@"originalUID"
,
self
.
service
.
serviceID
,
@"originalServiceID"
,
nil
];
}
[
self
.
oAuthWC
showWindow
:
self
];
}
-
(
void
)
oAuthWebViewController:
(
AIFacebookXMPPOAuthWebViewWindowController
*
)
wc
didSucceedWithToken:
(
NSString
*
)
token
{
[
self
setOAuthToken
:
token
];
NSString
*
urlstring
=
[
NSString
stringWithFormat
:
@"https://graph.facebook.com/me?access_token=%@"
,
[
self
oAuthToken
]];
NSURL
*
url
=
[
NSURL
URLWithString
:
[
urlstring
stringByAddingPercentEscapesUsingEncoding
:
NSUTF8StringEncoding
]];
NSURLRequest
*
request
=
[
NSURLRequest
requestWithURL
:
url
];
self
.
networkState
=
AIMeGraphAPINetworkState
;
self
.
connectionData
=
[
NSMutableData
data
];
self
.
connection
=
[
NSURLConnection
connectionWithRequest
:
request
delegate
:
self
];
[[
NSNotificationCenter
defaultCenter
]
postNotificationName
:
AIFacebookXMPPAuthProgressNotification
object
:
self
userInfo
:
[
NSDictionary
dictionaryWithObject
:
[
NSNumber
numberWithInt
:
AIFacebookXMPPAuthProgressContactingServer
]
forKey
:
KEY_FB_XMPP_AUTH_STEP
]];
}
-
(
void
)
oAuthWebViewControllerDidFail:
(
AIFacebookXMPPOAuthWebViewWindowController
*
)
wc
{
[
self
setOAuthToken
:
nil
];
[[
NSNotificationCenter
defaultCenter
]
postNotificationName
:
AIFacebookXMPPAuthProgressNotification
object
:
self
userInfo
:
[
NSDictionary
dictionaryWithObject
:
[
NSNumber
numberWithInt
:
AIFacebookXMPPAuthProgressFailure
]
forKey
:
KEY_FB_XMPP_AUTH_STEP
]];
}
-
(
void
)
meGraphAPIDidFinishLoading:
(
NSData
*
)
graphAPIData
response:
(
NSURLResponse
*
)
inResponse
error:
(
NSError
*
)
inError
{
if
(
inError
)
{
NSLog
(
@"error loading graph API: %@"
,
inError
);
// TODO: indicate setup failed
return
;
}
NSError
*
error
=
nil
;
NSDictionary
*
resp
=
[
graphAPIData
objectFromJSONDataWithParseOptions
:
JKParseOptionNone
error
:&
error
];
if
(
!
resp
)
{
NSLog
(
@"error decoding graph API response: %@"
,
error
);
// TODO: indicate setup failed
return
;
}
NSString
*
uuid
=
[
resp
objectForKey
:
@"id"
];
NSString
*
name
=
[
resp
objectForKey
:
@"name"
];
/* Passwords are keyed by UID, so we need to make this change before storing the password */
[
self
setName
:
name
UID
:
uuid
];
NSString
*
secretURLString
=
[
NSString
stringWithFormat
:
@"https://api.facebook.com/method/auth.promoteSession?access_token=%@&format=JSON"
,
[
self
oAuthToken
]];
NSURL
*
secretURL
=
[
NSURL
URLWithString
:
[
secretURLString
stringByAddingPercentEscapesUsingEncoding
:
NSUTF8StringEncoding
]];
NSURLRequest
*
secretRequest
=
[
NSURLRequest
requestWithURL
:
secretURL
];
self
.
networkState
=
AIPromoteSessionNetworkState
;
self
.
connectionData
=
[
NSMutableData
data
];
self
.
connection
=
[
NSURLConnection
connectionWithRequest
:
secretRequest
delegate
:
self
];
[[
NSNotificationCenter
defaultCenter
]
postNotificationName
:
AIFacebookXMPPAuthProgressNotification
object
:
self
userInfo
:
[
NSDictionary
dictionaryWithObject
:
[
NSNumber
numberWithInt
:
AIFacebookXMPPAuthProgressPromotingForChat
]
forKey
:
KEY_FB_XMPP_AUTH_STEP
]];
}
-
(
void
)
didCompleteFacebookAuthorization
{
/* Restart the connect process; we're currently considered 'connecting', so passwordReturnedForConnect:::
* isn't going to restart it for us. */
[
self
connect
];
[[
NSNotificationCenter
defaultCenter
]
postNotificationName
:
AIFacebookXMPPAuthProgressNotification
object
:
self
userInfo
:
[
NSDictionary
dictionaryWithObject
:
[
NSNumber
numberWithInt
:
AIFacebookXMPPAuthProgressSuccess
]
forKey
:
KEY_FB_XMPP_AUTH_STEP
]];
}
-
(
void
)
promoteSessionDidFinishLoading:
(
NSData
*
)
secretData
response:
(
NSURLResponse
*
)
response
error:
(
NSError
*
)
inError
{
if
(
inError
)
{
NSLog
(
@"error promoting session: %@"
,
inError
);
// TODO: indicate setup failed
return
;
}
/* Uncomment the below to store the Session Secret in the keychain. It doesn't seem to be used.
NSString *secret = [[[NSString alloc] initWithData:secretData encoding:NSUTF8StringEncoding] autorelease];
secret = [secret substringWithRange:NSMakeRange(1, [secret length] - 2)]; // strip off the quotes
//Delete before adding; otherwise we'll just get errSecDuplicateItem
[[AIKeychain defaultKeychain_error:NULL] deleteGenericPasswordForService:self.service.serviceID
account:self.apiSecretAccountName
error:NULL];
[[AIKeychain defaultKeychain_error:NULL] addGenericPassword:secret
forService:self.service.serviceID
account:self.apiSecretAccountName
keychainItem:NULL
error:NULL];
*/
NSString
*
sessionKey
=
[
self
oAuthToken
];
[[
adium
accountController
]
setPassword
:
sessionKey
forAccount
:
self
];
/* When we're newly authorized, connect! */
[
self
passwordReturnedForConnect
:
sessionKey
returnCode
:
AIPasswordPromptOKReturn
context
:
nil
];
[
self
didCompleteFacebookAuthorization
];
self
.
oAuthWC
=
nil
;
self
.
oAuthToken
=
nil
;
}
#pragma mark NSURLConnectionDelegate
-
(
void
)
connection:
(
NSURLConnection
*
)
inConnection
didReceiveResponse:
(
NSURLResponse
*
)
response
{
[[
self
connectionData
]
setLength
:
0
];
[
self
setConnectionResponse
:
response
];
}
-
(
void
)
connection:
(
NSURLConnection
*
)
inConnection
didReceiveData:
(
NSData
*
)
data
{
[[
self
connectionData
]
appendData
:
data
];
}
-
(
void
)
connection:
(
NSURLConnection
*
)
inConnection
didFailWithError:
(
NSError
*
)
error
{
NSUInteger
state
=
[
self
networkState
];
[
self
setNetworkState
:
AINoNetworkState
];
[
self
setConnection
:
nil
];
[
self
setConnectionResponse
:
nil
];
[
self
setConnectionData
:
nil
];
if
(
state
==
AIMeGraphAPINetworkState
)
{
[
self
meGraphAPIDidFinishLoading
:
nil
response
:
nil
error
:
error
];
}
else
if
(
state
==
AIPromoteSessionNetworkState
)
{
[
self
promoteSessionDidFinishLoading
:
nil
response
:
nil
error
:
error
];
}
}
-
(
void
)
connectionDidFinishLoading:
(
NSURLConnection
*
)
inConnection
{
NSURLResponse
*
response
=
[[[
self
connectionResponse
]
retain
]
autorelease
];
NSMutableData
*
data
=
[[[
self
connectionData
]
retain
]
autorelease
];
NSUInteger
state
=
[
self
networkState
];
[
self
setNetworkState
:
AINoNetworkState
];
[
self
setConnection
:
nil
];
[
self
setConnectionResponse
:
nil
];
[
self
setConnectionData
:
nil
];
if
(
state
==
AIMeGraphAPINetworkState
)
{
[
self
meGraphAPIDidFinishLoading
:
data
response
:
response
error
:
nil
];
}
else
if
(
state
==
AIPromoteSessionNetworkState
)
{
[
self
promoteSessionDidFinishLoading
:
data
response
:
response
error
:
nil
];
}
}
@end