diff --git a/conf/default.conf.php b/conf/default.conf.php index 077787e9e2..3261b22c6a 100644 --- a/conf/default.conf.php +++ b/conf/default.conf.php @@ -1,215 +1,237 @@ null, // The Conduit URI for API access to this install. Normally this is just // the 'base-uri' plus "/api/" (e.g. "http://phabricator.example.com/api/"), // but make sure you specify 'https' if you have HTTPS configured. 'phabricator.conduit-uri' => null, // The default PHID for users who haven't uploaded a profile image. It should // be 50x50px. 'user.default-profile-image-phid' => 'PHID-FILE-f57aaefce707fc4060ef', // -- Access Control -------------------------------------------------------- // // Phabricator users have one of three access levels: "anyone", "verified", // or "admin". "anyone" means every user, including users who do not have // accounts or are not logged into the system. "verified" is users who have // accounts, are logged in, and have satisfied whatever verification steps // the configuration requires (e.g., email verification and/or manual // approval). "admin" is verified users with the "administrator" flag set. // These configuration options control which access level is required to read // data from Phabricator (e.g., view revisions and comments in Differential) // and write data to Phabricator (e.g., upload files and create diffs). By // default they are both set to "verified", meaning only verified user // accounts can interact with the system in any meaningful way. // If you are configuring an install for an open source project, you may // want to reduce the "phabricator.read-access" requirement to "anyone". This // will allow anyone to browse Phabricator content, even without logging in. // Alternatively, you could raise the "phabricator.write-access" requirement // to "admin", effectively creating a read-only install. // Controls the minimum access level required to read data from Phabricator // (e.g., view revisions in Differential). Allowed values are "anyone", // "verified", or "admin". Note that "anyone" includes users who are not // logged in! You should leave this at 'verified' unless you want your data // to be publicly readable (e.g., you are developing open source software). 'phabricator.read-access' => 'verified', // Controls the minimum access level required to write data to Phabricator // (e.g., create new revisions in Differential). Allowed values are // "verified" or "admin". Setting this to "admin" will effectively create a // read-only install. 'phabricator.write-access' => 'verified', // -- DarkConsole ----------------------------------------------------------- // // DarkConsole is a administrative debugging/profiling tool built into // Phabricator. You can leave it disabled unless you're developing against // Phabricator. // Determines whether or not DarkConsole is available. DarkConsole exposes // some data like queries and stack traces, so you should be careful about // turning it on in production (although users can not normally see it, even // if the deployment configuration enables it). 'darkconsole.enabled' => true, // Always enable DarkConsole, even for logged out users. This potentially // exposes sensitive information to users, so make sure untrusted users can // not access an install running in this mode. You should definitely leave // this off in production. It is only really useful for using DarkConsole // utilties to debug or profile logged-out pages. You must set // 'darkconsole.enabled' to use this option. 'darkconsole.always-on' => false, // Allows you to mask certain configuration values from appearing in the // "Config" tab of DarkConsole. 'darkconsole.config-mask' => array( 'mysql.pass', 'amazon-ses.secret-key', 'recaptcha.private-key', 'phabricator.csrf-key', 'facebook.application-secret', + 'github.secret', ), // -- MySQL --------------------------------------------------------------- // // The username to use when connecting to MySQL. 'mysql.user' => 'root', // The password to use when connecting to MySQL. 'mysql.pass' => '', // The MySQL server to connect to. 'mysql.host' => 'localhost', // -- Email ----------------------------------------------------------------- // // Some Phabricator tools send email notifications, e.g. when Differential // revisions are updated or Maniphest tasks are changed. These options allow // you to configure how email is delivered. // You can test your mail setup by going to "MetaMTA" in the web interface, // clicking "Send New Message", and then composing a message. // Default address to send mail "From". 'metamta.default-address' => 'noreply@example.com', // When a user takes an action which generates an email notification (like // commenting on a Differential revision), Phabricator can either send that // mail "From" the user's email address (like "alincoln@logcabin.com") or // "From" the 'metamta.default-address' address. The user experience is // generally better if Phabricator uses the user's real address as the "From" // since the messages are easier to organize when they appear in mail clients, // but this will only work if the server is authorized to send email on behalf // of the "From" domain. Practically, this means: // - If you are doing an install for Example Corp and all the users will // have corporate @corp.example.com addresses and any hosts Phabricator // is running on are authorized to send email from corp.example.com, // you can enable this to make the user experience a little better. // - If you are doing an install for an open source project and your // users will be registering via Facebook and using personal email // addresses, you MUST NOT enable this or virtually all of your outgoing // email will vanish into SFP blackholes. // - If your install is anything else, you're much safer leaving this // off since the risk in turning it on is that your outgoing mail will // mostly never arrive. 'metamta.can-send-as-user' => false, // Adapter class to use to transmit mail to the MTA. The default uses // PHPMailerLite, which will invoke PHP's mail() function. This is appropriate // if mail() actually works on your host, but if you haven't configured mail // it may not be so great. You can also use Amazon SES, by changing this to // 'PhabricatorMailImplementationAmazonSESAdapter', signing up for SES, and // filling in your 'amazon-ses.access-key' and 'amazon-ses.secret-key' below. 'metamta.mail-adapter' => 'PhabricatorMailImplementationPHPMailerLiteAdapter', // When email is sent, try to hand it off to the MTA immediately. This may // be worth disabling if your MTA infrastructure is slow or unreliable. If you // disable this option, you must run the 'metamta_mta.php' daemon or mail // won't be handed off to the MTA. If you're using Amazon SES it can be a // little slugish sometimes so it may be worth disabling this and moving to // the daemon after you've got your install up and running. If you have a // properly configured local MTA it should not be necessary to disable this. 'metamta.send-immediately' => true, // If you're using Amazon SES to send email, provide your AWS access key // and AWS secret key here. To set up Amazon SES with Phabricator, you need // to: // - Make sure 'metamta.mail-adapter' is set to: // "PhabricatorMailImplementationAmazonSESAdapter" // - Make sure 'metamta.can-send-as-user' is false. // - Make sure 'metamta.default-address' is configured to something sensible. // - Make sure 'metamta.default-address' is a validated SES "From" address. 'amazon-ses.access-key' => null, 'amazon-ses.secret-key' => null, // -- Facebook ------------------------------------------------------------ // // Can users use Facebook credentials to login to Phabricator? 'facebook.auth-enabled' => false, // The Facebook "Application ID" to use for Facebook API access. 'facebook.application-id' => null, // The Facebook "Application Secret" to use for Facebook API access. 'facebook.application-secret' => null, +// -- Github ---------------------------------------------------------------- // + + // Can users use Github credentials to login to Phabricator? + 'github.auth-enabled' => false, + + // The Github "Client ID" to use for Github API access. + 'github.application-id' => null, + + // The Github "Secret" to use for Github API access. + 'github.application-secret' => null, + + + // Github Authorize URI. You don't need to change this unless Github changes + // its API in the future (this is unlikely). + 'github.authorize-uri' => 'https://github.com/login/oauth/authorize', + + // Github Access Token URI. You don't need to change this unless Github + // changes its API in the future (this is unlikely). + 'github.access-token-uri' => 'https://github.com/login/oauth/access_token', + + // -- Recaptcha ------------------------------------------------------------- // // Is Recaptcha enabled? If disabled, captchas will not appear. 'recaptcha.enabled' => false, // Your Recaptcha public key, obtained from Recaptcha. 'recaptcha.public-key' => null, // Your Recaptcha private key, obtained from Recaptcha. 'recaptcha.private-key' => null, // -- Misc ------------------------------------------------------------------ // // This is hashed with other inputs to generate CSRF tokens. If you want, you // can change it to some other string which is unique to your install. This // will make your install more secure in a vague, mostly theoretical way. But // it will take you like 3 seconds of mashing on your keyboard to set it up so // you might as well. 'phabricator.csrf-key' => '0b7ec0592e0a2829d8b71df2fa269b2c6172eca3', // Version string displayed in the footer. You probably should leave this // alone. 'phabricator.version' => 'UNSTABLE', ); diff --git a/resources/sql/patches/002.oauth.sql b/resources/sql/patches/002.oauth.sql new file mode 100644 index 0000000000..7cdaeb0a73 --- /dev/null +++ b/resources/sql/patches/002.oauth.sql @@ -0,0 +1,18 @@ +create table phabricator_user.user_oauthinfo ( + id int unsigned not null auto_increment primary key, + userID int unsigned not null, + oauthProvider varchar(255) not null, + oauthUID varchar(255) not null, + unique key (userID, oauthProvider), + unique key (oauthProvider, oauthUID), + dateCreated int unsigned not null, + dateModified int unsigned not null +); + +insert into phabricator_user.user_oauthinfo + (userID, oauthProvider, oauthUID, dateCreated, dateModified) + SELECT id, 'facebook', facebookUID, UNIX_TIMESTAMP(), UNIX_TIMESTAMP() + FROM phabricator_user.user + WHERE facebookUID is not null; + +alter table phabricator_user.user drop facebookUID; \ No newline at end of file diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 1e60390467..61286449f3 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1,520 +1,529 @@ 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', - 'PhabricatorFacebookAuthController' => 'applications/auth/controller/facebookauth', - 'PhabricatorFacebookAuthDiagnosticsController' => 'applications/auth/controller/facebookauth/diagnostics', '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', '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', - 'PhabricatorFacebookAuthController' => 'PhabricatorAuthController', - 'PhabricatorFacebookAuthDiagnosticsController' => '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', '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 5e3b169bcb..3b3dee2f44 100644 --- a/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php +++ b/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php @@ -1,279 +1,286 @@ 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', + ), + ), + '/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/facebookauth/PhabricatorFacebookAuthController.php b/src/applications/auth/controller/facebookauth/PhabricatorFacebookAuthController.php deleted file mode 100644 index ab23fefbb7..0000000000 --- a/src/applications/auth/controller/facebookauth/PhabricatorFacebookAuthController.php +++ /dev/null @@ -1,293 +0,0 @@ -'. - 'Diagnose Facebook Auth Problems'. - ''; - - $request = $this->getRequest(); - - if ($request->getStr('error')) { - $view = new AphrontRequestFailureView(); - $view->setHeader('Facebook Auth Failed'); - $view->appendChild( - '

