1
0
Fork 0
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:
epriestley 2013-11-20 09:13:35 -08:00
parent 557121a709
commit 91d084624b
30 changed files with 1715 additions and 13 deletions

View 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;

View file

@ -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',

View file

@ -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'),

View file

@ -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',
));
}
}

View file

@ -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;
}
}

View file

@ -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,
));
}
}

View file

@ -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);
}
}

View file

@ -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,
));
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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();
}
}

View file

@ -0,0 +1,10 @@
<?php
abstract class PassphraseCredentialTypeSSHPrivateKey
extends PassphraseCredentialType {
final public function getProvidesType() {
return 'provides/ssh-key-file';
}
}

View 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();
}
}

View file

@ -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');
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View 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';
}
}

View file

@ -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);
}
}

View file

@ -0,0 +1,10 @@
<?php
final class PassphraseCredentialTransactionQuery
extends PhabricatorApplicationTransactionQuery {
public function getTemplateApplicationTransaction() {
return new PassphraseCredentialTransaction();
}
}

View 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;
}
}

View file

@ -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();
}
}

View file

@ -0,0 +1,9 @@
<?php
abstract class PassphraseDAO extends PhabricatorLiskDAO {
public function getApplicationName() {
return 'passphrase';
}
}

View file

@ -0,0 +1,13 @@
<?php
final class PassphraseSecret extends PassphraseDAO {
protected $secretData;
public function getConfiguration() {
return array(
self::CONFIG_TIMESTAMPS => false,
) + parent::getConfiguration();
}
}

View file

@ -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:

View file

@ -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;
}
}

View file

@ -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 )------------------------------------------------------- */

View file

@ -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'),
),
);
}
}

View file

@ -103,6 +103,7 @@ final class AphrontFormPolicyControl extends AphrontFormControl {
$options,
array(
PhabricatorPolicyType::TYPE_GLOBAL,
PhabricatorPolicyType::TYPE_USER,
PhabricatorPolicyType::TYPE_CUSTOM,
PhabricatorPolicyType::TYPE_PROJECT,
));