Page MenuHomestyx hydra

No OneTemporary

diff --git a/scripts/user/add_user.php b/scripts/user/add_user.php
new file mode 100755
index 0000000000..af00b6b6b4
--- /dev/null
+++ b/scripts/user/add_user.php
@@ -0,0 +1,69 @@
+#!/usr/bin/env php
+<?php
+
+/*
+ * Copyright 2012 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+$root = dirname(dirname(dirname(__FILE__)));
+require_once $root.'/scripts/__init_script__.php';
+
+phutil_require_module('phutil', 'console');
+phutil_require_module('phutil', 'future/exec');
+
+if ($argc !== 5) {
+ echo "usage: add_user.php <username> <email> <realname> <admin_user>\n";
+ exit(1);
+}
+
+$username = $argv[1];
+$email = $argv[2];
+$realname = $argv[3];
+$admin = $argv[4];
+
+$admin = id(new PhabricatorUser())->loadOneWhere(
+ 'username = %s',
+ $argv[4]);
+if (!$admin) {
+ throw new Exception(
+ "Admin user must be the username of a valid Phabricator account, used ".
+ "to send the new user a welcome email.");
+}
+
+$existing_user = id(new PhabricatorUser())->loadOneWhere(
+ 'username = %s',
+ $username);
+if ($existing_user) {
+ throw new Exception(
+ "There is already a user with the username '{$username}'!");
+}
+
+$existing_user = id(new PhabricatorUser())->loadOneWhere(
+ 'email = %s',
+ $email);
+if ($existing_user) {
+ throw new Exception(
+ "There is already a user with the email '{$email}'!");
+}
+
+$user = new PhabricatorUser();
+$user->setUsername($username);
+$user->setEmail($email);
+$user->setRealname($realname);
+$user->save();
+
+$user->sendWelcomeEmail($admin);
+
+echo "Created user '{$username}' (realname='{$realname}', email='{$email}').\n";
diff --git a/src/applications/people/controller/edit/PhabricatorPeopleEditController.php b/src/applications/people/controller/edit/PhabricatorPeopleEditController.php
index 3642677633..1a940d92a7 100644
--- a/src/applications/people/controller/edit/PhabricatorPeopleEditController.php
+++ b/src/applications/people/controller/edit/PhabricatorPeopleEditController.php
@@ -1,480 +1,442 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
class PhabricatorPeopleEditController extends PhabricatorPeopleController {
public function shouldRequireAdmin() {
return true;
}
private $id;
private $view;
public function willProcessRequest(array $data) {
$this->id = idx($data, 'id');
$this->view = idx($data, 'view');
}
public function processRequest() {
$request = $this->getRequest();
$admin = $request->getUser();
if ($this->id) {
$user = id(new PhabricatorUser())->load($this->id);
if (!$user) {
return new Aphront404Response();
}
} else {
$user = new PhabricatorUser();
}
$views = array(
'basic' => 'Basic Information',
'role' => 'Edit Role',
'cert' => 'Conduit Certificate',
);
if (!$user->getID()) {
$view = 'basic';
} else if (isset($views[$this->view])) {
$view = $this->view;
} else {
$view = 'basic';
}
$content = array();
if ($request->getStr('saved')) {
$notice = new AphrontErrorView();
$notice->setSeverity(AphrontErrorView::SEVERITY_NOTICE);
$notice->setTitle('Changes Saved');
$notice->appendChild('<p>Your changes were saved.</p>');
$content[] = $notice;
}
switch ($view) {
case 'basic':
$response = $this->processBasicRequest($user);
break;
case 'role':
$response = $this->processRoleRequest($user);
break;
case 'cert':
$response = $this->processCertificateRequest($user);
break;
}
if ($response instanceof AphrontResponse) {
return $response;
}
$content[] = $response;
if ($user->getID()) {
$side_nav = new AphrontSideNavView();
$side_nav->appendChild($content);
foreach ($views as $key => $name) {
$side_nav->addNavItem(
phutil_render_tag(
'a',
array(
'href' => '/people/edit/'.$user->getID().'/'.$key.'/',
'class' => ($key == $view)
? 'aphront-side-nav-selected'
: null,
),
phutil_escape_html($name)));
}
$content = $side_nav;
}
return $this->buildStandardPageResponse(
$content,
array(
'title' => 'Edit User',
));
}
private function processBasicRequest(PhabricatorUser $user) {
$request = $this->getRequest();
$admin = $request->getUser();
$e_username = true;
$e_realname = true;
$e_email = true;
$errors = array();
$welcome_checked = true;
- $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
$request = $this->getRequest();
if ($request->isFormPost()) {
$welcome_checked = $request->getInt('welcome');
if (!$user->getID()) {
$user->setUsername($request->getStr('username'));
$user->setEmail($request->getStr('email'));
if ($request->getStr('role') == 'agent') {
$user->setIsSystemAgent(true);
}
}
$user->setRealName($request->getStr('realname'));
if (!strlen($user->getUsername())) {
$errors[] = "Username is required.";
$e_username = 'Required';
} else if (!preg_match('/^[a-z0-9]+$/', $user->getUsername())) {
$errors[] = "Username must consist of only numbers and letters.";
$e_username = 'Invalid';
} else {
$e_username = null;
}
if (!strlen($user->getRealName())) {
$errors[] = 'Real name is required.';
$e_realname = 'Required';
} else {
$e_realname = null;
}
if (!strlen($user->getEmail())) {
$errors[] = 'Email is required.';
$e_email = 'Required';
} else {
$e_email = null;
}
if (!$errors) {
try {
$is_new = !$user->getID();
$user->save();
if ($is_new) {
$log = PhabricatorUserLog::newLog(
$admin,
$user,
PhabricatorUserLog::ACTION_CREATE);
$log->save();
if ($welcome_checked) {
- $admin_username = $admin->getUserName();
- $admin_realname = $admin->getRealName();
- $user_username = $user->getUserName();
-
- $base_uri = PhabricatorEnv::getProductionURI('/');
-
- $uri = $user->getEmailLoginURI();
- $body = <<<EOBODY
-Welcome to Phabricator!
-
-{$admin_username} ({$admin_realname}) has created an account for you.
-
- Username: {$user_username}
-
-To login to Phabricator, follow this link and set a password:
-
- {$uri}
-
-After you have set a password, you can login in the future by going here:
-
- {$base_uri}
-
-EOBODY;
-
- if (!$is_serious) {
- $body .= <<<EOBODY
-Love,
-Phabricator
-
-EOBODY;
- }
-
- $mail = id(new PhabricatorMetaMTAMail())
- ->addTos(array($user->getPHID()))
- ->setSubject('[Phabricator] Welcome to Phabricator')
- ->setBody($body)
- ->setFrom($admin->getPHID())
- ->saveAndSend();
+ $user->sendWelcomeEmail($admin);
}
}
$response = id(new AphrontRedirectResponse())
->setURI('/people/edit/'.$user->getID().'/?saved=true');
return $response;
} catch (AphrontQueryDuplicateKeyException $ex) {
$errors[] = 'Username and email must be unique.';
$same_username = id(new PhabricatorUser())
->loadOneWhere('username = %s', $user->getUsername());
$same_email = id(new PhabricatorUser())
->loadOneWhere('email = %s', $user->getEmail());
if ($same_username) {
$e_username = 'Duplicate';
}
if ($same_email) {
$e_email = 'Duplicate';
}
}
}
}
$error_view = null;
if ($errors) {
$error_view = id(new AphrontErrorView())
->setTitle('Form Errors')
->setErrors($errors);
}
$form = new AphrontFormView();
$form->setUser($admin);
if ($user->getID()) {
$form->setAction('/people/edit/'.$user->getID().'/');
} else {
$form->setAction('/people/edit/');
}
if ($user->getID()) {
$is_immutable = true;
} else {
$is_immutable = false;
}
$form
->appendChild(
id(new AphrontFormTextControl())
->setLabel('Username')
->setName('username')
->setValue($user->getUsername())
->setError($e_username)
->setDisabled($is_immutable)
->setCaption('Usernames are permanent and can not be changed later!'))
->appendChild(
id(new AphrontFormTextControl())
->setLabel('Real Name')
->setName('realname')
->setValue($user->getRealName())
->setError($e_realname))
->appendChild(
id(new AphrontFormTextControl())
->setLabel('Email')
->setName('email')
->setDisabled($is_immutable)
->setValue($user->getEmail())
->setError($e_email));
if (!$user->getID()) {
$form
->appendChild(
id(new AphrontFormSelectControl())
->setLabel('Role')
->setName('role')
->setValue('user')
->setOptions(
array(
'user' => 'Normal User',
'agent' => 'System Agent',
))
->setCaption(
'You can create a "system agent" account for bots, scripts, '.
'etc.'))
->appendChild(
id(new AphrontFormCheckboxControl())
->addCheckbox(
'welcome',
1,
'Send "Welcome to Phabricator" email.',
$welcome_checked));
} else {
$form->appendChild(
id(new AphrontFormStaticControl())
->setLabel('Role')
->setValue(
$user->getIsSystemAgent()
? 'System Agent'
: 'Normal User'));
}
$form
->appendChild(
id(new AphrontFormSubmitControl())
->setValue('Save'));
$panel = new AphrontPanelView();
if ($user->getID()) {
$panel->setHeader('Edit User');
} else {
$panel->setHeader('Create New User');
}
$panel->appendChild($form);
$panel->setWidth(AphrontPanelView::WIDTH_FORM);
return array($error_view, $panel);
}
private function processRoleRequest(PhabricatorUser $user) {
$request = $this->getRequest();
$admin = $request->getUser();
$is_self = ($user->getID() == $admin->getID());
$errors = array();
if ($request->isFormPost()) {
$log_template = PhabricatorUserLog::newLog(
$admin,
$user,
null);
$logs = array();
if ($is_self) {
$errors[] = "You can not edit your own role.";
} else {
$new_admin = (bool)$request->getBool('is_admin');
$old_admin = (bool)$user->getIsAdmin();
if ($new_admin != $old_admin) {
$log = clone $log_template;
$log->setAction(PhabricatorUserLog::ACTION_ADMIN);
$log->setOldValue($old_admin);
$log->setNewValue($new_admin);
$user->setIsAdmin($new_admin);
$logs[] = $log;
}
$new_disabled = (bool)$request->getBool('is_disabled');
$old_disabled = (bool)$user->getIsDisabled();
if ($new_disabled != $old_disabled) {
$log = clone $log_template;
$log->setAction(PhabricatorUserLog::ACTION_DISABLE);
$log->setOldValue($old_disabled);
$log->setNewValue($new_disabled);
$user->setIsDisabled($new_disabled);
$logs[] = $log;
}
}
if (!$errors) {
$user->save();
foreach ($logs as $log) {
$log->save();
}
return id(new AphrontRedirectResponse())
->setURI($request->getRequestURI()->alter('saved', 'true'));
}
}
$error_view = null;
if ($errors) {
$error_view = id(new AphrontErrorView())
->setTitle('Form Errors')
->setErrors($errors);
}
$form = id(new AphrontFormView())
->setUser($admin)
->setAction($request->getRequestURI()->alter('saved', null));
if ($is_self) {
$form->appendChild(
'<p class="aphront-form-instructions">NOTE: You can not edit your own '.
'role.</p>');
}
$form
->appendChild(
id(new AphrontFormCheckboxControl())
->addCheckbox(
'is_admin',
1,
'Admin: wields absolute power.',
$user->getIsAdmin())
->setDisabled($is_self))
->appendChild(
id(new AphrontFormCheckboxControl())
->addCheckbox(
'is_disabled',
1,
'Disabled: can not login.',
$user->getIsDisabled())
->setDisabled($is_self));
if (!$is_self) {
$form
->appendChild(
id(new AphrontFormSubmitControl())
->setValue('Edit Role'));
}
$panel = new AphrontPanelView();
$panel->setHeader('Edit Role');
$panel->setWidth(AphrontPanelView::WIDTH_FORM);
$panel->appendChild($form);
return array($error_view, $panel);
}
private function processCertificateRequest($user) {
$request = $this->getRequest();
$admin = $request->getUser();
$form = new AphrontFormView();
$form
->setUser($admin)
->setAction($request->getRequestURI())
->appendChild(
'<p class="aphront-form-instructions">You can use this certificate '.
'to write scripts or bots which interface with Phabricator over '.
'Conduit.</p>');
if ($user->getIsSystemAgent()) {
$form
->appendChild(
id(new AphrontFormTextControl())
->setLabel('Username')
->setValue($user->getUsername()))
->appendChild(
id(new AphrontFormTextAreaControl())
->setLabel('Certificate')
->setValue($user->getConduitCertificate()));
} else {
$form->appendChild(
id(new AphrontFormStaticControl())
->setLabel('Certificate')
->setValue(
'You may only view the certificates of System Agents.'));
}
$panel = new AphrontPanelView();
$panel->setHeader('Conduit Certificate');
$panel->setWidth(AphrontPanelView::WIDTH_FORM);
$panel->appendChild($form);
return array($panel);
}
}
diff --git a/src/applications/people/controller/edit/__init__.php b/src/applications/people/controller/edit/__init__.php
index b64ccccd5c..1cb7290bc0 100644
--- a/src/applications/people/controller/edit/__init__.php
+++ b/src/applications/people/controller/edit/__init__.php
@@ -1,31 +1,29 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'aphront/response/404');
phutil_require_module('phabricator', 'aphront/response/redirect');
-phutil_require_module('phabricator', 'applications/metamta/storage/mail');
phutil_require_module('phabricator', 'applications/people/controller/base');
phutil_require_module('phabricator', 'applications/people/storage/log');
phutil_require_module('phabricator', 'applications/people/storage/user');
-phutil_require_module('phabricator', 'infrastructure/env');
phutil_require_module('phabricator', 'view/form/base');
phutil_require_module('phabricator', 'view/form/control/checkbox');
phutil_require_module('phabricator', 'view/form/control/select');
phutil_require_module('phabricator', 'view/form/control/static');
phutil_require_module('phabricator', 'view/form/control/submit');
phutil_require_module('phabricator', 'view/form/control/text');
phutil_require_module('phabricator', 'view/form/control/textarea');
phutil_require_module('phabricator', 'view/form/error');
phutil_require_module('phabricator', 'view/layout/panel');
phutil_require_module('phabricator', 'view/layout/sidenav');
phutil_require_module('phutil', 'markup');
phutil_require_module('phutil', 'utils');
phutil_require_source('PhabricatorPeopleEditController.php');
diff --git a/src/applications/people/storage/user/PhabricatorUser.php b/src/applications/people/storage/user/PhabricatorUser.php
index bfc50ecede..26b3d3dd0d 100644
--- a/src/applications/people/storage/user/PhabricatorUser.php
+++ b/src/applications/people/storage/user/PhabricatorUser.php
@@ -1,460 +1,503 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
class PhabricatorUser extends PhabricatorUserDAO {
const SESSION_TABLE = 'phabricator_session';
const NAMETOKEN_TABLE = 'user_nametoken';
protected $phid;
protected $userName;
protected $realName;
protected $email;
protected $passwordSalt;
protected $passwordHash;
protected $profileImagePHID;
protected $timezoneIdentifier = '';
protected $consoleEnabled = 0;
protected $consoleVisible = 0;
protected $consoleTab = '';
protected $conduitCertificate;
protected $isSystemAgent = 0;
protected $isAdmin = 0;
protected $isDisabled = 0;
private $preferences = null;
protected function readField($field) {
switch ($field) {
case 'profileImagePHID':
return nonempty(
$this->profileImagePHID,
PhabricatorEnv::getEnvConfig('user.default-profile-image-phid'));
case 'timezoneIdentifier':
// If the user hasn't set one, guess the server's time.
return nonempty(
$this->timezoneIdentifier,
date_default_timezone_get());
// Make sure these return booleans.
case 'isAdmin':
return (bool)$this->isAdmin;
case 'isDisabled':
return (bool)$this->isDisabled;
case 'isSystemAgent':
return (bool)$this->isSystemAgent;
default:
return parent::readField($field);
}
}
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_PARTIAL_OBJECTS => true,
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhabricatorPHIDConstants::PHID_TYPE_USER);
}
public function setPassword($password) {
if (!$this->getPHID()) {
throw new Exception(
"You can not set a password for an unsaved user because their PHID ".
"is a salt component in the password hash.");
}
if (!strlen($password)) {
$this->setPasswordHash('');
} else {
$this->setPasswordSalt(md5(mt_rand()));
$hash = $this->hashPassword($password);
$this->setPasswordHash($hash);
}
return $this;
}
public function isLoggedIn() {
return !($this->getPHID() === null);
}
public function save() {
if (!$this->getConduitCertificate()) {
$this->setConduitCertificate($this->generateConduitCertificate());
}
$result = parent::save();
$this->updateNameTokens();
PhabricatorSearchUserIndexer::indexUser($this);
return $result;
}
private function generateConduitCertificate() {
return Filesystem::readRandomCharacters(255);
}
public function comparePassword($password) {
if (!strlen($password)) {
return false;
}
if (!strlen($this->getPasswordHash())) {
return false;
}
$password = $this->hashPassword($password);
return ($password === $this->getPasswordHash());
}
private function hashPassword($password) {
$password = $this->getUsername().
$password.
$this->getPHID().
$this->getPasswordSalt();
for ($ii = 0; $ii < 1000; $ii++) {
$password = md5($password);
}
return $password;
}
const CSRF_CYCLE_FREQUENCY = 3600;
const CSRF_TOKEN_LENGTH = 16;
const EMAIL_CYCLE_FREQUENCY = 86400;
const EMAIL_TOKEN_LENGTH = 24;
public function getCSRFToken($offset = 0) {
return $this->generateToken(
time() + (self::CSRF_CYCLE_FREQUENCY * $offset),
self::CSRF_CYCLE_FREQUENCY,
PhabricatorEnv::getEnvConfig('phabricator.csrf-key'),
self::CSRF_TOKEN_LENGTH);
}
public function validateCSRFToken($token) {
// When the user posts a form, we check that it contains a valid CSRF token.
// Tokens cycle each hour (every CSRF_CYLCE_FREQUENCY seconds) and we accept
// either the current token, the next token (users can submit a "future"
// token if you have two web frontends that have some clock skew) or any of
// the last 6 tokens. This means that pages are valid for up to 7 hours.
// There is also some Javascript which periodically refreshes the CSRF
// tokens on each page, so theoretically pages should be valid indefinitely.
// However, this code may fail to run (if the user loses their internet
// connection, or there's a JS problem, or they don't have JS enabled).
// Choosing the size of the window in which we accept old CSRF tokens is
// an issue of balancing concerns between security and usability. We could
// choose a very narrow (e.g., 1-hour) window to reduce vulnerability to
// attacks using captured CSRF tokens, but it's also more likely that real
// users will be affected by this, e.g. if they close their laptop for an
// hour, open it back up, and try to submit a form before the CSRF refresh
// can kick in. Since the user experience of submitting a form with expired
// CSRF is often quite bad (you basically lose data, or it's a big pain to
// recover at least) and I believe we gain little additional protection
// by keeping the window very short (the overwhelming value here is in
// preventing blind attacks, and most attacks which can capture CSRF tokens
// can also just capture authentication information [sniffing networks]
// or act as the user [xss]) the 7 hour default seems like a reasonable
// balance. Other major platforms have much longer CSRF token lifetimes,
// like Rails (session duration) and Django (forever), which suggests this
// is a reasonable analysis.
$csrf_window = 6;
for ($ii = -$csrf_window; $ii <= 1; $ii++) {
$valid = $this->getCSRFToken($ii);
if ($token == $valid) {
return true;
}
}
return false;
}
private function generateToken($epoch, $frequency, $key, $len) {
$time_block = floor($epoch / $frequency);
$vec = $this->getPHID().$this->getPasswordHash().$key.$time_block;
return substr(PhabricatorHash::digest($vec), 0, $len);
}
/**
* Issue a new session key to this user. Phabricator supports different
* types of sessions (like "web" and "conduit") and each session type may
* have multiple concurrent sessions (this allows a user to be logged in on
* multiple browsers at the same time, for instance).
*
* Note that this method is transport-agnostic and does not set cookies or
* issue other types of tokens, it ONLY generates a new session key.
*
* You can configure the maximum number of concurrent sessions for various
* session types in the Phabricator configuration.
*
* @param string Session type, like "web".
* @return string Newly generated session key.
*/
public function establishSession($session_type) {
$conn_w = $this->establishConnection('w');
if (strpos($session_type, '-') !== false) {
throw new Exception("Session type must not contain hyphen ('-')!");
}
// We allow multiple sessions of the same type, so when a caller requests
// a new session of type "web", we give them the first available session in
// "web-1", "web-2", ..., "web-N", up to some configurable limit. If none
// of these sessions is available, we overwrite the oldest session and
// reissue a new one in its place.
$session_limit = 1;
switch ($session_type) {
case 'web':
$session_limit = PhabricatorEnv::getEnvConfig('auth.sessions.web');
break;
case 'conduit':
$session_limit = PhabricatorEnv::getEnvConfig('auth.sessions.conduit');
break;
default:
throw new Exception("Unknown session type '{$session_type}'!");
}
$session_limit = (int)$session_limit;
if ($session_limit <= 0) {
throw new Exception(
"Session limit for '{$session_type}' must be at least 1!");
}
// NOTE: Session establishment is sensitive to race conditions, as when
// piping `arc` to `arc`:
//
// arc export ... | arc paste ...
//
// To avoid this, we overwrite an old session only if it hasn't been
// re-established since we read it.
// Consume entropy to generate a new session key, forestalling the eventual
// heat death of the universe.
$session_key = Filesystem::readRandomCharacters(40);
// Load all the currently active sessions.
$sessions = queryfx_all(
$conn_w,
'SELECT type, sessionKey, sessionStart FROM %T
WHERE userPHID = %s AND type LIKE %>',
PhabricatorUser::SESSION_TABLE,
$this->getPHID(),
$session_type.'-');
$sessions = ipull($sessions, null, 'type');
$sessions = isort($sessions, 'sessionStart');
$existing_sessions = array_keys($sessions);
$retries = 0;
while (true) {
// Choose which 'type' we'll actually establish, i.e. what number we're
// going to append to the basic session type. To do this, just check all
// the numbers sequentially until we find an available session.
$establish_type = null;
for ($ii = 1; $ii <= $session_limit; $ii++) {
$try_type = $session_type.'-'.$ii;
if (!in_array($try_type, $existing_sessions)) {
$establish_type = $try_type;
$expect_key = $session_key;
$existing_sessions[] = $try_type;
// Ensure the row exists so we can issue an update below. We don't
// care if we race here or not.
queryfx(
$conn_w,
'INSERT IGNORE INTO %T (userPHID, type, sessionKey, sessionStart)
VALUES (%s, %s, %s, 0)',
self::SESSION_TABLE,
$this->getPHID(),
$establish_type,
$session_key);
break;
}
}
// If we didn't find an available session, choose the oldest session and
// overwrite it.
if (!$establish_type) {
$oldest = reset($sessions);
$establish_type = $oldest['type'];
$expect_key = $oldest['sessionKey'];
}
// UNGUARDED WRITES: Logging-in users don't have CSRF stuff yet.
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
// This is so that we'll only overwrite the session if it hasn't been
// refreshed since we read it. If it has, the session key will be
// different and we know we're racing other processes. Whichever one
// won gets the session, we go back and try again.
queryfx(
$conn_w,
'UPDATE %T SET sessionKey = %s, sessionStart = UNIX_TIMESTAMP()
WHERE userPHID = %s AND type = %s AND sessionKey = %s',
self::SESSION_TABLE,
$session_key,
$this->getPHID(),
$establish_type,
$expect_key);
unset($unguarded);
if ($conn_w->getAffectedRows()) {
// The update worked, so the session is valid.
break;
} else {
// We know this just got grabbed, so don't try it again.
unset($sessions[$establish_type]);
}
if (++$retries > $session_limit) {
throw new Exception("Failed to establish a session!");
}
}
$log = PhabricatorUserLog::newLog(
$this,
$this,
PhabricatorUserLog::ACTION_LOGIN);
$log->setDetails(
array(
'session_type' => $session_type,
'session_issued' => $establish_type,
));
$log->setSession($session_key);
$log->save();
return $session_key;
}
public function destroySession($session_key) {
$conn_w = $this->establishConnection('w');
queryfx(
$conn_w,
'DELETE FROM %T WHERE userPHID = %s AND sessionKey = %s',
self::SESSION_TABLE,
$this->getPHID(),
$session_key);
}
private function generateEmailToken($offset = 0) {
return $this->generateToken(
time() + ($offset * self::EMAIL_CYCLE_FREQUENCY),
self::EMAIL_CYCLE_FREQUENCY,
PhabricatorEnv::getEnvConfig('phabricator.csrf-key').$this->getEmail(),
self::EMAIL_TOKEN_LENGTH);
}
public function validateEmailToken($token) {
for ($ii = -1; $ii <= 1; $ii++) {
$valid = $this->generateEmailToken($ii);
if ($token == $valid) {
return true;
}
}
return false;
}
public function getEmailLoginURI() {
$token = $this->generateEmailToken();
$uri = PhabricatorEnv::getProductionURI('/login/etoken/'.$token.'/');
$uri = new PhutilURI($uri);
return $uri->alter('email', $this->getEmail());
}
public function loadPreferences() {
if ($this->preferences) {
return $this->preferences;
}
$preferences = id(new PhabricatorUserPreferences())->loadOneWhere(
'userPHID = %s',
$this->getPHID());
if (!$preferences) {
$preferences = new PhabricatorUserPreferences();
$preferences->setUserPHID($this->getPHID());
$default_dict = array(
PhabricatorUserPreferences::PREFERENCE_TITLES => 'glyph',
PhabricatorUserPreferences::PREFERENCE_MONOSPACED => '');
$preferences->setPreferences($default_dict);
}
$this->preferences = $preferences;
return $preferences;
}
private static function tokenizeName($name) {
if (function_exists('mb_strtolower')) {
$name = mb_strtolower($name, 'UTF-8');
} else {
$name = strtolower($name);
}
$name = trim($name);
if (!strlen($name)) {
return array();
}
return preg_split('/\s+/', $name);
}
/**
* Populate the nametoken table, which used to fetch typeahead results. When
* a user types "linc", we want to match "Abraham Lincoln" from on-demand
* typeahead sources. To do this, we need a separate table of name fragments.
*/
public function updateNameTokens() {
$tokens = array_merge(
self::tokenizeName($this->getRealName()),
self::tokenizeName($this->getUserName()));
$tokens = array_unique($tokens);
$table = self::NAMETOKEN_TABLE;
$conn_w = $this->establishConnection('w');
$sql = array();
foreach ($tokens as $token) {
$sql[] = qsprintf(
$conn_w,
'(%d, %s)',
$this->getID(),
$token);
}
queryfx(
$conn_w,
'DELETE FROM %T WHERE userID = %d',
$table,
$this->getID());
if ($sql) {
queryfx(
$conn_w,
'INSERT INTO %T (userID, token) VALUES %Q',
$table,
implode(', ', $sql));
}
}
+ public function sendWelcomeEmail(PhabricatorUser $admin) {
+ $admin_username = $admin->getUserName();
+ $admin_realname = $admin->getRealName();
+ $user_username = $this->getUserName();
+ $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
+
+ $base_uri = PhabricatorEnv::getProductionURI('/');
+
+ $uri = $this->getEmailLoginURI();
+ $body = <<<EOBODY
+Welcome to Phabricator!
+
+{$admin_username} ({$admin_realname}) has created an account for you.
+
+ Username: {$user_username}
+
+To login to Phabricator, follow this link and set a password:
+
+ {$uri}
+
+After you have set a password, you can login in the future by going here:
+
+ {$base_uri}
+
+EOBODY;
+
+ if (!$is_serious) {
+ $body .= <<<EOBODY
+
+Love,
+Phabricator
+
+EOBODY;
+ }
+
+ $mail = id(new PhabricatorMetaMTAMail())
+ ->addTos(array($this->getPHID()))
+ ->setSubject('[Phabricator] Welcome to Phabricator')
+ ->setBody($body)
+ ->setFrom($admin->getPHID())
+ ->saveAndSend();
+ }
+
}
diff --git a/src/applications/people/storage/user/__init__.php b/src/applications/people/storage/user/__init__.php
index 8cdfe8db6e..29e3e21402 100644
--- a/src/applications/people/storage/user/__init__.php
+++ b/src/applications/people/storage/user/__init__.php
@@ -1,26 +1,27 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'aphront/writeguard');
+phutil_require_module('phabricator', 'applications/metamta/storage/mail');
phutil_require_module('phabricator', 'applications/people/storage/base');
phutil_require_module('phabricator', 'applications/people/storage/log');
phutil_require_module('phabricator', 'applications/people/storage/preferences');
phutil_require_module('phabricator', 'applications/phid/constants');
phutil_require_module('phabricator', 'applications/phid/storage/phid');
phutil_require_module('phabricator', 'applications/search/index/indexer/user');
phutil_require_module('phabricator', 'infrastructure/env');
phutil_require_module('phabricator', 'infrastructure/util/hash');
phutil_require_module('phabricator', 'storage/qsprintf');
phutil_require_module('phabricator', 'storage/queryfx');
phutil_require_module('phutil', 'filesystem');
phutil_require_module('phutil', 'parser/uri');
phutil_require_module('phutil', 'utils');
phutil_require_source('PhabricatorUser.php');
diff --git a/src/docs/configuration/configuring_accounts_and_registration.diviner b/src/docs/configuration/configuring_accounts_and_registration.diviner
index a6b4096b2e..d2d933b861 100644
--- a/src/docs/configuration/configuring_accounts_and_registration.diviner
+++ b/src/docs/configuration/configuring_accounts_and_registration.diviner
@@ -1,144 +1,159 @@
@title Configuring Accounts and Registration
@group config
Describes how to configure user access to Phabricator.
= Overview =
Phabricator supports a number of login systems, like traditional
username/password, Facebook OAuth, GitHub OAuth, and Google OAuth. You can
enable or disable these systems to configure who can register for and access
your install, and how users with existing accounts can login.
By default, only username/password auth is enabled, and there are no valid
accounts. Start by creating a new account with the
##phabricator/bin/accountadmin## script.
= Using accountadmin =
##accountadmin## is a user-friendly command line interface for creating and
editing accounts. To use ##accountadmin##, just run the script:
$ ./phabricator/bin/accountadmin
Enter a username to create a new account or edit an existing account.
Enter a username:
This will walk you through the process of creating an initial user account.
Once you've created an account, you can login with it and use the web console
to create and manage accounts more easily (provided you make your first account
an administrator).
You can use this script later to create or edit accounts if you, for example,
accidentally remove your admin flag.
= Managing Accounts with the Web Console =
To manage accounts from the web, login as an administrator account and go to
##/people/## or click "People" on the homepage. Provided you're an admin,
you'll see options to create or edit accounts.
+= Managing Accounts from the Command Line =
+
+You can use ##scripts/user/add_user.php## to batch create accounts. Run it
+like:
+
+ $ ./add_user.php <username> <email> <realname> <admin>
+
+For example:
+
+ $ ./add_user.php alincoln alincoln@logcabin.com 'Abraham Lincoln' tjefferson
+
+This will create a new ##alincoln## user and send them a "Welcome to
+Phabricator" email from ##tjefferson## with instructions on how to log in and
+set a password.
+
= Configuring Facebook OAuth =
You can configure Facebook OAuth to allow login, login and registration, or
nothing (the default). If registration is not allowed, users must have an
existing account in order to link a Facebook account to it, but can use
Facebook to login once the accounts are linked.
To configure Facebook OAuth, create a new Facebook Application:
https://www.facebook.com/developers/createapp.php
You should set these things in your application:
- **Site URL**: Set this to your full domain with protocol, like
"##https://phabricator.example.com/##".
- **Site Domain**: Set this to the entire domain, like ##example.com##. You
might be able to get away with including the subdomain if you want to
scope more tightly.
Once that is set up, edit your Phabricator configuration and set these keys:
- **facebook.auth-enabled**: set this to ##true##.
- **facebook.application-id**: set to your Facebook application's ID. Make
sure you set this as a string.
- **facebook.application-secret**: set to your Facebook application's
secret key.
- **facebook.registration-enabled**: set this to ##true## to let users
register for your install with a Facebook account (this is a very open
setting) or ##false## to prevent users from registering with Facebook.
- **facebook.auth-permanent**: you can set this to prevent account unlinking.
It is unlikely you want to prevent it, but Facebook's internal install uses
this option since Facebook uses Facebook as its only auth mechanism.
= Configuring GitHub OAuth =
You can configure GitHub OAuth to allow login, login and registration, or
nothing (the default).
To configure GitHub OAuth, create a new GitHub Application:
https://github.com/account/applications/new
You should set these things in your application:
- **URL**: Set this to the full domain with protocol, like
"##https://phabricator.example.com/##".
- **Callback URL**: Set this to your domain plus "##/oauth/github/login/##",
like "##https://phabricator.example.com/oauth/github/login/##".
Once you've created an application, edit your Phabricator configuration and
set these keys:
- **github.auth-enabled**: set this to ##true##.
- **github.application-id**: set this to your application/client ID.
- **github.application-secret**: set this to your application secret.
- **github.registration-enabled**: set to ##true## to let users register with
just GitHub credentials (this is a very open setting) or ##false## to
prevent users from registering. If set to ##false##, users may still link
existing accounts and use GitHub to login, they just can't create new
accounts.
- **github.auth-permanent**: set to ##true## to prevent unlinking Phabricator
accounts from GitHub accounts.
Note that you can see a list of your GitHub applications here, although it's not
immediately clear how to get there via the UI:
https://github.com/account/applications/
= Configuring Google OAuth =
You can configure Google OAuth to allow login, login and registration, or
nothing (the default).
To configure Google OAuth, create a new Google "API Project":
https://code.google.com/apis/console/
You don't need to enable any **Services**, just go to **API Access**, click
**"Create an OAuth 2.0 client ID..."**, and configure these settings:
- Click **More Options** next to **Authorized Redirect APIs** and add the
full domain (with protocol) plus ##/oauth/google/login/## to the list.
For example, ##https://phabricator.example.com/oauth/google/login/##
- Click **Create Client ID**.
Once you've created a client ID, edit your Phabricator configuration and set
these keys:
- **google.auth-enabled**: set this to ##true##.
- **google.application-id**: set this to your Client ID (from above).
- **google.application-secret**: set this to your Client Secret (from above).
- **google.registration-enabled**: set this to ##true## to let users register
with just Google credentials (this is a very open setting) or ##false## to
prevent users from registering. If set to ##false##, users may still link
existing accounts and use Google to login, they jus can't create new
accounts.
- **google.auth-permanent**: set this to ##true## to prevent unlinking
Phabricator accounts from Google accounts.
= Next Steps =
Continue by:
- returning to the @{article:Configuration Guide}.

File Metadata

Mime Type
text/x-diff
Expires
Fri, Nov 14, 6:35 AM (1 d, 7 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
336835
Default Alt Text
(41 KB)

Event Timeline