1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-22 23:02:42 +01:00

Add a "Lock Permanently" action to Passphrase

Summary: Fixes T4931. Each new credential should come with the ability to lock the credential permanently, so that no one can ever edit again. Each existing credential must allow user to lock existing credential.

Test Plan: Create new credential, verify that you can lock it before saving it. Open existing unlocked credential, verify that option to lock it exists. Once credential is locked, the option to reveal it should be disabled, and editing the credential won't allow username/password updates.

Reviewers: #blessed_reviewers, epriestley

Reviewed By: #blessed_reviewers, epriestley

Subscribers: epriestley, Korvin

Maniphest Tasks: T4931

Differential Revision: https://secure.phabricator.com/D8947
This commit is contained in:
lkassianik 2014-05-02 18:05:19 -07:00 committed by epriestley
parent c995e93bc1
commit d7b7b19337
11 changed files with 183 additions and 32 deletions

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_passphrase.passphrase_credential
ADD COLUMN isLocked BOOL NOT NULL;

View file

@ -1046,6 +1046,7 @@ phutil_register_library_map(array(
'PassphraseCredentialDestroyController' => 'applications/passphrase/controller/PassphraseCredentialDestroyController.php', 'PassphraseCredentialDestroyController' => 'applications/passphrase/controller/PassphraseCredentialDestroyController.php',
'PassphraseCredentialEditController' => 'applications/passphrase/controller/PassphraseCredentialEditController.php', 'PassphraseCredentialEditController' => 'applications/passphrase/controller/PassphraseCredentialEditController.php',
'PassphraseCredentialListController' => 'applications/passphrase/controller/PassphraseCredentialListController.php', 'PassphraseCredentialListController' => 'applications/passphrase/controller/PassphraseCredentialListController.php',
'PassphraseCredentialLockController' => 'applications/passphrase/controller/PassphraseCredentialLockController.php',
'PassphraseCredentialPublicController' => 'applications/passphrase/controller/PassphraseCredentialPublicController.php', 'PassphraseCredentialPublicController' => 'applications/passphrase/controller/PassphraseCredentialPublicController.php',
'PassphraseCredentialQuery' => 'applications/passphrase/query/PassphraseCredentialQuery.php', 'PassphraseCredentialQuery' => 'applications/passphrase/query/PassphraseCredentialQuery.php',
'PassphraseCredentialRevealController' => 'applications/passphrase/controller/PassphraseCredentialRevealController.php', 'PassphraseCredentialRevealController' => 'applications/passphrase/controller/PassphraseCredentialRevealController.php',
@ -3806,6 +3807,7 @@ phutil_register_library_map(array(
0 => 'PassphraseController', 0 => 'PassphraseController',
1 => 'PhabricatorApplicationSearchResultsControllerInterface', 1 => 'PhabricatorApplicationSearchResultsControllerInterface',
), ),
'PassphraseCredentialLockController' => 'PassphraseController',
'PassphraseCredentialPublicController' => 'PassphraseController', 'PassphraseCredentialPublicController' => 'PassphraseController',
'PassphraseCredentialQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PassphraseCredentialQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PassphraseCredentialRevealController' => 'PassphraseController', 'PassphraseCredentialRevealController' => 'PassphraseController',

View file

@ -41,6 +41,7 @@ final class PhabricatorApplicationPassphrase extends PhabricatorApplication {
'destroy/(?P<id>\d+)/' => 'PassphraseCredentialDestroyController', 'destroy/(?P<id>\d+)/' => 'PassphraseCredentialDestroyController',
'reveal/(?P<id>\d+)/' => 'PassphraseCredentialRevealController', 'reveal/(?P<id>\d+)/' => 'PassphraseCredentialRevealController',
'public/(?P<id>\d+)/' => 'PassphraseCredentialPublicController', 'public/(?P<id>\d+)/' => 'PassphraseCredentialPublicController',
'lock/(?P<id>\d+)/' => 'PassphraseCredentialLockController',
)); ));
} }

View file

