Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2894054
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Advanced/Developer...
View Handle
View Hovercard
Size
23 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/applications/conduit/controller/PhabricatorConduitTokenEditController.php b/src/applications/conduit/controller/PhabricatorConduitTokenEditController.php
index 17a6de0f75..ff06173f50 100644
--- a/src/applications/conduit/controller/PhabricatorConduitTokenEditController.php
+++ b/src/applications/conduit/controller/PhabricatorConduitTokenEditController.php
@@ -1,100 +1,109 @@
<?php
final class PhabricatorConduitTokenEditController
extends PhabricatorConduitController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$id = $request->getURIData('id');
if ($id) {
$token = id(new PhabricatorConduitTokenQuery())
->setViewer($viewer)
->withIDs(array($id))
->withExpired(false)
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$token) {
return new Aphront404Response();
}
$object = $token->getObject();
$is_new = false;
$title = pht('View API Token');
} else {
$object = id(new PhabricatorObjectQuery())
->setViewer($viewer)
->withPHIDs(array($request->getStr('objectPHID')))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$object) {
return new Aphront404Response();
}
$token = PhabricatorConduitToken::initializeNewToken(
$object->getPHID(),
PhabricatorConduitToken::TYPE_STANDARD);
$is_new = true;
$title = pht('Generate API Token');
$submit_button = pht('Generate Token');
}
if ($viewer->getPHID() == $object->getPHID()) {
$panel_uri = '/settings/panel/apitokens/';
} else {
$panel_uri = '/settings/'.$object->getID().'/panel/apitokens/';
}
id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession(
$viewer,
$request,
$panel_uri);
if ($request->isFormPost()) {
$token->save();
if ($is_new) {
$token_uri = '/conduit/token/edit/'.$token->getID().'/';
} else {
$token_uri = $panel_uri;
}
return id(new AphrontRedirectResponse())->setURI($token_uri);
}
$dialog = $this->newDialog()
->setTitle($title)
->addHiddenInput('objectPHID', $object->getPHID());
if ($is_new) {
$dialog
->appendParagraph(pht('Generate a new API token?'))
->addSubmitButton($submit_button)
->addCancelButton($panel_uri);
} else {
$form = id(new AphrontFormView())
- ->setUser($viewer)
- ->appendChild(
+ ->setUser($viewer);
+
+ if ($token->getTokenType() === PhabricatorConduitToken::TYPE_CLUSTER) {
+ $dialog->appendChild(
+ pht(
+ 'This token is automatically generated by Phabricator, and used '.
+ 'to make requests between nodes in a Phabricator cluster. You '.
+ 'can not use this token in external applications.'));
+ } else {
+ $form->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Token'))
->setValue($token->getToken()));
+ }
$dialog
->appendForm($form)
->addCancelButton($panel_uri, pht('Done'));
}
return $dialog;
}
}
diff --git a/src/applications/conduit/query/PhabricatorConduitTokenQuery.php b/src/applications/conduit/query/PhabricatorConduitTokenQuery.php
index e0d0886273..38a258c4ed 100644
--- a/src/applications/conduit/query/PhabricatorConduitTokenQuery.php
+++ b/src/applications/conduit/query/PhabricatorConduitTokenQuery.php
@@ -1,115 +1,128 @@
<?php
final class PhabricatorConduitTokenQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $objectPHIDs;
private $expired;
private $tokens;
+ private $tokenTypes;
public function withExpired($expired) {
$this->expired = $expired;
return $this;
}
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withObjectPHIDs(array $phids) {
$this->objectPHIDs = $phids;
return $this;
}
public function withTokens(array $tokens) {
$this->tokens = $tokens;
return $this;
}
+ public function withTokenTypes(array $types) {
+ $this->tokenTypes = $types;
+ return $this;
+ }
+
public function loadPage() {
$table = new PhabricatorConduitToken();
$conn_r = $table->establishConnection('r');
$data = queryfx_all(
$conn_r,
'SELECT * FROM %T %Q %Q %Q',
$table->getTableName(),
$this->buildWhereClause($conn_r),
$this->buildOrderClause($conn_r),
$this->buildLimitClause($conn_r));
return $table->loadAllFromArray($data);;
}
private function buildWhereClause(AphrontDatabaseConnection $conn_r) {
$where = array();
if ($this->ids !== null) {
$where[] = qsprintf(
$conn_r,
'id IN (%Ld)',
$this->ids);
}
if ($this->objectPHIDs !== null) {
$where[] = qsprintf(
$conn_r,
'objectPHID IN (%Ls)',
$this->objectPHIDs);
}
if ($this->tokens !== null) {
$where[] = qsprintf(
$conn_r,
'token IN (%Ls)',
$this->tokens);
}
+ if ($this->tokenTypes !== null) {
+ $where[] = qsprintf(
+ $conn_r,
+ 'tokenType IN (%Ls)',
+ $this->tokenTypes);
+ }
+
if ($this->expired !== null) {
if ($this->expired) {
$where[] = qsprintf(
$conn_r,
'expires <= %d',
PhabricatorTime::getNow());
} else {
$where[] = qsprintf(
$conn_r,
'expires IS NULL OR expires > %d',
PhabricatorTime::getNow());
}
}
$where[] = $this->buildPagingClause($conn_r);
return $this->formatWhereClause($where);
}
protected function willFilterPage(array $tokens) {
$object_phids = mpull($tokens, 'getObjectPHID');
$objects = id(new PhabricatorObjectQuery())
->setViewer($this->getViewer())
->setParentQuery($this)
->withPHIDs($object_phids)
->execute();
$objects = mpull($objects, null, 'getPHID');
foreach ($tokens as $key => $token) {
$object = idx($objects, $token->getObjectPHID(), null);
if (!$object) {
$this->didRejectResult($token);
unset($tokens[$key]);
continue;
}
$token->attachObject($object);
}
return $tokens;
}
public function getQueryApplicationClass() {
return 'PhabricatorConduitApplication';
}
}
diff --git a/src/applications/conduit/settings/PhabricatorConduitSettingsPanel.php b/src/applications/conduit/settings/PhabricatorConduitSettingsPanel.php
index 40c982477b..1e2d1d9d5c 100644
--- a/src/applications/conduit/settings/PhabricatorConduitSettingsPanel.php
+++ b/src/applications/conduit/settings/PhabricatorConduitSettingsPanel.php
@@ -1,116 +1,116 @@
<?php
final class PhabricatorConduitSettingsPanel
extends PhabricatorSettingsPanel {
public function isEditableByAdministrators() {
return true;
}
public function getPanelKey() {
return 'apitokens';
}
public function getPanelName() {
return pht('Conduit API Tokens');
}
public function getPanelGroup() {
return pht('Sessions and Logs');
}
public function isEnabled() {
return true;
}
public function processRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$user = $this->getUser();
$tokens = id(new PhabricatorConduitTokenQuery())
->setViewer($viewer)
->withObjectPHIDs(array($user->getPHID()))
->withExpired(false)
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->execute();
$rows = array();
foreach ($tokens as $token) {
$rows[] = array(
javelin_tag(
'a',
array(
'href' => '/conduit/token/edit/'.$token->getID().'/',
'sigil' => 'workflow',
),
- substr($token->getToken(), 0, 8).'...'),
+ $token->getPublicTokenName()),
PhabricatorConduitToken::getTokenTypeName($token->getTokenType()),
phabricator_datetime($token->getDateCreated(), $viewer),
($token->getExpires()
? phabricator_datetime($token->getExpires(), $viewer)
: pht('Never')),
javelin_tag(
'a',
array(
'class' => 'button small grey',
'href' => '/conduit/token/terminate/'.$token->getID().'/',
'sigil' => 'workflow',
),
pht('Terminate')),
);
}
$table = new AphrontTableView($rows);
$table->setNoDataString(pht("You don't have any active API tokens."));
$table->setHeaders(
array(
pht('Token'),
pht('Type'),
pht('Created'),
pht('Expires'),
null,
));
$table->setColumnClasses(
array(
'wide pri',
'',
'right',
'right',
'action',
));
$generate_icon = id(new PHUIIconView())
->setIconFont('fa-plus');
$generate_button = id(new PHUIButtonView())
->setText(pht('Generate API Token'))
->setHref('/conduit/token/edit/?objectPHID='.$user->getPHID())
->setTag('a')
->setWorkflow(true)
->setIcon($generate_icon);
$terminate_icon = id(new PHUIIconView())
->setIconFont('fa-exclamation-triangle');
$terminate_button = id(new PHUIButtonView())
->setText(pht('Terminate All Tokens'))
->setHref('/conduit/token/terminate/?objectPHID='.$user->getPHID())
->setTag('a')
->setWorkflow(true)
->setIcon($terminate_icon);
$header = id(new PHUIHeaderView())
->setHeader(pht('Active API Tokens'))
->addActionLink($generate_button)
->addActionLink($terminate_button);
$panel = id(new PHUIObjectBoxView())
->setHeader($header)
->appendChild($table);
return $panel;
}
}
diff --git a/src/applications/conduit/storage/PhabricatorConduitToken.php b/src/applications/conduit/storage/PhabricatorConduitToken.php
index 8ebcecb16b..5c39173c04 100644
--- a/src/applications/conduit/storage/PhabricatorConduitToken.php
+++ b/src/applications/conduit/storage/PhabricatorConduitToken.php
@@ -1,118 +1,165 @@
<?php
final class PhabricatorConduitToken
extends PhabricatorConduitDAO
implements PhabricatorPolicyInterface {
protected $objectPHID;
protected $tokenType;
protected $token;
protected $expires;
private $object = self::ATTACHABLE;
const TYPE_STANDARD = 'api';
- const TYPE_TEMPORARY = 'tmp';
const TYPE_COMMANDLINE = 'cli';
+ const TYPE_CLUSTER = 'clr';
public function getConfiguration() {
return array(
self::CONFIG_COLUMN_SCHEMA => array(
'tokenType' => 'text32',
'token' => 'text32',
'expires' => 'epoch?',
),
self::CONFIG_KEY_SCHEMA => array(
'key_object' => array(
'columns' => array('objectPHID', 'tokenType'),
),
'key_token' => array(
'columns' => array('token'),
'unique' => true,
),
'key_expires' => array(
'columns' => array('expires'),
),
),
) + parent::getConfiguration();
}
+ public static function loadClusterTokenForUser(PhabricatorUser $user) {
+ if (!$user->isLoggedIn()) {
+ return null;
+ }
+
+ $tokens = id(new PhabricatorConduitTokenQuery())
+ ->setViewer($user)
+ ->withObjectPHIDs(array($user->getPHID()))
+ ->withTokenTypes(array(self::TYPE_CLUSTER))
+ ->withExpired(false)
+ ->execute();
+
+ // Only return a token if it has at least 5 minutes left before
+ // expiration. Cluster tokens cycle regularly, so we don't want to use
+ // one that's going to expire momentarily.
+ $now = PhabricatorTime::getNow();
+ $must_expire_after = $now + phutil_units('5 minutes in seconds');
+
+ foreach ($tokens as $token) {
+ if ($token->getExpires() > $must_expire_after) {
+ return $token;
+ }
+ }
+
+ // We didn't find any existing tokens (or the existing tokens are all about
+ // to expire) so generate a new token.
+
+ $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
+ $token = PhabricatorConduitToken::initializeNewToken(
+ $user->getPHID(),
+ self::TYPE_CLUSTER);
+ $token->save();
+ unset($unguarded);
+
+ return $token;
+ }
+
public static function initializeNewToken($object_phid, $token_type) {
$token = new PhabricatorConduitToken();
$token->objectPHID = $object_phid;
$token->tokenType = $token_type;
$token->expires = $token->getTokenExpires($token_type);
$secret = $token_type.'-'.Filesystem::readRandomCharacters(32);
$secret = substr($secret, 0, 32);
$token->token = $secret;
return $token;
}
public static function getTokenTypeName($type) {
$map = array(
self::TYPE_STANDARD => pht('Standard API Token'),
- self::TYPE_TEMPORARY => pht('Temporary API Token'),
self::TYPE_COMMANDLINE => pht('Command Line API Token'),
+ self::TYPE_CLUSTER => pht('Cluster API Token'),
);
return idx($map, $type, $type);
}
public static function getAllTokenTypes() {
return array(
self::TYPE_STANDARD,
- self::TYPE_TEMPORARY,
self::TYPE_COMMANDLINE,
+ self::TYPE_CLUSTER,
);
}
private function getTokenExpires($token_type) {
+ $now = PhabricatorTime::getNow();
switch ($token_type) {
case self::TYPE_STANDARD:
return null;
- case self::TYPE_TEMPORARY:
- return PhabricatorTime::getNow() + phutil_units('24 hours in seconds');
case self::TYPE_COMMANDLINE:
- return PhabricatorTime::getNow() + phutil_units('1 hour in seconds');
+ return $now + phutil_units('1 hour in seconds');
+ case self::TYPE_CLUSTER:
+ return $now + phutil_units('30 minutes in seconds');
default:
throw new Exception(
pht('Unknown Conduit token type "%s"!', $token_type));
}
}
+ public function getPublicTokenName() {
+ switch ($this->getTokenType()) {
+ case self::TYPE_CLUSTER:
+ return pht('Cluster API Token');
+ default:
+ return substr($this->getToken(), 0, 8).'...';
+ }
+ }
+
public function getObject() {
return $this->assertAttached($this->object);
}
public function attachObject(PhabricatorUser $object) {
$this->object = $object;
return $this;
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
return $this->getObject()->getPolicy($capability);
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return $this->getObject()->hasAutomaticCapability($capability, $viewer);
}
public function describeAutomaticCapability($capability) {
return pht(
'Conduit tokens inherit the policies of the user they authenticate.');
}
}
diff --git a/src/applications/diffusion/query/DiffusionQuery.php b/src/applications/diffusion/query/DiffusionQuery.php
index 87714249fe..642927c062 100644
--- a/src/applications/diffusion/query/DiffusionQuery.php
+++ b/src/applications/diffusion/query/DiffusionQuery.php
@@ -1,252 +1,256 @@
<?php
abstract class DiffusionQuery extends PhabricatorQuery {
private $request;
final protected function __construct() {
// <protected>
}
protected static function newQueryObject(
$base_class,
DiffusionRequest $request) {
$repository = $request->getRepository();
$obj = self::initQueryObject($base_class, $repository);
$obj->request = $request;
return $obj;
}
final protected static function initQueryObject(
$base_class,
PhabricatorRepository $repository) {
$map = array(
PhabricatorRepositoryType::REPOSITORY_TYPE_GIT => 'Git',
PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL => 'Mercurial',
PhabricatorRepositoryType::REPOSITORY_TYPE_SVN => 'Svn',
);
$name = idx($map, $repository->getVersionControlSystem());
if (!$name) {
throw new Exception('Unsupported VCS!');
}
$class = str_replace('Diffusion', 'Diffusion'.$name, $base_class);
$obj = new $class();
return $obj;
}
final protected function getRequest() {
return $this->request;
}
final public static function callConduitWithDiffusionRequest(
PhabricatorUser $user,
DiffusionRequest $drequest,
$method,
array $params = array()) {
$repository = $drequest->getRepository();
$core_params = array(
'callsign' => $repository->getCallsign(),
);
if ($drequest->getBranch() !== null) {
$core_params['branch'] = $drequest->getBranch();
}
$params = $params + $core_params;
$service_phid = $repository->getAlmanacServicePHID();
if ($service_phid === null) {
return id(new ConduitCall($method, $params))
->setUser($user)
->execute();
}
$service = id(new AlmanacServiceQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withPHIDs(array($service_phid))
->needBindings(true)
->executeOne();
if (!$service) {
throw new Exception(
pht(
'The Alamnac service for this repository is invalid or could not '.
'be loaded.'));
}
$bindings = $service->getBindings();
if (!$bindings) {
throw new Exception(
pht(
'The Alamanc service for this repository is not bound to any '.
'interfaces.'));
}
$uris = array();
foreach ($bindings as $binding) {
$iface = $binding->getInterface();
$protocol = $binding->getAlmanacPropertyValue('protocol');
if ($protocol === 'http') {
$uris[] = 'http://'.$iface->renderDisplayAddress().'/';
} else if ($protocol === 'https' || $protocol === null) {
$uris[] = 'https://'.$iface->renderDisplayAddress().'/';
} else {
throw new Exception(
pht(
'The Almanac service for this repository has a binding to an '.
'invalid interface with an unknown protocol ("%s").',
$protocol));
}
}
shuffle($uris);
$uri = head($uris);
$domain = id(new PhutilURI(PhabricatorEnv::getURI('/')))->getDomain();
- // TODO: This call needs authentication, which is blocked by T5955.
+ $client = id(new ConduitClient($uri))
+ ->setHost($domain);
- return id(new ConduitClient($uri))
- ->setHost($domain)
- ->callMethodSynchronous($method, $params);
+ $token = PhabricatorConduitToken::loadClusterTokenForUser($user);
+ if ($token) {
+ $client->setConduitToken($token->getToken());
+ }
+
+ return $client->callMethodSynchronous($method, $params);
}
public function execute() {
return $this->executeQuery();
}
abstract protected function executeQuery();
/* -( Query Utilities )---------------------------------------------------- */
final public static function loadCommitsByIdentifiers(
array $identifiers,
DiffusionRequest $drequest) {
if (!$identifiers) {
return array();
}
$commits = array();
$commit_data = array();
$repository = $drequest->getRepository();
$commits = id(new PhabricatorRepositoryCommit())->loadAllWhere(
'repositoryID = %d AND commitIdentifier IN (%Ls)',
$repository->getID(),
$identifiers);
$commits = mpull($commits, null, 'getCommitIdentifier');
// Build empty commit objects for every commit, so we can show unparsed
// commits in history views (as "Importing") instead of not showing them.
// This makes the process of importing and parsing commits clearer to the
// user.
$commit_list = array();
foreach ($identifiers as $identifier) {
$commit_obj = idx($commits, $identifier);
if (!$commit_obj) {
$commit_obj = new PhabricatorRepositoryCommit();
$commit_obj->setRepositoryID($repository->getID());
$commit_obj->setCommitIdentifier($identifier);
$commit_obj->makeEphemeral();
}
$commit_list[$identifier] = $commit_obj;
}
$commits = $commit_list;
$commit_ids = array_filter(mpull($commits, 'getID'));
if ($commit_ids) {
$commit_data = id(new PhabricatorRepositoryCommitData())->loadAllWhere(
'commitID in (%Ld)',
$commit_ids);
$commit_data = mpull($commit_data, null, 'getCommitID');
}
foreach ($commits as $commit) {
if (!$commit->getID()) {
continue;
}
if (idx($commit_data, $commit->getID())) {
$commit->attachCommitData($commit_data[$commit->getID()]);
}
}
return $commits;
}
final public static function loadHistoryForCommitIdentifiers(
array $identifiers,
DiffusionRequest $drequest) {
if (!$identifiers) {
return array();
}
$repository = $drequest->getRepository();
$commits = self::loadCommitsByIdentifiers($identifiers, $drequest);
if (!$commits) {
return array();
}
$path = $drequest->getPath();
$conn_r = $repository->establishConnection('r');
$path_normal = DiffusionPathIDQuery::normalizePath($path);
$paths = queryfx_all(
$conn_r,
'SELECT id, path FROM %T WHERE pathHash IN (%Ls)',
PhabricatorRepository::TABLE_PATH,
array(md5($path_normal)));
$paths = ipull($paths, 'id', 'path');
$path_id = idx($paths, $path_normal);
$commit_ids = array_filter(mpull($commits, 'getID'));
$path_changes = array();
if ($path_id && $commit_ids) {
$path_changes = queryfx_all(
$conn_r,
'SELECT * FROM %T WHERE commitID IN (%Ld) AND pathID = %d',
PhabricatorRepository::TABLE_PATHCHANGE,
$commit_ids,
$path_id);
$path_changes = ipull($path_changes, null, 'commitID');
}
$history = array();
foreach ($identifiers as $identifier) {
$item = new DiffusionPathChange();
$item->setCommitIdentifier($identifier);
$commit = idx($commits, $identifier);
if ($commit) {
$item->setCommit($commit);
try {
$item->setCommitData($commit->getCommitData());
} catch (Exception $ex) {
// Ignore, commit just doesn't have data.
}
$change = idx($path_changes, $commit->getID());
if ($change) {
$item->setChangeType($change['changeType']);
$item->setFileType($change['fileType']);
}
}
$history[] = $item;
}
return $history;
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Jan 19, 19:18 (1 d, 20 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1127896
Default Alt Text
(23 KB)
Attached To
Mode
rP Phorge
Attached
Detach File
Event Timeline
Log In to Comment