diff --git a/.gitignore b/.gitignore index a969224fa6..07a2f0da43 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ ._* /docs/ /src/.phutil_module_cache +/conf/custom/* diff --git a/conf/default.conf.php b/conf/default.conf.php new file mode 100644 index 0000000000..a5bb05d2fa --- /dev/null +++ b/conf/default.conf.php @@ -0,0 +1,46 @@ + null, + + + // + 'phabricator.csrf-key' => '0b7ec0592e0a2829d8b71df2fa269b2c6172eca3', + + +// -- 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, + + 'recaptcha.public-key' => null, + 'recaptcha.private-key' => null, + + + +); diff --git a/conf/development.conf.php b/conf/development.conf.php new file mode 100644 index 0000000000..85673eab81 --- /dev/null +++ b/conf/development.conf.php @@ -0,0 +1,22 @@ + $value ) + $req .= $key . '=' . urlencode( stripslashes($value) ) . '&'; + + // Cut the last '&' + $req=substr($req,0,strlen($req)-1); + return $req; +} + + + +/** + * Submits an HTTP POST to a reCAPTCHA server + * @param string $host + * @param string $path + * @param array $data + * @param int port + * @return array response + */ +function _recaptcha_http_post($host, $path, $data, $port = 80) { + + $req = _recaptcha_qsencode ($data); + + $http_request = "POST $path HTTP/1.0\r\n"; + $http_request .= "Host: $host\r\n"; + $http_request .= "Content-Type: application/x-www-form-urlencoded;\r\n"; + $http_request .= "Content-Length: " . strlen($req) . "\r\n"; + $http_request .= "User-Agent: reCAPTCHA/PHP\r\n"; + $http_request .= "\r\n"; + $http_request .= $req; + + $response = ''; + if( false == ( $fs = @fsockopen($host, $port, $errno, $errstr, 10) ) ) { + die ('Could not open socket'); + } + + fwrite($fs, $http_request); + + while ( !feof($fs) ) + $response .= fgets($fs, 1160); // One TCP-IP packet + fclose($fs); + $response = explode("\r\n\r\n", $response, 2); + + return $response; +} + + + +/** + * Gets the challenge HTML (javascript and non-javascript version). + * This is called from the browser, and the resulting reCAPTCHA HTML widget + * is embedded within the HTML form it was called from. + * @param string $pubkey A public key for reCAPTCHA + * @param string $error The error given by reCAPTCHA (optional, default is null) + * @param boolean $use_ssl Should the request be made over ssl? (optional, default is false) + + * @return string - The HTML to be embedded in the user's form. + */ +function recaptcha_get_html ($pubkey, $error = null, $use_ssl = false) +{ + if ($pubkey == null || $pubkey == '') { + die ("To use reCAPTCHA you must get an API key from https://www.google.com/recaptcha/admin/create"); + } + + if ($use_ssl) { + $server = RECAPTCHA_API_SECURE_SERVER; + } else { + $server = RECAPTCHA_API_SERVER; + } + + $errorpart = ""; + if ($error) { + $errorpart = "&error=" . $error; + } + return ' + + '; +} + + + + +/** + * A ReCaptchaResponse is returned from recaptcha_check_answer() + */ +class ReCaptchaResponse { + var $is_valid; + var $error; +} + + +/** + * Calls an HTTP POST function to verify if the user's guess was correct + * @param string $privkey + * @param string $remoteip + * @param string $challenge + * @param string $response + * @param array $extra_params an array of extra variables to post to the server + * @return ReCaptchaResponse + */ +function recaptcha_check_answer ($privkey, $remoteip, $challenge, $response, $extra_params = array()) +{ + if ($privkey == null || $privkey == '') { + die ("To use reCAPTCHA you must get an API key from https://www.google.com/recaptcha/admin/create"); + } + + if ($remoteip == null || $remoteip == '') { + die ("For security reasons, you must pass the remote ip to reCAPTCHA"); + } + + + + //discard spam submissions + if ($challenge == null || strlen($challenge) == 0 || $response == null || strlen($response) == 0) { + $recaptcha_response = new ReCaptchaResponse(); + $recaptcha_response->is_valid = false; + $recaptcha_response->error = 'incorrect-captcha-sol'; + return $recaptcha_response; + } + + $response = _recaptcha_http_post (RECAPTCHA_VERIFY_SERVER, "/recaptcha/api/verify", + array ( + 'privatekey' => $privkey, + 'remoteip' => $remoteip, + 'challenge' => $challenge, + 'response' => $response + ) + $extra_params + ); + + $answers = explode ("\n", $response [1]); + $recaptcha_response = new ReCaptchaResponse(); + + if (trim ($answers [0]) == 'true') { + $recaptcha_response->is_valid = true; + } + else { + $recaptcha_response->is_valid = false; + $recaptcha_response->error = $answers [1]; + } + return $recaptcha_response; + +} + +/** + * gets a URL where the user can sign up for reCAPTCHA. If your application + * has a configuration page where you enter a key, you should provide a link + * using this function. + * @param string $domain The domain where the page is hosted + * @param string $appname The name of your application + */ +function recaptcha_get_signup_url ($domain = null, $appname = null) { + return "https://www.google.com/recaptcha/admin/create?" . _recaptcha_qsencode (array ('domains' => $domain, 'app' => $appname)); +} + +function _recaptcha_aes_pad($val) { + $block_size = 16; + $numpad = $block_size - (strlen ($val) % $block_size); + return str_pad($val, strlen ($val) + $numpad, chr($numpad)); +} + +/* Mailhide related code */ + +function _recaptcha_aes_encrypt($val,$ky) { + if (! function_exists ("mcrypt_encrypt")) { + die ("To use reCAPTCHA Mailhide, you need to have the mcrypt php module installed."); + } + $mode=MCRYPT_MODE_CBC; + $enc=MCRYPT_RIJNDAEL_128; + $val=_recaptcha_aes_pad($val); + return mcrypt_encrypt($enc, $ky, $val, $mode, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"); +} + + +function _recaptcha_mailhide_urlbase64 ($x) { + return strtr(base64_encode ($x), '+/', '-_'); +} + +/* gets the reCAPTCHA Mailhide url for a given email, public key and private key */ +function recaptcha_mailhide_url($pubkey, $privkey, $email) { + if ($pubkey == '' || $pubkey == null || $privkey == "" || $privkey == null) { + die ("To use reCAPTCHA Mailhide, you have to sign up for a public and private key, " . + "you can do so at http://www.google.com/recaptcha/mailhide/apikey"); + } + + + $ky = pack('H*', $privkey); + $cryptmail = _recaptcha_aes_encrypt ($email, $ky); + + return "http://www.google.com/recaptcha/mailhide/d?k=" . $pubkey . "&c=" . _recaptcha_mailhide_urlbase64 ($cryptmail); +} + +/** + * gets the parts of the email to expose to the user. + * eg, given johndoe@example,com return ["john", "example.com"]. + * the email is then displayed as john...@example.com + */ +function _recaptcha_mailhide_email_parts ($email) { + $arr = preg_split("/@/", $email ); + + if (strlen ($arr[0]) <= 4) { + $arr[0] = substr ($arr[0], 0, 1); + } else if (strlen ($arr[0]) <= 6) { + $arr[0] = substr ($arr[0], 0, 3); + } else { + $arr[0] = substr ($arr[0], 0, 4); + } + return $arr; +} + +/** + * Gets html to display an email address given a public an private key. + * to get a key, go to: + * + * http://www.google.com/recaptcha/mailhide/apikey + */ +function recaptcha_mailhide_html($pubkey, $privkey, $email) { + $emailparts = _recaptcha_mailhide_email_parts ($email); + $url = recaptcha_mailhide_url ($pubkey, $privkey, $email); + + return htmlentities($emailparts[0]) . "...@" . htmlentities ($emailparts [1]); + +} + + +?> diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 7f24c77fc2..261d63ec43 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -25,6 +25,8 @@ phutil_register_library_map(array( 'AphrontFormControl' => 'view/form/control/base', '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', @@ -54,10 +56,10 @@ phutil_register_library_map(array( 'AphrontURIMapper' => 'aphront/mapper', 'AphrontView' => 'view/base', 'AphrontWebpageResponse' => 'aphront/response/webpage', - 'CelerityAPI' => 'infratructure/celerity/api', - 'CelerityResourceController' => 'infratructure/celerity/controller', - 'CelerityResourceMap' => 'infratructure/celerity/map', - 'CelerityStaticResourceResponse' => 'infratructure/celerity/response', + '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', @@ -105,7 +107,7 @@ phutil_register_library_map(array( 'DifferentialRevisionUpdateHistoryView' => 'applications/differential/view/revisionupdatehistory', 'DifferentialRevisionViewController' => 'applications/differential/controller/revisionview', 'DifferentialUnitStatus' => 'applications/differential/constants/unitstatus', - 'Javelin' => 'infratructure/javelin/api', + 'Javelin' => 'infrastructure/javelin/api', 'LiskDAO' => 'storage/lisk/dao', 'Phabricator404Controller' => 'applications/base/controller/404', 'PhabricatorAuthController' => 'applications/auth/controller/base', @@ -128,7 +130,11 @@ phutil_register_library_map(array( 'PhabricatorDirectoryItemEditController' => 'applications/directory/controller/itemedit', 'PhabricatorDirectoryItemListController' => 'applications/directory/controller/itemlist', 'PhabricatorDirectoryMainController' => 'applications/directory/controller/main', - 'PhabricatorFacebookConnectController' => 'applications/auth/controller/facebookconnect', + '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', @@ -176,9 +182,9 @@ phutil_register_library_map(array( array( '_qsprintf_check_scalar_type' => 'storage/qsprintf', '_qsprintf_check_type' => 'storage/qsprintf', - 'celerity_generate_unique_node_id' => 'infratructure/celerity/api', - 'celerity_register_resource_map' => 'infratructure/celerity/map', - 'javelin_render_tag' => 'infratructure/javelin/markup', + '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', @@ -186,7 +192,7 @@ phutil_register_library_map(array( 'queryfx' => 'storage/queryfx', 'queryfx_all' => 'storage/queryfx', 'queryfx_one' => 'storage/queryfx', - 'require_celerity_resource' => 'infratructure/celerity/api', + 'require_celerity_resource' => 'infrastructure/celerity/api', 'vqsprintf' => 'storage/qsprintf', 'vqueryfx' => 'storage/queryfx', 'vqueryfx_all' => 'storage/queryfx', @@ -207,6 +213,8 @@ phutil_register_library_map(array( 'AphrontFormControl' => 'AphrontView', 'AphrontFormFileControl' => 'AphrontFormControl', 'AphrontFormMarkupControl' => 'AphrontFormControl', + 'AphrontFormPasswordControl' => 'AphrontFormControl', + 'AphrontFormRecaptchaControl' => 'AphrontFormControl', 'AphrontFormSelectControl' => 'AphrontFormControl', 'AphrontFormStaticControl' => 'AphrontFormControl', 'AphrontFormSubmitControl' => 'AphrontFormControl', @@ -285,7 +293,10 @@ phutil_register_library_map(array( 'PhabricatorDirectoryItemEditController' => 'PhabricatorDirectoryController', 'PhabricatorDirectoryItemListController' => 'PhabricatorDirectoryController', 'PhabricatorDirectoryMainController' => 'PhabricatorDirectoryController', - 'PhabricatorFacebookConnectController' => 'PhabricatorAuthController', + 'PhabricatorEmailLoginController' => 'PhabricatorAuthController', + 'PhabricatorEmailTokenController' => 'PhabricatorAuthController', + 'PhabricatorFacebookAuthController' => 'PhabricatorAuthController', + 'PhabricatorFacebookAuthDiagnosticsController' => 'PhabricatorAuthController', 'PhabricatorFile' => 'PhabricatorFileDAO', 'PhabricatorFileController' => 'PhabricatorController', 'PhabricatorFileDAO' => 'PhabricatorLiskDAO', diff --git a/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php b/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php index ef0619b0cc..a7dbbe58cf 100644 --- a/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php +++ b/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php @@ -115,9 +115,16 @@ class AphrontDefaultApplicationConfiguration => 'PhabricatorMetaMTAMailingListEditController', ), - '/login/$' => 'PhabricatorLoginController', + '/login/' => array( + '$' => 'PhabricatorLoginController', + 'email/$' => 'PhabricatorEmailLoginController', + 'etoken/(?\w+)/$' => 'PhabricatorEmailTokenController', + ), '/logout/$' => 'PhabricatorLogoutController', - '/facebook-connect/$' => 'PhabricatorFacebookConnectController', + '/facebook-auth/' => array( + '$' => 'PhabricatorFacebookAuthController', + 'diagnose/$' => 'PhabricatorFacebookAuthDiagnosticsController', + ), ); } diff --git a/src/aphront/request/AphrontRequest.php b/src/aphront/request/AphrontRequest.php index 6cc50fa2f5..a231ff30e2 100644 --- a/src/aphront/request/AphrontRequest.php +++ b/src/aphront/request/AphrontRequest.php @@ -29,6 +29,16 @@ class AphrontRequest { private $path; private $requestData; private $user; + private $env; + + final public function setEnvConfig(array $conf) { + $this->env = $conf; + return $this; + } + + final public function getEnvConfig($key, $default = null) { + return idx($this->env, $key, $default); + } final public function __construct($host, $path) { $this->host = $host; @@ -122,6 +132,4 @@ class AphrontRequest { return $this->user; } - - } diff --git a/src/aphront/response/ajax/__init__.php b/src/aphront/response/ajax/__init__.php index c0dd276bb7..fd1560d506 100644 --- a/src/aphront/response/ajax/__init__.php +++ b/src/aphront/response/ajax/__init__.php @@ -7,7 +7,7 @@ phutil_require_module('phabricator', 'aphront/response/base'); -phutil_require_module('phabricator', 'infratructure/celerity/api'); +phutil_require_module('phabricator', 'infrastructure/celerity/api'); phutil_require_source('AphrontAjaxResponse.php'); diff --git a/src/applications/auth/controller/email/PhabricatorEmailLoginController.php b/src/applications/auth/controller/email/PhabricatorEmailLoginController.php new file mode 100644 index 0000000000..e79998a254 --- /dev/null +++ b/src/applications/auth/controller/email/PhabricatorEmailLoginController.php @@ -0,0 +1,131 @@ +getRequest(); + + $e_email = true; + $e_captcha = true; + $errors = array(); + + if ($request->isFormPost()) { + $e_email = null; + $e_captcha = 'Again'; + + $captcha_ok = AphrontFormRecaptchaControl::processCaptcha($request); + if (!$captcha_ok) { + $errors[] = "Captcha response is incorrect, try again."; + $e_captcha = 'Invalid'; + } + + $email = $request->getStr('email'); + if (!strlen($email)) { + $errors[] = "You must provide an email address."; + $e_email = 'Required'; + } + + if (!$errors) { + // NOTE: Don't validate the email unless the captcha is good; this makes + // it expensive to fish for valid email addresses while giving the user + // a better error if they goof their email. + + $target_user = id(new PhabricatorUser())->loadOneWhere( + 'email = %s', + $email); + + if (!$target_user) { + $errors[] = "There is no account associated with that email address."; + $e_email = "Invalid"; + } + + if (!$errors) { + $etoken = $target_user->generateEmailToken(); + + $mail = new PhabricatorMetaMTAMail(); + $mail->setSubject('Phabricator Email Authentication'); + $mail->addTos( + array( + $target_user->getEmail(), + )); + $mail->setBody( + "blah blah blah ". + PhabricatorEnv::getURI('/login/etoken/'.$etoken.'/').'?email='.phutil_escape_uri($target_user->getEmail())); + $mail->save(); + + $view = new AphrontRequestFailureView(); + $view->setHeader('Check Your Email'); + $view->appendChild( + '

An email has been sent with a link you can use to login.

'); + return $this->buildStandardPageResponse( + $view, + array( + 'title' => 'Email Sent', + )); + } + } + + } + + $email_auth = new AphrontFormView(); + $email_auth + ->setAction('/login/email/') + ->setUser($request->getUser()) + ->appendChild( + id(new AphrontFormTextControl()) + ->setLabel('Email') + ->setName('email') + ->setValue($request->getStr('email')) + ->setError($e_email)) + ->appendChild( + id(new AphrontFormRecaptchaControl()) + ->setLabel('Captcha') + ->setError($e_captcha)) + ->appendChild( + id(new AphrontFormSubmitControl()) + ->setValue('Send Email')); + + $error_view = null; + if ($errors) { + $error_view = new AphrontErrorView(); + $error_view->setTitle('Login Error'); + $error_view->setErrors($errors); + } + + + $panel = new AphrontPanelView(); + $panel->setWidth(AphrontPanelView::WIDTH_FORM); + $panel->appendChild('

Forgot Password / Email Login

'); + $panel->appendChild($email_auth); + + return $this->buildStandardPageResponse( + array( + $error_view, + $panel, + ), + array( + 'title' => 'Create New Account', + )); + } + +} diff --git a/src/applications/auth/controller/email/__init__.php b/src/applications/auth/controller/email/__init__.php new file mode 100644 index 0000000000..72d4c63071 --- /dev/null +++ b/src/applications/auth/controller/email/__init__.php @@ -0,0 +1,24 @@ +token = $data['token']; + } + + public function processRequest() { + $request = $this->getRequest(); + + $token = $this->token; + $email = $request->getStr('email'); + + $target_user = id(new PhabricatorUser())->loadOneWhere( + 'email = %s', + $email); + + if (!$target_user || !$target_user->validateEmailToken($token)) { + $view = new AphrontRequestFailureView(); + $view->setHeader('Unable to Login'); + $view->appendChild( + '

The authentication information in the link you clicked is '. + 'invalid or out of date. Make sure you are copy-and-pasting the '. + 'entire link into your browser. You can try again, or request '. + 'a new email.

'); + $view->appendChild( + '
'. + 'Send Another Email'. + '
'); + return $this->buildStandardPageResponse( + $view, + array( + 'title' => 'Email Sent', + )); + } + + if ($request->getUser()->getPHID() != $target_user->getPHID()) { + $session_key = $target_user->establishSession('web'); + $request->setCookie('phusr', $target_user->getUsername()); + $request->setCookie('phsid', $session_key); + } + + $errors = array(); + + $e_pass = true; + $e_confirm = true; + + if ($request->isFormPost()) { + $e_pass = 'Error'; + $e_confirm = 'Error'; + + $pass = $request->getStr('password'); + $confirm = $request->getStr('confirm'); + + if (strlen($pass) < 3) { + $errors[] = 'That password is ridiculously short.'; + } + + if ($pass !== $confirm) { + $errors[] = "Passwords do not match."; + } + + if (!$errors) { + $target_user->setPassword($pass); + $target_user->save(); + return id(new AphrontRedirectResponse()) + ->setURI('/'); + } + } + + if ($errors) { + $error_view = new AphrontErrorView(); + $error_view->setTitle('Password Reset Failed'); + $error_view->setErrors($errors); + } else { + $error_view = null; + } + + $form = new AphrontFormView(); + $form + ->setUser($target_user) + ->setAction('/login/etoken/'.$token.'/') + ->addHiddenInput('email', $email) + ->appendChild( + id(new AphrontFormPasswordControl()) + ->setLabel('New Password') + ->setName('password') + ->setError($e_pass)) + ->appendChild( + id(new AphrontFormPasswordControl()) + ->setLabel('Confirm Password') + ->setName('confirm') + ->setError($e_confirm)) + ->appendChild( + id(new AphrontFormSubmitControl()) + ->setValue('Reset Password') + ->addCancelButton('/', 'Skip')); + + $panel = new AphrontPanelView(); + $panel->setWidth(AphrontPanelView::WIDTH_FORM); + $panel->setHeader('Reset Password'); + $panel->appendChild($form); + + return $this->buildStandardPageResponse( + array( + $error_view, + $panel, + ), + array( + 'title' => 'Create New Account', + )); + } + +} diff --git a/src/applications/auth/controller/facebookconnect/__init__.php b/src/applications/auth/controller/emailtoken/__init__.php similarity index 76% rename from src/applications/auth/controller/facebookconnect/__init__.php rename to src/applications/auth/controller/emailtoken/__init__.php index 7c02260f35..fd917cbcc1 100644 --- a/src/applications/auth/controller/facebookconnect/__init__.php +++ b/src/applications/auth/controller/emailtoken/__init__.php @@ -8,15 +8,14 @@ phutil_require_module('phabricator', 'aphront/response/redirect'); phutil_require_module('phabricator', 'applications/auth/controller/base'); -phutil_require_module('phabricator', 'applications/files/storage/file'); phutil_require_module('phabricator', 'applications/people/storage/user'); phutil_require_module('phabricator', 'view/form/base'); phutil_require_module('phabricator', 'view/form/control/submit'); phutil_require_module('phabricator', 'view/form/error'); phutil_require_module('phabricator', 'view/layout/panel'); +phutil_require_module('phabricator', 'view/page/failure'); -phutil_require_module('phutil', 'markup'); phutil_require_module('phutil', 'utils'); -phutil_require_source('PhabricatorFacebookConnectController.php'); +phutil_require_source('PhabricatorEmailTokenController.php'); diff --git a/src/applications/auth/controller/facebookconnect/PhabricatorFacebookConnectController.php b/src/applications/auth/controller/facebookauth/PhabricatorFacebookAuthController.php similarity index 65% rename from src/applications/auth/controller/facebookconnect/PhabricatorFacebookConnectController.php rename to src/applications/auth/controller/facebookauth/PhabricatorFacebookAuthController.php index 34b2116ea2..7f86928722 100644 --- a/src/applications/auth/controller/facebookconnect/PhabricatorFacebookConnectController.php +++ b/src/applications/auth/controller/facebookauth/PhabricatorFacebookAuthController.php @@ -16,31 +16,99 @@ * limitations under the License. */ -class PhabricatorFacebookConnectController extends PhabricatorAuthController { +class PhabricatorFacebookAuthController extends PhabricatorAuthController { public function shouldRequireLogin() { return false; } public function processRequest() { + $auth_enabled = PhabricatorEnv::getEnvConfig('facebook.auth-enabled'); + if (!$auth_enabled) { + return new Aphront400Response(); + } + + $diagnose_auth = + ''. + 'Diagnose Facebook Auth Problems'. + ''; + $request = $this->getRequest(); if ($request->getStr('error')) { - die("OMG 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 = 'https://graph.facebook.com/oauth/access_token'. - '?client_id=184510521580034'. - '&redirect_uri=http://local.aphront.com/facebook-connect/'. - '&client_secret=OMGSECRETS'. - '&code='.$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) { - throw new Exception('failed to open oauth thing'); + $view = new AphrontRequestFailureView(); + $view->setHeader('Facebook Auth Failed'); + $view->appendChild( + '

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

'. + ''. + '

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(); @@ -89,12 +157,12 @@ class PhabricatorFacebookConnectController extends PhabricatorAuthController { $form ->addHiddenInput('token', $token) ->setUser($request->getUser()) - ->setAction('/facebook-connect/') + ->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 Connect?") + "you can login with Facebook?") ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Link Accounts') @@ -170,7 +238,7 @@ class PhabricatorFacebookConnectController extends PhabricatorAuthController { $error_view = null; if ($errors) { $error_view = new AphrontErrorView(); - $error_view->setTitle('Facebook Connect Failed'); + $error_view->setTitle('Facebook Auth Failed'); $error_view->setErrors($errors); } @@ -178,7 +246,7 @@ class PhabricatorFacebookConnectController extends PhabricatorAuthController { $form ->addHiddenInput('token', $token) ->setUser($request->getUser()) - ->setAction('/facebook-connect/') + ->setAction('/facebook-auth/') ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Username') diff --git a/src/applications/auth/controller/facebookauth/__init__.php b/src/applications/auth/controller/facebookauth/__init__.php new file mode 100644 index 0000000000..f5b4a8f1a9 --- /dev/null +++ b/src/applications/auth/controller/facebookauth/__init__.php @@ -0,0 +1,25 @@ +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) { + $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, + 'Application ID is set.'); + } + + if (!$app_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, + '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/facebookauth/diagnostics/__init__.php new file mode 100644 index 0000000000..4603969d31 --- /dev/null +++ b/src/applications/auth/controller/facebookauth/diagnostics/__init__.php @@ -0,0 +1,18 @@ +setPassword('asdf'); - $user->save(); - $okay = false; if ($user) { if ($user->comparePassword($request->getStr('password'))) { @@ -71,13 +68,15 @@ class PhabricatorLoginController extends PhabricatorAuthController { ->setAction('/login/') ->appendChild( id(new AphrontFormTextControl()) - ->setLabel('Username') + ->setLabel('Username/Email') ->setName('username') ->setValue($username)) ->appendChild( - id(new AphrontFormTextControl()) + id(new AphrontFormPasswordControl()) ->setLabel('Password') - ->setName('password')) + ->setName('password') + ->setCaption( + 'Forgot your password? / Email Login')) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Login')); @@ -88,27 +87,39 @@ class PhabricatorLoginController extends PhabricatorAuthController { $panel->setWidth(AphrontPanelView::WIDTH_FORM); $panel->appendChild($form); + $fbauth_enabled = PhabricatorEnv::getEnvConfig('facebook.auth-enabled'); + if ($fbauth_enabled) { + $auth_uri = new PhutilURI("https://www.facebook.com/dialog/oauth"); - // TODO: Hardcoded junk - $connect_uri = "https://www.facebook.com/dialog/oauth"; + $user = $request->getUser(); - $user = $request->getUser(); + $redirect_uri = PhabricatorEnv::getURI('/facebook-auth/'); + $app_id = PhabricatorEnv::getEnvConfig('facebook.application-id'); - $facebook_connect = new AphrontFormView(); - $facebook_connect - ->setAction($connect_uri) - ->addHiddenInput('client_id', 184510521580034) - ->addHiddenInput('redirect_uri', 'http://local.aphront.com/facebook-connect/') - ->addHiddenInput('scope', 'email') - ->addHiddenInput('state', $user->getCSRFToken()) - ->setUser($request->getUser()) - ->setMethod('GET') - ->appendChild( - id(new AphrontFormSubmitControl()) - ->setValue("Login with Facebook Connect \xC2\xBB")); + // 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. - $panel->appendChild('

Login with Facebook

'); - $panel->appendChild($facebook_connect); + $facebook_auth = new AphrontFormView(); + $facebook_auth + ->setAction($auth_uri) + ->addHiddenInput('client_id', $app_id) + ->addHiddenInput('redirect_uri', $redirect_uri) + ->addHiddenInput('scope', 'email') + ->setUser($request->getUser()) + ->setMethod('GET') + ->appendChild( + '

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

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

Login with Facebook

'); + $panel->appendChild($facebook_auth); + } return $this->buildStandardPageResponse( array( diff --git a/src/applications/auth/controller/login/__init__.php b/src/applications/auth/controller/login/__init__.php index c3c5e62c3b..85ec5fcd3e 100644 --- a/src/applications/auth/controller/login/__init__.php +++ b/src/applications/auth/controller/login/__init__.php @@ -9,11 +9,13 @@ phutil_require_module('phabricator', 'aphront/response/redirect'); phutil_require_module('phabricator', 'applications/auth/controller/base'); phutil_require_module('phabricator', 'applications/people/storage/user'); +phutil_require_module('phabricator', 'infrastructure/env'); phutil_require_module('phabricator', 'view/form/base'); phutil_require_module('phabricator', 'view/form/control/submit'); phutil_require_module('phabricator', 'view/form/error'); phutil_require_module('phabricator', 'view/layout/panel'); +phutil_require_module('phutil', 'parser/uri'); phutil_require_module('phutil', 'utils'); diff --git a/src/applications/differential/controller/base/__init__.php b/src/applications/differential/controller/base/__init__.php index 5e0ce69e00..015845d45e 100644 --- a/src/applications/differential/controller/base/__init__.php +++ b/src/applications/differential/controller/base/__init__.php @@ -8,7 +8,7 @@ phutil_require_module('phabricator', 'aphront/response/webpage'); phutil_require_module('phabricator', 'applications/base/controller/base'); -phutil_require_module('phabricator', 'infratructure/celerity/api'); +phutil_require_module('phabricator', 'infrastructure/celerity/api'); phutil_require_module('phutil', 'utils'); diff --git a/src/applications/differential/view/changesetdetailview/__init__.php b/src/applications/differential/view/changesetdetailview/__init__.php index 575d0c87f4..4e50ea0f06 100644 --- a/src/applications/differential/view/changesetdetailview/__init__.php +++ b/src/applications/differential/view/changesetdetailview/__init__.php @@ -6,8 +6,8 @@ -phutil_require_module('phabricator', 'infratructure/celerity/api'); -phutil_require_module('phabricator', 'infratructure/javelin/markup'); +phutil_require_module('phabricator', 'infrastructure/celerity/api'); +phutil_require_module('phabricator', 'infrastructure/javelin/markup'); phutil_require_module('phabricator', 'view/base'); phutil_require_module('phutil', 'markup'); diff --git a/src/applications/differential/view/changesetlistview/__init__.php b/src/applications/differential/view/changesetlistview/__init__.php index f7d535542a..9f040ff17f 100644 --- a/src/applications/differential/view/changesetlistview/__init__.php +++ b/src/applications/differential/view/changesetlistview/__init__.php @@ -8,8 +8,8 @@ phutil_require_module('phabricator', 'applications/differential/constants/changetype'); phutil_require_module('phabricator', 'applications/differential/view/changesetdetailview'); -phutil_require_module('phabricator', 'infratructure/celerity/api'); -phutil_require_module('phabricator', 'infratructure/javelin/api'); +phutil_require_module('phabricator', 'infrastructure/celerity/api'); +phutil_require_module('phabricator', 'infrastructure/javelin/api'); phutil_require_module('phabricator', 'view/base'); phutil_require_module('phutil', 'markup'); diff --git a/src/applications/differential/view/difftableofcontents/__init__.php b/src/applications/differential/view/difftableofcontents/__init__.php index da5da976d1..a29530d370 100644 --- a/src/applications/differential/view/difftableofcontents/__init__.php +++ b/src/applications/differential/view/difftableofcontents/__init__.php @@ -7,7 +7,7 @@ phutil_require_module('phabricator', 'applications/differential/constants/changetype'); -phutil_require_module('phabricator', 'infratructure/celerity/api'); +phutil_require_module('phabricator', 'infrastructure/celerity/api'); phutil_require_module('phabricator', 'view/base'); phutil_require_module('phutil', 'markup'); diff --git a/src/applications/differential/view/revisioncomment/__init__.php b/src/applications/differential/view/revisioncomment/__init__.php index 9542bcce85..2f19fad814 100644 --- a/src/applications/differential/view/revisioncomment/__init__.php +++ b/src/applications/differential/view/revisioncomment/__init__.php @@ -7,7 +7,7 @@ phutil_require_module('phabricator', 'applications/differential/constants/action'); -phutil_require_module('phabricator', 'infratructure/celerity/api'); +phutil_require_module('phabricator', 'infrastructure/celerity/api'); phutil_require_module('phabricator', 'view/base'); phutil_require_module('phutil', 'markup'); diff --git a/src/applications/differential/view/revisioncommentlist/__init__.php b/src/applications/differential/view/revisioncommentlist/__init__.php index 2e8410cce2..446ab1fa36 100644 --- a/src/applications/differential/view/revisioncommentlist/__init__.php +++ b/src/applications/differential/view/revisioncommentlist/__init__.php @@ -8,7 +8,7 @@ phutil_require_module('phabricator', 'applications/differential/parser/markup'); phutil_require_module('phabricator', 'applications/differential/view/revisioncomment'); -phutil_require_module('phabricator', 'infratructure/celerity/api'); +phutil_require_module('phabricator', 'infrastructure/celerity/api'); phutil_require_module('phabricator', 'view/base'); diff --git a/src/applications/differential/view/revisiondetail/__init__.php b/src/applications/differential/view/revisiondetail/__init__.php index 9eb662528a..6d159f3325 100644 --- a/src/applications/differential/view/revisiondetail/__init__.php +++ b/src/applications/differential/view/revisiondetail/__init__.php @@ -6,7 +6,7 @@ -phutil_require_module('phabricator', 'infratructure/celerity/api'); +phutil_require_module('phabricator', 'infrastructure/celerity/api'); phutil_require_module('phabricator', 'view/base'); phutil_require_module('phutil', 'markup'); diff --git a/src/applications/differential/view/revisionupdatehistory/__init__.php b/src/applications/differential/view/revisionupdatehistory/__init__.php index 6e88bf893d..513616c042 100644 --- a/src/applications/differential/view/revisionupdatehistory/__init__.php +++ b/src/applications/differential/view/revisionupdatehistory/__init__.php @@ -6,7 +6,7 @@ -phutil_require_module('phabricator', 'infratructure/celerity/api'); +phutil_require_module('phabricator', 'infrastructure/celerity/api'); phutil_require_module('phabricator', 'view/base'); phutil_require_module('phutil', 'markup'); diff --git a/src/applications/directory/controller/main/__init__.php b/src/applications/directory/controller/main/__init__.php index a059f07cff..e9a5b9d06c 100644 --- a/src/applications/directory/controller/main/__init__.php +++ b/src/applications/directory/controller/main/__init__.php @@ -9,7 +9,7 @@ phutil_require_module('phabricator', 'applications/directory/controller/base'); phutil_require_module('phabricator', 'applications/directory/storage/category'); phutil_require_module('phabricator', 'applications/directory/storage/item'); -phutil_require_module('phabricator', 'infratructure/celerity/api'); +phutil_require_module('phabricator', 'infrastructure/celerity/api'); phutil_require_module('phutil', 'markup'); phutil_require_module('phutil', 'utils'); diff --git a/src/applications/people/storage/user/PhabricatorUser.php b/src/applications/people/storage/user/PhabricatorUser.php index fba75bbcb8..abef89f7a4 100644 --- a/src/applications/people/storage/user/PhabricatorUser.php +++ b/src/applications/people/storage/user/PhabricatorUser.php @@ -64,16 +64,23 @@ class PhabricatorUser extends PhabricatorUserDAO { return $password; } - const CSRF_CYCLE_FREQUENCY = 3600; + const CSRF_CYCLE_FREQUENCY = 3600; + const CSRF_TOKEN_LENGTH = 16; - public function getCSRFToken() { - return $this->generateCSRFToken(time()); + 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++) { - $time = time() + (self::CSRF_CYCLE_FREQUENCY * $ii); - $valid = $this->generateCSRFToken($time); + $valid = $this->getCSRFToken($ii); if ($token == $valid) { return true; } @@ -81,12 +88,10 @@ class PhabricatorUser extends PhabricatorUserDAO { return false; } - private function generateCSRFToken($epoch) { - $time_block = floor($epoch / (60 * 60)); - // TODO: this should be a secret lolol - $key = '0b7ec0592e0a2829d8b71df2fa269b2c6172eca3'; + private function generateToken($epoch, $frequency, $key, $len) { + $time_block = floor($epoch / $frequency); $vec = $this->getPHID().$this->passwordHash.$key.$time_block; - return substr(md5($vec), 0, 16); + return substr(sha1($vec), 0, $len); } public function establishSession($session_type) { @@ -96,6 +101,7 @@ class PhabricatorUser extends PhabricatorUserDAO { if (!$urandom) { throw new Exception("Failed to open /dev/urandom!"); } + $entropy = fread($urandom, 20); if (strlen($entropy) != 20) { throw new Exception("Failed to read /dev/urandom!"); @@ -118,4 +124,22 @@ class PhabricatorUser extends PhabricatorUserDAO { 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/user/__init__.php b/src/applications/people/storage/user/__init__.php index 6b89e2b125..ff97486c50 100644 --- a/src/applications/people/storage/user/__init__.php +++ b/src/applications/people/storage/user/__init__.php @@ -8,6 +8,7 @@ phutil_require_module('phabricator', 'applications/people/storage/base'); phutil_require_module('phabricator', 'applications/phid/storage/phid'); +phutil_require_module('phabricator', 'infrastructure/env'); phutil_require_module('phabricator', 'storage/queryfx'); diff --git a/src/infratructure/celerity/api/CelerityAPI.php b/src/infrastructure/celerity/api/CelerityAPI.php similarity index 100% rename from src/infratructure/celerity/api/CelerityAPI.php rename to src/infrastructure/celerity/api/CelerityAPI.php diff --git a/src/infratructure/celerity/api/__init__.php b/src/infrastructure/celerity/api/__init__.php similarity index 66% rename from src/infratructure/celerity/api/__init__.php rename to src/infrastructure/celerity/api/__init__.php index c05435461a..762576517c 100644 --- a/src/infratructure/celerity/api/__init__.php +++ b/src/infrastructure/celerity/api/__init__.php @@ -6,7 +6,7 @@ -phutil_require_module('phabricator', 'infratructure/celerity/response'); +phutil_require_module('phabricator', 'infrastructure/celerity/response'); phutil_require_source('CelerityAPI.php'); diff --git a/src/infratructure/celerity/controller/CelerityResourceController.php b/src/infrastructure/celerity/controller/CelerityResourceController.php similarity index 99% rename from src/infratructure/celerity/controller/CelerityResourceController.php rename to src/infrastructure/celerity/controller/CelerityResourceController.php index 198a23cc04..81ffbe8bdf 100644 --- a/src/infratructure/celerity/controller/CelerityResourceController.php +++ b/src/infrastructure/celerity/controller/CelerityResourceController.php @@ -17,11 +17,11 @@ */ class CelerityResourceController extends AphrontController { - + private $path; private $hash; private $package; - + public function willProcessRequest(array $data) { $this->path = $data['path']; $this->hash = $data['hash']; @@ -37,7 +37,7 @@ class CelerityResourceController extends AphrontController { if (!preg_match('/\.(css|js)$/', $path, $matches)) { throw new Exception("Only CSS and JS resources may be served."); } - + $type = $matches[1]; $root = dirname(phutil_get_library_root('phabricator')); @@ -48,7 +48,7 @@ class CelerityResourceController extends AphrontController { if (!$paths) { return new Aphront404Response(); } - + try { $data = array(); foreach ($paths as $path) { @@ -76,7 +76,7 @@ class CelerityResourceController extends AphrontController { $response->setMimeType("text/javascript; charset=utf-8"); break; } - + $response->setCacheDurationInSeconds(60 * 60 * 24 * 30); return $response; diff --git a/src/infratructure/celerity/controller/__init__.php b/src/infrastructure/celerity/controller/__init__.php similarity index 74% rename from src/infratructure/celerity/controller/__init__.php rename to src/infrastructure/celerity/controller/__init__.php index c31e65c785..47494296c7 100644 --- a/src/infratructure/celerity/controller/__init__.php +++ b/src/infrastructure/celerity/controller/__init__.php @@ -7,7 +7,9 @@ phutil_require_module('phabricator', 'aphront/controller'); +phutil_require_module('phabricator', 'aphront/response/404'); phutil_require_module('phabricator', 'aphront/response/file'); +phutil_require_module('phabricator', 'infrastructure/celerity/map'); phutil_require_module('phutil', 'filesystem'); phutil_require_module('phutil', 'moduleutils'); diff --git a/src/infratructure/celerity/map/CelerityResourceMap.php b/src/infrastructure/celerity/map/CelerityResourceMap.php similarity index 99% rename from src/infratructure/celerity/map/CelerityResourceMap.php rename to src/infrastructure/celerity/map/CelerityResourceMap.php index 6ea9d8bdb9..a6ca2f22d3 100644 --- a/src/infratructure/celerity/map/CelerityResourceMap.php +++ b/src/infrastructure/celerity/map/CelerityResourceMap.php @@ -67,11 +67,11 @@ final class CelerityResourceMap { $map[$symbol] = $info; } - + public function setPackageMap($package_map) { $this->packageMap = $package_map; } - + public function packageResources(array $resolved_map) { $packaged = array(); $handled = array(); @@ -92,18 +92,18 @@ final class CelerityResourceMap { } return $packaged; } - + public function resolvePackage($package_hash) { $package = idx($this->packageMap['packages'], $package_hash); if (!$package) { return null; } - + $paths = array(); foreach ($package['symbols'] as $symbol) { $paths[] = $this->resourceMap[$symbol]['disk']; } - + return $paths; } diff --git a/src/infratructure/celerity/map/__init__.php b/src/infrastructure/celerity/map/__init__.php similarity index 82% rename from src/infratructure/celerity/map/__init__.php rename to src/infrastructure/celerity/map/__init__.php index e2362e7274..d34da6d1be 100644 --- a/src/infratructure/celerity/map/__init__.php +++ b/src/infrastructure/celerity/map/__init__.php @@ -7,6 +7,7 @@ phutil_require_module('phutil', 'moduleutils'); +phutil_require_module('phutil', 'utils'); phutil_require_source('CelerityResourceMap.php'); diff --git a/src/infratructure/celerity/response/CelerityStaticResourceResponse.php b/src/infrastructure/celerity/response/CelerityStaticResourceResponse.php similarity index 100% rename from src/infratructure/celerity/response/CelerityStaticResourceResponse.php rename to src/infrastructure/celerity/response/CelerityStaticResourceResponse.php diff --git a/src/infratructure/celerity/response/__init__.php b/src/infrastructure/celerity/response/__init__.php similarity index 75% rename from src/infratructure/celerity/response/__init__.php rename to src/infrastructure/celerity/response/__init__.php index f6c34a4047..443a0c6c64 100644 --- a/src/infratructure/celerity/response/__init__.php +++ b/src/infrastructure/celerity/response/__init__.php @@ -6,7 +6,7 @@ -phutil_require_module('phabricator', 'infratructure/celerity/map'); +phutil_require_module('phabricator', 'infrastructure/celerity/map'); phutil_require_module('phutil', 'markup'); diff --git a/src/infrastructure/env/PhabricatorEnv.php b/src/infrastructure/env/PhabricatorEnv.php new file mode 100644 index 0000000000..5dedac9344 --- /dev/null +++ b/src/infrastructure/env/PhabricatorEnv.php @@ -0,0 +1,34 @@ +shouldRender()) { + return null; + } + $custom_class = $this->getCustomControlClass(); if (strlen($this->getLabel())) { @@ -119,7 +127,7 @@ abstract class AphrontFormControl extends AphrontView { if (strlen($this->getCaption())) { $caption = '
'. - phutil_escape_html($this->getCaption()). + $this->getCaption(). '
'; } else { $caption = null; diff --git a/src/view/form/control/checkbox/__init__.php b/src/view/form/control/checkbox/__init__.php index 612300678f..9194f048d6 100644 --- a/src/view/form/control/checkbox/__init__.php +++ b/src/view/form/control/checkbox/__init__.php @@ -6,7 +6,7 @@ -phutil_require_module('phabricator', 'infratructure/celerity/api'); +phutil_require_module('phabricator', 'infrastructure/celerity/api'); phutil_require_module('phabricator', 'view/form/control/base'); phutil_require_module('phutil', 'markup'); diff --git a/src/view/form/control/password/AphrontFormPasswordControl.php b/src/view/form/control/password/AphrontFormPasswordControl.php new file mode 100755 index 0000000000..cb0f259fbb --- /dev/null +++ b/src/view/form/control/password/AphrontFormPasswordControl.php @@ -0,0 +1,36 @@ + 'password', + 'name' => $this->getName(), + 'value' => $this->getValue(), + 'disabled' => $this->getDisabled() ? 'disabled' : null, + )); + } + +} diff --git a/src/view/form/control/password/__init__.php b/src/view/form/control/password/__init__.php new file mode 100644 index 0000000000..42c6af6b7b --- /dev/null +++ b/src/view/form/control/password/__init__.php @@ -0,0 +1,14 @@ +getStr('recaptcha_challenge_field'); + $response = $request->getStr('recaptcha_response_field'); + $resp = recaptcha_check_answer( + PhabricatorEnv::getEnvConfig('recaptcha.private-key'), + $_SERVER['REMOTE_ADDR'], + $challenge, + $response); + + return (bool)@$resp->is_valid; + } + + protected function renderInput() { + self::requireLib(); + + return recaptcha_get_html( + PhabricatorEnv::getEnvConfig('recaptcha.public-key'), + $error = null, + $use_ssl = false); + } + +} diff --git a/src/view/form/control/recaptcha/__init__.php b/src/view/form/control/recaptcha/__init__.php new file mode 100644 index 0000000000..3803deffe0 --- /dev/null +++ b/src/view/form/control/recaptcha/__init__.php @@ -0,0 +1,15 @@ +', where '' ". + "is one of 'development', 'production', or a custom environment."); +} + +$conf = phabricator_read_config_file($env); +$conf['phabricator.env'] = $env; + setup_aphront_basics(); +phutil_require_module('phabricator', 'infrastructure/env'); +PhabricatorEnv::setEnvConfig($conf); + $host = $_SERVER['HTTP_HOST']; $path = $_REQUEST['__path__']; @@ -96,3 +112,14 @@ function setup_aphront_basics() { function __autoload($class_name) { PhutilSymbolLoader::loadClass($class_name); } + + +function phabricator_read_config_file($config) { + $root = dirname(dirname(__FILE__)); + $conf = include $root.'/conf/'.$config.'.conf.php'; + if ($conf === false) { + throw new Exception("Failed to read config file '{$config}'."); + } + return $conf; +} + diff --git a/webroot/rsrc/css/aphront/panel-view.css b/webroot/rsrc/css/aphront/panel-view.css index 1acd2f7560..1aaed03fa4 100644 --- a/webroot/rsrc/css/aphront/panel-view.css +++ b/webroot/rsrc/css/aphront/panel-view.css @@ -37,4 +37,3 @@ margin-right: auto; margin-left: auto; } - diff --git a/webroot/rsrc/css/aphront/request-failure-view.css b/webroot/rsrc/css/aphront/request-failure-view.css index de13f0a6a0..5d76af5123 100644 --- a/webroot/rsrc/css/aphront/request-failure-view.css +++ b/webroot/rsrc/css/aphront/request-failure-view.css @@ -19,9 +19,23 @@ } .aphront-request-failure-view .aphront-request-failure-body { - padding: 1em 2em; + padding: 1em 2em 1.5em; } .aphront-request-failure-view .aphront-request-failure-body p { - margin: .5em 0 1.25em; + margin: .5em 0; +} + +.aphront-failure-continue { + margin-top: 1.5em; + text-align: right; +} + +.aphront-failure-continue a.button { + margin-left: 1em; +} + +.aphront-request-failure-view ul { + list-style: disc; + margin-left: 3em; }