diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 61286449f3..86549d07f0 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1,529 +1,531 @@ array( 'Aphront400Response' => 'aphront/response/400', 'Aphront404Response' => 'aphront/response/404', 'AphrontAjaxResponse' => 'aphront/response/ajax', 'AphrontApplicationConfiguration' => 'aphront/applicationconfiguration', 'AphrontController' => 'aphront/controller', 'AphrontDatabaseConnection' => 'storage/connection/base', 'AphrontDefaultApplicationConfiguration' => 'aphront/default/configuration', 'AphrontDefaultApplicationController' => 'aphront/default/controller', 'AphrontDialogResponse' => 'aphront/response/dialog', 'AphrontDialogView' => 'view/dialog', 'AphrontErrorView' => 'view/form/error', 'AphrontException' => 'aphront/exception/base', 'AphrontFileResponse' => 'aphront/response/file', 'AphrontFormCheckboxControl' => 'view/form/control/checkbox', 'AphrontFormControl' => 'view/form/control/base', 'AphrontFormDividerControl' => 'view/form/control/divider', 'AphrontFormFileControl' => 'view/form/control/file', 'AphrontFormMarkupControl' => 'view/form/control/markup', 'AphrontFormPasswordControl' => 'view/form/control/password', 'AphrontFormRecaptchaControl' => 'view/form/control/recaptcha', 'AphrontFormSelectControl' => 'view/form/control/select', 'AphrontFormStaticControl' => 'view/form/control/static', 'AphrontFormSubmitControl' => 'view/form/control/submit', 'AphrontFormTextAreaControl' => 'view/form/control/textarea', 'AphrontFormTextControl' => 'view/form/control/text', 'AphrontFormTokenizerControl' => 'view/form/control/tokenizer', 'AphrontFormView' => 'view/form/base', 'AphrontHeadsupActionListView' => 'view/layout/headsup/actionlist', 'AphrontHeadsupActionView' => 'view/layout/headsup/action', 'AphrontMySQLDatabaseConnection' => 'storage/connection/mysql', 'AphrontNullView' => 'view/null', 'AphrontPageView' => 'view/page/base', 'AphrontPanelView' => 'view/layout/panel', 'AphrontQueryConnectionException' => 'storage/exception/connection', 'AphrontQueryConnectionLostException' => 'storage/exception/connectionlost', 'AphrontQueryCountException' => 'storage/exception/count', 'AphrontQueryDuplicateKeyException' => 'storage/exception/duplicatekey', 'AphrontQueryException' => 'storage/exception/base', 'AphrontQueryObjectMissingException' => 'storage/exception/objectmissing', 'AphrontQueryParameterException' => 'storage/exception/parameter', 'AphrontQueryRecoverableException' => 'storage/exception/recoverable', 'AphrontRedirectException' => 'aphront/exception/redirect', 'AphrontRedirectResponse' => 'aphront/response/redirect', 'AphrontRequest' => 'aphront/request', 'AphrontRequestFailureView' => 'view/page/failure', 'AphrontResponse' => 'aphront/response/base', 'AphrontSideNavView' => 'view/layout/sidenav', 'AphrontTableView' => 'view/control/table', 'AphrontURIMapper' => 'aphront/mapper', 'AphrontView' => 'view/base', 'AphrontWebpageResponse' => 'aphront/response/webpage', 'CelerityAPI' => 'infrastructure/celerity/api', 'CelerityResourceController' => 'infrastructure/celerity/controller', 'CelerityResourceMap' => 'infrastructure/celerity/map', 'CelerityStaticResourceResponse' => 'infrastructure/celerity/response', 'ConduitAPIMethod' => 'applications/conduit/method/base', 'ConduitAPIRequest' => 'applications/conduit/protocol/request', 'ConduitAPI_conduit_connect_Method' => 'applications/conduit/method/conduit/connect', 'ConduitAPI_conduit_ping_Method' => 'applications/conduit/method/conduit/ping', 'ConduitAPI_differential_creatediff_Method' => 'applications/conduit/method/differential/creatediff', 'ConduitAPI_differential_createrevision_Method' => 'applications/conduit/method/differential/createrevision', 'ConduitAPI_differential_find_Method' => 'applications/conduit/method/differential/find', 'ConduitAPI_differential_getcommitmessage_Method' => 'applications/conduit/method/differential/getcommitmessage', 'ConduitAPI_differential_getcommitpaths_Method' => 'applications/conduit/method/differential/getcommitpaths', 'ConduitAPI_differential_getdiff_Method' => 'applications/conduit/method/differential/getdiff', 'ConduitAPI_differential_markcommitted_Method' => 'applications/conduit/method/differential/markcommitted', 'ConduitAPI_differential_parsecommitmessage_Method' => 'applications/conduit/method/differential/parsecommitmessage', 'ConduitAPI_differential_setdiffproperty_Method' => 'applications/conduit/method/differential/setdiffproperty', 'ConduitAPI_differential_updaterevision_Method' => 'applications/conduit/method/differential/updaterevision', 'ConduitAPI_file_upload_Method' => 'applications/conduit/method/file/upload', 'ConduitAPI_user_find_Method' => 'applications/conduit/method/user/find', 'ConduitException' => 'applications/conduit/protocol/exception', 'DarkConsole' => 'aphront/console/api', 'DarkConsoleConfigPlugin' => 'aphront/console/plugin/config', 'DarkConsoleController' => 'aphront/console/controller', 'DarkConsoleCore' => 'aphront/console/core', 'DarkConsoleErrorLogPlugin' => 'aphront/console/plugin/errorlog', 'DarkConsoleErrorLogPluginAPI' => 'aphront/console/plugin/errorlog/api', 'DarkConsolePlugin' => 'aphront/console/plugin/base', 'DarkConsoleRequestPlugin' => 'aphront/console/plugin/request', 'DarkConsoleServicesPlugin' => 'aphront/console/plugin/services', 'DarkConsoleServicesPluginAPI' => 'aphront/console/plugin/services/api', 'DarkConsoleXHProfPlugin' => 'aphront/console/plugin/xhprof', 'DarkConsoleXHProfPluginAPI' => 'aphront/console/plugin/xhprof/api', 'DifferentialAction' => 'applications/differential/constants/action', 'DifferentialAddCommentView' => 'applications/differential/view/addcomment', 'DifferentialAttachController' => 'applications/differential/controller/attach', 'DifferentialCCWelcomeMail' => 'applications/differential/mail/ccwelcome', 'DifferentialChangeType' => 'applications/differential/constants/changetype', 'DifferentialChangeset' => 'applications/differential/storage/changeset', 'DifferentialChangesetDetailView' => 'applications/differential/view/changesetdetailview', 'DifferentialChangesetListView' => 'applications/differential/view/changesetlistview', 'DifferentialChangesetParser' => 'applications/differential/parser/changeset', 'DifferentialChangesetViewController' => 'applications/differential/controller/changesetview', 'DifferentialComment' => 'applications/differential/storage/comment', 'DifferentialCommentEditor' => 'applications/differential/editor/comment', 'DifferentialCommentMail' => 'applications/differential/mail/comment', 'DifferentialCommentPreviewController' => 'applications/differential/controller/commentpreview', 'DifferentialCommentSaveController' => 'applications/differential/controller/commentsave', 'DifferentialCommitMessage' => 'applications/differential/parser/commitmessage', 'DifferentialCommitMessageData' => 'applications/differential/data/commitmessage', 'DifferentialCommitMessageParserException' => 'applications/differential/parser/commitmessage/exception', 'DifferentialController' => 'applications/differential/controller/base', 'DifferentialDAO' => 'applications/differential/storage/base', 'DifferentialDiff' => 'applications/differential/storage/diff', 'DifferentialDiffContentMail' => 'applications/differential/mail/diffcontent', 'DifferentialDiffCreateController' => 'applications/differential/controller/diffcreate', 'DifferentialDiffProperty' => 'applications/differential/storage/diffproperty', 'DifferentialDiffTableOfContentsView' => 'applications/differential/view/difftableofcontents', 'DifferentialDiffViewController' => 'applications/differential/controller/diffview', 'DifferentialHunk' => 'applications/differential/storage/hunk', 'DifferentialInlineComment' => 'applications/differential/storage/inlinecomment', 'DifferentialInlineCommentEditController' => 'applications/differential/controller/inlinecommentedit', 'DifferentialInlineCommentPreviewController' => 'applications/differential/controller/inlinecommentpreview', 'DifferentialInlineCommentView' => 'applications/differential/view/inlinecomment', 'DifferentialLintStatus' => 'applications/differential/constants/lintstatus', 'DifferentialMail' => 'applications/differential/mail/base', 'DifferentialMarkupEngineFactory' => 'applications/differential/parser/markup', 'DifferentialNewDiffMail' => 'applications/differential/mail/newdiff', 'DifferentialReviewRequestMail' => 'applications/differential/mail/reviewrequest', 'DifferentialRevision' => 'applications/differential/storage/revision', 'DifferentialRevisionCommentListView' => 'applications/differential/view/revisioncommentlist', 'DifferentialRevisionCommentView' => 'applications/differential/view/revisioncomment', 'DifferentialRevisionControlSystem' => 'applications/differential/constants/revisioncontrolsystem', 'DifferentialRevisionDetailView' => 'applications/differential/view/revisiondetail', 'DifferentialRevisionEditController' => 'applications/differential/controller/revisionedit', 'DifferentialRevisionEditor' => 'applications/differential/editor/revision', 'DifferentialRevisionListController' => 'applications/differential/controller/revisionlist', 'DifferentialRevisionListData' => 'applications/differential/data/revisionlist', 'DifferentialRevisionStatus' => 'applications/differential/constants/revisionstatus', 'DifferentialRevisionUpdateHistoryView' => 'applications/differential/view/revisionupdatehistory', 'DifferentialRevisionViewController' => 'applications/differential/controller/revisionview', 'DifferentialSubscribeController' => 'applications/differential/controller/subscribe', 'DifferentialUnitStatus' => 'applications/differential/constants/unitstatus', 'Javelin' => 'infrastructure/javelin/api', 'LiskDAO' => 'storage/lisk/dao', 'ManiphestController' => 'applications/maniphest/controller/base', 'ManiphestDAO' => 'applications/maniphest/storage/base', 'ManiphestTask' => 'applications/maniphest/storage/task', 'ManiphestTaskDetailController' => 'applications/maniphest/controller/taskdetail', 'ManiphestTaskEditController' => 'applications/maniphest/controller/taskedit', 'ManiphestTaskListController' => 'applications/maniphest/controller/tasklist', 'ManiphestTaskListView' => 'applications/maniphest/view/tasklist', 'ManiphestTaskPriority' => 'applications/maniphest/constants/priority', 'ManiphestTaskSelectorSearchController' => 'applications/maniphest/controller/taskselectorsearch', 'ManiphestTaskStatus' => 'applications/maniphest/constants/status', 'ManiphestTaskSummaryView' => 'applications/maniphest/view/tasksummary', 'ManiphestTransaction' => 'applications/maniphest/storage/transaction', 'ManiphestTransactionDetailView' => 'applications/maniphest/view/transactiondetail', 'ManiphestTransactionEditor' => 'applications/maniphest/editor/transaction', 'ManiphestTransactionListView' => 'applications/maniphest/view/transactionlist', 'ManiphestTransactionSaveController' => 'applications/maniphest/controller/transactionsave', 'ManiphestTransactionType' => 'applications/maniphest/constants/transactiontype', 'Phabricator404Controller' => 'applications/base/controller/404', 'PhabricatorAuthController' => 'applications/auth/controller/base', 'PhabricatorConduitAPIController' => 'applications/conduit/controller/api', 'PhabricatorConduitConnectionLog' => 'applications/conduit/storage/connectionlog', 'PhabricatorConduitConsoleController' => 'applications/conduit/controller/console', 'PhabricatorConduitController' => 'applications/conduit/controller/base', 'PhabricatorConduitDAO' => 'applications/conduit/storage/base', 'PhabricatorConduitLogController' => 'applications/conduit/controller/log', 'PhabricatorConduitMethodCallLog' => 'applications/conduit/storage/methodcalllog', 'PhabricatorController' => 'applications/base/controller/base', 'PhabricatorDirectoryCategory' => 'applications/directory/storage/category', 'PhabricatorDirectoryCategoryDeleteController' => 'applications/directory/controller/categorydelete', 'PhabricatorDirectoryCategoryEditController' => 'applications/directory/controller/categoryedit', 'PhabricatorDirectoryCategoryListController' => 'applications/directory/controller/categorylist', 'PhabricatorDirectoryController' => 'applications/directory/controller/base', 'PhabricatorDirectoryDAO' => 'applications/directory/storage/base', 'PhabricatorDirectoryItem' => 'applications/directory/storage/item', 'PhabricatorDirectoryItemDeleteController' => 'applications/directory/controller/itemdelete', 'PhabricatorDirectoryItemEditController' => 'applications/directory/controller/itemedit', 'PhabricatorDirectoryItemListController' => 'applications/directory/controller/itemlist', 'PhabricatorDirectoryMainController' => 'applications/directory/controller/main', 'PhabricatorDraft' => 'applications/draft/storage/draft', 'PhabricatorDraftDAO' => 'applications/draft/storage/base', 'PhabricatorEmailLoginController' => 'applications/auth/controller/email', 'PhabricatorEmailTokenController' => 'applications/auth/controller/emailtoken', 'PhabricatorEnv' => 'infrastructure/env', 'PhabricatorFile' => 'applications/files/storage/file', 'PhabricatorFileController' => 'applications/files/controller/base', 'PhabricatorFileDAO' => 'applications/files/storage/base', 'PhabricatorFileListController' => 'applications/files/controller/list', 'PhabricatorFileStorageBlob' => 'applications/files/storage/storageblob', 'PhabricatorFileURI' => 'applications/files/uri', 'PhabricatorFileUploadController' => 'applications/files/controller/upload', 'PhabricatorFileViewController' => 'applications/files/controller/view', 'PhabricatorHandleObjectSelectorDataView' => 'applications/phid/handle/view/selector', 'PhabricatorLiskDAO' => 'applications/base/storage/lisk', 'PhabricatorLoginController' => 'applications/auth/controller/login', 'PhabricatorLogoutController' => 'applications/auth/controller/logout', 'PhabricatorMailImplementationAdapter' => 'applications/metamta/adapter/base', 'PhabricatorMailImplementationAmazonSESAdapter' => 'applications/metamta/adapter/amazonses', 'PhabricatorMailImplementationPHPMailerLiteAdapter' => 'applications/metamta/adapter/phpmailerlite', 'PhabricatorMetaMTAController' => 'applications/metamta/controller/base', 'PhabricatorMetaMTADAO' => 'applications/metamta/storage/base', 'PhabricatorMetaMTADaemon' => 'applications/metamta/daemon/mta', 'PhabricatorMetaMTAListController' => 'applications/metamta/controller/list', 'PhabricatorMetaMTAMail' => 'applications/metamta/storage/mail', 'PhabricatorMetaMTAMailingList' => 'applications/metamta/storage/mailinglist', 'PhabricatorMetaMTAMailingListEditController' => 'applications/metamta/controller/mailinglistedit', 'PhabricatorMetaMTAMailingListsController' => 'applications/metamta/controller/mailinglists', 'PhabricatorMetaMTASendController' => 'applications/metamta/controller/send', 'PhabricatorMetaMTAViewController' => 'applications/metamta/controller/view', 'PhabricatorOAuthDiagnosticsController' => 'applications/auth/controller/oauthdiagnostics', 'PhabricatorOAuthFailureView' => 'applications/auth/view/oauthfailure', 'PhabricatorOAuthLoginController' => 'applications/auth/controller/oauth', 'PhabricatorOAuthProvider' => 'applications/auth/oauth/provider/base', 'PhabricatorOAuthProviderFacebook' => 'applications/auth/oauth/provider/facebook', 'PhabricatorOAuthProviderGithub' => 'applications/auth/oauth/provider/github', + 'PhabricatorOAuthUnlinkController' => 'applications/auth/controller/unlink', 'PhabricatorObjectHandle' => 'applications/phid/handle', 'PhabricatorObjectHandleData' => 'applications/phid/handle/data', 'PhabricatorObjectSelectorDialog' => 'view/control/objectselector', 'PhabricatorPHID' => 'applications/phid/storage/phid', 'PhabricatorPHIDAllocateController' => 'applications/phid/controller/allocate', 'PhabricatorPHIDController' => 'applications/phid/controller/base', 'PhabricatorPHIDDAO' => 'applications/phid/storage/base', 'PhabricatorPHIDListController' => 'applications/phid/controller/list', 'PhabricatorPHIDLookupController' => 'applications/phid/controller/lookup', 'PhabricatorPHIDType' => 'applications/phid/storage/type', 'PhabricatorPHIDTypeEditController' => 'applications/phid/controller/typeedit', 'PhabricatorPHIDTypeListController' => 'applications/phid/controller/typelist', 'PhabricatorPeopleController' => 'applications/people/controller/base', 'PhabricatorPeopleEditController' => 'applications/people/controller/edit', 'PhabricatorPeopleListController' => 'applications/people/controller/list', 'PhabricatorPeopleProfileController' => 'applications/people/controller/profile', 'PhabricatorPeopleProfileEditController' => 'applications/people/controller/profileedit', 'PhabricatorProject' => 'applications/project/storage/project', 'PhabricatorProjectAffiliation' => 'applications/project/storage/affiliation', 'PhabricatorProjectAffiliationEditController' => 'applications/project/controller/editaffiliation', 'PhabricatorProjectController' => 'applications/project/controller/base', 'PhabricatorProjectDAO' => 'applications/project/storage/base', 'PhabricatorProjectEditController' => 'applications/project/controller/edit', 'PhabricatorProjectListController' => 'applications/project/controller/list', 'PhabricatorProjectProfile' => 'applications/project/storage/profile', 'PhabricatorProjectProfileController' => 'applications/project/controller/profile', 'PhabricatorRemarkupRuleDifferential' => 'infrastructure/markup/remarkup/markuprule/differential', 'PhabricatorRemarkupRuleManiphest' => 'infrastructure/markup/remarkup/markuprule/maniphest', 'PhabricatorRepository' => 'applications/repository/storage/repository', 'PhabricatorRepositoryController' => 'applications/repository/controller/base', 'PhabricatorRepositoryCreateController' => 'applications/repository/controller/create', 'PhabricatorRepositoryDAO' => 'applications/repository/storage/base', 'PhabricatorRepositoryEditController' => 'applications/repository/controller/edit', 'PhabricatorRepositoryGitHubNotification' => 'applications/repository/storage/githubnotification', 'PhabricatorRepositoryGitHubPostReceiveController' => 'applications/repository/controller/github-post-receive', 'PhabricatorRepositoryListController' => 'applications/repository/controller/list', 'PhabricatorSearchAbstractDocument' => 'applications/search/index/abstractdocument', 'PhabricatorSearchBaseController' => 'applications/search/controller/base', 'PhabricatorSearchController' => 'applications/search/controller/search', 'PhabricatorSearchDAO' => 'applications/search/storage/base', 'PhabricatorSearchDifferentialIndexer' => 'applications/search/index/indexer/differential', 'PhabricatorSearchDocument' => 'applications/search/storage/document/document', 'PhabricatorSearchDocumentField' => 'applications/search/storage/document/field', 'PhabricatorSearchDocumentIndexer' => 'applications/search/index/indexer/base', 'PhabricatorSearchDocumentRelationship' => 'applications/search/storage/document/relationship', 'PhabricatorSearchExecutor' => 'applications/search/execute/base', 'PhabricatorSearchField' => 'applications/search/constants/field', 'PhabricatorSearchManiphestIndexer' => 'applications/search/index/indexer/maniphest', 'PhabricatorSearchMySQLExecutor' => 'applications/search/execute/mysql', 'PhabricatorSearchQuery' => 'applications/search/storage/query', 'PhabricatorSearchRelationship' => 'applications/search/constants/relationship', 'PhabricatorStandardPageView' => 'view/page/standard', 'PhabricatorTypeaheadCommonDatasourceController' => 'applications/typeahead/controller/common', 'PhabricatorTypeaheadDatasourceController' => 'applications/typeahead/controller/base', 'PhabricatorUser' => 'applications/people/storage/user', 'PhabricatorUserDAO' => 'applications/people/storage/base', 'PhabricatorUserOAuthInfo' => 'applications/people/storage/useroauthinfo', 'PhabricatorUserProfile' => 'applications/people/storage/profile', 'PhabricatorUserSettingsController' => 'applications/people/controller/settings', 'PhabricatorXHProfController' => 'applications/xhprof/controller/base', 'PhabricatorXHProfProfileController' => 'applications/xhprof/controller/profile', 'PhabricatorXHProfProfileSymbolView' => 'applications/xhprof/view/symbol', 'PhabricatorXHProfProfileTopLevelView' => 'applications/xhprof/view/toplevel', ), 'function' => array( '_qsprintf_check_scalar_type' => 'storage/qsprintf', '_qsprintf_check_type' => 'storage/qsprintf', 'celerity_generate_unique_node_id' => 'infrastructure/celerity/api', 'celerity_register_resource_map' => 'infrastructure/celerity/map', 'javelin_render_tag' => 'infrastructure/javelin/markup', 'phabricator_format_relative_time' => 'view/utils', 'phabricator_format_timestamp' => 'view/utils', 'phabricator_format_units_generic' => 'view/utils', 'phabricator_render_form' => 'infrastructure/javelin/markup', 'qsprintf' => 'storage/qsprintf', 'queryfx' => 'storage/queryfx', 'queryfx_all' => 'storage/queryfx', 'queryfx_one' => 'storage/queryfx', 'require_celerity_resource' => 'infrastructure/celerity/api', 'vqsprintf' => 'storage/qsprintf', 'vqueryfx' => 'storage/queryfx', 'vqueryfx_all' => 'storage/queryfx', 'xsprintf_query' => 'storage/qsprintf', ), 'requires_class' => array( 'Aphront400Response' => 'AphrontResponse', 'Aphront404Response' => 'AphrontResponse', 'AphrontAjaxResponse' => 'AphrontResponse', 'AphrontDefaultApplicationConfiguration' => 'AphrontApplicationConfiguration', 'AphrontDefaultApplicationController' => 'AphrontController', 'AphrontDialogResponse' => 'AphrontResponse', 'AphrontDialogView' => 'AphrontView', 'AphrontErrorView' => 'AphrontView', 'AphrontFileResponse' => 'AphrontResponse', 'AphrontFormCheckboxControl' => 'AphrontFormControl', 'AphrontFormControl' => 'AphrontView', 'AphrontFormDividerControl' => 'AphrontFormControl', 'AphrontFormFileControl' => 'AphrontFormControl', 'AphrontFormMarkupControl' => 'AphrontFormControl', 'AphrontFormPasswordControl' => 'AphrontFormControl', 'AphrontFormRecaptchaControl' => 'AphrontFormControl', 'AphrontFormSelectControl' => 'AphrontFormControl', 'AphrontFormStaticControl' => 'AphrontFormControl', 'AphrontFormSubmitControl' => 'AphrontFormControl', 'AphrontFormTextAreaControl' => 'AphrontFormControl', 'AphrontFormTextControl' => 'AphrontFormControl', 'AphrontFormTokenizerControl' => 'AphrontFormControl', 'AphrontFormView' => 'AphrontView', 'AphrontHeadsupActionListView' => 'AphrontView', 'AphrontHeadsupActionView' => 'AphrontView', 'AphrontMySQLDatabaseConnection' => 'AphrontDatabaseConnection', 'AphrontNullView' => 'AphrontView', 'AphrontPageView' => 'AphrontView', 'AphrontPanelView' => 'AphrontView', 'AphrontQueryConnectionException' => 'AphrontQueryException', 'AphrontQueryConnectionLostException' => 'AphrontQueryRecoverableException', 'AphrontQueryCountException' => 'AphrontQueryException', 'AphrontQueryDuplicateKeyException' => 'AphrontQueryException', 'AphrontQueryObjectMissingException' => 'AphrontQueryException', 'AphrontQueryParameterException' => 'AphrontQueryException', 'AphrontQueryRecoverableException' => 'AphrontQueryException', 'AphrontRedirectException' => 'AphrontException', 'AphrontRedirectResponse' => 'AphrontResponse', 'AphrontRequestFailureView' => 'AphrontView', 'AphrontSideNavView' => 'AphrontView', 'AphrontTableView' => 'AphrontView', 'AphrontWebpageResponse' => 'AphrontResponse', 'CelerityResourceController' => 'AphrontController', 'ConduitAPI_conduit_connect_Method' => 'ConduitAPIMethod', 'ConduitAPI_conduit_ping_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_creatediff_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_createrevision_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_find_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_getcommitmessage_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_getcommitpaths_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_getdiff_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_markcommitted_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_parsecommitmessage_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_setdiffproperty_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_updaterevision_Method' => 'ConduitAPIMethod', 'ConduitAPI_file_upload_Method' => 'ConduitAPIMethod', 'ConduitAPI_user_find_Method' => 'ConduitAPIMethod', 'DarkConsoleConfigPlugin' => 'DarkConsolePlugin', 'DarkConsoleController' => 'PhabricatorController', 'DarkConsoleErrorLogPlugin' => 'DarkConsolePlugin', 'DarkConsoleRequestPlugin' => 'DarkConsolePlugin', 'DarkConsoleServicesPlugin' => 'DarkConsolePlugin', 'DarkConsoleXHProfPlugin' => 'DarkConsolePlugin', 'DifferentialAddCommentView' => 'AphrontView', 'DifferentialAttachController' => 'DifferentialController', 'DifferentialCCWelcomeMail' => 'DifferentialReviewRequestMail', 'DifferentialChangeset' => 'DifferentialDAO', 'DifferentialChangesetDetailView' => 'AphrontView', 'DifferentialChangesetListView' => 'AphrontView', 'DifferentialChangesetViewController' => 'DifferentialController', 'DifferentialComment' => 'DifferentialDAO', 'DifferentialCommentMail' => 'DifferentialMail', 'DifferentialCommentPreviewController' => 'DifferentialController', 'DifferentialCommentSaveController' => 'DifferentialController', 'DifferentialController' => 'PhabricatorController', 'DifferentialDAO' => 'PhabricatorLiskDAO', 'DifferentialDiff' => 'DifferentialDAO', 'DifferentialDiffContentMail' => 'DifferentialMail', 'DifferentialDiffCreateController' => 'DifferentialController', 'DifferentialDiffProperty' => 'DifferentialDAO', 'DifferentialDiffTableOfContentsView' => 'AphrontView', 'DifferentialDiffViewController' => 'DifferentialController', 'DifferentialHunk' => 'DifferentialDAO', 'DifferentialInlineComment' => 'DifferentialDAO', 'DifferentialInlineCommentEditController' => 'DifferentialController', 'DifferentialInlineCommentPreviewController' => 'DifferentialController', 'DifferentialInlineCommentView' => 'AphrontView', 'DifferentialNewDiffMail' => 'DifferentialReviewRequestMail', 'DifferentialReviewRequestMail' => 'DifferentialMail', 'DifferentialRevision' => 'DifferentialDAO', 'DifferentialRevisionCommentListView' => 'AphrontView', 'DifferentialRevisionCommentView' => 'AphrontView', 'DifferentialRevisionDetailView' => 'AphrontView', 'DifferentialRevisionEditController' => 'DifferentialController', 'DifferentialRevisionListController' => 'DifferentialController', 'DifferentialRevisionUpdateHistoryView' => 'AphrontView', 'DifferentialRevisionViewController' => 'DifferentialController', 'DifferentialSubscribeController' => 'DifferentialController', 'ManiphestController' => 'PhabricatorController', 'ManiphestDAO' => 'PhabricatorLiskDAO', 'ManiphestTask' => 'ManiphestDAO', 'ManiphestTaskDetailController' => 'ManiphestController', 'ManiphestTaskEditController' => 'ManiphestController', 'ManiphestTaskListController' => 'ManiphestController', 'ManiphestTaskListView' => 'AphrontView', 'ManiphestTaskSelectorSearchController' => 'ManiphestController', 'ManiphestTaskSummaryView' => 'AphrontView', 'ManiphestTransaction' => 'ManiphestDAO', 'ManiphestTransactionDetailView' => 'AphrontView', 'ManiphestTransactionListView' => 'AphrontView', 'ManiphestTransactionSaveController' => 'ManiphestController', 'Phabricator404Controller' => 'PhabricatorController', 'PhabricatorAuthController' => 'PhabricatorController', 'PhabricatorConduitAPIController' => 'PhabricatorConduitController', 'PhabricatorConduitConnectionLog' => 'PhabricatorConduitDAO', 'PhabricatorConduitConsoleController' => 'PhabricatorConduitController', 'PhabricatorConduitController' => 'PhabricatorController', 'PhabricatorConduitDAO' => 'PhabricatorLiskDAO', 'PhabricatorConduitLogController' => 'PhabricatorConduitController', 'PhabricatorConduitMethodCallLog' => 'PhabricatorConduitDAO', 'PhabricatorController' => 'AphrontController', 'PhabricatorDirectoryCategory' => 'PhabricatorDirectoryDAO', 'PhabricatorDirectoryCategoryDeleteController' => 'PhabricatorDirectoryController', 'PhabricatorDirectoryCategoryEditController' => 'PhabricatorDirectoryController', 'PhabricatorDirectoryCategoryListController' => 'PhabricatorDirectoryController', 'PhabricatorDirectoryController' => 'PhabricatorController', 'PhabricatorDirectoryDAO' => 'PhabricatorLiskDAO', 'PhabricatorDirectoryItem' => 'PhabricatorDirectoryDAO', 'PhabricatorDirectoryItemDeleteController' => 'PhabricatorDirectoryController', 'PhabricatorDirectoryItemEditController' => 'PhabricatorDirectoryController', 'PhabricatorDirectoryItemListController' => 'PhabricatorDirectoryController', 'PhabricatorDirectoryMainController' => 'PhabricatorDirectoryController', 'PhabricatorDraft' => 'PhabricatorDraftDAO', 'PhabricatorDraftDAO' => 'PhabricatorLiskDAO', 'PhabricatorEmailLoginController' => 'PhabricatorAuthController', 'PhabricatorEmailTokenController' => 'PhabricatorAuthController', 'PhabricatorFile' => 'PhabricatorFileDAO', 'PhabricatorFileController' => 'PhabricatorController', 'PhabricatorFileDAO' => 'PhabricatorLiskDAO', 'PhabricatorFileListController' => 'PhabricatorFileController', 'PhabricatorFileStorageBlob' => 'PhabricatorFileDAO', 'PhabricatorFileUploadController' => 'PhabricatorFileController', 'PhabricatorFileViewController' => 'PhabricatorFileController', 'PhabricatorLiskDAO' => 'LiskDAO', 'PhabricatorLoginController' => 'PhabricatorAuthController', 'PhabricatorLogoutController' => 'PhabricatorAuthController', 'PhabricatorMailImplementationAmazonSESAdapter' => 'PhabricatorMailImplementationPHPMailerLiteAdapter', 'PhabricatorMailImplementationPHPMailerLiteAdapter' => 'PhabricatorMailImplementationAdapter', 'PhabricatorMetaMTAController' => 'PhabricatorController', 'PhabricatorMetaMTADAO' => 'PhabricatorLiskDAO', 'PhabricatorMetaMTAListController' => 'PhabricatorMetaMTAController', 'PhabricatorMetaMTAMail' => 'PhabricatorMetaMTADAO', 'PhabricatorMetaMTAMailingList' => 'PhabricatorMetaMTADAO', 'PhabricatorMetaMTAMailingListEditController' => 'PhabricatorMetaMTAController', 'PhabricatorMetaMTAMailingListsController' => 'PhabricatorMetaMTAController', 'PhabricatorMetaMTASendController' => 'PhabricatorMetaMTAController', 'PhabricatorMetaMTAViewController' => 'PhabricatorMetaMTAController', 'PhabricatorOAuthDiagnosticsController' => 'PhabricatorAuthController', 'PhabricatorOAuthFailureView' => 'AphrontView', 'PhabricatorOAuthLoginController' => 'PhabricatorAuthController', 'PhabricatorOAuthProviderFacebook' => 'PhabricatorOAuthProvider', 'PhabricatorOAuthProviderGithub' => 'PhabricatorOAuthProvider', + 'PhabricatorOAuthUnlinkController' => 'PhabricatorAuthController', 'PhabricatorPHID' => 'PhabricatorPHIDDAO', 'PhabricatorPHIDAllocateController' => 'PhabricatorPHIDController', 'PhabricatorPHIDController' => 'PhabricatorController', 'PhabricatorPHIDDAO' => 'PhabricatorLiskDAO', 'PhabricatorPHIDListController' => 'PhabricatorPHIDController', 'PhabricatorPHIDLookupController' => 'PhabricatorPHIDController', 'PhabricatorPHIDType' => 'PhabricatorPHIDDAO', 'PhabricatorPHIDTypeEditController' => 'PhabricatorPHIDController', 'PhabricatorPHIDTypeListController' => 'PhabricatorPHIDController', 'PhabricatorPeopleController' => 'PhabricatorController', 'PhabricatorPeopleEditController' => 'PhabricatorPeopleController', 'PhabricatorPeopleListController' => 'PhabricatorPeopleController', 'PhabricatorPeopleProfileController' => 'PhabricatorPeopleController', 'PhabricatorPeopleProfileEditController' => 'PhabricatorPeopleController', 'PhabricatorProject' => 'PhabricatorProjectDAO', 'PhabricatorProjectAffiliation' => 'PhabricatorProjectDAO', 'PhabricatorProjectAffiliationEditController' => 'PhabricatorProjectController', 'PhabricatorProjectController' => 'PhabricatorController', 'PhabricatorProjectDAO' => 'PhabricatorLiskDAO', 'PhabricatorProjectEditController' => 'PhabricatorProjectController', 'PhabricatorProjectListController' => 'PhabricatorProjectController', 'PhabricatorProjectProfile' => 'PhabricatorProjectDAO', 'PhabricatorProjectProfileController' => 'PhabricatorProjectController', 'PhabricatorRemarkupRuleDifferential' => 'PhutilRemarkupRule', 'PhabricatorRemarkupRuleManiphest' => 'PhutilRemarkupRule', 'PhabricatorRepository' => 'PhabricatorRepositoryDAO', 'PhabricatorRepositoryController' => 'PhabricatorController', 'PhabricatorRepositoryCreateController' => 'PhabricatorController', 'PhabricatorRepositoryDAO' => 'PhabricatorLiskDAO', 'PhabricatorRepositoryEditController' => 'PhabricatorController', 'PhabricatorRepositoryGitHubNotification' => 'PhabricatorRepositoryDAO', 'PhabricatorRepositoryGitHubPostReceiveController' => 'PhabricatorRepositoryController', 'PhabricatorRepositoryListController' => 'PhabricatorController', 'PhabricatorSearchBaseController' => 'PhabricatorController', 'PhabricatorSearchController' => 'PhabricatorSearchBaseController', 'PhabricatorSearchDAO' => 'PhabricatorLiskDAO', 'PhabricatorSearchDifferentialIndexer' => 'PhabricatorSearchDocumentIndexer', 'PhabricatorSearchDocument' => 'PhabricatorSearchDAO', 'PhabricatorSearchDocumentField' => 'PhabricatorSearchDAO', 'PhabricatorSearchDocumentRelationship' => 'PhabricatorSearchDAO', 'PhabricatorSearchManiphestIndexer' => 'PhabricatorSearchDocumentIndexer', 'PhabricatorSearchMySQLExecutor' => 'PhabricatorSearchExecutor', 'PhabricatorSearchQuery' => 'PhabricatorSearchDAO', 'PhabricatorStandardPageView' => 'AphrontPageView', 'PhabricatorTypeaheadCommonDatasourceController' => 'PhabricatorTypeaheadDatasourceController', 'PhabricatorTypeaheadDatasourceController' => 'PhabricatorController', 'PhabricatorUser' => 'PhabricatorUserDAO', 'PhabricatorUserDAO' => 'PhabricatorLiskDAO', 'PhabricatorUserOAuthInfo' => 'PhabricatorUserDAO', 'PhabricatorUserProfile' => 'PhabricatorUserDAO', 'PhabricatorUserSettingsController' => 'PhabricatorPeopleController', 'PhabricatorXHProfController' => 'PhabricatorController', 'PhabricatorXHProfProfileController' => 'PhabricatorXHProfController', 'PhabricatorXHProfProfileSymbolView' => 'AphrontView', 'PhabricatorXHProfProfileTopLevelView' => 'AphrontView', ), 'requires_interface' => array( ), )); diff --git a/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php b/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php index 3b3dee2f44..4047a6c7c0 100644 --- a/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php +++ b/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php @@ -1,286 +1,287 @@ array( '$' => 'PhabricatorDirectoryMainController', ), '/directory/' => array( 'item/$' => 'PhabricatorDirectoryItemListController', 'item/edit/(?:(?P\d+)/)?$' => 'PhabricatorDirectoryItemEditController', 'item/delete/(?P\d+)/' => 'PhabricatorDirectoryItemDeleteController', 'category/$' => 'PhabricatorDirectoryCategoryListController', 'category/edit/(?:(?P\d+)/)?$' => 'PhabricatorDirectoryCategoryEditController', 'category/delete/(?P\d+)/' => 'PhabricatorDirectoryCategoryDeleteController', ), '/file/' => array( '$' => 'PhabricatorFileListController', 'upload/$' => 'PhabricatorFileUploadController', '(?Pinfo)/(?P[^/]+)/' => 'PhabricatorFileViewController', '(?Pview)/(?P[^/]+)/' => 'PhabricatorFileViewController', '(?Pdownload)/(?P[^/]+)/' => 'PhabricatorFileViewController', ), '/phid/' => array( '$' => 'PhabricatorPHIDLookupController', 'list/$' => 'PhabricatorPHIDListController', 'type/$' => 'PhabricatorPHIDTypeListController', 'type/edit/(?:(?P\d+)/)?$' => 'PhabricatorPHIDTypeEditController', 'new/$' => 'PhabricatorPHIDAllocateController', ), '/people/' => array( '$' => 'PhabricatorPeopleListController', 'edit/(?:(?P\w+)/)?$' => 'PhabricatorPeopleEditController', ), '/p/(?P\w+)/$' => 'PhabricatorPeopleProfileController', '/profile/' => array( 'edit/$' => 'PhabricatorPeopleProfileEditController', ), '/conduit/' => array( '$' => 'PhabricatorConduitConsoleController', 'method/(?P[^/]+)$' => 'PhabricatorConduitConsoleController', 'log/$' => 'PhabricatorConduitLogController', ), '/api/(?P[^/]+)$' => 'PhabricatorConduitAPIController', '/D(?P\d+)' => 'DifferentialRevisionViewController', '/differential/' => array( '$' => 'DifferentialRevisionListController', 'filter/(?P\w+)/$' => 'DifferentialRevisionListController', 'diff/' => array( '(?P\d+)/$' => 'DifferentialDiffViewController', 'create/$' => 'DifferentialDiffCreateController', ), 'changeset/$' => 'DifferentialChangesetViewController', 'revision/edit/(?:(?P\d+)/)?$' => 'DifferentialRevisionEditController', 'comment/' => array( 'preview/(?P\d+)/$' => 'DifferentialCommentPreviewController', 'save/$' => 'DifferentialCommentSaveController', 'inline/' => array( 'preview/(?P\d+)/$' => 'DifferentialInlineCommentPreviewController', 'edit/(?P\d+)/$' => 'DifferentialInlineCommentEditController', ), ), 'attach/(?P\d+)/(?P\w+)/$' => 'DifferentialAttachController', 'subscribe/(?Padd|rem)/(?P\d+)/$' => 'DifferentialSubscribeController', ), '/res/' => array( '(?Ppkg/)?(?P[a-f0-9]{8})/(?P.+\.(?:css|js))$' => 'CelerityResourceController', ), '/typeahead/' => array( 'common/(?P\w+)/$' => 'PhabricatorTypeaheadCommonDatasourceController', ), '/mail/' => array( '$' => 'PhabricatorMetaMTAListController', 'send/$' => 'PhabricatorMetaMTASendController', 'view/(?P\d+)/$' => 'PhabricatorMetaMTAViewController', 'lists/$' => 'PhabricatorMetaMTAMailingListsController', 'lists/edit/(?:(?P\d+)/)?$' => 'PhabricatorMetaMTAMailingListEditController', ), '/login/' => array( '$' => 'PhabricatorLoginController', 'email/$' => 'PhabricatorEmailLoginController', 'etoken/(?P\w+)/$' => 'PhabricatorEmailTokenController', ), '/logout/$' => 'PhabricatorLogoutController', '/facebook-auth/' => array( '$' => 'PhabricatorFacebookAuthController', 'diagnose/$' => 'PhabricatorFacebookAuthDiagnosticsController', ), '/oauth/' => array( '(?Pgithub|facebook)/' => array( 'login/$' => 'PhabricatorOAuthLoginController', 'diagnose/$' => 'PhabricatorOAuthDiagnosticsController', + 'unlink/$' => 'PhabricatorOAuthUnlinkController', ), ), '/xhprof/' => array( 'profile/(?P[^/]+)/$' => 'PhabricatorXHProfProfileController', ), '/~/' => 'DarkConsoleController', '/settings/' => array( '(?:page/(?P[^/]+)/)?$' => 'PhabricatorUserSettingsController', ), '/maniphest/' => array( '$' => 'ManiphestTaskListController', 'view/(?P\w+)/$' => 'ManiphestTaskListController', 'task/' => array( 'create/$' => 'ManiphestTaskEditController', 'edit/(?P\d+)/$' => 'ManiphestTaskEditController', ), 'transaction/' => array( 'save/' => 'ManiphestTransactionSaveController', ), 'select/search/$' => 'ManiphestTaskSelectorSearchController', ), '/T(?P\d+)$' => 'ManiphestTaskDetailController', '/github-post-receive/(?P\d+)/(?P[^/]+)/$' => 'PhabricatorRepositoryGitHubPostReceiveController', '/repository/' => array( '$' => 'PhabricatorRepositoryListController', 'create/$' => 'PhabricatorRepositoryCreateController', 'edit/(?P\d+)/$' => 'PhabricatorRepositoryEditController', 'delete/(?P\d+)/$' => 'PhabricatorRepositoryDeleteController', ), '/search/' => array( '$' => 'PhabricatorSearchController', '(?P\d+)/$' => 'PhabricatorSearchController', ), '/project/' => array( '$' => 'PhabricatorProjectListController', 'edit/(?:(?P\d+)/)?$' => 'PhabricatorProjectEditController', 'view/(?P\d+)/$' => 'PhabricatorProjectProfileController', 'affiliation/(?P\d+)/$' => 'PhabricatorProjectAffiliationEditController', ) ); } public function buildRequest() { $request = new AphrontRequest($this->getHost(), $this->getPath()); $request->setRequestData($_GET + $_POST); $request->setApplicationConfiguration($this); return $request; } public function handleException(Exception $ex) { $class = phutil_escape_html(get_class($ex)); $message = phutil_escape_html($ex->getMessage()); $content = '
'. '

Unhandled Exception "'.$class.'": '.$message.'

'. ''.phutil_escape_html((string)$ex).''. '
'; if ($this->getRequest()->isAjax()) { $dialog = new AphrontDialogView(); $dialog ->setTitle('Exception!') ->setClass('aphront-exception-dialog') ->setUser($this->getRequest()->getUser()) ->appendChild($content) ->addCancelButton('/'); $response = new AphrontDialogResponse(); $response->setDialog($dialog); return $response; } $view = new PhabricatorStandardPageView(); $view->setRequest($this->getRequest()); $view->appendChild($content); $response = new AphrontWebpageResponse(); $response->setContent($view->render()); return $response; } public function willSendResponse(AphrontResponse $response) { $request = $this->getRequest(); if ($response instanceof AphrontDialogResponse) { if (!$request->isAjax()) { $view = new PhabricatorStandardPageView(); $view->setRequest($request); $view->appendChild( '
'. $response->buildResponseString(). '
'); $response = new AphrontWebpageResponse(); $response->setContent($view->render()); return $response; } else { return id(new AphrontAjaxResponse()) ->setContent(array( 'dialog' => $response->buildResponseString(), )); } } else if ($response instanceof AphrontRedirectResponse) { if ($request->isAjax()) { return id(new AphrontAjaxResponse()) ->setContent( array( 'redirect' => $response->getURI(), )); } } else if ($response instanceof Aphront404Response) { $failure = new AphrontRequestFailureView(); $failure->setHeader('404 Not Found'); $failure->appendChild( '

The page you requested was not found.

'); $view = new PhabricatorStandardPageView(); $view->setTitle('404 Not Found'); $view->setRequest($this->getRequest()); $view->appendChild($failure); $response = new AphrontWebpageResponse(); $response->setContent($view->render()); $response->setHTTPResponseCode(404); return $response; } return $response; } public function build404Controller() { return array(new Phabricator404Controller($this->getRequest()), array()); } } diff --git a/src/applications/auth/controller/login/PhabricatorLoginController.php b/src/applications/auth/controller/login/PhabricatorLoginController.php index 57979f3fde..eca7c6c648 100644 --- a/src/applications/auth/controller/login/PhabricatorLoginController.php +++ b/src/applications/auth/controller/login/PhabricatorLoginController.php @@ -1,144 +1,149 @@ getRequest(); + if ($request->getUser()->getPHID()) { + // Kick the user out if they're already logged in. + return id(new AphrontRedirectResponse())->setURI('/'); + } + $error = false; $username = $request->getCookie('phusr'); if ($request->isFormPost()) { $username = $request->getStr('username'); $user = id(new PhabricatorUser())->loadOneWhere( 'username = %s', $username); $okay = false; if ($user) { if ($user->comparePassword($request->getStr('password'))) { $session_key = $user->establishSession('web'); $request->setCookie('phusr', $user->getUsername()); $request->setCookie('phsid', $session_key); return id(new AphrontRedirectResponse()) ->setURI('/'); } } if (!$okay) { $request->clearCookie('phusr'); $request->clearCookie('phsid'); } $error = true; } $error_view = null; if ($error) { $error_view = new AphrontErrorView(); $error_view->setTitle('Bad username/password.'); } $form = new AphrontFormView(); $form ->setUser($request->getUser()) ->setAction('/login/') ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Username/Email') ->setName('username') ->setValue($username)) ->appendChild( id(new AphrontFormPasswordControl()) ->setLabel('Password') ->setName('password') ->setCaption( 'Forgot your password? / Email Login')) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Login')); $panel = new AphrontPanelView(); $panel->setHeader('Phabricator Login'); $panel->setWidth(AphrontPanelView::WIDTH_FORM); // $panel->setCreateButton('Register New Account', '/login/register/'); $panel->appendChild($form); $providers = array( PhabricatorOAuthProvider::PROVIDER_FACEBOOK, PhabricatorOAuthProvider::PROVIDER_GITHUB, ); foreach ($providers as $provider_key) { $provider = PhabricatorOAuthProvider::newProvider($provider_key); $enabled = $provider->isProviderEnabled(); if (!$enabled) { continue; } $auth_uri = $provider->getAuthURI(); $redirect_uri = $provider->getRedirectURI(); $client_id = $provider->getClientID(); $provider_name = $provider->getProviderName(); // TODO: In theory we should use 'state' to prevent CSRF, but the total // effect of the CSRF attack is that an attacker can cause a user to login // to Phabricator if they're already logged into some OAuth provider. This // does not seem like the most severe threat in the world, and generating // CSRF for logged-out users is vaugely tricky. $auth_form = new AphrontFormView(); $auth_form ->setAction($auth_uri) ->addHiddenInput('client_id', $client_id) ->addHiddenInput('redirect_uri', $redirect_uri) ->setUser($request->getUser()) ->setMethod('GET') ->appendChild( '

