diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2091,6 +2091,7 @@ 'PhabricatorSourceCodeView' => 'view/layout/PhabricatorSourceCodeView.php', 'PhabricatorStandardCustomField' => 'infrastructure/customfield/standard/PhabricatorStandardCustomField.php', 'PhabricatorStandardCustomFieldBool' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldBool.php', + 'PhabricatorStandardCustomFieldCredential' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldCredential.php', 'PhabricatorStandardCustomFieldDate' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldDate.php', 'PhabricatorStandardCustomFieldHeader' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldHeader.php', 'PhabricatorStandardCustomFieldInt' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldInt.php', @@ -4937,6 +4938,7 @@ 'PhabricatorSourceCodeView' => 'AphrontView', 'PhabricatorStandardCustomField' => 'PhabricatorCustomField', 'PhabricatorStandardCustomFieldBool' => 'PhabricatorStandardCustomField', + 'PhabricatorStandardCustomFieldCredential' => 'PhabricatorStandardCustomField', 'PhabricatorStandardCustomFieldDate' => 'PhabricatorStandardCustomField', 'PhabricatorStandardCustomFieldHeader' => 'PhabricatorStandardCustomField', 'PhabricatorStandardCustomFieldInt' => 'PhabricatorStandardCustomField', diff --git a/src/applications/passphrase/credentialtype/PassphraseCredentialType.php b/src/applications/passphrase/credentialtype/PassphraseCredentialType.php --- a/src/applications/passphrase/credentialtype/PassphraseCredentialType.php +++ b/src/applications/passphrase/credentialtype/PassphraseCredentialType.php @@ -33,6 +33,14 @@ return $types; } + public static function getAllProvidesTypes() { + $types = array(); + foreach (self::getAllTypes() as $type) { + $types[] = $type->getProvidesType(); + } + return array_unique($types); + } + public static function getTypeByConstant($constant) { $all = self::getAllTypes(); $all = mpull($all, null, 'getCredentialType'); diff --git a/src/applications/passphrase/view/PassphraseCredentialControl.php b/src/applications/passphrase/view/PassphraseCredentialControl.php --- a/src/applications/passphrase/view/PassphraseCredentialControl.php +++ b/src/applications/passphrase/view/PassphraseCredentialControl.php @@ -98,4 +98,63 @@ )); } + /** + * Verify that a given actor has permission to use all of the credentials + * in a list of credential transactions. + * + * In general, the rule here is: + * + * - If you're editing an object and it uses a credential you can't use, + * that's fine as long as you don't change the credential. + * - If you do change the credential, the new credential must be one you + * can use. + * + * @param PhabricatorUser The acting user. + * @param list List of credential altering + * transactions. + * @return bool True if the transactions are valid. + */ + public static function validateTransactions( + PhabricatorUser $actor, + array $xactions) { + + $new_phids = array(); + foreach ($xactions as $xaction) { + $new = $xaction->getNewValue(); + if (!$new) { + // Removing a credential, so this is OK. + continue; + } + + $old = $xaction->getOldValue(); + if ($old == $new) { + // This is a no-op transaction, so this is also OK. + continue; + } + + // Otherwise, we need to check this credential. + $new_phids[] = $new; + } + + if (!$new_phids) { + // No new credentials being set, so this is fine. + return true; + } + + $usable_credentials = id(new PassphraseCredentialQuery()) + ->setViewer($actor) + ->withPHIDs($new_phids) + ->execute(); + $usable_credentials = mpull($usable_credentials, null, 'getPHID'); + + foreach ($new_phids as $phid) { + if (empty($usable_credentials[$phid])) { + return false; + } + } + + return true; + } + + } diff --git a/src/applications/repository/editor/PhabricatorRepositoryEditor.php b/src/applications/repository/editor/PhabricatorRepositoryEditor.php --- a/src/applications/repository/editor/PhabricatorRepositoryEditor.php +++ b/src/applications/repository/editor/PhabricatorRepositoryEditor.php @@ -310,4 +310,33 @@ } } + protected function validateTransaction( + PhabricatorLiskDAO $object, + $type, + array $xactions) { + + $errors = parent::validateTransaction($object, $type, $xactions); + + switch ($type) { + case self::TYPE_CREDENTIAL: + $ok = PassphraseCredentialControl::validateTransactions( + $this->getActor(), + $xactions); + if (!$ok) { + foreach ($xactions as $xaction) { + $errors[] = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Invalid'), + pht( + 'The selected credential does not exist, or you do not have '. + 'permission to use it.'), + $xaction); + } + } + break; + } + + return $errors; + } + } diff --git a/src/docs/user/configuration/custom_fields.diviner b/src/docs/user/configuration/custom_fields.diviner --- a/src/docs/user/configuration/custom_fields.diviner +++ b/src/docs/user/configuration/custom_fields.diviner @@ -121,6 +121,14 @@ into the new task, while others are not; by default, fields are not copied. If you want this field to be copied, specify `true` for the `copy` property. +Internally, Phabricator implements some additional custom field types and +options. These are not intended for general use and are subject to abrupt +change, but are documented here for completeness: + + - **Credentials**: Controls with type `credential` allow selection of a + Passphrase credential which provides `credential.provides`, and creation + of credentials of `credential.type`. + = Advanced Custom Fields = If you want custom fields to have advanced behaviors (sophisticated rendering, diff --git a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldCredential.php b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldCredential.php new file mode 100644 --- /dev/null +++ b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldCredential.php @@ -0,0 +1,138 @@ +getFieldValue(); + if (strlen($value)) { + $indexes[] = $this->newStringIndex($value); + } + + return $indexes; + } + + public function renderEditControl(array $handles) { + $provides_type = $this->getFieldConfigValue('credential.provides'); + $credential_type = $this->getFieldConfigValue('credential.type'); + + $all_types = PassphraseCredentialType::getAllProvidesTypes(); + if (!in_array($provides_type, $all_types)) { + $provides_type = PassphraseCredentialTypePassword::PROVIDES_TYPE; + } + + $credentials = id(new PassphraseCredentialQuery()) + ->setViewer($this->getViewer()) + ->withIsDestroyed(false) + ->withProvidesTypes(array($provides_type)) + ->execute(); + + return id(new PassphraseCredentialControl()) + ->setLabel($this->getFieldName()) + ->setName($this->getFieldKey()) + ->setCaption($this->getCaption()) + ->setAllowNull(!$this->getRequired()) + ->setCredentialType($credential_type) + ->setValue($this->getFieldValue()) + ->setError($this->getFieldError()) + ->setOptions($credentials); + } + + public function getRequiredHandlePHIDsForPropertyView() { + $value = $this->getFieldValue(); + if ($value) { + return array($value); + } + return array(); + } + + public function renderPropertyViewValue(array $handles) { + $value = $this->getFieldValue(); + if ($value) { + return $handles[$value]->renderLink(); + } + return null; + } + + public function validateApplicationTransactions( + PhabricatorApplicationTransactionEditor $editor, + $type, + array $xactions) { + + $errors = parent::validateApplicationTransactions( + $editor, + $type, + $xactions); + + $ok = PassphraseCredentialControl::validateTransactions( + $this->getViewer(), + $xactions); + + if (!$ok) { + foreach ($xactions as $xaction) { + $errors[] = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Invalid'), + pht( + 'The selected credential does not exist, or you do not have '. + 'permission to use it.'), + $xaction); + $this->setFieldError(pht('Invalid')); + } + } + + return $errors; + } + + public function getApplicationTransactionRequiredHandlePHIDs( + PhabricatorApplicationTransaction $xaction) { + $phids = array(); + $old = $xaction->getOldValue(); + $new = $xaction->getNewValue(); + if ($old) { + $phids[] = $old; + } + if ($new) { + $phids[] = $new; + } + return $phids; + } + + + public function getApplicationTransactionTitle( + PhabricatorApplicationTransaction $xaction) { + $author_phid = $xaction->getAuthorPHID(); + + $old = $xaction->getOldValue(); + $new = $xaction->getNewValue(); + + if ($old && !$new) { + return pht( + '%s removed %s as %s.', + $xaction->renderHandleLink($author_phid), + $xaction->renderHandleLink($old), + $this->getFieldName()); + } else if ($new && !$old) { + return pht( + '%s set %s to %s.', + $xaction->renderHandleLink($author_phid), + $this->getFieldName(), + $xaction->renderHandleLink($new)); + } else { + return pht( + '%s changed %s from %s to %s.', + $xaction->renderHandleLink($author_phid), + $this->getFieldName(), + $xaction->renderHandleLink($old), + $xaction->renderHandleLink($new)); + } + } + + +} diff --git a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldPHIDs.php b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldPHIDs.php --- a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldPHIDs.php +++ b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldPHIDs.php @@ -68,7 +68,7 @@ return array(); } - public function getRequiredHandlePHIDsForProperyView() { + public function getRequiredHandlePHIDsForPropertyView() { $value = $this->getFieldValue(); if ($value) { return $value;