@ -50,18 +50,16 @@ final class PassphraseCredentialDestroyController
return id(new AphrontRedirectResponse())->setURI($view_uri); return id(new AphrontRedirectResponse())->setURI($view_uri);
} }
$dialog = id(new AphrontDialogView()) return $this->newDialog()
->setUser($viewer) ->setUser($viewer)
->setTitle(pht('Really destroy credential?')) ->setTitle(pht('Really destroy credential?'))
->appendChild( ->appendChild(
pht( pht(
'This credential will be deactivated and the secret will be '. 'This credential will be deactivated and the secret will be '.
'unrecoverably destroyed. Anything relying on this credential will '. 'unrecoverably destroyed. Anything relying on this credential '.
'cease to function. This operation can not be undone.')) 'will cease to function. This operation can not be undone.'))
->addSubmitButton(pht('Destroy Credential')) ->addSubmitButton(pht('Destroy Credential'))
->addCancelButton($view_uri); ->addCancelButton($view_uri);
return id(new AphrontDialogResponse())->setDialog($dialog);
} }
} }

View file

@ -93,6 +93,7 @@ final class PassphraseCredentialEditController extends PassphraseController {
$v_username = $request->getStr('username'); $v_username = $request->getStr('username');
$v_view_policy = $request->getStr('viewPolicy'); $v_view_policy = $request->getStr('viewPolicy');
$v_edit_policy = $request->getStr('editPolicy'); $v_edit_policy = $request->getStr('editPolicy');
$v_is_locked = $request->getStr('lock');
$v_secret = $request->getStr('secret'); $v_secret = $request->getStr('secret');
$v_password = $request->getStr('password'); $v_password = $request->getStr('password');
@ -126,6 +127,7 @@ final class PassphraseCredentialEditController extends PassphraseController {
$type_username = PassphraseCredentialTransaction::TYPE_USERNAME; $type_username = PassphraseCredentialTransaction::TYPE_USERNAME;
$type_destroy = PassphraseCredentialTransaction::TYPE_DESTROY; $type_destroy = PassphraseCredentialTransaction::TYPE_DESTROY;
$type_secret_id = PassphraseCredentialTransaction::TYPE_SECRET_ID; $type_secret_id = PassphraseCredentialTransaction::TYPE_SECRET_ID;
$type_is_locked = PassphraseCredentialTransaction::TYPE_LOCK;
$type_view_policy = PhabricatorTransactions::TYPE_VIEW_POLICY; $type_view_policy = PhabricatorTransactions::TYPE_VIEW_POLICY;
$type_edit_policy = PhabricatorTransactions::TYPE_EDIT_POLICY; $type_edit_policy = PhabricatorTransactions::TYPE_EDIT_POLICY;
@ -139,10 +141,6 @@ final class PassphraseCredentialEditController extends PassphraseController {
->setTransactionType($type_desc) ->setTransactionType($type_desc)
->setNewValue($v_desc); ->setNewValue($v_desc);
$xactions[] = id(new PassphraseCredentialTransaction())
->setTransactionType($type_username)
->setNewValue($v_username);
$xactions[] = id(new PassphraseCredentialTransaction()) $xactions[] = id(new PassphraseCredentialTransaction())
->setTransactionType($type_view_policy) ->setTransactionType($type_view_policy)
->setNewValue($v_view_policy); ->setNewValue($v_view_policy);
@ -155,20 +153,30 @@ final class PassphraseCredentialEditController extends PassphraseController {
// the amount of code which handles secret plaintexts. // the amount of code which handles secret plaintexts.
$credential->openTransaction(); $credential->openTransaction();
$min_secret = str_replace($bullet, '', trim($v_decrypt)); if (!$credential->getIsLocked()) {
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()) $xactions[] = id(new PassphraseCredentialTransaction())
->setTransactionType($type_destroy) ->setTransactionType($type_username)
->setNewValue(0); ->setNewValue($v_username);
$min_secret = str_replace($bullet, '', trim($v_decrypt));
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_decrypt)
->save();
$xactions[] = id(new PassphraseCredentialTransaction())
->setTransactionType($type_secret_id)
->setNewValue($new_secret->getID());
}
$new_secret = id(new PassphraseSecret())
->setSecretData($v_decrypt)
->save();
$xactions[] = id(new PassphraseCredentialTransaction()) $xactions[] = id(new PassphraseCredentialTransaction())
->setTransactionType($type_secret_id) ->setTransactionType($type_is_locked)
->setNewValue($new_secret->getID()); ->setNewValue($v_is_locked);
} }
try { try {
@ -210,6 +218,7 @@ final class PassphraseCredentialEditController extends PassphraseController {
->execute(); ->execute();
$secret_control = $type->newSecretControl(); $secret_control = $type->newSecretControl();
$credential_is_locked = $credential->getIsLocked();
$form = id(new AphrontFormView()) $form = id(new AphrontFormView())
->setUser($viewer) ->setUser($viewer)
@ -245,17 +254,26 @@ final class PassphraseCredentialEditController extends PassphraseController {
->setCapability(PhabricatorPolicyCapability::CAN_EDIT) ->setCapability(PhabricatorPolicyCapability::CAN_EDIT)
->setPolicies($policies)) ->setPolicies($policies))
->appendChild( ->appendChild(
id(new AphrontFormDividerControl())) id(new AphrontFormDividerControl()));
if ($credential_is_locked) {
$form->appendRemarkupInstructions(
pht('This credential is permanently locked and can not be edited.'));
}
$form
->appendChild( ->appendChild(
id(new AphrontFormTextControl()) id(new AphrontFormTextControl())
->setName('username') ->setName('username')
->setLabel(pht('Login/Username')) ->setLabel(pht('Login/Username'))
->setValue($v_username) ->setValue($v_username)
->setDisabled($credential_is_locked)
->setError($e_username)) ->setError($e_username))
->appendChild( ->appendChild(
$secret_control $secret_control
->setName('secret') ->setName('secret')
->setLabel($type->getSecretLabel()) ->setLabel($type->getSecretLabel())
->setDisabled($credential_is_locked)
->setValue($v_secret)); ->setValue($v_secret));
if ($type->shouldShowPasswordField()) { if ($type->shouldShowPasswordField()) {
@ -263,9 +281,21 @@ final class PassphraseCredentialEditController extends PassphraseController {
id(new AphrontFormPasswordControl()) id(new AphrontFormPasswordControl())
->setName('password') ->setName('password')
->setLabel($type->getPasswordLabel()) ->setLabel($type->getPasswordLabel())
->setDisabled($credential_is_locked)
->setError($e_password)); ->setError($e_password));
} }
if ($is_new) {
$form->appendChild(
id(new AphrontFormCheckboxControl())
->addCheckbox(
'lock',
1,
pht('Lock Permanently'),
$v_is_locked)
->setDisabled($credential_is_locked));
}
$crumbs = $this->buildApplicationCrumbs(); $crumbs = $this->buildApplicationCrumbs();
if ($is_new) { if ($is_new) {

View file

@ -0,0 +1,74 @@
<?php
final class PassphraseCredentialLockController
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 ($credential->getIsLocked()) {
return $this->newDialog()
->setTitle(pht('Credential Already Locked'))
->appendChild(
pht(
'This credential has been locked and the secret is '.
'hidden forever. Anything relying on this credential will '.
'still function. This operation can not be undone.'))
->addCancelButton($view_uri, pht('Close'));
}
if ($request->isFormPost()) {
$xactions = array();
$xactions[] = id(new PassphraseCredentialTransaction())
->setTransactionType(PassphraseCredentialTransaction::TYPE_LOCK)
->setNewValue(1);
$editor = id(new PassphraseCredentialTransactionEditor())
->setActor($viewer)
->setContinueOnMissingFields(true)
->setContentSourceFromRequest($request)
->applyTransactions($credential, $xactions);
return id(new AphrontRedirectResponse())->setURI($view_uri);
}
return $this->newDialog()
->setTitle(pht('Really lock credential?'))
->appendChild(
pht(
'This credential will be locked and the secret will be '.
'hidden forever. Anything relying on this credential will '.
'still function. This operation can not be undone.'))
->addSubmitButton(pht('Lock Credential'))
->addCancelButton($view_uri);
}
}

View file

@ -33,6 +33,17 @@ final class PassphraseCredentialRevealController
$viewer, $viewer,
$request, $request,
$view_uri); $view_uri);
$is_locked = $credential->getIsLocked();
if ($is_locked) {
return $this->newDialog()
->setUser($viewer)
->setTitle(pht('Credential is locked'))
->appendChild(
pht(
'This credential can not be shown, because it is locked.'))
->addCancelButton($view_uri);
}
if ($request->isFormPost()) { if ($request->isFormPost()) {
if ($credential->getSecret()) { if ($credential->getSecret()) {
@ -73,6 +84,7 @@ final class PassphraseCredentialRevealController
} }
$is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
if ($is_serious) { if ($is_serious) {
$body = pht( $body = pht(
'The secret associated with this credential will be shown in plain '. 'The secret associated with this credential will be shown in plain '.
@ -80,19 +92,16 @@ final class PassphraseCredentialRevealController
} else { } else {
$body = pht( $body = pht(
'The secret associated with this credential will be shown in plain '. 'The secret associated with this credential will be shown in plain '.
'text on your screen. Before continuing, wrap your arms around your '. 'text on your screen. Before continuing, wrap your arms around '.
'monitor to create a human shield, keeping it safe from prying eyes. '. 'your monitor to create a human shield, keeping it safe from '.
'Protect company secrets!'); 'prying eyes. Protect company secrets!');
} }
return $this->newDialog()
$dialog = id(new AphrontDialogView())
->setUser($viewer) ->setUser($viewer)
->setTitle(pht('Really show secret?')) ->setTitle(pht('Really show secret?'))
->appendChild($body) ->appendChild($body)
->addSubmitButton(pht('Show Secret')) ->addSubmitButton(pht('Show Secret'))
->addCancelButton($view_uri); ->addCancelButton($view_uri);
return id(new AphrontDialogResponse())->setDialog($dialog);
} }
} }