Login or register for '. 'Phabricator using your '.$provider_name.' account.

') ->appendChild( id(new AphrontFormSubmitControl()) ->setValue("Login with {$provider_name} \xC2\xBB")); $panel->appendChild( '

Login or Register with '.$provider_name.'

'); $panel->appendChild($auth_form); } return $this->buildStandardPageResponse( array( $error_view, $panel, ), array( 'title' => 'Login', )); } } diff --git a/src/applications/auth/controller/oauth/PhabricatorOAuthLoginController.php b/src/applications/auth/controller/oauth/PhabricatorOAuthLoginController.php index d84e266de6..6238e30ad0 100644 --- a/src/applications/auth/controller/oauth/PhabricatorOAuthLoginController.php +++ b/src/applications/auth/controller/oauth/PhabricatorOAuthLoginController.php @@ -1,371 +1,417 @@ provider = PhabricatorOAuthProvider::newProvider($data['provider']); } public function processRequest() { $current_user = $this->getRequest()->getUser(); - if ($current_user->getPHID()) { - // If we're already logged in, ignore everything going on here. TODO: - // restore account linking. - return id(new AphrontRedirectResponse())->setURI('/'); - } $provider = $this->provider; if (!$provider->isProviderEnabled()) { return new Aphront400Response(); } + $provider_name = $provider->getProviderName(); + $provider_key = $provider->getProviderKey(); + $request = $this->getRequest(); if ($request->getStr('error')) { $error_view = id(new PhabricatorOAuthFailureView()) ->setRequest($request); return $this->buildErrorResponse($error_view); } $token = $request->getStr('token'); if (!$token) { $client_id = $provider->getClientID(); $client_secret = $provider->getClientSecret(); $redirect_uri = $provider->getRedirectURI(); $auth_uri = $provider->getTokenURI(); $code = $request->getStr('code'); $query_data = array( 'client_id' => $client_id, 'client_secret' => $client_secret, 'redirect_uri' => $redirect_uri, 'code' => $code, ); $stream_context = stream_context_create( array( 'http' => array( 'method' => 'POST', 'header' => 'Content-type: application/x-www-form-urlencoded', 'content' => http_build_query($query_data), ), )); $stream = fopen($auth_uri, 'r', false, $stream_context); $meta = stream_get_meta_data($stream); $response = stream_get_contents($stream); fclose($stream); if ($response === false) { return $this->buildErrorResponse(new PhabricatorOAuthFailureView()); } $data = array(); parse_str($response, $data); $token = idx($data, 'access_token'); if (!$token) { return $this->buildErrorResponse(new PhabricatorOAuthFailureView()); } } $userinfo_uri = new PhutilURI($provider->getUserInfoURI()); $userinfo_uri->setQueryParams( array( 'access_token' => $token, )); $user_json = @file_get_contents($userinfo_uri); $user_data = json_decode($user_json, true); $this->accessToken = $token; switch ($provider->getProviderKey()) { case PhabricatorOAuthProvider::PROVIDER_GITHUB: $user_data = $user_data['user']; break; } $this->userData = $user_data; $user_id = $this->retrieveUserID(); - // Login with known auth. - $known_oauth = id(new PhabricatorUserOAuthInfo())->loadOneWhere( 'oauthProvider = %s and oauthUID = %s', $provider->getProviderKey(), $user_id); + + if ($current_user->getPHID()) { + + if ($known_oauth) { + if ($known_oauth->getUserID() != $current_user->getID()) { + $dialog = new AphrontDialogView(); + $dialog->setUser($current_user); + $dialog->setTitle('Already Linked to Another Account'); + $dialog->appendChild( + '

