mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-26 08:42:41 +01:00
Passphrase v0
Summary: Ref T4122. Implements a credential management application for the uses described in T4122. @chad, this needs an icon, HA HA HAHA HA BWW HA HA HA bwahaha Test Plan: See screenshots. Reviewers: btrahan, chad Reviewed By: btrahan CC: chad, aran Maniphest Tasks: T4122 Differential Revision: https://secure.phabricator.com/D7608
This commit is contained in:
parent
557121a709
commit
91d084624b
30 changed files with 1715 additions and 13 deletions
46
resources/sql/patches/20131119.passphrase.sql
Normal file
46
resources/sql/patches/20131119.passphrase.sql
Normal file
|
@ -0,0 +1,46 @@
|
|||
CREATE TABLE {$NAMESPACE}_passphrase.passphrase_credential (
|
||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
phid VARCHAR(64) NOT NULL COLLATE utf8_bin,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
credentialType VARCHAR(64) NOT NULL COLLATE utf8_bin,
|
||||
providesType VARCHAR(64) NOT NULL COLLATE utf8_bin,
|
||||
viewPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin,
|
||||
editPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin,
|
||||
description LONGTEXT NOT NULL COLLATE utf8_bin,
|
||||
username VARCHAR(255) NOT NULL,
|
||||
secretID INT UNSIGNED,
|
||||
isDestroyed BOOL NOT NULL,
|
||||
dateCreated INT UNSIGNED NOT NULL,
|
||||
dateModified INT UNSIGNED NOT NULL,
|
||||
|
||||
UNIQUE KEY `key_phid` (phid),
|
||||
KEY `key_type` (credentialType),
|
||||
KEY `key_provides` (providesType),
|
||||
UNIQUE KEY `key_secret` (secretID)
|
||||
) ENGINE=InnoDB, COLLATE utf8_general_ci;
|
||||
|
||||
CREATE TABLE {$NAMESPACE}_passphrase.passphrase_secret (
|
||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
secretData LONGBLOB NOT NULL
|
||||
) ENGINE=InnoDB, COLLATE utf8_general_ci;
|
||||
|
||||
CREATE TABLE {$NAMESPACE}_passphrase.passphrase_credentialtransaction (
|
||||
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
phid VARCHAR(64) NOT NULL COLLATE utf8_bin,
|
||||
authorPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
|
||||
objectPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
|
||||
viewPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin,
|
||||
editPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin,
|
||||
commentPHID VARCHAR(64) COLLATE utf8_bin,
|
||||
commentVersion INT UNSIGNED NOT NULL,
|
||||
transactionType VARCHAR(32) NOT NULL COLLATE utf8_bin,
|
||||
oldValue LONGTEXT NOT NULL COLLATE utf8_bin,
|
||||
newValue LONGTEXT NOT NULL COLLATE utf8_bin,
|
||||
contentSource LONGTEXT NOT NULL COLLATE utf8_bin,
|
||||
metadata LONGTEXT NOT NULL COLLATE utf8_bin,
|
||||
dateCreated INT UNSIGNED NOT NULL,
|
||||
dateModified INT UNSIGNED NOT NULL,
|
||||
|
||||
UNIQUE KEY `key_phid` (phid),
|
||||
KEY `key_object` (objectPHID)
|
||||
) ENGINE=InnoDB, COLLATE utf8_general_ci;
|
|
@ -943,6 +943,27 @@ phutil_register_library_map(array(
|
|||
'PackageDeleteMail' => 'applications/owners/mail/PackageDeleteMail.php',
|
||||
'PackageMail' => 'applications/owners/mail/PackageMail.php',
|
||||
'PackageModifyMail' => 'applications/owners/mail/PackageModifyMail.php',
|
||||
'PassphraseController' => 'applications/passphrase/controller/PassphraseController.php',
|
||||
'PassphraseCredential' => 'applications/passphrase/storage/PassphraseCredential.php',
|
||||
'PassphraseCredentialCreateController' => 'applications/passphrase/controller/PassphraseCredentialCreateController.php',
|
||||
'PassphraseCredentialDestroyController' => 'applications/passphrase/controller/PassphraseCredentialDestroyController.php',
|
||||
'PassphraseCredentialEditController' => 'applications/passphrase/controller/PassphraseCredentialEditController.php',
|
||||
'PassphraseCredentialListController' => 'applications/passphrase/controller/PassphraseCredentialListController.php',
|
||||
'PassphraseCredentialQuery' => 'applications/passphrase/query/PassphraseCredentialQuery.php',
|
||||
'PassphraseCredentialRevealController' => 'applications/passphrase/controller/PassphraseCredentialRevealController.php',
|
||||
'PassphraseCredentialSearchEngine' => 'applications/passphrase/query/PassphraseCredentialSearchEngine.php',
|
||||
'PassphraseCredentialTransaction' => 'applications/passphrase/storage/PassphraseCredentialTransaction.php',
|
||||
'PassphraseCredentialTransactionEditor' => 'applications/passphrase/editor/PassphraseCredentialTransactionEditor.php',
|
||||
'PassphraseCredentialTransactionQuery' => 'applications/passphrase/query/PassphraseCredentialTransactionQuery.php',
|
||||
'PassphraseCredentialType' => 'applications/passphrase/credentialtype/PassphraseCredentialType.php',
|
||||
'PassphraseCredentialTypePassword' => 'applications/passphrase/credentialtype/PassphraseCredentialTypePassword.php',
|
||||
'PassphraseCredentialTypeSSHPrivateKey' => 'applications/passphrase/credentialtype/PassphraseCredentialTypeSSHPrivateKey.php',
|
||||
'PassphraseCredentialTypeSSHPrivateKeyFile' => 'applications/passphrase/credentialtype/PassphraseCredentialTypeSSHPrivateKeyFile.php',
|
||||
'PassphraseCredentialTypeSSHPrivateKeyText' => 'applications/passphrase/credentialtype/PassphraseCredentialTypeSSHPrivateKeyText.php',
|
||||
'PassphraseCredentialViewController' => 'applications/passphrase/controller/PassphraseCredentialViewController.php',
|
||||
'PassphraseDAO' => 'applications/passphrase/storage/PassphraseDAO.php',
|
||||
'PassphrasePHIDTypeCredential' => 'applications/passphrase/phid/PassphrasePHIDTypeCredential.php',
|
||||
'PassphraseSecret' => 'applications/passphrase/storage/PassphraseSecret.php',
|
||||
'PasteCapabilityDefaultView' => 'applications/paste/capability/PasteCapabilityDefaultView.php',
|
||||
'PasteCreateMailReceiver' => 'applications/paste/mail/PasteCreateMailReceiver.php',
|
||||
'PasteEmbedView' => 'applications/paste/view/PasteEmbedView.php',
|
||||
|
@ -998,6 +1019,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorApplicationOwners' => 'applications/owners/application/PhabricatorApplicationOwners.php',
|
||||
'PhabricatorApplicationPHIDTypeApplication' => 'applications/meta/phid/PhabricatorApplicationPHIDTypeApplication.php',
|
||||
'PhabricatorApplicationPHPAST' => 'applications/phpast/application/PhabricatorApplicationPHPAST.php',
|
||||
'PhabricatorApplicationPassphrase' => 'applications/passphrase/application/PhabricatorApplicationPassphrase.php',
|
||||
'PhabricatorApplicationPaste' => 'applications/paste/application/PhabricatorApplicationPaste.php',
|
||||
'PhabricatorApplicationPeople' => 'applications/people/application/PhabricatorApplicationPeople.php',
|
||||
'PhabricatorApplicationPhame' => 'applications/phame/application/PhabricatorApplicationPhame.php',
|
||||
|
@ -3299,6 +3321,35 @@ phutil_register_library_map(array(
|
|||
'PackageDeleteMail' => 'PackageMail',
|
||||
'PackageMail' => 'PhabricatorMail',
|
||||
'PackageModifyMail' => 'PackageMail',
|
||||
'PassphraseController' => 'PhabricatorController',
|
||||
'PassphraseCredential' =>
|
||||
array(
|
||||
0 => 'PassphraseDAO',
|
||||
1 => 'PhabricatorPolicyInterface',
|
||||
),
|
||||
'PassphraseCredentialCreateController' => 'PassphraseController',
|
||||
'PassphraseCredentialDestroyController' => 'PassphraseController',
|
||||
'PassphraseCredentialEditController' => 'PassphraseController',
|
||||
'PassphraseCredentialListController' =>
|
||||
array(
|
||||
0 => 'PassphraseController',
|
||||
1 => 'PhabricatorApplicationSearchResultsControllerInterface',
|
||||
),
|
||||
'PassphraseCredentialQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||
'PassphraseCredentialRevealController' => 'PassphraseController',
|
||||
'PassphraseCredentialSearchEngine' => 'PhabricatorApplicationSearchEngine',
|
||||
'PassphraseCredentialTransaction' => 'PhabricatorApplicationTransaction',
|
||||
'PassphraseCredentialTransactionEditor' => 'PhabricatorApplicationTransactionEditor',
|
||||
'PassphraseCredentialTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
||||
'PassphraseCredentialType' => 'Phobject',
|
||||
'PassphraseCredentialTypePassword' => 'PassphraseCredentialType',
|
||||
'PassphraseCredentialTypeSSHPrivateKey' => 'PassphraseCredentialType',
|
||||
'PassphraseCredentialTypeSSHPrivateKeyFile' => 'PassphraseCredentialTypeSSHPrivateKey',
|
||||
'PassphraseCredentialTypeSSHPrivateKeyText' => 'PassphraseCredentialTypeSSHPrivateKey',
|
||||
'PassphraseCredentialViewController' => 'PassphraseController',
|
||||
'PassphraseDAO' => 'PhabricatorLiskDAO',
|
||||
'PassphrasePHIDTypeCredential' => 'PhabricatorPHIDType',
|
||||
'PassphraseSecret' => 'PassphraseDAO',
|
||||
'PasteCapabilityDefaultView' => 'PhabricatorPolicyCapability',
|
||||
'PasteCreateMailReceiver' => 'PhabricatorMailReceiver',
|
||||
'PasteEmbedView' => 'AphrontView',
|
||||
|
@ -3353,6 +3404,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorApplicationOwners' => 'PhabricatorApplication',
|
||||
'PhabricatorApplicationPHIDTypeApplication' => 'PhabricatorPHIDType',
|
||||
'PhabricatorApplicationPHPAST' => 'PhabricatorApplication',
|
||||
'PhabricatorApplicationPassphrase' => 'PhabricatorApplication',
|
||||
'PhabricatorApplicationPaste' => 'PhabricatorApplication',
|
||||
'PhabricatorApplicationPeople' => 'PhabricatorApplication',
|
||||
'PhabricatorApplicationPhame' => 'PhabricatorApplication',
|
||||
|
|
|
@ -64,14 +64,11 @@ final class HarbormasterBuildPlanEditor
|
|||
|
||||
switch ($type) {
|
||||
case HarbormasterBuildPlanTransaction::TYPE_NAME:
|
||||
$missing_name = true;
|
||||
if (strlen($object->getName()) && empty($xactions)) {
|
||||
$missing_name = false;
|
||||
} else if (strlen(last($xactions)->getNewValue())) {
|
||||
$missing_name = false;
|
||||
}
|
||||
$missing = $this->validateIsEmptyTextField(
|
||||
$object->getName(),
|
||||
$xactions);
|
||||
|
||||
if ($missing_name) {
|
||||
if ($missing) {
|
||||
$error = new PhabricatorApplicationTransactionValidationError(
|
||||
$type,
|
||||
pht('Required'),
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorApplicationPassphrase extends PhabricatorApplication {
|
||||
|
||||
public function getBaseURI() {
|
||||
return '/passphrase/';
|
||||
}
|
||||
|
||||
public function getShortDescription() {
|
||||
return pht('Credential Management');
|
||||
}
|
||||
|
||||
public function getIconName() {
|
||||
return 'passphrase';
|
||||
}
|
||||
|
||||
public function getTitleGlyph() {
|
||||
return "\xE2\x97\x88";
|
||||
}
|
||||
|
||||
public function getFlavorText() {
|
||||
return pht('Put your secrets in a lockbox.');
|
||||
}
|
||||
|
||||
public function getApplicationGroup() {
|
||||
return self::GROUP_UTILITIES;
|
||||
}
|
||||
|
||||
public function isBeta() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getRoutes() {
|
||||
return array(
|
||||
'/K(?P<id>\d+)' => 'PassphraseCredentialViewController',
|
||||
'/passphrase/' => array(
|
||||
'(?:query/(?P<queryKey>[^/]+)/)?'
|
||||
=> 'PassphraseCredentialListController',
|
||||
'create/' => 'PassphraseCredentialCreateController',
|
||||
'edit/(?:(?P<id>\d+)/)?' => 'PassphraseCredentialEditController',
|
||||
'destroy/(?P<id>\d+)/' => 'PassphraseCredentialDestroyController',
|
||||
'reveal/(?P<id>\d+)/' => 'PassphraseCredentialRevealController',
|
||||
));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
abstract class PassphraseController extends PhabricatorController {
|
||||
|
||||
public function buildSideNavView($for_app = false) {
|
||||
$user = $this->getRequest()->getUser();
|
||||
|
||||
$nav = new AphrontSideNavFilterView();
|
||||
$nav->setBaseURI(new PhutilURI($this->getApplicationURI()));
|
||||
|
||||
if ($for_app) {
|
||||
$nav->addFilter('create', pht('Create Credential'));
|
||||
}
|
||||
|
||||
id(new PassphraseCredentialSearchEngine())
|
||||
->setViewer($user)
|
||||
->addNavigationItems($nav->getMenu());
|
||||
|
||||
$nav->selectFilter(null);
|
||||
|
||||
return $nav;
|
||||
}
|
||||
|
||||
public function buildApplicationMenu() {
|
||||
return $this->buildSideNavView(true)->getMenu();
|
||||
}
|
||||
|
||||
public function buildApplicationCrumbs() {
|
||||
$crumbs = parent::buildApplicationCrumbs();
|
||||
|
||||
$crumbs->addAction(
|
||||
id(new PHUIListItemView())
|
||||
->setName(pht('Create Credential'))
|
||||
->setHref($this->getApplicationURI('create/'))
|
||||
->setIcon('create'));
|
||||
|
||||
return $crumbs;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
<?php
|
||||
|
||||
final class PassphraseCredentialCreateController extends PassphraseController {
|
||||
|
||||
public function processRequest() {
|
||||
$request = $this->getRequest();
|
||||
$viewer = $request->getUser();
|
||||
|
||||
$types = PassphraseCredentialType::getAllTypes();
|
||||
$types = mpull($types, null, 'getCredentialType');
|
||||
$types = msort($types, 'getCredentialTypeName');
|
||||
|
||||
$errors = array();
|
||||
$e_type = null;
|
||||
|
||||
if ($request->isFormPost()) {
|
||||
$type = $request->getStr('type');
|
||||
if (empty($types[$type])) {
|
||||
$errors[] = pht('You must choose a credential type.');
|
||||
$e_type = pht('Required');
|
||||
}
|
||||
|
||||
if (!$errors) {
|
||||
$uri = $this->getApplicationURI('edit/?type='.$type);
|
||||
return id(new AphrontRedirectResponse())->setURI($uri);
|
||||
}
|
||||
}
|
||||
|
||||
$error_view = null;
|
||||
if ($errors) {
|
||||
$error_view = id(new AphrontErrorView())
|
||||
->setErrors($errors);
|
||||
}
|
||||
|
||||
$types_control = id(new AphrontFormRadioButtonControl())
|
||||
->setName('type')
|
||||
->setLabel(pht('Credential Type'))
|
||||
->setError($e_type);
|
||||
|
||||
foreach ($types as $type) {
|
||||
$types_control->addButton(
|
||||
$type->getCredentialType(),
|
||||
$type->getCredentialTypeName(),
|
||||
$type->getCredentialTypeDescription());
|
||||
}
|
||||
|
||||
$form = id(new AphrontFormView())
|
||||
->setUser($viewer)
|
||||
->appendChild($types_control)
|
||||
->appendChild(
|
||||
id(new AphrontFormSubmitControl())
|
||||
->setValue(pht('Continue'))
|
||||
->addCancelButton($this->getApplicationURI()));
|
||||
|
||||
$title = pht('New Credential');
|
||||
|
||||
$crumbs = $this->buildApplicationCrumbs();
|
||||
$crumbs->addCrumb(
|
||||
id(new PhabricatorCrumbView())
|
||||
->setName(pht('Create')));
|
||||
|
||||
$box = id(new PHUIObjectBoxView())
|
||||
->setHeaderText(pht('Create New Credential'))
|
||||
->setFormError($error_view)
|
||||
->setForm($form);
|
||||
|
||||
return $this->buildApplicationPage(
|
||||
array(
|
||||
$crumbs,
|
||||
$box,
|
||||
),
|
||||
array(
|
||||
'title' => $title,
|
||||
'device' => true,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
|
||||
final class PassphraseCredentialDestroyController
|
||||
extends PassphraseController {
|
||||
|
||||
private $id;
|
||||
|
||||
public function willProcessRequest(array $data) {
|
||||
$this->id = $data['id'];
|
||||
}
|
||||
|
||||
public function processRequest() {
|
||||
$request = $this->getRequest();
|
||||
$viewer = $request->getUser();
|
||||
|
||||
$credential = id(new PassphraseCredentialQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($this->id))
|
||||
->requireCapabilities(
|
||||
array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
PhabricatorPolicyCapability::CAN_EDIT,
|
||||
))
|
||||
->executeOne();
|
||||
if (!$credential) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$type = PassphraseCredentialType::getTypeByConstant(
|
||||
$credential->getCredentialType());
|
||||
if (!$type) {
|
||||
throw new Exception(pht('Credential has invalid type "%s"!', $type));
|
||||
}
|
||||
|
||||
$view_uri = '/K'.$credential->getID();
|
||||
|
||||
if ($request->isFormPost()) {
|
||||
|
||||
$xactions = array();
|
||||
$xactions[] = id(new PassphraseCredentialTransaction())
|
||||
->setTransactionType(PassphraseCredentialTransaction::TYPE_DESTROY)
|
||||
->setNewValue(1);
|
||||
|
||||
$editor = id(new PassphraseCredentialTransactionEditor())
|
||||
->setActor($viewer)
|
||||
->setContinueOnMissingFields(true)
|
||||
->setContentSourceFromRequest($request)
|
||||
->applyTransactions($credential, $xactions);
|
||||
|
||||
return id(new AphrontRedirectResponse())->setURI($view_uri);
|
||||
}
|
||||
|
||||
$dialog = id(new AphrontDialogView())
|
||||
->setUser($viewer)
|
||||
->setTitle(pht('Really destroy credential?'))
|
||||
->appendChild(
|
||||
pht(
|
||||
'This credential will be deactivated and the secret will be '.
|
||||
'unrecoverably destroyed. Anything relying on this credential will '.
|
||||
'cease to function. This operation can not be undone.'))
|
||||
->addSubmitButton(pht('Destroy Credential'))
|
||||
->addCancelButton($view_uri);
|
||||
|
||||
return id(new AphrontDialogResponse())->setDialog($dialog);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,241 @@
|
|||
<?php
|
||||
|
||||
final class PassphraseCredentialEditController extends PassphraseController {
|
||||
|
||||
private $id;
|
||||
|
||||
public function willProcessRequest(array $data) {
|
||||
$this->id = idx($data, 'id');
|
||||
}
|
||||
|
||||
public function processRequest() {
|
||||
$request = $this->getRequest();
|
||||
$viewer = $request->getUser();
|
||||
|
||||
if ($this->id) {
|
||||
$credential = id(new PassphraseCredentialQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($this->id))
|
||||
->requireCapabilities(
|
||||
array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
PhabricatorPolicyCapability::CAN_EDIT,
|
||||
))
|
||||
->executeOne();
|
||||
if (!$credential) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$type = PassphraseCredentialType::getTypeByConstant(
|
||||
$credential->getCredentialType());
|
||||
if (!$type) {
|
||||
throw new Exception(pht('Credential has invalid type "%s"!', $type));
|
||||
}
|
||||
|
||||
$is_new = false;
|
||||
} else {
|
||||
$type_const = $request->getStr('type');
|
||||
$type = PassphraseCredentialType::getTypeByConstant($type_const);
|
||||
if (!$type) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$credential = PassphraseCredential::initializeNewCredential($viewer)
|
||||
->setCredentialType($type->getCredentialType())
|
||||
->setProvidesType($type->getProvidesType());
|
||||
|
||||
$is_new = true;
|
||||
}
|
||||
|
||||
$errors = array();
|
||||
|
||||
$v_name = $credential->getName();
|
||||
$e_name = true;
|
||||
|
||||
$v_desc = $credential->getDescription();
|
||||
|
||||
$v_username = $credential->getUsername();
|
||||
$e_username = true;
|
||||
|
||||
$bullet = "\xE2\x80\xA2";
|
||||
|
||||
$v_secret = $credential->getSecretID() ? str_repeat($bullet, 32) : null;
|
||||
|
||||
$validation_exception = null;
|
||||
if ($request->isFormPost()) {
|
||||
$v_name = $request->getStr('name');
|
||||
$v_desc = $request->getStr('description');
|
||||
$v_username = $request->getStr('username');
|
||||
$v_secret = $request->getStr('secret');
|
||||
$v_view_policy = $request->getStr('viewPolicy');
|
||||
$v_edit_policy = $request->getStr('editPolicy');
|
||||
|
||||
$type_name = PassphraseCredentialTransaction::TYPE_NAME;
|
||||
$type_desc = PassphraseCredentialTransaction::TYPE_DESCRIPTION;
|
||||
$type_username = PassphraseCredentialTransaction::TYPE_USERNAME;
|
||||
$type_destroy = PassphraseCredentialTransaction::TYPE_DESTROY;
|
||||
$type_secret_id = PassphraseCredentialTransaction::TYPE_SECRET_ID;
|
||||
$type_view_policy = PhabricatorTransactions::TYPE_VIEW_POLICY;
|
||||
$type_edit_policy = PhabricatorTransactions::TYPE_EDIT_POLICY;
|
||||
|
||||
$xactions = array();
|
||||
|
||||
$xactions[] = id(new PassphraseCredentialTransaction())
|
||||
->setTransactionType($type_name)
|
||||
->setNewValue($v_name);
|
||||
|
||||
$xactions[] = id(new PassphraseCredentialTransaction())
|
||||
->setTransactionType($type_desc)
|
||||
->setNewValue($v_desc);
|
||||
|
||||
$xactions[] = id(new PassphraseCredentialTransaction())
|
||||
->setTransactionType($type_username)
|
||||
->setNewValue($v_username);
|
||||
|
||||
$xactions[] = id(new PassphraseCredentialTransaction())
|
||||
->setTransactionType($type_view_policy)
|
||||
->setNewValue($v_view_policy);
|
||||
|
||||
$xactions[] = id(new PassphraseCredentialTransaction())
|
||||
->setTransactionType($type_edit_policy)
|
||||
->setNewValue($v_edit_policy);
|
||||
|
||||
// Open a transaction in case we're writing a new secret; this limits
|
||||
// the amount of code which handles secret plaintexts.
|
||||
$credential->openTransaction();
|
||||
|
||||
$min_secret = str_replace($bullet, '', trim($v_secret));
|
||||
if (strlen($min_secret)) {
|
||||
// If the credential was previously destroyed, restore it when it is
|
||||
// edited if a secret is provided.
|
||||
$xactions[] = id(new PassphraseCredentialTransaction())
|
||||
->setTransactionType($type_destroy)
|
||||
->setNewValue(0);
|
||||
|
||||
$new_secret = id(new PassphraseSecret())
|
||||
->setSecretData($v_secret)
|
||||
->save();
|
||||
$xactions[] = id(new PassphraseCredentialTransaction())
|
||||
->setTransactionType($type_secret_id)
|
||||
->setNewValue($new_secret->getID());
|
||||
}
|
||||
|
||||
try {
|
||||
$editor = id(new PassphraseCredentialTransactionEditor())
|
||||
->setActor($viewer)
|
||||
->setContinueOnNoEffect(true)
|
||||
->setContentSourceFromRequest($request)
|
||||
->applyTransactions($credential, $xactions);
|
||||
|
||||
$credential->saveTransaction();
|
||||
|
||||
return id(new AphrontRedirectResponse())
|
||||
->setURI('/K'.$credential->getID());
|
||||
} catch (PhabricatorApplicationTransactionValidationException $ex) {
|
||||
$credential->killTransaction();
|
||||
|
||||
$validation_exception = $ex;
|
||||
|
||||
$e_name = $ex->getShortMessage($type_name);
|
||||
$e_username = $ex->getShortMessage($type_username);
|
||||
|
||||
$credential->setViewPolicy($v_view_policy);
|
||||
$credential->setEditPolicy($v_edit_policy);
|
||||
}
|
||||
}
|
||||
|
||||
$policies = id(new PhabricatorPolicyQuery())
|
||||
->setViewer($viewer)
|
||||
->setObject($credential)
|
||||
->execute();
|
||||
|
||||
$secret_control = $type->newSecretControl();
|
||||
|
||||
$form = id(new AphrontFormView())
|
||||
->setUser($viewer)
|
||||
->appendChild(
|
||||
id(new AphrontFormTextControl())
|
||||
->setName('name')
|
||||
->setLabel(pht('Name'))
|
||||
->setValue($v_name)
|
||||
->setError($e_name))
|
||||
->appendChild(
|
||||
id(new AphrontFormTextAreaControl())
|
||||
->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_SHORT)
|
||||
->setName('description')
|
||||
->setLabel(pht('Description'))
|
||||
->setValue($v_desc))
|
||||
->appendChild(
|
||||
id(new AphrontFormMarkupControl())
|
||||
->setLabel(pht('Credential Type'))
|
||||
->setValue($type->getCredentialTypeName()))
|
||||
->appendChild(
|
||||
id(new AphrontFormDividerControl()))
|
||||
->appendChild(
|
||||
id(new AphrontFormPolicyControl())
|
||||
->setName('viewPolicy')
|
||||
->setPolicyObject($credential)
|
||||
->setCapability(PhabricatorPolicyCapability::CAN_VIEW)
|
||||
->setPolicies($policies))
|
||||
->appendChild(
|
||||
id(new AphrontFormPolicyControl())
|
||||
->setName('editPolicy')
|
||||
->setPolicyObject($credential)
|
||||
->setCapability(PhabricatorPolicyCapability::CAN_EDIT)
|
||||
->setPolicies($policies))
|
||||
->appendChild(
|
||||
id(new AphrontFormDividerControl()))
|
||||
->appendChild(
|
||||
id(new AphrontFormTextControl())
|
||||
->setName('username')
|
||||
->setLabel(pht('Login/Username'))
|
||||
->setValue($v_username)
|
||||
->setError($e_username))
|
||||
->appendChild(
|
||||
$secret_control
|
||||
->setName('secret')
|
||||
->setLabel($type->getSecretLabel())
|
||||
->setValue($v_secret));
|
||||
|
||||
$form->appendChild(
|
||||
id(new AphrontFormSubmitControl())
|
||||
->setValue(pht('Save'))
|
||||
->addCancelButton($this->getApplicationURI()));
|
||||
|
||||
$crumbs = $this->buildApplicationCrumbs();
|
||||
|
||||
if ($is_new) {
|
||||
$title = pht('Create Credential');
|
||||
$header = pht('Create New Credential');
|
||||
$crumbs->addCrumb(
|
||||
id(new PhabricatorCrumbView())
|
||||
->setName(pht('Create')));
|
||||
} else {
|
||||
$title = pht('Edit Credential');
|
||||
$header = pht('Edit Credential %s', 'K'.$credential->getID());
|
||||
$crumbs->addCrumb(
|
||||
id(new PhabricatorCrumbView())
|
||||
->setName('K'.$credential->getID())
|
||||
->setHref('/K'.$credential->getID()));
|
||||
$crumbs->addCrumb(
|
||||
id(new PhabricatorCrumbView())
|
||||
->setName(pht('Edit')));
|
||||
}
|
||||
|
||||
$box = id(new PHUIObjectBoxView())
|
||||
->setHeaderText($header)
|
||||
->setValidationException($validation_exception)
|
||||
->setForm($form);
|
||||
|
||||
return $this->buildApplicationPage(
|
||||
array(
|
||||
$crumbs,
|
||||
$box,
|
||||
),
|
||||
array(
|
||||
'title' => $title,
|
||||
'device' => true,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
final class PassphraseCredentialListController extends PassphraseController
|
||||
implements PhabricatorApplicationSearchResultsControllerInterface {
|
||||
|
||||
private $queryKey;
|
||||
|
||||
public function shouldAllowPublic() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function willProcessRequest(array $data) {
|
||||
$this->queryKey = idx($data, 'queryKey');
|
||||
}
|
||||
|
||||
public function processRequest() {
|
||||
$request = $this->getRequest();
|
||||
$controller = id(new PhabricatorApplicationSearchController($request))
|
||||
->setQueryKey($this->queryKey)
|
||||
->setSearchEngine(new PassphraseCredentialSearchEngine())
|
||||
->setNavigation($this->buildSideNavView());
|
||||
|
||||
return $this->delegateToController($controller);
|
||||
}
|
||||
|
||||
public function renderResultsList(
|
||||
array $credentials,
|
||||
PhabricatorSavedQuery $query) {
|
||||
assert_instances_of($credentials, 'PassphraseCredential');
|
||||
|
||||
$viewer = $this->getRequest()->getUser();
|
||||
|
||||
$list = new PHUIObjectItemListView();
|
||||
$list->setUser($viewer);
|
||||
foreach ($credentials as $credential) {
|
||||
|
||||
$item = id(new PHUIObjectItemView())
|
||||
->setObjectName('K'.$credential->getID())
|
||||
->setHeader($credential->getName())
|
||||
->setHref('/K'.$credential->getID())
|
||||
->setObject($credential);
|
||||
|
||||
$item->addAttribute(
|
||||
pht('Login: %s', $credential->getUsername()));
|
||||
|
||||
if ($credential->getIsDestroyed()) {
|
||||
$item->addIcon('disable', pht('Destroyed'));
|
||||
$item->setDisabled(true);
|
||||
}
|
||||
|
||||
$type = PassphraseCredentialType::getTypeByConstant(
|
||||
$credential->getCredentialType());
|
||||
if ($type) {
|
||||
$item->addIcon('wrench', $type->getCredentialTypeName());
|
||||
}
|
||||
|
||||
$list->addItem($item);
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
<?php
|
||||
|
||||
final class PassphraseCredentialRevealController
|
||||
extends PassphraseController {
|
||||
|
||||
private $id;
|
||||
|
||||
public function willProcessRequest(array $data) {
|
||||
$this->id = $data['id'];
|
||||
}
|
||||
|
||||
public function processRequest() {
|
||||
$request = $this->getRequest();
|
||||
$viewer = $request->getUser();
|
||||
|
||||
$credential = id(new PassphraseCredentialQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($this->id))
|
||||
->requireCapabilities(
|
||||
array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
PhabricatorPolicyCapability::CAN_EDIT,
|
||||
))
|
||||
->needSecrets(true)
|
||||
->executeOne();
|
||||
if (!$credential) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$view_uri = '/K'.$credential->getID();
|
||||
|
||||
if ($request->isFormPost()) {
|
||||
if ($credential->getSecret()) {
|
||||
$body = id(new PHUIFormLayoutView())
|
||||
->appendChild(
|
||||
id(new AphrontFormTextAreaControl())
|
||||
->setLabel(pht('Plaintext'))
|
||||
->setValue($credential->getSecret()->openEnvelope()));
|
||||
} else {
|
||||
$body = pht('This credential has no associated secret.');
|
||||
}
|
||||
|
||||
$dialog = id(new AphrontDialogView())
|
||||
->setUser($viewer)
|
||||
->setTitle(pht('Credential Secret'))
|
||||
->appendChild($body)
|
||||
->addCancelButton($view_uri, pht('Done'));
|
||||
|
||||
return id(new AphrontDialogResponse())->setDialog($dialog);
|
||||
}
|
||||
|
||||
$is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
|
||||
if ($is_serious) {
|
||||
$body = pht(
|
||||
'The secret associated with this credential will be shown in plain '.
|
||||
'text on your screen.');
|
||||
} else {
|
||||
$body = pht(
|
||||
'The secret associated with this credential will be shown in plain '.
|
||||
'text on your screen. Before continuing, wrap your arms around your '.
|
||||
'monitor to create a human shield, keeping it safe from prying eyes. '.
|
||||
'Protect company secrets!');
|
||||
}
|
||||
|
||||
$dialog = id(new AphrontDialogView())
|
||||
->setUser($viewer)
|
||||
->setTitle(pht('Really show secret?'))
|
||||
->appendChild($body)
|
||||
->addSubmitButton(pht('Show Secret'))
|
||||
->addCancelButton($view_uri);
|
||||
|
||||
return id(new AphrontDialogResponse())->setDialog($dialog);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,170 @@
|
|||
<?php
|
||||
|
||||
final class PassphraseCredentialViewController extends PassphraseController {
|
||||
|
||||
private $id;
|
||||
|
||||
public function willProcessRequest(array $data) {
|
||||
$this->id = $data['id'];
|
||||
}
|
||||
|
||||
public function processRequest() {
|
||||
$request = $this->getRequest();
|
||||
$viewer = $request->getUser();
|
||||
|
||||
$credential = id(new PassphraseCredentialQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($this->id))
|
||||
->executeOne();
|
||||
if (!$credential) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$type = PassphraseCredentialType::getTypeByConstant(
|
||||
$credential->getCredentialType());
|
||||
if (!$type) {
|
||||
throw new Exception(pht('Credential has invalid type "%s"!', $type));
|
||||
}
|
||||
|
||||
$xactions = id(new PassphraseCredentialTransactionQuery())
|
||||
->setViewer($viewer)
|
||||
->withObjectPHIDs(array($credential->getPHID()))
|
||||
->execute();
|
||||
|
||||
$engine = id(new PhabricatorMarkupEngine())
|
||||
->setViewer($viewer);
|
||||
|
||||
$timeline = id(new PhabricatorApplicationTransactionView())
|
||||
->setUser($viewer)
|
||||
->setObjectPHID($credential->getPHID())
|
||||
->setTransactions($xactions);
|
||||
|
||||
$title = pht('%s %s', 'K'.$credential->getID(), $credential->getName());
|
||||
$crumbs = $this->buildApplicationCrumbs();
|
||||
$crumbs->addCrumb(
|
||||
id(new PhabricatorCrumbView())
|
||||
->setName('K'.$credential->getID()));
|
||||
|
||||
$header = $this->buildHeaderView($credential);
|
||||
$actions = $this->buildActionView($credential);
|
||||
$properties = $this->buildPropertyView($credential, $type, $actions);
|
||||
|
||||
$box = id(new PHUIObjectBoxView())
|
||||
->setHeader($header)
|
||||
->addPropertyList($properties);
|
||||
|
||||
return $this->buildApplicationPage(
|
||||
array(
|
||||
$crumbs,
|
||||
$box,
|
||||
$timeline,
|
||||
),
|
||||
array(
|
||||
'title' => $title,
|
||||
'device' => true,
|
||||
));
|
||||
}
|
||||
|
||||
private function buildHeaderView(PassphraseCredential $credential) {
|
||||
$viewer = $this->getRequest()->getUser();
|
||||
|
||||
$header = id(new PHUIHeaderView())
|
||||
->setUser($viewer)
|
||||
->setHeader($credential->getName())
|
||||
->setPolicyObject($credential);
|
||||
|
||||
if ($credential->getIsDestroyed()) {
|
||||
$header->setStatus('reject', 'red', pht('Destroyed'));
|
||||
}
|
||||
|
||||
return $header;
|
||||
}
|
||||
|
||||
private function buildActionView(PassphraseCredential $credential) {
|
||||
$viewer = $this->getRequest()->getUser();
|
||||
|
||||
$id = $credential->getID();
|
||||
|
||||
$actions = id(new PhabricatorActionListView())
|
||||
->setObjectURI('/K'.$id)
|
||||
->setUser($viewer);
|
||||
|
||||
$can_edit = PhabricatorPolicyFilter::hasCapability(
|
||||
$viewer,
|
||||
$credential,
|
||||
PhabricatorPolicyCapability::CAN_EDIT);
|
||||
|
||||
$actions->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setName(pht('Edit Credential'))
|
||||
->setIcon('edit')
|
||||
->setHref($this->getApplicationURI("edit/{$id}/"))
|
||||
->setDisabled(!$can_edit)
|
||||
->setWorkflow(!$can_edit));
|
||||
|
||||
if (!$credential->getIsDestroyed()) {
|
||||
$actions->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setName(pht('Destroy Credential'))
|
||||
->setIcon('delete')
|
||||
->setHref($this->getApplicationURI("destroy/{$id}/"))
|
||||
->setDisabled(!$can_edit)
|
||||
->setWorkflow(true));
|
||||
|
||||
$actions->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setName(pht('Show Secret'))
|
||||
->setIcon('preview')
|
||||
->setHref($this->getApplicationURI("reveal/{$id}/"))
|
||||
->setDisabled(!$can_edit)
|
||||
->setWorkflow(true));
|
||||
}
|
||||
|
||||
|
||||
return $actions;
|
||||
}
|
||||
|
||||
private function buildPropertyView(
|
||||
PassphraseCredential $credential,
|
||||
PassphraseCredentialType $type,
|
||||
PhabricatorActionListView $actions) {
|
||||
$viewer = $this->getRequest()->getUser();
|
||||
|
||||
$properties = id(new PHUIPropertyListView())
|
||||
->setUser($viewer)
|
||||
->setObject($credential)
|
||||
->setActionList($actions);
|
||||
|
||||
$properties->addProperty(
|
||||
pht('Credential Type'),
|
||||
$type->getCredentialTypeName());
|
||||
|
||||
$descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions(
|
||||
$viewer,
|
||||
$credential);
|
||||
|
||||
$properties->addProperty(
|
||||
pht('Editable By'),
|
||||
$descriptions[PhabricatorPolicyCapability::CAN_EDIT]);
|
||||
|
||||
$properties->addProperty(
|
||||
pht('Username'),
|
||||
$credential->getUsername());
|
||||
|
||||
$description = $credential->getDescription();
|
||||
if (strlen($description)) {
|
||||
$properties->addSectionHeader(
|
||||
pht('Description'),
|
||||
PHUIPropertyListView::ICON_SUMMARY);
|
||||
$properties->addTextContent(
|
||||
PhabricatorMarkupEngine::renderOneObject(
|
||||
id(new PhabricatorMarkupOneOff())
|
||||
->setContent($description),
|
||||
'default',
|
||||
$viewer));
|
||||
}
|
||||
|
||||
return $properties;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
abstract class PassphraseCredentialType extends Phobject {
|
||||
|
||||
abstract public function getCredentialType();
|
||||
abstract public function getProvidesType();
|
||||
abstract public function getCredentialTypeName();
|
||||
abstract public function getCredentialTypeDescription();
|
||||
abstract public function getSecretLabel();
|
||||
|
||||
public function newSecretControl() {
|
||||
return new AphrontFormTextAreaControl();
|
||||
}
|
||||
|
||||
public static function getAllTypes() {
|
||||
$types = id(new PhutilSymbolLoader())
|
||||
->setAncestorClass(__CLASS__)
|
||||
->loadObjects();
|
||||
return $types;
|
||||
}
|
||||
|
||||
public static function getTypeByConstant($constant) {
|
||||
$all = self::getAllTypes();
|
||||
$all = mpull($all, null, 'getCredentialType');
|
||||
return idx($all, $constant);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
final class PassphraseCredentialTypePassword
|
||||
extends PassphraseCredentialType {
|
||||
|
||||
public function getCredentialType() {
|
||||
return 'password';
|
||||
}
|
||||
|
||||
public function getProvidesType() {
|
||||
return 'provides/password';
|
||||
}
|
||||
|
||||
public function getCredentialTypeName() {
|
||||
return pht('Password');
|
||||
}
|
||||
|
||||
public function getCredentialTypeDescription() {
|
||||
return pht('Store a plaintext password.');
|
||||
}
|
||||
|
||||
public function getSecretLabel() {
|
||||
return pht('Password');
|
||||
}
|
||||
|
||||
public function newSecretControl() {
|
||||
return new AphrontFormPasswordControl();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
abstract class PassphraseCredentialTypeSSHPrivateKey
|
||||
extends PassphraseCredentialType {
|
||||
|
||||
final public function getProvidesType() {
|
||||
return 'provides/ssh-key-file';
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
final class PassphraseCredentialTypeSSHPrivateKeyFile
|
||||
extends PassphraseCredentialTypeSSHPrivateKey {
|
||||
|
||||
public function getCredentialType() {
|
||||
return 'ssh-key-file';
|
||||
}
|
||||
|
||||
public function getCredentialTypeName() {
|
||||
return pht('SSH Private Key File');
|
||||
}
|
||||
|
||||
public function getCredentialTypeDescription() {
|
||||
return pht('Store the path on disk to an SSH private key.');
|
||||
}
|
||||
|
||||
public function getSecretLabel() {
|
||||
return pht('Path On Disk');
|
||||
}
|
||||
|
||||
public function newSecretControl() {
|
||||
return new AphrontFormTextControl();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
final class PassphraseCredentialTypeSSHPrivateKeyText
|
||||
extends PassphraseCredentialTypeSSHPrivateKey {
|
||||
|
||||
public function getCredentialType() {
|
||||
return 'ssh-key-text';
|
||||
}
|
||||
|
||||
public function getCredentialTypeName() {
|
||||
return pht('SSH Private Key');
|
||||
}
|
||||
|
||||
public function getCredentialTypeDescription() {
|
||||
return pht('Store the plaintext of an SSH private key.');
|
||||
}
|
||||
|
||||
public function getSecretLabel() {
|
||||
return pht('Private Key');
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,164 @@
|
|||
<?php
|
||||
|
||||
final class PassphraseCredentialTransactionEditor
|
||||
extends PhabricatorApplicationTransactionEditor {
|
||||
|
||||
public function getTransactionTypes() {
|
||||
$types = parent::getTransactionTypes();
|
||||
|
||||
$types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
|
||||
$types[] = PhabricatorTransactions::TYPE_EDIT_POLICY;
|
||||
|
||||
$types[] = PassphraseCredentialTransaction::TYPE_NAME;
|
||||
$types[] = PassphraseCredentialTransaction::TYPE_DESCRIPTION;
|
||||
$types[] = PassphraseCredentialTransaction::TYPE_USERNAME;
|
||||
$types[] = PassphraseCredentialTransaction::TYPE_SECRET_ID;
|
||||
$types[] = PassphraseCredentialTransaction::TYPE_DESTROY;
|
||||
|
||||
return $types;
|
||||
}
|
||||
|
||||
protected function getCustomTransactionOldValue(
|
||||
PhabricatorLiskDAO $object,
|
||||
PhabricatorApplicationTransaction $xaction) {
|
||||
switch ($xaction->getTransactionType()) {
|
||||
case PassphraseCredentialTransaction::TYPE_NAME:
|
||||
if ($this->getIsNewObject()) {
|
||||
return null;
|
||||
}
|
||||
return $object->getName();
|
||||
case PassphraseCredentialTransaction::TYPE_DESCRIPTION:
|
||||
return $object->getDescription();
|
||||
case PassphraseCredentialTransaction::TYPE_USERNAME:
|
||||
return $object->getUsername();
|
||||
case PassphraseCredentialTransaction::TYPE_SECRET_ID:
|
||||
return $object->getSecretID();
|
||||
case PassphraseCredentialTransaction::TYPE_DESTROY:
|
||||
return $object->getIsDestroyed();
|
||||
}
|
||||
|
||||
return parent::getCustomTransactionOldValue($object, $xaction);
|
||||
}
|
||||
|
||||
protected function getCustomTransactionNewValue(
|
||||
PhabricatorLiskDAO $object,
|
||||
PhabricatorApplicationTransaction $xaction) {
|
||||
switch ($xaction->getTransactionType()) {
|
||||
case PassphraseCredentialTransaction::TYPE_NAME:
|
||||
case PassphraseCredentialTransaction::TYPE_DESCRIPTION:
|
||||
case PassphraseCredentialTransaction::TYPE_USERNAME:
|
||||
case PassphraseCredentialTransaction::TYPE_SECRET_ID:
|
||||
case PassphraseCredentialTransaction::TYPE_DESTROY:
|
||||
return $xaction->getNewValue();
|
||||
}
|
||||
return parent::getCustomTransactionNewValue($object, $xaction);
|
||||
}
|
||||
|
||||
protected function applyCustomInternalTransaction(
|
||||
PhabricatorLiskDAO $object,
|
||||
PhabricatorApplicationTransaction $xaction) {
|
||||
switch ($xaction->getTransactionType()) {
|
||||
case PassphraseCredentialTransaction::TYPE_NAME:
|
||||
$object->setName($xaction->getNewValue());
|
||||
return;
|
||||
case PassphraseCredentialTransaction::TYPE_DESCRIPTION:
|
||||
$object->setDescription($xaction->getNewValue());
|
||||
return;
|
||||
case PassphraseCredentialTransaction::TYPE_USERNAME:
|
||||
$object->setUsername($xaction->getNewValue());
|
||||
return;
|
||||
case PassphraseCredentialTransaction::TYPE_SECRET_ID:
|
||||
$old_id = $object->getSecretID();
|
||||
if ($old_id) {
|
||||
$this->destroySecret($old_id);
|
||||
}
|
||||
$object->setSecretID($xaction->getNewValue());
|
||||
return;
|
||||
case PassphraseCredentialTransaction::TYPE_DESTROY:
|
||||
// When destroying a credential, wipe out its secret.
|
||||
$is_destroyed = $xaction->getNewValue();
|
||||
$object->setIsDestroyed($is_destroyed);
|
||||
if ($is_destroyed) {
|
||||
$secret_id = $object->getSecretID();
|
||||
if ($secret_id) {
|
||||
$this->destroySecret($secret_id);
|
||||
$object->setSecretID(null);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
return parent::applyCustomInternalTransaction($object, $xaction);
|
||||
}
|
||||
|
||||
protected function applyCustomExternalTransaction(
|
||||
PhabricatorLiskDAO $object,
|
||||
PhabricatorApplicationTransaction $xaction) {
|
||||
|
||||
switch ($xaction->getTransactionType()) {
|
||||
case PassphraseCredentialTransaction::TYPE_NAME:
|
||||
case PassphraseCredentialTransaction::TYPE_DESCRIPTION:
|
||||
case PassphraseCredentialTransaction::TYPE_USERNAME:
|
||||
case PassphraseCredentialTransaction::TYPE_SECRET_ID:
|
||||
case PassphraseCredentialTransaction::TYPE_DESTROY:
|
||||
return;
|
||||
}
|
||||
|
||||
return parent::applyCustomExternalTransaction($object, $xaction);
|
||||
}
|
||||
|
||||
private function destroySecret($secret_id) {
|
||||
$table = new PassphraseSecret();
|
||||
queryfx(
|
||||
$table->establishConnection('w'),
|
||||
'DELETE FROM %T WHERE id = %d',
|
||||
$table->getTableName(),
|
||||
$secret_id);
|
||||
}
|
||||
|
||||
protected function validateTransaction(
|
||||
PhabricatorLiskDAO $object,
|
||||
$type,
|
||||
array $xactions) {
|
||||
|
||||
$errors = parent::validateTransaction($object, $type, $xactions);
|
||||
|
||||
switch ($type) {
|
||||
case PassphraseCredentialTransaction::TYPE_NAME:
|
||||
$missing = $this->validateIsEmptyTextField(
|
||||
$object->getName(),
|
||||
$xactions);
|
||||
|
||||
if ($missing) {
|
||||
$error = new PhabricatorApplicationTransactionValidationError(
|
||||
$type,
|
||||
pht('Required'),
|
||||
pht('Credential name is required.'),
|
||||
nonempty(last($xactions), null));
|
||||
|
||||
$error->setIsMissingFieldError(true);
|
||||
$errors[] = $error;
|
||||
}
|
||||
break;
|
||||
case PassphraseCredentialTransaction::TYPE_USERNAME:
|
||||
$missing = $this->validateIsEmptyTextField(
|
||||
$object->getUsername(),
|
||||
$xactions);
|
||||
|
||||
if ($missing) {
|
||||
$error = new PhabricatorApplicationTransactionValidationError(
|
||||
$type,
|
||||
pht('Required'),
|
||||
pht('Username is required.'),
|
||||
nonempty(last($xactions), null));
|
||||
|
||||
$error->setIsMissingFieldError(true);
|
||||
$errors[] = $error;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
<?php
|
||||
|
||||
final class PassphrasePHIDTypeCredential extends PhabricatorPHIDType {
|
||||
|
||||
const TYPECONST = 'CDTL';
|
||||
|
||||
public function getTypeConstant() {
|
||||
return self::TYPECONST;
|
||||
}
|
||||
|
||||
public function getTypeName() {
|
||||
return pht('Credential');
|
||||
}
|
||||
|
||||
public function newObject() {
|
||||
return new PassphraseCredential();
|
||||
}
|
||||
|
||||
protected function buildQueryForObjects(
|
||||
PhabricatorObjectQuery $query,
|
||||
array $phids) {
|
||||
|
||||
return id(new PassphraseCredentialQuery())
|
||||
->withPHIDs($phids);
|
||||
}
|
||||
|
||||
public function loadHandles(
|
||||
PhabricatorHandleQuery $query,
|
||||
array $handles,
|
||||
array $objects) {
|
||||
|
||||
foreach ($handles as $phid => $handle) {
|
||||
$credential = $objects[$phid];
|
||||
$id = $credential->getID();
|
||||
$name = $credential->getName();
|
||||
|
||||
$handle->setName("K{$id}");
|
||||
$handle->setFullName("K{$id} {$name}");
|
||||
$handle->setURI("/K{$id}");
|
||||
|
||||
if ($credential->getIsDestroyed()) {
|
||||
$handle->setStatus(PhabricatorObjectHandleStatus::STATUS_CLOSED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function canLoadNamedObject($name) {
|
||||
return preg_match('/^K\d*[1-9]\d*$/i', $name);
|
||||
}
|
||||
|
||||
public function loadNamedObjects(
|
||||
PhabricatorObjectQuery $query,
|
||||
array $names) {
|
||||
|
||||
$id_map = array();
|
||||
foreach ($names as $name) {
|
||||
$id = (int)substr($name, 1);
|
||||
$id_map[$id][] = $name;
|
||||
}
|
||||
|
||||
$objects = id(new PassphraseCredentialQuery())
|
||||
->setViewer($query->getViewer())
|
||||
->withIDs(array_keys($id_map))
|
||||
->execute();
|
||||
|
||||
$results = array();
|
||||
foreach ($objects as $id => $object) {
|
||||
foreach (idx($id_map, $id, array()) as $name) {
|
||||
$results[$name] = $object;
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
}
|
137
src/applications/passphrase/query/PassphraseCredentialQuery.php
Normal file
137
src/applications/passphrase/query/PassphraseCredentialQuery.php
Normal file
|
@ -0,0 +1,137 @@
|
|||
<?php
|
||||
|
||||
final class PassphraseCredentialQuery
|
||||
extends PhabricatorCursorPagedPolicyAwareQuery {
|
||||
|
||||
private $ids;
|
||||
private $phids;
|
||||
private $credentialTypes;
|
||||
private $providesTypes;
|
||||
private $isDestroyed;
|
||||
|
||||
private $needSecrets;
|
||||
|
||||
public function withIDs(array $ids) {
|
||||
$this->ids = $ids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withPHIDs(array $phids) {
|
||||
$this->phids = $phids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withCredentialTypes(array $credential_types) {
|
||||
$this->credentialTypes = $credential_types;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withProvidesTypes(array $provides_types) {
|
||||
$this->providesTypes = $provides_types;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withIsDestroyed($destroyed) {
|
||||
$this->isDestroyed = $destroyed;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function needSecrets($need_secrets) {
|
||||
$this->needSecrets = $need_secrets;
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function loadPage() {
|
||||
$table = new PassphraseCredential();
|
||||
$conn_r = $table->establishConnection('r');
|
||||
|
||||
$rows = 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($rows);
|
||||
}
|
||||
|
||||
protected function willFilterPage(array $page) {
|
||||
if ($this->needSecrets) {
|
||||
$secret_ids = mpull($page, 'getSecretID');
|
||||
$secret_ids = array_filter($secret_ids);
|
||||
|
||||
$secrets = array();
|
||||
if ($secret_ids) {
|
||||
$secret_objects = id(new PassphraseSecret())->loadAllWhere(
|
||||
'id IN (%Ld)',
|
||||
$secret_ids);
|
||||
foreach ($secret_objects as $secret) {
|
||||
$secret_data = $secret->getSecretData();
|
||||
$secrets[$secret->getID()] = new PhutilOpaqueEnvelope($secret_data);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($page as $key => $credential) {
|
||||
$secret_id = $credential->getSecretID();
|
||||
if (!$secret_id) {
|
||||
$credential->attachSecret(null);
|
||||
} else if (isset($secrets[$secret_id])) {
|
||||
$credential->attachSecret($secrets[$secret_id]);
|
||||
} else {
|
||||
unset($page[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $page;
|
||||
}
|
||||
|
||||
private function buildWhereClause(AphrontDatabaseConnection $conn_r) {
|
||||
$where = array();
|
||||
|
||||
$where[] = $this->buildPagingClause($conn_r);
|
||||
|
||||
if ($this->ids) {
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
'id IN (%Ld)',
|
||||
$this->ids);
|
||||
}
|
||||
|
||||
if ($this->phids) {
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
'phid IN (%Ls)',
|
||||
$this->phids);
|
||||
}
|
||||
|
||||
if ($this->credentialTypes) {
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
'credentialType in (%Ls)',
|
||||
$this->credentialTypes);
|
||||
}
|
||||
|
||||
if ($this->providesTypes) {
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
'providesType IN (%Ls)',
|
||||
$this->providesTypes);
|
||||
}
|
||||
|
||||
if ($this->isDestroyed !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
'isDestroyed = %d',
|
||||
(int)$this->isDestroyed);
|
||||
}
|
||||
|
||||
return $this->formatWhereClause($where);
|
||||
}
|
||||
|
||||
public function getQueryApplicationClass() {
|
||||
return 'PhabricatorApplicationPassphrase';
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
|
||||
final class PassphraseCredentialSearchEngine
|
||||
extends PhabricatorApplicationSearchEngine {
|
||||
|
||||
public function buildSavedQueryFromRequest(AphrontRequest $request) {
|
||||
$saved = new PhabricatorSavedQuery();
|
||||
|
||||
$saved->setParameter(
|
||||
'isDestroyed',
|
||||
$this->readBoolFromRequest($request, 'isDestroyed'));
|
||||
|
||||
return $saved;
|
||||
}
|
||||
|
||||
public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) {
|
||||
$query = id(new PassphraseCredentialQuery());
|
||||
|
||||
$destroyed = $saved->getParameter('isDestroyed');
|
||||
if ($destroyed !== null) {
|
||||
$query->withIsDestroyed($destroyed);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
public function buildSearchForm(
|
||||
AphrontFormView $form,
|
||||
PhabricatorSavedQuery $saved_query) {
|
||||
|
||||
$form->appendChild(
|
||||
id(new AphrontFormSelectControl())
|
||||
->setName('isDestroyed')
|
||||
->setLabel(pht('Status'))
|
||||
->setValue($this->getBoolFromQuery($saved_query, 'isDestroyed'))
|
||||
->setOptions(
|
||||
array(
|
||||
'' => pht('Show All Credentials'),
|
||||
'false' => pht('Show Only Active Credentials'),
|
||||
'true' => pht('Show Only Destroyed Credentials'),
|
||||
)));
|
||||
|
||||
}
|
||||
|
||||
protected function getURI($path) {
|
||||
return '/passphrase/'.$path;
|
||||
}
|
||||
|
||||
public function getBuiltinQueryNames() {
|
||||
$names = array(
|
||||
'active' => pht('Active Credentials'),
|
||||
'all' => pht('All Credentials'),
|
||||
);
|
||||
|
||||
return $names;
|
||||
}
|
||||
|
||||
public function buildSavedQueryFromBuiltin($query_key) {
|
||||
|
||||
$query = $this->newSavedQuery();
|
||||
$query->setQueryKey($query_key);
|
||||
|
||||
switch ($query_key) {
|
||||
case 'all':
|
||||
return $query;
|
||||
case 'active':
|
||||
return $query->setParameter('isDestroyed', false);
|
||||
}
|
||||
|
||||
return parent::buildSavedQueryFromBuiltin($query_key);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
final class PassphraseCredentialTransactionQuery
|
||||
extends PhabricatorApplicationTransactionQuery {
|
||||
|
||||
public function getTemplateApplicationTransaction() {
|
||||
return new PassphraseCredentialTransaction();
|
||||
}
|
||||
|
||||
}
|
75
src/applications/passphrase/storage/PassphraseCredential.php
Normal file
75
src/applications/passphrase/storage/PassphraseCredential.php
Normal file
|
@ -0,0 +1,75 @@
|
|||
<?php
|
||||
|
||||
final class PassphraseCredential extends PassphraseDAO
|
||||
implements PhabricatorPolicyInterface {
|
||||
|
||||
protected $name;
|
||||
protected $credentialType;
|
||||
protected $providesType;
|
||||
protected $viewPolicy;
|
||||
protected $editPolicy;
|
||||
protected $description;
|
||||
protected $username;
|
||||
protected $secretID;
|
||||
protected $isDestroyed;
|
||||
|
||||
private $secret = self::ATTACHABLE;
|
||||
|
||||
public static function initializeNewCredential(PhabricatorUser $actor) {
|
||||
return id(new PassphraseCredential())
|
||||
->setName('')
|
||||
->setUsername('')
|
||||
->setIsDestroyed(0)
|
||||
->setViewPolicy($actor->getPHID())
|
||||
->setEditPolicy($actor->getPHID());
|
||||
}
|
||||
|
||||
public function getConfiguration() {
|
||||
return array(
|
||||
self::CONFIG_AUX_PHID => true,
|
||||
) + parent::getConfiguration();
|
||||
}
|
||||
|
||||
public function generatePHID() {
|
||||
return PhabricatorPHID::generateNewPHID(
|
||||
PassphrasePHIDTypeCredential::TYPECONST);
|
||||
}
|
||||
|
||||
public function attachSecret(PhutilOpaqueEnvelope $secret = null) {
|
||||
$this->secret = $secret;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getSecret() {
|
||||
return $this->assertAttached($this->secret);
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
||||
|
||||
|
||||
public function getCapabilities() {
|
||||
return array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
PhabricatorPolicyCapability::CAN_EDIT,
|
||||
);
|
||||
}
|
||||
|
||||
public function getPolicy($capability) {
|
||||
switch ($capability) {
|
||||
case PhabricatorPolicyCapability::CAN_VIEW:
|
||||
return $this->getViewPolicy();
|
||||
case PhabricatorPolicyCapability::CAN_EDIT:
|
||||
return $this->getEditPolicy();
|
||||
}
|
||||
}
|
||||
|
||||
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function describeAutomaticCapability($capability) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
<?php
|
||||
|
||||
final class PassphraseCredentialTransaction
|
||||
extends PhabricatorApplicationTransaction {
|
||||
|
||||
const TYPE_NAME = 'passphrase:name';
|
||||
const TYPE_DESCRIPTION = 'passphrase:description';
|
||||
const TYPE_USERNAME = 'passphrase:username';
|
||||
const TYPE_SECRET_ID = 'passphrase:secretID';
|
||||
const TYPE_DESTROY = 'passphrase:destroy';
|
||||
|
||||
public function getApplicationName() {
|
||||
return 'passphrase';
|
||||
}
|
||||
|
||||
public function getApplicationTransactionType() {
|
||||
return PassphrasePHIDTypeCredential::TYPECONST;
|
||||
}
|
||||
|
||||
public function getApplicationTransactionCommentObject() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public function shouldHide() {
|
||||
$old = $this->getOldValue();
|
||||
switch ($this->getTransactionType()) {
|
||||
case self::TYPE_DESCRIPTION:
|
||||
return ($old === null);
|
||||
case self::TYPE_USERNAME:
|
||||
return !strlen($old);
|
||||
}
|
||||
return parent::shouldHide();
|
||||
}
|
||||
|
||||
public function getTitle() {
|
||||
$old = $this->getOldValue();
|
||||
$new = $this->getNewValue();
|
||||
$author_phid = $this->getAuthorPHID();
|
||||
|
||||
switch ($this->getTransactionType()) {
|
||||
case self::TYPE_NAME:
|
||||
if ($old === null) {
|
||||
return pht(
|
||||
'%s created this credential.',
|
||||
$this->renderHandleLink($author_phid));
|
||||
} else {
|
||||
return pht(
|
||||
'%s renamed this credential from "%s" to "%s".',
|
||||
$this->renderHandleLink($author_phid),
|
||||
$old,
|
||||
$new);
|
||||
}
|
||||
break;
|
||||
case self::TYPE_DESCRIPTION:
|
||||
return pht(
|
||||
'%s updated the description for this credential.',
|
||||
$this->renderHandleLink($author_phid));
|
||||
case self::TYPE_USERNAME:
|
||||
if (strlen($old)) {
|
||||
return pht(
|
||||
'%s changed the username for this credential from "%s" to "%s".',
|
||||
$this->renderHandleLink($author_phid),
|
||||
$old,
|
||||
$new);
|
||||
} else {
|
||||
return pht(
|
||||
'%s set the username for this credential to "%s".',
|
||||
$this->renderHandleLink($author_phid),
|
||||
$new);
|
||||
}
|
||||
break;
|
||||
case self::TYPE_SECRET_ID:
|
||||
return pht(
|
||||
'%s updated the secret for this credential.',
|
||||
$this->renderHandleLink($author_phid));
|
||||
case self::TYPE_DESTROY:
|
||||
return pht(
|
||||
'%s destroyed this credential.',
|
||||
$this->renderHandleLink($author_phid));
|
||||
}
|
||||
|
||||
return parent::getTitle();
|
||||
}
|
||||
|
||||
public function hasChangeDetails() {
|
||||
switch ($this->getTransactionType()) {
|
||||
case self::TYPE_DESCRIPTION:
|
||||
return true;
|
||||
}
|
||||
return parent::hasChangeDetails();
|
||||
}
|
||||
|
||||
public function renderChangeDetails(PhabricatorUser $viewer) {
|
||||
$old = $this->getOldValue();
|
||||
$new = $this->getNewValue();
|
||||
|
||||
$view = id(new PhabricatorApplicationTransactionTextDiffDetailView())
|
||||
->setUser($viewer)
|
||||
->setOldText(json_encode($old))
|
||||
->setNewText(json_encode($new));
|
||||
|
||||
return $view->render();
|
||||
}
|
||||
|
||||
|
||||
}
|
9
src/applications/passphrase/storage/PassphraseDAO.php
Normal file
9
src/applications/passphrase/storage/PassphraseDAO.php
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
|
||||
abstract class PassphraseDAO extends PhabricatorLiskDAO {
|
||||
|
||||
public function getApplicationName() {
|
||||
return 'passphrase';
|
||||
}
|
||||
|
||||
}
|
13
src/applications/passphrase/storage/PassphraseSecret.php
Normal file
13
src/applications/passphrase/storage/PassphraseSecret.php
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
final class PassphraseSecret extends PassphraseDAO {
|
||||
|
||||
protected $secretData;
|
||||
|
||||
public function getConfiguration() {
|
||||
return array(
|
||||
self::CONFIG_TIMESTAMPS => false,
|
||||
) + parent::getConfiguration();
|
||||
}
|
||||
|
||||
}
|
|
@ -3,6 +3,7 @@
|
|||
final class PhabricatorPolicyType extends PhabricatorPolicyConstants {
|
||||
|
||||
const TYPE_GLOBAL = 'global';
|
||||
const TYPE_USER = 'user';
|
||||
const TYPE_CUSTOM = 'custom';
|
||||
const TYPE_PROJECT = 'project';
|
||||
const TYPE_MASKED = 'masked';
|
||||
|
@ -10,8 +11,9 @@ final class PhabricatorPolicyType extends PhabricatorPolicyConstants {
|
|||
public static function getPolicyTypeOrder($type) {
|
||||
static $map = array(
|
||||
self::TYPE_GLOBAL => 0,
|
||||
self::TYPE_CUSTOM => 1,
|
||||
self::TYPE_PROJECT => 2,
|
||||
self::TYPE_USER => 1,
|
||||
self::TYPE_CUSTOM => 2,
|
||||
self::TYPE_PROJECT => 3,
|
||||
self::TYPE_MASKED => 9,
|
||||
);
|
||||
return idx($map, $type, 9);
|
||||
|
@ -21,6 +23,8 @@ final class PhabricatorPolicyType extends PhabricatorPolicyConstants {
|
|||
switch ($type) {
|
||||
case self::TYPE_GLOBAL:
|
||||
return pht('Basic Policies');
|
||||
case self::TYPE_USER:
|
||||
return pht('User Policies');
|
||||
case self::TYPE_CUSTOM:
|
||||
return pht('Advanced');
|
||||
case self::TYPE_PROJECT:
|
||||
|
|
|
@ -66,6 +66,10 @@ final class PhabricatorPolicy
|
|||
$policy->setType(PhabricatorPolicyType::TYPE_PROJECT);
|
||||
$policy->setName($handle->getName());
|
||||
break;
|
||||
case PhabricatorPeoplePHIDTypeUser::TYPECONST:
|
||||
$policy->setType(PhabricatorPolicyType::TYPE_USER);
|
||||
$policy->setName($handle->getFullName());
|
||||
break;
|
||||
case PhabricatorPolicyPHIDTypePolicy::TYPECONST:
|
||||
// TODO: This creates a weird handle-based version of a rule policy.
|
||||
// It behaves correctly, but can't be applied since it doesn't have
|
||||
|
@ -138,17 +142,15 @@ final class PhabricatorPolicy
|
|||
PhabricatorPolicies::POLICY_NOONE => 'policy-noone',
|
||||
);
|
||||
return idx($map, $this->getPHID(), 'policy-unknown');
|
||||
break;
|
||||
case PhabricatorPolicyType::TYPE_USER:
|
||||
return 'policy-user';
|
||||
case PhabricatorPolicyType::TYPE_PROJECT:
|
||||
return 'policy-project';
|
||||
break;
|
||||
case PhabricatorPolicyType::TYPE_CUSTOM:
|
||||
case PhabricatorPolicyType::TYPE_MASKED:
|
||||
return 'policy-custom';
|
||||
break;
|
||||
default:
|
||||
return 'policy-unknown';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1129,6 +1129,39 @@ abstract class PhabricatorApplicationTransactionEditor
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check for a missing text field.
|
||||
*
|
||||
* A text field is missing if the object has no value and there are no
|
||||
* transactions which set a value, or if the transactions remove the value.
|
||||
* This method is intended to make implementing @{method:validateTransaction}
|
||||
* more convenient:
|
||||
*
|
||||
* $missing = $this->validateIsEmptyTextField(
|
||||
* $object->getName(),
|
||||
* $xactions);
|
||||
*
|
||||
* This will return `true` if the net effect of the object and transactions
|
||||
* is an empty field.
|
||||
*
|
||||
* @param wild Current field value.
|
||||
* @param list<PhabricatorApplicationTransaction> Transactions editing the
|
||||
* field.
|
||||
* @return bool True if the field will be an empty text field after edits.
|
||||
*/
|
||||
protected function validateIsEmptyTextField($field_value, array $xactions) {
|
||||
if (strlen($field_value) && empty($xactions)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($xactions && strlen(last($xactions)->getNewValue())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/* -( Implicit CCs )------------------------------------------------------- */
|
||||
|
||||
|
||||
|
|
|
@ -212,6 +212,10 @@ final class PhabricatorBuiltinPatchList extends PhabricatorSQLPatchList {
|
|||
'type' => 'db',
|
||||
'name' => 'nuance',
|
||||
),
|
||||
'db.passphrase' => array(
|
||||
'type' => 'db',
|
||||
'name' => 'passphrase',
|
||||
),
|
||||
'0000.legacy.sql' => array(
|
||||
'type' => 'sql',
|
||||
'name' => $this->getPatchPath('0000.legacy.sql'),
|
||||
|
@ -1760,6 +1764,10 @@ final class PhabricatorBuiltinPatchList extends PhabricatorSQLPatchList {
|
|||
'type' => 'php',
|
||||
'name' => $this->getPatchPath('20131118.ownerorder.php'),
|
||||
),
|
||||
'20131119.passphrase.sql' => array(
|
||||
'type' => 'sql',
|
||||
'name' => $this->getPatchPath('20131119.passphrase.sql'),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -103,6 +103,7 @@ final class AphrontFormPolicyControl extends AphrontFormControl {
|
|||
$options,
|
||||
array(
|
||||
PhabricatorPolicyType::TYPE_GLOBAL,
|
||||
PhabricatorPolicyType::TYPE_USER,
|
||||
PhabricatorPolicyType::TYPE_CUSTOM,
|
||||
PhabricatorPolicyType::TYPE_PROJECT,
|
||||
));
|
||||
|
|
Loading…
Reference in a new issue