\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(
+ '');
+ 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(
+ '');
+
+ 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:
'.
+ ''.
+ '- 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(
+ '');
+
+ 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;
}