The '.$provider_name.' account you just authorized '. + 'is already linked to another Phabricator account. Before you can '. + 'associate your '.$provider_name.' account with this Phabriactor '. + 'account, you must unlink it from the Phabricator account it is '. + 'currently linked to.

'); + $dialog->addCancelButton('/settings/page/'.$provider_key.'/'); + + return id(new AphrontDialogResponse())->setDialog($dialog); + } else { + return id(new AphrontRedirectResponse()) + ->setURI('/settings/page/'.$provider_key.'/'); + } + } + + if (!$request->isDialogFormPost()) { + $dialog = new AphrontDialogView(); + $dialog->setUser($current_user); + $dialog->setTitle('Link '.$provider_name.' Account'); + $dialog->appendChild( + '

Link your '.$provider_name.' account to your Phabricator '. + 'account?

'); + $dialog->addHiddenInput('token', $token); + $dialog->addSubmitButton('Link Accounts'); + $dialog->addCancelButton('/settings/page/'.$provider_key.'/'); + + return id(new AphrontDialogResponse())->setDialog($dialog); + } + + $oauth_info = new PhabricatorUserOAuthInfo(); + $oauth_info->setUserID($current_user->getID()); + $oauth_info->setOAuthProvider($provider_key); + $oauth_info->setOAuthUID($user_id); + $oauth_info->save(); + + return id(new AphrontRedirectResponse()) + ->setURI('/settings/page/'.$provider_key.'/'); + } + + + // Login with known auth. + if ($known_oauth) { $known_user = id(new PhabricatorUser())->load($known_oauth->getUserID()); $session_key = $known_user->establishSession('web'); $request->setCookie('phusr', $known_user->getUsername()); $request->setCookie('phsid', $session_key); return id(new AphrontRedirectResponse()) ->setURI('/'); } // Merge accounts based on shared email. TODO: should probably get rid of // this. $oauth_email = $this->retrieveUserEmail(); if ($oauth_email) { $known_email = id(new PhabricatorUser()) ->loadOneWhere('email = %s', $oauth_email); if ($known_email) { $known_oauth = id(new PhabricatorUserOAuthInfo())->loadOneWhere( 'userID = %d AND oauthProvider = %s', $known_email->getID(), $provider->getProviderKey()); if ($known_oauth) { $provider_name = $provider->getName(); throw new Exception( "The email associated with the ".$provider_name." account you ". "just logged in with is already associated with another ". "Phabricator account which is, in turn, associated with a ". $provider_name." account different from the one you just logged ". "in with."); } $oauth_info = new PhabricatorUserOAuthInfo(); $oauth_info->setUserID($known_email->getID()); $oauth_info->setOAuthProvider($provider->getProviderKey()); $oauth_info->setOAuthUID($user_id); $oauth_info->save(); $session_key = $known_email->establishSession('web'); $request->setCookie('phusr', $known_email->getUsername()); $request->setCookie('phsid', $session_key); return id(new AphrontRedirectResponse()) ->setURI('/'); } } $errors = array(); $e_username = true; $e_email = true; $e_realname = true; $user = new PhabricatorUser(); $suggestion = $this->retrieveUsernameSuggestion(); $user->setUsername($suggestion); $oauth_realname = $this->retreiveRealNameSuggestion(); if ($request->isFormPost()) { $user->setUsername($request->getStr('username')); $username = $user->getUsername(); $matches = null; if (!strlen($user->getUsername())) { $e_username = 'Required'; $errors[] = 'Username is required.'; } else if (!preg_match('/^[a-zA-Z0-9]+$/', $username, $matches)) { $e_username = 'Invalid'; $errors[] = 'Username may only contain letters and numbers.'; } else { $e_username = null; } if ($oauth_email) { $user->setEmail($oauth_email); } else { $user->setEmail($request->getStr('email')); if (!strlen($user->getEmail())) { $e_email = 'Required'; $errors[] = 'Email is required.'; } else { $e_email = null; } } if ($oauth_realname) { $user->setRealName($oauth_realname); } else { $user->setRealName($request->getStr('realname')); if (!strlen($user->getStr('realname'))) { $e_realname = 'Required'; $errors[] = 'Real name is required.'; } else { $e_realname = null; } } if (!$errors) { $image = $this->retreiveProfileImageSuggestion(); if ($image) { $file = PhabricatorFile::newFromFileData( $image, array( 'name' => $provider->getProviderKey().'-profile.jpg' )); $user->setProfileImagePHID($file->getPHID()); } try { $user->save(); $oauth_info = new PhabricatorUserOAuthInfo(); $oauth_info->setUserID($user->getID()); $oauth_info->setOAuthProvider($provider->getProviderKey()); $oauth_info->setOAuthUID($user_id); $oauth_info->save(); $session_key = $user->establishSession('web'); $request->setCookie('phusr', $user->getUsername()); $request->setCookie('phsid', $session_key); return id(new AphrontRedirectResponse())->setURI('/'); } catch (AphrontQueryDuplicateKeyException $exception) { $key = $exception->getDuplicateKey(); if ($key == 'userName') { $e_username = 'Duplicate'; $errors[] = 'That username is not unique.'; } else if ($key == 'email') { $e_email = 'Duplicate'; $errors[] = 'That email is not unique.'; } else { throw $exception; } } } } $error_view = null; if ($errors) { $error_view = new AphrontErrorView(); $error_view->setTitle('Registration Failed'); $error_view->setErrors($errors); } $form = new AphrontFormView(); $form ->addHiddenInput('token', $token) ->setUser($request->getUser()) ->setAction($provider->getRedirectURI()) ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Username') ->setName('username') ->setValue($user->getUsername()) ->setError($e_username)); if (!$oauth_email) { $form->appendChild( id(new AphrontFormTextControl()) ->setLabel('Email') ->setName('email') ->setValue($request->getStr('email')) ->setError($e_email)); } if (!$oauth_realname) { $form->appendChild( id(new AphrontFormTextControl()) ->setLabel('Real Name') ->setName('realname') ->setValue($request->getStr('realname')) ->setError($e_realname)); } $form ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Create Account')); $panel = new AphrontPanelView(); $panel->setHeader('Create New Account'); $panel->setWidth(AphrontPanelView::WIDTH_FORM); $panel->appendChild($form); return $this->buildStandardPageResponse( array( $error_view, $panel, ), array( 'title' => 'Create New Account', )); } private function buildErrorResponse(PhabricatorOAuthFailureView $view) { $provider = $this->provider; $provider_name = $provider->getProviderName(); $view->setOAuthProvider($provider); return $this->buildStandardPageResponse( $view, array( 'title' => $provider_name.' Auth Failed', )); } private function retrieveUserID() { return $this->userData['id']; } private function retrieveUserEmail() { return $this->userData['email']; } private function retrieveUsernameSuggestion() { switch ($this->provider->getProviderKey()) { case PhabricatorOAuthProvider::PROVIDER_FACEBOOK: $matches = null; $link = $this->userData['link']; if (preg_match('@/([a-zA-Z0-9]+)$@', $link, $matches)) { return $matches[1]; } break; case PhabricatorOAuthProvider::PROVIDER_GITHUB: return $this->userData['login']; } return null; } private function retreiveProfileImageSuggestion() { switch ($this->provider->getProviderKey()) { case PhabricatorOAuthProvider::PROVIDER_FACEBOOK: $uri = 'https://graph.facebook.com/me/picture?access_token='; return @file_get_contents($uri.$this->accessToken); case PhabricatorOAuthProvider::PROVIDER_GITHUB: $id = $this->userData['gravatar_id']; if ($id) { $uri = 'http://www.gravatar.com/avatar/'.$id.'?s=50'; return @file_get_contents($uri); } } return null; } private function retreiveRealNameSuggestion() { return $this->userData['name']; } } diff --git a/src/applications/auth/controller/oauth/__init__.php b/src/applications/auth/controller/oauth/__init__.php index d9e4cc1b35..19082b7aa2 100644 --- a/src/applications/auth/controller/oauth/__init__.php +++ b/src/applications/auth/controller/oauth/__init__.php @@ -1,27 +1,29 @@ provider = PhabricatorOAuthProvider::newProvider($data['provider']); + } + + public function processRequest() { + $request = $this->getRequest(); + $user = $request->getUser(); + + $provider = $this->provider; + $provider_name = $provider->getProviderName(); + $provider_key = $provider->getProviderKey(); + + $oauth_info = id(new PhabricatorUserOAuthInfo())->loadOneWhere( + 'userID = %d AND oauthProvider = %s', + $user->getID(), + $provider_key); + + if (!$oauth_info) { + return new Aphront400Response(); + } + + if (!$request->isDialogFormPost()) { + $dialog = new AphrontDialogView(); + $dialog->setUser($user); + $dialog->setTitle('Really unlink account?'); + $dialog->appendChild( + '