View file

@ -85,6 +85,15 @@ final class PassphraseCredentialViewController extends PassphraseController {
$id = $credential->getID(); $id = $credential->getID();
$is_locked = $credential->getIsLocked();
if ($is_locked) {
$credential_lock_text = pht('Locked Permanently');
$credential_lock_icon = 'lock';
} else {
$credential_lock_text = pht('Lock Permanently');
$credential_lock_icon = 'unlock';
}
$actions = id(new PhabricatorActionListView()) $actions = id(new PhabricatorActionListView())
->setObjectURI('/K'.$id) ->setObjectURI('/K'.$id)
->setUser($viewer); ->setUser($viewer);
@ -116,7 +125,7 @@ final class PassphraseCredentialViewController extends PassphraseController {
->setName(pht('Show Secret')) ->setName(pht('Show Secret'))
->setIcon('preview') ->setIcon('preview')
->setHref($this->getApplicationURI("reveal/{$id}/")) ->setHref($this->getApplicationURI("reveal/{$id}/"))
->setDisabled(!$can_edit) ->setDisabled(!$can_edit || $is_locked)
->setWorkflow(true)); ->setWorkflow(true));
if ($type->hasPublicKey()) { if ($type->hasPublicKey()) {
@ -125,8 +134,17 @@ final class PassphraseCredentialViewController extends PassphraseController {
->setName(pht('Show Public Key')) ->setName(pht('Show Public Key'))
->setIcon('download-alt') ->setIcon('download-alt')
->setHref($this->getApplicationURI("public/{$id}/")) ->setHref($this->getApplicationURI("public/{$id}/"))
->setWorkflow(true)); ->setWorkflow(true)
->setDisabled($is_locked));
} }
$actions->addAction(
id(new PhabricatorActionView())
->setName($credential_lock_text)
->setIcon($credential_lock_icon)
->setHref($this->getApplicationURI("lock/{$id}/"))
->setDisabled($is_locked)
->setWorkflow(true));
} }