'. - 'Description: '. - phutil_escape_html($request->getStr('error_description')). - '

'); - $view->appendChild( - '

'. - 'Error: '. - phutil_escape_html($request->getStr('error')). - '

'); - $view->appendChild( - '

'. - 'Error Reason: '. - phutil_escape_html($request->getStr('error_reason')). - '

'); - $view->appendChild( - '
'. - 'Continue'. - '
'); - - return $this->buildStandardPageResponse( - $view, - array( - 'title' => 'Facebook Auth Failed', - )); - } - - $token = $request->getStr('token'); - if (!$token) { - $app_id = PhabricatorEnv::getEnvConfig('facebook.application-id'); - $app_secret = PhabricatorEnv::getEnvConfig('facebook.application-secret'); - $redirect_uri = PhabricatorEnv::getURI('/facebook-auth/'); - - $code = $request->getStr('code'); - $auth_uri = new PhutilURI( - "https://graph.facebook.com/oauth/access_token"); - $auth_uri->setQueryParams( - array( - 'client_id' => $app_id, - 'redirect_uri' => $redirect_uri, - 'client_secret' => $app_secret, - 'code' => $code, - )); - - $response = @file_get_contents($auth_uri); - if ($response === false) { - $view = new AphrontRequestFailureView(); - $view->setHeader('Facebook Auth Failed'); - $view->appendChild( - '