You will not be able to login using this account '. + 'once you unlink it. Continue?

'); + $dialog->addSubmitButton('Unlink Account'); + $dialog->addCancelButton('/settings/page/'.$provider_key.'/'); + + return id(new AphrontDialogResponse())->setDialog($dialog); + } + + $oauth_info->delete(); + + return id(new AphrontRedirectResponse()) + ->setURI('/settings/page/'.$provider_key.'/'); + } + +} diff --git a/src/applications/auth/controller/unlink/__init__.php b/src/applications/auth/controller/unlink/__init__.php new file mode 100644 index 0000000000..53b32b66c2 --- /dev/null +++ b/src/applications/auth/controller/unlink/__init__.php @@ -0,0 +1,20 @@ +page = idx($data, 'page'); } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $pages = array( 'account' => 'Account', 'email' => 'Email', // 'password' => 'Password', -// 'facebook' => 'Facebook Account', 'arcanist' => 'Arcanist Certificate', ); + $oauth_providers = PhabricatorOAuthProvider::getAllProviders(); + foreach ($oauth_providers as $provider) { + if (!$provider->isProviderEnabled()) { + continue; + } + $key = $provider->getProviderKey(); + $name = $provider->getProviderName(); + $pages[$key] = $name.' Account'; + } + if (empty($pages[$this->page])) { $this->page = key($pages); } if ($request->isFormPost()) { switch ($this->page) { case 'email': $user->setEmail($request->getStr('email')); $user->save(); return id(new AphrontRedirectResponse()) ->setURI('/settings/page/email/?saved=true'); case 'arcanist': if (!$request->isDialogFormPost()) { $dialog = new AphrontDialogView(); $dialog->setUser($user); $dialog->setTitle('Really regenerate session?'); $dialog->setSubmitURI('/settings/page/arcanist/'); $dialog->addSubmitButton('Regenerate'); $dialog->addCancelbutton('/settings/page/arcanist/'); $dialog->appendChild( '