View file

@ -15,6 +15,7 @@ final class PassphraseCredentialTransactionEditor
$types[] = PassphraseCredentialTransaction::TYPE_SECRET_ID; $types[] = PassphraseCredentialTransaction::TYPE_SECRET_ID;
$types[] = PassphraseCredentialTransaction::TYPE_DESTROY; $types[] = PassphraseCredentialTransaction::TYPE_DESTROY;
$types[] = PassphraseCredentialTransaction::TYPE_LOOKEDATSECRET; $types[] = PassphraseCredentialTransaction::TYPE_LOOKEDATSECRET;
$types[] = PassphraseCredentialTransaction::TYPE_LOCK;
return $types; return $types;
} }
@ -35,7 +36,9 @@ final class PassphraseCredentialTransactionEditor
case PassphraseCredentialTransaction::TYPE_SECRET_ID: case PassphraseCredentialTransaction::TYPE_SECRET_ID:
return $object->getSecretID(); return $object->getSecretID();
case PassphraseCredentialTransaction::TYPE_DESTROY: case PassphraseCredentialTransaction::TYPE_DESTROY:
return $object->getIsDestroyed(); return (int)$object->getIsDestroyed();
case PassphraseCredentialTransaction::TYPE_LOCK:
return (int)$object->getIsLocked();
case PassphraseCredentialTransaction::TYPE_LOOKEDATSECRET: case PassphraseCredentialTransaction::TYPE_LOOKEDATSECRET:
return null; return null;
} }
@ -51,9 +54,11 @@ final class PassphraseCredentialTransactionEditor
case PassphraseCredentialTransaction::TYPE_DESCRIPTION: case PassphraseCredentialTransaction::TYPE_DESCRIPTION:
case PassphraseCredentialTransaction::TYPE_USERNAME: case PassphraseCredentialTransaction::TYPE_USERNAME:
case PassphraseCredentialTransaction::TYPE_SECRET_ID: case PassphraseCredentialTransaction::TYPE_SECRET_ID:
case PassphraseCredentialTransaction::TYPE_DESTROY:
case PassphraseCredentialTransaction::TYPE_LOOKEDATSECRET: case PassphraseCredentialTransaction::TYPE_LOOKEDATSECRET:
return $xaction->getNewValue(); return $xaction->getNewValue();
case PassphraseCredentialTransaction::TYPE_DESTROY:
case PassphraseCredentialTransaction::TYPE_LOCK:
return (int)$xaction->getNewValue();
} }
return parent::getCustomTransactionNewValue($object, $xaction); return parent::getCustomTransactionNewValue($object, $xaction);
} }
@ -98,6 +103,9 @@ final class PassphraseCredentialTransactionEditor
return; return;
case PassphraseCredentialTransaction::TYPE_LOOKEDATSECRET: case PassphraseCredentialTransaction::TYPE_LOOKEDATSECRET:
return; return;
case PassphraseCredentialTransaction::TYPE_LOCK:
$object->setIsLocked((int)$xaction->getNewValue());
return;
} }
return parent::applyCustomInternalTransaction($object, $xaction); return parent::applyCustomInternalTransaction($object, $xaction);
@ -114,6 +122,7 @@ final class PassphraseCredentialTransactionEditor
case PassphraseCredentialTransaction::TYPE_SECRET_ID: case PassphraseCredentialTransaction::TYPE_SECRET_ID:
case PassphraseCredentialTransaction::TYPE_DESTROY: case PassphraseCredentialTransaction::TYPE_DESTROY:
case PassphraseCredentialTransaction::TYPE_LOOKEDATSECRET: case PassphraseCredentialTransaction::TYPE_LOOKEDATSECRET:
case PassphraseCredentialTransaction::TYPE_LOCK:
case PhabricatorTransactions::TYPE_VIEW_POLICY: case PhabricatorTransactions::TYPE_VIEW_POLICY:
case PhabricatorTransactions::TYPE_EDIT_POLICY: case PhabricatorTransactions::TYPE_EDIT_POLICY:
return; return;

