[^/]+)/' =>
'PhabricatorEmailVerificationController',
);
}
protected function getResourceURIMapRules() {
return array(
'/res/' => array(
'(?Ppkg/)?'.
'(?P[a-f0-9]{8})/'.
'(?P.+\.(?:css|js|jpg|png|swf|gif))'
=> 'CelerityResourceController',
),
);
}
public function buildRequest() {
$request = new AphrontRequest($this->getHost(), $this->getPath());
$request->setRequestData($_GET + $_POST);
$request->setApplicationConfiguration($this);
return $request;
}
public function handleException(Exception $ex) {
$request = $this->getRequest();
// For Conduit requests, return a Conduit response.
if ($request->isConduit()) {
$response = new ConduitAPIResponse();
$response->setErrorCode(get_class($ex));
$response->setErrorInfo($ex->getMessage());
return id(new AphrontJSONResponse())
->setContent($response->toDictionary());
}
// For non-workflow requests, return a Ajax response.
if ($request->isAjax() && !$request->isJavelinWorkflow()) {
$response = new AphrontAjaxResponse();
$response->setError(
array(
'code' => get_class($ex),
'info' => $ex->getMessage(),
));
return $response;
}
$is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
$user = $request->getUser();
if (!$user) {
// If we hit an exception very early, we won't have a user.
$user = new PhabricatorUser();
}
if ($ex instanceof PhabricatorPolicyException) {
$content =
''.
phutil_escape_html($ex->getMessage()).
'';
$dialog = new AphrontDialogView();
$dialog
->setTitle(
$is_serious
? 'Access Denied'
: "You Shall Not Pass")
->setClass('aphront-access-dialog')
->setUser($user)
->appendChild($content);
if ($this->getRequest()->isAjax()) {
$dialog->addCancelButton('/', 'Close');
} else {
$dialog->addCancelButton('/', $is_serious ? 'OK' : 'Away With Thee');
}
$response = new AphrontDialogResponse();
$response->setDialog($dialog);
return $response;
}
if ($ex instanceof AphrontUsageException) {
$error = new AphrontErrorView();
$error->setTitle(phutil_escape_html($ex->getTitle()));
$error->appendChild(phutil_escape_html($ex->getMessage()));
$view = new PhabricatorStandardPageView();
$view->setRequest($this->getRequest());
$view->appendChild($error);
$response = new AphrontWebpageResponse();
$response->setContent($view->render());
return $response;
}
// Always log the unhandled exception.
phlog($ex);
$class = phutil_escape_html(get_class($ex));
$message = phutil_escape_html($ex->getMessage());
if (PhabricatorEnv::getEnvConfig('phabricator.show-stack-traces')) {
$trace = $this->renderStackTrace($ex->getTrace(), $user);
} else {
$trace = null;
}
$content =
''.
''.
$trace.
'';
$dialog = new AphrontDialogView();
$dialog
->setTitle('Unhandled Exception ("'.$class.'")')
->setClass('aphront-exception-dialog')
->setUser($user)
->appendChild($content);
if ($this->getRequest()->isAjax()) {
$dialog->addCancelButton('/', 'Close');
}
$response = new AphrontDialogResponse();
$response->setDialog($dialog);
return $response;
}
public function willSendResponse(AphrontResponse $response) {
$request = $this->getRequest();
$response->setRequest($request);
if ($response instanceof AphrontDialogResponse) {
if (!$request->isAjax()) {
$view = new PhabricatorStandardPageView();
$view->setRequest($request);
$view->appendChild(
''.
$response->buildResponseString().
'');
$response = new AphrontWebpageResponse();
$response->setContent($view->render());
return $response;
} else {
return id(new AphrontAjaxResponse())
->setContent(array(
'dialog' => $response->buildResponseString(),
));
}
} else if ($response instanceof AphrontRedirectResponse) {
if ($request->isAjax()) {
return id(new AphrontAjaxResponse())
->setContent(
array(
'redirect' => $response->getURI(),
));
}
}
return $response;
}
public function build404Controller() {
return array(new Phabricator404Controller($this->getRequest()), array());
}
public function buildRedirectController($uri) {
return array(
new PhabricatorRedirectController($this->getRequest()),
array(
'uri' => $uri,
));
}
private function renderStackTrace($trace, PhabricatorUser $user) {
$libraries = PhutilBootloader::getInstance()->getAllLibraries();
// TODO: Make this configurable?
$path = 'https://secure.phabricator.com/diffusion/%s/browse/master/src/';
$callsigns = array(
'arcanist' => 'ARC',
'phutil' => 'PHU',
'phabricator' => 'P',
);
$rows = array();
$depth = count($trace);
foreach ($trace as $part) {
$lib = null;
$file = idx($part, 'file');
$relative = $file;
foreach ($libraries as $library) {
$root = phutil_get_library_root($library);
if (Filesystem::isDescendant($file, $root)) {
$lib = $library;
$relative = Filesystem::readablePath($file, $root);
break;
}
}
$where = '';
if (isset($part['class'])) {
$where .= $part['class'].'::';
}
if (isset($part['function'])) {
$where .= $part['function'].'()';
}
if ($file) {
if (isset($callsigns[$lib])) {
$attrs = array('title' => $file);
try {
$attrs['href'] = $user->loadEditorLink(
'/src/'.$relative,
$part['line'],
$callsigns[$lib]);
} catch (Exception $ex) {
// The database can be inaccessible.
}
if (empty($attrs['href'])) {
$attrs['href'] = sprintf($path, $callsigns[$lib]).
str_replace(DIRECTORY_SEPARATOR, '/', $relative).
'$'.$part['line'];
$attrs['target'] = '_blank';
}
$file_name = phutil_render_tag(
'a',
$attrs,
phutil_escape_html($relative));
} else {
$file_name = phutil_render_tag(
'span',
array(
'title' => $file,
),
phutil_escape_html($relative));
}
$file_name = $file_name.' : '.(int)$part['line'];
} else {
$file_name = '(Internal)';
}
$rows[] = array(
$depth--,
phutil_escape_html($lib),
$file_name,
phutil_escape_html($where),
);
}
$table = new AphrontTableView($rows);
$table->setHeaders(
array(
'Depth',
'Library',
'File',
'Where',
));
$table->setColumnClasses(
array(
'n',
'',
'',
'wide',
));
return
''.
'Stack Trace'.
$table->render().
'';
}
}
diff --git a/src/applications/auth/controller/PhabricatorLDAPLoginController.php b/src/applications/auth/controller/PhabricatorLDAPLoginController.php
new file mode 100644
index 0000000000..0525625373
--- /dev/null
+++ b/src/applications/auth/controller/PhabricatorLDAPLoginController.php
@@ -0,0 +1,187 @@
+provider = new PhabricatorLDAPProvider();
+ }
+
+ public function processRequest() {
+ if (!$this->provider->isProviderEnabled()) {
+ return new Aphront400Response();
+ }
+
+ $current_user = $this->getRequest()->getUser();
+ $request = $this->getRequest();
+
+ if ($request->isFormPost()) {
+ try {
+ $this->provider->auth($request->getStr('username'),
+ $request->getStr('password'));
+
+ } catch (Exception $e) {
+ $errors[] = $e->getMessage();
+ }
+
+ if (empty($errors)) {
+ $ldap_info = $this->retrieveLDAPInfo($this->provider);
+
+ if ($current_user->getPHID()) {
+ if ($ldap_info->getID()) {
+ $existing_ldap = id(new PhabricatorUserLDAPInfo())->loadOneWhere(
+ 'userID = %d',
+ $current_user->getID());
+
+ if ($ldap_info->getUserID() != $current_user->getID() ||
+ $existing_ldap) {
+ $dialog = new AphrontDialogView();
+ $dialog->setUser($current_user);
+ $dialog->setTitle('Already Linked to Another Account');
+ $dialog->appendChild(
+ 'The LDAP account you just authorized is already linked to '.
+ 'another Phabricator account. Before you can link it to a '.
+ 'different LDAP account, you must unlink the old account.
'
+ );
+ $dialog->addCancelButton('/settings/page/ldap/');
+
+ return id(new AphrontDialogResponse())->setDialog($dialog);
+ } else {
+ return id(new AphrontRedirectResponse())
+ ->setURI('/settings/page/ldap/');
+ }
+ }
+
+ if (!$request->isDialogFormPost()) {
+ $dialog = new AphrontDialogView();
+ $dialog->setUser($current_user);
+ $dialog->setTitle('Link LDAP Account');
+ $dialog->appendChild(
+ 'Link your LDAP account to your Phabricator account?
');
+ $dialog->addHiddenInput('username', $request->getStr('username'));
+ $dialog->addHiddenInput('password', $request->getStr('password'));
+ $dialog->addSubmitButton('Link Accounts');
+ $dialog->addCancelButton('/settings/page/ldap/');
+
+ return id(new AphrontDialogResponse())->setDialog($dialog);
+ }
+
+ $ldap_info->setUserID($current_user->getID());
+
+ $this->saveLDAPInfo($ldap_info);
+
+ return id(new AphrontRedirectResponse())
+ ->setURI('/settings/page/ldap/');
+ }
+
+ if ($ldap_info->getID()) {
+ $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
+
+ $known_user = id(new PhabricatorUser())->load(
+ $ldap_info->getUserID());
+
+ $session_key = $known_user->establishSession('web');
+
+ $this->saveLDAPInfo($ldap_info);
+
+ $request->setCookie('phusr', $known_user->getUsername());
+ $request->setCookie('phsid', $session_key);
+
+ $uri = new PhutilURI('/login/validate/');
+ $uri->setQueryParams(
+ array(
+ 'phusr' => $known_user->getUsername(),
+ ));
+
+ return id(new AphrontRedirectResponse())->setURI((string)$uri);
+ }
+
+ $controller = newv('PhabricatorLDAPRegistrationController',
+ array($this->getRequest()));
+ $controller->setLDAPProvider($this->provider);
+ $controller->setLDAPInfo($ldap_info);
+
+ return $this->delegateToController($controller);
+ }
+ }
+
+ $ldap_username = $request->getCookie('phusr');
+ $ldap_form = new AphrontFormView();
+ $ldap_form
+ ->setUser($request->getUser())
+ ->setAction('/ldap/login/')
+ ->appendChild(
+ id(new AphrontFormTextControl())
+ ->setLabel('LDAP username')
+ ->setName('username')
+ ->setValue($ldap_username))
+ ->appendChild(
+ id(new AphrontFormPasswordControl())
+ ->setLabel('Password')
+ ->setName('password'));
+
+ $ldap_form
+ ->appendChild(
+ id(new AphrontFormSubmitControl())
+ ->setValue('Login'));
+
+ $panel = new AphrontPanelView();
+ $panel->setWidth(AphrontPanelView::WIDTH_FORM);
+ $panel->appendChild('LDAP login
');
+ $panel->appendChild($ldap_form);
+
+ if (isset($errors) && count($errors) > 0) {
+ $error_view = new AphrontErrorView();
+ $error_view->setTitle('Login Failed');
+ $error_view->setErrors($errors);
+ }
+
+ return $this->buildStandardPageResponse(
+ array(
+ isset($error_view) ? $error_view : null,
+ $panel,
+ ),
+ array(
+ 'title' => 'Login',
+ ));
+ }
+
+ private function retrieveLDAPInfo(PhabricatorLDAPProvider $provider) {
+ $ldap_info = id(new PhabricatorUserLDAPInfo())->loadOneWhere(
+ 'ldapUsername = %s',
+ $provider->retrieveUsername());
+
+ if (!$ldap_info) {
+ $ldap_info = new PhabricatorUserLDAPInfo();
+ $ldap_info->setLDAPUsername($provider->retrieveUsername());
+ }
+
+ return $ldap_info;
+ }
+
+ private function saveLDAPInfo(PhabricatorUserLDAPInfo $info) {
+ // UNGUARDED WRITES: Logging-in users don't have their CSRF set up yet.
+ $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
+ $info->save();
+ }
+}
diff --git a/src/applications/auth/controller/PhabricatorLDAPRegistrationController.php b/src/applications/auth/controller/PhabricatorLDAPRegistrationController.php
new file mode 100644
index 0000000000..111c21a9e3
--- /dev/null
+++ b/src/applications/auth/controller/PhabricatorLDAPRegistrationController.php
@@ -0,0 +1,236 @@
+ldapProvider = $provider;
+ return $this;
+ }
+
+ public function getLDAProvider() {
+ return $this->ldapProvider;
+ }
+
+ public function setLDAPInfo($info) {
+ $this->ldapInfo = $info;
+ return $this;
+ }
+
+ public function getLDAPInfo() {
+ return $this->ldapInfo;
+ }
+
+ public function processRequest() {
+ $provider = $this->getLDAProvider();
+ $ldap_info = $this->getLDAPInfo();
+ $request = $this->getRequest();
+
+ $errors = array();
+ $e_username = true;
+ $e_email = true;
+ $e_realname = true;
+
+ $user = new PhabricatorUser();
+ $user->setUsername();
+ $user->setRealname($provider->retrieveUserRealName());
+
+ $new_email = $provider->retrieveUserEmail();
+
+ if ($new_email) {
+ // If the user's LDAP provider account has an email address but the
+ // email address domain is not allowed by the Phabricator configuration,
+ // we just pretend the provider did not supply an address.
+ //
+ // For instance, if the user uses LDAP Auth and their email address
+ // is "joe@personal.com" but Phabricator is configured to require users
+ // use "@company.com" addresses, we show a prompt below and tell the user
+ // to provide their "@company.com" address. They can still use the LDAP
+ // account to login, they just need to associate their account with an
+ // allowed address.
+ //
+ // If the email address is fine, we just use it and don't prompt the user.
+ if (!PhabricatorUserEmail::isAllowedAddress($new_email)) {
+ $new_email = null;
+ }
+ }
+
+ $show_email_input = ($new_email === null);
+
+ if ($request->isFormPost()) {
+ $user->setUsername($request->getStr('username'));
+ $username = $user->getUsername();
+ if (!strlen($user->getUsername())) {
+ $e_username = 'Required';
+ $errors[] = 'Username is required.';
+ } else if (!PhabricatorUser::validateUsername($username)) {
+ $e_username = 'Invalid';
+ $errors[] = PhabricatorUser::describeValidUsername();
+ } else {
+ $e_username = null;
+ }
+
+ if (!$new_email) {
+ $new_email = trim($request->getStr('email'));
+ if (!$new_email) {
+ $e_email = 'Required';
+ $errors[] = 'Email is required.';
+ } else {
+ $e_email = null;
+ }
+ }
+
+ if ($new_email) {
+ if (!PhabricatorUserEmail::isAllowedAddress($new_email)) {
+ $e_email = 'Invalid';
+ $errors[] = PhabricatorUserEmail::describeAllowedAddresses();
+ }
+ }
+
+ if (!strlen($user->getRealName())) {
+ $user->setRealName($request->getStr('realname'));
+ if (!strlen($user->getRealName())) {
+ $e_realname = 'Required';
+ $errors[] = 'Real name is required.';
+ } else {
+ $e_realname = null;
+ }
+ }
+
+ if (!$errors) {
+ try {
+ // NOTE: We don't verify LDAP email addresses by default because
+ // LDAP providers might associate email addresses with accounts that
+ // haven't actually verified they own them. We could selectively
+ // auto-verify some providers that we trust here, but the stakes for
+ // verifying an email address are high because having a corporate
+ // address at a company is sometimes the key to the castle.
+
+ $email_obj = id(new PhabricatorUserEmail())
+ ->setAddress($new_email)
+ ->setIsVerified(0);
+
+ id(new PhabricatorUserEditor())
+ ->setActor($user)
+ ->createNewUser($user, $email_obj);
+
+ $ldap_info->setUserID($user->getID());
+ $ldap_info->save();
+
+ $session_key = $user->establishSession('web');
+ $request->setCookie('phusr', $user->getUsername());
+ $request->setCookie('phsid', $session_key);
+
+ $email_obj->sendVerificationEmail($user);
+
+ return id(new AphrontRedirectResponse())->setURI('/');
+ } catch (AphrontQueryDuplicateKeyException $exception) {
+
+ $same_username = id(new PhabricatorUser())->loadOneWhere(
+ 'userName = %s',
+ $user->getUserName());
+
+ $same_email = id(new PhabricatorUserEmail())->loadOneWhere(
+ 'address = %s',
+ $new_email);
+
+ if ($same_username) {
+ $e_username = 'Duplicate';
+ $errors[] = 'That username or email is not unique.';
+ } else if ($same_email) {
+ $e_email = 'Duplicate';
+ $errors[] = 'That email is not unique.';
+ } else {
+ throw $exception;
+ }
+ }
+ }
+ }
+
+
+ $error_view = null;
+ if ($errors) {
+ $error_view = new AphrontErrorView();
+ $error_view->setTitle('Registration Failed');
+ $error_view->setErrors($errors);
+ }
+
+ // Strip the URI down to the path, because otherwise we'll trigger
+ // external CSRF protection (by having a protocol in the form "action")
+ // and generate a form with no CSRF token.
+ $action_uri = new PhutilURI('/ldap/login/');
+ $action_path = $action_uri->getPath();
+
+ $form = new AphrontFormView();
+ $form
+ ->setUser($request->getUser())
+ ->setAction($action_path)
+ ->appendChild(
+ id(new AphrontFormTextControl())
+ ->setLabel('Username')
+ ->setName('username')
+ ->setValue($user->getUsername())
+ ->setError($e_username));
+
+ $form->appendChild(
+ id(new AphrontFormPasswordControl())
+ ->setLabel('Password')
+ ->setName('password'));
+
+ if ($show_email_input) {
+ $form->appendChild(
+ id(new AphrontFormTextControl())
+ ->setLabel('Email')
+ ->setName('email')
+ ->setValue($request->getStr('email'))
+ ->setError($e_email));
+ }
+
+ if ($provider->retrieveUserRealName() === null) {
+ $form->appendChild(
+ id(new AphrontFormTextControl())
+ ->setLabel('Real Name')
+ ->setName('realname')
+ ->setValue($request->getStr('realname'))
+ ->setError($e_realname));
+ }
+
+ $form
+ ->appendChild(
+ id(new AphrontFormSubmitControl())
+ ->setValue('Create Account'));
+
+ $panel = new AphrontPanelView();
+ $panel->setHeader('Create New Account');
+ $panel->setWidth(AphrontPanelView::WIDTH_FORM);
+ $panel->appendChild($form);
+
+ return $this->buildStandardPageResponse(
+ array(
+ $error_view,
+ $panel,
+ ),
+ array(
+ 'title' => 'Create New Account',
+ ));
+ }
+
+}
diff --git a/src/applications/auth/controller/PhabricatorLDAPUnlinkController.php b/src/applications/auth/controller/PhabricatorLDAPUnlinkController.php
new file mode 100644
index 0000000000..c793716d5d
--- /dev/null
+++ b/src/applications/auth/controller/PhabricatorLDAPUnlinkController.php
@@ -0,0 +1,52 @@
+getRequest();
+ $user = $request->getUser();
+
+ $ldap_info = id(new PhabricatorUserLDAPInfo())->loadOneWhere(
+ 'userID = %d',
+ $user->getID());
+
+ if (!$ldap_info) {
+ return new Aphront400Response();
+ }
+
+ if (!$request->isDialogFormPost()) {
+ $dialog = new AphrontDialogView();
+ $dialog->setUser($user);
+ $dialog->setTitle('Really unlink account?');
+ $dialog->appendChild(
+ 'You will not be able to login using this account '.
+ 'once you unlink it. Continue?
');
+ $dialog->addSubmitButton('Unlink Account');
+ $dialog->addCancelButton('/settings/page/ldap/');
+
+ return id(new AphrontDialogResponse())->setDialog($dialog);
+ }
+
+ $ldap_info->delete();
+
+ return id(new AphrontRedirectResponse())
+ ->setURI('/settings/page/ldap/');
+ }
+
+}
diff --git a/src/applications/auth/controller/PhabricatorLoginController.php b/src/applications/auth/controller/PhabricatorLoginController.php
index 5e7df9ee59..bedd20965a 100644
--- a/src/applications/auth/controller/PhabricatorLoginController.php
+++ b/src/applications/auth/controller/PhabricatorLoginController.php
@@ -1,268 +1,292 @@
getRequest();
if ($request->getUser()->getPHID()) {
// Kick the user out if they're already logged in.
return id(new AphrontRedirectResponse())->setURI('/');
}
if ($request->isConduit()) {
// A common source of errors in Conduit client configuration is getting
// the request path wrong. The client will end up here, so make some
// effort to give them a comprehensible error message.
$request_path = $this->getRequest()->getPath();
$conduit_path = '/api/';
$example_path = '/api/conduit.ping';
$message =
"ERROR: You are making a Conduit API request to '{$request_path}', ".
"but the correct HTTP request path to use in order to access a ".
"Conduit method is '{$conduit_path}' (for example, ".
"'{$example_path}'). Check your configuration.";
return id(new AphrontPlainTextResponse())->setContent($message);
}
$error_view = null;
if ($request->getCookie('phusr') && $request->getCookie('phsid')) {
// The session cookie is invalid, so clear it.
$request->clearCookie('phusr');
$request->clearCookie('phsid');
$error_view = new AphrontErrorView();
$error_view->setTitle('Invalid Session');
$error_view->setErrors(array(
"Your login session is invalid. Try logging in again. If that ".
"doesn't work, clear your browser cookies."
));
}
$next_uri_path = $this->getRequest()->getPath();
if ($next_uri_path == '/login/') {
$next_uri = '/';
} else {
$next_uri = $this->getRequest()->getRequestURI();
}
if (!$request->isFormPost()) {
$request->setCookie('next_uri', $next_uri);
}
$password_auth = PhabricatorEnv::getEnvConfig('auth.password-auth-enabled');
$forms = array();
$errors = array();
if ($password_auth) {
$require_captcha = false;
$e_captcha = true;
$username_or_email = $request->getCookie('phusr');
if ($request->isFormPost()) {
if (AphrontFormRecaptchaControl::isRecaptchaEnabled()) {
$failed_attempts = PhabricatorUserLog::loadRecentEventsFromThisIP(
PhabricatorUserLog::ACTION_LOGIN_FAILURE,
60 * 15);
if (count($failed_attempts) > 5) {
$require_captcha = true;
if (!AphrontFormRecaptchaControl::processCaptcha($request)) {
if (AphrontFormRecaptchaControl::hasCaptchaResponse($request)) {
$e_captcha = 'Invalid';
$errors[] = 'CAPTCHA was not entered correctly.';
} else {
$e_captcha = 'Required';
$errors[] = 'Too many login failures recently. You must '.
'submit a CAPTCHA with your login request.';
}
}
}
}
$username_or_email = $request->getStr('username_or_email');
$user = id(new PhabricatorUser())->loadOneWhere(
'username = %s',
$username_or_email);
if (!$user) {
$user = PhabricatorUser::loadOneWithEmailAddress($username_or_email);
}
if (!$errors) {
// Perform username/password tests only if we didn't get rate limited
// by the CAPTCHA.
if (!$user || !$user->comparePassword($request->getStr('password'))) {
$errors[] = 'Bad username/password.';
}
}
if (!$errors) {
$session_key = $user->establishSession('web');
$request->setCookie('phusr', $user->getUsername());
$request->setCookie('phsid', $session_key);
$uri = new PhutilURI('/login/validate/');
$uri->setQueryParams(
array(
'phusr' => $user->getUsername(),
));
return id(new AphrontRedirectResponse())
->setURI((string)$uri);
} else {
$log = PhabricatorUserLog::newLog(
null,
$user,
PhabricatorUserLog::ACTION_LOGIN_FAILURE);
$log->save();
$request->clearCookie('phusr');
$request->clearCookie('phsid');
}
}
if ($errors) {
$error_view = new AphrontErrorView();
$error_view->setTitle('Login Failed');
$error_view->setErrors($errors);
}
$form = new AphrontFormView();
$form
->setUser($request->getUser())
->setAction('/login/')
->appendChild(
id(new AphrontFormTextControl())
->setLabel('Username/Email')
->setName('username_or_email')
->setValue($username_or_email))
->appendChild(
id(new AphrontFormPasswordControl())
->setLabel('Password')
->setName('password')
->setCaption(
''.
'Forgot your password? / Email Login'));
if ($require_captcha) {
$form->appendChild(
id(new AphrontFormRecaptchaControl())
->setError($e_captcha));
}
$form
->appendChild(
id(new AphrontFormSubmitControl())
->setValue('Login'));
// $panel->setCreateButton('Register New Account', '/login/register/');
$forms['Phabricator Login'] = $form;
+
+ $ldap_provider = new PhabricatorLDAPProvider();
+ if ($ldap_provider->isProviderEnabled()) {
+ $ldap_form = new AphrontFormView();
+ $ldap_form
+ ->setUser($request->getUser())
+ ->setAction('/ldap/login/')
+ ->appendChild(
+ id(new AphrontFormTextControl())
+ ->setLabel('LDAP username')
+ ->setName('username')
+ ->setValue($username_or_email))
+ ->appendChild(
+ id(new AphrontFormPasswordControl())
+ ->setLabel('Password')
+ ->setName('password'));
+
+ $ldap_form
+ ->appendChild(
+ id(new AphrontFormSubmitControl())
+ ->setValue('Login'));
+
+ $forms['LDAP Login'] = $ldap_form;
+ }
}
$providers = PhabricatorOAuthProvider::getAllProviders();
foreach ($providers as $provider) {
$enabled = $provider->isProviderEnabled();
if (!$enabled) {
continue;
}
$auth_uri = $provider->getAuthURI();
$redirect_uri = $provider->getRedirectURI();
$client_id = $provider->getClientID();
$provider_name = $provider->getProviderName();
$minimum_scope = $provider->getMinimumScope();
$extra_auth = $provider->getExtraAuthParameters();
// TODO: In theory we should use 'state' to prevent CSRF, but the total
// effect of the CSRF attack is that an attacker can cause a user to login
// to Phabricator if they're already logged into some OAuth provider. This
// does not seem like the most severe threat in the world, and generating
// CSRF for logged-out users is vaugely tricky.
if ($provider->isProviderRegistrationEnabled()) {
$title = "Login or Register with {$provider_name}";
$body = 'Login or register for Phabricator using your '.
phutil_escape_html($provider_name).' account.';
$button = "Login or Register with {$provider_name}";
} else {
$title = "Login with {$provider_name}";
$body = 'Login to your existing Phabricator account using your '.
phutil_escape_html($provider_name).' account.
'.
'You can not use '.
phutil_escape_html($provider_name).' to register a new '.
'account.';
$button = "Login with {$provider_name}";
}
$auth_form = new AphrontFormView();
$auth_form
->setAction($auth_uri)
->addHiddenInput('client_id', $client_id)
->addHiddenInput('redirect_uri', $redirect_uri)
->addHiddenInput('scope', $minimum_scope);
foreach ($extra_auth as $key => $value) {
$auth_form->addHiddenInput($key, $value);
}
$auth_form
->setUser($request->getUser())
->setMethod('GET')
->appendChild(
''.$body.'
')
->appendChild(
id(new AphrontFormSubmitControl())
->setValue("{$button} \xC2\xBB"));
$forms[$title] = $auth_form;
}
$panel = new AphrontPanelView();
$panel->setWidth(AphrontPanelView::WIDTH_FORM);
foreach ($forms as $name => $form) {
$panel->appendChild(''.$name.'
');
$panel->appendChild($form);
$panel->appendChild('
');
}
return $this->buildStandardPageResponse(
array(
$error_view,
$panel,
),
array(
'title' => 'Login',
));
}
}
diff --git a/src/applications/auth/ldap/PhabricatorLDAPProvider.php b/src/applications/auth/ldap/PhabricatorLDAPProvider.php
new file mode 100644
index 0000000000..d1d0b29c31
--- /dev/null
+++ b/src/applications/auth/ldap/PhabricatorLDAPProvider.php
@@ -0,0 +1,149 @@
+connection)) {
+ ldap_unbind($this->connection);
+ }
+ }
+
+ public function isProviderEnabled() {
+ return PhabricatorEnv::getEnvConfig('ldap.auth-enabled');
+ }
+
+ public function getHostname() {
+ return PhabricatorEnv::getEnvConfig('ldap.hostname');
+ }
+
+ public function getBaseDN() {
+ return PhabricatorEnv::getEnvConfig('ldap.base_dn');
+ }
+
+ public function getSearchAttribute() {
+ return PhabricatorEnv::getEnvConfig('ldap.search_attribute');
+ }
+
+ public function getLDAPVersion() {
+ return PhabricatorEnv::getEnvConfig('ldap.version');
+ }
+
+ public function retrieveUserEmail() {
+ return $this->userData['mail'][0];
+ }
+
+ public function retrieveUserRealName() {
+ $name_attributes = PhabricatorEnv::getEnvConfig(
+ 'ldap.real_name_attributes');
+
+ $real_name = '';
+ if (is_array($name_attributes)) {
+ foreach ($name_attributes AS $attribute) {
+ if (isset($this->userData[$attribute][0])) {
+ $real_name .= $this->userData[$attribute][0] . ' ';
+ }
+ }
+
+ trim($real_name);
+ } else if (isset($this->userData[$name_attributes][0])) {
+ $real_name = $this->userData[$name_attributes][0];
+ }
+
+ if ($real_name == '') {
+ return null;
+ }
+
+ return $real_name;
+ }
+
+ public function retrieveUsername() {
+ return $this->userData[$this->getSearchAttribute()][0];
+ }
+
+ public function getConnection() {
+ if (!isset($this->connection)) {
+ $this->connection = ldap_connect($this->getHostname());
+
+ if (!$this->connection) {
+ throw new Exception('Could not connect to LDAP host at ' .
+ $this->getHostname());
+ }
+
+ ldap_set_option($this->connection, LDAP_OPT_PROTOCOL_VERSION,
+ $this->getLDAPVersion());
+ }
+
+ return $this->connection;
+ }
+
+ public function getUserData() {
+ return $this->userData;
+ }
+
+ public function auth($username, $password) {
+ if (strlen(trim($username)) == 0 || strlen(trim($password)) == 0) {
+ throw new Exception('Username and/or password can not be empty');
+ }
+
+ $result = ldap_bind($this->getConnection(),
+ $this->getSearchAttribute() . '=' . $username . ',' .
+ $this->getBaseDN(),
+ $password);
+
+ if (!$result) {
+ throw new Exception('Bad username/password.');
+ }
+
+ $this->userData = $this->getUser($username);
+ return $this->userData;
+ }
+
+ private function getUser($username) {
+ $result = ldap_search($this->getConnection(), $this->getBaseDN(),
+ $this->getSearchAttribute() . '=' . $username);
+
+ if (!$result) {
+ throw new Exception('Search failed. Please check your LDAP and HTTP '.
+ 'logs for more information.');
+ }
+
+ $entries = ldap_get_entries($this->getConnection(), $result);
+
+ if ($entries === false) {
+ throw new Exception('Could not get entries');
+ }
+
+ if ($entries['count'] > 1) {
+ throw new Exception('Found more then one user with this ' .
+ $this->getSearchAttribute());
+ }
+
+ if ($entries['count'] == 0) {
+ throw new Exception('Could not find user');
+ }
+
+ return $entries[0];
+ }
+}
diff --git a/src/applications/people/controller/PhabricatorUserSettingsController.php b/src/applications/people/controller/PhabricatorUserSettingsController.php
index a5d3dc4b1f..199cb5cad9 100644
--- a/src/applications/people/controller/PhabricatorUserSettingsController.php
+++ b/src/applications/people/controller/PhabricatorUserSettingsController.php
@@ -1,138 +1,146 @@
page = idx($data, 'page');
}
public function processRequest() {
$request = $this->getRequest();
$oauth_providers = PhabricatorOAuthProvider::getAllProviders();
$sidenav = $this->renderSideNav($oauth_providers);
$this->page = $sidenav->selectFilter($this->page, 'account');
switch ($this->page) {
case 'account':
$delegate = new PhabricatorUserAccountSettingsPanelController($request);
break;
case 'profile':
$delegate = new PhabricatorUserProfileSettingsPanelController($request);
break;
case 'email':
$delegate = new PhabricatorUserEmailSettingsPanelController($request);
break;
case 'emailpref':
$delegate = new PhabricatorUserEmailPreferenceSettingsPanelController(
$request);
break;
case 'password':
$delegate = new PhabricatorUserPasswordSettingsPanelController(
$request);
break;
case 'conduit':
$delegate = new PhabricatorUserConduitSettingsPanelController($request);
break;
case 'sshkeys':
$delegate = new PhabricatorUserSSHKeysSettingsPanelController($request);
break;
case 'preferences':
$delegate = new PhabricatorUserPreferenceSettingsPanelController(
$request);
break;
case 'search':
$delegate = new PhabricatorUserSearchSettingsPanelController($request);
break;
+ case 'ldap':
+ $delegate = new PhabricatorUserLDAPSettingsPanelController($request);
+ break;
default:
$delegate = new PhabricatorUserOAuthSettingsPanelController($request);
$delegate->setOAuthProvider($oauth_providers[$this->page]);
break;
}
$response = $this->delegateToController($delegate);
if ($response instanceof AphrontView) {
$sidenav->appendChild($response);
return $this->buildStandardPageResponse(
$sidenav,
array(
'title' => 'Account Settings',
));
} else {
return $response;
}
}
private function renderSideNav($oauth_providers) {
$sidenav = new AphrontSideNavFilterView();
$sidenav
->setBaseURI(new PhutilURI('/settings/page/'))
->addLabel('Account Information')
->addFilter('account', 'Account')
->addFilter('profile', 'Profile')
->addSpacer()
->addLabel('Email')
->addFilter('email', 'Email Addresses')
->addFilter('emailpref', 'Email Preferences')
->addSpacer()
->addLabel('Authentication');
if (PhabricatorEnv::getEnvConfig('account.editable') &&
PhabricatorEnv::getEnvConfig('auth.password-auth-enabled')) {
$sidenav->addFilter('password', 'Password');
}
$sidenav->addFilter('conduit', 'Conduit Certificate');
if (PhabricatorUserSSHKeysSettingsPanelController::isEnabled()) {
$sidenav->addFilter('sshkeys', 'SSH Public Keys');
}
$sidenav->addSpacer();
$sidenav->addLabel('Application Settings');
$sidenav->addFilter('preferences', 'Display Preferences');
$sidenav->addFilter('search', 'Search Preferences');
$items = array();
foreach ($oauth_providers as $provider) {
if (!$provider->isProviderEnabled()) {
continue;
}
$key = $provider->getProviderKey();
$name = $provider->getProviderName();
$items[$key] = $name.' Account';
}
+ $ldap_provider = new PhabricatorLDAPProvider();
+ if ($ldap_provider->isProviderEnabled()) {
+ $items['ldap'] = 'LDAP Account';
+ }
+
if ($items) {
$sidenav->addSpacer();
$sidenav->addLabel('Linked Accounts');
foreach ($items as $key => $name) {
$sidenav->addFilter($key, $name);
}
}
return $sidenav;
}
}
diff --git a/src/applications/people/controller/settings/panels/PhabricatorUserLDAPSettingsPanelController.php b/src/applications/people/controller/settings/panels/PhabricatorUserLDAPSettingsPanelController.php
new file mode 100644
index 0000000000..c422c1db73
--- /dev/null
+++ b/src/applications/people/controller/settings/panels/PhabricatorUserLDAPSettingsPanelController.php
@@ -0,0 +1,87 @@
+getRequest();
+ $user = $request->getUser();
+
+ $ldap_info = id(new PhabricatorUserLDAPInfo())->loadOneWhere(
+ 'userID = %d',
+ $user->getID());
+
+ $forms = array();
+
+ if (!$ldap_info) {
+ $unlink = 'Link LDAP Account';
+ $unlink_form = new AphrontFormView();
+ $unlink_form
+ ->setUser($user)
+ ->setAction('/ldap/login/')
+ ->appendChild(
+ 'There is currently no '.
+ 'LDAP account linked to your Phabricator account. You can link an ' .
+ 'account, which will allow you to use it to log into Phabricator
')
+ ->appendChild(
+ id(new AphrontFormTextControl())
+ ->setLabel('LDAP username')
+ ->setName('username'))
+ ->appendChild(
+ id(new AphrontFormPasswordControl())
+ ->setLabel('Password')
+ ->setName('password'))
+ ->appendChild(
+ id(new AphrontFormSubmitControl())
+ ->setValue("Link LDAP Account \xC2\xBB"));
+
+ $forms['Link Account'] = $unlink_form;
+ } else {
+ $unlink = 'Unlink LDAP Account';
+ $unlink_form = new AphrontFormView();
+ $unlink_form
+ ->setUser($user)
+ ->appendChild(
+ 'You may unlink this account '.
+ 'from your LDAP account. This will prevent you from logging in with '.
+ 'your LDAP credentials.
')
+ ->appendChild(
+ id(new AphrontFormSubmitControl())
+ ->addCancelButton('/ldap/unlink/', $unlink));
+
+ $forms['Unlink Account'] = $unlink_form;
+ }
+
+ $panel = new AphrontPanelView();
+ $panel->setHeader('LDAP Account Settings');
+ $panel->setWidth(AphrontPanelView::WIDTH_FORM);
+ foreach ($forms as $name => $form) {
+ if ($name) {
+ $panel->appendChild('
'.$name.'
');
+ }
+ $panel->appendChild($form);
+ }
+
+ return id(new AphrontNullView())
+ ->appendChild(
+ array(
+ $panel,
+ ));
+ }
+}
diff --git a/src/applications/people/storage/PhabricatorUserLDAPInfo.php b/src/applications/people/storage/PhabricatorUserLDAPInfo.php
new file mode 100644
index 0000000000..a7c901fa23
--- /dev/null
+++ b/src/applications/people/storage/PhabricatorUserLDAPInfo.php
@@ -0,0 +1,22 @@
+ array(
'type' => 'db',
'name' => 'audit',
'after' => array( /* First Patch */ ),
),
'db.calendar' => array(
'type' => 'db',
'name' => 'calendar',
),
'db.chatlog' => array(
'type' => 'db',
'name' => 'chatlog',
),
'db.conduit' => array(
'type' => 'db',
'name' => 'conduit',
),
'db.countdown' => array(
'type' => 'db',
'name' => 'countdown',
),
'db.daemon' => array(
'type' => 'db',
'name' => 'daemon',
),
'db.differential' => array(
'type' => 'db',
'name' => 'differential',
),
'db.draft' => array(
'type' => 'db',
'name' => 'draft',
),
'db.drydock' => array(
'type' => 'db',
'name' => 'drydock',
),
'db.feed' => array(
'type' => 'db',
'name' => 'feed',
),
'db.file' => array(
'type' => 'db',
'name' => 'file',
),
'db.flag' => array(
'type' => 'db',
'name' => 'flag',
),
'db.harbormaster' => array(
'type' => 'db',
'name' => 'harbormaster',
),
'db.herald' => array(
'type' => 'db',
'name' => 'herald',
),
'db.maniphest' => array(
'type' => 'db',
'name' => 'maniphest',
),
'db.meta_data' => array(
'type' => 'db',
'name' => 'meta_data',
),
'db.metamta' => array(
'type' => 'db',
'name' => 'metamta',
),
'db.oauth_server' => array(
'type' => 'db',
'name' => 'oauth_server',
),
'db.owners' => array(
'type' => 'db',
'name' => 'owners',
),
'db.pastebin' => array(
'type' => 'db',
'name' => 'pastebin',
),
'db.phame' => array(
'type' => 'db',
'name' => 'phame',
),
'db.phriction' => array(
'type' => 'db',
'name' => 'phriction',
),
'db.project' => array(
'type' => 'db',
'name' => 'project',
),
'db.repository' => array(
'type' => 'db',
'name' => 'repository',
),
'db.search' => array(
'type' => 'db',
'name' => 'search',
),
'db.slowvote' => array(
'type' => 'db',
'name' => 'slowvote',
),
'db.timeline' => array(
'type' => 'db',
'name' => 'timeline',
),
'db.user' => array(
'type' => 'db',
'name' => 'user',
),
'db.worker' => array(
'type' => 'db',
'name' => 'worker',
),
'db.xhpastview' => array(
'type' => 'db',
'name' => 'xhpastview',
),
'0000.legacy.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('0000.legacy.sql'),
'legacy' => 0,
),
'000.project.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('000.project.sql'),
'legacy' => 0,
),
'001.maniphest_projects.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('001.maniphest_projects.sql'),
'legacy' => 1,
),
'002.oauth.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('002.oauth.sql'),
'legacy' => 2,
),
'003.more_oauth.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('003.more_oauth.sql'),
'legacy' => 3,
),
'004.daemonrepos.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('004.daemonrepos.sql'),
'legacy' => 4,
),
'005.workers.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('005.workers.sql'),
'legacy' => 5,
),
'006.repository.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('006.repository.sql'),
'legacy' => 6,
),
'007.daemonlog.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('007.daemonlog.sql'),
'legacy' => 7,
),
'008.repoopt.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('008.repoopt.sql'),
'legacy' => 8,
),
'009.repo_summary.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('009.repo_summary.sql'),
'legacy' => 9,
),
'010.herald.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('010.herald.sql'),
'legacy' => 10,
),
'011.badcommit.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('011.badcommit.sql'),
'legacy' => 11,
),
'012.dropphidtype.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('012.dropphidtype.sql'),
'legacy' => 12,
),
'013.commitdetail.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('013.commitdetail.sql'),
'legacy' => 13,
),
'014.shortcuts.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('014.shortcuts.sql'),
'legacy' => 14,
),
'015.preferences.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('015.preferences.sql'),
'legacy' => 15,
),
'016.userrealnameindex.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('016.userrealnameindex.sql'),
'legacy' => 16,
),
'017.sessionkeys.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('017.sessionkeys.sql'),
'legacy' => 17,
),
'018.owners.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('018.owners.sql'),
'legacy' => 18,
),
'019.arcprojects.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('019.arcprojects.sql'),
'legacy' => 19,
),
'020.pathcapital.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('020.pathcapital.sql'),
'legacy' => 20,
),
'021.xhpastview.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('021.xhpastview.sql'),
'legacy' => 21,
),
'022.differentialcommit.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('022.differentialcommit.sql'),
'legacy' => 22,
),
'023.dxkeys.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('023.dxkeys.sql'),
'legacy' => 23,
),
'024.mlistkeys.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('024.mlistkeys.sql'),
'legacy' => 24,
),
'025.commentopt.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('025.commentopt.sql'),
'legacy' => 25,
),
'026.diffpropkey.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('026.diffpropkey.sql'),
'legacy' => 26,
),
'027.metamtakeys.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('027.metamtakeys.sql'),
'legacy' => 27,
),
'028.systemagent.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('028.systemagent.sql'),
'legacy' => 28,
),
'029.cursors.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('029.cursors.sql'),
'legacy' => 29,
),
'030.imagemacro.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('030.imagemacro.sql'),
'legacy' => 30,
),
'031.workerrace.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('031.workerrace.sql'),
'legacy' => 31,
),
'032.viewtime.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('032.viewtime.sql'),
'legacy' => 32,
),
'033.privtest.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('033.privtest.sql'),
'legacy' => 33,
),
'034.savedheader.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('034.savedheader.sql'),
'legacy' => 34,
),
'035.proxyimage.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('035.proxyimage.sql'),
'legacy' => 35,
),
'036.mailkey.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('036.mailkey.sql'),
'legacy' => 36,
),
'037.setuptest.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('037.setuptest.sql'),
'legacy' => 37,
),
'038.admin.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('038.admin.sql'),
'legacy' => 38,
),
'039.userlog.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('039.userlog.sql'),
'legacy' => 39,
),
'040.transform.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('040.transform.sql'),
'legacy' => 40,
),
'041.heraldrepetition.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('041.heraldrepetition.sql'),
'legacy' => 41,
),
'042.commentmetadata.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('042.commentmetadata.sql'),
'legacy' => 42,
),
'043.pastebin.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('043.pastebin.sql'),
'legacy' => 43,
),
'044.countdown.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('044.countdown.sql'),
'legacy' => 44,
),
'045.timezone.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('045.timezone.sql'),
'legacy' => 45,
),
'046.conduittoken.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('046.conduittoken.sql'),
'legacy' => 46,
),
'047.projectstatus.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('047.projectstatus.sql'),
'legacy' => 47,
),
'048.relationshipkeys.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('048.relationshipkeys.sql'),
'legacy' => 48,
),
'049.projectowner.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('049.projectowner.sql'),
'legacy' => 49,
),
'050.taskdenormal.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('050.taskdenormal.sql'),
'legacy' => 50,
),
'051.projectfilter.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('051.projectfilter.sql'),
'legacy' => 51,
),
'052.pastelanguage.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('052.pastelanguage.sql'),
'legacy' => 52,
),
'053.feed.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('053.feed.sql'),
'legacy' => 53,
),
'054.subscribers.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('054.subscribers.sql'),
'legacy' => 54,
),
'055.add_author_to_files.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('055.add_author_to_files.sql'),
'legacy' => 55,
),
'056.slowvote.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('056.slowvote.sql'),
'legacy' => 56,
),
'057.parsecache.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('057.parsecache.sql'),
'legacy' => 57,
),
'058.missingkeys.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('058.missingkeys.sql'),
'legacy' => 58,
),
'059.engines.php' => array(
'type' => 'php',
'name' => $this->getPatchPath('059.engines.php'),
'legacy' => 59,
),
'060.phriction.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('060.phriction.sql'),
'legacy' => 60,
),
'061.phrictioncontent.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('061.phrictioncontent.sql'),
'legacy' => 61,
),
'062.phrictionmenu.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('062.phrictionmenu.sql'),
'legacy' => 62,
),
'063.pasteforks.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('063.pasteforks.sql'),
'legacy' => 63,
),
'064.subprojects.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('064.subprojects.sql'),
'legacy' => 64,
),
'065.sshkeys.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('065.sshkeys.sql'),
'legacy' => 65,
),
'066.phrictioncontent.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('066.phrictioncontent.sql'),
'legacy' => 66,
),
'067.preferences.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('067.preferences.sql'),
'legacy' => 67,
),
'068.maniphestauxiliarystorage.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('068.maniphestauxiliarystorage.sql'),
'legacy' => 68,
),
'069.heraldxscript.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('069.heraldxscript.sql'),
'legacy' => 69,
),
'070.differentialaux.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('070.differentialaux.sql'),
'legacy' => 70,
),
'071.contentsource.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('071.contentsource.sql'),
'legacy' => 71,
),
'072.blamerevert.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('072.blamerevert.sql'),
'legacy' => 72,
),
'073.reposymbols.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('073.reposymbols.sql'),
'legacy' => 73,
),
'074.affectedpath.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('074.affectedpath.sql'),
'legacy' => 74,
),
'075.revisionhash.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('075.revisionhash.sql'),
'legacy' => 75,
),
'076.indexedlanguages.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('076.indexedlanguages.sql'),
'legacy' => 76,
),
'077.originalemail.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('077.originalemail.sql'),
'legacy' => 77,
),
'078.nametoken.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('078.nametoken.sql'),
'legacy' => 78,
),
'079.nametokenindex.php' => array(
'type' => 'php',
'name' => $this->getPatchPath('079.nametokenindex.php'),
'legacy' => 79,
),
'080.filekeys.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('080.filekeys.sql'),
'legacy' => 80,
),
'081.filekeys.php' => array(
'type' => 'php',
'name' => $this->getPatchPath('081.filekeys.php'),
'legacy' => 81,
),
'082.xactionkey.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('082.xactionkey.sql'),
'legacy' => 82,
),
'083.dxviewtime.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('083.dxviewtime.sql'),
'legacy' => 83,
),
'084.pasteauthorkey.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('084.pasteauthorkey.sql'),
'legacy' => 84,
),
'085.packagecommitrelationship.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('085.packagecommitrelationship.sql'),
'legacy' => 85,
),
'086.formeraffil.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('086.formeraffil.sql'),
'legacy' => 86,
),
'087.phrictiondelete.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('087.phrictiondelete.sql'),
'legacy' => 87,
),
'088.audit.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('088.audit.sql'),
'legacy' => 88,
),
'089.projectwiki.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('089.projectwiki.sql'),
'legacy' => 89,
),
'090.forceuniqueprojectnames.php' => array(
'type' => 'php',
'name' => $this->getPatchPath('090.forceuniqueprojectnames.php'),
'legacy' => 90,
),
'091.uniqueslugkey.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('091.uniqueslugkey.sql'),
'legacy' => 91,
),
'092.dropgithubnotification.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('092.dropgithubnotification.sql'),
'legacy' => 92,
),
'093.gitremotes.php' => array(
'type' => 'php',
'name' => $this->getPatchPath('093.gitremotes.php'),
'legacy' => 93,
),
'094.phrictioncolumn.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('094.phrictioncolumn.sql'),
'legacy' => 94,
),
'095.directory.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('095.directory.sql'),
'legacy' => 95,
),
'096.filename.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('096.filename.sql'),
'legacy' => 96,
),
'097.heraldruletypes.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('097.heraldruletypes.sql'),
'legacy' => 97,
),
'098.heraldruletypemigration.php' => array(
'type' => 'php',
'name' => $this->getPatchPath('098.heraldruletypemigration.php'),
'legacy' => 98,
),
'099.drydock.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('099.drydock.sql'),
'legacy' => 99,
),
'100.projectxaction.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('100.projectxaction.sql'),
'legacy' => 100,
),
'101.heraldruleapplied.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('101.heraldruleapplied.sql'),
'legacy' => 101,
),
'102.heraldcleanup.php' => array(
'type' => 'php',
'name' => $this->getPatchPath('102.heraldcleanup.php'),
'legacy' => 102,
),
'103.heraldedithistory.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('103.heraldedithistory.sql'),
'legacy' => 103,
),
'104.searchkey.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('104.searchkey.sql'),
'legacy' => 104,
),
'105.mimetype.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('105.mimetype.sql'),
'legacy' => 105,
),
'106.chatlog.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('106.chatlog.sql'),
'legacy' => 106,
),
'107.oauthserver.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('107.oauthserver.sql'),
'legacy' => 107,
),
'108.oauthscope.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('108.oauthscope.sql'),
'legacy' => 108,
),
'109.oauthclientphidkey.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('109.oauthclientphidkey.sql'),
'legacy' => 109,
),
'110.commitaudit.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('110.commitaudit.sql'),
'legacy' => 110,
),
'111.commitauditmigration.php' => array(
'type' => 'php',
'name' => $this->getPatchPath('111.commitauditmigration.php'),
'legacy' => 111,
),
'112.oauthaccesscoderedirecturi.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('112.oauthaccesscoderedirecturi.sql'),
'legacy' => 112,
),
'113.lastreviewer.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('113.lastreviewer.sql'),
'legacy' => 113,
),
'114.auditrequest.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('114.auditrequest.sql'),
'legacy' => 114,
),
'115.prepareutf8.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('115.prepareutf8.sql'),
'legacy' => 115,
),
'116.utf8-backup-first-expect-wait.sql' => array(
'type' => 'sql',
'name' =>
$this->getPatchPath('116.utf8-backup-first-expect-wait.sql'),
'legacy' => 116,
),
'117.repositorydescription.php' => array(
'type' => 'php',
'name' => $this->getPatchPath('117.repositorydescription.php'),
'legacy' => 117,
),
'118.auditinline.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('118.auditinline.sql'),
'legacy' => 118,
),
'119.filehash.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('119.filehash.sql'),
'legacy' => 119,
),
'120.noop.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('120.noop.sql'),
'legacy' => 120,
),
'121.drydocklog.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('121.drydocklog.sql'),
'legacy' => 121,
),
'122.flag.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('122.flag.sql'),
'legacy' => 122,
),
'123.heraldrulelog.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('123.heraldrulelog.sql'),
'legacy' => 123,
),
'124.subpriority.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('124.subpriority.sql'),
'legacy' => 124,
),
'125.ipv6.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('125.ipv6.sql'),
'legacy' => 125,
),
'126.edges.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('126.edges.sql'),
'legacy' => 126,
),
'127.userkeybody.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('127.userkeybody.sql'),
'legacy' => 127,
),
'128.phabricatorcom.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('128.phabricatorcom.sql'),
'legacy' => 128,
),
'129.savedquery.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('129.savedquery.sql'),
'legacy' => 129,
),
'130.denormalrevisionquery.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('130.denormalrevisionquery.sql'),
'legacy' => 130,
),
'131.migraterevisionquery.php' => array(
'type' => 'php',
'name' => $this->getPatchPath('131.migraterevisionquery.php'),
'legacy' => 131,
),
'132.phame.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('132.phame.sql'),
'legacy' => 132,
),
'133.imagemacro.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('133.imagemacro.sql'),
'legacy' => 133,
),
'134.emptysearch.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('134.emptysearch.sql'),
'legacy' => 134,
),
'135.datecommitted.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('135.datecommitted.sql'),
'legacy' => 135,
),
'136.sex.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('136.sex.sql'),
'legacy' => 136,
),
'137.auditmetadata.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('137.auditmetadata.sql'),
'legacy' => 137,
),
'138.notification.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('138.notification.sql'),
),
'holidays.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('holidays.sql'),
),
'userstatus.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('userstatus.sql'),
),
'emailtable.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('emailtable.sql'),
),
'emailtableport.sql' => array(
'type' => 'php',
'name' => $this->getPatchPath('emailtableport.php'),
),
'emailtableremove.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('emailtableremove.sql'),
),
'phiddrop.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('phiddrop.sql'),
),
'testdatabase.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('testdatabase.sql'),
),
+ 'ldapinfo.sql' => array(
+ 'type' => 'sql',
+ 'name' => $this->getPatchPath('ldapinfo.sql'),
+ ),
);
}
}