2013-06-25 00:54:54 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
final class DoorkeeperBridgeAsana extends DoorkeeperBridge {
|
|
|
|
|
2013-06-26 01:32:45 +02:00
|
|
|
const APPTYPE_ASANA = 'asana';
|
|
|
|
const APPDOMAIN_ASANA = 'asana.com';
|
|
|
|
const OBJTYPE_TASK = 'asana:task';
|
|
|
|
|
2013-06-25 00:54:54 +02:00
|
|
|
public function canPullRef(DoorkeeperObjectRef $ref) {
|
2013-06-26 01:32:45 +02:00
|
|
|
if ($ref->getApplicationType() != self::APPTYPE_ASANA) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($ref->getApplicationDomain() != self::APPDOMAIN_ASANA) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
$types = array(
|
|
|
|
self::OBJTYPE_TASK => true,
|
|
|
|
);
|
|
|
|
|
|
|
|
return isset($types[$ref->getObjectType()]);
|
2013-06-25 00:54:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public function pullRefs(array $refs) {
|
|
|
|
|
|
|
|
$id_map = mpull($refs, 'getObjectID', 'getObjectKey');
|
|
|
|
$viewer = $this->getViewer();
|
|
|
|
|
|
|
|
$provider = PhabricatorAuthProviderOAuthAsana::getAsanaProvider();
|
|
|
|
if (!$provider) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$accounts = id(new PhabricatorExternalAccountQuery())
|
|
|
|
->setViewer($viewer)
|
|
|
|
->withUserPHIDs(array($viewer->getPHID()))
|
|
|
|
->withAccountTypes(array($provider->getProviderType()))
|
|
|
|
->withAccountDomains(array($provider->getProviderDomain()))
|
|
|
|
->execute();
|
|
|
|
|
2013-06-25 00:55:08 +02:00
|
|
|
if (!$accounts) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-06-25 00:54:54 +02:00
|
|
|
// TODO: If the user has several linked Asana accounts, we just pick the
|
|
|
|
// first one arbitrarily. We might want to try using all of them or do
|
|
|
|
// something with more finesse. There's no UI way to link multiple accounts
|
|
|
|
// right now so this is currently moot.
|
|
|
|
$account = head($accounts);
|
|
|
|
|
Provide an auto-refresh mechanism for OAuth providers to deliver fresh tokens
Summary:
Ref T2852. Give OAuth providers a formal method so you can ask them for tokens; they issue a refresh request if necessary.
We could automatically refresh these tokens in daemons as they near expiry to improve performance; refreshes are blocking in-process round trip requests. If we do this for all tokens, it's a lot of requests (say, 20k users * 2 auth mechanisms * 1-hour tokens ~= a million requests a day). We could do it selectively for tokens that are actually in use (i.e., if we refresh a token in response to a user request, we keep refreshing it for 24 hours automatically). For now, I'm not pursuing any of this.
If we fail to refresh a token, we don't have a great way to communicate it to the user right now. The remedy is "log out and log in again", but there's no way for them to figure this out. The major issue is that a lot of OAuth integrations should not throw if they fail, or can't reasonably be rasied to the user (e.g., activity in daemons, loading profile pictures, enriching links, etc). For now, this shouldn't really happen. In future diffs, I plan to make the "External Accounts" settings page provide some information about tokens again, and possibly push some flag to accounts like "you should refresh your X link", but we'll see if issues crop up.
Test Plan: Used `bin/auth refresh` to verify refreshes. I'll wait an hour and reload a page with an Asana link to verify the auto-refresh part.
Reviewers: btrahan
Reviewed By: btrahan
CC: aran
Maniphest Tasks: T2852
Differential Revision: https://secure.phabricator.com/D6280
2013-06-25 00:56:01 +02:00
|
|
|
$token = $provider->getOAuthAccessToken($account);
|
2013-06-25 00:54:54 +02:00
|
|
|
if (!$token) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$template = id(new PhutilAsanaFuture())
|
|
|
|
->setAccessToken($token);
|
|
|
|
|
|
|
|
$futures = array();
|
|
|
|
foreach ($id_map as $key => $id) {
|
|
|
|
$futures[$key] = id(clone $template)
|
|
|
|
->setRawAsanaQuery("tasks/{$id}");
|
|
|
|
}
|
|
|
|
|
|
|
|
$results = array();
|
2013-07-16 19:29:52 +02:00
|
|
|
$failed = array();
|
2013-06-25 00:54:54 +02:00
|
|
|
foreach (Futures($futures) as $key => $future) {
|
2013-06-25 00:55:08 +02:00
|
|
|
try {
|
|
|
|
$results[$key] = $future->resolve();
|
|
|
|
} catch (Exception $ex) {
|
2013-07-16 19:29:52 +02:00
|
|
|
if (($ex instanceof HTTPFutureResponseStatus) &&
|
|
|
|
($ex->getStatusCode() == 404)) {
|
|
|
|
// This indicates that the object has been deleted (or never existed,
|
|
|
|
// or isn't visible to the current user) but it's a successful sync of
|
|
|
|
// an object which isn't visible.
|
|
|
|
} else {
|
|
|
|
// This is something else, so consider it a synchronization failure.
|
|
|
|
phlog($ex);
|
|
|
|
$failed[$key] = $ex;
|
|
|
|
}
|
2013-06-25 00:55:08 +02:00
|
|
|
}
|
2013-06-25 00:54:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($refs as $ref) {
|
2013-06-25 00:55:08 +02:00
|
|
|
$ref->setAttribute('name', pht('Asana Task %s', $ref->getObjectID()));
|
|
|
|
|
2013-07-21 22:40:20 +02:00
|
|
|
$did_fail = idx($failed, $ref->getObjectKey());
|
|
|
|
if ($did_fail) {
|
2013-07-16 19:29:52 +02:00
|
|
|
$ref->setSyncFailed(true);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2013-06-25 00:54:54 +02:00
|
|
|
$result = idx($results, $ref->getObjectKey());
|
|
|
|
if (!$result) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$ref->setIsVisible(true);
|
|
|
|
$ref->setAttribute('asana.data', $result);
|
2013-06-25 00:55:08 +02:00
|
|
|
$ref->setAttribute('fullname', pht('Asana: %s', $result['name']));
|
|
|
|
$ref->setAttribute('title', $result['name']);
|
2013-06-25 00:54:54 +02:00
|
|
|
$ref->setAttribute('description', $result['notes']);
|
|
|
|
|
|
|
|
$obj = $ref->getExternalObject();
|
|
|
|
if ($obj->getID()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2013-06-26 01:30:44 +02:00
|
|
|
$this->fillObjectFromData($obj, $result);
|
2013-06-25 00:54:54 +02:00
|
|
|
|
|
|
|
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
|
|
|
$obj->save();
|
|
|
|
unset($unguarded);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-06-26 01:30:44 +02:00
|
|
|
public function fillObjectFromData(DoorkeeperExternalObject $obj, $result) {
|
|
|
|
$id = $result['id'];
|
|
|
|
$uri = "https://app.asana.com/0/{$id}/{$id}";
|
|
|
|
$obj->setObjectURI($uri);
|
|
|
|
}
|
|
|
|
|
2013-06-25 00:54:54 +02:00
|
|
|
}
|