Unable to authenticate with Facebook. There are several reasons '. - 'this might happen:

'. - '
    '. - '
  • Phabricator may be configured with the wrong Application '. - 'Secret; or
  • '. - '
  • the Facebook OAuth access token may have expired; or
  • '. - '
  • Facebook may have revoked authorization for the '. - 'Application; or
  • '. - '
  • Facebook may be having technical problems.
  • '. - '
'. - '

You can try again, or login using another method.

'); - $view->appendChild( - '
'. - $diagnose_auth. - 'Continue'. - '
'); - - return $this->buildStandardPageResponse( - $view, - array( - 'title' => 'Facebook Auth Failed', - )); - } - - $data = array(); - parse_str($response, $data); - - $token = $data['access_token']; - } - - $user_json = @file_get_contents('https://graph.facebook.com/me?access_token='.$token); - $user_data = json_decode($user_json, true); - - $user_id = $user_data['id']; - - $known_user = id(new PhabricatorUser()) - ->loadOneWhere('facebookUID = %d', $user_id); - if ($known_user) { - $session_key = $known_user->establishSession('web'); - $request->setCookie('phusr', $known_user->getUsername()); - $request->setCookie('phsid', $session_key); - return id(new AphrontRedirectResponse()) - ->setURI('/'); - } - - $known_email = id(new PhabricatorUser()) - ->loadOneWhere('email = %s', $user_data['email']); - if ($known_email) { - if ($known_email->getFacebookUID()) { - throw new Exception( - "The email associated with the Facebook account you just logged in ". - "with is already associated with another Phabricator account which ". - "is, in turn, associated with a Facebook account different from ". - "the one you just logged in with."); - } - $known_email->setFacebookUID($user_id); - $session_key = $known_email->establishSession('web'); - $request->setCookie('phusr', $known_email->getUsername()); - $request->setCookie('phsid', $session_key); - return id(new AphrontRedirectResponse()) - ->setURI('/'); - } - - $current_user = $this->getRequest()->getUser(); - if ($current_user->getPHID()) { - if ($current_user->getFacebookUID() && - $current_user->getFacebookUID() != $user_id) { - throw new Exception( - "Your account is already associated with a Facebook user ID other ". - "than the one you just logged in with...?"); - } - - if ($request->isFormPost()) { - $current_user->setFacebookUID($user_id); - $current_user->save(); - - // TODO: ship them back to the 'account' page or whatever? - return id(new AphrontRedirectResponse()) - ->setURI('/'); - } - - $ph_account = $current_user->getUsername(); - $fb_account = phutil_escape_html($user_data['name']); - - $form = new AphrontFormView(); - $form - ->addHiddenInput('token', $token) - ->setUser($request->getUser()) - ->setAction('/facebook-auth/') - ->appendChild( - '

