Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2894777
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Advanced/Developer...
View Handle
View Hovercard
Size
23 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/resources/ircbot/example_config.json b/resources/ircbot/example_config.json
index e909689e13..a310b74bb4 100644
--- a/resources/ircbot/example_config.json
+++ b/resources/ircbot/example_config.json
@@ -1,11 +1,15 @@
{
"server" : "irc.freenode.net",
"port" : 6667,
"nick" : "phabot",
"join" : [
"#phabot-test"
],
"handlers" : [
"PhabricatorIRCProtocolHandler"
- ]
+ ],
+
+ "conduit.uri" : null,
+ "conduit.user" : null,
+ "conduit.cert" : null
}
diff --git a/src/applications/people/controller/edit/PhabricatorPeopleEditController.php b/src/applications/people/controller/edit/PhabricatorPeopleEditController.php
index 97d4290ebe..fba33053cb 100644
--- a/src/applications/people/controller/edit/PhabricatorPeopleEditController.php
+++ b/src/applications/people/controller/edit/PhabricatorPeopleEditController.php
@@ -1,379 +1,426 @@
<?php
/*
* Copyright 2011 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',
'password' => 'Reset Password',
'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('Changed Saved');
$notice->appendChild('<p>Your changes were saved.</p>');
$content[] = $notice;
}
switch ($view) {
case 'basic':
$response = $this->processBasicRequest($user);
break;
case 'password':
$response = $this->processPasswordRequest($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();
$request = $this->getRequest();
if ($request->isFormPost()) {
if (!$user->getID()) {
$user->setUsername($request->getStr('username'));
}
$user->setRealName($request->getStr('realname'));
$user->setEmail($request->getStr('email'));
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 {
$user->save();
$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')
->setValue($user->getEmail())
->setError($e_email))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue('Save')
->addCancelButton('/people/'));
$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 processPasswordRequest(PhabricatorUser $user) {
$request = $this->getRequest();
$admin = $request->getUser();
$e_password = true;
$errors = array();
if ($request->isFormPost()) {
if (strlen($request->getStr('password'))) {
$user->setPassword($request->getStr('password'));
$e_password = null;
} else {
$errors[] = 'Password is required.';
$e_password = 'Required';
}
if (!$errors) {
$user->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))
->appendChild(
'<p class="aphront-form-instructions">Submitting this form will '.
'change this user\'s password. They will no longer be able to login '.
'with their old password.</p>')
->appendChild(
id(new AphrontFormTextControl())
->setLabel('New Password')
->setName('password')
->setError($e_password))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue('Reset Password'));
$panel = new AphrontPanelView();
$panel->setHeader('Reset Password');
$panel->setWidth(AphrontPanelView::WIDTH_FORM);
$panel->appendChild($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()) {
if ($is_self) {
$errors[] = "You can not edit your own role.";
} else {
$user->setIsAdmin($request->getInt('is_admin'));
$user->setIsDisabled($request->getInt('is_disabled'));
$user->setIsSystemAgent($request->getInt('is_agent'));
}
if (!$errors) {
$user->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))
->appendChild(
id(new AphrontFormCheckboxControl())
->addCheckbox(
'is_agent',
1,
'Agent: system agent (robot).',
$user->getIsSystemAgent())
->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 for System Agents. Mark '.
+ 'this account as a "system agent" in the "Edit Role" tab to '.
+ 'view the corresponding certificate.'));
+ }
+
+ $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 b689acf53d..742bbef230 100644
--- a/src/applications/people/controller/edit/__init__.php
+++ b/src/applications/people/controller/edit/__init__.php
@@ -1,25 +1,27 @@
<?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/people/controller/base');
phutil_require_module('phabricator', 'applications/people/storage/user');
phutil_require_module('phabricator', 'view/form/base');
phutil_require_module('phabricator', 'view/form/control/checkbox');
+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/infrastructure/daemon/irc/bot/PhabricatorIRCBot.php b/src/infrastructure/daemon/irc/bot/PhabricatorIRCBot.php
index 8d5db14d92..605ead6ebc 100644
--- a/src/infrastructure/daemon/irc/bot/PhabricatorIRCBot.php
+++ b/src/infrastructure/daemon/irc/bot/PhabricatorIRCBot.php
@@ -1,193 +1,227 @@
<?php
/*
* Copyright 2011 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.
*/
/**
* Simple IRC bot which runs as a Phabricator daemon. Although this bot is
* somewhat useful, it is also intended to serve as a demo of how to write
* "system agents" which communicate with Phabricator over Conduit, so you can
* script system interactions and integrate with other systems.
*
* NOTE: This is super janky and experimental right now.
*
* @group irc
*/
final class PhabricatorIRCBot extends PhabricatorDaemon {
private $socket;
private $handlers;
private $writeBuffer;
private $readBuffer;
+ private $conduit;
+
public function run() {
$argv = $this->getArgv();
if (count($argv) !== 1) {
throw new Exception("usage: PhabricatorIRCBot <json_config_file>");
}
$json_raw = Filesystem::readFile($argv[0]);
$config = json_decode($json_raw, true);
if (!is_array($config)) {
throw new Exception("File '{$argv[0]}' is not valid JSON!");
}
$server = idx($config, 'server');
$port = idx($config, 'port', 6667);
$join = idx($config, 'join', array());
$handlers = idx($config, 'handlers', array());
$nick = idx($config, 'nick', 'phabot');
if (!preg_match('/^[A-Za-z0-9_]+$/', $nick)) {
throw new Exception(
"Nickname '{$nick}' is invalid, must be alphanumeric!");
}
if (!$join) {
throw new Exception("No channels to 'join' in config!");
}
foreach ($handlers as $handler) {
$obj = newv($handler, array($this));
$this->handlers[] = $obj;
}
+ $conduit_uri = idx($config, 'conduit.uri');
+ if ($conduit_uri) {
+ $conduit_user = idx($config, 'conduit.user');
+ $conduit_cert = idx($config, 'conduit.cert');
+
+ $conduit = new ConduitClient($conduit_uri);
+ $response = $conduit->callMethodSynchronous(
+ 'conduit.connect',
+ array(
+ 'client' => 'PhabricatorIRCBot',
+ 'clientVersion' => '1.0',
+ 'clientDescription' => php_uname('n').':'.$nick,
+ 'user' => $conduit_user,
+ 'certificate' => $conduit_cert,
+ ));
+
+ $this->conduit = $conduit;
+ }
+
$errno = null;
$error = null;
$socket = fsockopen($server, $port, $errno, $error);
if (!$socket) {
throw new Exception("Failed to connect, #{$errno}: {$error}");
}
$ok = stream_set_blocking($socket, false);
if (!$ok) {
throw new Exception("Failed to set stream nonblocking.");
}
$this->socket = $socket;
$this->writeCommand('USER', "{$nick} 0 * :{$nick}");
$this->writeCommand('NICK', "{$nick}");
foreach ($join as $channel) {
$this->writeCommand('JOIN', "{$channel}");
}
$this->runSelectLoop();
}
private function runSelectLoop() {
do {
$read = array($this->socket);
if (strlen($this->writeBuffer)) {
$write = array($this->socket);
} else {
$write = array();
}
$except = array();
$ok = @stream_select($read, $write, $except, $timeout_sec = 1);
if ($ok === false) {
throw new Exception(
"socket_select() failed: ".socket_strerror(socket_last_error()));
}
if ($read) {
do {
$data = fread($this->socket, 4096);
if ($data === false) {
throw new Exception("fread() failed!");
} else {
$this->debugLog(true, $data);
$this->readBuffer .= $data;
}
} while (strlen($data));
}
if ($write) {
do {
$len = fwrite($this->socket, $this->writeBuffer);
if ($len === false) {
throw new Exception("fwrite() failed!");
} else {
$this->debugLog(false, substr($this->writeBuffer, 0, $len));
$this->writeBuffer = substr($this->writeBuffer, $len);
}
} while (strlen($this->writeBuffer));
}
- $this->processReadBuffer();
+ do {
+ $routed_message = $this->processReadBuffer();
+ } while ($routed_message);
} while (true);
}
private function write($message) {
$this->writeBuffer .= $message;
return $this;
}
public function writeCommand($command, $message) {
return $this->write($command.' '.$message."\r\n");
}
private function processReadBuffer() {
$until = strpos($this->readBuffer, "\r\n");
if ($until === false) {
- return;
+ return false;
}
$message = substr($this->readBuffer, 0, $until);
$this->readBuffer = substr($this->readBuffer, $until + 2);
$pattern =
'/^'.
'(?:(?P<sender>:(\S+)) )?'. // This may not be present.
'(?P<command>[A-Z0-9]+) '.
'(?P<data>.*)'.
'$/';
$matches = null;
if (!preg_match($pattern, $message, $matches)) {
throw new Exception("Unexpected message from server: {$message}");
}
$irc_message = new PhabricatorIRCMessage(
idx($matches, 'sender'),
$matches['command'],
$matches['data']);
$this->routeMessage($irc_message);
+
+ return true;
}
private function routeMessage(PhabricatorIRCMessage $message) {
foreach ($this->handlers as $handler) {
$handler->receiveMessage($message);
}
}
public function __destroy() {
$this->write("QUIT Goodbye.\r\n");
fclose($this->socket);
}
private function debugLog($is_read, $message) {
echo $is_read ? '<<< ' : '>>> ';
echo addcslashes($message, "\0..\37\177..\377");
echo "\n";
}
+ public function getConduit() {
+ if (empty($this->conduit)) {
+ throw new Exception(
+ "This bot is not configured with a Conduit uplink. Set 'conduit.uri', ".
+ "'conduit.user' and 'conduit.cert' in the configuration to connect.");
+ }
+ return $this->conduit;
+ }
+
}
diff --git a/src/infrastructure/daemon/irc/bot/__init__.php b/src/infrastructure/daemon/irc/bot/__init__.php
index 2bad43e7d8..9c84970a76 100644
--- a/src/infrastructure/daemon/irc/bot/__init__.php
+++ b/src/infrastructure/daemon/irc/bot/__init__.php
@@ -1,16 +1,17 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'infrastructure/daemon/base');
phutil_require_module('phabricator', 'infrastructure/daemon/irc/message');
+phutil_require_module('phutil', 'conduit/client');
phutil_require_module('phutil', 'filesystem');
phutil_require_module('phutil', 'utils');
phutil_require_source('PhabricatorIRCBot.php');
diff --git a/src/infrastructure/daemon/irc/handler/base/PhabricatorIRCHandler.php b/src/infrastructure/daemon/irc/handler/base/PhabricatorIRCHandler.php
index 79d81302c1..755824273e 100644
--- a/src/infrastructure/daemon/irc/handler/base/PhabricatorIRCHandler.php
+++ b/src/infrastructure/daemon/irc/handler/base/PhabricatorIRCHandler.php
@@ -1,40 +1,44 @@
<?php
/*
* Copyright 2011 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.
*/
/**
* Responds to IRC messages. You plug a bunch of these into a
* @{class:PhabricatorIRCBot} to give it special behavior.
*
* @group irc
*/
abstract class PhabricatorIRCHandler {
private $bot;
final public function __construct(PhabricatorIRCBot $irc_bot) {
$this->bot = $irc_bot;
}
final protected function write($command, $message) {
$this->bot->writeCommand($command, $message);
return $this;
}
+ final protected function getConduit() {
+ return $this->bot->getConduit();
+ }
+
abstract public function receiveMessage(PhabricatorIRCMessage $message);
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Jan 19, 20:27 (1 w, 5 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1128438
Default Alt Text
(23 KB)
Attached To
Mode
rP Phorge
Attached
Detach File
Event Timeline
Log In to Comment