2011-01-16 22:51:39 +01:00
|
|
|
<?php
|
|
|
|
|
2011-01-23 02:48:55 +01:00
|
|
|
abstract class PhabricatorController extends AphrontController {
|
2011-01-16 22:51:39 +01:00
|
|
|
|
2012-08-15 19:45:06 +02:00
|
|
|
private $handles;
|
|
|
|
|
2011-01-26 22:21:12 +01:00
|
|
|
public function shouldRequireLogin() {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2011-05-12 19:06:54 +02:00
|
|
|
public function shouldRequireAdmin() {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function shouldRequireEnabledUser() {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2012-10-01 04:44:09 +02:00
|
|
|
public function shouldAllowPublic() {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2014-05-01 19:23:02 +02:00
|
|
|
public function shouldAllowPartialSessions() {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
Allow installs to require email verification
Summary:
Allow installs to require users to verify email addresses before they can use Phabricator. If a user logs in without a verified email address, they're given instructions to verify their address.
This isn't too useful on its own since we don't actually have arbitrary email registration, but the next step is to allow installs to restrict email to only some domains (e.g., @mycompany.com).
Test Plan:
- Verification
- Set verification requirement to `true`.
- Tried to use Phabricator with an unverified account, was told to verify.
- Tried to use Conduit, was given a verification error.
- Verified account, used Phabricator.
- Unverified account, reset password, verified implicit verification, used Phabricator.
- People Admin Interface
- Viewed as admin. Clicked "Administrate User".
- Viewed as non-admin
- Sanity Checks
- Used Conduit normally from web/CLI with a verified account.
- Logged in/out.
- Sent password reset email.
- Created a new user.
- Logged in with an unverified user but with the configuration set to off.
Reviewers: btrahan, vrana, jungejason
Reviewed By: btrahan
CC: aran, csilvers
Maniphest Tasks: T1184
Differential Revision: https://secure.phabricator.com/D2520
2012-05-21 21:47:38 +02:00
|
|
|
public function shouldRequireEmailVerification() {
|
2013-10-04 04:05:47 +02:00
|
|
|
return PhabricatorUserEmail::isEmailVerificationRequired();
|
Allow installs to require email verification
Summary:
Allow installs to require users to verify email addresses before they can use Phabricator. If a user logs in without a verified email address, they're given instructions to verify their address.
This isn't too useful on its own since we don't actually have arbitrary email registration, but the next step is to allow installs to restrict email to only some domains (e.g., @mycompany.com).
Test Plan:
- Verification
- Set verification requirement to `true`.
- Tried to use Phabricator with an unverified account, was told to verify.
- Tried to use Conduit, was given a verification error.
- Verified account, used Phabricator.
- Unverified account, reset password, verified implicit verification, used Phabricator.
- People Admin Interface
- Viewed as admin. Clicked "Administrate User".
- Viewed as non-admin
- Sanity Checks
- Used Conduit normally from web/CLI with a verified account.
- Logged in/out.
- Sent password reset email.
- Created a new user.
- Logged in with an unverified user but with the configuration set to off.
Reviewers: btrahan, vrana, jungejason
Reviewed By: btrahan
CC: aran, csilvers
Maniphest Tasks: T1184
Differential Revision: https://secure.phabricator.com/D2520
2012-05-21 21:47:38 +02:00
|
|
|
}
|
|
|
|
|
Whitelist controllers which can receive a 'code' parameter
Summary:
Ref T4593. There are a variety of clever attacks against OAuth which involve changing the redirect URI to some other URI on the same domain which exhibits unexpected behavior in response to an OAuth request. The best approach to dealing with this is for providers to lock to a specific path and refuse to redirect elsewhere, but not all providers do this.
We haven't had any specific issues related to this, but the anchor issue in T4593 was only a step away.
To mitigate this in general, we can reject the OAuth2 `'code'` parameter on //every// page by default, and then whitelist it on the tiny number of controllers which should be able to receive it.
This is very coarse, kind of overkill, and has some fallout (we can't use `'code'` as a normal parameter in the application), but I think it's relatively well-contained and seems reasonable. A better approach might be to whitelist parameters on every controller (i.e., have each controller specify the parameters it can receive), but that would be a ton of work and probably cause a lot of false positives for a long time.
Since we don't use `'code'` normally anywhere (as far as I can tell), the coarseness of this approach seems reasonable.
Test Plan:
- Logged in with OAuth.
- Hit any other page with `?code=...` in the URL, got an exception.
- Grepped for `'code'` and `"code"`, and examined each use to see if it was impacted.
Reviewers: btrahan
Reviewed By: btrahan
Subscribers: aran, epriestley
Maniphest Tasks: T4593
Differential Revision: https://secure.phabricator.com/D8499
2014-03-12 19:30:04 +01:00
|
|
|
public function shouldAllowRestrictedParameter($parameter_name) {
|
|
|
|
return false;
|
|
|
|
}
|
2011-01-26 22:21:12 +01:00
|
|
|
|
2014-06-04 01:50:27 +02:00
|
|
|
public function shouldRequireMultiFactorEnrollment() {
|
|
|
|
if (!$this->shouldRequireLogin()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$this->shouldRequireEnabledUser()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($this->shouldAllowPartialSessions()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
$user = $this->getRequest()->getUser();
|
|
|
|
if (!$user->getIsStandardUser()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return PhabricatorEnv::getEnvConfig('security.require-multi-factor-auth');
|
|
|
|
}
|
|
|
|
|
Whitelist controllers which can receive a 'code' parameter
Summary:
Ref T4593. There are a variety of clever attacks against OAuth which involve changing the redirect URI to some other URI on the same domain which exhibits unexpected behavior in response to an OAuth request. The best approach to dealing with this is for providers to lock to a specific path and refuse to redirect elsewhere, but not all providers do this.
We haven't had any specific issues related to this, but the anchor issue in T4593 was only a step away.
To mitigate this in general, we can reject the OAuth2 `'code'` parameter on //every// page by default, and then whitelist it on the tiny number of controllers which should be able to receive it.
This is very coarse, kind of overkill, and has some fallout (we can't use `'code'` as a normal parameter in the application), but I think it's relatively well-contained and seems reasonable. A better approach might be to whitelist parameters on every controller (i.e., have each controller specify the parameters it can receive), but that would be a ton of work and probably cause a lot of false positives for a long time.
Since we don't use `'code'` normally anywhere (as far as I can tell), the coarseness of this approach seems reasonable.
Test Plan:
- Logged in with OAuth.
- Hit any other page with `?code=...` in the URL, got an exception.
- Grepped for `'code'` and `"code"`, and examined each use to see if it was impacted.
Reviewers: btrahan
Reviewed By: btrahan
Subscribers: aran, epriestley
Maniphest Tasks: T4593
Differential Revision: https://secure.phabricator.com/D8499
2014-03-12 19:30:04 +01:00
|
|
|
public function willBeginExecution() {
|
2011-01-26 22:21:12 +01:00
|
|
|
$request = $this->getRequest();
|
Whitelist controllers which can receive a 'code' parameter
Summary:
Ref T4593. There are a variety of clever attacks against OAuth which involve changing the redirect URI to some other URI on the same domain which exhibits unexpected behavior in response to an OAuth request. The best approach to dealing with this is for providers to lock to a specific path and refuse to redirect elsewhere, but not all providers do this.
We haven't had any specific issues related to this, but the anchor issue in T4593 was only a step away.
To mitigate this in general, we can reject the OAuth2 `'code'` parameter on //every// page by default, and then whitelist it on the tiny number of controllers which should be able to receive it.
This is very coarse, kind of overkill, and has some fallout (we can't use `'code'` as a normal parameter in the application), but I think it's relatively well-contained and seems reasonable. A better approach might be to whitelist parameters on every controller (i.e., have each controller specify the parameters it can receive), but that would be a ton of work and probably cause a lot of false positives for a long time.
Since we don't use `'code'` normally anywhere (as far as I can tell), the coarseness of this approach seems reasonable.
Test Plan:
- Logged in with OAuth.
- Hit any other page with `?code=...` in the URL, got an exception.
- Grepped for `'code'` and `"code"`, and examined each use to see if it was impacted.
Reviewers: btrahan
Reviewed By: btrahan
Subscribers: aran, epriestley
Maniphest Tasks: T4593
Differential Revision: https://secure.phabricator.com/D8499
2014-03-12 19:30:04 +01:00
|
|
|
|
2013-10-04 04:05:47 +02:00
|
|
|
if ($request->getUser()) {
|
|
|
|
// NOTE: Unit tests can set a user explicitly. Normal requests are not
|
|
|
|
// permitted to do this.
|
|
|
|
PhabricatorTestCase::assertExecutingUnitTests();
|
|
|
|
$user = $request->getUser();
|
|
|
|
} else {
|
|
|
|
$user = new PhabricatorUser();
|
2014-01-23 23:03:54 +01:00
|
|
|
$session_engine = new PhabricatorAuthSessionEngine();
|
2013-10-04 04:05:47 +02:00
|
|
|
|
2014-01-23 23:01:35 +01:00
|
|
|
$phsid = $request->getCookie(PhabricatorCookies::COOKIE_SESSION);
|
Issue "anonymous" sessions for logged-out users
Summary:
Ref T4339. Ref T4310. Currently, sessions look like `"afad85d675fda87a4fadd54"`, and are only issued for logged-in users. To support logged-out CSRF and (eventually) external user sessions, I made two small changes:
- First, sessions now have a "kind", which is indicated by a prefix, like `"A/ab987asdcas7dca"`. This mostly allows us to issue session queries more efficiently: we don't have to issue a query at all for anonymous sessions, and can join the correct table for user and external sessions and save a query. Generally, this gives us more debugging information and more opportunity to recover from issues in a user-friendly way, as with the "invalid session" error in this diff.
- Secondly, if you load a page and don't have a session, we give you an anonymous session. This is just a secret with no special significance.
This does not implement CSRF yet, but gives us a client secret we can use to implement it.
Test Plan:
- Logged in.
- Logged out.
- Browsed around.
- Logged in again.
- Went through link/register.
Reviewers: btrahan
Reviewed By: btrahan
CC: aran
Maniphest Tasks: T4310, T4339
Differential Revision: https://secure.phabricator.com/D8043
2014-01-23 23:03:22 +01:00
|
|
|
if (strlen($phsid)) {
|
2014-01-23 23:03:54 +01:00
|
|
|
$session_user = $session_engine->loadUserForSession(
|
|
|
|
PhabricatorAuthSession::TYPE_WEB,
|
|
|
|
$phsid);
|
2014-01-14 22:22:27 +01:00
|
|
|
if ($session_user) {
|
|
|
|
$user = $session_user;
|
2013-10-04 04:05:47 +02:00
|
|
|
}
|
2014-01-23 23:03:54 +01:00
|
|
|
} else {
|
|
|
|
// If the client doesn't have a session token, generate an anonymous
|
|
|
|
// session. This is used to provide CSRF protection to logged-out users.
|
|
|
|
$phsid = $session_engine->establishSession(
|
|
|
|
PhabricatorAuthSession::TYPE_WEB,
|
2014-05-01 19:23:02 +02:00
|
|
|
null,
|
|
|
|
$partial = false);
|
Don't try to set anonymous session cookie on CDN/file domain
Summary:
Ref T2380. If an install has a CDN domain configured, but does not list it as an alternate domain (which is standard/correct, but not incredibly common, see T2380), we'll currently try to set anonymous cookies on it. These will correctly fail security rules.
Instead, don't try to set these cookies.
I missed this in testing yesterday because I have a file domain, but I also have it configured as an alternate domain, which allows cookies to be set. Generally, domain management is due for some refactoring.
Test Plan: Set file domain but not as an alternate, logged out, nuked file domain cookies, reloaded page. No error after patch.
Reviewers: btrahan, csilvers
Reviewed By: btrahan
CC: aran
Maniphest Tasks: T2380
Differential Revision: https://secure.phabricator.com/D8057
2014-01-24 21:29:03 +01:00
|
|
|
|
|
|
|
// This may be a resource request, in which case we just don't set
|
|
|
|
// the cookie.
|
|
|
|
if ($request->canSetCookies()) {
|
|
|
|
$request->setCookie(PhabricatorCookies::COOKIE_SESSION, $phsid);
|
|
|
|
}
|
2014-01-23 23:03:54 +01:00
|
|
|
}
|
|
|
|
|
Don't try to set anonymous session cookie on CDN/file domain
Summary:
Ref T2380. If an install has a CDN domain configured, but does not list it as an alternate domain (which is standard/correct, but not incredibly common, see T2380), we'll currently try to set anonymous cookies on it. These will correctly fail security rules.
Instead, don't try to set these cookies.
I missed this in testing yesterday because I have a file domain, but I also have it configured as an alternate domain, which allows cookies to be set. Generally, domain management is due for some refactoring.
Test Plan: Set file domain but not as an alternate, logged out, nuked file domain cookies, reloaded page. No error after patch.
Reviewers: btrahan, csilvers
Reviewed By: btrahan
CC: aran
Maniphest Tasks: T2380
Differential Revision: https://secure.phabricator.com/D8057
2014-01-24 21:29:03 +01:00
|
|
|
|
2014-01-23 23:03:54 +01:00
|
|
|
if (!$user->isLoggedIn()) {
|
|
|
|
$user->attachAlternateCSRFString(PhabricatorHash::digest($phsid));
|
2011-01-26 22:21:12 +01:00
|
|
|
}
|
2013-10-04 04:05:47 +02:00
|
|
|
|
|
|
|
$request->setUser($user);
|
2011-01-26 22:21:12 +01:00
|
|
|
}
|
|
|
|
|
2012-06-15 03:08:06 +02:00
|
|
|
$translation = $user->getTranslation();
|
|
|
|
if ($translation &&
|
2012-06-16 08:21:25 +02:00
|
|
|
$translation != PhabricatorEnv::getEnvConfig('translation.provider')) {
|
2012-06-15 03:08:06 +02:00
|
|
|
$translation = newv($translation, array());
|
|
|
|
PhutilTranslator::getInstance()
|
|
|
|
->setLanguage($translation->getLanguage())
|
|
|
|
->addTranslations($translation->getTranslations());
|
|
|
|
}
|
|
|
|
|
2013-10-04 04:05:47 +02:00
|
|
|
$preferences = $user->loadPreferences();
|
|
|
|
if (PhabricatorEnv::getEnvConfig('darkconsole.enabled')) {
|
|
|
|
$dark_console = PhabricatorUserPreferences::PREFERENCE_DARK_CONSOLE;
|
|
|
|
if ($preferences->getPreference($dark_console) ||
|
|
|
|
PhabricatorEnv::getEnvConfig('darkconsole.always-on')) {
|
|
|
|
$console = new DarkConsoleCore();
|
|
|
|
$request->getApplicationConfiguration()->setConsole($console);
|
|
|
|
}
|
|
|
|
}
|
2011-02-05 21:20:18 +01:00
|
|
|
|
Whitelist controllers which can receive a 'code' parameter
Summary:
Ref T4593. There are a variety of clever attacks against OAuth which involve changing the redirect URI to some other URI on the same domain which exhibits unexpected behavior in response to an OAuth request. The best approach to dealing with this is for providers to lock to a specific path and refuse to redirect elsewhere, but not all providers do this.
We haven't had any specific issues related to this, but the anchor issue in T4593 was only a step away.
To mitigate this in general, we can reject the OAuth2 `'code'` parameter on //every// page by default, and then whitelist it on the tiny number of controllers which should be able to receive it.
This is very coarse, kind of overkill, and has some fallout (we can't use `'code'` as a normal parameter in the application), but I think it's relatively well-contained and seems reasonable. A better approach might be to whitelist parameters on every controller (i.e., have each controller specify the parameters it can receive), but that would be a ton of work and probably cause a lot of false positives for a long time.
Since we don't use `'code'` normally anywhere (as far as I can tell), the coarseness of this approach seems reasonable.
Test Plan:
- Logged in with OAuth.
- Hit any other page with `?code=...` in the URL, got an exception.
- Grepped for `'code'` and `"code"`, and examined each use to see if it was impacted.
Reviewers: btrahan
Reviewed By: btrahan
Subscribers: aran, epriestley
Maniphest Tasks: T4593
Differential Revision: https://secure.phabricator.com/D8499
2014-03-12 19:30:04 +01:00
|
|
|
// NOTE: We want to set up the user first so we can render a real page
|
|
|
|
// here, but fire this before any real logic.
|
|
|
|
$restricted = array(
|
|
|
|
'code',
|
|
|
|
);
|
|
|
|
foreach ($restricted as $parameter) {
|
|
|
|
if ($request->getExists($parameter)) {
|
|
|
|
if (!$this->shouldAllowRestrictedParameter($parameter)) {
|
|
|
|
throw new Exception(
|
|
|
|
pht(
|
|
|
|
'Request includes restricted parameter "%s", but this '.
|
|
|
|
'controller ("%s") does not whitelist it. Refusing to '.
|
|
|
|
'serve this request because it might be part of a redirection '.
|
|
|
|
'attack.',
|
|
|
|
$parameter,
|
|
|
|
get_class($this)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-11-13 20:24:38 +01:00
|
|
|
if ($this->shouldRequireEnabledUser()) {
|
|
|
|
if ($user->isLoggedIn() && !$user->getIsApproved()) {
|
|
|
|
$controller = new PhabricatorAuthNeedsApprovalController($request);
|
|
|
|
return $this->delegateToController($controller);
|
|
|
|
}
|
|
|
|
if ($user->getIsDisabled()) {
|
|
|
|
$controller = new PhabricatorDisabledUserController($request);
|
|
|
|
return $this->delegateToController($controller);
|
|
|
|
}
|
2011-05-12 19:06:54 +02:00
|
|
|
}
|
|
|
|
|
2012-09-26 02:37:52 +02:00
|
|
|
$event = new PhabricatorEvent(
|
|
|
|
PhabricatorEventType::TYPE_CONTROLLER_CHECKREQUEST,
|
|
|
|
array(
|
|
|
|
'request' => $request,
|
2013-01-23 22:44:22 +01:00
|
|
|
'controller' => $this,
|
2012-09-26 02:37:52 +02:00
|
|
|
));
|
|
|
|
$event->setUser($user);
|
|
|
|
PhutilEventEngine::dispatchEvent($event);
|
|
|
|
$checker_controller = $event->getValue('controller');
|
2013-01-23 22:44:22 +01:00
|
|
|
if ($checker_controller != $this) {
|
2012-09-26 02:37:52 +02:00
|
|
|
return $this->delegateToController($checker_controller);
|
|
|
|
}
|
|
|
|
|
2014-07-23 02:03:09 +02:00
|
|
|
$auth_class = 'PhabricatorAuthApplication';
|
2014-05-01 19:23:02 +02:00
|
|
|
$auth_application = PhabricatorApplication::getByClass($auth_class);
|
|
|
|
|
|
|
|
// Require partial sessions to finish login before doing anything.
|
|
|
|
if (!$this->shouldAllowPartialSessions()) {
|
|
|
|
if ($user->hasSession() &&
|
|
|
|
$user->getSession()->getIsPartial()) {
|
|
|
|
$login_controller = new PhabricatorAuthFinishController($request);
|
|
|
|
$this->setCurrentApplication($auth_application);
|
|
|
|
return $this->delegateToController($login_controller);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-06-04 01:50:27 +02:00
|
|
|
// Check if the user needs to configure MFA.
|
|
|
|
$need_mfa = $this->shouldRequireMultiFactorEnrollment();
|
|
|
|
$have_mfa = $user->getIsEnrolledInMultiFactor();
|
|
|
|
if ($need_mfa && !$have_mfa) {
|
|
|
|
// Check if the cache is just out of date. Otherwise, roadblock the user
|
|
|
|
// and require MFA enrollment.
|
|
|
|
$user->updateMultiFactorEnrollment();
|
|
|
|
if (!$user->getIsEnrolledInMultiFactor()) {
|
|
|
|
$mfa_controller = new PhabricatorAuthNeedsMultiFactorController(
|
|
|
|
$request);
|
|
|
|
$this->setCurrentApplication($auth_application);
|
|
|
|
return $this->delegateToController($mfa_controller);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-10-04 04:05:47 +02:00
|
|
|
if ($this->shouldRequireLogin()) {
|
|
|
|
// This actually means we need either:
|
|
|
|
// - a valid user, or a public controller; and
|
|
|
|
// - permission to see the application.
|
2013-01-20 02:40:48 +01:00
|
|
|
|
2013-10-04 04:05:47 +02:00
|
|
|
$allow_public = $this->shouldAllowPublic() &&
|
|
|
|
PhabricatorEnv::getEnvConfig('policy.allow-public');
|
Allow restriction of permitted email domains
Summary:
Allow allowed email addresses to be restricted to certain domains. This implies email must be verified.
This probably isn't QUITE ready for prime-time without a few other tweaks (better administrative tools, notably) but we're nearly there.
Test Plan:
- With no restrictions:
- Registered with OAuth
- Created an account with accountadmin
- Added an email
- With restrictions:
- Tried to OAuth register with a restricted address, was prompted to provide a valid one.
- Tried to OAuth register with a valid address, worked fine.
- Tried to accountadmin a restricted address, got blocked.
- Tried to accountadmin a valid address, worked fine.
- Tried to add a restricted address, blocked.
- Tried to add a valid address, worked fine.
- Created a user with People with an invalid address, got blocked.
- Created a user with People with a valid address, worked fine.
Reviewers: btrahan, csilvers
Reviewed By: csilvers
CC: aran, joe, csilvers
Maniphest Tasks: T1184
Differential Revision: https://secure.phabricator.com/D2581
2012-05-26 15:04:35 +02:00
|
|
|
|
2013-10-04 04:05:47 +02:00
|
|
|
// If this controller isn't public, and the user isn't logged in, require
|
|
|
|
// login.
|
|
|
|
if (!$allow_public && !$user->isLoggedIn()) {
|
|
|
|
$login_controller = new PhabricatorAuthStartController($request);
|
|
|
|
$this->setCurrentApplication($auth_application);
|
|
|
|
return $this->delegateToController($login_controller);
|
Allow installs to require email verification
Summary:
Allow installs to require users to verify email addresses before they can use Phabricator. If a user logs in without a verified email address, they're given instructions to verify their address.
This isn't too useful on its own since we don't actually have arbitrary email registration, but the next step is to allow installs to restrict email to only some domains (e.g., @mycompany.com).
Test Plan:
- Verification
- Set verification requirement to `true`.
- Tried to use Phabricator with an unverified account, was told to verify.
- Tried to use Conduit, was given a verification error.
- Verified account, used Phabricator.
- Unverified account, reset password, verified implicit verification, used Phabricator.
- People Admin Interface
- Viewed as admin. Clicked "Administrate User".
- Viewed as non-admin
- Sanity Checks
- Used Conduit normally from web/CLI with a verified account.
- Logged in/out.
- Sent password reset email.
- Created a new user.
- Logged in with an unverified user but with the configuration set to off.
Reviewers: btrahan, vrana, jungejason
Reviewed By: btrahan
CC: aran, csilvers
Maniphest Tasks: T1184
Differential Revision: https://secure.phabricator.com/D2520
2012-05-21 21:47:38 +02:00
|
|
|
}
|
2013-10-04 04:05:47 +02:00
|
|
|
|
|
|
|
if ($user->isLoggedIn()) {
|
|
|
|
if ($this->shouldRequireEmailVerification()) {
|
Improve handling of email verification and "activated" accounts
Summary:
Small step forward which improves existing stuff or lays groudwork for future stuff:
- Currently, to check for email verification, we have to single-query the email address on every page. Instead, denoramlize it into the user object.
- Migrate all the existing users.
- When the user verifies an email, mark them as `isEmailVerified` if the email is their primary email.
- Just make the checks look at the `isEmailVerified` field.
- Add a new check, `isUserActivated()`, to cover email-verified plus disabled. Currently, a non-verified-but-not-disabled user could theoretically use Conduit over SSH, if anyone deployed it. Tighten that up.
- Add an `isApproved` flag, which is always true for now. In a future diff, I want to add a default-on admin approval queue for new accounts, to prevent configuration mistakes. The way it will work is:
- When the queue is enabled, registering users are created with `isApproved = false`.
- Admins are sent an email, "[Phabricator] New User Approval (alincoln)", telling them that a new user is waiting for approval.
- They go to the web UI and approve the user.
- Manually-created accounts are auto-approved.
- The email will have instructions for disabling the queue.
I think this queue will be helpful for new installs and give them peace of mind, and when you go to disable it we have a better opportunity to warn you about exactly what that means.
Generally, I want to improve the default safety of registration, since if you just blindly coast through the path of least resistance right now your install ends up pretty open, and realistically few installs are on VPNs.
Test Plan:
- Ran migration, verified `isEmailVerified` populated correctly.
- Created a new user, checked DB for verified (not verified).
- Verified, checked DB (now verified).
- Used Conduit, People, Diffusion.
Reviewers: btrahan
Reviewed By: btrahan
CC: chad, aran
Differential Revision: https://secure.phabricator.com/D7572
2013-11-12 23:37:04 +01:00
|
|
|
if (!$user->getIsEmailVerified()) {
|
2013-10-04 04:05:47 +02:00
|
|
|
$controller = new PhabricatorMustVerifyEmailController($request);
|
|
|
|
$this->setCurrentApplication($auth_application);
|
|
|
|
return $this->delegateToController($controller);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the user doesn't have access to the application, don't let them use
|
|
|
|
// any of its controllers. We query the application in order to generate
|
|
|
|
// a policy exception if the viewer doesn't have permission.
|
|
|
|
|
|
|
|
$application = $this->getCurrentApplication();
|
|
|
|
if ($application) {
|
|
|
|
id(new PhabricatorApplicationQuery())
|
|
|
|
->setViewer($user)
|
|
|
|
->withPHIDs(array($application->getPHID()))
|
|
|
|
->executeOne();
|
Allow installs to require email verification
Summary:
Allow installs to require users to verify email addresses before they can use Phabricator. If a user logs in without a verified email address, they're given instructions to verify their address.
This isn't too useful on its own since we don't actually have arbitrary email registration, but the next step is to allow installs to restrict email to only some domains (e.g., @mycompany.com).
Test Plan:
- Verification
- Set verification requirement to `true`.
- Tried to use Phabricator with an unverified account, was told to verify.
- Tried to use Conduit, was given a verification error.
- Verified account, used Phabricator.
- Unverified account, reset password, verified implicit verification, used Phabricator.
- People Admin Interface
- Viewed as admin. Clicked "Administrate User".
- Viewed as non-admin
- Sanity Checks
- Used Conduit normally from web/CLI with a verified account.
- Logged in/out.
- Sent password reset email.
- Created a new user.
- Logged in with an unverified user but with the configuration set to off.
Reviewers: btrahan, vrana, jungejason
Reviewed By: btrahan
CC: aran, csilvers
Maniphest Tasks: T1184
Differential Revision: https://secure.phabricator.com/D2520
2012-05-21 21:47:38 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-10-04 04:05:47 +02:00
|
|
|
// NOTE: We do this last so that users get a login page instead of a 403
|
|
|
|
// if they need to login.
|
2011-05-12 19:06:54 +02:00
|
|
|
if ($this->shouldRequireAdmin() && !$user->getIsAdmin()) {
|
2012-01-15 10:07:56 +01:00
|
|
|
return new Aphront403Response();
|
2011-05-12 19:06:54 +02:00
|
|
|
}
|
2011-01-26 22:21:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public function buildStandardPageView() {
|
|
|
|
$view = new PhabricatorStandardPageView();
|
|
|
|
$view->setRequest($this->getRequest());
|
2012-08-05 23:12:43 +02:00
|
|
|
$view->setController($this);
|
2011-01-26 22:21:12 +01:00
|
|
|
return $view;
|
|
|
|
}
|
|
|
|
|
2011-02-03 07:38:42 +01:00
|
|
|
public function buildStandardPageResponse($view, array $data) {
|
2011-01-26 22:21:12 +01:00
|
|
|
$page = $this->buildStandardPageView();
|
2011-01-23 02:48:55 +01:00
|
|
|
$page->appendChild($view);
|
|
|
|
$response = new AphrontWebpageResponse();
|
|
|
|
$response->setContent($page->render());
|
|
|
|
return $response;
|
2011-01-16 22:51:39 +01:00
|
|
|
}
|
|
|
|
|
2012-08-13 04:19:46 +02:00
|
|
|
public function getApplicationURI($path = '') {
|
|
|
|
if (!$this->getCurrentApplication()) {
|
2014-06-09 20:36:49 +02:00
|
|
|
throw new Exception('No application!');
|
2012-08-13 04:19:46 +02:00
|
|
|
}
|
2013-05-31 01:37:51 +02:00
|
|
|
return $this->getCurrentApplication()->getApplicationURI($path);
|
2012-08-13 04:19:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public function buildApplicationPage($view, array $options) {
|
|
|
|
$page = $this->buildStandardPageView();
|
|
|
|
|
2013-03-30 20:46:03 +01:00
|
|
|
$title = PhabricatorEnv::getEnvConfig('phabricator.serious-business') ?
|
|
|
|
'Phabricator' :
|
|
|
|
pht('Bacon Ice Cream for Breakfast');
|
|
|
|
|
2012-08-13 04:19:46 +02:00
|
|
|
$application = $this->getCurrentApplication();
|
2013-03-30 20:46:03 +01:00
|
|
|
$page->setTitle(idx($options, 'title', $title));
|
2012-08-13 04:19:46 +02:00
|
|
|
if ($application) {
|
|
|
|
$page->setApplicationName($application->getName());
|
|
|
|
if ($application->getTitleGlyph()) {
|
|
|
|
$page->setGlyph($application->getTitleGlyph());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!($view instanceof AphrontSideNavFilterView)) {
|
|
|
|
$nav = new AphrontSideNavFilterView();
|
|
|
|
$nav->appendChild($view);
|
|
|
|
$view = $nav;
|
|
|
|
}
|
|
|
|
|
2013-05-19 16:51:31 +02:00
|
|
|
$user = $this->getRequest()->getUser();
|
|
|
|
$view->setUser($user);
|
2012-08-13 21:37:06 +02:00
|
|
|
|
2012-08-13 04:19:46 +02:00
|
|
|
$page->appendChild($view);
|
|
|
|
|
2013-05-19 16:51:31 +02:00
|
|
|
$object_phids = idx($options, 'pageObjects', array());
|
|
|
|
if ($object_phids) {
|
|
|
|
$page->appendPageObjects($object_phids);
|
|
|
|
foreach ($object_phids as $object_phid) {
|
|
|
|
PhabricatorFeedStoryNotification::updateObjectNotificationViews(
|
|
|
|
$user,
|
|
|
|
$object_phid);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-06-24 00:15:11 +02:00
|
|
|
if (idx($options, 'device', true)) {
|
2012-08-15 19:45:06 +02:00
|
|
|
$page->setDeviceReady(true);
|
|
|
|
}
|
|
|
|
|
2014-09-10 23:44:34 +02:00
|
|
|
$page->setShowFooter(idx($options, 'showFooter', true));
|
2013-02-25 21:48:55 +01:00
|
|
|
$page->setShowChrome(idx($options, 'chrome', true));
|
|
|
|
|
2012-12-07 22:34:44 +01:00
|
|
|
$application_menu = $this->buildApplicationMenu();
|
|
|
|
if ($application_menu) {
|
|
|
|
$page->setApplicationMenu($application_menu);
|
|
|
|
}
|
|
|
|
|
2012-08-13 04:19:46 +02:00
|
|
|
$response = new AphrontWebpageResponse();
|
|
|
|
return $response->setContent($page->render());
|
|
|
|
}
|
|
|
|
|
2012-08-05 23:12:43 +02:00
|
|
|
public function didProcessRequest($response) {
|
2014-03-21 22:40:05 +01:00
|
|
|
// If a bare DialogView is returned, wrap it in a DialogResponse.
|
|
|
|
if ($response instanceof AphrontDialogView) {
|
|
|
|
$response = id(new AphrontDialogResponse())->setDialog($response);
|
|
|
|
}
|
|
|
|
|
2012-08-05 23:12:43 +02:00
|
|
|
$request = $this->getRequest();
|
|
|
|
$response->setRequest($request);
|
2012-12-12 02:27:25 +01:00
|
|
|
|
|
|
|
$seen = array();
|
|
|
|
while ($response instanceof AphrontProxyResponse) {
|
|
|
|
$hash = spl_object_hash($response);
|
|
|
|
if (isset($seen[$hash])) {
|
|
|
|
$seen[] = get_class($response);
|
|
|
|
throw new Exception(
|
2014-06-09 20:36:49 +02:00
|
|
|
'Cycle while reducing proxy responses: '.
|
2012-12-12 02:27:25 +01:00
|
|
|
implode(' -> ', $seen));
|
|
|
|
}
|
|
|
|
$seen[$hash] = get_class($response);
|
|
|
|
|
|
|
|
$response = $response->reduceProxyResponse();
|
|
|
|
}
|
|
|
|
|
2012-08-05 23:12:43 +02:00
|
|
|
if ($response instanceof AphrontDialogResponse) {
|
|
|
|
if (!$request->isAjax()) {
|
2014-03-21 22:40:05 +01:00
|
|
|
$dialog = $response->getDialog();
|
|
|
|
|
|
|
|
$title = $dialog->getTitle();
|
|
|
|
$short = $dialog->getShortTitle();
|
|
|
|
|
|
|
|
$crumbs = $this->buildApplicationCrumbs();
|
|
|
|
$crumbs->addTextCrumb(coalesce($short, $title));
|
|
|
|
|
|
|
|
$page_content = array(
|
|
|
|
$crumbs,
|
|
|
|
$response->buildResponseString(),
|
|
|
|
);
|
|
|
|
|
|
|
|
$view = id(new PhabricatorStandardPageView())
|
|
|
|
->setRequest($request)
|
|
|
|
->setController($this)
|
2014-07-03 03:49:06 +02:00
|
|
|
->setDeviceReady(true)
|
2014-03-21 22:40:05 +01:00
|
|
|
->setTitle($title)
|
|
|
|
->appendChild($page_content);
|
|
|
|
|
|
|
|
$response = id(new AphrontWebpageResponse())
|
|
|
|
->setContent($view->render())
|
|
|
|
->setHTTPResponseCode($response->getHTTPResponseCode());
|
2012-08-05 23:12:43 +02:00
|
|
|
} else {
|
2013-06-17 01:31:14 +02:00
|
|
|
$response->getDialog()->setIsStandalone(true);
|
|
|
|
|
2012-08-05 23:12:43 +02:00
|
|
|
return id(new AphrontAjaxResponse())
|
|
|
|
->setContent(array(
|
|
|
|
'dialog' => $response->buildResponseString(),
|
|
|
|
));
|
|
|
|
}
|
|
|
|
} else if ($response instanceof AphrontRedirectResponse) {
|
|
|
|
if ($request->isAjax()) {
|
|
|
|
return id(new AphrontAjaxResponse())
|
|
|
|
->setContent(
|
|
|
|
array(
|
|
|
|
'redirect' => $response->getURI(),
|
|
|
|
));
|
|
|
|
}
|
|
|
|
}
|
2014-03-21 22:40:05 +01:00
|
|
|
|
2012-08-05 23:12:43 +02:00
|
|
|
return $response;
|
|
|
|
}
|
|
|
|
|
2012-08-15 19:45:06 +02:00
|
|
|
protected function getHandle($phid) {
|
|
|
|
if (empty($this->handles[$phid])) {
|
|
|
|
throw new Exception(
|
|
|
|
"Attempting to access handle which wasn't loaded: {$phid}");
|
|
|
|
}
|
|
|
|
return $this->handles[$phid];
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function loadHandles(array $phids) {
|
|
|
|
$phids = array_filter($phids);
|
2012-09-05 04:02:56 +02:00
|
|
|
$this->handles = $this->loadViewerHandles($phids);
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2012-11-07 00:35:26 +01:00
|
|
|
protected function getLoadedHandles() {
|
|
|
|
return $this->handles;
|
|
|
|
}
|
|
|
|
|
2012-09-05 04:02:56 +02:00
|
|
|
protected function loadViewerHandles(array $phids) {
|
2013-09-11 21:27:28 +02:00
|
|
|
return id(new PhabricatorHandleQuery())
|
2012-08-15 19:45:06 +02:00
|
|
|
->setViewer($this->getRequest()->getUser())
|
2013-09-11 21:27:28 +02:00
|
|
|
->withPHIDs($phids)
|
|
|
|
->execute();
|
2012-08-15 19:45:06 +02:00
|
|
|
}
|
2012-08-15 22:45:53 +02:00
|
|
|
|
2012-12-12 02:15:59 +01:00
|
|
|
/**
|
|
|
|
* Render a list of links to handles, identified by PHIDs. The handles must
|
|
|
|
* already be loaded.
|
|
|
|
*
|
|
|
|
* @param list<phid> List of PHIDs to render links to.
|
|
|
|
* @param string Style, one of "\n" (to put each item on its own line)
|
|
|
|
* or "," (to list items inline, separated by commas).
|
|
|
|
* @return string Rendered list of handle links.
|
|
|
|
*/
|
|
|
|
protected function renderHandlesForPHIDs(array $phids, $style = "\n") {
|
|
|
|
$style_map = array(
|
2013-02-06 00:14:18 +01:00
|
|
|
"\n" => phutil_tag('br'),
|
2012-12-12 02:15:59 +01:00
|
|
|
',' => ', ',
|
|
|
|
);
|
|
|
|
|
|
|
|
if (empty($style_map[$style])) {
|
|
|
|
throw new Exception("Unknown handle list style '{$style}'!");
|
|
|
|
}
|
|
|
|
|
2013-04-06 02:01:35 +02:00
|
|
|
return implode_selected_handle_links($style_map[$style],
|
|
|
|
$this->getLoadedHandles(),
|
2013-10-16 21:46:34 +02:00
|
|
|
array_filter($phids));
|
2012-08-15 22:45:53 +02:00
|
|
|
}
|
|
|
|
|
2012-12-07 22:34:44 +01:00
|
|
|
protected function buildApplicationMenu() {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2012-12-07 22:35:17 +01:00
|
|
|
protected function buildApplicationCrumbs() {
|
|
|
|
$crumbs = array();
|
|
|
|
|
|
|
|
$application = $this->getCurrentApplication();
|
|
|
|
if ($application) {
|
Use application icons for "Eye" menu and Crumbs
Summary:
Issues here:
- Need an application-sized "eye", or a "home" icon for "Phabricator Home".
- Some of the "apps_lb_2x" sliced images are the "_dark_" versions, not the light versions.
- If you slice an application-sized "logout" (power off) icon and application-sized "help" (questionmark in circle) icon I can replace the current menu icons and nearly get rid of "autosprite".
- To replace the icons on /applications/, the non-retina size is "4x", so we'd need "8x" for retina. Alternatively I can reduce the icon sizes by 50%.
- The "Help", "Settings" and "Logout" items currently have a "glowing" hover state, which needs a variant (or we can drop it).
- The /applications/ icons have a white hover state (or we can drop it).
- The 1x application (14x14) icons aren't used anywhere right now, should they be? Maybe in the feed in the future, etc?
- The "apps-2x" and "apps-large" sheets are the same image, but getting them to actually use the same file is a bit tricky, so I just left them separate for now.
Test Plan:
{F26698}
{F26699}
Reviewers: chad
Reviewed By: chad
CC: aran
Maniphest Tasks: T1960
Differential Revision: https://secure.phabricator.com/D4108
2012-12-07 22:37:28 +01:00
|
|
|
$sprite = $application->getIconName();
|
2012-12-07 22:35:17 +01:00
|
|
|
if (!$sprite) {
|
Use application icons for "Eye" menu and Crumbs
Summary:
Issues here:
- Need an application-sized "eye", or a "home" icon for "Phabricator Home".
- Some of the "apps_lb_2x" sliced images are the "_dark_" versions, not the light versions.
- If you slice an application-sized "logout" (power off) icon and application-sized "help" (questionmark in circle) icon I can replace the current menu icons and nearly get rid of "autosprite".
- To replace the icons on /applications/, the non-retina size is "4x", so we'd need "8x" for retina. Alternatively I can reduce the icon sizes by 50%.
- The "Help", "Settings" and "Logout" items currently have a "glowing" hover state, which needs a variant (or we can drop it).
- The /applications/ icons have a white hover state (or we can drop it).
- The 1x application (14x14) icons aren't used anywhere right now, should they be? Maybe in the feed in the future, etc?
- The "apps-2x" and "apps-large" sheets are the same image, but getting them to actually use the same file is a bit tricky, so I just left them separate for now.
Test Plan:
{F26698}
{F26699}
Reviewers: chad
Reviewed By: chad
CC: aran
Maniphest Tasks: T1960
Differential Revision: https://secure.phabricator.com/D4108
2012-12-07 22:37:28 +01:00
|
|
|
$sprite = 'application';
|
2012-12-07 22:35:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
$crumbs[] = id(new PhabricatorCrumbView())
|
|
|
|
->setHref($this->getApplicationURI())
|
2014-05-01 17:55:45 +02:00
|
|
|
->setAural($application->getName())
|
Use application icons for "Eye" menu and Crumbs
Summary:
Issues here:
- Need an application-sized "eye", or a "home" icon for "Phabricator Home".
- Some of the "apps_lb_2x" sliced images are the "_dark_" versions, not the light versions.
- If you slice an application-sized "logout" (power off) icon and application-sized "help" (questionmark in circle) icon I can replace the current menu icons and nearly get rid of "autosprite".
- To replace the icons on /applications/, the non-retina size is "4x", so we'd need "8x" for retina. Alternatively I can reduce the icon sizes by 50%.
- The "Help", "Settings" and "Logout" items currently have a "glowing" hover state, which needs a variant (or we can drop it).
- The /applications/ icons have a white hover state (or we can drop it).
- The 1x application (14x14) icons aren't used anywhere right now, should they be? Maybe in the feed in the future, etc?
- The "apps-2x" and "apps-large" sheets are the same image, but getting them to actually use the same file is a bit tricky, so I just left them separate for now.
Test Plan:
{F26698}
{F26699}
Reviewers: chad
Reviewed By: chad
CC: aran
Maniphest Tasks: T1960
Differential Revision: https://secure.phabricator.com/D4108
2012-12-07 22:37:28 +01:00
|
|
|
->setIcon($sprite);
|
2012-12-07 22:35:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
$view = new PhabricatorCrumbsView();
|
|
|
|
foreach ($crumbs as $crumb) {
|
|
|
|
$view->addCrumb($crumb);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $view;
|
|
|
|
}
|
|
|
|
|
2013-10-05 00:15:48 +02:00
|
|
|
protected function hasApplicationCapability($capability) {
|
|
|
|
return PhabricatorPolicyFilter::hasCapability(
|
|
|
|
$this->getRequest()->getUser(),
|
|
|
|
$this->getCurrentApplication(),
|
|
|
|
$capability);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function requireApplicationCapability($capability) {
|
|
|
|
PhabricatorPolicyFilter::requireCapability(
|
|
|
|
$this->getRequest()->getUser(),
|
|
|
|
$this->getCurrentApplication(),
|
|
|
|
$capability);
|
|
|
|
}
|
|
|
|
|
2013-10-09 22:52:04 +02:00
|
|
|
protected function explainApplicationCapability(
|
|
|
|
$capability,
|
|
|
|
$positive_message,
|
|
|
|
$negative_message) {
|
|
|
|
|
|
|
|
$can_act = $this->hasApplicationCapability($capability);
|
|
|
|
if ($can_act) {
|
|
|
|
$message = $positive_message;
|
2014-05-12 19:08:32 +02:00
|
|
|
$icon_name = 'fa-play-circle-o lightgreytext';
|
2013-10-09 22:52:04 +02:00
|
|
|
} else {
|
|
|
|
$message = $negative_message;
|
2014-05-12 19:08:32 +02:00
|
|
|
$icon_name = 'fa-lock';
|
2013-10-09 22:52:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
$icon = id(new PHUIIconView())
|
2014-05-12 19:08:32 +02:00
|
|
|
->setIconFont($icon_name);
|
2013-10-09 22:52:04 +02:00
|
|
|
|
|
|
|
require_celerity_resource('policy-css');
|
|
|
|
|
|
|
|
$phid = $this->getCurrentApplication()->getPHID();
|
|
|
|
$explain_uri = "/policy/explain/{$phid}/{$capability}/";
|
|
|
|
|
|
|
|
$message = phutil_tag(
|
|
|
|
'div',
|
|
|
|
array(
|
|
|
|
'class' => 'policy-capability-explanation',
|
|
|
|
),
|
|
|
|
array(
|
|
|
|
$icon,
|
|
|
|
javelin_tag(
|
|
|
|
'a',
|
|
|
|
array(
|
|
|
|
'href' => $explain_uri,
|
|
|
|
'sigil' => 'workflow',
|
|
|
|
),
|
|
|
|
$message),
|
|
|
|
));
|
|
|
|
|
|
|
|
return array($can_act, $message);
|
2013-10-05 00:15:48 +02:00
|
|
|
}
|
|
|
|
|
2014-01-02 20:59:35 +01:00
|
|
|
public function getDefaultResourceSource() {
|
|
|
|
return 'phabricator';
|
|
|
|
}
|
|
|
|
|
2014-03-21 22:40:05 +01:00
|
|
|
/**
|
|
|
|
* Create a new @{class:AphrontDialogView} with defaults filled in.
|
|
|
|
*
|
|
|
|
* @return AphrontDialogView New dialog.
|
|
|
|
*/
|
2014-10-09 02:23:02 +02:00
|
|
|
public function newDialog() {
|
2014-03-21 22:40:05 +01:00
|
|
|
$submit_uri = new PhutilURI($this->getRequest()->getRequestURI());
|
|
|
|
$submit_uri = $submit_uri->getPath();
|
|
|
|
|
|
|
|
return id(new AphrontDialogView())
|
|
|
|
->setUser($this->getRequest()->getUser())
|
|
|
|
->setSubmitURI($submit_uri);
|
|
|
|
}
|
|
|
|
|
2011-01-16 22:51:39 +01:00
|
|
|
}
|