Do you want to link your '. - "existing Phabricator account ({$ph_account}) ". - "with your Facebook account ({$fb_account}) so ". - "you can login with Facebook?") - ->appendChild( - id(new AphrontFormSubmitControl()) - ->setValue('Link Accounts') - ->addCancelButton('/login/')); - - $panel = new AphrontPanelView(); - $panel->setHeader('Link Facebook Account'); - $panel->setWidth(AphrontPanelView::WIDTH_FORM); - $panel->appendChild($form); - - return $this->buildStandardPageResponse( - $panel, - array( - 'title' => 'Link Facebook Account', - )); - } - - $errors = array(); - $e_username = true; - - $user = new PhabricatorUser(); - - $matches = null; - if (preg_match('@/([a-zA-Z0-9]+)$@', $user_data['link'], $matches)) { - $user->setUsername($matches[1]); - } - - if ($request->isFormPost()) { - - $username = $request->getStr('username'); - if (!strlen($username)) { - $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.'; - } - - $user->setUsername($username); - $user->setFacebookUID($user_id); - $user->setEmail($user_data['email']); - - if (!$errors) { - $image = @file_get_contents('https://graph.facebook.com/me/picture?access_token='.$token); - $file = PhabricatorFile::newFromFileData( - $image, - array( - 'name' => 'fbprofile.jpg' - )); - - $user->setProfileImagePHID($file->getPHID()); - $user->setRealName($user_data['name']); - - try { - $user->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 { - throw $exception; - } - } - } - } - - $error_view = null; - if ($errors) { - $error_view = new AphrontErrorView(); - $error_view->setTitle('Facebook Auth Failed'); - $error_view->setErrors($errors); - } - - $form = new AphrontFormView(); - $form - ->addHiddenInput('token', $token) - ->setUser($request->getUser()) - ->setAction('/facebook-auth/') - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel('Username') - ->setName('username') - ->setValue($user->getUsername()) - ->setError($e_username)) - ->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', - )); - } - -} diff --git a/src/applications/auth/controller/login/PhabricatorLoginController.php b/src/applications/auth/controller/login/PhabricatorLoginController.php index 774a26d7e9..57979f3fde 100644 --- a/src/applications/auth/controller/login/PhabricatorLoginController.php +++ b/src/applications/auth/controller/login/PhabricatorLoginController.php @@ -1,135 +1,144 @@ getRequest(); $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); - $fbauth_enabled = PhabricatorEnv::getEnvConfig('facebook.auth-enabled'); - if ($fbauth_enabled) { - $auth_uri = new PhutilURI("https://www.facebook.com/dialog/oauth"); - - $user = $request->getUser(); + $providers = array( + PhabricatorOAuthProvider::PROVIDER_FACEBOOK, + PhabricatorOAuthProvider::PROVIDER_GITHUB, + ); + foreach ($providers as $provider_key) { + $provider = PhabricatorOAuthProvider::newProvider($provider_key); + + $enabled = $provider->isProviderEnabled(); + if (!$enabled) { + continue; + } - $redirect_uri = PhabricatorEnv::getURI('/facebook-auth/'); - $app_id = PhabricatorEnv::getEnvConfig('facebook.application-id'); + $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 Facebook. This does not - // seem like the most severe threat in the world, and generating CSRF for - // logged-out users is vaugely tricky. + // 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. - $facebook_auth = new AphrontFormView(); - $facebook_auth + $auth_form = new AphrontFormView(); + $auth_form ->setAction($auth_uri) - ->addHiddenInput('client_id', $app_id) + ->addHiddenInput('client_id', $client_id) ->addHiddenInput('redirect_uri', $redirect_uri) - ->addHiddenInput('scope', 'email') ->setUser($request->getUser()) ->setMethod('GET') ->appendChild( '

