diff --git a/src/applications/auth/management/PhabricatorAuthManagementRecoverWorkflow.php b/src/applications/auth/management/PhabricatorAuthManagementRecoverWorkflow.php index 3190a842f7..21ef32ae05 100644 --- a/src/applications/auth/management/PhabricatorAuthManagementRecoverWorkflow.php +++ b/src/applications/auth/management/PhabricatorAuthManagementRecoverWorkflow.php @@ -1,91 +1,90 @@ setName('recover') ->setExamples('**recover** __username__') ->setSynopsis( pht( - 'Recover access to an account if you have locked yourself out '. - 'of Phabricator.')) + 'Recover access to an account if you have locked yourself out.')) ->setArguments( array( array( 'name' => 'force-full-session', 'help' => pht( 'Recover directly into a full session without requiring MFA '. 'or other login checks.'), ), array( 'name' => 'username', 'wildcard' => true, ), )); } public function execute(PhutilArgumentParser $args) { $usernames = $args->getArg('username'); if (!$usernames) { throw new PhutilArgumentUsageException( pht('You must specify the username of the account to recover.')); } else if (count($usernames) > 1) { throw new PhutilArgumentUsageException( pht('You can only recover the username for one account.')); } $username = head($usernames); $user = id(new PhabricatorPeopleQuery()) ->setViewer($this->getViewer()) ->withUsernames(array($username)) ->executeOne(); if (!$user) { throw new PhutilArgumentUsageException( pht( 'No such user "%s" to recover.', $username)); } if (!$user->canEstablishWebSessions()) { throw new PhutilArgumentUsageException( pht( 'This account ("%s") can not establish web sessions, so it is '. 'not possible to generate a functional recovery link. Special '. 'accounts like daemons and mailing lists can not log in via the '. 'web UI.', $username)); } $force_full_session = $args->getArg('force-full-session'); $engine = new PhabricatorAuthSessionEngine(); $onetime_uri = $engine->getOneTimeLoginURI( $user, null, PhabricatorAuthSessionEngine::ONETIME_RECOVER, $force_full_session); $console = PhutilConsole::getConsole(); $console->writeOut( pht( 'Use this link to recover access to the "%s" account from the web '. 'interface:', $username)); $console->writeOut("\n\n"); $console->writeOut(' %s', $onetime_uri); $console->writeOut("\n\n"); $console->writeOut( "%s\n", pht( 'After logging in, you can use the "Auth" application to add or '. 'restore authentication providers and allow normal logins to '. 'succeed.')); return 0; } } diff --git a/src/applications/auth/provider/PhabricatorAmazonAuthProvider.php b/src/applications/auth/provider/PhabricatorAmazonAuthProvider.php index c35bd547f3..4f62ee9f45 100644 --- a/src/applications/auth/provider/PhabricatorAmazonAuthProvider.php +++ b/src/applications/auth/provider/PhabricatorAmazonAuthProvider.php @@ -1,46 +1,46 @@ getLoginURI()); $uri = new PhutilURI(PhabricatorEnv::getProductionURI('/')); $https_note = null; if ($uri->getProtocol() !== 'https') { $https_note = pht( - 'NOTE: Amazon **requires** HTTPS, but your Phabricator install does '. + 'NOTE: Amazon **requires** HTTPS, but this service does '. 'not use HTTPS. **You will not be able to add Amazon as an '. 'authentication provider until you configure HTTPS on this install**.'); } return pht( "%s\n\n". "To configure Amazon OAuth, create a new 'API Project' here:". "\n\n". "http://login.amazon.com/manageApps". "\n\n". "Use these settings:". "\n\n". " - **Allowed Return URLs:** Add this: `%s`". "\n\n". "After completing configuration, copy the **Client ID** and ". "**Client Secret** to the fields above.", $https_note, $login_uri); } protected function newOAuthAdapter() { return new PhutilAmazonAuthAdapter(); } protected function getLoginIcon() { return 'Amazon'; } } diff --git a/src/applications/auth/provider/PhabricatorGitHubAuthProvider.php b/src/applications/auth/provider/PhabricatorGitHubAuthProvider.php index 6a7667e2d6..52bcae4916 100644 --- a/src/applications/auth/provider/PhabricatorGitHubAuthProvider.php +++ b/src/applications/auth/provider/PhabricatorGitHubAuthProvider.php @@ -1,44 +1,44 @@ getLoginURI()); return pht( "To configure GitHub OAuth, create a new GitHub Application here:". "\n\n". "https://github.com/settings/applications/new". "\n\n". "You should use these settings in your application:". "\n\n". " - **URL:** Set this to your full domain with protocol. For this ". - " Phabricator install, the correct value is: `%s`\n". + " server, the correct value is: `%s`\n". " - **Callback URL**: Set this to: `%s`\n". "\n\n". "Once you've created an application, copy the **Client ID** and ". "**Client Secret** into the fields above.", $uri, $callback_uri); } protected function newOAuthAdapter() { return new PhutilGitHubAuthAdapter(); } protected function getLoginIcon() { return 'Github'; } public function getLoginURI() { // TODO: Clean this up. See PhabricatorAuthOldOAuthRedirectController. return '/oauth/github/login/'; } } diff --git a/src/applications/auth/provider/PhabricatorJIRAAuthProvider.php b/src/applications/auth/provider/PhabricatorJIRAAuthProvider.php index 8a858f2bea..7b2756dad5 100644 --- a/src/applications/auth/provider/PhabricatorJIRAAuthProvider.php +++ b/src/applications/auth/provider/PhabricatorJIRAAuthProvider.php @@ -1,370 +1,371 @@ getProviderConfigurationHelp(); } protected function getProviderConfigurationHelp() { if ($this->isSetup()) { return pht( "**Step 1 of 2**: Provide the name and URI for your JIRA install.\n\n". "In the next step, you will configure JIRA."); } else { $login_uri = PhabricatorEnv::getURI($this->getLoginURI()); return pht( "**Step 2 of 2**: In this step, you will configure JIRA.\n\n". "**Create a JIRA Application**: Log into JIRA and go to ". "**Administration**, then **Add-ons**, then **Application Links**. ". "Click the button labeled **Add Application Link**, and use these ". "settings to create an application:\n\n". " - **Server URL**: `%s`\n". " - Then, click **Next**. On the second page:\n". - " - **Application Name**: `Phabricator`\n". + " - **Application Name**: `%s`\n". " - **Application Type**: `Generic Application`\n". " - Then, click **Create**.\n\n". "**Configure Your Application**: Find the application you just ". "created in the table, and click the **Configure** link under ". "**Actions**. Select **Incoming Authentication** and click the ". "**OAuth** tab (it may be selected by default). Then, use these ". "settings:\n\n". " - **Consumer Key**: Set this to the \"Consumer Key\" value in the ". "form above.\n". - " - **Consumer Name**: `Phabricator`\n". + " - **Consumer Name**: `%s`\n". " - **Public Key**: Set this to the \"Public Key\" value in the ". "form above.\n". " - **Consumer Callback URL**: `%s`\n". "Click **Save** in JIRA. Authentication should now be configured, ". "and this provider should work correctly.", PhabricatorEnv::getProductionURI('/'), + PlatformSymbols::getPlatformServerName(), + PlatformSymbols::getPlatformServerName(), $login_uri); } } protected function newOAuthAdapter() { $config = $this->getProviderConfig(); return id(new PhutilJIRAAuthAdapter()) ->setAdapterDomain($config->getProviderDomain()) ->setJIRABaseURI($config->getProperty(self::PROPERTY_JIRA_URI)) ->setPrivateKey( new PhutilOpaqueEnvelope( $config->getProperty(self::PROPERTY_PRIVATE_KEY))); } protected function getLoginIcon() { return 'Jira'; } private function isSetup() { return !$this->getProviderConfig()->getID(); } const PROPERTY_JIRA_NAME = 'oauth1:jira:name'; const PROPERTY_JIRA_URI = 'oauth1:jira:uri'; const PROPERTY_PUBLIC_KEY = 'oauth1:jira:key:public'; const PROPERTY_PRIVATE_KEY = 'oauth1:jira:key:private'; const PROPERTY_REPORT_LINK = 'oauth1:jira:report:link'; const PROPERTY_REPORT_COMMENT = 'oauth1:jira:report:comment'; public function readFormValuesFromProvider() { $config = $this->getProviderConfig(); $uri = $config->getProperty(self::PROPERTY_JIRA_URI); return array( self::PROPERTY_JIRA_NAME => $this->getProviderDomain(), self::PROPERTY_JIRA_URI => $uri, ); } public function readFormValuesFromRequest(AphrontRequest $request) { $is_setup = $this->isSetup(); if ($is_setup) { $name = $request->getStr(self::PROPERTY_JIRA_NAME); } else { $name = $this->getProviderDomain(); } return array( self::PROPERTY_JIRA_NAME => $name, self::PROPERTY_JIRA_URI => $request->getStr(self::PROPERTY_JIRA_URI), self::PROPERTY_REPORT_LINK => $request->getInt(self::PROPERTY_REPORT_LINK, 0), self::PROPERTY_REPORT_COMMENT => $request->getInt(self::PROPERTY_REPORT_COMMENT, 0), ); } public function processEditForm( AphrontRequest $request, array $values) { $errors = array(); $issues = array(); $is_setup = $this->isSetup(); $key_name = self::PROPERTY_JIRA_NAME; $key_uri = self::PROPERTY_JIRA_URI; if (!strlen($values[$key_name])) { $errors[] = pht('JIRA instance name is required.'); $issues[$key_name] = pht('Required'); } else if (!preg_match('/^[a-z0-9.]+\z/', $values[$key_name])) { $errors[] = pht( 'JIRA instance name must contain only lowercase letters, digits, and '. 'period.'); $issues[$key_name] = pht('Invalid'); } if (!strlen($values[$key_uri])) { $errors[] = pht('JIRA base URI is required.'); $issues[$key_uri] = pht('Required'); } else { $uri = new PhutilURI($values[$key_uri]); if (!$uri->getProtocol()) { $errors[] = pht( 'JIRA base URI should include protocol (like "https://").'); $issues[$key_uri] = pht('Invalid'); } } if (!$errors && $is_setup) { $config = $this->getProviderConfig(); $config->setProviderDomain($values[$key_name]); $consumer_key = 'phjira.'.Filesystem::readRandomCharacters(16); list($public, $private) = PhutilJIRAAuthAdapter::newJIRAKeypair(); $config->setProperty(self::PROPERTY_PUBLIC_KEY, $public); $config->setProperty(self::PROPERTY_PRIVATE_KEY, $private); $config->setProperty(self::PROPERTY_CONSUMER_KEY, $consumer_key); } return array($errors, $issues, $values); } public function extendEditForm( AphrontRequest $request, AphrontFormView $form, array $values, array $issues) { if (!function_exists('openssl_pkey_new')) { // TODO: This could be a bit prettier. throw new Exception( pht( "The PHP 'openssl' extension is not installed. You must install ". "this extension in order to add a JIRA authentication provider, ". "because JIRA OAuth requests use the RSA-SHA1 signing algorithm. ". - "Install the 'openssl' extension, restart Phabricator, and try ". + "Install the 'openssl' extension, restart everything, and try ". "again.")); } $form->appendRemarkupInstructions( pht( 'NOTE: This provider **only supports JIRA 6**. It will not work with '. 'JIRA 5 or earlier.')); $is_setup = $this->isSetup(); $viewer = $request->getViewer(); $e_required = $request->isFormPost() ? null : true; $v_name = $values[self::PROPERTY_JIRA_NAME]; if ($is_setup) { $e_name = idx($issues, self::PROPERTY_JIRA_NAME, $e_required); } else { $e_name = null; } $v_uri = $values[self::PROPERTY_JIRA_URI]; $e_uri = idx($issues, self::PROPERTY_JIRA_URI, $e_required); if ($is_setup) { $form ->appendRemarkupInstructions( pht( "**JIRA Instance Name**\n\n". - "Choose a permanent name for this instance of JIRA. Phabricator ". - "uses this name internally to keep track of this instance of ". + "Choose a permanent name for this instance of JIRA. This name is ". + "used internally to keep track of this particular instance of ". "JIRA, in case the URL changes later.\n\n". "Use lowercase letters, digits, and period. For example, ". "`jira`, `jira.mycompany` or `jira.engineering` are reasonable ". "names.")) ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('JIRA Instance Name')) ->setValue($v_name) ->setName(self::PROPERTY_JIRA_NAME) ->setError($e_name)); } else { $form ->appendChild( id(new AphrontFormStaticControl()) ->setLabel(pht('JIRA Instance Name')) ->setValue($v_name)); } $form ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('JIRA Base URI')) ->setValue($v_uri) ->setName(self::PROPERTY_JIRA_URI) ->setCaption( pht( 'The URI where JIRA is installed. For example: %s', phutil_tag('tt', array(), 'https://jira.mycompany.com/'))) ->setError($e_uri)); if (!$is_setup) { $config = $this->getProviderConfig(); $ckey = $config->getProperty(self::PROPERTY_CONSUMER_KEY); $ckey = phutil_tag('tt', array(), $ckey); $pkey = $config->getProperty(self::PROPERTY_PUBLIC_KEY); $pkey = phutil_escape_html_newlines($pkey); $pkey = phutil_tag('tt', array(), $pkey); $form ->appendRemarkupInstructions( pht( 'NOTE: **To complete setup**, copy and paste these keys into JIRA '. 'according to the instructions below.')) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel(pht('Consumer Key')) ->setValue($ckey)) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel(pht('Public Key')) ->setValue($pkey)); $form ->appendRemarkupInstructions( pht( '= Integration Options = '."\n". 'Configure how to record Revisions on JIRA tasks.'."\n\n". 'Note you\'ll have to restart the daemons for this to take '. 'effect.')) ->appendChild( id(new AphrontFormCheckboxControl()) ->addCheckbox( self::PROPERTY_REPORT_LINK, 1, new PHUIRemarkupView( $viewer, pht( 'Create **Issue Link** to the Revision, as an "implemented '. 'in" relationship.')), $this->shouldCreateJIRALink())) ->appendChild( id(new AphrontFormCheckboxControl()) ->addCheckbox( self::PROPERTY_REPORT_COMMENT, 1, new PHUIRemarkupView( $viewer, pht( - '**Post a comment** in the JIRA task, similar to the '. - 'emails Phabricator sends.')), + '**Post a comment** in the JIRA task.')), $this->shouldCreateJIRAComment())); } } /** * JIRA uses a setup step to generate public/private keys. */ public function hasSetupStep() { return true; } public static function getJIRAProvider() { $providers = self::getAllEnabledProviders(); foreach ($providers as $provider) { if ($provider instanceof PhabricatorJIRAAuthProvider) { return $provider; } } return null; } public function newJIRAFuture( PhabricatorExternalAccount $account, $path, $method, $params = array()) { $adapter = clone $this->getAdapter(); $adapter->setToken($account->getProperty('oauth1.token')); $adapter->setTokenSecret($account->getProperty('oauth1.token.secret')); return $adapter->newJIRAFuture($path, $method, $params); } public function shouldCreateJIRALink() { $config = $this->getProviderConfig(); return $config->getProperty(self::PROPERTY_REPORT_LINK, true); } public function shouldCreateJIRAComment() { $config = $this->getProviderConfig(); return $config->getProperty(self::PROPERTY_REPORT_COMMENT, true); } /* -( DoorkeeperRemarkupURIInterface )------------------------------------- */ public function getDoorkeeperURIRef(PhutilURI $uri) { $uri_string = phutil_string_cast($uri); $pattern = '((https?://\S+?)/browse/([A-Z][A-Z0-9]*-[1-9]\d*))'; $matches = null; if (!preg_match($pattern, $uri_string, $matches)) { return null; } if (strlen($uri->getFragment())) { return null; } if ($uri->getQueryParamsAsPairList()) { return null; } $domain = $matches[1]; $issue = $matches[2]; $config = $this->getProviderConfig(); $base_uri = $config->getProperty(self::PROPERTY_JIRA_URI); if ($domain !== rtrim($base_uri, '/')) { return null; } return id(new DoorkeeperURIRef()) ->setURI($uri) ->setApplicationType(DoorkeeperBridgeJIRA::APPTYPE_JIRA) ->setApplicationDomain($this->getProviderDomain()) ->setObjectType(DoorkeeperBridgeJIRA::OBJTYPE_ISSUE) ->setObjectID($issue); } } diff --git a/src/applications/auth/provider/PhabricatorLDAPAuthProvider.php b/src/applications/auth/provider/PhabricatorLDAPAuthProvider.php index c899caca71..d1db832aa2 100644 --- a/src/applications/auth/provider/PhabricatorLDAPAuthProvider.php +++ b/src/applications/auth/provider/PhabricatorLDAPAuthProvider.php @@ -1,496 +1,496 @@ setProperty(self::KEY_PORT, 389) ->setProperty(self::KEY_VERSION, 3); } public function getAdapter() { if (!$this->adapter) { $conf = $this->getProviderConfig(); $realname_attributes = $conf->getProperty(self::KEY_REALNAME_ATTRIBUTES); if (!is_array($realname_attributes)) { $realname_attributes = array(); } $search_attributes = $conf->getProperty(self::KEY_SEARCH_ATTRIBUTES); $search_attributes = phutil_split_lines($search_attributes, false); $search_attributes = array_filter($search_attributes); $adapter = id(new PhutilLDAPAuthAdapter()) ->setHostname( $conf->getProperty(self::KEY_HOSTNAME)) ->setPort( $conf->getProperty(self::KEY_PORT)) ->setBaseDistinguishedName( $conf->getProperty(self::KEY_DISTINGUISHED_NAME)) ->setSearchAttributes($search_attributes) ->setUsernameAttribute( $conf->getProperty(self::KEY_USERNAME_ATTRIBUTE)) ->setRealNameAttributes($realname_attributes) ->setLDAPVersion( $conf->getProperty(self::KEY_VERSION)) ->setLDAPReferrals( $conf->getProperty(self::KEY_REFERRALS)) ->setLDAPStartTLS( $conf->getProperty(self::KEY_START_TLS)) ->setAlwaysSearch($conf->getProperty(self::KEY_ALWAYS_SEARCH)) ->setAnonymousUsername( $conf->getProperty(self::KEY_ANONYMOUS_USERNAME)) ->setAnonymousPassword( new PhutilOpaqueEnvelope( $conf->getProperty(self::KEY_ANONYMOUS_PASSWORD))) ->setActiveDirectoryDomain( $conf->getProperty(self::KEY_ACTIVEDIRECTORY_DOMAIN)); $this->adapter = $adapter; } return $this->adapter; } protected function renderLoginForm(AphrontRequest $request, $mode) { $viewer = $request->getUser(); $dialog = id(new AphrontDialogView()) ->setSubmitURI($this->getLoginURI()) ->setUser($viewer); if ($mode == 'link') { $dialog->setTitle(pht('Link LDAP Account')); $dialog->addSubmitButton(pht('Link Accounts')); $dialog->addCancelButton($this->getSettingsURI()); } else if ($mode == 'refresh') { $dialog->setTitle(pht('Refresh LDAP Account')); $dialog->addSubmitButton(pht('Refresh Account')); $dialog->addCancelButton($this->getSettingsURI()); } else { if ($this->shouldAllowRegistration()) { $dialog->setTitle(pht('Log In or Register with LDAP')); $dialog->addSubmitButton(pht('Log In or Register')); } else { $dialog->setTitle(pht('Log In with LDAP')); $dialog->addSubmitButton(pht('Log In')); } if ($mode == 'login') { $dialog->addCancelButton($this->getStartURI()); } } $v_user = $request->getStr('ldap_username'); $e_user = null; $e_pass = null; $errors = array(); if ($request->isHTTPPost()) { // NOTE: This is intentionally vague so as not to disclose whether a // given username exists. $e_user = pht('Invalid'); $e_pass = pht('Invalid'); $errors[] = pht('Username or password are incorrect.'); } $form = id(new PHUIFormLayoutView()) ->setUser($viewer) ->setFullWidth(true) ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('LDAP Username')) ->setName('ldap_username') ->setAutofocus(true) ->setValue($v_user) ->setError($e_user)) ->appendChild( id(new AphrontFormPasswordControl()) ->setLabel(pht('LDAP Password')) ->setName('ldap_password') ->setError($e_pass)); if ($errors) { $errors = id(new PHUIInfoView())->setErrors($errors); } $dialog->appendChild($errors); $dialog->appendChild($form); return $dialog; } public function processLoginRequest( PhabricatorAuthLoginController $controller) { $request = $controller->getRequest(); $viewer = $request->getUser(); $response = null; $account = null; $username = $request->getStr('ldap_username'); $password = $request->getStr('ldap_password'); $has_password = strlen($password); $password = new PhutilOpaqueEnvelope($password); if (!strlen($username) || !$has_password) { $response = $controller->buildProviderPageResponse( $this, $this->renderLoginForm($request, 'login')); return array($account, $response); } if ($request->isFormPost()) { try { if (strlen($username) && $has_password) { $adapter = $this->getAdapter(); $adapter->setLoginUsername($username); $adapter->setLoginPassword($password); // TODO: This calls ldap_bind() eventually, which dumps cleartext // passwords to the error log. See note in PhutilLDAPAuthAdapter. // See T3351. DarkConsoleErrorLogPluginAPI::enableDiscardMode(); $identifiers = $adapter->getAccountIdentifiers(); DarkConsoleErrorLogPluginAPI::disableDiscardMode(); } else { throw new Exception(pht('Username and password are required!')); } } catch (PhutilAuthCredentialException $ex) { $response = $controller->buildProviderPageResponse( $this, $this->renderLoginForm($request, 'login')); return array($account, $response); } catch (Exception $ex) { // TODO: Make this cleaner. throw $ex; } } $account = $this->newExternalAccountForIdentifiers($identifiers); return array($account, $response); } const KEY_HOSTNAME = 'ldap:host'; const KEY_PORT = 'ldap:port'; const KEY_DISTINGUISHED_NAME = 'ldap:dn'; const KEY_SEARCH_ATTRIBUTES = 'ldap:search-attribute'; const KEY_USERNAME_ATTRIBUTE = 'ldap:username-attribute'; const KEY_REALNAME_ATTRIBUTES = 'ldap:realname-attributes'; const KEY_VERSION = 'ldap:version'; const KEY_REFERRALS = 'ldap:referrals'; const KEY_START_TLS = 'ldap:start-tls'; // TODO: This is misspelled! See T13005. const KEY_ANONYMOUS_USERNAME = 'ldap:anoynmous-username'; const KEY_ANONYMOUS_PASSWORD = 'ldap:anonymous-password'; const KEY_ALWAYS_SEARCH = 'ldap:always-search'; const KEY_ACTIVEDIRECTORY_DOMAIN = 'ldap:activedirectory-domain'; private function getPropertyKeys() { return array_keys($this->getPropertyLabels()); } private function getPropertyLabels() { return array( self::KEY_HOSTNAME => pht('LDAP Hostname'), self::KEY_PORT => pht('LDAP Port'), self::KEY_DISTINGUISHED_NAME => pht('Base Distinguished Name'), self::KEY_SEARCH_ATTRIBUTES => pht('Search Attributes'), self::KEY_ALWAYS_SEARCH => pht('Always Search'), self::KEY_ANONYMOUS_USERNAME => pht('Anonymous Username'), self::KEY_ANONYMOUS_PASSWORD => pht('Anonymous Password'), self::KEY_USERNAME_ATTRIBUTE => pht('Username Attribute'), self::KEY_REALNAME_ATTRIBUTES => pht('Realname Attributes'), self::KEY_VERSION => pht('LDAP Version'), self::KEY_REFERRALS => pht('Enable Referrals'), self::KEY_START_TLS => pht('Use TLS'), self::KEY_ACTIVEDIRECTORY_DOMAIN => pht('ActiveDirectory Domain'), ); } public function readFormValuesFromProvider() { $properties = array(); foreach ($this->getPropertyLabels() as $key => $ignored) { $properties[$key] = $this->getProviderConfig()->getProperty($key); } return $properties; } public function readFormValuesFromRequest(AphrontRequest $request) { $values = array(); foreach ($this->getPropertyKeys() as $key) { switch ($key) { case self::KEY_REALNAME_ATTRIBUTES: $values[$key] = $request->getStrList($key, array()); break; default: $values[$key] = $request->getStr($key); break; } } return $values; } public function processEditForm( AphrontRequest $request, array $values) { $errors = array(); $issues = array(); return array($errors, $issues, $values); } public static function assertLDAPExtensionInstalled() { if (!function_exists('ldap_bind')) { throw new Exception( pht( 'Before you can set up or use LDAP, you need to install the PHP '. 'LDAP extension. It is not currently installed, so PHP can not '. 'talk to LDAP. Usually you can install it with '. '`%s`, `%s`, or a similar package manager command.', 'yum install php-ldap', 'apt-get install php5-ldap')); } } public function extendEditForm( AphrontRequest $request, AphrontFormView $form, array $values, array $issues) { self::assertLDAPExtensionInstalled(); $labels = $this->getPropertyLabels(); $captions = array( self::KEY_HOSTNAME => pht('Example: %s%sFor LDAPS, use: %s', phutil_tag('tt', array(), pht('ldap.example.com')), phutil_tag('br'), phutil_tag('tt', array(), pht('ldaps://ldaps.example.com/'))), self::KEY_DISTINGUISHED_NAME => pht('Example: %s', phutil_tag('tt', array(), pht('ou=People, dc=example, dc=com'))), self::KEY_USERNAME_ATTRIBUTE => pht('Example: %s', phutil_tag('tt', array(), pht('sn'))), self::KEY_REALNAME_ATTRIBUTES => pht('Example: %s', phutil_tag('tt', array(), pht('firstname, lastname'))), self::KEY_REFERRALS => pht('Follow referrals. Disable this for Windows AD 2003.'), self::KEY_START_TLS => pht('Start TLS after binding to the LDAP server.'), self::KEY_ALWAYS_SEARCH => pht('Always bind and search, even without a username and password.'), ); $types = array( self::KEY_REFERRALS => 'checkbox', self::KEY_START_TLS => 'checkbox', self::KEY_SEARCH_ATTRIBUTES => 'textarea', self::KEY_REALNAME_ATTRIBUTES => 'list', self::KEY_ANONYMOUS_PASSWORD => 'password', self::KEY_ALWAYS_SEARCH => 'checkbox', ); $instructions = array( self::KEY_SEARCH_ATTRIBUTES => pht( - "When a user types their LDAP username and password into Phabricator, ". - "Phabricator can either bind to LDAP with those credentials directly ". + "When a user provides their LDAP username and password, this ". + "software can either bind to LDAP with those credentials directly ". "(which is simpler, but not as powerful) or bind to LDAP with ". "anonymous credentials, then search for record matching the supplied ". "credentials (which is more complicated, but more powerful).\n\n". "For many installs, direct binding is sufficient. However, you may ". "want to search first if:\n\n". " - You want users to be able to log in with either their username ". " or their email address.\n". " - The login/username is not part of the distinguished name in ". " your LDAP records.\n". " - You want to restrict logins to a subset of users (like only ". " those in certain departments).\n". " - Your LDAP server is configured in some other way that prevents ". " direct binding from working correctly.\n\n". "**To bind directly**, enter the LDAP attribute corresponding to the ". "login name into the **Search Attributes** box below. Often, this is ". "something like `sn` or `uid`. This is the simplest configuration, ". "but will only work if the username is part of the distinguished ". "name, and won't let you apply complex restrictions to logins.\n\n". " lang=text,name=Simple Direct Binding\n". " sn\n\n". "**To search first**, provide an anonymous username and password ". "below (or check the **Always Search** checkbox), then enter one ". "or more search queries into this field, one per line. ". "After binding, these queries will be used to identify the ". "record associated with the login name the user typed.\n\n". "Searches will be tried in order until a matching record is found. ". "Each query can be a simple attribute name (like `sn` or `mail`), ". "which will search for a matching record, or it can be a complex ". "query that uses the string `\${login}` to represent the login ". "name.\n\n". "A common simple configuration is just an attribute name, like ". "`sn`, which will work the same way direct binding works:\n\n". " lang=text,name=Simple Example\n". " sn\n\n". "A slightly more complex configuration might let the user log in with ". "either their login name or email address:\n\n". " lang=text,name=Match Several Attributes\n". " mail\n". " sn\n\n". "If your LDAP directory is more complex, or you want to perform ". "sophisticated filtering, you can use more complex queries. Depending ". "on your directory structure, this example might allow users to log ". "in with either their email address or username, but only if they're ". "in specific departments:\n\n". " lang=text,name=Complex Example\n". " (&(mail=\${login})(|(departmentNumber=1)(departmentNumber=2)))\n". " (&(sn=\${login})(|(departmentNumber=1)(departmentNumber=2)))\n\n". "All of the attribute names used here are just examples: your LDAP ". "server may use different attribute names."), self::KEY_ALWAYS_SEARCH => pht( 'To search for an LDAP record before authenticating, either check '. 'the **Always Search** checkbox or enter an anonymous '. 'username and password to use to perform the search.'), self::KEY_USERNAME_ATTRIBUTE => pht( 'Optionally, specify a username attribute to use to prefill usernames '. 'when registering a new account. This is purely cosmetic and does not '. 'affect the login process, but you can configure it to make sure '. 'users get the same default username as their LDAP username, so '. 'usernames remain consistent across systems.'), self::KEY_REALNAME_ATTRIBUTES => pht( 'Optionally, specify one or more comma-separated attributes to use to '. 'prefill the "Real Name" field when registering a new account. This '. 'is purely cosmetic and does not affect the login process, but can '. 'make registration a little easier.'), ); foreach ($labels as $key => $label) { $caption = idx($captions, $key); $type = idx($types, $key); $value = idx($values, $key); $control = null; switch ($type) { case 'checkbox': $control = id(new AphrontFormCheckboxControl()) ->addCheckbox( $key, 1, hsprintf('%s: %s', $label, $caption), $value); break; case 'list': $control = id(new AphrontFormTextControl()) ->setName($key) ->setLabel($label) ->setCaption($caption) ->setValue($value ? implode(', ', $value) : null); break; case 'password': $control = id(new AphrontFormPasswordControl()) ->setName($key) ->setLabel($label) ->setCaption($caption) ->setDisableAutocomplete(true) ->setValue($value); break; case 'textarea': $control = id(new AphrontFormTextAreaControl()) ->setName($key) ->setLabel($label) ->setCaption($caption) ->setValue($value); break; default: $control = id(new AphrontFormTextControl()) ->setName($key) ->setLabel($label) ->setCaption($caption) ->setValue($value); break; } $instruction_text = idx($instructions, $key); if (strlen($instruction_text)) { $form->appendRemarkupInstructions($instruction_text); } $form->appendChild($control); } } public function renderConfigPropertyTransactionTitle( PhabricatorAuthProviderConfigTransaction $xaction) { $author_phid = $xaction->getAuthorPHID(); $old = $xaction->getOldValue(); $new = $xaction->getNewValue(); $key = $xaction->getMetadataValue( PhabricatorAuthProviderConfigTransaction::PROPERTY_KEY); $labels = $this->getPropertyLabels(); if (isset($labels[$key])) { $label = $labels[$key]; $mask = false; switch ($key) { case self::KEY_ANONYMOUS_PASSWORD: $mask = true; break; } if ($mask) { return pht( '%s updated the "%s" value.', $xaction->renderHandleLink($author_phid), $label); } if ($old === null || $old === '') { return pht( '%s set the "%s" value to "%s".', $xaction->renderHandleLink($author_phid), $label, $new); } else { return pht( '%s changed the "%s" value from "%s" to "%s".', $xaction->renderHandleLink($author_phid), $label, $old, $new); } } return parent::renderConfigPropertyTransactionTitle($xaction); } public static function getLDAPProvider() { $providers = self::getAllEnabledProviders(); foreach ($providers as $provider) { if ($provider instanceof PhabricatorLDAPAuthProvider) { return $provider; } } return null; } } diff --git a/src/applications/auth/provider/PhabricatorWordPressAuthProvider.php b/src/applications/auth/provider/PhabricatorWordPressAuthProvider.php index 092915f58c..8189685f81 100644 --- a/src/applications/auth/provider/PhabricatorWordPressAuthProvider.php +++ b/src/applications/auth/provider/PhabricatorWordPressAuthProvider.php @@ -1,38 +1,38 @@ getLoginURI()); return pht( "To configure WordPress.com OAuth, create a new WordPress.com ". "Application here:\n\n". "https://developer.wordpress.com/apps/new/.". "\n\n". "You should use these settings in your application:". "\n\n". " - **URL:** Set this to your full domain with protocol. For this ". - " Phabricator install, the correct value is: `%s`\n". + " server, the correct value is: `%s`\n". " - **Redirect URL**: Set this to: `%s`\n". "\n\n". "Once you've created an application, copy the **Client ID** and ". "**Client Secret** into the fields above.", $uri, $callback_uri); } protected function newOAuthAdapter() { return new PhutilWordPressAuthAdapter(); } protected function getLoginIcon() { return 'WordPressCOM'; } } diff --git a/src/applications/base/PhabricatorApplication.php b/src/applications/base/PhabricatorApplication.php index 6c5e424bdd..ba1edc26d3 100644 --- a/src/applications/base/PhabricatorApplication.php +++ b/src/applications/base/PhabricatorApplication.php @@ -1,655 +1,655 @@ pht('Core Applications'), self::GROUP_UTILITIES => pht('Utilities'), self::GROUP_ADMIN => pht('Administration'), self::GROUP_DEVELOPER => pht('Developer Tools'), ); } final public function getApplicationName() { return 'application'; } final public function getTableName() { return 'application_application'; } final protected function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, ) + parent::getConfiguration(); } final public function generatePHID() { return $this->getPHID(); } final public function save() { // When "save()" is called on applications, we just return without // actually writing anything to the database. return $this; } /* -( Application Information )-------------------------------------------- */ abstract public function getName(); public function getShortDescription() { return pht('%s Application', $this->getName()); } final public function isInstalled() { if (!$this->canUninstall()) { return true; } $prototypes = PhabricatorEnv::getEnvConfig('phabricator.show-prototypes'); if (!$prototypes && $this->isPrototype()) { return false; } $uninstalled = PhabricatorEnv::getEnvConfig( 'phabricator.uninstalled-applications'); return empty($uninstalled[get_class($this)]); } public function isPrototype() { return false; } /** * Return `true` if this application should never appear in application lists * in the UI. Primarily intended for unit test applications or other * pseudo-applications. * * Few applications should be unlisted. For most applications, use * @{method:isLaunchable} to hide them from main launch views instead. * * @return bool True to remove application from UI lists. */ public function isUnlisted() { return false; } /** * Return `true` if this application is a normal application with a base * URI and a web interface. * * Launchable applications can be pinned to the home page, and show up in the * "Launcher" view of the Applications application. Making an application * unlaunchable prevents pinning and hides it from this view. * * Usually, an application should be marked unlaunchable if: * * - it is available on every page anyway (like search); or * - it does not have a web interface (like subscriptions); or * - it is still pre-release and being intentionally buried. * * To hide applications more completely, use @{method:isUnlisted}. * * @return bool True if the application is launchable. */ public function isLaunchable() { return true; } /** * Return `true` if this application should be pinned by default. * * Users who have not yet set preferences see a default list of applications. * * @param PhabricatorUser User viewing the pinned application list. * @return bool True if this application should be pinned by default. */ public function isPinnedByDefault(PhabricatorUser $viewer) { return false; } /** * Returns true if an application is first-party and false otherwise. * * @return bool True if this application is first-party. */ final public function isFirstParty() { $where = id(new ReflectionClass($this))->getFileName(); $root = phutil_get_library_root('phabricator'); if (!Filesystem::isDescendant($where, $root)) { return false; } if (Filesystem::isDescendant($where, $root.'/extensions')) { return false; } return true; } public function canUninstall() { return true; } final public function getPHID() { return 'PHID-APPS-'.get_class($this); } public function getTypeaheadURI() { return $this->isLaunchable() ? $this->getBaseURI() : null; } public function getBaseURI() { return null; } final public function getApplicationURI($path = '') { return $this->getBaseURI().ltrim($path, '/'); } public function getIcon() { return 'fa-puzzle-piece'; } public function getApplicationOrder() { return PHP_INT_MAX; } public function getApplicationGroup() { return self::GROUP_CORE; } public function getTitleGlyph() { return null; } final public function getHelpMenuItems(PhabricatorUser $viewer) { $items = array(); $articles = $this->getHelpDocumentationArticles($viewer); if ($articles) { foreach ($articles as $article) { $item = id(new PhabricatorActionView()) ->setName($article['name']) ->setHref($article['href']) ->addSigil('help-item') ->setOpenInNewWindow(true); $items[] = $item; } } $command_specs = $this->getMailCommandObjects(); if ($command_specs) { foreach ($command_specs as $key => $spec) { $object = $spec['object']; $class = get_class($this); $href = '/applications/mailcommands/'.$class.'/'.$key.'/'; $item = id(new PhabricatorActionView()) ->setName($spec['name']) ->setHref($href) ->addSigil('help-item') ->setOpenInNewWindow(true); $items[] = $item; } } if ($items) { $divider = id(new PhabricatorActionView()) ->addSigil('help-item') ->setType(PhabricatorActionView::TYPE_DIVIDER); array_unshift($items, $divider); } return array_values($items); } public function getHelpDocumentationArticles(PhabricatorUser $viewer) { return array(); } public function getOverview() { return null; } public function getEventListeners() { return array(); } public function getRemarkupRules() { return array(); } public function getQuicksandURIPatternBlacklist() { return array(); } public function getMailCommandObjects() { return array(); } /* -( URI Routing )-------------------------------------------------------- */ public function getRoutes() { return array(); } public function getResourceRoutes() { return array(); } /* -( Email Integration )-------------------------------------------------- */ public function supportsEmailIntegration() { return false; } final protected function getInboundEmailSupportLink() { return PhabricatorEnv::getDoclink('Configuring Inbound Email'); } public function getAppEmailBlurb() { throw new PhutilMethodNotImplementedException(); } /* -( Fact Integration )--------------------------------------------------- */ public function getFactObjectsForAnalysis() { return array(); } /* -( UI Integration )----------------------------------------------------- */ /** * You can provide an optional piece of flavor text for the application. This * is currently rendered in application launch views if the application has no * status elements. * * @return string|null Flavor text. * @task ui */ public function getFlavorText() { return null; } /** * Build items for the main menu. * * @param PhabricatorUser The viewing user. * @param AphrontController The current controller. May be null for special * pages like 404, exception handlers, etc. * @return list List of menu items. * @task ui */ public function buildMainMenuItems( PhabricatorUser $user, PhabricatorController $controller = null) { return array(); } /* -( Application Management )--------------------------------------------- */ final public static function getByClass($class_name) { $selected = null; $applications = self::getAllApplications(); foreach ($applications as $application) { if (get_class($application) == $class_name) { $selected = $application; break; } } if (!$selected) { throw new Exception(pht("No application '%s'!", $class_name)); } return $selected; } final public static function getAllApplications() { static $applications; if ($applications === null) { $apps = id(new PhutilClassMapQuery()) ->setAncestorClass(__CLASS__) ->setSortMethod('getApplicationOrder') ->execute(); // Reorder the applications into "application order". Notably, this // ensures their event handlers register in application order. $apps = mgroup($apps, 'getApplicationGroup'); $group_order = array_keys(self::getApplicationGroups()); $apps = array_select_keys($apps, $group_order) + $apps; $apps = array_mergev($apps); $applications = $apps; } return $applications; } final public static function getAllInstalledApplications() { $all_applications = self::getAllApplications(); $apps = array(); foreach ($all_applications as $app) { if (!$app->isInstalled()) { continue; } $apps[] = $app; } return $apps; } /** * Determine if an application is installed, by application class name. * * To check if an application is installed //and// available to a particular * viewer, user @{method:isClassInstalledForViewer}. * * @param string Application class name. * @return bool True if the class is installed. * @task meta */ final public static function isClassInstalled($class) { return self::getByClass($class)->isInstalled(); } /** * Determine if an application is installed and available to a viewer, by * application class name. * * To check if an application is installed at all, use * @{method:isClassInstalled}. * * @param string Application class name. * @param PhabricatorUser Viewing user. * @return bool True if the class is installed for the viewer. * @task meta */ final public static function isClassInstalledForViewer( $class, PhabricatorUser $viewer) { if ($viewer->isOmnipotent()) { return true; } $cache = PhabricatorCaches::getRequestCache(); $viewer_fragment = $viewer->getCacheFragment(); $key = 'app.'.$class.'.installed.'.$viewer_fragment; $result = $cache->getKey($key); if ($result === null) { if (!self::isClassInstalled($class)) { $result = false; } else { $application = self::getByClass($class); if (!$application->canUninstall()) { // If the application can not be uninstalled, always allow viewers // to see it. In particular, this allows logged-out viewers to see // Settings and load global default settings even if the install // does not allow public viewers. $result = true; } else { $result = PhabricatorPolicyFilter::hasCapability( $viewer, self::getByClass($class), PhabricatorPolicyCapability::CAN_VIEW); } } $cache->setKey($key, $result); } return $result; } /* -( PhabricatorPolicyInterface )----------------------------------------- */ public function getCapabilities() { return array_merge( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, ), array_keys($this->getCustomCapabilities())); } public function getPolicy($capability) { $default = $this->getCustomPolicySetting($capability); if ($default) { return $default; } switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: return PhabricatorPolicies::getMostOpenPolicy(); case PhabricatorPolicyCapability::CAN_EDIT: return PhabricatorPolicies::POLICY_ADMIN; default: $spec = $this->getCustomCapabilitySpecification($capability); return idx($spec, 'default', PhabricatorPolicies::POLICY_USER); } } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { return false; } /* -( Policies )----------------------------------------------------------- */ protected function getCustomCapabilities() { return array(); } private function getCustomPolicySetting($capability) { if (!$this->isCapabilityEditable($capability)) { return null; } $policy_locked = PhabricatorEnv::getEnvConfig('policy.locked'); if (isset($policy_locked[$capability])) { return $policy_locked[$capability]; } $config = PhabricatorEnv::getEnvConfig('phabricator.application-settings'); $app = idx($config, $this->getPHID()); if (!$app) { return null; } $policy = idx($app, 'policy'); if (!$policy) { return null; } return idx($policy, $capability); } private function getCustomCapabilitySpecification($capability) { $custom = $this->getCustomCapabilities(); if (!isset($custom[$capability])) { throw new Exception(pht("Unknown capability '%s'!", $capability)); } return $custom[$capability]; } final public function getCapabilityLabel($capability) { switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: return pht('Can Use Application'); case PhabricatorPolicyCapability::CAN_EDIT: return pht('Can Configure Application'); } $capobj = PhabricatorPolicyCapability::getCapabilityByKey($capability); if ($capobj) { return $capobj->getCapabilityName(); } return null; } final public function isCapabilityEditable($capability) { switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: return $this->canUninstall(); case PhabricatorPolicyCapability::CAN_EDIT: return true; default: $spec = $this->getCustomCapabilitySpecification($capability); return idx($spec, 'edit', true); } } final public function getCapabilityCaption($capability) { switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: if (!$this->canUninstall()) { return pht( - 'This application is required for Phabricator to operate, so all '. + 'This application is required, so all '. 'users must have access to it.'); } else { return null; } case PhabricatorPolicyCapability::CAN_EDIT: return null; default: $spec = $this->getCustomCapabilitySpecification($capability); return idx($spec, 'caption'); } } final public function getCapabilityTemplatePHIDType($capability) { switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: case PhabricatorPolicyCapability::CAN_EDIT: return null; } $spec = $this->getCustomCapabilitySpecification($capability); return idx($spec, 'template'); } final public function getDefaultObjectTypePolicyMap() { $map = array(); foreach ($this->getCustomCapabilities() as $capability => $spec) { if (empty($spec['template'])) { continue; } if (empty($spec['capability'])) { continue; } $default = $this->getPolicy($capability); $map[$spec['template']][$spec['capability']] = $default; } return $map; } public function getApplicationSearchDocumentTypes() { return array(); } protected function getEditRoutePattern($base = null) { return $base.'(?:'. '(?P[0-9]\d*)/)?'. '(?:'. '(?:'. '(?Pparameters|nodefault|nocreate|nomanage|comment)/'. '|'. '(?:form/(?P[^/]+)/)?(?:page/(?P[^/]+)/)?'. ')'. ')?'; } protected function getBulkRoutePattern($base = null) { return $base.'(?:query/(?P[^/]+)/)?'; } protected function getQueryRoutePattern($base = null) { return $base.'(?:query/(?P[^/]+)/(?:(?P[^/]+)/)?)?'; } protected function getProfileMenuRouting($controller) { $edit_route = $this->getEditRoutePattern(); $mode_route = '(?Pglobal|custom)/'; return array( '(?Pview)/(?P[^/]+)/' => $controller, '(?Phide)/(?P[^/]+)/' => $controller, '(?Pdefault)/(?P[^/]+)/' => $controller, '(?Pconfigure)/' => $controller, '(?Pconfigure)/'.$mode_route => $controller, '(?Preorder)/'.$mode_route => $controller, '(?Pedit)/'.$edit_route => $controller, '(?Pnew)/'.$mode_route.'(?[^/]+)/'.$edit_route => $controller, '(?Pbuiltin)/(?[^/]+)/'.$edit_route => $controller, ); } /* -( PhabricatorApplicationTransactionInterface )------------------------- */ public function getApplicationTransactionEditor() { return new PhabricatorApplicationEditor(); } public function getApplicationTransactionTemplate() { return new PhabricatorApplicationApplicationTransaction(); } } diff --git a/src/applications/cache/spec/PhabricatorCacheSpec.php b/src/applications/cache/spec/PhabricatorCacheSpec.php index 1ed4e713f1..3f3273fd2a 100644 --- a/src/applications/cache/spec/PhabricatorCacheSpec.php +++ b/src/applications/cache/spec/PhabricatorCacheSpec.php @@ -1,120 +1,120 @@ name = $name; return $this; } public function getName() { return $this->name; } public function setIsEnabled($is_enabled) { $this->isEnabled = $is_enabled; return $this; } public function getIsEnabled() { return $this->isEnabled; } public function setVersion($version) { $this->version = $version; return $this; } public function getVersion() { return $this->version; } protected function newIssue($key) { $issue = id(new PhabricatorSetupIssue()) ->setIssueKey($key); $this->issues[$key] = $issue; return $issue; } public function getIssues() { return $this->issues; } public function setUsedMemory($used_memory) { $this->usedMemory = $used_memory; return $this; } public function getUsedMemory() { return $this->usedMemory; } public function setTotalMemory($total_memory) { $this->totalMemory = $total_memory; return $this; } public function getTotalMemory() { return $this->totalMemory; } public function setEntryCount($entry_count) { $this->entryCount = $entry_count; return $this; } public function getEntryCount() { return $this->entryCount; } protected function raiseInstallAPCIssue() { $message = pht( "Installing the PHP extension 'APC' (Alternative PHP Cache) will ". "dramatically improve performance. Note that APC versions 3.1.14 and ". "3.1.15 are broken; 3.1.13 is recommended instead."); return $this ->newIssue('extension.apc') ->setShortName(pht('APC')) ->setName(pht("PHP Extension 'APC' Not Installed")) ->setMessage($message) ->addPHPExtension('apc'); } protected function raiseEnableAPCIssue() { $summary = pht('Enabling APC/APCu will improve performance.'); $message = pht( 'The APC or APCu PHP extensions are installed, but not enabled in your '. - 'PHP configuration. Enabling these extensions will improve Phabricator '. - 'performance. Edit the "%s" setting to enable these extensions.', + 'PHP configuration. Enabling these extensions will improve performance. '. + 'Edit the "%s" setting to enable these extensions.', 'apc.enabled'); return $this ->newIssue('extension.apc.enabled') ->setShortName(pht('APC/APCu Disabled')) ->setName(pht('APC/APCu Extensions Not Enabled')) ->setSummary($summary) ->setMessage($message) ->addPHPConfig('apc.enabled'); } public function setClearCacheCallback($callback) { $this->clearCacheCallback = $callback; return $this; } public function getClearCacheCallback() { return $this->clearCacheCallback; } } diff --git a/src/applications/cache/spec/PhabricatorDataCacheSpec.php b/src/applications/cache/spec/PhabricatorDataCacheSpec.php index 103b0d8394..0c0c449c53 100644 --- a/src/applications/cache/spec/PhabricatorDataCacheSpec.php +++ b/src/applications/cache/spec/PhabricatorDataCacheSpec.php @@ -1,158 +1,158 @@ cacheSummary = $cache_summary; return $this; } public function getCacheSummary() { return $this->cacheSummary; } public static function getActiveCacheSpec() { $spec = new PhabricatorDataCacheSpec(); // NOTE: If APCu is installed, it reports that APC is installed. if (extension_loaded('apc') && !extension_loaded('apcu')) { $spec->initAPCSpec(); } else if (extension_loaded('apcu')) { $spec->initAPCuSpec(); } else { $spec->initNoneSpec(); } return $spec; } private function initAPCSpec() { $this ->setName(pht('APC User Cache')) ->setVersion(phpversion('apc')); if (ini_get('apc.enabled')) { $this ->setIsEnabled(true) ->setClearCacheCallback('apc_clear_cache'); $this->initAPCCommonSpec(); } else { $this->setIsEnabled(false); $this->raiseEnableAPCIssue(); } } private function initAPCuSpec() { $this ->setName(pht('APCu')) ->setVersion(phpversion('apcu')); if (ini_get('apc.enabled')) { if (function_exists('apcu_clear_cache')) { $clear_callback = 'apcu_clear_cache'; } else { $clear_callback = 'apc_clear_cache'; } $this ->setIsEnabled(true) ->setClearCacheCallback($clear_callback); $this->initAPCCommonSpec(); } else { $this->setIsEnabled(false); $this->raiseEnableAPCIssue(); } } private function initNoneSpec() { if (version_compare(phpversion(), '5.5', '>=')) { $message = pht( 'Installing the "APCu" PHP extension will improve performance. '. - 'This extension is strongly recommended. Without it, Phabricator '. + 'This extension is strongly recommended. Without it, this software '. 'must rely on a very inefficient disk-based cache.'); $this ->newIssue('extension.apcu') ->setShortName(pht('APCu')) ->setName(pht('PHP Extension "APCu" Not Installed')) ->setMessage($message) ->addPHPExtension('apcu'); } else { $this->raiseInstallAPCIssue(); } } private function initAPCCommonSpec() { $state = array(); if (function_exists('apcu_sma_info')) { $mem = apcu_sma_info(); $info = apcu_cache_info(); } else if (function_exists('apc_sma_info')) { $mem = apc_sma_info(); $info = apc_cache_info('user'); } else { $mem = null; } if ($mem) { $this->setTotalMemory($mem['num_seg'] * $mem['seg_size']); $this->setUsedMemory($info['mem_size']); $this->setEntryCount(count($info['cache_list'])); $cache = $info['cache_list']; $state = array(); foreach ($cache as $item) { // Some older versions of APCu report the cachekey as "key", while // newer APCu and APC report it as "info". Just check both indexes // for commpatibility. See T13164 for details. $info = idx($item, 'info'); if ($info === null) { $info = idx($item, 'key'); } if ($info === null) { $key = ''; } else { $key = self::getKeyPattern($info); } if (empty($state[$key])) { $state[$key] = array( 'max' => 0, 'total' => 0, 'count' => 0, ); } $state[$key]['max'] = max($state[$key]['max'], $item['mem_size']); $state[$key]['total'] += $item['mem_size']; $state[$key]['count']++; } } $this->setCacheSummary($state); } private static function getKeyPattern($key) { // If this key isn't in the current cache namespace, don't reveal any // information about it. $namespace = PhabricatorEnv::getEnvConfig('phabricator.cache-namespace'); if (strncmp($key, $namespace.':', strlen($namespace) + 1)) { return ''; } $key = preg_replace('/(?initAPCSpec(); } else if (extension_loaded('Zend OPcache')) { $spec->initOpcacheSpec(); } else { $spec->initNoneSpec(); } return $spec; } private function initAPCSpec() { $this ->setName(pht('APC')) ->setVersion(phpversion('apc')); if (ini_get('apc.enabled')) { $this ->setIsEnabled(true) ->setClearCacheCallback('apc_clear_cache'); $mem = apc_sma_info(); $this->setTotalMemory($mem['num_seg'] * $mem['seg_size']); $info = apc_cache_info(); $this->setUsedMemory($info['mem_size']); $write_lock = ini_get('apc.write_lock'); $slam_defense = ini_get('apc.slam_defense'); if (!$write_lock || $slam_defense) { $summary = pht('Adjust APC settings to quiet unnecessary errors.'); $message = pht( 'Some versions of APC may emit unnecessary errors into the '. 'error log under the current APC settings. To resolve this, '. 'enable "%s" and disable "%s" in your PHP configuration.', 'apc.write_lock', 'apc.slam_defense'); $this ->newIssue('extension.apc.write-lock') ->setShortName(pht('Noisy APC')) ->setName(pht('APC Has Noisy Configuration')) ->setSummary($summary) ->setMessage($message) ->addPHPConfig('apc.write_lock') ->addPHPConfig('apc.slam_defense'); } $is_dev = PhabricatorEnv::getEnvConfig('phabricator.developer-mode'); $is_stat_enabled = ini_get('apc.stat'); if ($is_stat_enabled && !$is_dev) { $summary = pht( '"%s" is currently enabled, but should probably be disabled.', 'apc.stat'); $message = pht( 'The "%s" setting is currently enabled in your PHP configuration. '. 'In production mode, "%s" should be disabled. '. 'This will improve performance slightly.', 'apc.stat', 'apc.stat'); $this ->newIssue('extension.apc.stat-enabled') ->setShortName(pht('"%s" Enabled', 'apc.stat')) ->setName(pht('"%s" Enabled in Production', 'apc.stat')) ->setSummary($summary) ->setMessage($message) ->addPHPConfig('apc.stat') ->addPhabricatorConfig('phabricator.developer-mode'); } else if (!$is_stat_enabled && $is_dev) { $summary = pht( '"%s" is currently disabled, but should probably be enabled.', 'apc.stat'); $message = pht( 'The "%s" setting is currently disabled in your PHP configuration, '. - 'but Phabricator is running in development mode. This option should '. - 'normally be enabled in development so you do not need to restart '. - 'anything after making changes to the code.', + 'but this software is running in development mode. This option '. + 'should normally be enabled in development so you do not need to '. + 'restart anything after making changes to the code.', 'apc.stat'); $this ->newIssue('extension.apc.stat-disabled') ->setShortName(pht('"%s" Disabled', 'apc.stat')) ->setName(pht('"%s" Disabled in Development', 'apc.stat')) ->setSummary($summary) ->setMessage($message) ->addPHPConfig('apc.stat') ->addPhabricatorConfig('phabricator.developer-mode'); } } else { $this->setIsEnabled(false); $this->raiseEnableAPCIssue(); } } private function initOpcacheSpec() { $this ->setName(pht('Zend OPcache')) ->setVersion(phpversion('Zend OPcache')); if (ini_get('opcache.enable')) { $this ->setIsEnabled(true) ->setClearCacheCallback('opcache_reset'); $status = opcache_get_status(); $memory = $status['memory_usage']; $mem_used = $memory['used_memory']; $mem_free = $memory['free_memory']; $mem_junk = $memory['wasted_memory']; $this->setUsedMemory($mem_used + $mem_junk); $this->setTotalMemory($mem_used + $mem_junk + $mem_free); $this->setEntryCount($status['opcache_statistics']['num_cached_keys']); $is_dev = PhabricatorEnv::getEnvConfig('phabricator.developer-mode'); $validate = ini_get('opcache.validate_timestamps'); $freq = ini_get('opcache.revalidate_freq'); if ($is_dev && (!$validate || $freq)) { $summary = pht( 'OPcache is not configured properly for development.'); $message = pht( 'In development, OPcache should be configured to always reload '. 'code so nothing needs to be restarted after making changes. To do '. 'this, enable "%s" and set "%s" to 0.', 'opcache.validate_timestamps', 'opcache.revalidate_freq'); $this ->newIssue('extension.opcache.devmode') ->setShortName(pht('OPcache Config')) ->setName(pht('OPcache Not Configured for Development')) ->setSummary($summary) ->setMessage($message) ->addPHPConfig('opcache.validate_timestamps') ->addPHPConfig('opcache.revalidate_freq') ->addPhabricatorConfig('phabricator.developer-mode'); } else if (!$is_dev && $validate) { $summary = pht('OPcache is not configured ideally for production.'); $message = pht( 'In production, OPcache should be configured to never '. 'revalidate code. This will slightly improve performance. '. 'To do this, disable "%s" in your PHP configuration.', 'opcache.validate_timestamps'); $this ->newIssue('extension.opcache.production') ->setShortName(pht('OPcache Config')) ->setName(pht('OPcache Not Configured for Production')) ->setSummary($summary) ->setMessage($message) ->addPHPConfig('opcache.validate_timestamps') ->addPhabricatorConfig('phabricator.developer-mode'); } } else { $this->setIsEnabled(false); $summary = pht('Enabling OPcache will dramatically improve performance.'); $message = pht( 'The PHP "Zend OPcache" extension is installed, but not enabled in '. 'your PHP configuration. Enabling it will dramatically improve '. - 'Phabricator performance. Edit the "%s" setting to '. - 'enable the extension.', + 'performance. Edit the "%s" setting to enable the extension.', 'opcache.enable'); $this->newIssue('extension.opcache.enable') ->setShortName(pht('OPcache Disabled')) ->setName(pht('Zend OPcache Not Enabled')) ->setSummary($summary) ->setMessage($message) ->addPHPConfig('opcache.enable'); } } private function initNoneSpec() { if (version_compare(phpversion(), '5.5', '>=')) { $message = pht( 'Installing the "Zend OPcache" extension will dramatically improve '. 'performance.'); $this ->newIssue('extension.opcache') ->setShortName(pht('OPcache')) ->setName(pht('Zend OPcache Not Installed')) ->setMessage($message) ->addPHPExtension('Zend OPcache'); } else { $this->raiseInstallAPCIssue(); } } }