View file

@ -12,6 +12,7 @@ final class PassphraseCredential extends PassphraseDAO
protected $username; protected $username;
protected $secretID; protected $secretID;
protected $isDestroyed; protected $isDestroyed;
protected $isLocked = 0;
private $secret = self::ATTACHABLE; private $secret = self::ATTACHABLE;

View file

@ -9,6 +9,7 @@ final class PassphraseCredentialTransaction
const TYPE_SECRET_ID = 'passphrase:secretID'; const TYPE_SECRET_ID = 'passphrase:secretID';
const TYPE_DESTROY = 'passphrase:destroy'; const TYPE_DESTROY = 'passphrase:destroy';
const TYPE_LOOKEDATSECRET = 'passphrase:lookedAtSecret'; const TYPE_LOOKEDATSECRET = 'passphrase:lookedAtSecret';
const TYPE_LOCK = 'passphrase:lock';
public function getApplicationName() { public function getApplicationName() {
return 'passphrase'; return 'passphrase';
@ -27,6 +28,8 @@ final class PassphraseCredentialTransaction
switch ($this->getTransactionType()) { switch ($this->getTransactionType()) {
case self::TYPE_DESCRIPTION: case self::TYPE_DESCRIPTION:
return ($old === null); return ($old === null);
case self::TYPE_LOCK:
return ($old === null);
case self::TYPE_USERNAME: case self::TYPE_USERNAME:
return !strlen($old); return !strlen($old);
case self::TYPE_LOOKEDATSECRET: case self::TYPE_LOOKEDATSECRET:
@ -84,6 +87,10 @@ final class PassphraseCredentialTransaction
return pht( return pht(
'%s examined the secret plaintext for this credential.', '%s examined the secret plaintext for this credential.',
$this->renderHandleLink($author_phid)); $this->renderHandleLink($author_phid));
case self::TYPE_LOCK:
return pht(
'%s locked this credential.',
$this->renderHandleLink($author_phid));
} }
return parent::getTitle(); return parent::getTitle();