Login or register for '. - 'Phabricator using your Facebook account.

') + 'Phabricator using your '.$provider_name.' account.

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

Login or Register with '.$provider_name.'

'); - $panel->appendChild('

Login or Register with Facebook

'); - $panel->appendChild($facebook_auth); + $panel->appendChild($auth_form); } return $this->buildStandardPageResponse( array( $error_view, $panel, ), array( 'title' => 'Login', )); } } diff --git a/src/applications/auth/controller/login/__init__.php b/src/applications/auth/controller/login/__init__.php index 85ec5fcd3e..532ff90b92 100644 --- a/src/applications/auth/controller/login/__init__.php +++ b/src/applications/auth/controller/login/__init__.php @@ -1,22 +1,21 @@ 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(); + } + + $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 ($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/facebookauth/__init__.php b/src/applications/auth/controller/oauth/__init__.php similarity index 68% rename from src/applications/auth/controller/facebookauth/__init__.php rename to src/applications/auth/controller/oauth/__init__.php index f5b4a8f1a9..d9e4cc1b35 100644 --- a/src/applications/auth/controller/facebookauth/__init__.php +++ b/src/applications/auth/controller/oauth/__init__.php @@ -1,25 +1,27 @@ provider = PhabricatorOAuthProvider::newProvider($data['provider']); + } + public function processRequest() { - $auth_enabled = PhabricatorEnv::getEnvConfig('facebook.auth-enabled'); - $app_id = PhabricatorEnv::getEnvConfig('facebook.application-id'); - $app_secret = PhabricatorEnv::getEnvConfig('facebook.application-secret'); + $provider = $this->provider; + + + + $auth_enabled = $provider->isProviderEnabled(); + $client_id = $provider->getClientID(); + $client_secret = $provider->getClientSecret(); $res_ok = 'OK'; $res_no = 'NO'; $res_na = 'N/A'; $results = array(); if (!$auth_enabled) { $results['facebook.auth-enabled'] = array( $res_no, 'false', 'Facebook authentication is disabled in the configuration. Edit the '. 'environmental configuration to enable "facebook.auth-enabled".'); } else { $results['facebook.auth-enabled'] = array( $res_ok, 'true', 'Facebook authentication is enabled.'); } - if (!$app_id) { + if (!$client_id) { $results['facebook.application-id'] = array( $res_no, null, 'No Facebook Application ID is configured. Edit the environmental '. 'configuration to specify an application ID in '. '"facebook.application-id". To generate an ID, sign into Facebook, '. 'install the "Developer" application, and use it to create a new '. 'Facebook application.'); } else { $results['facebook.application-id'] = array( $res_ok, - $app_id, + $client_id, 'Application ID is set.'); } - if (!$app_secret) { + if (!$client_secret) { $results['facebook.application-secret'] = array( $res_no, null, 'No Facebook Application secret is configured. Edit the environmental '. 'configuration to specify an Application Secret, in '. '"facebook.application-secret". You can find the application secret '. 'in the Facebook "Developer" application on Facebook.'); } else { $results['facebook.application-secret'] = array( $res_ok, "It's a secret!", 'Application secret is set.'); } $timeout = stream_context_create( array( 'http' => array( 'ignore_errors' => true, 'timeout' => 5, ), )); $timeout_strict = stream_context_create( array( 'http' => array( 'timeout' => 5, ), )); $internet = @file_get_contents("http://google.com/", false, $timeout); if ($internet === false) { $results['internet'] = array( $res_no, null, 'Unable to make an HTTP request to Google. Check your outbound '. 'internet connection and firewall/filtering settings.'); } else { $results['internet'] = array( $res_ok, null, 'Internet seems OK.'); } $facebook = @file_get_contents("http://facebook.com/", false, $timeout); if ($facebook === false) { $results['facebook.com'] = array( $res_no, null, 'Unable to make an HTTP request to facebook.com. Facebook may be '. 'down or inaccessible.'); } else { $results['facebook.com'] = array( $res_ok, null, 'Made a request to facebook.com.'); } $graph = @file_get_contents( "https://graph.facebook.com/me", false, $timeout); if ($graph === false) { $results['Facebook Graph'] = array( $res_no, null, "Unable to make an HTTPS request to graph.facebook.com. ". "The Facebook graph may be down or inaccessible."); } else { $results['Facebook Graph'] = array( $res_ok, null, 'Made a request to graph.facebook.com.'); } $test_uri = new PhutilURI('https://graph.facebook.com/oauth/access_token'); $test_uri->setQueryParams( array( - 'client_id' => $app_id, - 'client_secret' => $app_secret, + 'client_id' => $client_id, + 'client_secret' => $client_secret, 'grant_type' => 'client_credentials', )); $token_value = @file_get_contents($test_uri, false, $timeout); $token_strict = @file_get_contents($test_uri, false, $timeout_strict); if ($token_value === false) { $results['App Login'] = array( $res_no, null, "Unable to perform an application login with your Application ID and ". "Application Secret. You may have mistyped or misconfigured them; ". "Facebook may have revoked your authorization; or Facebook may be ". "having technical problems."); } else { if ($token_strict) { $results['App Login'] = array( $res_ok, $token_strict, "Raw application login to Facebook works."); } else { $data = json_decode($token_value, true); if (!is_array($data)) { $results['App Login'] = array( $res_no, $token_value, "Application Login failed but the graph server did not respond ". "with valid JSON error information. Facebook may be experiencing ". "technical problems."); } else { $results['App Login'] = array( $res_no, null, "Application Login failed with error: ".$token_value); } } } return $this->renderResults($results); } private function renderResults($results) { $rows = array(); foreach ($results as $key => $result) { $rows[] = array( phutil_escape_html($key), $result[0], phutil_escape_html($result[1]), phutil_escape_html($result[2]), ); } $table_view = new AphrontTableView($rows); $table_view->setHeaders( array( 'Test', 'Result', 'Value', 'Details', )); $table_view->setColumnClasses( array( null, null, null, 'wide', )); $panel_view = new AphrontPanelView(); $panel_view->setHeader('Facebook Auth Diagnostics'); $panel_view->appendChild( '

