mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-15 18:10:53 +01:00
(stable) Promote 2017 Week 14
This commit is contained in:
commit
699ab153e3
89 changed files with 1389 additions and 582 deletions
8
resources/sql/autopatches/20170406.hmac.01.keystore.sql
Normal file
8
resources/sql/autopatches/20170406.hmac.01.keystore.sql
Normal file
|
@ -0,0 +1,8 @@
|
|||
CREATE TABLE {$NAMESPACE}_auth.auth_hmackey (
|
||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
keyName VARCHAR(64) NOT NULL COLLATE {$COLLATE_TEXT},
|
||||
keyValue VARCHAR(128) NOT NULL COLLATE {$COLLATE_TEXT},
|
||||
dateCreated INT UNSIGNED NOT NULL,
|
||||
dateModified INT UNSIGNED NOT NULL,
|
||||
UNIQUE KEY `key_name` (keyName)
|
||||
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
|
|
@ -14,7 +14,7 @@ foreach ($sessions as $session) {
|
|||
$conn,
|
||||
'UPDATE %T SET sessionKey = %s WHERE userPHID = %s AND type = %s',
|
||||
PhabricatorUser::SESSION_TABLE,
|
||||
PhabricatorHash::digest($session['sessionKey']),
|
||||
PhabricatorHash::weakDigest($session['sessionKey']),
|
||||
$session['userPHID'],
|
||||
$session['type']);
|
||||
}
|
||||
|
|
|
@ -502,6 +502,7 @@ phutil_register_library_map(array(
|
|||
'DifferentialReviewersCommitMessageField' => 'applications/differential/field/DifferentialReviewersCommitMessageField.php',
|
||||
'DifferentialReviewersField' => 'applications/differential/customfield/DifferentialReviewersField.php',
|
||||
'DifferentialReviewersHeraldAction' => 'applications/differential/herald/DifferentialReviewersHeraldAction.php',
|
||||
'DifferentialReviewersSearchEngineAttachment' => 'applications/differential/engineextension/DifferentialReviewersSearchEngineAttachment.php',
|
||||
'DifferentialReviewersView' => 'applications/differential/view/DifferentialReviewersView.php',
|
||||
'DifferentialRevision' => 'applications/differential/storage/DifferentialRevision.php',
|
||||
'DifferentialRevisionAbandonTransaction' => 'applications/differential/xaction/DifferentialRevisionAbandonTransaction.php',
|
||||
|
@ -1919,6 +1920,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorAuthFactorConfig' => 'applications/auth/storage/PhabricatorAuthFactorConfig.php',
|
||||
'PhabricatorAuthFactorTestCase' => 'applications/auth/factor/__tests__/PhabricatorAuthFactorTestCase.php',
|
||||
'PhabricatorAuthFinishController' => 'applications/auth/controller/PhabricatorAuthFinishController.php',
|
||||
'PhabricatorAuthHMACKey' => 'applications/auth/storage/PhabricatorAuthHMACKey.php',
|
||||
'PhabricatorAuthHighSecurityRequiredException' => 'applications/auth/exception/PhabricatorAuthHighSecurityRequiredException.php',
|
||||
'PhabricatorAuthHighSecurityToken' => 'applications/auth/data/PhabricatorAuthHighSecurityToken.php',
|
||||
'PhabricatorAuthInvite' => 'applications/auth/storage/PhabricatorAuthInvite.php',
|
||||
|
@ -2743,7 +2745,6 @@ phutil_register_library_map(array(
|
|||
'PhabricatorFileChunk' => 'applications/files/storage/PhabricatorFileChunk.php',
|
||||
'PhabricatorFileChunkIterator' => 'applications/files/engine/PhabricatorFileChunkIterator.php',
|
||||
'PhabricatorFileChunkQuery' => 'applications/files/query/PhabricatorFileChunkQuery.php',
|
||||
'PhabricatorFileCommentController' => 'applications/files/controller/PhabricatorFileCommentController.php',
|
||||
'PhabricatorFileComposeController' => 'applications/files/controller/PhabricatorFileComposeController.php',
|
||||
'PhabricatorFileController' => 'applications/files/controller/PhabricatorFileController.php',
|
||||
'PhabricatorFileDAO' => 'applications/files/storage/PhabricatorFileDAO.php',
|
||||
|
@ -2751,6 +2752,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorFileDeleteController' => 'applications/files/controller/PhabricatorFileDeleteController.php',
|
||||
'PhabricatorFileDropUploadController' => 'applications/files/controller/PhabricatorFileDropUploadController.php',
|
||||
'PhabricatorFileEditController' => 'applications/files/controller/PhabricatorFileEditController.php',
|
||||
'PhabricatorFileEditEngine' => 'applications/files/editor/PhabricatorFileEditEngine.php',
|
||||
'PhabricatorFileEditField' => 'applications/transactions/editfield/PhabricatorFileEditField.php',
|
||||
'PhabricatorFileEditor' => 'applications/files/editor/PhabricatorFileEditor.php',
|
||||
'PhabricatorFileExternalRequest' => 'applications/files/storage/PhabricatorFileExternalRequest.php',
|
||||
|
@ -2762,13 +2764,16 @@ phutil_register_library_map(array(
|
|||
'PhabricatorFileImageProxyController' => 'applications/files/controller/PhabricatorFileImageProxyController.php',
|
||||
'PhabricatorFileImageTransform' => 'applications/files/transform/PhabricatorFileImageTransform.php',
|
||||
'PhabricatorFileInfoController' => 'applications/files/controller/PhabricatorFileInfoController.php',
|
||||
'PhabricatorFileIntegrityException' => 'applications/files/exception/PhabricatorFileIntegrityException.php',
|
||||
'PhabricatorFileLightboxController' => 'applications/files/controller/PhabricatorFileLightboxController.php',
|
||||
'PhabricatorFileLinkView' => 'view/layout/PhabricatorFileLinkView.php',
|
||||
'PhabricatorFileListController' => 'applications/files/controller/PhabricatorFileListController.php',
|
||||
'PhabricatorFileNameTransaction' => 'applications/files/xaction/PhabricatorFileNameTransaction.php',
|
||||
'PhabricatorFileQuery' => 'applications/files/query/PhabricatorFileQuery.php',
|
||||
'PhabricatorFileROT13StorageFormat' => 'applications/files/format/PhabricatorFileROT13StorageFormat.php',
|
||||
'PhabricatorFileRawStorageFormat' => 'applications/files/format/PhabricatorFileRawStorageFormat.php',
|
||||
'PhabricatorFileSchemaSpec' => 'applications/files/storage/PhabricatorFileSchemaSpec.php',
|
||||
'PhabricatorFileSearchConduitAPIMethod' => 'applications/files/conduit/PhabricatorFileSearchConduitAPIMethod.php',
|
||||
'PhabricatorFileSearchEngine' => 'applications/files/query/PhabricatorFileSearchEngine.php',
|
||||
'PhabricatorFileStorageBlob' => 'applications/files/storage/PhabricatorFileStorageBlob.php',
|
||||
'PhabricatorFileStorageConfigurationException' => 'applications/files/exception/PhabricatorFileStorageConfigurationException.php',
|
||||
|
@ -2783,6 +2788,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorFileTransaction' => 'applications/files/storage/PhabricatorFileTransaction.php',
|
||||
'PhabricatorFileTransactionComment' => 'applications/files/storage/PhabricatorFileTransactionComment.php',
|
||||
'PhabricatorFileTransactionQuery' => 'applications/files/query/PhabricatorFileTransactionQuery.php',
|
||||
'PhabricatorFileTransactionType' => 'applications/files/xaction/PhabricatorFileTransactionType.php',
|
||||
'PhabricatorFileTransform' => 'applications/files/transform/PhabricatorFileTransform.php',
|
||||
'PhabricatorFileTransformController' => 'applications/files/controller/PhabricatorFileTransformController.php',
|
||||
'PhabricatorFileTransformListController' => 'applications/files/controller/PhabricatorFileTransformListController.php',
|
||||
|
@ -2805,6 +2811,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorFilesManagementEncodeWorkflow' => 'applications/files/management/PhabricatorFilesManagementEncodeWorkflow.php',
|
||||
'PhabricatorFilesManagementEnginesWorkflow' => 'applications/files/management/PhabricatorFilesManagementEnginesWorkflow.php',
|
||||
'PhabricatorFilesManagementGenerateKeyWorkflow' => 'applications/files/management/PhabricatorFilesManagementGenerateKeyWorkflow.php',
|
||||
'PhabricatorFilesManagementIntegrityWorkflow' => 'applications/files/management/PhabricatorFilesManagementIntegrityWorkflow.php',
|
||||
'PhabricatorFilesManagementMigrateWorkflow' => 'applications/files/management/PhabricatorFilesManagementMigrateWorkflow.php',
|
||||
'PhabricatorFilesManagementPurgeWorkflow' => 'applications/files/management/PhabricatorFilesManagementPurgeWorkflow.php',
|
||||
'PhabricatorFilesManagementRebuildWorkflow' => 'applications/files/management/PhabricatorFilesManagementRebuildWorkflow.php',
|
||||
|
@ -2858,6 +2865,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorGuideModule' => 'applications/guides/module/PhabricatorGuideModule.php',
|
||||
'PhabricatorGuideModuleController' => 'applications/guides/controller/PhabricatorGuideModuleController.php',
|
||||
'PhabricatorGuideQuickStartModule' => 'applications/guides/module/PhabricatorGuideQuickStartModule.php',
|
||||
'PhabricatorHMACTestCase' => 'infrastructure/util/__tests__/PhabricatorHMACTestCase.php',
|
||||
'PhabricatorHTTPParameterTypeTableView' => 'applications/config/view/PhabricatorHTTPParameterTypeTableView.php',
|
||||
'PhabricatorHandleList' => 'applications/phid/handle/pool/PhabricatorHandleList.php',
|
||||
'PhabricatorHandleObjectSelectorDataView' => 'applications/phid/handle/view/PhabricatorHandleObjectSelectorDataView.php',
|
||||
|
@ -5271,6 +5279,7 @@ phutil_register_library_map(array(
|
|||
'DifferentialReviewersCommitMessageField' => 'DifferentialCommitMessageField',
|
||||
'DifferentialReviewersField' => 'DifferentialCoreCustomField',
|
||||
'DifferentialReviewersHeraldAction' => 'HeraldAction',
|
||||
'DifferentialReviewersSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment',
|
||||
'DifferentialReviewersView' => 'AphrontView',
|
||||
'DifferentialRevision' => array(
|
||||
'DifferentialDAO',
|
||||
|
@ -6893,6 +6902,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorAuthFactorConfig' => 'PhabricatorAuthDAO',
|
||||
'PhabricatorAuthFactorTestCase' => 'PhabricatorTestCase',
|
||||
'PhabricatorAuthFinishController' => 'PhabricatorAuthController',
|
||||
'PhabricatorAuthHMACKey' => 'PhabricatorAuthDAO',
|
||||
'PhabricatorAuthHighSecurityRequiredException' => 'Exception',
|
||||
'PhabricatorAuthHighSecurityToken' => 'Phobject',
|
||||
'PhabricatorAuthInvite' => array(
|
||||
|
@ -7845,6 +7855,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorFlaggableInterface',
|
||||
'PhabricatorPolicyInterface',
|
||||
'PhabricatorDestructibleInterface',
|
||||
'PhabricatorConduitResultInterface',
|
||||
),
|
||||
'PhabricatorFileAES256StorageFormat' => 'PhabricatorFileStorageFormat',
|
||||
'PhabricatorFileBundleLoader' => 'Phobject',
|
||||
|
@ -7858,7 +7869,6 @@ phutil_register_library_map(array(
|
|||
'Iterator',
|
||||
),
|
||||
'PhabricatorFileChunkQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||
'PhabricatorFileCommentController' => 'PhabricatorFileController',
|
||||
'PhabricatorFileComposeController' => 'PhabricatorFileController',
|
||||
'PhabricatorFileController' => 'PhabricatorController',
|
||||
'PhabricatorFileDAO' => 'PhabricatorLiskDAO',
|
||||
|
@ -7866,6 +7876,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorFileDeleteController' => 'PhabricatorFileController',
|
||||
'PhabricatorFileDropUploadController' => 'PhabricatorFileController',
|
||||
'PhabricatorFileEditController' => 'PhabricatorFileController',
|
||||
'PhabricatorFileEditEngine' => 'PhabricatorEditEngine',
|
||||
'PhabricatorFileEditField' => 'PhabricatorEditField',
|
||||
'PhabricatorFileEditor' => 'PhabricatorApplicationTransactionEditor',
|
||||
'PhabricatorFileExternalRequest' => array(
|
||||
|
@ -7887,13 +7898,16 @@ phutil_register_library_map(array(
|
|||
'PhabricatorFileImageProxyController' => 'PhabricatorFileController',
|
||||
'PhabricatorFileImageTransform' => 'PhabricatorFileTransform',
|
||||
'PhabricatorFileInfoController' => 'PhabricatorFileController',
|
||||
'PhabricatorFileIntegrityException' => 'Exception',
|
||||
'PhabricatorFileLightboxController' => 'PhabricatorFileController',
|
||||
'PhabricatorFileLinkView' => 'AphrontTagView',
|
||||
'PhabricatorFileListController' => 'PhabricatorFileController',
|
||||
'PhabricatorFileNameTransaction' => 'PhabricatorFileTransactionType',
|
||||
'PhabricatorFileQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||
'PhabricatorFileROT13StorageFormat' => 'PhabricatorFileStorageFormat',
|
||||
'PhabricatorFileRawStorageFormat' => 'PhabricatorFileStorageFormat',
|
||||
'PhabricatorFileSchemaSpec' => 'PhabricatorConfigSchemaSpec',
|
||||
'PhabricatorFileSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
|
||||
'PhabricatorFileSearchEngine' => 'PhabricatorApplicationSearchEngine',
|
||||
'PhabricatorFileStorageBlob' => 'PhabricatorFileDAO',
|
||||
'PhabricatorFileStorageConfigurationException' => 'Exception',
|
||||
|
@ -7905,9 +7919,10 @@ phutil_register_library_map(array(
|
|||
'PhabricatorFileTestCase' => 'PhabricatorTestCase',
|
||||
'PhabricatorFileTestDataGenerator' => 'PhabricatorTestDataGenerator',
|
||||
'PhabricatorFileThumbnailTransform' => 'PhabricatorFileImageTransform',
|
||||
'PhabricatorFileTransaction' => 'PhabricatorApplicationTransaction',
|
||||
'PhabricatorFileTransaction' => 'PhabricatorModularTransaction',
|
||||
'PhabricatorFileTransactionComment' => 'PhabricatorApplicationTransactionComment',
|
||||
'PhabricatorFileTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
||||
'PhabricatorFileTransactionType' => 'PhabricatorModularTransactionType',
|
||||
'PhabricatorFileTransform' => 'Phobject',
|
||||
'PhabricatorFileTransformController' => 'PhabricatorFileController',
|
||||
'PhabricatorFileTransformListController' => 'PhabricatorFileController',
|
||||
|
@ -7930,6 +7945,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorFilesManagementEncodeWorkflow' => 'PhabricatorFilesManagementWorkflow',
|
||||
'PhabricatorFilesManagementEnginesWorkflow' => 'PhabricatorFilesManagementWorkflow',
|
||||
'PhabricatorFilesManagementGenerateKeyWorkflow' => 'PhabricatorFilesManagementWorkflow',
|
||||
'PhabricatorFilesManagementIntegrityWorkflow' => 'PhabricatorFilesManagementWorkflow',
|
||||
'PhabricatorFilesManagementMigrateWorkflow' => 'PhabricatorFilesManagementWorkflow',
|
||||
'PhabricatorFilesManagementPurgeWorkflow' => 'PhabricatorFilesManagementWorkflow',
|
||||
'PhabricatorFilesManagementRebuildWorkflow' => 'PhabricatorFilesManagementWorkflow',
|
||||
|
@ -7985,6 +8001,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorGuideModule' => 'Phobject',
|
||||
'PhabricatorGuideModuleController' => 'PhabricatorGuideController',
|
||||
'PhabricatorGuideQuickStartModule' => 'PhabricatorGuideModule',
|
||||
'PhabricatorHMACTestCase' => 'PhabricatorTestCase',
|
||||
'PhabricatorHTTPParameterTypeTableView' => 'AphrontView',
|
||||
'PhabricatorHandleList' => array(
|
||||
'Phobject',
|
||||
|
|
|
@ -94,10 +94,15 @@ final class AphrontFileResponse extends AphrontResponse {
|
|||
array('Accept-Ranges', 'bytes'),
|
||||
);
|
||||
|
||||
if ($this->rangeMin || $this->rangeMax) {
|
||||
if ($this->rangeMin !== null || $this->rangeMax !== null) {
|
||||
$len = $this->getContentLength();
|
||||
$min = $this->rangeMin;
|
||||
|
||||
$max = $this->rangeMax;
|
||||
if ($max === null) {
|
||||
$max = ($len - 1);
|
||||
}
|
||||
|
||||
$headers[] = array('Content-Range', "bytes {$min}-{$max}/{$len}");
|
||||
$content_len = ($max - $min) + 1;
|
||||
} else {
|
||||
|
@ -105,7 +110,7 @@ final class AphrontFileResponse extends AphrontResponse {
|
|||
}
|
||||
|
||||
if (!$this->shouldCompressResponse()) {
|
||||
$headers[] = array('Content-Length', $this->getContentLength());
|
||||
$headers[] = array('Content-Length', $content_len);
|
||||
}
|
||||
|
||||
if (strlen($this->getDownload())) {
|
||||
|
|
|
@ -194,7 +194,7 @@ abstract class PhabricatorAuthController extends PhabricatorController {
|
|||
// hijacking registration sessions.
|
||||
|
||||
$actual = $account->getProperty('registrationKey');
|
||||
$expect = PhabricatorHash::digest($registration_key);
|
||||
$expect = PhabricatorHash::weakDigest($registration_key);
|
||||
if (!phutil_hashes_are_identical($actual, $expect)) {
|
||||
$response = $this->renderError(
|
||||
pht(
|
||||
|
|
|
@ -194,7 +194,7 @@ final class PhabricatorAuthLoginController
|
|||
$registration_key = Filesystem::readRandomCharacters(32);
|
||||
$account->setProperty(
|
||||
'registrationKey',
|
||||
PhabricatorHash::digest($registration_key));
|
||||
PhabricatorHash::weakDigest($registration_key));
|
||||
|
||||
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
||||
$account->save();
|
||||
|
|
|
@ -135,7 +135,7 @@ final class PhabricatorAuthOneTimeLoginController
|
|||
->setTokenResource($target_user->getPHID())
|
||||
->setTokenType($password_type)
|
||||
->setTokenExpires(time() + phutil_units('1 hour in seconds'))
|
||||
->setTokenCode(PhabricatorHash::digest($key))
|
||||
->setTokenCode(PhabricatorHash::weakDigest($key))
|
||||
->save();
|
||||
unset($unguarded);
|
||||
|
||||
|
|
|
@ -24,11 +24,11 @@ final class PhabricatorAuthSSHKeyGenerateController
|
|||
$keys = PhabricatorSSHKeyGenerator::generateKeypair();
|
||||
list($public_key, $private_key) = $keys;
|
||||
|
||||
$file = PhabricatorFile::buildFromFileDataOrHash(
|
||||
$file = PhabricatorFile::newFromFileData(
|
||||
$private_key,
|
||||
array(
|
||||
'name' => $default_name.'.key',
|
||||
'ttl' => time() + (60 * 10),
|
||||
'ttl.relative' => phutil_units('10 minutes in seconds'),
|
||||
'viewPolicy' => $viewer->getPHID(),
|
||||
));
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ final class PhabricatorAuthTerminateSessionController
|
|||
$query->withIDs(array($id));
|
||||
}
|
||||
|
||||
$current_key = PhabricatorHash::digest(
|
||||
$current_key = PhabricatorHash::weakDigest(
|
||||
$request->getCookie(PhabricatorCookies::COOKIE_SESSION));
|
||||
|
||||
$sessions = $query->execute();
|
||||
|
|
|
@ -110,7 +110,7 @@ final class PhabricatorAuthSessionEngine extends Phobject {
|
|||
$session_table = new PhabricatorAuthSession();
|
||||
$user_table = new PhabricatorUser();
|
||||
$conn_r = $session_table->establishConnection('r');
|
||||
$session_key = PhabricatorHash::digest($session_token);
|
||||
$session_key = PhabricatorHash::weakDigest($session_token);
|
||||
|
||||
$cache_parts = $this->getUserCacheQueryParts($conn_r);
|
||||
list($cache_selects, $cache_joins, $cache_map, $types_map) = $cache_parts;
|
||||
|
@ -240,7 +240,7 @@ final class PhabricatorAuthSessionEngine extends Phobject {
|
|||
// This has a side effect of validating the session type.
|
||||
$session_ttl = PhabricatorAuthSession::getSessionTypeTTL($session_type);
|
||||
|
||||
$digest_key = PhabricatorHash::digest($session_key);
|
||||
$digest_key = PhabricatorHash::weakDigest($session_key);
|
||||
|
||||
// Logging-in users don't have CSRF stuff yet, so we have to unguard this
|
||||
// write.
|
||||
|
@ -306,7 +306,7 @@ final class PhabricatorAuthSessionEngine extends Phobject {
|
|||
->execute();
|
||||
|
||||
if ($except_session !== null) {
|
||||
$except_session = PhabricatorHash::digest($except_session);
|
||||
$except_session = PhabricatorHash::weakDigest($except_session);
|
||||
}
|
||||
|
||||
foreach ($sessions as $key => $session) {
|
||||
|
@ -755,7 +755,7 @@ final class PhabricatorAuthSessionEngine extends Phobject {
|
|||
$parts[] = $email->getVerificationCode();
|
||||
}
|
||||
|
||||
return PhabricatorHash::digest(implode(':', $parts));
|
||||
return PhabricatorHash::weakDigest(implode(':', $parts));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ final class PhabricatorTOTPAuthFactor extends PhabricatorAuthFactor {
|
|||
->withTokenResources(array($user->getPHID()))
|
||||
->withTokenTypes(array($totp_token_type))
|
||||
->withExpired(false)
|
||||
->withTokenCodes(array(PhabricatorHash::digest($key)))
|
||||
->withTokenCodes(array(PhabricatorHash::weakDigest($key)))
|
||||
->executeOne();
|
||||
if (!$temporary_token) {
|
||||
// If we don't have a matching token, regenerate the key below.
|
||||
|
@ -58,7 +58,7 @@ final class PhabricatorTOTPAuthFactor extends PhabricatorAuthFactor {
|
|||
->setTokenResource($user->getPHID())
|
||||
->setTokenType($totp_token_type)
|
||||
->setTokenExpires(time() + phutil_units('1 hour in seconds'))
|
||||
->setTokenCode(PhabricatorHash::digest($key))
|
||||
->setTokenCode(PhabricatorHash::weakDigest($key))
|
||||
->save();
|
||||
unset($unguarded);
|
||||
}
|
||||
|
|
|
@ -474,7 +474,7 @@ abstract class PhabricatorAuthProvider extends Phobject {
|
|||
true);
|
||||
}
|
||||
|
||||
return PhabricatorHash::digest($phcid);
|
||||
return PhabricatorHash::weakDigest($phcid);
|
||||
}
|
||||
|
||||
protected function verifyAuthCSRFCode(AphrontRequest $request, $actual) {
|
||||
|
|
|
@ -85,7 +85,7 @@ final class PhabricatorAuthSessionQuery
|
|||
if ($this->sessionKeys) {
|
||||
$hashes = array();
|
||||
foreach ($this->sessionKeys as $session_key) {
|
||||
$hashes[] = PhabricatorHash::digest($session_key);
|
||||
$hashes[] = PhabricatorHash::weakDigest($session_key);
|
||||
}
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
|
|
24
src/applications/auth/storage/PhabricatorAuthHMACKey.php
Normal file
24
src/applications/auth/storage/PhabricatorAuthHMACKey.php
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorAuthHMACKey
|
||||
extends PhabricatorAuthDAO {
|
||||
|
||||
protected $keyName;
|
||||
protected $keyValue;
|
||||
|
||||
protected function getConfiguration() {
|
||||
return array(
|
||||
self::CONFIG_COLUMN_SCHEMA => array(
|
||||
'keyName' => 'text64',
|
||||
'keyValue' => 'text128',
|
||||
),
|
||||
self::CONFIG_KEY_SCHEMA => array(
|
||||
'key_name' => array(
|
||||
'columns' => array('keyName'),
|
||||
'unique' => true,
|
||||
),
|
||||
),
|
||||
) + parent::getConfiguration();
|
||||
}
|
||||
|
||||
}
|
|
@ -43,7 +43,7 @@ final class PhabricatorBadgesBadgeNameTransaction
|
|||
$new_value = $xaction->getNewValue();
|
||||
$new_length = strlen($new_value);
|
||||
if ($new_length > $max_length) {
|
||||
$errors[] = $this->newRequiredError(
|
||||
$errors[] = $this->newInvalidError(
|
||||
pht('The name can be no longer than %s characters.',
|
||||
new PhutilNumber($max_length)));
|
||||
}
|
||||
|
|
|
@ -98,7 +98,7 @@ abstract class PhabricatorController extends AphrontController {
|
|||
|
||||
|
||||
if (!$user->isLoggedIn()) {
|
||||
$user->attachAlternateCSRFString(PhabricatorHash::digest($phsid));
|
||||
$user->attachAlternateCSRFString(PhabricatorHash::weakDigest($phsid));
|
||||
}
|
||||
|
||||
$request->setUser($user);
|
||||
|
|
|
@ -1182,9 +1182,8 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
|
|||
* @task markup
|
||||
*/
|
||||
public function getMarkupFieldKey($field) {
|
||||
$hash = PhabricatorHash::digest($this->getMarkupText($field));
|
||||
$id = $this->getID();
|
||||
return "calendar:T{$id}:{$field}:{$hash}";
|
||||
$content = $this->getMarkupText($field);
|
||||
return PhabricatorMarkupEngine::digestRemarkupContent($this, $content);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ abstract class CelerityResources extends Phobject {
|
|||
|
||||
public function getCelerityHash($data) {
|
||||
$tail = PhabricatorEnv::getEnvConfig('celerity.resource-hash');
|
||||
$hash = PhabricatorHash::digest($data, $tail);
|
||||
$hash = PhabricatorHash::weakDigest($data, $tail);
|
||||
return substr($hash, 0, 8);
|
||||
}
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ final class PhabricatorConfigApplication extends PhabricatorApplication {
|
|||
}
|
||||
|
||||
public function getName() {
|
||||
return 'Config';
|
||||
return pht('Config');
|
||||
}
|
||||
|
||||
public function getShortDescription() {
|
||||
|
|
|
@ -107,7 +107,7 @@ final class PhabricatorPathSetupCheck extends PhabricatorSetupCheck {
|
|||
|
||||
if ($bad_paths) {
|
||||
foreach ($bad_paths as $path_part => $message) {
|
||||
$digest = substr(PhabricatorHash::digest($path_part), 0, 8);
|
||||
$digest = substr(PhabricatorHash::weakDigest($path_part), 0, 8);
|
||||
|
||||
$this
|
||||
->newIssue('config.PATH.'.$digest)
|
||||
|
|
|
@ -109,7 +109,8 @@ EOTEXT
|
|||
'Default key for HMAC digests where the key is not important '.
|
||||
'(i.e., the hash itself is secret). You can change this if you '.
|
||||
'want (to any other string), but doing so will break existing '.
|
||||
'sessions and CSRF tokens.')),
|
||||
'sessions and CSRF tokens. This option is deprecated. Newer '.
|
||||
'code automatically manages HMAC keys.')),
|
||||
$this->newOption('security.require-https', 'bool', false)
|
||||
->setLocked(true)
|
||||
->setSummary(
|
||||
|
|
|
@ -357,7 +357,7 @@ final class DifferentialChangesetViewController extends DifferentialController {
|
|||
array(
|
||||
'name' => $changeset->getFilename(),
|
||||
'mime-type' => 'text/plain',
|
||||
'ttl' => phutil_units('24 hours in seconds'),
|
||||
'ttl.relative' => phutil_units('24 hours in seconds'),
|
||||
'viewPolicy' => PhabricatorPolicies::POLICY_NOONE,
|
||||
));
|
||||
|
||||
|
|
|
@ -889,15 +889,15 @@ final class DifferentialRevisionViewController extends DifferentialController {
|
|||
}
|
||||
$file_name .= 'diff';
|
||||
|
||||
$file = PhabricatorFile::buildFromFileDataOrHash(
|
||||
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
||||
$file = PhabricatorFile::newFromFileData(
|
||||
$raw_diff,
|
||||
array(
|
||||
'name' => $file_name,
|
||||
'ttl' => (60 * 60 * 24),
|
||||
'ttl.relative' => phutil_units('24 hours in seconds'),
|
||||
'viewPolicy' => PhabricatorPolicies::POLICY_NOONE,
|
||||
));
|
||||
|
||||
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
||||
$file->attachToObject($revision->getPHID());
|
||||
unset($unguarded);
|
||||
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
final class DifferentialReviewersSearchEngineAttachment
|
||||
extends PhabricatorSearchEngineAttachment {
|
||||
|
||||
public function getAttachmentName() {
|
||||
return pht('Differential Reviewers');
|
||||
}
|
||||
|
||||
public function getAttachmentDescription() {
|
||||
return pht('Get the reviewers for each revision.');
|
||||
}
|
||||
|
||||
public function willLoadAttachmentData($query, $spec) {
|
||||
$query->needReviewers(true);
|
||||
}
|
||||
|
||||
public function getAttachmentForObject($object, $data, $spec) {
|
||||
$reviewers = $object->getReviewers();
|
||||
|
||||
$status_blocking = DifferentialReviewerStatus::STATUS_BLOCKING;
|
||||
|
||||
$list = array();
|
||||
foreach ($reviewers as $reviewer) {
|
||||
$status = $reviewer->getReviewerStatus();
|
||||
$is_blocking = ($status == $status_blocking);
|
||||
|
||||
$list[] = array(
|
||||
'reviewerPHID' => $reviewer->getReviewerPHID(),
|
||||
'status' => $status,
|
||||
'isBlocking' => $is_blocking,
|
||||
'actorPHID' => $reviewer->getLastActorPHID(),
|
||||
);
|
||||
}
|
||||
|
||||
return array(
|
||||
'reviewers' => $list,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -260,8 +260,8 @@ final class DifferentialInlineComment
|
|||
|
||||
|
||||
public function getMarkupFieldKey($field) {
|
||||
// We can't use ID because synthetic comments don't have it.
|
||||
return 'DI:'.PhabricatorHash::digest($this->getContent());
|
||||
$content = $this->getMarkupText($field);
|
||||
return PhabricatorMarkupEngine::digestRemarkupContent($this, $content);
|
||||
}
|
||||
|
||||
public function newMarkupEngine($field) {
|
||||
|
|
|
@ -907,7 +907,10 @@ final class DifferentialRevision extends DifferentialDAO
|
|||
}
|
||||
|
||||
public function getConduitSearchAttachments() {
|
||||
return array();
|
||||
return array(
|
||||
id(new DifferentialReviewersSearchEngineAttachment())
|
||||
->setAttachmentKey('reviewers'),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -183,8 +183,19 @@ abstract class DifferentialRevisionReviewTransaction
|
|||
// In all cases, you affect yourself.
|
||||
$map[$viewer->getPHID()] = $status;
|
||||
|
||||
// If the user has submitted a specific list of reviewers to act as (by
|
||||
// unchecking some checkboxes under "Accept"), only affect those reviewers.
|
||||
// If we're applying an "accept the defaults" transaction, and this
|
||||
// transaction type uses checkboxes, replace the value with the list of
|
||||
// defaults.
|
||||
if (!is_array($value)) {
|
||||
list($options, $default) = $this->getActionOptions($viewer, $revision);
|
||||
if ($options) {
|
||||
$value = $default;
|
||||
}
|
||||
}
|
||||
|
||||
// If we have a specific list of reviewers to act on, usually because the
|
||||
// user has submitted a specific list of reviewers to act as by
|
||||
// unchecking some checkboxes under "Accept", only affect those reviewers.
|
||||
if (is_array($value)) {
|
||||
$map = array_select_keys($map, $value);
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ final class DiffusionRepositoryListController extends DiffusionController {
|
|||
->setName(pht('Commits'));
|
||||
|
||||
$items[] = id(new PHUIListItemView())
|
||||
->setName('Browse Commits')
|
||||
->setName(pht('Browse Commits'))
|
||||
->setHref($this->getApplicationURI('commit/'));
|
||||
|
||||
return id(new PhabricatorRepositorySearchEngine())
|
||||
|
|
|
@ -652,7 +652,7 @@ final class DiffusionServeController extends DiffusionController {
|
|||
}
|
||||
|
||||
$lfs_pass = $password->openEnvelope();
|
||||
$lfs_hash = PhabricatorHash::digest($lfs_pass);
|
||||
$lfs_hash = PhabricatorHash::weakDigest($lfs_pass);
|
||||
|
||||
$token = id(new PhabricatorAuthTemporaryTokenQuery())
|
||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||
|
|
|
@ -507,6 +507,10 @@ final class DiffusionURIEditor
|
|||
->synchronizeWorkingCopyAfterHostingChange();
|
||||
}
|
||||
|
||||
$repository->writeStatusMessage(
|
||||
PhabricatorRepositoryStatusMessage::TYPE_NEEDS_UPDATE,
|
||||
null);
|
||||
|
||||
return $xactions;
|
||||
}
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ final class DiffusionGitLFSTemporaryTokenType
|
|||
|
||||
$lfs_user = self::HTTP_USERNAME;
|
||||
$lfs_pass = Filesystem::readRandomCharacters(32);
|
||||
$lfs_hash = PhabricatorHash::digest($lfs_pass);
|
||||
$lfs_hash = PhabricatorHash::weakDigest($lfs_pass);
|
||||
|
||||
$ttl = PhabricatorTime::getNow() + phutil_units('1 day in seconds');
|
||||
|
||||
|
|
|
@ -93,7 +93,7 @@ abstract class DiffusionFileFutureQuery
|
|||
$drequest = $this->getRequest();
|
||||
|
||||
$name = basename($drequest->getPath());
|
||||
$ttl = PhabricatorTime::getNow() + phutil_units('48 hours in seconds');
|
||||
$relative_ttl = phutil_units('48 hours in seconds');
|
||||
|
||||
try {
|
||||
$threshold = PhabricatorFileStorageEngine::getChunkThreshold();
|
||||
|
@ -101,7 +101,7 @@ abstract class DiffusionFileFutureQuery
|
|||
|
||||
$source = id(new PhabricatorExecFutureFileUploadSource())
|
||||
->setName($name)
|
||||
->setTTL($ttl)
|
||||
->setRelativeTTL($relative_ttl)
|
||||
->setViewPolicy(PhabricatorPolicies::POLICY_NOONE)
|
||||
->setExecFuture($future);
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ final class DrydockManagementLeaseWorkflow
|
|||
array(
|
||||
'name' => 'attributes',
|
||||
'param' => 'name=value,...',
|
||||
'help' => pht('Resource specficiation.'),
|
||||
'help' => pht('Resource specification.'),
|
||||
),
|
||||
));
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ final class PhabricatorImageTransformer extends Phobject {
|
|||
$image,
|
||||
array(
|
||||
'name' => 'meme-'.$file->getName(),
|
||||
'ttl' => time() + 60 * 60 * 24,
|
||||
'ttl.relative' => phutil_units('24 hours in seconds'),
|
||||
'canCDN' => true,
|
||||
));
|
||||
}
|
||||
|
|
|
@ -78,7 +78,8 @@ final class PhabricatorFilesApplication extends PhabricatorApplication {
|
|||
'comment/(?P<id>[1-9]\d*)/' => 'PhabricatorFileCommentController',
|
||||
'thread/(?P<phid>[^/]+)/' => 'PhabricatorFileLightboxController',
|
||||
'delete/(?P<id>[1-9]\d*)/' => 'PhabricatorFileDeleteController',
|
||||
'edit/(?P<id>[1-9]\d*)/' => 'PhabricatorFileEditController',
|
||||
$this->getEditRoutePattern('edit/')
|
||||
=> 'PhabricatorFileEditController',
|
||||
'info/(?P<phid>[^/]+)/' => 'PhabricatorFileInfoController',
|
||||
'imageproxy/' => 'PhabricatorFileImageProxyController',
|
||||
'transforms/(?P<id>[1-9]\d*)/' =>
|
||||
|
|
|
@ -36,23 +36,26 @@ final class FileAllocateConduitAPIMethod
|
|||
$properties = array(
|
||||
'name' => $name,
|
||||
'authorPHID' => $viewer->getPHID(),
|
||||
'viewPolicy' => $view_policy,
|
||||
'isExplicitUpload' => true,
|
||||
);
|
||||
|
||||
if ($view_policy !== null) {
|
||||
$properties['viewPolicy'] = $view_policy;
|
||||
}
|
||||
|
||||
$ttl = $request->getValue('deleteAfterEpoch');
|
||||
if ($ttl) {
|
||||
$properties['ttl'] = $ttl;
|
||||
$properties['ttl.absolute'] = $ttl;
|
||||
}
|
||||
|
||||
$file = null;
|
||||
if ($hash) {
|
||||
if ($hash !== null) {
|
||||
$file = PhabricatorFile::newFileFromContentHash(
|
||||
$hash,
|
||||
$properties);
|
||||
}
|
||||
|
||||
if ($hash && !$file) {
|
||||
if ($hash !== null && !$file) {
|
||||
$chunked_hash = PhabricatorChunkedFileStorageEngine::getChunkedHash(
|
||||
$viewer,
|
||||
$hash);
|
||||
|
@ -103,7 +106,7 @@ final class FileAllocateConduitAPIMethod
|
|||
if ($chunk_engines) {
|
||||
$chunk_properties = $properties;
|
||||
|
||||
if ($hash) {
|
||||
if ($hash !== null) {
|
||||
$chunk_properties += array(
|
||||
'chunkedHash' => $chunked_hash,
|
||||
);
|
||||
|
|
|
@ -10,6 +10,16 @@ final class FileInfoConduitAPIMethod extends FileConduitAPIMethod {
|
|||
return pht('Get information about a file.');
|
||||
}
|
||||
|
||||
public function getMethodStatus() {
|
||||
return self::METHOD_STATUS_FROZEN;
|
||||
}
|
||||
|
||||
public function getMethodStatusDescription() {
|
||||
return pht(
|
||||
'This method is frozen and will eventually be deprecated. New code '.
|
||||
'should use "file.search" instead.');
|
||||
}
|
||||
|
||||
protected function defineParamTypes() {
|
||||
return array(
|
||||
'phid' => 'optional phid',
|
||||
|
|
|
@ -27,21 +27,27 @@ final class FileUploadConduitAPIMethod extends FileConduitAPIMethod {
|
|||
$viewer = $request->getUser();
|
||||
|
||||
$name = $request->getValue('name');
|
||||
$can_cdn = $request->getValue('canCDN');
|
||||
$can_cdn = (bool)$request->getValue('canCDN');
|
||||
$view_policy = $request->getValue('viewPolicy');
|
||||
|
||||
$data = $request->getValue('data_base64');
|
||||
$data = $this->decodeBase64($data);
|
||||
|
||||
$file = PhabricatorFile::newFromFileData(
|
||||
$data,
|
||||
array(
|
||||
'name' => $name,
|
||||
$params = array(
|
||||
'authorPHID' => $viewer->getPHID(),
|
||||
'viewPolicy' => $view_policy,
|
||||
'canCDN' => $can_cdn,
|
||||
'isExplicitUpload' => true,
|
||||
));
|
||||
);
|
||||
|
||||
if ($name !== null) {
|
||||
$params['name'] = $name;
|
||||
}
|
||||
|
||||
if ($view_policy !== null) {
|
||||
$params['viewPolicy'] = $view_policy;
|
||||
}
|
||||
|
||||
$file = PhabricatorFile::newFromFileData($data, $params);
|
||||
|
||||
return $file->getPHID();
|
||||
}
|
||||
|
|
|
@ -3,12 +3,21 @@
|
|||
final class FileUploadHashConduitAPIMethod extends FileConduitAPIMethod {
|
||||
|
||||
public function getAPIMethodName() {
|
||||
// TODO: Deprecate this in favor of `file.allocate`.
|
||||
return 'file.uploadhash';
|
||||
}
|
||||
|
||||
public function getMethodStatus() {
|
||||
return self::METHOD_STATUS_DEPRECATED;
|
||||
}
|
||||
|
||||
public function getMethodStatusDescription() {
|
||||
return pht(
|
||||
'This method is deprecated. Callers should use "file.allocate" '.
|
||||
'instead.');
|
||||
}
|
||||
|
||||
public function getMethodDescription() {
|
||||
return pht('Upload a file to the server using content hash.');
|
||||
return pht('Obsolete. Has no effect.');
|
||||
}
|
||||
|
||||
protected function defineParamTypes() {
|
||||
|
@ -19,25 +28,11 @@ final class FileUploadHashConduitAPIMethod extends FileConduitAPIMethod {
|
|||
}
|
||||
|
||||
protected function defineReturnType() {
|
||||
return 'phid or null';
|
||||
return 'null';
|
||||
}
|
||||
|
||||
protected function execute(ConduitAPIRequest $request) {
|
||||
$hash = $request->getValue('hash');
|
||||
$name = $request->getValue('name');
|
||||
$user = $request->getUser();
|
||||
|
||||
$file = PhabricatorFile::newFileFromContentHash(
|
||||
$hash,
|
||||
array(
|
||||
'name' => $name,
|
||||
'authorPHID' => $user->getPHID(),
|
||||
));
|
||||
|
||||
if ($file) {
|
||||
return $file->getPHID();
|
||||
}
|
||||
return $file;
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorFileSearchConduitAPIMethod
|
||||
extends PhabricatorSearchEngineAPIMethod {
|
||||
|
||||
public function getAPIMethodName() {
|
||||
return 'file.search';
|
||||
}
|
||||
|
||||
public function newSearchEngine() {
|
||||
return new PhabricatorFileSearchEngine();
|
||||
}
|
||||
|
||||
public function getMethodSummary() {
|
||||
return pht('Read information about files.');
|
||||
}
|
||||
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorFileCommentController extends PhabricatorFileController {
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $request->getViewer();
|
||||
$id = $request->getURIData('id');
|
||||
|
||||
if (!$request->isFormPost()) {
|
||||
return new Aphront400Response();
|
||||
}
|
||||
|
||||
$file = id(new PhabricatorFileQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($id))
|
||||
->executeOne();
|
||||
if (!$file) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$is_preview = $request->isPreviewRequest();
|
||||
$draft = PhabricatorDraft::buildFromRequest($request);
|
||||
|
||||
$view_uri = $file->getInfoURI();
|
||||
|
||||
$xactions = array();
|
||||
$xactions[] = id(new PhabricatorFileTransaction())
|
||||
->setTransactionType(PhabricatorTransactions::TYPE_COMMENT)
|
||||
->attachComment(
|
||||
id(new PhabricatorFileTransactionComment())
|
||||
->setContent($request->getStr('comment')));
|
||||
|
||||
$editor = id(new PhabricatorFileEditor())
|
||||
->setActor($viewer)
|
||||
->setContinueOnNoEffect($request->isContinueRequest())
|
||||
->setContentSourceFromRequest($request)
|
||||
->setIsPreview($is_preview);
|
||||
|
||||
try {
|
||||
$xactions = $editor->applyTransactions($file, $xactions);
|
||||
} catch (PhabricatorApplicationTransactionNoEffectException $ex) {
|
||||
return id(new PhabricatorApplicationTransactionNoEffectResponse())
|
||||
->setCancelURI($view_uri)
|
||||
->setException($ex);
|
||||
}
|
||||
|
||||
if ($draft) {
|
||||
$draft->replaceOrDelete();
|
||||
}
|
||||
|
||||
if ($request->isAjax() && $is_preview) {
|
||||
return id(new PhabricatorApplicationTransactionResponse())
|
||||
->setViewer($viewer)
|
||||
->setTransactions($xactions)
|
||||
->setIsPreview($is_preview);
|
||||
} else {
|
||||
return id(new AphrontRedirectResponse())
|
||||
->setURI($view_uri);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -64,14 +64,21 @@ final class PhabricatorFileDataController extends PhabricatorFileController {
|
|||
$range = $request->getHTTPHeader('range');
|
||||
if ($range) {
|
||||
$matches = null;
|
||||
if (preg_match('/^bytes=(\d+)-(\d+)$/', $range, $matches)) {
|
||||
if (preg_match('/^bytes=(\d+)-(\d*)$/', $range, $matches)) {
|
||||
// Note that the "Range" header specifies bytes differently than
|
||||
// we do internally: the range 0-1 has 2 bytes (byte 0 and byte 1).
|
||||
$begin = (int)$matches[1];
|
||||
$end = (int)$matches[2] + 1;
|
||||
|
||||
// The "Range" may be "200-299" or "200-", meaning "until end of file".
|
||||
if (strlen($matches[2])) {
|
||||
$range_end = (int)$matches[2];
|
||||
$end = $range_end + 1;
|
||||
} else {
|
||||
$range_end = null;
|
||||
}
|
||||
|
||||
$response->setHTTPResponseCode(206);
|
||||
$response->setRange($begin, ($end - 1));
|
||||
$response->setRange($begin, $range_end);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -84,18 +91,28 @@ final class PhabricatorFileDataController extends PhabricatorFileController {
|
|||
if ($is_viewable && !$force_download) {
|
||||
$response->setMimeType($file->getViewableMimeType());
|
||||
} else {
|
||||
if (!$request->isHTTPPost() && !$is_alternate_domain && !$is_lfs) {
|
||||
// NOTE: Require POST to download files from the primary domain. We'd
|
||||
// rather go full-bore and do a real CSRF check, but can't currently
|
||||
// authenticate users on the file domain. This should blunt any
|
||||
// attacks based on iframes, script tags, applet tags, etc., at least.
|
||||
// Send the user to the "info" page if they're using some other method.
|
||||
$is_public = !$viewer->isLoggedIn();
|
||||
$is_post = $request->isHTTPPost();
|
||||
|
||||
// NOTE: Require POST to download files from the primary domain if the
|
||||
// request includes credentials. The "Download File" links we generate
|
||||
// in the web UI are forms which use POST to satisfy this requirement.
|
||||
|
||||
// The intent is to make attacks based on tags like "<iframe />" and
|
||||
// "<script />" (which can issue GET requests, but can not easily issue
|
||||
// POST requests) more difficult to execute.
|
||||
|
||||
// The best defense against these attacks is to use an alternate file
|
||||
// domain, which is why we strongly recommend doing so.
|
||||
|
||||
$is_safe = ($is_alternate_domain || $is_lfs || $is_post || $is_public);
|
||||
if (!$is_safe) {
|
||||
// This is marked as "external" because it is fully qualified.
|
||||
return id(new AphrontRedirectResponse())
|
||||
->setIsExternal(true)
|
||||
->setURI(PhabricatorEnv::getProductionURI($file->getBestURI()));
|
||||
}
|
||||
|
||||
$response->setMimeType($file->getMimeType());
|
||||
$response->setDownload($file->getName());
|
||||
}
|
||||
|
|
|
@ -1,114 +1,12 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorFileEditController extends PhabricatorFileController {
|
||||
final class PhabricatorFileEditController
|
||||
extends PhabricatorFileController {
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $request->getViewer();
|
||||
$id = $request->getURIData('id');
|
||||
|
||||
$file = id(new PhabricatorFileQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($id))
|
||||
->requireCapabilities(
|
||||
array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
PhabricatorPolicyCapability::CAN_EDIT,
|
||||
))
|
||||
->executeOne();
|
||||
if (!$file) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$title = pht('Edit File: %s', $file->getName());
|
||||
$file_name = $file->getName();
|
||||
$header_icon = 'fa-pencil';
|
||||
$view_uri = '/'.$file->getMonogram();
|
||||
$error_name = true;
|
||||
$validation_exception = null;
|
||||
|
||||
if ($request->isFormPost()) {
|
||||
$can_view = $request->getStr('canView');
|
||||
$file_name = $request->getStr('name');
|
||||
$errors = array();
|
||||
|
||||
$type_name = PhabricatorFileTransaction::TYPE_NAME;
|
||||
|
||||
$xactions = array();
|
||||
|
||||
$xactions[] = id(new PhabricatorFileTransaction())
|
||||
->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY)
|
||||
->setNewValue($can_view);
|
||||
|
||||
$xactions[] = id(new PhabricatorFileTransaction())
|
||||
->setTransactionType(PhabricatorFileTransaction::TYPE_NAME)
|
||||
->setNewValue($file_name);
|
||||
|
||||
$editor = id(new PhabricatorFileEditor())
|
||||
->setActor($viewer)
|
||||
->setContentSourceFromRequest($request)
|
||||
->setContinueOnNoEffect(true);
|
||||
|
||||
try {
|
||||
$editor->applyTransactions($file, $xactions);
|
||||
return id(new AphrontRedirectResponse())->setURI($view_uri);
|
||||
} catch (PhabricatorApplicationTransactionValidationException $ex) {
|
||||
$validation_exception = $ex;
|
||||
$error_name = $ex->getShortMessage($type_name);
|
||||
|
||||
$file->setViewPolicy($can_view);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$policies = id(new PhabricatorPolicyQuery())
|
||||
->setViewer($viewer)
|
||||
->setObject($file)
|
||||
->execute();
|
||||
|
||||
$form = id(new AphrontFormView())
|
||||
->setUser($viewer)
|
||||
->appendChild(
|
||||
id(new AphrontFormTextControl())
|
||||
->setName('name')
|
||||
->setValue($file_name)
|
||||
->setLabel(pht('Name'))
|
||||
->setError($error_name))
|
||||
->appendChild(
|
||||
id(new AphrontFormPolicyControl())
|
||||
->setUser($viewer)
|
||||
->setCapability(PhabricatorPolicyCapability::CAN_VIEW)
|
||||
->setPolicyObject($file)
|
||||
->setPolicies($policies)
|
||||
->setName('canView'))
|
||||
->appendChild(
|
||||
id(new AphrontFormSubmitControl())
|
||||
->addCancelButton($view_uri)
|
||||
->setValue(pht('Save Changes')));
|
||||
|
||||
$crumbs = $this->buildApplicationCrumbs()
|
||||
->addTextCrumb($file->getMonogram(), $view_uri)
|
||||
->addTextCrumb(pht('Edit'))
|
||||
->setBorder(true);
|
||||
|
||||
$box = id(new PHUIObjectBoxView())
|
||||
->setHeaderText($title)
|
||||
->setValidationException($validation_exception)
|
||||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||
->appendChild($form);
|
||||
|
||||
$header = id(new PHUIHeaderView())
|
||||
->setHeader($title)
|
||||
->setHeaderIcon($header_icon);
|
||||
|
||||
$view = id(new PHUITwoColumnView())
|
||||
->setHeader($header)
|
||||
->setFooter($box);
|
||||
|
||||
return $this->newPage()
|
||||
->setTitle($title)
|
||||
->setCrumbs($crumbs)
|
||||
->appendChild($view);
|
||||
|
||||
return id(new PhabricatorFileEditEngine())
|
||||
->setController($this)
|
||||
->buildResponse();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -94,25 +94,18 @@ final class PhabricatorFileInfoController extends PhabricatorFileController {
|
|||
$file,
|
||||
new PhabricatorFileTransactionQuery());
|
||||
|
||||
$is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
|
||||
$comment_view = id(new PhabricatorFileEditEngine())
|
||||
->setViewer($viewer)
|
||||
->buildEditEngineCommentView($file);
|
||||
|
||||
$add_comment_header = $is_serious
|
||||
? pht('Add Comment')
|
||||
: pht('Question File Integrity');
|
||||
$monogram = $file->getMonogram();
|
||||
|
||||
$draft = PhabricatorDraft::newFromUserAndKey($viewer, $file->getPHID());
|
||||
|
||||
$add_comment_form = id(new PhabricatorApplicationTransactionCommentView())
|
||||
->setUser($viewer)
|
||||
->setObjectPHID($file->getPHID())
|
||||
->setDraft($draft)
|
||||
->setHeaderText($add_comment_header)
|
||||
->setAction($this->getApplicationURI('/comment/'.$file->getID().'/'))
|
||||
->setSubmitButtonName(pht('Add Comment'));
|
||||
$timeline->setQuoteRef($monogram);
|
||||
$comment_view->setTransactionTimeline($timeline);
|
||||
|
||||
return array(
|
||||
$timeline,
|
||||
$add_comment_form,
|
||||
$comment_view,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -219,6 +212,18 @@ final class PhabricatorFileInfoController extends PhabricatorFileController {
|
|||
pht('Mime Type'),
|
||||
$file->getMimeType());
|
||||
|
||||
$ttl = $file->getTtl();
|
||||
if ($ttl) {
|
||||
$delta = $ttl - PhabricatorTime::getNow();
|
||||
|
||||
$finfo->addProperty(
|
||||
pht('Expires'),
|
||||
pht(
|
||||
'%s (%s)',
|
||||
phabricator_datetime($ttl, $viewer),
|
||||
phutil_format_relative_time_detailed($delta)));
|
||||
}
|
||||
|
||||
$width = $file->getImageWidth();
|
||||
if ($width) {
|
||||
$finfo->addProperty(
|
||||
|
|
79
src/applications/files/editor/PhabricatorFileEditEngine.php
Normal file
79
src/applications/files/editor/PhabricatorFileEditEngine.php
Normal file
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorFileEditEngine
|
||||
extends PhabricatorEditEngine {
|
||||
|
||||
const ENGINECONST = 'files.file';
|
||||
|
||||
public function getEngineName() {
|
||||
return pht('Files');
|
||||
}
|
||||
|
||||
protected function supportsEditEngineConfiguration() {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function getCreateNewObjectPolicy() {
|
||||
// TODO: For now, this EditEngine can only edit objects, since there is
|
||||
// a lot of complexity in dealing with file data during file creation.
|
||||
return PhabricatorPolicies::POLICY_NOONE;
|
||||
}
|
||||
|
||||
public function getSummaryHeader() {
|
||||
return pht('Configure Files Forms');
|
||||
}
|
||||
|
||||
public function getSummaryText() {
|
||||
return pht('Configure creation and editing forms in Files.');
|
||||
}
|
||||
|
||||
public function getEngineApplicationClass() {
|
||||
return 'PhabricatorFilesApplication';
|
||||
}
|
||||
|
||||
protected function newEditableObject() {
|
||||
return PhabricatorFile::initializeNewFile();
|
||||
}
|
||||
|
||||
protected function newObjectQuery() {
|
||||
return new PhabricatorFileQuery();
|
||||
}
|
||||
|
||||
protected function getObjectCreateTitleText($object) {
|
||||
return pht('Create New File');
|
||||
}
|
||||
|
||||
protected function getObjectEditTitleText($object) {
|
||||
return pht('Edit File: %s', $object->getName());
|
||||
}
|
||||
|
||||
protected function getObjectEditShortText($object) {
|
||||
return $object->getMonogram();
|
||||
}
|
||||
|
||||
protected function getObjectCreateShortText() {
|
||||
return pht('Create File');
|
||||
}
|
||||
|
||||
protected function getObjectName() {
|
||||
return pht('File');
|
||||
}
|
||||
|
||||
protected function getObjectViewURI($object) {
|
||||
return $object->getURI();
|
||||
}
|
||||
|
||||
protected function buildCustomEditFields($object) {
|
||||
return array(
|
||||
id(new PhabricatorTextEditField())
|
||||
->setKey('name')
|
||||
->setLabel(pht('Name'))
|
||||
->setTransactionType(PhabricatorFileNameTransaction::TRANSACTIONTYPE)
|
||||
->setDescription(pht('The name of the file.'))
|
||||
->setConduitDescription(pht('Rename the file.'))
|
||||
->setConduitTypeDescription(pht('New file name.'))
|
||||
->setValue($object->getName()),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -17,46 +17,9 @@ final class PhabricatorFileEditor
|
|||
$types[] = PhabricatorTransactions::TYPE_COMMENT;
|
||||
$types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
|
||||
|
||||
$types[] = PhabricatorFileTransaction::TYPE_NAME;
|
||||
|
||||
return $types;
|
||||
}
|
||||
|
||||
protected function getCustomTransactionOldValue(
|
||||
PhabricatorLiskDAO $object,
|
||||
PhabricatorApplicationTransaction $xaction) {
|
||||
|
||||
switch ($xaction->getTransactionType()) {
|
||||
case PhabricatorFileTransaction::TYPE_NAME:
|
||||
return $object->getName();
|
||||
}
|
||||
}
|
||||
|
||||
protected function getCustomTransactionNewValue(
|
||||
PhabricatorLiskDAO $object,
|
||||
PhabricatorApplicationTransaction $xaction) {
|
||||
|
||||
switch ($xaction->getTransactionType()) {
|
||||
case PhabricatorFileTransaction::TYPE_NAME:
|
||||
return $xaction->getNewValue();
|
||||
}
|
||||
}
|
||||
|
||||
protected function applyCustomInternalTransaction(
|
||||
PhabricatorLiskDAO $object,
|
||||
PhabricatorApplicationTransaction $xaction) {
|
||||
|
||||
switch ($xaction->getTransactionType()) {
|
||||
case PhabricatorFileTransaction::TYPE_NAME:
|
||||
$object->setName($xaction->getNewValue());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected function applyCustomExternalTransaction(
|
||||
PhabricatorLiskDAO $object,
|
||||
PhabricatorApplicationTransaction $xaction) {}
|
||||
|
||||
protected function shouldSendMail(
|
||||
PhabricatorLiskDAO $object,
|
||||
array $xactions) {
|
||||
|
@ -111,34 +74,4 @@ final class PhabricatorFileEditor
|
|||
return false;
|
||||
}
|
||||
|
||||
protected function validateTransaction(
|
||||
PhabricatorLiskDAO $object,
|
||||
$type,
|
||||
array $xactions) {
|
||||
|
||||
$errors = parent::validateTransaction($object, $type, $xactions);
|
||||
|
||||
switch ($type) {
|
||||
case PhabricatorFileTransaction::TYPE_NAME:
|
||||
$missing = $this->validateIsEmptyTextField(
|
||||
$object->getName(),
|
||||
$xactions);
|
||||
|
||||
if ($missing) {
|
||||
$error = new PhabricatorApplicationTransactionValidationError(
|
||||
$type,
|
||||
pht('Required'),
|
||||
pht('File name is required.'),
|
||||
nonempty(last($xactions), null));
|
||||
|
||||
$error->setIsMissingFieldError(true);
|
||||
$errors[] = $error;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -102,7 +102,7 @@ final class PhabricatorChunkedFileStorageEngine
|
|||
}
|
||||
|
||||
public static function getChunkedHashForInput($input) {
|
||||
$rehash = PhabricatorHash::digest($input);
|
||||
$rehash = PhabricatorHash::weakDigest($input);
|
||||
|
||||
// Add a suffix to identify this as a chunk hash.
|
||||
$rehash = substr($rehash, 0, -2).'-C';
|
||||
|
@ -174,7 +174,16 @@ final class PhabricatorChunkedFileStorageEngine
|
|||
return (4 * 1024 * 1024);
|
||||
}
|
||||
|
||||
public function getRawFileDataIterator(PhabricatorFile $file, $begin, $end) {
|
||||
public function getRawFileDataIterator(
|
||||
PhabricatorFile $file,
|
||||
$begin,
|
||||
$end,
|
||||
PhabricatorFileStorageFormat $format) {
|
||||
|
||||
// NOTE: It is currently impossible for files stored with the chunk
|
||||
// engine to have their own formatting (instead, the individual chunks
|
||||
// are formatted), so we ignore the format object.
|
||||
|
||||
$chunks = id(new PhabricatorFileChunkQuery())
|
||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||
->withChunkHandles(array($file->getStorageHandle()))
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
*/
|
||||
abstract class PhabricatorFileStorageEngine extends Phobject {
|
||||
|
||||
const HMAC_INTEGRITY = 'file.integrity';
|
||||
|
||||
/**
|
||||
* Construct a new storage engine.
|
||||
*
|
||||
|
@ -325,10 +327,32 @@ abstract class PhabricatorFileStorageEngine extends Phobject {
|
|||
return $engine->getChunkSize();
|
||||
}
|
||||
|
||||
public function getRawFileDataIterator(PhabricatorFile $file, $begin, $end) {
|
||||
// The default implementation is trivial and just loads the entire file
|
||||
// upfront.
|
||||
$data = $this->readFile($file->getStorageHandle());
|
||||
public function getRawFileDataIterator(
|
||||
PhabricatorFile $file,
|
||||
$begin,
|
||||
$end,
|
||||
PhabricatorFileStorageFormat $format) {
|
||||
|
||||
$formatted_data = $this->readFile($file->getStorageHandle());
|
||||
|
||||
$known_integrity = $file->getIntegrityHash();
|
||||
if ($known_integrity !== null) {
|
||||
$new_integrity = $this->newIntegrityHash($formatted_data, $format);
|
||||
if (!phutil_hashes_are_identical($known_integrity, $new_integrity)) {
|
||||
throw new PhabricatorFileIntegrityException(
|
||||
pht(
|
||||
'File data integrity check failed. Dark forces have corrupted '.
|
||||
'or tampered with this file. The file data can not be read.'));
|
||||
}
|
||||
}
|
||||
|
||||
$formatted_data = array($formatted_data);
|
||||
|
||||
$data = '';
|
||||
$format_iterator = $format->newReadIterator($formatted_data);
|
||||
foreach ($format_iterator as $raw_chunk) {
|
||||
$data .= $raw_chunk;
|
||||
}
|
||||
|
||||
if ($begin !== null && $end !== null) {
|
||||
$data = substr($data, $begin, ($end - $begin));
|
||||
|
@ -341,4 +365,18 @@ abstract class PhabricatorFileStorageEngine extends Phobject {
|
|||
return array($data);
|
||||
}
|
||||
|
||||
public function newIntegrityHash(
|
||||
$data,
|
||||
PhabricatorFileStorageFormat $format) {
|
||||
|
||||
$hmac_name = self::HMAC_INTEGRITY;
|
||||
|
||||
$data_hash = PhabricatorHash::digestWithNamedKey($data, $hmac_name);
|
||||
$format_hash = $format->newFormatIntegrityHash();
|
||||
|
||||
$full_hash = "{$data_hash}/{$format_hash}";
|
||||
|
||||
return PhabricatorHash::digestWithNamedKey($full_hash, $hmac_name);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -47,4 +47,8 @@ final class PhabricatorTestStorageEngine
|
|||
unset(self::$storage[$handle]);
|
||||
}
|
||||
|
||||
public function tamperWithFile($handle, $data) {
|
||||
self::$storage[$handle] = $data;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorFileIntegrityException
|
||||
extends Exception {}
|
|
@ -56,6 +56,22 @@ final class PhabricatorFileAES256StorageFormat
|
|||
return array($data);
|
||||
}
|
||||
|
||||
public function newFormatIntegrityHash() {
|
||||
$file = $this->getFile();
|
||||
list($key_envelope, $iv_envelope) = $this->extractKeyAndIV($file);
|
||||
|
||||
// NOTE: We include the IV in the format integrity hash. If we do not,
|
||||
// attackers can potentially forge the first block of decrypted data
|
||||
// in CBC mode if they are able to substitute a chosen IV and predict
|
||||
// the plaintext. (Normally, they can not tamper with the IV.)
|
||||
|
||||
$input = self::FORMATKEY.'/iv:'.$iv_envelope->openEnvelope();
|
||||
|
||||
return PhabricatorHash::digestWithNamedKey(
|
||||
$input,
|
||||
PhabricatorFileStorageEngine::HMAC_INTEGRITY);
|
||||
}
|
||||
|
||||
public function newStorageProperties() {
|
||||
// Generate a unique key and IV for this block of data.
|
||||
$key_envelope = self::newAES256Key();
|
||||
|
|
|
@ -22,6 +22,10 @@ abstract class PhabricatorFileStorageFormat
|
|||
abstract public function newReadIterator($raw_iterator);
|
||||
abstract public function newWriteIterator($raw_iterator);
|
||||
|
||||
public function newFormatIntegrityHash() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public function newStorageProperties() {
|
||||
return array();
|
||||
}
|
||||
|
|
|
@ -33,6 +33,15 @@ final class PhabricatorFileStorageFormatTestCase extends PhabricatorTestCase {
|
|||
// The actual raw data in the storage engine should be encoded.
|
||||
$raw_data = $engine->readFile($file->getStorageHandle());
|
||||
$this->assertEqual($expect, $raw_data);
|
||||
|
||||
// If we generate an iterator over a slice of the file, it should return
|
||||
// the decrypted file.
|
||||
$iterator = $file->getFileDataIterator(4, 14);
|
||||
$raw_data = '';
|
||||
foreach ($iterator as $data_chunk) {
|
||||
$raw_data .= $data_chunk;
|
||||
}
|
||||
$this->assertEqual('cow jumped', $raw_data);
|
||||
}
|
||||
|
||||
public function testAES256Storage() {
|
||||
|
@ -73,5 +82,55 @@ final class PhabricatorFileStorageFormatTestCase extends PhabricatorTestCase {
|
|||
// input data.
|
||||
$raw_data = $engine->readFile($file->getStorageHandle());
|
||||
$this->assertTrue($data !== $raw_data);
|
||||
|
||||
// If we generate an iterator over a slice of the file, it should return
|
||||
// the decrypted file.
|
||||
$iterator = $file->getFileDataIterator(4, 14);
|
||||
$raw_data = '';
|
||||
foreach ($iterator as $data_chunk) {
|
||||
$raw_data .= $data_chunk;
|
||||
}
|
||||
$this->assertEqual('cow jumped', $raw_data);
|
||||
|
||||
$iterator = $file->getFileDataIterator(4, null);
|
||||
$raw_data = '';
|
||||
foreach ($iterator as $data_chunk) {
|
||||
$raw_data .= $data_chunk;
|
||||
}
|
||||
$this->assertEqual('cow jumped over the full moon.', $raw_data);
|
||||
|
||||
}
|
||||
|
||||
public function testStorageTampering() {
|
||||
$engine = new PhabricatorTestStorageEngine();
|
||||
|
||||
$good = 'The cow jumped over the full moon.';
|
||||
$evil = 'The cow slept quietly, honoring the glorious dictator.';
|
||||
|
||||
$params = array(
|
||||
'name' => 'message.txt',
|
||||
'storageEngines' => array(
|
||||
$engine,
|
||||
),
|
||||
);
|
||||
|
||||
// First, write the file normally.
|
||||
$file = PhabricatorFile::newFromFileData($good, $params);
|
||||
$this->assertEqual($good, $file->loadFileData());
|
||||
|
||||
// As an adversary, tamper with the file.
|
||||
$engine->tamperWithFile($file->getStorageHandle(), $evil);
|
||||
|
||||
// Attempts to read the file data should now fail the integrity check.
|
||||
$caught = null;
|
||||
try {
|
||||
$file->loadFileData();
|
||||
} catch (PhabricatorFileIntegrityException $ex) {
|
||||
$caught = $ex;
|
||||
}
|
||||
|
||||
$this->assertTrue($caught instanceof PhabricatorFileIntegrityException);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -19,6 +19,13 @@ final class PhabricatorFilesManagementCatWorkflow
|
|||
'param' => 'bytes',
|
||||
'help' => pht('End printing at a specific offset.'),
|
||||
),
|
||||
array(
|
||||
'name' => 'salvage',
|
||||
'help' => pht(
|
||||
'DANGEROUS. Attempt to salvage file content even if the '.
|
||||
'integrity check fails. If an adversary has tampered with '.
|
||||
'the file, the conent may be unsafe.'),
|
||||
),
|
||||
array(
|
||||
'name' => 'names',
|
||||
'wildcard' => true,
|
||||
|
@ -43,10 +50,29 @@ final class PhabricatorFilesManagementCatWorkflow
|
|||
$begin = $args->getArg('begin');
|
||||
$end = $args->getArg('end');
|
||||
|
||||
$file->makeEphemeral();
|
||||
|
||||
// If we're running in "salvage" mode, wipe out any integrity hash which
|
||||
// may be present. This makes us read file data without performing an
|
||||
// integrity check.
|
||||
$salvage = $args->getArg('salvage');
|
||||
if ($salvage) {
|
||||
$file->setIntegrityHash(null);
|
||||
}
|
||||
|
||||
try {
|
||||
$iterator = $file->getFileDataIterator($begin, $end);
|
||||
foreach ($iterator as $data) {
|
||||
echo $data;
|
||||
}
|
||||
} catch (PhabricatorFileIntegrityException $ex) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
'File data integrity check failed. Use "--salvage" to bypass '.
|
||||
'integrity checks. This flag is dangerous, use it at your own '.
|
||||
'risk. Underlying error: %s',
|
||||
$ex->getMessage()));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,325 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorFilesManagementIntegrityWorkflow
|
||||
extends PhabricatorFilesManagementWorkflow {
|
||||
|
||||
protected function didConstruct() {
|
||||
$this
|
||||
->setName('integrity')
|
||||
->setSynopsis(pht('Verify or recalculate file integrity hashes.'))
|
||||
->setArguments(
|
||||
array(
|
||||
array(
|
||||
'name' => 'all',
|
||||
'help' => pht('Affect all files.'),
|
||||
),
|
||||
array(
|
||||
'name' => 'strip',
|
||||
'help' => pht(
|
||||
'DANGEROUS. Strip integrity hashes from files. This makes '.
|
||||
'files vulnerable to corruption or tampering.'),
|
||||
),
|
||||
array(
|
||||
'name' => 'corrupt',
|
||||
'help' => pht(
|
||||
'Corrupt integrity hashes for given files. This is intended '.
|
||||
'for debugging.'),
|
||||
),
|
||||
array(
|
||||
'name' => 'compute',
|
||||
'help' => pht(
|
||||
'Compute and update integrity hashes for files which do not '.
|
||||
'yet have them.'),
|
||||
),
|
||||
array(
|
||||
'name' => 'overwrite',
|
||||
'help' => pht(
|
||||
'DANGEROUS. Recompute and update integrity hashes, overwriting '.
|
||||
'invalid hashes. This may mark corrupt or dangerous files as '.
|
||||
'valid.'),
|
||||
),
|
||||
array(
|
||||
'name' => 'force',
|
||||
'short' => 'f',
|
||||
'help' => pht(
|
||||
'Execute dangerous operations without prompting for '.
|
||||
'confirmation.'),
|
||||
),
|
||||
array(
|
||||
'name' => 'names',
|
||||
'wildcard' => true,
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
public function execute(PhutilArgumentParser $args) {
|
||||
$modes = array();
|
||||
|
||||
$is_strip = $args->getArg('strip');
|
||||
if ($is_strip) {
|
||||
$modes[] = 'strip';
|
||||
}
|
||||
|
||||
$is_corrupt = $args->getArg('corrupt');
|
||||
if ($is_corrupt) {
|
||||
$modes[] = 'corrupt';
|
||||
}
|
||||
|
||||
$is_compute = $args->getArg('compute');
|
||||
if ($is_compute) {
|
||||
$modes[] = 'compute';
|
||||
}
|
||||
|
||||
$is_overwrite = $args->getArg('overwrite');
|
||||
if ($is_overwrite) {
|
||||
$modes[] = 'overwrite';
|
||||
}
|
||||
|
||||
$is_verify = !$modes;
|
||||
if ($is_verify) {
|
||||
$modes[] = 'verify';
|
||||
}
|
||||
|
||||
if (count($modes) > 1) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
'You have selected multiple operation modes (%s). Choose a '.
|
||||
'single mode to operate in.',
|
||||
implode(', ', $modes)));
|
||||
}
|
||||
|
||||
$is_force = $args->getArg('force');
|
||||
if (!$is_force) {
|
||||
$prompt = null;
|
||||
if ($is_strip) {
|
||||
$prompt = pht(
|
||||
'Stripping integrity hashes is dangerous and makes files '.
|
||||
'vulnerable to corruption or tampering.');
|
||||
}
|
||||
|
||||
if ($is_corrupt) {
|
||||
$prompt = pht(
|
||||
'Corrupting integrity hashes will prevent files from being '.
|
||||
'accessed. This mode is intended only for development and '.
|
||||
'debugging.');
|
||||
}
|
||||
|
||||
if ($is_overwrite) {
|
||||
$prompt = pht(
|
||||
'Overwriting integrity hashes is dangerous and may mark files '.
|
||||
'which have been corrupted or tampered with as safe.');
|
||||
}
|
||||
|
||||
if ($prompt) {
|
||||
$this->logWarn(pht('DANGEROUS'), $prompt);
|
||||
|
||||
if (!phutil_console_confirm(pht('Continue anyway?'))) {
|
||||
throw new PhutilArgumentUsageException(pht('Aborted workflow.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$iterator = $this->buildIterator($args);
|
||||
if (!$iterator) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
'Either specify a list of files to affect, or use "--all" to '.
|
||||
'affect all files.'));
|
||||
}
|
||||
|
||||
$failure_count = 0;
|
||||
$total_count = 0;
|
||||
|
||||
foreach ($iterator as $file) {
|
||||
$total_count++;
|
||||
$display_name = $file->getMonogram();
|
||||
|
||||
$old_hash = $file->getIntegrityHash();
|
||||
|
||||
if ($is_strip) {
|
||||
if ($old_hash === null) {
|
||||
$this->logInfo(
|
||||
pht('SKIPPED'),
|
||||
pht(
|
||||
'File "%s" does not have an integrity hash to strip.',
|
||||
$display_name));
|
||||
} else {
|
||||
$file
|
||||
->setIntegrityHash(null)
|
||||
->save();
|
||||
|
||||
$this->logWarn(
|
||||
pht('STRIPPED'),
|
||||
pht(
|
||||
'Stripped integrity hash for "%s".',
|
||||
$display_name));
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$need_hash = ($is_verify && $old_hash) ||
|
||||
($is_compute && ($old_hash === null)) ||
|
||||
($is_corrupt) ||
|
||||
($is_overwrite);
|
||||
if ($need_hash) {
|
||||
try {
|
||||
$new_hash = $file->newIntegrityHash();
|
||||
} catch (Exception $ex) {
|
||||
$failure_count++;
|
||||
|
||||
$this->logFail(
|
||||
pht('ERROR'),
|
||||
pht(
|
||||
'Unable to compute integrity hash for file "%s": %s',
|
||||
$display_name,
|
||||
$ex->getMessage()));
|
||||
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
$new_hash = null;
|
||||
}
|
||||
|
||||
// NOTE: When running in "corrupt" mode, we only corrupt the hash if
|
||||
// we're able to compute a valid hash. Some files, like chunked files,
|
||||
// do not support integrity hashing so corrupting them would create an
|
||||
// unusual state.
|
||||
|
||||
if ($is_corrupt) {
|
||||
if ($new_hash === null) {
|
||||
$this->logInfo(
|
||||
pht('IGNORED'),
|
||||
pht(
|
||||
'Storage for file "%s" does not support integrity hashing.',
|
||||
$display_name));
|
||||
} else {
|
||||
$file
|
||||
->setIntegrityHash('<corrupted>')
|
||||
->save();
|
||||
|
||||
$this->logWarn(
|
||||
pht('CORRUPTED'),
|
||||
pht(
|
||||
'Corrupted integrity hash for file "%s".',
|
||||
$display_name));
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($is_verify) {
|
||||
if ($old_hash === null) {
|
||||
$this->logInfo(
|
||||
pht('NONE'),
|
||||
pht(
|
||||
'File "%s" has no stored integrity hash.',
|
||||
$display_name));
|
||||
} else if ($new_hash === null) {
|
||||
$failure_count++;
|
||||
|
||||
$this->logWarn(
|
||||
pht('UNEXPECTED'),
|
||||
pht(
|
||||
'Storage for file "%s" does not support integrity hashing, '.
|
||||
'but the file has an integrity hash.',
|
||||
$display_name));
|
||||
} else if (phutil_hashes_are_identical($old_hash, $new_hash)) {
|
||||
$this->logOkay(
|
||||
pht('VALID'),
|
||||
pht(
|
||||
'File "%s" has a valid integrity hash.',
|
||||
$display_name));
|
||||
} else {
|
||||
$failure_count++;
|
||||
|
||||
$this->logFail(
|
||||
pht('MISMATCH'),
|
||||
pht(
|
||||
'File "%s" has an invalid integrity hash!',
|
||||
$display_name));
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($is_compute) {
|
||||
if ($old_hash !== null) {
|
||||
$this->logInfo(
|
||||
pht('SKIP'),
|
||||
pht(
|
||||
'File "%s" already has an integrity hash.',
|
||||
$display_name));
|
||||
} else if ($new_hash === null) {
|
||||
$this->logInfo(
|
||||
pht('IGNORED'),
|
||||
pht(
|
||||
'Storage for file "%s" does not support integrity hashing.',
|
||||
$display_name));
|
||||
} else {
|
||||
$file
|
||||
->setIntegrityHash($new_hash)
|
||||
->save();
|
||||
|
||||
$this->logOkay(
|
||||
pht('COMPUTE'),
|
||||
pht(
|
||||
'Computed and stored integrity hash for file "%s".',
|
||||
$display_name));
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($is_overwrite) {
|
||||
$same_hash = ($old_hash !== null) &&
|
||||
($new_hash !== null) &&
|
||||
phutil_hashes_are_identical($old_hash, $new_hash);
|
||||
|
||||
if ($new_hash === null) {
|
||||
$this->logInfo(
|
||||
pht('IGNORED'),
|
||||
pht(
|
||||
'Storage for file "%s" does not support integrity hashing.',
|
||||
$display_name));
|
||||
} else if ($same_hash) {
|
||||
$this->logInfo(
|
||||
pht('UNCHANGED'),
|
||||
pht(
|
||||
'File "%s" already has the correct integrity hash.',
|
||||
$display_name));
|
||||
} else {
|
||||
$file
|
||||
->setIntegrityHash($new_hash)
|
||||
->save();
|
||||
|
||||
$this->logOkay(
|
||||
pht('OVERWRITE'),
|
||||
pht(
|
||||
'Overwrote integrity hash for file "%s".',
|
||||
$display_name));
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if ($failure_count) {
|
||||
$this->logFail(
|
||||
pht('FAIL'),
|
||||
pht(
|
||||
'Processed %s file(s), encountered %s error(s).',
|
||||
new PhutilNumber($total_count),
|
||||
new PhutilNumber($failure_count)));
|
||||
} else {
|
||||
$this->logOkay(
|
||||
pht('DONE'),
|
||||
pht(
|
||||
'Processed %s file(s) with no errors.',
|
||||
new PhutilNumber($total_count)));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
|
@ -9,10 +9,13 @@
|
|||
*
|
||||
* | name | Human readable filename.
|
||||
* | authorPHID | User PHID of uploader.
|
||||
* | ttl | Temporary file lifetime, in seconds.
|
||||
* | ttl.absolute | Temporary file lifetime as an epoch timestamp.
|
||||
* | ttl.relative | Temporary file lifetime, relative to now, in seconds.
|
||||
* | viewPolicy | File visibility policy.
|
||||
* | isExplicitUpload | Used to show users files they explicitly uploaded.
|
||||
* | canCDN | Allows the file to be cached and delivered over a CDN.
|
||||
* | profile | Marks the file as a profile image.
|
||||
* | format | Internal encoding format.
|
||||
* | mime-type | Optional, explicit file MIME type.
|
||||
* | builtin | Optional filename, identifies this as a builtin.
|
||||
*
|
||||
|
@ -24,7 +27,8 @@ final class PhabricatorFile extends PhabricatorFileDAO
|
|||
PhabricatorSubscribableInterface,
|
||||
PhabricatorFlaggableInterface,
|
||||
PhabricatorPolicyInterface,
|
||||
PhabricatorDestructibleInterface {
|
||||
PhabricatorDestructibleInterface,
|
||||
PhabricatorConduitResultInterface {
|
||||
|
||||
const METADATA_IMAGE_WIDTH = 'width';
|
||||
const METADATA_IMAGE_HEIGHT = 'height';
|
||||
|
@ -33,6 +37,7 @@ final class PhabricatorFile extends PhabricatorFileDAO
|
|||
const METADATA_PARTIAL = 'partial';
|
||||
const METADATA_PROFILE = 'profile';
|
||||
const METADATA_STORAGE = 'storage';
|
||||
const METADATA_INTEGRITY = 'integrity';
|
||||
|
||||
protected $name;
|
||||
protected $mimeType;
|
||||
|
@ -90,7 +95,7 @@ final class PhabricatorFile extends PhabricatorFileDAO
|
|||
'storageHandle' => 'text255',
|
||||
'authorPHID' => 'phid?',
|
||||
'secretKey' => 'bytes20?',
|
||||
'contentHash' => 'bytes40?',
|
||||
'contentHash' => 'bytes64?',
|
||||
'ttl' => 'epoch?',
|
||||
'isExplicitUpload' => 'bool?',
|
||||
'mailKey' => 'bytes20',
|
||||
|
@ -193,51 +198,19 @@ final class PhabricatorFile extends PhabricatorFileDAO
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Given a block of data, try to load an existing file with the same content
|
||||
* if one exists. If it does not, build a new file.
|
||||
*
|
||||
* This method is generally used when we have some piece of semi-trusted data
|
||||
* like a diff or a file from a repository that we want to show to the user.
|
||||
* We can't just dump it out because it may be dangerous for any number of
|
||||
* reasons; instead, we need to serve it through the File abstraction so it
|
||||
* ends up on the CDN domain if one is configured and so on. However, if we
|
||||
* simply wrote a new file every time we'd potentially end up with a lot
|
||||
* of redundant data in file storage.
|
||||
*
|
||||
* To solve these problems, we use file storage as a cache and reuse the
|
||||
* same file again if we've previously written it.
|
||||
*
|
||||
* NOTE: This method unguards writes.
|
||||
*
|
||||
* @param string Raw file data.
|
||||
* @param dict Dictionary of file information.
|
||||
*/
|
||||
public static function buildFromFileDataOrHash(
|
||||
$data,
|
||||
array $params = array()) {
|
||||
|
||||
$file = id(new PhabricatorFile())->loadOneWhere(
|
||||
'name = %s AND contentHash = %s LIMIT 1',
|
||||
idx($params, 'name'),
|
||||
self::hashFileContent($data));
|
||||
|
||||
if (!$file) {
|
||||
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
||||
$file = self::newFromFileData($data, $params);
|
||||
unset($unguarded);
|
||||
}
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
public static function newFileFromContentHash($hash, array $params) {
|
||||
// Check to see if a file with same contentHash exist
|
||||
if ($hash === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check to see if a file with same hash already exists.
|
||||
$file = id(new PhabricatorFile())->loadOneWhere(
|
||||
'contentHash = %s LIMIT 1',
|
||||
$hash);
|
||||
if (!$file) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($file) {
|
||||
$copy_of_storage_engine = $file->getStorageEngine();
|
||||
$copy_of_storage_handle = $file->getStorageHandle();
|
||||
$copy_of_storage_format = $file->getStorageFormat();
|
||||
|
@ -264,9 +237,6 @@ final class PhabricatorFile extends PhabricatorFileDAO
|
|||
return $new_file;
|
||||
}
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
public static function newChunkedFile(
|
||||
PhabricatorFileStorageEngine $engine,
|
||||
$length,
|
||||
|
@ -355,15 +325,18 @@ final class PhabricatorFile extends PhabricatorFileDAO
|
|||
|
||||
$data_handle = null;
|
||||
$engine_identifier = null;
|
||||
$integrity_hash = null;
|
||||
$exceptions = array();
|
||||
foreach ($engines as $engine) {
|
||||
$engine_class = get_class($engine);
|
||||
try {
|
||||
list($engine_identifier, $data_handle) = $file->writeToEngine(
|
||||
$result = $file->writeToEngine(
|
||||
$engine,
|
||||
$data,
|
||||
$params);
|
||||
|
||||
list($engine_identifier, $data_handle, $integrity_hash) = $result;
|
||||
|
||||
// We stored the file somewhere so stop trying to write it to other
|
||||
// places.
|
||||
break;
|
||||
|
@ -387,11 +360,15 @@ final class PhabricatorFile extends PhabricatorFileDAO
|
|||
}
|
||||
|
||||
$file->setByteSize(strlen($data));
|
||||
$file->setContentHash(self::hashFileContent($data));
|
||||
|
||||
$hash = self::hashFileContent($data);
|
||||
$file->setContentHash($hash);
|
||||
|
||||
$file->setStorageEngine($engine_identifier);
|
||||
$file->setStorageHandle($data_handle);
|
||||
|
||||
$file->setIntegrityHash($integrity_hash);
|
||||
|
||||
$file->readPropertiesFromParameters($params);
|
||||
|
||||
if (!$file->getMimeType()) {
|
||||
|
@ -413,11 +390,13 @@ final class PhabricatorFile extends PhabricatorFileDAO
|
|||
|
||||
public static function newFromFileData($data, array $params = array()) {
|
||||
$hash = self::hashFileContent($data);
|
||||
$file = self::newFileFromContentHash($hash, $params);
|
||||
|
||||
if ($hash !== null) {
|
||||
$file = self::newFileFromContentHash($hash, $params);
|
||||
if ($file) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
return self::buildFromFileData($data, $params);
|
||||
}
|
||||
|
@ -436,7 +415,7 @@ final class PhabricatorFile extends PhabricatorFileDAO
|
|||
'name' => $this->getName(),
|
||||
);
|
||||
|
||||
list($new_identifier, $new_handle) = $this->writeToEngine(
|
||||
list($new_identifier, $new_handle, $integrity_hash) = $this->writeToEngine(
|
||||
$engine,
|
||||
$data,
|
||||
$params);
|
||||
|
@ -447,6 +426,7 @@ final class PhabricatorFile extends PhabricatorFileDAO
|
|||
|
||||
$this->setStorageEngine($new_identifier);
|
||||
$this->setStorageHandle($new_handle);
|
||||
$this->setIntegrityHash($integrity_hash);
|
||||
$this->save();
|
||||
|
||||
if (!$make_copy) {
|
||||
|
@ -513,14 +493,14 @@ final class PhabricatorFile extends PhabricatorFileDAO
|
|||
|
||||
$engine_class = get_class($engine);
|
||||
|
||||
$key = $this->getStorageFormat();
|
||||
$format = id(clone PhabricatorFileStorageFormat::requireFormat($key))
|
||||
->setFile($this);
|
||||
$format = $this->newStorageFormat();
|
||||
|
||||
$data_iterator = array($data);
|
||||
$formatted_iterator = $format->newWriteIterator($data_iterator);
|
||||
$formatted_data = $this->loadDataFromIterator($formatted_iterator);
|
||||
|
||||
$integrity_hash = $engine->newIntegrityHash($formatted_data, $format);
|
||||
|
||||
$data_handle = $engine->writeFile($formatted_data, $params);
|
||||
|
||||
if (!$data_handle || strlen($data_handle) > 255) {
|
||||
|
@ -545,7 +525,7 @@ final class PhabricatorFile extends PhabricatorFileDAO
|
|||
$engine_identifier));
|
||||
}
|
||||
|
||||
return array($engine_identifier, $data_handle);
|
||||
return array($engine_identifier, $data_handle, $integrity_hash);
|
||||
}
|
||||
|
||||
|
||||
|
@ -744,9 +724,18 @@ final class PhabricatorFile extends PhabricatorFileDAO
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
public static function hashFileContent($data) {
|
||||
return sha1($data);
|
||||
// NOTE: Hashing can fail if the algorithm isn't available in the current
|
||||
// build of PHP. It's fine if we're unable to generate a content hash:
|
||||
// it just means we'll store extra data when users upload duplicate files
|
||||
// instead of being able to deduplicate it.
|
||||
|
||||
$hash = hash('sha256', $data, $raw_output = false);
|
||||
if ($hash === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $hash;
|
||||
}
|
||||
|
||||
public function loadFileData() {
|
||||
|
@ -764,16 +753,21 @@ final class PhabricatorFile extends PhabricatorFileDAO
|
|||
*/
|
||||
public function getFileDataIterator($begin = null, $end = null) {
|
||||
$engine = $this->instantiateStorageEngine();
|
||||
$raw_iterator = $engine->getRawFileDataIterator($this, $begin, $end);
|
||||
|
||||
$key = $this->getStorageFormat();
|
||||
$format = $this->newStorageFormat();
|
||||
|
||||
$format = id(clone PhabricatorFileStorageFormat::requireFormat($key))
|
||||
->setFile($this);
|
||||
$iterator = $engine->getRawFileDataIterator(
|
||||
$this,
|
||||
$begin,
|
||||
$end,
|
||||
$format);
|
||||
|
||||
return $format->newReadIterator($raw_iterator);
|
||||
return $iterator;
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
return $this->getInfoURI();
|
||||
}
|
||||
|
||||
public function getViewURI() {
|
||||
if (!$this->getPHID()) {
|
||||
|
@ -1106,7 +1100,7 @@ final class PhabricatorFile extends PhabricatorFileDAO
|
|||
|
||||
$params = array(
|
||||
'name' => $builtin->getBuiltinDisplayName(),
|
||||
'ttl' => time() + (60 * 60 * 24 * 7),
|
||||
'ttl.relative' => phutil_units('7 days in seconds'),
|
||||
'canCDN' => true,
|
||||
'builtin' => $key,
|
||||
);
|
||||
|
@ -1231,6 +1225,30 @@ final class PhabricatorFile extends PhabricatorFileDAO
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function setIntegrityHash($integrity_hash) {
|
||||
$this->metadata[self::METADATA_INTEGRITY] = $integrity_hash;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getIntegrityHash() {
|
||||
return idx($this->metadata, self::METADATA_INTEGRITY);
|
||||
}
|
||||
|
||||
public function newIntegrityHash() {
|
||||
$engine = $this->instantiateStorageEngine();
|
||||
|
||||
if ($engine->isChunkEngine()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$format = $this->newStorageFormat();
|
||||
|
||||
$storage_handle = $this->getStorageHandle();
|
||||
$data = $engine->readFile($storage_handle);
|
||||
|
||||
return $engine->newIntegrityHash($data, $format);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the policy edge between this file and some object.
|
||||
*
|
||||
|
@ -1276,14 +1294,68 @@ final class PhabricatorFile extends PhabricatorFileDAO
|
|||
* @return this
|
||||
*/
|
||||
private function readPropertiesFromParameters(array $params) {
|
||||
PhutilTypeSpec::checkMap(
|
||||
$params,
|
||||
array(
|
||||
'name' => 'optional string',
|
||||
'authorPHID' => 'optional string',
|
||||
'ttl.relative' => 'optional int',
|
||||
'ttl.absolute' => 'optional int',
|
||||
'viewPolicy' => 'optional string',
|
||||
'isExplicitUpload' => 'optional bool',
|
||||
'canCDN' => 'optional bool',
|
||||
'profile' => 'optional bool',
|
||||
'format' => 'optional string|PhabricatorFileStorageFormat',
|
||||
'mime-type' => 'optional string',
|
||||
'builtin' => 'optional string',
|
||||
'storageEngines' => 'optional list<PhabricatorFileStorageEngine>',
|
||||
));
|
||||
|
||||
$file_name = idx($params, 'name');
|
||||
$this->setName($file_name);
|
||||
|
||||
$author_phid = idx($params, 'authorPHID');
|
||||
$this->setAuthorPHID($author_phid);
|
||||
|
||||
$file_ttl = idx($params, 'ttl');
|
||||
$this->setTtl($file_ttl);
|
||||
$absolute_ttl = idx($params, 'ttl.absolute');
|
||||
$relative_ttl = idx($params, 'ttl.relative');
|
||||
if ($absolute_ttl !== null && $relative_ttl !== null) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Specify an absolute TTL or a relative TTL, but not both.'));
|
||||
} else if ($absolute_ttl !== null) {
|
||||
if ($absolute_ttl < PhabricatorTime::getNow()) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Absolute TTL must be in the present or future, but TTL "%s" '.
|
||||
'is in the past.',
|
||||
$absolute_ttl));
|
||||
}
|
||||
|
||||
$this->setTtl($absolute_ttl);
|
||||
} else if ($relative_ttl !== null) {
|
||||
if ($relative_ttl < 0) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Relative TTL must be zero or more seconds, but "%s" is '.
|
||||
'negative.',
|
||||
$relative_ttl));
|
||||
}
|
||||
|
||||
$max_relative = phutil_units('365 days in seconds');
|
||||
if ($relative_ttl > $max_relative) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Relative TTL must not be more than "%s" seconds, but TTL '.
|
||||
'"%s" was specified.',
|
||||
$max_relative,
|
||||
$relative_ttl));
|
||||
}
|
||||
|
||||
$absolute_ttl = PhabricatorTime::getNow() + $relative_ttl;
|
||||
|
||||
$this->setTtl($absolute_ttl);
|
||||
}
|
||||
|
||||
$view_policy = idx($params, 'viewPolicy');
|
||||
if ($view_policy) {
|
||||
|
@ -1345,6 +1417,16 @@ final class PhabricatorFile extends PhabricatorFileDAO
|
|||
return $this->assertAttachedKey($this->transforms, $key);
|
||||
}
|
||||
|
||||
public function newStorageFormat() {
|
||||
$key = $this->getStorageFormat();
|
||||
$template = PhabricatorFileStorageFormat::requireFormat($key);
|
||||
|
||||
$format = id(clone $template)
|
||||
->setFile($this);
|
||||
|
||||
return $format;
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
|
||||
|
||||
|
@ -1465,4 +1547,37 @@ final class PhabricatorFile extends PhabricatorFileDAO
|
|||
$this->saveTransaction();
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorConduitResultInterface )---------------------------------- */
|
||||
|
||||
|
||||
public function getFieldSpecificationsForConduit() {
|
||||
return array(
|
||||
id(new PhabricatorConduitSearchFieldSpecification())
|
||||
->setKey('name')
|
||||
->setType('string')
|
||||
->setDescription(pht('The name of the file.')),
|
||||
id(new PhabricatorConduitSearchFieldSpecification())
|
||||
->setKey('dataURI')
|
||||
->setType('string')
|
||||
->setDescription(pht('Download URI for the file data.')),
|
||||
id(new PhabricatorConduitSearchFieldSpecification())
|
||||
->setKey('size')
|
||||
->setType('int')
|
||||
->setDescription(pht('File size, in bytes.')),
|
||||
);
|
||||
}
|
||||
|
||||
public function getFieldValuesForConduit() {
|
||||
return array(
|
||||
'name' => $this->getName(),
|
||||
'dataURI' => $this->getCDNURI(),
|
||||
'size' => (int)$this->getByteSize(),
|
||||
);
|
||||
}
|
||||
|
||||
public function getConduitSearchAttachments() {
|
||||
return array();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorFileTransaction
|
||||
extends PhabricatorApplicationTransaction {
|
||||
|
||||
const TYPE_NAME = 'file:name';
|
||||
extends PhabricatorModularTransaction {
|
||||
|
||||
public function getApplicationName() {
|
||||
return 'file';
|
||||
|
@ -17,69 +15,8 @@ final class PhabricatorFileTransaction
|
|||
return new PhabricatorFileTransactionComment();
|
||||
}
|
||||
|
||||
public function getTitle() {
|
||||
$author_phid = $this->getAuthorPHID();
|
||||
|
||||
$old = $this->getOldValue();
|
||||
$new = $this->getNewValue();
|
||||
|
||||
switch ($this->getTransactionType()) {
|
||||
case self::TYPE_NAME:
|
||||
return pht(
|
||||
'%s updated the name for this file from "%s" to "%s".',
|
||||
$this->renderHandleLink($author_phid),
|
||||
$old,
|
||||
$new);
|
||||
break;
|
||||
public function getBaseTransactionClass() {
|
||||
return 'PhabricatorFileTransactionType';
|
||||
}
|
||||
|
||||
return parent::getTitle();
|
||||
}
|
||||
|
||||
public function getTitleForFeed() {
|
||||
$author_phid = $this->getAuthorPHID();
|
||||
$object_phid = $this->getObjectPHID();
|
||||
|
||||
$old = $this->getOldValue();
|
||||
$new = $this->getNewValue();
|
||||
|
||||
$type = $this->getTransactionType();
|
||||
switch ($type) {
|
||||
case self::TYPE_NAME:
|
||||
return pht(
|
||||
'%s updated the name of %s from "%s" to "%s".',
|
||||
$this->renderHandleLink($author_phid),
|
||||
$this->renderHandleLink($object_phid),
|
||||
$old,
|
||||
$new);
|
||||
break;
|
||||
}
|
||||
|
||||
return parent::getTitleForFeed();
|
||||
}
|
||||
|
||||
public function getIcon() {
|
||||
$old = $this->getOldValue();
|
||||
$new = $this->getNewValue();
|
||||
|
||||
switch ($this->getTransactionType()) {
|
||||
case self::TYPE_NAME:
|
||||
return 'fa-pencil';
|
||||
}
|
||||
|
||||
return parent::getIcon();
|
||||
}
|
||||
|
||||
|
||||
public function getColor() {
|
||||
$old = $this->getOldValue();
|
||||
$new = $this->getNewValue();
|
||||
|
||||
switch ($this->getTransactionType()) {
|
||||
case self::TYPE_NAME:
|
||||
return PhabricatorTransactions::COLOR_BLUE;
|
||||
}
|
||||
|
||||
return parent::getColor();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ final class PhabricatorFileTestCase extends PhabricatorTestCase {
|
|||
// First, change the name: this should not scramble the secret.
|
||||
$xactions = array();
|
||||
$xactions[] = id(new PhabricatorFileTransaction())
|
||||
->setTransactionType(PhabricatorFileTransaction::TYPE_NAME)
|
||||
->setTransactionType(PhabricatorFileNameTransaction::TRANSACTIONTYPE)
|
||||
->setNewValue('test.dat2');
|
||||
|
||||
$engine = id(new PhabricatorFileEditor())
|
||||
|
@ -301,6 +301,11 @@ final class PhabricatorFileTestCase extends PhabricatorTestCase {
|
|||
|
||||
$data = Filesystem::readRandomCharacters(64);
|
||||
|
||||
$hash = PhabricatorFile::hashFileContent($data);
|
||||
if ($hash === null) {
|
||||
$this->assertSkipped(pht('File content hashing is not available.'));
|
||||
}
|
||||
|
||||
$params = array(
|
||||
'name' => 'test.dat',
|
||||
'storageEngines' => array(
|
||||
|
@ -370,11 +375,11 @@ final class PhabricatorFileTestCase extends PhabricatorTestCase {
|
|||
|
||||
$data = Filesystem::readRandomCharacters(64);
|
||||
|
||||
$ttl = (time() + 60 * 60 * 24);
|
||||
$ttl = (PhabricatorTime::getNow() + phutil_units('24 hours in seconds'));
|
||||
|
||||
$params = array(
|
||||
'name' => 'test.dat',
|
||||
'ttl' => ($ttl),
|
||||
'ttl.absolute' => $ttl,
|
||||
'storageEngines' => array(
|
||||
$engine,
|
||||
),
|
||||
|
|
|
@ -4,7 +4,7 @@ abstract class PhabricatorFileUploadSource
|
|||
extends Phobject {
|
||||
|
||||
private $name;
|
||||
private $ttl;
|
||||
private $relativeTTL;
|
||||
private $viewPolicy;
|
||||
|
||||
private $rope;
|
||||
|
@ -22,13 +22,13 @@ abstract class PhabricatorFileUploadSource
|
|||
return $this->name;
|
||||
}
|
||||
|
||||
public function setTTL($ttl) {
|
||||
$this->ttl = $ttl;
|
||||
public function setRelativeTTL($relative_ttl) {
|
||||
$this->relativeTTL = $relative_ttl;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getTTL() {
|
||||
return $this->ttl;
|
||||
public function getRelativeTTL() {
|
||||
return $this->relativeTTL;
|
||||
}
|
||||
|
||||
public function setViewPolicy($view_policy) {
|
||||
|
@ -214,7 +214,7 @@ abstract class PhabricatorFileUploadSource
|
|||
private function getNewFileParameters() {
|
||||
return array(
|
||||
'name' => $this->getName(),
|
||||
'ttl' => $this->getTTL(),
|
||||
'ttl.relative' => $this->getRelativeTTL(),
|
||||
'viewPolicy' => $this->getViewPolicy(),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorFileNameTransaction
|
||||
extends PhabricatorFileTransactionType {
|
||||
|
||||
const TRANSACTIONTYPE = 'file:name';
|
||||
|
||||
public function generateOldValue($object) {
|
||||
return $object->getName();
|
||||
}
|
||||
|
||||
public function applyInternalEffects($object, $value) {
|
||||
$object->setName($value);
|
||||
}
|
||||
|
||||
public function getTitle() {
|
||||
return pht(
|
||||
'%s updated the name for this file from "%s" to "%s".',
|
||||
$this->renderAuthor(),
|
||||
$this->renderOldValue(),
|
||||
$this->renderNewValue());
|
||||
}
|
||||
|
||||
public function getTitleForFeed() {
|
||||
return pht(
|
||||
'%s updated the name of %s from "%s" to "%s".',
|
||||
$this->renderAuthor(),
|
||||
$this->renderObject(),
|
||||
$this->renderOldValue(),
|
||||
$this->renderNewValue());
|
||||
}
|
||||
|
||||
public function validateTransactions($object, array $xactions) {
|
||||
$errors = array();
|
||||
|
||||
if ($this->isEmptyTextTransaction($object->getName(), $xactions)) {
|
||||
$errors[] = $this->newRequiredError(pht('Files must have a name.'));
|
||||
}
|
||||
|
||||
$max_length = $object->getColumnMaximumByteLength('name');
|
||||
foreach ($xactions as $xaction) {
|
||||
$new_value = $xaction->getNewValue();
|
||||
$new_length = strlen($new_value);
|
||||
if ($new_length > $max_length) {
|
||||
$errors[] = $this->newInvalidError(
|
||||
pht(
|
||||
'File names must not be longer than %s character(s).',
|
||||
new PhutilNumber($max_length)));
|
||||
}
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
<?php
|
||||
|
||||
abstract class PhabricatorFileTransactionType
|
||||
extends PhabricatorModularTransactionType {}
|
|
@ -6,7 +6,7 @@ final class HarbormasterExternalBuildStepGroup
|
|||
const GROUPKEY = 'harbormaster.external';
|
||||
|
||||
public function getGroupName() {
|
||||
return pht('Interacting with External Build Sytems');
|
||||
return pht('Interacting with External Build Systems');
|
||||
}
|
||||
|
||||
public function getGroupOrder() {
|
||||
|
|
|
@ -39,8 +39,8 @@ final class LegalpadDocumentBody extends LegalpadDAO
|
|||
|
||||
|
||||
public function getMarkupFieldKey($field) {
|
||||
$hash = PhabricatorHash::digest($this->getMarkupText($field));
|
||||
return 'LEGB:'.$hash;
|
||||
$content = $this->getMarkupText($field);
|
||||
return PhabricatorMarkupEngine::digestRemarkupContent($this, $content);
|
||||
}
|
||||
|
||||
public function newMarkupEngine($field) {
|
||||
|
|
|
@ -301,9 +301,8 @@ final class ManiphestTask extends ManiphestDAO
|
|||
* @task markup
|
||||
*/
|
||||
public function getMarkupFieldKey($field) {
|
||||
$hash = PhabricatorHash::digest($this->getMarkupText($field));
|
||||
$id = $this->getID();
|
||||
return "maniphest:T{$id}:{$field}:{$hash}";
|
||||
$content = $this->getMarkupText($field);
|
||||
return PhabricatorMarkupEngine::digestRemarkupContent($this, $content);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -201,7 +201,7 @@ abstract class PhabricatorObjectMailReceiver extends PhabricatorMailReceiver {
|
|||
public static function computeMailHash($mail_key, $phid) {
|
||||
$global_mail_key = PhabricatorEnv::getEnvConfig('phabricator.mail-key');
|
||||
|
||||
$hash = PhabricatorHash::digest($mail_key.$global_mail_key.$phid);
|
||||
$hash = PhabricatorHash::weakDigest($mail_key.$global_mail_key.$phid);
|
||||
return substr($hash, 0, 16);
|
||||
}
|
||||
|
||||
|
|
|
@ -79,7 +79,6 @@ final class PhabricatorPasteContentTransaction
|
|||
'mime-type' => 'text/plain; charset=utf-8',
|
||||
'authorPHID' => $actor->getPHID(),
|
||||
'viewPolicy' => PhabricatorPolicies::POLICY_NOONE,
|
||||
'editPolicy' => PhabricatorPolicies::POLICY_NOONE,
|
||||
));
|
||||
}
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ final class PhabricatorPeopleLdapController
|
|||
|
||||
$crumbs = $this->buildApplicationCrumbs();
|
||||
$crumbs->addTextCrumb(
|
||||
pht('Import Ldap Users'),
|
||||
pht('Import LDAP Users'),
|
||||
$this->getApplicationURI('/ldap/'));
|
||||
|
||||
$nav = $this->buildSideNavView();
|
||||
|
@ -56,7 +56,7 @@ final class PhabricatorPeopleLdapController
|
|||
}
|
||||
|
||||
return $this->newPage()
|
||||
->setTitle(pht('Import Ldap Users'))
|
||||
->setTitle(pht('Import LDAP Users'))
|
||||
->setCrumbs($crumbs)
|
||||
->setNavigation($nav);
|
||||
}
|
||||
|
|
|
@ -389,7 +389,7 @@ final class PhabricatorUser
|
|||
// Generate a token hash to mitigate BREACH attacks against SSL. See
|
||||
// discussion in T3684.
|
||||
$token = $this->getRawCSRFToken();
|
||||
$hash = PhabricatorHash::digest($token, $salt);
|
||||
$hash = PhabricatorHash::weakDigest($token, $salt);
|
||||
return self::CSRF_BREACH_PREFIX.$salt.substr(
|
||||
$hash, 0, self::CSRF_TOKEN_LENGTH);
|
||||
}
|
||||
|
@ -435,7 +435,7 @@ final class PhabricatorUser
|
|||
for ($ii = -$csrf_window; $ii <= 1; $ii++) {
|
||||
$valid = $this->getRawCSRFToken($ii);
|
||||
|
||||
$digest = PhabricatorHash::digest($valid, $salt);
|
||||
$digest = PhabricatorHash::weakDigest($valid, $salt);
|
||||
$digest = substr($digest, 0, self::CSRF_TOKEN_LENGTH);
|
||||
if (phutil_hashes_are_identical($digest, $token)) {
|
||||
return true;
|
||||
|
@ -459,7 +459,7 @@ final class PhabricatorUser
|
|||
$time_block = floor($epoch / $frequency);
|
||||
$vec = $vec.$key.$time_block;
|
||||
|
||||
return substr(PhabricatorHash::digest($vec), 0, $len);
|
||||
return substr(PhabricatorHash::weakDigest($vec), 0, $len);
|
||||
}
|
||||
|
||||
public function getUserProfile() {
|
||||
|
|
|
@ -289,8 +289,8 @@ final class PhameBlog extends PhameDAO
|
|||
|
||||
|
||||
public function getMarkupFieldKey($field) {
|
||||
$hash = PhabricatorHash::digest($this->getMarkupText($field));
|
||||
return $this->getPHID().':'.$field.':'.$hash;
|
||||
$content = $this->getMarkupText($field);
|
||||
return PhabricatorMarkupEngine::digestRemarkupContent($this, $content);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -241,8 +241,8 @@ final class PhamePost extends PhameDAO
|
|||
|
||||
|
||||
public function getMarkupFieldKey($field) {
|
||||
$hash = PhabricatorHash::digest($this->getMarkupText($field));
|
||||
return $this->getPHID().':'.$field.':'.$hash;
|
||||
$content = $this->getMarkupText($field);
|
||||
return PhabricatorMarkupEngine::digestRemarkupContent($this, $content);
|
||||
}
|
||||
|
||||
public function newMarkupEngine($field) {
|
||||
|
|
|
@ -84,8 +84,8 @@ final class PholioImage extends PholioDAO
|
|||
|
||||
|
||||
public function getMarkupFieldKey($field) {
|
||||
$hash = PhabricatorHash::digest($this->getMarkupText($field));
|
||||
return 'M:'.$hash;
|
||||
$content = $this->getMarkupText($field);
|
||||
return PhabricatorMarkupEngine::digestRemarkupContent($this, $content);
|
||||
}
|
||||
|
||||
public function newMarkupEngine($field) {
|
||||
|
|
|
@ -217,8 +217,8 @@ final class PholioMock extends PholioDAO
|
|||
|
||||
|
||||
public function getMarkupFieldKey($field) {
|
||||
$hash = PhabricatorHash::digest($this->getMarkupText($field));
|
||||
return 'M:'.$hash;
|
||||
$content = $this->getMarkupText($field);
|
||||
return PhabricatorMarkupEngine::digestRemarkupContent($this, $content);
|
||||
}
|
||||
|
||||
public function newMarkupEngine($field) {
|
||||
|
|
|
@ -174,12 +174,15 @@ final class PhragmentGetPatchConduitAPIMethod
|
|||
unset($patches[$key]['fileOld']);
|
||||
unset($patches[$key]['fileNew']);
|
||||
|
||||
$file = PhabricatorFile::buildFromFileDataOrHash(
|
||||
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
||||
$file = PhabricatorFile::newFromFileData(
|
||||
$data,
|
||||
array(
|
||||
'name' => 'patch.dmp',
|
||||
'ttl' => time() + 60 * 60 * 24,
|
||||
'ttl.relative' => phutil_units('24 hours in seconds'),
|
||||
));
|
||||
unset($unguarded);
|
||||
|
||||
$patches[$key]['patchURI'] = $file->getDownloadURI();
|
||||
}
|
||||
|
||||
|
|
|
@ -78,15 +78,15 @@ final class PhragmentPatchController extends PhragmentController {
|
|||
$return = $request->getStr('return');
|
||||
}
|
||||
|
||||
$result = PhabricatorFile::buildFromFileDataOrHash(
|
||||
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
||||
$result = PhabricatorFile::newFromFileData(
|
||||
$patch,
|
||||
array(
|
||||
'name' => $name,
|
||||
'mime-type' => 'text/plain',
|
||||
'ttl' => time() + 60 * 60 * 24,
|
||||
'ttl.relative' => phutil_units('24 hours in seconds'),
|
||||
));
|
||||
|
||||
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
||||
$result->attachToObject($version_b->getFragmentPHID());
|
||||
unset($unguarded);
|
||||
|
||||
|
|
|
@ -100,14 +100,15 @@ final class PhragmentZIPController extends PhragmentController {
|
|||
}
|
||||
|
||||
$data = Filesystem::readFile((string)$temp);
|
||||
$file = PhabricatorFile::buildFromFileDataOrHash(
|
||||
|
||||
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
||||
$file = PhabricatorFile::newFromFileData(
|
||||
$data,
|
||||
array(
|
||||
'name' => $zip_name,
|
||||
'ttl' => time() + 60 * 60 * 24,
|
||||
'ttl.relative' => phutil_units('24 hours in seconds'),
|
||||
));
|
||||
|
||||
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
||||
$file->attachToObject($fragment->getPHID());
|
||||
unset($unguarded);
|
||||
|
||||
|
|
|
@ -68,12 +68,8 @@ final class PhrictionContent extends PhrictionDAO
|
|||
* @task markup
|
||||
*/
|
||||
public function getMarkupFieldKey($field) {
|
||||
if ($this->shouldUseMarkupCache($field)) {
|
||||
$id = $this->getID();
|
||||
} else {
|
||||
$id = PhabricatorHash::digest($this->getMarkupText($field));
|
||||
}
|
||||
return "phriction:{$field}:{$id}";
|
||||
$content = $this->getMarkupText($field);
|
||||
return PhabricatorMarkupEngine::digestRemarkupContent($this, $content);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -136,9 +136,8 @@ final class PonderAnswer extends PonderDAO
|
|||
// Markup interface
|
||||
|
||||
public function getMarkupFieldKey($field) {
|
||||
$hash = PhabricatorHash::digest($this->getMarkupText($field));
|
||||
$id = $this->getID();
|
||||
return "ponder:A{$id}:{$field}:{$hash}";
|
||||
$content = $this->getMarkupText($field);
|
||||
return PhabricatorMarkupEngine::digestRemarkupContent($this, $content);
|
||||
}
|
||||
|
||||
public function getMarkupText($field) {
|
||||
|
|
|
@ -155,9 +155,8 @@ final class PonderQuestion extends PonderDAO
|
|||
// Markup interface
|
||||
|
||||
public function getMarkupFieldKey($field) {
|
||||
$hash = PhabricatorHash::digest($this->getMarkupText($field));
|
||||
$id = $this->getID();
|
||||
return "ponder:Q{$id}:{$field}:{$hash}";
|
||||
$content = $this->getMarkupText($field);
|
||||
return PhabricatorMarkupEngine::digestRemarkupContent($this, $content);
|
||||
}
|
||||
|
||||
public function getMarkupText($field) {
|
||||
|
|
|
@ -236,12 +236,14 @@ abstract class ReleephFieldSpecification
|
|||
}
|
||||
|
||||
final public function getMarkupFieldKey($field) {
|
||||
return sprintf(
|
||||
$content = sprintf(
|
||||
'%s:%s:%s:%s',
|
||||
$this->getReleephRequest()->getPHID(),
|
||||
$this->getStorageKey(),
|
||||
$field,
|
||||
PhabricatorHash::digest($this->getMarkupText($field)));
|
||||
$this->getMarkupText($field));
|
||||
|
||||
return PhabricatorMarkupEngine::digestRemarkupContent($this, $content);
|
||||
}
|
||||
|
||||
final public function newMarkupEngine($field) {
|
||||
|
|
|
@ -68,6 +68,7 @@ final class PhabricatorRepositoryPullLocalDaemon
|
|||
$retry_after = array();
|
||||
|
||||
$min_sleep = 15;
|
||||
$max_sleep = phutil_units('5 minutes in seconds');
|
||||
$max_futures = 4;
|
||||
$futures = array();
|
||||
$queue = array();
|
||||
|
@ -228,7 +229,7 @@ final class PhabricatorRepositoryPullLocalDaemon
|
|||
continue;
|
||||
}
|
||||
|
||||
$should_hibernate = $this->waitForUpdates($min_sleep, $retry_after);
|
||||
$should_hibernate = $this->waitForUpdates($max_sleep, $retry_after);
|
||||
if ($should_hibernate) {
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -634,6 +634,10 @@ final class PhabricatorRepositoryEditor
|
|||
->synchronizeWorkingCopyAfterCreation();
|
||||
}
|
||||
|
||||
$object->writeStatusMessage(
|
||||
PhabricatorRepositoryStatusMessage::TYPE_NEEDS_UPDATE,
|
||||
null);
|
||||
|
||||
return $xactions;
|
||||
}
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ final class PhabricatorPasswordSettingsPanel extends PhabricatorSettingsPanel {
|
|||
->setViewer($user)
|
||||
->withTokenResources(array($user->getPHID()))
|
||||
->withTokenTypes(array($password_type))
|
||||
->withTokenCodes(array(PhabricatorHash::digest($key)))
|
||||
->withTokenCodes(array(PhabricatorHash::weakDigest($key)))
|
||||
->withExpired(false)
|
||||
->executeOne();
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ final class PhabricatorSessionsSettingsPanel extends PhabricatorSettingsPanel {
|
|||
->withPHIDs($identity_phids)
|
||||
->execute();
|
||||
|
||||
$current_key = PhabricatorHash::digest(
|
||||
$current_key = PhabricatorHash::weakDigest(
|
||||
$request->getCookie(PhabricatorCookies::COOKIE_SESSION));
|
||||
|
||||
$rows = array();
|
||||
|
|
|
@ -20,6 +20,7 @@ final class PhabricatorRobotsController extends PhabricatorController {
|
|||
|
||||
$out[] = 'User-Agent: *';
|
||||
$out[] = 'Disallow: /diffusion/';
|
||||
$out[] = 'Disallow: /source/';
|
||||
|
||||
// Add a small crawl delay (number of seconds between requests) for spiders
|
||||
// which respect it. The intent here is to prevent spiders from affecting
|
||||
|
|
|
@ -1149,12 +1149,20 @@ abstract class PhabricatorApplicationTransaction
|
|||
$this->renderHandleLink($author_phid),
|
||||
$this->renderHandleLink($object_phid));
|
||||
case PhabricatorTransactions::TYPE_SPACE:
|
||||
if ($this->getIsCreateTransaction()) {
|
||||
return pht(
|
||||
'%s created %s in the %s space.',
|
||||
$this->renderHandleLink($author_phid),
|
||||
$this->renderHandleLink($object_phid),
|
||||
$this->renderHandleLink($new));
|
||||
} else {
|
||||
return pht(
|
||||
'%s shifted %s from the %s space to the %s space.',
|
||||
$this->renderHandleLink($author_phid),
|
||||
$this->renderHandleLink($object_phid),
|
||||
$this->renderHandleLink($old),
|
||||
$this->renderHandleLink($new));
|
||||
}
|
||||
case PhabricatorTransactions::TYPE_EDGE:
|
||||
$new = ipull($new, 'dst');
|
||||
$old = ipull($old, 'dst');
|
||||
|
|
|
@ -31,4 +31,40 @@ abstract class PhabricatorManagementWorkflow extends PhutilArgumentWorkflow {
|
|||
PhabricatorConsoleContentSource::SOURCECONST);
|
||||
}
|
||||
|
||||
protected function logInfo($label, $message) {
|
||||
$this->logRaw(
|
||||
tsprintf(
|
||||
"**<bg:blue> %s </bg>** %s\n",
|
||||
$label,
|
||||
$message));
|
||||
}
|
||||
|
||||
protected function logOkay($label, $message) {
|
||||
$this->logRaw(
|
||||
tsprintf(
|
||||
"**<bg:green> %s </bg>** %s\n",
|
||||
$label,
|
||||
$message));
|
||||
}
|
||||
|
||||
protected function logWarn($label, $message) {
|
||||
$this->logRaw(
|
||||
tsprintf(
|
||||
"**<bg:yellow> %s </bg>** %s\n",
|
||||
$label,
|
||||
$message));
|
||||
}
|
||||
|
||||
protected function logFail($label, $message) {
|
||||
$this->logRaw(
|
||||
tsprintf(
|
||||
"**<bg:red> %s </bg>** %s\n",
|
||||
$label,
|
||||
$message));
|
||||
}
|
||||
|
||||
private function logRaw($message) {
|
||||
fprintf(STDERR, '%s', $message);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -694,4 +694,19 @@ final class PhabricatorMarkupEngine extends Phobject {
|
|||
->execute();
|
||||
}
|
||||
|
||||
public static function digestRemarkupContent($object, $content) {
|
||||
$parts = array();
|
||||
$parts[] = get_class($object);
|
||||
|
||||
if ($object instanceof PhabricatorLiskDAO) {
|
||||
$parts[] = $object->getID();
|
||||
}
|
||||
|
||||
$parts[] = $content;
|
||||
|
||||
$message = implode("\n", $parts);
|
||||
|
||||
return PhabricatorHash::digestWithNamedKey($message, 'remarkup');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -5,12 +5,15 @@ final class PhabricatorHash extends Phobject {
|
|||
const INDEX_DIGEST_LENGTH = 12;
|
||||
|
||||
/**
|
||||
* Digest a string for general use, including use which relates to security.
|
||||
* Digest a string using HMAC+SHA1.
|
||||
*
|
||||
* Because a SHA1 collision is now known, this method should be considered
|
||||
* weak. Callers should prefer @{method:digestWithNamedKey}.
|
||||
*
|
||||
* @param string Input string.
|
||||
* @return string 32-byte hexidecimal SHA1+HMAC hash.
|
||||
*/
|
||||
public static function digest($string, $key = null) {
|
||||
public static function weakDigest($string, $key = null) {
|
||||
if ($key === null) {
|
||||
$key = PhabricatorEnv::getEnvConfig('security.hmac-key');
|
||||
}
|
||||
|
@ -37,7 +40,7 @@ final class PhabricatorHash extends Phobject {
|
|||
}
|
||||
|
||||
for ($ii = 0; $ii < 1000; $ii++) {
|
||||
$result = self::digest($result, $salt);
|
||||
$result = self::weakDigest($result, $salt);
|
||||
}
|
||||
|
||||
return $result;
|
||||
|
@ -139,5 +142,89 @@ final class PhabricatorHash extends Phobject {
|
|||
return $prefix.'-'.$hash;
|
||||
}
|
||||
|
||||
public static function digestWithNamedKey($message, $key_name) {
|
||||
$key_bytes = self::getNamedHMACKey($key_name);
|
||||
return self::digestHMACSHA256($message, $key_bytes);
|
||||
}
|
||||
|
||||
public static function digestHMACSHA256($message, $key) {
|
||||
if (!strlen($key)) {
|
||||
throw new Exception(
|
||||
pht('HMAC-SHA256 requires a nonempty key.'));
|
||||
}
|
||||
|
||||
$result = hash_hmac('sha256', $message, $key, $raw_output = false);
|
||||
|
||||
if ($result === false) {
|
||||
throw new Exception(
|
||||
pht('Unable to compute HMAC-SHA256 digest of message.'));
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
/* -( HMAC Key Management )------------------------------------------------ */
|
||||
|
||||
|
||||
private static function getNamedHMACKey($hmac_name) {
|
||||
$cache = PhabricatorCaches::getImmutableCache();
|
||||
|
||||
$cache_key = "hmac.key({$hmac_name})";
|
||||
|
||||
$hmac_key = $cache->getKey($cache_key);
|
||||
if (!strlen($hmac_key)) {
|
||||
$hmac_key = self::readHMACKey($hmac_name);
|
||||
|
||||
if ($hmac_key === null) {
|
||||
$hmac_key = self::newHMACKey($hmac_name);
|
||||
self::writeHMACKey($hmac_name, $hmac_key);
|
||||
}
|
||||
|
||||
$cache->setKey($cache_key, $hmac_key);
|
||||
}
|
||||
|
||||
// The "hex2bin()" function doesn't exist until PHP 5.4.0 so just
|
||||
// implement it inline.
|
||||
$result = '';
|
||||
for ($ii = 0; $ii < strlen($hmac_key); $ii += 2) {
|
||||
$result .= pack('H*', substr($hmac_key, $ii, 2));
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private static function newHMACKey($hmac_name) {
|
||||
$hmac_key = Filesystem::readRandomBytes(64);
|
||||
return bin2hex($hmac_key);
|
||||
}
|
||||
|
||||
private static function writeHMACKey($hmac_name, $hmac_key) {
|
||||
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
||||
|
||||
id(new PhabricatorAuthHMACKey())
|
||||
->setKeyName($hmac_name)
|
||||
->setKeyValue($hmac_key)
|
||||
->save();
|
||||
|
||||
unset($unguarded);
|
||||
}
|
||||
|
||||
private static function readHMACKey($hmac_name) {
|
||||
$table = new PhabricatorAuthHMACKey();
|
||||
$conn = $table->establishConnection('r');
|
||||
|
||||
$row = queryfx_one(
|
||||
$conn,
|
||||
'SELECT keyValue FROM %T WHERE keyName = %s',
|
||||
$table->getTableName(),
|
||||
$hmac_name);
|
||||
if (!$row) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $row['keyValue'];
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorHMACTestCase extends PhabricatorTestCase {
|
||||
|
||||
protected function getPhabricatorTestCaseConfiguration() {
|
||||
return array(
|
||||
self::PHABRICATOR_TESTCONFIG_BUILD_STORAGE_FIXTURES => true,
|
||||
);
|
||||
}
|
||||
|
||||
public function testHMACKeyGeneration() {
|
||||
$input = 'quack';
|
||||
|
||||
$hash_1 = PhabricatorHash::digestWithNamedKey($input, 'test');
|
||||
$hash_2 = PhabricatorHash::digestWithNamedKey($input, 'test');
|
||||
|
||||
$this->assertEqual($hash_1, $hash_2);
|
||||
}
|
||||
|
||||
public function testSHA256Hashing() {
|
||||
$input = 'quack';
|
||||
$key = 'duck';
|
||||
$expect =
|
||||
'5274473dc34fc61bd7a6a5ff258e6505'.
|
||||
'4b26644fb7a272d74f276ab677361b9a';
|
||||
|
||||
$hash = PhabricatorHash::digestHMACSHA256($input, $key);
|
||||
$this->assertEqual($expect, $hash);
|
||||
|
||||
$input = 'The quick brown fox jumps over the lazy dog';
|
||||
$key = 'key';
|
||||
$expect =
|
||||
'f7bc83f430538424b13298e6aa6fb143'.
|
||||
'ef4d59a14946175997479dbc2d1a3cd8';
|
||||
|
||||
$hash = PhabricatorHash::digestHMACSHA256($input, $key);
|
||||
$this->assertEqual($expect, $hash);
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue