mirror of
https://we.phorge.it/source/phorge.git
synced 2025-01-09 14:21:02 +01:00
(stable) Promote 2018 Week 51
This commit is contained in:
commit
a3acd3450d
191 changed files with 3107 additions and 1924 deletions
|
@ -9,8 +9,8 @@ return array(
|
|||
'names' => array(
|
||||
'conpherence.pkg.css' => 'e68cf1fa',
|
||||
'conpherence.pkg.js' => '15191c65',
|
||||
'core.pkg.css' => '9d1148a4',
|
||||
'core.pkg.js' => '4bde473b',
|
||||
'core.pkg.css' => '47535fd5',
|
||||
'core.pkg.js' => 'bd89cb1d',
|
||||
'differential.pkg.css' => '06dc617c',
|
||||
'differential.pkg.js' => 'ef0b989b',
|
||||
'diffusion.pkg.css' => 'a2d17c7d',
|
||||
|
@ -151,7 +151,7 @@ return array(
|
|||
'rsrc/css/phui/phui-document.css' => 'c4ac41f9',
|
||||
'rsrc/css/phui/phui-feed-story.css' => '44a9c8e9',
|
||||
'rsrc/css/phui/phui-fontkit.css' => '1320ed01',
|
||||
'rsrc/css/phui/phui-form-view.css' => '2f43fae7',
|
||||
'rsrc/css/phui/phui-form-view.css' => 'b04e08d9',
|
||||
'rsrc/css/phui/phui-form.css' => '7aaa04e3',
|
||||
'rsrc/css/phui/phui-head-thing.css' => 'fd311e5f',
|
||||
'rsrc/css/phui/phui-header-view.css' => '1ba8b707',
|
||||
|
@ -425,7 +425,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',
|
||||
|
@ -639,7 +639,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',
|
||||
|
@ -819,7 +819,7 @@ return array(
|
|||
'phui-font-icon-base-css' => '870a7360',
|
||||
'phui-fontkit-css' => '1320ed01',
|
||||
'phui-form-css' => '7aaa04e3',
|
||||
'phui-form-view-css' => '2f43fae7',
|
||||
'phui-form-view-css' => 'b04e08d9',
|
||||
'phui-head-thing-view-css' => 'fd311e5f',
|
||||
'phui-header-view-css' => '1ba8b707',
|
||||
'phui-hovercard' => '1bd28176',
|
||||
|
@ -950,6 +950,12 @@ return array(
|
|||
'javelin-install',
|
||||
'phuix-button-view',
|
||||
),
|
||||
'0e1eca96' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-stratcom',
|
||||
'javelin-dom',
|
||||
'phabricator-busy',
|
||||
),
|
||||
'0f764c35' => array(
|
||||
'javelin-install',
|
||||
'javelin-util',
|
||||
|
@ -1581,12 +1587,6 @@ return array(
|
|||
'8e1baf68' => array(
|
||||
'phui-button-css',
|
||||
),
|
||||
'8f29b364' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-stratcom',
|
||||
'javelin-dom',
|
||||
'phabricator-busy',
|
||||
),
|
||||
'8ff5e24c' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-stratcom',
|
||||
|
|
12
resources/sql/autopatches/20181213.auth.06.challenge.sql
Normal file
12
resources/sql/autopatches/20181213.auth.06.challenge.sql
Normal 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};
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE {$NAMESPACE}_auth.auth_challenge
|
||||
ADD workflowKey VARCHAR(255) NOT NULL COLLATE {$COLLATE_TEXT};
|
2
resources/sql/autopatches/20181217.auth.01.digest.sql
Normal file
2
resources/sql/autopatches/20181217.auth.01.digest.sql
Normal file
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE {$NAMESPACE}_auth.auth_challenge
|
||||
ADD responseDigest VARCHAR(255) COLLATE {$COLLATE_TEXT};
|
2
resources/sql/autopatches/20181217.auth.02.ttl.sql
Normal file
2
resources/sql/autopatches/20181217.auth.02.ttl.sql
Normal file
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE {$NAMESPACE}_auth.auth_challenge
|
||||
ADD responseTTL INT UNSIGNED;
|
2
resources/sql/autopatches/20181217.auth.03.completed.sql
Normal file
2
resources/sql/autopatches/20181217.auth.03.completed.sql
Normal file
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE {$NAMESPACE}_auth.auth_challenge
|
||||
ADD isCompleted BOOL NOT NULL;
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE {$NAMESPACE}_pholio.pholio_image
|
||||
ADD authorPHID VARBINARY(64) NOT NULL;
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE {$NAMESPACE}_pholio.pholio_image
|
||||
ADD mockPHID VARBINARY(64);
|
|
@ -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']);
|
||||
}
|
2
resources/sql/autopatches/20181219.pholio.03.imageid.sql
Normal file
2
resources/sql/autopatches/20181219.pholio.03.imageid.sql
Normal file
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE {$NAMESPACE}_pholio.pholio_image
|
||||
DROP mockID;
|
28
resources/sql/autopatches/20181220.pholio.01.mailkey.php
Normal file
28
resources/sql/autopatches/20181220.pholio.01.mailkey.php
Normal 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());
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE {$NAMESPACE}_pholio.pholio_mock
|
||||
DROP mailKey;
|
|
@ -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";
|
||||
|
|
|
@ -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,7 +1632,6 @@ 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',
|
||||
'LiskDAOTestCase' => 'infrastructure/storage/lisk/__tests__/LiskDAOTestCase.php',
|
||||
|
@ -1730,6 +1731,7 @@ phutil_register_library_map(array(
|
|||
'ManiphestTaskListController' => 'applications/maniphest/controller/ManiphestTaskListController.php',
|
||||
'ManiphestTaskListHTTPParameterType' => 'applications/maniphest/httpparametertype/ManiphestTaskListHTTPParameterType.php',
|
||||
'ManiphestTaskListView' => 'applications/maniphest/view/ManiphestTaskListView.php',
|
||||
'ManiphestTaskMFAEngine' => 'applications/maniphest/engine/ManiphestTaskMFAEngine.php',
|
||||
'ManiphestTaskMailReceiver' => 'applications/maniphest/mail/ManiphestTaskMailReceiver.php',
|
||||
'ManiphestTaskMergeInRelationship' => 'applications/maniphest/relationship/ManiphestTaskMergeInRelationship.php',
|
||||
'ManiphestTaskMergedFromTransaction' => 'applications/maniphest/xaction/ManiphestTaskMergedFromTransaction.php',
|
||||
|
@ -1944,6 +1946,7 @@ phutil_register_library_map(array(
|
|||
'PHUIFormInsetView' => 'view/form/PHUIFormInsetView.php',
|
||||
'PHUIFormLayoutView' => 'view/form/PHUIFormLayoutView.php',
|
||||
'PHUIFormNumberControl' => 'view/form/control/PHUIFormNumberControl.php',
|
||||
'PHUIFormTimerControl' => 'view/form/control/PHUIFormTimerControl.php',
|
||||
'PHUIHandleListView' => 'applications/phid/view/PHUIHandleListView.php',
|
||||
'PHUIHandleTagListView' => 'applications/phid/view/PHUIHandleTagListView.php',
|
||||
'PHUIHandleView' => 'applications/phid/view/PHUIHandleView.php',
|
||||
|
@ -2187,6 +2190,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',
|
||||
|
@ -2198,6 +2205,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',
|
||||
|
@ -2225,6 +2233,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorAuthLoginController' => 'applications/auth/controller/PhabricatorAuthLoginController.php',
|
||||
'PhabricatorAuthLoginHandler' => 'applications/auth/handler/PhabricatorAuthLoginHandler.php',
|
||||
'PhabricatorAuthLogoutConduitAPIMethod' => 'applications/auth/conduit/PhabricatorAuthLogoutConduitAPIMethod.php',
|
||||
'PhabricatorAuthMFAEditEngineExtension' => 'applications/auth/engineextension/PhabricatorAuthMFAEditEngineExtension.php',
|
||||
'PhabricatorAuthMainMenuBarExtension' => 'applications/auth/extension/PhabricatorAuthMainMenuBarExtension.php',
|
||||
'PhabricatorAuthManagementCachePKCS8Workflow' => 'applications/auth/management/PhabricatorAuthManagementCachePKCS8Workflow.php',
|
||||
'PhabricatorAuthManagementLDAPWorkflow' => 'applications/auth/management/PhabricatorAuthManagementLDAPWorkflow.php',
|
||||
|
@ -2666,6 +2675,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorConfigProxySource' => 'infrastructure/env/PhabricatorConfigProxySource.php',
|
||||
'PhabricatorConfigPurgeCacheController' => 'applications/config/controller/PhabricatorConfigPurgeCacheController.php',
|
||||
'PhabricatorConfigRegexOptionType' => 'applications/config/custom/PhabricatorConfigRegexOptionType.php',
|
||||
'PhabricatorConfigRemarkupRule' => 'infrastructure/markup/rule/PhabricatorConfigRemarkupRule.php',
|
||||
'PhabricatorConfigRequestExceptionHandlerModule' => 'applications/config/module/PhabricatorConfigRequestExceptionHandlerModule.php',
|
||||
'PhabricatorConfigResponse' => 'applications/config/response/PhabricatorConfigResponse.php',
|
||||
'PhabricatorConfigSchemaQuery' => 'applications/config/schema/PhabricatorConfigSchemaQuery.php',
|
||||
|
@ -2969,6 +2979,8 @@ phutil_register_library_map(array(
|
|||
'PhabricatorEditEngineListController' => 'applications/transactions/controller/PhabricatorEditEngineListController.php',
|
||||
'PhabricatorEditEngineLock' => 'applications/transactions/editengine/PhabricatorEditEngineLock.php',
|
||||
'PhabricatorEditEngineLockableInterface' => 'applications/transactions/editengine/PhabricatorEditEngineLockableInterface.php',
|
||||
'PhabricatorEditEngineMFAEngine' => 'applications/transactions/editengine/PhabricatorEditEngineMFAEngine.php',
|
||||
'PhabricatorEditEngineMFAInterface' => 'applications/transactions/editengine/PhabricatorEditEngineMFAInterface.php',
|
||||
'PhabricatorEditEnginePointsCommentAction' => 'applications/transactions/commentaction/PhabricatorEditEnginePointsCommentAction.php',
|
||||
'PhabricatorEditEngineProfileMenuItem' => 'applications/search/menuitem/PhabricatorEditEngineProfileMenuItem.php',
|
||||
'PhabricatorEditEngineQuery' => 'applications/transactions/query/PhabricatorEditEngineQuery.php',
|
||||
|
@ -4428,6 +4440,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',
|
||||
|
@ -4527,6 +4540,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',
|
||||
|
@ -4617,6 +4632,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',
|
||||
|
@ -4865,6 +4881,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',
|
||||
|
@ -5985,6 +6002,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorSubscribableInterface',
|
||||
'PhabricatorCustomFieldInterface',
|
||||
'PhabricatorApplicationTransactionInterface',
|
||||
'PhabricatorTimelineInterface',
|
||||
'PhabricatorMentionableInterface',
|
||||
'PhabricatorDestructibleInterface',
|
||||
'PhabricatorProjectInterface',
|
||||
|
@ -6066,6 +6084,7 @@ phutil_register_library_map(array(
|
|||
'DifferentialRevisionSummaryTransaction' => 'DifferentialRevisionTransactionType',
|
||||
'DifferentialRevisionTestPlanHeraldField' => 'DifferentialRevisionHeraldField',
|
||||
'DifferentialRevisionTestPlanTransaction' => 'DifferentialRevisionTransactionType',
|
||||
'DifferentialRevisionTimelineEngine' => 'PhabricatorTimelineEngine',
|
||||
'DifferentialRevisionTitleHeraldField' => 'DifferentialRevisionHeraldField',
|
||||
'DifferentialRevisionTitleTransaction' => 'DifferentialRevisionTransactionType',
|
||||
'DifferentialRevisionTransactionType' => 'PhabricatorModularTransactionType',
|
||||
|
@ -6188,6 +6207,7 @@ phutil_register_library_map(array(
|
|||
'DiffusionCommitSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
|
||||
'DiffusionCommitStateTransaction' => 'DiffusionCommitTransactionType',
|
||||
'DiffusionCommitTagsController' => 'DiffusionController',
|
||||
'DiffusionCommitTimelineEngine' => 'PhabricatorTimelineEngine',
|
||||
'DiffusionCommitTransactionType' => 'PhabricatorModularTransactionType',
|
||||
'DiffusionCommitVerifyTransaction' => 'DiffusionCommitAuditTransaction',
|
||||
'DiffusionCompareController' => 'DiffusionController',
|
||||
|
@ -7195,7 +7215,6 @@ phutil_register_library_map(array(
|
|||
'LegalpadTransaction' => 'PhabricatorModularTransaction',
|
||||
'LegalpadTransactionComment' => 'PhabricatorApplicationTransactionComment',
|
||||
'LegalpadTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
||||
'LegalpadTransactionView' => 'PhabricatorApplicationTransactionView',
|
||||
'LiskChunkTestCase' => 'PhabricatorTestCase',
|
||||
'LiskDAO' => array(
|
||||
'Phobject',
|
||||
|
@ -7283,6 +7302,7 @@ phutil_register_library_map(array(
|
|||
'DoorkeeperBridgedObjectInterface',
|
||||
'PhabricatorEditEngineSubtypeInterface',
|
||||
'PhabricatorEditEngineLockableInterface',
|
||||
'PhabricatorEditEngineMFAInterface',
|
||||
),
|
||||
'ManiphestTaskAssignHeraldAction' => 'HeraldAction',
|
||||
'ManiphestTaskAssignOtherHeraldAction' => 'ManiphestTaskAssignHeraldAction',
|
||||
|
@ -7321,6 +7341,7 @@ phutil_register_library_map(array(
|
|||
'ManiphestTaskListController' => 'ManiphestController',
|
||||
'ManiphestTaskListHTTPParameterType' => 'AphrontListHTTPParameterType',
|
||||
'ManiphestTaskListView' => 'ManiphestView',
|
||||
'ManiphestTaskMFAEngine' => 'PhabricatorEditEngineMFAEngine',
|
||||
'ManiphestTaskMailReceiver' => 'PhabricatorObjectMailReceiver',
|
||||
'ManiphestTaskMergeInRelationship' => 'ManiphestTaskRelationship',
|
||||
'ManiphestTaskMergedFromTransaction' => 'ManiphestTaskTransactionType',
|
||||
|
@ -7554,6 +7575,7 @@ phutil_register_library_map(array(
|
|||
'PHUIFormInsetView' => 'AphrontView',
|
||||
'PHUIFormLayoutView' => 'AphrontView',
|
||||
'PHUIFormNumberControl' => 'AphrontFormControl',
|
||||
'PHUIFormTimerControl' => 'AphrontFormControl',
|
||||
'PHUIHandleListView' => 'AphrontTagView',
|
||||
'PHUIHandleTagListView' => 'AphrontTagView',
|
||||
'PHUIHandleView' => 'AphrontView',
|
||||
|
@ -7822,6 +7844,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',
|
||||
|
@ -7833,6 +7862,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorAuthEditController' => 'PhabricatorAuthProviderConfigController',
|
||||
'PhabricatorAuthFactor' => 'Phobject',
|
||||
'PhabricatorAuthFactorConfig' => 'PhabricatorAuthDAO',
|
||||
'PhabricatorAuthFactorResult' => 'Phobject',
|
||||
'PhabricatorAuthFactorTestCase' => 'PhabricatorTestCase',
|
||||
'PhabricatorAuthFinishController' => 'PhabricatorAuthController',
|
||||
'PhabricatorAuthHMACKey' => 'PhabricatorAuthDAO',
|
||||
|
@ -7863,6 +7893,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorAuthLoginController' => 'PhabricatorAuthController',
|
||||
'PhabricatorAuthLoginHandler' => 'Phobject',
|
||||
'PhabricatorAuthLogoutConduitAPIMethod' => 'PhabricatorAuthConduitAPIMethod',
|
||||
'PhabricatorAuthMFAEditEngineExtension' => 'PhabricatorEditEngineExtension',
|
||||
'PhabricatorAuthMainMenuBarExtension' => 'PhabricatorMainMenuBarExtension',
|
||||
'PhabricatorAuthManagementCachePKCS8Workflow' => 'PhabricatorAuthManagementWorkflow',
|
||||
'PhabricatorAuthManagementLDAPWorkflow' => 'PhabricatorAuthManagementWorkflow',
|
||||
|
@ -8388,6 +8419,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorConfigProxySource' => 'PhabricatorConfigSource',
|
||||
'PhabricatorConfigPurgeCacheController' => 'PhabricatorConfigController',
|
||||
'PhabricatorConfigRegexOptionType' => 'PhabricatorConfigJSONOptionType',
|
||||
'PhabricatorConfigRemarkupRule' => 'PhutilRemarkupRule',
|
||||
'PhabricatorConfigRequestExceptionHandlerModule' => 'PhabricatorConfigModule',
|
||||
'PhabricatorConfigResponse' => 'AphrontStandaloneHTMLResponse',
|
||||
'PhabricatorConfigSchemaQuery' => 'Phobject',
|
||||
|
@ -8729,6 +8761,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorEditEngineExtensionModule' => 'PhabricatorConfigModule',
|
||||
'PhabricatorEditEngineListController' => 'PhabricatorEditEngineController',
|
||||
'PhabricatorEditEngineLock' => 'Phobject',
|
||||
'PhabricatorEditEngineMFAEngine' => 'Phobject',
|
||||
'PhabricatorEditEnginePointsCommentAction' => 'PhabricatorEditEngineCommentAction',
|
||||
'PhabricatorEditEngineProfileMenuItem' => 'PhabricatorProfileMenuItem',
|
||||
'PhabricatorEditEngineQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||
|
@ -10065,6 +10098,7 @@ phutil_register_library_map(array(
|
|||
'HarbormasterBuildkiteBuildableInterface',
|
||||
'PhabricatorCustomFieldInterface',
|
||||
'PhabricatorApplicationTransactionInterface',
|
||||
'PhabricatorTimelineInterface',
|
||||
'PhabricatorFulltextInterface',
|
||||
'PhabricatorFerretInterface',
|
||||
'PhabricatorConduitResultInterface',
|
||||
|
@ -10455,6 +10489,7 @@ phutil_register_library_map(array(
|
|||
'AphrontResponseProducerInterface',
|
||||
),
|
||||
'PhabricatorStandardSelectCustomFieldDatasource' => 'PhabricatorTypeaheadDatasource',
|
||||
'PhabricatorStandardTimelineEngine' => 'PhabricatorTimelineEngine',
|
||||
'PhabricatorStaticEditField' => 'PhabricatorEditField',
|
||||
'PhabricatorStatusController' => 'PhabricatorController',
|
||||
'PhabricatorStatusUIExample' => 'PhabricatorUIExample',
|
||||
|
@ -10553,6 +10588,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorTimeFormatSetting' => 'PhabricatorSelectSetting',
|
||||
'PhabricatorTimeGuard' => 'Phobject',
|
||||
'PhabricatorTimeTestCase' => 'PhabricatorTestCase',
|
||||
'PhabricatorTimelineEngine' => 'Phobject',
|
||||
'PhabricatorTimezoneIgnoreOffsetSetting' => 'PhabricatorInternalSetting',
|
||||
'PhabricatorTimezoneSetting' => 'PhabricatorOptionGroupSetting',
|
||||
'PhabricatorTimezoneSetupCheck' => 'PhabricatorSetupCheck',
|
||||
|
@ -10670,6 +10706,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorUserEditorTestCase' => 'PhabricatorTestCase',
|
||||
'PhabricatorUserEmail' => 'PhabricatorUserDAO',
|
||||
'PhabricatorUserEmailTestCase' => 'PhabricatorTestCase',
|
||||
'PhabricatorUserEmpowerTransaction' => 'PhabricatorUserTransactionType',
|
||||
'PhabricatorUserFerretEngine' => 'PhabricatorFerretEngine',
|
||||
'PhabricatorUserFulltextEngine' => 'PhabricatorFulltextEngine',
|
||||
'PhabricatorUserIconField' => 'PhabricatorUserCustomField',
|
||||
|
@ -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',
|
||||
|
|
|
@ -29,40 +29,80 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
$is_upgrade = $throwable->getIsSessionUpgrade();
|
||||
|
||||
if ($is_upgrade) {
|
||||
$title = pht('Enter High Security');
|
||||
} else {
|
||||
$title = pht('Provide MFA Credentials');
|
||||
}
|
||||
|
||||
if ($is_wait) {
|
||||
$submit = pht('Wait Patiently');
|
||||
} else if ($is_upgrade) {
|
||||
$submit = pht('Enter High Security');
|
||||
} else {
|
||||
$submit = pht('Continue');
|
||||
}
|
||||
|
||||
$dialog = id(new AphrontDialogView())
|
||||
->setUser($viewer)
|
||||
->setTitle(pht('Entering High Security'))
|
||||
->setTitle($title)
|
||||
->setShortTitle(pht('Security Checkpoint'))
|
||||
->setWidth(AphrontDialogView::WIDTH_FORM)
|
||||
->addHiddenInput(AphrontRequest::TYPE_HISEC, true)
|
||||
->setErrors(
|
||||
array(
|
||||
pht(
|
||||
'You are taking an action which requires you to enter '.
|
||||
'high security.'),
|
||||
))
|
||||
->appendParagraph(
|
||||
pht(
|
||||
'High security mode helps protect your account from security '.
|
||||
'threats, like session theft or someone messing with your stuff '.
|
||||
'while you\'re grabbing a coffee. To enter high security mode, '.
|
||||
'confirm your credentials.'))
|
||||
->appendChild($form->buildLayoutView())
|
||||
->appendParagraph(
|
||||
pht(
|
||||
'Your account will remain in high security mode for a short '.
|
||||
'period of time. When you are finished taking sensitive '.
|
||||
'actions, you should leave high security.'))
|
||||
->setSubmitURI($request->getPath())
|
||||
->addCancelButton($throwable->getCancelURI())
|
||||
->addSubmitButton(pht('Enter High Security'));
|
||||
->addSubmitButton($submit);
|
||||
|
||||
$form_layout = $form->buildLayoutView();
|
||||
|
||||
if ($is_upgrade) {
|
||||
$dialog
|
||||
->setErrors(
|
||||
array(
|
||||
pht(
|
||||
'You are taking an action which requires you to enter '.
|
||||
'high security.'),
|
||||
))
|
||||
->appendParagraph(
|
||||
pht(
|
||||
'High security mode helps protect your account from security '.
|
||||
'threats, like session theft or someone messing with your stuff '.
|
||||
'while you\'re grabbing a coffee. To enter high security mode, '.
|
||||
'confirm your credentials.'))
|
||||
->appendChild($form_layout)
|
||||
->appendParagraph(
|
||||
pht(
|
||||
'Your account will remain in high security mode for a short '.
|
||||
'period of time. When you are finished taking sensitive '.
|
||||
'actions, you should leave high security.'));
|
||||
} else {
|
||||
$dialog
|
||||
->setErrors(
|
||||
array(
|
||||
pht(
|
||||
'You are taking an action which requires you to provide '.
|
||||
'multi-factor credentials.'),
|
||||
))
|
||||
->appendChild($form_layout);
|
||||
}
|
||||
|
||||
$request_parameters = $request->getPassthroughRequestParameters(
|
||||
$respect_quicksand = true);
|
||||
|
|
|
@ -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 )----------------------------------- */
|
||||
|
||||
|
|
|
@ -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 )----------------------------------- */
|
||||
|
||||
|
|
|
@ -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 )---------------------------------- */
|
||||
|
||||
|
|
|
@ -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 )----------------------------------- */
|
||||
|
||||
|
|
|
@ -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 )----------------------------------------- */
|
||||
|
||||
|
|
|
@ -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 )----------------------------------- */
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -32,10 +32,6 @@ final class PhabricatorAuditTransaction
|
|||
return new PhabricatorAuditTransactionComment();
|
||||
}
|
||||
|
||||
public function getApplicationTransactionViewObject() {
|
||||
return new PhabricatorAuditTransactionView();
|
||||
}
|
||||
|
||||
public function getRemarkupBlocks() {
|
||||
$blocks = parent::getRemarkupBlocks();
|
||||
|
||||
|
|
|
@ -45,45 +45,39 @@ abstract class PhabricatorAuthController extends PhabricatorController {
|
|||
* event and do something else if they prefer.
|
||||
*
|
||||
* @param PhabricatorUser User to log the viewer in as.
|
||||
* @param bool True to issue a full session immediately, bypassing MFA.
|
||||
* @return AphrontResponse Response which continues the login process.
|
||||
*/
|
||||
protected function loginUser(PhabricatorUser $user) {
|
||||
protected function loginUser(
|
||||
PhabricatorUser $user,
|
||||
$force_full_session = false) {
|
||||
|
||||
$response = $this->buildLoginValidateResponse($user);
|
||||
$session_type = PhabricatorAuthSession::TYPE_WEB;
|
||||
|
||||
$event_type = PhabricatorEventType::TYPE_AUTH_WILLLOGINUSER;
|
||||
$event_data = array(
|
||||
'user' => $user,
|
||||
'type' => $session_type,
|
||||
'response' => $response,
|
||||
'shouldLogin' => true,
|
||||
);
|
||||
|
||||
$event = id(new PhabricatorEvent($event_type, $event_data))
|
||||
->setUser($user);
|
||||
PhutilEventEngine::dispatchEvent($event);
|
||||
|
||||
$should_login = $event->getValue('shouldLogin');
|
||||
if ($should_login) {
|
||||
$session_key = id(new PhabricatorAuthSessionEngine())
|
||||
->establishSession($session_type, $user->getPHID(), $partial = true);
|
||||
|
||||
// NOTE: We allow disabled users to login and roadblock them later, so
|
||||
// there's no check for users being disabled here.
|
||||
|
||||
$request = $this->getRequest();
|
||||
$request->setCookie(
|
||||
PhabricatorCookies::COOKIE_USERNAME,
|
||||
$user->getUsername());
|
||||
$request->setCookie(
|
||||
PhabricatorCookies::COOKIE_SESSION,
|
||||
$session_key);
|
||||
|
||||
$this->clearRegistrationCookies();
|
||||
if ($force_full_session) {
|
||||
$partial_session = false;
|
||||
} else {
|
||||
$partial_session = true;
|
||||
}
|
||||
|
||||
return $event->getValue('response');
|
||||
$session_key = id(new PhabricatorAuthSessionEngine())
|
||||
->establishSession($session_type, $user->getPHID(), $partial_session);
|
||||
|
||||
// NOTE: We allow disabled users to login and roadblock them later, so
|
||||
// there's no check for users being disabled here.
|
||||
|
||||
$request = $this->getRequest();
|
||||
$request->setCookie(
|
||||
PhabricatorCookies::COOKIE_USERNAME,
|
||||
$user->getUsername());
|
||||
$request->setCookie(
|
||||
PhabricatorCookies::COOKIE_SESSION,
|
||||
$session_key);
|
||||
|
||||
$this->clearRegistrationCookies();
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
protected function clearRegistrationCookies() {
|
||||
|
|
|
@ -152,7 +152,12 @@ final class PhabricatorAuthOneTimeLoginController
|
|||
|
||||
PhabricatorCookies::setNextURICookie($request, $next, $force = true);
|
||||
|
||||
return $this->loginUser($target_user);
|
||||
$force_full_session = false;
|
||||
if ($link_type === PhabricatorAuthSessionEngine::ONETIME_RECOVER) {
|
||||
$force_full_session = $token->getShouldForceFullSession();
|
||||
}
|
||||
|
||||
return $this->loginUser($target_user, $force_full_session);
|
||||
}
|
||||
|
||||
// NOTE: We need to CSRF here so attackers can't generate an email link,
|
||||
|
|
|
@ -416,7 +416,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());
|
||||
|
|
|
@ -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.
|
||||
|
@ -193,26 +213,7 @@ final class PhabricatorAuthSessionEngine extends Phobject {
|
|||
|
||||
$session = id(new PhabricatorAuthSession())->loadFromArray($session_dict);
|
||||
|
||||
$ttl = PhabricatorAuthSession::getSessionTypeTTL($session_type);
|
||||
|
||||
// If more than 20% of the time on this session has been used, refresh the
|
||||
// TTL back up to the full duration. The idea here is that sessions are
|
||||
// good forever if used regularly, but get GC'd when they fall out of use.
|
||||
|
||||
// NOTE: If we begin rotating session keys when extending sessions, the
|
||||
// CSRF code needs to be updated so CSRF tokens survive session rotation.
|
||||
|
||||
if (time() + (0.80 * $ttl) > $session->getSessionExpires()) {
|
||||
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
||||
$conn_w = $session_table->establishConnection('w');
|
||||
queryfx(
|
||||
$conn_w,
|
||||
'UPDATE %T SET sessionExpires = UNIX_TIMESTAMP() + %d WHERE id = %d',
|
||||
$session->getTableName(),
|
||||
$ttl,
|
||||
$session->getID());
|
||||
unset($unguarded);
|
||||
}
|
||||
$this->extendSession($session);
|
||||
|
||||
// TODO: Remove this, see T13225.
|
||||
if ($is_weak) {
|
||||
|
@ -264,7 +265,9 @@ final class PhabricatorAuthSessionEngine extends Phobject {
|
|||
$conn_w = $session_table->establishConnection('w');
|
||||
|
||||
// This has a side effect of validating the session type.
|
||||
$session_ttl = PhabricatorAuthSession::getSessionTypeTTL($session_type);
|
||||
$session_ttl = PhabricatorAuthSession::getSessionTypeTTL(
|
||||
$session_type,
|
||||
$partial);
|
||||
|
||||
$digest_key = PhabricatorAuthSession::newSessionDigest(
|
||||
new PhutilOpaqueEnvelope($session_key));
|
||||
|
@ -431,7 +434,7 @@ final class PhabricatorAuthSessionEngine extends Phobject {
|
|||
$viewer,
|
||||
$request,
|
||||
$cancel_uri,
|
||||
false,
|
||||
$jump_into_hisec,
|
||||
true);
|
||||
}
|
||||
|
||||
|
@ -473,6 +476,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(
|
||||
|
@ -480,38 +487,129 @@ 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)) {
|
||||
|
||||
// Limit factor verification rates to prevent brute force attacks.
|
||||
PhabricatorSystemActionEngine::willTakeAction(
|
||||
array($viewer->getPHID()),
|
||||
new PhabricatorAuthTryFactorAction(),
|
||||
1);
|
||||
|
||||
$ok = true;
|
||||
$any_attempt = false;
|
||||
foreach ($factors as $factor) {
|
||||
$id = $factor->getID();
|
||||
$impl = $factor->requireImplementation();
|
||||
|
||||
$validation_results[$id] = $impl->processValidateFactorForm(
|
||||
$factor,
|
||||
$viewer,
|
||||
$request);
|
||||
|
||||
if (!$impl->isFactorValid($factor, $validation_results[$id])) {
|
||||
$ok = false;
|
||||
if ($impl->getRequestHasChallengeResponse($factor, $request)) {
|
||||
$any_attempt = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($ok) {
|
||||
// Give the user a credit back for a successful factor verification.
|
||||
if ($any_attempt) {
|
||||
PhabricatorSystemActionEngine::willTakeAction(
|
||||
array($viewer->getPHID()),
|
||||
new PhabricatorAuthTryFactorAction(),
|
||||
-1);
|
||||
1);
|
||||
}
|
||||
|
||||
foreach ($factors as $factor) {
|
||||
$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_result = $impl->getResultFromChallengeResponse(
|
||||
$factor,
|
||||
$viewer,
|
||||
$request,
|
||||
$issued_challenges);
|
||||
|
||||
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.
|
||||
if ($any_attempt) {
|
||||
PhabricatorSystemActionEngine::willTakeAction(
|
||||
array($viewer->getPHID()),
|
||||
new PhabricatorAuthTryFactorAction(),
|
||||
-1);
|
||||
}
|
||||
|
||||
if ($session->getIsPartial() && !$jump_into_hisec) {
|
||||
// If we have a partial session and are not jumping directly into
|
||||
|
@ -555,8 +653,21 @@ 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)
|
||||
->setIsSessionUpgrade($upgrade_session)
|
||||
->setFactors($factors)
|
||||
->setFactorValidationResults($validation_results);
|
||||
}
|
||||
|
@ -595,21 +706,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;
|
||||
}
|
||||
|
||||
|
@ -748,24 +876,28 @@ final class PhabricatorAuthSessionEngine extends Phobject {
|
|||
* link is used.
|
||||
* @param string Optional context string for the URI. This is purely cosmetic
|
||||
* and used only to customize workflow and error messages.
|
||||
* @param bool True to generate a URI which forces an immediate upgrade to
|
||||
* a full session, bypassing MFA and other login checks.
|
||||
* @return string Login URI.
|
||||
* @task onetime
|
||||
*/
|
||||
public function getOneTimeLoginURI(
|
||||
PhabricatorUser $user,
|
||||
PhabricatorUserEmail $email = null,
|
||||
$type = self::ONETIME_RESET) {
|
||||
$type = self::ONETIME_RESET,
|
||||
$force_full_session = false) {
|
||||
|
||||
$key = Filesystem::readRandomCharacters(32);
|
||||
$key_hash = $this->getOneTimeLoginKeyHash($user, $email, $key);
|
||||
$onetime_type = PhabricatorAuthOneTimeLoginTemporaryTokenType::TOKENTYPE;
|
||||
|
||||
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
||||
id(new PhabricatorAuthTemporaryToken())
|
||||
$token = id(new PhabricatorAuthTemporaryToken())
|
||||
->setTokenResource($user->getPHID())
|
||||
->setTokenType($onetime_type)
|
||||
->setTokenExpires(time() + phutil_units('1 day in seconds'))
|
||||
->setTokenCode($key_hash)
|
||||
->setShouldForceFullSession($force_full_session)
|
||||
->save();
|
||||
unset($unguarded);
|
||||
|
||||
|
@ -937,4 +1069,40 @@ final class PhabricatorAuthSessionEngine extends Phobject {
|
|||
}
|
||||
}
|
||||
|
||||
private function extendSession(PhabricatorAuthSession $session) {
|
||||
$is_partial = $session->getIsPartial();
|
||||
|
||||
// Don't extend partial sessions. You have a relatively short window to
|
||||
// upgrade into a full session, and your session expires otherwise.
|
||||
if ($is_partial) {
|
||||
return;
|
||||
}
|
||||
|
||||
$session_type = $session->getType();
|
||||
|
||||
$ttl = PhabricatorAuthSession::getSessionTypeTTL(
|
||||
$session_type,
|
||||
$session->getIsPartial());
|
||||
|
||||
// If more than 20% of the time on this session has been used, refresh the
|
||||
// TTL back up to the full duration. The idea here is that sessions are
|
||||
// good forever if used regularly, but get GC'd when they fall out of use.
|
||||
|
||||
$now = PhabricatorTime::getNow();
|
||||
if ($now + (0.80 * $ttl) <= $session->getSessionExpires()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
||||
queryfx(
|
||||
$session->establishConnection('w'),
|
||||
'UPDATE %R SET sessionExpires = UNIX_TIMESTAMP() + %d
|
||||
WHERE id = %d',
|
||||
$session,
|
||||
$ttl,
|
||||
$session->getID());
|
||||
unset($unguarded);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorAuthMFAEditEngineExtension
|
||||
extends PhabricatorEditEngineExtension {
|
||||
|
||||
const EXTENSIONKEY = 'auth.mfa';
|
||||
const FIELDKEY = 'mfa';
|
||||
|
||||
public function getExtensionPriority() {
|
||||
return 12000;
|
||||
}
|
||||
|
||||
public function isExtensionEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getExtensionName() {
|
||||
return pht('MFA');
|
||||
}
|
||||
|
||||
public function supportsObject(
|
||||
PhabricatorEditEngine $engine,
|
||||
PhabricatorApplicationTransactionInterface $object) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function buildCustomEditFields(
|
||||
PhabricatorEditEngine $engine,
|
||||
PhabricatorApplicationTransactionInterface $object) {
|
||||
|
||||
$mfa_type = PhabricatorTransactions::TYPE_MFA;
|
||||
|
||||
$viewer = $engine->getViewer();
|
||||
|
||||
$mfa_field = id(new PhabricatorApplyEditField())
|
||||
->setViewer($viewer)
|
||||
->setKey(self::FIELDKEY)
|
||||
->setLabel(pht('MFA'))
|
||||
->setIsFormField(false)
|
||||
->setCommentActionLabel(pht('Sign With MFA'))
|
||||
->setCommentActionOrder(12000)
|
||||
->setActionDescription(
|
||||
pht('You will be prompted to provide MFA when you submit.'))
|
||||
->setDescription(pht('Sign this transaction group with MFA.'))
|
||||
->setTransactionType($mfa_type);
|
||||
|
||||
return array(
|
||||
$mfa_field,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -5,8 +5,10 @@ final class PhabricatorAuthHighSecurityRequiredException extends Exception {
|
|||
private $cancelURI;
|
||||
private $factors;
|
||||
private $factorValidationResults;
|
||||
private $isSessionUpgrade;
|
||||
|
||||
public function setFactorValidationResults(array $results) {
|
||||
assert_instances_of($results, 'PhabricatorAuthFactorResult');
|
||||
$this->factorValidationResults = $results;
|
||||
return $this;
|
||||
}
|
||||
|
@ -34,4 +36,13 @@ final class PhabricatorAuthHighSecurityRequiredException extends Exception {
|
|||
return $this->cancelURI;
|
||||
}
|
||||
|
||||
public function setIsSessionUpgrade($is_upgrade) {
|
||||
$this->isSessionUpgrade = $is_upgrade;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getIsSessionUpgrade() {
|
||||
return $this->isSessionUpgrade;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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,181 @@ 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());
|
||||
}
|
||||
|
||||
abstract public function getRequestHasChallengeResponse(
|
||||
PhabricatorAuthFactorConfig $config,
|
||||
AphrontRequest $response);
|
||||
|
||||
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();
|
||||
|
||||
$icon = id(new PHUIIconView())
|
||||
->setIcon('fa-clock-o', 'red');
|
||||
|
||||
return id(new PHUIFormTimerControl())
|
||||
->setIcon($icon)
|
||||
->appendChild($error)
|
||||
->setError(pht('Wait'));
|
||||
}
|
||||
|
||||
private function newAnsweredControl(
|
||||
PhabricatorAuthFactorResult $result) {
|
||||
|
||||
$icon = id(new PHUIIconView())
|
||||
->setIcon('fa-check-circle-o', 'green');
|
||||
|
||||
return id(new PHUIFormTimerControl())
|
||||
->setIcon($icon)
|
||||
->appendChild(
|
||||
pht('You responded to this challenge correctly.'));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
75
src/applications/auth/factor/PhabricatorAuthFactorResult.php
Normal file
75
src/applications/auth/factor/PhabricatorAuthFactorResult.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -77,8 +77,8 @@ 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);
|
||||
|
||||
|
@ -150,77 +150,229 @@ 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();
|
||||
$name = $this->getChallengeResponseParameterName($config);
|
||||
|
||||
$control = id(new PHUIFormNumberControl())
|
||||
->setName($name)
|
||||
->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(
|
||||
public function getRequestHasChallengeResponse(
|
||||
PhabricatorAuthFactorConfig $config,
|
||||
PhabricatorUser $viewer,
|
||||
AphrontRequest $request) {
|
||||
|
||||
$code = $request->getStr($this->getParameterName($config, 'totpcode'));
|
||||
$key = new PhutilOpaqueEnvelope($config->getFactorSecret());
|
||||
|
||||
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,
|
||||
);
|
||||
}
|
||||
$value = $this->getChallengeResponseFromRequest($config, $request);
|
||||
return (bool)strlen($value);
|
||||
}
|
||||
|
||||
|
||||
protected function newResultFromIssuedChallenges(
|
||||
PhabricatorAuthFactorConfig $config,
|
||||
PhabricatorUser $viewer,
|
||||
array $challenges) {
|
||||
|
||||
// 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();
|
||||
|
||||
$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 = $this->getChallengeResponseFromRequest(
|
||||
$config,
|
||||
$request);
|
||||
|
||||
$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()),
|
||||
$code);
|
||||
|
||||
if ($valid_timestep) {
|
||||
$ttl = PhabricatorTime::getNow() + 60;
|
||||
|
||||
$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);
|
||||
|
||||
|
@ -313,4 +465,58 @@ 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 1;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
private function getChallengeResponseParameterName(
|
||||
PhabricatorAuthFactorConfig $config) {
|
||||
return $this->getParameterName($config, 'totpcode');
|
||||
}
|
||||
|
||||
private function getChallengeResponseFromRequest(
|
||||
PhabricatorAuthFactorConfig $config,
|
||||
AphrontRequest $request) {
|
||||
|
||||
$name = $this->getChallengeResponseParameterName($config);
|
||||
|
||||
$value = $request->getStr($name);
|
||||
$value = (string)$value;
|
||||
$value = trim($value);
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -13,7 +13,13 @@ final class PhabricatorAuthManagementRecoverWorkflow
|
|||
'of Phabricator.'))
|
||||
->setArguments(
|
||||
array(
|
||||
'username' => array(
|
||||
array(
|
||||
'name' => 'force-full-session',
|
||||
'help' => pht(
|
||||
'Recover directly into a full session without requiring MFA '.
|
||||
'or other login checks.'),
|
||||
),
|
||||
array(
|
||||
'name' => 'username',
|
||||
'wildcard' => true,
|
||||
),
|
||||
|
@ -54,11 +60,14 @@ final class PhabricatorAuthManagementRecoverWorkflow
|
|||
$username));
|
||||
}
|
||||
|
||||
$force_full_session = $args->getArg('force-full-session');
|
||||
|
||||
$engine = new PhabricatorAuthSessionEngine();
|
||||
$onetime_uri = $engine->getOneTimeLoginURI(
|
||||
$user,
|
||||
null,
|
||||
PhabricatorAuthSessionEngine::ONETIME_RECOVER);
|
||||
PhabricatorAuthSessionEngine::ONETIME_RECOVER,
|
||||
$force_full_session);
|
||||
|
||||
$console = PhutilConsole::getConsole();
|
||||
$console->writeOut(
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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';
|
||||
}
|
||||
|
||||
}
|
256
src/applications/auth/storage/PhabricatorAuthChallenge.php
Normal file
256
src/applications/auth/storage/PhabricatorAuthChallenge.php
Normal 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());
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -217,20 +217,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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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 )----------------------------------------- */
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -72,10 +72,14 @@ final class PhabricatorAuthSession extends PhabricatorAuthDAO
|
|||
return $this->assertAttached($this->identityObject);
|
||||
}
|
||||
|
||||
public static function getSessionTypeTTL($session_type) {
|
||||
public static function getSessionTypeTTL($session_type, $is_partial) {
|
||||
switch ($session_type) {
|
||||
case self::TYPE_WEB:
|
||||
return phutil_units('30 days in seconds');
|
||||
if ($is_partial) {
|
||||
return phutil_units('30 minutes in seconds');
|
||||
} else {
|
||||
return phutil_units('30 days in seconds');
|
||||
}
|
||||
case self::TYPE_CONDUIT:
|
||||
return phutil_units('24 hours in seconds');
|
||||
default:
|
||||
|
|
|
@ -106,6 +106,16 @@ final class PhabricatorAuthTemporaryToken extends PhabricatorAuthDAO
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function setShouldForceFullSession($force_full) {
|
||||
return $this->setTemporaryTokenProperty('force-full-session', $force_full);
|
||||
}
|
||||
|
||||
public function getShouldForceFullSession() {
|
||||
return $this->getTemporaryTokenProperty('force-full-session', false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 )----------------------------------- */
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 )----------------------------------- */
|
||||
|
||||
|
|
|
@ -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 )----------------------------------- */
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ final class PhabricatorAuthenticationConfigOptions
|
|||
pht(
|
||||
'If true, email addresses must be verified (by clicking a link '.
|
||||
'in an email) before a user can login. By default, verification '.
|
||||
'is optional unless {{auth.email-domains}} is nonempty.')),
|
||||
'is optional unless @{config:auth.email-domains} is nonempty.')),
|
||||
$this->newOption('auth.require-approval', 'bool', true)
|
||||
->setBoolOptions(
|
||||
array(
|
||||
|
@ -55,7 +55,7 @@ final class PhabricatorAuthenticationConfigOptions
|
|||
"registration, you can disable the queue to reduce administrative ".
|
||||
"overhead.\n\n".
|
||||
"NOTE: Before you disable the queue, make sure ".
|
||||
"{{auth.email-domains}} is configured correctly ".
|
||||
"@{config:auth.email-domains} is configured correctly ".
|
||||
"for your install!")),
|
||||
$this->newOption('auth.email-domains', 'list<string>', array())
|
||||
->setSummary(pht('Only allow registration from particular domains.'))
|
||||
|
@ -66,7 +66,7 @@ final class PhabricatorAuthenticationConfigOptions
|
|||
"here.\n\nUsers will only be allowed to register using email ".
|
||||
"addresses at one of the domains, and will only be able to add ".
|
||||
"new email addresses for these domains. If you configure this, ".
|
||||
"it implies {{auth.require-email-verification}}.\n\n".
|
||||
"it implies @{config:auth.require-email-verification}.\n\n".
|
||||
"You should omit the `@` from domains. Note that the domain must ".
|
||||
"match exactly. If you allow `yourcompany.com`, that permits ".
|
||||
"`joe@yourcompany.com` but rejects `joe@mail.yourcompany.com`."))
|
||||
|
|
|
@ -209,12 +209,6 @@ final class PhabricatorConfigOption
|
|||
return null;
|
||||
}
|
||||
|
||||
// TODO: Some day, we should probably implement this as a real rule.
|
||||
$description = preg_replace(
|
||||
'/{{([^}]+)}}/',
|
||||
'[[/config/edit/\\1/ | \\1]]',
|
||||
$description);
|
||||
|
||||
return new PHUIRemarkupView($viewer, $description);
|
||||
}
|
||||
|
||||
|
|
|
@ -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 )----------------------------------------- */
|
||||
|
||||
|
|
|
@ -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 )------------------------------------------ */
|
||||
|
||||
|
|
|
@ -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 )---------------------------------- */
|
||||
|
||||
|
|
|
@ -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 )----------------------------------------- */
|
||||
|
||||
|
|
|
@ -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 )----------------------------------------- */
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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 )----------------------------------- */
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -740,8 +740,6 @@ final class DiffusionCommitController extends DiffusionController {
|
|||
$commit,
|
||||
new PhabricatorAuditTransactionQuery());
|
||||
|
||||
$commit->willRenderTimeline($timeline, $this->getRequest());
|
||||
|
||||
$timeline->setQuoteRef($commit->getMonogram());
|
||||
|
||||
return $timeline;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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 )--------------------------------------- */
|
||||
|
||||
|
||||
|
|
|
@ -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 )----------------------------------------- */
|
||||
|
||||
|
|
|
@ -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 )-------------------------- */
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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 )----------------------------------- */
|
||||
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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 )----------------------------------------- */
|
||||
|
||||
|
|
|
@ -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 )----------------------------------------- */
|
||||
|
||||
|
|
|
@ -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 )----------------------------------------- */
|
||||
|
||||
|
||||
|
|
|
@ -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 )----------------------------------------- */
|
||||
|
||||
|
|
|
@ -50,6 +50,19 @@ final class HeraldWebhookViewController
|
|||
->setLimit(20)
|
||||
->execute();
|
||||
|
||||
$warnings = array();
|
||||
if (PhabricatorEnv::getEnvConfig('phabricator.silent')) {
|
||||
$message = pht(
|
||||
'Phabricator is currently configured in silent mode, so it will not '.
|
||||
'publish webhooks. To adjust this setting, see '.
|
||||
'@{config:phabricator.silent} in Config.');
|
||||
|
||||
$warnings[] = id(new PHUIInfoView())
|
||||
->setTitle(pht('Silent Mode'))
|
||||
->setSeverity(PHUIInfoView::SEVERITY_WARNING)
|
||||
->appendChild(new PHUIRemarkupView($viewer, $message));
|
||||
}
|
||||
|
||||
$requests_table = id(new HeraldWebhookRequestListView())
|
||||
->setViewer($viewer)
|
||||
->setRequests($requests)
|
||||
|
|
|
@ -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 )----------------------------------------- */
|
||||
|
||||
|
|
|
@ -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 )----------------------------------- */
|
||||
|
||||
|
|
|
@ -26,6 +26,15 @@ final class HeraldWebhookRequest
|
|||
const RESULT_OKAY = 'okay';
|
||||
const RESULT_FAIL = 'fail';
|
||||
|
||||
const ERRORTYPE_HOOK = 'hook';
|
||||
const ERRORTYPE_HTTP = 'http';
|
||||
const ERRORTYPE_TIMEOUT = 'timeout';
|
||||
|
||||
const ERROR_SILENT = 'silent';
|
||||
const ERROR_DISABLED = 'disabled';
|
||||
const ERROR_URI = 'uri';
|
||||
const ERROR_OBJECT = 'object';
|
||||
|
||||
protected function getConfiguration() {
|
||||
return array(
|
||||
self::CONFIG_AUX_PHID => true,
|
||||
|
@ -108,6 +117,28 @@ final class HeraldWebhookRequest
|
|||
return $this->getProperty('errorCode');
|
||||
}
|
||||
|
||||
public function getErrorTypeForDisplay() {
|
||||
$map = array(
|
||||
self::ERRORTYPE_HOOK => pht('Hook Error'),
|
||||
self::ERRORTYPE_HTTP => pht('HTTP Error'),
|
||||
self::ERRORTYPE_TIMEOUT => pht('Request Timeout'),
|
||||
);
|
||||
|
||||
$type = $this->getErrorType();
|
||||
return idx($map, $type, $type);
|
||||
}
|
||||
|
||||
public function getErrorCodeForDisplay() {
|
||||
$code = $this->getErrorCode();
|
||||
|
||||
if ($this->getErrorType() !== self::ERRORTYPE_HOOK) {
|
||||
return $code;
|
||||
}
|
||||
|
||||
$spec = $this->getHookErrorSpec($code);
|
||||
return idx($spec, 'display', $code);
|
||||
}
|
||||
|
||||
public function setTransactionPHIDs(array $phids) {
|
||||
return $this->setProperty('transactionPHIDs', $phids);
|
||||
}
|
||||
|
@ -187,6 +218,28 @@ final class HeraldWebhookRequest
|
|||
->setTooltip($tooltip);
|
||||
}
|
||||
|
||||
private function getHookErrorSpec($code) {
|
||||
$map = $this->getHookErrorMap();
|
||||
return idx($map, $code, array());
|
||||
}
|
||||
|
||||
private function getHookErrorMap() {
|
||||
return array(
|
||||
self::ERROR_SILENT => array(
|
||||
'display' => pht('In Silent Mode'),
|
||||
),
|
||||
self::ERROR_DISABLED => array(
|
||||
'display' => pht('Hook Disabled'),
|
||||
),
|
||||
self::ERROR_URI => array(
|
||||
'display' => pht('Invalid URI'),
|
||||
),
|
||||
self::ERROR_OBJECT => array(
|
||||
'display' => pht('Invalid Object'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
||||
|
||||
|
|
|
@ -55,8 +55,8 @@ final class HeraldWebhookRequestListView
|
|||
$request->getID(),
|
||||
$icon,
|
||||
$handles[$request->getObjectPHID()]->renderLink(),
|
||||
$request->getErrorType(),
|
||||
$request->getErrorCode(),
|
||||
$request->getErrorTypeForDisplay(),
|
||||
$request->getErrorCodeForDisplay(),
|
||||
$last_request,
|
||||
);
|
||||
}
|
||||
|
@ -66,7 +66,7 @@ final class HeraldWebhookRequestListView
|
|||
->setHeaders(
|
||||
array(
|
||||
pht('ID'),
|
||||
'',
|
||||
null,
|
||||
pht('Object'),
|
||||
pht('Type'),
|
||||
pht('Code'),
|
||||
|
|
|
@ -35,14 +35,20 @@ final class HeraldWebhookWorker
|
|||
// If we're in silent mode, permanently fail the webhook request and then
|
||||
// return to complete this task.
|
||||
if (PhabricatorEnv::getEnvConfig('phabricator.silent')) {
|
||||
$this->failRequest($request, 'hook', 'silent');
|
||||
$this->failRequest(
|
||||
$request,
|
||||
HeraldWebhookRequest::ERRORTYPE_HOOK,
|
||||
HeraldWebhookRequest::ERROR_SILENT);
|
||||
return;
|
||||
}
|
||||
|
||||
$hook = $request->getWebhook();
|
||||
|
||||
if ($hook->isDisabled()) {
|
||||
$this->failRequest($request, 'hook', 'disabled');
|
||||
$this->failRequest(
|
||||
$request,
|
||||
HeraldWebhookRequest::ERRORTYPE_HOOK,
|
||||
HeraldWebhookRequest::ERROR_DISABLED);
|
||||
throw new PhabricatorWorkerPermanentFailureException(
|
||||
pht(
|
||||
'Associated hook ("%s") for webhook request ("%s") is disabled.',
|
||||
|
@ -59,7 +65,10 @@ final class HeraldWebhookWorker
|
|||
'https',
|
||||
));
|
||||
} catch (Exception $ex) {
|
||||
$this->failRequest($request, 'hook', 'uri');
|
||||
$this->failRequest(
|
||||
$request,
|
||||
HeraldWebhookRequest::ERRORTYPE_HOOK,
|
||||
HeraldWebhookRequest::ERROR_URI);
|
||||
throw new PhabricatorWorkerPermanentFailureException(
|
||||
pht(
|
||||
'Associated hook ("%s") for webhook request ("%s") has invalid '.
|
||||
|
@ -76,7 +85,11 @@ final class HeraldWebhookWorker
|
|||
->withPHIDs(array($object_phid))
|
||||
->executeOne();
|
||||
if (!$object) {
|
||||
$this->failRequest($request, 'hook', 'object');
|
||||
$this->failRequest(
|
||||
$request,
|
||||
HeraldWebhookRequest::ERRORTYPE_HOOK,
|
||||
HeraldWebhookRequest::ERROR_OBJECT);
|
||||
|
||||
throw new PhabricatorWorkerPermanentFailureException(
|
||||
pht(
|
||||
'Unable to load object ("%s") for webhook request ("%s").',
|
||||
|
@ -182,9 +195,9 @@ final class HeraldWebhookWorker
|
|||
list($status) = $future->resolve();
|
||||
|
||||
if ($status->isTimeout()) {
|
||||
$error_type = 'timeout';
|
||||
$error_type = HeraldWebhookRequest::ERRORTYPE_TIMEOUT;
|
||||
} else {
|
||||
$error_type = 'http';
|
||||
$error_type = HeraldWebhookRequest::ERRORTYPE_HTTP;
|
||||
}
|
||||
$error_code = $status->getStatusCode();
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 )----------------------------------- */
|
||||
|
||||
|
|
|
@ -14,10 +14,6 @@ final class LegalpadTransaction extends PhabricatorModularTransaction {
|
|||
return new LegalpadTransactionComment();
|
||||
}
|
||||
|
||||
public function getApplicationTransactionViewObject() {
|
||||
return new LegalpadTransactionView();
|
||||
}
|
||||
|
||||
public function getBaseTransactionClass() {
|
||||
return 'LegalpadDocumentTransactionType';
|
||||
}
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
<?php
|
||||
|
||||
final class LegalpadTransactionView
|
||||
extends PhabricatorApplicationTransactionView {}
|
|
@ -98,21 +98,10 @@ final class PhabricatorFileImageMacro extends PhabricatorFileDAO
|
|||
return new PhabricatorMacroEditor();
|
||||
}
|
||||
|
||||
public function getApplicationTransactionObject() {
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getApplicationTransactionTemplate() {
|
||||
return new PhabricatorMacroTransaction();
|
||||
}
|
||||
|
||||
public function willRenderTimeline(
|
||||
PhabricatorApplicationTransactionView $timeline,
|
||||
AphrontRequest $request) {
|
||||
|
||||
return $timeline;
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorSubscribableInterface )----------------------------------- */
|
||||
|
||||
|
|
|
@ -212,6 +212,8 @@ The keys you can provide in a specification are:
|
|||
status.
|
||||
- `locked` //Optional bool.// Lock tasks in this status, preventing users
|
||||
from commenting.
|
||||
- `mfa` //Optional bool.// Require all edits to this task to be signed with
|
||||
multi-factor authentication.
|
||||
|
||||
Statuses will appear in the UI in the order specified. Note the status marked
|
||||
`special` as `duplicate` is not settable directly and will not appear in UI
|
||||
|
|
|
@ -160,6 +160,10 @@ final class ManiphestTaskStatus extends ManiphestConstants {
|
|||
return self::getStatusAttribute($status, 'locked', false);
|
||||
}
|
||||
|
||||
public static function isMFAStatus($status) {
|
||||
return self::getStatusAttribute($status, 'mfa', false);
|
||||
}
|
||||
|
||||
public static function getStatusActionName($status) {
|
||||
return self::getStatusAttribute($status, 'name.action');
|
||||
}
|
||||
|
@ -282,6 +286,7 @@ final class ManiphestTaskStatus extends ManiphestConstants {
|
|||
'disabled' => 'optional bool',
|
||||
'claim' => 'optional bool',
|
||||
'locked' => 'optional bool',
|
||||
'mfa' => 'optional bool',
|
||||
));
|
||||
}
|
||||
|
||||
|
|
11
src/applications/maniphest/engine/ManiphestTaskMFAEngine.php
Normal file
11
src/applications/maniphest/engine/ManiphestTaskMFAEngine.php
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
final class ManiphestTaskMFAEngine
|
||||
extends PhabricatorEditEngineMFAEngine {
|
||||
|
||||
public function shouldRequireMFA() {
|
||||
$status = $this->getObject()->getStatus();
|
||||
return ManiphestTaskStatus::isMFAStatus($status);
|
||||
}
|
||||
|
||||
}
|
|
@ -19,7 +19,8 @@ final class ManiphestTask extends ManiphestDAO
|
|||
PhabricatorFerretInterface,
|
||||
DoorkeeperBridgedObjectInterface,
|
||||
PhabricatorEditEngineSubtypeInterface,
|
||||
PhabricatorEditEngineLockableInterface {
|
||||
PhabricatorEditEngineLockableInterface,
|
||||
PhabricatorEditEngineMFAInterface {
|
||||
|
||||
const MARKUP_FIELD_DESCRIPTION = 'markup:desc';
|
||||
|
||||
|
@ -452,21 +453,10 @@ final class ManiphestTask extends ManiphestDAO
|
|||
return new ManiphestTransactionEditor();
|
||||
}
|
||||
|
||||
public function getApplicationTransactionObject() {
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getApplicationTransactionTemplate() {
|
||||
return new ManiphestTransaction();
|
||||
}
|
||||
|
||||
public function willRenderTimeline(
|
||||
PhabricatorApplicationTransactionView $timeline,
|
||||
AphrontRequest $request) {
|
||||
|
||||
return $timeline;
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorSpacesInterface )----------------------------------------- */
|
||||
|
||||
|
@ -630,4 +620,12 @@ final class ManiphestTask extends ManiphestDAO
|
|||
return new ManiphestTaskFerretEngine();
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorEditEngineMFAInterface )---------------------------------- */
|
||||
|
||||
|
||||
public function newEditEngineMFAEngine() {
|
||||
return new ManiphestTaskMFAEngine();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -123,20 +123,10 @@ final class PhabricatorMetaMTAApplicationEmail
|
|||
return new PhabricatorMetaMTAApplicationEmailEditor();
|
||||
}
|
||||
|
||||
public function getApplicationTransactionObject() {
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getApplicationTransactionTemplate() {
|
||||
return new PhabricatorMetaMTAApplicationEmailTransaction();
|
||||
}
|
||||
|
||||
public function willRenderTimeline(
|
||||
PhabricatorApplicationTransactionView $timeline,
|
||||
AphrontRequest $request) {
|
||||
return $timeline;
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorDestructibleInterface )----------------------------------- */
|
||||
|
||||
|
|
|
@ -193,18 +193,8 @@ final class NuanceItem
|
|||
return new NuanceItemEditor();
|
||||
}
|
||||
|
||||
public function getApplicationTransactionObject() {
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getApplicationTransactionTemplate() {
|
||||
return new NuanceItemTransaction();
|
||||
}
|
||||
|
||||
public function willRenderTimeline(
|
||||
PhabricatorApplicationTransactionView $timeline,
|
||||
AphrontRequest $request) {
|
||||
return $timeline;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -79,18 +79,8 @@ final class NuanceQueue
|
|||
return new NuanceQueueEditor();
|
||||
}
|
||||
|
||||
public function getApplicationTransactionObject() {
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getApplicationTransactionTemplate() {
|
||||
return new NuanceQueueTransaction();
|
||||
}
|
||||
|
||||
public function willRenderTimeline(
|
||||
PhabricatorApplicationTransactionView $timeline,
|
||||
AphrontRequest $request) {
|
||||
return $timeline;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -99,21 +99,10 @@ final class NuanceSource extends NuanceDAO
|
|||
return new NuanceSourceEditor();
|
||||
}
|
||||
|
||||
public function getApplicationTransactionObject() {
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getApplicationTransactionTemplate() {
|
||||
return new NuanceSourceTransaction();
|
||||
}
|
||||
|
||||
public function willRenderTimeline(
|
||||
PhabricatorApplicationTransactionView $timeline,
|
||||
AphrontRequest $request) {
|
||||
|
||||
return $timeline;
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
||||
|
||||
|
|
|
@ -91,20 +91,10 @@ final class PhabricatorOAuthServerClient
|
|||
return new PhabricatorOAuthServerEditor();
|
||||
}
|
||||
|
||||
public function getApplicationTransactionObject() {
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getApplicationTransactionTemplate() {
|
||||
return new PhabricatorOAuthServerTransaction();
|
||||
}
|
||||
|
||||
public function willRenderTimeline(
|
||||
PhabricatorApplicationTransactionView $timeline,
|
||||
AphrontRequest $request) {
|
||||
return $timeline;
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorDestructibleInterface )----------------------------------- */
|
||||
|
||||
|
|
|
@ -607,20 +607,10 @@ final class PhabricatorOwnersPackage
|
|||
return new PhabricatorOwnersPackageTransactionEditor();
|
||||
}
|
||||
|
||||
public function getApplicationTransactionObject() {
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getApplicationTransactionTemplate() {
|
||||
return new PhabricatorOwnersPackageTransaction();
|
||||
}
|
||||
|
||||
public function willRenderTimeline(
|
||||
PhabricatorApplicationTransactionView $timeline,
|
||||
AphrontRequest $request) {
|
||||
return $timeline;
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorCustomFieldInterface )------------------------------------ */
|
||||
|
||||
|
|
|
@ -189,20 +189,10 @@ final class PhabricatorPackagesPackage
|
|||
return new PhabricatorPackagesPackageEditor();
|
||||
}
|
||||
|
||||
public function getApplicationTransactionObject() {
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getApplicationTransactionTemplate() {
|
||||
return new PhabricatorPackagesPackageTransaction();
|
||||
}
|
||||
|
||||
public function willRenderTimeline(
|
||||
PhabricatorApplicationTransactionView $timeline,
|
||||
AphrontRequest $request) {
|
||||
return $timeline;
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorNgramsInterface )----------------------------------------- */
|
||||
|
||||
|
|
|
@ -165,20 +165,10 @@ final class PhabricatorPackagesPublisher
|
|||
return new PhabricatorPackagesPublisherEditor();
|
||||
}
|
||||
|
||||
public function getApplicationTransactionObject() {
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getApplicationTransactionTemplate() {
|
||||
return new PhabricatorPackagesPublisherTransaction();
|
||||
}
|
||||
|
||||
public function willRenderTimeline(
|
||||
PhabricatorApplicationTransactionView $timeline,
|
||||
AphrontRequest $request) {
|
||||
return $timeline;
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorNgramsInterface )----------------------------------------- */
|
||||
|
||||
|
|
|
@ -156,20 +156,10 @@ final class PhabricatorPackagesVersion
|
|||
return new PhabricatorPackagesVersionEditor();
|
||||
}
|
||||
|
||||
public function getApplicationTransactionObject() {
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getApplicationTransactionTemplate() {
|
||||
return new PhabricatorPackagesVersionTransaction();
|
||||
}
|
||||
|
||||
public function willRenderTimeline(
|
||||
PhabricatorApplicationTransactionView $timeline,
|
||||
AphrontRequest $request) {
|
||||
return $timeline;
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorNgramsInterface )----------------------------------------- */
|
||||
|
||||
|
|
|
@ -117,21 +117,10 @@ final class PassphraseCredential extends PassphraseDAO
|
|||
return new PassphraseCredentialTransactionEditor();
|
||||
}
|
||||
|
||||
public function getApplicationTransactionObject() {
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getApplicationTransactionTemplate() {
|
||||
return new PassphraseCredentialTransaction();
|
||||
}
|
||||
|
||||
public function willRenderTimeline(
|
||||
PhabricatorApplicationTransactionView $timeline,
|
||||
AphrontRequest $request) {
|
||||
|
||||
return $timeline;
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
||||
|
||||
|
|
|
@ -219,21 +219,10 @@ final class PhabricatorPaste extends PhabricatorPasteDAO
|
|||
return new PhabricatorPasteEditor();
|
||||
}
|
||||
|
||||
public function getApplicationTransactionObject() {
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getApplicationTransactionTemplate() {
|
||||
return new PhabricatorPasteTransaction();
|
||||
}
|
||||
|
||||
public function willRenderTimeline(
|
||||
PhabricatorApplicationTransactionView $timeline,
|
||||
AphrontRequest $request) {
|
||||
|
||||
return $timeline;
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorSpacesInterface )----------------------------------------- */
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue