diff --git a/src/applications/passphrase/conduit/PassphraseQueryConduitAPIMethod.php b/src/applications/passphrase/conduit/PassphraseQueryConduitAPIMethod.php index 18010efd73..387e01cb2e 100644 --- a/src/applications/passphrase/conduit/PassphraseQueryConduitAPIMethod.php +++ b/src/applications/passphrase/conduit/PassphraseQueryConduitAPIMethod.php @@ -1,132 +1,135 @@ 'optional list', 'phids' => 'optional list', 'needSecrets' => 'optional bool', 'needPublicKeys' => 'optional bool', ); } protected function defineReturnType() { return 'list'; } protected function execute(ConduitAPIRequest $request) { $query = $this->newQueryForRequest($request); if ($request->getValue('ids')) { $query->withIDs($request->getValue('ids')); } if ($request->getValue('phids')) { $query->withPHIDs($request->getValue('phids')); } if ($request->getValue('needSecrets')) { $query->needSecrets(true); } $pager = $this->newPager($request); $credentials = $query->executeWithCursorPager($pager); $results = array(); foreach ($credentials as $credential) { $type = PassphraseCredentialType::getTypeByConstant( $credential->getCredentialType()); if (!$type) { continue; } $public_key = null; if ($request->getValue('needPublicKeys') && $type->hasPublicKey()) { $public_key = $type->getPublicKey( $request->getUser(), $credential); } $material = array(); + $is_locked = $credential->getIsLocked(); + $allow_api = ($credential->getAllowConduit() && !$is_locked); + $secret = null; if ($request->getValue('needSecrets')) { - if ($credential->getAllowConduit()) { + if ($allow_api) { $secret = $credential->getSecret(); if ($secret) { $secret = $secret->openEnvelope(); } else { $material['destroyed'] = pht( 'The private material for this credential has been '. 'destroyed.'); } } } switch ($credential->getCredentialType()) { case PassphraseSSHPrivateKeyFileCredentialType::CREDENTIAL_TYPE: if ($secret) { $material['file'] = $secret; } if ($public_key) { $material['publicKey'] = $public_key; } break; case PassphraseSSHGeneratedKeyCredentialType::CREDENTIAL_TYPE: case PassphraseSSHPrivateKeyTextCredentialType::CREDENTIAL_TYPE: if ($secret) { $material['privateKey'] = $secret; } if ($public_key) { $material['publicKey'] = $public_key; } break; case PassphrasePasswordCredentialType::CREDENTIAL_TYPE: if ($secret) { $material['password'] = $secret; } break; } - if (!$credential->getAllowConduit()) { + if (!$allow_api) { $material['noAPIAccess'] = pht( 'This private material for this credential is not accessible via '. 'API calls.'); } $results[$credential->getPHID()] = array( 'id' => $credential->getID(), 'phid' => $credential->getPHID(), 'type' => $credential->getCredentialType(), 'name' => $credential->getName(), 'description' => $credential->getDescription(), 'uri' => PhabricatorEnv::getProductionURI('/'.$credential->getMonogram()), 'monogram' => $credential->getMonogram(), 'username' => $credential->getUsername(), 'material' => $material, ); } $result = array( 'data' => $results, ); return $this->addPagerResults($result, $pager); } } diff --git a/src/applications/passphrase/controller/PassphraseCredentialConduitController.php b/src/applications/passphrase/controller/PassphraseCredentialConduitController.php index b86d18c227..ce8f21f62d 100644 --- a/src/applications/passphrase/controller/PassphraseCredentialConduitController.php +++ b/src/applications/passphrase/controller/PassphraseCredentialConduitController.php @@ -1,75 +1,89 @@ getViewer(); $id = $request->getURIData('id'); $credential = id(new PassphraseCredentialQuery()) ->setViewer($viewer) ->withIDs(array($id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->executeOne(); if (!$credential) { return new Aphront404Response(); } $view_uri = '/K'.$credential->getID(); $token = id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession( $viewer, $request, $view_uri); $type = PassphraseCredentialType::getTypeByConstant( $credential->getCredentialType()); if (!$type) { throw new Exception(pht('Credential has invalid type "%s"!', $type)); } + $is_locked = $credential->getIsLocked(); + + if ($is_locked) { + return $this->newDialog() + ->setUser($viewer) + ->setTitle(pht('Credential Locked')) + ->appendChild( + pht( + 'This credential can not be made available via Conduit because '. + 'it is locked.')) + ->addCancelButton($view_uri); + } + if ($request->isFormPost()) { $xactions = array(); + $xactions[] = id(new PassphraseCredentialTransaction()) ->setTransactionType(PassphraseCredentialTransaction::TYPE_CONDUIT) ->setNewValue(!$credential->getAllowConduit()); $editor = id(new PassphraseCredentialTransactionEditor()) ->setActor($viewer) ->setContinueOnMissingFields(true) ->setContentSourceFromRequest($request) ->applyTransactions($credential, $xactions); return id(new AphrontRedirectResponse())->setURI($view_uri); } if ($credential->getAllowConduit()) { return $this->newDialog() ->setTitle(pht('Prevent Conduit access?')) ->appendChild( pht( 'This credential and its secret will no longer be able '. 'to be retrieved using the `%s` method in Conduit.', 'passphrase.query')) ->addSubmitButton(pht('Prevent Conduit Access')) ->addCancelButton($view_uri); } else { return $this->newDialog() ->setTitle(pht('Allow Conduit access?')) ->appendChild( pht( 'This credential will be able to be retrieved via the Conduit '. 'API by users who have access to this credential. You should '. 'only enable this for credentials which need to be accessed '. 'programmatically (such as from build agents).')) ->addSubmitButton(pht('Allow Conduit Access')) ->addCancelButton($view_uri); } } } diff --git a/src/applications/passphrase/controller/PassphraseCredentialEditController.php b/src/applications/passphrase/controller/PassphraseCredentialEditController.php index a814b5dc72..bdb1802880 100644 --- a/src/applications/passphrase/controller/PassphraseCredentialEditController.php +++ b/src/applications/passphrase/controller/PassphraseCredentialEditController.php @@ -1,386 +1,385 @@ getViewer(); $id = $request->getURIData('id'); if ($id) { $credential = id(new PassphraseCredentialQuery()) ->setViewer($viewer) ->withIDs(array($id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->executeOne(); if (!$credential) { return new Aphront404Response(); } $type = $this->getCredentialType($credential->getCredentialType()); $type_const = $type->getCredentialType(); $is_new = false; } else { $type_const = $request->getStr('type'); $type = $this->getCredentialType($type_const); if (!$type->isCreateable()) { throw new Exception( pht( 'Credential has noncreateable type "%s"!', $type_const)); } $credential = PassphraseCredential::initializeNewCredential($viewer) ->setCredentialType($type->getCredentialType()) ->setProvidesType($type->getProvidesType()) ->attachImplementation($type); $is_new = true; // Prefill username if provided. $credential->setUsername((string)$request->getStr('username')); if (!$request->getStr('isInitialized')) { $type->didInitializeNewCredential($viewer, $credential); } } $errors = array(); $v_name = $credential->getName(); $e_name = true; $v_desc = $credential->getDescription(); $v_space = $credential->getSpacePHID(); $v_username = $credential->getUsername(); $e_username = true; $v_is_locked = false; $bullet = "\xE2\x80\xA2"; $v_secret = $credential->getSecretID() ? str_repeat($bullet, 32) : null; if ($is_new && ($v_secret === null)) { // If we're creating a new credential, the credential type may have // populated the secret for us (for example, generated an SSH key). In // this case, try { $v_secret = $credential->getSecret()->openEnvelope(); } catch (Exception $ex) { // Ignore this. } } $validation_exception = null; $errors = array(); $e_password = null; if ($request->isFormPost()) { $v_name = $request->getStr('name'); $v_desc = $request->getStr('description'); $v_username = $request->getStr('username'); $v_view_policy = $request->getStr('viewPolicy'); $v_edit_policy = $request->getStr('editPolicy'); $v_is_locked = $request->getStr('lock'); $v_secret = $request->getStr('secret'); $v_space = $request->getStr('spacePHID'); $v_password = $request->getStr('password'); $v_decrypt = $v_secret; $env_secret = new PhutilOpaqueEnvelope($v_secret); $env_password = new PhutilOpaqueEnvelope($v_password); if ($type->requiresPassword($env_secret)) { if (strlen($v_password)) { $v_decrypt = $type->decryptSecret($env_secret, $env_password); if ($v_decrypt === null) { $e_password = pht('Incorrect'); $errors[] = pht( 'This key requires a password, but the password you provided '. 'is incorrect.'); } else { $v_decrypt = $v_decrypt->openEnvelope(); } } else { $e_password = pht('Required'); $errors[] = pht( 'This key requires a password. You must provide the password '. 'for the key.'); } } if (!$errors) { $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_is_locked = PassphraseCredentialTransaction::TYPE_LOCK; $type_view_policy = PhabricatorTransactions::TYPE_VIEW_POLICY; $type_edit_policy = PhabricatorTransactions::TYPE_EDIT_POLICY; $type_space = PhabricatorTransactions::TYPE_SPACE; $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_view_policy) ->setNewValue($v_view_policy); $xactions[] = id(new PassphraseCredentialTransaction()) ->setTransactionType($type_edit_policy) ->setNewValue($v_edit_policy); $xactions[] = id(new PassphraseCredentialTransaction()) ->setTransactionType($type_space) ->setNewValue($v_space); // Open a transaction in case we're writing a new secret; this limits // the amount of code which handles secret plaintexts. $credential->openTransaction(); if (!$credential->getIsLocked()) { if ($type->shouldRequireUsername()) { $xactions[] = id(new PassphraseCredentialTransaction()) ->setTransactionType($type_username) ->setNewValue($v_username); } // If some value other than a sequence of bullets was provided for // the credential, update it. In particular, note that we are // explicitly allowing empty secrets: one use case is HTTP auth where // the username is a secret token which covers both identity and // authentication. if (!preg_match('/^('.$bullet.')+$/', trim($v_decrypt))) { // 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()); } $xactions[] = id(new PassphraseCredentialTransaction()) ->setTransactionType($type_is_locked) ->setNewValue($v_is_locked); } try { $editor = id(new PassphraseCredentialTransactionEditor()) ->setActor($viewer) ->setContinueOnNoEffect(true) ->setContentSourceFromRequest($request) ->applyTransactions($credential, $xactions); $credential->saveTransaction(); if ($request->isAjax()) { return id(new AphrontAjaxResponse())->setContent( array( 'phid' => $credential->getPHID(), 'name' => 'K'.$credential->getID().' '.$credential->getName(), )); } else { 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(); $credential_is_locked = $credential->getIsLocked(); $form = id(new AphrontFormView()) ->setUser($viewer) ->addHiddenInput('isInitialized', true) ->addHiddenInput('type', $type_const) ->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())) ->appendControl( id(new AphrontFormPolicyControl()) ->setName('viewPolicy') ->setPolicyObject($credential) ->setSpacePHID($v_space) ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) ->setPolicies($policies)) ->appendControl( id(new AphrontFormPolicyControl()) ->setName('editPolicy') ->setPolicyObject($credential) ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) ->setPolicies($policies)) ->appendChild( id(new AphrontFormDividerControl())); if ($credential_is_locked) { $form->appendRemarkupInstructions( pht('This credential is permanently locked and can not be edited.')); } if ($type->shouldRequireUsername()) { - $form - ->appendChild( + $form->appendChild( id(new AphrontFormTextControl()) ->setName('username') ->setLabel(pht('Login/Username')) ->setValue($v_username) ->setDisabled($credential_is_locked) ->setError($e_username)); } - $form - ->appendChild( - $secret_control - ->setName('secret') - ->setLabel($type->getSecretLabel()) - ->setDisabled($credential_is_locked) - ->setValue($v_secret)); + + $form->appendChild( + $secret_control + ->setName('secret') + ->setLabel($type->getSecretLabel()) + ->setDisabled($credential_is_locked) + ->setValue($v_secret)); if ($type->shouldShowPasswordField()) { $form->appendChild( id(new AphrontFormPasswordControl()) ->setDisableAutocomplete(true) ->setName('password') ->setLabel($type->getPasswordLabel()) ->setDisabled($credential_is_locked) ->setError($e_password)); } if ($is_new) { $form->appendChild( id(new AphrontFormCheckboxControl()) ->addCheckbox( 'lock', 1, array( phutil_tag('strong', array(), pht('Lock Permanently:')), ' ', pht('Prevent the secret from being revealed or changed.'), ), $v_is_locked) ->setDisabled($credential_is_locked)); } $crumbs = $this->buildApplicationCrumbs(); $crumbs->setBorder(true); if ($is_new) { $title = pht('Create New Credential'); $crumbs->addTextCrumb(pht('Create')); $cancel_uri = $this->getApplicationURI(); $header_icon = 'fa-plus-square'; } else { $title = pht('Edit Credential: %s', $credential->getName()); $crumbs->addTextCrumb( 'K'.$credential->getID(), '/K'.$credential->getID()); $crumbs->addTextCrumb(pht('Edit')); $cancel_uri = '/K'.$credential->getID(); $header_icon = 'fa-pencil'; } if ($request->isAjax()) { if ($errors) { $errors = id(new PHUIInfoView())->setErrors($errors); } return $this->newDialog() ->setWidth(AphrontDialogView::WIDTH_FORM) ->setTitle($title) ->appendChild($errors) ->appendChild($form->buildLayoutView()) ->addSubmitButton(pht('Create Credential')) ->addCancelButton($cancel_uri); } $form->appendChild( id(new AphrontFormSubmitControl()) ->setValue(pht('Save')) ->addCancelButton($cancel_uri)); $box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Credential')) ->setFormErrors($errors) ->setValidationException($validation_exception) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); $header = id(new PHUIHeaderView()) ->setHeader($title) ->setHeaderIcon($header_icon); $view = id(new PHUITwoColumnView()) ->setHeader($header) ->setFooter(array( $box, )); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) ->appendChild($view); } private function getCredentialType($type_const) { $type = PassphraseCredentialType::getTypeByConstant($type_const); if (!$type) { throw new Exception( pht('Credential has invalid type "%s"!', $type_const)); } return $type; } } diff --git a/src/applications/passphrase/controller/PassphraseCredentialLockController.php b/src/applications/passphrase/controller/PassphraseCredentialLockController.php index 4a872d8667..9832705427 100644 --- a/src/applications/passphrase/controller/PassphraseCredentialLockController.php +++ b/src/applications/passphrase/controller/PassphraseCredentialLockController.php @@ -1,68 +1,72 @@ getViewer(); $id = $request->getURIData('id'); $credential = id(new PassphraseCredentialQuery()) ->setViewer($viewer) ->withIDs(array($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.')) + pht('This credential is already locked.')) ->addCancelButton($view_uri, pht('Close')); } if ($request->isFormPost()) { $xactions = array(); + + $xactions[] = id(new PassphraseCredentialTransaction()) + ->setTransactionType(PassphraseCredentialTransaction::TYPE_CONDUIT) + ->setNewValue(0); + $xactions[] = id(new PassphraseCredentialTransaction()) ->setTransactionType(PassphraseCredentialTransaction::TYPE_LOCK) ->setNewValue(1); $editor = id(new PassphraseCredentialTransactionEditor()) ->setActor($viewer) ->setContinueOnMissingFields(true) + ->setContinueOnNoEffect(true) ->setContentSourceFromRequest($request) ->applyTransactions($credential, $xactions); return id(new AphrontRedirectResponse())->setURI($view_uri); } return $this->newDialog() - ->setTitle(pht('Really lock credential?')) + ->setTitle(pht('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.')) + 'This credential will be locked and the secret will be hidden '. + 'forever. If Conduit access is enabled, it will be revoked. '. + 'Anything relying on this credential will still function. This '. + 'operation can not be undone.')) ->addSubmitButton(pht('Lock Credential')) ->addCancelButton($view_uri); } } diff --git a/src/applications/passphrase/controller/PassphraseCredentialViewController.php b/src/applications/passphrase/controller/PassphraseCredentialViewController.php index a154f846d1..aabb3821e0 100644 --- a/src/applications/passphrase/controller/PassphraseCredentialViewController.php +++ b/src/applications/passphrase/controller/PassphraseCredentialViewController.php @@ -1,217 +1,219 @@ getViewer(); $id = $request->getURIData('id'); $credential = id(new PassphraseCredentialQuery()) ->setViewer($viewer) ->withIDs(array($id)) ->executeOne(); if (!$credential) { return new Aphront404Response(); } $type = $credential->getImplementation(); $timeline = $this->buildTransactionTimeline( $credential, new PassphraseCredentialTransactionQuery()); $timeline->setShouldTerminate(true); $title = pht('%s %s', $credential->getMonogram(), $credential->getName()); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($credential->getMonogram()); $crumbs->setBorder(true); $header = $this->buildHeaderView($credential); $curtain = $this->buildCurtain($credential, $type); $subheader = $this->buildSubheaderView($credential); $content = $this->buildPropertySectionView($credential, $type); $view = id(new PHUITwoColumnView()) ->setHeader($header) ->setSubheader($subheader) ->setCurtain($curtain) ->setMainColumn($timeline) ->addPropertySection(pht('Properties'), $content); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) ->appendChild($view); } private function buildHeaderView(PassphraseCredential $credential) { $viewer = $this->getRequest()->getUser(); $header = id(new PHUIHeaderView()) ->setUser($viewer) ->setHeader($credential->getName()) ->setPolicyObject($credential) ->setHeaderIcon('fa-user-secret'); if ($credential->getIsDestroyed()) { $header->setStatus('fa-ban', 'red', pht('Destroyed')); } return $header; } private function buildSubheaderView( PassphraseCredential $credential) { $viewer = $this->getViewer(); $author = $viewer->renderHandle($credential->getAuthorPHID())->render(); $date = phabricator_datetime($credential->getDateCreated(), $viewer); $author = phutil_tag('strong', array(), $author); $person = id(new PhabricatorPeopleQuery()) ->setViewer($viewer) ->withPHIDs(array($credential->getAuthorPHID())) ->needProfileImage(true) ->executeOne(); if (!$person) { return null; } $image_uri = $person->getProfileImageURI(); $image_href = '/p/'.$credential->getUsername(); $content = pht('Created by %s on %s.', $author, $date); return id(new PHUIHeadThingView()) ->setImage($image_uri) ->setImageHref($image_href) ->setContent($content); } private function buildCurtain( PassphraseCredential $credential, PassphraseCredentialType $type) { $viewer = $this->getViewer(); $id = $credential->getID(); $is_locked = $credential->getIsLocked(); if ($is_locked) { $credential_lock_text = pht('Locked Permanently'); $credential_lock_icon = 'fa-lock'; } else { $credential_lock_text = pht('Lock Permanently'); $credential_lock_icon = 'fa-unlock'; } $allow_conduit = $credential->getAllowConduit(); if ($allow_conduit) { $credential_conduit_text = pht('Prevent Conduit Access'); $credential_conduit_icon = 'fa-ban'; } else { $credential_conduit_text = pht('Allow Conduit Access'); $credential_conduit_icon = 'fa-wrench'; } $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $credential, PhabricatorPolicyCapability::CAN_EDIT); + $can_conduit = ($can_edit && !$is_locked); + $curtain = $this->newCurtainView($credential); $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Credential')) ->setIcon('fa-pencil') ->setHref($this->getApplicationURI("edit/{$id}/")) ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); if (!$credential->getIsDestroyed()) { $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Destroy Credential')) ->setIcon('fa-times') ->setHref($this->getApplicationURI("destroy/{$id}/")) ->setDisabled(!$can_edit) ->setWorkflow(true)); $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Show Secret')) ->setIcon('fa-eye') ->setHref($this->getApplicationURI("reveal/{$id}/")) ->setDisabled(!$can_edit || $is_locked) ->setWorkflow(true)); if ($type->hasPublicKey()) { $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Show Public Key')) ->setIcon('fa-download') ->setHref($this->getApplicationURI("public/{$id}/")) ->setDisabled(!$can_edit) ->setWorkflow(true)); } $curtain->addAction( id(new PhabricatorActionView()) ->setName($credential_conduit_text) ->setIcon($credential_conduit_icon) ->setHref($this->getApplicationURI("conduit/{$id}/")) - ->setDisabled(!$can_edit) + ->setDisabled(!$can_conduit) ->setWorkflow(true)); $curtain->addAction( id(new PhabricatorActionView()) ->setName($credential_lock_text) ->setIcon($credential_lock_icon) ->setHref($this->getApplicationURI("lock/{$id}/")) ->setDisabled(!$can_edit || $is_locked) ->setWorkflow(true)); } return $curtain; } private function buildPropertySectionView( PassphraseCredential $credential, PassphraseCredentialType $type) { $viewer = $this->getRequest()->getUser(); $properties = id(new PHUIPropertyListView()) ->setUser($viewer); $properties->addProperty( pht('Credential Type'), $type->getCredentialTypeName()); $descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions( $viewer, $credential); $properties->addProperty( pht('Editable By'), $descriptions[PhabricatorPolicyCapability::CAN_EDIT]); if ($type->shouldRequireUsername()) { $properties->addProperty( pht('Username'), $credential->getUsername()); } $description = $credential->getDescription(); if (strlen($description)) { $properties->addSectionHeader( pht('Description'), PHUIPropertyListView::ICON_SUMMARY); $properties->addTextContent( new PHUIRemarkupView($viewer, $description)); } return $properties; } }