Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2894474
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Advanced/Developer...
View Handle
View Hovercard
Size
27 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/applications/auth/controller/PhabricatorAuthNeedsApprovalController.php b/src/applications/auth/controller/PhabricatorAuthNeedsApprovalController.php
index 1121dfb14a..b098a07ddd 100644
--- a/src/applications/auth/controller/PhabricatorAuthNeedsApprovalController.php
+++ b/src/applications/auth/controller/PhabricatorAuthNeedsApprovalController.php
@@ -1,68 +1,68 @@
<?php
final class PhabricatorAuthNeedsApprovalController
extends PhabricatorAuthController {
public function shouldRequireLogin() {
return false;
}
public function shouldRequireEmailVerification() {
return false;
}
public function shouldRequireEnabledUser() {
return false;
}
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$instructions = $this->newCustomWaitForApprovalInstructions();
$wait_for_approval = pht(
"Your account has been created, but needs to be approved by an ".
"administrator. You'll receive an email once your account is approved.");
$dialog = $this->newDialog()
->setTitle(pht('Wait for Approval'))
->appendChild($wait_for_approval)
->addCancelButton('/', pht('Wait Patiently'));
$crumbs = $this->buildApplicationCrumbs()
->addTextCrumb(pht('Wait For Approval'))
->setBorder(true);
return $this->newPage()
->setTitle(pht('Wait For Approval'))
->setCrumbs($crumbs)
->appendChild(
array(
$instructions,
$dialog,
));
}
private function newCustomWaitForApprovalInstructions() {
$viewer = $this->getViewer();
$text = PhabricatorAuthMessage::loadMessageText(
$viewer,
PhabricatorAuthWaitForApprovalMessageType::MESSAGEKEY);
- if (!strlen($text)) {
+ if (!phutil_nonempty_string($text)) {
return null;
}
$remarkup_view = new PHUIRemarkupView($viewer, $text);
return phutil_tag(
'div',
array(
'class' => 'auth-custom-message',
),
$remarkup_view);
}
}
diff --git a/src/applications/auth/engine/PhabricatorAuthPasswordEngine.php b/src/applications/auth/engine/PhabricatorAuthPasswordEngine.php
index a1fec4a6d2..0efb1fe4fc 100644
--- a/src/applications/auth/engine/PhabricatorAuthPasswordEngine.php
+++ b/src/applications/auth/engine/PhabricatorAuthPasswordEngine.php
@@ -1,392 +1,394 @@
<?php
final class PhabricatorAuthPasswordEngine
extends Phobject {
private $viewer;
private $contentSource;
private $object;
private $passwordType;
private $upgradeHashers = true;
public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
public function getViewer() {
return $this->viewer;
}
public function setContentSource(PhabricatorContentSource $content_source) {
$this->contentSource = $content_source;
return $this;
}
public function getContentSource() {
return $this->contentSource;
}
public function setObject(PhabricatorAuthPasswordHashInterface $object) {
$this->object = $object;
return $this;
}
public function getObject() {
return $this->object;
}
public function setPasswordType($password_type) {
$this->passwordType = $password_type;
return $this;
}
public function getPasswordType() {
return $this->passwordType;
}
public function setUpgradeHashers($upgrade_hashers) {
$this->upgradeHashers = $upgrade_hashers;
return $this;
}
public function getUpgradeHashers() {
return $this->upgradeHashers;
}
public function checkNewPassword(
PhutilOpaqueEnvelope $password,
PhutilOpaqueEnvelope $confirm,
$can_skip = false) {
$raw_password = $password->openEnvelope();
if (!strlen($raw_password)) {
if ($can_skip) {
throw new PhabricatorAuthPasswordException(
pht('You must choose a password or skip this step.'),
pht('Required'));
} else {
throw new PhabricatorAuthPasswordException(
pht('You must choose a password.'),
pht('Required'));
}
}
$min_len = PhabricatorEnv::getEnvConfig('account.minimum-password-length');
$min_len = (int)$min_len;
if ($min_len) {
if (strlen($raw_password) < $min_len) {
throw new PhabricatorAuthPasswordException(
pht(
'The selected password is too short. Passwords must be a minimum '.
'of %s characters long.',
new PhutilNumber($min_len)),
pht('Too Short'));
}
}
$raw_confirm = $confirm->openEnvelope();
if (!strlen($raw_confirm)) {
throw new PhabricatorAuthPasswordException(
pht('You must confirm the selected password.'),
null,
pht('Required'));
}
if ($raw_password !== $raw_confirm) {
throw new PhabricatorAuthPasswordException(
pht('The password and confirmation do not match.'),
pht('Invalid'),
pht('Invalid'));
}
if (PhabricatorCommonPasswords::isCommonPassword($raw_password)) {
throw new PhabricatorAuthPasswordException(
pht(
'The selected password is very weak: it is one of the most common '.
'passwords in use. Choose a stronger password.'),
pht('Very Weak'));
}
// If we're creating a brand new object (like registering a new user)
// and it does not have a PHID yet, it isn't possible for it to have any
// revoked passwords or colliding passwords either, so we can skip these
// checks.
$object = $this->getObject();
if ($object->getPHID()) {
if ($this->isRevokedPassword($password)) {
throw new PhabricatorAuthPasswordException(
pht(
'The password you entered has been revoked. You can not reuse '.
'a password which has been revoked. Choose a new password.'),
pht('Revoked'));
}
if (!$this->isUniquePassword($password)) {
throw new PhabricatorAuthPasswordException(
pht(
'The password you entered is the same as another password '.
'associated with your account. Each password must be unique.'),
pht('Not Unique'));
}
}
// Prevent use of passwords which are similar to any object identifier.
// For example, if your username is "alincoln", your password may not be
// "alincoln", "lincoln", or "alincoln1".
$viewer = $this->getViewer();
$blocklist = $object->newPasswordBlocklist($viewer, $this);
// Smallest number of overlapping characters that we'll consider to be
// too similar.
$minimum_similarity = 4;
// Add the domain name to the blocklist.
$base_uri = PhabricatorEnv::getAnyBaseURI();
$base_uri = new PhutilURI($base_uri);
$blocklist[] = $base_uri->getDomain();
+ $blocklist = array_filter($blocklist);
+
// Generate additional subterms by splitting the raw blocklist on
// characters like "@", " " (space), and "." to break up email addresses,
// readable names, and domain names into components.
$terms_map = array();
foreach ($blocklist as $term) {
$terms_map[$term] = $term;
foreach (preg_split('/[ @.]/', $term) as $subterm) {
$terms_map[$subterm] = $term;
}
}
// Skip very short terms: it's okay if your password has the substring
// "com" in it somewhere even if the install is on "mycompany.com".
foreach ($terms_map as $term => $source) {
if (strlen($term) < $minimum_similarity) {
unset($terms_map[$term]);
}
}
// Normalize terms for comparison.
$normal_map = array();
foreach ($terms_map as $term => $source) {
$term = phutil_utf8_strtolower($term);
$normal_map[$term] = $source;
}
// Finally, make sure that none of the terms appear in the password,
// and that the password does not appear in any of the terms.
$normal_password = phutil_utf8_strtolower($raw_password);
if (strlen($normal_password) >= $minimum_similarity) {
foreach ($normal_map as $term => $source) {
// See T2312. This may be required if the term list includes numeric
// strings like "12345", which will be cast to integers when used as
// array keys.
$term = phutil_string_cast($term);
if (strpos($term, $normal_password) === false &&
strpos($normal_password, $term) === false) {
continue;
}
throw new PhabricatorAuthPasswordException(
pht(
'The password you entered is very similar to a nonsecret account '.
'identifier (like a username or email address). Choose a more '.
'distinct password.'),
pht('Not Distinct'));
}
}
}
public function isValidPassword(PhutilOpaqueEnvelope $envelope) {
$this->requireSetup();
$password_type = $this->getPasswordType();
$passwords = $this->newQuery()
->withPasswordTypes(array($password_type))
->withIsRevoked(false)
->execute();
$matches = $this->getMatches($envelope, $passwords);
if (!$matches) {
return false;
}
if ($this->shouldUpgradeHashers()) {
$this->upgradeHashers($envelope, $matches);
}
return true;
}
public function isUniquePassword(PhutilOpaqueEnvelope $envelope) {
$this->requireSetup();
$password_type = $this->getPasswordType();
// To test that the password is unique, we're loading all active and
// revoked passwords for all roles for the given user, then throwing out
// the active passwords for the current role (so a password can't
// collide with itself).
// Note that two different objects can have the same password (say,
// users @alice and @bailey). We're only preventing @alice from using
// the same password for everything.
$passwords = $this->newQuery()
->execute();
foreach ($passwords as $key => $password) {
$same_type = ($password->getPasswordType() === $password_type);
$is_active = !$password->getIsRevoked();
if ($same_type && $is_active) {
unset($passwords[$key]);
}
}
$matches = $this->getMatches($envelope, $passwords);
return !$matches;
}
public function isRevokedPassword(PhutilOpaqueEnvelope $envelope) {
$this->requireSetup();
// To test if a password is revoked, we're loading all revoked passwords
// across all roles for the given user. If a password was revoked in one
// role, you can't reuse it in a different role.
$passwords = $this->newQuery()
->withIsRevoked(true)
->execute();
$matches = $this->getMatches($envelope, $passwords);
return (bool)$matches;
}
private function requireSetup() {
if (!$this->getObject()) {
throw new PhutilInvalidStateException('setObject');
}
if (!$this->getPasswordType()) {
throw new PhutilInvalidStateException('setPasswordType');
}
if (!$this->getViewer()) {
throw new PhutilInvalidStateException('setViewer');
}
if ($this->shouldUpgradeHashers()) {
if (!$this->getContentSource()) {
throw new PhutilInvalidStateException('setContentSource');
}
}
}
private function shouldUpgradeHashers() {
if (!$this->getUpgradeHashers()) {
return false;
}
if (PhabricatorEnv::isReadOnly()) {
// Don't try to upgrade hashers if we're in read-only mode, since we
// won't be able to write the new hash to the database.
return false;
}
return true;
}
private function newQuery() {
$viewer = $this->getViewer();
$object = $this->getObject();
$password_type = $this->getPasswordType();
return id(new PhabricatorAuthPasswordQuery())
->setViewer($viewer)
->withObjectPHIDs(array($object->getPHID()));
}
private function getMatches(
PhutilOpaqueEnvelope $envelope,
array $passwords) {
$object = $this->getObject();
$matches = array();
foreach ($passwords as $password) {
try {
$is_match = $password->comparePassword($envelope, $object);
} catch (PhabricatorPasswordHasherUnavailableException $ex) {
$is_match = false;
}
if ($is_match) {
$matches[] = $password;
}
}
return $matches;
}
private function upgradeHashers(
PhutilOpaqueEnvelope $envelope,
array $passwords) {
assert_instances_of($passwords, 'PhabricatorAuthPassword');
$need_upgrade = array();
foreach ($passwords as $password) {
if (!$password->canUpgrade()) {
continue;
}
$need_upgrade[] = $password;
}
if (!$need_upgrade) {
return;
}
$upgrade_type = PhabricatorAuthPasswordUpgradeTransaction::TRANSACTIONTYPE;
$viewer = $this->getViewer();
$content_source = $this->getContentSource();
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
foreach ($need_upgrade as $password) {
// This does the actual upgrade. We then apply a transaction to make
// the upgrade more visible and auditable.
$old_hasher = $password->getHasher();
$password->upgradePasswordHasher($envelope, $this->getObject());
$new_hasher = $password->getHasher();
// NOTE: We must save the change before applying transactions because
// the editor will reload the object to obtain a read lock.
$password->save();
$xactions = array();
$xactions[] = $password->getApplicationTransactionTemplate()
->setTransactionType($upgrade_type)
->setNewValue($new_hasher->getHashName());
$editor = $password->getApplicationTransactionEditor()
->setActor($viewer)
->setContinueOnNoEffect(true)
->setContinueOnMissingFields(true)
->setContentSource($content_source)
->setOldHasher($old_hasher)
->applyTransactions($password, $xactions);
}
unset($unguarded);
}
}
diff --git a/src/infrastructure/customfield/standard/PhabricatorStandardCustomField.php b/src/infrastructure/customfield/standard/PhabricatorStandardCustomField.php
index c41148a4aa..37f9e808ab 100644
--- a/src/infrastructure/customfield/standard/PhabricatorStandardCustomField.php
+++ b/src/infrastructure/customfield/standard/PhabricatorStandardCustomField.php
@@ -1,517 +1,517 @@
<?php
abstract class PhabricatorStandardCustomField
extends PhabricatorCustomField {
private $rawKey;
private $fieldKey;
private $fieldName;
private $fieldValue;
private $fieldDescription;
private $fieldConfig;
private $applicationField;
private $strings = array();
private $caption;
private $fieldError;
private $required;
private $default;
private $isCopyable;
private $hasStorageValue;
private $isBuiltin;
private $isEnabled = true;
abstract public function getFieldType();
public static function buildStandardFields(
PhabricatorCustomField $template,
array $config,
$builtin = false) {
$types = id(new PhutilClassMapQuery())
->setAncestorClass(__CLASS__)
->setUniqueMethod('getFieldType')
->execute();
$fields = array();
foreach ($config as $key => $value) {
$type = idx($value, 'type', 'text');
if (empty($types[$type])) {
// TODO: We should have better typechecking somewhere, and then make
// this more serious.
continue;
}
$namespace = $template->getStandardCustomFieldNamespace();
$full_key = "std:{$namespace}:{$key}";
$template = clone $template;
$standard = id(clone $types[$type])
->setRawStandardFieldKey($key)
->setFieldKey($full_key)
->setFieldConfig($value)
->setApplicationField($template);
if ($builtin) {
$standard->setIsBuiltin(true);
}
$field = $template->setProxy($standard);
$fields[] = $field;
}
return $fields;
}
public function setApplicationField(
PhabricatorStandardCustomFieldInterface $application_field) {
$this->applicationField = $application_field;
return $this;
}
public function getApplicationField() {
return $this->applicationField;
}
public function setFieldName($name) {
$this->fieldName = $name;
return $this;
}
public function getFieldValue() {
return $this->fieldValue;
}
public function setFieldValue($value) {
$this->fieldValue = $value;
return $this;
}
public function setCaption($caption) {
$this->caption = $caption;
return $this;
}
public function getCaption() {
return $this->caption;
}
public function setFieldDescription($description) {
$this->fieldDescription = $description;
return $this;
}
public function setIsBuiltin($is_builtin) {
$this->isBuiltin = $is_builtin;
return $this;
}
public function getIsBuiltin() {
return $this->isBuiltin;
}
public function setFieldConfig(array $config) {
foreach ($config as $key => $value) {
switch ($key) {
case 'name':
$this->setFieldName($value);
break;
case 'description':
$this->setFieldDescription($value);
break;
case 'strings':
$this->setStrings($value);
break;
case 'caption':
$this->setCaption($value);
break;
case 'required':
if ($value) {
$this->setRequired($value);
$this->setFieldError(true);
}
break;
case 'default':
$this->setFieldValue($value);
break;
case 'copy':
$this->setIsCopyable($value);
break;
case 'type':
// We set this earlier on.
break;
}
}
$this->fieldConfig = $config;
return $this;
}
public function getFieldConfigValue($key, $default = null) {
return idx($this->fieldConfig, $key, $default);
}
public function setFieldError($field_error) {
$this->fieldError = $field_error;
return $this;
}
public function getFieldError() {
return $this->fieldError;
}
public function setRequired($required) {
$this->required = $required;
return $this;
}
public function getRequired() {
return $this->required;
}
public function setRawStandardFieldKey($raw_key) {
$this->rawKey = $raw_key;
return $this;
}
public function getRawStandardFieldKey() {
return $this->rawKey;
}
public function setIsEnabled($is_enabled) {
$this->isEnabled = $is_enabled;
return $this;
}
public function getIsEnabled() {
return $this->isEnabled;
}
public function isFieldEnabled() {
return $this->getIsEnabled();
}
/* -( PhabricatorCustomField )--------------------------------------------- */
public function setFieldKey($field_key) {
$this->fieldKey = $field_key;
return $this;
}
public function getFieldKey() {
return $this->fieldKey;
}
public function getFieldName() {
return coalesce($this->fieldName, parent::getFieldName());
}
public function getFieldDescription() {
return coalesce($this->fieldDescription, parent::getFieldDescription());
}
public function setStrings(array $strings) {
$this->strings = $strings;
return;
}
public function getString($key, $default = null) {
return idx($this->strings, $key, $default);
}
public function setIsCopyable($is_copyable) {
$this->isCopyable = $is_copyable;
return $this;
}
public function getIsCopyable() {
return $this->isCopyable;
}
public function shouldUseStorage() {
try {
$object = $this->newStorageObject();
return true;
} catch (PhabricatorCustomFieldImplementationIncompleteException $ex) {
return false;
}
}
public function getValueForStorage() {
return $this->getFieldValue();
}
public function setValueFromStorage($value) {
return $this->setFieldValue($value);
}
public function didSetValueFromStorage() {
$this->hasStorageValue = true;
return $this;
}
public function getOldValueForApplicationTransactions() {
if ($this->hasStorageValue) {
return $this->getValueForStorage();
} else {
return null;
}
}
public function shouldAppearInApplicationTransactions() {
return true;
}
public function shouldAppearInEditView() {
return $this->getFieldConfigValue('edit', true);
}
public function readValueFromRequest(AphrontRequest $request) {
$value = $request->getStr($this->getFieldKey());
if (!strlen($value)) {
$value = null;
}
$this->setFieldValue($value);
}
public function getInstructionsForEdit() {
return $this->getFieldConfigValue('instructions');
}
public function getPlaceholder() {
return $this->getFieldConfigValue('placeholder', null);
}
public function renderEditControl(array $handles) {
return id(new AphrontFormTextControl())
->setName($this->getFieldKey())
->setCaption($this->getCaption())
->setValue($this->getFieldValue())
->setError($this->getFieldError())
->setLabel($this->getFieldName())
->setPlaceholder($this->getPlaceholder());
}
public function newStorageObject() {
return $this->getApplicationField()->newStorageObject();
}
public function shouldAppearInPropertyView() {
return $this->getFieldConfigValue('view', true);
}
public function renderPropertyViewValue(array $handles) {
- if (!strlen($this->getFieldValue())) {
+ if (!phutil_nonempty_string($this->getFieldValue())) {
return null;
}
return $this->getFieldValue();
}
public function shouldAppearInApplicationSearch() {
return $this->getFieldConfigValue('search', false);
}
protected function newStringIndexStorage() {
return $this->getApplicationField()->newStringIndexStorage();
}
protected function newNumericIndexStorage() {
return $this->getApplicationField()->newNumericIndexStorage();
}
public function buildFieldIndexes() {
return array();
}
public function buildOrderIndex() {
return null;
}
public function readApplicationSearchValueFromRequest(
PhabricatorApplicationSearchEngine $engine,
AphrontRequest $request) {
return;
}
public function applyApplicationSearchConstraintToQuery(
PhabricatorApplicationSearchEngine $engine,
PhabricatorCursorPagedPolicyAwareQuery $query,
$value) {
return;
}
public function appendToApplicationSearchForm(
PhabricatorApplicationSearchEngine $engine,
AphrontFormView $form,
$value) {
return;
}
public function validateApplicationTransactions(
PhabricatorApplicationTransactionEditor $editor,
$type,
array $xactions) {
$this->setFieldError(null);
$errors = parent::validateApplicationTransactions(
$editor,
$type,
$xactions);
if ($this->getRequired()) {
$value = $this->getOldValueForApplicationTransactions();
$transaction = null;
foreach ($xactions as $xaction) {
$value = $xaction->getNewValue();
if (!$this->isValueEmpty($value)) {
$transaction = $xaction;
break;
}
}
if ($this->isValueEmpty($value)) {
$error = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Required'),
pht('%s is required.', $this->getFieldName()),
$transaction);
$error->setIsMissingFieldError(true);
$errors[] = $error;
$this->setFieldError(pht('Required'));
}
}
return $errors;
}
protected function isValueEmpty($value) {
if (is_array($value)) {
return empty($value);
}
return $value === null || !strlen($value);
}
public function getApplicationTransactionTitle(
PhabricatorApplicationTransaction $xaction) {
$author_phid = $xaction->getAuthorPHID();
$old = $xaction->getOldValue();
$new = $xaction->getNewValue();
if (!$old) {
return pht(
'%s set %s to %s.',
$xaction->renderHandleLink($author_phid),
$this->getFieldName(),
$new);
} else if (!$new) {
return pht(
'%s removed %s.',
$xaction->renderHandleLink($author_phid),
$this->getFieldName());
} else {
return pht(
'%s changed %s from %s to %s.',
$xaction->renderHandleLink($author_phid),
$this->getFieldName(),
$old,
$new);
}
}
public function getApplicationTransactionTitleForFeed(
PhabricatorApplicationTransaction $xaction) {
$author_phid = $xaction->getAuthorPHID();
$object_phid = $xaction->getObjectPHID();
$old = $xaction->getOldValue();
$new = $xaction->getNewValue();
if (!$old) {
return pht(
'%s set %s to %s on %s.',
$xaction->renderHandleLink($author_phid),
$this->getFieldName(),
$new,
$xaction->renderHandleLink($object_phid));
} else if (!$new) {
return pht(
'%s removed %s on %s.',
$xaction->renderHandleLink($author_phid),
$this->getFieldName(),
$xaction->renderHandleLink($object_phid));
} else {
return pht(
'%s changed %s from %s to %s on %s.',
$xaction->renderHandleLink($author_phid),
$this->getFieldName(),
$old,
$new,
$xaction->renderHandleLink($object_phid));
}
}
public function getHeraldFieldValue() {
return $this->getFieldValue();
}
public function getFieldControlID($key = null) {
$key = coalesce($key, $this->getRawStandardFieldKey());
return 'std:control:'.$key;
}
public function shouldAppearInGlobalSearch() {
return $this->getFieldConfigValue('fulltext', false);
}
public function updateAbstractDocument(
PhabricatorSearchAbstractDocument $document) {
$field_key = $this->getFieldConfigValue('fulltext');
// If the caller or configuration didn't specify a valid field key,
// generate one automatically from the field index.
if (!is_string($field_key) || (strlen($field_key) != 4)) {
$field_key = '!'.substr($this->getFieldIndex(), 0, 3);
}
$field_value = $this->getFieldValue();
if (strlen($field_value)) {
$document->addField($field_key, $field_value);
}
}
protected function newStandardEditField() {
$short = $this->getModernFieldKey();
return parent::newStandardEditField()
->setEditTypeKey($short)
->setIsCopyable($this->getIsCopyable());
}
public function shouldAppearInConduitTransactions() {
return true;
}
public function shouldAppearInConduitDictionary() {
return true;
}
public function getModernFieldKey() {
if ($this->getIsBuiltin()) {
return $this->getRawStandardFieldKey();
} else {
return 'custom.'.$this->getRawStandardFieldKey();
}
}
public function getConduitDictionaryValue() {
return $this->getFieldValue();
}
public function newExportData() {
return $this->getFieldValue();
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Jan 19, 19:56 (2 w, 4 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1128207
Default Alt Text
(27 KB)
Attached To
Mode
rP Phorge
Attached
Detach File
Event Timeline
Log In to Comment