Page MenuHomestyx hydra

No OneTemporary

diff --git a/src/applications/config/controller/PhabricatorConfigEditController.php b/src/applications/config/controller/PhabricatorConfigEditController.php
index 370ba01f37..0c93c86f09 100644
--- a/src/applications/config/controller/PhabricatorConfigEditController.php
+++ b/src/applications/config/controller/PhabricatorConfigEditController.php
@@ -1,546 +1,586 @@
<?php
final class PhabricatorConfigEditController
extends PhabricatorConfigController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$key = $request->getURIData('key');
$options = PhabricatorApplicationConfigOptions::loadAllOptions();
if (empty($options[$key])) {
$ancient = PhabricatorExtraConfigSetupCheck::getAncientConfig();
if (isset($ancient[$key])) {
$desc = pht(
"This configuration has been removed. You can safely delete ".
"it.\n\n%s",
$ancient[$key]);
} else {
$desc = pht(
'This configuration option is unknown. It may be misspelled, '.
'or have existed in a previous version of Phabricator.');
}
// This may be a dead config entry, which existed in the past but no
// longer exists. Allow it to be edited so it can be reviewed and
// deleted.
$option = id(new PhabricatorConfigOption())
->setKey($key)
->setType('wild')
->setDefault(null)
->setDescription($desc);
$group = null;
$group_uri = $this->getApplicationURI();
} else {
$option = $options[$key];
$group = $option->getGroup();
$group_uri = $this->getApplicationURI('group/'.$group->getKey().'/');
}
$issue = $request->getStr('issue');
if ($issue) {
// If the user came here from an open setup issue, send them back.
$done_uri = $this->getApplicationURI('issue/'.$issue.'/');
} else {
$done_uri = $group_uri;
}
// Check if the config key is already stored in the database.
// Grab the value if it is.
$config_entry = id(new PhabricatorConfigEntry())
->loadOneWhere(
'configKey = %s AND namespace = %s',
$key,
'default');
if (!$config_entry) {
$config_entry = id(new PhabricatorConfigEntry())
->setConfigKey($key)
->setNamespace('default')
->setIsDeleted(true);
$config_entry->setPHID($config_entry->generatePHID());
}
$e_value = null;
$errors = array();
if ($request->isFormPost() && !$option->getLocked()) {
$result = $this->readRequest(
$option,
$request);
list($e_value, $value_errors, $display_value, $xaction) = $result;
$errors = array_merge($errors, $value_errors);
if (!$errors) {
$editor = id(new PhabricatorConfigEditor())
->setActor($viewer)
->setContinueOnNoEffect(true)
->setContentSourceFromRequest($request);
try {
$editor->applyTransactions($config_entry, array($xaction));
return id(new AphrontRedirectResponse())->setURI($done_uri);
} catch (PhabricatorConfigValidationException $ex) {
$e_value = pht('Invalid');
$errors[] = $ex->getMessage();
}
}
} else {
if ($config_entry->getIsDeleted()) {
$display_value = null;
} else {
$display_value = $this->getDisplayValue(
$option,
$config_entry,
$config_entry->getValue());
}
}
$form = new AphrontFormView();
$error_view = null;
if ($errors) {
$error_view = id(new PHUIInfoView())
->setErrors($errors);
- } else if ($option->getHidden()) {
- $msg = pht(
+ }
+
+ $status_items = array();
+ if ($option->getHidden()) {
+ $message = pht(
'This configuration is hidden and can not be edited or viewed from '.
'the web interface.');
- $error_view = id(new PHUIInfoView())
- ->setTitle(pht('Configuration Hidden'))
- ->setSeverity(PHUIInfoView::SEVERITY_WARNING)
- ->appendChild(phutil_tag('p', array(), $msg));
+ $status_items[] = id(new PHUIStatusItemView())
+ ->setIcon('fa-eye-slash red')
+ ->setTarget(phutil_tag('strong', array(), pht('Configuration Hidden')))
+ ->setNote($message);
} else if ($option->getLocked()) {
+ $message = $option->getLockedMessage();
- $msg = $option->getLockedMessage();
- $error_view = id(new PHUIInfoView())
- ->setTitle(pht('Configuration Locked'))
- ->setSeverity(PHUIInfoView::SEVERITY_NOTICE)
- ->appendChild(phutil_tag('p', array(), $msg));
+ $status_items[] = id(new PHUIStatusItemView())
+ ->setIcon('fa-lock red')
+ ->setTarget(phutil_tag('strong', array(), pht('Configuration Locked')))
+ ->setNote($message);
+ }
+
+ if ($status_items) {
+ $doc_href = PhabricatorEnv::getDoclink(
+ 'Configuration Guide: Locked and Hidden Configuration');
+
+ $doc_link = phutil_tag(
+ 'a',
+ array(
+ 'href' => $doc_href,
+ 'target' => '_blank',
+ ),
+ pht('Configuration Guide: Locked and Hidden Configuration'));
+
+ $status_items[] = id(new PHUIStatusItemView())
+ ->setIcon('fa-book')
+ ->setTarget(phutil_tag('strong', array(), pht('Learn More')))
+ ->setNote($doc_link);
}
if ($option->getHidden() || $option->getLocked()) {
$control = null;
} else {
$control = $this->renderControl(
$option,
$display_value,
$e_value);
}
$engine = new PhabricatorMarkupEngine();
$engine->setViewer($viewer);
$engine->addObject($option, 'description');
$engine->process();
$description = phutil_tag(
'div',
array(
'class' => 'phabricator-remarkup',
),
$engine->getOutput($option, 'description'));
$form
->setUser($viewer)
- ->addHiddenInput('issue', $request->getStr('issue'))
- ->appendChild(
+ ->addHiddenInput('issue', $request->getStr('issue'));
+
+ if ($status_items) {
+ $status_view = id(new PHUIStatusListView());
+
+ foreach ($status_items as $status_item) {
+ $status_view->addItem($status_item);
+ }
+
+ $form->appendControl(
id(new AphrontFormMarkupControl())
- ->setLabel(pht('Description'))
- ->setValue($description));
+ ->setValue($status_view));
+ }
+
+ $description = $option->getDescription();
+ if (strlen($description)) {
+ $description_view = new PHUIRemarkupView($viewer, $description);
+
+ $form
+ ->appendChild(
+ id(new AphrontFormMarkupControl())
+ ->setLabel(pht('Description'))
+ ->setValue($description_view));
+ }
if ($group) {
$extra = $group->renderContextualDescription(
$option,
$request);
if ($extra !== null) {
$form->appendChild(
id(new AphrontFormMarkupControl())
->setValue($extra));
}
}
$form
->appendChild($control);
if (!$option->getLocked()) {
$form->appendChild(
id(new AphrontFormSubmitControl())
->addCancelButton($done_uri)
->setValue(pht('Save Config Entry')));
}
if (!$option->getHidden()) {
$form->appendChild(
id(new AphrontFormMarkupControl())
->setLabel(pht('Current Configuration'))
->setValue($this->renderDefaults($option, $config_entry)));
}
$examples = $this->renderExamples($option);
if ($examples) {
$form->appendChild(
id(new AphrontFormMarkupControl())
->setLabel(pht('Examples'))
->setValue($examples));
}
$title = pht('Edit %s', $key);
$short = pht('Edit');
$form_box = id(new PHUIObjectBoxView())
->setHeaderText($title)
->setForm($form);
if ($error_view) {
- $form_box->setInfoView($error_view);
+ $form_box->setInfoView($error_view);
}
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('Config'), $this->getApplicationURI());
if ($group) {
$crumbs->addTextCrumb($group->getName(), $group_uri);
}
$crumbs->addTextCrumb($key, '/config/edit/'.$key);
$timeline = $this->buildTransactionTimeline(
$config_entry,
new PhabricatorConfigTransactionQuery());
$timeline->setShouldTerminate(true);
return $this->buildApplicationPage(
array(
$crumbs,
$form_box,
$timeline,
),
array(
'title' => $title,
));
}
private function readRequest(
PhabricatorConfigOption $option,
AphrontRequest $request) {
$xaction = new PhabricatorConfigTransaction();
$xaction->setTransactionType(PhabricatorConfigTransaction::TYPE_EDIT);
$e_value = null;
$errors = array();
$value = $request->getStr('value');
if (!strlen($value)) {
$value = null;
$xaction->setNewValue(
array(
'deleted' => true,
'value' => null,
));
return array($e_value, $errors, $value, $xaction);
}
if ($option->isCustomType()) {
$info = $option->getCustomObject()->readRequest($option, $request);
list($e_value, $errors, $set_value, $value) = $info;
} else {
$type = $option->getType();
$set_value = null;
switch ($type) {
case 'int':
if (preg_match('/^-?[0-9]+$/', trim($value))) {
$set_value = (int)$value;
} else {
$e_value = pht('Invalid');
$errors[] = pht('Value must be an integer.');
}
break;
case 'string':
case 'enum':
$set_value = (string)$value;
break;
case 'list<string>':
case 'list<regex>':
$set_value = phutil_split_lines(
$request->getStr('value'),
$retain_endings = false);
foreach ($set_value as $key => $v) {
if (!strlen($v)) {
unset($set_value[$key]);
}
}
$set_value = array_values($set_value);
break;
case 'set':
$set_value = array_fill_keys($request->getStrList('value'), true);
break;
case 'bool':
switch ($value) {
case 'true':
$set_value = true;
break;
case 'false':
$set_value = false;
break;
default:
$e_value = pht('Invalid');
$errors[] = pht('Value must be boolean, "true" or "false".');
break;
}
break;
case 'class':
if (!class_exists($value)) {
$e_value = pht('Invalid');
$errors[] = pht('Class does not exist.');
} else {
$base = $option->getBaseClass();
if (!is_subclass_of($value, $base)) {
$e_value = pht('Invalid');
$errors[] = pht('Class is not of valid type.');
} else {
$set_value = $value;
}
}
break;
default:
$json = json_decode($value, true);
if ($json === null && strtolower($value) != 'null') {
$e_value = pht('Invalid');
$errors[] = pht(
'The given value must be valid JSON. This means, among '.
'other things, that you must wrap strings in double-quotes.');
} else {
$set_value = $json;
}
break;
}
}
if (!$errors) {
$xaction->setNewValue(
array(
'deleted' => false,
'value' => $set_value,
));
} else {
$xaction = null;
}
return array($e_value, $errors, $value, $xaction);
}
private function getDisplayValue(
PhabricatorConfigOption $option,
PhabricatorConfigEntry $entry,
$value) {
if ($option->isCustomType()) {
return $option->getCustomObject()->getDisplayValue(
$option,
$entry,
$value);
} else {
$type = $option->getType();
switch ($type) {
case 'int':
case 'string':
case 'enum':
case 'class':
return $value;
case 'bool':
return $value ? 'true' : 'false';
case 'list<string>':
case 'list<regex>':
return implode("\n", nonempty($value, array()));
case 'set':
return implode("\n", nonempty(array_keys($value), array()));
default:
return PhabricatorConfigJSON::prettyPrintJSON($value);
}
}
}
private function renderControl(
PhabricatorConfigOption $option,
$display_value,
$e_value) {
if ($option->isCustomType()) {
$control = $option->getCustomObject()->renderControl(
$option,
$display_value,
$e_value);
} else {
$type = $option->getType();
switch ($type) {
case 'int':
case 'string':
$control = id(new AphrontFormTextControl());
break;
case 'bool':
$control = id(new AphrontFormSelectControl())
->setOptions(
array(
'' => pht('(Use Default)'),
'true' => idx($option->getBoolOptions(), 0),
'false' => idx($option->getBoolOptions(), 1),
));
break;
case 'enum':
$options = array_mergev(
array(
array('' => pht('(Use Default)')),
$option->getEnumOptions(),
));
$control = id(new AphrontFormSelectControl())
->setOptions($options);
break;
case 'class':
$symbols = id(new PhutilSymbolLoader())
->setType('class')
->setAncestorClass($option->getBaseClass())
->setConcreteOnly(true)
->selectSymbolsWithoutLoading();
$names = ipull($symbols, 'name', 'name');
asort($names);
$names = array(
'' => pht('(Use Default)'),
) + $names;
$control = id(new AphrontFormSelectControl())
->setOptions($names);
break;
case 'list<string>':
case 'list<regex>':
$control = id(new AphrontFormTextAreaControl())
->setCaption(pht('Separate values with newlines.'));
break;
case 'set':
$control = id(new AphrontFormTextAreaControl())
->setCaption(pht('Separate values with newlines or commas.'));
break;
default:
$control = id(new AphrontFormTextAreaControl())
->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL)
->setCustomClass('PhabricatorMonospaced')
->setCaption(pht('Enter value in JSON.'));
break;
}
$control
->setLabel(pht('Database Value'))
->setError($e_value)
->setValue($display_value)
->setName('value');
}
return $control;
}
private function renderExamples(PhabricatorConfigOption $option) {
$examples = $option->getExamples();
if (!$examples) {
return null;
}
$table = array();
$table[] = phutil_tag('tr', array('class' => 'column-labels'), array(
phutil_tag('th', array(), pht('Example')),
phutil_tag('th', array(), pht('Value')),
));
foreach ($examples as $example) {
list($value, $description) = $example;
if ($value === null) {
$value = phutil_tag('em', array(), pht('(empty)'));
} else {
if (is_array($value)) {
$value = implode("\n", $value);
}
}
$table[] = phutil_tag('tr', array(), array(
phutil_tag('th', array(), $description),
phutil_tag('td', array(), $value),
));
}
require_celerity_resource('config-options-css');
return phutil_tag(
'table',
array(
'class' => 'config-option-table',
),
$table);
}
private function renderDefaults(
PhabricatorConfigOption $option,
PhabricatorConfigEntry $entry) {
$stack = PhabricatorEnv::getConfigSourceStack();
$stack = $stack->getStack();
$table = array();
$table[] = phutil_tag('tr', array('class' => 'column-labels'), array(
phutil_tag('th', array(), pht('Source')),
phutil_tag('th', array(), pht('Value')),
));
$is_effective_value = true;
foreach ($stack as $key => $source) {
$row_classes = array(
'column-labels',
);
$value = $source->getKeys(
array(
$option->getKey(),
));
if (!array_key_exists($option->getKey(), $value)) {
$value = phutil_tag('em', array(), pht('(No Value Configured)'));
} else {
$value = $this->getDisplayValue(
$option,
$entry,
$value[$option->getKey()]);
if ($is_effective_value) {
$is_effective_value = false;
$row_classes[] = 'config-options-effective-value';
}
}
$table[] = phutil_tag(
'tr',
array(
'class' => implode(' ', $row_classes),
),
array(
phutil_tag('th', array(), $source->getName()),
phutil_tag('td', array(), $value),
));
}
require_celerity_resource('config-options-css');
return phutil_tag(
'table',
array(
'class' => 'config-option-table',
),
$table);
}
}
diff --git a/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php b/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php
index 97586a77dd..f6d40a5126 100644
--- a/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php
+++ b/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php
@@ -1,172 +1,173 @@
<?php
final class PhabricatorConfigManagementSetWorkflow
extends PhabricatorConfigManagementWorkflow {
protected function didConstruct() {
$this
->setName('set')
->setExamples('**set** __key__ __value__')
->setSynopsis(pht('Set a local configuration value.'))
->setArguments(
array(
array(
'name' => 'database',
'help' => pht(
'Update configuration in the database instead of '.
'in local configuration.'),
),
array(
'name' => 'args',
'wildcard' => true,
),
));
}
public function execute(PhutilArgumentParser $args) {
$console = PhutilConsole::getConsole();
$argv = $args->getArg('args');
if (count($argv) == 0) {
throw new PhutilArgumentUsageException(
pht('Specify a configuration key and a value to set it to.'));
}
$key = $argv[0];
if (count($argv) == 1) {
throw new PhutilArgumentUsageException(
pht(
"Specify a value to set the key '%s' to.",
$key));
}
$value = $argv[1];
if (count($argv) > 2) {
throw new PhutilArgumentUsageException(
pht(
'Too many arguments: expected one key and one value.'));
}
$options = PhabricatorApplicationConfigOptions::loadAllOptions();
if (empty($options[$key])) {
throw new PhutilArgumentUsageException(
pht(
"No such configuration key '%s'! Use `%s` to list all keys.",
$key,
'config list'));
}
$option = $options[$key];
$type = $option->getType();
switch ($type) {
case 'string':
case 'class':
case 'enum':
$value = (string)$value;
break;
case 'int':
if (!ctype_digit($value)) {
throw new PhutilArgumentUsageException(
pht(
"Config key '%s' is of type '%s'. Specify an integer.",
$key,
$type));
}
$value = (int)$value;
break;
case 'bool':
if ($value == 'true') {
$value = true;
} else if ($value == 'false') {
$value = false;
} else {
throw new PhutilArgumentUsageException(
pht(
"Config key '%s' is of type '%s'. Specify '%s' or '%s'.",
$key,
$type,
'true',
'false'));
}
break;
default:
$value = json_decode($value, true);
if (!is_array($value)) {
switch ($type) {
case 'set':
$command = csprintf(
'./bin/config set %R %s',
$key,
'{"value1": true, "value2": true}');
$message = sprintf(
"%s\n\n %s\n",
pht(
'Config key "%s" is of type "%s". Specify it in JSON. '.
'For example:',
$key,
$type),
$command);
break;
default:
if (preg_match('/^list</', $type)) {
$command = csprintf(
'./bin/config set %R %s',
$key,
'["a", "b", "c"]');
$message = sprintf(
"%s\n\n %s\n",
pht(
'Config key "%s" is of type "%s". Specify it in JSON. '.
'For example:',
$key,
$type),
$command);
} else {
$message = pht(
'Config key "%s" is of type "%s". Specify it in JSON.',
$key,
$type);
}
break;
}
throw new PhutilArgumentUsageException($message);
}
break;
}
$use_database = $args->getArg('database');
if ($option->getLocked() && $use_database) {
throw new PhutilArgumentUsageException(
pht(
- "Config key '%s' is locked and can only be set in local ".
- "configuration.",
- $key));
+ 'Config key "%s" is locked and can only be set in local '.
+ 'configuration. To learn more, see "%s" in the documentation.',
+ $key,
+ pht('Configuration Guide: Locked and Hidden Configuration')));
}
try {
$option->getGroup()->validateOption($option, $value);
} catch (PhabricatorConfigValidationException $validation) {
// Convert this into a usage exception so we don't dump a stack trace.
throw new PhutilArgumentUsageException($validation->getMessage());
}
if ($use_database) {
$config_type = 'database';
$config_entry = PhabricatorConfigEntry::loadConfigEntry($key);
$config_entry->setValue($value);
$config_entry->save();
} else {
$config_type = 'local';
id(new PhabricatorConfigLocalSource())
->setKeys(array($key => $value));
}
$console->writeOut(
"%s\n",
pht("Set '%s' in %s configuration.", $key, $config_type));
}
}
diff --git a/src/docs/user/configuration/configuration_locked.diviner b/src/docs/user/configuration/configuration_locked.diviner
new file mode 100644
index 0000000000..040b838177
--- /dev/null
+++ b/src/docs/user/configuration/configuration_locked.diviner
@@ -0,0 +1,101 @@
+@title Configuration Guide: Locked and Hidden Configuration
+@group config
+
+Details about locked and hidden configuration.
+
+
+Overview
+========
+
+Some configuration options are **Locked** or **Hidden**. If an option has one
+of these attributes, it means:
+
+ - **Locked Configuration**: This setting can not be written from the web UI.
+ - **Hidden Configuration**: This setting can not be read or written from
+ the web UI.
+
+This document explains these attributes in more detail.
+
+
+Locked Configuration
+====================
+
+**Locked Configuration** can not be edited from the web UI. In general, you
+can edit it from the CLI instead, with `bin/config`:
+
+```
+phabricator/ $ ./bin/config set <key> <value>
+```
+
+A few settings have alternate CLI tools. Refer to the setting page for
+details.
+
+Note that these settings can not be written to the database, even from the
+CLI.
+
+Locked values can not be unlocked: they are locked because of what the setting
+does or how the setting operates. Some of the reasons configuration options are
+locked include:
+
+
+**Required for bootstrapping**: Some options, like `mysql.host`, must be
+available before Phabricator can read configuration from the database.
+
+If you stored `mysql.host` only in the database, Phabricator would not know how
+to connect to the database in order to read the value in the first place.
+
+These options must be provided in a configuration source which is read earlier
+in the bootstrapping process, before Phabricator connects to the database.
+
+
+**Errors could not be fixed from the web UI**: Some options, like
+`phabricator.base-uri`, can effectively disable the web UI if they are
+configured incorrectly.
+
+If these options could be configured from the web UI, you could not fix them if
+you made a mistake (because the web UI would no longer work, so you could not
+load the page to change the value).
+
+We require these options to be edited from the CLI to make sure the editor has
+access to fix any mistakes.
+
+
+**Attackers could gain greater access**: Some options could be modified by an
+attacker who has gained access to an administrator account in order to gain
+greater access.
+
+For example, an attacker who could modify `metamta.mail-adapter` (and other
+similar options), could potentially reconfigure Phabricator to send mail
+through an evil server they controlled, then trigger password resets on other
+user accounts to compromise them.
+
+We require these options to be edited from the CLI to make sure the editor
+has full access to the install.
+
+
+Hidden Configuration
+====================
+
+**Hidden Configuration** is similar to locked configuration, but also can not
+be //read// from the web UI.
+
+In almost all cases, configuration is hidden because it is some sort of secret
+key or access token for an external service. These values are hidden from the
+web UI to prevent administrators (or attackers who have compromised
+administrator accounts) from reading them.
+
+You can review (and edit) hidden configuration from the CLI:
+
+```
+phabricator/ $ ./bin/config get <key>
+phabricator/ $ ./bin/config set <key> <value>
+
+```
+
+
+Next Steps
+==========
+
+Continue by:
+
+ - returning to the @{article: Configuration Guide}.

File Metadata

Mime Type
text/x-diff
Expires
Thu, Jul 3, 2:04 PM (3 h, 58 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
165857
Default Alt Text
(26 KB)

Event Timeline