Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[DB MIGRATION REQ'D] Alert user about expired Facebook tokens
As of July 5th, 2012, Facebook removed the offline_access permission. Instead, it automatically expires OAuth tokens after 60 days (for server-side apps like ThinkUp) and requires users to re-authorize to extend the application's rights for another 60 days.
More info:
https://developers.facebook.com/roadmap/offline-access-removal/
https://developers.facebook.com/docs/authentication/access-token-expiration/
https://developers.facebook.com/blog/post/2011/05/13/how-to--handle-expired-access-tokens/

As of this commit, ThinkUp now:
* Tracks service authorization errors in the new tu_owner_instances.auth_error field
* Throws APIOAuthException when Facebook token is not valid during data capture
* Alerts user when Facebook OAuth tokens have expired via email and in an error message on the service user dashboard
* Does not crawl Facebook instances which have expired tokens
* Resumes data capture when user has re-authorized their account
Closes #1349
  • Loading branch information
ginatrapani committed Jul 5, 2012
1 parent 67ae631 commit d5252dd
Show file tree
Hide file tree
Showing 22 changed files with 394 additions and 100 deletions.
57 changes: 46 additions & 11 deletions tests/TestOfInstanceMySQLDAO.php
Expand Up @@ -45,28 +45,30 @@ protected function buildData() {
$builders = array();

$builders[] = FixtureBuilder::build('instances', array('network_user_id'=>10, 'network_username'=>'jack',
'network'=>'twitter', 'network_viewer_id'=>10, 'crawler_last_run'=>'1988-01-20 12:00:00', 'is_active'=>1,
'network'=>'twitter', 'network_viewer_id'=>10, 'crawler_last_run'=>'1988-01-20 12:00:00', 'is_active'=>1,
'is_public'=>0));

$builders[] = FixtureBuilder::build('instances', array('network_user_id'=>12, 'network_username'=>'jill',
'network'=>'twitter', 'network_viewer_id'=>12, 'crawler_last_run'=>'2010-01-20 12:00:00', 'is_active'=>1,
'network'=>'twitter', 'network_viewer_id'=>12, 'crawler_last_run'=>'2010-01-20 12:00:00', 'is_active'=>1,
'is_public'=>0));

$builders[] = FixtureBuilder::build('instances', array('network_user_id'=>13, 'network_username'=>'stuart',
'network'=>'twitter', 'network_viewer_id'=>13, 'crawler_last_run'=>'2010-01-01 12:00:00', 'is_active'=>0,
'network'=>'twitter', 'network_viewer_id'=>13, 'crawler_last_run'=>'2010-01-01 12:00:00', 'is_active'=>0,
'is_public'=>1));

$builders[] = FixtureBuilder::build('instances', array('network_user_id'=>15,
'network_username'=>'Jillian Dickerson', 'network'=>'facebook', 'network_viewer_id'=>15,
'network_username'=>'Jillian Dickerson', 'network'=>'facebook', 'network_viewer_id'=>15,
'crawler_last_run'=>'2010-01-01 12:00:01', 'is_active'=>1, 'is_public'=>1));

$builders[] = FixtureBuilder::build('instances', array('network_user_id'=>16, 'network_username'=>'Paul Clark',
'network'=>'facebook', 'network_viewer_id'=>16, 'crawler_last_run'=>'2010-01-01 12:00:02', 'is_active'=>0,
'is_public'=>1));

$builders[] = FixtureBuilder::build('owner_instances', array('owner_id'=>2, 'instance_id'=>1));
$builders[] = FixtureBuilder::build('owner_instances', array('owner_id'=>2, 'instance_id'=>1,
'auth_error'=>"There has been an error."));

$builders[] = FixtureBuilder::build('owner_instances', array('owner_id'=>2, 'instance_id'=>2));
$builders[] = FixtureBuilder::build('owner_instances', array('owner_id'=>2, 'instance_id'=>2,
'auth_error'=>''));

return $builders;
}
Expand Down Expand Up @@ -662,7 +664,7 @@ public function testIsUserConfigured(){
public function testGetByUserAndViewerId() {
$this->DAO = new InstanceMySQLDAO();
$builders[] = FixtureBuilder::build('instances', array('network_user_id'=>17,
'network_username'=>'Jillian Micheals', 'network'=>'facebook', 'network_viewer_id'=>15,
'network_username'=>'Jillian Micheals', 'network'=>'facebook', 'network_viewer_id'=>15,
'crawler_last_run'=>'2010-01-01 12:00:01', 'is_active'=>1));

$result = $this->DAO->getByUserAndViewerId(10, 10, 'twitter');
Expand All @@ -675,7 +677,7 @@ public function testGetByUserAndViewerId() {
public function testGetByViewerId() {
$this->DAO = new InstanceMySQLDAO();
$builders[] = FixtureBuilder::build('instances', array('network_user_id'=>17,
'network_username'=>'Jillian Micheals', 'network'=>'facebook', 'network_viewer_id'=>15,
'network_username'=>'Jillian Micheals', 'network'=>'facebook', 'network_viewer_id'=>15,
'crawler_last_run'=>'2010-01-01 12:00:01', 'is_active'=>1));

$result = $this->DAO->getByViewerId(15);
Expand All @@ -686,11 +688,11 @@ public function testGetByViewerId() {
public function testGetByUsernameOnNetwork() {
$this->DAO = new InstanceMySQLDAO();
$builders[] = FixtureBuilder::build('instances', array('network_user_id'=>17,
'network_username'=>'salma', 'network'=>'facebook', 'network_viewer_id'=>15,
'network_username'=>'salma', 'network'=>'facebook', 'network_viewer_id'=>15,
'crawler_last_run'=>'2010-01-01 12:00:01', 'is_active'=>1));

$builders[] = FixtureBuilder::build('instances', array('network_user_id'=>18,
'network_username'=>'salma', 'network'=>'facebook page', 'network_viewer_id'=>15,
'network_username'=>'salma', 'network'=>'facebook page', 'network_viewer_id'=>15,
'crawler_last_run'=>'2010-01-01 12:00:01', 'is_active'=>1));

$result = $this->DAO->getByUsernameOnNetwork('salma', 'facebook');
Expand Down Expand Up @@ -733,7 +735,7 @@ public function testGetPublicInstances() {
public function testUpdateInstanceUsername() {
$this->DAO = new InstanceMySQLDAO();
$builders[] = FixtureBuilder::build('instances', array('network_user_id'=>17,
'network_username'=>'johndoe', 'network'=>'twitter', 'network_viewer_id'=>15,
'network_username'=>'johndoe', 'network'=>'twitter', 'network_viewer_id'=>15,
'crawler_last_run'=>'2010-01-01 12:00:01', 'is_active'=>1));

$instance = $this->DAO->getByUsername('johndoe');
Expand All @@ -744,4 +746,37 @@ public function testUpdateInstanceUsername() {
$instance = $this->DAO->getByUsername('johndoe2');
$this->assertEqual($instance->network_username, "johndoe2" );
}

public function testGetActiveInstancesStalestFirstForOwnerByNetworkNoAuthError() {
$this->builders[] = FixtureBuilder::build('instances', array('network_user_id'=>17, 'network_username'=>'yaya',
'network'=>'twitter', 'network_viewer_id'=>17, 'crawler_last_run'=>'2010-01-20 12:00:00', 'is_active'=>1,
'is_public'=>0));

$this->builders[] = FixtureBuilder::build('owner_instances', array('owner_id'=>3, 'instance_id'=>6,
'auth_error'=>''));

$this->DAO = new InstanceMySQLDAO();
$owner = new Owner();
$owner->id = 2;

//Owner isn't an admin
$owner->is_admin = false;

//Should only return 1 result
$result = $this->DAO->getActiveInstancesStalestFirstForOwnerByNetworkNoAuthError($owner, 'twitter');
$this->assertEqual(sizeof($result), 1);
$this->assertEqual($result[0]->id, 2);
$this->assertEqual($result[0]->network_username, "jill");

//Owner is an admin
$owner->is_admin = true;

//Should return 2 results
$result = $this->DAO->getActiveInstancesStalestFirstForOwnerByNetworkNoAuthError($owner, 'twitter');
$this->assertEqual(sizeof($result), 2);
$this->assertEqual($result[0]->id, 2);
$this->assertEqual($result[0]->network_username, "jill");
$this->assertEqual($result[1]->id, 6);
$this->assertEqual($result[1]->network_username, "yaya");
}
}
31 changes: 31 additions & 0 deletions tests/TestOfOwnerInstanceMySQLDAO.php
Expand Up @@ -279,4 +279,35 @@ public function testDoesOwnerHaveAccessToPost() {
$this->assertEqual(
OwnerInstanceMySQLDAO::$post_access_query_cache['20-twitter-follower_id_cache'][0]['follower_id'], 10);
}

public function testSetAuthError() {
$builder = FixtureBuilder::build(self::TEST_TABLE_OI, array('instance_id' => 20, 'owner_id'=>50,
'auth_error'=>'') );

$dao = new OwnerInstanceMySQLDAO();

$owner_instance = $dao->get(50, 20);
$this->assertNotNull($owner_instance);
$this->assertIsA($owner_instance, 'OwnerInstance');
$this->assertEqual($owner_instance->auth_error, '');

$res = $dao->setAuthError(50, 20, 'Error validating access token: Session has expired at unix time SOME_TIME. '.
'The current unix time is SOME_TIME.');
$this->assertTrue($res);
$owner_instance = $dao->get(50, 20);
$this->assertNotNull($owner_instance);
$this->assertIsA($owner_instance, 'OwnerInstance');
$this->assertEqual($owner_instance->auth_error, 'Error validating access token: Session has expired at '.
'unix time SOME_TIME. The current unix time is SOME_TIME.');

$res = $dao->setAuthError(49, 20, 'Error validating access token: Session has expired at unix time SOME_TIME. '.
'The current unix time is SOME_TIME.');
$this->assertFalse($res);

$res = $dao->setAuthError(50, 20);
$this->assertTrue($res);
$owner_instance = $dao->get(50, 20);
$this->assertIsA($owner_instance, 'OwnerInstance');
$this->assertEqual($owner_instance->auth_error, '');
}
}
46 changes: 23 additions & 23 deletions tests/TestOfPostMySQLDAO.php
Expand Up @@ -1848,13 +1848,13 @@ public function testAddManyNativeRetweetsOfPost2() {
* Test deletePost
*/
public function testDeletePost() {
$pdao = new PostMySQLDAO();
$post_dao = new PostMySQLDAO();

// no such post
$this->assertEqual(0, $pdao->deletePost(-99));
$this->assertEqual(0, $post_dao->deletePost(-99));

// post deleted
$this->assertEqual(1, $pdao->deletePost(1));
$this->assertEqual(1, $post_dao->deletePost(1));
$sql = "select * from " . $this->table_prefix . 'posts where id = 1';
$stmt = PostMySQLDAO::$PDO->query($sql);
$this->assertFalse($stmt->fetch(PDO::FETCH_ASSOC));
Expand All @@ -1864,8 +1864,8 @@ public function testDeletePost() {
* Test getTotalPostsByUser
*/
public function testGetTotalPostsByUser() {
$pdao = new PostMySQLDAO();
$total_posts = $pdao->getTotalPostsByUser('ev', 'twitter');
$post_dao = new PostMySQLDAO();
$total_posts = $post_dao->getTotalPostsByUser('ev', 'twitter');

$this->assertEqual($total_posts, 40);
}
Expand All @@ -1886,13 +1886,13 @@ public function testGetMostRepliedToPostsInLastWeek() {
'reply_count_cache'=>$counter));
$counter++;
}
$pdao = new PostMySQLDAO();
$posts = $pdao->getMostRepliedToPostsInLastWeek('user3', 'twitter', 5);
$post_dao = new PostMySQLDAO();
$posts = $post_dao->getMostRepliedToPostsInLastWeek('user3', 'twitter', 5);
$this->assertEqual(sizeof($posts), 5);
$this->assertEqual($posts[0]->reply_count_cache, 7);
$this->assertEqual($posts[1]->reply_count_cache, 6);

$posts = $pdao->getMostRepliedToPostsInLastWeek('user2', 'twitter', 5);
$posts = $post_dao->getMostRepliedToPostsInLastWeek('user2', 'twitter', 5);
$this->assertEqual(sizeof($posts), 0);
}

Expand All @@ -1914,15 +1914,15 @@ public function testGetMostRetweetedPostsInLastWeek() {
));
$counter++;
}
$pdao = new PostMySQLDAO();
$posts = $pdao->getMostRetweetedPostsInLastWeek('user3', 'twitter', 5);
$post_dao = new PostMySQLDAO();
$posts = $post_dao->getMostRetweetedPostsInLastWeek('user3', 'twitter', 5);
$this->assertEqual(sizeof($posts), 5);
$this->assertEqual($posts[0]->reply_count_cache, 0);
$this->assertEqual($posts[1]->reply_count_cache, 0);
$this->assertTrue(($posts[0]->retweet_count_cache + $posts[0]->old_retweet_count_cache) >=
($posts[1]->retweet_count_cache + $posts[1]->old_retweet_count_cache));

$posts = $pdao->getMostRetweetedPostsInLastWeek('user2', 'twitter', 5);
$posts = $post_dao->getMostRetweetedPostsInLastWeek('user2', 'twitter', 5);
$this->assertEqual(sizeof($posts), 0);
}

Expand All @@ -1934,7 +1934,7 @@ public function testPostRetweetFields() {
$counter = 0;
$id = 1500;
$builders = array();
$pdao = new PostMySQLDAO();
$post_dao = new PostMySQLDAO();

$builders[] = FixtureBuilder::build('posts', array(
'id'=>$id,
Expand Down Expand Up @@ -1964,13 +1964,13 @@ public function testPostRetweetFields() {
'old_retweet_count_cache' => 5
));

$post = $pdao->getPost(1500, 'twitter');
$post = $post_dao->getPost(1500, 'twitter');
$this->assertEqual($post->rt_threshold, 0);
$this->assertEqual($post->all_retweets, 155);
$post = $pdao->getPost(1501, 'twitter');
$post = $post_dao->getPost(1501, 'twitter');
$this->assertEqual($post->rt_threshold, 0);
$this->assertEqual($post->all_retweets, 97);
$post = $pdao->getPost(1502, 'twitter');
$post = $post_dao->getPost(1502, 'twitter');
$this->assertEqual($post->rt_threshold, 1);
$this->assertEqual($post->all_retweets, 105);
}
Expand All @@ -1979,7 +1979,7 @@ public function testPostRetweetFields() {
* Test that detection of an old-style RT for an already-stored post is properly processed
*/
public function testCatchOldStyleRT() {
$pdao = new PostMySQLDAO();
$post_dao = new PostMySQLDAO();
$builders = array();
$builders[] = FixtureBuilder::build('posts', array(
'id' => 5000,
Expand All @@ -2006,9 +2006,9 @@ public function testCatchOldStyleRT() {
'in_retweet_of_post_id' => null
));

$post = $pdao->getPost(12345, 'twitter');
$post = $post_dao->getPost(12345, 'twitter');
$this->assertEqual($post->in_retweet_of_post_id, null);
$post = $pdao->getPost('13708601491193856', 'twitter');
$post = $post_dao->getPost('13708601491193856', 'twitter');
$this->assertEqual($post->old_retweet_count_cache, 0);

// now try adding that post again with the in_retweet_of_post_id field set.
Expand Down Expand Up @@ -2045,15 +2045,15 @@ public function testCatchOldStyleRT() {
$vals['in_retweet_of_post_id'] = '13708601491193856';
$vals['in_rt_of_user_id'] = 100;

$pdao->addPost($vals);
$post = $pdao->getPost(12345, 'twitter');
$post_dao->addPost($vals);
$post = $post_dao->getPost(12345, 'twitter');
$this->assertEqual($post->in_retweet_of_post_id, '13708601491193856');
$post = $pdao->getPost('13708601491193856', 'twitter');
$post = $post_dao->getPost('13708601491193856', 'twitter');
$this->assertEqual($post->old_retweet_count_cache, 1);
$this->assertEqual($post->retweet_count_cache, 2);
// repeat, make sure no duplication now that the in_retweet_of_post_id IS set
$pdao->addPost($vals);
$post = $pdao->getPost('13708601491193856', 'twitter');
$post_dao->addPost($vals);
$post = $post_dao->getPost('13708601491193856', 'twitter');
$this->assertEqual($post->old_retweet_count_cache, 1);
$this->assertEqual($post->retweet_count_cache, 2);
}
Expand Down
10 changes: 9 additions & 1 deletion webapp/_lib/controller/class.DashboardController.php
Expand Up @@ -125,10 +125,10 @@ private function setInstance() {
if ($this->isLoggedIn()) {
$owner_dao = DAOFactory::getDAO('OwnerDAO');
$owner = $owner_dao->getByEmail($this->getLoggedInUser());
$owner_instance_dao = DAOFactory::getDAO('OwnerInstanceDAO');
if (isset($_GET["u"]) && isset($_GET['n'])) {
$instance = $instance_dao->getByUsernameOnNetwork(stripslashes($_GET["u"]), $_GET['n']);
if (isset($instance)) {
$owner_instance_dao = DAOFactory::getDAO('OwnerInstanceDAO');
if ($owner_instance_dao->doesOwnerHaveAccessToInstance($owner, $instance)) {
$this->instance = $instance;
} else {
Expand All @@ -141,6 +141,14 @@ private function setInstance() {
} else {
$this->instance = $instance_dao->getFreshestByOwnerId($owner->id);
}
$owner_instance = $owner_instance_dao->get($owner->id, $this->instance->id);
if (isset($owner_instance) && $owner_instance->auth_error != '') {
$this->addErrorMessage("ThinkUp is having trouble accessing your ".
ucwords($this->instance->network). " data. To fix this problem, in <a href=\"account/?p=".
(($this->instance->network=='facebook page')?'facebook':$this->instance->network)."\">".
ucwords($this->instance->network).
" settings</a>, re-add your account.", null, true);
}
$this->addToView('instances', $instance_dao->getByOwner($owner));
} else {
if (isset($_GET["u"]) && isset($_GET['n'])) {
Expand Down
22 changes: 22 additions & 0 deletions webapp/_lib/model/class.InstanceMySQLDAO.php
Expand Up @@ -68,6 +68,28 @@ public function getAllActiveInstancesStalestFirstByNetwork($network = "twitter")
return $this->getAllInstances("ASC", true, $network);
}

public function getActiveInstancesStalestFirstForOwnerByNetworkNoAuthError( Owner $owner, $network ) {
$q = "SELECT ".$this->getFieldList();
$q .= "FROM ".$this->getTableName()." ";
$q .= $this->getMetaTableJoin();
$q .= "INNER JOIN #prefix#owner_instances oi ON oi.instance_id = ".$this->getTableName().".id ";
$q .= "WHERE network=:network AND (oi.auth_error = '' OR oi.auth_error IS NULL) ";
if (!$owner->is_admin) {
$q .= "AND oi.owner_id = :owner_id ";
}
$q .= "AND is_active = 1 ";
$q .= "ORDER BY crawler_last_run ".$order;
$vars = array(
':network'=>$network
);
if (!$owner->is_admin) {
$vars[':owner_id'] = $owner->id;
}
if ($this->profiler_enabled) Profiler::setDAOMethod(__METHOD__);
$ps = $this->execute($q, $vars);
return $this->getDataRowsAsObjects($ps, $this->object_name);
}

public function insert($network_user_id, $network_username, $network = "twitter", $viewer_id = false) {
$q = "INSERT INTO ".$this->getTableName()." ";
$q .= "(network_user_id, network_username, network, network_viewer_id) ";
Expand Down
41 changes: 28 additions & 13 deletions webapp/_lib/model/class.OwnerInstance.php
Expand Up @@ -32,23 +32,38 @@
*
*/
class OwnerInstance {
/*
* @var int owner id
/**
* @var int Internal unique ID.
*/
var $id;
/**
* @var int Owner ID.
*/
var $owner_id;
/*
* @var int instance id
/**
* @var int Instance ID.
*/
var $instance_id;

/**
* Constructor
* @param int owner id - optional
* @param int instance id - optional
* @var str OAuth access token (optional).
*/
var $oauth_access_token;
/**
* @var str OAuth secret access token (optional).
*/
var $oauth_access_token_secret;
/**
* @var str Last authorization error, if there was one.
*/
public function __construct($oid = null, $iid = null) {
if ($oid) { $this->owner_id = $oid; }
if ($iid) { $this->instance_id = $iid; }
var $auth_error;
public function __construct($row = false) {
if ($row) {
$this->id = $row['id'];
$this->owner_id = $row['owner_id'];
$this->instance_id = $row['instance_id'];
$this->oauth_access_token = $row['oauth_access_token'];
$this->oauth_access_token_secret = $row['oauth_access_token_secret'];
$this->auth_error = $row['auth_error'];
}
}
}

}

0 comments on commit d5252dd

Please sign in to comment.