Really destroy the old certificate? Any established '. 'sessions will be terminated.'); return id(new AphrontDialogResponse()) ->setDialog($dialog); } $conn = $user->establishConnection('w'); queryfx( $conn, 'DELETE FROM %T WHERE userPHID = %s AND type LIKE %>', PhabricatorUser::SESSION_TABLE, $user->getPHID(), 'conduit'); // This implicitly regenerates the certificate. $user->setConduitCertificate(null); $user->save(); return id(new AphrontRedirectResponse()) ->setURI('/settings/page/arcanist/?regenerated=true'); case 'account': if (!empty($_FILES['profile'])) { $err = idx($_FILES['profile'], 'error'); if ($err != UPLOAD_ERR_NO_FILE) { $file = PhabricatorFile::newFromPHPUpload($_FILES['profile']); $user->setProfileImagePHID($file->getPHID()); } } $user->save(); return id(new AphrontRedirectResponse()) ->setURI('/settings/page/account/'); } } switch ($this->page) { case 'arcanist': $content = $this->renderArcanistCertificateForm(); break; case 'account': $content = $this->renderAccountForm(); break; case 'email': $content = $this->renderEmailForm(); break; default: - $content = 'derp derp'; + if (empty($pages[$this->page])) { + return new Aphront404Response(); + } + $content = $this->renderOAuthForm($oauth_providers[$this->page]); break; } $sidenav = new AphrontSideNavView(); foreach ($pages as $page => $name) { $sidenav->addNavItem( phutil_render_tag( 'a', array( 'href' => '/settings/page/'.$page.'/', 'class' => ($page == $this->page) ? 'aphront-side-nav-selected' : null, ), phutil_escape_html($name))); } $sidenav->appendChild($content); return $this->buildStandardPageResponse( $sidenav, array( 'title' => 'Account Settings', )); } private function renderArcanistCertificateForm() { $request = $this->getRequest(); $user = $request->getUser(); if ($request->getStr('regenerated')) { $notice = new AphrontErrorView(); $notice->setSeverity(AphrontErrorView::SEVERITY_NOTICE); $notice->setTitle('Certificate Regenerated'); $notice->appendChild( '