These tests may be able to '. 'help diagnose the root cause of problems you experience with '. 'Facebook Authentication. Reload the page to run the tests again.

'); $panel_view->appendChild($table_view); return $this->buildStandardPageResponse( $panel_view, array( 'title' => 'Facebook Auth Diagnostics', )); } } diff --git a/src/applications/auth/controller/facebookauth/diagnostics/__init__.php b/src/applications/auth/controller/oauthdiagnostics/__init__.php similarity index 72% rename from src/applications/auth/controller/facebookauth/diagnostics/__init__.php rename to src/applications/auth/controller/oauthdiagnostics/__init__.php index 4603969d31..bebfb1718c 100644 --- a/src/applications/auth/controller/facebookauth/diagnostics/__init__.php +++ b/src/applications/auth/controller/oauthdiagnostics/__init__.php @@ -1,18 +1,18 @@ request = $request; + return $this; + } + + public function setOAuthProvider($provider) { + $this->provider = $provider; + return $this; + } + + public function render() { + $request = $this->request; + $provider = $this->provider; + $provider_name = $provider->getProviderName(); + + $diagnose = null; + + $view = new AphrontRequestFailureView(); + $view->setHeader($provider_name.' Auth Failed'); + if ($this->request) { + $view->appendChild( + '

'. + 'Description: '. + phutil_escape_html($request->getStr('error_description')). + '

