Merge branch 'master' into blender-tweaks

This commit is contained in:
2018-12-26 11:44:27 +01:00
247 changed files with 3823 additions and 2583 deletions

View File

@@ -9,8 +9,8 @@ return array(
'names' => array(
'conpherence.pkg.css' => 'e68cf1fa',
'conpherence.pkg.js' => '15191c65',
'core.pkg.css' => '7489ba0d',
'core.pkg.js' => '4bde473b',
'core.pkg.css' => '2b7e2a42',
'core.pkg.js' => 'bd89cb1d',
'differential.pkg.css' => '06dc617c',
'differential.pkg.js' => 'ef0b989b',
'diffusion.pkg.css' => 'a2d17c7d',
@@ -127,7 +127,7 @@ return array(
'rsrc/css/phui/calendar/phui-calendar-list.css' => '576be600',
'rsrc/css/phui/calendar/phui-calendar-month.css' => '21154caf',
'rsrc/css/phui/calendar/phui-calendar.css' => 'f1ddf11c',
'rsrc/css/phui/object-item/phui-oi-big-ui.css' => '628f59de',
'rsrc/css/phui/object-item/phui-oi-big-ui.css' => '7a7c22af',
'rsrc/css/phui/object-item/phui-oi-color.css' => 'cd2b9b77',
'rsrc/css/phui/object-item/phui-oi-drag-ui.css' => '08f4ccc3',
'rsrc/css/phui/object-item/phui-oi-flush-ui.css' => '9d9685d6',
@@ -216,7 +216,7 @@ return array(
'rsrc/externals/javelin/core/__tests__/stratcom.js' => '88bf7313',
'rsrc/externals/javelin/core/__tests__/util.js' => 'e251703d',
'rsrc/externals/javelin/core/init.js' => '8d83d2a1',
'rsrc/externals/javelin/core/init_node.js' => 'c234aded',
'rsrc/externals/javelin/core/init_node.js' => 'f7732951',
'rsrc/externals/javelin/core/install.js' => '05270951',
'rsrc/externals/javelin/core/util.js' => '93cc50d6',
'rsrc/externals/javelin/docs/Base.js' => '74676256',
@@ -427,7 +427,7 @@ return array(
'rsrc/js/application/transactions/behavior-comment-actions.js' => '59e27e74',
'rsrc/js/application/transactions/behavior-reorder-configs.js' => 'd7a74243',
'rsrc/js/application/transactions/behavior-reorder-fields.js' => 'b59e1e96',
'rsrc/js/application/transactions/behavior-show-older-transactions.js' => '8f29b364',
'rsrc/js/application/transactions/behavior-show-older-transactions.js' => '0e1eca96',
'rsrc/js/application/transactions/behavior-transaction-comment-form.js' => 'b23b49e6',
'rsrc/js/application/transactions/behavior-transaction-list.js' => '1f6794f6',
'rsrc/js/application/typeahead/behavior-typeahead-browse.js' => '635de1ec',
@@ -471,6 +471,7 @@ return array(
'rsrc/js/core/behavior-keyboard-shortcuts.js' => '01fca1f0',
'rsrc/js/core/behavior-lightbox-attachments.js' => '6b31879a',
'rsrc/js/core/behavior-line-linker.js' => '66a62306',
'rsrc/js/core/behavior-linked-container.js' => '291da458',
'rsrc/js/core/behavior-more.js' => 'a80d0378',
'rsrc/js/core/behavior-object-selector.js' => '77c1f0b0',
'rsrc/js/core/behavior-oncopy.js' => '2926fff2',
@@ -618,6 +619,7 @@ return array(
'javelin-behavior-launch-icon-composer' => '48086888',
'javelin-behavior-lightbox-attachments' => '6b31879a',
'javelin-behavior-line-chart' => 'e4232876',
'javelin-behavior-linked-container' => '291da458',
'javelin-behavior-maniphest-batch-selector' => 'ad54037e',
'javelin-behavior-maniphest-list-editor' => 'a9f88de2',
'javelin-behavior-maniphest-subpriority-editor' => '71237763',
@@ -639,7 +641,7 @@ return array(
'javelin-behavior-phabricator-remarkup-assist' => 'acd29eee',
'javelin-behavior-phabricator-reveal-content' => '60821bc7',
'javelin-behavior-phabricator-search-typeahead' => 'c3e917d9',
'javelin-behavior-phabricator-show-older-transactions' => '8f29b364',
'javelin-behavior-phabricator-show-older-transactions' => '0e1eca96',
'javelin-behavior-phabricator-tooltips' => 'c420b0b9',
'javelin-behavior-phabricator-transaction-comment-form' => 'b23b49e6',
'javelin-behavior-phabricator-transaction-list' => '1f6794f6',
@@ -835,7 +837,7 @@ return array(
'phui-lightbox-css' => '0a035e40',
'phui-list-view-css' => '38f8c9bd',
'phui-object-box-css' => '9cff003c',
'phui-oi-big-ui-css' => '628f59de',
'phui-oi-big-ui-css' => '7a7c22af',
'phui-oi-color-css' => 'cd2b9b77',
'phui-oi-drag-ui-css' => '08f4ccc3',
'phui-oi-flush-ui-css' => '9d9685d6',
@@ -951,6 +953,12 @@ return array(
'javelin-install',
'phuix-button-view',
),
'0e1eca96' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'phabricator-busy',
),
'0f764c35' => array(
'javelin-install',
'javelin-util',
@@ -1027,6 +1035,10 @@ return array(
'javelin-uri',
'phabricator-notification',
),
'291da458' => array(
'javelin-behavior',
'javelin-dom',
),
'2926fff2' => array(
'javelin-behavior',
'javelin-dom',
@@ -1348,9 +1360,6 @@ return array(
'javelin-magical-init',
'javelin-util',
),
'628f59de' => array(
'phui-oi-list-view-css',
),
'62dfea03' => array(
'javelin-install',
'javelin-util',
@@ -1511,6 +1520,9 @@ return array(
'owners-path-editor',
'javelin-behavior',
),
'7a7c22af' => array(
'phui-oi-list-view-css',
),
'7cbe244b' => array(
'javelin-install',
'javelin-util',
@@ -1578,12 +1590,6 @@ return array(
'8e1baf68' => array(
'phui-button-css',
),
'8f29b364' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'phabricator-busy',
),
'8ff5e24c' => array(
'javelin-behavior',
'javelin-stratcom',

View File

@@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_user.phabricator_session
ADD phid VARBINARY(64) NOT NULL;

View File

@@ -0,0 +1,18 @@
<?php
$table = new PhabricatorAuthSession();
$iterator = new LiskMigrationIterator($table);
$conn = $table->establishConnection('w');
foreach ($iterator as $session) {
if (strlen($session->getPHID())) {
continue;
}
queryfx(
$conn,
'UPDATE %R SET phid = %s WHERE id = %d',
$table,
$session->generatePHID(),
$session->getID());
}

View File

@@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_user.phabricator_session
ADD UNIQUE KEY `key_phid` (phid);

View File

@@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_user.phabricator_session
CHANGE sessionKey sessionKey VARBINARY(64) NOT NULL;

View File

@@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_user.user_log
CHANGE session session VARBINARY(64);

View File

@@ -0,0 +1,12 @@
CREATE TABLE {$NAMESPACE}_auth.auth_challenge (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
phid VARBINARY(64) NOT NULL,
userPHID VARBINARY(64) NOT NULL,
factorPHID VARBINARY(64) NOT NULL,
sessionPHID VARBINARY(64) NOT NULL,
challengeKey VARCHAR(255) NOT NULL COLLATE {$COLLATE_TEXT},
challengeTTL INT UNSIGNED NOT NULL,
properties LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT},
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};

View File

@@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_auth.auth_challenge
ADD workflowKey VARCHAR(255) NOT NULL COLLATE {$COLLATE_TEXT};

View File

@@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_auth.auth_challenge
ADD responseDigest VARCHAR(255) COLLATE {$COLLATE_TEXT};

View File

@@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_auth.auth_challenge
ADD responseTTL INT UNSIGNED;

View File

@@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_auth.auth_challenge
ADD isCompleted BOOL NOT NULL;

View File

@@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_pholio.pholio_image
ADD authorPHID VARBINARY(64) NOT NULL;

View File

@@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_pholio.pholio_image
ADD mockPHID VARBINARY(64);

View File

@@ -0,0 +1,35 @@
<?php
// Old images used a "mockID" instead of a "mockPHID" to reference mocks.
// Set the "mockPHID" column to the value that corresponds to the "mockID".
$image = new PholioImage();
$mock = new PholioMock();
$conn = $image->establishConnection('w');
$iterator = new LiskRawMigrationIterator($conn, $image->getTableName());
foreach ($iterator as $image_row) {
if ($image_row['mockPHID']) {
continue;
}
$mock_id = $image_row['mockID'];
$mock_row = queryfx_one(
$conn,
'SELECT phid FROM %R WHERE id = %d',
$mock,
$mock_id);
if (!$mock_row) {
continue;
}
queryfx(
$conn,
'UPDATE %R SET mockPHID = %s WHERE id = %d',
$image,
$mock_row['phid'],
$image_row['id']);
}

View File

@@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_pholio.pholio_image
DROP mockID;

View File

@@ -0,0 +1,28 @@
<?php
$mock_table = new PholioMock();
$mock_conn = $mock_table->establishConnection('w');
$properties_table = new PhabricatorMetaMTAMailProperties();
$conn = $properties_table->establishConnection('w');
$iterator = new LiskRawMigrationIterator(
$mock_conn,
$mock_table->getTableName());
foreach ($iterator as $row) {
queryfx(
$conn,
'INSERT IGNORE INTO %T
(objectPHID, mailProperties, dateCreated, dateModified)
VALUES
(%s, %s, %d, %d)',
$properties_table->getTableName(),
$row['phid'],
phutil_json_encode(
array(
'mailKey' => $row['mailKey'],
)),
PhabricatorTime::getNow(),
PhabricatorTime::getNow());
}

View File

@@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_pholio.pholio_mock
DROP mailKey;

View File

@@ -12,9 +12,9 @@ foreach ($commits as $commit) {
continue;
}
$data = $commit->loadOneRelative(
new PhabricatorRepositoryCommitData(),
'commitID');
$data = id(new PhabricatorRepositoryCommitData())->loadOneWhere(
'commitID = %d',
$commit->getID());
if (!$data) {
continue;

View File

@@ -8,7 +8,9 @@ $commit_table->establishConnection('w');
$edges = 0;
foreach (new LiskMigrationIterator($commit_table) as $commit) {
$data = $commit->loadOneRelative($data_table, 'commitID');
$data = $data_table->loadOneWhere(
'commitID = %d',
$commit->getID());
if (!$data) {
continue;
}

View File

@@ -1,22 +1,7 @@
<?php
$table = new PhabricatorUser();
$table->openTransaction();
$conn = $table->establishConnection('w');
$sessions = queryfx_all(
$conn,
'SELECT userPHID, type, sessionKey FROM %T FOR UPDATE',
PhabricatorUser::SESSION_TABLE);
foreach ($sessions as $session) {
queryfx(
$conn,
'UPDATE %T SET sessionKey = %s WHERE userPHID = %s AND type = %s',
PhabricatorUser::SESSION_TABLE,
PhabricatorHash::weakDigest($session['sessionKey']),
$session['userPHID'],
$session['type']);
}
$table->saveTransaction();
// See T13225. Long ago, this upgraded session key storage from unhashed to
// HMAC-SHA1 here. We later upgraded storage to HMAC-SHA256, so this is initial
// upgrade is now fairly pointless. Dropping this migration entirely only logs
// users out of installs that waited more than 5 years to upgrade, which seems
// like a reasonable behavior.

View File

@@ -200,9 +200,28 @@ $user->openTransaction();
$editor->updateUser($user, $verify_email);
}
$editor->makeAdminUser($user, $set_admin);
$editor->makeSystemAgentUser($user, $set_system_agent);
$xactions = array();
$xactions[] = id(new PhabricatorUserTransaction())
->setTransactionType(
PhabricatorUserEmpowerTransaction::TRANSACTIONTYPE)
->setNewValue($set_admin);
$actor = PhabricatorUser::getOmnipotentUser();
$content_source = PhabricatorContentSource::newForSource(
PhabricatorConsoleContentSource::SOURCECONST);
$people_application_phid = id(new PhabricatorPeopleApplication())->getPHID();
$transaction_editor = id(new PhabricatorUserTransactionEditor())
->setActor($actor)
->setActingAsPHID($people_application_phid)
->setContentSource($content_source)
->setContinueOnMissingFields(true);
$transaction_editor->applyTransactions($user, $xactions);
$user->saveTransaction();
echo pht('Saved changes.')."\n";

View File

@@ -647,6 +647,7 @@ phutil_register_library_map(array(
'DifferentialRevisionSummaryTransaction' => 'applications/differential/xaction/DifferentialRevisionSummaryTransaction.php',
'DifferentialRevisionTestPlanHeraldField' => 'applications/differential/herald/DifferentialRevisionTestPlanHeraldField.php',
'DifferentialRevisionTestPlanTransaction' => 'applications/differential/xaction/DifferentialRevisionTestPlanTransaction.php',
'DifferentialRevisionTimelineEngine' => 'applications/differential/engine/DifferentialRevisionTimelineEngine.php',
'DifferentialRevisionTitleHeraldField' => 'applications/differential/herald/DifferentialRevisionTitleHeraldField.php',
'DifferentialRevisionTitleTransaction' => 'applications/differential/xaction/DifferentialRevisionTitleTransaction.php',
'DifferentialRevisionTransactionType' => 'applications/differential/xaction/DifferentialRevisionTransactionType.php',
@@ -769,6 +770,7 @@ phutil_register_library_map(array(
'DiffusionCommitSearchConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionCommitSearchConduitAPIMethod.php',
'DiffusionCommitStateTransaction' => 'applications/diffusion/xaction/DiffusionCommitStateTransaction.php',
'DiffusionCommitTagsController' => 'applications/diffusion/controller/DiffusionCommitTagsController.php',
'DiffusionCommitTimelineEngine' => 'applications/diffusion/engine/DiffusionCommitTimelineEngine.php',
'DiffusionCommitTransactionType' => 'applications/diffusion/xaction/DiffusionCommitTransactionType.php',
'DiffusionCommitVerifyTransaction' => 'applications/diffusion/xaction/DiffusionCommitVerifyTransaction.php',
'DiffusionCompareController' => 'applications/diffusion/controller/DiffusionCompareController.php',
@@ -1630,10 +1632,8 @@ phutil_register_library_map(array(
'LegalpadTransaction' => 'applications/legalpad/storage/LegalpadTransaction.php',
'LegalpadTransactionComment' => 'applications/legalpad/storage/LegalpadTransactionComment.php',
'LegalpadTransactionQuery' => 'applications/legalpad/query/LegalpadTransactionQuery.php',
'LegalpadTransactionView' => 'applications/legalpad/view/LegalpadTransactionView.php',
'LiskChunkTestCase' => 'infrastructure/storage/lisk/__tests__/LiskChunkTestCase.php',
'LiskDAO' => 'infrastructure/storage/lisk/LiskDAO.php',
'LiskDAOSet' => 'infrastructure/storage/lisk/LiskDAOSet.php',
'LiskDAOTestCase' => 'infrastructure/storage/lisk/__tests__/LiskDAOTestCase.php',
'LiskEphemeralObjectException' => 'infrastructure/storage/lisk/LiskEphemeralObjectException.php',
'LiskFixtureTestCase' => 'infrastructure/storage/lisk/__tests__/LiskFixtureTestCase.php',
@@ -1761,6 +1761,7 @@ phutil_register_library_map(array(
'ManiphestTaskStatusTestCase' => 'applications/maniphest/constants/__tests__/ManiphestTaskStatusTestCase.php',
'ManiphestTaskStatusTransaction' => 'applications/maniphest/xaction/ManiphestTaskStatusTransaction.php',
'ManiphestTaskSubpriorityTransaction' => 'applications/maniphest/xaction/ManiphestTaskSubpriorityTransaction.php',
'ManiphestTaskSubtaskController' => 'applications/maniphest/controller/ManiphestTaskSubtaskController.php',
'ManiphestTaskSubtypeDatasource' => 'applications/maniphest/typeahead/ManiphestTaskSubtypeDatasource.php',
'ManiphestTaskTestCase' => 'applications/maniphest/__tests__/ManiphestTaskTestCase.php',
'ManiphestTaskTitleHeraldField' => 'applications/maniphest/herald/ManiphestTaskTitleHeraldField.php',
@@ -2085,6 +2086,7 @@ phutil_register_library_map(array(
'PhabricatorAmazonAuthProvider' => 'applications/auth/provider/PhabricatorAmazonAuthProvider.php',
'PhabricatorAnchorView' => 'view/layout/PhabricatorAnchorView.php',
'PhabricatorAphlictManagementDebugWorkflow' => 'applications/aphlict/management/PhabricatorAphlictManagementDebugWorkflow.php',
'PhabricatorAphlictManagementNotifyWorkflow' => 'applications/aphlict/management/PhabricatorAphlictManagementNotifyWorkflow.php',
'PhabricatorAphlictManagementRestartWorkflow' => 'applications/aphlict/management/PhabricatorAphlictManagementRestartWorkflow.php',
'PhabricatorAphlictManagementStartWorkflow' => 'applications/aphlict/management/PhabricatorAphlictManagementStartWorkflow.php',
'PhabricatorAphlictManagementStatusWorkflow' => 'applications/aphlict/management/PhabricatorAphlictManagementStatusWorkflow.php',
@@ -2189,6 +2191,10 @@ phutil_register_library_map(array(
'PhabricatorAuthApplication' => 'applications/auth/application/PhabricatorAuthApplication.php',
'PhabricatorAuthAuthFactorPHIDType' => 'applications/auth/phid/PhabricatorAuthAuthFactorPHIDType.php',
'PhabricatorAuthAuthProviderPHIDType' => 'applications/auth/phid/PhabricatorAuthAuthProviderPHIDType.php',
'PhabricatorAuthChallenge' => 'applications/auth/storage/PhabricatorAuthChallenge.php',
'PhabricatorAuthChallengeGarbageCollector' => 'applications/auth/garbagecollector/PhabricatorAuthChallengeGarbageCollector.php',
'PhabricatorAuthChallengePHIDType' => 'applications/auth/phid/PhabricatorAuthChallengePHIDType.php',
'PhabricatorAuthChallengeQuery' => 'applications/auth/query/PhabricatorAuthChallengeQuery.php',
'PhabricatorAuthChangePasswordAction' => 'applications/auth/action/PhabricatorAuthChangePasswordAction.php',
'PhabricatorAuthConduitAPIMethod' => 'applications/auth/conduit/PhabricatorAuthConduitAPIMethod.php',
'PhabricatorAuthConduitTokenRevoker' => 'applications/auth/revoker/PhabricatorAuthConduitTokenRevoker.php',
@@ -2200,6 +2206,7 @@ phutil_register_library_map(array(
'PhabricatorAuthEditController' => 'applications/auth/controller/config/PhabricatorAuthEditController.php',
'PhabricatorAuthFactor' => 'applications/auth/factor/PhabricatorAuthFactor.php',
'PhabricatorAuthFactorConfig' => 'applications/auth/storage/PhabricatorAuthFactorConfig.php',
'PhabricatorAuthFactorResult' => 'applications/auth/factor/PhabricatorAuthFactorResult.php',
'PhabricatorAuthFactorTestCase' => 'applications/auth/factor/__tests__/PhabricatorAuthFactorTestCase.php',
'PhabricatorAuthFinishController' => 'applications/auth/controller/PhabricatorAuthFinishController.php',
'PhabricatorAuthHMACKey' => 'applications/auth/storage/PhabricatorAuthHMACKey.php',
@@ -2298,6 +2305,7 @@ phutil_register_library_map(array(
'PhabricatorAuthSessionEngineExtensionModule' => 'applications/auth/engine/PhabricatorAuthSessionEngineExtensionModule.php',
'PhabricatorAuthSessionGarbageCollector' => 'applications/auth/garbagecollector/PhabricatorAuthSessionGarbageCollector.php',
'PhabricatorAuthSessionInfo' => 'applications/auth/data/PhabricatorAuthSessionInfo.php',
'PhabricatorAuthSessionPHIDType' => 'applications/auth/phid/PhabricatorAuthSessionPHIDType.php',
'PhabricatorAuthSessionQuery' => 'applications/auth/query/PhabricatorAuthSessionQuery.php',
'PhabricatorAuthSessionRevoker' => 'applications/auth/revoker/PhabricatorAuthSessionRevoker.php',
'PhabricatorAuthSetPasswordController' => 'applications/auth/controller/PhabricatorAuthSetPasswordController.php',
@@ -2980,6 +2988,7 @@ phutil_register_library_map(array(
'PhabricatorEditEngineStaticCommentAction' => 'applications/transactions/commentaction/PhabricatorEditEngineStaticCommentAction.php',
'PhabricatorEditEngineSubtype' => 'applications/transactions/editengine/PhabricatorEditEngineSubtype.php',
'PhabricatorEditEngineSubtypeInterface' => 'applications/transactions/editengine/PhabricatorEditEngineSubtypeInterface.php',
'PhabricatorEditEngineSubtypeMap' => 'applications/transactions/editengine/PhabricatorEditEngineSubtypeMap.php',
'PhabricatorEditEngineSubtypeTestCase' => 'applications/transactions/editengine/__tests__/PhabricatorEditEngineSubtypeTestCase.php',
'PhabricatorEditEngineTokenizerCommentAction' => 'applications/transactions/commentaction/PhabricatorEditEngineTokenizerCommentAction.php',
'PhabricatorEditField' => 'applications/transactions/editfield/PhabricatorEditField.php',
@@ -3506,7 +3515,6 @@ phutil_register_library_map(array(
'PhabricatorNotificationServersConfigType' => 'applications/notification/config/PhabricatorNotificationServersConfigType.php',
'PhabricatorNotificationStatusView' => 'applications/notification/view/PhabricatorNotificationStatusView.php',
'PhabricatorNotificationTestController' => 'applications/notification/controller/PhabricatorNotificationTestController.php',
'PhabricatorNotificationTestFeedStory' => 'applications/notification/feed/PhabricatorNotificationTestFeedStory.php',
'PhabricatorNotificationUIExample' => 'applications/uiexample/examples/PhabricatorNotificationUIExample.php',
'PhabricatorNotificationsApplication' => 'applications/notification/application/PhabricatorNotificationsApplication.php',
'PhabricatorNotificationsSetting' => 'applications/settings/setting/PhabricatorNotificationsSetting.php',
@@ -3764,6 +3772,7 @@ phutil_register_library_map(array(
'PhabricatorPeopleAnyOwnerDatasource' => 'applications/people/typeahead/PhabricatorPeopleAnyOwnerDatasource.php',
'PhabricatorPeopleApplication' => 'applications/people/application/PhabricatorPeopleApplication.php',
'PhabricatorPeopleApproveController' => 'applications/people/controller/PhabricatorPeopleApproveController.php',
'PhabricatorPeopleAvailabilitySearchEngineAttachment' => 'applications/people/engineextension/PhabricatorPeopleAvailabilitySearchEngineAttachment.php',
'PhabricatorPeopleBadgesProfileMenuItem' => 'applications/people/menuitem/PhabricatorPeopleBadgesProfileMenuItem.php',
'PhabricatorPeopleCommitsProfileMenuItem' => 'applications/people/menuitem/PhabricatorPeopleCommitsProfileMenuItem.php',
'PhabricatorPeopleController' => 'applications/people/controller/PhabricatorPeopleController.php',
@@ -4430,6 +4439,7 @@ phutil_register_library_map(array(
'PhabricatorStandardCustomFieldUsers' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldUsers.php',
'PhabricatorStandardPageView' => 'view/page/PhabricatorStandardPageView.php',
'PhabricatorStandardSelectCustomFieldDatasource' => 'infrastructure/customfield/datasource/PhabricatorStandardSelectCustomFieldDatasource.php',
'PhabricatorStandardTimelineEngine' => 'applications/transactions/engine/PhabricatorStandardTimelineEngine.php',
'PhabricatorStaticEditField' => 'applications/transactions/editfield/PhabricatorStaticEditField.php',
'PhabricatorStatusController' => 'applications/system/controller/PhabricatorStatusController.php',
'PhabricatorStatusUIExample' => 'applications/uiexample/examples/PhabricatorStatusUIExample.php',
@@ -4529,6 +4539,8 @@ phutil_register_library_map(array(
'PhabricatorTimeFormatSetting' => 'applications/settings/setting/PhabricatorTimeFormatSetting.php',
'PhabricatorTimeGuard' => 'infrastructure/time/PhabricatorTimeGuard.php',
'PhabricatorTimeTestCase' => 'infrastructure/time/__tests__/PhabricatorTimeTestCase.php',
'PhabricatorTimelineEngine' => 'applications/transactions/engine/PhabricatorTimelineEngine.php',
'PhabricatorTimelineInterface' => 'applications/transactions/interface/PhabricatorTimelineInterface.php',
'PhabricatorTimezoneIgnoreOffsetSetting' => 'applications/settings/setting/PhabricatorTimezoneIgnoreOffsetSetting.php',
'PhabricatorTimezoneSetting' => 'applications/settings/setting/PhabricatorTimezoneSetting.php',
'PhabricatorTimezoneSetupCheck' => 'applications/config/check/PhabricatorTimezoneSetupCheck.php',
@@ -4599,6 +4611,7 @@ phutil_register_library_map(array(
'PhabricatorUnknownContentSource' => 'infrastructure/contentsource/PhabricatorUnknownContentSource.php',
'PhabricatorUnsubscribedFromObjectEdgeType' => 'applications/transactions/edges/PhabricatorUnsubscribedFromObjectEdgeType.php',
'PhabricatorUser' => 'applications/people/storage/PhabricatorUser.php',
'PhabricatorUserApproveTransaction' => 'applications/people/xaction/PhabricatorUserApproveTransaction.php',
'PhabricatorUserBadgesCacheType' => 'applications/people/cache/PhabricatorUserBadgesCacheType.php',
'PhabricatorUserBlurbField' => 'applications/people/customfield/PhabricatorUserBlurbField.php',
'PhabricatorUserCache' => 'applications/people/storage/PhabricatorUserCache.php',
@@ -4618,6 +4631,7 @@ phutil_register_library_map(array(
'PhabricatorUserEditorTestCase' => 'applications/people/editor/__tests__/PhabricatorUserEditorTestCase.php',
'PhabricatorUserEmail' => 'applications/people/storage/PhabricatorUserEmail.php',
'PhabricatorUserEmailTestCase' => 'applications/people/storage/__tests__/PhabricatorUserEmailTestCase.php',
'PhabricatorUserEmpowerTransaction' => 'applications/people/xaction/PhabricatorUserEmpowerTransaction.php',
'PhabricatorUserFerretEngine' => 'applications/people/search/PhabricatorUserFerretEngine.php',
'PhabricatorUserFulltextEngine' => 'applications/people/search/PhabricatorUserFulltextEngine.php',
'PhabricatorUserIconField' => 'applications/people/customfield/PhabricatorUserIconField.php',
@@ -4625,6 +4639,7 @@ phutil_register_library_map(array(
'PhabricatorUserLogView' => 'applications/people/view/PhabricatorUserLogView.php',
'PhabricatorUserMessageCountCacheType' => 'applications/people/cache/PhabricatorUserMessageCountCacheType.php',
'PhabricatorUserNotificationCountCacheType' => 'applications/people/cache/PhabricatorUserNotificationCountCacheType.php',
'PhabricatorUserNotifyTransaction' => 'applications/people/xaction/PhabricatorUserNotifyTransaction.php',
'PhabricatorUserPHIDResolver' => 'applications/phid/resolver/PhabricatorUserPHIDResolver.php',
'PhabricatorUserPreferences' => 'applications/settings/storage/PhabricatorUserPreferences.php',
'PhabricatorUserPreferencesCacheType' => 'applications/people/cache/PhabricatorUserPreferencesCacheType.php',
@@ -4646,6 +4661,7 @@ phutil_register_library_map(array(
'PhabricatorUserTransaction' => 'applications/people/storage/PhabricatorUserTransaction.php',
'PhabricatorUserTransactionEditor' => 'applications/people/editor/PhabricatorUserTransactionEditor.php',
'PhabricatorUserTransactionType' => 'applications/people/xaction/PhabricatorUserTransactionType.php',
'PhabricatorUserUsernameTransaction' => 'applications/people/xaction/PhabricatorUserUsernameTransaction.php',
'PhabricatorUsersEditField' => 'applications/transactions/editfield/PhabricatorUsersEditField.php',
'PhabricatorUsersPolicyRule' => 'applications/people/policyrule/PhabricatorUsersPolicyRule.php',
'PhabricatorUsersSearchField' => 'applications/people/searchfield/PhabricatorUsersSearchField.php',
@@ -4864,6 +4880,7 @@ phutil_register_library_map(array(
'PholioMockSearchEngine' => 'applications/pholio/query/PholioMockSearchEngine.php',
'PholioMockStatusTransaction' => 'applications/pholio/xaction/PholioMockStatusTransaction.php',
'PholioMockThumbGridView' => 'applications/pholio/view/PholioMockThumbGridView.php',
'PholioMockTimelineEngine' => 'applications/pholio/engine/PholioMockTimelineEngine.php',
'PholioMockTransactionType' => 'applications/pholio/xaction/PholioMockTransactionType.php',
'PholioMockViewController' => 'applications/pholio/controller/PholioMockViewController.php',
'PholioRemarkupRule' => 'applications/pholio/remarkup/PholioRemarkupRule.php',
@@ -5986,6 +6003,7 @@ phutil_register_library_map(array(
'PhabricatorSubscribableInterface',
'PhabricatorCustomFieldInterface',
'PhabricatorApplicationTransactionInterface',
'PhabricatorTimelineInterface',
'PhabricatorMentionableInterface',
'PhabricatorDestructibleInterface',
'PhabricatorProjectInterface',
@@ -6067,6 +6085,7 @@ phutil_register_library_map(array(
'DifferentialRevisionSummaryTransaction' => 'DifferentialRevisionTransactionType',
'DifferentialRevisionTestPlanHeraldField' => 'DifferentialRevisionHeraldField',
'DifferentialRevisionTestPlanTransaction' => 'DifferentialRevisionTransactionType',
'DifferentialRevisionTimelineEngine' => 'PhabricatorTimelineEngine',
'DifferentialRevisionTitleHeraldField' => 'DifferentialRevisionHeraldField',
'DifferentialRevisionTitleTransaction' => 'DifferentialRevisionTransactionType',
'DifferentialRevisionTransactionType' => 'PhabricatorModularTransactionType',
@@ -6189,6 +6208,7 @@ phutil_register_library_map(array(
'DiffusionCommitSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'DiffusionCommitStateTransaction' => 'DiffusionCommitTransactionType',
'DiffusionCommitTagsController' => 'DiffusionController',
'DiffusionCommitTimelineEngine' => 'PhabricatorTimelineEngine',
'DiffusionCommitTransactionType' => 'PhabricatorModularTransactionType',
'DiffusionCommitVerifyTransaction' => 'DiffusionCommitAuditTransaction',
'DiffusionCompareController' => 'DiffusionController',
@@ -7196,13 +7216,11 @@ phutil_register_library_map(array(
'LegalpadTransaction' => 'PhabricatorModularTransaction',
'LegalpadTransactionComment' => 'PhabricatorApplicationTransactionComment',
'LegalpadTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'LegalpadTransactionView' => 'PhabricatorApplicationTransactionView',
'LiskChunkTestCase' => 'PhabricatorTestCase',
'LiskDAO' => array(
'Phobject',
'AphrontDatabaseTableRefInterface',
),
'LiskDAOSet' => 'Phobject',
'LiskDAOTestCase' => 'PhabricatorTestCase',
'LiskEphemeralObjectException' => 'Exception',
'LiskFixtureTestCase' => 'PhabricatorTestCase',
@@ -7353,6 +7371,7 @@ phutil_register_library_map(array(
'ManiphestTaskStatusTestCase' => 'PhabricatorTestCase',
'ManiphestTaskStatusTransaction' => 'ManiphestTaskTransactionType',
'ManiphestTaskSubpriorityTransaction' => 'ManiphestTaskTransactionType',
'ManiphestTaskSubtaskController' => 'ManiphestController',
'ManiphestTaskSubtypeDatasource' => 'PhabricatorTypeaheadDatasource',
'ManiphestTaskTestCase' => 'PhabricatorTestCase',
'ManiphestTaskTitleHeraldField' => 'ManiphestTaskHeraldField',
@@ -7706,6 +7725,7 @@ phutil_register_library_map(array(
'PhabricatorAmazonAuthProvider' => 'PhabricatorOAuth2AuthProvider',
'PhabricatorAnchorView' => 'AphrontView',
'PhabricatorAphlictManagementDebugWorkflow' => 'PhabricatorAphlictManagementWorkflow',
'PhabricatorAphlictManagementNotifyWorkflow' => 'PhabricatorAphlictManagementWorkflow',
'PhabricatorAphlictManagementRestartWorkflow' => 'PhabricatorAphlictManagementWorkflow',
'PhabricatorAphlictManagementStartWorkflow' => 'PhabricatorAphlictManagementWorkflow',
'PhabricatorAphlictManagementStatusWorkflow' => 'PhabricatorAphlictManagementWorkflow',
@@ -7825,6 +7845,13 @@ phutil_register_library_map(array(
'PhabricatorAuthApplication' => 'PhabricatorApplication',
'PhabricatorAuthAuthFactorPHIDType' => 'PhabricatorPHIDType',
'PhabricatorAuthAuthProviderPHIDType' => 'PhabricatorPHIDType',
'PhabricatorAuthChallenge' => array(
'PhabricatorAuthDAO',
'PhabricatorPolicyInterface',
),
'PhabricatorAuthChallengeGarbageCollector' => 'PhabricatorGarbageCollector',
'PhabricatorAuthChallengePHIDType' => 'PhabricatorPHIDType',
'PhabricatorAuthChallengeQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorAuthChangePasswordAction' => 'PhabricatorSystemAction',
'PhabricatorAuthConduitAPIMethod' => 'ConduitAPIMethod',
'PhabricatorAuthConduitTokenRevoker' => 'PhabricatorAuthRevoker',
@@ -7836,6 +7863,7 @@ phutil_register_library_map(array(
'PhabricatorAuthEditController' => 'PhabricatorAuthProviderConfigController',
'PhabricatorAuthFactor' => 'Phobject',
'PhabricatorAuthFactorConfig' => 'PhabricatorAuthDAO',
'PhabricatorAuthFactorResult' => 'Phobject',
'PhabricatorAuthFactorTestCase' => 'PhabricatorTestCase',
'PhabricatorAuthFinishController' => 'PhabricatorAuthController',
'PhabricatorAuthHMACKey' => 'PhabricatorAuthDAO',
@@ -7953,6 +7981,7 @@ phutil_register_library_map(array(
'PhabricatorAuthSessionEngineExtensionModule' => 'PhabricatorConfigModule',
'PhabricatorAuthSessionGarbageCollector' => 'PhabricatorGarbageCollector',
'PhabricatorAuthSessionInfo' => 'Phobject',
'PhabricatorAuthSessionPHIDType' => 'PhabricatorPHIDType',
'PhabricatorAuthSessionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorAuthSessionRevoker' => 'PhabricatorAuthRevoker',
'PhabricatorAuthSetPasswordController' => 'PhabricatorAuthController',
@@ -8740,6 +8769,7 @@ phutil_register_library_map(array(
'PhabricatorEditEngineSettingsPanel' => 'PhabricatorSettingsPanel',
'PhabricatorEditEngineStaticCommentAction' => 'PhabricatorEditEngineCommentAction',
'PhabricatorEditEngineSubtype' => 'Phobject',
'PhabricatorEditEngineSubtypeMap' => 'Phobject',
'PhabricatorEditEngineSubtypeTestCase' => 'PhabricatorTestCase',
'PhabricatorEditEngineTokenizerCommentAction' => 'PhabricatorEditEngineCommentAction',
'PhabricatorEditField' => 'Phobject',
@@ -9328,7 +9358,6 @@ phutil_register_library_map(array(
'PhabricatorNotificationServersConfigType' => 'PhabricatorJSONConfigType',
'PhabricatorNotificationStatusView' => 'AphrontTagView',
'PhabricatorNotificationTestController' => 'PhabricatorNotificationController',
'PhabricatorNotificationTestFeedStory' => 'PhabricatorFeedStory',
'PhabricatorNotificationUIExample' => 'PhabricatorUIExample',
'PhabricatorNotificationsApplication' => 'PhabricatorApplication',
'PhabricatorNotificationsSetting' => 'PhabricatorInternalSetting',
@@ -9649,6 +9678,7 @@ phutil_register_library_map(array(
'PhabricatorPeopleAnyOwnerDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorPeopleApplication' => 'PhabricatorApplication',
'PhabricatorPeopleApproveController' => 'PhabricatorPeopleController',
'PhabricatorPeopleAvailabilitySearchEngineAttachment' => 'PhabricatorSearchEngineAttachment',
'PhabricatorPeopleBadgesProfileMenuItem' => 'PhabricatorProfileMenuItem',
'PhabricatorPeopleCommitsProfileMenuItem' => 'PhabricatorProfileMenuItem',
'PhabricatorPeopleController' => 'PhabricatorController',
@@ -10067,6 +10097,7 @@ phutil_register_library_map(array(
'HarbormasterBuildkiteBuildableInterface',
'PhabricatorCustomFieldInterface',
'PhabricatorApplicationTransactionInterface',
'PhabricatorTimelineInterface',
'PhabricatorFulltextInterface',
'PhabricatorFerretInterface',
'PhabricatorConduitResultInterface',
@@ -10458,6 +10489,7 @@ phutil_register_library_map(array(
'AphrontResponseProducerInterface',
),
'PhabricatorStandardSelectCustomFieldDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorStandardTimelineEngine' => 'PhabricatorTimelineEngine',
'PhabricatorStaticEditField' => 'PhabricatorEditField',
'PhabricatorStatusController' => 'PhabricatorController',
'PhabricatorStatusUIExample' => 'PhabricatorUIExample',
@@ -10556,6 +10588,7 @@ phutil_register_library_map(array(
'PhabricatorTimeFormatSetting' => 'PhabricatorSelectSetting',
'PhabricatorTimeGuard' => 'Phobject',
'PhabricatorTimeTestCase' => 'PhabricatorTestCase',
'PhabricatorTimelineEngine' => 'Phobject',
'PhabricatorTimezoneIgnoreOffsetSetting' => 'PhabricatorInternalSetting',
'PhabricatorTimezoneSetting' => 'PhabricatorOptionGroupSetting',
'PhabricatorTimezoneSetupCheck' => 'PhabricatorSetupCheck',
@@ -10650,6 +10683,7 @@ phutil_register_library_map(array(
'PhabricatorConduitResultInterface',
'PhabricatorAuthPasswordHashInterface',
),
'PhabricatorUserApproveTransaction' => 'PhabricatorUserTransactionType',
'PhabricatorUserBadgesCacheType' => 'PhabricatorUserCacheType',
'PhabricatorUserBlurbField' => 'PhabricatorUserCustomField',
'PhabricatorUserCache' => 'PhabricatorUserDAO',
@@ -10672,6 +10706,7 @@ phutil_register_library_map(array(
'PhabricatorUserEditorTestCase' => 'PhabricatorTestCase',
'PhabricatorUserEmail' => 'PhabricatorUserDAO',
'PhabricatorUserEmailTestCase' => 'PhabricatorTestCase',
'PhabricatorUserEmpowerTransaction' => 'PhabricatorUserTransactionType',
'PhabricatorUserFerretEngine' => 'PhabricatorFerretEngine',
'PhabricatorUserFulltextEngine' => 'PhabricatorFulltextEngine',
'PhabricatorUserIconField' => 'PhabricatorUserCustomField',
@@ -10682,6 +10717,7 @@ phutil_register_library_map(array(
'PhabricatorUserLogView' => 'AphrontView',
'PhabricatorUserMessageCountCacheType' => 'PhabricatorUserCacheType',
'PhabricatorUserNotificationCountCacheType' => 'PhabricatorUserCacheType',
'PhabricatorUserNotifyTransaction' => 'PhabricatorUserTransactionType',
'PhabricatorUserPHIDResolver' => 'PhabricatorPHIDResolver',
'PhabricatorUserPreferences' => array(
'PhabricatorUserDAO',
@@ -10708,6 +10744,7 @@ phutil_register_library_map(array(
'PhabricatorUserTransaction' => 'PhabricatorModularTransaction',
'PhabricatorUserTransactionEditor' => 'PhabricatorApplicationTransactionEditor',
'PhabricatorUserTransactionType' => 'PhabricatorModularTransactionType',
'PhabricatorUserUsernameTransaction' => 'PhabricatorUserTransactionType',
'PhabricatorUsersEditField' => 'PhabricatorTokenizerEditField',
'PhabricatorUsersPolicyRule' => 'PhabricatorPolicyRule',
'PhabricatorUsersSearchField' => 'PhabricatorSearchTokenizerField',
@@ -10931,8 +10968,8 @@ phutil_register_library_map(array(
'PholioDefaultViewCapability' => 'PhabricatorPolicyCapability',
'PholioImage' => array(
'PholioDAO',
'PhabricatorMarkupInterface',
'PhabricatorPolicyInterface',
'PhabricatorExtendedPolicyInterface',
),
'PholioImageDescriptionTransaction' => 'PholioImageTransactionType',
'PholioImageFileTransaction' => 'PholioImageTransactionType',
@@ -10947,12 +10984,12 @@ phutil_register_library_map(array(
'PholioInlineListController' => 'PholioController',
'PholioMock' => array(
'PholioDAO',
'PhabricatorMarkupInterface',
'PhabricatorPolicyInterface',
'PhabricatorSubscribableInterface',
'PhabricatorTokenReceiverInterface',
'PhabricatorFlaggableInterface',
'PhabricatorApplicationTransactionInterface',
'PhabricatorTimelineInterface',
'PhabricatorProjectInterface',
'PhabricatorDestructibleInterface',
'PhabricatorSpacesInterface',
@@ -10987,6 +11024,7 @@ phutil_register_library_map(array(
'PholioMockSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PholioMockStatusTransaction' => 'PholioMockTransactionType',
'PholioMockThumbGridView' => 'AphrontView',
'PholioMockTimelineEngine' => 'PhabricatorTimelineEngine',
'PholioMockTransactionType' => 'PholioTransactionType',
'PholioMockViewController' => 'PholioController',
'PholioRemarkupRule' => 'PhabricatorObjectRemarkupRule',

View File

@@ -29,13 +29,28 @@ final class PhabricatorHighSecurityRequestExceptionHandler
$throwable) {
$viewer = $this->getViewer($request);
$results = $throwable->getFactorValidationResults();
$form = id(new PhabricatorAuthSessionEngine())->renderHighSecurityForm(
$throwable->getFactors(),
$throwable->getFactorValidationResults(),
$results,
$viewer,
$request);
$is_wait = false;
foreach ($results as $result) {
if ($result->getIsWait()) {
$is_wait = true;
break;
}
}
if ($is_wait) {
$submit = pht('Wait Patiently');
} else {
$submit = pht('Enter High Security');
}
$dialog = id(new AphrontDialogView())
->setUser($viewer)
->setTitle(pht('Entering High Security'))
@@ -62,7 +77,7 @@ final class PhabricatorHighSecurityRequestExceptionHandler
'actions, you should leave high security.'))
->setSubmitURI($request->getPath())
->addCancelButton($throwable->getCancelURI())
->addSubmitButton(pht('Enter High Security'));
->addSubmitButton($submit);
$request_parameters = $request->getPassthroughRequestParameters(
$respect_quicksand = true);

View File

@@ -206,20 +206,10 @@ final class AlmanacBinding
return new AlmanacBindingEditor();
}
public function getApplicationTransactionObject() {
return $this;
}
public function getApplicationTransactionTemplate() {
return new AlmanacBindingTransaction();
}
public function willRenderTimeline(
PhabricatorApplicationTransactionView $timeline,
AphrontRequest $request) {
return $timeline;
}
/* -( PhabricatorDestructibleInterface )----------------------------------- */

View File

@@ -204,21 +204,10 @@ final class AlmanacDevice
return new AlmanacDeviceEditor();
}
public function getApplicationTransactionObject() {
return $this;
}
public function getApplicationTransactionTemplate() {
return new AlmanacDeviceTransaction();
}
public function willRenderTimeline(
PhabricatorApplicationTransactionView $timeline,
AphrontRequest $request) {
return $timeline;
}
/* -( PhabricatorSSHPublicKeyInterface )----------------------------------- */

View File

@@ -168,20 +168,10 @@ final class AlmanacInterface
return new AlmanacInterfaceEditor();
}
public function getApplicationTransactionObject() {
return $this;
}
public function getApplicationTransactionTemplate() {
return new AlmanacInterfaceTransaction();
}
public function willRenderTimeline(
PhabricatorApplicationTransactionView $timeline,
AphrontRequest $request) {
return $timeline;
}
/* -( PhabricatorConduitResultInterface )---------------------------------- */

View File

@@ -191,20 +191,10 @@ final class AlmanacNamespace
return new AlmanacNamespaceEditor();
}
public function getApplicationTransactionObject() {
return $this;
}
public function getApplicationTransactionTemplate() {
return new AlmanacNamespaceTransaction();
}
public function willRenderTimeline(
PhabricatorApplicationTransactionView $timeline,
AphrontRequest $request) {
return $timeline;
}
/* -( PhabricatorDestructibleInterface )----------------------------------- */

View File

@@ -61,21 +61,10 @@ final class AlmanacNetwork
return new AlmanacNetworkEditor();
}
public function getApplicationTransactionObject() {
return $this;
}
public function getApplicationTransactionTemplate() {
return new AlmanacNetworkTransaction();
}
public function willRenderTimeline(
PhabricatorApplicationTransactionView $timeline,
AphrontRequest $request) {
return $timeline;
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */

View File

@@ -226,21 +226,10 @@ final class AlmanacService
return new AlmanacServiceEditor();
}
public function getApplicationTransactionObject() {
return $this;
}
public function getApplicationTransactionTemplate() {
return new AlmanacServiceTransaction();
}
public function willRenderTimeline(
PhabricatorApplicationTransactionView $timeline,
AphrontRequest $request) {
return $timeline;
}
/* -( PhabricatorDestructibleInterface )----------------------------------- */

View File

@@ -55,11 +55,22 @@ final class AlmanacBindingInterfaceTransaction
}
public function getTitle() {
return pht(
'%s changed the interface for this binding from %s to %s.',
$this->renderAuthor(),
$this->renderOldHandle(),
$this->renderNewHandle());
if ($this->getOldValue() === null) {
return pht(
'%s set the interface for this binding to %s.',
$this->renderAuthor(),
$this->renderNewHandle());
} else if ($this->getNewValue() == null) {
return pht(
'%s removed the interface for this binding.',
$this->renderAuthor());
} else {
return pht(
'%s changed the interface for this binding from %s to %s.',
$this->renderAuthor(),
$this->renderOldHandle(),
$this->renderNewHandle());
}
}
public function validateTransactions($object, array $xactions) {

View File

@@ -0,0 +1,81 @@
<?php
final class PhabricatorAphlictManagementNotifyWorkflow
extends PhabricatorAphlictManagementWorkflow {
protected function didConstruct() {
$this
->setName('notify')
->setSynopsis(pht('Send a notification to a user.'))
->setArguments(
array(
array(
'name' => 'user',
'param' => 'username',
'help' => pht('User to notify.'),
),
array(
'name' => 'message',
'param' => 'text',
'help' => pht('Message to send.'),
),
));
}
public function execute(PhutilArgumentParser $args) {
$viewer = $this->getViewer();
$username = $args->getArg('user');
if (!strlen($username)) {
throw new PhutilArgumentUsageException(
pht(
'Specify a user to notify with "--user".'));
}
$user = id(new PhabricatorPeopleQuery())
->setViewer($viewer)
->withUsernames(array($username))
->executeOne();
if (!$user) {
throw new PhutilArgumentUsageException(
pht(
'No user with username "%s" exists.',
$username));
}
$message = $args->getArg('message');
if (!strlen($message)) {
throw new PhutilArgumentUsageException(
pht(
'Specify a message to send with "--message".'));
}
$application_phid = id(new PhabricatorNotificationsApplication())
->getPHID();
$content_source = $this->newContentSource();
$xactions = array();
$xactions[] = id(new PhabricatorUserTransaction())
->setTransactionType(
PhabricatorUserNotifyTransaction::TRANSACTIONTYPE)
->setNewValue($message)
->setForceNotifyPHIDs(array($user->getPHID()));
$editor = id(new PhabricatorUserTransactionEditor())
->setActor($viewer)
->setActingAsPHID($application_phid)
->setContentSource($content_source);
$editor->applyTransactions($user, $xactions);
echo tsprintf(
"%s\n",
pht('Sent notification.'));
return 0;
}
}

View File

@@ -59,10 +59,6 @@ final class PhabricatorAuditEditor
$this->oldAuditStatus = $object->getAuditStatus();
$object->loadAndAttachAuditAuthority(
$this->getActor(),
$this->getActingAsPHID());
return parent::expandTransactions($object, $xactions);
}
@@ -255,47 +251,16 @@ final class PhabricatorAuditEditor
case PhabricatorTransactions::TYPE_COMMENT:
$this->didExpandInlineState = true;
$actor_phid = $this->getActingAsPHID();
$author_phid = $object->getAuthorPHID();
$actor_is_author = ($actor_phid == $author_phid);
$query_template = id(new DiffusionDiffInlineCommentQuery())
->withCommitPHIDs(array($object->getPHID()));
$state_map = PhabricatorTransactions::getInlineStateMap();
$state_xaction = $this->newInlineStateTransaction(
$object,
$query_template);
$query = id(new DiffusionDiffInlineCommentQuery())
->setViewer($this->getActor())
->withCommitPHIDs(array($object->getPHID()))
->withFixedStates(array_keys($state_map));
$inlines = array();
$inlines[] = id(clone $query)
->withAuthorPHIDs(array($actor_phid))
->withHasTransaction(false)
->execute();
if ($actor_is_author) {
$inlines[] = id(clone $query)
->withHasTransaction(true)
->execute();
if ($state_xaction) {
$xactions[] = $state_xaction;
}
$inlines = array_mergev($inlines);
if (!$inlines) {
break;
}
$old_value = mpull($inlines, 'getFixedState', 'getPHID');
$new_value = array();
foreach ($old_value as $key => $state) {
$new_value[$key] = $state_map[$state];
}
$xactions[] = id(new PhabricatorAuditTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_INLINESTATE)
->setIgnoreOnNoEffect(true)
->setOldValue($old_value)
->setNewValue($new_value);
break;
}
}

View File

@@ -32,10 +32,6 @@ final class PhabricatorAuditTransaction
return new PhabricatorAuditTransactionComment();
}
public function getApplicationTransactionViewObject() {
return new PhabricatorAuditTransactionView();
}
public function getRemarkupBlocks() {
$blocks = parent::getRemarkupBlocks();

View File

@@ -9,7 +9,7 @@ final class PhabricatorAuthTryFactorAction extends PhabricatorSystemAction {
}
public function getScoreThreshold() {
return 10 / phutil_units('1 hour in seconds');
return 100 / phutil_units('1 hour in seconds');
}
public function getLimitExplanation() {

View File

@@ -424,7 +424,26 @@ final class PhabricatorAuthRegisterController
}
if ($is_setup) {
$editor->makeAdminUser($user, true);
$xactions = array();
$xactions[] = id(new PhabricatorUserTransaction())
->setTransactionType(
PhabricatorUserEmpowerTransaction::TRANSACTIONTYPE)
->setNewValue(true);
$actor = PhabricatorUser::getOmnipotentUser();
$content_source = PhabricatorContentSource::newFromRequest(
$request);
$people_application_phid = id(new PhabricatorPeopleApplication())
->getPHID();
$transaction_editor = id(new PhabricatorUserTransactionEditor())
->setActor($actor)
->setActingAsPHID($people_application_phid)
->setContentSource($content_source)
->setContinueOnMissingFields(true);
$transaction_editor->applyTransactions($user, $xactions);
}
$account->setUserPHID($user->getPHID());

View File

@@ -16,8 +16,9 @@ final class PhabricatorAuthTerminateSessionController
$query->withIDs(array($id));
}
$current_key = PhabricatorHash::weakDigest(
$request->getCookie(PhabricatorCookies::COOKIE_SESSION));
$current_key = PhabricatorAuthSession::newSessionDigest(
new PhutilOpaqueEnvelope(
$request->getCookie(PhabricatorCookies::COOKIE_SESSION)));
$sessions = $query->execute();
foreach ($sessions as $key => $session) {

View File

@@ -56,7 +56,8 @@ final class PhabricatorAuthUnlinkController
id(new PhabricatorAuthSessionEngine())->terminateLoginSessions(
$viewer,
$request->getCookie(PhabricatorCookies::COOKIE_SESSION));
new PhutilOpaqueEnvelope(
$request->getCookie(PhabricatorCookies::COOKIE_SESSION)));
return id(new AphrontRedirectResponse())->setURI($this->getDoneURI());
}

View File

@@ -46,6 +46,26 @@ final class PhabricatorAuthSessionEngine extends Phobject {
const ONETIME_USERNAME = 'rename';
private $workflowKey;
public function setWorkflowKey($workflow_key) {
$this->workflowKey = $workflow_key;
return $this;
}
public function getWorkflowKey() {
// TODO: A workflow key should become required in order to issue an MFA
// challenge, but allow things to keep working for now until we can update
// callsites.
if ($this->workflowKey === null) {
return 'legacy';
}
return $this->workflowKey;
}
/**
* Get the session kind (e.g., anonymous, user, external account) from a
* session token. Returns a `KIND_` constant.
@@ -109,36 +129,49 @@ final class PhabricatorAuthSessionEngine extends Phobject {
$session_table = new PhabricatorAuthSession();
$user_table = new PhabricatorUser();
$conn_r = $session_table->establishConnection('r');
$session_key = PhabricatorHash::weakDigest($session_token);
$conn = $session_table->establishConnection('r');
$cache_parts = $this->getUserCacheQueryParts($conn_r);
// TODO: See T13225. We're moving sessions to a more modern digest
// algorithm, but still accept older cookies for compatibility.
$session_key = PhabricatorAuthSession::newSessionDigest(
new PhutilOpaqueEnvelope($session_token));
$weak_key = PhabricatorHash::weakDigest($session_token);
$cache_parts = $this->getUserCacheQueryParts($conn);
list($cache_selects, $cache_joins, $cache_map, $types_map) = $cache_parts;
$info = queryfx_one(
$conn_r,
$conn,
'SELECT
s.id AS s_id,
s.phid AS s_phid,
s.sessionExpires AS s_sessionExpires,
s.sessionStart AS s_sessionStart,
s.highSecurityUntil AS s_highSecurityUntil,
s.isPartial AS s_isPartial,
s.signedLegalpadDocuments as s_signedLegalpadDocuments,
IF(s.sessionKey = %P, 1, 0) as s_weak,
u.*
%Q
FROM %T u JOIN %T s ON u.phid = s.userPHID
AND s.type = %s AND s.sessionKey = %P %Q',
FROM %R u JOIN %R s ON u.phid = s.userPHID
AND s.type = %s AND s.sessionKey IN (%P, %P) %Q',
new PhutilOpaqueEnvelope($weak_key),
$cache_selects,
$user_table->getTableName(),
$session_table->getTableName(),
$user_table,
$session_table,
$session_type,
new PhutilOpaqueEnvelope($session_key),
new PhutilOpaqueEnvelope($weak_key),
$cache_joins);
if (!$info) {
return null;
}
// TODO: Remove this, see T13225.
$is_weak = (bool)$info['s_weak'];
unset($info['s_weak']);
$session_dict = array(
'userPHID' => $info['phid'],
'sessionKey' => $session_key,
@@ -201,6 +234,19 @@ final class PhabricatorAuthSessionEngine extends Phobject {
unset($unguarded);
}
// TODO: Remove this, see T13225.
if ($is_weak) {
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$conn_w = $session_table->establishConnection('w');
queryfx(
$conn_w,
'UPDATE %T SET sessionKey = %P WHERE id = %d',
$session->getTableName(),
new PhutilOpaqueEnvelope($session_key),
$session->getID());
unset($unguarded);
}
$user->attachSession($session);
return $user;
}
@@ -240,7 +286,8 @@ final class PhabricatorAuthSessionEngine extends Phobject {
// This has a side effect of validating the session type.
$session_ttl = PhabricatorAuthSession::getSessionTypeTTL($session_type);
$digest_key = PhabricatorHash::weakDigest($session_key);
$digest_key = PhabricatorAuthSession::newSessionDigest(
new PhutilOpaqueEnvelope($session_key));
// Logging-in users don't have CSRF stuff yet, so we have to unguard this
// write.
@@ -298,7 +345,7 @@ final class PhabricatorAuthSessionEngine extends Phobject {
*/
public function terminateLoginSessions(
PhabricatorUser $user,
$except_session = null) {
PhutilOpaqueEnvelope $except_session = null) {
$sessions = id(new PhabricatorAuthSessionQuery())
->setViewer($user)
@@ -306,7 +353,8 @@ final class PhabricatorAuthSessionEngine extends Phobject {
->execute();
if ($except_session !== null) {
$except_session = PhabricatorHash::weakDigest($except_session);
$except_session = PhabricatorAuthSession::newSessionDigest(
$except_session);
}
foreach ($sessions as $key => $session) {
@@ -445,6 +493,10 @@ final class PhabricatorAuthSessionEngine extends Phobject {
return $this->issueHighSecurityToken($session, true);
}
foreach ($factors as $factor) {
$factor->setSessionEngine($this);
}
// Check for a rate limit without awarding points, so the user doesn't
// get partway through the workflow only to get blocked.
PhabricatorSystemActionEngine::willTakeAction(
@@ -452,7 +504,64 @@ final class PhabricatorAuthSessionEngine extends Phobject {
new PhabricatorAuthTryFactorAction(),
0);
$now = PhabricatorTime::getNow();
// We need to do challenge validation first, since this happens whether you
// submitted responses or not. You can't get a "bad response" error before
// you actually submit a response, but you can get a "wait, we can't
// issue a challenge yet" response. Load all issued challenges which are
// currently valid.
$challenges = id(new PhabricatorAuthChallengeQuery())
->setViewer($viewer)
->withFactorPHIDs(mpull($factors, 'getPHID'))
->withUserPHIDs(array($viewer->getPHID()))
->withChallengeTTLBetween($now, null)
->execute();
PhabricatorAuthChallenge::newChallengeResponsesFromRequest(
$challenges,
$request);
$challenge_map = mgroup($challenges, 'getFactorPHID');
$validation_results = array();
$ok = true;
// Validate each factor against issued challenges. For example, this
// prevents you from receiving or responding to a TOTP challenge if another
// challenge was recently issued to a different session.
foreach ($factors as $factor) {
$factor_phid = $factor->getPHID();
$issued_challenges = idx($challenge_map, $factor_phid, array());
$impl = $factor->requireImplementation();
$new_challenges = $impl->getNewIssuedChallenges(
$factor,
$viewer,
$issued_challenges);
foreach ($new_challenges as $new_challenge) {
$issued_challenges[] = $new_challenge;
}
$challenge_map[$factor_phid] = $issued_challenges;
if (!$issued_challenges) {
continue;
}
$result = $impl->getResultFromIssuedChallenges(
$factor,
$viewer,
$issued_challenges);
if (!$result) {
continue;
}
$ok = false;
$validation_results[$factor_phid] = $result;
}
if ($request->isHTTPPost()) {
$request->validateCSRF();
if ($request->getExists(AphrontRequest::TYPE_HISEC)) {
@@ -463,22 +572,43 @@ final class PhabricatorAuthSessionEngine extends Phobject {
new PhabricatorAuthTryFactorAction(),
1);
$ok = true;
foreach ($factors as $factor) {
$id = $factor->getID();
$factor_phid = $factor->getPHID();
// If we already have a validation result from previously issued
// challenges, skip validating this factor.
if (isset($validation_results[$factor_phid])) {
continue;
}
$issued_challenges = idx($challenge_map, $factor_phid, array());
$impl = $factor->requireImplementation();
$validation_results[$id] = $impl->processValidateFactorForm(
$validation_result = $impl->getResultFromChallengeResponse(
$factor,
$viewer,
$request);
$request,
$issued_challenges);
if (!$impl->isFactorValid($factor, $validation_results[$id])) {
if (!$validation_result->getIsValid()) {
$ok = false;
}
$validation_results[$factor_phid] = $validation_result;
}
if ($ok) {
// We're letting you through, so mark all the challenges you
// responded to as completed. These challenges can never be used
// again, even by the same session and workflow: you can't use the
// same response to take two different actions, even if those actions
// are of the same type.
foreach ($validation_results as $validation_result) {
$challenge = $validation_result->getAnsweredChallenge()
->markChallengeAsCompleted();
}
// Give the user a credit back for a successful factor verification.
PhabricatorSystemActionEngine::willTakeAction(
array($viewer->getPHID()),
@@ -527,6 +657,18 @@ final class PhabricatorAuthSessionEngine extends Phobject {
return $token;
}
// If we don't have a validation result for some factors yet, fill them
// in with an empty result so form rendering doesn't have to care if the
// results exist or not. This happens when you first load the form and have
// not submitted any responses yet.
foreach ($factors as $factor) {
$factor_phid = $factor->getPHID();
if (isset($validation_results[$factor_phid])) {
continue;
}
$validation_results[$factor_phid] = new PhabricatorAuthFactorResult();
}
throw id(new PhabricatorAuthHighSecurityRequiredException())
->setCancelURI($cancel_uri)
->setFactors($factors)
@@ -567,21 +709,38 @@ final class PhabricatorAuthSessionEngine extends Phobject {
array $validation_results,
PhabricatorUser $viewer,
AphrontRequest $request) {
assert_instances_of($validation_results, 'PhabricatorAuthFactorResult');
$form = id(new AphrontFormView())
->setUser($viewer)
->appendRemarkupInstructions('');
$answered = array();
foreach ($factors as $factor) {
$result = $validation_results[$factor->getPHID()];
$factor->requireImplementation()->renderValidateFactorForm(
$factor,
$form,
$viewer,
idx($validation_results, $factor->getID()));
$result);
$answered_challenge = $result->getAnsweredChallenge();
if ($answered_challenge) {
$answered[] = $answered_challenge;
}
}
$form->appendRemarkupInstructions('');
if ($answered) {
$http_params = PhabricatorAuthChallenge::newHTTPParametersFromChallenges(
$answered);
foreach ($http_params as $key => $value) {
$form->addHiddenInput($key, $value);
}
}
return $form;
}

View File

@@ -7,6 +7,7 @@ final class PhabricatorAuthHighSecurityRequiredException extends Exception {
private $factorValidationResults;
public function setFactorValidationResults(array $results) {
assert_instances_of($results, 'PhabricatorAuthFactorResult');
$this->factorValidationResults = $results;
return $this;
}

View File

@@ -14,18 +14,7 @@ abstract class PhabricatorAuthFactor extends Phobject {
PhabricatorAuthFactorConfig $config,
AphrontFormView $form,
PhabricatorUser $viewer,
$validation_result);
abstract public function processValidateFactorForm(
PhabricatorAuthFactorConfig $config,
PhabricatorUser $viewer,
AphrontRequest $request);
public function isFactorValid(
PhabricatorAuthFactorConfig $config,
$validation_result) {
return (idx($validation_result, 'valid') === true);
}
PhabricatorAuthFactorResult $validation_result);
public function getParameterName(
PhabricatorAuthFactorConfig $config,
@@ -46,4 +35,168 @@ abstract class PhabricatorAuthFactor extends Phobject {
->setFactorKey($this->getFactorKey());
}
protected function newResult() {
return new PhabricatorAuthFactorResult();
}
protected function newChallenge(
PhabricatorAuthFactorConfig $config,
PhabricatorUser $viewer) {
$engine = $config->getSessionEngine();
return PhabricatorAuthChallenge::initializeNewChallenge()
->setUserPHID($viewer->getPHID())
->setSessionPHID($viewer->getSession()->getPHID())
->setFactorPHID($config->getPHID())
->setWorkflowKey($engine->getWorkflowKey());
}
final public function getNewIssuedChallenges(
PhabricatorAuthFactorConfig $config,
PhabricatorUser $viewer,
array $challenges) {
assert_instances_of($challenges, 'PhabricatorAuthChallenge');
$now = PhabricatorTime::getNow();
$new_challenges = $this->newIssuedChallenges(
$config,
$viewer,
$challenges);
assert_instances_of($new_challenges, 'PhabricatorAuthChallenge');
foreach ($new_challenges as $new_challenge) {
$ttl = $new_challenge->getChallengeTTL();
if (!$ttl) {
throw new Exception(
pht('Newly issued MFA challenges must have a valid TTL!'));
}
if ($ttl < $now) {
throw new Exception(
pht(
'Newly issued MFA challenges must have a future TTL. This '.
'factor issued a bad TTL ("%s"). (Did you use a relative '.
'time instead of an epoch?)',
$ttl));
}
}
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
foreach ($new_challenges as $challenge) {
$challenge->save();
}
unset($unguarded);
return $new_challenges;
}
abstract protected function newIssuedChallenges(
PhabricatorAuthFactorConfig $config,
PhabricatorUser $viewer,
array $challenges);
final public function getResultFromIssuedChallenges(
PhabricatorAuthFactorConfig $config,
PhabricatorUser $viewer,
array $challenges) {
assert_instances_of($challenges, 'PhabricatorAuthChallenge');
$result = $this->newResultFromIssuedChallenges(
$config,
$viewer,
$challenges);
if ($result === null) {
return $result;
}
if (!($result instanceof PhabricatorAuthFactorResult)) {
throw new Exception(
pht(
'Expected "newResultFromIssuedChallenges()" to return null or '.
'an object of class "%s"; got something else (in "%s").',
'PhabricatorAuthFactorResult',
get_class($this)));
}
$result->setIssuedChallenges($challenges);
return $result;
}
abstract protected function newResultFromIssuedChallenges(
PhabricatorAuthFactorConfig $config,
PhabricatorUser $viewer,
array $challenges);
final public function getResultFromChallengeResponse(
PhabricatorAuthFactorConfig $config,
PhabricatorUser $viewer,
AphrontRequest $request,
array $challenges) {
assert_instances_of($challenges, 'PhabricatorAuthChallenge');
$result = $this->newResultFromChallengeResponse(
$config,
$viewer,
$request,
$challenges);
if (!($result instanceof PhabricatorAuthFactorResult)) {
throw new Exception(
pht(
'Expected "newResultFromChallengeResponse()" to return an object '.
'of class "%s"; got something else (in "%s").',
'PhabricatorAuthFactorResult',
get_class($this)));
}
$result->setIssuedChallenges($challenges);
return $result;
}
abstract protected function newResultFromChallengeResponse(
PhabricatorAuthFactorConfig $config,
PhabricatorUser $viewer,
AphrontRequest $request,
array $challenges);
final protected function newAutomaticControl(
PhabricatorAuthFactorResult $result) {
$is_answered = (bool)$result->getAnsweredChallenge();
if ($is_answered) {
return $this->newAnsweredControl($result);
}
$is_wait = $result->getIsWait();
if ($is_wait) {
return $this->newWaitControl($result);
}
return null;
}
private function newWaitControl(
PhabricatorAuthFactorResult $result) {
$error = $result->getErrorMessage();
return id(new AphrontFormMarkupControl())
->setValue($error)
->setError(pht('Wait'));
}
private function newAnsweredControl(
PhabricatorAuthFactorResult $result) {
return id(new AphrontFormMarkupControl())
->setValue(pht('Answered!'));
}
}

View File

@@ -0,0 +1,75 @@
<?php
final class PhabricatorAuthFactorResult
extends Phobject {
private $answeredChallenge;
private $isWait = false;
private $errorMessage;
private $value;
private $issuedChallenges = array();
public function setAnsweredChallenge(PhabricatorAuthChallenge $challenge) {
if (!$challenge->getIsAnsweredChallenge()) {
throw new PhutilInvalidStateException('markChallengeAsAnswered');
}
if ($challenge->getIsCompleted()) {
throw new Exception(
pht(
'A completed challenge was provided as an answered challenge. '.
'The underlying factor is implemented improperly, challenges '.
'may not be reused.'));
}
$this->answeredChallenge = $challenge;
return $this;
}
public function getAnsweredChallenge() {
return $this->answeredChallenge;
}
public function getIsValid() {
return (bool)$this->getAnsweredChallenge();
}
public function setIsWait($is_wait) {
$this->isWait = $is_wait;
return $this;
}
public function getIsWait() {
return $this->isWait;
}
public function setErrorMessage($error_message) {
$this->errorMessage = $error_message;
return $this;
}
public function getErrorMessage() {
return $this->errorMessage;
}
public function setValue($value) {
$this->value = $value;
return $this;
}
public function getValue() {
return $this->value;
}
public function setIssuedChallenges(array $issued_challenges) {
assert_instances_of($issued_challenges, 'PhabricatorAuthChallenge');
$this->issuedChallenges = $issued_challenges;
return $this;
}
public function getIssuedChallenges() {
return $this->issuedChallenges;
}
}

View File

@@ -2,6 +2,8 @@
final class PhabricatorTOTPAuthFactor extends PhabricatorAuthFactor {
const DIGEST_TEMPORARY_KEY = 'mfa.totp.sync';
public function getFactorKey() {
return 'totp';
}
@@ -34,12 +36,16 @@ final class PhabricatorTOTPAuthFactor extends PhabricatorAuthFactor {
// (We store and verify the hash of the key, not the key itself, to limit
// how useful the data in the table is to an attacker.)
$token_code = PhabricatorHash::digestWithNamedKey(
$key,
self::DIGEST_TEMPORARY_KEY);
$temporary_token = id(new PhabricatorAuthTemporaryTokenQuery())
->setViewer($user)
->withTokenResources(array($user->getPHID()))
->withTokenTypes(array($totp_token_type))
->withExpired(false)
->withTokenCodes(array(PhabricatorHash::weakDigest($key)))
->withTokenCodes(array($token_code))
->executeOne();
if (!$temporary_token) {
// If we don't have a matching token, regenerate the key below.
@@ -53,12 +59,16 @@ final class PhabricatorTOTPAuthFactor extends PhabricatorAuthFactor {
// Mark this key as one we generated, so the user is allowed to submit
// a response for it.
$token_code = PhabricatorHash::digestWithNamedKey(
$key,
self::DIGEST_TEMPORARY_KEY);
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
id(new PhabricatorAuthTemporaryToken())
->setTokenResource($user->getPHID())
->setTokenType($totp_token_type)
->setTokenExpires(time() + phutil_units('1 hour in seconds'))
->setTokenCode(PhabricatorHash::weakDigest($key))
->setTokenCode($token_code)
->save();
unset($unguarded);
}
@@ -67,10 +77,10 @@ final class PhabricatorTOTPAuthFactor extends PhabricatorAuthFactor {
$e_code = true;
if ($request->getExists('totp')) {
$okay = self::verifyTOTPCode(
$user,
$okay = (bool)$this->getTimestepAtWhichResponseIsValid(
$this->getAllowedTimesteps($this->getCurrentTimestep()),
new PhutilOpaqueEnvelope($key),
$code);
(string)$code);
if ($okay) {
$config = $this->newConfigForUser($user)
@@ -140,77 +150,220 @@ final class PhabricatorTOTPAuthFactor extends PhabricatorAuthFactor {
}
protected function newIssuedChallenges(
PhabricatorAuthFactorConfig $config,
PhabricatorUser $viewer,
array $challenges) {
$current_step = $this->getCurrentTimestep();
// If we already issued a valid challenge, don't issue a new one.
if ($challenges) {
return array();
}
// Otherwise, generate a new challenge for the current timestep and compute
// the TTL.
// When computing the TTL, note that we accept codes within a certain
// window of the challenge timestep to account for clock skew and users
// needing time to enter codes.
// We don't want this challenge to expire until after all valid responses
// to it are no longer valid responses to any other challenge we might
// issue in the future. If the challenge expires too quickly, we may issue
// a new challenge which can accept the same TOTP code response.
// This means that we need to keep this challenge alive for double the
// window size: if we're currently at timestep 3, the user might respond
// with the code for timestep 5. This is valid, since timestep 5 is within
// the window for timestep 3.
// But the code for timestep 5 can be used to respond at timesteps 3, 4, 5,
// 6, and 7. To prevent any valid response to this challenge from being
// used again, we need to keep this challenge active until timestep 8.
$window_size = $this->getTimestepWindowSize();
$step_duration = $this->getTimestepDuration();
$ttl_steps = ($window_size * 2) + 1;
$ttl_seconds = ($ttl_steps * $step_duration);
return array(
$this->newChallenge($config, $viewer)
->setChallengeKey($current_step)
->setChallengeTTL(PhabricatorTime::getNow() + $ttl_seconds),
);
}
public function renderValidateFactorForm(
PhabricatorAuthFactorConfig $config,
AphrontFormView $form,
PhabricatorUser $viewer,
$validation_result) {
PhabricatorAuthFactorResult $result) {
if (!$validation_result) {
$validation_result = array();
$control = $this->newAutomaticControl($result);
if (!$control) {
$value = $result->getValue();
$error = $result->getErrorMessage();
$control = id(new PHUIFormNumberControl())
->setName($this->getParameterName($config, 'totpcode'))
->setDisableAutocomplete(true)
->setValue($value)
->setError($error);
}
$form->appendChild(
id(new PHUIFormNumberControl())
->setName($this->getParameterName($config, 'totpcode'))
->setLabel(pht('App Code'))
->setDisableAutocomplete(true)
->setCaption(pht('Factor Name: %s', $config->getFactorName()))
->setValue(idx($validation_result, 'value'))
->setError(idx($validation_result, 'error', true)));
$control
->setLabel(pht('App Code'))
->setCaption(pht('Factor Name: %s', $config->getFactorName()));
$form->appendChild($control);
}
public function processValidateFactorForm(
protected function newResultFromIssuedChallenges(
PhabricatorAuthFactorConfig $config,
PhabricatorUser $viewer,
AphrontRequest $request) {
array $challenges) {
$code = $request->getStr($this->getParameterName($config, 'totpcode'));
$key = new PhutilOpaqueEnvelope($config->getFactorSecret());
// If we've already issued a challenge at the current timestep or any
// nearby timestep, require that it was issued to the current session.
// This is defusing attacks where you (broadly) look at someone's phone
// and type the code in more quickly than they do.
$session_phid = $viewer->getSession()->getPHID();
$now = PhabricatorTime::getNow();
if (self::verifyTOTPCode($viewer, $key, $code)) {
return array(
'error' => null,
'value' => $code,
'valid' => true,
);
} else {
return array(
'error' => strlen($code) ? pht('Invalid') : pht('Required'),
'value' => $code,
'valid' => false,
);
$engine = $config->getSessionEngine();
$workflow_key = $engine->getWorkflowKey();
$current_timestep = $this->getCurrentTimestep();
foreach ($challenges as $challenge) {
$challenge_timestep = (int)$challenge->getChallengeKey();
$wait_duration = ($challenge->getChallengeTTL() - $now) + 1;
if ($challenge->getSessionPHID() !== $session_phid) {
return $this->newResult()
->setIsWait(true)
->setErrorMessage(
pht(
'This factor recently issued a challenge to a different login '.
'session. Wait %s second(s) for the code to cycle, then try '.
'again.',
new PhutilNumber($wait_duration)));
}
if ($challenge->getWorkflowKey() !== $workflow_key) {
return $this->newResult()
->setIsWait(true)
->setErrorMessage(
pht(
'This factor recently issued a challenge for a different '.
'workflow. Wait %s second(s) for the code to cycle, then try '.
'again.',
new PhutilNumber($wait_duration)));
}
// If the current realtime timestep isn't a valid response to the current
// challenge but the challenge hasn't expired yet, we're locking out
// the factor to prevent challenge windows from overlapping. Let the user
// know that they should wait for a new challenge.
$challenge_timesteps = $this->getAllowedTimesteps($challenge_timestep);
if (!isset($challenge_timesteps[$current_timestep])) {
return $this->newResult()
->setIsWait(true)
->setErrorMessage(
pht(
'This factor recently issued a challenge which has expired. '.
'A new challenge can not be issued yet. Wait %s second(s) for '.
'the code to cycle, then try again.',
new PhutilNumber($wait_duration)));
}
if ($challenge->getIsReusedChallenge()) {
return $this->newResult()
->setIsWait(true)
->setErrorMessage(
pht(
'You recently provided a response to this factor. Responses '.
'may not be reused. Wait %s second(s) for the code to cycle, '.
'then try again.',
new PhutilNumber($wait_duration)));
}
}
return null;
}
protected function newResultFromChallengeResponse(
PhabricatorAuthFactorConfig $config,
PhabricatorUser $viewer,
AphrontRequest $request,
array $challenges) {
$code = $request->getStr($this->getParameterName($config, 'totpcode'));
$result = $this->newResult()
->setValue($code);
// We expect to reach TOTP validation with exactly one valid challenge.
if (count($challenges) !== 1) {
throw new Exception(
pht(
'Reached TOTP challenge validation with an unexpected number of '.
'unexpired challenges (%d), expected exactly one.',
phutil_count($challenges)));
}
$challenge = head($challenges);
// If the client has already provided a valid answer to this challenge and
// submitted a token proving they answered it, we're all set.
if ($challenge->getIsAnsweredChallenge()) {
return $result->setAnsweredChallenge($challenge);
}
$challenge_timestep = (int)$challenge->getChallengeKey();
$current_timestep = $this->getCurrentTimestep();
$challenge_timesteps = $this->getAllowedTimesteps($challenge_timestep);
$current_timesteps = $this->getAllowedTimesteps($current_timestep);
// We require responses be both valid for the challenge and valid for the
// current timestep. A longer challenge TTL doesn't let you use older
// codes for a longer period of time.
$valid_timestep = $this->getTimestepAtWhichResponseIsValid(
array_intersect_key($challenge_timesteps, $current_timesteps),
new PhutilOpaqueEnvelope($config->getFactorSecret()),
(string)$code);
if ($valid_timestep) {
$now = PhabricatorTime::getNow();
$step_duration = $this->getTimestepDuration();
$step_window = $this->getTimestepWindowSize();
$ttl = $now + ($step_duration * $step_window);
$challenge
->setProperty('totp.timestep', $valid_timestep)
->markChallengeAsAnswered($ttl);
$result->setAnsweredChallenge($challenge);
} else {
if (strlen($code)) {
$error_message = pht('Invalid');
} else {
$error_message = pht('Required');
}
$result->setErrorMessage($error_message);
}
return $result;
}
public static function generateNewTOTPKey() {
return strtoupper(Filesystem::readRandomCharacters(32));
}
public static function verifyTOTPCode(
PhabricatorUser $user,
PhutilOpaqueEnvelope $key,
$code) {
$now = (int)(time() / 30);
// Allow the user to enter a code a few minutes away on either side, in
// case the server or client has some clock skew.
for ($offset = -2; $offset <= 2; $offset++) {
$real = self::getTOTPCode($key, $now + $offset);
if (phutil_hashes_are_identical($real, $code)) {
return true;
}
}
// TODO: After validating a code, this should mark it as used and prevent
// it from being reused.
return false;
}
public static function base32Decode($buf) {
$buf = strtoupper($buf);
@@ -303,4 +456,43 @@ final class PhabricatorTOTPAuthFactor extends PhabricatorAuthFactor {
$rows);
}
private function getTimestepDuration() {
return 30;
}
private function getCurrentTimestep() {
$duration = $this->getTimestepDuration();
return (int)(PhabricatorTime::getNow() / $duration);
}
private function getAllowedTimesteps($at_timestep) {
$window = $this->getTimestepWindowSize();
$range = range($at_timestep - $window, $at_timestep + $window);
return array_fuse($range);
}
private function getTimestepWindowSize() {
// The user is allowed to provide a code from the recent past or the
// near future to account for minor clock skew between the client
// and server, and the time it takes to actually enter a code.
return 2;
}
private function getTimestepAtWhichResponseIsValid(
array $timesteps,
PhutilOpaqueEnvelope $key,
$code) {
foreach ($timesteps as $timestep) {
$expect_code = self::getTOTPCode($key, $timestep);
if (phutil_hashes_are_identical($code, $expect_code)) {
return $timestep;
}
}
return null;
}
}

View File

@@ -0,0 +1,28 @@
<?php
final class PhabricatorAuthChallengeGarbageCollector
extends PhabricatorGarbageCollector {
const COLLECTORCONST = 'auth.challenges';
public function getCollectorName() {
return pht('Authentication Challenges');
}
public function hasAutomaticPolicy() {
return true;
}
protected function collectGarbage() {
$challenge_table = new PhabricatorAuthChallenge();
$conn = $challenge_table->establishConnection('w');
queryfx(
$conn,
'DELETE FROM %R WHERE challengeTTL < UNIX_TIMESTAMP() LIMIT 100',
$challenge_table);
return ($conn->getAffectedRows() == 100);
}
}

View File

@@ -0,0 +1,32 @@
<?php
final class PhabricatorAuthChallengePHIDType extends PhabricatorPHIDType {
const TYPECONST = 'CHAL';
public function getTypeName() {
return pht('Auth Challenge');
}
public function newObject() {
return new PhabricatorAuthChallenge();
}
public function getPHIDTypeApplicationClass() {
return 'PhabricatorAuthApplication';
}
protected function buildQueryForObjects(
PhabricatorObjectQuery $query,
array $phids) {
return new PhabricatorAuthChallengeQuery();
}
public function loadHandles(
PhabricatorHandleQuery $query,
array $handles,
array $objects) {
return;
}
}

View File

@@ -0,0 +1,34 @@
<?php
final class PhabricatorAuthSessionPHIDType
extends PhabricatorPHIDType {
const TYPECONST = 'SSSN';
public function getTypeName() {
return pht('Session');
}
public function newObject() {
return new PhabricatorAuthSession();
}
public function getPHIDTypeApplicationClass() {
return 'PhabricatorAuthApplication';
}
protected function buildQueryForObjects(
PhabricatorObjectQuery $query,
array $phids) {
return id(new PhabricatorAuthSessionQuery())
->withPHIDs($phids);
}
public function loadHandles(
PhabricatorHandleQuery $query,
array $handles,
array $objects) {
return;
}
}

View File

@@ -0,0 +1,99 @@
<?php
final class PhabricatorAuthChallengeQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $userPHIDs;
private $factorPHIDs;
private $challengeTTLMin;
private $challengeTTLMax;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withUserPHIDs(array $user_phids) {
$this->userPHIDs = $user_phids;
return $this;
}
public function withFactorPHIDs(array $factor_phids) {
$this->factorPHIDs = $factor_phids;
return $this;
}
public function withChallengeTTLBetween($challenge_min, $challenge_max) {
$this->challengeTTLMin = $challenge_min;
$this->challengeTTLMax = $challenge_max;
return $this;
}
public function newResultObject() {
return new PhabricatorAuthChallenge();
}
protected function loadPage() {
return $this->loadStandardPage($this->newResultObject());
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'phid IN (%Ls)',
$this->phids);
}
if ($this->userPHIDs !== null) {
$where[] = qsprintf(
$conn,
'userPHID IN (%Ls)',
$this->userPHIDs);
}
if ($this->factorPHIDs !== null) {
$where[] = qsprintf(
$conn,
'factorPHID IN (%Ls)',
$this->factorPHIDs);
}
if ($this->challengeTTLMin !== null) {
$where[] = qsprintf(
$conn,
'challengeTTL >= %d',
$this->challengeTTLMin);
}
if ($this->challengeTTLMax !== null) {
$where[] = qsprintf(
$conn,
'challengeTTL <= %d',
$this->challengeTTLMax);
}
return $where;
}
public function getQueryApplicationClass() {
return 'PhabricatorAuthApplication';
}
}

View File

@@ -4,6 +4,7 @@ final class PhabricatorAuthSessionQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $identityPHIDs;
private $sessionKeys;
private $sessionTypes;
@@ -28,19 +29,17 @@ final class PhabricatorAuthSessionQuery
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function newResultObject() {
return new PhabricatorAuthSession();
}
protected function loadPage() {
$table = new PhabricatorAuthSession();
$conn_r = $table->establishConnection('r');
$data = queryfx_all(
$conn_r,
'SELECT * FROM %T %Q %Q %Q',
$table->getTableName(),
$this->buildWhereClause($conn_r),
$this->buildOrderClause($conn_r),
$this->buildLimitClause($conn_r));
return $table->loadAllFromArray($data);
return $this->loadStandardPage($this->newResultObject());
}
protected function willFilterPage(array $sessions) {
@@ -65,8 +64,8 @@ final class PhabricatorAuthSessionQuery
return $sessions;
}
protected function buildWhereClause(AphrontDatabaseConnection $conn) {
$where = array();
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
@@ -75,6 +74,13 @@ final class PhabricatorAuthSessionQuery
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'phid IN (%Ls)',
$this->phids);
}
if ($this->identityPHIDs !== null) {
$where[] = qsprintf(
$conn,
@@ -85,7 +91,8 @@ final class PhabricatorAuthSessionQuery
if ($this->sessionKeys !== null) {
$hashes = array();
foreach ($this->sessionKeys as $session_key) {
$hashes[] = PhabricatorHash::weakDigest($session_key);
$hashes[] = PhabricatorAuthSession::newSessionDigest(
new PhutilOpaqueEnvelope($session_key));
}
$where[] = qsprintf(
$conn,
@@ -100,9 +107,7 @@ final class PhabricatorAuthSessionQuery
$this->sessionTypes);
}
$where[] = $this->buildPagingClause($conn);
return $this->formatWhereClause($conn, $where);
return $where;
}
public function getQueryApplicationClass() {

View File

@@ -0,0 +1,256 @@
<?php
final class PhabricatorAuthChallenge
extends PhabricatorAuthDAO
implements PhabricatorPolicyInterface {
protected $userPHID;
protected $factorPHID;
protected $sessionPHID;
protected $workflowKey;
protected $challengeKey;
protected $challengeTTL;
protected $responseDigest;
protected $responseTTL;
protected $isCompleted;
protected $properties = array();
private $responseToken;
const HTTPKEY = '__hisec.challenges__';
const TOKEN_DIGEST_KEY = 'auth.challenge.token';
public static function initializeNewChallenge() {
return id(new self())
->setIsCompleted(0);
}
public static function newHTTPParametersFromChallenges(array $challenges) {
assert_instances_of($challenges, __CLASS__);
$token_list = array();
foreach ($challenges as $challenge) {
$token = $challenge->getResponseToken();
if ($token) {
$token_list[] = sprintf(
'%s:%s',
$challenge->getPHID(),
$token->openEnvelope());
}
}
if (!$token_list) {
return array();
}
$token_list = implode(' ', $token_list);
return array(
self::HTTPKEY => $token_list,
);
}
public static function newChallengeResponsesFromRequest(
array $challenges,
AphrontRequest $request) {
assert_instances_of($challenges, __CLASS__);
$token_list = $request->getStr(self::HTTPKEY);
$token_list = explode(' ', $token_list);
$token_map = array();
foreach ($token_list as $token_element) {
$token_element = trim($token_element, ' ');
if (!strlen($token_element)) {
continue;
}
// NOTE: This error message is intentionally not printing the token to
// avoid disclosing it. As a result, it isn't terribly useful, but no
// normal user should ever end up here.
if (!preg_match('/^[^:]+:/', $token_element)) {
throw new Exception(
pht(
'This request included an improperly formatted MFA challenge '.
'token and can not be processed.'));
}
list($phid, $token) = explode(':', $token_element, 2);
if (isset($token_map[$phid])) {
throw new Exception(
pht(
'This request improperly specifies an MFA challenge token ("%s") '.
'multiple times and can not be processed.',
$phid));
}
$token_map[$phid] = new PhutilOpaqueEnvelope($token);
}
$challenges = mpull($challenges, null, 'getPHID');
$now = PhabricatorTime::getNow();
foreach ($challenges as $challenge_phid => $challenge) {
// If the response window has expired, don't attach the token.
if ($challenge->getResponseTTL() < $now) {
continue;
}
$token = idx($token_map, $challenge_phid);
if (!$token) {
continue;
}
$challenge->setResponseToken($token);
}
}
protected function getConfiguration() {
return array(
self::CONFIG_SERIALIZATION => array(
'properties' => self::SERIALIZATION_JSON,
),
self::CONFIG_AUX_PHID => true,
self::CONFIG_COLUMN_SCHEMA => array(
'challengeKey' => 'text255',
'challengeTTL' => 'epoch',
'workflowKey' => 'text255',
'responseDigest' => 'text255?',
'responseTTL' => 'epoch?',
'isCompleted' => 'bool',
),
self::CONFIG_KEY_SCHEMA => array(
'key_issued' => array(
'columns' => array('userPHID', 'challengeTTL'),
),
'key_collection' => array(
'columns' => array('challengeTTL'),
),
),
) + parent::getConfiguration();
}
public function getPHIDType() {
return PhabricatorAuthChallengePHIDType::TYPECONST;
}
public function getIsReusedChallenge() {
if ($this->getIsCompleted()) {
return true;
}
if (!$this->getIsAnsweredChallenge()) {
return false;
}
// If the challenge has been answered but the client has provided a token
// proving that they answered it, this is still a valid response.
if ($this->getResponseToken()) {
return false;
}
return true;
}
public function getIsAnsweredChallenge() {
return (bool)$this->getResponseDigest();
}
public function markChallengeAsAnswered($ttl) {
$token = Filesystem::readRandomCharacters(32);
$token = new PhutilOpaqueEnvelope($token);
return $this
->setResponseToken($token)
->setResponseTTL($ttl)
->save();
}
public function markChallengeAsCompleted() {
return $this
->setIsCompleted(true)
->save();
}
public function setResponseToken(PhutilOpaqueEnvelope $token) {
if (!$this->getUserPHID()) {
throw new PhutilInvalidStateException('setUserPHID');
}
if ($this->responseToken) {
throw new Exception(
pht(
'This challenge already has a response token; you can not '.
'set a new response token.'));
}
if (preg_match('/ /', $token->openEnvelope())) {
throw new Exception(
pht(
'The response token for this challenge is invalid: response '.
'tokens may not include spaces.'));
}
$digest = PhabricatorHash::digestWithNamedKey(
$token->openEnvelope(),
self::TOKEN_DIGEST_KEY);
if ($this->responseDigest !== null) {
if (!phutil_hashes_are_identical($digest, $this->responseDigest)) {
throw new Exception(
pht(
'Invalid response token for this challenge: token digest does '.
'not match stored digest.'));
}
} else {
$this->responseDigest = $digest;
}
$this->responseToken = $token;
return $this;
}
public function getResponseToken() {
return $this->responseToken;
}
public function setResponseDigest($value) {
throw new Exception(
pht(
'You can not set the response digest for a challenge directly. '.
'Instead, set a response token. A response digest will be computed '.
'automatically.'));
}
public function setProperty($key, $value) {
$this->properties[$key] = $value;
return $this;
}
public function getProperty($key, $default = null) {
return $this->properties[$key];
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
);
}
public function getPolicy($capability) {
return PhabricatorPolicies::POLICY_NOONE;
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return ($viewer->getPHID() === $this->getUserPHID());
}
}

View File

@@ -8,6 +8,8 @@ final class PhabricatorAuthFactorConfig extends PhabricatorAuthDAO {
protected $factorSecret;
protected $properties = array();
private $sessionEngine;
protected function getConfiguration() {
return array(
self::CONFIG_SERIALIZATION => array(
@@ -49,4 +51,17 @@ final class PhabricatorAuthFactorConfig extends PhabricatorAuthDAO {
return $impl;
}
public function setSessionEngine(PhabricatorAuthSessionEngine $engine) {
$this->sessionEngine = $engine;
return $this;
}
public function getSessionEngine() {
if (!$this->sessionEngine) {
throw new PhutilInvalidStateException('setSessionEngine');
}
return $this->sessionEngine;
}
}

View File

@@ -223,20 +223,8 @@ final class PhabricatorAuthPassword
return new PhabricatorAuthPasswordEditor();
}
public function getApplicationTransactionObject() {
return $this;
}
public function getApplicationTransactionTemplate() {
return new PhabricatorAuthPasswordTransaction();
}
public function willRenderTimeline(
PhabricatorApplicationTransactionView $timeline,
AphrontRequest $request) {
return $timeline;
}
}

View File

@@ -91,21 +91,10 @@ final class PhabricatorAuthProviderConfig
return new PhabricatorAuthProviderConfigEditor();
}
public function getApplicationTransactionObject() {
return $this;
}
public function getApplicationTransactionTemplate() {
return new PhabricatorAuthProviderConfigTransaction();
}
public function willRenderTimeline(
PhabricatorApplicationTransactionView $timeline,
AphrontRequest $request) {
return $timeline;
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */

View File

@@ -159,18 +159,8 @@ final class PhabricatorAuthSSHKey
return new PhabricatorAuthSSHKeyEditor();
}
public function getApplicationTransactionObject() {
return $this;
}
public function getApplicationTransactionTemplate() {
return new PhabricatorAuthSSHKeyTransaction();
}
public function willRenderTimeline(
PhabricatorApplicationTransactionView $timeline,
AphrontRequest $request) {
return $timeline;
}
}

View File

@@ -6,6 +6,8 @@ final class PhabricatorAuthSession extends PhabricatorAuthDAO
const TYPE_WEB = 'web';
const TYPE_CONDUIT = 'conduit';
const SESSION_DIGEST_KEY = 'session.digest';
protected $userPHID;
protected $type;
protected $sessionKey;
@@ -17,12 +19,19 @@ final class PhabricatorAuthSession extends PhabricatorAuthDAO
private $identityObject = self::ATTACHABLE;
public static function newSessionDigest(PhutilOpaqueEnvelope $session_token) {
return PhabricatorHash::digestWithNamedKey(
$session_token->openEnvelope(),
self::SESSION_DIGEST_KEY);
}
protected function getConfiguration() {
return array(
self::CONFIG_TIMESTAMPS => false,
self::CONFIG_AUX_PHID => true,
self::CONFIG_COLUMN_SCHEMA => array(
'type' => 'text32',
'sessionKey' => 'bytes40',
'sessionKey' => 'text64',
'sessionStart' => 'epoch',
'sessionExpires' => 'epoch',
'highSecurityUntil' => 'epoch?',
@@ -74,6 +83,10 @@ final class PhabricatorAuthSession extends PhabricatorAuthDAO
}
}
public function getPHIDType() {
return PhabricatorAuthSessionPHIDType::TYPECONST;
}
public function isHighSecuritySession() {
$until = $this->getHighSecurityUntil();

View File

@@ -51,6 +51,7 @@ final class PhabricatorBadgesCommentController
if ($request->isAjax() && $is_preview) {
return id(new PhabricatorApplicationTransactionResponse())
->setObject($badge)
->setViewer($viewer)
->setTransactions($xactions)
->setIsPreview($is_preview);

View File

@@ -125,21 +125,10 @@ final class PhabricatorBadgesBadge extends PhabricatorBadgesDAO
return new PhabricatorBadgesEditor();
}
public function getApplicationTransactionObject() {
return $this;
}
public function getApplicationTransactionTemplate() {
return new PhabricatorBadgesTransaction();
}
public function willRenderTimeline(
PhabricatorApplicationTransactionView $timeline,
AphrontRequest $request) {
return $timeline;
}
/* -( PhabricatorSubscribableInterface )----------------------------------- */

View File

@@ -649,18 +649,8 @@ abstract class PhabricatorApplication
return new PhabricatorApplicationEditor();
}
public function getApplicationTransactionObject() {
return $this;
}
public function getApplicationTransactionTemplate() {
return new PhabricatorApplicationApplicationTransaction();
}
public function willRenderTimeline(
PhabricatorApplicationTransactionView $timeline,
AphrontRequest $request) {
return $timeline;
}
}

View File

@@ -482,14 +482,14 @@ abstract class PhabricatorController extends AphrontController {
PhabricatorApplicationTransactionInterface $object,
PhabricatorApplicationTransactionQuery $query,
PhabricatorMarkupEngine $engine = null,
$render_data = array()) {
$view_data = array()) {
$viewer = $this->getRequest()->getUser();
$request = $this->getRequest();
$viewer = $this->getViewer();
$xaction = $object->getApplicationTransactionTemplate();
$view = $xaction->getApplicationTransactionViewObject();
$pager = id(new AphrontCursorPagerView())
->readFromRequest($this->getRequest())
->readFromRequest($request)
->setURI(new PhutilURI(
'/transactions/showolder/'.$object->getPHID().'/'));
@@ -500,6 +500,13 @@ abstract class PhabricatorController extends AphrontController {
->executeWithCursorPager($pager);
$xactions = array_reverse($xactions);
$timeline_engine = PhabricatorTimelineEngine::newForObject($object)
->setViewer($viewer)
->setTransactions($xactions)
->setViewData($view_data);
$view = $timeline_engine->buildTimelineView();
if ($engine) {
foreach ($xactions as $xaction) {
if ($xaction->getComment()) {
@@ -513,14 +520,9 @@ abstract class PhabricatorController extends AphrontController {
}
$timeline = $view
->setUser($viewer)
->setObjectPHID($object->getPHID())
->setTransactions($xactions)
->setPager($pager)
->setRenderData($render_data)
->setQuoteTargetID($this->getRequest()->getStr('quoteTargetID'))
->setQuoteRef($this->getRequest()->getStr('quoteRef'));
$object->willRenderTimeline($timeline, $this->getRequest());
return $timeline;
}

View File

@@ -1311,20 +1311,10 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
return new PhabricatorCalendarEventEditor();
}
public function getApplicationTransactionObject() {
return $this;
}
public function getApplicationTransactionTemplate() {
return new PhabricatorCalendarEventTransaction();
}
public function willRenderTimeline(
PhabricatorApplicationTransactionView $timeline,
AphrontRequest $request) {
return $timeline;
}
/* -( PhabricatorSubscribableInterface )----------------------------------- */

View File

@@ -166,21 +166,10 @@ final class PhabricatorCalendarExport extends PhabricatorCalendarDAO
return new PhabricatorCalendarExportEditor();
}
public function getApplicationTransactionObject() {
return $this;
}
public function getApplicationTransactionTemplate() {
return new PhabricatorCalendarExportTransaction();
}
public function willRenderTimeline(
PhabricatorApplicationTransactionView $timeline,
AphrontRequest $request) {
return $timeline;
}
/* -( PhabricatorDestructibleInterface )----------------------------------- */

View File

@@ -140,20 +140,10 @@ final class PhabricatorCalendarImport
return new PhabricatorCalendarImportEditor();
}
public function getApplicationTransactionObject() {
return $this;
}
public function getApplicationTransactionTemplate() {
return new PhabricatorCalendarImportTransaction();
}
public function willRenderTimeline(
PhabricatorApplicationTransactionView $timeline,
AphrontRequest $request) {
return $timeline;
}
public function newLogMessage($type, array $parameters) {
$parameters = array(
'type' => $type,

View File

@@ -16,18 +16,14 @@ final class PhabricatorConfigHistoryController
$xaction = $object->getApplicationTransactionTemplate();
$view = $xaction->getApplicationTransactionViewObject();
$timeline = $view
->setUser($viewer)
$timeline = id(new PhabricatorApplicationTransactionView())
->setViewer($viewer)
->setTransactions($xactions)
->setRenderAsFeed(true)
->setObjectPHID(PhabricatorPHIDConstants::PHID_VOID);
$timeline->setShouldTerminate(true);
$object->willRenderTimeline($timeline, $this->getRequest());
$title = pht('Settings History');
$header = $this->buildHeaderView($title);

View File

@@ -61,21 +61,10 @@ final class PhabricatorConfigEntry
return new PhabricatorConfigEditor();
}
public function getApplicationTransactionObject() {
return $this;
}
public function getApplicationTransactionTemplate() {
return new PhabricatorConfigTransaction();
}
public function willRenderTimeline(
PhabricatorApplicationTransactionView $timeline,
AphrontRequest $request) {
return $timeline;
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */

View File

@@ -311,19 +311,10 @@ final class ConpherenceThread extends ConpherenceDAO
return new ConpherenceEditor();
}
public function getApplicationTransactionObject() {
return $this;
}
public function getApplicationTransactionTemplate() {
return new ConpherenceTransaction();
}
public function willRenderTimeline(
PhabricatorApplicationTransactionView $timeline,
AphrontRequest $request) {
return $timeline;
}
/* -( PhabricatorNgramInterface )------------------------------------------ */

View File

@@ -95,20 +95,10 @@ final class PhabricatorCountdown extends PhabricatorCountdownDAO
return new PhabricatorCountdownEditor();
}
public function getApplicationTransactionObject() {
return $this;
}
public function getApplicationTransactionTemplate() {
return new PhabricatorCountdownTransaction();
}
public function willRenderTimeline(
PhabricatorApplicationTransactionView $timeline,
AphrontRequest $request) {
return $timeline;
}
/* -( PhabricatorTokenReceiverInterface )---------------------------------- */

View File

@@ -130,21 +130,10 @@ final class PhabricatorDashboard extends PhabricatorDashboardDAO
return new PhabricatorDashboardTransactionEditor();
}
public function getApplicationTransactionObject() {
return $this;
}
public function getApplicationTransactionTemplate() {
return new PhabricatorDashboardTransaction();
}
public function willRenderTimeline(
PhabricatorApplicationTransactionView $timeline,
AphrontRequest $request) {
return $timeline;
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */

View File

@@ -113,21 +113,10 @@ final class PhabricatorDashboardPanel
return new PhabricatorDashboardPanelTransactionEditor();
}
public function getApplicationTransactionObject() {
return $this;
}
public function getApplicationTransactionTemplate() {
return new PhabricatorDashboardPanelTransaction();
}
public function willRenderTimeline(
PhabricatorApplicationTransactionView $timeline,
AphrontRequest $request) {
return $timeline;
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */

View File

@@ -279,36 +279,17 @@ final class DifferentialRevisionEditEngine
$object);
$inlines = msort($inlines, 'getID');
foreach ($inlines as $inline) {
$xactions[] = id(new DifferentialTransaction())
->setTransactionType(DifferentialTransaction::TYPE_INLINE)
->attachComment($inline);
}
$editor = $object->getApplicationTransactionEditor()
->setActor($viewer);
$viewer_phid = $viewer->getPHID();
$viewer_is_author = ($object->getAuthorPHID() == $viewer_phid);
if ($viewer_is_author) {
$state_map = PhabricatorTransactions::getInlineStateMap();
$query_template = id(new DifferentialDiffInlineCommentQuery())
->withRevisionPHIDs(array($object->getPHID()));
$inlines = id(new DifferentialDiffInlineCommentQuery())
->setViewer($viewer)
->withRevisionPHIDs(array($object->getPHID()))
->withFixedStates(array_keys($state_map))
->execute();
if ($inlines) {
$old_value = mpull($inlines, 'getFixedState', 'getPHID');
$new_value = array();
foreach ($old_value as $key => $state) {
$new_value[$key] = $state_map[$state];
}
$xactions[] = id(new DifferentialTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_INLINESTATE)
->setIgnoreOnNoEffect(true)
->setOldValue($old_value)
->setNewValue($new_value);
}
}
$xactions = $editor->newAutomaticInlineTransactions(
$object,
$inlines,
DifferentialTransaction::TYPE_INLINE,
$query_template);
return $xactions;
}

View File

@@ -247,50 +247,16 @@ final class DifferentialTransactionEditor
case DifferentialTransaction::TYPE_INLINE:
$this->didExpandInlineState = true;
$actor_phid = $this->getActingAsPHID();
$author_phid = $object->getAuthorPHID();
$actor_is_author = ($actor_phid == $author_phid);
$query_template = id(new DifferentialDiffInlineCommentQuery())
->withRevisionPHIDs(array($object->getPHID()));
$state_map = PhabricatorTransactions::getInlineStateMap();
$state_xaction = $this->newInlineStateTransaction(
$object,
$query_template);
$query = id(new DifferentialDiffInlineCommentQuery())
->setViewer($this->getActor())
->withRevisionPHIDs(array($object->getPHID()))
->withFixedStates(array_keys($state_map));
$inlines = array();
// We're going to undraft any "done" marks on your own inlines.
$inlines[] = id(clone $query)
->withAuthorPHIDs(array($actor_phid))
->withHasTransaction(false)
->execute();
// If you're the author, we also undraft any "done" marks on other
// inlines.
if ($actor_is_author) {
$inlines[] = id(clone $query)
->withHasTransaction(true)
->execute();
if ($state_xaction) {
$results[] = $state_xaction;
}
$inlines = array_mergev($inlines);
if (!$inlines) {
break;
}
$old_value = mpull($inlines, 'getFixedState', 'getPHID');
$new_value = array();
foreach ($old_value as $key => $state) {
$new_value[$key] = $state_map[$state];
}
$results[] = id(new DifferentialTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_INLINESTATE)
->setIgnoreOnNoEffect(true)
->setOldValue($old_value)
->setNewValue($new_value);
break;
}
}
@@ -1367,9 +1333,9 @@ final class DifferentialTransactionEditor
foreach (array_chunk($sql, 256) as $chunk) {
queryfx(
$conn_w,
'INSERT INTO %T (repositoryID, pathID, epoch, revisionID) VALUES %Q',
'INSERT INTO %T (repositoryID, pathID, epoch, revisionID) VALUES %LQ',
$table->getTableName(),
implode(', ', $chunk));
$chunk);
}
}
@@ -1444,9 +1410,9 @@ final class DifferentialTransactionEditor
if ($sql) {
queryfx(
$conn_w,
'INSERT INTO %T (revisionID, type, hash) VALUES %Q',
'INSERT INTO %T (revisionID, type, hash) VALUES %LQ',
ArcanistDifferentialRevisionHash::TABLE_NAME,
implode(', ', $sql));
$sql);
}
}

View File

@@ -0,0 +1,78 @@
<?php
final class DifferentialRevisionTimelineEngine
extends PhabricatorTimelineEngine {
protected function newTimelineView() {
$viewer = $this->getViewer();
$xactions = $this->getTransactions();
$revision = $this->getObject();
$view_data = $this->getViewData();
if (!$view_data) {
$view_data = array();
}
$left = idx($view_data, 'left');
$right = idx($view_data, 'right');
$diffs = id(new DifferentialDiffQuery())
->setViewer($viewer)
->withIDs(array($left, $right))
->execute();
$diffs = mpull($diffs, null, 'getID');
$left_diff = $diffs[$left];
$right_diff = $diffs[$right];
$old_ids = idx($view_data, 'old');
$new_ids = idx($view_data, 'new');
$old_ids = array_filter(explode(',', $old_ids));
$new_ids = array_filter(explode(',', $new_ids));
$type_inline = DifferentialTransaction::TYPE_INLINE;
$changeset_ids = array_merge($old_ids, $new_ids);
$inlines = array();
foreach ($xactions as $xaction) {
if ($xaction->getTransactionType() == $type_inline) {
$inlines[] = $xaction->getComment();
$changeset_ids[] = $xaction->getComment()->getChangesetID();
}
}
if ($changeset_ids) {
$changesets = id(new DifferentialChangesetQuery())
->setViewer($viewer)
->withIDs($changeset_ids)
->execute();
$changesets = mpull($changesets, null, 'getID');
} else {
$changesets = array();
}
foreach ($inlines as $key => $inline) {
$inlines[$key] = DifferentialInlineComment::newFromModernComment(
$inline);
}
$query = id(new DifferentialInlineCommentQuery())
->needHidden(true)
->setViewer($viewer);
// NOTE: This is a bit sketchy: this method adjusts the inlines as a
// side effect, which means it will ultimately adjust the transaction
// comments and affect timeline rendering.
$query->adjustInlinesForChangesets(
$inlines,
array_select_keys($changesets, $old_ids),
array_select_keys($changesets, $new_ids),
$revision);
return id(new DifferentialTransactionView())
->setViewData($view_data)
->setChangesets($changesets)
->setRevision($revision)
->setLeftDiff($left_diff)
->setRightDiff($right_diff);
}
}

View File

@@ -699,21 +699,10 @@ final class DifferentialDiff
return new DifferentialDiffEditor();
}
public function getApplicationTransactionObject() {
return $this;
}
public function getApplicationTransactionTemplate() {
return new DifferentialDiffTransaction();
}
public function willRenderTimeline(
PhabricatorApplicationTransactionView $timeline,
AphrontRequest $request) {
return $timeline;
}
/* -( PhabricatorDestructibleInterface )----------------------------------- */

View File

@@ -11,6 +11,7 @@ final class DifferentialRevision extends DifferentialDAO
PhabricatorSubscribableInterface,
PhabricatorCustomFieldInterface,
PhabricatorApplicationTransactionInterface,
PhabricatorTimelineInterface,
PhabricatorMentionableInterface,
PhabricatorDestructibleInterface,
PhabricatorProjectInterface,
@@ -990,81 +991,10 @@ final class DifferentialRevision extends DifferentialDAO
return new DifferentialTransactionEditor();
}
public function getApplicationTransactionObject() {
return $this;
}
public function getApplicationTransactionTemplate() {
return new DifferentialTransaction();
}
public function willRenderTimeline(
PhabricatorApplicationTransactionView $timeline,
AphrontRequest $request) {
$viewer = $request->getViewer();
$render_data = $timeline->getRenderData();
$left = $request->getInt('left', idx($render_data, 'left'));
$right = $request->getInt('right', idx($render_data, 'right'));
$diffs = id(new DifferentialDiffQuery())
->setViewer($request->getUser())
->withIDs(array($left, $right))
->execute();
$diffs = mpull($diffs, null, 'getID');
$left_diff = $diffs[$left];
$right_diff = $diffs[$right];
$old_ids = $request->getStr('old', idx($render_data, 'old'));
$new_ids = $request->getStr('new', idx($render_data, 'new'));
$old_ids = array_filter(explode(',', $old_ids));
$new_ids = array_filter(explode(',', $new_ids));
$type_inline = DifferentialTransaction::TYPE_INLINE;
$changeset_ids = array_merge($old_ids, $new_ids);
$inlines = array();
foreach ($timeline->getTransactions() as $xaction) {
if ($xaction->getTransactionType() == $type_inline) {
$inlines[] = $xaction->getComment();
$changeset_ids[] = $xaction->getComment()->getChangesetID();
}
}
if ($changeset_ids) {
$changesets = id(new DifferentialChangesetQuery())
->setViewer($request->getUser())
->withIDs($changeset_ids)
->execute();
$changesets = mpull($changesets, null, 'getID');
} else {
$changesets = array();
}
foreach ($inlines as $key => $inline) {
$inlines[$key] = DifferentialInlineComment::newFromModernComment(
$inline);
}
$query = id(new DifferentialInlineCommentQuery())
->needHidden(true)
->setViewer($viewer);
// NOTE: This is a bit sketchy: this method adjusts the inlines as a
// side effect, which means it will ultimately adjust the transaction
// comments and affect timeline rendering.
$query->adjustInlinesForChangesets(
$inlines,
array_select_keys($changesets, $old_ids),
array_select_keys($changesets, $new_ids),
$this);
return $timeline
->setChangesets($changesets)
->setRevision($this)
->setLeftDiff($left_diff)
->setRightDiff($right_diff);
}
/* -( PhabricatorDestructibleInterface )----------------------------------- */
@@ -1206,4 +1136,13 @@ final class DifferentialRevision extends DifferentialDAO
return new DifferentialRevisionDraftEngine();
}
/* -( PhabricatorTimelineInterface )--------------------------------------- */
public function newTimelineEngine() {
return new DifferentialRevisionTimelineEngine();
}
}

View File

@@ -65,10 +65,6 @@ final class DifferentialTransaction
return new DifferentialTransactionComment();
}
public function getApplicationTransactionViewObject() {
return new DifferentialTransactionView();
}
public function shouldHide() {
$old = $this->getOldValue();
$new = $this->getNewValue();

View File

@@ -45,6 +45,7 @@ final class DiffusionCommitController extends DiffusionController {
->withIdentifiers(array($commit_identifier))
->needCommitData(true)
->needAuditRequests(true)
->needAuditAuthority(array($viewer))
->setLimit(100)
->needIdentities(true)
->execute();
@@ -111,7 +112,6 @@ final class DiffusionCommitController extends DiffusionController {
}
$audit_requests = $commit->getAudits();
$commit->loadAndAttachAuditAuthority($viewer);
$commit_data = $commit->getCommitData();
$is_foreign = $commit_data->getCommitDetail('foreign-svn-stub');
@@ -740,8 +740,6 @@ final class DiffusionCommitController extends DiffusionController {
$commit,
new PhabricatorAuditTransactionQuery());
$commit->willRenderTimeline($timeline, $this->getRequest());
$timeline->setQuoteRef($commit->getMonogram());
return $timeline;

View File

@@ -43,9 +43,12 @@ final class DiffusionCommitEditEngine
}
protected function newObjectQuery() {
$viewer = $this->getViewer();
return id(new DiffusionCommitQuery())
->needCommitData(true)
->needAuditRequests(true);
->needAuditRequests(true)
->needAuditAuthority(array($viewer));
}
protected function getEditorURI() {
@@ -167,36 +170,17 @@ final class DiffusionCommitEditEngine
$raw = true);
$inlines = msort($inlines, 'getID');
foreach ($inlines as $inline) {
$xactions[] = $object->getApplicationTransactionTemplate()
->setTransactionType(PhabricatorAuditActionConstants::INLINE)
->attachComment($inline);
}
$editor = $object->getApplicationTransactionEditor()
->setActor($viewer);
$viewer_phid = $viewer->getPHID();
$viewer_is_author = ($object->getAuthorPHID() == $viewer_phid);
if ($viewer_is_author) {
$state_map = PhabricatorTransactions::getInlineStateMap();
$query_template = id(new DiffusionDiffInlineCommentQuery())
->withCommitPHIDs(array($object->getPHID()));
$inlines = id(new DiffusionDiffInlineCommentQuery())
->setViewer($viewer)
->withCommitPHIDs(array($object->getPHID()))
->withFixedStates(array_keys($state_map))
->execute();
if ($inlines) {
$old_value = mpull($inlines, 'getFixedState', 'getPHID');
$new_value = array();
foreach ($old_value as $key => $state) {
$new_value[$key] = $state_map[$state];
}
$xactions[] = $object->getApplicationTransactionTemplate()
->setTransactionType(PhabricatorTransactions::TYPE_INLINESTATE)
->setIgnoreOnNoEffect(true)
->setOldValue($old_value)
->setNewValue($new_value);
}
}
$xactions = $editor->newAutomaticInlineTransactions(
$object,
$inlines,
PhabricatorAuditActionConstants::INLINE,
$query_template);
return $xactions;
}

View File

@@ -212,11 +212,8 @@ final class DiffusionRepositoryEditEngine
->setObject($object)
->execute();
$track_value = $object->getDetail('branch-filter', array());
$track_value = array_keys($track_value);
$autoclose_value = $object->getDetail('close-commits-filter', array());
$autoclose_value = array_keys($autoclose_value);
$track_value = $object->getTrackOnlyRules();
$autoclose_value = $object->getAutocloseOnlyRules();
$automation_instructions = pht(
"Configure **Repository Automation** to allow Phabricator to ".

View File

@@ -0,0 +1,30 @@
<?php
final class DiffusionCommitTimelineEngine
extends PhabricatorTimelineEngine {
protected function newTimelineView() {
$xactions = $this->getTransactions();
$path_ids = array();
foreach ($xactions as $xaction) {
if ($xaction->hasComment()) {
$path_id = $xaction->getComment()->getPathID();
if ($path_id) {
$path_ids[] = $path_id;
}
}
}
$path_map = array();
if ($path_ids) {
$path_map = id(new DiffusionPathQuery())
->withPathIDs($path_ids)
->execute();
$path_map = ipull($path_map, 'path', 'id');
}
return id(new PhabricatorAuditTransactionView())
->setPathMap($path_map);
}
}

View File

@@ -23,8 +23,8 @@ final class DiffusionRepositoryBranchesManagementPanel
$has_any =
$repository->getDetail('default-branch') ||
$repository->getDetail('branch-filter') ||
$repository->getDetail('close-commits-filter');
$repository->getTrackOnlyRules() ||
$repository->getAutocloseOnlyRules();
if ($has_any) {
return 'fa-code-fork';
@@ -74,17 +74,21 @@ final class DiffusionRepositoryBranchesManagementPanel
->setViewer($viewer);
$default_branch = nonempty(
$repository->getHumanReadableDetail('default-branch'),
$repository->getDetail('default-branch'),
phutil_tag('em', array(), $repository->getDefaultBranch()));
$view->addProperty(pht('Default Branch'), $default_branch);
$track_only_rules = $repository->getTrackOnlyRules();
$track_only_rules = implode(', ', $track_only_rules);
$track_only = nonempty(
$repository->getHumanReadableDetail('branch-filter', array()),
$track_only_rules,
phutil_tag('em', array(), pht('Track All Branches')));
$view->addProperty(pht('Track Only'), $track_only);
$autoclose_rules = $repository->getAutocloseOnlyRules();
$autoclose_rules = implode(', ', $autoclose_rules);
$autoclose_only = nonempty(
$repository->getHumanReadableDetail('close-commits-filter', array()),
$autoclose_rules,
phutil_tag('em', array(), pht('Autoclose On All Branches')));
$autoclose_disabled = false;

View File

@@ -68,7 +68,7 @@ final class DiffusionRepositorySubversionManagementPanel
->setViewer($viewer);
$default_branch = nonempty(
$repository->getHumanReadableDetail('svn-subpath'),
$repository->getDetail('svn-subpath'),
phutil_tag('em', array(), pht('Import Entire Repository')));
$view->addProperty(pht('Import Only'), $default_branch);

View File

@@ -17,6 +17,7 @@ final class DiffusionCommitQuery
private $unreachable;
private $needAuditRequests;
private $needAuditAuthority;
private $auditIDs;
private $auditorPHIDs;
private $epochMin;
@@ -121,6 +122,12 @@ final class DiffusionCommitQuery
return $this;
}
public function needAuditAuthority(array $users) {
assert_instances_of($users, 'PhabricatorUser');
$this->needAuditAuthority = $users;
return $this;
}
public function withAuditIDs(array $ids) {
$this->auditIDs = $ids;
return $this;
@@ -231,14 +238,27 @@ final class DiffusionCommitQuery
}
if (count($subqueries) > 1) {
foreach ($subqueries as $key => $subquery) {
$subqueries[$key] = '('.$subquery.')';
$unions = null;
foreach ($subqueries as $subquery) {
if (!$unions) {
$unions = qsprintf(
$conn,
'(%Q)',
$subquery);
continue;
}
$unions = qsprintf(
$conn,
'%Q UNION DISTINCT (%Q)',
$unions,
$subquery);
}
$query = qsprintf(
$conn,
'%Q %Q %Q',
implode(' UNION DISTINCT ', $subqueries),
$unions,
$this->buildOrderClause($conn, true),
$this->buildLimitClause($conn));
} else {
@@ -423,6 +443,72 @@ final class DiffusionCommitQuery
$commits);
}
if ($this->needAuditAuthority) {
$authority_users = $this->needAuditAuthority;
// NOTE: This isn't very efficient since we're running two queries per
// user, but there's currently no way to figure out authority for
// multiple users in one query. Today, we only ever request authority for
// a single user and single commit, so this has no practical impact.
// NOTE: We're querying with the viewership of query viewer, not the
// actual users. If the viewer can't see a project or package, they
// won't be able to see who has authority on it. This is safer than
// showing them true authority, and should never matter today, but it
// also doesn't seem like a significant disclosure and might be
// reasonable to adjust later if it causes something weird or confusing
// to happen.
$authority_map = array();
foreach ($authority_users as $authority_user) {
$authority_phid = $authority_user->getPHID();
if (!$authority_phid) {
continue;
}
$result_phids = array();
// Users have authority over themselves.
$result_phids[] = $authority_phid;
// Users have authority over packages they own.
$owned_packages = id(new PhabricatorOwnersPackageQuery())
->setViewer($viewer)
->withAuthorityPHIDs(array($authority_phid))
->execute();
foreach ($owned_packages as $package) {
$result_phids[] = $package->getPHID();
}
// Users have authority over projects they're members of.
$projects = id(new PhabricatorProjectQuery())
->setViewer($viewer)
->withMemberPHIDs(array($authority_phid))
->execute();
foreach ($projects as $project) {
$result_phids[] = $project->getPHID();
}
$result_phids = array_fuse($result_phids);
foreach ($commits as $commit) {
$attach_phids = $result_phids;
// NOTE: When modifying your own commits, you act only on behalf of
// yourself, not your packages or projects. The idea here is that you
// can't accept your own commits. In the future, this might change or
// depend on configuration.
$author_phid = $commit->getAuthorPHID();
if ($author_phid == $authority_phid) {
$attach_phids = array($author_phid);
$attach_phids = array_fuse($attach_phids);
}
$commit->attachAuditAuthority($authority_user, $attach_phids);
}
}
}
return $commits;
}

View File

@@ -43,12 +43,13 @@ final class DiffusionPathChangeQuery extends Phobject {
$conn_r = $repository->establishConnection('r');
$limit = '';
if ($this->limit) {
$limit = qsprintf(
$conn_r,
'LIMIT %d',
$this->limit + 1);
} else {
$limit = qsprintf($conn_r, '');
}
$raw_changes = queryfx_all(

View File

@@ -143,21 +143,10 @@ final class DivinerLiveBook extends DivinerDAO
return new DivinerLiveBookEditor();
}
public function getApplicationTransactionObject() {
return $this;
}
public function getApplicationTransactionTemplate() {
return new DivinerLiveBookTransaction();
}
public function willRenderTimeline(
PhabricatorApplicationTransactionView $timeline,
AphrontRequest $request) {
return $timeline;
}
/* -( PhabricatorFulltextInterface )--------------------------------------- */

View File

@@ -34,6 +34,7 @@ final class DrydockConsoleController extends DrydockController {
->setHeader(pht('Blueprints'))
->setImageIcon('fa-map-o')
->setHref($this->getApplicationURI('blueprint/'))
->setClickable(true)
->addAttribute(
pht(
'Configure blueprints so Drydock can build resources, like '.
@@ -44,6 +45,7 @@ final class DrydockConsoleController extends DrydockController {
->setHeader(pht('Resources'))
->setImageIcon('fa-map')
->setHref($this->getApplicationURI('resource/'))
->setClickable(true)
->addAttribute(
pht('View and manage resources Drydock has built, like hosts.')));
@@ -52,6 +54,7 @@ final class DrydockConsoleController extends DrydockController {
->setHeader(pht('Leases'))
->setImageIcon('fa-link')
->setHref($this->getApplicationURI('lease/'))
->setClickable(true)
->addAttribute(pht('Manage leases on resources.')));
$menu->addItem(
@@ -59,6 +62,7 @@ final class DrydockConsoleController extends DrydockController {
->setHeader(pht('Repository Operations'))
->setImageIcon('fa-fighter-jet')
->setHref($this->getApplicationURI('operation/'))
->setClickable(true)
->addAttribute(pht('Review the repository operation queue.')));
$crumbs = $this->buildApplicationCrumbs();

View File

@@ -295,21 +295,10 @@ final class DrydockBlueprint extends DrydockDAO
return new DrydockBlueprintEditor();
}
public function getApplicationTransactionObject() {
return $this;
}
public function getApplicationTransactionTemplate() {
return new DrydockBlueprintTransaction();
}
public function willRenderTimeline(
PhabricatorApplicationTransactionView $timeline,
AphrontRequest $request) {
return $timeline;
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */

View File

@@ -12,11 +12,8 @@ final class PhabricatorFactUpdateIterator extends PhutilBufferedIterator {
private $position;
private $ignoreUpdatesDuration = 15;
private $set;
public function __construct(LiskDAO $object) {
$this->set = new LiskDAOSet();
$this->object = $object->putInSet($this->set);
$this->object = $object;
}
public function setPosition($position) {
@@ -41,8 +38,6 @@ final class PhabricatorFactUpdateIterator extends PhutilBufferedIterator {
}
protected function loadPage() {
$this->set->clearSet();
if ($this->object->hasProperty('dateModified')) {
if ($this->cursor) {
list($after_epoch, $after_id) = explode(':', $this->cursor);

View File

@@ -34,7 +34,15 @@ final class PhabricatorFeedQuery
}
protected function willFilterPage(array $data) {
return PhabricatorFeedStory::loadAllFromRows($data, $this->getViewer());
$stories = PhabricatorFeedStory::loadAllFromRows($data, $this->getViewer());
foreach ($stories as $key => $story) {
if (!$story->isVisibleInFeed()) {
unset($stories[$key]);
}
}
return $stories;
}
protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) {

View File

@@ -418,6 +418,14 @@ abstract class PhabricatorFeedStory
return array();
}
public function isVisibleInFeed() {
return true;
}
public function isVisibleInNotifications() {
return true;
}
/* -( PhabricatorPolicyInterface Implementation )-------------------------- */

View File

@@ -1544,21 +1544,10 @@ final class PhabricatorFile extends PhabricatorFileDAO
return new PhabricatorFileEditor();
}
public function getApplicationTransactionObject() {
return $this;
}
public function getApplicationTransactionTemplate() {
return new PhabricatorFileTransaction();
}
public function willRenderTimeline(
PhabricatorApplicationTransactionView $timeline,
AphrontRequest $request) {
return $timeline;
}
/* -( PhabricatorPolicyInterface Implementation )-------------------------- */

View File

@@ -110,19 +110,8 @@ final class FundBacker extends FundDAO
return new FundBackerEditor();
}
public function getApplicationTransactionObject() {
return $this;
}
public function getApplicationTransactionTemplate() {
return new FundBackerTransaction();
}
public function willRenderTimeline(
PhabricatorApplicationTransactionView $timeline,
AphrontRequest $request) {
return $timeline;
}
}

View File

@@ -160,21 +160,10 @@ final class FundInitiative extends FundDAO
return new FundInitiativeEditor();
}
public function getApplicationTransactionObject() {
return $this;
}
public function getApplicationTransactionTemplate() {
return new FundInitiativeTransaction();
}
public function willRenderTimeline(
PhabricatorApplicationTransactionView $timeline,
AphrontRequest $request) {
return $timeline;
}
/* -( PhabricatorSubscribableInterface )----------------------------------- */

View File

@@ -96,9 +96,7 @@ abstract class HarbormasterBuildableEngine
$publishable = $this->getPublishableObject();
$editor = $this->newEditor();
$editor->applyTransactions(
$publishable->getApplicationTransactionObject(),
$xactions);
$editor->applyTransactions($publishable, $xactions);
}
public function getAuthorIdentity() {

View File

@@ -283,21 +283,10 @@ final class HarbormasterBuildable
return new HarbormasterBuildableTransactionEditor();
}
public function getApplicationTransactionObject() {
return $this;
}
public function getApplicationTransactionTemplate() {
return new HarbormasterBuildableTransaction();
}
public function willRenderTimeline(
PhabricatorApplicationTransactionView $timeline,
AphrontRequest $request) {
return $timeline;
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */

View File

@@ -393,21 +393,10 @@ final class HarbormasterBuild extends HarbormasterDAO
return new HarbormasterBuildTransactionEditor();
}
public function getApplicationTransactionObject() {
return $this;
}
public function getApplicationTransactionTemplate() {
return new HarbormasterBuildTransaction();
}
public function willRenderTimeline(
PhabricatorApplicationTransactionView $timeline,
AphrontRequest $request) {
return $timeline;
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */

View File

@@ -136,22 +136,10 @@ final class HarbormasterBuildPlan extends HarbormasterDAO
return new HarbormasterBuildPlanEditor();
}
public function getApplicationTransactionObject() {
return $this;
}
public function getApplicationTransactionTemplate() {
return new HarbormasterBuildPlanTransaction();
}
public function willRenderTimeline(
PhabricatorApplicationTransactionView $timeline,
AphrontRequest $request) {
return $timeline;
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */

View File

@@ -121,21 +121,10 @@ final class HarbormasterBuildStep extends HarbormasterDAO
return new HarbormasterBuildStepEditor();
}
public function getApplicationTransactionObject() {
return $this;
}
public function getApplicationTransactionTemplate() {
return new HarbormasterBuildStepTransaction();
}
public function willRenderTimeline(
PhabricatorApplicationTransactionView $timeline,
AphrontRequest $request) {
return $timeline;
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */

View File

@@ -243,9 +243,9 @@ final class HeraldEngine extends Phobject {
}
queryfx(
$conn_w,
'INSERT IGNORE INTO %T (phid, ruleID) VALUES %Q',
'INSERT IGNORE INTO %T (phid, ruleID) VALUES %LQ',
HeraldRule::TABLE_RULE_APPLIED,
implode(', ', $sql));
$sql);
}
}
}

View File

@@ -318,21 +318,10 @@ final class HeraldRule extends HeraldDAO
return new HeraldRuleEditor();
}
public function getApplicationTransactionObject() {
return $this;
}
public function getApplicationTransactionTemplate() {
return new HeraldRuleTransaction();
}
public function willRenderTimeline(
PhabricatorApplicationTransactionView $timeline,
AphrontRequest $request) {
return $timeline;
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */

View File

@@ -200,20 +200,10 @@ final class HeraldWebhook
return new HeraldWebhookEditor();
}
public function getApplicationTransactionObject() {
return $this;
}
public function getApplicationTransactionTemplate() {
return new HeraldWebhookTransaction();
}
public function willRenderTimeline(
PhabricatorApplicationTransactionView $timeline,
AphrontRequest $request) {
return $timeline;
}
/* -( PhabricatorDestructibleInterface )----------------------------------- */

View File

@@ -151,16 +151,6 @@ final class LegalpadDocumentSignController extends LegalpadController {
$errors = array();
$hisec_token = null;
if ($request->isFormOrHisecPost() && !$has_signed) {
// Require two-factor auth to sign legal documents.
if ($viewer->isLoggedIn()) {
$hisec_token = id(new PhabricatorAuthSessionEngine())
->requireHighSecurityToken(
$viewer,
$request,
$document->getURI());
}
list($form_data, $errors, $field_errors) = $this->readSignatureForm(
$document,
$request);
@@ -187,6 +177,20 @@ final class LegalpadDocumentSignController extends LegalpadController {
$signature->setVerified($verified);
if (!$errors) {
// Require MFA to sign legal documents.
if ($viewer->isLoggedIn()) {
$workflow_key = sprintf(
'legalpad.sign(%s)',
$document->getPHID());
$hisec_token = id(new PhabricatorAuthSessionEngine())
->setWorkflowKey($workflow_key)
->requireHighSecurityToken(
$viewer,
$request,
$document->getURI());
}
$signature->save();
// If the viewer is logged in, signing for themselves, send them to

View File

@@ -209,21 +209,10 @@ final class LegalpadDocument extends LegalpadDAO
return new LegalpadDocumentEditor();
}
public function getApplicationTransactionObject() {
return $this;
}
public function getApplicationTransactionTemplate() {
return new LegalpadTransaction();
}
public function willRenderTimeline(
PhabricatorApplicationTransactionView $timeline,
AphrontRequest $request) {
return $timeline;
}
/* -( PhabricatorDestructibleInterface )----------------------------------- */

View File

@@ -14,10 +14,6 @@ final class LegalpadTransaction extends PhabricatorModularTransaction {
return new LegalpadTransactionComment();
}
public function getApplicationTransactionViewObject() {
return new LegalpadTransactionView();
}
public function getBaseTransactionClass() {
return 'LegalpadDocumentTransactionType';
}

Some files were not shown because too many files have changed in this diff Show More