Your old certificate has been destroyed and you have been issued '. 'a new certificate. Sessions established under the old certificate '. 'are no longer valid.

'); $notice = $notice->render(); } else { $notice = null; } $host = PhabricatorEnv::getEnvConfig('phabricator.conduit-uri'); $cert_form = new AphrontFormView(); $cert_form ->setUser($user) ->appendChild( '

Copy and paste this certificate '. 'into your ~/.arcrc in the "hosts" section to enable '. 'Arcanist to authenticate against this host.

') ->appendChild( id(new AphrontFormTextAreaControl()) ->setLabel('Certificate') ->setHeight(AphrontFormTextAreaControl::HEIGHT_SHORT) ->setValue($user->getConduitCertificate())); $cert = new AphrontPanelView(); $cert->setHeader('Arcanist Certificate'); $cert->appendChild($cert_form); $cert->setWidth(AphrontPanelView::WIDTH_FORM); $regen_form = new AphrontFormView(); $regen_form ->setUser($user) ->setWorkflow(true) ->setAction('/settings/page/arcanist/') ->appendChild( '

You can regenerate this '. 'certificate, which will invalidate the old certificate and create '. 'a new one.

') ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Regenerate Certificate')); $regen = new AphrontPanelView(); $regen->setHeader('Regenerate Certificate'); $regen->appendChild($regen_form); $regen->setWidth(AphrontPanelView::WIDTH_FORM); return $notice.$cert->render().$regen->render(); } private function renderAccountForm() { $request = $this->getRequest(); $user = $request->getUser(); $img_src = PhabricatorFileURI::getViewURIForPHID( $user->getProfileImagePHID()); $form = new AphrontFormView(); $form ->setUser($user) ->setEncType('multipart/form-data') ->appendChild( id(new AphrontFormStaticControl()) ->setLabel('Username') ->setValue($user->getUsername())) ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Real Name') ->setValue($user->getRealName())) ->appendChild( id(new AphrontFormMarkupControl()) ->setValue('
')) ->appendChild( id(new AphrontFormMarkupControl()) ->setLabel('Profile Image') ->setValue( phutil_render_tag( 'img', array( 'src' => $img_src, )))) ->appendChild( id(new AphrontFormFileControl()) ->setLabel('Change Image') ->setName('profile') ->setCaption('Upload a 50x50px image.')) ->appendChild( id(new AphrontFormMarkupControl()) ->setValue('
')) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Save')); $panel = new AphrontPanelView(); $panel->setHeader('Profile Settings'); $panel->setWidth(AphrontPanelView::WIDTH_FORM); $panel->appendChild($form); return $panel->render(); } private function renderEmailForm() { $request = $this->getRequest(); $user = $request->getUser(); - if ($request->getStr('saved')) { $notice = new AphrontErrorView(); $notice->setSeverity(AphrontErrorView::SEVERITY_NOTICE); $notice->setTitle('Changed Saved'); $notice->appendChild('

Your changes have been saved.

'); $notice = $notice->render(); } else { $notice = null; } $form = new AphrontFormView(); $form ->setUser($user) ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Email') ->setName('email') ->setCaption( 'Note: there is no email validation yet; double-check your '. 'typing.') ->setValue($user->getEmail())) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Save')); $panel = new AphrontPanelView(); $panel->setHeader('Email Settings'); $panel->setWidth(AphrontPanelView::WIDTH_FORM); $panel->appendChild($form); return $notice.$panel->render(); } + private function renderOAuthForm(PhabricatorOAuthProvider $provider) { + + $request = $this->getRequest(); + $user = $request->getUser(); + + $notice = null; + + $provider_name = $provider->getProviderName(); + $provider_key = $provider->getProviderKey(); + + $oauth_info = id(new PhabricatorUserOAuthInfo())->loadOneWhere( + 'userID = %d AND oauthProvider = %s', + $user->getID(), + $provider->getProviderKey()); + + $form = new AphrontFormView(); + $form + ->setUser($user); + + $forms = array(); + $forms[] = $form; + if (!$oauth_info) { + $form + ->appendChild( + '

There is currently no '. + $provider_name.' account linked to your Phabricator account. You '. + 'can link an account, which will allow you to use it to log into '. + 'Phabricator.

'); + + switch ($provider_key) { + case PhabricatorOAuthProvider::PROVIDER_GITHUB: + $form->appendChild( + '

Additionally, you must '. + 'link your Github account before Phabricator can access any '. + 'information about hosted repositories.

'); + break; + } + + $auth_uri = $provider->getAuthURI(); + $client_id = $provider->getClientID(); + $redirect_uri = $provider->getRedirectURI(); + + $form + ->setAction($auth_uri) + ->setMethod('GET') + ->addHiddenInput('redirect_uri', $redirect_uri) + ->addHiddenInput('client_id', $client_id) + ->appendChild( + id(new AphrontFormSubmitControl()) + ->setValue('Link '.$provider_name." Account \xC2\xBB")); + } else { + $form + ->appendChild( + '

Your account is linked with '. + 'a '.$provider_name.' account. You may use your '.$provider_name.' '. + 'credentials to log into Phabricator.

') + ->appendChild( + id(new AphrontFormStaticControl()) + ->setLabel($provider_name.' ID') + ->setValue($oauth_info->getOAuthUID())); + + + $unlink = 'Unlink '.$provider_name.' Account'; + $unlink_form = new AphrontFormView(); + $unlink_form + ->setUser($user) + ->appendChild( + '

You may unlink this account '. + 'from your '.$provider_name.' account. This will prevent you from '. + 'logging in with your '.$provider_name.' credentials.

') + ->appendChild( + id(new AphrontFormSubmitControl()) + ->addCancelButton('/oauth/'.$provider_key.'/unlink/', $unlink)); + $forms['Unlink Account'] = $unlink_form; + } + + $panel = new AphrontPanelView(); + $panel->setHeader($provider_name.' Account Settings'); + $panel->setWidth(AphrontPanelView::WIDTH_FORM); + foreach ($forms as $name => $form) { + if ($name) { + $panel->appendChild('

'.$name.'

'); + } + $panel->appendChild($form); + } + + return $notice.$panel->render(); + + + } + } diff --git a/src/applications/people/controller/settings/__init__.php b/src/applications/people/controller/settings/__init__.php index 7a772e97cd..b57f21861e 100644 --- a/src/applications/people/controller/settings/__init__.php +++ b/src/applications/people/controller/settings/__init__.php @@ -1,29 +1,33 @@