'); + $view->appendChild( + '

'. + 'Error: '. + phutil_escape_html($request->getStr('error')). + '

'); + $view->appendChild( + '

'. + 'Error Reason: '. + phutil_escape_html($request->getStr('error_reason')). + '

'); + } else { + // TODO: We can probably refine this. + $view->appendChild( + '

Unable to authenticate with '.$provider_name.'. '. + 'There are several reasons this might happen:

'. + '
    '. + '
  • Phabricator may be configured with the wrong Application '. + 'Secret; or
  • '. + '
  • the '.$provider_name.' OAuth access token may have expired; '. + 'or
  • '. + '
  • '.$provider_name.' may have revoked authorization for the '. + 'Application; or
  • '. + '
  • '.$provider_name.' may be having technical problems.
  • '. + '
'. + '

You can try again, or login using another method.

'); + + $provider_key = $provider->getProviderKey(); + $diagnose = + ''. + 'Diagnose '.$provider_name.' OAuth Problems'. + ''; + } + + $view->appendChild( + '
'. + $diagnose. + 'Continue'. + '
'); + + return $view->render(); + } + +} diff --git a/src/applications/auth/view/oauthfailure/__init__.php b/src/applications/auth/view/oauthfailure/__init__.php new file mode 100644 index 0000000000..d94eeeaf2d --- /dev/null +++ b/src/applications/auth/view/oauthfailure/__init__.php @@ -0,0 +1,15 @@ +profileImagePHID, PhabricatorEnv::getEnvConfig('user.default-profile-image-phid')); } public function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, ) + parent::getConfiguration(); } public function generatePHID() { return PhabricatorPHID::generateNewPHID(self::PHID_TYPE); } public function setPassword($password) { $this->setPasswordSalt(md5(mt_rand())); $hash = $this->hashPassword($password); $this->setPasswordHash($hash); return $this; } public function save() { if (!$this->conduitCertificate) { $this->conduitCertificate = $this->generateConduitCertificate(); } return parent::save(); } private function generateConduitCertificate() { $entropy = Filesystem::readRandomBytes(256); $entropy = base64_encode($entropy); $entropy = substr($entropy, 0, 255); return $entropy; } public function comparePassword($password) { $password = $this->hashPassword($password); return ($password === $this->getPasswordHash()); } private function hashPassword($password) { $password = $this->getUsername(). $password. $this->getPHID(). $this->getPasswordSalt(); for ($ii = 0; $ii < 1000; $ii++) { $password = md5($password); } return $password; } const CSRF_CYCLE_FREQUENCY = 3600; const CSRF_TOKEN_LENGTH = 16; const EMAIL_CYCLE_FREQUENCY = 86400; const EMAIL_TOKEN_LENGTH = 24; public function getCSRFToken($offset = 0) { return $this->generateToken( time() + (self::CSRF_CYCLE_FREQUENCY * $offset), self::CSRF_CYCLE_FREQUENCY, PhabricatorEnv::getEnvConfig('phabricator.csrf-key'), self::CSRF_TOKEN_LENGTH); } public function validateCSRFToken($token) { for ($ii = -1; $ii <= 1; $ii++) { $valid = $this->getCSRFToken($ii); if ($token == $valid) { return true; } } return false; } private function generateToken($epoch, $frequency, $key, $len) { $time_block = floor($epoch / $frequency); $vec = $this->getPHID().$this->passwordHash.$key.$time_block; return substr(sha1($vec), 0, $len); } public function establishSession($session_type) { $conn_w = $this->establishConnection('w'); $entropy = Filesystem::readRandomBytes(20); $session_key = sha1($entropy); queryfx( $conn_w, 'INSERT INTO %T '. '(userPHID, type, sessionKey, sessionStart)'. ' VALUES '. '(%s, %s, %s, UNIX_TIMESTAMP()) '. 'ON DUPLICATE KEY UPDATE '. 'sessionKey = VALUES(sessionKey), '. 'sessionStart = VALUES(sessionStart)', self::SESSION_TABLE, $this->getPHID(), $session_type, $session_key); $this->sessionKey = $session_key; return $session_key; } public function generateEmailToken($offset = 0) { return $this->generateToken( time() + ($offset * self::EMAIL_CYCLE_FREQUENCY), self::EMAIL_CYCLE_FREQUENCY, PhabricatorEnv::getEnvConfig('phabricator.csrf-key').$this->getEmail(), self::EMAIL_TOKEN_LENGTH); } public function validateEmailToken($token) { for ($ii = -1; $ii <= 1; $ii++) { $valid = $this->generateEmailToken($ii); if ($token == $valid) { return true; } } return false; } } diff --git a/src/applications/people/storage/useroauthinfo/PhabricatorUserOAuthInfo.php b/src/applications/people/storage/useroauthinfo/PhabricatorUserOAuthInfo.php new file mode 100644 index 0000000000..742bbf7591 --- /dev/null +++ b/src/applications/people/storage/useroauthinfo/PhabricatorUserOAuthInfo.php @@ -0,0 +1,25 @@ +